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