/*- * APM (Advanced Power Management) Event Dispatcher * * Copyright (c) 1999 Mitsuru IWASAKI * Copyright (c) 1999 KOIE Hidetaka * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "apmd.h" int debug_level = 0; int verbose = 0; int soft_power_state_change = 0; const char *apmd_configfile = APMD_CONFIGFILE; const char *apmd_pidfile = APMD_PIDFILE; int apmctl_fd = -1, apmnorm_fd = -1; /* * table of event handlers */ #define EVENT_CONFIG_INITIALIZER(EV,R) { #EV, NULL, R }, struct event_config events[EVENT_MAX] = { EVENT_CONFIG_INITIALIZER(NOEVENT, 0) EVENT_CONFIG_INITIALIZER(STANDBYREQ, 1) EVENT_CONFIG_INITIALIZER(SUSPENDREQ, 1) EVENT_CONFIG_INITIALIZER(NORMRESUME, 0) EVENT_CONFIG_INITIALIZER(CRITRESUME, 0) EVENT_CONFIG_INITIALIZER(BATTERYLOW, 0) EVENT_CONFIG_INITIALIZER(POWERSTATECHANGE, 0) EVENT_CONFIG_INITIALIZER(UPDATETIME, 0) EVENT_CONFIG_INITIALIZER(CRITSUSPEND, 1) EVENT_CONFIG_INITIALIZER(USERSTANDBYREQ, 1) EVENT_CONFIG_INITIALIZER(USERSUSPENDREQ, 1) EVENT_CONFIG_INITIALIZER(STANDBYRESUME, 0) EVENT_CONFIG_INITIALIZER(CAPABILITIESCHANGE, 0) }; /* * List of battery events */ struct battery_watch_event *battery_watch_list = NULL; #define BATT_CHK_INTV 10 /* how many seconds between battery state checks? */ /* * default procedure */ struct event_cmd * event_cmd_default_clone(void *this) { struct event_cmd * oldone = this; struct event_cmd * newone = malloc(oldone->len); newone->next = NULL; newone->len = oldone->len; newone->name = oldone->name; newone->op = oldone->op; return newone; } /* * exec command */ int event_cmd_exec_act(void *this) { struct event_cmd_exec * p = this; int status = -1; pid_t pid; switch ((pid = fork())) { case -1: warn("cannot fork"); break; case 0: /* child process */ signal(SIGHUP, SIG_DFL); signal(SIGCHLD, SIG_DFL); signal(SIGTERM, SIG_DFL); execl(_PATH_BSHELL, "sh", "-c", p->line, (char *)NULL); _exit(127); default: /* parent process */ do { pid = waitpid(pid, &status, 0); } while (pid == -1 && errno == EINTR); break; } return status; } void event_cmd_exec_dump(void *this, FILE *fp) { fprintf(fp, " \"%s\"", ((struct event_cmd_exec *)this)->line); } struct event_cmd * event_cmd_exec_clone(void *this) { struct event_cmd_exec * newone = (struct event_cmd_exec *) event_cmd_default_clone(this); struct event_cmd_exec * oldone = this; newone->evcmd.next = NULL; newone->evcmd.len = oldone->evcmd.len; newone->evcmd.name = oldone->evcmd.name; newone->evcmd.op = oldone->evcmd.op; if ((newone->line = strdup(oldone->line)) == NULL) err(1, "out of memory"); return (struct event_cmd *) newone; } void event_cmd_exec_free(void *this) { free(((struct event_cmd_exec *)this)->line); } struct event_cmd_op event_cmd_exec_ops = { event_cmd_exec_act, event_cmd_exec_dump, event_cmd_exec_clone, event_cmd_exec_free }; /* * reject command */ int event_cmd_reject_act(void *this __unused) { int rc = 0; if (ioctl(apmctl_fd, APMIO_REJECTLASTREQ, NULL)) { syslog(LOG_NOTICE, "fail to reject\n"); rc = -1; } return rc; } struct event_cmd_op event_cmd_reject_ops = { event_cmd_reject_act, NULL, event_cmd_default_clone, NULL }; /* * manipulate event_config */ struct event_cmd * clone_event_cmd_list(struct event_cmd *p) { struct event_cmd dummy; struct event_cmd *q = &dummy; for ( ;p; p = p->next) { assert(p->op->clone); if ((q->next = p->op->clone(p)) == NULL) err(1, "out of memory"); q = q->next; } q->next = NULL; return dummy.next; } void free_event_cmd_list(struct event_cmd *p) { struct event_cmd * q; for ( ; p ; p = q) { q = p->next; if (p->op->free) p->op->free(p); free(p); } } int register_battery_handlers( int level, int direction, struct event_cmd *cmdlist) { /* * level is negative if it's in "minutes", non-negative if * percentage. * * direction =1 means we care about this level when charging, * direction =-1 means we care about it when discharging. */ if (level>100) /* percentage > 100 */ return -1; if (abs(direction) != 1) /* nonsense direction value */ return -1; if (cmdlist) { struct battery_watch_event *we; if ((we = malloc(sizeof(struct battery_watch_event))) == NULL) err(1, "out of memory"); we->next = battery_watch_list; /* starts at NULL */ battery_watch_list = we; we->level = abs(level); we->type = (level<0)?BATTERY_MINUTES:BATTERY_PERCENT; we->direction = (direction<0)?BATTERY_DISCHARGING: BATTERY_CHARGING; we->done = 0; we->cmdlist = clone_event_cmd_list(cmdlist); } return 0; } int register_apm_event_handlers( bitstr_t bit_decl(evlist, EVENT_MAX), struct event_cmd *cmdlist) { if (cmdlist) { bitstr_t bit_decl(tmp, EVENT_MAX); memcpy(&tmp, evlist, bitstr_size(EVENT_MAX)); for (;;) { int n; struct event_cmd *p; struct event_cmd *q; bit_ffs(tmp, EVENT_MAX, &n); if (n < 0) break; p = events[n].cmdlist; if ((q = clone_event_cmd_list(cmdlist)) == NULL) err(1, "out of memory"); if (p) { while (p->next != NULL) p = p->next; p->next = q; } else { events[n].cmdlist = q; } bit_clear(tmp, n); } } return 0; } /* * execute command */ int exec_run_cmd(struct event_cmd *p) { int status = 0; for (; p; p = p->next) { assert(p->op->act); if (verbose) syslog(LOG_INFO, "action: %s", p->name); status = p->op->act(p); if (status) { syslog(LOG_NOTICE, "command finished with %d\n", status); break; } } return status; } /* * execute command -- the event version */ int exec_event_cmd(struct event_config *ev) { int status = 0; status = exec_run_cmd(ev->cmdlist); if (status && ev->rejectable) { syslog(LOG_ERR, "canceled"); event_cmd_reject_act(NULL); } return status; } /* * read config file */ extern FILE * yyin; extern int yydebug; void read_config(void) { int i; if ((yyin = fopen(apmd_configfile, "r")) == NULL) { err(1, "cannot open config file"); } #ifdef DEBUG yydebug = debug_level; #endif if (yyparse() != 0) err(1, "cannot parse config file"); fclose(yyin); /* enable events */ for (i = 0; i < EVENT_MAX; i++) { if (events[i].cmdlist) { u_int event_type = i; if (write(apmctl_fd, &event_type, sizeof(u_int)) == -1) { err(1, "cannot enable event 0x%x", event_type); } } } } void dump_config(void) { int i; struct battery_watch_event *q; for (i = 0; i < EVENT_MAX; i++) { struct event_cmd * p; if ((p = events[i].cmdlist)) { fprintf(stderr, "apm_event %s {\n", events[i].name); for ( ; p ; p = p->next) { fprintf(stderr, "\t%s", p->name); if (p->op->dump) p->op->dump(p, stderr); fprintf(stderr, ";\n"); } fprintf(stderr, "}\n"); } } for (q = battery_watch_list ; q != NULL ; q = q -> next) { struct event_cmd * p; fprintf(stderr, "apm_battery %d%s %s {\n", q -> level, (q -> type == BATTERY_PERCENT)?"%":"m", (q -> direction == BATTERY_CHARGING)?"charging": "discharging"); for ( p = q -> cmdlist; p ; p = p->next) { fprintf(stderr, "\t%s", p->name); if (p->op->dump) p->op->dump(p, stderr); fprintf(stderr, ";\n"); } fprintf(stderr, "}\n"); } } void destroy_config(void) { int i; struct battery_watch_event *q; /* disable events */ for (i = 0; i < EVENT_MAX; i++) { if (events[i].cmdlist) { u_int event_type = i; if (write(apmctl_fd, &event_type, sizeof(u_int)) == -1) { err(1, "cannot disable event 0x%x", event_type); } } } for (i = 0; i < EVENT_MAX; i++) { struct event_cmd * p; if ((p = events[i].cmdlist)) free_event_cmd_list(p); events[i].cmdlist = NULL; } for( ; battery_watch_list; battery_watch_list = battery_watch_list -> next) { free_event_cmd_list(battery_watch_list->cmdlist); q = battery_watch_list->next; free(battery_watch_list); battery_watch_list = q; } } void restart(void) { destroy_config(); read_config(); if (verbose) dump_config(); } /* * write pid file */ static void write_pid(void) { FILE *fp = fopen(apmd_pidfile, "w"); if (fp) { fprintf(fp, "%ld\n", (long)getpid()); fclose(fp); } } /* * handle signals */ static int signal_fd[2]; void enque_signal(int sig) { if (write(signal_fd[1], &sig, sizeof sig) != sizeof sig) err(1, "cannot process signal."); } void wait_child(void) { int status; while (waitpid(-1, &status, WNOHANG) > 0) ; } int proc_signal(int fd) { int rc = 0; int sig; while (read(fd, &sig, sizeof sig) == sizeof sig) { syslog(LOG_INFO, "caught signal: %d", sig); switch (sig) { case SIGHUP: syslog(LOG_NOTICE, "restart by SIG"); restart(); break; case SIGTERM: syslog(LOG_NOTICE, "going down on signal %d", sig); rc = -1; return rc; case SIGCHLD: wait_child(); break; default: warn("unexpected signal(%d) received.", sig); break; } } return rc; } void proc_apmevent(int fd) { struct apm_event_info apmevent; while (ioctl(fd, APMIO_NEXTEVENT, &apmevent) == 0) { int status; syslog(LOG_NOTICE, "apmevent %04x index %d\n", apmevent.type, apmevent.index); syslog(LOG_INFO, "apm event: %s", events[apmevent.type].name); if (fork() == 0) { status = exec_event_cmd(&events[apmevent.type]); exit(status); } } } #define AC_POWER_STATE ((pw_info.ai_acline == 1) ? BATTERY_CHARGING :\ BATTERY_DISCHARGING) void check_battery(void) { static int first_time=1, last_state; int status; struct apm_info pw_info; struct battery_watch_event *p; /* If we don't care, don't bother */ if (battery_watch_list == NULL) return; if (first_time) { if ( ioctl(apmnorm_fd, APMIO_GETINFO, &pw_info) < 0) err(1, "cannot check battery state."); /* * This next statement isn't entirely true. The spec does not tie AC * line state to battery charging or not, but this is a bit lazier to do. */ last_state = AC_POWER_STATE; first_time = 0; return; /* We can't process events, we have no baseline */ } /* * XXX - should we do this a bunch of times and perform some sort * of smoothing or correction? */ if ( ioctl(apmnorm_fd, APMIO_GETINFO, &pw_info) < 0) err(1, "cannot check battery state."); /* * If we're not in the state now that we were in last time, * then it's a transition, which means we must clean out * the event-caught state. */ if (last_state != AC_POWER_STATE) { if (soft_power_state_change && fork() == 0) { status = exec_event_cmd(&events[PMEV_POWERSTATECHANGE]); exit(status); } last_state = AC_POWER_STATE; for (p = battery_watch_list ; p!=NULL ; p = p -> next) p->done = 0; } for (p = battery_watch_list ; p != NULL ; p = p -> next) if (p -> direction == AC_POWER_STATE && !(p -> done) && ((p -> type == BATTERY_PERCENT && p -> level == (int)pw_info.ai_batt_life) || (p -> type == BATTERY_MINUTES && p -> level == (pw_info.ai_batt_time / 60)))) { p -> done++; if (verbose) syslog(LOG_NOTICE, "Caught battery event: %s, %d%s", (p -> direction == BATTERY_CHARGING)?"charging":"discharging", p -> level, (p -> type == BATTERY_PERCENT)?"%":" minutes"); if (fork() == 0) { status = exec_run_cmd(p -> cmdlist); exit(status); } } } void event_loop(void) { int fdmax = 0; struct sigaction nsa; fd_set master_rfds; sigset_t sigmask, osigmask; FD_ZERO(&master_rfds); FD_SET(apmctl_fd, &master_rfds); fdmax = apmctl_fd > fdmax ? apmctl_fd : fdmax; FD_SET(signal_fd[0], &master_rfds); fdmax = signal_fd[0] > fdmax ? signal_fd[0] : fdmax; memset(&nsa, 0, sizeof nsa); nsa.sa_handler = enque_signal; sigfillset(&nsa.sa_mask); nsa.sa_flags = SA_RESTART; sigaction(SIGHUP, &nsa, NULL); sigaction(SIGCHLD, &nsa, NULL); sigaction(SIGTERM, &nsa, NULL); sigemptyset(&sigmask); sigaddset(&sigmask, SIGHUP); sigaddset(&sigmask, SIGCHLD); sigaddset(&sigmask, SIGTERM); sigprocmask(SIG_SETMASK, &sigmask, &osigmask); while (1) { fd_set rfds; int res; struct timeval to; to.tv_sec = BATT_CHK_INTV; to.tv_usec = 0; memcpy(&rfds, &master_rfds, sizeof rfds); sigprocmask(SIG_SETMASK, &osigmask, NULL); if ((res=select(fdmax + 1, &rfds, 0, 0, &to)) < 0) { if (errno != EINTR) err(1, "select"); } sigprocmask(SIG_SETMASK, &sigmask, NULL); if (res == 0) { /* time to check the battery */ check_battery(); continue; } if (FD_ISSET(signal_fd[0], &rfds)) { if (proc_signal(signal_fd[0]) < 0) return; } if (FD_ISSET(apmctl_fd, &rfds)) proc_apmevent(apmctl_fd); } } int main(int ac, char* av[]) { int ch; int daemonize = 1; char *prog; int logopt = LOG_NDELAY | LOG_PID; while ((ch = getopt(ac, av, "df:sv")) != -1) { switch (ch) { case 'd': daemonize = 0; debug_level++; break; case 'f': apmd_configfile = optarg; break; case 's': soft_power_state_change = 1; break; case 'v': verbose = 1; break; default: err(1, "unknown option `%c'", ch); } } if (daemonize) daemon(0, 0); #ifdef NICE_INCR nice(NICE_INCR); #endif if (!daemonize) logopt |= LOG_PERROR; prog = strrchr(av[0], '/'); openlog(prog ? prog+1 : av[0], logopt, LOG_DAEMON); syslog(LOG_NOTICE, "start"); if (pipe(signal_fd) < 0) err(1, "pipe"); if (fcntl(signal_fd[0], F_SETFL, O_NONBLOCK) < 0) err(1, "fcntl"); if ((apmnorm_fd = open(APM_NORM_DEVICEFILE, O_RDWR)) == -1) { err(1, "cannot open device file `%s'", APM_NORM_DEVICEFILE); } if (fcntl(apmnorm_fd, F_SETFD, 1) == -1) { err(1, "cannot set close-on-exec flag for device file '%s'", APM_NORM_DEVICEFILE); } if ((apmctl_fd = open(APM_CTL_DEVICEFILE, O_RDWR)) == -1) { err(1, "cannot open device file `%s'", APM_CTL_DEVICEFILE); } if (fcntl(apmctl_fd, F_SETFD, 1) == -1) { err(1, "cannot set close-on-exec flag for device file '%s'", APM_CTL_DEVICEFILE); } restart(); write_pid(); event_loop(); exit(EXIT_SUCCESS); }