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