Merge branch 'misc'
[dragonfly.git] / usr.sbin / apmd / apmd.c
blobbcefcde4be4302ca8483b138d256a2e543af84fe
1 /*-
2 * APM (Advanced Power Management) Event Dispatcher
4 * Copyright (c) 1999 Mitsuru IWASAKI <iwasaki@FreeBSD.org>
5 * Copyright (c) 1999 KOIE Hidetaka <koie@suri.co.jp>
6 * All rights reserved.
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.
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.
29 * $FreeBSD: src/usr.sbin/apmd/apmd.c,v 1.3.2.1 2001/08/13 17:30:30 nsayer Exp $
30 * $DragonFly: src/usr.sbin/apmd/apmd.c,v 1.7 2008/07/10 18:29:52 swildner Exp $
33 #include <assert.h>
34 #include <bitstring.h>
35 #include <err.h>
36 #include <errno.h>
37 #include <fcntl.h>
38 #include <libutil.h>
39 #include <paths.h>
40 #include <signal.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <syslog.h>
45 #include <unistd.h>
46 #include <sys/ioctl.h>
47 #include <sys/types.h>
48 #include <sys/time.h>
49 #include <sys/wait.h>
50 #include <machine/apm_bios.h>
52 #include "apmd.h"
54 extern int yyparse(void);
56 int debug_level = 0;
57 int verbose = 0;
58 const char *apmd_configfile = APMD_CONFIGFILE;
59 int apmctl_fd = -1, apmnorm_fd = -1;
62 * table of event handlers
64 #define EVENT_CONFIG_INITIALIZER(EV,R) { #EV, NULL, R },
65 struct event_config events[EVENT_MAX] = {
66 EVENT_CONFIG_INITIALIZER(NOEVENT, 0)
67 EVENT_CONFIG_INITIALIZER(STANDBYREQ, 1)
68 EVENT_CONFIG_INITIALIZER(SUSPENDREQ, 1)
69 EVENT_CONFIG_INITIALIZER(NORMRESUME, 0)
70 EVENT_CONFIG_INITIALIZER(CRITRESUME, 0)
71 EVENT_CONFIG_INITIALIZER(BATTERYLOW, 0)
72 EVENT_CONFIG_INITIALIZER(POWERSTATECHANGE, 0)
73 EVENT_CONFIG_INITIALIZER(UPDATETIME, 0)
74 EVENT_CONFIG_INITIALIZER(CRITSUSPEND, 1)
75 EVENT_CONFIG_INITIALIZER(USERSTANDBYREQ, 1)
76 EVENT_CONFIG_INITIALIZER(USERSUSPENDREQ, 1)
77 EVENT_CONFIG_INITIALIZER(STANDBYRESUME, 0)
78 EVENT_CONFIG_INITIALIZER(CAPABILITIESCHANGE, 0)
82 * List of battery events
84 struct battery_watch_event *battery_watch_list = NULL;
86 #define BATT_CHK_INTV 10 /* how many seconds between battery state checks? */
89 * default procedure
91 struct event_cmd *
92 event_cmd_default_clone(void *this)
94 struct event_cmd * oldone = this;
95 struct event_cmd * newone = malloc(oldone->len);
97 newone->next = NULL;
98 newone->len = oldone->len;
99 newone->name = oldone->name;
100 newone->op = oldone->op;
101 return newone;
105 * exec command
108 event_cmd_exec_act(void *this)
110 struct event_cmd_exec * p = this;
111 int status = -1;
112 pid_t pid;
114 switch ((pid = fork())) {
115 case -1:
116 warn("cannot fork");
117 goto out;
118 case 0:
119 /* child process */
120 execl(_PATH_BSHELL, "sh", "-c", p->line, (char *)NULL);
121 _exit(127);
122 default:
123 /* parent process */
124 do {
125 pid = waitpid(pid, &status, 0);
126 } while (pid == -1 && errno == EINTR);
127 break;
129 out:
130 return status;
132 void
133 event_cmd_exec_dump(void *this, FILE *fp)
135 fprintf(fp, " \"%s\"", ((struct event_cmd_exec *)this)->line);
137 struct event_cmd *
138 event_cmd_exec_clone(void *this)
140 struct event_cmd_exec * newone = (struct event_cmd_exec *) event_cmd_default_clone(this);
141 struct event_cmd_exec * oldone = this;
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;
151 void
152 event_cmd_exec_free(void *this)
154 free(((struct event_cmd_exec *)this)->line);
156 struct 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
164 * reject commad
167 event_cmd_reject_act(void *this)
169 int rc = -1;
171 if (ioctl(apmctl_fd, APMIO_REJECTLASTREQ, NULL)) {
172 syslog(LOG_NOTICE, "fail to reject\n");
173 goto out;
175 rc = 0;
176 out:
177 return rc;
179 struct event_cmd_op event_cmd_reject_ops = {
180 event_cmd_reject_act,
181 NULL,
182 event_cmd_default_clone,
183 NULL
187 * manipulate event_config
189 struct event_cmd *
190 clone_event_cmd_list(struct event_cmd *p)
192 struct event_cmd dummy;
193 struct event_cmd *q = &dummy;
194 for ( ;p; p = p->next) {
195 assert(p->op->clone);
196 if ((q->next = p->op->clone(p)) == NULL)
197 err(1, "out of memory");
198 q = q->next;
200 q->next = NULL;
201 return dummy.next;
203 void
204 free_event_cmd_list(struct event_cmd *p)
206 struct event_cmd * q;
207 for ( ; p ; p = q) {
208 q = p->next;
209 if (p->op->free)
210 p->op->free(p);
211 free(p);
215 register_battery_handlers(
216 int level, int direction,
217 struct event_cmd *cmdlist)
220 * level is negative if it's in "minutes", non-negative if
221 * percentage.
223 * direction =1 means we care about this level when charging,
224 * direction =-1 means we care about it when discharging.
226 if (level>100) /* percentage > 100 */
227 return -1;
228 if (abs(direction) != 1) /* nonsense direction value */
229 return -1;
231 if (cmdlist) {
232 struct battery_watch_event *we;
234 if ((we = malloc(sizeof(struct battery_watch_event))) == NULL)
235 err(1, "out of memory");
237 we->next = battery_watch_list; /* starts at NULL */
238 battery_watch_list = we;
239 we->level = abs(level);
240 we->type = (level<0)?BATTERY_MINUTES:BATTERY_PERCENT;
241 we->direction = (direction<0)?BATTERY_DISCHARGING:
242 BATTERY_CHARGING;
243 we->done = 0;
244 we->cmdlist = clone_event_cmd_list(cmdlist);
246 return 0;
249 register_apm_event_handlers(
250 bitstr_t bit_decl(evlist, EVENT_MAX),
251 struct event_cmd *cmdlist)
253 if (cmdlist) {
254 bitstr_t bit_decl(tmp, EVENT_MAX);
255 memcpy(&tmp, evlist, bitstr_size(EVENT_MAX));
257 for (;;) {
258 int n;
259 struct event_cmd *p;
260 struct event_cmd *q;
261 bit_ffs(tmp, EVENT_MAX, &n);
262 if (n < 0)
263 break;
264 p = events[n].cmdlist;
265 if ((q = clone_event_cmd_list(cmdlist)) == NULL)
266 err(1, "out of memory");
267 if (p) {
268 while (p->next != NULL)
269 p = p->next;
270 p->next = q;
271 } else {
272 events[n].cmdlist = q;
274 bit_clear(tmp, n);
277 return 0;
281 * execute command
284 exec_run_cmd(struct event_cmd *p)
286 int status = 0;
288 for (; p; p = p->next) {
289 assert(p->op->act);
290 if (verbose)
291 syslog(LOG_INFO, "action: %s", p->name);
292 status = p->op->act(p);
293 if (status) {
294 syslog(LOG_NOTICE, "command finished with %d\n", status);
295 break;
298 return status;
302 * execute command -- the event version
305 exec_event_cmd(struct event_config *ev)
307 int status = 0;
309 status = exec_run_cmd(ev->cmdlist);
310 if (status && ev->rejectable) {
311 syslog(LOG_ERR, "canceled");
312 event_cmd_reject_act(NULL);
314 return status;
318 * read config file
320 extern FILE * yyin;
321 extern int yydebug;
323 void
324 read_config(void)
326 int i;
328 if ((yyin = fopen(apmd_configfile, "r")) == NULL) {
329 err(1, "cannot open config file");
332 #ifdef DEBUG
333 yydebug = debug_level;
334 #endif
336 if (yyparse() != 0)
337 err(1, "cannot parse config file");
339 fclose(yyin);
341 /* enable events */
342 for (i = 0; i < EVENT_MAX; i++) {
343 if (events[i].cmdlist) {
344 u_int event_type = i;
345 if (write(apmctl_fd, &event_type, sizeof(u_int)) == -1) {
346 err(1, "cannot enable event 0x%x", event_type);
352 void
353 dump_config(void)
355 int i;
356 struct battery_watch_event *q;
358 for (i = 0; i < EVENT_MAX; i++) {
359 struct event_cmd * p;
360 if ((p = events[i].cmdlist)) {
361 fprintf(stderr, "apm_event %s {\n", events[i].name);
362 for ( ; p ; p = p->next) {
363 fprintf(stderr, "\t%s", p->name);
364 if (p->op->dump)
365 p->op->dump(p, stderr);
366 fprintf(stderr, ";\n");
368 fprintf(stderr, "}\n");
371 for (q = battery_watch_list ; q != NULL ; q = q -> next) {
372 struct event_cmd * p;
373 fprintf(stderr, "apm_battery %d%s %s {\n",
374 q -> level,
375 (q -> type == BATTERY_PERCENT)?"%":"m",
376 (q -> direction == BATTERY_CHARGING)?"charging":
377 "discharging");
378 for ( p = q -> cmdlist; p ; p = p->next) {
379 fprintf(stderr, "\t%s", p->name);
380 if (p->op->dump)
381 p->op->dump(p, stderr);
382 fprintf(stderr, ";\n");
384 fprintf(stderr, "}\n");
388 void
389 destroy_config(void)
391 int i;
392 struct battery_watch_event *q;
394 /* disable events */
395 for (i = 0; i < EVENT_MAX; i++) {
396 if (events[i].cmdlist) {
397 u_int event_type = i;
398 if (write(apmctl_fd, &event_type, sizeof(u_int)) == -1) {
399 err(1, "cannot disable event 0x%x", event_type);
404 for (i = 0; i < EVENT_MAX; i++) {
405 struct event_cmd * p;
406 if ((p = events[i].cmdlist))
407 free_event_cmd_list(p);
408 events[i].cmdlist = NULL;
411 for( ; battery_watch_list; battery_watch_list = battery_watch_list -> next) {
412 free_event_cmd_list(battery_watch_list->cmdlist);
413 q = battery_watch_list->next;
414 free(battery_watch_list);
415 battery_watch_list = q;
419 void
420 restart(void)
422 destroy_config();
423 read_config();
424 if (verbose)
425 dump_config();
429 * handle signals
431 static int signal_fd[2];
433 void
434 enque_signal(int sig)
436 if (write(signal_fd[1], &sig, sizeof sig) != sizeof sig)
437 err(1, "cannot process signal.");
440 void
441 wait_child(void)
443 int status;
444 while (waitpid(-1, &status, WNOHANG) > 0)
449 proc_signal(int fd)
451 int rc = -1;
452 int sig;
454 while (read(fd, &sig, sizeof sig) == sizeof sig) {
455 syslog(LOG_INFO, "caught signal: %d", sig);
456 switch (sig) {
457 case SIGHUP:
458 syslog(LOG_NOTICE, "restart by SIG");
459 restart();
460 break;
461 case SIGTERM:
462 syslog(LOG_NOTICE, "going down on signal %d", sig);
463 rc = 1;
464 goto out;
465 case SIGCHLD:
466 wait_child();
467 break;
468 default:
469 warn("unexpected signal(%d) received.", sig);
470 break;
473 rc = 0;
474 out:
475 return rc;
477 void
478 proc_apmevent(int fd)
480 struct apm_event_info apmevent;
482 while (ioctl(fd, APMIO_NEXTEVENT, &apmevent) == 0) {
483 int status;
484 syslog(LOG_NOTICE, "apmevent %04x index %d\n",
485 apmevent.type, apmevent.index);
486 syslog(LOG_INFO, "apm event: %s", events[apmevent.type].name);
487 if (fork() == 0) {
488 status = exec_event_cmd(&events[apmevent.type]);
489 exit(status);
494 #define AC_POWER_STATE ((pw_info.ai_acline == 1) ? BATTERY_CHARGING :\
495 BATTERY_DISCHARGING)
497 void
498 check_battery(void)
501 static int first_time=1, last_state;
503 struct apm_info pw_info;
504 struct battery_watch_event *p;
506 /* If we don't care, don't bother */
507 if (battery_watch_list == NULL)
508 return;
510 if (first_time) {
511 if ( ioctl(apmnorm_fd, APMIO_GETINFO, &pw_info) < 0)
512 err(1, "cannot check battery state.");
514 * This next statement isn't entirely true. The spec does not tie AC
515 * line state to battery charging or not, but this is a bit lazier to do.
517 last_state = AC_POWER_STATE;
518 first_time = 0;
519 return; /* We can't process events, we have no baseline */
523 * XXX - should we do this a bunch of times and perform some sort
524 * of smoothing or correction?
526 if ( ioctl(apmnorm_fd, APMIO_GETINFO, &pw_info) < 0)
527 err(1, "cannot check battery state.");
530 * If we're not in the state now that we were in last time,
531 * then it's a transition, which means we must clean out
532 * the event-caught state.
534 if (last_state != AC_POWER_STATE) {
535 last_state = AC_POWER_STATE;
536 for (p = battery_watch_list ; p!=NULL ; p = p -> next)
537 p->done = 0;
539 for (p = battery_watch_list ; p != NULL ; p = p -> next)
540 if (p -> direction == AC_POWER_STATE &&
541 !(p -> done) &&
542 ((p -> type == BATTERY_PERCENT &&
543 p -> level == pw_info.ai_batt_life) ||
544 (p -> type == BATTERY_MINUTES &&
545 p -> level == (pw_info.ai_batt_time / 60)))) {
546 p -> done++;
547 if (verbose)
548 syslog(LOG_NOTICE, "Caught battery event: %s, %d%s",
549 (p -> direction == BATTERY_CHARGING)?"charging":"discharging",
550 p -> level,
551 (p -> type == BATTERY_PERCENT)?"%":" minutes");
552 if (fork() == 0) {
553 int status;
554 status = exec_run_cmd(p -> cmdlist);
555 exit(status);
559 void
560 event_loop(void)
562 int fdmax = 0;
563 struct sigaction nsa;
564 fd_set master_rfds;
565 sigset_t sigmask, osigmask;
567 FD_ZERO(&master_rfds);
568 FD_SET(apmctl_fd, &master_rfds);
569 fdmax = apmctl_fd > fdmax ? apmctl_fd : fdmax;
571 FD_SET(signal_fd[0], &master_rfds);
572 fdmax = signal_fd[0] > fdmax ? signal_fd[0] : fdmax;
574 memset(&nsa, 0, sizeof nsa);
575 nsa.sa_handler = enque_signal;
576 sigfillset(&nsa.sa_mask);
577 nsa.sa_flags = SA_RESTART;
578 sigaction(SIGHUP, &nsa, NULL);
579 sigaction(SIGCHLD, &nsa, NULL);
580 sigaction(SIGTERM, &nsa, NULL);
582 sigemptyset(&sigmask);
583 sigaddset(&sigmask, SIGHUP);
584 sigaddset(&sigmask, SIGCHLD);
585 sigaddset(&sigmask, SIGTERM);
586 sigprocmask(SIG_SETMASK, &sigmask, &osigmask);
588 while (1) {
589 fd_set rfds;
590 int res;
591 struct timeval to;
593 to.tv_sec = BATT_CHK_INTV;
594 to.tv_usec = 0;
596 memcpy(&rfds, &master_rfds, sizeof rfds);
597 sigprocmask(SIG_SETMASK, &osigmask, NULL);
598 if ((res=select(fdmax + 1, &rfds, 0, 0, &to)) < 0) {
599 if (errno != EINTR)
600 err(1, "select");
602 sigprocmask(SIG_SETMASK, &sigmask, NULL);
604 if (res == 0) { /* time to check the battery */
605 check_battery();
606 continue;
609 if (FD_ISSET(signal_fd[0], &rfds)) {
610 if (proc_signal(signal_fd[0]) < 0)
611 goto out;
614 if (FD_ISSET(apmctl_fd, &rfds))
615 proc_apmevent(apmctl_fd);
617 out:
618 return;
622 main(int ac, char* av[])
624 int ch;
625 int daemonize = 1;
626 char *prog;
627 int logopt = LOG_NDELAY | LOG_PID;
629 while ((ch = getopt(ac, av, "df:v")) != -1) {
630 switch (ch) {
631 case 'd':
632 daemonize = 0;
633 debug_level++;
634 break;
635 case 'f':
636 apmd_configfile = optarg;
637 break;
638 case 'v':
639 verbose = 1;
640 break;
641 default:
642 err(1, "unknown option `%c'", ch);
646 if (daemonize)
647 daemon(0, 0);
649 #ifdef NICE_INCR
650 nice(NICE_INCR);
651 #endif
653 if (!daemonize)
654 logopt |= LOG_PERROR;
656 prog = strrchr(av[0], '/');
657 openlog(prog ? prog+1 : av[0], logopt, LOG_DAEMON);
659 syslog(LOG_NOTICE, "start");
661 if (pipe(signal_fd) < 0)
662 err(1, "pipe");
663 if (fcntl(signal_fd[0], F_SETFL, O_NONBLOCK) < 0)
664 err(1, "fcntl");
666 if ((apmnorm_fd = open(APM_NORM_DEVICEFILE, O_RDWR)) == -1) {
667 err(1, "cannot open device file `%s'", APM_NORM_DEVICEFILE);
670 if ((apmctl_fd = open(APM_CTL_DEVICEFILE, O_RDWR)) == -1) {
671 err(1, "cannot open device file `%s'", APM_CTL_DEVICEFILE);
674 restart();
675 pidfile(NULL);
676 event_loop();
677 exit(EXIT_SUCCESS);