about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLaurent Bercot <ska-skaware@skarnet.org>2022-07-24 00:28:32 +0000
committerLaurent Bercot <ska@appnovation.com>2022-07-24 00:28:32 +0000
commit8ad0f7f39526ee0d8f52f44f5434dfd518f8897a (patch)
tree41b3da33258b25b56ab85973b91e4e98dd833ae8
parentd9f63961ec01dc02131ad0967734ae1a058b27b8 (diff)
downloadmdevd-8ad0f7f39526ee0d8f52f44f5434dfd518f8897a.tar.gz
mdevd-8ad0f7f39526ee0d8f52f44f5434dfd518f8897a.tar.xz
mdevd-8ad0f7f39526ee0d8f52f44f5434dfd518f8897a.zip
Add "settle" functionality to mdevd-colplug
- Still untested.
- Also prepare for 0.1.6.0.
- Includes a refactor of several functions in mdevd, moved
into an internal libmdevd that is also used by mdevd-coldplug.

Signed-off-by: Laurent Bercot <ska@appnovation.com>
-rw-r--r--.gitignore5
-rw-r--r--INSTALL4
-rw-r--r--NEWS6
-rw-r--r--doc/index.html6
-rw-r--r--doc/mdevd-coldplug.html17
-rw-r--r--doc/upgrade.html10
-rw-r--r--package/deps.mak18
-rw-r--r--package/info2
-rw-r--r--src/mdevd/deps-exe/mdevd1
-rw-r--r--src/mdevd/deps-exe/mdevd-coldplug1
-rw-r--r--src/mdevd/deps-lib/mdevd3
-rw-r--r--src/mdevd/mdevd-coldplug.c61
-rw-r--r--src/mdevd/mdevd-internal.h24
-rw-r--r--src/mdevd/mdevd.c140
-rw-r--r--src/mdevd/mdevd_netlink_init.c31
-rw-r--r--src/mdevd/mdevd_uevent_getvar.c16
-rw-r--r--src/mdevd/mdevd_uevent_read.c87
17 files changed, 283 insertions, 149 deletions
diff --git a/.gitignore b/.gitignore
index 147e421..e11a6fe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,7 @@
 *.o
-*.a
+/*.a.xyzzy
 *.lo
-*.so
-*.so.*
+/*.so.xyzzy
 /config.mak
 /src/include/mdevd/config.h
 /mdevd
diff --git a/INSTALL b/INSTALL
index 8e4fc04..422d895 100644
--- a/INSTALL
+++ b/INSTALL
@@ -6,8 +6,8 @@ Build Instructions
 
   - A Linux-based system with a standard C development environment
   - GNU make version 3.81 or later
-  - skalibs version 2.12.0.0 or later: https://skarnet.org/software/skalibs/
-  - optional: execline version 2.9.0.0 or later: https://skarnet.org/software/execline/
+  - skalibs version 2.12.0.1 or later: https://skarnet.org/software/skalibs/
+  - optional: execline version 2.9.0.1 or later: https://skarnet.org/software/execline/
 
  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 028923a..f382c60 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,11 @@
 Changelog for mdevd.
 
+In 0.1.6.0
+----------
+
+ - mdevd-coldplug can now be synchronous with the -O option.
+
+
 In 0.1.5.2
 ----------
 
diff --git a/doc/index.html b/doc/index.html
index e51e85c..c6261c4 100644
--- a/doc/index.html
+++ b/doc/index.html
@@ -99,7 +99,7 @@ and does not pull in any unnecessary code. </li>
 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.12.0.0 or later. It's a build-time requirement. It's also a run-time
+2.12.0.1 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>
  <li> Optionally: <a href="//skarnet.org/software/execline/">nsss</a>
@@ -108,7 +108,7 @@ you use the <tt>--enable-nsss</tt> option to configure; but you would
 only do that if you're replacing nsswitch on your whole system, and most
 people are not. </li>
  <li> Optionally: <a href="//skarnet.org/software/execline/">execline</a>
-version 2.9.0.0 or later. It's a only a run-time requirement, and only
+version 2.9.0.1 or later. It's a only a run-time requirement, and only
 if your <tt>mdev.conf</tt> file contains <tt>+</tt>, <tt>-</tt> or
 <tt>&amp;</tt> command directives. </li>
 </ul>
@@ -124,7 +124,7 @@ if your <tt>mdev.conf</tt> file contains <tt>+</tt>, <tt>-</tt> or
 
 <ul>
  <li> The current released version of mdevd is
-<a href="mdevd-0.1.5.2.tar.gz">0.1.5.2</a>. </li>
+<a href="mdevd-0.1.6.0.tar.gz">0.1.6.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>:
diff --git a/doc/mdevd-coldplug.html b/doc/mdevd-coldplug.html
index 027c849..36654eb 100644
--- a/doc/mdevd-coldplug.html
+++ b/doc/mdevd-coldplug.html
@@ -28,7 +28,7 @@ for all these devices.
 <h2> Interface </h2>
 
 <pre>
-     mdevd-coldplug [ -s <em>slashsys</em> ]
+     mdevd-coldplug [ -v <em>verbosity</em> ] [ -s <em>slashsys</em> ] [ -O <em>nlgroup</em> ] [ -b <em>kbufsize</em> ]
 </pre>
 
 <ul>
@@ -43,8 +43,23 @@ at this point, it will pick up the series of events. </li>
 <h2> Options </h2>
 
 <ul>
+ <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>-s</tt>&nbsp;<em>slashsys</em>&nbsp;: assume the sysfs
 pseudo-filesystem is mounted on <em>slashsys</em>. Default is <tt>/sys</tt>. </li>
+ <li> <tt>-O</tt>&nbsp;<em>nlgroup</em>&nbsp;: synchronous coldplug.
+<tt>mdevd-coldplug</tt> will not exit until the last uevent it triggers
+has been handled by <a href="mdevd.html">mdevd</a>. <em>nlgroup</em>
+needs to be a netlink group that <a href="mdevd.html">mdevd</a> is
+rebroadcasting handled events to. Note that this option can only work
+if <a href="mdevd.html">mdevd</a> is running with a similar <tt>-O</tt>
+option with a compatible value; else, <tt>mdevd-coldplug</tt> will wait
+forever. </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. The default is 500 kB;
+if you're getting "No buffer space available" errors, try increasing this number.
+This option is only meaningful when paired with the <tt>-O</tt> option. </li>
 </ul>
 
 <h2> Exit codes </h2>
diff --git a/doc/upgrade.html b/doc/upgrade.html
index 5ae8dd3..28485ee 100644
--- a/doc/upgrade.html
+++ b/doc/upgrade.html
@@ -18,6 +18,15 @@
 
 <h1> What has changed in mdevd </h1>
 
+<h2> in 0.1.6.0 </h2>
+
+<ul>
+ <li> <a href="//skarnet.org/software/skalibs/">skalibs</a>
+dependency bumped to 2.12.0.1. </li>
+ <li> <a href="//skarnet.org/software/execline/">execline</a>
+optional dependency bumped to 2.9.0.1. </li>
+</ul>
+
 <h2> in 0.1.5.2 </h2>
 
 <ul>
@@ -27,7 +36,6 @@ dependency bumped to 2.12.0.0. </li>
 optional dependency bumped to 2.9.0.0. </li>
 </ul>
 
-
 <h2> in 0.1.5.1 </h2>
 
 <ul>
diff --git a/package/deps.mak b/package/deps.mak
index a6e8248..7820c93 100644
--- a/package/deps.mak
+++ b/package/deps.mak
@@ -2,10 +2,20 @@
 # 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.o src/mdevd/mdevd.lo: src/mdevd/mdevd.c src/include/mdevd/config.h
+src/mdevd/mdevd-coldplug.o src/mdevd/mdevd-coldplug.lo: src/mdevd/mdevd-coldplug.c src/mdevd/mdevd-internal.h
+src/mdevd/mdevd.o src/mdevd/mdevd.lo: src/mdevd/mdevd.c src/mdevd/mdevd-internal.h src/include/mdevd/config.h
+src/mdevd/mdevd_netlink_init.o src/mdevd/mdevd_netlink_init.lo: src/mdevd/mdevd_netlink_init.c
+src/mdevd/mdevd_uevent_getvar.o src/mdevd/mdevd_uevent_getvar.lo: src/mdevd/mdevd_uevent_getvar.c src/mdevd/mdevd-internal.h
+src/mdevd/mdevd_uevent_read.o src/mdevd/mdevd_uevent_read.lo: src/mdevd/mdevd_uevent_read.c src/mdevd/mdevd-internal.h
 
+ifeq ($(strip $(STATIC_LIBS_ARE_PIC)),)
+libmdevd.a.xyzzy: src/mdevd/mdevd_netlink_init.o src/mdevd/mdevd_uevent_read.o src/mdevd/mdevd_uevent_getvar.o
+else
+libmdevd.a.xyzzy: src/mdevd/mdevd_netlink_init.lo src/mdevd/mdevd_uevent_read.lo src/mdevd/mdevd_uevent_getvar.lo
+endif
+libmdevd.so.xyzzy: EXTRA_LIBS :=
+libmdevd.so.xyzzy: src/mdevd/mdevd_netlink_init.lo src/mdevd/mdevd_uevent_read.lo src/mdevd/mdevd_uevent_getvar.lo
 mdevd: EXTRA_LIBS := -lskarnet ${MAYBEPTHREAD_LIB}
-mdevd: src/mdevd/mdevd.o ${LIBNSSS}
+mdevd: src/mdevd/mdevd.o libmdevd.a.xyzzy ${LIBNSSS}
 mdevd-coldplug: EXTRA_LIBS := -lskarnet
-mdevd-coldplug: src/mdevd/mdevd-coldplug.o
+mdevd-coldplug: src/mdevd/mdevd-coldplug.o libmdevd.a.xyzzy
diff --git a/package/info b/package/info
index 2c62800..d2a6a5d 100644
--- a/package/info
+++ b/package/info
@@ -1,4 +1,4 @@
 package=mdevd
-version=0.1.5.2
+version=0.1.6.0
 category=admin
 package_macro_name=MDEVD
diff --git a/src/mdevd/deps-exe/mdevd b/src/mdevd/deps-exe/mdevd
index ee4ed0b..a410ca2 100644
--- a/src/mdevd/deps-exe/mdevd
+++ b/src/mdevd/deps-exe/mdevd
@@ -1,3 +1,4 @@
+libmdevd.a.xyzzy
 ${LIBNSSS}
 -lskarnet
 ${MAYBEPTHREAD_LIB}
diff --git a/src/mdevd/deps-exe/mdevd-coldplug b/src/mdevd/deps-exe/mdevd-coldplug
index e7187fe..31063b4 100644
--- a/src/mdevd/deps-exe/mdevd-coldplug
+++ b/src/mdevd/deps-exe/mdevd-coldplug
@@ -1 +1,2 @@
+libmdevd.a.xyzzy
 -lskarnet
diff --git a/src/mdevd/deps-lib/mdevd b/src/mdevd/deps-lib/mdevd
new file mode 100644
index 0000000..1e9efc3
--- /dev/null
+++ b/src/mdevd/deps-lib/mdevd
@@ -0,0 +1,3 @@
+mdevd_netlink_init.o
+mdevd_uevent_read.o
+mdevd_uevent_getvar.o
diff --git a/src/mdevd/mdevd-coldplug.c b/src/mdevd/mdevd-coldplug.c
index 596001c..2be7be0 100644
--- a/src/mdevd/mdevd-coldplug.c
+++ b/src/mdevd/mdevd-coldplug.c
@@ -5,23 +5,35 @@
 #include <string.h>
 #include <unistd.h>
 #include <errno.h>
+#include <limits.h>
+
 #include <skalibs/sgetopt.h>
+#include <skalibs/types.h>
 #include <skalibs/strerr2.h>
 #include <skalibs/direntry.h>
+#include <skalibs/djbunix.h>
+
+#include "mdevd-internal.h"
 
-#define USAGE "mdevd-coldplug [ -s slashsys ]"
+#define USAGE "mdevd-coldplug [ -v verbosity ] [ -s slashsys ] [ -O nlgroup ] [ -b kbufsize ]"
 #define dieusage() strerr_dieusage(100, USAGE)
 
-static void scan_subdir (int fdat, char const *pathat, char const *list)
+static char subsystem[PATH_MAX] = "" ;
+static char mdev[PATH_MAX] = "" ;
+
+static int scan_subdir (int fdat, char const *pathat, char const *list)
 {
+  int r = 0 ;
   DIR *dir ;
+  direntry *d = 0 ;
+  direntry *lastd ;
   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 (;;)
   {
-    direntry *d ;
+    lastd = d ;
     errno = 0 ;
     d = readdir(dir) ;
     if (!d) break ;
@@ -37,10 +49,13 @@ static void scan_subdir (int fdat, char const *pathat, char const *list)
       if (write(fd, "add\n", 4) < 4)
         strerr_warnwu6sys("write to ", pathat, "/", list, "/", fn) ;
       close(fd) ;
+      r = 1 ;
     }
   }
   if (errno) strerr_diefu4sys(111, "readdir ", pathat, "/", list) ;
+  if (r) strncpy(mdev, lastd->d_name, PATH_MAX) ;
   dir_close(dir) ;
+  return r ;
 }
 
 static int scan_dir (char const *path, int add_devices)
@@ -63,9 +78,11 @@ static int scan_dir (char const *path, int add_devices)
       char fn[dlen + 9] ;
       memcpy(fn, d->d_name, dlen) ;
       memcpy(fn + dlen, "/devices", 9) ;
-      scan_subdir(fdpath, path, fn) ;
+      if (scan_subdir(fdpath, path, fn))
+        strncpy(subsystem, d->d_name, PATH_MAX) ;
     }
-    else scan_subdir(fdpath, path, d->d_name) ;
+    else if (scan_subdir(fdpath, path, d->d_name))
+      strncpy(subsystem, d->d_name, PATH_MAX) ;
   }
   if (errno) strerr_diefu2sys(111, "readdir ", path) ;
   dir_close(dir) ;
@@ -76,22 +93,40 @@ static int scan_dir (char const *path, int add_devices)
 int main (int argc, char const *const *argv, char const *const *envp)
 {
   char const *slashsys = "/sys" ;
+  unsigned int verbosity = 1 ;
+  unsigned int nlgroup = 0 ;
+  unsigned int kbufsz = 512288 ;
+  int nlfd = -1 ;
   PROG = "mdevd-coldplug" ;
   {
     subgetopt l = SUBGETOPT_ZERO ;
     for (;;)
     {
-      int opt = subgetopt_r(argc, argv, "s:", &l) ;
+      int opt = subgetopt_r(argc, argv, "v:s:O:b:", &l) ;
       if (opt == -1) break ;
       switch (opt)
       {
+        case 'v' : if (!uint0_scan(l.arg, &verbosity)) dieusage() ; break ;
         case 's' : slashsys = l.arg ; break ;
+        case 'O' : if (!uint0_scan(l.arg, &nlgroup)) dieusage() ; break ;
+        case 'b' : if (!uint0_scan(l.arg, &kbufsz)) dieusage() ; break ;
         default : dieusage() ;
       }
     }
     argc -= l.ind ; argv += l.ind ;
   }
 
+  nlgroup &= ~1 ;
+  if (nlgroup)
+  {
+    nlfd = mdevd_netlink_init(nlgroup, kbufsz) ;
+    if (nlfd == -1)
+      strerr_diefu1sys(111, "init netlink") ;
+    if (ndelay_off(nlfd) == -1)
+      strerr_diefu1sys(111, "make netlink socket non-blocking") ;
+  }
+
+ /* Trigger the uevents */
   {
     size_t slashsyslen = strlen(slashsys) ;
     char fn[slashsyslen + 11] ;
@@ -105,5 +140,19 @@ int main (int argc, char const *const *argv, char const *const *envp)
       if (!scan_dir(fn, 1)) strerr_diefu2sys(111, "open ", fn) ;
     }
   }
+
+ /* Wait for the last triggered uevent to be processed */
+  if (nlgroup && subsystem[0] && mdev[0])
+  {
+    struct uevent_s event ;
+    for (;;) if (mdevd_uevent_read(nlfd, &event, verbosity))
+    {
+      char *x = mdevd_uevent_getvar(&event, "SUBSYSTEM") ;
+      if (strcmp(x, subsystem)) continue ;
+      x = mdevd_uevent_getvar(&event, "MDEV") ;
+      if (!strcmp(x, mdev)) break ;
+    }
+  }
+
   return 0 ;
 }
diff --git a/src/mdevd/mdevd-internal.h b/src/mdevd/mdevd-internal.h
new file mode 100644
index 0000000..cc98a6d
--- /dev/null
+++ b/src/mdevd/mdevd-internal.h
@@ -0,0 +1,24 @@
+/* ISC license. */
+
+#ifndef MDEVD_INTERNAL_H
+#define MDEVD_INTERNAL_H
+
+#define UEVENT_MAX_VARS 63
+#define UEVENT_MAX_SIZE 8192
+
+#include <limits.h>
+
+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 }
+
+extern int mdevd_netlink_init (unsigned int, unsigned int) ;
+extern int mdevd_uevent_read (int, struct uevent_s *, unsigned int) ;
+extern char *mdevd_uevent_getvar (struct uevent_s *, char const *) ;
+
+#endif
diff --git a/src/mdevd/mdevd.c b/src/mdevd/mdevd.c
index bc663a4..2bc6e08 100644
--- a/src/mdevd/mdevd.c
+++ b/src/mdevd/mdevd.c
@@ -40,6 +40,7 @@
 #include <skalibs/unix-transactional.h>
 
 #include <mdevd/config.h>
+#include "mdevd-internal.h"
 
 #define USAGE "mdevd [ -v verbosity ] [ -D notif ] [ -o outputfd ] [ -O nlgroups ] [ -b kbufsz ] [ -f conffile ] [ -n ] [ -s slashsys ] [ -d slashdev ] [ -F fwbase ] [ -C ]"
 #define dieusage() strerr_dieusage(100, USAGE)
@@ -123,15 +124,6 @@ static scriptelem const scriptelem_catchall =
   .envmatchs = 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 }
-
 typedef struct udata_s udata, *udata_ref ;
 struct udata_s
 {
@@ -188,34 +180,6 @@ 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, O_NONBLOCK|O_CLOEXEC) ;
-  if (fd < 0) return -1 ;
-  if (bind(fd, (struct sockaddr *)&nl, sizeof(struct sockaddr_nl)) < 0) goto err ;
-  if (setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, &kbufsz, sizeof(unsigned int)) < 0)
-  {
-    if (errno != EPERM
-     || setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &kbufsz, sizeof(unsigned int)) < 0) goto err ;
-  }
-  return fd ;
-
- err:
-  fd_close(fd) ;
-  return -1 ;
-}
-
 static inline int rebc_init (unsigned int groups)
 {
   struct sockaddr_nl nl = { .nl_family = AF_NETLINK, .nl_pad = 0, .nl_groups = groups & ~1U, .nl_pid = 0 } ;
@@ -229,76 +193,6 @@ static inline int rebc_init (unsigned int groups)
   return fd ;
 }
 
-static inline size_t netlink_read (int fd, 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(fd, &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 (int fd, struct uevent_s *event)
-{
-  unsigned short len = 0 ;
-  event->len = netlink_read(fd, 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. */
 
@@ -623,16 +517,6 @@ static inline void load_firmware (char const *fw, char const *sysdevpath)
 
  /* 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" ;
@@ -694,7 +578,7 @@ static inline int run_scriptelem (struct uevent_s *event, scriptelem const *elem
   unsigned short i = 0 ;
   for (; i < elem->envmatchlen ; i++)
   {
-    char const *x = event_getvar(event, storage + envmatch[elem->envmatchs + i].var) ;
+    char const *x = mdevd_uevent_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 ;
   }
@@ -799,7 +683,7 @@ static inline int run_scriptelem (struct uevent_s *event, scriptelem const *elem
 
   if (elem->cmdtype == ACTION_ANY || ud->action == elem->cmdtype)
   {
-    if (!event_getvar(event, "MDEV"))
+    if (!mdevd_uevent_getvar(event, "MDEV"))
     {
       event->vars[event->varn++] = event->len ;
       memcpy(event->buf + event->len, "MDEV=", 5) ;
@@ -852,14 +736,14 @@ static inline int act_on_event (struct uevent_s *event, char *sysdevpath, size_t
 {
   ssize_t hasmajmin = 0 ;
   unsigned int mmaj, mmin ;
-  char const *x = event_getvar(event, "MAJOR") ;
+  char const *x = mdevd_uevent_getvar(event, "MAJOR") ;
   ud->devtype = S_IFCHR ;
   ud->action = action ;
   if (action == ACTION_ADD)
   {
     if (x && uint0_scan(x, &mmaj))
     {
-      x = event_getvar(event, "MINOR") ;
+      x = mdevd_uevent_getvar(event, "MINOR") ;
       if (x && uint0_scan(x, &mmin)) hasmajmin = 1 ;
     }
     if (!hasmajmin)
@@ -883,7 +767,7 @@ static inline int act_on_event (struct uevent_s *event, char *sysdevpath, size_t
   ud->mmaj = hasmajmin > 0 ? mmaj : -1 ;
   ud->mmin = hasmajmin > 0 ? mmin : -1 ;
 
-  ud->devname = event_getvar(event, "DEVNAME") ;
+  ud->devname = mdevd_uevent_getvar(event, "DEVNAME") ;
   if (!ud->devname)
   {
     ssize_t r ;
@@ -910,7 +794,7 @@ static inline int act_on_event (struct uevent_s *event, char *sysdevpath, size_t
   if (strstr(sysdevpath, "/block/")) ud->devtype = S_IFBLK ;
   else
   {
-    x = event_getvar(event, "SUBSYSTEM") ;
+    x = mdevd_uevent_getvar(event, "SUBSYSTEM") ;
     if (x && str_start(x, "block")) ud->devtype = S_IFBLK ;
   }
   ud->i = 0 ;
@@ -920,12 +804,12 @@ static inline int act_on_event (struct uevent_s *event, char *sysdevpath, size_t
 static inline int on_event (struct uevent_s *event, scriptelem const *script, unsigned short scriptlen, char const *storage, struct envmatch_s const *envmatch, udata *ud)
 {
   unsigned int action ;
-  char const *x = event_getvar(event, "ACTION") ;
+  char const *x = mdevd_uevent_getvar(event, "ACTION") ;
   if (!x) return 1 ;
   if (!strcmp(x, "add")) action = ACTION_ADD ;
   else if (!strcmp(x, "remove")) action = ACTION_REMOVE ;
   else action = ACTION_ANY ;
-  x = event_getvar(event, "DEVPATH") ;
+  x = mdevd_uevent_getvar(event, "DEVPATH") ;
   if (!x) return 1 ;
   {
     int done = 1 ;
@@ -934,7 +818,7 @@ static inline int on_event (struct uevent_s *event, scriptelem const *script, un
     char sysdevpath[devpathlen + slashsyslen + 8] ; /* act_on_event needs the extra storage */
     memcpy(sysdevpath, slashsys, slashsyslen) ;
     memcpy(sysdevpath + slashsyslen, x, devpathlen + 1) ;
-    x = event_getvar(event, "FIRMWARE") ;
+    x = mdevd_uevent_getvar(event, "FIRMWARE") ;
     if (action == ACTION_ADD || !x) done = act_on_event(event, sysdevpath, slashsyslen + devpathlen, action, script, scriptlen, storage, envmatch, ud) ;
     if (action == ACTION_ADD && x) load_firmware(x, sysdevpath) ;
     return done ;
@@ -978,7 +862,7 @@ static inline int handle_signals (struct uevent_s *event, scriptelem const *scri
 
 static inline int handle_event (int fd, struct uevent_s *event, scriptelem const *script, unsigned short scriptlen, char const *storage, struct envmatch_s const *envmatch, udata *ud)
 {
-  if (!uevent_read(fd, event) || event->varn <= 1) return 0 ;
+  if (!mdevd_uevent_read(fd, event, verbosity) || event->varn <= 1) return 0 ;
   return on_event(event, script, scriptlen, storage, envmatch, ud) ;
 }
 
@@ -1066,7 +950,7 @@ int main (int argc, char const *const *argv)
     root_min = minor(st.st_dev) ;
   }
 
-  x[1].fd = netlink_init(kbufsz) ;
+  x[1].fd = mdevd_netlink_init(1, kbufsz) ;
   if (x[1].fd < 0) strerr_diefu1sys(111, "init netlink") ;
   if (rebc)
   {
diff --git a/src/mdevd/mdevd_netlink_init.c b/src/mdevd/mdevd_netlink_init.c
new file mode 100644
index 0000000..264240b
--- /dev/null
+++ b/src/mdevd/mdevd_netlink_init.c
@@ -0,0 +1,31 @@
+/* ISC license. */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <linux/netlink.h>
+
+#include <skalibs/socket.h>
+#include <skalibs/djbunix.h>
+
+int mdevd_netlink_init (unsigned int group, unsigned int kbufsz)
+{
+  struct sockaddr_nl nl = { .nl_family = AF_NETLINK, .nl_pad = 0, .nl_groups = group, .nl_pid = 0 } ;
+  int fd = socket_internal(AF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT, O_NONBLOCK|O_CLOEXEC) ;
+  if (fd < 0) return -1 ;
+  if (bind(fd, (struct sockaddr *)&nl, sizeof(struct sockaddr_nl)) < 0) goto err ;
+  if (setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, &kbufsz, sizeof(unsigned int)) < 0)
+  {
+    if (errno != EPERM
+     || setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &kbufsz, sizeof(unsigned int)) < 0) goto err ;
+  }
+  return fd ;
+
+ err:
+  fd_close(fd) ;
+  return -1 ;
+}
diff --git a/src/mdevd/mdevd_uevent_getvar.c b/src/mdevd/mdevd_uevent_getvar.c
new file mode 100644
index 0000000..8eb8583
--- /dev/null
+++ b/src/mdevd/mdevd_uevent_getvar.c
@@ -0,0 +1,16 @@
+/* ISC license. */
+
+#include <string.h>
+
+#include "mdevd-internal.h"
+
+char *mdevd_uevent_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 ;
+}
+
diff --git a/src/mdevd/mdevd_uevent_read.c b/src/mdevd/mdevd_uevent_read.c
new file mode 100644
index 0000000..2cb4245
--- /dev/null
+++ b/src/mdevd/mdevd_uevent_read.c
@@ -0,0 +1,87 @@
+/* ISC license. */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <errno.h>
+#include <string.h>
+#include <sys/uio.h>
+#include <sys/socket.h>
+#include <linux/netlink.h>
+
+#include <skalibs/types.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/strerr2.h>
+
+#include "mdevd-internal.h"
+
+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 size_t netlink_read (int fd, char *s, unsigned int verbosity)
+{
+  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(fd, &msg)) ;
+  if (r < 0)
+    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 ;
+}
+
+int mdevd_uevent_read (int fd, struct uevent_s *event, unsigned int verbosity)
+{
+  unsigned short len = 0 ;
+  event->len = netlink_read(fd, event->buf, verbosity) ;
+  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 ;
+}