Merge pull request #3 from illiliti/hotplugging_new

implement hotplugging support
This commit is contained in:
illiliti
2020-08-23 00:55:49 +03:00
committed by GitHub
7 changed files with 452 additions and 32 deletions

View File

@@ -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=<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 <dir>" > /proc/sys/kernel/hotplug # change <dir> 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
------

63
contrib/helper.c Normal file
View File

@@ -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 <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <limits.h>
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;
}

13
contrib/helper.sh Normal file
View File

@@ -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.$$"

13
contrib/mdev.conf Normal file
View File

@@ -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.$$

3
udev.h
View File

@@ -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

View File

@@ -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

View File

@@ -1,33 +1,180 @@
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <limits.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/inotify.h>
#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;
}