Skip to content

Sunil Nimmagadda

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;