summaryrefslogtreecommitdiffstats
path: root/usr.sbin/apmd/apmd.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/apmd/apmd.c')
-rw-r--r--usr.sbin/apmd/apmd.c705
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);
+}
+
OpenPOWER on IntegriCloud