apmd.c revision 116666
1/*- 2 * APM (Advanced Power Management) Event Dispatcher 3 * 4 * Copyright (c) 1999 Mitsuru IWASAKI <iwasaki@FreeBSD.org> 5 * Copyright (c) 1999 KOIE Hidetaka <koie@suri.co.jp> 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 */ 29 30#ifndef lint 31static const char rcsid[] = 32 "$FreeBSD: head/usr.sbin/apmd/apmd.c 116666 2003-06-22 05:34:45Z mdodd $"; 33#endif /* not lint */ 34 35#include <assert.h> 36#include <bitstring.h> 37#include <err.h> 38#include <errno.h> 39#include <fcntl.h> 40#include <paths.h> 41#include <signal.h> 42#include <stdio.h> 43#include <stdlib.h> 44#include <string.h> 45#include <syslog.h> 46#include <unistd.h> 47#include <sys/ioctl.h> 48#include <sys/types.h> 49#include <sys/time.h> 50#include <sys/wait.h> 51#include <machine/apm_bios.h> 52 53#include "apmd.h" 54 55extern int yyparse(void); 56 57int debug_level = 0; 58int verbose = 0; 59int soft_power_state_change = 0; 60const char *apmd_configfile = APMD_CONFIGFILE; 61const char *apmd_pidfile = APMD_PIDFILE; 62int apmctl_fd = -1, apmnorm_fd = -1; 63 64/* 65 * table of event handlers 66 */ 67#define EVENT_CONFIG_INITIALIZER(EV,R) { #EV, NULL, R }, 68struct event_config events[EVENT_MAX] = { 69 EVENT_CONFIG_INITIALIZER(NOEVENT, 0) 70 EVENT_CONFIG_INITIALIZER(STANDBYREQ, 1) 71 EVENT_CONFIG_INITIALIZER(SUSPENDREQ, 1) 72 EVENT_CONFIG_INITIALIZER(NORMRESUME, 0) 73 EVENT_CONFIG_INITIALIZER(CRITRESUME, 0) 74 EVENT_CONFIG_INITIALIZER(BATTERYLOW, 0) 75 EVENT_CONFIG_INITIALIZER(POWERSTATECHANG, 0) 76 EVENT_CONFIG_INITIALIZER(UPDATETIME, 0) 77 EVENT_CONFIG_INITIALIZER(CRITSUSPEND, 1) 78 EVENT_CONFIG_INITIALIZER(USERSTANDBYREQ, 1) 79 EVENT_CONFIG_INITIALIZER(USERSUSPENDREQ, 1) 80 EVENT_CONFIG_INITIALIZER(STANDBYRESUME, 0) 81 EVENT_CONFIG_INITIALIZER(CAPABILITIESCHANGE, 0) 82}; 83 84/* 85 * List of battery events 86 */ 87struct battery_watch_event *battery_watch_list = NULL; 88 89#define BATT_CHK_INTV 10 /* how many seconds between battery state checks? */ 90 91/* 92 * default procedure 93 */ 94struct event_cmd * 95event_cmd_default_clone(void *this) 96{ 97 struct event_cmd * oldone = this; 98 struct event_cmd * newone = malloc(oldone->len); 99 100 newone->next = NULL; 101 newone->len = oldone->len; 102 newone->name = oldone->name; 103 newone->op = oldone->op; 104 return newone; 105} 106 107/* 108 * exec command 109 */ 110int 111event_cmd_exec_act(void *this) 112{ 113 struct event_cmd_exec * p = this; 114 int status = -1; 115 pid_t pid; 116 117 switch ((pid = fork())) { 118 case -1: 119 (void) warn("cannot fork"); 120 goto out; 121 case 0: 122 /* child process */ 123 execl(_PATH_BSHELL, "sh", "-c", p->line, (char *)NULL); 124 _exit(127); 125 default: 126 /* parent process */ 127 do { 128 pid = waitpid(pid, &status, 0); 129 } while (pid == -1 && errno == EINTR); 130 break; 131 } 132 out: 133 return status; 134} 135void 136event_cmd_exec_dump(void *this, FILE *fp) 137{ 138 fprintf(fp, " \"%s\"", ((struct event_cmd_exec *)this)->line); 139} 140struct event_cmd * 141event_cmd_exec_clone(void *this) 142{ 143 struct event_cmd_exec * newone = (struct event_cmd_exec *) event_cmd_default_clone(this); 144 struct event_cmd_exec * oldone = this; 145 146 newone->evcmd.next = NULL; 147 newone->evcmd.len = oldone->evcmd.len; 148 newone->evcmd.name = oldone->evcmd.name; 149 newone->evcmd.op = oldone->evcmd.op; 150 if ((newone->line = strdup(oldone->line)) == NULL) 151 err(1, "out of memory"); 152 return (struct event_cmd *) newone; 153} 154void 155event_cmd_exec_free(void *this) 156{ 157 free(((struct event_cmd_exec *)this)->line); 158} 159struct event_cmd_op event_cmd_exec_ops = { 160 event_cmd_exec_act, 161 event_cmd_exec_dump, 162 event_cmd_exec_clone, 163 event_cmd_exec_free 164}; 165 166/* 167 * reject commad 168 */ 169int 170event_cmd_reject_act(void *this) 171{ 172 int rc = -1; 173 174 if (ioctl(apmctl_fd, APMIO_REJECTLASTREQ, NULL)) { 175 syslog(LOG_NOTICE, "fail to reject\n"); 176 goto out; 177 } 178 rc = 0; 179 out: 180 return rc; 181} 182struct event_cmd_op event_cmd_reject_ops = { 183 event_cmd_reject_act, 184 NULL, 185 event_cmd_default_clone, 186 NULL 187}; 188 189/* 190 * manipulate event_config 191 */ 192struct event_cmd * 193clone_event_cmd_list(struct event_cmd *p) 194{ 195 struct event_cmd dummy; 196 struct event_cmd *q = &dummy; 197 for ( ;p; p = p->next) { 198 assert(p->op->clone); 199 if ((q->next = p->op->clone(p)) == NULL) 200 (void) err(1, "out of memory"); 201 q = q->next; 202 } 203 q->next = NULL; 204 return dummy.next; 205} 206void 207free_event_cmd_list(struct event_cmd *p) 208{ 209 struct event_cmd * q; 210 for ( ; p ; p = q) { 211 q = p->next; 212 if (p->op->free) 213 p->op->free(p); 214 free(p); 215 } 216} 217int 218register_battery_handlers( 219 int level, int direction, 220 struct event_cmd *cmdlist) 221{ 222 /* 223 * level is negative if it's in "minutes", non-negative if 224 * percentage. 225 * 226 * direction =1 means we care about this level when charging, 227 * direction =-1 means we care about it when discharging. 228 */ 229 if (level>100) /* percentage > 100 */ 230 return -1; 231 if (abs(direction) != 1) /* nonsense direction value */ 232 return -1; 233 234 if (cmdlist) { 235 struct battery_watch_event *we; 236 237 if ((we = malloc(sizeof(struct battery_watch_event))) == NULL) 238 (void) err(1, "out of memory"); 239 240 we->next = battery_watch_list; /* starts at NULL */ 241 battery_watch_list = we; 242 we->level = abs(level); 243 we->type = (level<0)?BATTERY_MINUTES:BATTERY_PERCENT; 244 we->direction = (direction<0)?BATTERY_DISCHARGING: 245 BATTERY_CHARGING; 246 we->done = 0; 247 we->cmdlist = clone_event_cmd_list(cmdlist); 248 } 249 return 0; 250} 251int 252register_apm_event_handlers( 253 bitstr_t bit_decl(evlist, EVENT_MAX), 254 struct event_cmd *cmdlist) 255{ 256 if (cmdlist) { 257 bitstr_t bit_decl(tmp, EVENT_MAX); 258 memcpy(&tmp, evlist, bitstr_size(EVENT_MAX)); 259 260 for (;;) { 261 int n; 262 struct event_cmd *p; 263 struct event_cmd *q; 264 bit_ffs(tmp, EVENT_MAX, &n); 265 if (n < 0) 266 break; 267 p = events[n].cmdlist; 268 if ((q = clone_event_cmd_list(cmdlist)) == NULL) 269 (void) err(1, "out of memory"); 270 if (p) { 271 while (p->next != NULL) 272 p = p->next; 273 p->next = q; 274 } else { 275 events[n].cmdlist = q; 276 } 277 bit_clear(tmp, n); 278 } 279 } 280 return 0; 281} 282 283/* 284 * execute command 285 */ 286int 287exec_run_cmd(struct event_cmd *p) 288{ 289 int status = 0; 290 291 for (; p; p = p->next) { 292 assert(p->op->act); 293 if (verbose) 294 syslog(LOG_INFO, "action: %s", p->name); 295 status = p->op->act(p); 296 if (status) { 297 syslog(LOG_NOTICE, "command finished with %d\n", status); 298 break; 299 } 300 } 301 return status; 302} 303 304/* 305 * execute command -- the event version 306 */ 307int 308exec_event_cmd(struct event_config *ev) 309{ 310 int status = 0; 311 312 status = exec_run_cmd(ev->cmdlist); 313 if (status && ev->rejectable) { 314 syslog(LOG_ERR, "canceled"); 315 (void) event_cmd_reject_act(NULL); 316 } 317 return status; 318} 319 320/* 321 * read config file 322 */ 323extern FILE * yyin; 324extern int yydebug; 325 326void 327read_config(void) 328{ 329 int i; 330 331 if ((yyin = fopen(apmd_configfile, "r")) == NULL) { 332 (void) err(1, "cannot open config file"); 333 } 334 335#ifdef DEBUG 336 yydebug = debug_level; 337#endif 338 339 if (yyparse() != 0) 340 (void) err(1, "cannot parse config file"); 341 342 fclose(yyin); 343 344 /* enable events */ 345 for (i = 0; i < EVENT_MAX; i++) { 346 if (events[i].cmdlist) { 347 u_int event_type = i; 348 if (write(apmctl_fd, &event_type, sizeof(u_int)) == -1) { 349 (void) err(1, "cannot enable event 0x%x", event_type); 350 } 351 } 352 } 353} 354 355void 356dump_config() 357{ 358 int i; 359 struct battery_watch_event *q; 360 361 for (i = 0; i < EVENT_MAX; i++) { 362 struct event_cmd * p; 363 if ((p = events[i].cmdlist)) { 364 fprintf(stderr, "apm_event %s {\n", events[i].name); 365 for ( ; p ; p = p->next) { 366 fprintf(stderr, "\t%s", p->name); 367 if (p->op->dump) 368 p->op->dump(p, stderr); 369 fprintf(stderr, ";\n"); 370 } 371 fprintf(stderr, "}\n"); 372 } 373 } 374 for (q = battery_watch_list ; q != NULL ; q = q -> next) { 375 struct event_cmd * p; 376 fprintf(stderr, "apm_battery %d%s %s {\n", 377 q -> level, 378 (q -> type == BATTERY_PERCENT)?"%":"m", 379 (q -> direction == BATTERY_CHARGING)?"charging": 380 "discharging"); 381 for ( p = q -> cmdlist; p ; p = p->next) { 382 fprintf(stderr, "\t%s", p->name); 383 if (p->op->dump) 384 p->op->dump(p, stderr); 385 fprintf(stderr, ";\n"); 386 } 387 fprintf(stderr, "}\n"); 388 } 389} 390 391void 392destroy_config() 393{ 394 int i; 395 struct battery_watch_event *q; 396 397 /* disable events */ 398 for (i = 0; i < EVENT_MAX; i++) { 399 if (events[i].cmdlist) { 400 u_int event_type = i; 401 if (write(apmctl_fd, &event_type, sizeof(u_int)) == -1) { 402 (void) err(1, "cannot disable event 0x%x", event_type); 403 } 404 } 405 } 406 407 for (i = 0; i < EVENT_MAX; i++) { 408 struct event_cmd * p; 409 if ((p = events[i].cmdlist)) 410 free_event_cmd_list(p); 411 events[i].cmdlist = NULL; 412 } 413 414 for( ; battery_watch_list; battery_watch_list = battery_watch_list -> next) { 415 free_event_cmd_list(battery_watch_list->cmdlist); 416 q = battery_watch_list->next; 417 free(battery_watch_list); 418 battery_watch_list = q; 419 } 420} 421 422void 423restart() 424{ 425 destroy_config(); 426 read_config(); 427 if (verbose) 428 dump_config(); 429} 430 431/* 432 * write pid file 433 */ 434static void 435write_pid() 436{ 437 FILE *fp = fopen(apmd_pidfile, "w"); 438 439 if (fp) { 440 fprintf(fp, "%d\n", getpid()); 441 fclose(fp); 442 } 443} 444 445/* 446 * handle signals 447 */ 448static int signal_fd[2]; 449 450void 451enque_signal(int sig) 452{ 453 if (write(signal_fd[1], &sig, sizeof sig) != sizeof sig) 454 (void) err(1, "cannot process signal."); 455} 456 457void 458wait_child() 459{ 460 int status; 461 while (waitpid(-1, &status, WNOHANG) > 0) 462 ; 463} 464 465int 466proc_signal(int fd) 467{ 468 int rc = -1; 469 int sig; 470 471 while (read(fd, &sig, sizeof sig) == sizeof sig) { 472 syslog(LOG_INFO, "caught signal: %d", sig); 473 switch (sig) { 474 case SIGHUP: 475 syslog(LOG_NOTICE, "restart by SIG"); 476 restart(); 477 break; 478 case SIGTERM: 479 syslog(LOG_NOTICE, "going down on signal %d", sig); 480 rc = -1; 481 goto out; 482 case SIGCHLD: 483 wait_child(); 484 break; 485 default: 486 (void) warn("unexpected signal(%d) received.", sig); 487 break; 488 } 489 } 490 rc = 0; 491 out: 492 return rc; 493} 494void 495proc_apmevent(int fd) 496{ 497 struct apm_event_info apmevent; 498 499 while (ioctl(fd, APMIO_NEXTEVENT, &apmevent) == 0) { 500 int status; 501 syslog(LOG_NOTICE, "apmevent %04x index %d\n", 502 apmevent.type, apmevent.index); 503 syslog(LOG_INFO, "apm event: %s", events[apmevent.type].name); 504 if (fork() == 0) { 505 status = exec_event_cmd(&events[apmevent.type]); 506 exit(status); 507 } 508 } 509} 510 511#define AC_POWER_STATE ((pw_info.ai_acline == 1) ? BATTERY_CHARGING :\ 512 BATTERY_DISCHARGING) 513 514void 515check_battery() 516{ 517 518 static int first_time=1, last_state; 519 int status; 520 521 struct apm_info pw_info; 522 struct battery_watch_event *p; 523 524 /* If we don't care, don't bother */ 525 if (battery_watch_list == NULL) 526 return; 527 528 if (first_time) { 529 if ( ioctl(apmnorm_fd, APMIO_GETINFO, &pw_info) < 0) 530 (void) err(1, "cannot check battery state."); 531/* 532 * This next statement isn't entirely true. The spec does not tie AC 533 * line state to battery charging or not, but this is a bit lazier to do. 534 */ 535 last_state = AC_POWER_STATE; 536 first_time = 0; 537 return; /* We can't process events, we have no baseline */ 538 } 539 540 /* 541 * XXX - should we do this a bunch of times and perform some sort 542 * of smoothing or correction? 543 */ 544 if ( ioctl(apmnorm_fd, APMIO_GETINFO, &pw_info) < 0) 545 (void) err(1, "cannot check battery state."); 546 547 /* 548 * If we're not in the state now that we were in last time, 549 * then it's a transition, which means we must clean out 550 * the event-caught state. 551 */ 552 if (last_state != AC_POWER_STATE) { 553 if (soft_power_state_change && fork() == 0) { 554 status = exec_event_cmd(&events[PMEV_POWERSTATECHANGE]); 555 exit(status); 556 } 557 last_state = AC_POWER_STATE; 558 for (p = battery_watch_list ; p!=NULL ; p = p -> next) 559 p->done = 0; 560 } 561 for (p = battery_watch_list ; p != NULL ; p = p -> next) 562 if (p -> direction == AC_POWER_STATE && 563 !(p -> done) && 564 ((p -> type == BATTERY_PERCENT && 565 p -> level == pw_info.ai_batt_life) || 566 (p -> type == BATTERY_MINUTES && 567 p -> level == (pw_info.ai_batt_time / 60)))) { 568 p -> done++; 569 if (verbose) 570 syslog(LOG_NOTICE, "Caught battery event: %s, %d%s", 571 (p -> direction == BATTERY_CHARGING)?"charging":"discharging", 572 p -> level, 573 (p -> type == BATTERY_PERCENT)?"%":" minutes"); 574 if (fork() == 0) { 575 status = exec_run_cmd(p -> cmdlist); 576 exit(status); 577 } 578 } 579} 580void 581event_loop(void) 582{ 583 int fdmax = 0; 584 struct sigaction nsa; 585 fd_set master_rfds; 586 sigset_t sigmask, osigmask; 587 588 FD_ZERO(&master_rfds); 589 FD_SET(apmctl_fd, &master_rfds); 590 fdmax = apmctl_fd > fdmax ? apmctl_fd : fdmax; 591 592 FD_SET(signal_fd[0], &master_rfds); 593 fdmax = signal_fd[0] > fdmax ? signal_fd[0] : fdmax; 594 595 memset(&nsa, 0, sizeof nsa); 596 nsa.sa_handler = enque_signal; 597 sigfillset(&nsa.sa_mask); 598 nsa.sa_flags = SA_RESTART; 599 sigaction(SIGHUP, &nsa, NULL); 600 sigaction(SIGCHLD, &nsa, NULL); 601 sigaction(SIGTERM, &nsa, NULL); 602 603 sigemptyset(&sigmask); 604 sigaddset(&sigmask, SIGHUP); 605 sigaddset(&sigmask, SIGCHLD); 606 sigaddset(&sigmask, SIGTERM); 607 sigprocmask(SIG_SETMASK, &sigmask, &osigmask); 608 609 while (1) { 610 fd_set rfds; 611 int res; 612 struct timeval to; 613 614 to.tv_sec = BATT_CHK_INTV; 615 to.tv_usec = 0; 616 617 memcpy(&rfds, &master_rfds, sizeof rfds); 618 sigprocmask(SIG_SETMASK, &osigmask, NULL); 619 if ((res=select(fdmax + 1, &rfds, 0, 0, &to)) < 0) { 620 if (errno != EINTR) 621 (void) err(1, "select"); 622 } 623 sigprocmask(SIG_SETMASK, &sigmask, NULL); 624 625 if (res == 0) { /* time to check the battery */ 626 check_battery(); 627 continue; 628 } 629 630 if (FD_ISSET(signal_fd[0], &rfds)) { 631 if (proc_signal(signal_fd[0]) < 0) 632 goto out; 633 } 634 635 if (FD_ISSET(apmctl_fd, &rfds)) 636 proc_apmevent(apmctl_fd); 637 } 638out: 639 return; 640} 641 642int 643main(int ac, char* av[]) 644{ 645 int ch; 646 int daemonize = 1; 647 char *prog; 648 int logopt = LOG_NDELAY | LOG_PID; 649 650 while ((ch = getopt(ac, av, "df:sv")) != EOF) { 651 switch (ch) { 652 case 'd': 653 daemonize = 0; 654 debug_level++; 655 break; 656 case 'f': 657 apmd_configfile = optarg; 658 break; 659 case 's': 660 soft_power_state_change = 1; 661 break; 662 case 'v': 663 verbose = 1; 664 break; 665 default: 666 (void) err(1, "unknown option `%c'", ch); 667 } 668 } 669 670 if (daemonize) 671 daemon(0, 0); 672 673#ifdef NICE_INCR 674 (void) nice(NICE_INCR); 675#endif 676 677 if (!daemonize) 678 logopt |= LOG_PERROR; 679 680 prog = strrchr(av[0], '/'); 681 openlog(prog ? prog+1 : av[0], logopt, LOG_DAEMON); 682 683 syslog(LOG_NOTICE, "start"); 684 685 if (pipe(signal_fd) < 0) 686 (void) err(1, "pipe"); 687 if (fcntl(signal_fd[0], F_SETFL, O_NONBLOCK) < 0) 688 (void) err(1, "fcntl"); 689 690 if ((apmnorm_fd = open(APM_NORM_DEVICEFILE, O_RDWR)) == -1) { 691 (void) err(1, "cannot open device file `%s'", APM_NORM_DEVICEFILE); 692 } 693 694 if ((apmctl_fd = open(APM_CTL_DEVICEFILE, O_RDWR)) == -1) { 695 (void) err(1, "cannot open device file `%s'", APM_CTL_DEVICEFILE); 696 } 697 698 restart(); 699 write_pid(); 700 event_loop(); 701 exit(EXIT_SUCCESS); 702} 703 704