logo

oasis

Own branch of Oasis Linux (upstream: <https://git.sr.ht/~mcf/oasis/>) git clone https://anongit.hacktivis.me/git/oasis.git
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:

Asrc/devd-trigger.c106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/devd.c164+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/gen.lua10++++++----
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'}))