diff --git a/README.md b/README.md
index a13a136..7c8ee44 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# libudev-zero
-Drop-in replacement for `libudev` that intended to work with any device manager
+Drop-in replacement for `libudev` intended to work with any device manager
## Why?
@@ -11,15 +11,11 @@ to rewrite[0] this crappy library because `libinput` hard-depends on `udev`.
Without `libinput` you can't use `wayland` and many other cool stuff.
Michael Forney (author of `cproc`, `samurai`, Oasis Linux, ...) decided to
-fork[1] `libinput` and remove the hard dependency on `udev`. Is this a
-solution? Yes. Is this a complete solution? No. This fork has a lot of
-disadvantages like requiring patching applications to use `libinput_netlink`
+fork[1] `libinput` and remove the hard dependency on `udev`. However, this
+fork has a drawback that requires patching applications to use `libinput_netlink`
instead of the `libinput_udev` API in order to use the automatic detection of
-input devices and hotplugging. Static configuration is also required for
-anything other than input devices (e.g drm devices). Moreover hotplugging is
-vulnerable to race conditions when `libinput` handles the `uevent` faster than
-the device manager which can lead to file permission issues. `libudev-zero`
-prevents these race conditions by design.
+input devices and hotplugging. Static configuration is also required for anything
+other than input devices (e.g drm devices).
Thankfully `udev` has stable API and hopefully no changes will be made to it
the future. On this basis I decided to create this clean-room implementation
@@ -45,7 +41,6 @@ of `libudev` which can be used with any or without a device manager.
* C99 compiler (build time)
* POSIX make (build time)
* POSIX & XSI libc
-* inotify & eventfd
* Linux >= 2.6.39
## Installation
@@ -60,18 +55,21 @@ make PREFIX=/usr install
Note that hotplugging support is fully optional. You can skip
this step if you don't have a need for the hotplugging capability.
-In order to use hotplugging, you need to configure device manager to send
-`uevent` messages to `UDEV_MONITOR_DIR`. `UDEV_MONITOR_DIR` is arbitrary
-shared directory used by `libudev-zero` to receive `uevent` messages. By
-default, `UDEV_MONITOR_DIR` points to `/tmp/.libudev-zero`. You can change
-that directory at compile time by passing `-DUDEV_MONITOR_DIR=
` to
-`CFLAGS` or at runtime by setting `UDEV_MONITOR_DIR` environment variable.
+If you're using mdev-like device manager, refer to [mdev.conf](contrib/mdev.conf)
+for config example.
-Keep in mind that already processed `uevent` messages wouldn't be automatically
-purged. You can set `UDEV_MONITOR_DIR` to directory on tmpfs to purge them on
-reboot/shutdown.
+If you're using other device manager, you need to configure it to rebroadcast
+kernel uevents. You can do this by either patching(see below) device manager
+or simply executing [helper.c](contrib/helper.c) for each uevent.
-Refer to [contrib](contrib) for usage examples and configs.
+If you're developing your own device manager, you need to rebroadcast kernel
+uevents to `0x4` netlink group of `NETLINK_KOBJECT_UEVENT`. This is required
+because libudev-zero can't simply listen to kernel uevents due to potential
+race conditions. Refer(but don't copy blindly) to [helper.c](contrib/helper.c)
+for example how it could be implemented in C.
+
+Don't hesitate to ask me everything you don't understand. I'm usually hanging
+around in #kisslinux at libera.chat, but you can also email me or open an issue here.
## Donate
diff --git a/contrib/helper.c b/contrib/helper.c
index 912fd57..6fff27e 100644
--- a/contrib/helper.c
+++ b/contrib/helper.c
@@ -15,50 +15,27 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*
- * NOTE: you don't need this if you have mdev/mdevd, refer to mdev.conf
- * NOTE: you need this if you want to use bare-bones CONFIG_UEVENT_HELPER
- *
- * build:
- * cc helper.c -o helper
- *
- * usage:
- * echo /full/path/to/helper > /proc/sys/kernel/hotplug
- * echo "/full/path/to/helper UDEV_MONITOR_DIR" > /proc/sys/kernel/hotplug
+ * Construct uevent message from environment and send it to 0x4 netlink group.
*/
#include
#include
#include
-#include
-#include
-#include
+#include
+#include
int main(int argc, char **argv)
{
+ struct sockaddr_nl sa = {0};
+ struct msghdr hdr = {0};
+ struct iovec iov = {0};
extern char **environ;
- char path[PATH_MAX];
- char *dir;
- int fd, i;
+ char buf[8192];
+ size_t len;
+ int i, fd;
- switch (argc) {
- case 1:
- dir = "/tmp/.libudev-zero";
- break;
- case 2:
- dir = argv[1];
- break;
- default:
- fprintf(stderr, "usage: %s [dir]\n", argv[0]);
- return 2;
- }
-
- snprintf(path, sizeof(path), "%s/uevent.XXXXXX", dir);
- fd = mkstemp(path);
-
- if (fd == -1) {
- perror("mkstemp");
- return 1;
- }
+ iov.iov_base = buf;
+ iov.iov_len = 0;
for (i = 0; environ[i]; i++) {
if (strncmp(environ[i], "PATH=", 5) == 0 ||
@@ -66,16 +43,44 @@ int main(int argc, char **argv)
continue;
}
- if (write(fd, environ[i], strlen(environ[i])) == -1 ||
- write(fd, "\n", 1) == -1) {
- perror("write");
- close(fd);
- unlink(path);
+ len = strlen(environ[i]) + 1;
+
+ if (iov.iov_len + len > sizeof(buf)) {
+ fprintf(stderr, "%s: uevent exceeds buffer size", argv[0]);
return 1;
}
+
+ memcpy(buf + iov.iov_len, environ[i], len);
+ iov.iov_len += len;
+ }
+
+ sa.nl_family = AF_NETLINK;
+ sa.nl_groups = 0x4; // XXX
+
+ hdr.msg_name = &sa;
+ hdr.msg_namelen = sizeof(sa);
+ hdr.msg_iov = &iov;
+ hdr.msg_iovlen = 1;
+
+ fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
+
+ if (fd == -1) {
+ perror("socket");
+ return 1;
+ }
+
+ if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {
+ perror("bind");
+ close(fd);
+ return 1;
+ }
+
+ if (sendmsg(fd, &hdr, 0) == -1) {
+ perror("sendmsg");
+ close(fd);
+ return 1;
}
- fchmod(fd, 0444);
close(fd);
return 0;
}
diff --git a/contrib/helper.sh b/contrib/helper.sh
deleted file mode 100644
index faa1d61..0000000
--- a/contrib/helper.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/sh -f
-#
-# NOTE: you don't need this if you have mdev/mdevd, refer to mdev.conf
-# NOTE: you need this if you want to use bare-bones CONFIG_UEVENT_HELPER
-#
-# usage:
-# echo /full/path/to/helper.sh > /proc/sys/kernel/hotplug
-# echo "/full/path/to/helper.sh UDEV_MONITOR_DIR" > /proc/sys/kernel/hotplug
-
-exec env > "${1:-/tmp/.libudev-zero}/uevent.$$"
diff --git a/contrib/mdev.conf b/contrib/mdev.conf
index 7bc3c09..18c9dc5 100644
--- a/contrib/mdev.conf
+++ b/contrib/mdev.conf
@@ -1,12 +1,10 @@
-#
# example rules for mdev.conf
#
-# NOTE: you must change "/tmp/.libudev-zero" if you use non-default UDEV_MONITOR_DIR
-# NOTE: you don't need helper.c or helper.sh, just add this to /etc/mdev.conf
+# NOTE: replace /path/to/helper with path to compiled binary of helper.c
# handle all uevents(not recommended)
-#-.* root:root 660 *env > /tmp/.libudev-zero/uevent.$$
+#-.* root:root 660 */path/to/helper
# handle only drm and input uevents(recommended)
-SUBSYSTEM=drm;.* root:video 660 *env > /tmp/.libudev-zero/uevent.$$
-SUBSYSTEM=input;.* root:input 660 *env > /tmp/.libudev-zero/uevent.$$
+SUBSYSTEM=drm;.* root:video 660 */path/to/helper
+SUBSYSTEM=input;.* root:input 660 */path/to/helper
diff --git a/udev.h b/udev.h
index 39e4043..057d4af 100644
--- a/udev.h
+++ b/udev.h
@@ -129,7 +129,7 @@ struct udev_hwdb *udev_hwdb_unref(struct udev_hwdb *hwdb);
struct udev_list_entry *udev_hwdb_get_properties_list_entry(struct udev_hwdb *hwdb, const char *modalias, unsigned int flags);
// this is "libudev-zero" extension. do not use if portability is concern
-struct udev_device *udev_device_new_from_file(struct udev *udev, const char *path);
+struct udev_device *udev_device_new_from_uevent(struct udev *udev, char *buf, size_t len);
#ifdef __cplusplus
}
diff --git a/udev_device.c b/udev_device.c
index d9ca6c7..939ab46 100644
--- a/udev_device.c
+++ b/udev_device.c
@@ -632,35 +632,20 @@ struct udev_device *udev_device_new_from_subsystem_sysname(struct udev *udev, co
return NULL;
}
-struct udev_device *udev_device_new_from_file(struct udev *udev, const char *path)
+struct udev_device *udev_device_new_from_uevent(struct udev *udev, char *buf, size_t len)
{
- char line[LINE_MAX], syspath[PATH_MAX], devnode[PATH_MAX];
+ char syspath[PATH_MAX], devnode[PATH_MAX];
struct udev_device *udev_device;
- struct stat st;
- char *sysname;
- FILE *file;
- int i, cnt;
- char *pos;
-
- if (stat(path, &st) != 0 || st.st_size > 8192) {
- return NULL;
- }
-
- file = fopen(path, "r");
-
- if (!file) {
- return NULL;
- }
+ const char *sysname;
+ char *end, *pos;
+ int i, cnt = 0;
udev_device = calloc(1, sizeof(*udev_device));
if (!udev_device) {
- fclose(file);
return NULL;
}
- cnt = 0;
-
udev_device->udev = udev;
udev_device->refcount = 1;
udev_device->parent = NULL;
@@ -668,13 +653,11 @@ struct udev_device *udev_device_new_from_file(struct udev *udev, const char *pat
udev_list_entry_init(&udev_device->properties);
udev_list_entry_init(&udev_device->sysattrs);
- while (fgets(line, sizeof(line), file)) {
- line[strlen(line) - 1] = '\0';
-
- if (strncmp(line, "DEVPATH=", 8) == 0) {
- snprintf(syspath, sizeof(syspath), "/sys%s", line + 8);
+ for (end = buf + len; buf < end; buf += strlen(buf) + 1) {
+ if (strncmp(buf, "DEVPATH=", 8) == 0) {
+ snprintf(syspath, sizeof(syspath), "/sys%s", buf + 8);
udev_list_entry_add(&udev_device->properties, "SYSPATH", syspath, 0);
- udev_list_entry_add(&udev_device->properties, "DEVPATH", line + 8, 0);
+ udev_list_entry_add(&udev_device->properties, "DEVPATH", buf + 8, 0);
sysname = strrchr(syspath, '/') + 1;
udev_list_entry_add(&udev_device->properties, "SYSNAME", sysname, 0);
@@ -688,35 +671,30 @@ struct udev_device *udev_device_new_from_file(struct udev *udev, const char *pat
cnt++;
}
- else if (strncmp(line, "DEVNAME=", 8) == 0) {
- snprintf(devnode, sizeof(devnode), "/dev/%s", line + 8);
+ else if (strncmp(buf, "DEVNAME=", 8) == 0) {
+ snprintf(devnode, sizeof(devnode), "/dev/%s", buf + 8);
udev_list_entry_add(&udev_device->properties, "DEVNAME", devnode, 0);
}
else {
- pos = strchr(line, '=');
+ pos = strchr(buf, '=');
- // file is malformed, abort here.
if (!pos) {
- cnt = 0;
- break;
+ continue;
}
*pos = '\0';
- if (strncmp(line, "SUBSYSTEM", 9) == 0 ||
- strncmp(line, "ACTION", 6) == 0 ||
- strncmp(line, "SEQNUM", 6) == 0) {
+ if (strcmp(buf, "SUBSYSTEM") == 0 ||
+ strcmp(buf, "ACTION") == 0 ||
+ strcmp(buf, "SEQNUM") == 0) {
cnt++;
}
- udev_list_entry_add(&udev_device->properties, line, pos + 1, 0);
+ udev_list_entry_add(&udev_device->properties, buf, pos + 1, 0);
+ *pos = '=';
}
}
- fclose(file);
-
- // https://freedesktop.org/software/systemd/man/udev_device_new_from_environment.html
- // > The keys DEVPATH, SUBSYSTEM, ACTION, and SEQNUM are mandatory.
if (cnt != 4) {
udev_device_unref(udev_device);
return NULL;
diff --git a/udev_monitor.c b/udev_monitor.c
index 1e79310..820d6a3 100644
--- a/udev_monitor.c
+++ b/udev_monitor.c
@@ -15,36 +15,26 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#include
-#include
-#include
#include
#include
#include
-#include
-#include
-#include
#include
-#include
-#include
+#include
#include "udev.h"
#include "udev_list.h"
-#ifndef UDEV_MONITOR_DIR
-#define UDEV_MONITOR_DIR "/tmp/.libudev-zero"
+#ifndef UDEV_MONITOR_NLGRP
+#define UDEV_MONITOR_NLGRP 0x4
#endif
struct udev_monitor {
struct udev_list_entry subsystem_match;
struct udev_list_entry devtype_match;
struct udev *udev;
- pthread_t thread;
- const char *dir;
- int signal_fd;
int refcount;
- int sfd[2];
- int ifd;
+ int nlgrp;
+ int fd;
};
static int filter_devtype(struct udev_monitor *udev_monitor, struct udev_device *udev_device)
@@ -103,106 +93,75 @@ static int filter_subsystem(struct udev_monitor *udev_monitor, struct udev_devic
struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monitor)
{
- char file[PATH_MAX], data[4096];
struct udev_device *udev_device;
-
- if (recv(udev_monitor->sfd[0], data, sizeof(data), 0) == -1) {
- return NULL;
- }
-
- // check truncation error to make gcc happy
- if ((unsigned)snprintf(file, sizeof(file), "%s/%s", udev_monitor->dir, data) >= sizeof(file)) {
- return NULL;
- }
-
- udev_device = udev_device_new_from_file(udev_monitor->udev, file);
-
- if (!udev_device) {
- return NULL;
- }
-
- if (!filter_subsystem(udev_monitor, udev_device) ||
- !filter_devtype(udev_monitor, udev_device)) {
- udev_device_unref(udev_device);
- return NULL;
- }
-
- return udev_device;
-}
-
-static void *handle_event(void *ptr)
-{
- struct udev_monitor *udev_monitor = ptr;
- struct inotify_event *event;
- struct pollfd poll_fds[2];
- char data[4096];
+ struct sockaddr_nl sa = {0};
+ struct msghdr hdr = {0};
+ struct iovec iov = {0};
+ char buf[8192];
ssize_t len;
- int i;
- poll_fds[0].fd = udev_monitor->ifd;
- poll_fds[0].events = POLLIN;
+ iov.iov_base = buf;
+ iov.iov_len = sizeof(buf);
- poll_fds[1].fd = udev_monitor->signal_fd;
- poll_fds[1].events = POLLIN;
+ hdr.msg_name = &sa;
+ hdr.msg_namelen = sizeof(sa);
+ hdr.msg_iov = &iov;
+ hdr.msg_iovlen = 1;
while (1) {
- if (poll(poll_fds, 2, -1) == -1) {
- if (errno == EINTR) {
- continue;
- }
+ len = recvmsg(udev_monitor->fd, &hdr, 0);
- return NULL;
+ if (len <= 0) {
+ break;
}
- // exit on explicit signal
- if (poll_fds[1].revents & POLLIN) {
- return NULL;
+ if (hdr.msg_flags & MSG_TRUNC) {
+ continue;
}
- // exit on poll error
- if (!(poll_fds[0].revents & POLLIN)) {
- return NULL;
+ if (sa.nl_groups == 0x0 || (sa.nl_groups == 0x1 && sa.nl_pid)) {
+ continue;
}
- len = read(udev_monitor->ifd, data, sizeof(data));
+ udev_device = udev_device_new_from_uevent(udev_monitor->udev, buf, len);
- if (len == -1) {
- return NULL;
+ if (!udev_device) {
+ continue;
}
- for (i = 0; i < len; i += sizeof(struct inotify_event) + event->len) {
- event = (struct inotify_event *)&data[i];
-
- // TODO directory is removed
- if (event->mask & IN_IGNORED) {
- break;
- }
-
- if (event->mask & IN_ISDIR) {
- continue;
- }
-
- send(udev_monitor->sfd[1], event->name, event->len, 0);
+ if (!filter_subsystem(udev_monitor, udev_device) ||
+ !filter_devtype(udev_monitor, udev_device)) {
+ udev_device_unref(udev_device);
+ continue;
}
+
+ return udev_device;
}
- // unreachable
return NULL;
}
int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor)
{
- return udev_monitor ? (pthread_create(&udev_monitor->thread, NULL, handle_event, udev_monitor) == 0) - 1 : -1;
+ struct sockaddr_nl sa = {0};
+
+ if (!udev_monitor) {
+ return -1;
+ }
+
+ sa.nl_family = AF_NETLINK;
+ sa.nl_groups = udev_monitor->nlgrp;
+ return bind(udev_monitor->fd, (struct sockaddr *)&sa, sizeof(sa));
}
-/* XXX NOT IMPLEMENTED */ int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int size)
+int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int size)
{
- return 0;
+ return udev_monitor ? setsockopt(udev_monitor->fd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)) : -1;
}
int udev_monitor_get_fd(struct udev_monitor *udev_monitor)
{
- return udev_monitor ? udev_monitor->sfd[0] : -1;
+ return udev_monitor ? udev_monitor->fd : -1;
}
struct udev *udev_monitor_get_udev(struct udev_monitor *udev_monitor)
@@ -254,60 +213,27 @@ struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char
return NULL;
}
- udev_monitor->signal_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
-
- if (udev_monitor->signal_fd == -1) {
- goto free_monitor;
+ if (strcmp(name, "udev") == 0) {
+ udev_monitor->nlgrp = UDEV_MONITOR_NLGRP;
+ }
+ else if (strcmp(name, "kernel") == 0) {
+ udev_monitor->nlgrp = 0x1;
+ }
+ else {
+ free(udev_monitor);
+ return NULL;
}
- udev_monitor->ifd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
+ udev_monitor->fd = socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, NETLINK_KOBJECT_UEVENT);
- if (udev_monitor->ifd == -1) {
- goto close_signal_fd;
- }
-
- // TODO docs
- udev_monitor->dir = getenv("UDEV_MONITOR_DIR");
-
- if (!udev_monitor->dir || udev_monitor->dir[0] == '\0') {
- udev_monitor->dir = UDEV_MONITOR_DIR;
-
- if (access(udev_monitor->dir, F_OK) == -1) {
- if (errno != ENOENT) {
- goto close_ifd;
- }
-
- if (mkdir(udev_monitor->dir, 0) == -1) {
- goto close_ifd;
- }
-
- if (chmod(udev_monitor->dir, 0777) == -1) {
- goto close_ifd;
- }
- }
- }
-
- if (inotify_add_watch(udev_monitor->ifd, udev_monitor->dir, IN_CLOSE_WRITE | IN_EXCL_UNLINK | IN_ONLYDIR) == -1) {
- goto close_ifd;
- }
-
- if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, udev_monitor->sfd) == -1) {
- goto close_ifd;
+ if (udev_monitor->fd == -1) {
+ free(udev_monitor);
+ return NULL;
}
udev_monitor->refcount = 1;
udev_monitor->udev = udev;
return udev_monitor;
-
-close_ifd:
- close(udev_monitor->ifd);
-
-close_signal_fd:
- close(udev_monitor->signal_fd);
-
-free_monitor:
- free(udev_monitor);
- return NULL;
}
struct udev_monitor *udev_monitor_ref(struct udev_monitor *udev_monitor)
@@ -322,8 +248,6 @@ struct udev_monitor *udev_monitor_ref(struct udev_monitor *udev_monitor)
struct udev_monitor *udev_monitor_unref(struct udev_monitor *udev_monitor)
{
- int i;
-
if (!udev_monitor) {
return NULL;
}
@@ -332,20 +256,10 @@ struct udev_monitor *udev_monitor_unref(struct udev_monitor *udev_monitor)
return NULL;
}
- // Wake up the event thread
- eventfd_write(udev_monitor->signal_fd, 1);
- // waiting for event thread to end before freeing udev_monitor
- pthread_join(udev_monitor->thread, NULL);
-
udev_list_entry_free_all(&udev_monitor->devtype_match);
udev_list_entry_free_all(&udev_monitor->subsystem_match);
- for (i = 0; i < 2; i++) {
- close(udev_monitor->sfd[i]);
- }
-
- close(udev_monitor->signal_fd);
- close(udev_monitor->ifd);
+ close(udev_monitor->fd);
free(udev_monitor);
return NULL;
}