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$";
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
55int		debug_level = 0;
56int		verbose = 0;
57int		soft_power_state_change = 0;
58const char	*apmd_configfile = APMD_CONFIGFILE;
59const char	*apmd_pidfile = APMD_PIDFILE;
60int             apmctl_fd = -1, apmnorm_fd = -1;
61
62/*
63 * table of event handlers
64 */
65#define EVENT_CONFIG_INITIALIZER(EV,R) { #EV, NULL, R },
66struct 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 */
85struct 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 */
92struct event_cmd *
93event_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 */
108int
109event_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}
135void
136event_cmd_exec_dump(void *this, FILE *fp)
137{
138	fprintf(fp, " \"%s\"", ((struct event_cmd_exec *)this)->line);
139}
140struct event_cmd *
141event_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}
154void
155event_cmd_exec_free(void *this)
156{
157	free(((struct event_cmd_exec *)this)->line);
158}
159struct 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 */
169int
170event_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}
180struct 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 */
190struct event_cmd *
191clone_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}
204void
205free_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}
215int
216register_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}
249int
250register_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 */
284int
285exec_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 */
305int
306exec_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 */
321extern FILE * yyin;
322extern int yydebug;
323
324void
325read_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
353void
354dump_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
389void
390destroy_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
420void
421restart(void)
422{
423	destroy_config();
424	read_config();
425	if (verbose)
426		dump_config();
427}
428
429/*
430 * write pid file
431 */
432static void
433write_pid(void)
434{
435	FILE *fp = fopen(apmd_pidfile, "w");
436
437	if (fp) {
438		fprintf(fp, "%ld\n", (long)getpid());
439		fclose(fp);
440	}
441}
442
443/*
444 * handle signals
445 */
446static int signal_fd[2];
447
448void
449enque_signal(int sig)
450{
451	if (write(signal_fd[1], &sig, sizeof sig) != sizeof sig)
452		err(1, "cannot process signal.");
453}
454
455void
456wait_child(void)
457{
458	int status;
459	while (waitpid(-1, &status, WNOHANG) > 0)
460		;
461}
462
463int
464proc_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}
490void
491proc_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
510void
511check_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}
576void
577event_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
636int
637main(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