diff --git a/usr.sbin/apmd/apmd.c b/usr.sbin/apmd/apmd.c index 470bcc6481e8..a285b9b2dad8 100644 --- a/usr.sbin/apmd/apmd.c +++ b/usr.sbin/apmd/apmd.c @@ -58,7 +58,7 @@ int debug_level = 0; int verbose = 0; const char *apmd_configfile = APMD_CONFIGFILE; const char *apmd_pidfile = APMD_PIDFILE; -int apmctl_fd = -1; +int apmctl_fd = -1, apmnorm_fd = -1; /* * table of event handlers @@ -80,6 +80,13 @@ struct event_config events[EVENT_MAX] = { 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 */ @@ -207,6 +214,40 @@ free_event_cmd_list(struct event_cmd *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) + (void) 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) @@ -242,11 +283,10 @@ register_apm_event_handlers( * execute command */ int -exec_event_cmd(struct event_config *ev) +exec_run_cmd(struct event_cmd *p) { int status = 0; - struct event_cmd *p = ev->cmdlist; for (; p; p = p->next) { assert(p->op->act); if (verbose) @@ -254,16 +294,28 @@ exec_event_cmd(struct event_config *ev) status = p->op->act(p); if (status) { syslog(LOG_NOTICE, "command finished with %d\n", status); - if (ev->rejectable) { - syslog(LOG_ERR, "canceled"); - (void) event_cmd_reject_act(NULL); - } 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"); + (void) event_cmd_reject_act(NULL); + } + return status; +} + /* * read config file */ @@ -303,6 +355,7 @@ void dump_config() { int i; + struct battery_watch_event *q; for (i = 0; i < EVENT_MAX; i++) { struct event_cmd * p; @@ -317,12 +370,28 @@ dump_config() 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() { int i; + struct battery_watch_event *q; /* disable events */ for (i = 0; i < EVENT_MAX; i++) { @@ -340,6 +409,13 @@ destroy_config() 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 @@ -430,6 +506,72 @@ proc_apmevent(int fd) } } } + +#define AC_POWER_STATE ((pw_info.ai_acline == 1) ? BATTERY_CHARGING :\ + BATTERY_DISCHARGING) + +void +check_battery() +{ + + static int first_time=1, last_state; + + 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) + (void) 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) + (void) 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) { + 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 == 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) { + int status; + status = exec_run_cmd(p -> cmdlist); + exit(status); + } + } +} void event_loop(void) { @@ -461,19 +603,30 @@ event_loop(void) 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 (select(fdmax + 1, &rfds, 0, 0, 0) < 0) { + if ((res=select(fdmax + 1, &rfds, 0, 0, &to)) < 0) { if (errno != EINTR) (void) 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) goto out; } + if (FD_ISSET(apmctl_fd, &rfds)) proc_apmevent(apmctl_fd); } @@ -526,6 +679,10 @@ main(int ac, char* av[]) if (fcntl(signal_fd[0], F_SETFL, O_NONBLOCK) < 0) (void) err(1, "fcntl"); + if ((apmnorm_fd = open(APM_NORM_DEVICEFILE, O_RDWR)) == -1) { + (void) err(1, "cannot open device file `%s'", APM_NORM_DEVICEFILE); + } + if ((apmctl_fd = open(APM_CTL_DEVICEFILE, O_RDWR)) == -1) { (void) err(1, "cannot open device file `%s'", APM_CTL_DEVICEFILE); } diff --git a/usr.sbin/apmd/apmd.h b/usr.sbin/apmd/apmd.h index e45b84a2b17c..b716d0d15c18 100644 --- a/usr.sbin/apmd/apmd.h +++ b/usr.sbin/apmd/apmd.h @@ -31,6 +31,7 @@ #define APMD_CONFIGFILE "/etc/apmd.conf" #define APM_CTL_DEVICEFILE "/dev/apmctl" +#define APM_NORM_DEVICEFILE "/dev/apm" #define APMD_PIDFILE "/var/run/apmd.pid" #define NICE_INCR -20 @@ -77,10 +78,30 @@ struct event_config { int rejectable; }; +struct battery_watch_event { + struct battery_watch_event *next; + int level; + enum { + BATTERY_CHARGING, + BATTERY_DISCHARGING + } direction; + enum { + BATTERY_MINUTES, + BATTERY_PERCENT + } type; + int done; + struct event_cmd *cmdlist; +}; + + extern struct event_cmd_op event_cmd_exec_ops; extern struct event_cmd_op event_cmd_reject_ops; extern struct event_config events[EVENT_MAX]; +extern struct battery_watch_event *battery_watch_list; +extern int register_battery_handlers( + int level, int direction, + struct event_cmd *cmdlist); extern int register_apm_event_handlers( bitstr_t bit_decl(evlist, EVENT_MAX), struct event_cmd *cmdlist); diff --git a/usr.sbin/apmd/apmdlex.l b/usr.sbin/apmd/apmdlex.l index 34598bb927b6..0e5cff30036e 100644 --- a/usr.sbin/apmd/apmdlex.l +++ b/usr.sbin/apmd/apmdlex.l @@ -74,6 +74,19 @@ int first_time; STANDBYRESUME { yylval.ev = EVENT_STANDBYRESUME; return EVENT; } CAPABILITIESCHANGE { yylval.ev = EVENT_CAPABILITIESCHANGE; return EVENT; } +apm_battery { return APMBATT; } + +charging { return BATTCHARGE; } +discharging { return BATTDISCHARGE; } +[0-9]+% { + yylval.i = atoi(yytext); + return BATTPERCENT; + } +[0-9]+[Mm] { + yylval.i = -atoi(yytext); + return BATTTIME; + } + exec { return EXECCMD; } reject { return REJECTCMD; } diff --git a/usr.sbin/apmd/apmdparse.y b/usr.sbin/apmd/apmdparse.y index ce9c9dcce341..f0acea32b508 100644 --- a/usr.sbin/apmd/apmdparse.y +++ b/usr.sbin/apmd/apmdparse.y @@ -32,6 +32,7 @@ #include #include +#include #include "apmd.h" #ifdef DEBUG @@ -47,15 +48,21 @@ extern int first_time; bitstr_t bit_decl(evlist, EVENT_MAX); int ev; struct event_cmd * evcmd; + int i; } %token BEGINBLOCK ENDBLOCK %token COMMA SEMICOLON %token APMEVENT +%token APMBATT +%token BATTCHARGE BATTDISCHARGE +%token BATTTIME BATTPERCENT %token EXECCMD REJECTCMD %token EVENT %token STRING UNKNOWN +%type apm_battery_level +%type apm_battery_direction %type string %type unknown %type event_list @@ -76,6 +83,7 @@ config_list config : apm_event_statement + | apm_battery_statement ; apm_event_statement @@ -87,6 +95,37 @@ apm_event_statement } ; +apm_battery_level + : BATTPERCENT + { + $$ = $1; + } + | BATTTIME + { + $$ = $1; + } + ; + +apm_battery_direction + : BATTCHARGE + { + $$ = 1; + } + | BATTDISCHARGE + { + $$ = -1; + } + ; +apm_battery_statement + : APMBATT apm_battery_level apm_battery_direction + BEGINBLOCK cmd_list ENDBLOCK + { + if (register_battery_handlers($2, $3, $5) < 0) + abort(); /* XXX */ + free_event_cmd_list($5); + } + ; + event_list : EVENT {