xref: /openbsd-src/usr.sbin/apmd/apmd.c (revision 50b7afb2c2c0993b0894d4e34bf857cb13ed9c80)
1 /*	$OpenBSD: apmd.c,v 1.63 2013/11/13 04:50:21 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 <sys/dkstat.h>
41 #include <sys/sysctl.h>
42 #include <stdio.h>
43 #include <syslog.h>
44 #include <fcntl.h>
45 #include <unistd.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <signal.h>
49 #include <errno.h>
50 #include <err.h>
51 #include <machine/apmvar.h>
52 #include "pathnames.h"
53 #include "apm-proto.h"
54 
55 #define TRUE 1
56 #define FALSE 0
57 
58 const char apmdev[] = _PATH_APM_CTLDEV;
59 const char sockfile[] = _PATH_APM_SOCKET;
60 
61 int debug = 0;
62 
63 int doperf = PERF_NONE;
64 #define PERFINC 50
65 #define PERFDEC 20
66 #define PERFMIN 0
67 #define PERFMAX 100
68 #define PERFINCTHRES 10
69 #define PERFDECTHRES 30
70 
71 extern char *__progname;
72 
73 void usage(void);
74 int power_status(int fd, int force, struct apm_power_info *pinfo);
75 int bind_socket(const char *sn);
76 enum apm_state handle_client(int sock_fd, int ctl_fd);
77 int  get_avg_idle_mp(int ncpu);
78 int  get_avg_idle_up(void);
79 void perf_status(struct apm_power_info *pinfo, int ncpu);
80 void suspend(int ctl_fd);
81 void stand_by(int ctl_fd);
82 void hibernate(int ctl_fd);
83 void setperf(int new_perf);
84 void sigexit(int signo);
85 void do_etc_file(const char *file);
86 void sockunlink(void);
87 void error(const char *fmt, const char *arg);
88 void set_driver_messages(int fd, int mode);
89 
90 /* ARGSUSED */
91 void
92 sigexit(int signo)
93 {
94 	sockunlink();
95 	_exit(1);
96 }
97 
98 void
99 usage(void)
100 {
101 	fprintf(stderr,
102 	    "usage: %s [-AaCdHLs] [-f devname] [-S sockname] [-t seconds]\n",
103 	    __progname);
104 	exit(1);
105 }
106 
107 void
108 error(const char *fmt, const char *arg)
109 {
110 	char buf[128];
111 
112 	if (debug)
113 		err(1, fmt, arg);
114 	else {
115 		strlcpy(buf, fmt, sizeof(buf));
116 		strlcat(buf, ": %m", sizeof(buf));
117 		syslog(LOG_ERR, buf, arg);
118 		exit(1);
119 	}
120 }
121 
122 
123 /*
124  * tell the driver if it should display messages or not.
125  */
126 void
127 set_driver_messages(int fd, int mode)
128 {
129 	if (ioctl(fd, APM_IOC_PRN_CTL, &mode) == -1)
130 		syslog(LOG_DEBUG, "can't disable driver messages, error: %m");
131 }
132 
133 int
134 power_status(int fd, int force, struct apm_power_info *pinfo)
135 {
136 	struct apm_power_info bstate;
137 	static struct apm_power_info last;
138 	int acon = 0;
139 
140 	if (fd == -1) {
141 		if (pinfo) {
142 			bstate.battery_state = 255;
143 			bstate.ac_state = 255;
144 			bstate.battery_life = 0;
145 			bstate.minutes_left = -1;
146 			*pinfo = bstate;
147 		}
148 
149 		return 0;
150 	}
151 
152 	if (ioctl(fd, APM_IOC_GETPOWER, &bstate) == 0) {
153 	/* various conditions under which we report status:  something changed
154 	 * enough since last report, or asked to force a print */
155 		if (bstate.ac_state == APM_AC_ON)
156 			acon = 1;
157 		if (force ||
158 		    bstate.ac_state != last.ac_state ||
159 		    bstate.battery_state != last.battery_state ||
160 		    (bstate.minutes_left && bstate.minutes_left < 15) ||
161 		    abs(bstate.battery_life - last.battery_life) > 20) {
162 #ifdef __powerpc__
163 			/*
164 			 * When the battery is charging, the estimated life
165 			 * time is in fact the estimated remaining charge time
166 			 * on Apple machines, so lie in the stats.
167 			 * We still want an useful message if the battery or
168 			 * ac status changes, however.
169 			 */
170 			if (bstate.minutes_left != 0 &&
171 			    bstate.battery_state != APM_BATT_CHARGING)
172 #else
173 			if ((int)bstate.minutes_left > 0)
174 #endif
175 				syslog(LOG_NOTICE, "battery status: %s. "
176 				    "external power status: %s. "
177 				    "estimated battery life %d%% (%u minutes)",
178 				    battstate(bstate.battery_state),
179 				    ac_state(bstate.ac_state),
180 				    bstate.battery_life,
181 				    bstate.minutes_left);
182 			else
183 				syslog(LOG_NOTICE, "battery status: %s. "
184 				    "external power status: %s. "
185 				    "estimated battery life %d%%",
186 				    battstate(bstate.battery_state),
187 				    ac_state(bstate.ac_state),
188 				    bstate.battery_life);
189 			last = bstate;
190 		}
191 		if (pinfo)
192 			*pinfo = bstate;
193 	} else
194 		syslog(LOG_ERR, "cannot fetch power status: %m");
195 
196 	return acon;
197 }
198 
199 /* multi- and uni-processor case */
200 int
201 get_avg_idle_mp(int ncpu)
202 {
203 	static int64_t **cp_time_old;
204 	static int64_t **cp_time;
205 	static int *avg_idle;
206 	int64_t change, sum, idle;
207 	int i, cpu, min_avg_idle;
208 	size_t cp_time_sz = CPUSTATES * sizeof(int64_t);
209 
210 	if (!cp_time_old)
211 		if ((cp_time_old = calloc(sizeof(int64_t *), ncpu)) == NULL)
212 			return -1;
213 
214 	if (!cp_time)
215 		if ((cp_time = calloc(sizeof(int64_t *), ncpu)) == NULL)
216 			return -1;
217 
218 	if (!avg_idle)
219 		if ((avg_idle = calloc(sizeof(int), ncpu)) == NULL)
220 			return -1;
221 
222 	min_avg_idle = 0;
223 	for (cpu = 0; cpu < ncpu; cpu++) {
224 		int cp_time_mib[] = {CTL_KERN, KERN_CPTIME2, cpu};
225 
226 		if (!cp_time_old[cpu])
227 			if ((cp_time_old[cpu] =
228 			    calloc(sizeof(int64_t), CPUSTATES)) == NULL)
229 				return -1;
230 
231 		if (!cp_time[cpu])
232 			if ((cp_time[cpu] =
233 			    calloc(sizeof(int64_t), CPUSTATES)) == NULL)
234 				return -1;
235 
236 		if (sysctl(cp_time_mib, 3, cp_time[cpu], &cp_time_sz, NULL, 0)
237 		    < 0)
238 			syslog(LOG_INFO, "cannot read kern.cp_time2");
239 
240 		sum = 0;
241 		for (i = 0; i < CPUSTATES; i++) {
242 			if ((change = cp_time[cpu][i] - cp_time_old[cpu][i])
243 			    < 0) {
244 				/* counter wrapped */
245 				change = ((uint64_t)cp_time[cpu][i] -
246 				    (uint64_t)cp_time_old[cpu][i]);
247 			}
248 			sum += change;
249 			if (i == CP_IDLE)
250 				idle = change;
251 		}
252 		if (sum == 0)
253 			sum = 1;
254 
255 		/* smooth data */
256 		avg_idle[cpu] = (avg_idle[cpu] + (100 * idle) / sum) / 2;
257 
258 		if (cpu == 0)
259 			min_avg_idle = avg_idle[cpu];
260 
261 		if (avg_idle[cpu] < min_avg_idle)
262 			min_avg_idle = avg_idle[cpu];
263 
264 		memcpy(cp_time_old[cpu], cp_time[cpu], cp_time_sz);
265 	}
266 
267 	return min_avg_idle;
268 }
269 
270 int
271 get_avg_idle_up(void)
272 {
273 	static long cp_time_old[CPUSTATES];
274 	static int avg_idle;
275 	long change, cp_time[CPUSTATES];
276 	int cp_time_mib[] = {CTL_KERN, KERN_CPTIME};
277 	size_t cp_time_sz = sizeof(cp_time);
278 	int i, idle, sum = 0;
279 
280 	if (sysctl(cp_time_mib, 2, &cp_time, &cp_time_sz, NULL, 0) < 0)
281 		syslog(LOG_INFO, "cannot read kern.cp_time");
282 
283 	for (i = 0; i < CPUSTATES; i++) {
284 		if ((change = cp_time[i] - cp_time_old[i]) < 0) {
285 			/* counter wrapped */
286 			change = ((unsigned long)cp_time[i] -
287 			    (unsigned long)cp_time_old[i]);
288 		}
289 		sum += change;
290 		if (i == CP_IDLE)
291 			idle = change;
292 	}
293 	if (sum == 0)
294 		sum = 1;
295 
296 	/* smooth data */
297 	avg_idle = (avg_idle + (100 * idle) / sum) / 2;
298 
299 	memcpy(cp_time_old, cp_time, sizeof(cp_time_old));
300 
301 	return avg_idle;
302 }
303 
304 void
305 perf_status(struct apm_power_info *pinfo, int ncpu)
306 {
307 	int avg_idle;
308 	int hw_perf_mib[] = {CTL_HW, HW_SETPERF};
309 	int perf;
310 	int forcehi = 0;
311 	size_t perf_sz = sizeof(perf);
312 
313 	if (ncpu > 1) {
314 		avg_idle = get_avg_idle_mp(ncpu);
315 	} else {
316 		avg_idle = get_avg_idle_up();
317 	}
318 
319 	if (avg_idle == -1)
320 		return;
321 
322 	switch (doperf) {
323 	case PERF_AUTO:
324 		/*
325 		 * force setperf towards the max if we are connected to AC
326 		 * power and have a battery life greater than 15%, or if
327 		 * the battery is absent
328 		 */
329 		if ((pinfo->ac_state == APM_AC_ON && pinfo->battery_life > 15) ||
330 		    pinfo->battery_state == APM_BATTERY_ABSENT)
331 			forcehi = 1;
332 		break;
333 	case PERF_COOL:
334 		forcehi = 0;
335 		break;
336 	}
337 
338 	if (sysctl(hw_perf_mib, 2, &perf, &perf_sz, NULL, 0) < 0)
339 		syslog(LOG_INFO, "cannot read hw.setperf");
340 
341 	if (forcehi || (avg_idle < PERFINCTHRES && perf < PERFMAX)) {
342 		perf += PERFINC;
343 		if (perf > PERFMAX)
344 			perf = PERFMAX;
345 		setperf(perf);
346 	} else if (avg_idle > PERFDECTHRES && perf > PERFMIN) {
347 		perf -= PERFDEC;
348 		if (perf < PERFMIN)
349 			perf = PERFMIN;
350 		setperf(perf);
351 	}
352 }
353 
354 char socketname[MAXPATHLEN];
355 
356 void
357 sockunlink(void)
358 {
359 	if (socketname[0])
360 		remove(socketname);
361 }
362 
363 int
364 bind_socket(const char *sockname)
365 {
366 	struct sockaddr_un s_un;
367 	mode_t old_umask;
368 	int sock;
369 
370 	sock = socket(AF_UNIX, SOCK_STREAM, 0);
371 	if (sock == -1)
372 		error("cannot create local socket", NULL);
373 
374 	s_un.sun_family = AF_UNIX;
375 	strncpy(s_un.sun_path, sockname, sizeof(s_un.sun_path));
376 	s_un.sun_len = SUN_LEN(&s_un);
377 
378 	/* remove it if present, we're moving in */
379 	(void) remove(sockname);
380 
381 	old_umask = umask(077);
382 	if (bind(sock, (struct sockaddr *)&s_un, s_un.sun_len) == -1)
383 		error("cannot connect to APM socket", NULL);
384 	umask(old_umask);
385 	if (chmod(sockname, 0660) == -1 || chown(sockname, 0, 0) == -1)
386 		error("cannot set socket mode/owner/group to 660/0/0", NULL);
387 
388 	listen(sock, 1);
389 	strlcpy(socketname, sockname, sizeof socketname);
390 	atexit(sockunlink);
391 
392 	return sock;
393 }
394 
395 enum apm_state
396 handle_client(int sock_fd, int ctl_fd)
397 {
398 	/* accept a handle from the client, process it, then clean up */
399 	int cli_fd;
400 	struct sockaddr_un from;
401 	socklen_t fromlen;
402 	struct apm_command cmd;
403 	struct apm_reply reply;
404 	int cpuspeed_mib[] = {CTL_HW, HW_CPUSPEED};
405 	int cpuspeed = 0;
406 	size_t cpuspeed_sz = sizeof(cpuspeed);
407 
408 	fromlen = sizeof(from);
409 	cli_fd = accept(sock_fd, (struct sockaddr *)&from, &fromlen);
410 	if (cli_fd == -1) {
411 		syslog(LOG_INFO, "client accept failure: %m");
412 		return NORMAL;
413 	}
414 
415 	if (recv(cli_fd, &cmd, sizeof(cmd), 0) != sizeof(cmd)) {
416 		(void) close(cli_fd);
417 		syslog(LOG_INFO, "client size botch");
418 		return NORMAL;
419 	}
420 
421 	if (cmd.vno != APMD_VNO) {
422 		close(cli_fd);			/* terminate client */
423 		/* no error message, just drop it. */
424 		return NORMAL;
425 	}
426 
427 	power_status(ctl_fd, 0, &reply.batterystate);
428 	switch (cmd.action) {
429 	case SUSPEND:
430 		reply.newstate = SUSPENDING;
431 		break;
432 	case STANDBY:
433 		reply.newstate = STANDING_BY;
434 		break;
435 	case HIBERNATE:
436 		reply.newstate = HIBERNATING;
437 		break;
438 	case SETPERF_LOW:
439 		doperf = PERF_MANUAL;
440 		reply.newstate = NORMAL;
441 		syslog(LOG_NOTICE, "setting hw.setperf to %d", PERFMIN);
442 		setperf(PERFMIN);
443 		break;
444 	case SETPERF_HIGH:
445 		doperf = PERF_MANUAL;
446 		reply.newstate = NORMAL;
447 		syslog(LOG_NOTICE, "setting hw.setperf to %d", PERFMAX);
448 		setperf(PERFMAX);
449 		break;
450 	case SETPERF_AUTO:
451 		doperf = PERF_AUTO;
452 		reply.newstate = NORMAL;
453 		syslog(LOG_NOTICE, "setting hw.setperf automatically");
454 		break;
455 	case SETPERF_COOL:
456 		doperf = PERF_COOL;
457 		reply.newstate = NORMAL;
458 		syslog(LOG_NOTICE, "setting hw.setperf for cool running");
459 		break;
460 	default:
461 		reply.newstate = NORMAL;
462 		break;
463 	}
464 
465 	if (sysctl(cpuspeed_mib, 2, &cpuspeed, &cpuspeed_sz, NULL, 0) < 0)
466 		syslog(LOG_INFO, "cannot read hw.cpuspeed");
467 
468 	reply.cpuspeed = cpuspeed;
469 	reply.perfmode = doperf;
470 	reply.vno = APMD_VNO;
471 	if (send(cli_fd, &reply, sizeof(reply), 0) != sizeof(reply))
472 		syslog(LOG_INFO, "client reply botch");
473 	close(cli_fd);
474 
475 	return reply.newstate;
476 }
477 
478 void
479 suspend(int ctl_fd)
480 {
481 	syslog(LOG_NOTICE, "system suspending");
482 	do_etc_file(_PATH_APM_ETC_SUSPEND);
483 	sync();
484 	sleep(1);
485 	ioctl(ctl_fd, APM_IOC_SUSPEND, 0);
486 }
487 
488 void
489 stand_by(int ctl_fd)
490 {
491 	syslog(LOG_NOTICE, "system entering standby");
492 	do_etc_file(_PATH_APM_ETC_STANDBY);
493 	sync();
494 	sleep(1);
495 	ioctl(ctl_fd, APM_IOC_STANDBY, 0);
496 }
497 
498 void
499 hibernate(int ctl_fd)
500 {
501 	syslog(LOG_NOTICE, "system hibernating");
502 	do_etc_file(_PATH_APM_ETC_HIBERNATE);
503 	sync();
504 	sleep(1);
505 	ioctl(ctl_fd, APM_IOC_HIBERNATE, 0);
506 }
507 
508 #define TIMO (10*60)			/* 10 minutes */
509 
510 int
511 main(int argc, char *argv[])
512 {
513 	const char *fname = apmdev;
514 	int ctl_fd, sock_fd, ch, suspends, standbys, resumes;
515 	int statonly = 0;
516 	int powerstatus = 0, powerbak = 0, powerchange = 0;
517 	int noacsleep = 0;
518 	struct timespec ts = {TIMO, 0}, sts = {0, 0};
519 	struct apm_power_info pinfo;
520 	time_t apmtimeout = 0;
521 	const char *sockname = sockfile;
522 	int kq, nchanges;
523 	struct kevent ev[2];
524 	int ncpu_mib[2] = { CTL_HW, HW_NCPU };
525 	int ncpu;
526 	size_t ncpu_sz = sizeof(ncpu);
527 
528 	while ((ch = getopt(argc, argv, "aACdHLsf:t:S:")) != -1)
529 		switch(ch) {
530 		case 'a':
531 			noacsleep = 1;
532 			break;
533 		case 'd':
534 			debug = 1;
535 			break;
536 		case 'f':
537 			fname = optarg;
538 			break;
539 		case 'S':
540 			sockname = optarg;
541 			break;
542 		case 't':
543 			ts.tv_sec = strtoul(optarg, NULL, 0);
544 			if (ts.tv_sec == 0)
545 				usage();
546 			break;
547 		case 's':	/* status only */
548 			statonly = 1;
549 			break;
550 		case 'A':
551 			if (doperf != PERF_NONE)
552 				usage();
553 			doperf = PERF_AUTO;
554 			break;
555 		case 'C':
556 			if (doperf != PERF_NONE)
557 				usage();
558 			doperf = PERF_COOL;
559 			break;
560 		case 'L':
561 			if (doperf != PERF_NONE)
562 				usage();
563 			doperf = PERF_MANUAL;
564 			setperf(PERFMIN);
565 			break;
566 		case 'H':
567 			if (doperf != PERF_NONE)
568 				usage();
569 			doperf = PERF_MANUAL;
570 			setperf(PERFMAX);
571 			break;
572 		case '?':
573 		default:
574 			usage();
575 		}
576 
577 	argc -= optind;
578 	argv += optind;
579 
580 	if (argc != 0)
581 		usage();
582 
583 	if (doperf == PERF_NONE)
584 		doperf = PERF_MANUAL;
585 
586 	if (debug)
587 		openlog(__progname, LOG_CONS, LOG_LOCAL1);
588 	else {
589 		if (daemon(0, 0) < 0)
590 			error("failed to daemonize", NULL);
591 		openlog(__progname, LOG_CONS, LOG_DAEMON);
592 		setlogmask(LOG_UPTO(LOG_NOTICE));
593 	}
594 
595 	(void) signal(SIGTERM, sigexit);
596 	(void) signal(SIGHUP, sigexit);
597 	(void) signal(SIGINT, sigexit);
598 
599 	if ((ctl_fd = open(fname, O_RDWR)) == -1) {
600 		if (errno != ENXIO && errno != ENOENT)
601 			error("cannot open device file `%s'", fname);
602 	} else if (fcntl(ctl_fd, F_SETFD, FD_CLOEXEC) == -1)
603 		error("cannot set close-on-exec for `%s'", fname);
604 
605 	sock_fd = bind_socket(sockname);
606 
607 	if (fcntl(sock_fd, F_SETFD, FD_CLOEXEC) == -1)
608 		error("cannot set close-on-exec for the socket", NULL);
609 
610 	power_status(ctl_fd, 1, &pinfo);
611 
612 	if (statonly)
613 		exit(0);
614 
615 	set_driver_messages(ctl_fd, APM_PRINT_OFF);
616 
617 	kq = kqueue();
618 	if (kq <= 0)
619 		error("kqueue", NULL);
620 
621 	EV_SET(&ev[0], sock_fd, EVFILT_READ, EV_ADD | EV_ENABLE | EV_CLEAR,
622 	    0, 0, NULL);
623 	if (ctl_fd == -1)
624 		nchanges = 1;
625 	else {
626 		EV_SET(&ev[1], ctl_fd, EVFILT_READ, EV_ADD | EV_ENABLE |
627 		    EV_CLEAR, 0, 0, NULL);
628 		nchanges = 2;
629 	}
630 	if (kevent(kq, ev, nchanges, NULL, 0, &sts) < 0)
631 		error("kevent", NULL);
632 
633 	if (sysctl(ncpu_mib, 2, &ncpu, &ncpu_sz, NULL, 0) < 0)
634 		error("cannot read hw.ncpu", NULL);
635 
636 	if (doperf == PERF_AUTO || doperf == PERF_COOL) {
637 		setperf(0);
638 		setperf(100);
639 	}
640 	for (;;) {
641 		int rv;
642 
643 		sts = ts;
644 
645 		if (doperf == PERF_AUTO || doperf == PERF_COOL) {
646 			sts.tv_sec = 1;
647 			perf_status(&pinfo, ncpu);
648 		}
649 
650 		apmtimeout += sts.tv_sec;
651 		if ((rv = kevent(kq, NULL, 0, ev, 1, &sts)) < 0)
652 			break;
653 
654 		if (apmtimeout >= ts.tv_sec) {
655 			apmtimeout = 0;
656 
657 			/* wakeup for timeout: take status */
658 			powerbak = power_status(ctl_fd, 0, &pinfo);
659 			if (powerstatus != powerbak) {
660 				powerstatus = powerbak;
661 				powerchange = 1;
662 			}
663 		}
664 
665 		if (!rv)
666 			continue;
667 
668 		if (ev->ident == ctl_fd) {
669 			suspends = standbys = resumes = 0;
670 			syslog(LOG_DEBUG, "apmevent %04x index %d",
671 			    (int)APM_EVENT_TYPE(ev->data),
672 			    (int)APM_EVENT_INDEX(ev->data));
673 
674 			switch (APM_EVENT_TYPE(ev->data)) {
675 			case APM_SUSPEND_REQ:
676 			case APM_USER_SUSPEND_REQ:
677 			case APM_CRIT_SUSPEND_REQ:
678 			case APM_BATTERY_LOW:
679 				suspends++;
680 				break;
681 			case APM_USER_STANDBY_REQ:
682 			case APM_STANDBY_REQ:
683 				standbys++;
684 				break;
685 #if 0
686 			case APM_CANCEL:
687 				suspends = standbys = 0;
688 				break;
689 #endif
690 			case APM_NORMAL_RESUME:
691 			case APM_CRIT_RESUME:
692 			case APM_SYS_STANDBY_RESUME:
693 				powerbak = power_status(ctl_fd, 0, &pinfo);
694 				if (powerstatus != powerbak) {
695 					powerstatus = powerbak;
696 					powerchange = 1;
697 				}
698 				resumes++;
699 				break;
700 			case APM_POWER_CHANGE:
701 				powerbak = power_status(ctl_fd, 0, &pinfo);
702 				if (powerstatus != powerbak) {
703 					powerstatus = powerbak;
704 					powerchange = 1;
705 				}
706 				break;
707 			default:
708 				;
709 			}
710 
711 			if ((standbys || suspends) && noacsleep &&
712 			    power_status(ctl_fd, 0, &pinfo))
713 				syslog(LOG_DEBUG, "no! sleep! till brooklyn!");
714 			else if (suspends)
715 				suspend(ctl_fd);
716 			else if (standbys)
717 				stand_by(ctl_fd);
718 			else if (resumes) {
719 				do_etc_file(_PATH_APM_ETC_RESUME);
720 				syslog(LOG_NOTICE,
721 				    "system resumed from sleep");
722 			}
723 
724 			if (powerchange) {
725 				if (powerstatus)
726 					do_etc_file(_PATH_APM_ETC_POWERUP);
727 				else
728 					do_etc_file(_PATH_APM_ETC_POWERDOWN);
729 				powerchange = 0;
730 			}
731 
732 		} else if (ev->ident == sock_fd)
733 			switch (handle_client(sock_fd, ctl_fd)) {
734 			case NORMAL:
735 				break;
736 			case SUSPENDING:
737 				suspend(ctl_fd);
738 				break;
739 			case STANDING_BY:
740 				stand_by(ctl_fd);
741 				break;
742 			case HIBERNATING:
743 				hibernate(ctl_fd);
744 				break;
745 			}
746 	}
747 	error("kevent loop", NULL);
748 
749 	return 1;
750 }
751 
752 void
753 setperf(int new_perf)
754 {
755 	int hw_perf_mib[] = {CTL_HW, HW_SETPERF};
756 	int perf;
757 	size_t perf_sz = sizeof(perf);
758 
759 	if (sysctl(hw_perf_mib, 2, &perf, &perf_sz, &new_perf, perf_sz) < 0)
760 		syslog(LOG_INFO, "cannot set hw.setperf");
761 }
762 
763 void
764 do_etc_file(const char *file)
765 {
766 	pid_t pid;
767 	int status;
768 	const char *prog;
769 
770 	/* If file doesn't exist, do nothing. */
771 	if (access(file, X_OK|R_OK)) {
772 		syslog(LOG_DEBUG, "do_etc_file(): cannot access file %s", file);
773 		return;
774 	}
775 
776 	prog = strrchr(file, '/');
777 	if (prog)
778 		prog++;
779 	else
780 		prog = file;
781 
782 	pid = fork();
783 	switch (pid) {
784 	case -1:
785 		syslog(LOG_ERR, "failed to fork(): %m");
786 		return;
787 	case 0:
788 		/* We are the child. */
789 		execl(file, prog, (char *)NULL);
790 		syslog(LOG_ERR, "failed to exec %s: %m", file);
791 		_exit(1);
792 		/* NOTREACHED */
793 	default:
794 		/* We are the parent. */
795 		wait4(pid, &status, 0, 0);
796 		if (WIFEXITED(status))
797 			syslog(LOG_DEBUG, "%s exited with status %d", file,
798 			    WEXITSTATUS(status));
799 		else
800 			syslog(LOG_ERR, "%s exited abnormally.", file);
801 	}
802 }
803