summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurent Bercot <ska-skaware@skarnet.org>2017-10-22 17:31:48 +0000
committerLaurent Bercot <ska-skaware@skarnet.org>2017-10-22 17:31:48 +0000
commit597f109b0a599d7c53eeb379300b658988b9adcd (patch)
treeb09a0a05f1da766cdb0786525e8e004138f649fc /src
parent32759d402e7327865ea18a203bd1c09f98735bd1 (diff)
downloadmdevd-597f109b0a599d7c53eeb379300b658988b9adcd.tar.xz
Fix .gitignore, add missing files
Diffstat (limited to 'src')
-rw-r--r--src/mdevd/PARSING.txt191
-rw-r--r--src/mdevd/deps-exe/mdevd1
-rw-r--r--src/mdevd/deps-exe/mdevd-coldplug1
-rw-r--r--src/mdevd/deps-exe/mdevd-netlink1
-rw-r--r--src/mdevd/mdevd-coldplug.c133
-rw-r--r--src/mdevd/mdevd-netlink.c177
-rw-r--r--src/mdevd/mdevd.c991
-rw-r--r--src/mdevd/mdevd.h8
8 files changed, 1503 insertions, 0 deletions
diff --git a/src/mdevd/PARSING.txt b/src/mdevd/PARSING.txt
new file mode 100644
index 0000000..500a8a7
--- /dev/null
+++ b/src/mdevd/PARSING.txt
@@ -0,0 +1,191 @@
+ /etc/mdev.conf parsing
+
+ First pass: just count envmatches and scriptelems, to allocate arrays in the stack.
+
+class | 0 1 2 3 4 5 6 7 8
+st\ev | \0 space ; $ # - @ \n other
+
+START | env+ele ele ele
+00 | END START X ENDLINE ENDLINE MINUS ENDLINE START DEVVAR
+
+MINUS | env+ele ele ele
+01 | X X X ENDLINE X X ENDLINE X DEVVAR
+
+DEVVAR | env
+02 | X ENDLINE SEMI DEVVAR X DEVVAR DEVVAR X DEVVAR
+
+SEMI |
+03 | X X X DEVVAR X DEVVAR DEVVAR X DEVVAR
+
+ENDLINE |
+04 | END ENDLINE ENDLINE ENDLINE ENDLINE ENDLINE ENDLINE START ENDLINE
+
+ END = 05, X = 06
+ States -> 0x00 to 0x06
+ Actions -> 0x10 (add an envmatch) and 0x20 (add a scriptelem)
+ Everything fits easily in an unsigned char.
+
+
+ Second pass: we need to fill the envmatches and scriptelems with values.
+ The whole mdev.conf semantics must be encoded in here.
+
+class | ` a b c d e f g h i j k l m n o p
+st\ev | \0 space ; $ # - @ \n , = > : * ! 0-7 8-9 other
+
+START | $ cont @ + m|d m|d m|d m|d m|d m|d m|d
+00 | END START X ENV1 COMMENT MINUS TOMAJ START DEVNAME X X DEVNAME DEVNAME DEVNAME DEVNAME DEVNAME DEVNAME
+
+COMMENT | +
+01 | END COMMENT COMMENT COMMENT COMMENT COMMENT COMMENT START COMMENT COMMENT COMMENT COMMENT COMMENT COMMENT COMMENT COMMENT COMMENT
+
+MINUS | $ @ m|d m|d m|d m|d m|d m|d m|d
+02 | X X X ENV1 X X TOMAJ X DEVNAME X X DEVNAME DEVNAME DEVNAME DEVNAME DEVNAME DEVNAME
+
+ENV1 | m|v m|v m|v m|v m|v m|v m|v m|v m|v m|v m|v
+03 | X X X INVAR1 X INVAR1 INVAR1 X INVAR1 X INVAR1 INVAR1 INVAR1 INVAR1 INVAR1 INVAR1 INVAR1
+
+INVAR1 | z
+04 | X X X INVAR1 X INVAR1 INVAR1 X INVAR1 TOVAL1 INVAR1 INVAR1 INVAR1 INVAR1 INVAR1 INVAR1 INVAR1
+
+TOVAL1 | m m m m m m m m m m
+05 | X X X X X INVAL1 INVAL1 X INVAL1 X INVAL1 INVAL1 INVAL1 INVAL1 INVAL1 INVAL1 INVAL1
+
+INVAL1 | z|re$
+06 | X TOUSER X INVAL1 X INVAL1 INVAL1 X INVAL1 INVAL1 INVAL1 INVAL1 INVAL1 INVAL1 INVAL1 INVAL1 INVAL1
+
+TOMAJ | m m
+07 | X X X X X X X X X X X X X X INMAJ INMAJ X
+
+INMAJ | z|mj
+08 | X X X X X X X X TOMIN X X X X X INMAJ INMAJ X
+
+TOMIN | m m
+09 | X X X X X X X X X X X X X X INMIL INMIL X
+
+INMIL | z|ml|h z|ml
+0a | X TOUSER X X X TOMIH X X X X X X X X INMIL INMIL X
+
+TOMIH | m m
+0b | X X X X X X X X X X X X X X INMIH INMIH X
+
+INMIH | z|mh
+0c | X TOUSER X X X X X X X X X X X X INMIH INMIH X
+
+DEVNAME | z|re z|v
+0d | X TOUSER X DEVNAME X DEVNAME DEVNAME X DEVNAME TORE X DEVNAME DEVNAME DEVNAME DEVNAME DEVNAME DEVNAME
+
+TORE | m m m m m m m m m m m m
+0e | X X X INRE X INRE INRE X INRE INRE INRE INRE INRE INRE INRE INRE INRE
+
+INRE | z|re$
+0f | X X TODEV INRE X INRE INRE X INRE INRE INRE INRE INRE INRE INRE INRE INRE
+
+TODEV | m m m m m m m m m m
+10 | X X X LASTRE X DEVNAME DEVNAME X DEVNAME X X DEVNAME DEVNAME DEVNAME DEVNAME DEVNAME DEVNAME
+
+LASTRE | z|re
+11 | X TOUSER X LASTRE X LASTRE LASTRE X LASTRE LASTRE LASTRE LASTRE LASTRE LASTRE LASTRE LASTRE LASTRE
+
+TOUSER | m m m m
+12 | X TOUSER X X X INUSER X X X X X X X X INUSER INUSER INUSER
+
+INUSER | z|u
+13 | X X X X X INUSER X X X X X TOGRP X X INUSER INUSER INUSER
+
+TOGRP | m m m m
+14 | X X X X X INGRP X X X X X X X X INGRP INGRP INGRP
+
+INGRP | z|g
+15 | X TOMODE X X X INGRP X X X X X X X X INGRP INGRP INGRP
+
+TOMODE | m
+16 | X TOMODE X X X X X X X X X X X X INMODE X X
+
+INMODE | z|M z|M|L|+
+17 | X TOPATH X X X X X START X X X X X X INMODE X X
+
+TOPATH | L r L a L|+ = > r|a !
+18 | END TOPATH X CMD COMMENT X CMD START X PATH PATH X CMD TOCMD X X X
+
+PATH | p p p p p p p p p
+19 | X X X X X INPATH INPATH X INPATH INPATH X INPATH X INPATH INPATH INPATH INPATH
+
+INPATH | L z z|L z|L|+
+1a | END TOCMD X X COMMENT INPATH INPATH START INPATH INPATH X INPATH X INPATH INPATH INPATH INPATH
+
+TOCMD | L r L a L|+ r|a
+1b | END TOCMD X CMD COMMENT X CMD START X X X X CMD X X X X
+
+CMD | c c c c c c c c c c c c c
+1c | X INCMD X INCMD X INCMD INCMD X INCMD INCMD INCMD INCMD INCMD INCMD INCMD INCMD INCMD
+
+INCMD | L z|L|+
+1d | END INCMD INCMD INCMD INCMD INCMD INCMD START INCMD INCMD INCMD INCMD INCMD INCMD INCMD INCMD INCMD
+
+st/ev | \0 space ; $ # - @ \n , = > : * ! 0-7 8-9 other
+class | ` a b c d e f g h i j k l m n o p
+
+END = 1e, X = 1f
+
+ States : 30+2. They fit exactly into 5 bits.
+ Actions: 25.
+
+ 0x80000000 unused, to avoid having to explicitly write U everywhere
+ 0x40000000 $: prepare an envmatch, the scriptelem is DEVMATCH_NOTHING
+ 0x20000000 @: the scriptelem is DEVMATCH_MAJMIN
+ 0x10000000 d: the scriptelem is DEVMATCH_DEVRE
+ 0x08000000 cont: scriptelem.flagcont = 1
+ 0x04000000 m: set the marker
+ 0x02000000 z: zero the current character
+ 0x01000000 re$: compile regex, finalize envmatch
+ 0x00800000 mj: compute maj
+ 0x00400000 ml: compute minlo
+ 0x00200000 h: minhi = minlo
+ 0x00100000 mh: compute minhi
+ 0x00080000 re: compile regex -> devmatch
+ 0x00040000 v: mark is a variable name, prepare envmatch
+ 0x00020000 u: compute uid
+ 0x00010000 g: compute gid
+ 0x00008000 M: compute mode
+ 0x00004000 L: finalize/accept scriptelem, move to next one
+ 0x00002000 r: elem->cmdtype |= ACTION_REMOVE
+ 0x00001000 a: elem->cmdtype |= ACTION_ADD
+ 0x00000800 =: elem->movetype = MOVEINFO_MOVE
+ 0x00000400 >: elem->movetype = MOVEINFO_MOVEANDLINK
+ 0x00000200 !: elem->movetype = MOVEINFO_NOCREATE
+ 0x00000100 p: set movepath
+ 0x00000080 c: set command
+ 0x00000040 +: line++
+ 0x00000020 unused.
+
+ Actions + states fit in a uint32_t.
+
+ To debug script_secondpass: at the start of the loop, add
+
+ LOLDEBUG("state: %02x char: %c newstate: %02x actions: %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", state, s[pos] == '\n' ? '\\' : s[pos], what & 0x1f,
+ what & 0x40000000 ? "$ " : "",
+ what & 0x20000000 ? "@ " : "",
+ what & 0x10000000 ? "d " : "",
+ what & 0x08000000 ? "cont " : "",
+ what & 0x04000000 ? "m " : "",
+ what & 0x02000000 ? "z " : "",
+ what & 0x01000000 ? "re$ " : "",
+ what & 0x00800000 ? "mj " : "",
+ what & 0x00400000 ? "ml " : "",
+ what & 0x00200000 ? "h " : "",
+ what & 0x00100000 ? "mh " : "",
+ what & 0x00080000 ? "re " : "",
+ what & 0x00040000 ? "v " : "",
+ what & 0x00020000 ? "u " : "",
+ what & 0x00010000 ? "g " : "",
+ what & 0x00008000 ? "M " : "",
+ what & 0x00004000 ? "L " : "",
+ what & 0x00002000 ? "r " : "",
+ what & 0x00001000 ? "a " : "",
+ what & 0x00000800 ? "= " : "",
+ what & 0x00000400 ? "> " : "",
+ what & 0x00000200 ? "! " : "",
+ what & 0x00000100 ? "p " : "",
+ what & 0x00000080 ? "c " : "",
+ what & 0x00000040 ? "+ " : "") ;
+
diff --git a/src/mdevd/deps-exe/mdevd b/src/mdevd/deps-exe/mdevd
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/mdevd/deps-exe/mdevd
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/mdevd/deps-exe/mdevd-coldplug b/src/mdevd/deps-exe/mdevd-coldplug
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/mdevd/deps-exe/mdevd-coldplug
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/mdevd/deps-exe/mdevd-netlink b/src/mdevd/deps-exe/mdevd-netlink
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/mdevd/deps-exe/mdevd-netlink
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/mdevd/mdevd-coldplug.c b/src/mdevd/mdevd-coldplug.c
new file mode 100644
index 0000000..dabd678
--- /dev/null
+++ b/src/mdevd/mdevd-coldplug.c
@@ -0,0 +1,133 @@
+/* ISC license. */
+
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <limits.h>
+#include <unistd.h>
+#include <errno.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/buffer.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/direntry.h>
+#include "mdevd.h"
+
+#define USAGE "mdevd-coldplug [ -s slashsys ]"
+#define dieusage() strerr_dieusage(100, USAGE)
+#define die1() strerr_diefu1sys(111, "write to stdout")
+
+static inline void create_event (int fddir, char const *sysdev, char const *sub, char const *name)
+{
+ if (faccessat(fddir, "dev", R_OK, AT_EACCESS) < 0)
+ {
+ if (errno == ENOENT) return ;
+ strerr_diefu6sys(111, "access dev in ", sysdev, "/", sub, "/", name) ;
+ }
+ if (buffer_put(buffer_1, "add@/", 5) < 0
+ || buffer_puts(buffer_1, sub) < 0
+ || buffer_put(buffer_1, "/", 1) < 0
+ || buffer_puts(buffer_1, name) < 0
+ || buffer_put(buffer_1, "\0ACTION=add\0DEVPATH=/dev/", sizeof("\0ACTION=add\0DEVPATH=/dev/") - 1) < 0
+ || buffer_puts(buffer_1, sub) < 0
+ || buffer_put(buffer_1, "/", 1) < 0
+ || buffer_puts(buffer_1, name) < 0
+ || buffer_put(buffer_1, "\0SUBSYSTEM=", sizeof("\0SUBSYSTEM=") - 1) < 0)
+ die1() ;
+
+ {
+ char *p ;
+ ssize_t r ;
+ char buf[PATH_MAX] ;
+ r = readlinkat(fddir, "subsystem", buf, PATH_MAX - 1) ;
+ if (r < 0) strerr_diefu6sys(111, "readlink subsystem in ", sysdev, "/", sub, "/", name) ;
+ buf[r] = 0 ;
+ p = strrchr(buf, '/') ;
+ if (p && buffer_put(buffer_1, p+1, strlen(p)) < 0) die1() ;
+ }
+
+ {
+ size_t len ;
+ size_t i = 0 ;
+ char buf[UEVENT_MAX_SIZE] ;
+ int fd = openat(fddir, "uevent", O_RDONLY) ;
+ if (fd < 0)
+ strerr_diefu6sys(111, "open uevent in ", sysdev, "/", sub, "/", name) ;
+ len = allread(fd, buf, UEVENT_MAX_SIZE) ;
+ if (!len) strerr_diefu6sys(111, "read uevent in ", sysdev, "/", sub, "/", name) ;
+ close(fd) ;
+ for (; i < len ; i++) if (buf[i] == '\n') buf[i] = 0 ;
+ if (buffer_put(buffer_1, buf, len) < 0) die1() ;
+ }
+
+ if (buffer_put(buffer_1, "", 1) < 1) die1() ;
+}
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+ char const *slashsys = "/sys" ;
+ size_t slashsyslen ;
+ PROG = "mdevd-coldplug" ;
+ {
+ subgetopt_t l = SUBGETOPT_ZERO ;
+ for (;;)
+ {
+ int opt = subgetopt_r(argc, argv, "s:", &l) ;
+ if (opt == -1) break ;
+ switch (opt)
+ {
+ case 's' : slashsys = l.arg ; break ;
+ default : dieusage() ;
+ }
+ }
+ argc -= l.ind ; argv += l.ind ;
+ }
+
+ slashsyslen = strlen(slashsys) ;
+ {
+ int fdsysdev ;
+ DIR *dirsysdev ;
+ char sysdev[slashsyslen + 5] ;
+ memcpy(sysdev, slashsys, slashsyslen) ;
+ memcpy(sysdev + slashsyslen, "/dev", 5) ;
+ fdsysdev = open(sysdev, O_RDONLY | O_DIRECTORY) ;
+ if (fdsysdev < 0) strerr_diefu2sys(111, "open ", sysdev) ;
+ dirsysdev = fdopendir(fdsysdev) ;
+ if (!dirsysdev) strerr_diefu2sys(111, "fdopendir ", sysdev) ;
+ for (;;)
+ {
+ direntry *d ;
+ int fdsub ;
+ DIR *dirsub ;
+ errno = 0 ;
+ d = readdir(dirsysdev) ;
+ if (!d) break ;
+ if (d->d_name[0] == '.') continue ;
+ fdsub = openat(fdsysdev, d->d_name, O_RDONLY | O_DIRECTORY) ;
+ if (fdsub < 0) strerr_diefu4sys(111, "open ", sysdev, "/", d->d_name) ;
+ dirsub = fdopendir(fdsub) ;
+ if (!dirsub) strerr_diefu4sys(111, "fdopendir ", sysdev, "/", d->d_name) ;
+ for (;;)
+ {
+ direntry *dd ;
+ int fddevice ;
+ errno = 0 ;
+ dd = readdir(dirsub) ;
+ if (!dd) break ;
+ if (dd->d_name[0] == '.') continue ;
+ fddevice = openat(fdsub, dd->d_name, O_RDONLY | O_DIRECTORY) ;
+ if (fddevice < 0) strerr_diefu6sys(111, "open ", sysdev, "/", d->d_name, "/", dd->d_name) ;
+ create_event(fddevice, sysdev, d->d_name, dd->d_name) ;
+ close(fddevice) ;
+ }
+ if (errno) strerr_diefu4sys(111, "readdir ", sysdev, "/", d->d_name) ;
+ dir_close(dirsub) ;
+ close(fdsub) ;
+ }
+ if (errno) strerr_diefu2sys(111, "readdir ", sysdev) ;
+ dir_close(dirsysdev) ;
+ close(fdsysdev) ;
+ }
+ if (!buffer_flush(buffer_1)) die1() ;
+ return 0 ;
+}
diff --git a/src/mdevd/mdevd-netlink.c b/src/mdevd/mdevd-netlink.c
new file mode 100644
index 0000000..3f55f23
--- /dev/null
+++ b/src/mdevd/mdevd-netlink.c
@@ -0,0 +1,177 @@
+/* ISC license. */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <linux/netlink.h>
+#include <skalibs/types.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/siovec.h>
+#include <skalibs/buffer.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/error.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/iopause.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/sig.h>
+#include <skalibs/selfpipe.h>
+#include "mdevd.h"
+
+#define USAGE "mdevd-netlink [ -d notification-fd ] [ -v verbosity ] [ -b kbufsz ]"
+#define dieusage() strerr_dieusage(100, USAGE)
+#define dienomem() strerr_diefu1sys(111, "build string") ;
+
+static unsigned int cont = 1, verbosity = 1 ;
+
+static inline ssize_t fd_recvmsg (int fd, struct msghdr *hdr)
+{
+ ssize_t r ;
+ do r = recvmsg(fd, hdr, MSG_DONTWAIT) ;
+ while ((r == -1) && (errno == EINTR)) ;
+ return r ;
+}
+
+static inline int netlink_init_stdin (unsigned int kbufsz)
+{
+ struct sockaddr_nl nl = { .nl_family = AF_NETLINK, .nl_pad = 0, .nl_groups = 1, .nl_pid = 0 } ;
+ close(0) ;
+ if (socket_internal(AF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT, DJBUNIX_FLAG_NB|DJBUNIX_FLAG_COE) < 0
+ || bind(0, (struct sockaddr *)&nl, sizeof(struct sockaddr_nl)) < 0)
+ return 0 ;
+
+ if (setsockopt(0, SOL_SOCKET, SO_RCVBUFFORCE, &kbufsz, sizeof(unsigned int)) < 0
+ && errno == EPERM
+ && setsockopt(0, SOL_SOCKET, SO_RCVBUF, &kbufsz, sizeof(unsigned int)) < 0)
+ return 0 ;
+ return 1 ;
+}
+
+static inline void handle_signals (void)
+{
+ for (;;)
+ {
+ int c = selfpipe_read() ;
+ switch (c)
+ {
+ case -1 : strerr_diefu1sys(111, "selfpipe_read") ;
+ case 0 : return ;
+ case SIGTERM :
+ cont = 0 ;
+ fd_close(0) ;
+ break ;
+ default :
+ strerr_dief1x(101, "internal error: inconsistent signal state. Please submit a bug-report.") ;
+ }
+ }
+}
+
+static inline void handle_stdout (void)
+{
+ if (!buffer_flush(buffer_1) && !error_isagain(errno))
+ strerr_diefu1sys(111, "flush stdout") ;
+}
+
+static inline void handle_netlink (void)
+{
+ struct sockaddr_nl nl;
+ struct iovec v[2] ;
+ struct msghdr msg =
+ {
+ .msg_name = &nl,
+ .msg_namelen = sizeof(struct sockaddr_nl),
+ .msg_iov = v,
+ .msg_iovlen = 2,
+ .msg_control = 0,
+ .msg_controllen = 0,
+ .msg_flags = 0
+ } ;
+ ssize_t r ;
+ buffer_wpeek(buffer_1, v) ;
+ siovec_trunc(v, 2, siovec_len(v, 2) - 1) ;
+ r = sanitize_read(fd_recvmsg(0, &msg)) ;
+ if (r < 0)
+ {
+ if (errno == EPIPE)
+ {
+ if (verbosity >= 2) strerr_warnw1x("received EOF on netlink") ;
+ cont = 0 ;
+ fd_close(0) ;
+ return ;
+ }
+ else strerr_diefu1sys(111, "receive netlink message") ;
+ }
+ if (!r) return ;
+ if (msg.msg_flags & MSG_TRUNC)
+ strerr_diefu2x(111, "buffer too small for ", "netlink message") ;
+ if (nl.nl_pid)
+ {
+ if (verbosity >= 3)
+ {
+ char fmt[PID_FMT] ;
+ fmt[pid_fmt(fmt, nl.nl_pid)] = 0 ;
+ strerr_warnw3x("netlink message", " from userspace process ", fmt) ;
+ }
+ return ;
+ }
+ buffer_wseek(buffer_1, r) ;
+ buffer_putnoflush(buffer_1, "", 1) ;
+}
+
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+ iopause_fd x[3] = { { .events = IOPAUSE_READ }, { .fd = 1 }, { .fd = 0 } } ;
+ unsigned int notif = 0 ;
+ PROG = "mdevd-netlink" ;
+ {
+ unsigned int kbufsz = 65536 ;
+ subgetopt_t l = SUBGETOPT_ZERO ;
+ for (;;)
+ {
+ int opt = subgetopt_r(argc, argv, "d:v:b:", &l) ;
+ if (opt == -1) break ;
+ switch (opt)
+ {
+ case 'd' : if (!uint0_scan(l.arg, &notif) || notif < 3) dieusage() ; break ;
+ case 'v' : if (!uint0_scan(l.arg, &verbosity)) dieusage() ; break ;
+ case 'b' : if (!uint0_scan(l.arg, &kbufsz)) dieusage() ; break ;
+ default : dieusage() ;
+ }
+ }
+ argc -= l.ind ; argv += l.ind ;
+ if (!netlink_init_stdin(kbufsz)) strerr_diefu1sys(111, "init netlink") ;
+ }
+
+ x[0].fd = selfpipe_init() ;
+ if (x[0].fd < 0) strerr_diefu1sys(111, "init selfpipe") ;
+ if (selfpipe_trap(SIGTERM) < 0) strerr_diefu1sys(111, "trap SIGTERM") ;
+
+ if (verbosity >= 2) strerr_warni1x("starting") ;
+ if (notif)
+ {
+ fd_write(notif, "\n", 1) ;
+ fd_close(notif) ;
+ }
+
+ while (cont || buffer_len(buffer_1))
+ {
+ int r ;
+ x[1].events = buffer_len(buffer_1) ? IOPAUSE_WRITE : 0 ;
+ x[2].events = buffer_available(buffer_1) >= UEVENT_MAX_SIZE + 1 ? IOPAUSE_READ : 0 ;
+ r = iopause(x, 2 + cont, 0, 0) ;
+ if (r < 0) strerr_diefu1sys(111, "iopause") ;
+ if (!r) continue ;
+ if (x[1].revents & IOPAUSE_EXCEPT) break ;
+ if (x[1].revents & IOPAUSE_WRITE) handle_stdout() ;
+ if (x[0].revents & (IOPAUSE_READ | IOPAUSE_EXCEPT)) handle_signals() ;
+ if (cont && x[2].events & IOPAUSE_READ && x[2].revents & (IOPAUSE_READ | IOPAUSE_EXCEPT))
+ handle_netlink() ;
+ }
+ if (verbosity >= 2) strerr_warni1x("exiting") ;
+ return 0 ;
+}
diff --git a/src/mdevd/mdevd.c b/src/mdevd/mdevd.c
new file mode 100644
index 0000000..2a419bc
--- /dev/null
+++ b/src/mdevd/mdevd.c
@@ -0,0 +1,991 @@
+/* ISC license. */
+
+#include <sys/sysmacros.h> /* makedev, major, minor */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <limits.h>
+#include <signal.h>
+#include <pwd.h>
+#include <grp.h>
+#include <regex.h>
+#include <libgen.h> /* basename */
+#include <stdio.h> /* rename */
+#include <skalibs/types.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/sig.h>
+#include <skalibs/selfpipe.h>
+#include <skalibs/tai.h>
+#include <skalibs/environ.h>
+#include <skalibs/env.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/iopause.h>
+#include <skalibs/skamisc.h>
+#include <skalibs/surf.h>
+#include <skalibs/random.h>
+#include <skalibs/lolstdio.h>
+#include "mdevd.h"
+
+#define USAGE "mdevd [ -v verbosity ] [ -f conffile ] [ -n ] [ -s slashsys ] [ -d slashdev ]"
+#define dieusage() strerr_dieusage(100, USAGE)
+
+#define BUFSIZE 8192
+#define UEVENT_MAX_VARS 63
+
+#define ACTION_NONE 0x0
+#define ACTION_ADD 0x1
+#define ACTION_REMOVE 0x2
+
+static int dryrun = 0 ;
+static int cont = 1 ;
+static pid_t pid = 0 ;
+static unsigned int verbosity = 1 ;
+static char const *slashsys = "/sys" ;
+static char const *fwbase = "/lib/firmware" ;
+static SURFSchedule surf_ctx = SURFSCHEDULE_ZERO ;
+static unsigned int root_maj, root_min ;
+
+struct envmatch_s
+{
+ size_t var ;
+ regex_t re ;
+} ;
+
+struct majmin_s
+{
+ unsigned int maj ;
+ unsigned int minlo ;
+ unsigned int minhi ;
+} ;
+
+union devmatch_u
+{
+ regex_t devre ;
+ struct majmin_s majmin ;
+} ;
+
+#define DEVMATCH_NOTHING 0
+#define DEVMATCH_CATCHALL 1
+#define DEVMATCH_DEVRE 2
+#define DEVMATCH_MAJMIN 3
+
+#define MOVEINFO_NOTHING 0
+#define MOVEINFO_NOCREATE 1
+#define MOVEINFO_MOVE 2
+#define MOVEINFO_MOVEANDLINK 3
+
+typedef struct scriptelem_s scriptelem, *scriptelem_ref ;
+struct scriptelem_s
+{
+ uid_t uid ;
+ gid_t gid ;
+ mode_t mode ;
+ size_t movepath ;
+ size_t command ;
+ union devmatch_u devmatch ;
+ unsigned int envmatchlen : 11 ;
+ unsigned int devmatchtype : 2 ;
+ unsigned int movetype : 2 ;
+ unsigned int cmdtype : 2 ;
+ unsigned int flagcont : 1 ;
+ unsigned short envmatchs ;
+} ;
+
+static scriptelem const scriptelem_catchall =
+{
+ .uid = 0,
+ .gid = 0,
+ .mode = 0660,
+ .movepath = 0,
+ .command = 0,
+ .envmatchlen = 0,
+ .devmatchtype = DEVMATCH_CATCHALL,
+ .movetype = MOVEINFO_NOTHING,
+ .cmdtype = ACTION_NONE,
+ .flagcont = 0
+} ;
+
+struct uevent_s
+{
+ unsigned short len ;
+ unsigned short varn ;
+ unsigned short vars[UEVENT_MAX_VARS + 1] ;
+ char buf[UEVENT_MAX_SIZE + PATH_MAX + 5] ;
+} ;
+#define UEVENT_ZERO { .len = 0, .varn = 0 }
+
+
+ /* Utility functions */
+
+static inline void script_free (scriptelem *script, unsigned short scriptlen, struct envmatch_s *envmatch, unsigned short envmatchlen)
+{
+ unsigned short i = 0 ;
+ for (; i < scriptlen ; i++)
+ if (script[i].devmatchtype == DEVMATCH_DEVRE)
+ regfree(&script[i].devmatch.devre) ;
+ for (i = 0 ; i < envmatchlen ; i++) regfree(&envmatch[i].re) ;
+}
+
+static void mdevd_random_init (void)
+{
+ char seed[160] ;
+ random_makeseed(seed) ;
+ surf_init(&surf_ctx, seed) ;
+}
+
+static inline void surfname (char *s, size_t n)
+{
+ static char const oklist[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZghijklmnopqrstuvwxyz-_0123456789abcdef" ;
+ surf(&surf_ctx, s, n) ;
+ while (n--) s[n] = oklist[s[n] & 63] ;
+}
+
+static inline int mkdirp (char *s)
+{
+ size_t n = strlen(s) ;
+ size_t i = 0 ;
+ for (; i < n ; i++)
+ {
+ if (s[i] == '/')
+ {
+ int r = 0 ;
+ s[i] = 0 ;
+ if (dryrun) strerr_warni2x("dry run: mkdir ", s) ;
+ else r = mkdir(s, 0755) ;
+ s[i] = '/' ;
+ if (r < 0) break ;
+ }
+ }
+ return i >= n ;
+}
+
+static int makesubdirs (char *path)
+{
+ if (strrchr(path, '/') && !mkdirp(path))
+ {
+ if (verbosity) strerr_warnwu2sys("create subdirectories for ", path) ;
+ return 0 ;
+ }
+ return 1 ;
+}
+
+
+ /* mdev.conf parsing. See PARSING.txt for details. */
+
+ /* The first pass is simple. The goal is just to compute scriptlen and envmatchlen. */
+
+static inline unsigned char firstpass_cclass (char c)
+{
+ static unsigned char const classtable[65] = "08888888817881888888888888888888188438888888858888888888888288886" ;
+ return (unsigned char)c < 65 ? classtable[(unsigned char)c] - '0' : 8 ;
+}
+
+static inline void script_firstpass (char *s, unsigned short *scriptlen, unsigned short *envmatchlen)
+{
+ static unsigned char const table[5][9] =
+ {
+ { 0x05, 0x00, 0x06, 0x34, 0x04, 0x01, 0x24, 0x00, 0x22 },
+ { 0x06, 0x06, 0x06, 0x34, 0x06, 0x06, 0x24, 0x06, 0x22 },
+ { 0x06, 0x04, 0x13, 0x02, 0x06, 0x02, 0x02, 0x06, 0x02 },
+ { 0x06, 0x06, 0x06, 0x02, 0x06, 0x02, 0x02, 0x06, 0x02 },
+ { 0x05, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x04 }
+ } ;
+ size_t i = 0 ;
+ unsigned short n = 0, m = 0 ;
+ unsigned int state = 0 ;
+ while (state < 5)
+ {
+ unsigned char what = table[state][firstpass_cclass(s[i++])] ;
+ state = what & 0x07 ;
+ if (what & 0x10) m++ ;
+ if (what & 0x20) n++ ;
+ }
+ if (state == 6)
+ {
+ s[--i] = 0 ;
+ while (i && !strchr(" \n\r\t", s[i])) i-- ;
+ strerr_dief2x(2, "syntax error during first pass, after string: ", (char *)s + i) ;
+ }
+
+ *scriptlen = n ;
+ *envmatchlen = m ;
+}
+
+ /* The second pass is the real, complete mdev.conf parsing. */
+
+static inline unsigned char secondpass_cclass (char c)
+{
+ static unsigned char const classtable[65] = "`ppppppppagppappppppppppppppppppampdcppppplpheppnnnnnnnnookbpijpf" ;
+ return (unsigned char)c < 65 ? classtable[(unsigned char)c] - '`' : 16 ;
+}
+
+static inline void script_secondpass (char *s, scriptelem *script, struct envmatch_s *envmatch)
+{
+ static uint32_t const table[30][17] =
+ {
+ { 0x0000001e, 0x00000000, 0x0000001f, 0x40000003, 0x00000001, 0x08000002, 0x20000007, 0x00000040, 0x1400000d, 0x0000001f, 0x0000001f, 0x1400000d, 0x1400000d, 0x1400000d, 0x1400000d, 0x1400000d, 0x1400000d },
+ { 0x0000001e, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000040, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001 },
+ { 0x0000001f, 0x0000001f, 0x0000001f, 0x40000003, 0x0000001f, 0x0000001f, 0x20000007, 0x0000001f, 0x1400000d, 0x0000001f, 0x0000001f, 0x1400000d, 0x1400000d, 0x1400000d, 0x1400000d, 0x1400000d, 0x1400000d },
+ { 0x0000001f, 0x0000001f, 0x0000001f, 0x04040004, 0x0000001f, 0x04040004, 0x04040004, 0x0000001f, 0x04040004, 0x0000001f, 0x04040004, 0x04040004, 0x04040004, 0x04040004, 0x04040004, 0x04040004, 0x04040004 },
+ { 0x0000001f, 0x0000001f, 0x0000001f, 0x00000004, 0x0000001f, 0x00000004, 0x00000004, 0x0000001f, 0x00000004, 0x02000005, 0x00000004, 0x00000004, 0x00000004, 0x00000004, 0x00000004, 0x00000004, 0x00000004 },
+ { 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x04000006, 0x04000006, 0x0000001f, 0x04000006, 0x0000001f, 0x04000006, 0x04000006, 0x04000006, 0x04000006, 0x04000006, 0x04000006, 0x04000006 },
+ { 0x0000001f, 0x03000012, 0x0000001f, 0x00000006, 0x0000001f, 0x00000006, 0x00000006, 0x0000001f, 0x00000006, 0x00000006, 0x00000006, 0x00000006, 0x00000006, 0x00000006, 0x00000006, 0x00000006, 0x00000006 },
+ { 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x04000008, 0x04000008, 0x0000001f },
+ { 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x02800009, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x00000008, 0x00000008, 0x0000001f },
+ { 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0400000a, 0x0400000a, 0x0000001f },
+ { 0x0000001f, 0x02600012, 0x0000001f, 0x0000001f, 0x0000001f, 0x0240000b, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000000a, 0x0000000a, 0x0000001f },
+ { 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0400000c, 0x0400000c, 0x0000001f },
+ { 0x0000001f, 0x02100012, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000000c, 0x0000000c, 0x0000001f },
+ { 0x0000001f, 0x02080012, 0x0000001f, 0x0000000d, 0x0000001f, 0x0000000d, 0x0000000d, 0x0000001f, 0x0000000d, 0x0204000e, 0x0000001f, 0x0000000d, 0x0000000d, 0x0000000d, 0x0000000d, 0x0000000d, 0x0000000d },
+ { 0x0000001f, 0x0000001f, 0x0000001f, 0x0400000f, 0x0000001f, 0x0400000f, 0x0400000f, 0x0000001f, 0x0400000f, 0x0400000f, 0x0400000f, 0x0400000f, 0x0400000f, 0x0400000f, 0x0400000f, 0x0400000f, 0x0400000f },
+ { 0x0000001f, 0x0000001f, 0x03000010, 0x0000000f, 0x0000001f, 0x0000000f, 0x0000000f, 0x0000001f, 0x0000000f, 0x0000000f, 0x0000000f, 0x0000000f, 0x0000000f, 0x0000000f, 0x0000000f, 0x0000000f, 0x0000000f },
+ { 0x0000001f, 0x0000001f, 0x0000001f, 0x04000011, 0x0000001f, 0x0400000d, 0x0400000d, 0x0000001f, 0x0400000d, 0x0000001f, 0x0000001f, 0x0400000d, 0x0400000d, 0x0400000d, 0x0400000d, 0x0400000d, 0x0400000d },
+ { 0x0000001f, 0x02080012, 0x0000001f, 0x00000011, 0x0000001f, 0x00000011, 0x00000011, 0x0000001f, 0x00000011, 0x00000011, 0x00000011, 0x00000011, 0x00000011, 0x00000011, 0x00000011, 0x00000011, 0x00000011 },
+ { 0x0000001f, 0x00000012, 0x0000001f, 0x0000001f, 0x0000001f, 0x04000013, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x04000013, 0x04000013, 0x04000013 },
+ { 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x00000013, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x02020014, 0x0000001f, 0x0000001f, 0x00000013, 0x00000013, 0x00000013 },
+ { 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x04000015, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x04000015, 0x04000015, 0x04000015 },
+ { 0x0000001f, 0x02010016, 0x0000001f, 0x0000001f, 0x0000001f, 0x00000015, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x00000015, 0x00000015, 0x00000015 },
+ { 0x0000001f, 0x00000016, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x04000017, 0x0000001f, 0x0000001f },
+ { 0x0000001f, 0x02008018, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0200c040, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x00000017, 0x0000001f, 0x0000001f },
+ { 0x0000401e, 0x00000018, 0x0000001f, 0x0000201c, 0x00004001, 0x0000001f, 0x0000101c, 0x00004040, 0x0000001f, 0x00000819, 0x00000419, 0x0000001f, 0x0000301c, 0x0000021b, 0x0000001f, 0x0000001f, 0x0000001f },
+ { 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000011a, 0x0000011a, 0x0000001f, 0x0000011a, 0x0000011a, 0x0000001f, 0x0000011a, 0x0000001f, 0x0000011a, 0x0000011a, 0x0000011a, 0x0000011a },
+ { 0x0000401e, 0x0200001b, 0x0000001f, 0x0000001f, 0x02004001, 0x0000001a, 0x0000001a, 0x02004040, 0x0000001a, 0x0000001a, 0x0000001f, 0x0000001a, 0x0000001f, 0x0000001a, 0x0000001a, 0x0000001a, 0x0000001a },
+ { 0x0000401e, 0x0000001b, 0x0000001f, 0x0000201c, 0x00004001, 0x0000001f, 0x0000101c, 0x00004040, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000301c, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f },
+ { 0x0000001f, 0x0000009d, 0x0000001f, 0x0000009d, 0x0000001f, 0x0000009d, 0x0000009d, 0x0000001f, 0x0000009d, 0x0000009d, 0x0000009d, 0x0000009d, 0x0000009d, 0x0000009d, 0x0000009d, 0x0000009d, 0x0000009d },
+ { 0x0000401e, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x02004040, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d }
+ } ;
+ size_t mark = 0 ;
+ size_t col0 = 0 ;
+ size_t pos = 0 ;
+ unsigned int line = 0 ;
+ uint32_t state = 0 ;
+ unsigned short i = 0 ; /* current scriptelem index */
+ unsigned short j = 0 ; /* current envmatch index */
+ LOLDEBUG("in script_secondpass") ;
+ while (state < 0x1e)
+ {
+ uint32_t what = table[state][secondpass_cclass(s[pos])] ;
+ state = what & 0x1f ;
+ if (what & 0x40000000)
+ {
+ script[i].devmatchtype = DEVMATCH_NOTHING ;
+ script[i].envmatchs = j ;
+ }
+ if (what & 0x20000000) script[i].devmatchtype = DEVMATCH_MAJMIN ;
+ if (what & 0x10000000)
+ {
+ script[i].devmatchtype = DEVMATCH_DEVRE ;
+ script[i].envmatchs = j ;
+ }
+ if (what & 0x08000000) script[i].flagcont = 1 ;
+ if (what & 0x04000000) mark = pos ;
+ if (what & 0x02000000) s[pos] = 0 ;
+ if (what & 0x01000000)
+ {
+ int r = regcomp(&envmatch[j].re, s + mark, REG_EXTENDED) ;
+ if (r)
+ {
+ char errbuf[256] ;
+ char fmtline[UINT_MAX] ;
+ fmtline[uint_fmt(fmtline, line)] = 0 ;
+ regerror(r, &envmatch[j].re, errbuf, 256) ;
+ strerr_dief8x(2, "syntax error during second pass: ", "line ", fmtline, ": unable to compile regular expression ", "for envmatch: ", s + mark, ": ", errbuf) ;
+ }
+ j++ ;
+ script[i].envmatchlen++ ;
+ }
+ if (what & 0x00800000)
+ {
+ if (!uint0_scan(s + mark, &script[i].devmatch.majmin.maj))
+ {
+ char fmtline[UINT_MAX] ;
+ fmtline[uint_fmt(fmtline, line)] = 0 ;
+ strerr_dief5x(2, "syntax error during second pass: ", "line ", fmtline, ": unable to scan major from string: ", s + mark) ;
+ }
+ }
+ if (what & 0x00400000)
+ {
+ if (!uint0_scan(s + mark, &script[i].devmatch.majmin.minlo))
+ {
+ char fmtline[UINT_MAX] ;
+ fmtline[uint_fmt(fmtline, line)] = 0 ;
+ strerr_dief5x(2, "syntax error during second pass: ", "line ", fmtline, ": unable to scan minor from string: ", s + mark) ;
+ }
+ }
+ if (what & 0x00200000) script[i].devmatch.majmin.minhi = script[i].devmatch.majmin.minlo ;
+ if (what & 0x00100000)
+ {
+ if (!uint0_scan(s + mark, &script[i].devmatch.majmin.minhi))
+ {
+ char fmtline[UINT_MAX] ;
+ fmtline[uint_fmt(fmtline, line)] = 0 ;
+ strerr_dief5x(2, "syntax error during second pass: ", "line ", fmtline, ": unable to scan minor from string: ", s + mark) ;
+ }
+ }
+ if (what & 0x00080000)
+ {
+ int r = regcomp(&script[i].devmatch.devre, s + mark, REG_EXTENDED) ;
+ if (r)
+ {
+ char errbuf[256] ;
+ char fmtline[UINT_MAX] ;
+ fmtline[uint_fmt(fmtline, line)] = 0 ;
+ regerror(r, &envmatch[j].re, errbuf, 256) ;
+ strerr_dief8x(2, "syntax error during second pass: ", "line ", fmtline, ": unable to compile regular expression ", " for devmatch: ", s + mark, ": ", errbuf) ;
+ }
+ }
+ if (what & 0x00040000) envmatch[j].var = mark ;
+ if (what & 0x00020000)
+ {
+ struct passwd *pw = getpwnam(s + mark) ;
+ if (pw) script[i].uid = pw->pw_uid ;
+ else if (!uid0_scan(s + mark, &script[i].uid))
+ {
+ char fmtline[UINT_MAX] ;
+ fmtline[uint_fmt(fmtline, line)] = 0 ;
+ strerr_dief5x(2, "syntax error during second pass: ", "line ", fmtline, ": unable to get uid from string: ", s + mark) ;
+ }
+ }
+ if (what & 0x00010000)
+ {
+ struct group *gr = getgrnam(s + mark) ;
+ if (gr) script[i].gid = gr->gr_gid ;
+ else if (!gid0_scan(s + mark, &script[i].gid))
+ {
+ char fmtline[UINT_MAX] ;
+ fmtline[uint_fmt(fmtline, line)] = 0 ;
+ strerr_dief5x(2, "syntax error during second pass: ", "line ", fmtline, ": unable to get gid from string: ", s + mark) ;
+ }
+ }
+ if (what & 0x00008000)
+ {
+ unsigned int m ;
+ if (!uint0_oscan(s + mark, &m))
+ {
+ char fmtline[UINT_MAX] ;
+ fmtline[uint_fmt(fmtline, line)] = 0 ;
+ strerr_dief5x(2, "syntax error during second pass: ", "line ", fmtline, ": unable to scan mode from string: ", s + mark) ;
+ }
+ script[i].mode = m ;
+ }
+ if (what & 0x00002000) script[i].cmdtype |= ACTION_REMOVE ;
+ if (what & 0x00001000) script[i].cmdtype |= ACTION_ADD ;
+ if (what & 0x00000800) script[i].movetype = MOVEINFO_MOVE ;
+ if (what & 0x00000400) script[i].movetype = MOVEINFO_MOVEANDLINK ;
+ if (what & 0x00000200) script[i].movetype = MOVEINFO_NOCREATE ;
+ if (what & 0x00000100) script[i].movepath = pos ;
+ if (what & 0x00000080) script[i].command = pos ;
+ if (what & 0x00000040) { line++ ; col0 = pos ; }
+ if (what & 0x00004000) i++ ;
+ pos++ ;
+ }
+
+ if (state == 0x1f)
+ {
+ char fmtline[UINT_MAX] ;
+ char fmtcol[UINT_MAX] ;
+ fmtline[uint_fmt(fmtline, line)] = 0 ;
+ fmtcol[uint_fmt(fmtcol, pos - col0 - 1)] = 0 ;
+ strerr_dief5x(2, "syntax error during second pass: ", "line ", fmtline, " column ", fmtcol) ;
+ }
+}
+
+
+ /* Firmware management */
+
+static inline int wait_for_loading (char const *sysdevpath, size_t sysdevpathlen)
+{
+ int lfd = -1 ;
+ unsigned int n = 150 ;
+ static tain_t const period = { .sec = TAI_ZERO, .nano = 200000000 } ;
+ char loadingfn[sysdevpathlen + 9] ;
+ memcpy(loadingfn, sysdevpath, sysdevpathlen) ;
+ memcpy(loadingfn + sysdevpathlen, "/loading", 9) ;
+ while (n--) /* sysfs doesn't support inotify, so we have to poll -_- */
+ {
+ tain_t deadline ;
+ lfd = open_write(loadingfn) ;
+ if (lfd >= 0) break ;
+ tain_add_g(&deadline, &period) ;
+ deepsleepuntil_g(&deadline) ;
+ }
+ if (ndelay_off(lfd) < 0)
+ {
+ fd_close(lfd) ;
+ return -1 ;
+ }
+ return lfd ;
+}
+
+static inline void load_firmware (char const *fw, char const *sysdevpath)
+{
+ size_t fwbaselen = strlen(fwbase) ;
+ size_t fwlen = strlen(fw) ;
+ size_t sysdevpathlen = strlen(sysdevpath) ;
+ int fwfd, loadingfd ;
+ char fwfn[fwbaselen + fwlen + 2] ;
+ memcpy(fwfn, fwbase, fwbaselen) ;
+ fwfn[fwbaselen] = '/' ;
+ memcpy(fwfn + fwbaselen + 1, fw, fwlen + 1) ;
+ if (dryrun)
+ {
+ strerr_warni5x("dry run: copy ", fwfn, " to ", sysdevpath, "/data") ;
+ return ;
+ }
+ fwfd = open_readb(fwfn) ;
+ if (fwfd < 0 && verbosity >= 2) strerr_warnwu3sys("open ", fwfn, " for reading") ;
+ loadingfd = wait_for_loading(sysdevpath, sysdevpathlen) ;
+ if (loadingfd < 0)
+ {
+ if (verbosity >= 2) strerr_warnwu3sys("open ", sysdevpath, "/loading for writing") ;
+ goto errclosef ;
+ }
+ if (fwfd < 0) goto errclosel ;
+ if (fd_write(loadingfd, "1", 1) < 1)
+ {
+ if (verbosity >= 2) strerr_warnwu3sys("write 1 to ", sysdevpath, "/loading") ;
+ goto errclosel ;
+ }
+ {
+ int datafd ;
+ char datafn[sysdevpathlen + 6] ;
+ memcpy(datafn, sysdevpath, sysdevpathlen) ;
+ memcpy(datafn + sysdevpathlen, "/data", 6) ;
+ datafd = open_write(datafn) ;
+ if (datafd < 0)
+ {
+ if (verbosity >= 2) strerr_warnwu3sys("open ", datafn, " for writing") ;
+ goto errload ;
+ }
+ if (ndelay_off(datafd) < 0)
+ {
+ if (verbosity >= 2) strerr_warnwu2sys("ndelay_off ", datafn) ;
+ goto errload ;
+ }
+ if (fd_cat(fwfd, datafd) < 0)
+ {
+ if (verbosity >= 2) strerr_warnwu4sys("copy ", fwfn, " to ", datafn) ;
+ goto errload ;
+ }
+ fd_close(datafd) ;
+ fd_write(loadingfd, "0", 1) ;
+ }
+ fd_close(loadingfd) ;
+ fd_close(fwfd) ;
+ return ;
+
+ errload:
+ allwrite(loadingfd, "-1", 2) ;
+ errclosel:
+ fd_close(loadingfd) ;
+ errclosef:
+ if (fwfd >= 0) fd_close(fwfd) ;
+}
+
+
+ /* uevent management */
+
+static char *event_getvar (struct uevent_s *event, char const *var)
+{
+ size_t varlen = strlen(var) ;
+ unsigned short i = 1 ;
+ for (; i < event->varn ; i++)
+ if (!strncmp(var, event->buf + event->vars[i], varlen) && event->buf[event->vars[i] + varlen] == '=')
+ break ;
+ return i < event->varn ? event->buf + event->vars[i] + varlen + 1 : 0 ;
+}
+
+static inline unsigned char format_cclass (char c)
+{
+ static unsigned char const classtable[58] = "0333333333333333333333333333333333333133333333332222222222" ;
+ return (unsigned char)c < 58 ? classtable[(unsigned char)c] - '0' : 3 ;
+}
+
+static ssize_t alias_format (char *out, size_t outmax, char const *in, char const *data, regmatch_t const *off)
+{
+ static unsigned char const table[2][4] = { { 0x12, 0x01, 0x10, 0x10 }, { 0x03, 0x10, 0x20, 0x03 } } ;
+ size_t w = 0 ;
+ unsigned int state = 0 ;
+ while (state < 2)
+ {
+ char next = *in++ ;
+ unsigned char what = table[state][format_cclass(next)] ;
+ state = what & 0x03 ;
+ if (what & 0x10)
+ {
+ if (w >= outmax) return (errno = ENAMETOOLONG, -1) ;
+ if (out) out[w] = next ;
+ w++ ;
+ }
+ if (what & 0x20)
+ {
+ unsigned int i = next - '0' ;
+ size_t len = off[i].rm_eo - off[i].rm_so ;
+ if (w + len > outmax) return -1 ;
+ if (out) memcpy(out + w, data + off[i].rm_so, len) ;
+ w += len ;
+ }
+ }
+ if (state == 3) return (errno = EINVAL, -1) ;
+ return w ;
+}
+
+static inline void spawn_command (char const *command, struct uevent_s const *event)
+{
+ char const *argv[4] = { "/bin/sh", "-c", command, 0 } ;
+ size_t envlen = env_len((char const **)environ) ;
+ char const *envp[envlen + event->varn + 1] ;
+ if (!env_merge(envp, envlen + event->varn + 1, (char const **)environ, envlen, event->buf + event->vars[1], event->len - event->vars[1]))
+ {
+ if (verbosity) strerr_warnwu1sys("merge environment to spawn command") ;
+ return ;
+ }
+ pid = child_spawn0(argv[0], argv, envp) ;
+ if (!pid)
+ {
+ if (verbosity) strerr_warnwu1sys("spawn command") ;
+ }
+}
+
+static inline int run_scriptelem (struct uevent_s *event, scriptelem const *elem, char const *storage, struct envmatch_s const *envmatch, char *devname, mode_t devtype, unsigned int action, int mmaj, int mmin)
+{
+ size_t devnamelen = strlen(devname) ;
+ size_t nodelen = 0 ;
+ char *node = event->buf + event->len + 5 ;
+ regmatch_t off[10] ;
+ unsigned short i = 0 ;
+ for (; i < elem->envmatchlen ; i++)
+ {
+ char const *x = event_getvar(event, storage + envmatch[elem->envmatchs + i].var) ;
+ if (!x) return 0 ;
+ if (regexec(&envmatch[elem->envmatchs + i].re, x, 0, 0, 0)) return 0 ;
+ }
+
+ switch (elem->devmatchtype)
+ {
+ case DEVMATCH_NOTHING:
+ case DEVMATCH_CATCHALL: break ;
+ case DEVMATCH_MAJMIN:
+ if (mmaj >= 0 && mmin >= 0 && mmaj == elem->devmatch.majmin.maj && mmin >= elem->devmatch.majmin.minlo && mmin <= elem->devmatch.majmin.minhi) break ;
+ return 0 ;
+ case DEVMATCH_DEVRE:
+ if (!regexec(&elem->devmatch.devre, devname, 10, off, 0)
+ && !off[0].rm_so && off[0].rm_eo == strlen(devname))
+ break ;
+ return 0 ;
+ }
+
+ switch (elem->movetype)
+ {
+ case MOVEINFO_NOTHING :
+ case MOVEINFO_NOCREATE :
+ memcpy(node, devname, devnamelen + 1) ;
+ nodelen = devnamelen ;
+ break ;
+ case MOVEINFO_MOVE :
+ case MOVEINFO_MOVEANDLINK :
+ {
+ ssize_t r = alias_format(node, PATH_MAX, storage + elem->movepath, devname, off) ;
+ if (r <= 1)
+ {
+ if (verbosity) strerr_warnwu5sys("process expression \"", storage + elem->movepath, "\" with devname \"", devname, "\"") ;
+ return -1 ;
+ }
+ if (node[r - 2] == '/')
+ {
+ if (r + devnamelen >= PATH_MAX)
+ {
+ errno = ENAMETOOLONG ;
+ if (verbosity) strerr_warnwu2sys("create alias for ", devname) ;
+ return -1 ;
+ }
+ memcpy(node + r - 1, devname, devnamelen + 1) ;
+ nodelen = r + devnamelen - 1 ;
+ }
+ break ;
+ }
+ }
+ if (elem->movetype != MOVEINFO_NOCREATE && action == ACTION_ADD && mmaj >= 0)
+ {
+ if (!makesubdirs(node)) return -1 ;
+ if (dryrun)
+ {
+ char fmtmaj[UINT_FMT] ;
+ char fmtmin[UINT_FMT] ;
+ char fmtmode[UINT_OFMT] ;
+ fmtmaj[uint_fmt(fmtmaj, mmaj)] = 0 ;
+ fmtmin[uint_fmt(fmtmin, mmin)] = 0 ;
+ fmtmode[uint_ofmt(fmtmode, elem->mode)] = 0 ;
+ strerr_warni6x("dry run: mknod ", node, S_ISBLK(devtype) ? " b " : " c ", fmtmaj, " ", fmtmin) ;
+ strerr_warni4x("dry run: chmod ", fmtmode, " ", node) ;
+ }
+ else if (mknod(node, elem->mode | devtype, makedev(mmaj, mmin)) < 0)
+ {
+ if (errno != EEXIST)
+ {
+ if (verbosity) strerr_warnwu2sys("mknod ", node) ;
+ return -1 ;
+ }
+ if (chmod(node, elem->mode) < 0 && verbosity >= 2)
+ strerr_warnwu2sys("chmod ", node) ;
+ }
+ if (elem->uid || elem->gid)
+ {
+ if (dryrun)
+ {
+ char fmtuid[UID_FMT] ;
+ char fmtgid[GID_FMT] ;
+ fmtuid[uid_fmt(fmtuid, elem->uid)] = 0 ;
+ fmtgid[gid_fmt(fmtgid, elem->gid)] = 0 ;
+ strerr_warni6x("dry run: chown ", fmtuid, ":", fmtgid, " ", node) ;
+ }
+ else if (chown(node, elem->uid, elem->gid) < 0 && verbosity >= 2)
+ strerr_warnwu2sys("chown ", node) ;
+ }
+ if (mmaj == root_maj && mmin == root_min)
+ {
+ if (dryrun) strerr_warni3x("dry run: symlink ", node, " to root") ;
+ else symlink(node, "root") ;
+ }
+ if (elem->movetype == MOVEINFO_MOVEANDLINK)
+ {
+ if (!makesubdirs(devname)) return -1 ;
+ if (dryrun) strerr_warni4x("dry run: symlink ", node, " to ", devname) ;
+ else if (symlink(node, devname) < 0)
+ {
+ if (errno != EEXIST)
+ {
+ if (verbosity)
+ strerr_warnwu4sys("symlink ", node, " to ", devname) ;
+ return -1 ;
+ }
+ else
+ {
+ char tmppath[devnamelen + 20] ;
+ memcpy(tmppath, devname, devnamelen) ;
+ memcpy(tmppath + devnamelen, ":mdevd-", 7) ;
+ surfname(tmppath + devnamelen + 7, 12) ;
+ tmppath[devnamelen + 19] = 0 ;
+ if (symlink(node, tmppath) < 0)
+ {
+ if (verbosity)
+ strerr_warnwu4sys("symlink ", node, " to ", tmppath) ;
+ }
+ else if (rename(tmppath, devname) < 0)
+ {
+ if (verbosity)
+ strerr_warnwu4sys("rename ", tmppath, " to ", devname) ;
+ unlink(tmppath) ;
+ }
+ }
+ }
+ }
+ }
+
+ if (action & elem->cmdtype)
+ {
+ event->vars[event->varn++] = event->len ;
+ memcpy(event->buf + event->len, "MDEV=", 5) ;
+ event->len += 6 + nodelen ;
+ if (dryrun)
+ {
+ strerr_warni2x("dry run: spawn ", storage + elem->command) ;
+ if (verbosity >= 2)
+ {
+ char buf[UEVENT_MAX_SIZE + PATH_MAX + 5] ;
+ size_t j = 0 ;
+ unsigned short i = 1 ;
+ for (; i < event->varn ; i++)
+ {
+ size_t len = strlen(event->buf + event->vars[i]) ;
+ memcpy(buf + j, event->buf + event->vars[i], len) ;
+ buf[j+len] = ' ' ;
+ j += len+1 ;
+ }
+ buf[j-1] = 0 ;
+ strerr_warni2x("dry run: added variables: ", buf) ;
+ }
+ }
+ else spawn_command(storage + elem->command, event) ;
+ }
+
+ if (elem->movetype != MOVEINFO_NOCREATE && action == ACTION_REMOVE && mmaj >= 0)
+ {
+ if (elem->movetype == MOVEINFO_MOVEANDLINK)
+ {
+ if (dryrun) strerr_warni2x("dry run: unlink ", devname) ;
+ else unlink(devname) ;
+ }
+ if (dryrun) strerr_warni2x("dry run: unlink ", node) ;
+ else unlink(node) ;
+ }
+
+ return !elem->flagcont ;
+}
+
+static inline void run_script (struct uevent_s *event, scriptelem const *script, unsigned short scriptlen, char const *storage, struct envmatch_s const *envmatch, char *devname, mode_t devtype, unsigned int action, int mmaj, int mmin)
+{
+ unsigned short i = 0 ;
+ for (; i < scriptlen ; i++)
+ {
+ int r ;
+ r = run_scriptelem(event, script + i, storage, envmatch, devname, devtype, action, mmaj, mmin) ;
+ if (r) break ;
+ }
+}
+
+static inline void act_on_event (struct uevent_s *event, char *sysdevpath, size_t sysdevpathlen, unsigned int action, scriptelem const *script, unsigned short scriptlen, char const *storage, struct envmatch_s const *envmatch)
+{
+ ssize_t hasmajmin = 0 ;
+ unsigned int mmaj, mmin ;
+ mode_t devtype = S_IFCHR ;
+ char *devname ;
+ char const *x = event_getvar(event, "MAJOR") ;
+ char buf[UEVENT_MAX_SIZE] ;
+ if (action == ACTION_ADD)
+ {
+ if (x && uint0_scan(x, &mmaj))
+ {
+ x = event_getvar(event, "MINOR") ;
+ if (x && uint0_scan(x, &mmin)) hasmajmin = 1 ;
+ }
+ if (!hasmajmin)
+ {
+ memcpy(sysdevpath + sysdevpathlen, "/dev", 5) ;
+ hasmajmin = openreadnclose(sysdevpath, buf, UINT_FMT << 1) ;
+ sysdevpath[sysdevpathlen] = 0 ;
+ if (hasmajmin > 0)
+ {
+ size_t i = uint_scan(buf, &mmaj) ;
+ if (i > 0 && buf[i] == ':')
+ {
+ size_t j = uint_scan(buf + i + 1, &mmin) ;
+ if (j > 0 && buf[i+1+j] == '\n') ;
+ else hasmajmin = 0 ;
+ }
+ else hasmajmin = 0 ;
+ }
+ }
+ }
+
+ devname = event_getvar(event, "DEVNAME") ;
+ if (!devname)
+ {
+ ssize_t r ;
+ memcpy(sysdevpath + sysdevpathlen, "/uevent", 8) ;
+ r = openreadnclose(sysdevpath, buf, UEVENT_MAX_SIZE-1) ;
+ sysdevpath[sysdevpathlen] = 0 ;
+ if (r > 0)
+ {
+ buf[r] = 0 ;
+ devname = strstr(buf, "\nDEVNAME=") ;
+ if (devname)
+ {
+ devname += 9 ;
+ *strchr(devname, '\n') = 0 ;
+ }
+ }
+ if (!devname) devname = basename(sysdevpath) ;
+ }
+ if (strlen(devname) >= PATH_MAX - 1)
+ {
+ if (verbosity) strerr_warnwu2x("device name too long: ", devname) ;
+ return ;
+ }
+ if (strstr(sysdevpath, "/block/")) devtype = S_IFBLK ;
+ else
+ {
+ x = event_getvar(event, "SUBSYSTEM") ;
+ if (x && str_start(x, "block")) devtype = S_IFBLK ;
+ }
+ run_script(event, script, scriptlen, storage, envmatch, devname, devtype, action, hasmajmin > 0 ? mmaj : -1, hasmajmin > 0 ? mmin : -1) ;
+}
+
+static inline void on_event (struct uevent_s *event, scriptelem const *script, unsigned short scriptlen, char const *storage, struct envmatch_s const *envmatch)
+{
+ unsigned int action ;
+ char const *x = event_getvar(event, "ACTION") ;
+ if (!x) return ;
+ if (!strcmp(x, "add")) action = ACTION_ADD ;
+ else if (!strcmp(x, "remove")) action = ACTION_REMOVE ;
+ else return ;
+ x = event_getvar(event, "DEVPATH") ;
+ if (!x) return ;
+ {
+ size_t devpathlen = strlen(x) ;
+ size_t slashsyslen = strlen(slashsys) ;
+ char sysdevpath[devpathlen + slashsyslen + 2 + 7] ;
+ memcpy(sysdevpath, slashsys, slashsyslen) ;
+ sysdevpath[slashsyslen] = '/' ;
+ memcpy(sysdevpath + slashsyslen + 1, x, devpathlen + 1) ;
+ x = event_getvar(event, "FIRMWARE") ;
+ if (action == ACTION_ADD || !x) act_on_event(event, sysdevpath, slashsyslen + devpathlen + 1, action, script, scriptlen, storage, envmatch) ;
+ if (action == ACTION_ADD && x) load_firmware(x, sysdevpath) ;
+ }
+}
+
+
+ /* Tying it all together */
+
+static inline int handle_signals (void)
+{
+ for (;;)
+ {
+ int c = selfpipe_read() ;
+ switch (c)
+ {
+ case -1 : strerr_diefu1sys(111, "selfpipe_read") ;
+ case 0 : return 0 ;
+ case SIGHUP : return 1 ;
+ case SIGCHLD :
+ if (!pid) wait_reap() ;
+ else
+ {
+ int wstat ;
+ int r = wait_pid_nohang(pid, &wstat) ;
+ if (r < 0)
+ if (errno != ECHILD) strerr_diefu1sys(111, "wait_pid_nohang") ;
+ else break ;
+ else if (!r) break ;
+ pid = 0 ;
+ }
+ break ;
+ default :
+ strerr_dief1x(101, "internal error: inconsistent signal handling. Please submit a bug-report.") ;
+ }
+ }
+}
+
+static void handle_stdin (struct uevent_s *event, scriptelem const *script, unsigned short scriptlen, char const *storage, struct envmatch_s const *envmatch, size_t *w)
+{
+ while (!pid)
+ {
+ ssize_t r ;
+ if (event->len >= UEVENT_MAX_SIZE)
+ strerr_dief2x(1, "received invalid event: ", "too long") ;
+ r = sanitize_read(getlnmax(buffer_0, event->buf + event->len, UEVENT_MAX_SIZE - event->len, w, '\0')) ;
+ if (r < 0)
+ {
+ cont = 0 ;
+ if (errno != EPIPE && verbosity) strerr_warnwu1sys("read from stdin") ;
+ if (event->len) strerr_dief1x(1, "received incomplete event") ;
+ }
+ if (r <= 0) break ;
+ if (*w > 1)
+ {
+ if (event->varn >= UEVENT_MAX_VARS)
+ strerr_dief2x(1, "received invalid event: ", "too many variables") ;
+ event->vars[event->varn++] = event->len ;
+ event->len += *w ;
+ }
+ else
+ {
+ if (event->varn > 1) on_event(event, script, scriptlen, storage, envmatch) ;
+ event->len = 0 ;
+ event->varn = 0 ;
+ }
+ *w = 0 ;
+ }
+}
+
+int main (int argc, char const *const *argv)
+{
+ char const *configfile = "/etc/mdev.conf" ;
+ iopause_fd x[2] = { { .events = IOPAUSE_READ }, { .fd = 0 } } ;
+ PROG = "mdevd" ;
+ {
+ char const *slashdev = "/dev" ;
+ subgetopt_t l = SUBGETOPT_ZERO ;
+ for (;;)
+ {
+ int opt = subgetopt_r(argc, argv, "nv:f:s:d:F:", &l) ;
+ if (opt == -1) break ;
+ switch (opt)
+ {
+ case 'n' : dryrun = 1 ; break ;
+ case 'v' : if (!uint0_scan(l.arg, &verbosity)) dieusage() ; break ;
+ case 'f' : configfile = l.arg ; break ;
+ case 's' : slashsys = l.arg ; break ;
+ case 'd' : slashdev = l.arg ; break ;
+ case 'F' : fwbase = l.arg ; break ;
+ default : dieusage() ;
+ }
+ }
+ argc -= l.ind ; argv += l.ind ;
+ if (configfile[0] != '/') strerr_dief1x(100, "conffile must be an absolute path") ;
+ if (chdir(slashdev) < 0) strerr_diefu2sys(111, "chdir to ", slashdev) ;
+ }
+
+ if (strlen(slashsys) >= PATH_MAX - 1) strerr_dief1x(100, "paths too long") ;
+ if (!fd_sanitize()) strerr_diefu1sys(111, "sanitize standard fds") ;
+
+ {
+ struct stat st ;
+ if (stat("/", &st) < 0) strerr_diefu1sys(111, "stat /") ;
+ root_maj = major(st.st_dev) ;
+ root_min = minor(st.st_dev) ;
+ }
+
+ if (ndelay_on(0) < 0) strerr_diefu1sys(111, "set stdin nonblocking") ;
+ x[0].fd = selfpipe_init() ;
+ if (x[0].fd < 0) strerr_diefu1sys(111, "init selfpipe") ;
+ if (sig_ignore(SIGPIPE) < 0) strerr_diefu1sys(111, "ignore SIGPIPE") ;
+ {
+ sigset_t set ;
+ sigemptyset(&set) ;
+ sigaddset(&set, SIGCHLD) ;
+ sigaddset(&set, SIGHUP) ;
+ if (selfpipe_trapset(&set) < 0)
+ strerr_diefu1sys(111, "trap signals") ;
+ }
+ mdevd_random_init() ;
+ umask(0) ;
+
+ while (cont || pid)
+ {
+ ssize_t len ;
+ unsigned short scriptlen = 0 ;
+ unsigned short envmatchlen = 0 ;
+ char buf[BUFSIZE] ;
+ len = openreadnclose(configfile, buf, BUFSIZE - 1) ;
+ if (len < 0)
+ {
+ if (errno != ENOENT) strerr_diefu2sys(111, "read ", configfile) ;
+ strerr_warnwu2sys("read ", configfile) ;
+ len = 0 ;
+ }
+ buf[len++] = 0 ;
+ script_firstpass(buf, &scriptlen, &envmatchlen) ;
+
+ {
+ size_t w = 0 ;
+ struct uevent_s event = UEVENT_ZERO ;
+ struct envmatch_s envmatch[envmatchlen ? envmatchlen : 1] ;
+ scriptelem script[scriptlen + 1] ;
+ memset(script, 0, scriptlen * sizeof(scriptelem)) ;
+ script[scriptlen++] = scriptelem_catchall ;
+ LOLDEBUG("before script_secondpass: len: %lld scriptlen: %hu envmatchlen: %hu sizeof(scriptelem): %llu", len, scriptlen, envmatchlen, sizeof(scriptelem)) ;
+ script_secondpass(buf, script, envmatch) ;
+ while (cont || pid)
+ {
+ static tain_t const deadline = TAIN_INFINITE ;
+ if (buffer_len(buffer_0)) handle_stdin(&event, script, scriptlen, buf, envmatch, &w) ;
+ x[1].events = pid ? 0 : IOPAUSE_READ ;
+ if (iopause_g(x, 1 + cont, &deadline) < 0) strerr_diefu1sys(111, "iopause") ;
+ if (x[0].revents & IOPAUSE_READ && handle_signals()) break ;
+ if (cont && !pid && x[1].revents & IOPAUSE_READ)
+ handle_stdin(&event, script, scriptlen, buf, envmatch, &w) ;
+ }
+ script_free(script, scriptlen, envmatch, envmatchlen) ;
+ }
+ }
+ return 0 ;
+}
diff --git a/src/mdevd/mdevd.h b/src/mdevd/mdevd.h
new file mode 100644
index 0000000..a99f692
--- /dev/null
+++ b/src/mdevd/mdevd.h
@@ -0,0 +1,8 @@
+ /* ISC license. */
+
+#ifndef MDEVD_H
+#define MDEVD_H
+
+#define UEVENT_MAX_SIZE 4096
+
+#endif