diff options
Diffstat (limited to 'usr.sbin/apmd/apmd.c')
-rw-r--r-- | usr.sbin/apmd/apmd.c | 705 |
1 files changed, 705 insertions, 0 deletions
diff --git a/usr.sbin/apmd/apmd.c b/usr.sbin/apmd/apmd.c new file mode 100644 index 0000000..585383c --- /dev/null +++ b/usr.sbin/apmd/apmd.c @@ -0,0 +1,705 @@ +/*- + * APM (Advanced Power Management) Event Dispatcher + * + * Copyright (c) 1999 Mitsuru IWASAKI <iwasaki@FreeBSD.org> + * Copyright (c) 1999 KOIE Hidetaka <koie@suri.co.jp> + * 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 <assert.h> +#include <bitstring.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <paths.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <machine/apm_bios.h> + +#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); +} + |