commit: 017f3556207f619de26c3a27162988cdc6f91dc0
parent 2e9b607d05081b5ebe68af664971e1c4f68ca90b
Author: Michael Forney <mforney@mforney.org>
Date: Sun, 3 Nov 2019 17:59:46 -0800
Add devd server for device hotplug
Diffstat:
A | src/devd-trigger.c | 106 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/devd.c | 164 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | src/gen.lua | 10 | ++++++---- |
3 files changed, 276 insertions(+), 4 deletions(-)
diff --git a/src/devd-trigger.c b/src/devd-trigger.c
@@ -0,0 +1,106 @@
+#define _XOPEN_SOURCE 700
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdnoreturn.h>
+#include <string.h>
+
+#include <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+static char path[PATH_MAX] = "/sys/devices";
+static size_t pathlen;
+static int ret;
+
+static void
+error(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ if (fmt[0] && fmt[strlen(fmt) - 1] == ':') {
+ fputc(' ', stderr);
+ perror(NULL);
+ } else {
+ fputc('\n', stderr);
+ }
+ ret = 1;
+}
+
+static void
+trigger(int fd, const char *name)
+{
+ static const char action[] = "add";
+
+ fd = openat(fd, name, O_WRONLY);
+ if (fd == -1) {
+ error("open %s:", path);
+ } else {
+ if (write(fd, action, sizeof(action) - 1) != (ssize_t)(sizeof(action) - 1))
+ error("write %s:", path);
+ close(fd);
+ }
+}
+
+static void
+search(int fd, const char *name)
+{
+ DIR *dir;
+ struct dirent *d;
+ char *nul;
+ size_t oldlen;
+ struct stat st;
+
+ fd = openat(fd, name, O_RDONLY | O_DIRECTORY);
+ if (fd == -1) {
+ error("opendir %s:", path);
+ return;
+ }
+ dir = fdopendir(fd);
+ if (!dir) {
+ error("opendir %s:", path);
+ return;
+ }
+ oldlen = pathlen;
+ for (;; pathlen = oldlen) {
+ errno = 0;
+ d = readdir(dir);
+ if (!d)
+ break;
+ if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0)
+ continue;
+ path[pathlen++] = '/';
+ nul = memccpy(path + pathlen, d->d_name, '\0', sizeof(path) - pathlen);
+ if (!nul) {
+ error("path too large");
+ continue;
+ }
+ pathlen = nul - path - 1;
+ if (fstatat(fd, d->d_name, &st, AT_SYMLINK_NOFOLLOW) == -1) {
+ error("stat %s:", path);
+ continue;
+ }
+ if (S_ISDIR(st.st_mode))
+ search(fd, d->d_name);
+ else if (S_ISREG(st.st_mode) && strcmp(d->d_name, "uevent") == 0)
+ trigger(fd, d->d_name);
+ }
+ path[pathlen] = '\0';
+ if (errno)
+ error("readdir %s", path);
+ closedir(dir);
+}
+
+int
+main(int argc, char *argv[])
+{
+ pathlen = strlen(path);
+ search(AT_FDCWD, path);
+ return ret;
+}
diff --git a/src/devd.c b/src/devd.c
@@ -0,0 +1,164 @@
+/*
+devd reads kernel uevents via netlink and for each one spawns the
+/etc/hotplug script with an environment consisting of the uevent
+keys and values.
+
+After setting up the netlink socket, devd does a double fork to
+execute an orphaned /libexec/devd-trigger, which walks /sys/devices,
+triggering "add" uevents for each one.
+*/
+#define _POSIX_C_SOURCE 200809L
+#include <errno.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdnoreturn.h>
+#include <string.h>
+
+#include <fcntl.h>
+#include <spawn.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <linux/netlink.h>
+
+#define HOTPLUG "/etc/hotplug"
+#define TRIGGER "/libexec/devd-trigger"
+
+#define LEN(a) (sizeof(a) / sizeof((a)[0]))
+
+static posix_spawn_file_actions_t fileactions;
+
+static noreturn void
+fatal(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ if (fmt[0] && fmt[strlen(fmt) - 1] == ':') {
+ fputc(' ', stderr);
+ perror(NULL);
+ } else {
+ fputc('\n', stderr);
+ }
+ exit(1);
+}
+
+static int
+spawn(char *cmd, char *const env[])
+{
+ int err, status;
+ pid_t pid;
+
+ err = posix_spawn(&pid, cmd, &fileactions, NULL, (char *[]){cmd, NULL}, env);
+ if (err) {
+ fprintf(stderr, "spawn %s: %s\n", cmd, strerror(err));
+ return -1;
+ }
+ if (waitpid(pid, &status, 0) == -1) {
+ fprintf(stderr, "wait %jd: %s\n", (intmax_t)pid, strerror(errno));
+ return -1;
+ }
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
+ return -1;
+ return 0;
+}
+
+int
+main(int argc, char *argv[])
+{
+ extern char **environ;
+ int sock, err, status, rcvbuf = 8 * 1024 * 1024;
+ struct sockaddr_nl addr = {
+ .nl_family = AF_NETLINK,
+ .nl_groups = 1,
+ };
+ char buf[8192], *var, *eql, *nul, **envp, *env[256];
+ size_t envlen, envbase;
+ struct msghdr msg = {
+ .msg_iov = (struct iovec[]){{buf, sizeof(buf)}},
+ .msg_iovlen = 1,
+ };
+ ssize_t ret;
+ pid_t pid;
+
+ /* setup environment */
+ envlen = 0;
+ for (envp = environ; *envp; ++envp) {
+ if (strncmp(*envp, "PATH=", 5) == 0 ||
+ strncmp(*envp, "HOME=", 5) == 0)
+ {
+ env[envlen++] = *envp;
+ }
+ }
+ envbase = envlen;
+
+ /* create spawn file actions for child */
+ err = posix_spawn_file_actions_init(&fileactions);
+ if (err) {
+ perror(NULL);
+ return 1;
+ }
+ err = posix_spawn_file_actions_addopen(&fileactions, 0, "/dev/null", O_RDONLY, 0);
+ if (err) {
+ perror(NULL);
+ return 1;
+ }
+
+ /* open netlink socket */
+ sock = socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT);
+ if (sock == -1)
+ fatal("socket:");
+ if (setsockopt(sock, SOL_SOCKET, SO_RCVBUFFORCE, &rcvbuf, sizeof(rcvbuf)) == -1)
+ fatal("setsockopt:");
+ if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1)
+ fatal("bind:");
+
+ /* trigger system uevents */
+ pid = fork();
+ if (pid == -1)
+ fatal("fork:");
+ if (pid == 0) {
+ err = posix_spawn(&pid, TRIGGER, NULL, NULL, (char *[]){TRIGGER, NULL}, environ);
+ if (err)
+ fatal("spawn %s:", TRIGGER);
+ return 0;
+ }
+ if (waitpid(pid, &status, 0) == -1)
+ fatal("waitpid %jd:", (intmax_t)pid);
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
+ return 1;
+
+next:
+ for (;;) {
+ ret = recvmsg(sock, &msg, sizeof(buf));
+ if (ret == 0)
+ break;
+ if (ret == -1)
+ fatal("recvmsg:");
+ if (msg.msg_flags & MSG_TRUNC)
+ continue;
+ envlen = envbase;
+ for (var = buf; var < buf + ret; var = nul + 1) {
+ nul = memchr(var, '\0', ret - (var - buf));
+ if (!nul) {
+ fprintf(stderr, "netlink message is not NULL-terminated\n");
+ goto next;
+ }
+ eql = strchr(var, '=');
+ if (!eql)
+ continue;
+ if (envlen == LEN(env) - 1) {
+ fprintf(stderr, "uevent has too many variables\n");
+ goto next;
+ }
+ env[envlen++] = var;
+ }
+ env[envlen] = NULL;
+ spawn(HOTPLUG, env);
+ }
+}
diff --git a/src/gen.lua b/src/gen.lua
@@ -4,7 +4,9 @@ cflags{
'-std=c11',
}
-file('libexec/applyperms', '755', exe('applyperms', {'applyperms.c'}))
-file('libexec/mergeperms', '755', exe('mergeperms', {'mergeperms.c'}))
-file('libexec/shutdown', '755', exe('shutdown', {'shutdown.c'}))
-file('bin/syslogd', '755', exe('syslogd', {'syslogd.c'}))
+file('libexec/applyperms', '755', exe('applyperms', {'applyperms.c'}))
+file('libexec/devd-trigger', '755', exe('devd-trigger', {'devd-trigger.c'}))
+file('libexec/mergeperms', '755', exe('mergeperms', {'mergeperms.c'}))
+file('libexec/shutdown', '755', exe('shutdown', {'shutdown.c'}))
+file('bin/devd', '755', exe('devd', {'devd.c'}))
+file('bin/syslogd', '755', exe('syslogd', {'syslogd.c'}))