apmd.c revision 166509
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 166509 2007-02-05 07:35:23Z kevlo $"; 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(POWERSTATECHANGE, 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 signal(SIGHUP, SIG_DFL); 124 signal(SIGCHLD, SIG_DFL); 125 signal(SIGTERM, SIG_DFL); 126 execl(_PATH_BSHELL, "sh", "-c", p->line, (char *)NULL); 127 _exit(127); 128 default: 129 /* parent process */ 130 do { 131 pid = waitpid(pid, &status, 0); 132 } while (pid == -1 && errno == EINTR); 133 break; 134 } 135 out: 136 return status; 137} 138void 139event_cmd_exec_dump(void *this, FILE *fp) 140{ 141 fprintf(fp, " \"%s\"", ((struct event_cmd_exec *)this)->line); 142} 143struct event_cmd * 144event_cmd_exec_clone(void *this) 145{ 146 struct event_cmd_exec * newone = (struct event_cmd_exec *) event_cmd_default_clone(this); 147 struct event_cmd_exec * oldone = this; 148 149 newone->evcmd.next = NULL; 150 newone->evcmd.len = oldone->evcmd.len; 151 newone->evcmd.name = oldone->evcmd.name; 152 newone->evcmd.op = oldone->evcmd.op; 153 if ((newone->line = strdup(oldone->line)) == NULL) 154 err(1, "out of memory"); 155 return (struct event_cmd *) newone; 156} 157void 158event_cmd_exec_free(void *this) 159{ 160 free(((struct event_cmd_exec *)this)->line); 161} 162struct event_cmd_op event_cmd_exec_ops = { 163 event_cmd_exec_act, 164 event_cmd_exec_dump, 165 event_cmd_exec_clone, 166 event_cmd_exec_free 167}; 168 169/* 170 * reject commad 171 */ 172int 173event_cmd_reject_act(void *this) 174{ 175 int rc = -1; 176 177 if (ioctl(apmctl_fd, APMIO_REJECTLASTREQ, NULL)) { 178 syslog(LOG_NOTICE, "fail to reject\n"); 179 goto out; 180 } 181 rc = 0; 182 out: 183 return rc; 184} 185struct event_cmd_op event_cmd_reject_ops = { 186 event_cmd_reject_act, 187 NULL, 188 event_cmd_default_clone, 189 NULL 190}; 191 192/* 193 * manipulate event_config 194 */ 195struct event_cmd * 196clone_event_cmd_list(struct event_cmd *p) 197{ 198 struct event_cmd dummy; 199 struct event_cmd *q = &dummy; 200 for ( ;p; p = p->next) { 201 assert(p->op->clone); 202 if ((q->next = p->op->clone(p)) == NULL) 203 (void) err(1, "out of memory"); 204 q = q->next; 205 } 206 q->next = NULL; 207 return dummy.next; 208} 209void 210free_event_cmd_list(struct event_cmd *p) 211{ 212 struct event_cmd * q; 213 for ( ; p ; p = q) { 214 q = p->next; 215 if (p->op->free) 216 p->op->free(p); 217 free(p); 218 } 219} 220int 221register_battery_handlers( 222 int level, int direction, 223 struct event_cmd *cmdlist) 224{ 225 /* 226 * level is negative if it's in "minutes", non-negative if 227 * percentage. 228 * 229 * direction =1 means we care about this level when charging, 230 * direction =-1 means we care about it when discharging. 231 */ 232 if (level>100) /* percentage > 100 */ 233 return -1; 234 if (abs(direction) != 1) /* nonsense direction value */ 235 return -1; 236 237 if (cmdlist) { 238 struct battery_watch_event *we; 239 240 if ((we = malloc(sizeof(struct battery_watch_event))) == NULL) 241 (void) err(1, "out of memory"); 242 243 we->next = battery_watch_list; /* starts at NULL */ 244 battery_watch_list = we; 245 we->level = abs(level); 246 we->type = (level<0)?BATTERY_MINUTES:BATTERY_PERCENT; 247 we->direction = (direction<0)?BATTERY_DISCHARGING: 248 BATTERY_CHARGING; 249 we->done = 0; 250 we->cmdlist = clone_event_cmd_list(cmdlist); 251 } 252 return 0; 253} 254int 255register_apm_event_handlers( 256 bitstr_t bit_decl(evlist, EVENT_MAX), 257 struct event_cmd *cmdlist) 258{ 259 if (cmdlist) { 260 bitstr_t bit_decl(tmp, EVENT_MAX); 261 memcpy(&tmp, evlist, bitstr_size(EVENT_MAX)); 262 263 for (;;) { 264 int n; 265 struct event_cmd *p; 266 struct event_cmd *q; 267 bit_ffs(tmp, EVENT_MAX, &n); 268 if (n < 0) 269 break; 270 p = events[n].cmdlist; 271 if ((q = clone_event_cmd_list(cmdlist)) == NULL) 272 (void) err(1, "out of memory"); 273 if (p) { 274 while (p->next != NULL) 275 p = p->next; 276 p->next = q; 277 } else { 278 events[n].cmdlist = q; 279 } 280 bit_clear(tmp, n); 281 } 282 } 283 return 0; 284} 285 286/* 287 * execute command 288 */ 289int 290exec_run_cmd(struct event_cmd *p) 291{ 292 int status = 0; 293 294 for (; p; p = p->next) { 295 assert(p->op->act); 296 if (verbose) 297 syslog(LOG_INFO, "action: %s", p->name); 298 status = p->op->act(p); 299 if (status) { 300 syslog(LOG_NOTICE, "command finished with %d\n", status); 301 break; 302 } 303 } 304 return status; 305} 306 307/* 308 * execute command -- the event version 309 */ 310int 311exec_event_cmd(struct event_config *ev) 312{ 313 int status = 0; 314 315 status = exec_run_cmd(ev->cmdlist); 316 if (status && ev->rejectable) { 317 syslog(LOG_ERR, "canceled"); 318 (void) event_cmd_reject_act(NULL); 319 } 320 return status; 321} 322 323/* 324 * read config file 325 */ 326extern FILE * yyin; 327extern int yydebug; 328 329void 330read_config(void) 331{ 332 int i; 333 334 if ((yyin = fopen(apmd_configfile, "r")) == NULL) { 335 (void) err(1, "cannot open config file"); 336 } 337 338#ifdef DEBUG 339 yydebug = debug_level; 340#endif 341 342 if (yyparse() != 0) 343 (void) err(1, "cannot parse config file"); 344 345 fclose(yyin); 346 347 /* enable events */ 348 for (i = 0; i < EVENT_MAX; i++) { 349 if (events[i].cmdlist) { 350 u_int event_type = i; 351 if (write(apmctl_fd, &event_type, sizeof(u_int)) == -1) { 352 (void) err(1, "cannot enable event 0x%x", event_type); 353 } 354 } 355 } 356} 357 358void 359dump_config() 360{ 361 int i; 362 struct battery_watch_event *q; 363 364 for (i = 0; i < EVENT_MAX; i++) { 365 struct event_cmd * p; 366 if ((p = events[i].cmdlist)) { 367 fprintf(stderr, "apm_event %s {\n", events[i].name); 368 for ( ; p ; p = p->next) { 369 fprintf(stderr, "\t%s", p->name); 370 if (p->op->dump) 371 p->op->dump(p, stderr); 372 fprintf(stderr, ";\n"); 373 } 374 fprintf(stderr, "}\n"); 375 } 376 } 377 for (q = battery_watch_list ; q != NULL ; q = q -> next) { 378 struct event_cmd * p; 379 fprintf(stderr, "apm_battery %d%s %s {\n", 380 q -> level, 381 (q -> type == BATTERY_PERCENT)?"%":"m", 382 (q -> direction == BATTERY_CHARGING)?"charging": 383 "discharging"); 384 for ( p = q -> cmdlist; p ; p = p->next) { 385 fprintf(stderr, "\t%s", p->name); 386 if (p->op->dump) 387 p->op->dump(p, stderr); 388 fprintf(stderr, ";\n"); 389 } 390 fprintf(stderr, "}\n"); 391 } 392} 393 394void 395destroy_config() 396{ 397 int i; 398 struct battery_watch_event *q; 399 400 /* disable events */ 401 for (i = 0; i < EVENT_MAX; i++) { 402 if (events[i].cmdlist) { 403 u_int event_type = i; 404 if (write(apmctl_fd, &event_type, sizeof(u_int)) == -1) { 405 (void) err(1, "cannot disable event 0x%x", event_type); 406 } 407 } 408 } 409 410 for (i = 0; i < EVENT_MAX; i++) { 411 struct event_cmd * p; 412 if ((p = events[i].cmdlist)) 413 free_event_cmd_list(p); 414 events[i].cmdlist = NULL; 415 } 416 417 for( ; battery_watch_list; battery_watch_list = battery_watch_list -> next) { 418 free_event_cmd_list(battery_watch_list->cmdlist); 419 q = battery_watch_list->next; 420 free(battery_watch_list); 421 battery_watch_list = q; 422 } 423} 424 425void 426restart() 427{ 428 destroy_config(); 429 read_config(); 430 if (verbose) 431 dump_config(); 432} 433 434/* 435 * write pid file 436 */ 437static void 438write_pid() 439{ 440 FILE *fp = fopen(apmd_pidfile, "w"); 441 442 if (fp) { 443 fprintf(fp, "%d\n", getpid()); 444 fclose(fp); 445 } 446} 447 448/* 449 * handle signals 450 */ 451static int signal_fd[2]; 452 453void 454enque_signal(int sig) 455{ 456 if (write(signal_fd[1], &sig, sizeof sig) != sizeof sig) 457 (void) err(1, "cannot process signal."); 458} 459 460void 461wait_child() 462{ 463 int status; 464 while (waitpid(-1, &status, WNOHANG) > 0) 465 ; 466} 467 468int 469proc_signal(int fd) 470{ 471 int rc = -1; 472 int sig; 473 474 while (read(fd, &sig, sizeof sig) == sizeof sig) { 475 syslog(LOG_INFO, "caught signal: %d", sig); 476 switch (sig) { 477 case SIGHUP: 478 syslog(LOG_NOTICE, "restart by SIG"); 479 restart(); 480 break; 481 case SIGTERM: 482 syslog(LOG_NOTICE, "going down on signal %d", sig); 483 rc = -1; 484 goto out; 485 case SIGCHLD: 486 wait_child(); 487 break; 488 default: 489 (void) warn("unexpected signal(%d) received.", sig); 490 break; 491 } 492 } 493 rc = 0; 494 out: 495 return rc; 496} 497void 498proc_apmevent(int fd) 499{ 500 struct apm_event_info apmevent; 501 502 while (ioctl(fd, APMIO_NEXTEVENT, &apmevent) == 0) { 503 int status; 504 syslog(LOG_NOTICE, "apmevent %04x index %d\n", 505 apmevent.type, apmevent.index); 506 syslog(LOG_INFO, "apm event: %s", events[apmevent.type].name); 507 if (fork() == 0) { 508 status = exec_event_cmd(&events[apmevent.type]); 509 exit(status); 510 } 511 } 512} 513 514#define AC_POWER_STATE ((pw_info.ai_acline == 1) ? BATTERY_CHARGING :\ 515 BATTERY_DISCHARGING) 516 517void 518check_battery() 519{ 520 521 static int first_time=1, last_state; 522 int status; 523 524 struct apm_info pw_info; 525 struct battery_watch_event *p; 526 527 /* If we don't care, don't bother */ 528 if (battery_watch_list == NULL) 529 return; 530 531 if (first_time) { 532 if ( ioctl(apmnorm_fd, APMIO_GETINFO, &pw_info) < 0) 533 (void) err(1, "cannot check battery state."); 534/* 535 * This next statement isn't entirely true. The spec does not tie AC 536 * line state to battery charging or not, but this is a bit lazier to do. 537 */ 538 last_state = AC_POWER_STATE; 539 first_time = 0; 540 return; /* We can't process events, we have no baseline */ 541 } 542 543 /* 544 * XXX - should we do this a bunch of times and perform some sort 545 * of smoothing or correction? 546 */ 547 if ( ioctl(apmnorm_fd, APMIO_GETINFO, &pw_info) < 0) 548 (void) err(1, "cannot check battery state."); 549 550 /* 551 * If we're not in the state now that we were in last time, 552 * then it's a transition, which means we must clean out 553 * the event-caught state. 554 */ 555 if (last_state != AC_POWER_STATE) { 556 if (soft_power_state_change && fork() == 0) { 557 status = exec_event_cmd(&events[PMEV_POWERSTATECHANGE]); 558 exit(status); 559 } 560 last_state = AC_POWER_STATE; 561 for (p = battery_watch_list ; p!=NULL ; p = p -> next) 562 p->done = 0; 563 } 564 for (p = battery_watch_list ; p != NULL ; p = p -> next) 565 if (p -> direction == AC_POWER_STATE && 566 !(p -> done) && 567 ((p -> type == BATTERY_PERCENT && 568 p -> level == pw_info.ai_batt_life) || 569 (p -> type == BATTERY_MINUTES && 570 p -> level == (pw_info.ai_batt_time / 60)))) { 571 p -> done++; 572 if (verbose) 573 syslog(LOG_NOTICE, "Caught battery event: %s, %d%s", 574 (p -> direction == BATTERY_CHARGING)?"charging":"discharging", 575 p -> level, 576 (p -> type == BATTERY_PERCENT)?"%":" minutes"); 577 if (fork() == 0) { 578 status = exec_run_cmd(p -> cmdlist); 579 exit(status); 580 } 581 } 582} 583void 584event_loop(void) 585{ 586 int fdmax = 0; 587 struct sigaction nsa; 588 fd_set master_rfds; 589 sigset_t sigmask, osigmask; 590 591 FD_ZERO(&master_rfds); 592 FD_SET(apmctl_fd, &master_rfds); 593 fdmax = apmctl_fd > fdmax ? apmctl_fd : fdmax; 594 595 FD_SET(signal_fd[0], &master_rfds); 596 fdmax = signal_fd[0] > fdmax ? signal_fd[0] : fdmax; 597 598 memset(&nsa, 0, sizeof nsa); 599 nsa.sa_handler = enque_signal; 600 sigfillset(&nsa.sa_mask); 601 nsa.sa_flags = SA_RESTART; 602 sigaction(SIGHUP, &nsa, NULL); 603 sigaction(SIGCHLD, &nsa, NULL); 604 sigaction(SIGTERM, &nsa, NULL); 605 606 sigemptyset(&sigmask); 607 sigaddset(&sigmask, SIGHUP); 608 sigaddset(&sigmask, SIGCHLD); 609 sigaddset(&sigmask, SIGTERM); 610 sigprocmask(SIG_SETMASK, &sigmask, &osigmask); 611 612 while (1) { 613 fd_set rfds; 614 int res; 615 struct timeval to; 616 617 to.tv_sec = BATT_CHK_INTV; 618 to.tv_usec = 0; 619 620 memcpy(&rfds, &master_rfds, sizeof rfds); 621 sigprocmask(SIG_SETMASK, &osigmask, NULL); 622 if ((res=select(fdmax + 1, &rfds, 0, 0, &to)) < 0) { 623 if (errno != EINTR) 624 (void) err(1, "select"); 625 } 626 sigprocmask(SIG_SETMASK, &sigmask, NULL); 627 628 if (res == 0) { /* time to check the battery */ 629 check_battery(); 630 continue; 631 } 632 633 if (FD_ISSET(signal_fd[0], &rfds)) { 634 if (proc_signal(signal_fd[0]) < 0) 635 goto out; 636 } 637 638 if (FD_ISSET(apmctl_fd, &rfds)) 639 proc_apmevent(apmctl_fd); 640 } 641out: 642 return; 643} 644 645int 646main(int ac, char* av[]) 647{ 648 int ch; 649 int daemonize = 1; 650 char *prog; 651 int logopt = LOG_NDELAY | LOG_PID; 652 653 while ((ch = getopt(ac, av, "df:sv")) != -1) { 654 switch (ch) { 655 case 'd': 656 daemonize = 0; 657 debug_level++; 658 break; 659 case 'f': 660 apmd_configfile = optarg; 661 break; 662 case 's': 663 soft_power_state_change = 1; 664 break; 665 case 'v': 666 verbose = 1; 667 break; 668 default: 669 (void) err(1, "unknown option `%c'", ch); 670 } 671 } 672 673 if (daemonize) 674 daemon(0, 0); 675 676#ifdef NICE_INCR 677 (void) nice(NICE_INCR); 678#endif 679 680 if (!daemonize) 681 logopt |= LOG_PERROR; 682 683 prog = strrchr(av[0], '/'); 684 openlog(prog ? prog+1 : av[0], logopt, LOG_DAEMON); 685 686 syslog(LOG_NOTICE, "start"); 687 688 if (pipe(signal_fd) < 0) 689 (void) err(1, "pipe"); 690 if (fcntl(signal_fd[0], F_SETFL, O_NONBLOCK) < 0) 691 (void) err(1, "fcntl"); 692 693 if ((apmnorm_fd = open(APM_NORM_DEVICEFILE, O_RDWR)) == -1) { 694 (void) err(1, "cannot open device file `%s'", APM_NORM_DEVICEFILE); 695 } 696 697 if (fcntl(apmnorm_fd, F_SETFD, 1) == -1) { 698 (void) err(1, "cannot set close-on-exec flag for device file '%s'", APM_NORM_DEVICEFILE); 699 } 700 701 if ((apmctl_fd = open(APM_CTL_DEVICEFILE, O_RDWR)) == -1) { 702 (void) err(1, "cannot open device file `%s'", APM_CTL_DEVICEFILE); 703 } 704 705 if (fcntl(apmctl_fd, F_SETFD, 1) == -1) { 706 (void) err(1, "cannot set close-on-exec flag for device file '%s'", APM_CTL_DEVICEFILE); 707 } 708 709 restart(); 710 write_pid(); 711 event_loop(); 712 exit(EXIT_SUCCESS); 713} 714 715