xref: /openbsd-src/usr.sbin/apmd/apmd.c (revision 8500990981f885cbe5e6a4958549cacc238b5ae6)
1 /*	$OpenBSD: apmd.c,v 1.29 2003/09/26 17:03:22 deraadt Exp $	*/
2 
3 /*
4  *  Copyright (c) 1995, 1996 John T. Kohl
5  *  All rights reserved.
6  *
7  *  Redistribution and use in source and binary forms, with or without
8  *  modification, are permitted provided that the following conditions
9  *  are met:
10  *  1. Redistributions of source code must retain the above copyright
11  *     notice, this list of conditions and the following disclaimer.
12  *  2. Redistributions in binary form must reproduce the above copyright
13  *     notice, this list of conditions and the following disclaimer in the
14  *     documentation and/or other materials provided with the distribution.
15  *  3. The name of the author may not be used to endorse or promote products
16  *     derived from this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
22  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGE.
29  *
30  */
31 
32 #include <sys/param.h>
33 #include <sys/stat.h>
34 #include <sys/ioctl.h>
35 #include <sys/socket.h>
36 #include <sys/un.h>
37 #include <sys/wait.h>
38 #include <sys/event.h>
39 #include <sys/time.h>
40 #include <stdio.h>
41 #include <syslog.h>
42 #include <fcntl.h>
43 #include <unistd.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <signal.h>
47 #include <errno.h>
48 #include <err.h>
49 #include <machine/apmvar.h>
50 #include "pathnames.h"
51 #include "apm-proto.h"
52 
53 #define TRUE 1
54 #define FALSE 0
55 
56 const char apmdev[] = _PATH_APM_CTLDEV;
57 const char sockfile[] = _PATH_APM_SOCKET;
58 
59 int debug = 0;
60 
61 extern char *__progname;
62 
63 void usage(void);
64 int power_status(int fd, int force, struct apm_power_info *pinfo);
65 int bind_socket(const char *sn);
66 enum apm_state handle_client(int sock_fd, int ctl_fd);
67 void suspend(int ctl_fd);
68 void stand_by(int ctl_fd);
69 void sigexit(int signo);
70 void make_noise(int howmany);
71 void do_etc_file(const char *file);
72 void sockunlink(void);
73 
74 void
75 sigexit(int signo)
76 {
77 	sockunlink();
78 	_exit(1);
79 }
80 
81 void
82 usage(void)
83 {
84 	fprintf(stderr,
85 	    "usage: %s [-adempqs] [-f devfile] [-S sockfile] [-t timo]\n",
86 	    __progname);
87 	exit(1);
88 }
89 
90 void
91 error(const char *fmt, const char *arg)
92 {
93 	char buf[128];
94 
95 	if (debug)
96 		err(1, fmt, arg);
97 	else {
98 		strlcpy(buf, fmt, sizeof(buf));
99 		strlcat(buf, ": %m", sizeof(buf));
100 		syslog(LOG_ERR, buf, arg);
101 		exit(1);
102 	}
103 }
104 
105 
106 /*
107  * tell the driver if it should display messages or not.
108  */
109 void
110 set_driver_messages(int fd, int mode)
111 {
112 	if (ioctl(fd, APM_IOC_PRN_CTL, &mode) == -1)
113 		syslog(LOG_DEBUG, "can't disable driver messages, error: %m");
114 }
115 
116 int
117 power_status(int fd, int force, struct apm_power_info *pinfo)
118 {
119 	struct apm_power_info bstate;
120 	static struct apm_power_info last;
121 	int acon = 0;
122 
123 	if (ioctl(fd, APM_IOC_GETPOWER, &bstate) == 0) {
124 	/* various conditions under which we report status:  something changed
125 	 * enough since last report, or asked to force a print */
126 		if (bstate.ac_state == APM_AC_ON)
127 			acon = 1;
128 		if (force ||
129 		    bstate.ac_state != last.ac_state ||
130 		    bstate.battery_state != last.battery_state ||
131 		    (bstate.minutes_left && bstate.minutes_left < 15) ||
132 		    abs(bstate.battery_life - last.battery_life) > 20) {
133 #ifdef __powerpc__
134 			/*
135 			 * When the battery is charging, the estimated life
136 			 * time is in fact the estimated remaining charge time
137 			 * on Apple machines, so lie in the stats.
138 			 * We still want an useful message if the battery or
139 			 * ac status changes, however.
140 			 */
141 			if (bstate.minutes_left != 0 &&
142 			    bstate.battery_state != APM_BATT_CHARGING)
143 #else
144 			if (bstate.minutes_left)
145 #endif
146 				syslog(LOG_NOTICE, "battery status: %s. "
147 				    "external power status: %s. "
148 				    "estimated battery life %d%% (%u minutes)",
149 				    battstate(bstate.battery_state),
150 				    ac_state(bstate.ac_state),
151 				    bstate.battery_life,
152 				    bstate.minutes_left);
153 			else
154 				syslog(LOG_NOTICE, "battery status: %s. "
155 				    "external power status: %s. "
156 				    "estimated battery life %d%%",
157 				    battstate(bstate.battery_state),
158 				    ac_state(bstate.ac_state),
159 				    bstate.battery_life);
160 			last = bstate;
161 		}
162 		if (pinfo)
163 			*pinfo = bstate;
164 	} else
165 		syslog(LOG_ERR, "cannot fetch power status: %m");
166 
167 	return acon;
168 }
169 
170 char socketname[MAXPATHLEN];
171 
172 void
173 sockunlink(void)
174 {
175 	if (socketname[0])
176 		remove(socketname);
177 }
178 
179 int
180 bind_socket(const char *sockname)
181 {
182 	struct sockaddr_un s_un;
183 	int sock;
184 
185 	sock = socket(AF_UNIX, SOCK_STREAM, 0);
186 	if (sock == -1)
187 		error("cannot create local socket", NULL);
188 
189 	s_un.sun_family = AF_UNIX;
190 	strncpy(s_un.sun_path, sockname, sizeof(s_un.sun_path));
191 	s_un.sun_len = SUN_LEN(&s_un);
192 
193 	/* remove it if present, we're moving in */
194 	(void) remove(sockname);
195 	umask(077);
196 
197 	if (bind(sock, (struct sockaddr *)&s_un, s_un.sun_len) == -1)
198 		error("cannot connect to APM socket", NULL);
199 	if (chmod(sockname, 0660) == -1 || chown(sockname, 0, 0) == -1)
200 		error("cannot set socket mode/owner/group to 660/0/0", NULL);
201 
202 	listen(sock, 1);
203 	strlcpy(socketname, sockname, sizeof socketname);
204 	atexit(sockunlink);
205 
206 	return sock;
207 }
208 
209 enum apm_state
210 handle_client(int sock_fd, int ctl_fd)
211 {
212 	/* accept a handle from the client, process it, then clean up */
213 	int cli_fd;
214 	struct sockaddr_un from;
215 	socklen_t fromlen;
216 	struct apm_command cmd;
217 	struct apm_reply reply;
218 
219 	cli_fd = accept(sock_fd, (struct sockaddr *)&from, &fromlen);
220 	if (cli_fd == -1) {
221 		syslog(LOG_INFO, "client accept failure: %m");
222 		return NORMAL;
223 	}
224 
225 	if (recv(cli_fd, &cmd, sizeof(cmd), 0) != sizeof(cmd)) {
226 		(void) close(cli_fd);
227 		syslog(LOG_INFO, "client size botch");
228 		return NORMAL;
229 	}
230 
231 	if (cmd.vno != APMD_VNO) {
232 		close(cli_fd);			/* terminate client */
233 		/* no error message, just drop it. */
234 		return NORMAL;
235 	}
236 
237 	power_status(ctl_fd, 0, &reply.batterystate);
238 	switch (cmd.action) {
239 	case SUSPEND:
240 		reply.newstate = SUSPENDING;
241 		break;
242 	case STANDBY:
243 		reply.newstate = STANDING_BY;
244 		break;
245 	default:
246 		reply.newstate = NORMAL;
247 		break;
248 	}
249 
250 	reply.vno = APMD_VNO;
251 	if (send(cli_fd, &reply, sizeof(reply), 0) != sizeof(reply))
252 		syslog(LOG_INFO, "client reply botch");
253 	close(cli_fd);
254 
255 	return reply.newstate;
256 }
257 
258 int speaker_ok = TRUE;
259 
260 void
261 make_noise(int howmany)
262 {
263 	int spkrfd = -1;
264 	int trycnt;
265 
266 	if (!speaker_ok)		/* don't bother after sticky errors */
267 		return;
268 
269 	for (trycnt = 0; trycnt < 3; trycnt++) {
270 		spkrfd = open(_PATH_DEV_SPEAKER, O_WRONLY);
271 		if (spkrfd == -1) {
272 			switch (errno) {
273 			case EBUSY:
274 				usleep(500000);
275 				errno = EBUSY;
276 				continue;
277 			case ENOENT:
278 			case ENODEV:
279 			case ENXIO:
280 			case EPERM:
281 			case EACCES:
282 				syslog(LOG_WARNING, "speaker device "
283 				    _PATH_DEV_SPEAKER " unavailable: %m");
284 				speaker_ok = FALSE;
285 				return;
286 			}
287 		} else
288 			break;
289 	}
290 
291 	if (spkrfd == -1) {
292 		syslog(LOG_WARNING, "cannot open " _PATH_DEV_SPEAKER ": %m");
293 		return;
294 	}
295 
296 	syslog(LOG_DEBUG, "sending %d tones to speaker", howmany);
297 	write(spkrfd, "o4cc", 2 + howmany);
298 	close(spkrfd);
299 }
300 
301 void
302 suspend(int ctl_fd)
303 {
304 	do_etc_file(_PATH_APM_ETC_SUSPEND);
305 	sync();
306 	make_noise(2);
307 	sync();
308 	sync();
309 	sleep(1);
310 	ioctl(ctl_fd, APM_IOC_SUSPEND, 0);
311 }
312 
313 void
314 stand_by(int ctl_fd)
315 {
316 	do_etc_file(_PATH_APM_ETC_STANDBY);
317 	sync();
318 	make_noise(1);
319 	sync();
320 	sync();
321 	sleep(1);
322 	ioctl(ctl_fd, APM_IOC_STANDBY, 0);
323 }
324 
325 #define TIMO (1*60)			/* 10 minutes */
326 
327 int
328 main(int argc, char *argv[])
329 {
330 	const char *fname = apmdev;
331 	int ctl_fd, sock_fd, ch, suspends, standbys, resumes;
332 	int statonly = 0;
333 	int enableonly = 0;
334 	int pctonly = 0;
335 	int powerstatus = 0, powerbak = 0, powerchange = 0;
336 	int messages = 0;
337 	int noacsleep = 0;
338 	struct timespec ts = {TIMO, 0}, sts = {0, 0};
339 	const char *sockname = sockfile;
340 	int kq;
341 	struct kevent ev[2];
342 
343 	while ((ch = getopt(argc, argv, "qadsepmf:t:S:")) != -1)
344 		switch(ch) {
345 		case 'q':
346 			speaker_ok = FALSE;
347 			break;
348 		case 'a':
349 			noacsleep = 1;
350 			break;
351 		case 'd':
352 			debug = 1;
353 			break;
354 		case 'f':
355 			fname = optarg;
356 			break;
357 		case 'S':
358 			sockname = optarg;
359 			break;
360 		case 't':
361 			ts.tv_sec = strtoul(optarg, NULL, 0);
362 			if (ts.tv_sec == 0)
363 				usage();
364 			break;
365 		case 's':	/* status only */
366 			statonly = 1;
367 			break;
368 		case 'e':
369 			enableonly = 1;
370 			break;
371 		case 'p':
372 			pctonly = 1;
373 			break;
374 		case 'm':
375 			messages = 1;
376 			break;
377 		case '?':
378 		default:
379 			usage();
380 		}
381 
382 	argc -= optind;
383 	argv += optind;
384 
385 	if (debug)
386 		openlog(__progname, LOG_CONS, LOG_LOCAL1);
387 	else {
388 		daemon(0, 0);
389 		openlog(__progname, LOG_CONS, LOG_DAEMON);
390 		setlogmask(LOG_UPTO(LOG_NOTICE));
391 	}
392 
393 	(void) signal(SIGTERM, sigexit);
394 	(void) signal(SIGHUP, sigexit);
395 	(void) signal(SIGINT, sigexit);
396 
397 	if ((ctl_fd = open(fname, O_RDWR)) == -1)
398 		error("cannot open device file `%s'", fname);
399 
400 	if (fcntl(ctl_fd, F_SETFD, 1) == -1)
401 		error("cannot set close-on-exec for `%s'", fname);
402 
403 	sock_fd = bind_socket(sockname);
404 
405 	if (fcntl(sock_fd, F_SETFD, 1) == -1)
406 		error("cannot set close-on-exec for the socket", NULL);
407 
408 	power_status(ctl_fd, 1, 0);
409 
410 	if (statonly)
411 		exit(0);
412 
413 	if (enableonly) {
414 		set_driver_messages(ctl_fd, APM_PRINT_ON);
415 		exit(0);
416 	}
417 
418 	if (pctonly) {
419 		set_driver_messages(ctl_fd, APM_PRINT_PCT);
420 		exit(0);
421 	}
422 
423 	if (!messages)
424 		set_driver_messages(ctl_fd, APM_PRINT_OFF);
425 
426 	kq = kqueue();
427 	if (kq <= 0)
428 		error("kqueue", NULL);
429 
430 	EV_SET(&ev[0], ctl_fd, EVFILT_READ, EV_ADD | EV_ENABLE | EV_CLEAR,
431 	    0, 0, NULL);
432 	EV_SET(&ev[1], sock_fd, EVFILT_READ, EV_ADD | EV_ENABLE | EV_CLEAR,
433 	    0, 0, NULL);
434 	if (kevent(kq, ev, 2, NULL, 0, &sts) < 0)
435 		error("kevent", NULL);
436 
437 	for (;;) {
438 		int rv;
439 
440 		sts = ts;
441 
442 		if ((rv = kevent(kq, NULL, 0, ev, 1, &sts)) < 0)
443 			break;
444 
445 		/* wakeup for timeout: take status */
446 		powerbak = power_status(ctl_fd, 0, 0);
447 		if (powerstatus != powerbak) {
448 			powerstatus = powerbak;
449 			powerchange = 1;
450 		}
451 
452 		if (!rv)
453 			continue;
454 
455 		if (ev->ident == ctl_fd) {
456 			suspends = standbys = resumes = 0;
457 			syslog(LOG_DEBUG, "apmevent %04x index %d",
458 			    APM_EVENT_TYPE(ev->data),
459 			    APM_EVENT_INDEX(ev->data));
460 
461 			switch (APM_EVENT_TYPE(ev->data)) {
462 			case APM_SUSPEND_REQ:
463 			case APM_USER_SUSPEND_REQ:
464 			case APM_CRIT_SUSPEND_REQ:
465 			case APM_BATTERY_LOW:
466 				suspends++;
467 				break;
468 			case APM_USER_STANDBY_REQ:
469 			case APM_STANDBY_REQ:
470 				standbys++;
471 				break;
472 #if 0
473 			case APM_CANCEL:
474 				suspends = standbys = 0;
475 				break;
476 #endif
477 			case APM_NORMAL_RESUME:
478 			case APM_CRIT_RESUME:
479 			case APM_SYS_STANDBY_RESUME:
480 				powerbak = power_status(ctl_fd, 0, 0);
481 				if (powerstatus != powerbak) {
482 					powerstatus = powerbak;
483 					powerchange = 1;
484 				}
485 				resumes++;
486 				break;
487 			case APM_POWER_CHANGE:
488 				powerbak = power_status(ctl_fd, 0, 0);
489 				if (powerstatus != powerbak) {
490 					powerstatus = powerbak;
491 					powerchange = 1;
492 				}
493 				break;
494 			default:
495 				;
496 			}
497 
498 			if ((standbys || suspends) && noacsleep &&
499 			    power_status(ctl_fd, 0, 0))
500 				syslog(LOG_DEBUG, "no! sleep! till brooklyn!");
501 			else if (suspends)
502 				suspend(ctl_fd);
503 			else if (standbys)
504 				stand_by(ctl_fd);
505 			else if (resumes) {
506 				do_etc_file(_PATH_APM_ETC_RESUME);
507 				syslog(LOG_NOTICE,
508 				    "system resumed from APM sleep");
509 			}
510 
511 			if (powerchange) {
512 				if (powerstatus)
513 					do_etc_file(_PATH_APM_ETC_POWERUP);
514 				else
515 					do_etc_file(_PATH_APM_ETC_POWERDOWN);
516 				powerchange = 0;
517 			}
518 
519 		} else if (ev->ident == sock_fd)
520 			switch (handle_client(sock_fd, ctl_fd)) {
521 			case NORMAL:
522 				break;
523 			case SUSPENDING:
524 				suspend(ctl_fd);
525 				break;
526 			case STANDING_BY:
527 				stand_by(ctl_fd);
528 				break;
529 			}
530 	}
531 	error("kevent loop", NULL);
532 
533 	return 1;
534 }
535 
536 void
537 do_etc_file(const char *file)
538 {
539 	pid_t pid;
540 	int status;
541 	const char *prog;
542 
543 	/* If file doesn't exist, do nothing. */
544 	if (access(file, X_OK|R_OK)) {
545 		syslog(LOG_DEBUG, "do_etc_file(): cannot access file %s", file);
546 		return;
547 	}
548 
549 	prog = strrchr(file, '/');
550 	if (prog)
551 		prog++;
552 	else
553 		prog = file;
554 
555 	pid = fork();
556 	switch (pid) {
557 	case -1:
558 		syslog(LOG_ERR, "failed to fork(): %m");
559 		return;
560 	case 0:
561 		/* We are the child. */
562 		execl(file, prog, (char *)NULL);
563 		_exit(1);
564 		/* NOTREACHED */
565 	default:
566 		/* We are the parent. */
567 		wait4(pid, &status, 0, 0);
568 		if (WIFEXITED(status))
569 			syslog(LOG_DEBUG, "%s exited with status %d", file,
570 			    WEXITSTATUS(status));
571 		else
572 			syslog(LOG_ERR, "%s exited abnormally.", file);
573 	}
574 }
575