summary refs log tree commit diff
diff options
context:
space:
mode:
authorLaurent Bercot <ska-skaware@skarnet.org>2018-01-14 17:54:42 +0000
committerLaurent Bercot <ska-skaware@skarnet.org>2018-01-14 17:54:42 +0000
commit4984158b95c5a0c3922cd34940c393652d080f99 (patch)
treebd110462519e041679dc0332088b43132214fe45
parentc5e7afbd8d62f3217687f21c0691a02797dc0df5 (diff)
downloadmdevd-4984158b95c5a0c3922cd34940c393652d080f99.tar.gz
mdevd-4984158b95c5a0c3922cd34940c393652d080f99.tar.xz
mdevd-4984158b95c5a0c3922cd34940c393652d080f99.zip
New mdevd model, prepare for 0.1.0.0
 - mdevd-netlink removed
 - mdevd listens to the netlink itself
 - mdevd-coldplug writes nothing to stdout, but triggers the kernel to create uevents
-rw-r--r--COPYING2
-rw-r--r--INSTALL2
-rw-r--r--NEWS9
-rw-r--r--doc/index.html5
-rw-r--r--doc/mdevd-coldplug.html25
-rw-r--r--doc/mdevd-netlink.html124
-rw-r--r--doc/mdevd.html44
-rw-r--r--doc/upgrade.html11
-rw-r--r--package/deps.mak7
-rw-r--r--package/info2
-rw-r--r--package/modes1
-rw-r--r--package/targets.mak1
-rw-r--r--src/mdevd/deps-exe/mdevd-netlink1
-rw-r--r--src/mdevd/mdevd-coldplug.c152
-rw-r--r--src/mdevd/mdevd-netlink.c177
-rw-r--r--src/mdevd/mdevd.c219
-rw-r--r--src/mdevd/mdevd.h8
17 files changed, 279 insertions, 511 deletions
diff --git a/COPYING b/COPYING
index 9e2739b..8a62df1 100644
--- a/COPYING
+++ b/COPYING
@@ -1,4 +1,4 @@
-Copyright (c) 2017 Laurent Bercot <ska-skaware@skarnet.org>
+Copyright (c) 2017-2018 Laurent Bercot <ska-skaware@skarnet.org>
 
 Permission to use, copy, modify, and distribute this software for any
 purpose with or without fee is hereby granted, provided that the above
diff --git a/INSTALL b/INSTALL
index 7385164..876e1da 100644
--- a/INSTALL
+++ b/INSTALL
@@ -6,7 +6,7 @@ Build Instructions
 
   - A Linux-based system with a standard C development environment
   - GNU make version 3.81 or later
-  - skalibs version 2.6.0.2 or later: http://skarnet.org/software/skalibs/
+  - skalibs version 2.6.3.0 or later: http://skarnet.org/software/skalibs/
 
  This software is Linux-specific. It will run on a Linux kernel,
 version 2.6.10 or later.
diff --git a/NEWS b/NEWS
index 78f3327..32d7343 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,14 @@
 Changelog for mdevd.
 
+In 0.1.0.0
+----------
+
+ - mdevd-netlink removed.
+ - mdevd now listens to the netlink itself.
+ - mdevd-coldplug now doesn't print events to stdout; it triggers
+the events in the kernel instead. This means mdevd should run first
+to catch the events, and then mdevd-coldplug should be ran.
+
 
 In 0.0.1.0
 ----------
diff --git a/doc/index.html b/doc/index.html
index c11b508..19a43a4 100644
--- a/doc/index.html
+++ b/doc/index.html
@@ -53,7 +53,7 @@ entirely compatible with advanced mdev usage such as
 The Linux kernel must be 2.6.10 or later. </li>
  <li> GNU make, version 3.81 or later </li>
  <li> <a href="//skarnet.org/software/skalibs/">skalibs</a> version
-2.6.0.2 or later. It's a build-time requirement. It's also a run-time
+2.6.3.0 or later. It's a build-time requirement. It's also a run-time
 requirement if you link against the shared version of the skalibs
 library. </li>
 </ul>
@@ -69,7 +69,7 @@ library. </li>
 
 <ul>
  <li> The current released version of mdevd is
-<a href="mdevd-0.0.1.0.tar.gz">0.0.1.0</a>. </li>
+<a href="mdevd-0.1.0.0.tar.gz">0.1.0.0</a>. </li>
  <li> Alternatively, you can checkout a copy of the
 <a href="//git.skarnet.org/cgi-bin/cgit.cgi/mdevd/">mdevd
 git repository</a>:
@@ -100,7 +100,6 @@ the previous versions of mdevd and the current one. </li>
 
 <ul>
 <li><a href="mdevd.html">The <tt>mdevd</tt> program</a></li>
-<li><a href="mdevd-netlink.html">The <tt>mdevd-netlink</tt> program</a></li>
 <li><a href="mdevd-coldplug.html">The <tt>mdevd-coldplug</tt> program</a></li>
 </ul>
 
diff --git a/doc/mdevd-coldplug.html b/doc/mdevd-coldplug.html
index fbbb5aa..027c849 100644
--- a/doc/mdevd-coldplug.html
+++ b/doc/mdevd-coldplug.html
@@ -21,8 +21,8 @@
 <p>
 <tt>mdevd-coldplug</tt> performs a <em>coldplug</em>: it scans
 <tt>/sys</tt> for all registered devices a uevent manager would
-want to perform actions on, and generates uevents for all these
-devices.
+want to perform actions on, and tells the kernel to generate uevents
+for all these devices.
 </p>
 
 <h2> Interface </h2>
@@ -33,17 +33,13 @@ devices.
 
 <ul>
  <li> mdevd-coldplug scans <tt>/sys</tt> for devices. </li>
- <li> For every suitable device it finds, it generates a
-uevent and writes it to its stdout, using the same format
-as <a href="mdevd-netlink.html">mdevd-netlink</a>. </li>
- <li> It exits when it has finished scanning. </li>
+ <li> For every suitable device it finds, it tells the kernel
+to generate an event. If a device manager such as
+as <a href="mdevd.html">mdevd</a> is listening to the netlink
+at this point, it will pick up the series of events. </li>
+ <li> mdevd-coldplug exits when it has finished scanning. </li>
 </ul>
 
-<p>
- This implies that the <tt>mdevd-coldplug | mdevd</tt> command line
-will function as a coldplug manager, just like <tt>mdev -s</tt>.
-</p>
-
 <h2> Options </h2>
 
 <ul>
@@ -62,12 +58,7 @@ pseudo-filesystem is mounted on <em>slashsys</em>. Default is <tt>/sys</tt>. </l
 
 <ul>
  <li> mdevd-coldplug is a short-lived program, just like
-<tt>mdev -s</tt>. </li>
- <li> Unlike <tt>mdev -s</tt>, however, mdevd-coldplug does
-not act on the uevents it generates. It simply prints them.
-This allows for easy debugging. </li>
- <li> To act on the uevents, simply pipe the output of
-<tt>mdevd-coldplug</tt> into <a href="mdevd.html">mdevd</a>. </li>
+<tt>mdev -s</tt> or <tt>udevadm trigger</tt>. </li>
 </ul>
 
 </body>
diff --git a/doc/mdevd-netlink.html b/doc/mdevd-netlink.html
deleted file mode 100644
index d0aa054..0000000
--- a/doc/mdevd-netlink.html
+++ /dev/null
@@ -1,124 +0,0 @@
-<html>
-  <head>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
-    <meta http-equiv="Content-Language" content="en" />
-    <title>mdevd: the mdevd-netlink program</title>
-    <meta name="Description" content="mdevd: the mdevd-netlink program" />
-    <meta name="Keywords" content="mdevd linux administration root utilities devd mdev udev s6-uevent-listener uevent netlink" />
-    <!-- <link rel="stylesheet" type="text/css" href="//skarnet.org/default.css" /> -->
-  </head>
-<body>
-
-<p>
-<a href="index.html">mdevd</a><br />
-<a href="//skarnet.org/software/">Software</a><br />
-<a href="//skarnet.org/">skarnet.org</a>
-</p>
-
-<h1> The <tt>mdevd-netlink</tt> program </h1>
-
-<p>
-<tt>mdevd-netlink</tt> listens to the netlink interface for uevents
-(also called "hotplug" or "udev" events), and writes those uevents to
-its standard output, using a simple format.
-</p>
-
-<p>
-<a href="mdevd.html">mdevd</a> expects uevents in the same
-format mdevd-netlink writes them. So the
-<tt>mdevd-netlink | mdevd</tt> command line will function as
-a daemon reading uevents from the netlink and handling them.
-</p>
-
-<h2> Interface </h2>
-
-<pre>
-     mdevd-netlink [ -d notif ] [ -v <em>verbosity</em> ] [ -b kbufsz ]
-</pre>
-
-<ul>
- <li> mdevd-netlink binds to the netlink interface and listens for
-hotplug events, as the <em>udevd</em> program does. </li>
- <li> It writes event information to its stdout. The output contains
-null characters, so a terminal will not display them correctly. To
-properly use mdevd-netlink, it should be piped into a handler
-program such as <a href="mdevd.html">mdevd</a>. </li>
- <li> mdevd-netlink is a long-lived program.
-When it receives a SIGTERM, it stops listening to hotplug events;
-it will exit as soon as it has flushed its event queue to stdout. </li>
-</ul>
-
-<h2> Options </h2>
-
-<ul>
- <li> <tt>-d</tt>&nbsp;<em>notif</em>&nbsp;: when ready (actually
-listening to the netlink), write a newline to file descriptor <em>notif</em>
-then close it. This allows mdevd-netlink to use the
-<a href="//skarnet.org/software/s6/notifywhenup.html">s6 mechanism to
-notify readiness</a>. <em>notif</em> cannot be lesser than 3. If this
-option is not given, no readiness notification is sent. </li>
- <li> <tt>-v</tt>&nbsp;<em>verbosity</em>&nbsp;: be more or less verbose.
-Default verbosity is 1. 0 will only print fatal error messages, 3 will
-print warnings every time the netlink interface sends something
-unexpected. </li>
- <li> <tt>-b</tt>&nbsp;<em>kbufsz</em>&nbsp;: try and reserve a kernel buffer of
-<em>kbufsz</em> bytes for the netlink queue. Too large a buffer wastes kernel memory;
-too small a buffer risks losing events. The default is 65536 (which is on
-the large side). </li>
-</ul>
-
-<h2> Protocol </h2>
-
-<ul>
- <li> An event is a series of null-terminated strings as they are sent by
-the kernel to the netlink; mdevd-netlink adds a final empty string
-(i.e. an additional null character) to mark the end of the series. </li>
- <li> The first string is a short description of the event; it normally
-contains the string "@/". Other strings after the first are of the form
-"VARIABLE=value", and describe the environment which a hotplug helper
-for the event (registered in <tt>/proc/sys/kernel/hotplug</tt>) would be
-spawned with. </li>
- <li> Example (newlines added for clarity): <pre>
-add@/class/input/input9/mouse2\0
-ACTION=add\0
-DEVPATH=/class/input/input9/mouse2\0
-SUBSYSTEM=input\0
-SEQNUM=106\0
-PHYSDEVPATH=/devices/pci0000:00/0000:00:1d.1/usb2/2­2/2­2:1.0
-PHYSDEVBUS=usb\0
-PHYSDEVDRIVER=usbhid\0
-MAJOR=13\0
-MINOR=34\0
-\0 </pre> </li>
-</ul>
-
-<h2> Notes </h2>
-
-<ul>
- <li> mdevd-netlink is a daemon; it should be run under a proper supervision system such
-as <a href="//skarnet.org/software/s6/">s6</a>. </li>
-<li> If you are running mdevd-netlink, the program you pipe its
-output into should be the only program handling uevents; that means
-that <tt>/proc/sys/kernel/hotplug</tt> should be empty. </li>
-<li> The <tt>mdevd-netlink | mdevd</tt> command line is a quick
-way of running a uevent manager, but there are ways to improve it:
- <ul>
-  <li> Using the
-<a href="//skarnet.org/software/execline/">execline</a> scripting
-language, the <tt>pipeline { mdevd-netlink } mdevd</tt> command
-line will avoid leaving a shell around. </li>
-  <li> The best way to use mdevd-netlink and mdevd is with a
-supervision system:
-under <a href="//skarnet.org/software/s6/">s6</a> or
-<a href="//skarnet.org/software/s6-rc/">s6-rc</a>, write a service
-pipeline where <tt>mdevd-netlink</tt> is a producer and
-<tt>mdevd</tt> is a consumer. This setup has the advantage, among
-others, that you can restart the netlink listener and the event handler
-separately. The <tt>examples/</tt> subdirectory of the mdevd
-package contains example configurations for such a setup. </li>
- </ul> </li>
-</ul>
-
-</body>
-</html>
diff --git a/doc/mdevd.html b/doc/mdevd.html
index 14d2867..0ea61bb 100644
--- a/doc/mdevd.html
+++ b/doc/mdevd.html
@@ -19,8 +19,8 @@
 <h1> The <tt>mdevd</tt> program </h1>
 
 <p>
-<tt>mdevd</tt> is a uevent manager. It reads a series of
-uevents on its stdin; for every uevent it reads, it performs
+<tt>mdevd</tt> is a uevent manager. It connects to the netlink and reads
+a series of uevents; for every uevent it reads, it performs
 actions according to its configuration file. Actions can
 be inserting a kernel module, creating or modifying device
 entries in <tt>/dev</tt>, etc.
@@ -36,35 +36,29 @@ The differences between mdevd and mdev are:
 <ul>
  <li> mdev needs to be registered as a hotplug manager and the
 kernel spawns an instance of mdev per uevent; for every uevent,
-mdev has to parse its configuration file. By contrast, there
-is only one instance of mdevd, reading a series of uevents and
+mdev has to parse its configuration file. By contrast, mdevd is
+a daemon: it's long-lived, and there is only one instance,
+reading a series of uevents and
 performing actions without forking; the configuration file is
 read and parsed only once. </li>
- <li> mdevd reads uevents on its stdin. It is not suitable as
-a hotplug manager, and it does not connect to the netlink itself
-either. It is meant to be used in conjunction with
-<a href="mdevd-netlink.html">mdevd-netlink</a>, which reads
-uevents from the netlink, or with
-<a href="mdevd-coldplug.html">mdevd-coldplug</a>, which generates
-coldplug uevents. </li>
 </ul>
 
 <h2> Interface </h2>
 
 <pre>
-     mdevd [ -v <em>verbosity</em> ] [ -f <em>conffile</em> ] [ -n ] [ -s <em>slashsys</em> ] [ -d <em>slashdev</em> ] [ -F <em>fwbase</em> ]
+     mdevd [ -v <em>verbosity</em> ] [ -D <em>notif</em> ] [ -b <em>kbufsz</em> ] [ -f <em>conffile</em> ] [ -n ] [ -s <em>slashsys</em> ] [ -d <em>slashdev</em> ] [ -F <em>fwbase</em> ]
 </pre>
 
 <ul>
  <li> mdevd reads and parses its configuration file <tt>/etc/mdev.conf</tt>. </li>
- <li> It then reads its stdin, waiting for uevents.
- <li> It exits when the stream of uevents ends. (EOF on stdin.) </li>
+ <li> It then connects to the netlink and reads from it, waiting for uevents.
+ <li> It exits 0 on a SIGTERM. </li>
 </ul>
 
 <h2> Exit codes </h2>
 
 <ul>
- <li> 0: EOF read on standard input </li>
+ <li> 0: SIGTERM received, clean exit </li>
  <li> 1: received an invalid event </li>
  <li> 2: syntax error in the configuration file </li>
  <li> 100: wrong usage </li>
@@ -79,6 +73,7 @@ coldplug uevents. </li>
 
 <ul>
  <li> SIGHUP: re-read the configuration file </li>
+ <li> SIGTERM: exit as soon as possible </li>
 </ul>
 
 <h2> Options </h2>
@@ -87,6 +82,17 @@ coldplug uevents. </li>
  <li> <tt>-v</tt>&nbsp;<em>verbosity</em>&nbsp;: be more or less verbose.
 Default verbosity is 1. 0 will only print fatal error messages, 3 or more
 is seriously verbose debugging. </li>
+ <li> <tt>-D</tt>&nbsp;<em>notif</em>&nbsp;: when ready
+(actually listening to the netlink),
+write a newline to file descriptor <em>notif</em> then close it.
+This allows mdevd to use the
+<a href="//skarnet.org/software/s6/notifywhenup.html">s6 mechanism to notify
+readiness</a>. <em>notif</em> cannot be lesser than 3.
+If this option is not given, no readiness notification is sent. </li>
+ <li> <tt>-b</tt>&nbsp;<em>kbufsz</em>&nbsp;: try and reserve a kernel buffer of
+<em>kbufsz</em> bytes for the netlink queue. Too large a buffer wastes kernel memory;
+too small a buffer risks losing events. The default is 65536 (which is reasonable
+for average systems). </li>
  <li> <tt>-n</tt>&nbsp;: dry run. mdevd will not create or delete
 device nodes, and it will not spawn commands. Instead, it will print to stdout
 the actions it would have performed. </li>
@@ -121,14 +127,6 @@ nothing else.
 <h2> Notes </h2>
 
 <ul>
- <li> Strictly speaking, mdevd is a short-lived program: it has
-a normal exit condition, which is when it receives EOF on its stdin.
-That allows it to work as a coldplug manager when paired with
-<a href="mdevd-coldplug.html">mdevd-coldplug</a>. </li>
- <li> However, when paired with <a href="mdevd-netlink.html">mdevd-netlink</a>,
-it acts as a daemon, because mdev-netlink normally never exits until
-the end of the machine lifetime and never closes its stdout, so
-mdevd's stdin never receives EOF. </li>
  <li> The <tt>examples/</tt> subdirectory of the mdevd package contains
 examples on how to run mdevd under various init systems / supervisors. </li>
 </ul>
diff --git a/doc/upgrade.html b/doc/upgrade.html
index 06c1654..fbbcc00 100644
--- a/doc/upgrade.html
+++ b/doc/upgrade.html
@@ -18,6 +18,17 @@
 
 <h1> What has changed in mdevd </h1>
 
+<h2> in 0.1.0.0 </h2>
+
+<ul>
+ <li> <a href="//skarnet.org/software/skalibs/">skalibs</a>
+dependency bumped to 2.6.3.0. </li>
+ <li> The <tt>mdevd-netlink</tt> program doesn't exist anymore. </li>
+ <li> <a href="mdevd.html">mdevd</a> now listens to the netlink itself. </li>
+ <li> <a href="mdevd-coldplug.html">mdevd-coldplug</a> does not print events
+to stdout anymore. Instead, it makes the kernel trigger events. </li>
+</ul>
+
 <h2> in 0.0.1.0 </h2>
 
 <ul>
diff --git a/package/deps.mak b/package/deps.mak
index 64c7965..2d9cd98 100644
--- a/package/deps.mak
+++ b/package/deps.mak
@@ -2,13 +2,10 @@
 # This file has been generated by tools/gen-deps.sh
 #
 
-src/mdevd/mdevd-coldplug.o src/mdevd/mdevd-coldplug.lo: src/mdevd/mdevd-coldplug.c src/mdevd/mdevd.h
-src/mdevd/mdevd-netlink.o src/mdevd/mdevd-netlink.lo: src/mdevd/mdevd-netlink.c src/mdevd/mdevd.h
-src/mdevd/mdevd.o src/mdevd/mdevd.lo: src/mdevd/mdevd.c src/mdevd/mdevd.h
+src/mdevd/mdevd-coldplug.o src/mdevd/mdevd-coldplug.lo: src/mdevd/mdevd-coldplug.c
+src/mdevd/mdevd.o src/mdevd/mdevd.lo: src/mdevd/mdevd.c
 
 mdevd: EXTRA_LIBS :=
 mdevd: src/mdevd/mdevd.o -lskarnet
 mdevd-coldplug: EXTRA_LIBS :=
 mdevd-coldplug: src/mdevd/mdevd-coldplug.o -lskarnet
-mdevd-netlink: EXTRA_LIBS :=
-mdevd-netlink: src/mdevd/mdevd-netlink.o -lskarnet
diff --git a/package/info b/package/info
index 41df5d6..1c86703 100644
--- a/package/info
+++ b/package/info
@@ -1,4 +1,4 @@
 package=mdevd
-version=0.0.1.0
+version=0.1.0.0
 category=admin
 package_macro_name=MDEVD
diff --git a/package/modes b/package/modes
index 63c3a48..9ea80ce 100644
--- a/package/modes
+++ b/package/modes
@@ -1,3 +1,2 @@
 mdevd			0755
-mdevd-netlink		0755
 mdevd-coldplug		0755
diff --git a/package/targets.mak b/package/targets.mak
index 6b16bfa..79e56ea 100644
--- a/package/targets.mak
+++ b/package/targets.mak
@@ -1,6 +1,5 @@
 BIN_TARGETS := \
 mdevd \
-mdevd-netlink \
 mdevd-coldplug
 
 LIBEXEC_TARGETS :=
diff --git a/src/mdevd/deps-exe/mdevd-netlink b/src/mdevd/deps-exe/mdevd-netlink
deleted file mode 100644
index e7187fe..0000000
--- a/src/mdevd/deps-exe/mdevd-netlink
+++ /dev/null
@@ -1 +0,0 @@
--lskarnet
diff --git a/src/mdevd/mdevd-coldplug.c b/src/mdevd/mdevd-coldplug.c
index dabd678..0d64007 100644
--- a/src/mdevd/mdevd-coldplug.c
+++ b/src/mdevd/mdevd-coldplug.c
@@ -3,70 +3,83 @@
 #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)
+static void scan_subdir (int fdat, char const *pathat, char const *list)
 {
-  if (faccessat(fddir, "dev", R_OK, AT_EACCESS) < 0)
+  DIR *dir ;
+  int fdlist = openat(fdat, list, O_RDONLY | O_DIRECTORY) ;
+  if (fdlist < 0) strerr_diefu4sys(111, "open ", pathat, "/", list) ;
+  dir = fdopendir(fdlist) ;
+  if (!dir) strerr_diefu4sys(111, "fdopendir ", pathat, "/", list) ;
+  for (;;)
   {
-    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() ;
+    direntry *d ;
+    errno = 0 ;
+    d = readdir(dir) ;
+    if (!d) break ;
+    if (d->d_name[0] == '.') continue ;
+    {
+      int fd ;
+      size_t dlen = strlen(d->d_name) ;
+      char fn[dlen + 8] ;
+      memcpy(fn, d->d_name, dlen) ;
+      memcpy(fn + dlen, "/uevent", 8) ;
+      fd = openat(fdlist, fn, O_WRONLY) ;
+      if (fd < 0)
+      {
+        strerr_warnwu6sys("open ", pathat, "/", list, "/", fn) ;
+        continue ;
+      }
+      if (write(fd, "add\n", 4) < 4)
+        strerr_warnwu6sys("write to ", pathat, "/", list, "/", fn) ;
+      close(fd) ;
+    }
   }
+  if (errno) strerr_diefu4sys(111, "readdir ", pathat, "/", list) ;
+  dir_close(dir) ;
+}
 
+static int scan_dir (char const *path, int add_devices)
+{
+  DIR *dir ;
+  int fdpath = open(path, O_RDONLY | O_DIRECTORY) ;
+  if (fdpath < 0) return 0 ;
+  dir = fdopendir(fdpath) ;
+  if (!dir) strerr_diefu2sys(111, "fdopendir ", path) ;
+  for (;;)
   {
-    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() ;
+    direntry *d ;
+    errno = 0 ;
+    d = readdir(dir) ;
+    if (!d) break ;
+    if (d->d_name[0] == '.') continue ;
+    if (add_devices)
+    {
+      size_t dlen = strlen(d->d_name) ;
+      char fn[dlen + 9] ;
+      memcpy(fn, d->d_name, dlen) ;
+      memcpy(fn + dlen, "/devices", 9) ;
+      scan_subdir(fdpath, path, fn) ;
+    }
+    else scan_subdir(fdpath, path, d->d_name) ;
   }
-
-  if (buffer_put(buffer_1, "", 1) < 1) die1() ;
+  if (errno) strerr_diefu2sys(111, "readdir ", path) ;
+  dir_close(dir) ;
+  return 1 ;
 }
 
+
 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 ;
@@ -83,51 +96,18 @@ int main (int argc, char const *const *argv, char const *const *envp)
     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 (;;)
+    size_t slashsyslen = strlen(slashsys) ;
+    char fn[slashsyslen + 11] ;
+    memcpy(fn, slashsys, slashsyslen) ;
+    memcpy(fn + slashsyslen, "/subsystem", 11) ;
+    if (!scan_dir(fn, 1))
     {
-      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) ;
+      memcpy(fn + slashsyslen + 1, "class", 6) ;
+      if (!scan_dir(fn, 0)) strerr_diefu2sys(111, "open ", fn) ;
+      memcpy(fn + slashsyslen + 1, "bus", 4) ;
+      if (!scan_dir(fn, 1)) strerr_diefu2sys(111, "open ", fn) ;
     }
-    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
deleted file mode 100644
index 3f55f23..0000000
--- a/src/mdevd/mdevd-netlink.c
+++ /dev/null
@@ -1,177 +0,0 @@
-/* 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
index 7fafea0..ca1e070 100644
--- a/src/mdevd/mdevd.c
+++ b/src/mdevd/mdevd.c
@@ -1,7 +1,11 @@
 /* ISC license. */
 
-#include <sys/sysmacros.h>  /* makedev, major, minor */
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
 #include <sys/types.h>
+#include <sys/sysmacros.h>  /* makedev, major, minor */
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <stdint.h>
@@ -15,10 +19,12 @@
 #include <regex.h>
 #include <libgen.h>  /* basename */
 #include <stdio.h>  /* rename */
+#include <sys/socket.h>
+#include <linux/netlink.h>
+
 #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>
@@ -28,16 +34,17 @@
 #include <skalibs/env.h>
 #include <skalibs/djbunix.h>
 #include <skalibs/iopause.h>
+#include <skalibs/socket.h>
 #include <skalibs/skamisc.h>
 #include <skalibs/surf.h>
 #include <skalibs/random.h>
-#include "mdevd.h"
 
-#define USAGE "mdevd [ -v verbosity ] [ -f conffile ] [ -n ] [ -s slashsys ] [ -d slashdev ]"
+#define USAGE "mdevd [ -v verbosity ] [ -1 ] [ -b kbufsz ] [ -f conffile ] [ -n ] [ -s slashsys ] [ -d slashdev ]"
 #define dieusage() strerr_dieusage(100, USAGE)
 
-#define BUFSIZE 8192
+#define CONFBUFSIZE 8192
 #define UEVENT_MAX_VARS 63
+#define UEVENT_MAX_SIZE 4096
 
 #define ACTION_NONE 0x0
 #define ACTION_ADD 0x1
@@ -177,6 +184,103 @@ static int makesubdirs (char *path)
 }
 
 
+ /* Netlink isolation layer */
+
+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 (unsigned int kbufsz)
+{
+  struct sockaddr_nl nl = { .nl_family = AF_NETLINK, .nl_pad = 0, .nl_groups = 1, .nl_pid = 0 } ;
+  int fd = socket_internal(AF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT, DJBUNIX_FLAG_NB|DJBUNIX_FLAG_COE) ;
+  if (fd < 0) return -1 ;
+  if (bind(fd, (struct sockaddr *)&nl, sizeof(struct sockaddr_nl)) < 0
+   || (setsockopt(0, SOL_SOCKET, SO_RCVBUFFORCE, &kbufsz, sizeof(unsigned int)) < 0
+    && errno == EPERM
+    && setsockopt(0, SOL_SOCKET, SO_RCVBUF, &kbufsz, sizeof(unsigned int)) < 0))
+  {
+    fd_close(fd) ;
+    return -1 ;
+  }
+  return fd ;
+}
+
+static inline size_t netlink_read (char *s)
+{
+  struct sockaddr_nl nl;
+  struct iovec v = { .iov_base = s, .iov_len = UEVENT_MAX_SIZE } ;
+  struct msghdr msg =
+  {
+    .msg_name = &nl,
+    .msg_namelen = sizeof(struct sockaddr_nl),
+    .msg_iov = &v,
+    .msg_iovlen = 1,
+    .msg_control = 0,
+    .msg_controllen = 0,
+    .msg_flags = 0
+  } ;
+  ssize_t r = sanitize_read(fd_recvmsg(0, &msg)) ;
+  if (r < 0)
+  {
+    if (errno == EPIPE)
+    {
+      if (verbosity >= 2) strerr_warnw1x("received EOF on netlink") ;
+      cont = 0 ;
+      return 0 ;
+    }
+    else strerr_diefu1sys(111, "receive netlink message") ;
+  }
+  if (!r) return 0 ;
+  if (msg.msg_flags & MSG_TRUNC)
+    strerr_diefu1x(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_warnw2x("received netlink message from userspace process ", fmt) ;
+    }
+    return 0 ;
+  }
+  if (s[r-1])
+  {
+    if (verbosity) strerr_warnw2x("received invalid event: ", "improperly terminated") ;
+    return 0 ;
+  }
+  if (!strstr(s, "@/"))
+  {
+    if (verbosity) strerr_warnw2x("received invalid event: ", "bad initial summary") ;
+    return 0 ;
+  }
+  return r ;
+}
+
+static inline int uevent_read (struct uevent_s *event)
+{
+  unsigned short len = 0 ;
+  event->len = netlink_read(event->buf) ;
+  if (!event->len) return 0 ;
+  event->varn = 0 ;
+  while (len < event->len)
+  {
+    if (event->varn >= UEVENT_MAX_VARS)
+    {
+      if (verbosity) strerr_warnw2x("received invalid event: ", "too many variables") ;
+      return 0 ;
+    }
+    event->vars[event->varn++] = len ;
+    len += strlen(event->buf + len) + 1 ;
+  }
+  return 1 ;
+}
+
+
  /* mdev.conf parsing. See PARSING.txt for details. */
 
  /* The first pass is simple. The goal is just to compute scriptlen and envmatchlen. */
@@ -840,7 +944,7 @@ static inline void on_event (struct uevent_s *event, scriptelem const *script, u
 
  /* Tying it all together */
 
-static inline int handle_signals (void)
+static inline void handle_signals (void)
 {
   for (;;)
   {
@@ -848,8 +952,9 @@ static inline int handle_signals (void)
     switch (c)
     {
       case -1 : strerr_diefu1sys(111, "selfpipe_read") ;
-      case 0 : return 0 ;
-      case SIGHUP : return 1 ;
+      case SIGTERM :
+      case SIGHUP : cont = c == SIGHUP ;
+      case 0 : return ;
       case SIGCHLD :
         if (!pid) wait_reap() ;
         else
@@ -869,54 +974,37 @@ static inline int handle_signals (void)
   }
 }
 
-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)
+static inline void handle_event (scriptelem const *script, unsigned short scriptlen, char const *storage, struct envmatch_s const *envmatch)
 {
-  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 ;
-  }
+  struct uevent_s event = UEVENT_ZERO ;
+  if (uevent_read(&event) && event.varn > 1)
+    on_event(&event, script, scriptlen, storage, envmatch) ;
 }
 
 int main (int argc, char const *const *argv)
 {
   char const *configfile = "/etc/mdev.conf" ;
-  iopause_fd x[2] = { { .events = IOPAUSE_READ }, { .fd = 0 } } ;
+  iopause_fd x[2] = { { .events = IOPAUSE_READ }, { .events = IOPAUSE_READ } } ;
+  unsigned int notif = 0 ;
+  unsigned int kbufsz = 65536 ;
+  char const *slashdev = "/dev" ;
   PROG = "mdevd" ;
   {
-    char const *slashdev = "/dev" ;
     subgetopt_t l = SUBGETOPT_ZERO ;
     for (;;)
     {
-      int opt = subgetopt_r(argc, argv, "nv:f:s:d:F:", &l) ;
+      int opt = subgetopt_r(argc, argv, "nv:D:b: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 'D' :
+          if (!uint0_scan(l.arg, &notif)) dieusage() ;
+          if (notif < 3) strerr_dief1x(100, "notification fd must be 3 or more") ;
+          if (fcntl(notif, F_GETFD) < 0) strerr_dief1sys(100, "invalid notification fd") ;
+          break ;
+        case 'b' : if (!uint0_scan(l.arg, &kbufsz)) dieusage() ; break ;
         case 'f' : configfile = l.arg ; break ;
         case 's' : slashsys = l.arg ; break ;
         case 'd' : slashdev = l.arg ; break ;
@@ -925,13 +1013,13 @@ int main (int argc, char const *const *argv)
       }
     }
     argc -= l.ind ; argv += l.ind ;
-    if (configfile[0] != '/') strerr_dief2x(100, configfile, " is not an absolute path") ;
-    if (slashsys[0] != '/') strerr_dief2x(100, slashsys, " is not an absolute path") ;
-    if (slashdev[0] != '/') strerr_dief2x(100, slashdev, " is not an absolute path") ;
-    if (fwbase[0] != '/') strerr_dief2x(100, fwbase, " is not an absolute path") ;
-    if (chdir(slashdev) < 0) strerr_diefu2sys(111, "chdir to ", slashdev) ;
   }
 
+  if (configfile[0] != '/') strerr_dief2x(100, configfile, " is not an absolute path") ;
+  if (slashsys[0] != '/') strerr_dief2x(100, slashsys, " is not an absolute path") ;
+  if (slashdev[0] != '/') strerr_dief2x(100, slashdev, " is not an absolute path") ;
+  if (fwbase[0] != '/') strerr_dief2x(100, fwbase, " is not 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") ;
 
@@ -942,13 +1030,16 @@ int main (int argc, char const *const *argv)
     root_min = minor(st.st_dev) ;
   }
 
-  if (ndelay_on(0) < 0) strerr_diefu1sys(111, "set stdin nonblocking") ;
+  x[1].fd = netlink_init(kbufsz) ;
+  if (x[1].fd < 0) strerr_diefu1sys(111, "init netlink") ;
+
   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, SIGTERM) ;
     sigaddset(&set, SIGCHLD) ;
     sigaddset(&set, SIGHUP) ;
     if (selfpipe_trapset(&set) < 0)
@@ -957,43 +1048,47 @@ int main (int argc, char const *const *argv)
   mdevd_random_init() ;
   umask(0) ;
 
+  if (notif)
+  {
+    fd_write(notif, "\n", 1) ;
+    fd_close(notif) ;
+  }
+
   while (cont)
   {
     ssize_t len ;
     unsigned short scriptlen = 0 ;
     unsigned short envmatchlen = 0 ;
-    char buf[BUFSIZE] ;
-    len = openreadnclose(configfile, buf, BUFSIZE - 1) ;
+    char storage[CONFBUFSIZE] ;
+    len = openreadnclose(configfile, storage, CONFBUFSIZE - 1) ;
     if (len < 0)
     {
       if (errno != ENOENT) strerr_diefu2sys(111, "read ", configfile) ;
       if (verbosity) strerr_warnwu2sys("read ", configfile) ;
       len = 0 ;
     }
-    buf[len++] = 0 ;
-    script_firstpass(buf, &scriptlen, &envmatchlen) ;
+    storage[len++] = 0 ;
+    script_firstpass(storage, &scriptlen, &envmatchlen) ;
 
     {
-      size_t w = 0 ;
-      int reload = 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 ;
-      script_secondpass(buf, script, envmatch) ;
-      while (pid || (cont && (!reload || buffer_len(buffer_0))))
+      script_secondpass(storage, script, envmatch) ;
+      cont = 2 ;
+
+      while (pid || cont == 2)
       {
-        if (buffer_len(buffer_0)) handle_stdin(&event, script, scriptlen, buf, envmatch, &w) ;
-        x[1].events = pid ? 0 : IOPAUSE_READ ;
-        if (iopause(x, 1 + cont, 0, 0) < 0) strerr_diefu1sys(111, "iopause") ;
-        if (x[0].revents & IOPAUSE_READ && handle_signals()) reload = 1 ;
-        if (cont && !pid && x[1].revents & IOPAUSE_READ)
-          handle_stdin(&event, script, scriptlen, buf, envmatch, &w) ;
+        if (iopause(x, 1 + (!pid && cont == 2), 0, 0) < 0) strerr_diefu1sys(111, "iopause") ;
+        if (x[0].revents & IOPAUSE_READ)
+          handle_signals() ;
+        if (!pid && cont == 2 && x[1].revents & IOPAUSE_READ)
+          handle_event(script, scriptlen, storage, envmatch) ;
       }
+
       script_free(script, scriptlen, envmatch, envmatchlen) ;
     }
   }
-  ndelay_off(0) ;
   return 0 ;
 }
diff --git a/src/mdevd/mdevd.h b/src/mdevd/mdevd.h
deleted file mode 100644
index a99f692..0000000
--- a/src/mdevd/mdevd.h
+++ /dev/null
@@ -1,8 +0,0 @@
- /* ISC license. */
-
-#ifndef MDEVD_H
-#define MDEVD_H
-
-#define UEVENT_MAX_SIZE 4096
-
-#endif