1/* $OpenBSD: apmd.c,v 1.112 2023/04/27 10:51:27 kn Exp $ */ 2 3/* 4 * Copyright (c) 1995, 1996 John T. Kohl 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. The name of the author may not be used to endorse or promote products 16 * derived from this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR 19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 22 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 26 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 27 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 * POSSIBILITY OF SUCH DAMAGE. 29 * 30 */ 31 32#include <sys/stat.h> 33#include <sys/ioctl.h> 34#include <sys/socket.h> 35#include <sys/un.h> 36#include <sys/wait.h> 37#include <sys/event.h> 38#include <sys/time.h> 39#include <sys/sysctl.h> 40#include <assert.h> 41#include <stdarg.h> 42#include <stdio.h> 43#include <syslog.h> 44#include <fcntl.h> 45#include <unistd.h> 46#include <stdlib.h> 47#include <string.h> 48#include <signal.h> 49#include <errno.h> 50#include <err.h> 51#include <limits.h> 52#include <machine/apmvar.h> 53 54#include "pathnames.h" 55#include "apm-proto.h" 56 57#define AUTO_SUSPEND 1 58#define AUTO_HIBERNATE 2 59 60int debug = 0; 61 62extern char *__progname; 63 64void usage(void); 65int power_status(int fd, int force, struct apm_power_info *pinfo); 66int bind_socket(const char *sn); 67void handle_client(int sock_fd, int ctl_fd); 68int suspend(int ctl_fd); 69int stand_by(int ctl_fd); 70int hibernate(int ctl_fd); 71void resumed(int ctl_fd); 72void setperfpolicy(char *policy); 73void sigexit(int signo); 74void do_etc_file(const char *file); 75void error(const char *fmt, const char *arg); 76void set_driver_messages(int fd, int mode); 77 78void 79sigexit(int signo) 80{ 81 _exit(1); 82} 83 84void 85logmsg(int prio, const char *msg, ...) 86{ 87 va_list ap; 88 89 va_start(ap, msg); 90 if (debug) { 91 vfprintf(stderr, msg, ap); 92 fprintf(stderr, "\n"); 93 } else { 94 vsyslog(prio, msg, ap); 95 } 96 va_end(ap); 97} 98 99void 100usage(void) 101{ 102 fprintf(stderr, 103 "usage: %s [-AadHLs] [-f devname] [-S sockname] [-t seconds] " 104 "[-Z percent] [-z percent]\n", __progname); 105 exit(1); 106} 107 108void 109error(const char *fmt, const char *arg) 110{ 111 char buf[128]; 112 113 if (debug) 114 err(1, fmt, arg); 115 else { 116 strlcpy(buf, fmt, sizeof(buf)); 117 strlcat(buf, ": %m", sizeof(buf)); 118 syslog(LOG_ERR, buf, arg); 119 exit(1); 120 } 121} 122 123 124/* 125 * tell the driver if it should display messages or not. 126 */ 127void 128set_driver_messages(int fd, int mode) 129{ 130 if (ioctl(fd, APM_IOC_PRN_CTL, &mode) == -1) 131 logmsg(LOG_DEBUG, "can't disable driver messages, error: %s", 132 strerror(errno)); 133} 134 135int 136power_status(int fd, int force, struct apm_power_info *pinfo) 137{ 138 struct apm_power_info bstate; 139 static struct apm_power_info last; 140 int acon = 0, priority = LOG_NOTICE; 141 142 if (fd == -1) { 143 if (pinfo) { 144 bstate.battery_state = 255; 145 bstate.ac_state = 255; 146 bstate.battery_life = 0; 147 bstate.minutes_left = -1; 148 *pinfo = bstate; 149 } 150 151 return 0; 152 } 153 154 if (ioctl(fd, APM_IOC_GETPOWER, &bstate) == 0) { 155 /* various conditions under which we report status: something changed 156 * enough since last report, or asked to force a print */ 157 if (bstate.ac_state == APM_AC_ON) 158 acon = 1; 159 if (bstate.battery_state == APM_BATT_CRITICAL && 160 bstate.battery_state != last.battery_state) 161 priority = LOG_EMERG; 162 if (force || 163 bstate.ac_state != last.ac_state || 164 bstate.battery_state != last.battery_state || 165 ((bstate.battery_state != APM_BATT_CHARGING) && 166 (bstate.minutes_left && bstate.minutes_left < 15)) || 167 abs(bstate.battery_life - last.battery_life) >= 10) { 168 if ((int)bstate.minutes_left > 0) 169 logmsg(priority, "battery status: %s. " 170 "external power status: %s. " 171 "estimated battery life %d%% " 172 "(%u minutes %s time estimate)", 173 battstate(bstate.battery_state), 174 ac_state(bstate.ac_state), 175 bstate.battery_life, 176 bstate.minutes_left, 177 (bstate.battery_state == APM_BATT_CHARGING) 178 ? "recharge" : "life"); 179 else 180 logmsg(priority, "battery status: %s. " 181 "external power status: %s. " 182 "estimated battery life %d%%", 183 battstate(bstate.battery_state), 184 ac_state(bstate.ac_state), 185 bstate.battery_life); 186 last = bstate; 187 } 188 if (pinfo) 189 *pinfo = bstate; 190 } else 191 logmsg(LOG_ERR, "cannot fetch power status: %s", strerror(errno)); 192 193 return acon; 194} 195 196int 197bind_socket(const char *sockname) 198{ 199 struct sockaddr_un s_un; 200 mode_t old_umask; 201 int sock; 202 203 sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); 204 if (sock == -1) 205 error("cannot create local socket", NULL); 206 207 s_un.sun_family = AF_UNIX; 208 strlcpy(s_un.sun_path, sockname, sizeof(s_un.sun_path)); 209 210 /* remove it if present, we're moving in */ 211 (void) remove(sockname); 212 213 old_umask = umask(077); 214 if (bind(sock, (struct sockaddr *)&s_un, sizeof(s_un)) == -1) 215 error("cannot bind on APM socket", NULL); 216 umask(old_umask); 217 if (chmod(sockname, 0660) == -1 || chown(sockname, 0, 0) == -1) 218 error("cannot set socket mode/owner/group to 660/0/0", NULL); 219 220 listen(sock, 1); 221 222 return sock; 223} 224 225void 226handle_client(int sock_fd, int ctl_fd) 227{ 228 /* accept a handle from the client, process it, then clean up */ 229 int cli_fd; 230 struct sockaddr_un from; 231 socklen_t fromlen; 232 struct apm_command cmd; 233 struct apm_reply reply; 234 int perfpol_mib[] = { CTL_HW, HW_PERFPOLICY }; 235 char perfpol[32]; 236 size_t perfpol_sz = sizeof(perfpol); 237 int cpuspeed_mib[] = { CTL_HW, HW_CPUSPEED }; 238 int cpuspeed = 0; 239 size_t cpuspeed_sz = sizeof(cpuspeed); 240 241 fromlen = sizeof(from); 242 cli_fd = accept(sock_fd, (struct sockaddr *)&from, &fromlen); 243 if (cli_fd == -1) { 244 logmsg(LOG_INFO, "client accept failure: %s", strerror(errno)); 245 return; 246 } 247 248 if (recv(cli_fd, &cmd, sizeof(cmd), 0) != sizeof(cmd)) { 249 (void) close(cli_fd); 250 logmsg(LOG_INFO, "client size botch"); 251 return; 252 } 253 254 if (cmd.vno != APMD_VNO) { 255 close(cli_fd); /* terminate client */ 256 /* no error message, just drop it. */ 257 return; 258 } 259 260 bzero(&reply, sizeof(reply)); 261 power_status(ctl_fd, 0, &reply.batterystate); 262 switch (cmd.action) { 263 case SUSPEND: 264 reply.newstate = SUSPENDING; 265 reply.error = suspend(ctl_fd); 266 break; 267 case STANDBY: 268 reply.newstate = STANDING_BY; 269 reply.error = stand_by(ctl_fd); 270 break; 271 case HIBERNATE: 272 reply.newstate = HIBERNATING; 273 reply.error = hibernate(ctl_fd); 274 break; 275 case SETPERF_LOW: 276 reply.newstate = NORMAL; 277 logmsg(LOG_NOTICE, "setting hw.perfpolicy to low"); 278 setperfpolicy("low"); 279 break; 280 case SETPERF_HIGH: 281 reply.newstate = NORMAL; 282 logmsg(LOG_NOTICE, "setting hw.perfpolicy to high"); 283 setperfpolicy("high"); 284 break; 285 case SETPERF_AUTO: 286 reply.newstate = NORMAL; 287 logmsg(LOG_NOTICE, "setting hw.perfpolicy to auto"); 288 setperfpolicy("auto"); 289 break; 290 default: 291 reply.newstate = NORMAL; 292 break; 293 } 294 295 reply.perfmode = PERF_NONE; 296 if (sysctl(perfpol_mib, 2, perfpol, &perfpol_sz, NULL, 0) == -1) 297 logmsg(LOG_INFO, "cannot read hw.perfpolicy"); 298 else { 299 if (strcmp(perfpol, "manual") == 0 || 300 strcmp(perfpol, "high") == 0) { 301 reply.perfmode = PERF_MANUAL; 302 } else if (strcmp(perfpol, "auto") == 0) 303 reply.perfmode = PERF_AUTO; 304 } 305 306 if (sysctl(cpuspeed_mib, 2, &cpuspeed, &cpuspeed_sz, NULL, 0) == -1) { 307 logmsg(LOG_INFO, "cannot read hw.cpuspeed"); 308 cpuspeed = 0; 309 } 310 reply.cpuspeed = cpuspeed; 311 reply.vno = APMD_VNO; 312 if (send(cli_fd, &reply, sizeof(reply), 0) != sizeof(reply)) 313 logmsg(LOG_INFO, "reply to client botched"); 314 close(cli_fd); 315} 316 317/* 318 * Refresh the random file read by the bootblocks, and remove the +t bit 319 * which the bootblock use to track "reuse of the file". 320 */ 321void 322fixrandom(void) 323{ 324 char buf[512]; 325 int fd; 326 327 fd = open("/etc/random.seed", O_WRONLY); 328 if (fd != -1) { 329 arc4random_buf(buf, sizeof buf); 330 write(fd, buf, sizeof buf); 331 fchmod(fd, 0600); 332 close(fd); 333 } 334} 335 336int 337suspend(int ctl_fd) 338{ 339 int error = 0; 340 341 logmsg(LOG_NOTICE, "system suspending"); 342 power_status(ctl_fd, 1, NULL); 343 fixrandom(); 344 do_etc_file(_PATH_APM_ETC_SUSPEND); 345 sync(); 346 sleep(1); 347 348 if (ioctl(ctl_fd, APM_IOC_SUSPEND, 0) == -1) { 349 error = errno; 350 logmsg(LOG_WARNING, "%s: %s", __func__, strerror(errno)); 351 } 352 353 return error; 354} 355 356int 357stand_by(int ctl_fd) 358{ 359 int error = 0; 360 361 logmsg(LOG_NOTICE, "system entering standby"); 362 power_status(ctl_fd, 1, NULL); 363 fixrandom(); 364 do_etc_file(_PATH_APM_ETC_STANDBY); 365 sync(); 366 sleep(1); 367 368 if (ioctl(ctl_fd, APM_IOC_STANDBY, 0) == -1) { 369 error = errno; 370 logmsg(LOG_WARNING, "%s: %s", __func__, strerror(errno)); 371 } 372 373 return error; 374} 375 376int 377hibernate(int ctl_fd) 378{ 379 int error = 0; 380 381 logmsg(LOG_NOTICE, "system hibernating"); 382 power_status(ctl_fd, 1, NULL); 383 fixrandom(); 384 do_etc_file(_PATH_APM_ETC_HIBERNATE); 385 sync(); 386 sleep(1); 387 388 if (ioctl(ctl_fd, APM_IOC_HIBERNATE, 0) == -1) { 389 error = errno; 390 logmsg(LOG_WARNING, "%s: %s", __func__, strerror(errno)); 391 } 392 393 return error; 394} 395 396void 397resumed(int ctl_fd) 398{ 399 do_etc_file(_PATH_APM_ETC_RESUME); 400 logmsg(LOG_NOTICE, "system resumed from sleep"); 401 power_status(ctl_fd, 1, NULL); 402} 403 404#define TIMO (10*60) /* 10 minutes */ 405#define AUTOACTION_GRACE_PERIOD (60) /* 1mn after resume */ 406 407int 408main(int argc, char *argv[]) 409{ 410 const char *fname = _PATH_APM_CTLDEV; 411 int ctl_fd, sock_fd, ch, suspends, standbys, hibernates, resumes; 412 int autoaction = 0, autoaction_inflight = 0; 413 int autolimit = 0; 414 int statonly = 0; 415 int powerstatus = 0, powerbak = 0, powerchange = 0; 416 int noacsleep = 0; 417 struct timespec ts = {TIMO, 0}, sts = {0, 0}; 418 struct timespec last_resume = { 0, 0 }; 419 struct apm_power_info pinfo; 420 const char *sockname = _PATH_APM_SOCKET; 421 const char *errstr; 422 int kq, nchanges; 423 struct kevent ev[2]; 424 int doperf = PERF_NONE; 425 426 while ((ch = getopt(argc, argv, "aACdHLsf:t:S:z:Z:")) != -1) 427 switch(ch) { 428 case 'a': 429 noacsleep = 1; 430 break; 431 case 'd': 432 debug = 1; 433 break; 434 case 'f': 435 fname = optarg; 436 break; 437 case 'S': 438 sockname = optarg; 439 break; 440 case 't': 441 ts.tv_sec = strtonum(optarg, 1, LLONG_MAX, &errstr); 442 if (errstr != NULL) 443 errx(1, "number of seconds is %s: %s", errstr, 444 optarg); 445 break; 446 case 's': /* status only */ 447 statonly = 1; 448 break; 449 case 'A': 450 case 'C': 451 if (doperf != PERF_NONE) 452 usage(); 453 doperf = PERF_AUTO; 454 setperfpolicy("auto"); 455 break; 456 case 'L': 457 if (doperf != PERF_NONE) 458 usage(); 459 doperf = PERF_MANUAL; 460 setperfpolicy("low"); 461 break; 462 case 'H': 463 if (doperf != PERF_NONE) 464 usage(); 465 doperf = PERF_MANUAL; 466 setperfpolicy("high"); 467 break; 468 case 'Z': 469 autoaction = AUTO_HIBERNATE; 470 autolimit = strtonum(optarg, 1, 100, &errstr); 471 if (errstr != NULL) 472 errx(1, "battery percentage is %s: %s", errstr, 473 optarg); 474 break; 475 case 'z': 476 autoaction = AUTO_SUSPEND; 477 autolimit = strtonum(optarg, 1, 100, &errstr); 478 if (errstr != NULL) 479 errx(1, "battery percentage is %s: %s", errstr, 480 optarg); 481 break; 482 default: 483 usage(); 484 } 485 486 argc -= optind; 487 argv += optind; 488 489 if (argc != 0) 490 usage(); 491 492 if (doperf == PERF_NONE) 493 doperf = PERF_MANUAL; 494 495 if (debug == 0) { 496 if (daemon(0, 0) == -1) 497 error("failed to daemonize", NULL); 498 openlog(__progname, LOG_CONS, LOG_DAEMON); 499 setlogmask(LOG_UPTO(LOG_NOTICE)); 500 } 501 502 (void) signal(SIGTERM, sigexit); 503 (void) signal(SIGHUP, sigexit); 504 (void) signal(SIGINT, sigexit); 505 506 if ((ctl_fd = open(fname, O_RDWR | O_CLOEXEC)) == -1) { 507 if (errno != ENXIO && errno != ENOENT) 508 error("cannot open device file `%s'", fname); 509 } 510 511 sock_fd = bind_socket(sockname); 512 513 power_status(ctl_fd, 1, &pinfo); 514 515 if (statonly) 516 exit(0); 517 518 if (unveil(_PATH_APM_ETC_DIR, "rx") == -1) 519 err(1, "unveil %s", _PATH_APM_ETC_DIR); 520 if (unveil("/etc/random.seed", "w") == -1) 521 err(1, "unveil /etc/random.seed"); 522 if (unveil(NULL, NULL) == -1) 523 err(1, "unveil"); 524 525 set_driver_messages(ctl_fd, APM_PRINT_OFF); 526 527 kq = kqueue(); 528 if (kq <= 0) 529 error("kqueue", NULL); 530 531 EV_SET(&ev[0], sock_fd, EVFILT_READ, EV_ADD | EV_ENABLE | EV_CLEAR, 532 0, 0, NULL); 533 if (ctl_fd == -1) 534 nchanges = 1; 535 else { 536 EV_SET(&ev[1], ctl_fd, EVFILT_READ, EV_ADD | EV_ENABLE | 537 EV_CLEAR, 0, 0, NULL); 538 nchanges = 2; 539 } 540 if (kevent(kq, ev, nchanges, NULL, 0, &sts) == -1) 541 error("kevent", NULL); 542 543 for (;;) { 544 int rv, event, index; 545 546 sts = ts; 547 548 if ((rv = kevent(kq, NULL, 0, ev, 1, &sts)) == -1) 549 break; 550 551 if (rv == 1 && ev->ident == sock_fd) { 552 handle_client(sock_fd, ctl_fd); 553 continue; 554 } 555 556 suspends = standbys = hibernates = resumes = 0; 557 558 if (rv == 0 && ctl_fd == -1) { 559 /* timeout and no way to query status */ 560 continue; 561 } else if (rv == 0) { 562 /* wakeup for timeout: take status */ 563 event = APM_POWER_CHANGE; 564 index = -1; 565 } else { 566 assert(rv == 1 && ev->ident == ctl_fd); 567 event = APM_EVENT_TYPE(ev->data); 568 index = APM_EVENT_INDEX(ev->data); 569 } 570 571 logmsg(LOG_DEBUG, "apmevent %04x index %d", event, index); 572 573 switch (event) { 574 case APM_SUSPEND_REQ: 575 case APM_USER_SUSPEND_REQ: 576 case APM_CRIT_SUSPEND_REQ: 577 case APM_BATTERY_LOW: 578 suspends++; 579 break; 580 case APM_USER_STANDBY_REQ: 581 case APM_STANDBY_REQ: 582 standbys++; 583 break; 584 case APM_USER_HIBERNATE_REQ: 585 hibernates++; 586 break; 587 case APM_NORMAL_RESUME: 588 case APM_CRIT_RESUME: 589 case APM_SYS_STANDBY_RESUME: 590 powerbak = power_status(ctl_fd, 0, &pinfo); 591 if (powerstatus != powerbak) { 592 powerstatus = powerbak; 593 powerchange = 1; 594 } 595 clock_gettime(CLOCK_MONOTONIC, &last_resume); 596 autoaction_inflight = 0; 597 resumes++; 598 break; 599 case APM_POWER_CHANGE: 600 powerbak = power_status(ctl_fd, 0, &pinfo); 601 if (powerstatus != powerbak) { 602 powerstatus = powerbak; 603 powerchange = 1; 604 } 605 606 if (!powerstatus && autoaction && 607 autolimit > (int)pinfo.battery_life) { 608 struct timespec graceperiod, now; 609 610 graceperiod = last_resume; 611 graceperiod.tv_sec += AUTOACTION_GRACE_PERIOD; 612 clock_gettime(CLOCK_MONOTONIC, &now); 613 614 logmsg(LOG_NOTICE, 615 "estimated battery life %d%%" 616 " below configured limit %d%%%s%s", 617 pinfo.battery_life, autolimit, 618 !autoaction_inflight ? "" : ", in flight", 619 timespeccmp(&now, &graceperiod, >) ? 620 "" : ", grace period" 621 ); 622 623 if (!autoaction_inflight && 624 timespeccmp(&now, &graceperiod, >)) { 625 if (autoaction == AUTO_SUSPEND) 626 suspends++; 627 else 628 hibernates++; 629 /* Block autoaction until next resume */ 630 autoaction_inflight = 1; 631 } 632 } 633 break; 634 default: 635 ; 636 } 637 638 if ((standbys || suspends) && noacsleep && 639 power_status(ctl_fd, 0, &pinfo)) 640 logmsg(LOG_DEBUG, "no! sleep! till brooklyn!"); 641 else if (suspends) 642 suspend(ctl_fd); 643 else if (standbys) 644 stand_by(ctl_fd); 645 else if (hibernates) 646 hibernate(ctl_fd); 647 else if (resumes) { 648 resumed(ctl_fd); 649 } 650 651 if (powerchange) { 652 if (powerstatus) 653 do_etc_file(_PATH_APM_ETC_POWERUP); 654 else 655 do_etc_file(_PATH_APM_ETC_POWERDOWN); 656 powerchange = 0; 657 } 658 } 659 error("kevent loop", NULL); 660 661 return 1; 662} 663 664void 665setperfpolicy(char *policy) 666{ 667 int hw_perfpol_mib[] = { CTL_HW, HW_PERFPOLICY }; 668 int hw_perf_mib[] = { CTL_HW, HW_SETPERF }; 669 int new_perf = -1; 670 671 if (strcmp(policy, "low") == 0) { 672 policy = "manual"; 673 new_perf = 0; 674 } else if (strcmp(policy, "high") == 0) { 675 policy = "manual"; 676 new_perf = 100; 677 } 678 679 if (sysctl(hw_perfpol_mib, 2, NULL, NULL, 680 policy, strlen(policy) + 1) == -1) 681 logmsg(LOG_INFO, "cannot set hw.perfpolicy"); 682 683 if (new_perf == -1) 684 return; 685 686 if (sysctl(hw_perf_mib, 2, NULL, NULL, 687 &new_perf, sizeof(new_perf)) == -1) 688 logmsg(LOG_INFO, "cannot set hw.setperf"); 689} 690 691void 692do_etc_file(const char *file) 693{ 694 pid_t pid; 695 int status; 696 const char *prog; 697 698 /* If file doesn't exist, do nothing. */ 699 if (access(file, X_OK|R_OK)) { 700 logmsg(LOG_DEBUG, "do_etc_file(): cannot access file %s", file); 701 return; 702 } 703 704 prog = strrchr(file, '/'); 705 if (prog) 706 prog++; 707 else 708 prog = file; 709 710 pid = fork(); 711 switch (pid) { 712 case -1: 713 logmsg(LOG_ERR, "failed to fork(): %s", strerror(errno)); 714 return; 715 case 0: 716 /* We are the child. */ 717 execl(file, prog, (char *)NULL); 718 logmsg(LOG_ERR, "failed to exec %s: %s", file, strerror(errno)); 719 _exit(1); 720 /* NOTREACHED */ 721 default: 722 /* We are the parent. */ 723 wait4(pid, &status, 0, 0); 724 if (WIFEXITED(status)) 725 logmsg(LOG_DEBUG, "%s exited with status %d", file, 726 WEXITSTATUS(status)); 727 else 728 logmsg(LOG_ERR, "%s exited abnormally.", file); 729 } 730} 731