diff --git a/README.md b/README.md
index 6105d9d..3bf3882 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,17 @@
libudev-zero
============
-Drop-in replacement for libudev intended to work without daemon
+Drop-in replacement for libudev enables you to use whatever
+device manager you like without worrying about udev dependency at all!
What Works
----------
+
* [x] xorg-server
* [x] libinput
* [x] wlroots
* [x] weston
-* [ ] libusb - doesn't work for now. temporary workaround is compiling libusb with --disable-udev
+* [x] libusb
* [x] kwin - [fix](https://github.com/dilyn-corner/KISS-kde/commit/0cc72748e46f859a0fced55b0c3fcc1dd9586a38)
* [ ] ???
@@ -19,6 +21,7 @@ Dependencies
* C99 compiler (build time)
* POSIX make (build time)
* POSIX & XSI libc
+* epoll & inotify
* Linux >= 2.6.39
Installation
@@ -31,12 +34,51 @@ make PREFIX=/usr install # will overwrite existing udev libraries if any
# here we go !
```
+Hotplugging
+-----------
+
+There is no complicated or overengineered way to use hotplugging. Everything is
+portable as much as possible. To use hotplugging the only thing you need is
+uevent's receiver (device manager, busybox `uevent`, CONFIG_UEVENT_HELPER, ...).
+I will describe only mdev and CONFIG_UEVENT_HELPER because their usage is very basic.
+For busybox `uevent` you need to write your own parser which is kinda ... complex.
+
+UDEV_MONITOR_DIR is arbitrary directory where uevent files stored.
+Default is `/tmp/.libudev-zero`. You can change it at build time by appending
+`-DUDEV_MONITOR_DIR=
` to CFLAGS. I don't recommend setting UDEV_MONITOR_DIR
+to regular fs (i.e non-tmpfs) because unneeded files aren't automatically discarded
+after reboot or termination (yet).
+
+* mdev
+
+ - merge [mdev.conf](contrib/mdev.conf) with your mdev.conf
+ - restart mdev daemon
+
+* CONFIG_UEVENT_HELPER
+
+ - ensure that CONFIG_UEVENT_HELPER enabled in kernel
+ - add full path of [helper.sh](contrib/helper.sh) (must be executable) or
+ [helper.c](contrib/helper.c) (compile it first) to /proc/sys/kernel/hotplug
+
+ example:
+ ```sh
+ echo /full/path/to/helper > /proc/sys/kernel/hotplug # will use default UDEV_MONITOR_DIR
+ OR
+ echo "/full/path/to/helper " > /proc/sys/kernel/hotplug # change to your UDEV_MONITOR_DIR
+ ```
+
+* run application which uses hotplugging (e.g xorg-server)
+* unplug and plug something to test working capacity
+
+That's all! If you realized that this doesn't work for you,
+you can always open an issue and describe your bug.
+
TODO
----
* [x] speed up performance
* [x] extend devices support
-* [ ] implement hotplugging support
+* [x] implement hotplugging support
Donate
------
diff --git a/contrib/helper.c b/contrib/helper.c
new file mode 100644
index 0000000..ba46094
--- /dev/null
+++ b/contrib/helper.c
@@ -0,0 +1,63 @@
+/*
+ * this helper pretty similar to helper.sh, but it
+ * doesn't write unrelated variables to file (e.g PWD or PATH)
+ *
+ * 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
+ */
+
+#include
+#include
+#include
+#include
+#include
+
+int main(int argc, char **argv)
+{
+ extern char **environ;
+ char path[PATH_MAX];
+ char *dir;
+ int fd, i;
+
+ 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 1;
+ }
+
+ snprintf(path, sizeof(path), "%s/uevent.XXXXXX", dir);
+ fd = mkstemp(path);
+
+ if (fd == -1) {
+ perror("mkstemp");
+ return 1;
+ }
+
+ for (i = 0; environ[i]; i++) {
+ if (strncmp(environ[i], "PATH", 4) == 0 ||
+ strncmp(environ[i], "HOME", 4) == 0) {
+ continue;
+ }
+
+ if (write(fd, environ[i], strlen(environ[i])) == -1 ||
+ write(fd, "\n", 1) == -1) {
+ perror("write");
+ close(fd);
+ unlink(path);
+ return 1;
+ }
+ }
+
+ close(fd);
+ return 0;
+}
diff --git a/contrib/helper.sh b/contrib/helper.sh
new file mode 100644
index 0000000..ef9877a
--- /dev/null
+++ b/contrib/helper.sh
@@ -0,0 +1,13 @@
+#!/bin/sh -f
+#
+# this helper required for CONFIG_UEVENT_HELPER aka /proc/sys/kernel/hotplug
+# for mdev you can use simple one-liner, refer to mdev.conf for more info
+#
+# usage:
+# echo /full/path/to/helper.sh > /proc/sys/kernel/hotplug
+# echo "/full/path/to/helper.sh UDEV_MONITOR_DIR" > /proc/sys/kernel/hotplug
+#
+
+# NOTE: writing variables to file (e.g PWD or PATH)
+# that are not related to uevent properties is harmless
+env > "${1:-/tmp/.libudev-zero}/uevent.$$"
diff --git a/contrib/mdev.conf b/contrib/mdev.conf
new file mode 100644
index 0000000..ff5334c
--- /dev/null
+++ b/contrib/mdev.conf
@@ -0,0 +1,13 @@
+#
+# example basic mdev config representing libudev-zero usage
+# uncomment needed rules
+#
+# NOTE: you must change "/tmp/.libudev-zero" if you use non-default UDEV_MONITOR_DIR
+# NOTE: mdev can only handle ADD and/or REMOVE events
+
+# will handle all uevents
+#-.* root:root 660 *env > /tmp/.libudev-zero/uevent.$$
+
+# will 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.$$
diff --git a/udev.h b/udev.h
index b8b2454..08188ea 100644
--- a/udev.h
+++ b/udev.h
@@ -96,6 +96,9 @@ struct udev_hwdb *udev_hwdb_ref(struct udev_hwdb *hwdb);
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);
+
#ifdef __cplusplus
}
#endif
diff --git a/udev_device.c b/udev_device.c
index e059ad3..bebfd2c 100644
--- a/udev_device.c
+++ b/udev_device.c
@@ -9,7 +9,7 @@
#include "udev.h"
#include "udev_list.h"
-enum { BITS_SIZE = 96 };
+enum { BITS_MAX = 96 };
struct udev_device {
struct udev_list_entry properties;
@@ -148,7 +148,7 @@ int udev_device_get_is_initialized(struct udev_device *udev_device)
const char *udev_device_get_action(struct udev_device *udev_device)
{
- return NULL;
+ return udev_device_get_property_value(udev_device, "ACTION");
}
int udev_device_has_tag(struct udev_device *udev_device, const char *tag)
@@ -193,7 +193,9 @@ const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const
struct udev_list_entry *list_entry;
char data[BUFSIZ], path[PATH_MAX];
struct stat st;
+ size_t len;
FILE *file;
+ char *pos;
if (!udev_device || !sysattr) {
return NULL;
@@ -217,13 +219,21 @@ const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const
return NULL;
}
- if (fread(data, 1, sizeof(data), file) != sizeof(data) && ferror(file)) {
+ len = fread(data, 1, sizeof(data), file);
+
+ if (len != sizeof(data) && ferror(file)) {
fclose(file);
return NULL;
}
+ if ((pos = memchr(data, '\n', len))) {
+ *pos = '\0';
+ }
+ else {
+ data[len] = '\0';
+ }
+
fclose(file);
- data[strcspn(data, "\n")] = '\0';
list_entry = udev_list_entry_add(&udev_device->sysattrs, sysattr, data, 0);
return udev_list_entry_get_value(list_entry);
}
@@ -282,7 +292,7 @@ static char *udev_device_read_symlink(struct udev_device *udev_device, const cha
static void udev_device_set_properties_from_uevent(struct udev_device *udev_device)
{
char line[LINE_MAX], path[PATH_MAX];
- char *key, *val, devnode[PATH_MAX];
+ char *pos, devnode[PATH_MAX];
FILE *file;
snprintf(path, sizeof(path), "%s/uevent", udev_device_get_syspath(udev_device));
@@ -299,12 +309,9 @@ static void udev_device_set_properties_from_uevent(struct udev_device *udev_devi
snprintf(devnode, sizeof(devnode), "/dev/%s", line + 8);
udev_list_entry_add(&udev_device->properties, "DEVNAME", devnode, 0);
}
- else if (strchr(line, '=')) {
- val = strchr(line, '=') + 1;
- key = line;
- key[strcspn(key, "=")] = '\0';
-
- udev_list_entry_add(&udev_device->properties, key, val, 1);
+ else if ((pos = strchr(line, '='))) {
+ *pos = '\0';
+ udev_list_entry_add(&udev_device->properties, line, pos + 1, 1);
}
}
@@ -328,7 +335,7 @@ static int populate_bit(unsigned long *arr, const char *val)
bit = strtok_r(bits, " ", &save);
- for (i = 0; bit && i < BITS_SIZE; i++) {
+ for (i = 0; bit && i < BITS_MAX; i++) {
arr[i] = strtoul(bit, NULL, 16);
bit = strtok_r(NULL, " ", &save);
}
@@ -357,11 +364,11 @@ static int find_bit(unsigned long *arr, int cnt, int bit)
static void udev_device_set_properties_from_evdev(struct udev_device *udev_device)
{
int ev_cnt, rel_cnt, key_cnt, abs_cnt, prop_cnt;
- unsigned long prop_bits[BITS_SIZE] = {0};
- unsigned long abs_bits[BITS_SIZE] = {0};
- unsigned long rel_bits[BITS_SIZE] = {0};
- unsigned long key_bits[BITS_SIZE] = {0};
- unsigned long ev_bits[BITS_SIZE] = {0};
+ unsigned long prop_bits[BITS_MAX] = {0};
+ unsigned long abs_bits[BITS_MAX] = {0};
+ unsigned long rel_bits[BITS_MAX] = {0};
+ unsigned long key_bits[BITS_MAX] = {0};
+ unsigned long ev_bits[BITS_MAX] = {0};
struct udev_device *parent;
const char *subsystem;
@@ -371,7 +378,7 @@ static void udev_device_set_properties_from_evdev(struct udev_device *udev_devic
return;
}
- parent = udev_device_get_parent_with_subsystem_devtype(udev_device, "input", NULL);
+ parent = udev_device;
while (1) {
if (!parent) {
@@ -548,6 +555,72 @@ 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)
+{
+ char line[LINE_MAX], syspath[PATH_MAX], devnode[PATH_MAX];
+ struct udev_device *udev_device;
+ char *pos, *sysname;
+ FILE *file;
+ int i;
+
+ udev_device = calloc(1, sizeof(struct udev_device));
+
+ if (!udev_device) {
+ return NULL;
+ }
+
+ udev_device->udev = udev;
+ udev_device->refcount = 1;
+ udev_device->parent = NULL;
+
+ udev_list_entry_init(&udev_device->properties);
+ udev_list_entry_init(&udev_device->sysattrs);
+
+ file = fopen(path, "r");
+
+ if (!file) {
+ return NULL;
+ }
+
+ while (fgets(line, sizeof(line), file)) {
+ line[strcspn(line, "\n")] = '\0';
+
+ if (strncmp(line, "DEVPATH", 7) == 0) {
+ snprintf(syspath, sizeof(syspath), "/sys%s", line + 8);
+ udev_list_entry_add(&udev_device->properties, "SYSPATH", syspath, 0);
+ udev_list_entry_add(&udev_device->properties, "DEVPATH", line + 8, 0);
+
+ sysname = strrchr(syspath, '/') + 1;
+ udev_list_entry_add(&udev_device->properties, "SYSNAME", sysname, 0);
+
+ for (i = 0; sysname[i] != '\0'; i++) {
+ if (sysname[i] >= '0' && sysname[i] <= '9') {
+ udev_list_entry_add(&udev_device->properties, "SYSNUM", sysname + i, 0);
+ break;
+ }
+ }
+ }
+ else if (strncmp(line, "DEVNAME", 7) == 0) {
+ snprintf(devnode, sizeof(devnode), "/dev/%s", line + 8);
+ udev_list_entry_add(&udev_device->properties, "DEVNAME", devnode, 0);
+ }
+ else if ((pos = strchr(line, '='))) {
+ *pos = '\0';
+ udev_list_entry_add(&udev_device->properties, line, pos + 1, 0);
+ }
+ }
+
+ fclose(file);
+
+ if (!udev_device_get_syspath(udev_device)) {
+ udev_device_unref(udev_device);
+ return NULL;
+ }
+
+ udev_device_set_properties_from_evdev(udev_device);
+ return udev_device;
+}
+
struct udev_device *udev_device_new_from_device_id(struct udev *udev, const char *id)
{
// XXX NOT IMPLEMENTED
diff --git a/udev_monitor.c b/udev_monitor.c
index 1e88b70..77fac66 100644
--- a/udev_monitor.c
+++ b/udev_monitor.c
@@ -1,33 +1,180 @@
-#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"
+#endif
+
+enum { THREADS_MAX = 5 };
struct udev_monitor {
+ struct udev_list_entry subsystem_match;
+ struct udev_list_entry devtype_match;
+ pthread_t thread[THREADS_MAX];
+ pthread_barrier_t barrier;
struct udev *udev;
int refcount;
- int fd[2];
+ int sfd[2];
+ int ifd;
+ int efd;
};
+static int udev_monitor_filter_devtype(struct udev_monitor *udev_monitor, struct udev_device *udev_device)
+{
+ struct udev_list_entry *list_entry;
+ const char *devtype;
+
+ devtype = udev_device_get_devtype(udev_device);
+ list_entry = udev_list_entry_get_next(&udev_monitor->devtype_match);
+
+ if (!list_entry) {
+ return 1;
+ }
+
+ if (!devtype) {
+ return 0;
+ }
+
+ while (list_entry) {
+ if (strcmp(devtype, udev_list_entry_get_name(list_entry)) == 0) {
+ return 1;
+ }
+
+ list_entry = udev_list_entry_get_next(list_entry);
+ }
+
+ return 0;
+}
+
+static int udev_monitor_filter_subsystem(struct udev_monitor *udev_monitor, struct udev_device *udev_device)
+{
+ struct udev_list_entry *list_entry;
+ const char *subsystem;
+
+ subsystem = udev_device_get_subsystem(udev_device);
+ list_entry = udev_list_entry_get_next(&udev_monitor->subsystem_match);
+
+ if (!list_entry) {
+ return 1;
+ }
+
+ if (!subsystem) {
+ return 0;
+ }
+
+ while (list_entry) {
+ if (strcmp(subsystem, udev_list_entry_get_name(list_entry)) == 0) {
+ return 1;
+ }
+
+ list_entry = udev_list_entry_get_next(list_entry);
+ }
+
+ return 0;
+}
+
struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monitor)
{
- return (void *)1;
+ char file[PATH_MAX + sizeof(UDEV_MONITOR_DIR)], data[4096];
+ struct udev_device *udev_device;
+
+ if (recv(udev_monitor->sfd[0], data, sizeof(data), 0) == -1) {
+ return NULL;
+ }
+
+ snprintf(file, sizeof(file), UDEV_MONITOR_DIR "/%s", data);
+ udev_device = udev_device_new_from_file(udev_monitor->udev, file);
+
+ if (!udev_device) {
+ return NULL;
+ }
+
+ if (!udev_monitor_filter_subsystem(udev_monitor, udev_device) ||
+ !udev_monitor_filter_devtype(udev_monitor, udev_device)) {
+ udev_device_unref(udev_device);
+ return NULL;
+ }
+
+ return udev_device;
+}
+
+static void *udev_monitor_handle_event(void *ptr)
+{
+ struct udev_monitor *udev_monitor = ptr;
+ struct inotify_event *event;
+ struct epoll_event epoll;
+ char data[4096];
+ sigset_t mask;
+ ssize_t len;
+ int i;
+
+ sigemptyset(&mask);
+
+ while (epoll_pwait(udev_monitor->efd, &epoll, 1, -1, &mask) != -1) {
+ len = read(udev_monitor->ifd, data, sizeof(data));
+
+ if (len == -1) {
+ continue;
+ }
+
+ for (i = 0; i < len; i += sizeof(struct inotify_event) + event->len) {
+ event = (struct inotify_event *)&data[i];
+
+ // TODO user deleted directory, what should we do ?
+ if (event->mask & IN_IGNORED) {
+ break;
+ }
+
+ send(udev_monitor->sfd[1], event->name, event->len, 0);
+ }
+ }
+
+ pthread_barrier_wait(&udev_monitor->barrier);
+ return NULL;
}
int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor)
{
+ pthread_attr_t attr;
+ int i;
+
+ if (!udev_monitor) {
+ return -1;
+ }
+
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ pthread_barrier_init(&udev_monitor->barrier, NULL, THREADS_MAX + 1);
+
+ for (i = 0; i < THREADS_MAX; i++) {
+ pthread_create(&udev_monitor->thread[i], &attr, udev_monitor_handle_event, udev_monitor);
+ }
+
+ pthread_attr_destroy(&attr);
return 0;
}
int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int size)
{
+ // XXX NOT IMPLEMENTED
return 0;
}
int udev_monitor_get_fd(struct udev_monitor *udev_monitor)
{
- return udev_monitor ? udev_monitor->fd[0] : -1;
+ return udev_monitor ? udev_monitor->sfd[0] : -1;
}
struct udev *udev_monitor_get_udev(struct udev_monitor *udev_monitor)
@@ -37,28 +184,42 @@ struct udev *udev_monitor_get_udev(struct udev_monitor *udev_monitor)
int udev_monitor_filter_update(struct udev_monitor *udev_monitor)
{
+ // XXX NOT IMPLEMENTED
return 0;
}
int udev_monitor_filter_remove(struct udev_monitor *udev_monitor)
{
+ // XXX NOT IMPLEMENTED
return 0;
}
int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_monitor, const char *subsystem, const char *devtype)
{
+ if (!udev_monitor || !subsystem) {
+ return -1;
+ }
+
+ udev_list_entry_add(&udev_monitor->subsystem_match, subsystem, NULL, 0);
+
+ if (devtype) {
+ udev_list_entry_add(&udev_monitor->devtype_match, devtype, NULL, 0);
+ }
+
return 0;
}
int udev_monitor_filter_add_match_tag(struct udev_monitor *udev_monitor, const char *tag)
{
+ // XXX NOT IMPLEMENTED
return 0;
}
struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char *name)
{
struct udev_monitor *udev_monitor;
- int i;
+ struct epoll_event epoll;
+ struct stat st;
if (!udev || !name) {
return NULL;
@@ -70,14 +231,54 @@ struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char
return NULL;
}
- // TODO better no-op, HELP WANTED !
- if (pipe(udev_monitor->fd) == -1) {
+ if (lstat(UDEV_MONITOR_DIR, &st) != 0) {
+ if (mkdir(UDEV_MONITOR_DIR, 0) == -1 ||
+ chmod(UDEV_MONITOR_DIR, 0777) == -1) {
+ free(udev_monitor);
+ return NULL;
+ }
+ }
+ else if (!S_ISDIR(st.st_mode)) {
free(udev_monitor);
return NULL;
}
- for (i = 0; i < 2; i++) {
- fcntl(udev_monitor->fd[i], F_SETFD, FD_CLOEXEC | O_NONBLOCK);
+ udev_monitor->efd = epoll_create1(EPOLL_CLOEXEC);
+
+ if (udev_monitor->efd == -1) {
+ free(udev_monitor);
+ return NULL;
+ }
+
+ udev_monitor->ifd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
+
+ if (udev_monitor->ifd == -1) {
+ close(udev_monitor->efd);
+ free(udev_monitor);
+ return NULL;
+ }
+
+ if (inotify_add_watch(udev_monitor->ifd, UDEV_MONITOR_DIR, IN_CLOSE_WRITE | IN_EXCL_UNLINK) == -1) {
+ close(udev_monitor->ifd);
+ close(udev_monitor->efd);
+ free(udev_monitor);
+ return NULL;
+ }
+
+ epoll.events = EPOLLIN | EPOLLET;
+
+ if (epoll_ctl(udev_monitor->efd, EPOLL_CTL_ADD, udev_monitor->ifd, &epoll) == -1) {
+ close(udev_monitor->ifd);
+ close(udev_monitor->efd);
+ free(udev_monitor);
+ return NULL;
+ }
+
+ if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, udev_monitor->sfd) == -1) {
+ close(udev_monitor->ifd);
+ close(udev_monitor->efd);
+ free(udev_monitor);
+ return NULL;
}
udev_monitor->refcount = 1;
@@ -107,10 +308,22 @@ struct udev_monitor *udev_monitor_unref(struct udev_monitor *udev_monitor)
return NULL;
}
- for (i = 0; i < 2; i++) {
- close(udev_monitor->fd[i]);
+ udev_list_entry_free_all(&udev_monitor->subsystem_match);
+ udev_list_entry_free_all(&udev_monitor->devtype_match);
+
+ for (i = 0; i < THREADS_MAX; i++) {
+ pthread_cancel(udev_monitor->thread[i]);
}
+ pthread_barrier_wait(&udev_monitor->barrier);
+ pthread_barrier_destroy(&udev_monitor->barrier);
+
+ for (i = 0; i < 2; i++) {
+ close(udev_monitor->sfd[i]);
+ }
+
+ close(udev_monitor->ifd);
+ close(udev_monitor->efd);
free(udev_monitor);
return NULL;
}