1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * APM (Advanced Power Management) Event Dispatcher 5 * 6 * Copyright (c) 1999 Mitsuru IWASAKI <iwasaki@FreeBSD.org> 7 * Copyright (c) 1999 KOIE Hidetaka <koie@suri.co.jp> 8 * All rights reserved. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #ifndef lint 33 static const char rcsid[] = 34 "$FreeBSD$"; 35 #endif /* not lint */ 36 37 #include <sys/types.h> 38 #include <assert.h> 39 #include <bitstring.h> 40 #include <err.h> 41 #include <errno.h> 42 #include <fcntl.h> 43 #include <paths.h> 44 #include <signal.h> 45 #include <stdio.h> 46 #include <stdlib.h> 47 #include <string.h> 48 #include <syslog.h> 49 #include <unistd.h> 50 #include <sys/ioctl.h> 51 #include <sys/time.h> 52 #include <sys/wait.h> 53 #include <machine/apm_bios.h> 54 55 #include "apmd.h" 56 57 int debug_level = 0; 58 int verbose = 0; 59 int soft_power_state_change = 0; 60 const char *apmd_configfile = APMD_CONFIGFILE; 61 const char *apmd_pidfile = APMD_PIDFILE; 62 int apmctl_fd = -1, apmnorm_fd = -1; 63 64 /* 65 * table of event handlers 66 */ 67 #define EVENT_CONFIG_INITIALIZER(EV,R) { #EV, NULL, R }, 68 struct 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 */ 87 struct 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 */ 94 struct event_cmd * 95 event_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 */ 110 int 111 event_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 warn("cannot fork"); 120 break; 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 return status; 136 } 137 void 138 event_cmd_exec_dump(void *this, FILE *fp) 139 { 140 fprintf(fp, " \"%s\"", ((struct event_cmd_exec *)this)->line); 141 } 142 struct event_cmd * 143 event_cmd_exec_clone(void *this) 144 { 145 struct event_cmd_exec * newone = (struct event_cmd_exec *) event_cmd_default_clone(this); 146 struct event_cmd_exec * oldone = this; 147 148 newone->evcmd.next = NULL; 149 newone->evcmd.len = oldone->evcmd.len; 150 newone->evcmd.name = oldone->evcmd.name; 151 newone->evcmd.op = oldone->evcmd.op; 152 if ((newone->line = strdup(oldone->line)) == NULL) 153 err(1, "out of memory"); 154 return (struct event_cmd *) newone; 155 } 156 void 157 event_cmd_exec_free(void *this) 158 { 159 free(((struct event_cmd_exec *)this)->line); 160 } 161 struct event_cmd_op event_cmd_exec_ops = { 162 event_cmd_exec_act, 163 event_cmd_exec_dump, 164 event_cmd_exec_clone, 165 event_cmd_exec_free 166 }; 167 168 /* 169 * reject command 170 */ 171 int 172 event_cmd_reject_act(void *this __unused) 173 { 174 int rc = 0; 175 176 if (ioctl(apmctl_fd, APMIO_REJECTLASTREQ, NULL)) { 177 syslog(LOG_NOTICE, "fail to reject\n"); 178 rc = -1; 179 } 180 return rc; 181 } 182 struct 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 */ 192 struct event_cmd * 193 clone_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 err(1, "out of memory"); 201 q = q->next; 202 } 203 q->next = NULL; 204 return dummy.next; 205 } 206 void 207 free_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 } 217 int 218 register_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 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 } 251 int 252 register_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 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 */ 286 int 287 exec_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 */ 307 int 308 exec_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 event_cmd_reject_act(NULL); 316 } 317 return status; 318 } 319 320 /* 321 * read config file 322 */ 323 extern FILE * yyin; 324 extern int yydebug; 325 326 void 327 read_config(void) 328 { 329 int i; 330 331 if ((yyin = fopen(apmd_configfile, "r")) == NULL) { 332 err(1, "cannot open config file"); 333 } 334 335 #ifdef DEBUG 336 yydebug = debug_level; 337 #endif 338 339 if (yyparse() != 0) 340 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 err(1, "cannot enable event 0x%x", event_type); 350 } 351 } 352 } 353 } 354 355 void 356 dump_config(void) 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 391 void 392 destroy_config(void) 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 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 422 void 423 restart(void) 424 { 425 destroy_config(); 426 read_config(); 427 if (verbose) 428 dump_config(); 429 } 430 431 /* 432 * write pid file 433 */ 434 static void 435 write_pid(void) 436 { 437 FILE *fp = fopen(apmd_pidfile, "w"); 438 439 if (fp) { 440 fprintf(fp, "%ld\n", (long)getpid()); 441 fclose(fp); 442 } 443 } 444 445 /* 446 * handle signals 447 */ 448 static int signal_fd[2]; 449 450 void 451 enque_signal(int sig) 452 { 453 if (write(signal_fd[1], &sig, sizeof sig) != sizeof sig) 454 err(1, "cannot process signal."); 455 } 456 457 void 458 wait_child(void) 459 { 460 int status; 461 while (waitpid(-1, &status, WNOHANG) > 0) 462 ; 463 } 464 465 int 466 proc_signal(int fd) 467 { 468 int rc = 0; 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 return rc; 482 case SIGCHLD: 483 wait_child(); 484 break; 485 default: 486 warn("unexpected signal(%d) received.", sig); 487 break; 488 } 489 } 490 return rc; 491 } 492 void 493 proc_apmevent(int fd) 494 { 495 struct apm_event_info apmevent; 496 497 while (ioctl(fd, APMIO_NEXTEVENT, &apmevent) == 0) { 498 int status; 499 syslog(LOG_NOTICE, "apmevent %04x index %d\n", 500 apmevent.type, apmevent.index); 501 syslog(LOG_INFO, "apm event: %s", events[apmevent.type].name); 502 if (fork() == 0) { 503 status = exec_event_cmd(&events[apmevent.type]); 504 exit(status); 505 } 506 } 507 } 508 509 #define AC_POWER_STATE ((pw_info.ai_acline == 1) ? BATTERY_CHARGING :\ 510 BATTERY_DISCHARGING) 511 512 void 513 check_battery(void) 514 { 515 516 static int first_time=1, last_state; 517 int status; 518 519 struct apm_info pw_info; 520 struct battery_watch_event *p; 521 522 /* If we don't care, don't bother */ 523 if (battery_watch_list == NULL) 524 return; 525 526 if (first_time) { 527 if ( ioctl(apmnorm_fd, APMIO_GETINFO, &pw_info) < 0) 528 err(1, "cannot check battery state."); 529 /* 530 * This next statement isn't entirely true. The spec does not tie AC 531 * line state to battery charging or not, but this is a bit lazier to do. 532 */ 533 last_state = AC_POWER_STATE; 534 first_time = 0; 535 return; /* We can't process events, we have no baseline */ 536 } 537 538 /* 539 * XXX - should we do this a bunch of times and perform some sort 540 * of smoothing or correction? 541 */ 542 if ( ioctl(apmnorm_fd, APMIO_GETINFO, &pw_info) < 0) 543 err(1, "cannot check battery state."); 544 545 /* 546 * If we're not in the state now that we were in last time, 547 * then it's a transition, which means we must clean out 548 * the event-caught state. 549 */ 550 if (last_state != AC_POWER_STATE) { 551 if (soft_power_state_change && fork() == 0) { 552 status = exec_event_cmd(&events[PMEV_POWERSTATECHANGE]); 553 exit(status); 554 } 555 last_state = AC_POWER_STATE; 556 for (p = battery_watch_list ; p!=NULL ; p = p -> next) 557 p->done = 0; 558 } 559 for (p = battery_watch_list ; p != NULL ; p = p -> next) 560 if (p -> direction == AC_POWER_STATE && 561 !(p -> done) && 562 ((p -> type == BATTERY_PERCENT && 563 p -> level == (int)pw_info.ai_batt_life) || 564 (p -> type == BATTERY_MINUTES && 565 p -> level == (pw_info.ai_batt_time / 60)))) { 566 p -> done++; 567 if (verbose) 568 syslog(LOG_NOTICE, "Caught battery event: %s, %d%s", 569 (p -> direction == BATTERY_CHARGING)?"charging":"discharging", 570 p -> level, 571 (p -> type == BATTERY_PERCENT)?"%":" minutes"); 572 if (fork() == 0) { 573 status = exec_run_cmd(p -> cmdlist); 574 exit(status); 575 } 576 } 577 } 578 void 579 event_loop(void) 580 { 581 int fdmax = 0; 582 struct sigaction nsa; 583 fd_set master_rfds; 584 sigset_t sigmask, osigmask; 585 586 FD_ZERO(&master_rfds); 587 FD_SET(apmctl_fd, &master_rfds); 588 fdmax = apmctl_fd > fdmax ? apmctl_fd : fdmax; 589 590 FD_SET(signal_fd[0], &master_rfds); 591 fdmax = signal_fd[0] > fdmax ? signal_fd[0] : fdmax; 592 593 memset(&nsa, 0, sizeof nsa); 594 nsa.sa_handler = enque_signal; 595 sigfillset(&nsa.sa_mask); 596 nsa.sa_flags = SA_RESTART; 597 sigaction(SIGHUP, &nsa, NULL); 598 sigaction(SIGCHLD, &nsa, NULL); 599 sigaction(SIGTERM, &nsa, NULL); 600 601 sigemptyset(&sigmask); 602 sigaddset(&sigmask, SIGHUP); 603 sigaddset(&sigmask, SIGCHLD); 604 sigaddset(&sigmask, SIGTERM); 605 sigprocmask(SIG_SETMASK, &sigmask, &osigmask); 606 607 while (1) { 608 fd_set rfds; 609 int res; 610 struct timeval to; 611 612 to.tv_sec = BATT_CHK_INTV; 613 to.tv_usec = 0; 614 615 memcpy(&rfds, &master_rfds, sizeof rfds); 616 sigprocmask(SIG_SETMASK, &osigmask, NULL); 617 if ((res=select(fdmax + 1, &rfds, 0, 0, &to)) < 0) { 618 if (errno != EINTR) 619 err(1, "select"); 620 } 621 sigprocmask(SIG_SETMASK, &sigmask, NULL); 622 623 if (res == 0) { /* time to check the battery */ 624 check_battery(); 625 continue; 626 } 627 628 if (FD_ISSET(signal_fd[0], &rfds)) { 629 if (proc_signal(signal_fd[0]) < 0) 630 return; 631 } 632 633 if (FD_ISSET(apmctl_fd, &rfds)) 634 proc_apmevent(apmctl_fd); 635 } 636 } 637 638 int 639 main(int ac, char* av[]) 640 { 641 int ch; 642 int daemonize = 1; 643 char *prog; 644 int logopt = LOG_NDELAY | LOG_PID; 645 646 while ((ch = getopt(ac, av, "df:sv")) != -1) { 647 switch (ch) { 648 case 'd': 649 daemonize = 0; 650 debug_level++; 651 break; 652 case 'f': 653 apmd_configfile = optarg; 654 break; 655 case 's': 656 soft_power_state_change = 1; 657 break; 658 case 'v': 659 verbose = 1; 660 break; 661 default: 662 err(1, "unknown option `%c'", ch); 663 } 664 } 665 666 if (daemonize) 667 daemon(0, 0); 668 669 #ifdef NICE_INCR 670 nice(NICE_INCR); 671 #endif 672 673 if (!daemonize) 674 logopt |= LOG_PERROR; 675 676 prog = strrchr(av[0], '/'); 677 openlog(prog ? prog+1 : av[0], logopt, LOG_DAEMON); 678 679 syslog(LOG_NOTICE, "start"); 680 681 if (pipe(signal_fd) < 0) 682 err(1, "pipe"); 683 if (fcntl(signal_fd[0], F_SETFL, O_NONBLOCK) < 0) 684 err(1, "fcntl"); 685 686 if ((apmnorm_fd = open(APM_NORM_DEVICEFILE, O_RDWR)) == -1) { 687 err(1, "cannot open device file `%s'", APM_NORM_DEVICEFILE); 688 } 689 690 if (fcntl(apmnorm_fd, F_SETFD, 1) == -1) { 691 err(1, "cannot set close-on-exec flag for device file '%s'", APM_NORM_DEVICEFILE); 692 } 693 694 if ((apmctl_fd = open(APM_CTL_DEVICEFILE, O_RDWR)) == -1) { 695 err(1, "cannot open device file `%s'", APM_CTL_DEVICEFILE); 696 } 697 698 if (fcntl(apmctl_fd, F_SETFD, 1) == -1) { 699 err(1, "cannot set close-on-exec flag for device file '%s'", APM_CTL_DEVICEFILE); 700 } 701 702 restart(); 703 write_pid(); 704 event_loop(); 705 exit(EXIT_SUCCESS); 706 } 707 708