1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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#ifndef lint
33static const char rcsid[] =
34  "$FreeBSD$";
35#endif /* not lint */
36
37#include <sys/types.h>
38#include <assert.h>
39#include <bitstring.h>
40#include <err.h>
41#include <errno.h>
42#include <fcntl.h>
43#include <paths.h>
44#include <signal.h>
45#include <stdio.h>
46#include <stdlib.h>
47#include <string.h>
48#include <syslog.h>
49#include <unistd.h>
50#include <sys/ioctl.h>
51#include <sys/time.h>
52#include <sys/wait.h>
53#include <machine/apm_bios.h>
54
55#include "apmd.h"
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		warn("cannot fork");
120		break;
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	return status;
136}
137void
138event_cmd_exec_dump(void *this, FILE *fp)
139{
140	fprintf(fp, " \"%s\"", ((struct event_cmd_exec *)this)->line);
141}
142struct event_cmd *
143event_cmd_exec_clone(void *this)
144{
145	struct event_cmd_exec * newone = (struct event_cmd_exec *) event_cmd_default_clone(this);
146	struct event_cmd_exec * oldone = this;
147
148	newone->evcmd.next = NULL;
149	newone->evcmd.len = oldone->evcmd.len;
150	newone->evcmd.name = oldone->evcmd.name;
151	newone->evcmd.op = oldone->evcmd.op;
152	if ((newone->line = strdup(oldone->line)) == NULL)
153		err(1, "out of memory");
154	return (struct event_cmd *) newone;
155}
156void
157event_cmd_exec_free(void *this)
158{
159	free(((struct event_cmd_exec *)this)->line);
160}
161struct event_cmd_op event_cmd_exec_ops = {
162	event_cmd_exec_act,
163	event_cmd_exec_dump,
164	event_cmd_exec_clone,
165	event_cmd_exec_free
166};
167
168/*
169 * reject command
170 */
171int
172event_cmd_reject_act(void *this __unused)
173{
174	int rc = 0;
175
176	if (ioctl(apmctl_fd, APMIO_REJECTLASTREQ, NULL)) {
177		syslog(LOG_NOTICE, "fail to reject\n");
178		rc = -1;
179	}
180	return rc;
181}
182struct event_cmd_op event_cmd_reject_ops = {
183	event_cmd_reject_act,
184	NULL,
185	event_cmd_default_clone,
186	NULL
187};
188
189/*
190 * manipulate event_config
191 */
192struct event_cmd *
193clone_event_cmd_list(struct event_cmd *p)
194{
195	struct event_cmd dummy;
196	struct event_cmd *q = &dummy;
197	for ( ;p; p = p->next) {
198		assert(p->op->clone);
199		if ((q->next = p->op->clone(p)) == NULL)
200			err(1, "out of memory");
201		q = q->next;
202	}
203	q->next = NULL;
204	return dummy.next;
205}
206void
207free_event_cmd_list(struct event_cmd *p)
208{
209	struct event_cmd * q;
210	for ( ; p ; p = q) {
211		q = p->next;
212		if (p->op->free)
213			p->op->free(p);
214		free(p);
215	}
216}
217int
218register_battery_handlers(
219	int level, int direction,
220	struct event_cmd *cmdlist)
221{
222	/*
223	 * level is negative if it's in "minutes", non-negative if
224	 * percentage.
225	 *
226	 * direction =1 means we care about this level when charging,
227	 * direction =-1 means we care about it when discharging.
228	 */
229	if (level>100) /* percentage > 100 */
230		return -1;
231	if (abs(direction) != 1) /* nonsense direction value */
232		return -1;
233
234	if (cmdlist) {
235		struct battery_watch_event *we;
236
237		if ((we = malloc(sizeof(struct battery_watch_event))) == NULL)
238			err(1, "out of memory");
239
240		we->next = battery_watch_list; /* starts at NULL */
241		battery_watch_list = we;
242		we->level = abs(level);
243		we->type = (level<0)?BATTERY_MINUTES:BATTERY_PERCENT;
244		we->direction = (direction<0)?BATTERY_DISCHARGING:
245			BATTERY_CHARGING;
246		we->done = 0;
247		we->cmdlist = clone_event_cmd_list(cmdlist);
248	}
249	return 0;
250}
251int
252register_apm_event_handlers(
253	bitstr_t bit_decl(evlist, EVENT_MAX),
254	struct event_cmd *cmdlist)
255{
256	if (cmdlist) {
257		bitstr_t bit_decl(tmp, EVENT_MAX);
258		memcpy(&tmp, evlist, bitstr_size(EVENT_MAX));
259
260		for (;;) {
261			int n;
262			struct event_cmd *p;
263			struct event_cmd *q;
264			bit_ffs(tmp, EVENT_MAX, &n);
265			if (n < 0)
266				break;
267			p = events[n].cmdlist;
268			if ((q = clone_event_cmd_list(cmdlist)) == NULL)
269				err(1, "out of memory");
270			if (p) {
271				while (p->next != NULL)
272					p = p->next;
273				p->next = q;
274			} else {
275				events[n].cmdlist = q;
276			}
277			bit_clear(tmp, n);
278		}
279	}
280	return 0;
281}
282
283/*
284 * execute command
285 */
286int
287exec_run_cmd(struct event_cmd *p)
288{
289	int status = 0;
290
291	for (; p; p = p->next) {
292		assert(p->op->act);
293		if (verbose)
294			syslog(LOG_INFO, "action: %s", p->name);
295		status = p->op->act(p);
296		if (status) {
297			syslog(LOG_NOTICE, "command finished with %d\n", status);
298			break;
299		}
300	}
301	return status;
302}
303
304/*
305 * execute command -- the event version
306 */
307int
308exec_event_cmd(struct event_config *ev)
309{
310	int status = 0;
311
312	status = exec_run_cmd(ev->cmdlist);
313	if (status && ev->rejectable) {
314		syslog(LOG_ERR, "canceled");
315		event_cmd_reject_act(NULL);
316	}
317	return status;
318}
319
320/*
321 * read config file
322 */
323extern FILE * yyin;
324extern int yydebug;
325
326void
327read_config(void)
328{
329	int i;
330
331	if ((yyin = fopen(apmd_configfile, "r")) == NULL) {
332		err(1, "cannot open config file");
333	}
334
335#ifdef DEBUG
336	yydebug = debug_level;
337#endif
338
339	if (yyparse() != 0)
340		err(1, "cannot parse config file");
341
342	fclose(yyin);
343
344	/* enable events */
345	for (i = 0; i < EVENT_MAX; i++) {
346		if (events[i].cmdlist) {
347			u_int event_type = i;
348			if (write(apmctl_fd, &event_type, sizeof(u_int)) == -1) {
349				err(1, "cannot enable event 0x%x", event_type);
350			}
351		}
352	}
353}
354
355void
356dump_config(void)
357{
358	int i;
359	struct battery_watch_event *q;
360
361	for (i = 0; i < EVENT_MAX; i++) {
362		struct event_cmd * p;
363		if ((p = events[i].cmdlist)) {
364			fprintf(stderr, "apm_event %s {\n", events[i].name);
365			for ( ; p ; p = p->next) {
366				fprintf(stderr, "\t%s", p->name);
367				if (p->op->dump)
368					p->op->dump(p, stderr);
369				fprintf(stderr, ";\n");
370			}
371			fprintf(stderr, "}\n");
372		}
373	}
374	for (q = battery_watch_list ; q != NULL ; q = q -> next) {
375		struct event_cmd * p;
376		fprintf(stderr, "apm_battery %d%s %s {\n",
377			q -> level,
378			(q -> type == BATTERY_PERCENT)?"%":"m",
379			(q -> direction == BATTERY_CHARGING)?"charging":
380				"discharging");
381		for ( p = q -> cmdlist; p ; p = p->next) {
382			fprintf(stderr, "\t%s", p->name);
383			if (p->op->dump)
384				p->op->dump(p, stderr);
385			fprintf(stderr, ";\n");
386		}
387		fprintf(stderr, "}\n");
388	}
389}
390
391void
392destroy_config(void)
393{
394	int i;
395	struct battery_watch_event *q;
396
397	/* disable events */
398	for (i = 0; i < EVENT_MAX; i++) {
399		if (events[i].cmdlist) {
400			u_int event_type = i;
401			if (write(apmctl_fd, &event_type, sizeof(u_int)) == -1) {
402				err(1, "cannot disable event 0x%x", event_type);
403			}
404		}
405	}
406
407	for (i = 0; i < EVENT_MAX; i++) {
408		struct event_cmd * p;
409		if ((p = events[i].cmdlist))
410			free_event_cmd_list(p);
411		events[i].cmdlist = NULL;
412	}
413
414	for( ; battery_watch_list; battery_watch_list = battery_watch_list -> next) {
415		free_event_cmd_list(battery_watch_list->cmdlist);
416		q = battery_watch_list->next;
417		free(battery_watch_list);
418		battery_watch_list = q;
419	}
420}
421
422void
423restart(void)
424{
425	destroy_config();
426	read_config();
427	if (verbose)
428		dump_config();
429}
430
431/*
432 * write pid file
433 */
434static void
435write_pid(void)
436{
437	FILE *fp = fopen(apmd_pidfile, "w");
438
439	if (fp) {
440		fprintf(fp, "%ld\n", (long)getpid());
441		fclose(fp);
442	}
443}
444
445/*
446 * handle signals
447 */
448static int signal_fd[2];
449
450void
451enque_signal(int sig)
452{
453	if (write(signal_fd[1], &sig, sizeof sig) != sizeof sig)
454		err(1, "cannot process signal.");
455}
456
457void
458wait_child(void)
459{
460	int status;
461	while (waitpid(-1, &status, WNOHANG) > 0)
462		;
463}
464
465int
466proc_signal(int fd)
467{
468	int rc = 0;
469	int sig;
470
471	while (read(fd, &sig, sizeof sig) == sizeof sig) {
472		syslog(LOG_INFO, "caught signal: %d", sig);
473		switch (sig) {
474		case SIGHUP:
475			syslog(LOG_NOTICE, "restart by SIG");
476			restart();
477			break;
478		case SIGTERM:
479			syslog(LOG_NOTICE, "going down on signal %d", sig);
480			rc = -1;
481			return rc;
482		case SIGCHLD:
483			wait_child();
484			break;
485		default:
486			warn("unexpected signal(%d) received.", sig);
487			break;
488		}
489	}
490	return rc;
491}
492void
493proc_apmevent(int fd)
494{
495	struct apm_event_info apmevent;
496
497	while (ioctl(fd, APMIO_NEXTEVENT, &apmevent) == 0) {
498		int status;
499		syslog(LOG_NOTICE, "apmevent %04x index %d\n",
500			apmevent.type, apmevent.index);
501		syslog(LOG_INFO, "apm event: %s", events[apmevent.type].name);
502		if (fork() == 0) {
503			status = exec_event_cmd(&events[apmevent.type]);
504			exit(status);
505		}
506	}
507}
508
509#define AC_POWER_STATE ((pw_info.ai_acline == 1) ? BATTERY_CHARGING :\
510	BATTERY_DISCHARGING)
511
512void
513check_battery(void)
514{
515
516	static int first_time=1, last_state;
517	int status;
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			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		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		if (soft_power_state_change && fork() == 0) {
552			status = exec_event_cmd(&events[PMEV_POWERSTATECHANGE]);
553			exit(status);
554		}
555		last_state = AC_POWER_STATE;
556		for (p = battery_watch_list ; p!=NULL ; p = p -> next)
557			p->done = 0;
558	}
559	for (p = battery_watch_list ; p != NULL ; p = p -> next)
560		if (p -> direction == AC_POWER_STATE &&
561			!(p -> done) &&
562			((p -> type == BATTERY_PERCENT &&
563				p -> level == (int)pw_info.ai_batt_life) ||
564			(p -> type == BATTERY_MINUTES &&
565				p -> level == (pw_info.ai_batt_time / 60)))) {
566			p -> done++;
567			if (verbose)
568				syslog(LOG_NOTICE, "Caught battery event: %s, %d%s",
569					(p -> direction == BATTERY_CHARGING)?"charging":"discharging",
570					p -> level,
571					(p -> type == BATTERY_PERCENT)?"%":" minutes");
572			if (fork() == 0) {
573				status = exec_run_cmd(p -> cmdlist);
574				exit(status);
575			}
576		}
577}
578void
579event_loop(void)
580{
581	int		fdmax = 0;
582	struct sigaction nsa;
583	fd_set          master_rfds;
584	sigset_t	sigmask, osigmask;
585
586	FD_ZERO(&master_rfds);
587	FD_SET(apmctl_fd, &master_rfds);
588	fdmax = apmctl_fd > fdmax ? apmctl_fd : fdmax;
589
590	FD_SET(signal_fd[0], &master_rfds);
591	fdmax = signal_fd[0] > fdmax ? signal_fd[0] : fdmax;
592
593	memset(&nsa, 0, sizeof nsa);
594	nsa.sa_handler = enque_signal;
595	sigfillset(&nsa.sa_mask);
596	nsa.sa_flags = SA_RESTART;
597	sigaction(SIGHUP, &nsa, NULL);
598	sigaction(SIGCHLD, &nsa, NULL);
599	sigaction(SIGTERM, &nsa, NULL);
600
601	sigemptyset(&sigmask);
602	sigaddset(&sigmask, SIGHUP);
603	sigaddset(&sigmask, SIGCHLD);
604	sigaddset(&sigmask, SIGTERM);
605	sigprocmask(SIG_SETMASK, &sigmask, &osigmask);
606
607	while (1) {
608		fd_set rfds;
609		int res;
610		struct timeval to;
611
612		to.tv_sec = BATT_CHK_INTV;
613		to.tv_usec = 0;
614
615		memcpy(&rfds, &master_rfds, sizeof rfds);
616		sigprocmask(SIG_SETMASK, &osigmask, NULL);
617		if ((res=select(fdmax + 1, &rfds, 0, 0, &to)) < 0) {
618			if (errno != EINTR)
619				err(1, "select");
620		}
621		sigprocmask(SIG_SETMASK, &sigmask, NULL);
622
623		if (res == 0) { /* time to check the battery */
624			check_battery();
625			continue;
626		}
627
628		if (FD_ISSET(signal_fd[0], &rfds)) {
629			if (proc_signal(signal_fd[0]) < 0)
630				return;
631		}
632
633		if (FD_ISSET(apmctl_fd, &rfds))
634			proc_apmevent(apmctl_fd);
635	}
636}
637
638int
639main(int ac, char* av[])
640{
641	int	ch;
642	int	daemonize = 1;
643	char	*prog;
644	int	logopt = LOG_NDELAY | LOG_PID;
645
646	while ((ch = getopt(ac, av, "df:sv")) != -1) {
647		switch (ch) {
648		case 'd':
649			daemonize = 0;
650			debug_level++;
651			break;
652		case 'f':
653			apmd_configfile = optarg;
654			break;
655		case 's':
656			soft_power_state_change = 1;
657			break;
658		case 'v':
659			verbose = 1;
660			break;
661		default:
662			err(1, "unknown option `%c'", ch);
663		}
664	}
665
666	if (daemonize)
667		daemon(0, 0);
668
669#ifdef NICE_INCR
670	nice(NICE_INCR);
671#endif
672
673	if (!daemonize)
674		logopt |= LOG_PERROR;
675
676	prog = strrchr(av[0], '/');
677	openlog(prog ? prog+1 : av[0], logopt, LOG_DAEMON);
678
679	syslog(LOG_NOTICE, "start");
680
681	if (pipe(signal_fd) < 0)
682		err(1, "pipe");
683	if (fcntl(signal_fd[0], F_SETFL, O_NONBLOCK) < 0)
684		err(1, "fcntl");
685
686	if ((apmnorm_fd = open(APM_NORM_DEVICEFILE, O_RDWR)) == -1) {
687		err(1, "cannot open device file `%s'", APM_NORM_DEVICEFILE);
688	}
689
690	if (fcntl(apmnorm_fd, F_SETFD, 1) == -1) {
691		err(1, "cannot set close-on-exec flag for device file '%s'", APM_NORM_DEVICEFILE);
692	}
693
694	if ((apmctl_fd = open(APM_CTL_DEVICEFILE, O_RDWR)) == -1) {
695		err(1, "cannot open device file `%s'", APM_CTL_DEVICEFILE);
696	}
697
698	if (fcntl(apmctl_fd, F_SETFD, 1) == -1) {
699		err(1, "cannot set close-on-exec flag for device file '%s'", APM_CTL_DEVICEFILE);
700	}
701
702	restart();
703	write_pid();
704	event_loop();
705	exit(EXIT_SUCCESS);
706}
707
708