xref: /freebsd/usr.sbin/apmd/apmd.c (revision cf7d8dea39cb00887476ad1a42480bce00b0b842)
1 /*-
2  * APM (Advanced Power Management) Event Dispatcher
3  *
4  * Copyright (c) 1999 Mitsuru IWASAKI <iwasaki@FreeBSD.org>
5  * Copyright (c) 1999 KOIE Hidetaka <koie@suri.co.jp>
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29 
30 #ifndef lint
31 static const char rcsid[] =
32   "$FreeBSD$";
33 #endif /* not lint */
34 
35 #include <assert.h>
36 #include <bitstring.h>
37 #include <err.h>
38 #include <errno.h>
39 #include <fcntl.h>
40 #include <paths.h>
41 #include <signal.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <syslog.h>
46 #include <unistd.h>
47 #include <sys/ioctl.h>
48 #include <sys/types.h>
49 #include <sys/time.h>
50 #include <sys/wait.h>
51 #include <machine/apm_bios.h>
52 
53 #include "apmd.h"
54 
55 int		debug_level = 0;
56 int		verbose = 0;
57 int		soft_power_state_change = 0;
58 const char	*apmd_configfile = APMD_CONFIGFILE;
59 const char	*apmd_pidfile = APMD_PIDFILE;
60 int             apmctl_fd = -1, apmnorm_fd = -1;
61 
62 /*
63  * table of event handlers
64  */
65 #define EVENT_CONFIG_INITIALIZER(EV,R) { #EV, NULL, R },
66 struct event_config events[EVENT_MAX] = {
67 	EVENT_CONFIG_INITIALIZER(NOEVENT, 0)
68 	EVENT_CONFIG_INITIALIZER(STANDBYREQ, 1)
69 	EVENT_CONFIG_INITIALIZER(SUSPENDREQ, 1)
70 	EVENT_CONFIG_INITIALIZER(NORMRESUME, 0)
71 	EVENT_CONFIG_INITIALIZER(CRITRESUME, 0)
72 	EVENT_CONFIG_INITIALIZER(BATTERYLOW, 0)
73 	EVENT_CONFIG_INITIALIZER(POWERSTATECHANGE, 0)
74 	EVENT_CONFIG_INITIALIZER(UPDATETIME, 0)
75 	EVENT_CONFIG_INITIALIZER(CRITSUSPEND, 1)
76 	EVENT_CONFIG_INITIALIZER(USERSTANDBYREQ, 1)
77 	EVENT_CONFIG_INITIALIZER(USERSUSPENDREQ, 1)
78 	EVENT_CONFIG_INITIALIZER(STANDBYRESUME, 0)
79 	EVENT_CONFIG_INITIALIZER(CAPABILITIESCHANGE, 0)
80 };
81 
82 /*
83  * List of battery events
84  */
85 struct battery_watch_event *battery_watch_list = NULL;
86 
87 #define BATT_CHK_INTV 10 /* how many seconds between battery state checks? */
88 
89 /*
90  * default procedure
91  */
92 struct event_cmd *
93 event_cmd_default_clone(void *this)
94 {
95 	struct event_cmd * oldone = this;
96 	struct event_cmd * newone = malloc(oldone->len);
97 
98 	newone->next = NULL;
99 	newone->len = oldone->len;
100 	newone->name = oldone->name;
101 	newone->op = oldone->op;
102 	return newone;
103 }
104 
105 /*
106  * exec command
107  */
108 int
109 event_cmd_exec_act(void *this)
110 {
111 	struct event_cmd_exec * p = this;
112 	int status = -1;
113 	pid_t pid;
114 
115 	switch ((pid = fork())) {
116 	case -1:
117 		warn("cannot fork");
118 		break;
119 	case 0:
120 		/* child process */
121 		signal(SIGHUP, SIG_DFL);
122 		signal(SIGCHLD, SIG_DFL);
123 		signal(SIGTERM, SIG_DFL);
124 		execl(_PATH_BSHELL, "sh", "-c", p->line, (char *)NULL);
125 		_exit(127);
126 	default:
127 		/* parent process */
128 		do {
129 			pid = waitpid(pid, &status, 0);
130 		} while (pid == -1 && errno == EINTR);
131 		break;
132 	}
133 	return status;
134 }
135 void
136 event_cmd_exec_dump(void *this, FILE *fp)
137 {
138 	fprintf(fp, " \"%s\"", ((struct event_cmd_exec *)this)->line);
139 }
140 struct event_cmd *
141 event_cmd_exec_clone(void *this)
142 {
143 	struct event_cmd_exec * newone = (struct event_cmd_exec *) event_cmd_default_clone(this);
144 	struct event_cmd_exec * oldone = this;
145 
146 	newone->evcmd.next = NULL;
147 	newone->evcmd.len = oldone->evcmd.len;
148 	newone->evcmd.name = oldone->evcmd.name;
149 	newone->evcmd.op = oldone->evcmd.op;
150 	if ((newone->line = strdup(oldone->line)) == NULL)
151 		err(1, "out of memory");
152 	return (struct event_cmd *) newone;
153 }
154 void
155 event_cmd_exec_free(void *this)
156 {
157 	free(((struct event_cmd_exec *)this)->line);
158 }
159 struct event_cmd_op event_cmd_exec_ops = {
160 	event_cmd_exec_act,
161 	event_cmd_exec_dump,
162 	event_cmd_exec_clone,
163 	event_cmd_exec_free
164 };
165 
166 /*
167  * reject command
168  */
169 int
170 event_cmd_reject_act(void *this __unused)
171 {
172 	int rc = 0;
173 
174 	if (ioctl(apmctl_fd, APMIO_REJECTLASTREQ, NULL)) {
175 		syslog(LOG_NOTICE, "fail to reject\n");
176 		rc = -1;
177 	}
178 	return rc;
179 }
180 struct event_cmd_op event_cmd_reject_ops = {
181 	event_cmd_reject_act,
182 	NULL,
183 	event_cmd_default_clone,
184 	NULL
185 };
186 
187 /*
188  * manipulate event_config
189  */
190 struct event_cmd *
191 clone_event_cmd_list(struct event_cmd *p)
192 {
193 	struct event_cmd dummy;
194 	struct event_cmd *q = &dummy;
195 	for ( ;p; p = p->next) {
196 		assert(p->op->clone);
197 		if ((q->next = p->op->clone(p)) == NULL)
198 			err(1, "out of memory");
199 		q = q->next;
200 	}
201 	q->next = NULL;
202 	return dummy.next;
203 }
204 void
205 free_event_cmd_list(struct event_cmd *p)
206 {
207 	struct event_cmd * q;
208 	for ( ; p ; p = q) {
209 		q = p->next;
210 		if (p->op->free)
211 			p->op->free(p);
212 		free(p);
213 	}
214 }
215 int
216 register_battery_handlers(
217 	int level, int direction,
218 	struct event_cmd *cmdlist)
219 {
220 	/*
221 	 * level is negative if it's in "minutes", non-negative if
222 	 * percentage.
223 	 *
224 	 * direction =1 means we care about this level when charging,
225 	 * direction =-1 means we care about it when discharging.
226 	 */
227 	if (level>100) /* percentage > 100 */
228 		return -1;
229 	if (abs(direction) != 1) /* nonsense direction value */
230 		return -1;
231 
232 	if (cmdlist) {
233 		struct battery_watch_event *we;
234 
235 		if ((we = malloc(sizeof(struct battery_watch_event))) == NULL)
236 			err(1, "out of memory");
237 
238 		we->next = battery_watch_list; /* starts at NULL */
239 		battery_watch_list = we;
240 		we->level = abs(level);
241 		we->type = (level<0)?BATTERY_MINUTES:BATTERY_PERCENT;
242 		we->direction = (direction<0)?BATTERY_DISCHARGING:
243 			BATTERY_CHARGING;
244 		we->done = 0;
245 		we->cmdlist = clone_event_cmd_list(cmdlist);
246 	}
247 	return 0;
248 }
249 int
250 register_apm_event_handlers(
251 	bitstr_t bit_decl(evlist, EVENT_MAX),
252 	struct event_cmd *cmdlist)
253 {
254 	if (cmdlist) {
255 		bitstr_t bit_decl(tmp, EVENT_MAX);
256 		memcpy(&tmp, evlist, bitstr_size(EVENT_MAX));
257 
258 		for (;;) {
259 			int n;
260 			struct event_cmd *p;
261 			struct event_cmd *q;
262 			bit_ffs(tmp, EVENT_MAX, &n);
263 			if (n < 0)
264 				break;
265 			p = events[n].cmdlist;
266 			if ((q = clone_event_cmd_list(cmdlist)) == NULL)
267 				err(1, "out of memory");
268 			if (p) {
269 				while (p->next != NULL)
270 					p = p->next;
271 				p->next = q;
272 			} else {
273 				events[n].cmdlist = q;
274 			}
275 			bit_clear(tmp, n);
276 		}
277 	}
278 	return 0;
279 }
280 
281 /*
282  * execute command
283  */
284 int
285 exec_run_cmd(struct event_cmd *p)
286 {
287 	int status = 0;
288 
289 	for (; p; p = p->next) {
290 		assert(p->op->act);
291 		if (verbose)
292 			syslog(LOG_INFO, "action: %s", p->name);
293 		status = p->op->act(p);
294 		if (status) {
295 			syslog(LOG_NOTICE, "command finished with %d\n", status);
296 			break;
297 		}
298 	}
299 	return status;
300 }
301 
302 /*
303  * execute command -- the event version
304  */
305 int
306 exec_event_cmd(struct event_config *ev)
307 {
308 	int status = 0;
309 
310 	status = exec_run_cmd(ev->cmdlist);
311 	if (status && ev->rejectable) {
312 		syslog(LOG_ERR, "canceled");
313 		event_cmd_reject_act(NULL);
314 	}
315 	return status;
316 }
317 
318 /*
319  * read config file
320  */
321 extern FILE * yyin;
322 extern int yydebug;
323 
324 void
325 read_config(void)
326 {
327 	int i;
328 
329 	if ((yyin = fopen(apmd_configfile, "r")) == NULL) {
330 		err(1, "cannot open config file");
331 	}
332 
333 #ifdef DEBUG
334 	yydebug = debug_level;
335 #endif
336 
337 	if (yyparse() != 0)
338 		err(1, "cannot parse config file");
339 
340 	fclose(yyin);
341 
342 	/* enable events */
343 	for (i = 0; i < EVENT_MAX; i++) {
344 		if (events[i].cmdlist) {
345 			u_int event_type = i;
346 			if (write(apmctl_fd, &event_type, sizeof(u_int)) == -1) {
347 				err(1, "cannot enable event 0x%x", event_type);
348 			}
349 		}
350 	}
351 }
352 
353 void
354 dump_config(void)
355 {
356 	int i;
357 	struct battery_watch_event *q;
358 
359 	for (i = 0; i < EVENT_MAX; i++) {
360 		struct event_cmd * p;
361 		if ((p = events[i].cmdlist)) {
362 			fprintf(stderr, "apm_event %s {\n", events[i].name);
363 			for ( ; p ; p = p->next) {
364 				fprintf(stderr, "\t%s", p->name);
365 				if (p->op->dump)
366 					p->op->dump(p, stderr);
367 				fprintf(stderr, ";\n");
368 			}
369 			fprintf(stderr, "}\n");
370 		}
371 	}
372 	for (q = battery_watch_list ; q != NULL ; q = q -> next) {
373 		struct event_cmd * p;
374 		fprintf(stderr, "apm_battery %d%s %s {\n",
375 			q -> level,
376 			(q -> type == BATTERY_PERCENT)?"%":"m",
377 			(q -> direction == BATTERY_CHARGING)?"charging":
378 				"discharging");
379 		for ( p = q -> cmdlist; p ; p = p->next) {
380 			fprintf(stderr, "\t%s", p->name);
381 			if (p->op->dump)
382 				p->op->dump(p, stderr);
383 			fprintf(stderr, ";\n");
384 		}
385 		fprintf(stderr, "}\n");
386 	}
387 }
388 
389 void
390 destroy_config(void)
391 {
392 	int i;
393 	struct battery_watch_event *q;
394 
395 	/* disable events */
396 	for (i = 0; i < EVENT_MAX; i++) {
397 		if (events[i].cmdlist) {
398 			u_int event_type = i;
399 			if (write(apmctl_fd, &event_type, sizeof(u_int)) == -1) {
400 				err(1, "cannot disable event 0x%x", event_type);
401 			}
402 		}
403 	}
404 
405 	for (i = 0; i < EVENT_MAX; i++) {
406 		struct event_cmd * p;
407 		if ((p = events[i].cmdlist))
408 			free_event_cmd_list(p);
409 		events[i].cmdlist = NULL;
410 	}
411 
412 	for( ; battery_watch_list; battery_watch_list = battery_watch_list -> next) {
413 		free_event_cmd_list(battery_watch_list->cmdlist);
414 		q = battery_watch_list->next;
415 		free(battery_watch_list);
416 		battery_watch_list = q;
417 	}
418 }
419 
420 void
421 restart(void)
422 {
423 	destroy_config();
424 	read_config();
425 	if (verbose)
426 		dump_config();
427 }
428 
429 /*
430  * write pid file
431  */
432 static void
433 write_pid(void)
434 {
435 	FILE *fp = fopen(apmd_pidfile, "w");
436 
437 	if (fp) {
438 		fprintf(fp, "%d\n", getpid());
439 		fclose(fp);
440 	}
441 }
442 
443 /*
444  * handle signals
445  */
446 static int signal_fd[2];
447 
448 void
449 enque_signal(int sig)
450 {
451 	if (write(signal_fd[1], &sig, sizeof sig) != sizeof sig)
452 		err(1, "cannot process signal.");
453 }
454 
455 void
456 wait_child(void)
457 {
458 	int status;
459 	while (waitpid(-1, &status, WNOHANG) > 0)
460 		;
461 }
462 
463 int
464 proc_signal(int fd)
465 {
466 	int rc = 0;
467 	int sig;
468 
469 	while (read(fd, &sig, sizeof sig) == sizeof sig) {
470 		syslog(LOG_INFO, "caught signal: %d", sig);
471 		switch (sig) {
472 		case SIGHUP:
473 			syslog(LOG_NOTICE, "restart by SIG");
474 			restart();
475 			break;
476 		case SIGTERM:
477 			syslog(LOG_NOTICE, "going down on signal %d", sig);
478 			rc = -1;
479 			return rc;
480 		case SIGCHLD:
481 			wait_child();
482 			break;
483 		default:
484 			warn("unexpected signal(%d) received.", sig);
485 			break;
486 		}
487 	}
488 	return rc;
489 }
490 void
491 proc_apmevent(int fd)
492 {
493 	struct apm_event_info apmevent;
494 
495 	while (ioctl(fd, APMIO_NEXTEVENT, &apmevent) == 0) {
496 		int status;
497 		syslog(LOG_NOTICE, "apmevent %04x index %d\n",
498 			apmevent.type, apmevent.index);
499 		syslog(LOG_INFO, "apm event: %s", events[apmevent.type].name);
500 		if (fork() == 0) {
501 			status = exec_event_cmd(&events[apmevent.type]);
502 			exit(status);
503 		}
504 	}
505 }
506 
507 #define AC_POWER_STATE ((pw_info.ai_acline == 1) ? BATTERY_CHARGING :\
508 	BATTERY_DISCHARGING)
509 
510 void
511 check_battery(void)
512 {
513 
514 	static int first_time=1, last_state;
515 	int status;
516 
517 	struct apm_info pw_info;
518 	struct battery_watch_event *p;
519 
520 	/* If we don't care, don't bother */
521 	if (battery_watch_list == NULL)
522 		return;
523 
524 	if (first_time) {
525 		if ( ioctl(apmnorm_fd, APMIO_GETINFO, &pw_info) < 0)
526 			err(1, "cannot check battery state.");
527 /*
528  * This next statement isn't entirely true. The spec does not tie AC
529  * line state to battery charging or not, but this is a bit lazier to do.
530  */
531 		last_state = AC_POWER_STATE;
532 		first_time = 0;
533 		return; /* We can't process events, we have no baseline */
534 	}
535 
536 	/*
537 	 * XXX - should we do this a bunch of times and perform some sort
538 	 * of smoothing or correction?
539 	 */
540 	if ( ioctl(apmnorm_fd, APMIO_GETINFO, &pw_info) < 0)
541 		err(1, "cannot check battery state.");
542 
543 	/*
544 	 * If we're not in the state now that we were in last time,
545 	 * then it's a transition, which means we must clean out
546 	 * the event-caught state.
547 	 */
548 	if (last_state != AC_POWER_STATE) {
549 		if (soft_power_state_change && fork() == 0) {
550 			status = exec_event_cmd(&events[PMEV_POWERSTATECHANGE]);
551 			exit(status);
552 		}
553 		last_state = AC_POWER_STATE;
554 		for (p = battery_watch_list ; p!=NULL ; p = p -> next)
555 			p->done = 0;
556 	}
557 	for (p = battery_watch_list ; p != NULL ; p = p -> next)
558 		if (p -> direction == AC_POWER_STATE &&
559 			!(p -> done) &&
560 			((p -> type == BATTERY_PERCENT &&
561 				p -> level == (int)pw_info.ai_batt_life) ||
562 			(p -> type == BATTERY_MINUTES &&
563 				p -> level == (pw_info.ai_batt_time / 60)))) {
564 			p -> done++;
565 			if (verbose)
566 				syslog(LOG_NOTICE, "Caught battery event: %s, %d%s",
567 					(p -> direction == BATTERY_CHARGING)?"charging":"discharging",
568 					p -> level,
569 					(p -> type == BATTERY_PERCENT)?"%":" minutes");
570 			if (fork() == 0) {
571 				status = exec_run_cmd(p -> cmdlist);
572 				exit(status);
573 			}
574 		}
575 }
576 void
577 event_loop(void)
578 {
579 	int		fdmax = 0;
580 	struct sigaction nsa;
581 	fd_set          master_rfds;
582 	sigset_t	sigmask, osigmask;
583 
584 	FD_ZERO(&master_rfds);
585 	FD_SET(apmctl_fd, &master_rfds);
586 	fdmax = apmctl_fd > fdmax ? apmctl_fd : fdmax;
587 
588 	FD_SET(signal_fd[0], &master_rfds);
589 	fdmax = signal_fd[0] > fdmax ? signal_fd[0] : fdmax;
590 
591 	memset(&nsa, 0, sizeof nsa);
592 	nsa.sa_handler = enque_signal;
593 	sigfillset(&nsa.sa_mask);
594 	nsa.sa_flags = SA_RESTART;
595 	sigaction(SIGHUP, &nsa, NULL);
596 	sigaction(SIGCHLD, &nsa, NULL);
597 	sigaction(SIGTERM, &nsa, NULL);
598 
599 	sigemptyset(&sigmask);
600 	sigaddset(&sigmask, SIGHUP);
601 	sigaddset(&sigmask, SIGCHLD);
602 	sigaddset(&sigmask, SIGTERM);
603 	sigprocmask(SIG_SETMASK, &sigmask, &osigmask);
604 
605 	while (1) {
606 		fd_set rfds;
607 		int res;
608 		struct timeval to;
609 
610 		to.tv_sec = BATT_CHK_INTV;
611 		to.tv_usec = 0;
612 
613 		memcpy(&rfds, &master_rfds, sizeof rfds);
614 		sigprocmask(SIG_SETMASK, &osigmask, NULL);
615 		if ((res=select(fdmax + 1, &rfds, 0, 0, &to)) < 0) {
616 			if (errno != EINTR)
617 				err(1, "select");
618 		}
619 		sigprocmask(SIG_SETMASK, &sigmask, NULL);
620 
621 		if (res == 0) { /* time to check the battery */
622 			check_battery();
623 			continue;
624 		}
625 
626 		if (FD_ISSET(signal_fd[0], &rfds)) {
627 			if (proc_signal(signal_fd[0]) < 0)
628 				return;
629 		}
630 
631 		if (FD_ISSET(apmctl_fd, &rfds))
632 			proc_apmevent(apmctl_fd);
633 	}
634 }
635 
636 int
637 main(int ac, char* av[])
638 {
639 	int	ch;
640 	int	daemonize = 1;
641 	char	*prog;
642 	int	logopt = LOG_NDELAY | LOG_PID;
643 
644 	while ((ch = getopt(ac, av, "df:sv")) != -1) {
645 		switch (ch) {
646 		case 'd':
647 			daemonize = 0;
648 			debug_level++;
649 			break;
650 		case 'f':
651 			apmd_configfile = optarg;
652 			break;
653 		case 's':
654 			soft_power_state_change = 1;
655 			break;
656 		case 'v':
657 			verbose = 1;
658 			break;
659 		default:
660 			err(1, "unknown option `%c'", ch);
661 		}
662 	}
663 
664 	if (daemonize)
665 		daemon(0, 0);
666 
667 #ifdef NICE_INCR
668 	nice(NICE_INCR);
669 #endif
670 
671 	if (!daemonize)
672 		logopt |= LOG_PERROR;
673 
674 	prog = strrchr(av[0], '/');
675 	openlog(prog ? prog+1 : av[0], logopt, LOG_DAEMON);
676 
677 	syslog(LOG_NOTICE, "start");
678 
679 	if (pipe(signal_fd) < 0)
680 		err(1, "pipe");
681 	if (fcntl(signal_fd[0], F_SETFL, O_NONBLOCK) < 0)
682 		err(1, "fcntl");
683 
684 	if ((apmnorm_fd = open(APM_NORM_DEVICEFILE, O_RDWR)) == -1) {
685 		err(1, "cannot open device file `%s'", APM_NORM_DEVICEFILE);
686 	}
687 
688 	if (fcntl(apmnorm_fd, F_SETFD, 1) == -1) {
689 		err(1, "cannot set close-on-exec flag for device file '%s'", APM_NORM_DEVICEFILE);
690 	}
691 
692 	if ((apmctl_fd = open(APM_CTL_DEVICEFILE, O_RDWR)) == -1) {
693 		err(1, "cannot open device file `%s'", APM_CTL_DEVICEFILE);
694 	}
695 
696 	if (fcntl(apmctl_fd, F_SETFD, 1) == -1) {
697 		err(1, "cannot set close-on-exec flag for device file '%s'", APM_CTL_DEVICEFILE);
698 	}
699 
700 	restart();
701 	write_pid();
702 	event_loop();
703 	exit(EXIT_SUCCESS);
704 }
705 
706