pub/sub POC for powerd(8)
While lurking on the #netbsd IRC channel, a discussion happened about
the need of a publish/subscribe mechanism for power events.
powerd(8), a daemon that monitors sysmon(4) events (via /dev/power
)
and responds by spawning user specified shell scripts against those
events could be a potential candidate to plug it in. Any userland
subscribers interested in power events could subscribe, get notified
and respond directly.
powerd(8) runs a non-blocking event loop using kqueue(2). This POC
patch attempts to listen(2) on a unix domain socket
(/var/run/powerd
) for subscribers upon specifying a new command-line
argument r
. As a POC it uses a static array with limited capacity
for the number of subscribers and stops listener once that capacity
exceeds. On every power event, this patch relays the payload to every
subscriber connected.
diff -r 079dd99f505c usr.sbin/powerd/powerd.c
--- a/usr.sbin/powerd/powerd.c Sun Jan 07 21:19:42 2024 +0000
+++ b/usr.sbin/powerd/powerd.c Sun Jan 07 02:17:06 2024 +0530
@@ -46,6 +46,10 @@
#include <sys/param.h>
#include <sys/event.h>
#include <sys/power.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
#include <sys/wait.h>
#include <err.h>
#include <errno.h>
@@ -63,19 +67,23 @@
#include "prog_ops.h"
-int debug, no_scripts;
+int debug, no_scripts, relay;
static int kq;
#define _PATH_POWERD_SCRIPTS "/etc/powerd/scripts"
+#define _PATH_SOCKETNAME "/var/run/powerd"
static void usage(void) __dead;
static void run_script(const char *[]);
static struct kevent *allocchange(void);
static int wait_for_events(struct kevent *, size_t);
+static void accept_local_conns(struct kevent *);
static void dispatch_dev_power(struct kevent *);
static void dispatch_power_event_state_change(int, power_event_t *);
static void powerd_log(int, const char *, ...) __printflike(2, 3);
+static int powerd_listen(const char *);
+static void powerd_relay(const char *);
static const char *script_paths[] = {
NULL,
@@ -95,7 +103,7 @@
if (prog_init && prog_init() == -1)
err(1, "init failed");
- while ((ch = getopt(argc, argv, "dn")) != -1) {
+ while ((ch = getopt(argc, argv, "dnr")) != -1) {
switch (ch) {
case 'd':
debug = 1;
@@ -105,6 +113,10 @@
no_scripts = 1;
break;
+ case 'r':
+ relay = 1;
+ break;
+
default:
usage();
}
@@ -127,6 +139,14 @@
exit(EX_OSERR);
}
+ if (relay) {
+ int sock;
+ sock = powerd_listen(_PATH_SOCKETNAME);
+ ev = allocchange();
+ EV_SET(ev, sock, EVFILT_READ, EV_ADD | EV_ENABLE,
+ 0, 0, (intptr_t) accept_local_conns);
+ }
+
if ((fd = prog_open(_PATH_POWER, O_RDONLY|O_NONBLOCK, 0600)) == -1) {
powerd_log(LOG_ERR, "open %s: %s", _PATH_POWER,
strerror(errno));
@@ -177,6 +197,31 @@
exit(EX_USAGE);
}
+static int
+powerd_listen(const char *path)
+{
+ struct sockaddr_un un;
+ int sock = -1;
+
+ if ((sock = socket(AF_LOCAL, SOCK_STREAM | SOCK_NONBLOCK, 0)) == -1) {
+ powerd_log(LOG_ERR, "socket: %s", strerror(errno));
+ exit(EX_OSERR);
+ }
+ memset(&un, 0, sizeof(un));
+ un.sun_family = AF_LOCAL;
+ strncpy(un.sun_path, _PATH_SOCKETNAME, sizeof(un.sun_path) - 1);
+ un.sun_len = SUN_LEN(&un);
+ (void)umask(07);
+ (void)unlink(_PATH_SOCKETNAME);
+ if (bind(sock, (struct sockaddr *)&un, un.sun_len) < 0) {
+ powerd_log(LOG_ERR, "bind: %s", strerror(errno));
+ exit(EX_OSERR);
+ }
+ (void)umask(0);
+ listen(sock, 5);
+ return sock;
+}
+
static void
run_script(const char *argv[])
{
@@ -278,6 +323,21 @@
return rv;
}
+/* XXX */
+static int conns[6];
+static size_t nconns;
+static void accept_local_conns(struct kevent *ev)
+{
+ int sock;
+
+ sock = accept(ev->ident, NULL, NULL);
+ (void)shutdown(sock, SHUT_RD);
+ conns[nconns++] = sock;
+ if (nconns == __arraycount(conns)) {
+ EV_SET(ev, ev->ident, EV_DELETE, 0, 0, 0, NULL);
+ }
+}
+
static void
dispatch_dev_power(struct kevent *ev)
{
@@ -359,10 +419,32 @@
argv[5] = NULL;
+ if (relay) {
+ /* XXX */
+ powerd_relay(argv[2]);
+ }
run_script(argv);
}
static void
+powerd_relay(const char *event_name)
+{
+ size_t i;
+ int ret;
+
+ for (i = 0; i < __arraycount(conns); i++) {
+ if (conns[i] == 0)
+ continue;
+ ret = send(conns[i], event_name, strlen(event_name), 0);
+ /* XXX */
+ if (ret == -1) {
+ close(conns[i]);
+ conns[i] = 0;
+ }
+ }
+}
+
+static void
powerd_log(int pri, const char *msg, ...)
{
va_list arglist;