xref: /openbsd-src/usr.sbin/apmd/apmd.c (revision 0b7734b3d77bb9b21afec6f4621cae6c805dbd45)
1 /*	$OpenBSD: apmd.c,v 1.79 2015/11/16 17:35:05 tedu 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/stat.h>
33 #include <sys/ioctl.h>
34 #include <sys/socket.h>
35 #include <sys/un.h>
36 #include <sys/wait.h>
37 #include <sys/event.h>
38 #include <sys/time.h>
39 #include <sys/sched.h>
40 #include <sys/sysctl.h>
41 #include <stdio.h>
42 #include <syslog.h>
43 #include <fcntl.h>
44 #include <unistd.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <signal.h>
48 #include <errno.h>
49 #include <err.h>
50 #include <limits.h>
51 #include <machine/apmvar.h>
52 
53 #include "pathnames.h"
54 #include "apm-proto.h"
55 
56 #define TRUE 1
57 #define FALSE 0
58 
59 const char apmdev[] = _PATH_APM_CTLDEV;
60 const char sockfile[] = _PATH_APM_SOCKET;
61 
62 int debug = 0;
63 
64 int doperf = PERF_NONE;
65 
66 extern char *__progname;
67 
68 void usage(void);
69 int power_status(int fd, int force, struct apm_power_info *pinfo);
70 int bind_socket(const char *sn);
71 enum apm_state handle_client(int sock_fd, int ctl_fd);
72 int  get_avg_idle_mp(int ncpu);
73 int  get_avg_idle_up(void);
74 void perf_status(struct apm_power_info *pinfo, int ncpu);
75 void suspend(int ctl_fd);
76 void stand_by(int ctl_fd);
77 void hibernate(int ctl_fd);
78 void setperfpolicy(char *policy);
79 void sigexit(int signo);
80 void do_etc_file(const char *file);
81 void sockunlink(void);
82 void error(const char *fmt, const char *arg);
83 void set_driver_messages(int fd, int mode);
84 
85 /* ARGSUSED */
86 void
87 sigexit(int signo)
88 {
89 	sockunlink();
90 	_exit(1);
91 }
92 
93 void
94 usage(void)
95 {
96 	fprintf(stderr,
97 	    "usage: %s [-AadHLs] [-f devname] [-S sockname] [-t seconds]\n",
98 	    __progname);
99 	exit(1);
100 }
101 
102 void
103 error(const char *fmt, const char *arg)
104 {
105 	char buf[128];
106 
107 	if (debug)
108 		err(1, fmt, arg);
109 	else {
110 		strlcpy(buf, fmt, sizeof(buf));
111 		strlcat(buf, ": %m", sizeof(buf));
112 		syslog(LOG_ERR, buf, arg);
113 		exit(1);
114 	}
115 }
116 
117 
118 /*
119  * tell the driver if it should display messages or not.
120  */
121 void
122 set_driver_messages(int fd, int mode)
123 {
124 	if (ioctl(fd, APM_IOC_PRN_CTL, &mode) == -1)
125 		syslog(LOG_DEBUG, "can't disable driver messages, error: %m");
126 }
127 
128 int
129 power_status(int fd, int force, struct apm_power_info *pinfo)
130 {
131 	struct apm_power_info bstate;
132 	static struct apm_power_info last;
133 	int acon = 0;
134 
135 	if (fd == -1) {
136 		if (pinfo) {
137 			bstate.battery_state = 255;
138 			bstate.ac_state = 255;
139 			bstate.battery_life = 0;
140 			bstate.minutes_left = -1;
141 			*pinfo = bstate;
142 		}
143 
144 		return 0;
145 	}
146 
147 	if (ioctl(fd, APM_IOC_GETPOWER, &bstate) == 0) {
148 	/* various conditions under which we report status:  something changed
149 	 * enough since last report, or asked to force a print */
150 		if (bstate.ac_state == APM_AC_ON)
151 			acon = 1;
152 		if (force ||
153 		    bstate.ac_state != last.ac_state ||
154 		    bstate.battery_state != last.battery_state ||
155 		    (bstate.minutes_left && bstate.minutes_left < 15) ||
156 		    abs(bstate.battery_life - last.battery_life) >= 10) {
157 #ifdef __powerpc__
158 			/*
159 			 * When the battery is charging, the estimated life
160 			 * time is in fact the estimated remaining charge time
161 			 * on Apple machines, so lie in the stats.
162 			 * We still want an useful message if the battery or
163 			 * ac status changes, however.
164 			 */
165 			if (bstate.minutes_left != 0 &&
166 			    bstate.battery_state != APM_BATT_CHARGING)
167 #else
168 			if ((int)bstate.minutes_left > 0)
169 #endif
170 				syslog(LOG_NOTICE, "battery status: %s. "
171 				    "external power status: %s. "
172 				    "estimated battery life %d%% (%u minutes)",
173 				    battstate(bstate.battery_state),
174 				    ac_state(bstate.ac_state),
175 				    bstate.battery_life,
176 				    bstate.minutes_left);
177 			else
178 				syslog(LOG_NOTICE, "battery status: %s. "
179 				    "external power status: %s. "
180 				    "estimated battery life %d%%",
181 				    battstate(bstate.battery_state),
182 				    ac_state(bstate.ac_state),
183 				    bstate.battery_life);
184 			last = bstate;
185 		}
186 		if (pinfo)
187 			*pinfo = bstate;
188 	} else
189 		syslog(LOG_ERR, "cannot fetch power status: %m");
190 
191 	return acon;
192 }
193 
194 char socketname[PATH_MAX];
195 
196 void
197 sockunlink(void)
198 {
199 	if (socketname[0])
200 		remove(socketname);
201 }
202 
203 int
204 bind_socket(const char *sockname)
205 {
206 	struct sockaddr_un s_un;
207 	mode_t old_umask;
208 	int sock;
209 
210 	sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
211 	if (sock == -1)
212 		error("cannot create local socket", NULL);
213 
214 	s_un.sun_family = AF_UNIX;
215 	strlcpy(s_un.sun_path, sockname, sizeof(s_un.sun_path));
216 
217 	/* remove it if present, we're moving in */
218 	(void) remove(sockname);
219 
220 	old_umask = umask(077);
221 	if (bind(sock, (struct sockaddr *)&s_un, sizeof(s_un)) == -1)
222 		error("cannot bind on APM socket", NULL);
223 	umask(old_umask);
224 	if (chmod(sockname, 0660) == -1 || chown(sockname, 0, 0) == -1)
225 		error("cannot set socket mode/owner/group to 660/0/0", NULL);
226 
227 	listen(sock, 1);
228 	strlcpy(socketname, sockname, sizeof socketname);
229 	atexit(sockunlink);
230 
231 	return sock;
232 }
233 
234 enum apm_state
235 handle_client(int sock_fd, int ctl_fd)
236 {
237 	/* accept a handle from the client, process it, then clean up */
238 	int cli_fd;
239 	struct sockaddr_un from;
240 	socklen_t fromlen;
241 	struct apm_command cmd;
242 	struct apm_reply reply;
243 	int cpuspeed_mib[] = {CTL_HW, HW_CPUSPEED};
244 	int cpuspeed = 0;
245 	size_t cpuspeed_sz = sizeof(cpuspeed);
246 
247 	fromlen = sizeof(from);
248 	cli_fd = accept(sock_fd, (struct sockaddr *)&from, &fromlen);
249 	if (cli_fd == -1) {
250 		syslog(LOG_INFO, "client accept failure: %m");
251 		return NORMAL;
252 	}
253 
254 	if (recv(cli_fd, &cmd, sizeof(cmd), 0) != sizeof(cmd)) {
255 		(void) close(cli_fd);
256 		syslog(LOG_INFO, "client size botch");
257 		return NORMAL;
258 	}
259 
260 	if (cmd.vno != APMD_VNO) {
261 		close(cli_fd);			/* terminate client */
262 		/* no error message, just drop it. */
263 		return NORMAL;
264 	}
265 
266 	power_status(ctl_fd, 0, &reply.batterystate);
267 	switch (cmd.action) {
268 	case SUSPEND:
269 		reply.newstate = SUSPENDING;
270 		break;
271 	case STANDBY:
272 		reply.newstate = STANDING_BY;
273 		break;
274 	case HIBERNATE:
275 		reply.newstate = HIBERNATING;
276 		break;
277 	case SETPERF_LOW:
278 		doperf = PERF_MANUAL;
279 		reply.newstate = NORMAL;
280 		syslog(LOG_NOTICE, "setting hw.perfpolicy to low");
281 		setperfpolicy("low");
282 		break;
283 	case SETPERF_HIGH:
284 		doperf = PERF_MANUAL;
285 		reply.newstate = NORMAL;
286 		syslog(LOG_NOTICE, "setting hw.perfpolicy to high");
287 		setperfpolicy("high");
288 		break;
289 	case SETPERF_AUTO:
290 	case SETPERF_COOL:
291 		doperf = PERF_AUTO;
292 		reply.newstate = NORMAL;
293 		syslog(LOG_NOTICE, "setting hw.perfpolicy to auto");
294 		setperfpolicy("auto");
295 		break;
296 	default:
297 		reply.newstate = NORMAL;
298 		break;
299 	}
300 
301 	if (sysctl(cpuspeed_mib, 2, &cpuspeed, &cpuspeed_sz, NULL, 0) < 0)
302 		syslog(LOG_INFO, "cannot read hw.cpuspeed");
303 
304 	reply.cpuspeed = cpuspeed;
305 	reply.perfmode = doperf;
306 	reply.vno = APMD_VNO;
307 	if (send(cli_fd, &reply, sizeof(reply), 0) != sizeof(reply))
308 		syslog(LOG_INFO, "reply to client botched");
309 	close(cli_fd);
310 
311 	return reply.newstate;
312 }
313 
314 void
315 suspend(int ctl_fd)
316 {
317 	syslog(LOG_NOTICE, "system suspending");
318 	do_etc_file(_PATH_APM_ETC_SUSPEND);
319 	sync();
320 	sleep(1);
321 	ioctl(ctl_fd, APM_IOC_SUSPEND, 0);
322 }
323 
324 void
325 stand_by(int ctl_fd)
326 {
327 	syslog(LOG_NOTICE, "system entering standby");
328 	do_etc_file(_PATH_APM_ETC_STANDBY);
329 	sync();
330 	sleep(1);
331 	ioctl(ctl_fd, APM_IOC_STANDBY, 0);
332 }
333 
334 void
335 hibernate(int ctl_fd)
336 {
337 	syslog(LOG_NOTICE, "system hibernating");
338 	do_etc_file(_PATH_APM_ETC_HIBERNATE);
339 	sync();
340 	sleep(1);
341 	ioctl(ctl_fd, APM_IOC_HIBERNATE, 0);
342 }
343 
344 #define TIMO (10*60)			/* 10 minutes */
345 
346 int
347 main(int argc, char *argv[])
348 {
349 	const char *fname = apmdev;
350 	int ctl_fd, sock_fd, ch, suspends, standbys, hibernates, resumes;
351 	int statonly = 0;
352 	int powerstatus = 0, powerbak = 0, powerchange = 0;
353 	int noacsleep = 0;
354 	struct timespec ts = {TIMO, 0}, sts = {0, 0};
355 	struct apm_power_info pinfo;
356 	time_t apmtimeout = 0;
357 	const char *sockname = sockfile;
358 	int kq, nchanges;
359 	struct kevent ev[2];
360 	int ncpu_mib[2] = { CTL_HW, HW_NCPU };
361 	int ncpu;
362 	size_t ncpu_sz = sizeof(ncpu);
363 
364 	while ((ch = getopt(argc, argv, "aACdHLsf:t:S:")) != -1)
365 		switch(ch) {
366 		case 'a':
367 			noacsleep = 1;
368 			break;
369 		case 'd':
370 			debug = 1;
371 			break;
372 		case 'f':
373 			fname = optarg;
374 			break;
375 		case 'S':
376 			sockname = optarg;
377 			break;
378 		case 't':
379 			ts.tv_sec = strtoul(optarg, NULL, 0);
380 			if (ts.tv_sec == 0)
381 				usage();
382 			break;
383 		case 's':	/* status only */
384 			statonly = 1;
385 			break;
386 		case 'A':
387 		case 'C':
388 			if (doperf != PERF_NONE)
389 				usage();
390 			doperf = PERF_AUTO;
391 			setperfpolicy("auto");
392 			break;
393 		case 'L':
394 			if (doperf != PERF_NONE)
395 				usage();
396 			doperf = PERF_MANUAL;
397 			setperfpolicy("low");
398 			break;
399 		case 'H':
400 			if (doperf != PERF_NONE)
401 				usage();
402 			doperf = PERF_MANUAL;
403 			setperfpolicy("high");
404 			break;
405 		case '?':
406 		default:
407 			usage();
408 		}
409 
410 	argc -= optind;
411 	argv += optind;
412 
413 	if (argc != 0)
414 		usage();
415 
416 	if (doperf == PERF_NONE)
417 		doperf = PERF_MANUAL;
418 
419 	if (debug)
420 		openlog(__progname, LOG_CONS, LOG_LOCAL1);
421 	else {
422 		if (daemon(0, 0) < 0)
423 			error("failed to daemonize", NULL);
424 		openlog(__progname, LOG_CONS, LOG_DAEMON);
425 		setlogmask(LOG_UPTO(LOG_NOTICE));
426 	}
427 
428 	(void) signal(SIGTERM, sigexit);
429 	(void) signal(SIGHUP, sigexit);
430 	(void) signal(SIGINT, sigexit);
431 
432 	if ((ctl_fd = open(fname, O_RDWR | O_CLOEXEC)) == -1) {
433 		if (errno != ENXIO && errno != ENOENT)
434 			error("cannot open device file `%s'", fname);
435 	}
436 
437 	sock_fd = bind_socket(sockname);
438 
439 	power_status(ctl_fd, 1, &pinfo);
440 
441 	if (statonly)
442 		exit(0);
443 
444 	set_driver_messages(ctl_fd, APM_PRINT_OFF);
445 
446 	kq = kqueue();
447 	if (kq <= 0)
448 		error("kqueue", NULL);
449 
450 	EV_SET(&ev[0], sock_fd, EVFILT_READ, EV_ADD | EV_ENABLE | EV_CLEAR,
451 	    0, 0, NULL);
452 	if (ctl_fd == -1)
453 		nchanges = 1;
454 	else {
455 		EV_SET(&ev[1], ctl_fd, EVFILT_READ, EV_ADD | EV_ENABLE |
456 		    EV_CLEAR, 0, 0, NULL);
457 		nchanges = 2;
458 	}
459 	if (kevent(kq, ev, nchanges, NULL, 0, &sts) < 0)
460 		error("kevent", NULL);
461 
462 	if (sysctl(ncpu_mib, 2, &ncpu, &ncpu_sz, NULL, 0) < 0)
463 		error("cannot read hw.ncpu", NULL);
464 
465 	for (;;) {
466 		int rv;
467 
468 		sts = ts;
469 
470 		apmtimeout += 1;
471 		if ((rv = kevent(kq, NULL, 0, ev, 1, &sts)) < 0)
472 			break;
473 
474 		if (apmtimeout >= ts.tv_sec) {
475 			apmtimeout = 0;
476 
477 			/* wakeup for timeout: take status */
478 			powerbak = power_status(ctl_fd, 0, &pinfo);
479 			if (powerstatus != powerbak) {
480 				powerstatus = powerbak;
481 				powerchange = 1;
482 			}
483 		}
484 
485 		if (!rv)
486 			continue;
487 
488 		if (ev->ident == ctl_fd) {
489 			suspends = standbys = hibernates = resumes = 0;
490 			syslog(LOG_DEBUG, "apmevent %04x index %d",
491 			    (int)APM_EVENT_TYPE(ev->data),
492 			    (int)APM_EVENT_INDEX(ev->data));
493 
494 			switch (APM_EVENT_TYPE(ev->data)) {
495 			case APM_SUSPEND_REQ:
496 			case APM_USER_SUSPEND_REQ:
497 			case APM_CRIT_SUSPEND_REQ:
498 			case APM_BATTERY_LOW:
499 				suspends++;
500 				break;
501 			case APM_USER_STANDBY_REQ:
502 			case APM_STANDBY_REQ:
503 				standbys++;
504 				break;
505 			case APM_USER_HIBERNATE_REQ:
506 				hibernates++;
507 				break;
508 #if 0
509 			case APM_CANCEL:
510 				suspends = standbys = 0;
511 				break;
512 #endif
513 			case APM_NORMAL_RESUME:
514 			case APM_CRIT_RESUME:
515 			case APM_SYS_STANDBY_RESUME:
516 				powerbak = power_status(ctl_fd, 0, &pinfo);
517 				if (powerstatus != powerbak) {
518 					powerstatus = powerbak;
519 					powerchange = 1;
520 				}
521 				resumes++;
522 				break;
523 			case APM_POWER_CHANGE:
524 				powerbak = power_status(ctl_fd, 0, &pinfo);
525 				if (powerstatus != powerbak) {
526 					powerstatus = powerbak;
527 					powerchange = 1;
528 				}
529 				break;
530 			default:
531 				;
532 			}
533 
534 			if ((standbys || suspends) && noacsleep &&
535 			    power_status(ctl_fd, 0, &pinfo))
536 				syslog(LOG_DEBUG, "no! sleep! till brooklyn!");
537 			else if (suspends)
538 				suspend(ctl_fd);
539 			else if (standbys)
540 				stand_by(ctl_fd);
541 			else if (hibernates)
542 				hibernate(ctl_fd);
543 			else if (resumes) {
544 				do_etc_file(_PATH_APM_ETC_RESUME);
545 				syslog(LOG_NOTICE,
546 				    "system resumed from sleep");
547 			}
548 
549 			if (powerchange) {
550 				if (powerstatus)
551 					do_etc_file(_PATH_APM_ETC_POWERUP);
552 				else
553 					do_etc_file(_PATH_APM_ETC_POWERDOWN);
554 				powerchange = 0;
555 			}
556 
557 		} else if (ev->ident == sock_fd)
558 			switch (handle_client(sock_fd, ctl_fd)) {
559 			case NORMAL:
560 				break;
561 			case SUSPENDING:
562 				suspend(ctl_fd);
563 				break;
564 			case STANDING_BY:
565 				stand_by(ctl_fd);
566 				break;
567 			case HIBERNATING:
568 				hibernate(ctl_fd);
569 				break;
570 			}
571 	}
572 	error("kevent loop", NULL);
573 
574 	return 1;
575 }
576 
577 void
578 setperfpolicy(char *policy)
579 {
580 	int hw_perfpol_mib[] = { CTL_HW, HW_PERFPOLICY };
581 	char oldpolicy[32];
582 	size_t oldsz = sizeof(oldpolicy);
583 	int setlo = 0;
584 
585 	if (strcmp(policy, "low") == 0) {
586 		policy = "manual";
587 		setlo = 1;
588 	}
589 
590 	if (sysctl(hw_perfpol_mib, 2, oldpolicy, &oldsz, policy, strlen(policy) + 1) < 0)
591 		syslog(LOG_INFO, "cannot set hw.perfpolicy");
592 
593 	if (setlo == 1) {
594 		int hw_perf_mib[] = {CTL_HW, HW_SETPERF};
595 		int perf;
596 		int new_perf = 0;
597 		size_t perf_sz = sizeof(perf);
598 		if (sysctl(hw_perf_mib, 2, &perf, &perf_sz, &new_perf, perf_sz) < 0)
599 			syslog(LOG_INFO, "cannot set hw.setperf");
600 	}
601 }
602 
603 void
604 do_etc_file(const char *file)
605 {
606 	pid_t pid;
607 	int status;
608 	const char *prog;
609 
610 	/* If file doesn't exist, do nothing. */
611 	if (access(file, X_OK|R_OK)) {
612 		syslog(LOG_DEBUG, "do_etc_file(): cannot access file %s", file);
613 		return;
614 	}
615 
616 	prog = strrchr(file, '/');
617 	if (prog)
618 		prog++;
619 	else
620 		prog = file;
621 
622 	pid = fork();
623 	switch (pid) {
624 	case -1:
625 		syslog(LOG_ERR, "failed to fork(): %m");
626 		return;
627 	case 0:
628 		/* We are the child. */
629 		execl(file, prog, (char *)NULL);
630 		syslog(LOG_ERR, "failed to exec %s: %m", file);
631 		_exit(1);
632 		/* NOTREACHED */
633 	default:
634 		/* We are the parent. */
635 		wait4(pid, &status, 0, 0);
636 		if (WIFEXITED(status))
637 			syslog(LOG_DEBUG, "%s exited with status %d", file,
638 			    WEXITSTATUS(status));
639 		else
640 			syslog(LOG_ERR, "%s exited abnormally.", file);
641 	}
642 }
643