diff options
Diffstat (limited to 'usr.sbin/apmd/apmd.c')
| -rw-r--r-- | usr.sbin/apmd/apmd.c | 702 | 
1 files changed, 702 insertions, 0 deletions
| diff --git a/usr.sbin/apmd/apmd.c b/usr.sbin/apmd/apmd.c new file mode 100644 index 000000000000..0a3fcf6315e0 --- /dev/null +++ b/usr.sbin/apmd/apmd.c @@ -0,0 +1,702 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + */ + +#include <sys/types.h> +#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/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); +} + | 
