xref: /freebsd/usr.sbin/apmd/apmd.c (revision 734e82fe33aa764367791a7d603b383996c6b40b)
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 #ifndef lint
33 static 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 
57 int		debug_level = 0;
58 int		verbose = 0;
59 int		soft_power_state_change = 0;
60 const char	*apmd_configfile = APMD_CONFIGFILE;
61 const char	*apmd_pidfile = APMD_PIDFILE;
62 int             apmctl_fd = -1, apmnorm_fd = -1;
63 
64 /*
65  * table of event handlers
66  */
67 #define EVENT_CONFIG_INITIALIZER(EV,R) { #EV, NULL, R },
68 struct 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  */
87 struct 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  */
94 struct event_cmd *
95 event_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  */
110 int
111 event_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 }
137 void
138 event_cmd_exec_dump(void *this, FILE *fp)
139 {
140 	fprintf(fp, " \"%s\"", ((struct event_cmd_exec *)this)->line);
141 }
142 struct event_cmd *
143 event_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 }
156 void
157 event_cmd_exec_free(void *this)
158 {
159 	free(((struct event_cmd_exec *)this)->line);
160 }
161 struct 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  */
171 int
172 event_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 }
182 struct 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  */
192 struct event_cmd *
193 clone_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 }
206 void
207 free_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 }
217 int
218 register_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 }
251 int
252 register_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  */
286 int
287 exec_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  */
307 int
308 exec_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  */
323 extern FILE * yyin;
324 extern int yydebug;
325 
326 void
327 read_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 
355 void
356 dump_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 
391 void
392 destroy_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 
422 void
423 restart(void)
424 {
425 	destroy_config();
426 	read_config();
427 	if (verbose)
428 		dump_config();
429 }
430 
431 /*
432  * write pid file
433  */
434 static void
435 write_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  */
448 static int signal_fd[2];
449 
450 void
451 enque_signal(int sig)
452 {
453 	if (write(signal_fd[1], &sig, sizeof sig) != sizeof sig)
454 		err(1, "cannot process signal.");
455 }
456 
457 void
458 wait_child(void)
459 {
460 	int status;
461 	while (waitpid(-1, &status, WNOHANG) > 0)
462 		;
463 }
464 
465 int
466 proc_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 }
492 void
493 proc_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 
512 void
513 check_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 }
578 void
579 event_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 
638 int
639 main(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