commit 706e76c5e31ba4a9ae1b94b73d055c9239f4b842 Author: dvab-sarma Date: Fri Jun 27 00:04:35 2025 -0500 android-16.0 diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000..fbd0983 --- /dev/null +++ b/Android.bp @@ -0,0 +1,17 @@ +cc_library { + name: "libudev", + vendor_available: true, + export_include_dirs: ["."], + + srcs: [ + "udev.c", + "udev_device.c", + "udev_enumerate.c", + "udev_list.c", + "udev_monitor.c" + ], + + cflags: [ + "-Wno-unused-parameter", + ], +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4f36858 --- /dev/null +++ b/LICENSE @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2020-2021 illiliti + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..58f70e4 --- /dev/null +++ b/Makefile @@ -0,0 +1,79 @@ +# SPDX-License-Identifier: ISC +.POSIX: + +PREFIX = /usr/local +LIBDIR = ${PREFIX}/lib +INCLUDEDIR = ${PREFIX}/include +PKGCONFIGDIR = ${LIBDIR}/pkgconfig +XCFLAGS = ${CPPFLAGS} ${CFLAGS} -std=c99 -fPIC -pthread -D_XOPEN_SOURCE=700 \ + -Wall -Wextra -Wpedantic -Wmissing-prototypes -Wstrict-prototypes \ + -Wno-unused-parameter +XLDFLAGS = ${LDFLAGS} -shared -Wl,-soname,libudev.so.1 +XARFLAGS = -rc +AR = ar + +OBJ = \ + udev.o \ + udev_list.o \ + udev_device.o \ + udev_monitor.o \ + udev_enumerate.o + +all: libudev.so.1 libudev.a + +.c.o: + ${CC} ${XCFLAGS} -c -o $@ $< + +libudev.a: ${OBJ} + ${AR} ${XARFLAGS} $@ ${OBJ} + +libudev.so.1: ${OBJ} + ${CC} ${XCFLAGS} -o $@ ${OBJ} ${XLDFLAGS} + +libudev.pc: libudev.pc.in + libdir="${LIBDIR}"; \ + if [ "$${libdir#${PREFIX}}" != "$$libdir" ]; then \ + libdir="\$${exec_prefix}$${libdir#${PREFIX}}"; \ + fi; \ + includedir="${INCLUDEDIR}"; \ + if [ "$${includedir#${PREFIX}}" != "$$includedir" ]; then \ + includedir="\$${prefix}$${includedir#${PREFIX}}"; \ + fi; \ + sed -e 's|@prefix@|${PREFIX}|g' \ + -e 's|@exec_prefix@|${PREFIX}|g' \ + -e "s|@libdir@|$$libdir|g" \ + -e "s|@includedir@|$$includedir|g" \ + -e 's|@VERSION@|243|g' \ + libudev.pc.in > libudev.pc + +install-headers: udev.h + mkdir -p ${DESTDIR}${INCLUDEDIR} + cp -f udev.h ${DESTDIR}${INCLUDEDIR}/libudev.h + +install-pkgconfig: libudev.pc + mkdir -p ${DESTDIR}${PKGCONFIGDIR} + cp -f libudev.pc ${DESTDIR}${PKGCONFIGDIR}/libudev.pc + +install-shared: libudev.so.1 install-headers install-pkgconfig + mkdir -p ${DESTDIR}${LIBDIR} + cp -f libudev.so.1 ${DESTDIR}${LIBDIR}/libudev.so.1 + ln -fs libudev.so.1 ${DESTDIR}${LIBDIR}/libudev.so + +install-static: libudev.a install-headers install-pkgconfig + mkdir -p ${DESTDIR}${LIBDIR} + cp -f libudev.a ${DESTDIR}${LIBDIR}/libudev.a + +install: install-shared install-static + +uninstall: + rm -f ${DESTDIR}${LIBDIR}/libudev.a \ + ${DESTDIR}${LIBDIR}/libudev.so \ + ${DESTDIR}${LIBDIR}/libudev.so.1 \ + ${DESTDIR}${PKGCONFIGDIR}/libudev.pc \ + ${DESTDIR}${INCLUDEDIR}/libudev.h + +clean: + rm -f libudev.so.1 libudev.a libudev.pc ${OBJ} + +.PHONY: all clean install-headers install-pkgconfig install-shared \ + install-static install uninstall diff --git a/README.md b/README.md new file mode 100644 index 0000000..21f8f19 --- /dev/null +++ b/README.md @@ -0,0 +1,100 @@ +# libudev-zero + +Drop-in replacement for `libudev` intended to work with any device manager + +## Why? + +We all know that systemd is very hostile towards portability. udev inherited +the same problem by exposing a very badly designed library interface. This +dramatically reduces portability, user choice and basically creates vendor +lock-in because the library interface highly tied to the udev daemon. + +Another udev problem is the non-portable home-grown language called "udev +rules". The udev authors definitely don't know(or care) why it's better to +avoid reinventing the wheel. Strictly speaking, I think they did that on +purpose to overcomplicate udev as much as possible. Why? So that only the +authors(RedHat/IBM) can rule and dictate the future of udev. The recent eudev +death only proves that it's really hard to support such unmaintainable mess. + +The udev hwdb is yet another illustration of a systemd-like approach. What the +hell does "userspace /dev" have to do with parsing hardware database(pci.ids, +usb.ids) and setting/remapping buttons? udev smells like systemd by trying to +implement all possible functionality in the single daemon/code base. Programs +that follow the UNIX philosophy is much better suited for such purposes. + +## Pros/Cons + +Keep in mind that libudev-zero isn't ideal. Here are some pros/cons: + +### Pros + +* Very portable. Doesn't depend on GNU features. +* No lock-in. Any device manager can be used, even smdev and CONFIG_UEVENT_HELPER. +* Source code is much cleaner than udev because of less abstractions and clever code. + +### Cons + +* Udev rules must be converted to shell script in order to work with any device manager. +* Udev hwdb interface isn't implemented. pciutils and usbutils will not display any meaningful info. +* Many functions and interfaces still aren't implemented, which may lead to breakage in some programs. + +## What doesn't work + +* dosfstools - requires udev_enumerate_add_match_parent() +* PulseAudio - highly depends on udev internal properties. [workaround](https://gist.github.com/capezotte/03ee5548218e819b06459819bb120b4b#pulseaudio) +* udisks2 - highly depends on udev internal properties +* android-tools - requires udev rules for non-root usage +* NetworkManager - needs investigation +* libgudev - needs investigation +* PipeWire - depends on udev internal properties. [patch](https://github.com/illiliti/libudev-zero/issues/26#issuecomment-846858706) +* ldm - depends on udev internal properties +* lvm2 - uses deprecated `udev_queue` API +* cups - needs investigation +* ??? + +## Dependencies + +* C99 compiler (build time) +* POSIX make (build time) +* POSIX & XSI libc +* Linux >= 2.6.39 + +## Installation + +```sh +make +make PREFIX=/usr install +``` + +## Hotplugging + +Note that hotplugging support is fully optional. You can skip +this step if you don't have a need for the hotplugging capability. + +If you're using an mdev-like device manager, refer to [mdev.conf](contrib/mdev.conf) +for a config example. + +If you're using another device manager, you need to configure it to rebroadcast +kernel uevents. You can do this by either patching(see below) the device manager +or simply executing [helper.c](contrib/helper.c) for each uevent. + +If you're developing your own device manager, you need to rebroadcast kernel +uevents to the `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 to (but don't copy blindly) [helper.c](contrib/helper.c) +for an example of how it could be implemented in C. + +Don't hesitate to ask me about anything 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. + +## Future directions + +1. Write a better cross-platform(*nix, maybe macos and windows) device enumeration library. +2. Convince mainstream apps(libinput, wlroots, ...) to use a new library instead of libudev. +3. Declare libudev as obsolete library and archive this project. + +## Donate + +You can send a donation to `BTC: 1BwrcsgtWZeLVvNeEQSg4A28a3yrGN3FpK` + +Thank you very much! diff --git a/contrib/helper.c b/contrib/helper.c new file mode 100644 index 0000000..9ead4e9 --- /dev/null +++ b/contrib/helper.c @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2020-2021 illiliti + * SPDX-License-Identifier: ISC + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * + * Construct uevent message from environment and send it to 0x4 netlink group. + */ + +#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 buf[8192]; + size_t len; + int i, fd; + + iov.iov_base = buf; + iov.iov_len = 0; + + for (i = 0; environ[i]; i++) { + if (strncmp(environ[i], "PATH=", 5) == 0 || + strncmp(environ[i], "HOME=", 5) == 0) { + continue; + } + + 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 (sendmsg(fd, &hdr, 0) == -1) { + perror("sendmsg"); + close(fd); + return 1; + } + + close(fd); + return 0; +} diff --git a/contrib/mdev.conf b/contrib/mdev.conf new file mode 100644 index 0000000..f7c8c1f --- /dev/null +++ b/contrib/mdev.conf @@ -0,0 +1,16 @@ +# example rules for mdev.conf +# +# NOTE: +# since https://github.com/skarnet/mdevd/commit/b61ab0373a724d38e059655d561b5fb3dcb2aff6, +# mdevd can rebroadcast uevents natively which means that you don't need to add anything +# to /etc/mdev.conf. In order to enable this functionality, you need to add `-O 4` to +# mdevd arguments. +# +# NOTE: replace /path/to/helper with path to compiled binary of helper.c + +# handle all uevents(not recommended) +#-.* root:root 660 */path/to/helper + +# handle only drm and input uevents(recommended) +SUBSYSTEM=drm;.* root:video 660 */path/to/helper +SUBSYSTEM=input;.* root:input 660 */path/to/helper diff --git a/libudev.h b/libudev.h new file mode 120000 index 0000000..adc211b --- /dev/null +++ b/libudev.h @@ -0,0 +1 @@ +udev.h \ No newline at end of file diff --git a/libudev.pc.in b/libudev.pc.in new file mode 100644 index 0000000..7e29d8c --- /dev/null +++ b/libudev.pc.in @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: ISC +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libudev +Description: Daemonless replacement for libudev +Version: @VERSION@ +URL: https://github.com/illiliti/libudev-zero +Libs: -L${libdir} -ludev +Libs.private: -pthread +Cflags: -I${includedir} diff --git a/udev.c b/udev.c new file mode 100644 index 0000000..ab5d33a --- /dev/null +++ b/udev.c @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2020-2021 illiliti + * SPDX-License-Identifier: ISC + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include "udev.h" + +struct udev { + int refcount; +}; + +struct udev *udev_new(void) +{ + struct udev *udev; + + udev = calloc(1, sizeof(*udev)); + + if (!udev) { + return NULL; + } + + udev->refcount = 1; + return udev; +} + +struct udev *udev_ref(struct udev *udev) +{ + if (!udev) { + return NULL; + } + + udev->refcount++; + return udev; +} + +struct udev *udev_unref(struct udev *udev) +{ + if (!udev) { + return NULL; + } + + if (--udev->refcount > 0) { + return udev; + } + + free(udev); + return NULL; +} + +void udev_set_log_fn(struct udev *udev, void (*log_fn)(struct udev *udev, + int priority, const char *file, int line, const char *fn, + const char *format, va_list args)) +{ +} + +void udev_set_log_priority(struct udev *udev, int priority) +{ +} + +int udev_get_log_priority(struct udev *udev) +{ + return 0; +} + +/* XXX NOT IMPLEMENTED */ struct udev_hwdb *udev_hwdb_new(struct udev *udev) +{ + return NULL; +} + +/* XXX NOT IMPLEMENTED */ struct udev_hwdb *udev_hwdb_ref(struct udev_hwdb *hwdb) +{ + return NULL; +} + +/* XXX NOT IMPLEMENTED */ struct udev_hwdb *udev_hwdb_unref(struct udev_hwdb *hwdb) +{ + return NULL; +} + +/* XXX NOT IMPLEMENTED */ struct udev_list_entry *udev_hwdb_get_properties_list_entry(struct udev_hwdb *hwdb, const char *modalias, unsigned int flags) +{ + return NULL; +} diff --git a/udev.h b/udev.h new file mode 100644 index 0000000..057d4af --- /dev/null +++ b/udev.h @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2020-2021 illiliti + * SPDX-License-Identifier: ISC + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +// we must keep this include here to fix some legacy programs +// https://freenode.logbot.info/kisslinux/20200722#c4467755 +#include + +#ifndef _LIBUDEV_H_ +#define _LIBUDEV_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define udev_list_entry_foreach(list_entry, first_entry) \ + for (list_entry = first_entry; list_entry; list_entry = udev_list_entry_get_next(list_entry)) + +struct udev; +struct udev_hwdb; +struct udev_device; +struct udev_monitor; +struct udev_enumerate; +struct udev_list_entry; + +struct udev *udev_new(void); +struct udev *udev_ref(struct udev *udev); +struct udev *udev_unref(struct udev *udev); + +void udev_set_log_fn(struct udev *udev, void (*log_fn)(struct udev *udev, + int priority, const char *file, int line, const char *fn, + const char *format, va_list args)); +void udev_set_log_priority(struct udev *udev, int priority); +int udev_get_log_priority(struct udev *udev); + +const char *udev_device_get_syspath(struct udev_device *udev_device); +const char *udev_device_get_sysname(struct udev_device *udev_device); +const char *udev_device_get_sysnum(struct udev_device *udev_device); +const char *udev_device_get_devpath(struct udev_device *udev_device); +const char *udev_device_get_devnode(struct udev_device *udev_device); +dev_t udev_device_get_devnum(struct udev_device *udev_device); +unsigned long long udev_device_get_seqnum(struct udev_device *udev_device); +unsigned long long udev_device_get_usec_since_initialized(struct udev_device *udev_device); +const char *udev_device_get_devtype(struct udev_device *udev_device); +const char *udev_device_get_subsystem(struct udev_device *udev_device); +const char *udev_device_get_driver(struct udev_device *udev_device); +struct udev *udev_device_get_udev(struct udev_device *udev_device); +struct udev_device *udev_device_get_parent(struct udev_device *udev_device); +struct udev_device *udev_device_get_parent_with_subsystem_devtype(struct udev_device *udev_device, const char *subsystem, const char *devtype); +int udev_device_get_is_initialized(struct udev_device *udev_device); +const char *udev_device_get_action(struct udev_device *udev_device); + +int udev_device_has_tag(struct udev_device *udev_device, const char *tag); +struct udev_list_entry *udev_device_get_devlinks_list_entry(struct udev_device *udev_device); +struct udev_list_entry *udev_device_get_properties_list_entry(struct udev_device *udev_device); +struct udev_list_entry *udev_device_get_tags_list_entry(struct udev_device *udev_device); +struct udev_list_entry *udev_device_get_sysattr_list_entry(struct udev_device *udev_device); +const char *udev_device_get_property_value(struct udev_device *udev_device, const char *key); +const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const char *sysattr); +int udev_device_set_sysattr_value(struct udev_device *udev_device, const char *sysattr, const char *value); + +struct udev_device *udev_device_new_from_syspath(struct udev *udev, const char *syspath); +struct udev_device *udev_device_new_from_devnum(struct udev *udev, char type, dev_t devnum); +struct udev_device *udev_device_new_from_subsystem_sysname(struct udev *udev, const char *subsystem, const char *sysname); +struct udev_device *udev_device_new_from_device_id(struct udev *udev, const char *id); +struct udev_device *udev_device_new_from_environment(struct udev *udev); +struct udev_device *udev_device_ref(struct udev_device *udev_device); +struct udev_device *udev_device_unref(struct udev_device *udev_device); + +int udev_enumerate_add_match_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem); +int udev_enumerate_add_nomatch_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem); +int udev_enumerate_add_match_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value); +int udev_enumerate_add_nomatch_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value); +int udev_enumerate_add_match_property(struct udev_enumerate *udev_enumerate, const char *property, const char *value); +int udev_enumerate_add_match_sysname(struct udev_enumerate *udev_enumerate, const char *sysname); +int udev_enumerate_add_match_tag(struct udev_enumerate *udev_enumerate, const char *tag); +int udev_enumerate_add_match_parent(struct udev_enumerate *udev_enumerate, struct udev_device *parent); +int udev_enumerate_add_match_is_initialized(struct udev_enumerate *udev_enumerate); + +int udev_enumerate_scan_devices(struct udev_enumerate *udev_enumerate); +int udev_enumerate_scan_subsystems(struct udev_enumerate *udev_enumerate); +struct udev_list_entry *udev_enumerate_get_list_entry(struct udev_enumerate *udev_enumerate); +int udev_enumerate_add_syspath(struct udev_enumerate *udev_enumerate, const char *syspath); +struct udev *udev_enumerate_get_udev(struct udev_enumerate *udev_enumerate); + +struct udev_enumerate *udev_enumerate_new(struct udev *udev); +struct udev_enumerate *udev_enumerate_ref(struct udev_enumerate *udev_enumerate); +struct udev_enumerate *udev_enumerate_unref(struct udev_enumerate *udev_enumerate); + +struct udev_list_entry *udev_list_entry_get_next(struct udev_list_entry *list_entry); +struct udev_list_entry *udev_list_entry_get_by_name(struct udev_list_entry *list_entry, const char *name); +const char *udev_list_entry_get_name(struct udev_list_entry *list_entry); +const char *udev_list_entry_get_value(struct udev_list_entry *list_entry); + +struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monitor); +int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor); +int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int size); +int udev_monitor_get_fd(struct udev_monitor *udev_monitor); +struct udev *udev_monitor_get_udev(struct udev_monitor *udev_monitor); + +int udev_monitor_filter_update(struct udev_monitor *udev_monitor); +int udev_monitor_filter_remove(struct udev_monitor *udev_monitor); +int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_monitor, const char *subsystem, const char *devtype); +int udev_monitor_filter_add_match_tag(struct udev_monitor *udev_monitor, const char *tag); + +struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char *name); +struct udev_monitor *udev_monitor_ref(struct udev_monitor *udev_monitor); +struct udev_monitor *udev_monitor_unref(struct udev_monitor *udev_monitor); + +struct udev_hwdb *udev_hwdb_new(struct udev *udev); +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_uevent(struct udev *udev, char *buf, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/udev_device.c b/udev_device.c new file mode 100644 index 0000000..173cbe4 --- /dev/null +++ b/udev_device.c @@ -0,0 +1,752 @@ +/* + * Copyright (c) 2020-2021 illiliti + * SPDX-License-Identifier: ISC + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "udev.h" +#include "udev_list.h" + +#ifndef LINE_MAX +#define LINE_MAX 2048 +#endif + +#ifndef LONG_BIT +#define LONG_BIT (sizeof(unsigned long) * 8) +#endif + +// https://kernel.org/doc/html/latest/input/ff.html#querying-device-capabilities +// https://kernel.org/doc/html/latest/input/input-programming.html#bits-to-longs-bit-word-bit-mask +#define BIT_WORD(x) ((x) / LONG_BIT) +#define BIT_MASK(x) (1UL << ((x) % LONG_BIT)) +#define BITS_TO_LONGS(x) (((x) + LONG_BIT - 1) / LONG_BIT) + +#ifndef INPUT_PROP_POINTING_STICK +#define INPUT_PROP_POINTING_STICK 0x05 +#endif + +#ifndef INPUT_PROP_ACCELEROMETER +#define INPUT_PROP_ACCELEROMETER 0x06 +#endif + +#ifndef INPUT_PROP_CNT +#define INPUT_PROP_CNT 0x20 +#endif + +struct udev_device { + struct udev_list_entry properties; + struct udev_list_entry sysattrs; + struct udev_device *parent; + struct udev *udev; + int refcount; +}; + +const char *udev_device_get_syspath(struct udev_device *udev_device) +{ + return udev_device_get_property_value(udev_device, "SYSPATH"); +} + +const char *udev_device_get_sysname(struct udev_device *udev_device) +{ + return udev_device_get_property_value(udev_device, "SYSNAME"); +} + +const char *udev_device_get_sysnum(struct udev_device *udev_device) +{ + return udev_device_get_property_value(udev_device, "SYSNUM"); +} + +const char *udev_device_get_devpath(struct udev_device *udev_device) +{ + return udev_device_get_property_value(udev_device, "DEVPATH"); +} + +const char *udev_device_get_devnode(struct udev_device *udev_device) +{ + return udev_device_get_property_value(udev_device, "DEVNAME"); +} + +unsigned long long udev_device_get_seqnum(struct udev_device *udev_device) +{ + const char *seqnum; + + seqnum = udev_device_get_property_value(udev_device, "SEQNUM"); + + if (!seqnum) { + return 0; + } + + return strtoull(seqnum, NULL, 10); +} + +unsigned long long udev_device_get_usec_since_initialized(struct udev_device *udev_device) +{ + return 0; +} + +dev_t udev_device_get_devnum(struct udev_device *udev_device) +{ + const char *major, *minor; + + major = udev_device_get_property_value(udev_device, "MAJOR"); + minor = udev_device_get_property_value(udev_device, "MINOR"); + + if (!major || !minor) { + return makedev(0, 0); + } + + return makedev(atoi(major), atoi(minor)); +} + +const char *udev_device_get_devtype(struct udev_device *udev_device) +{ + return udev_device_get_property_value(udev_device, "DEVTYPE"); +} + +const char *udev_device_get_subsystem(struct udev_device *udev_device) +{ + return udev_device_get_property_value(udev_device, "SUBSYSTEM"); +} + +const char *udev_device_get_driver(struct udev_device *udev_device) +{ + return udev_device_get_property_value(udev_device, "DRIVER"); +} + +struct udev *udev_device_get_udev(struct udev_device *udev_device) +{ + return udev_device ? udev_device->udev : NULL; +} + +struct udev_device *udev_device_get_parent(struct udev_device *udev_device) +{ + char *pos, *path, *syspath; + + if (!udev_device) { + return NULL; + } + + if (udev_device->parent) { + return udev_device->parent; + } + + syspath = strdup(udev_device_get_syspath(udev_device)); + + if (!syspath) { + return NULL; + } + + path = syspath + 5; + + do { + if ((pos = strrchr(path, '/'))) { + *pos = '\0'; + } + else { + break; + } + + udev_device->parent = udev_device_new_from_syspath(udev_device->udev, syspath); + } + while (!udev_device->parent); + + free(syspath); + return udev_device->parent; +} + +struct udev_device *udev_device_get_parent_with_subsystem_devtype(struct udev_device *udev_device, const char *subsystem, const char *devtype) +{ + const char *parent_subsystem, *parent_devtype; + struct udev_device *parent; + + if (!udev_device || !subsystem) { + return NULL; + } + + for (parent = udev_device_get_parent(udev_device); parent; parent = udev_device_get_parent(parent)) { + parent_subsystem = udev_device_get_subsystem(parent); + parent_devtype = udev_device_get_devtype(parent); + + if (!parent_subsystem || strcmp(parent_subsystem, subsystem) != 0) { + continue; + } + + if (!devtype || (parent_devtype && strcmp(parent_devtype, devtype) == 0)) { + return parent; + } + } + + return NULL; +} + +/* XXX NOT IMPLEMENTED */ int udev_device_get_is_initialized(struct udev_device *udev_device) +{ + return 1; +} + +const char *udev_device_get_action(struct udev_device *udev_device) +{ + return udev_device_get_property_value(udev_device, "ACTION"); +} + +/* XXX NOT IMPLEMENTED */ int udev_device_has_tag(struct udev_device *udev_device, const char *tag) +{ + return 0; +} + +/* XXX NOT IMPLEMENTED */ struct udev_list_entry *udev_device_get_devlinks_list_entry(struct udev_device *udev_device) +{ + return NULL; +} + +struct udev_list_entry *udev_device_get_properties_list_entry(struct udev_device *udev_device) +{ + return udev_device ? udev_list_entry_get_next(&udev_device->properties) : NULL; +} + +/* XXX NOT IMPLEMENTED */ struct udev_list_entry *udev_device_get_tags_list_entry(struct udev_device *udev_device) +{ + return NULL; +} + +struct udev_list_entry *udev_device_get_sysattr_list_entry(struct udev_device *udev_device) +{ + return udev_device ? udev_list_entry_get_next(&udev_device->sysattrs) : NULL; +} + +const char *udev_device_get_property_value(struct udev_device *udev_device, const char *key) +{ + if (!udev_device || !key) { + return NULL; + } + + return udev_list_entry_get_value(udev_list_entry_get_by_name(&udev_device->properties, key)); +} + +const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const char *sysattr) +{ + struct udev_list_entry *list_entry; + char data[4096], path[PATH_MAX]; + struct stat st; + size_t len; + FILE *file; + + if (!udev_device || !sysattr) { + return NULL; + } + + list_entry = udev_list_entry_get_by_name(&udev_device->sysattrs, sysattr); + + if (list_entry) { + return udev_list_entry_get_value(list_entry); + } + + snprintf(path, sizeof(path), "%s/%s", udev_device_get_syspath(udev_device), sysattr); + + if (lstat(path, &st) != 0 || !S_ISREG(st.st_mode)) { + return NULL; + } + + file = fopen(path, "r"); + + if (!file) { + return NULL; + } + + // TODO dynamic allocation of data + len = fread(data, 1, sizeof(data) - 1, file); + + if (len != sizeof(data) - 1 && ferror(file)) { + fclose(file); + return NULL; + } + + fclose(file); + data[len] = '\0'; + + while (len-- > 0 && data[len] == '\n') { + data[len] = '\0'; + } + + list_entry = udev_list_entry_add(&udev_device->sysattrs, sysattr, data, 0); + return udev_list_entry_get_value(list_entry); +} + +int udev_device_set_sysattr_value(struct udev_device *udev_device, const char *sysattr, const char *value) +{ + char path[PATH_MAX]; + struct stat st; + size_t len; + FILE *file; + + if (!udev_device || !sysattr || !value) { + return -1; + } + + snprintf(path, sizeof(path), "%s/%s", udev_device_get_syspath(udev_device), sysattr); + + if (lstat(path, &st) != 0 || !S_ISREG(st.st_mode)) { + return -1; + } + + file = fopen(path, "w"); + + if (!file) { + return -1; + } + + len = strlen(value); + + if (fwrite(value, 1, len, file) != len) { + fclose(file); + return -1; + } + + fclose(file); + udev_list_entry_add(&udev_device->sysattrs, sysattr, value, 1); + return 0; +} + +static char *read_symlink(const char *syspath, const char *name) +{ + char link[PATH_MAX], path[PATH_MAX]; + ssize_t len; + + snprintf(path, sizeof(path), "%s/%s", syspath, name); + len = readlink(path, link, sizeof(link)); + + if (len == -1) { + return NULL; + } + + link[len] = '\0'; + return strdup(strrchr(link, '/') + 1); +} + +static void set_properties_from_uevent(struct udev_device *udev_device) +{ + char line[LINE_MAX], path[PATH_MAX], devnode[PATH_MAX]; + FILE *file; + char *pos; + + snprintf(path, sizeof(path), "%s/uevent", udev_device_get_syspath(udev_device)); + file = fopen(path, "r"); + + if (!file) { + return; + } + + while (fgets(line, sizeof(line), file)) { + line[strlen(line) - 1] = '\0'; + + if (strncmp(line, "DEVNAME=", 8) == 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, 1); + } + } + + fclose(file); +} + +static void make_bit(unsigned long *arr, int cnt, const char *str) +{ + size_t len; + int i; + + if (!str) { + return; + } + + for (i = 0, len = strlen(str); len > 0; len--) { + if (str[len] == ' ') { + arr[i++] = strtoul(str + len + 1, NULL, 16); + + if (i == cnt) { + return; + } + } + } + + arr[i] = strtoul(str, NULL, 16); +} + +static int test_bit(unsigned long *arr, unsigned long bit) +{ + return !!(arr[BIT_WORD(bit)] & BIT_MASK(bit)); +} + +static void set_properties_from_evdev(struct udev_device *udev_device) +{ + // https://kernel.org/doc/html/latest/driver-api/input.html#c.input_dev + unsigned long prop_bits[BITS_TO_LONGS(INPUT_PROP_CNT)] = {0}; + unsigned long abs_bits[BITS_TO_LONGS(ABS_CNT)] = {0}; + unsigned long rel_bits[BITS_TO_LONGS(REL_CNT)] = {0}; + unsigned long key_bits[BITS_TO_LONGS(KEY_CNT)] = {0}; + unsigned long ev_bits[BITS_TO_LONGS(EV_CNT)] = {0}; + struct udev_device *parent; + const char *subsystem; + unsigned long bit; + + subsystem = udev_device_get_subsystem(udev_device); + + if (!subsystem || strcmp(subsystem, "input") != 0) { + return; + } + + parent = udev_device; + + while (1) { + if (!parent) { + return; + } + + if (udev_device_get_property_value(parent, "EV")) { + break; + } + + parent = udev_device_get_parent_with_subsystem_devtype(parent, "input", NULL); + } + + make_bit(ev_bits, sizeof(ev_bits) / sizeof(ev_bits[0]), udev_device_get_property_value(parent, "EV")); + make_bit(abs_bits, sizeof(abs_bits) / sizeof(abs_bits[0]), udev_device_get_property_value(parent, "ABS")); + make_bit(rel_bits, sizeof(rel_bits) / sizeof(rel_bits[0]), udev_device_get_property_value(parent, "REL")); + make_bit(key_bits, sizeof(key_bits) / sizeof(key_bits[0]), udev_device_get_property_value(parent, "KEY")); + make_bit(prop_bits, sizeof(prop_bits) / sizeof(prop_bits[0]), udev_device_get_property_value(parent, "PROP")); + + udev_list_entry_add(&udev_device->properties, "ID_INPUT", "1", 0); + + if (test_bit(prop_bits, INPUT_PROP_POINTING_STICK)) { + udev_list_entry_add(&udev_device->properties, "ID_INPUT_POINTINGSTICK", "1", 0); + } + + if (test_bit(prop_bits, INPUT_PROP_ACCELEROMETER)) { + udev_list_entry_add(&udev_device->properties, "ID_INPUT_ACCELEROMETER", "1", 0); + } + + if (test_bit(ev_bits, EV_SW)) { + udev_list_entry_add(&udev_device->properties, "ID_INPUT_SWITCH", "1", 0); + } + + if (test_bit(ev_bits, EV_REL)) { + if (test_bit(rel_bits, REL_Y) && test_bit(rel_bits, REL_X) && + test_bit(key_bits, BTN_MOUSE)) { + udev_list_entry_add(&udev_device->properties, "ID_INPUT_MOUSE", "1", 0); + } + } + else if (test_bit(ev_bits, EV_ABS)) { + if (test_bit(key_bits, BTN_SELECT) || test_bit(key_bits, BTN_TR) || + test_bit(key_bits, BTN_START) || test_bit(key_bits, BTN_TL)) { + if (test_bit(key_bits, BTN_TOUCH)) { + udev_list_entry_add(&udev_device->properties, "ID_INPUT_TOUCHSCREEN", "1", 0); + } + else { + udev_list_entry_add(&udev_device->properties, "ID_INPUT_JOYSTICK", "1", 0); + } + } + else if (test_bit(abs_bits, ABS_Y) && test_bit(abs_bits, ABS_X)) { + if (test_bit(abs_bits, ABS_Z) && !test_bit(ev_bits, EV_KEY)) { + udev_list_entry_add(&udev_device->properties, "ID_INPUT_ACCELEROMETER", "1", 0); + } + else if (test_bit(key_bits, BTN_STYLUS) || test_bit(key_bits, BTN_TOOL_PEN)) { + udev_list_entry_add(&udev_device->properties, "ID_INPUT_TABLET", "1", 0); + } + else if (test_bit(key_bits, BTN_TOUCH)) { + if (test_bit(key_bits, BTN_TOOL_FINGER)) { + udev_list_entry_add(&udev_device->properties, "ID_INPUT_TOUCHPAD", "1", 0); + } + else { + udev_list_entry_add(&udev_device->properties, "ID_INPUT_TOUCHSCREEN", "1", 0); + } + } + else if (test_bit(key_bits, BTN_MOUSE)) { + udev_list_entry_add(&udev_device->properties, "ID_INPUT_MOUSE", "1", 0); + } + } + } + + if (!test_bit(ev_bits, EV_KEY)) { + return; + } + + // iterate over key bits until BTN_* block is hit and test if bitmask has KEY_*. + // this way, we can check if device has keys or buttons. + // https://github.com/torvalds/linux/blob/f5b6eb1e018203913dfefcf6fa988649ad11ad6e/include/uapi/linux/input-event-codes.h#L76-L338 + for (bit = KEY_ESC; bit < BTN_MISC; bit++) { + if (!test_bit(key_bits, bit)) { + continue; + } + + udev_list_entry_add(&udev_device->properties, "ID_INPUT_KEY", "1", 0); + + if (test_bit(key_bits, KEY_ENTER)) { + udev_list_entry_add(&udev_device->properties, "ID_INPUT_KEYBOARD", "1", 0); + } + + return; + } +} + +static void set_properties_from_props(struct udev_device *udev_device) +{ + const char *sysname, *subsystem; + struct udev_device *parent; + char id[256]; + + subsystem = udev_device_get_subsystem(udev_device); + + if (!subsystem || strcmp(subsystem, "drm") != 0) { + return; + } + + parent = udev_device_get_parent_with_subsystem_devtype(udev_device, "pci", NULL); + sysname = udev_device_get_sysname(parent); + + if (!sysname) { + return; + } + + snprintf(id, sizeof(id), "pci-%s", sysname); + udev_list_entry_add(&udev_device->properties, "ID_PATH", id, 0); +} + +struct udev_device *udev_device_new_from_syspath(struct udev *udev, const char *syspath) +{ + char *subsystem, *driver, *sysname; + struct udev_device *udev_device; + char path[PATH_MAX]; + int i; + + if (!udev || !syspath) { + return NULL; + } + + subsystem = read_symlink(syspath, "subsystem"); + + if (!subsystem) { + return NULL; + } + + if (!realpath(syspath, path)) { + free(subsystem); + return NULL; + } + + udev_device = calloc(1, sizeof(*udev_device)); + + if (!udev_device) { + free(subsystem); + 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); + + udev_list_entry_add(&udev_device->properties, "SYSPATH", path, 0); + udev_list_entry_add(&udev_device->properties, "DEVPATH", path + 4, 0); + + sysname = strrchr(path, '/') + 1; + driver = read_symlink(path, "driver"); + + udev_list_entry_add(&udev_device->properties, "SUBSYSTEM", subsystem, 0); + udev_list_entry_add(&udev_device->properties, "SYSNAME", sysname, 0); + udev_list_entry_add(&udev_device->properties, "DRIVER", driver, 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; + } + } + + set_properties_from_uevent(udev_device); + set_properties_from_evdev(udev_device); + set_properties_from_props(udev_device); + + free(driver); + free(subsystem); + return udev_device; +} + +struct udev_device *udev_device_new_from_devnum(struct udev *udev, char type, dev_t devnum) +{ + char path[PATH_MAX]; + + if (!udev) { + return NULL; + } + + switch (type) { + case 'c': + snprintf(path, sizeof(path), "/sys/dev/char/%u:%u", major(devnum), minor(devnum)); + break; + case 'b': + snprintf(path, sizeof(path), "/sys/dev/block/%u:%u", major(devnum), minor(devnum)); + break; + default: + return NULL; + } + + return udev_device_new_from_syspath(udev, path); +} + +struct udev_device *udev_device_new_from_subsystem_sysname(struct udev *udev, const char *subsystem, const char *sysname) +{ + const char *fmt[] = { "/sys/bus/%s/devices/%s", "/sys/class/%s/%s", NULL }; + char path[PATH_MAX]; + int i; + + if (!udev || !subsystem || !sysname) { + return NULL; + } + + for (i = 0; fmt[i]; i++) { + snprintf(path, sizeof(path), fmt[i], subsystem, sysname); + + if (access(path, R_OK) == 0) { + return udev_device_new_from_syspath(udev, path); + } + } + + return NULL; +} + +struct udev_device *udev_device_new_from_uevent(struct udev *udev, char *buf, size_t len) +{ + char syspath[PATH_MAX], devnode[PATH_MAX]; + struct udev_device *udev_device; + const char *sysname; + char *end, *pos; + int i, cnt = 0; + + udev_device = calloc(1, sizeof(*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); + + 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", buf + 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; + } + } + + cnt++; + } + 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(buf, '='); + + if (!pos) { + continue; + } + + *pos = '\0'; + + if (strcmp(buf, "SUBSYSTEM") == 0 || + strcmp(buf, "ACTION") == 0 || + strcmp(buf, "SEQNUM") == 0) { + cnt++; + } + + udev_list_entry_add(&udev_device->properties, buf, pos + 1, 0); + *pos = '='; + } + } + + if (cnt != 4) { + udev_device_unref(udev_device); + return NULL; + } + + set_properties_from_props(udev_device); + set_properties_from_evdev(udev_device); + return udev_device; +} + +/* XXX NOT IMPLEMENTED */ struct udev_device *udev_device_new_from_device_id(struct udev *udev, const char *id) +{ + return NULL; +} + +/* XXX NOT IMPLEMENTED */ struct udev_device *udev_device_new_from_environment(struct udev *udev) +{ + return NULL; +} + +struct udev_device *udev_device_ref(struct udev_device *udev_device) +{ + if (!udev_device) { + return NULL; + } + + udev_device->refcount++; + return udev_device; +} + +struct udev_device *udev_device_unref(struct udev_device *udev_device) +{ + if (!udev_device) { + return NULL; + } + + if (--udev_device->refcount > 0) { + return NULL; + } + + if (udev_device->parent) { + udev_device_unref(udev_device->parent); + } + + udev_list_entry_free_all(&udev_device->properties); + udev_list_entry_free_all(&udev_device->sysattrs); + + free(udev_device); + return NULL; +} diff --git a/udev_enumerate.c b/udev_enumerate.c new file mode 100644 index 0000000..c553269 --- /dev/null +++ b/udev_enumerate.c @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2020-2021 illiliti + * SPDX-License-Identifier: ISC + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "udev.h" +#include "udev_list.h" + +struct udev_enumerate { + struct udev_list_entry subsystem_nomatch; + struct udev_list_entry subsystem_match; + struct udev_list_entry sysattr_nomatch; + struct udev_list_entry property_match; + struct udev_list_entry sysattr_match; + struct udev_list_entry sysname_match; + struct udev_list_entry devices; + struct udev *udev; + int refcount; +}; + +struct udev_enumerate_thread { + struct udev_enumerate *udev_enumerate; + pthread_mutex_t *mutex; + char path[PATH_MAX]; + pthread_t thread; +}; + +int udev_enumerate_add_match_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem) +{ + return udev_enumerate ? !!udev_list_entry_add(&udev_enumerate->subsystem_match, subsystem, NULL, 0) - 1 : -1; +} + +int udev_enumerate_add_nomatch_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem) +{ + return udev_enumerate ? !!udev_list_entry_add(&udev_enumerate->subsystem_nomatch, subsystem, NULL, 0) - 1 : -1; +} + +int udev_enumerate_add_match_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value) +{ + return udev_enumerate ? !!udev_list_entry_add(&udev_enumerate->sysattr_match, sysattr, value, 0) - 1 : -1; +} + +int udev_enumerate_add_nomatch_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value) +{ + return udev_enumerate ? !!udev_list_entry_add(&udev_enumerate->sysattr_nomatch, sysattr, value, 0) - 1 : -1; +} + +int udev_enumerate_add_match_property(struct udev_enumerate *udev_enumerate, const char *property, const char *value) +{ + return udev_enumerate ? !!udev_list_entry_add(&udev_enumerate->property_match, property, value, 0) - 1 : -1; +} + +int udev_enumerate_add_match_sysname(struct udev_enumerate *udev_enumerate, const char *sysname) +{ + return udev_enumerate ? !!udev_list_entry_add(&udev_enumerate->sysname_match, sysname, NULL, 0) - 1 : -1; +} + +/* XXX NOT IMPLEMENTED */ int udev_enumerate_add_match_tag(struct udev_enumerate *udev_enumerate, const char *tag) +{ + return 0; +} + +/* XXX NOT IMPLEMENTED */ int udev_enumerate_add_match_parent(struct udev_enumerate *udev_enumerate, struct udev_device *parent) +{ + return 0; +} + +/* XXX NOT IMPLEMENTED */ int udev_enumerate_add_match_is_initialized(struct udev_enumerate *udev_enumerate) +{ + return 0; +} + +static int filter_subsystem(struct udev_enumerate *udev_enumerate, 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_enumerate->subsystem_nomatch); + + if (!subsystem) { + return 0; + } + + while (list_entry) { + if (fnmatch(udev_list_entry_get_name(list_entry), subsystem, 0) == 0) { + return 0; + } + + list_entry = udev_list_entry_get_next(list_entry); + } + + list_entry = udev_list_entry_get_next(&udev_enumerate->subsystem_match); + + if (list_entry) { + while (list_entry) { + if (fnmatch(udev_list_entry_get_name(list_entry), subsystem, 0) == 0) { + return 1; + } + + list_entry = udev_list_entry_get_next(list_entry); + } + + return 0; + } + + return 1; +} + +static int filter_sysname(struct udev_enumerate *udev_enumerate, struct udev_device *udev_device) +{ + struct udev_list_entry *list_entry; + const char *sysname; + + sysname = udev_device_get_sysname(udev_device); + list_entry = udev_list_entry_get_next(&udev_enumerate->sysname_match); + + if (!list_entry) { + return 1; + } + + while (list_entry) { + if (fnmatch(udev_list_entry_get_name(list_entry), sysname, 0) == 0) { + return 1; + } + + list_entry = udev_list_entry_get_next(list_entry); + } + + return 0; +} + +static int filter_property(struct udev_enumerate *udev_enumerate, struct udev_device *udev_device) +{ + const char *property, *property2, *value, *value2; + struct udev_list_entry *list_entry, *list_entry2; + + list_entry = udev_list_entry_get_next(&udev_enumerate->property_match); + + if (!list_entry) { + return 1; + } + + while (list_entry) { + property = udev_list_entry_get_name(list_entry); + value = udev_list_entry_get_value(list_entry); + + list_entry2 = udev_device_get_properties_list_entry(udev_device); + + if (!list_entry2) { + return 0; + } + + while (list_entry2) { + property2 = udev_list_entry_get_name(list_entry2); + value2 = udev_list_entry_get_value(list_entry2); + + if (value && value2) { + if (fnmatch(property, property2, 0) == 0 && + fnmatch(value, value2, 0) == 0) { + return 1; + } + } + + list_entry2 = udev_list_entry_get_next(list_entry2); + } + + list_entry = udev_list_entry_get_next(list_entry); + } + + return 0; +} + +static int filter_sysattr(struct udev_enumerate *udev_enumerate, struct udev_device *udev_device) +{ + const char *sysattr, *value, *value2; + struct udev_list_entry *list_entry; + + list_entry = udev_list_entry_get_next(&udev_enumerate->sysattr_nomatch); + + while (list_entry) { + sysattr = udev_list_entry_get_name(list_entry); + value2 = udev_list_entry_get_value(list_entry); + value = udev_device_get_sysattr_value(udev_device, sysattr); + + if (value && value2 && fnmatch(value2, value, 0) == 0) { + return 0; + } + + list_entry = udev_list_entry_get_next(list_entry); + } + + list_entry = udev_list_entry_get_next(&udev_enumerate->sysattr_match); + + if (list_entry) { + while (list_entry) { + sysattr = udev_list_entry_get_name(list_entry); + value2 = udev_list_entry_get_value(list_entry); + value = udev_device_get_sysattr_value(udev_device, sysattr); + + if (value && value2 && fnmatch(value2, value, 0) == 0) { + return 1; + } + + list_entry = udev_list_entry_get_next(list_entry); + } + + return 0; + } + + return 1; +} + +static void *add_device(void *ptr) +{ + struct udev_enumerate_thread *thread = ptr; + struct udev_device *udev_device; + + udev_device = udev_device_new_from_syspath(thread->udev_enumerate->udev, thread->path); + + if (!udev_device) { + return NULL; + } + + if (!filter_subsystem(thread->udev_enumerate, udev_device) || + !filter_sysname(thread->udev_enumerate, udev_device) || + !filter_property(thread->udev_enumerate, udev_device) || + !filter_sysattr(thread->udev_enumerate, udev_device)) { + udev_device_unref(udev_device); + return NULL; + } + + pthread_mutex_lock(thread->mutex); + udev_list_entry_add(&thread->udev_enumerate->devices, udev_device_get_syspath(udev_device), NULL, 0); + pthread_mutex_unlock(thread->mutex); + + udev_device_unref(udev_device); + return NULL; +} + +static int filter_dot(const struct dirent *de) +{ + return de->d_name[0] != '.'; +} + +static int scan_devices(struct udev_enumerate *udev_enumerate, const char *path) +{ + struct udev_enumerate_thread *thread; + pthread_mutex_t mutex; + int i, cnt, ret = 1; + struct dirent **de; + + cnt = scandir(path, &de, filter_dot, NULL); + + if (cnt == -1) { + return 0; + } + + thread = calloc(cnt, sizeof(*thread)); + + if (!thread) { + ret = 0; + goto free_de; + } + + pthread_mutex_init(&mutex, NULL); + + for (i = 0; i < cnt; i++) { + thread[i].mutex = &mutex; + thread[i].udev_enumerate = udev_enumerate; + + snprintf(thread[i].path, sizeof(thread[i].path), "%s/%s", path, de[i]->d_name); + + if (pthread_create(&thread[i].thread, NULL, add_device, &thread[i]) != 0) { + ret = 0; + break; + } + } + + for (i = 0; i < cnt; i++) { + pthread_join(thread[i].thread, NULL); + } + + free(thread); + pthread_mutex_destroy(&mutex); + +free_de: + for (i = 0; i < cnt; i++) { + free(de[i]); + } + + free(de); + return ret; +} + +int udev_enumerate_scan_devices(struct udev_enumerate *udev_enumerate) +{ + const char *path[] = { "/sys/dev/block", "/sys/dev/char", NULL }; + int i; + + if (!udev_enumerate) { + return -1; + } + + for (i = 0; path[i]; i++) { + if (!scan_devices(udev_enumerate, path[i])) { + return -1; + } + } + + return 0; +} + +/* XXX NOT IMPLEMENTED */ int udev_enumerate_scan_subsystems(struct udev_enumerate *udev_enumerate) +{ + return 0; +} + +struct udev_list_entry *udev_enumerate_get_list_entry(struct udev_enumerate *udev_enumerate) +{ + return udev_enumerate ? udev_list_entry_get_next(&udev_enumerate->devices) : NULL; +} + +/* XXX NOT IMPLEMENTED */ int udev_enumerate_add_syspath(struct udev_enumerate *udev_enumerate, const char *syspath) +{ + return 0; +} + +struct udev *udev_enumerate_get_udev(struct udev_enumerate *udev_enumerate) +{ + return udev_enumerate ? udev_enumerate->udev : NULL; +} + +struct udev_enumerate *udev_enumerate_new(struct udev *udev) +{ + struct udev_enumerate *udev_enumerate; + + if (!udev) { + return NULL; + } + + udev_enumerate = calloc(1, sizeof(*udev_enumerate)); + + if (!udev_enumerate) { + return NULL; + } + + udev_enumerate->refcount = 1; + udev_enumerate->udev = udev; + + udev_list_entry_init(&udev_enumerate->subsystem_nomatch); + udev_list_entry_init(&udev_enumerate->subsystem_match); + udev_list_entry_init(&udev_enumerate->sysattr_nomatch); + udev_list_entry_init(&udev_enumerate->property_match); + udev_list_entry_init(&udev_enumerate->sysattr_match); + udev_list_entry_init(&udev_enumerate->sysname_match); + udev_list_entry_init(&udev_enumerate->devices); + + return udev_enumerate; +} + +struct udev_enumerate *udev_enumerate_ref(struct udev_enumerate *udev_enumerate) +{ + if (!udev_enumerate) { + return NULL; + } + + udev_enumerate->refcount++; + return udev_enumerate; +} + +struct udev_enumerate *udev_enumerate_unref(struct udev_enumerate *udev_enumerate) +{ + if (!udev_enumerate) { + return NULL; + } + + if (--udev_enumerate->refcount > 0) { + return NULL; + } + + udev_list_entry_free_all(&udev_enumerate->subsystem_nomatch); + udev_list_entry_free_all(&udev_enumerate->subsystem_match); + udev_list_entry_free_all(&udev_enumerate->sysattr_nomatch); + udev_list_entry_free_all(&udev_enumerate->property_match); + udev_list_entry_free_all(&udev_enumerate->sysattr_match); + udev_list_entry_free_all(&udev_enumerate->sysname_match); + udev_list_entry_free_all(&udev_enumerate->devices); + + free(udev_enumerate); + return NULL; +} diff --git a/udev_list.c b/udev_list.c new file mode 100644 index 0000000..b7fabca --- /dev/null +++ b/udev_list.c @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2020-2021 illiliti + * SPDX-License-Identifier: ISC + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include "udev.h" +#include "udev_list.h" + +void udev_list_entry_init(struct udev_list_entry *list_entry) +{ + list_entry->value = NULL; + list_entry->name = NULL; + list_entry->next = NULL; +} + +void udev_list_entry_free(struct udev_list_entry *list_entry) +{ + free(list_entry->value); + free(list_entry->name); + free(list_entry); +} + +void udev_list_entry_free_all(struct udev_list_entry *list_entry) +{ + struct udev_list_entry *list_entry2 = list_entry->next; + + while (list_entry2) { + list_entry = list_entry2; + list_entry2 = list_entry->next; + udev_list_entry_free(list_entry); + } +} + +struct udev_list_entry *udev_list_entry_add(struct udev_list_entry *list_entry, const char *name, const char *value, int uniq) +{ + struct udev_list_entry *list_entry2; + + if (uniq) { + list_entry2 = udev_list_entry_get_by_name(list_entry, name); + + if (list_entry2 && value) { + if (list_entry2->value && strcmp(list_entry2->value, value) == 0) { + return list_entry2; + } + + free(list_entry2->value); + list_entry2->value = strdup(value); + + if (!list_entry2->value) { + return NULL; + } + + return list_entry2; + } + } + + list_entry2 = calloc(1, sizeof(*list_entry2)); + + if (!list_entry2) { + return NULL; + } + + list_entry2->name = strdup(name); + + if (!list_entry2->name) { + free(list_entry2); + return NULL; + } + + if (value) { + list_entry2->value = strdup(value); + + if (!list_entry2->value) { + free(list_entry2->name); + free(list_entry2); + return NULL; + } + } + + list_entry2->next = list_entry->next; + list_entry->next = list_entry2; + return list_entry2; +} + +struct udev_list_entry *udev_list_entry_get_next(struct udev_list_entry *list_entry) +{ + return list_entry ? list_entry->next : NULL; +} + +struct udev_list_entry *udev_list_entry_get_by_name(struct udev_list_entry *list_entry, const char *name) +{ + if (!list_entry || !name) { + return NULL; + } + + do { + if (list_entry->name && strcmp(list_entry->name, name) == 0) { + return list_entry; + } + } + while ((list_entry = list_entry->next)); + + return NULL; +} + +const char *udev_list_entry_get_name(struct udev_list_entry *list_entry) +{ + return list_entry ? list_entry->name : NULL; +} + +const char *udev_list_entry_get_value(struct udev_list_entry *list_entry) +{ + return list_entry ? list_entry->value : NULL; +} diff --git a/udev_list.h b/udev_list.h new file mode 100644 index 0000000..9952825 --- /dev/null +++ b/udev_list.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2020-2021 illiliti + * SPDX-License-Identifier: ISC + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +struct udev_list_entry { + struct udev_list_entry *next; + char *value; + char *name; +}; + +void udev_list_entry_init(struct udev_list_entry *list_entry); +void udev_list_entry_free(struct udev_list_entry *list_entry); +void udev_list_entry_free_all(struct udev_list_entry *list_entry); +struct udev_list_entry *udev_list_entry_add(struct udev_list_entry *list_entry, const char *name, const char *value, int uniq); diff --git a/udev_monitor.c b/udev_monitor.c new file mode 100644 index 0000000..d533a48 --- /dev/null +++ b/udev_monitor.c @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2020-2021 illiliti + * SPDX-License-Identifier: ISC + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include "udev.h" +#include "udev_list.h" + +#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; + unsigned nlgrp; + int refcount; + int fd; +}; + +static int 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 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) +{ + struct udev_device *udev_device; + struct sockaddr_nl sa = {0}; + struct msghdr hdr = {0}; + struct iovec iov = {0}; + char buf[8192]; + ssize_t len; + + iov.iov_base = buf; + iov.iov_len = sizeof(buf); + + hdr.msg_name = &sa; + hdr.msg_namelen = sizeof(sa); + hdr.msg_iov = &iov; + hdr.msg_iovlen = 1; + + while (1) { + len = recvmsg(udev_monitor->fd, &hdr, 0); + + if (len <= 0) { + break; + } + + if (hdr.msg_flags & MSG_TRUNC) { + continue; + } + + if (sa.nl_groups == 0x0 || (sa.nl_groups == 0x1 && sa.nl_pid)) { + continue; + } + + udev_device = udev_device_new_from_uevent(udev_monitor->udev, buf, len); + + if (!udev_device) { + continue; + } + + if (!filter_subsystem(udev_monitor, udev_device) || + !filter_devtype(udev_monitor, udev_device)) { + udev_device_unref(udev_device); + continue; + } + + return udev_device; + } + + return NULL; +} + +int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor) +{ + 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)); +} + +int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int size) +{ + 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->fd : -1; +} + +struct udev *udev_monitor_get_udev(struct udev_monitor *udev_monitor) +{ + return udev_monitor ? udev_monitor->udev : NULL; +} + +/* XXX NOT IMPLEMENTED */ int udev_monitor_filter_update(struct udev_monitor *udev_monitor) +{ + return 0; +} + +/* XXX NOT IMPLEMENTED */ int udev_monitor_filter_remove(struct udev_monitor *udev_monitor) +{ + 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; +} + +/* XXX NOT IMPLEMENTED */ int udev_monitor_filter_add_match_tag(struct udev_monitor *udev_monitor, const char *tag) +{ + return 0; +} + +struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char *name) +{ + struct udev_monitor *udev_monitor; + + if (!udev || !name) { + return NULL; + } + + udev_monitor = calloc(1, sizeof(*udev_monitor)); + + if (!udev_monitor) { + return NULL; + } + + 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->fd = socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, NETLINK_KOBJECT_UEVENT); + + if (udev_monitor->fd == -1) { + free(udev_monitor); + return NULL; + } + + udev_monitor->refcount = 1; + udev_monitor->udev = udev; + return udev_monitor; +} + +struct udev_monitor *udev_monitor_ref(struct udev_monitor *udev_monitor) +{ + if (!udev_monitor) { + return NULL; + } + + udev_monitor->refcount++; + return udev_monitor; +} + +struct udev_monitor *udev_monitor_unref(struct udev_monitor *udev_monitor) +{ + if (!udev_monitor) { + return NULL; + } + + if (--udev_monitor->refcount > 0) { + return NULL; + } + + udev_list_entry_free_all(&udev_monitor->devtype_match); + udev_list_entry_free_all(&udev_monitor->subsystem_match); + + close(udev_monitor->fd); + free(udev_monitor); + return NULL; +}