?? apmd.c
字號:
/* apmd.c -- APM event-monitoring daemon * Created: Mon Jan 8 14:29:18 1996 by faith@acm.org * Revised: Fri Dec 26 21:38:28 1997 by faith@acm.org * 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. * * Changes to support pre_suspend, post_resume and low_battery commands * based on patches from Bjoern Kriews (bkr@cut.de), 1996/12/24. * *//* * Changes to support ac_offline, ac_online commands to * execute when the power status changes. * 1999/2/24 J.D. Smith - comments to jdsmith@alum.mit.edu. * This is useful to set hard drive spindown rates and other * configureable power saving options when the ac cord is * inserted or removed. * * New options: long form: * -a ac_online_cmd ac_online * -b ac_offline_cmd ac_offline *//* * Changes to support centralized dispatch routines, generalized logging, * general code cleanup, add support for apm_reject and APM_CAPABILITY_CHANGE. * Craig Markwardt, craigm@lheamail.gsfc.nasa.gov, 1999 May 21 *//* * Reduced spurious and repeated syslog messages, as well as the number of * times the proxy gets called during charging/discharging. Minor cleanup and * commenting. Documentation updated. Power status reporting now goes by AC * status only. * - David Brownell, db@post.harvard.edu, 14 June 1999 */#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/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>#define APM_TRACE 1 /* enable to compile in debug tracing */#ifdef APM_TRACE#define ADEBUG(lev,args...) \ if (APM_TRACE>=lev) syslog(LOG_DEBUG, __FUNCTION__ ": " args)#else#define ADEBUG(args...)#endif#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 "synthetic" APM 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 /* Daemon startup */#define APMD_STOP 0xad01 /* Daemon shutdown */#define APMD_BATTERY 0xad02 /* Battery charging update */#define APMD_SYNTHETIC_CHECK 0xad03 /* Check power */#define APMD_INVALID 0xad0f /* A non-event for completeness *//* This table defines how events are handled. For each event type, as * listed on the left, there are two entries, one for "proxy" and one * for "log". If a proxy entry is 1, then the corresponding event * causes the user proxy program to run (zero prevents the program * from running). Similarly, if a log entry is 1 then the * corresponding event is logged. */static int apmd_action[][3] ={ /* proxy log */ {APM_SYS_STANDBY, 1, 1}, /* potentially too frequent */ {APM_SYS_SUSPEND, 1, 1}, {APM_NORMAL_RESUME, 1, 1}, {APM_CRITICAL_RESUME, 1, 1}, {APM_LOW_BATTERY, 1, 1}, {APM_POWER_STATUS_CHANGE, 1, 1}, /* update time -- handled by recent APM BIOS drivers, and * may happen too often to fork processes or log in any case */ {APM_UPDATE_TIME, 0, 0}, /* critical suspend -- no time to do anything except suspend! */ {APM_CRITICAL_SUSPEND, 0, 0}, {APM_USER_STANDBY, 1, 1}, {APM_USER_SUSPEND, 1, 1}, {APM_STANDBY_RESUME, 1, 1}, /* potentially too frequent */#ifdef APM_CAPABILITY_CHANGE {APM_CAPABILITY_CHANGE, 1, 1},#endif /* These are synthesized by apmd */ {APMD_START, 1, 1}, {APMD_STOP, 1, 1}, {APMD_BATTERY, 1, 1}, {APMD_SYNTHETIC_CHECK, 0, 0}};#define apmd_num_actions (sizeof(apmd_action)/(3*sizeof(int)))/* From parsing the command line: */static int verbose = 0;static int quiet = 0;static int wall = 0;static int percent_change = 5; /* lot every 5% change */static int warn_level = 10; /* start warning at 10% remaining */static int check_power_time = -1; /* seconds between /proc/apm checks */static char *apmd_proxy = APMD_PROXY_NAME;/* From initialization: */static uid_t apmd_uid = 0;static int apmd_fd = -1;/* State collected during daemon operation: */static time_t pre_suspend_time = 0; /* Time at onset of suspend/standby */static time_t post_suspend_time = 0; /* Time upon resume suspend/standby */static int pre_suspend_percentage = 0; /* Battery level before suspend *//* A question of notation: * MINOR CHECKPOINT - occurs whenever a notification is passed to * the daemon and a message is printed here. * The minor checkpoint reflects the last time * a log entry was printed. * Time and battery percentage are recorded. * MAJOR CHECKPOINT - occurs upon startup and APM resume functions, * or when charging/discharging is complete. * Reflects the last time a major power-related * status change occurred, especially when the time * has been disrupted by a standby or suspend. */static time_t major_checkpoint = 0; /* Updated at init/resume/full/... */static time_t minor_checkpoint = 0; /* Updated at power status logging */static int major_percentage = -1;static int minor_percentage = -1;static int last_charging_status = -1;#ifndef abs#define abs(a) ((a)<0?(-(a)):(a))#endif#define IS_CHARGING(i) ( ((i).battery_status == 3) || ((i).battery_flags & 8) )static void usage(void){ fprintf(stderr, "usage: apmd [-c seconds] [-P apmd_proxy] [-p percent] [-qVvW] " "[-w percent] [-?]\n" ); exit(1);}static void warn(const char *message){ FILE *str; syslog(LOG_ALERT, "%s", message); if (wall) { str = popen("wall", "w"); fprintf(str, "%s\n", message); pclose(str); }}static char *ac_descr (int code){ if (code == 0) return "Battery"; else if (code == 1) return "AC"; else if (code == 2) return "Backup"; else return "???";}/* Generic logging function. Depending on the event type and dispatch * table, it may or may not be logged. */static void apmd_log(apm_event_t event, char *msg){ int i; /* Scan through dispatch table looking for this event type */ for (i = 0; i < apmd_num_actions; i++) if ((apm_event_t) apmd_action[i][0] == event) break; /* Event is not found */ if (i == apmd_num_actions || !apmd_action[i][2]) return; /* Success. Log it. */ syslog(LOG_INFO, "%s", msg);}/* apmd_call_proxy() is a generic outcalling dispatcher. It calls a * proxy program, defined by apmd_proxy, which can do further event * processing. When the kernel APM BIOS driver supports rejection of * suspend and standby events from userland, the return code from * apmd_proxy can be used as well. */static int apmd_call_proxy(apm_event_t event, apm_info * apmi){ const char *argv[4] = {NULL, NULL, NULL, NULL}; int i, fds[2]; pid_t pid; char line[256]; ADEBUG(4, "0x%04x\n", event); /* If the proxy flag is not set, then return 0 indicating that * the event is acceptable */ for (i = 0; i < apmd_num_actions; i++) if ((apm_event_t) apmd_action[i][0] == event) break; /* Event is not found or proxy shouldn't be called */ if (i == apmd_num_actions || !apmd_action[i][1]) return 0; /* Check that the proxy file actually exists */ if (access(apmd_proxy, X_OK)) return 0; argv[0] = apmd_proxy; /* Add the arguments depending on event type */ switch (event) { case APM_SYS_STANDBY: argv[1] = "standby"; argv[2] = "system"; break; case APM_USER_STANDBY: argv[1] = "standby"; argv[2] = "user"; break; case APM_SYS_SUSPEND: argv[1] = "suspend"; argv[2] = "system"; break; case APM_USER_SUSPEND: argv[1] = "suspend"; argv[2] = "user"; break; case APM_CRITICAL_SUSPEND: argv[1] = "suspend"; 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_CRITICAL_RESUME: argv[1] = "resume"; argv[2] = "critical"; 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 !! return 0; } if (pipe(fds)) { ADEBUG(0, "can't open fds for apmd_proxy: %s", strerror(errno)); return 2; } ADEBUG(1, "executing: '%s' '%s'", argv[0], argv[1]); pid = fork(); if (pid == 0) /* child */ { close(fds[0]); /* don't let them inherit stdin. Use /dev/null instead. */ close(0); open("/dev/null", O_RDONLY); /* stdout/stderr are the pipe */ dup2(fds[1], 1); dup2(fds[1], 2); /* don't close-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 if (pid < 0) /* can't fork */ { ADEBUG(0, "can't fork for apmd_proxy: %s", strerror(errno)); return 1; } else if (pid > 0) /* parent */ { int status, retval; ssize_t len; close(fds[1]); fcntl(fds[0], F_SETFL, O_RDONLY|O_NONBLOCK); /* capture the child's output, if any, but only until they terminate */ for (;;) { while ((len = read(fds[0], line, sizeof(line)-1)) > 0) { line[len] = 0; ADEBUG(1, "+ %s", line); } retval = waitpid(pid, &status, WNOHANG); if (retval == pid) break; // finished okay if (retval == -1 && errno != EINTR) { ADEBUG(0, "waitpid failed: %s", strerror(errno)); status = 143; break; } sleep(1); } /* flush any remaining data */ while ((len = read(fds[0], line, sizeof(line)-1)) > 0) { line[len] = 0; ADEBUG(1, "+ %s", line); } close(fds[0]); /* Collect the exit code */ if (WIFEXITED(status)) { ADEBUG(3, "%s exited with status %d", apmd_proxy, WEXITSTATUS(status)); /* Return the exit status of the program */ return WEXITSTATUS(status); } else ADEBUG(3, "%s exited on signal %d", apmd_proxy, WTERMSIG(status)); } return 0;}static inline int apmd_time(apm_info * apmi){ return (apmi->battery_time * (apmi->using_minutes ? 60 : 1));}/* "reset" power means that we totally reinitialize our state variables * concerning power status */static void apmd_power_reset(apm_event_t event, apm_info * apmi){ char msg[512]; ADEBUG(4, "0x%04x\n", event); if (last_charging_status == apmi->battery_status) return; /* Establish a major (and minor) checkpoint */ time(&major_checkpoint); major_percentage = apmi->battery_percentage; minor_checkpoint = major_checkpoint; minor_percentage = major_percentage; last_charging_status = apmi->battery_status; sprintf(msg, "%s: * * * (%d%% %s)", last_charging_status ? "Charge" : "Battery", apmi->battery_percentage, apm_time_nosec(apmd_time(apmi))); apmd_log(APMD_BATTERY, msg);}/* apmd_init occurs once when the daemon starts up */static void apmd_init(apm_info * apmi){ syslog(LOG_INFO, "Version %s (APM BIOS %d.%d, Linux driver %s)", VERSION, apmi->apm_version_major, apmi->apm_version_minor, apmi->driver_version); /* Call the proxy */ apmd_call_proxy(APMD_START, apmi); /* Print battery status at beginning of run */ apmd_power_reset(APMD_START, apmi);}/* Suspend handler. It calls the dispatcher. The dispatcher may reject * a suspend, in which case we must reply to the kernel driver anyway. */static int apmd_suspend(apm_event_t event, apm_info * apmi){ ADEBUG(4, "0x%04x\n", event); if (apmd_call_proxy(event, apmi)) {#ifdef APM_REJECT_ENABLED /* If kernel rejection enabled */ ADEBUG(5, "Suspend rejected\n"); return apm_reject(apmd_fd);#endif } /* Record current time and battery status */ time(&pre_suspend_time); pre_suspend_percentage = apmi->battery_percentage; /* Logging is okay here since the sync() happens afterward */ switch (event) { case APM_SYS_SUSPEND: apmd_log(event, "System Suspend"); break; case APM_USER_SUSPEND: apmd_log(event, "User Suspend"); break; } sync(); sleep(0); /* let syslogd write the message */ sync(); return apm_suspend(apmd_fd);}/* Standby handler. It calls the dispatcher. The dispatcher may reject * a standby, in which case we must reply to the kernel driver anyway. */static int apmd_standby(apm_event_t event, apm_info * apmi){ ADEBUG(4, "0x%04x\n", event); if (apmd_call_proxy(event, apmi)) {#ifdef APM_REJECT_ENABLED ADEBUG(5, "Standby rejected\n"); return apm_reject(apmd_fd);#endif } /* Record current time and battery status */
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -