1 /* $NetBSD: powerd.c,v 1.21 2023/08/03 08:03:19 mrg Exp $ */ 2 3 /* 4 * Copyright (c) 2003 Wasabi Systems, Inc. 5 * All rights reserved. 6 * 7 * Written by Jason R. Thorpe for Wasabi Systems, Inc. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 3. All advertising materials mentioning features or use of this software 18 * must display the following acknowledgement: 19 * This product includes software developed for the NetBSD Project by 20 * Wasabi Systems, Inc. 21 * 4. The name of Wasabi Systems, Inc. may not be used to endorse 22 * or promote products derived from this software without specific prior 23 * written permission. 24 * 25 * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND 26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 27 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 28 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL WASABI SYSTEMS, INC 29 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 30 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 31 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 32 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 33 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 35 * POSSIBILITY OF SUCH DAMAGE. 36 */ 37 38 /* 39 * Power management daemon for sysmon. 40 */ 41 42 #define SYSLOG_NAMES 43 44 #include <sys/cdefs.h> 45 #include <sys/ioctl.h> 46 #include <sys/param.h> 47 #include <sys/event.h> 48 #include <sys/power.h> 49 #include <sys/wait.h> 50 #include <err.h> 51 #include <errno.h> 52 #include <fcntl.h> 53 #include <paths.h> 54 #include <stdio.h> 55 #include <stdlib.h> 56 #include <sysexits.h> 57 #include <syslog.h> 58 #include <unistd.h> 59 #include <util.h> 60 #include <prop/proplib.h> 61 #include <stdarg.h> 62 #include <string.h> 63 64 #include "prog_ops.h" 65 66 int debug, no_scripts; 67 68 static int kq; 69 70 #define _PATH_POWERD_SCRIPTS "/etc/powerd/scripts" 71 72 static void usage(void) __dead; 73 static void run_script(const char *[]); 74 static struct kevent *allocchange(void); 75 static int wait_for_events(struct kevent *, size_t); 76 static void dispatch_dev_power(struct kevent *); 77 static void dispatch_power_event_state_change(int, power_event_t *); 78 static void powerd_log(int, const char *, ...) __printflike(2, 3); 79 80 static const char *script_paths[] = { 81 NULL, 82 _PATH_POWERD_SCRIPTS 83 }; 84 85 int 86 main(int argc, char *argv[]) 87 { 88 struct kevent *ev, events[16]; 89 struct power_type power_type; 90 char *cp; 91 int ch, fd; 92 93 setprogname(*argv); 94 95 if (prog_init && prog_init() == -1) 96 err(1, "init failed"); 97 98 while ((ch = getopt(argc, argv, "dn")) != -1) { 99 switch (ch) { 100 case 'd': 101 debug = 1; 102 break; 103 104 case 'n': 105 no_scripts = 1; 106 break; 107 108 default: 109 usage(); 110 } 111 } 112 argc -= optind; 113 argv += optind; 114 115 if (argc) 116 usage(); 117 118 if (debug == 0) { 119 (void)daemon(0, 0); 120 121 openlog("powerd", LOG_PID | LOG_NOWAIT, LOG_DAEMON); 122 (void)pidfile(NULL); 123 } 124 125 if ((kq = prog_kqueue()) == -1) { 126 powerd_log(LOG_ERR, "kqueue: %s", strerror(errno)); 127 exit(EX_OSERR); 128 } 129 130 if ((fd = prog_open(_PATH_POWER, O_RDONLY|O_NONBLOCK, 0600)) == -1) { 131 powerd_log(LOG_ERR, "open %s: %s", _PATH_POWER, 132 strerror(errno)); 133 exit(EX_OSERR); 134 } 135 136 if (prog_fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) { 137 powerd_log(LOG_ERR, "Cannot set close on exec in power fd: %s", 138 strerror(errno)); 139 exit(EX_OSERR); 140 } 141 142 if (prog_ioctl(fd, POWER_IOC_GET_TYPE, &power_type) == -1) { 143 powerd_log(LOG_ERR, "POWER_IOC_GET_TYPE: %s", strerror(errno)); 144 exit(EX_OSERR); 145 } 146 147 (void)asprintf(&cp, "%s/%s", _PATH_POWERD_SCRIPTS, 148 power_type.power_type); 149 if (cp == NULL) { 150 powerd_log(LOG_ERR, "allocating script path: %s", 151 strerror(errno)); 152 exit(EX_OSERR); 153 } 154 script_paths[0] = cp; 155 156 ev = allocchange(); 157 EV_SET(ev, fd, EVFILT_READ, EV_ADD | EV_ENABLE, 158 0, 0, (intptr_t) dispatch_dev_power); 159 160 for (;;) { 161 void (*handler)(struct kevent *); 162 int i, rv; 163 164 rv = wait_for_events(events, __arraycount(events)); 165 for (i = 0; i < rv; i++) { 166 handler = (void *) events[i].udata; 167 (*handler)(&events[i]); 168 } 169 } 170 } 171 172 static void 173 usage(void) 174 { 175 176 (void)fprintf(stderr, "usage: %s [-dn]\n", getprogname()); 177 exit(EX_USAGE); 178 } 179 180 static void 181 run_script(const char *argv[]) 182 { 183 char path[MAXPATHLEN+1]; 184 size_t i, j; 185 186 for (i = 0; i < __arraycount(script_paths); i++) { 187 (void)snprintf(path, sizeof(path), "%s/%s", script_paths[i], 188 argv[0]); 189 if (access(path, R_OK|X_OK) == 0) { 190 int status; 191 pid_t pid; 192 const char *orig_argv0 = argv[0]; 193 194 argv[0] = path; 195 196 if (debug) { 197 (void)fprintf(stderr, "%srunning script: %s", 198 no_scripts?"not ":"", argv[0]); 199 for (j = 1; argv[j] != NULL; j++) 200 (void)fprintf(stderr, " %s", argv[j]); 201 (void)fprintf(stderr, "\n"); 202 } 203 if (no_scripts != 0) { 204 argv[0] = orig_argv0; 205 return; 206 } 207 208 switch ((pid = vfork())) { 209 case -1: 210 powerd_log(LOG_ERR, "fork to run script: %s", 211 strerror(errno)); 212 argv[0] = orig_argv0; 213 return; 214 215 case 0: 216 /* Child. */ 217 (void) execv(path, __UNCONST(argv)); 218 _exit(1); 219 /* NOTREACHED */ 220 221 default: 222 /* Parent. */ 223 if (waitpid(pid, &status, 0) == -1) { 224 powerd_log(LOG_ERR, 225 "waitpid for %s: %s", path, 226 strerror(errno)); 227 break; 228 } 229 if (WIFEXITED(status) && 230 WEXITSTATUS(status) != 0) { 231 powerd_log(LOG_ERR, 232 "%s exited with status %d", 233 path, WEXITSTATUS(status)); 234 } else if (!WIFEXITED(status)) { 235 powerd_log(LOG_ERR, 236 "%s terminated abnormally", path); 237 } 238 break; 239 } 240 argv[0] = orig_argv0; 241 242 return; 243 } 244 } 245 246 powerd_log(LOG_ERR, "no script for %s", argv[0]); 247 } 248 249 static struct kevent changebuf[8]; 250 static size_t nchanges; 251 252 static struct kevent * 253 allocchange(void) 254 { 255 256 if (nchanges == __arraycount(changebuf)) { 257 (void)wait_for_events(NULL, 0); 258 nchanges = 0; 259 } 260 261 return &changebuf[nchanges++]; 262 } 263 264 static int 265 wait_for_events(struct kevent *events, size_t nevents) 266 { 267 int rv; 268 269 while ((rv = prog_kevent(kq, nchanges ? changebuf : NULL, nchanges, 270 events, nevents, NULL)) < 0) { 271 nchanges = 0; 272 if (errno != EINTR) { 273 powerd_log(LOG_ERR, "kevent: %s", strerror(errno)); 274 exit(EX_OSERR); 275 } 276 } 277 278 return rv; 279 } 280 281 static void 282 dispatch_dev_power(struct kevent *ev) 283 { 284 power_event_t pev; 285 int fd = ev->ident; 286 287 if (debug) 288 (void)fprintf(stderr, "%s: %" PRId64 289 " event%s available\n", __func__, 290 ev->data, ev->data > 1 ? "s" : ""); 291 292 again: 293 if (prog_read(fd, &pev, sizeof(pev)) != sizeof(pev)) { 294 if (errno == EWOULDBLOCK) 295 return; 296 powerd_log(LOG_ERR, "read of %s: %s", _PATH_POWER, 297 strerror(errno)); 298 exit(EX_OSERR); 299 } 300 301 if (debug) 302 (void)fprintf(stderr, "%s: event type %d\n", 303 __func__, pev.pev_type); 304 305 switch (pev.pev_type) { 306 case POWER_EVENT_ENVSYS_STATE_CHANGE: 307 case POWER_EVENT_SWITCH_STATE_CHANGE: 308 dispatch_power_event_state_change(fd, &pev); 309 break; 310 default: 311 powerd_log(LOG_INFO, "unknown %s event type: %d", 312 _PATH_POWER, pev.pev_type); 313 } 314 315 goto again; 316 } 317 318 static void 319 dispatch_power_event_state_change(int fd, power_event_t *pev) 320 { 321 prop_dictionary_t dict; 322 const char *argv[6]; 323 char *buf = NULL; 324 int error; 325 326 error = prop_dictionary_recv_ioctl(fd, POWER_EVENT_RECVDICT, &dict); 327 if (error) { 328 if (debug) 329 printf("%s: prop_dictionary_recv_ioctl error=%d\n", 330 __func__, error); 331 return; 332 } 333 334 if (debug) { 335 buf = prop_dictionary_externalize(dict); 336 printf("%s", buf); 337 free(buf); 338 } 339 340 /* First three arguments are not optional. */ 341 if (!prop_dictionary_get_string(dict, "powerd-script-name", &argv[0])) { 342 powerd_log(LOG_ERR, "dict does not have powerd-script-name"); 343 return; 344 } 345 if (!prop_dictionary_get_string(dict, "driver-name", &argv[1])) { 346 powerd_log(LOG_ERR, "dict does not have driver-name"); 347 return; 348 } 349 if (!prop_dictionary_get_string(dict, "powerd-event-name", &argv[2])) { 350 powerd_log(LOG_ERR, "dict does not have powerd-event-name"); 351 return; 352 } 353 354 /* These arguments are optional. */ 355 if (!prop_dictionary_get_string(dict, "sensor-name", &argv[3])) 356 argv[3] = NULL; 357 if (!prop_dictionary_get_string(dict, "state-description", &argv[4])) 358 argv[4] = NULL; 359 360 argv[5] = NULL; 361 362 run_script(argv); 363 } 364 365 static void 366 powerd_log(int pri, const char *msg, ...) 367 { 368 va_list arglist; 369 unsigned int i; 370 371 va_start(arglist, msg); 372 if (debug == 0) 373 vsyslog(pri, msg, arglist); 374 else { 375 for (i = 0; i < __arraycount(prioritynames); i++) 376 if (prioritynames[i].c_val == pri) 377 break; 378 fprintf(stderr, "%s: ", 379 (prioritynames[i].c_val == -1) ? 380 "UNKNOWN" : prioritynames[i].c_name); 381 vfprintf(stderr, msg, arglist); 382 } 383 va_end(arglist); 384 } 385