apmd.c revision 166509
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
31static const char rcsid[] =
32  "$FreeBSD: head/usr.sbin/apmd/apmd.c 166509 2007-02-05 07:35:23Z kevlo $";
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
55extern int	yyparse(void);
56
57int		debug_level = 0;
58int		verbose = 0;
59int		soft_power_state_change = 0;
60const char	*apmd_configfile = APMD_CONFIGFILE;
61const char	*apmd_pidfile = APMD_PIDFILE;
62int             apmctl_fd = -1, apmnorm_fd = -1;
63
64/*
65 * table of event handlers
66 */
67#define EVENT_CONFIG_INITIALIZER(EV,R) { #EV, NULL, R },
68struct 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 */
87struct 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 */
94struct event_cmd *
95event_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 */
110int
111event_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}
138void
139event_cmd_exec_dump(void *this, FILE *fp)
140{
141	fprintf(fp, " \"%s\"", ((struct event_cmd_exec *)this)->line);
142}
143struct event_cmd *
144event_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}
157void
158event_cmd_exec_free(void *this)
159{
160	free(((struct event_cmd_exec *)this)->line);
161}
162struct 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 */
172int
173event_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}
185struct 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 */
195struct event_cmd *
196clone_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}
209void
210free_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}
220int
221register_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}
254int
255register_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 */
289int
290exec_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 */
310int
311exec_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 */
326extern FILE * yyin;
327extern int yydebug;
328
329void
330read_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
358void
359dump_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
394void
395destroy_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
425void
426restart()
427{
428	destroy_config();
429	read_config();
430	if (verbose)
431		dump_config();
432}
433
434/*
435 * write pid file
436 */
437static void
438write_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 */
451static int signal_fd[2];
452
453void
454enque_signal(int sig)
455{
456	if (write(signal_fd[1], &sig, sizeof sig) != sizeof sig)
457		(void) err(1, "cannot process signal.");
458}
459
460void
461wait_child()
462{
463	int status;
464	while (waitpid(-1, &status, WNOHANG) > 0)
465		;
466}
467
468int
469proc_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}
497void
498proc_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
517void
518check_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}
583void
584event_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	}
641out:
642	return;
643}
644
645int
646main(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")) != -1) {
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