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