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