1*2bd8f802Smrg /* $NetBSD: powerd.c,v 1.21 2023/08/03 08:03:19 mrg Exp $ */
2539625c0Sthorpej
3539625c0Sthorpej /*
4539625c0Sthorpej * Copyright (c) 2003 Wasabi Systems, Inc.
5539625c0Sthorpej * All rights reserved.
6539625c0Sthorpej *
7539625c0Sthorpej * Written by Jason R. Thorpe for Wasabi Systems, Inc.
8539625c0Sthorpej *
9539625c0Sthorpej * Redistribution and use in source and binary forms, with or without
10539625c0Sthorpej * modification, are permitted provided that the following conditions
11539625c0Sthorpej * are met:
12539625c0Sthorpej * 1. Redistributions of source code must retain the above copyright
13539625c0Sthorpej * notice, this list of conditions and the following disclaimer.
14539625c0Sthorpej * 2. Redistributions in binary form must reproduce the above copyright
15539625c0Sthorpej * notice, this list of conditions and the following disclaimer in the
16539625c0Sthorpej * documentation and/or other materials provided with the distribution.
17539625c0Sthorpej * 3. All advertising materials mentioning features or use of this software
18539625c0Sthorpej * must display the following acknowledgement:
19539625c0Sthorpej * This product includes software developed for the NetBSD Project by
20539625c0Sthorpej * Wasabi Systems, Inc.
21539625c0Sthorpej * 4. The name of Wasabi Systems, Inc. may not be used to endorse
22539625c0Sthorpej * or promote products derived from this software without specific prior
23539625c0Sthorpej * written permission.
24539625c0Sthorpej *
25539625c0Sthorpej * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND
26539625c0Sthorpej * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
27539625c0Sthorpej * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
28539625c0Sthorpej * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL WASABI SYSTEMS, INC
29539625c0Sthorpej * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30539625c0Sthorpej * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
31539625c0Sthorpej * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32539625c0Sthorpej * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33539625c0Sthorpej * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34539625c0Sthorpej * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35539625c0Sthorpej * POSSIBILITY OF SUCH DAMAGE.
36539625c0Sthorpej */
37539625c0Sthorpej
38539625c0Sthorpej /*
39539625c0Sthorpej * Power management daemon for sysmon.
40539625c0Sthorpej */
41539625c0Sthorpej
425702902fSpgoyette #define SYSLOG_NAMES
435702902fSpgoyette
44d764ad2aSxtraeme #include <sys/cdefs.h>
45eab26c47Spgoyette #include <sys/ioctl.h>
46539625c0Sthorpej #include <sys/param.h>
47539625c0Sthorpej #include <sys/event.h>
48539625c0Sthorpej #include <sys/power.h>
49539625c0Sthorpej #include <sys/wait.h>
50eab26c47Spgoyette #include <err.h>
51539625c0Sthorpej #include <errno.h>
52539625c0Sthorpej #include <fcntl.h>
5373bd86deSjruoho #include <paths.h>
54539625c0Sthorpej #include <stdio.h>
55539625c0Sthorpej #include <stdlib.h>
56539625c0Sthorpej #include <sysexits.h>
57539625c0Sthorpej #include <syslog.h>
58539625c0Sthorpej #include <unistd.h>
59539625c0Sthorpej #include <util.h>
60af211bb4Sxtraeme #include <prop/proplib.h>
615702902fSpgoyette #include <stdarg.h>
625702902fSpgoyette #include <string.h>
63539625c0Sthorpej
64eab26c47Spgoyette #include "prog_ops.h"
65eab26c47Spgoyette
665702902fSpgoyette int debug, no_scripts;
67539625c0Sthorpej
68539625c0Sthorpej static int kq;
69539625c0Sthorpej
70539625c0Sthorpej #define _PATH_POWERD_SCRIPTS "/etc/powerd/scripts"
71539625c0Sthorpej
728b0f9554Sperry static void usage(void) __dead;
73539625c0Sthorpej static void run_script(const char *[]);
74539625c0Sthorpej static struct kevent *allocchange(void);
75539625c0Sthorpej static int wait_for_events(struct kevent *, size_t);
76539625c0Sthorpej static void dispatch_dev_power(struct kevent *);
77af211bb4Sxtraeme static void dispatch_power_event_state_change(int, power_event_t *);
7866dd2755Sjoerg static void powerd_log(int, const char *, ...) __printflike(2, 3);
79539625c0Sthorpej
80539625c0Sthorpej static const char *script_paths[] = {
81539625c0Sthorpej NULL,
82539625c0Sthorpej _PATH_POWERD_SCRIPTS
83539625c0Sthorpej };
84539625c0Sthorpej
85539625c0Sthorpej int
main(int argc,char * argv[])86539625c0Sthorpej main(int argc, char *argv[])
87539625c0Sthorpej {
88539625c0Sthorpej struct kevent *ev, events[16];
89539625c0Sthorpej struct power_type power_type;
90539625c0Sthorpej char *cp;
91539625c0Sthorpej int ch, fd;
92539625c0Sthorpej
93f3d0b6c6Schristos setprogname(*argv);
94f3d0b6c6Schristos
95eab26c47Spgoyette if (prog_init && prog_init() == -1)
96eab26c47Spgoyette err(1, "init failed");
97eab26c47Spgoyette
985702902fSpgoyette while ((ch = getopt(argc, argv, "dn")) != -1) {
99539625c0Sthorpej switch (ch) {
100539625c0Sthorpej case 'd':
101539625c0Sthorpej debug = 1;
102539625c0Sthorpej break;
103539625c0Sthorpej
1045702902fSpgoyette case 'n':
1055702902fSpgoyette no_scripts = 1;
1065702902fSpgoyette break;
1075702902fSpgoyette
108539625c0Sthorpej default:
109539625c0Sthorpej usage();
110539625c0Sthorpej }
111539625c0Sthorpej }
112539625c0Sthorpej argc -= optind;
113539625c0Sthorpej argv += optind;
114539625c0Sthorpej
115539625c0Sthorpej if (argc)
116539625c0Sthorpej usage();
117539625c0Sthorpej
1185702902fSpgoyette if (debug == 0) {
119f3d0b6c6Schristos (void)daemon(0, 0);
120539625c0Sthorpej
121539625c0Sthorpej openlog("powerd", LOG_PID | LOG_NOWAIT, LOG_DAEMON);
122f3d0b6c6Schristos (void)pidfile(NULL);
1235702902fSpgoyette }
124539625c0Sthorpej
125eab26c47Spgoyette if ((kq = prog_kqueue()) == -1) {
1265702902fSpgoyette powerd_log(LOG_ERR, "kqueue: %s", strerror(errno));
127539625c0Sthorpej exit(EX_OSERR);
128539625c0Sthorpej }
129539625c0Sthorpej
130eab26c47Spgoyette if ((fd = prog_open(_PATH_POWER, O_RDONLY|O_NONBLOCK, 0600)) == -1) {
1315702902fSpgoyette powerd_log(LOG_ERR, "open %s: %s", _PATH_POWER,
1325702902fSpgoyette strerror(errno));
133539625c0Sthorpej exit(EX_OSERR);
134539625c0Sthorpej }
135539625c0Sthorpej
136eab26c47Spgoyette if (prog_fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) {
1375702902fSpgoyette powerd_log(LOG_ERR, "Cannot set close on exec in power fd: %s",
1385702902fSpgoyette strerror(errno));
139f3d0b6c6Schristos exit(EX_OSERR);
140f3d0b6c6Schristos }
141f3d0b6c6Schristos
142eab26c47Spgoyette if (prog_ioctl(fd, POWER_IOC_GET_TYPE, &power_type) == -1) {
1435702902fSpgoyette powerd_log(LOG_ERR, "POWER_IOC_GET_TYPE: %s", strerror(errno));
144539625c0Sthorpej exit(EX_OSERR);
145539625c0Sthorpej }
146539625c0Sthorpej
147f3d0b6c6Schristos (void)asprintf(&cp, "%s/%s", _PATH_POWERD_SCRIPTS,
148f3d0b6c6Schristos power_type.power_type);
149539625c0Sthorpej if (cp == NULL) {
1505702902fSpgoyette powerd_log(LOG_ERR, "allocating script path: %s",
1515702902fSpgoyette strerror(errno));
152539625c0Sthorpej exit(EX_OSERR);
153539625c0Sthorpej }
154539625c0Sthorpej script_paths[0] = cp;
155539625c0Sthorpej
156539625c0Sthorpej ev = allocchange();
157539625c0Sthorpej EV_SET(ev, fd, EVFILT_READ, EV_ADD | EV_ENABLE,
158539625c0Sthorpej 0, 0, (intptr_t) dispatch_dev_power);
159539625c0Sthorpej
160539625c0Sthorpej for (;;) {
161539625c0Sthorpej void (*handler)(struct kevent *);
162539625c0Sthorpej int i, rv;
163539625c0Sthorpej
164d764ad2aSxtraeme rv = wait_for_events(events, __arraycount(events));
165539625c0Sthorpej for (i = 0; i < rv; i++) {
166539625c0Sthorpej handler = (void *) events[i].udata;
167539625c0Sthorpej (*handler)(&events[i]);
168539625c0Sthorpej }
169539625c0Sthorpej }
170539625c0Sthorpej }
171539625c0Sthorpej
172539625c0Sthorpej static void
usage(void)173539625c0Sthorpej usage(void)
174539625c0Sthorpej {
175539625c0Sthorpej
1765702902fSpgoyette (void)fprintf(stderr, "usage: %s [-dn]\n", getprogname());
177539625c0Sthorpej exit(EX_USAGE);
178539625c0Sthorpej }
179539625c0Sthorpej
180539625c0Sthorpej static void
run_script(const char * argv[])181539625c0Sthorpej run_script(const char *argv[])
182539625c0Sthorpej {
183539625c0Sthorpej char path[MAXPATHLEN+1];
184f3d0b6c6Schristos size_t i, j;
185539625c0Sthorpej
186d764ad2aSxtraeme for (i = 0; i < __arraycount(script_paths); i++) {
187f3d0b6c6Schristos (void)snprintf(path, sizeof(path), "%s/%s", script_paths[i],
188f3d0b6c6Schristos argv[0]);
189539625c0Sthorpej if (access(path, R_OK|X_OK) == 0) {
190539625c0Sthorpej int status;
191539625c0Sthorpej pid_t pid;
192*2bd8f802Smrg const char *orig_argv0 = argv[0];
193539625c0Sthorpej
194539625c0Sthorpej argv[0] = path;
195539625c0Sthorpej
196539625c0Sthorpej if (debug) {
1975702902fSpgoyette (void)fprintf(stderr, "%srunning script: %s",
1985702902fSpgoyette no_scripts?"not ":"", argv[0]);
199f3d0b6c6Schristos for (j = 1; argv[j] != NULL; j++)
200f3d0b6c6Schristos (void)fprintf(stderr, " %s", argv[j]);
201f3d0b6c6Schristos (void)fprintf(stderr, "\n");
202539625c0Sthorpej }
203*2bd8f802Smrg if (no_scripts != 0) {
204*2bd8f802Smrg argv[0] = orig_argv0;
2055702902fSpgoyette return;
206*2bd8f802Smrg }
207539625c0Sthorpej
208539625c0Sthorpej switch ((pid = vfork())) {
209539625c0Sthorpej case -1:
2105702902fSpgoyette powerd_log(LOG_ERR, "fork to run script: %s",
2115702902fSpgoyette strerror(errno));
212*2bd8f802Smrg argv[0] = orig_argv0;
213539625c0Sthorpej return;
214539625c0Sthorpej
215539625c0Sthorpej case 0:
216539625c0Sthorpej /* Child. */
217d8d9c208Schristos (void) execv(path, __UNCONST(argv));
218539625c0Sthorpej _exit(1);
219539625c0Sthorpej /* NOTREACHED */
220539625c0Sthorpej
221539625c0Sthorpej default:
222539625c0Sthorpej /* Parent. */
223f3d0b6c6Schristos if (waitpid(pid, &status, 0) == -1) {
2245702902fSpgoyette powerd_log(LOG_ERR,
2255702902fSpgoyette "waitpid for %s: %s", path,
2265702902fSpgoyette strerror(errno));
227f3d0b6c6Schristos break;
228f3d0b6c6Schristos }
229539625c0Sthorpej if (WIFEXITED(status) &&
230539625c0Sthorpej WEXITSTATUS(status) != 0) {
2315702902fSpgoyette powerd_log(LOG_ERR,
232539625c0Sthorpej "%s exited with status %d",
233539625c0Sthorpej path, WEXITSTATUS(status));
234539625c0Sthorpej } else if (!WIFEXITED(status)) {
2355702902fSpgoyette powerd_log(LOG_ERR,
236539625c0Sthorpej "%s terminated abnormally", path);
237539625c0Sthorpej }
238539625c0Sthorpej break;
239539625c0Sthorpej }
240*2bd8f802Smrg argv[0] = orig_argv0;
241539625c0Sthorpej
242539625c0Sthorpej return;
243539625c0Sthorpej }
244539625c0Sthorpej }
245539625c0Sthorpej
2465702902fSpgoyette powerd_log(LOG_ERR, "no script for %s", argv[0]);
247539625c0Sthorpej }
248539625c0Sthorpej
249539625c0Sthorpej static struct kevent changebuf[8];
250f3d0b6c6Schristos static size_t nchanges;
251539625c0Sthorpej
252539625c0Sthorpej static struct kevent *
allocchange(void)253539625c0Sthorpej allocchange(void)
254539625c0Sthorpej {
255539625c0Sthorpej
256d764ad2aSxtraeme if (nchanges == __arraycount(changebuf)) {
257539625c0Sthorpej (void)wait_for_events(NULL, 0);
258539625c0Sthorpej nchanges = 0;
259539625c0Sthorpej }
260539625c0Sthorpej
261f3d0b6c6Schristos return &changebuf[nchanges++];
262539625c0Sthorpej }
263539625c0Sthorpej
264539625c0Sthorpej static int
wait_for_events(struct kevent * events,size_t nevents)265539625c0Sthorpej wait_for_events(struct kevent *events, size_t nevents)
266539625c0Sthorpej {
267539625c0Sthorpej int rv;
268539625c0Sthorpej
269eab26c47Spgoyette while ((rv = prog_kevent(kq, nchanges ? changebuf : NULL, nchanges,
270539625c0Sthorpej events, nevents, NULL)) < 0) {
271539625c0Sthorpej nchanges = 0;
272539625c0Sthorpej if (errno != EINTR) {
2735702902fSpgoyette powerd_log(LOG_ERR, "kevent: %s", strerror(errno));
274539625c0Sthorpej exit(EX_OSERR);
275539625c0Sthorpej }
276539625c0Sthorpej }
277539625c0Sthorpej
278f3d0b6c6Schristos return rv;
279539625c0Sthorpej }
280539625c0Sthorpej
281539625c0Sthorpej static void
dispatch_dev_power(struct kevent * ev)282539625c0Sthorpej dispatch_dev_power(struct kevent *ev)
283539625c0Sthorpej {
284539625c0Sthorpej power_event_t pev;
285539625c0Sthorpej int fd = ev->ident;
286539625c0Sthorpej
287539625c0Sthorpej if (debug)
288af211bb4Sxtraeme (void)fprintf(stderr, "%s: %" PRId64
289af211bb4Sxtraeme " event%s available\n", __func__,
290af211bb4Sxtraeme ev->data, ev->data > 1 ? "s" : "");
291539625c0Sthorpej
292539625c0Sthorpej again:
293eab26c47Spgoyette if (prog_read(fd, &pev, sizeof(pev)) != sizeof(pev)) {
294539625c0Sthorpej if (errno == EWOULDBLOCK)
295539625c0Sthorpej return;
2965702902fSpgoyette powerd_log(LOG_ERR, "read of %s: %s", _PATH_POWER,
2975702902fSpgoyette strerror(errno));
298539625c0Sthorpej exit(EX_OSERR);
299539625c0Sthorpej }
300539625c0Sthorpej
301af211bb4Sxtraeme if (debug)
302af211bb4Sxtraeme (void)fprintf(stderr, "%s: event type %d\n",
303af211bb4Sxtraeme __func__, pev.pev_type);
304539625c0Sthorpej
305539625c0Sthorpej switch (pev.pev_type) {
306af211bb4Sxtraeme case POWER_EVENT_ENVSYS_STATE_CHANGE:
307539625c0Sthorpej case POWER_EVENT_SWITCH_STATE_CHANGE:
308af211bb4Sxtraeme dispatch_power_event_state_change(fd, &pev);
309539625c0Sthorpej break;
310539625c0Sthorpej default:
3115702902fSpgoyette powerd_log(LOG_INFO, "unknown %s event type: %d",
31273bd86deSjruoho _PATH_POWER, pev.pev_type);
313539625c0Sthorpej }
314539625c0Sthorpej
315539625c0Sthorpej goto again;
316539625c0Sthorpej }
317539625c0Sthorpej
318539625c0Sthorpej static void
dispatch_power_event_state_change(int fd,power_event_t * pev)319af211bb4Sxtraeme dispatch_power_event_state_change(int fd, power_event_t *pev)
320539625c0Sthorpej {
321af211bb4Sxtraeme prop_dictionary_t dict;
322af211bb4Sxtraeme const char *argv[6];
3236c73c21fSxtraeme char *buf = NULL;
324af211bb4Sxtraeme int error;
325539625c0Sthorpej
326af211bb4Sxtraeme error = prop_dictionary_recv_ioctl(fd, POWER_EVENT_RECVDICT, &dict);
327af211bb4Sxtraeme if (error) {
328af211bb4Sxtraeme if (debug)
329af211bb4Sxtraeme printf("%s: prop_dictionary_recv_ioctl error=%d\n",
330af211bb4Sxtraeme __func__, error);
331af211bb4Sxtraeme return;
332af211bb4Sxtraeme }
333539625c0Sthorpej
3346c73c21fSxtraeme if (debug) {
3356c73c21fSxtraeme buf = prop_dictionary_externalize(dict);
3366c73c21fSxtraeme printf("%s", buf);
3376d61a6b5Sxtraeme free(buf);
3386c73c21fSxtraeme }
339af211bb4Sxtraeme
340d3b0ce63Sroy /* First three arguments are not optional. */
341d3b0ce63Sroy if (!prop_dictionary_get_string(dict, "powerd-script-name", &argv[0])) {
342d3b0ce63Sroy powerd_log(LOG_ERR, "dict does not have powerd-script-name");
343d3b0ce63Sroy return;
344d3b0ce63Sroy }
345d3b0ce63Sroy if (!prop_dictionary_get_string(dict, "driver-name", &argv[1])) {
346d3b0ce63Sroy powerd_log(LOG_ERR, "dict does not have driver-name");
347d3b0ce63Sroy return;
348d3b0ce63Sroy }
349d3b0ce63Sroy if (!prop_dictionary_get_string(dict, "powerd-event-name", &argv[2])) {
350d3b0ce63Sroy powerd_log(LOG_ERR, "dict does not have powerd-event-name");
351d3b0ce63Sroy return;
352d3b0ce63Sroy }
353d3b0ce63Sroy
354d3b0ce63Sroy /* These arguments are optional. */
355d3b0ce63Sroy if (!prop_dictionary_get_string(dict, "sensor-name", &argv[3]))
356d3b0ce63Sroy argv[3] = NULL;
357d3b0ce63Sroy if (!prop_dictionary_get_string(dict, "state-description", &argv[4]))
358d3b0ce63Sroy argv[4] = NULL;
359d3b0ce63Sroy
360af211bb4Sxtraeme argv[5] = NULL;
361af211bb4Sxtraeme
362539625c0Sthorpej run_script(argv);
363539625c0Sthorpej }
3645702902fSpgoyette
3655702902fSpgoyette static void
powerd_log(int pri,const char * msg,...)3665702902fSpgoyette powerd_log(int pri, const char *msg, ...)
3675702902fSpgoyette {
3685702902fSpgoyette va_list arglist;
3695702902fSpgoyette unsigned int i;
3705702902fSpgoyette
3715702902fSpgoyette va_start(arglist, msg);
3725702902fSpgoyette if (debug == 0)
3735702902fSpgoyette vsyslog(pri, msg, arglist);
3745702902fSpgoyette else {
3755702902fSpgoyette for (i = 0; i < __arraycount(prioritynames); i++)
3765702902fSpgoyette if (prioritynames[i].c_val == pri)
3775702902fSpgoyette break;
3785702902fSpgoyette fprintf(stderr, "%s: ",
3795702902fSpgoyette (prioritynames[i].c_val == -1) ?
3805702902fSpgoyette "UNKNOWN" : prioritynames[i].c_name);
3815702902fSpgoyette vfprintf(stderr, msg, arglist);
3825702902fSpgoyette }
38360296e90Schristos va_end(arglist);
3845702902fSpgoyette }
385