Logo Search packages:      
Sourcecode: apmd version File versions  Download package

apmd.c

/* 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;
}

Generated by  Doxygen 1.6.0   Back to index