apmd.c revision 76611
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 76611 2001-05-15 05:13:45Z nsayer $";
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;
59const char	*apmd_configfile = APMD_CONFIGFILE;
60const char	*apmd_pidfile = APMD_PIDFILE;
61int             apmctl_fd = -1, apmnorm_fd = -1;
62
63/*
64 * table of event handlers
65 */
66#define EVENT_CONFIG_INITIALIZER(EV,R) { #EV, NULL, R },
67struct 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 */
86struct 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 */
93struct event_cmd *
94event_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 */
109int
110event_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}
134void
135event_cmd_exec_dump(void *this, FILE *fp)
136{
137	fprintf(fp, " \"%s\"", ((struct event_cmd_exec *)this)->line);
138}
139struct event_cmd *
140event_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}
153void
154event_cmd_exec_free(void *this)
155{
156	free(((struct event_cmd_exec *)this)->line);
157}
158struct 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 */
168int
169event_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}
181struct 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 */
191struct event_cmd *
192clone_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}
205void
206free_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}
216int
217register_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}
250int
251register_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 */
285int
286exec_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 */
306int
307exec_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 */
322extern FILE * yyin;
323extern int yydebug;
324
325void
326read_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
354void
355dump_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
390void
391destroy_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
421void
422restart()
423{
424	destroy_config();
425	read_config();
426	if (verbose)
427		dump_config();
428}
429
430/*
431 * write pid file
432 */
433static void
434write_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 */
447static int signal_fd[2];
448
449void
450enque_signal(int sig)
451{
452	if (write(signal_fd[1], &sig, sizeof sig) != sizeof sig)
453		(void) err(1, "cannot process signal.");
454}
455
456void
457wait_child()
458{
459	int status;
460	while (waitpid(-1, &status, WNOHANG) > 0)
461		;
462}
463
464int
465proc_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}
493void
494proc_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
513void
514check_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}
575void
576event_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	}
633out:
634	return;
635}
636
637int
638main(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