/* apmd.c -- APM event-monitoring daemon
 * Copyright 1996, 1997 Rickard E. Faith (faith@acm.org)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2, or (at your option) any
 * later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <time.h>
#include <syslog.h>
#include <signal.h>
#include <paths.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include "apm.h"
#include <errno.h>
#include <string.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>

#ifndef DEFAULT_PROXY_NAME
#define DEFAULT_PROXY_NAME      /etc/apmd_proxy
#endif

#ifndef DEFAULT_PROXY_TIMEOUT
#define DEFAULT_PROXY_TIMEOUT   30
#endif

#ifndef DEFAULT_CHECK_INTERVAL
#define DEFAULT_CHECK_INTERVAL  30
#endif

#ifndef DEFAULT_VERBOSITY
#define DEFAULT_VERBOSITY       LOG_NOTICE
#endif

/* Rate calcs over shorter times than this are bogus */
#ifndef MINIMUM_RATE_CALC_TIME
#define MINIMUM_RATE_CALC_TIME  120
#endif

/*
 * For the verbosity level feature to be useful,
 * we rely on the fact that syslog.h assigns adjacent
 * integer values to the following log level macros:
 *    LOG_NOTICE
 *    LOG_INFO
 *    LOG_DEBUG
 * and we also assume that
 *    LOG_ALERT
 *    LOG_ERR
 *    LOG_WARNING
 * are *less* than any of the above.
 */
#if ((defined (__STDC_VERSION__)) && (__STDC_VERSION__ >= 199901L))
#define APMD_SYSLOG(lev, ...) do					\
{									\
	if ((lev) <= verbosity) syslog((lev), __VA_ARGS__);		\
} while(0)
#else
#ifdef __GNUC__
#define APMD_SYSLOG(lev, args...) do					\
{									\
	if ((lev) <= verbosity) syslog((lev), args);			\
} while(0)
#endif /* __GNUC__ */
#endif /* __STDC_VERSION__ >= 199901L */

#define APMD_TRACE0(lev, fmt)						\
  APMD_SYSLOG ((lev), "%s(): " fmt, __FUNCTION__)

#define APMD_TRACE1(lev, fmt, arg)					\
  APMD_SYSLOG ((lev), "%s(): " fmt, __FUNCTION__, arg)

#define PID_FILE _PATH_VARRUN "apmd.pid"

#define MAX_EVENTS    8    /* Maximum events we accept from BIOS driver */
#define RESUME_HOURS  6    /* If resuming after N hours, show as days */

/*
 * These are events generated by APMD itself.  The benefit is
 * that we can do a simple lookup into the event dispatch
 * table.  (We're trusting that the APM BIOS spec doesn't get
 * updated to use these particular "reserved" codes for
 * anything...)
 */
#define APMD_START            0xad00
#define APMD_STOP             0xad01
#define APMD_PSEUDO_EVENT     0xad03

/*
 * This table defines how events are handled.  For each event type
 * there is a flag indicating whether the proxy should be run,
 * and a flag indicating whether the event should be logged.
 */

struct action
{
	apm_event_t event;
	int call_proxy;
	int log;
};

/*
 * These actions are listed here in the order
 * in which they will be processed when several
 * of them are received at the same time
 *
 * N.B. CRITICAL_SUSPEND and UPDATE_TIME may not be sent
 * to us by the APM subsystem, for performance reasons.
 */
static struct action actions[] = {
	{APMD_START, 1, 1},           /* Generated by apmd */
	/* APM_CRITICAL_SUSPEND omitted */
	{APM_CRITICAL_RESUME, 1, 1},
	{APM_STANDBY_RESUME, 1, 1},
	{APM_NORMAL_RESUME, 1, 1},
#ifdef APM_CAPABILITY_CHANGE
	{APM_CAPABILITY_CHANGE, 1, 1},
#endif
	{APM_POWER_STATUS_CHANGE, 1, 1},
	{APM_LOW_BATTERY, 1, 1},
	{APM_SYS_STANDBY, 1, 1},
	{APM_USER_STANDBY, 1, 1},
	{APM_UPDATE_TIME, 1, 1},
#if doing_something_on_pseudo_events
	{APMD_PSEUDO_EVENT, 0, 0},   /* Generated by apmd */
#endif
	{APM_SYS_SUSPEND, 1, 1},
	{APM_USER_SUSPEND, 1, 1},
	{APMD_STOP, 1, 1}            /* Generated by apmd */
};
#define NUM_ACTIONS  (sizeof(actions)/sizeof(struct action))

/* From parsing the command line: */
static char *proxy_name = DEFAULT_PROXY_NAME;
static int proxy_timeout = -1; /* -1=never */
static int wall; /* = 0 */
static int check_interval = -1; /* -1=infinity */
static int ignore_bios_batlow; /* = 0 */
static int percent_change = 5;
static int quiet_bios_batlow; /* = 0 */
static int verbosity = DEFAULT_VERBOSITY;
static int warn_level = 10;

static uid_t apmd_uid = 0;
static int apmd_fd = -1;

#ifndef abs
#define abs(a) ((a)<0?(-(a)):(a))
#endif

#define IS_CHARGING(i)   (                                              \
	((i).battery_status == BATTERY_STATUS_CHARGING)                 \
	|| ((i).battery_flags & BATTERY_FLAGS_CHARGING)                 \
)

#define IS_LOW(i)    (                                                  \
	((i).battery_status == BATTERY_STATUS_LOW)                      \
	|| ((i).battery_flags & BATTERY_FLAGS_LOW)                      \
)

#define IS_ABSENT(i)   (                                                \
	((i).battery_status == BATTERY_STATUS_ABSENT)                   \
	|| ((i).battery_flags & BATTERY_FLAGS_ABSENT)                   \
)

#define ISNT_ON_LINE(i)	 ((i).ac_line_status == AC_LINE_STATUS_OFF)

static void option_barf(FILE *fd)
{
	fprintf(
		fd,
		"apmd: Try `apmd --help' for usage information.\n"
	);
}

static void usage(FILE *fd)
{
	fprintf(
		fd,
		"Usage: apmd [-TVWciqv] [-P cmd] [-T seconds] [-c seconds]"
		" [-p percent] [-v level] [-w percent] [--help]\n"

	);
}


/*
 * call_proxy() is a generic outcalling dispatcher.  It calls a
 * proxy program named by proxy_name which can do further event
 * processing.  Returns the code returned by the proxy program,
 * which is meant to signal rejection of a suspend request
 * (except that such rejection isn't supported by the kernel yet).
 */
static int call_proxy(apm_event_t event)
{
	const char *argv[4] = {NULL, NULL, NULL, NULL};
	int i, fds[2];
	pid_t pid;
	char line[256];

	APMD_TRACE1(LOG_DEBUG, "0x%04x", event);

	if (!strlen(proxy_name)) {
		/* Proxy name set to null string */
		return 0;
	}

	for (i=0; i<NUM_ACTIONS; i++) {
		if (actions[i].event == event)
			break;
	}

	if (i == NUM_ACTIONS) {
		APMD_TRACE0(LOG_ERR, "Unrecognized event!!!");
		return 0;
	}

	if (!actions[i].call_proxy) {
		/* We don't call the proxy for this kind of event */
		return 0;
	}

	if (access(proxy_name, X_OK)) {
		/* Not executable.  This is not considered an error. */
		APMD_SYSLOG(LOG_INFO, "No executable proxy: %s", proxy_name );
		return 0;
	}

	argv[0] = proxy_name;

	/* Add the arguments depending on event type */
	switch (event)
	{
	/* APM_CRITICAL_SUSPEND omitted */
	case APM_SYS_SUSPEND:
		argv[1] = "suspend";
		argv[2] = "system";
		break;
	case APM_USER_SUSPEND:
		argv[1] = "suspend";
		argv[2] = "user";
		break;
	case APM_SYS_STANDBY:
		argv[1] = "standby";
		argv[2] = "system";
		break;
	case APM_USER_STANDBY:
		argv[1] = "standby";
		argv[2] = "user";
		break;
	case APM_CRITICAL_RESUME:
		argv[1] = "resume";
		argv[2] = "critical";
		break;
	case APM_NORMAL_RESUME:
		argv[1] = "resume";
		argv[2] = "suspend";
		break;
	case APM_STANDBY_RESUME:
		argv[1] = "resume";
		argv[2] = "standby";
		break;
	case APM_LOW_BATTERY:
		argv[1] = "change";
		argv[2] = "battery";
		break;
	case APM_POWER_STATUS_CHANGE:
		argv[1] = "change";
		argv[2] = "power";
		break;
	case APM_UPDATE_TIME:
		argv[1] = "change";
		argv[2] = "time";
		break;
	case APMD_START:
		argv[1] = "start";
		break;
	case APMD_STOP:
		argv[1] = "stop";
		break;
#ifdef APM_CAPABILITY_CHANGE
	case APM_CAPABILITY_CHANGE:
		argv[1] = "change";
		argv[2] = "capability";
		break;
#endif
	default:
		// should never happen
		APMD_TRACE0(LOG_ERR, "Unrecognized event!!!");
		return 0;
	} /* switch */

	if (pipe(fds)) {
		APMD_TRACE1(LOG_ERR, "Can't open fds for proxy: %s", strerror(errno));
		return 0;
	}

	if (argv[2] == NULL) {
		APMD_SYSLOG(LOG_INFO, "Executing proxy: '%s' '%s'", argv[0], argv[1]);
	} else {
		APMD_SYSLOG(LOG_INFO, "Executing proxy: '%s' '%s' '%s'", argv[0], argv[1], argv[2]);
	}

	pid = fork();
	if (pid == 0) {
		/* child */
		close(fds[0]);

		/* Don't inherit stdin; use /dev/null instead */
		close(0);
		open("/dev/null", O_RDONLY);

		/* Send stdout and stderr to the pipe */
		dup2(fds[1], 1);
		dup2(fds[1], 2);
		
		/* Don't close stdin|out|err on exec */
		fcntl(0, F_SETFD, 0);
		fcntl(1, F_SETFD, 0);
		fcntl(2, F_SETFD, 0);
		
		execvp(argv[0], (char **)argv);
		_exit(142); /* We only do this if the execvp fails */
	} else {
		/* parent */
		int status, retval;
		ssize_t len;
		time_t time_limit;

		if (pid < 0) {
			/* Couldn't fork */
			APMD_TRACE1(0, "Couldn't fork: %s", strerror(errno));
			return 0;
		}

		/* Forked */

		/* Capture the child's output, if any, but only until it terminates */
		close(fds[1]);
		fcntl(fds[0], F_SETFL, O_RDONLY|O_NONBLOCK);
		time_limit = time(0) + proxy_timeout;
		do {
			while ((len = read(fds[0], line, sizeof(line)-1)) > 0) {
				line[len] = 0;
				APMD_SYSLOG(LOG_INFO, "+ %s", line);
			}
				
			retval = waitpid(pid, &status, WNOHANG);
			if (retval == pid)
				goto proxy_done;
			if (retval < 0) {
				APMD_TRACE1(LOG_ERR, "waitpid() failed: %s", strerror(errno));
				status = __W_EXITCODE(0, SIGTERM) | __WCOREFLAG;
				goto proxy_done;
			}
				
			sleep(1);
		} while (
			(time(0) < time_limit)
			|| (proxy_timeout < 0)
		);

		APMD_SYSLOG(LOG_NOTICE, "Proxy has been running more than %d seconds; killing it", proxy_timeout);

		kill(pid, SIGTERM);
		time_limit = time(0) + 5;
		do {
			retval = waitpid(pid, &status, WNOHANG);
			if (retval == pid)
				goto proxy_done;
			if (retval < 0) {
				APMD_TRACE1(LOG_ERR, "waitpid() failed: %s", strerror(errno));
				status = __W_EXITCODE(0, SIGTERM) | __WCOREFLAG;
				goto proxy_done;
			}

			sleep(1);

		} while (time(0) < time_limit);

		kill(pid, SIGKILL);
		status = __W_EXITCODE(0, SIGKILL);

proxy_done:
		/* Flush any remaining data */
		while ((len = read(fds[0], line, sizeof(line)-1)) > 0) {
			line[len] = 0;
			APMD_SYSLOG(LOG_INFO, "+ %s", line);
		}
		close(fds[0]);
				
		/* Collect the exit code */
		if (WIFEXITED(status)) {
			APMD_SYSLOG(
				WEXITSTATUS(status) ? LOG_NOTICE : LOG_DEBUG,
				"Proxy exited with status %d",
				WEXITSTATUS(status)
			);
			return WEXITSTATUS(status);
		} else {
			APMD_SYSLOG(LOG_NOTICE, "Proxy exited on signal %d", WTERMSIG(status));
			return 0;
		}
	}
}

static inline int apmd_time(apm_info * apmi)
{
	return (apmi->battery_time * (apmi->using_minutes ? 60 : 1));
}

static char *ac_line_status_descr (int status)
{
	if (status == AC_LINE_STATUS_OFF)
		return "battery";
	else if (status == AC_LINE_STATUS_ON)
		return "line";
	else if (status == AC_LINE_STATUS_BACKUP)
		return "backup";
	else
		return "?";
}

static const char *battery_status_descr (apm_info * apmi)
{
	return
		IS_ABSENT(*apmi) ?
			"absent" :
			IS_CHARGING(*apmi) ?
				"charging" :
				ISNT_ON_LINE(*apmi) ?
					"discharging" : "not charging";
}

/*
 * Generic logging function.  Depending on the event type
 * and actions table, the event may or may not be logged.
 */
static void apmd_log_event(apm_event_t event, char *msg)
{
	int i;

	for (i=0; i<NUM_ACTIONS; i++) {
		if (actions[i].event == event)
			break;
	}

	if (i == NUM_ACTIONS)
		return;

	if (actions[i].log) {
		APMD_SYSLOG(LOG_INFO, "%s", msg);
	}

	return;
}

static int apmd_suspend(void)
{
	int result;

	APMD_SYSLOG(LOG_NOTICE, "Suspending now");

	sync();
	sleep(0);  /* let syslogd write messages */
	sync();

	result = apm_suspend(apmd_fd);

	if (result == -EBUSY) {
		APMD_SYSLOG(LOG_NOTICE, "Suspension was rejected by the kernel, so resuming");
		call_proxy(APM_NORMAL_RESUME);
	}

	return result;
}


static int apmd_standby(void)
{

	APMD_SYSLOG(LOG_NOTICE, "Standing by now");

	sync();
	sleep(0);  /* let syslogd write messages */
	sync();

	return apm_standby(apmd_fd);
}

/*
 * Take account of the event; call proxy; warn; log; etc.
 */
static void handle_event(apm_event_t event, apm_info * apmi)
{
	int do_log_batstatus = 0;
	int do_notify_batlow = 0, do_warn_batlow = 0, do_proxy_batlow = 0;
	int do_mark = 0, do_suspend = 0, do_standby = 0;
	int len = 0;
	char msg[512];

	static time_t last_marked_time;
	static int last_marked_content;
	static int last_logged_content;
	static int last_ac_line_status;
	static int last_battery_charging;
	static int last_battery_absent;
	static int suppress_bios_batlow;
	static int suppress_apmd_batlow;

	APMD_TRACE1(LOG_DEBUG, "0x%04x", event);

	/* Call proxy and perform special logging as needed */
	switch (event)
	{
	case APM_CRITICAL_SUSPEND:
		/* Do it fast */
		last_marked_time = time(0);
		last_marked_content = apmi->battery_percentage;
		ioctl(apmd_fd, APM_IOC_SUSPEND, NULL);
		break;
	case APMD_START:
		/* Initialise */
		call_proxy(event);
		last_marked_time = time(0);
		last_marked_content = apmi->battery_percentage;
		last_logged_content = -1;
		last_ac_line_status = apmi->ac_line_status;
		last_battery_charging = IS_CHARGING(*apmi);
		last_battery_absent = IS_ABSENT(*apmi);
		suppress_bios_batlow = 0;
		suppress_apmd_batlow = 0;
		return; /* Yes, return */
	case APM_SYS_SUSPEND:
	case APM_USER_SUSPEND:
		switch (event)
		{
		case APM_SYS_SUSPEND: apmd_log_event(event, "System Suspend"); break;
		case APM_USER_SUSPEND: apmd_log_event(event, "User Suspend"); break;
		}
		do_suspend = 1;
		if (call_proxy(event)) {
			APMD_SYSLOG(LOG_NOTICE, "Suspend rejected by proxy");
			if (apm_reject(apmd_fd) == -EINVAL) {
				APMD_SYSLOG(LOG_INFO, "Rejection is not supported by the apm subsystem. Proceeding with suspend.");
			} else {
				/* Rejection succeeded */
				do_suspend = 0;
			}
		}
		do_log_batstatus = 1;
		do_mark = 1;
		break;
	case APM_SYS_STANDBY:
	case APM_USER_STANDBY:
		switch (event)
		{
		case APM_SYS_STANDBY: apmd_log_event(event, "System Standby"); break;
		case APM_USER_STANDBY: apmd_log_event(event, "User Standby"); break;
		}
		do_standby = 1;
		if (call_proxy(event)) {
			APMD_SYSLOG(LOG_NOTICE, "Standby rejected by proxy");
			if (apm_reject(apmd_fd) == -EINVAL) {
				APMD_SYSLOG(LOG_INFO, "Rejection is not supported by the apm subsystem. Proceeding with standby.");
			} else {
				/* Rejection succeeded */
				do_standby = 0;
			}
		}
		do_log_batstatus = 1;
		do_mark = 1;
		break;
	case APM_CRITICAL_RESUME:
	case APM_NORMAL_RESUME:
	case APM_STANDBY_RESUME:
		switch(event)
		{
		case APM_CRITICAL_RESUME: apmd_log_event(event, "Critical Resume"); break;
		case APM_NORMAL_RESUME: apmd_log_event(event, "Normal Resume"); break;
		case APM_STANDBY_RESUME: apmd_log_event(event, "Standby Resume"); break;
		}
		call_proxy(event);
		do_log_batstatus = 1;
		do_mark = 1;
		suppress_bios_batlow = 0;
		suppress_apmd_batlow = 0;
		break;
	case APM_UPDATE_TIME:
		apmd_log_event(event, "Update Time");
		call_proxy(event);
		do_mark = 1;
		break;
	case APM_POWER_STATUS_CHANGE:
		/*
		 * With some APM BIOSes, power status changes can happen a LOT,
		 * e.g., for each change of estimated battery life (minutes,
		 * seconds, percent) when charging or discharging, battery full,
		 * empty, add/remove battery, etc.  Invoking the proxy on every
		 * such event could be wasteful.  Therefore, we just check
		 * (below) for power source changes.
		 */
		/* apmd_log_event(event, "Power Status Change"); */
		sleep(1); /* Wait for info to be updated after the change */
		if(apm_read(apmi))
			return;
		break;
	case APM_LOW_BATTERY:
		if (!ignore_bios_batlow && !suppress_bios_batlow && !suppress_apmd_batlow) {
			if (!quiet_bios_batlow)
				do_notify_batlow = 1;
			do_proxy_batlow = 1;
		}
		suppress_bios_batlow = 1;
		break;
#ifdef APM_CAPABILITY_CHANGE
	case APM_CAPABILITY_CHANGE:
		apmd_log_event(event, "Capability Change");
		call_proxy(event);
		break;
#endif
	case APMD_PSEUDO_EVENT:
#if doing_something_on_pseudo_events
		apmd_log_event(event, "Performing APM status check");
		call_proxy(event);
#endif
		break;
	default:
		/* These aren't errors; see the APM BIOS 1.2 spec.
		 * 0x000d-0x00ff	reserved system events
		 * 0x0100-0x01ff	reserved device events
		 * 0x0200-0x02ff	OEM-defined
		 * 0x0300-0xffff	reserved
		 */
		APMD_SYSLOG(LOG_ERR, "Received unknown event 0x%04x!!", event);
		break;
	} /* switch */

	/*
	 * Check for power source or charging changes.
	 *
	 * The main use for this is to give the proxy program the
	 * opportunity to reduce power consumption when running
	 * on batteries or charging them.
	 */
	if (
		apmi->ac_line_status != last_ac_line_status
		|| IS_CHARGING(*apmi) != last_battery_charging
	) {
		sprintf(
			msg,
			"Using %s power%s%s%s", 
			ac_line_status_descr(apmi->ac_line_status),
			ISNT_ON_LINE(*apmi) ?  "" : "; ",
			ISNT_ON_LINE(*apmi) ?  "" : battery_status_descr(apmi),
			ISNT_ON_LINE(*apmi) ?  "" : " battery"
		);
		apmd_log_event(APM_POWER_STATUS_CHANGE, msg);
		call_proxy(APM_POWER_STATUS_CHANGE);
		do_log_batstatus = 1;
		do_mark = 1;
		suppress_bios_batlow = 0;
		suppress_apmd_batlow = 0;
		last_ac_line_status = apmi->ac_line_status;
		last_battery_charging = IS_CHARGING(*apmi);
	}

	/*
	 * Check for battery insertion or removal
	 */
	if (
		IS_ABSENT(*apmi) != last_battery_absent
	) {
		do_log_batstatus = 1;
		last_battery_absent = IS_ABSENT(*apmi);
	}

	/*
	 * Some BIOSes fail to generate LOW BATTERY events,
	 * so we generate our own.
	 */
	if (!ignore_bios_batlow) {
		if (
			IS_LOW(*apmi)
			&& !IS_CHARGING(*apmi)
			&& ISNT_ON_LINE(*apmi)
		) {
			if (!suppress_bios_batlow && !suppress_apmd_batlow) {
				if (!quiet_bios_batlow)
					do_notify_batlow = 1;
				do_proxy_batlow = 1;
			}
			suppress_bios_batlow = 1;
		} else {
			suppress_bios_batlow = 0;
		}
	}

	if (
		warn_level >= 0
		&& apmi->battery_percentage >= 0
		&& apmi->battery_percentage <= 100
	) {
		if (
			apmi->battery_percentage <= warn_level
			&& !IS_CHARGING(*apmi)
			&& ISNT_ON_LINE(*apmi)
		) {
			if (!suppress_bios_batlow && !suppress_apmd_batlow) {
				do_notify_batlow = 1;
				do_proxy_batlow = 1;
			}
			suppress_apmd_batlow = 1;
		} else {
			suppress_apmd_batlow = 0;
		}
	}

	if ( do_notify_batlow ) {
		FILE *str;
		syslog(LOG_ALERT, "Warning: %s", "BATTERY IS LOW");
		/* Details will be given below if available */
		if (wall) {
			str = popen("wall", "w");
			fprintf(str, "Warning: %s\n", "BATTERY IS LOW");
			pclose(str);
		}
	}

	if ( do_proxy_batlow ) {
		call_proxy(APM_LOW_BATTERY);
	}

	/*
	 * If battery is now full or empty, reset charge rate calcs
	 */
	if (
		(apmi->battery_percentage == 0 && last_marked_content != 0)
		|| (apmi->battery_percentage == 100 && last_marked_content != 100)
	)
		do_mark = 1;

	do_warn_batlow = do_notify_batlow;

	if (
		(apmi->battery_percentage >= 0 && apmi->battery_percentage <= 100)
		&& apmi->battery_percentage != last_logged_content
		&& apmi->battery_percentage <= warn_level
		&& !IS_CHARGING(*apmi)
		&& ISNT_ON_LINE(*apmi)
	)
		do_warn_batlow = 1;

	if (
		(apmi->battery_percentage >= 0 && apmi->battery_percentage <= 100)
		&& apmi->battery_percentage != last_logged_content
		&& (
			apmi->battery_percentage == 0
			|| apmi->battery_percentage == 100
		)
	)
		do_log_batstatus = 1;

	if (
		(apmi->battery_percentage >= 0 && apmi->battery_percentage <= 100)
		&& apmi->battery_percentage != last_logged_content
		&& abs(apmi->battery_percentage - last_logged_content) >= percent_change
	)
		do_log_batstatus = 1;

	if ( IS_ABSENT(*apmi) ) {
		/* Battery absent */
		len = sprintf(
			msg,
			"Battery: %s",
			battery_status_descr(apmi)
		);
	} else if (apmi->battery_percentage < 0 || apmi->battery_percentage > 100) {
		/* Battery content value invalid */
		len = sprintf(
			msg,
			"Battery: ?%%, %s",
			battery_status_descr(apmi)
		);
	} else {
		/* Current battery content percentage value OK */
		int dt = time(0) - last_marked_time;
		int dp = apmi->battery_percentage - last_marked_content;
		int do_estimate_rate =
			last_marked_time > 0
			&& dt > 0
			&& last_marked_content >= 0
			&& last_marked_content <= 100;

		len = sprintf(
			msg,
			"Battery: %d%%, %s",
			apmi->battery_percentage,
			battery_status_descr(apmi)
		);

		if (
			event == APM_CRITICAL_RESUME
			|| event == APM_NORMAL_RESUME
			|| event == APM_STANDBY_RESUME
		) {
			/* We are resuming */
			if ( do_estimate_rate ) {
				if (
					dt > (60 * 60 * RESUME_HOURS)
					&& !IS_CHARGING(*apmi)
					&& dp < 0
				) {
					/*
					 * Suspend lasted a long time, so print the
					 * discharge rate in percentage points per day
					 */
					len += sprintf(
						msg + len,
						" (%+.2f%%/day over %s)",
						60 * 60 * 24 * (double)dp / (double)dt,
						apm_time(dt)
					);
				} else {
					/*
					 * Suspend didn't last a long time, so just print
					 * the change in content
					 */
					len += sprintf(
						msg + len,
						" (%+d%% over %s)",
						dp,
						apm_time(dt)
					);
				}
			}
		} else {
			/* We are not resuming */
			double ppm = dt ? (60. * (double)dp / (double)dt) : -1;

			if (
				do_estimate_rate
				&& dt >= MINIMUM_RATE_CALC_TIME
				&& (
					(dp > 0 && IS_CHARGING(*apmi))
					|| (dp < 0 && !IS_CHARGING(*apmi))
				)
				&& ppm <= 10.   /* Battery charging faster than 10%/min? Bogus! */
			) {
				len += sprintf(
					msg + len,
					" (%+.2f%%/min over %s)",
					ppm,
					apm_time(dt)
				);
			}
	
			len += sprintf(
				msg + len,
				", %s",
				apm_time(apmd_time(apmi))
			);

			if (
				do_estimate_rate
				&& dt >= MINIMUM_RATE_CALC_TIME
				&& dp > 0
				&& IS_CHARGING(*apmi)
			) {
				len += sprintf(
					msg + len,
					" to empty (%s to full)",
					apm_time((int)( (100 - apmi->battery_percentage) * (double)dt/dp ))
				);
			} else if (
				do_estimate_rate
				&& dt >= MINIMUM_RATE_CALC_TIME
				&& dp < 0
				&& !IS_CHARGING(*apmi)
			) {
				len += sprintf(
					msg + len,
					" (%s) to empty",
					apm_time((int)( apmi->battery_percentage * (double)dt/-dp ))
				);
			} else {
				/* Rate is not valid */
				len += sprintf(
					msg + len,
					" to empty"
				);
			}
		}
	}

	if (do_warn_batlow) {
		syslog(LOG_ALERT, "Warning: %s", msg);
		last_logged_content = apmi->battery_percentage;
	} else if (do_log_batstatus) {
		APMD_SYSLOG(LOG_INFO, "%s", msg);
		last_logged_content = apmi->battery_percentage;
	}

	if (do_mark) {
		last_marked_time = time(0);
		last_marked_content = apmi->battery_percentage;
	}

	if (do_suspend)
		apmd_suspend();

	if (do_standby)
		apmd_standby();

	return;
}

static void sig_handler(int sig)
{
	APMD_SYSLOG(LOG_INFO, "Exiting");
	call_proxy(APMD_STOP);
	unlink(PID_FILE);
	exit(0);
}

int main(int argc, char **argv)
{
	int c;
	int pid;
	FILE *str;
	apm_info apminfo;
	apm_event_t events[MAX_EVENTS];

	static struct option longopts[] =
	{
		{"proxy", required_argument, NULL, 'P'},
		{"apmd-proxy", required_argument, NULL, 'P'},
		{"apmd_proxy", required_argument, NULL, 'P'},
		{"proxy-timeout", optional_argument, NULL, 'T'},
		{"proxy_timeout", optional_argument, NULL, 'T'},
		{"apmd-proxy-timeout", optional_argument, NULL, 'T'},
		{"apmd_proxy_timeout", optional_argument, NULL, 'T'},
		{"version", no_argument, NULL, 'V'},
		{"wall", no_argument, NULL, 'W'},
		{"check", optional_argument, NULL, 'c'},
		{"ignore-bios-battery-low", no_argument, NULL, 'i'},
		{"ignore_bios_battery_low", no_argument, NULL, 'i'},
		{"percentage", required_argument, NULL, 'p'},
		{"quiet-bios-battery-low", no_argument, NULL, 'q'},
		{"quiet_bios_battery_low", no_argument, NULL, 'q'},
		{"verbose", optional_argument, NULL, 'v'},
		{"warn", required_argument, NULL, 'w'},
		{"help", no_argument, NULL, 'h'},
		{0, 0, 0, 0}
	};

	switch (apm_exists())
	{
	case 1:
		fprintf(stderr, "No APM support in kernel\n");
		exit(1);
	case 2:
		fprintf(stderr, "Old APM support in kernel\n");
		exit(2);
	} /* switch */

	opterr = 1; /* Switch on getopt() error message */

	while ((c=getopt_long(argc, argv, "P:T::VWc::ip:quv::w:", longopts, NULL)) != -1) {
		switch (c)
		{
		case 'P':
			if (optarg)
				proxy_name = optarg;
			break;
		case 'T':
			if (optarg) {
				proxy_timeout = atoi(optarg);
			} else if (
				optind<argc
				&& argv[optind][0]!='-'
			) {
				/*
				 * The next arg is not an option so we
				 * treat it as the argument of this option.
				 *
				 * getopt() does not return this to us via optarg
				 * because an argument is optional for this option
				 */
				proxy_timeout = atoi(argv[optind]);
				optind++;
			} else {
				/* no argument */
				proxy_timeout = DEFAULT_PROXY_TIMEOUT;
			}
			if (proxy_timeout < 0)
				proxy_timeout = -1; /* "never" */
			break;
		case 'V':
			fprintf(stderr, "apmd version %s\n", VERSION);
			exit(0);
			break;
		case 'W':
			++wall;
			break;
		case 'c':
			if (optarg) {
				check_interval = atoi(optarg);
			} else if (
				optind<argc
				&& argv[optind][0]!='-'
			) {
				/*
				 * The next arg is not an option so we
				 * treat it as the argument of this option.
				 *
				 * getopt() does not return this to us via optarg
				 * because an argument is optional for this option
				 */
				check_interval = atoi(argv[optind]);
				optind++;
			} else {
				/* no argument */
				check_interval = DEFAULT_CHECK_INTERVAL;
			}
			if (check_interval < 0)
				check_interval = -1; /* "infinity" */
			break;
		case 'h':
			usage(stdout);
			exit(0);
			break;
		case 'i':
			++ignore_bios_batlow;
			break;
		case 'p':
			if (optarg)
				percent_change = atoi(optarg);
			if (percent_change < 0)
				percent_change = 0; /* "always" */
			break;
		case 'q':
			++quiet_bios_batlow;
			break;
		case 'u':
			fprintf(
				stderr,
				"WARNING: The -u option has been eliminated.\n"
				"         To set the clock to UTC, use the proxy.\n"
			);
			break;
		case 'v':
			if (optarg) {
				verbosity = atoi(optarg);
			} else if (
				optind<argc
				&& argv[optind][0]!='-'
			) {
				/*
				 * The next arg is not an option so we
				 * treat it as the argument of this option.
				 *
				 * getopt() does not return this to us via optarg
				 * because an argument is optional for this option
				 */
				verbosity = atoi(argv[optind]);
				optind++;
			} else {
				/* no argument */
				verbosity++;
			}
			break;
		case 'w':
			if (optarg)
				warn_level = atoi(optarg);
			break;
		case '?':
		case ':':
		default:
			/* getopt() prints an error message */
			/* Add our own message */
			option_barf(stderr);
			exit(1);
			break;
		} /* switch */
	} /* while */

	/* Reject badly formed options */
	if (optind<argc) {
		fprintf(stderr, "apmd: Unrecognized argument `%s'\n", argv[optind]);
		option_barf(stderr);
		exit(1);
	}

	/* Reject badly formed option arguments */
	if (proxy_timeout == 0) {
		fprintf(stderr, "apmd: Illegal value for proxy timeout: 0\n");
		fprintf(stderr, "apmd: To disable this feature, set the timeout to a negative value.\n");
		exit(1);
	}

	/* Reject badly formed option arguments */
	if (check_interval == 0) {
		fprintf(stderr, "apmd: Illegal value for check interval: 0\n");
		fprintf(stderr, "apmd: To disable this feature, set the interval to a negative value.\n");
		exit(1);
	}

	if (!access(PID_FILE, R_OK)) {
		if ((str = fopen(PID_FILE, "r"))) {
			fscanf(str, "%d", &pid);
			fclose(str);
				
			if (!kill(pid, 0) || errno == EPERM) {
				fprintf(
					stderr,
					"It appears that an instance of apmd is already running as process %d.\n"
					"If in reality no instance of apmd is running, remove %s.\n",
					pid,
					PID_FILE
				);
				exit(1);
			}
		}
	}

	if ((apmd_uid = getuid())) {
		fprintf(stderr, "apmd: must be run as root\n");
		exit(1);
	}

	openlog("apmd", (verbosity>=LOG_DEBUG)?LOG_PERROR:0 | LOG_PID | LOG_CONS, LOG_DAEMON);

	/* Set up signal handler */
	if (signal(SIGINT, SIG_IGN) != SIG_IGN)
		signal(SIGINT, sig_handler);
	if (signal(SIGQUIT, SIG_IGN) != SIG_IGN)
		signal(SIGQUIT, sig_handler);
	if (signal(SIGTERM, SIG_IGN) != SIG_IGN)
		signal(SIGTERM, sig_handler);

	/* Write the pidfile */
	if ((pid = fork())) {
		/* parent */
		if (pid < 0) {
			APMD_TRACE0(LOG_ERR, "fork() failed: %m");
			unlink(PID_FILE);
			exit(1);
		}
		/* We forked */
		if ((str = fopen(PID_FILE, "w"))) {
			fprintf(str, "%d\n", pid);
			fclose(str);
		}
		exit(0);
	}

	/*
	 * child
	 *
	 * Follow the daemon rules in W. Richard Stevens,
	 * _Advanced Programming in the UNIX Environment_,
	 * Addison-Wesley Publishing Co., 1992, p. 417.
	 */
	if (setsid() < 0) {
		APMD_TRACE0(LOG_ERR, "setsid() failed: %m");
		unlink(PID_FILE);
		exit(1);
	}
	chdir("/");
	close(0);
	close(1);
	if (verbosity < LOG_DEBUG)
		close(2);
	umask(0);

	apmd_fd = open(APM_DEVICE, O_RDWR);
	if (apmd_fd < 0) {
		APMD_TRACE0(LOG_ERR, "open() failed: %m");
		unlink(PID_FILE);
		exit(1);
	}
	/* apmd_fd is good */

	if (apm_read(&apminfo)) {
		APMD_TRACE0(LOG_ERR, "apm_read() failed: %m");
		unlink(PID_FILE);
		exit(1);
	}
	/* apm_read() works */

	APMD_SYSLOG(
		LOG_NOTICE,
		"apmd %s interfacing with apm driver %s and APM BIOS %d.%d",
		VERSION,
		apminfo.driver_version,
		apminfo.apm_version_major, apminfo.apm_version_minor
	);

	APMD_SYSLOG(
		LOG_INFO,
		"apmd operating parameters: -T %d, -c %d, -p %d, -v %d, -w %d",
		proxy_timeout,
		check_interval,
		percent_change,
		verbosity,
		warn_level
	);

	handle_event(APMD_START, &apminfo);

	for (;;)
	{
		int num_events = apm_get_events(apmd_fd, check_interval, events, MAX_EVENTS);
		int e, a;

		apm_read(&apminfo);

		if (num_events == 0) {
			/* The call timed out */
			handle_event(APMD_PSEUDO_EVENT, &apminfo);
			continue;
		}
		/* num_events > 0 */

		for (a = 0; a < NUM_ACTIONS; a++) /* Process events in actions-table order */
		for (e = 0; e < num_events; e++)  /* ... and then in order received */
		{
			apm_event_t event = events[e];

			if (event != actions[a].event)
				continue;

			APMD_SYSLOG(
				LOG_DEBUG,
				"APM event 0x%04x: %s",
				event,
				apm_event_name(event)
			);

			handle_event(event, &apminfo);
		} /* for for */
	} /* for */

	return 0;
}
