diff --git a/.github/workflows/codeql-buildscript.sh b/.github/workflows/codeql-buildscript.sh new file mode 100644 index 0000000..d9eae58 --- /dev/null +++ b/.github/workflows/codeql-buildscript.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +make diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..8164922 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,126 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + # push: + # branches: [ "main", "master" ] + schedule: + - cron: '0 0 * * *' + pull_request: + branches: '*' + +jobs: + analyze: + name: Analyze + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners + # Consider using larger runners for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-20.04' }} + timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'cpp' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby', 'swift' ] + # Use only 'java' to analyze code written in Java, Kotlin or both + # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + submodules: recursive + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + queries: security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). + # If this step fails, then you should remove it and run the build manually (see below) + #- name: Autobuild + # uses: github/codeql-action/autobuild@v2 + + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + - run: | + ./.github/workflows/codeql-buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" + upload: false + id: step1 + + # Filter out rules with low severity or high false positve rate + # Also filter out warnings in third-party code + - name: Filter out unwanted errors and warnings + uses: advanced-security/filter-sarif@v1 + with: + patterns: | + -**:cpp/path-injection + -**:cpp/world-writable-file-creation + -**:cpp/poorly-documented-function + -**:cpp/potentially-dangerous-function + -**:cpp/use-of-goto + -**:cpp/integer-multiplication-cast-to-long + -**:cpp/comparison-with-wider-type + -**:cpp/leap-year/* + -**:cpp/ambiguously-signed-bit-field + -**:cpp/suspicious-pointer-scaling + -**:cpp/suspicious-pointer-scaling-void + -**:cpp/unsigned-comparison-zero + -**/cmake*/Modules/** + input: ${{ steps.step1.outputs.sarif-output }}/cpp.sarif + output: ${{ steps.step1.outputs.sarif-output }}/cpp.sarif + + - name: Upload CodeQL results to code scanning + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: ${{ steps.step1.outputs.sarif-output }} + category: "/language:${{matrix.language}}" + + - name: Upload CodeQL results as an artifact + if: success() || failure() + uses: actions/upload-artifact@v3 + with: + name: codeql-results + path: ${{ steps.step1.outputs.sarif-output }} + retention-days: 5 + + - name: Fail if an error is found + run: | + ./.github/workflows/fail_on_error.py \ + ${{ steps.step1.outputs.sarif-output }}/cpp.sarif diff --git a/.github/workflows/fail_on_error.py b/.github/workflows/fail_on_error.py new file mode 100755 index 0000000..2979174 --- /dev/null +++ b/.github/workflows/fail_on_error.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 + +import json +import sys + +# Return whether SARIF file contains error-level results +def codeql_sarif_contain_error(filename): + with open(filename, 'r') as f: + s = json.load(f) + + for run in s.get('runs', []): + rules_metadata = run['tool']['driver']['rules'] + if not rules_metadata: + rules_metadata = run['tool']['extensions'][0]['rules'] + + for res in run.get('results', []): + if 'ruleIndex' in res: + rule_index = res['ruleIndex'] + elif 'rule' in res and 'index' in res['rule']: + rule_index = res['rule']['index'] + else: + continue + try: + rule_level = rules_metadata[rule_index]['defaultConfiguration']['level'] + except IndexError as e: + print(e, rule_index, len(rules_metadata)) + else: + if rule_level == 'error': + return True + return False + +if __name__ == "__main__": + if codeql_sarif_contain_error(sys.argv[1]): + sys.exit(1) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..403cec9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.o +*.a +*.so +*.so.* +*.pc diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000..1f7dff0 --- /dev/null +++ b/Android.bp @@ -0,0 +1,49 @@ +package { + default_applicable_licenses: ["external_libudev_zero_license"], +} + +license { + name: "external_libudev_zero_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-ISC", + ], + license_text: [ + "LICENSE", + ], +} + +cc_genrule { + name: "libudev_zero_copy_headers", + srcs: ["udev.h"], + out: ["libudev.h"], + cmd: "cp $(in) $(out)", +} + +cc_library_static { + name: "libudev-zero", + visibility: [ + "//external/igt-gpu-tools", + ], + srcs: [ + "udev.c", + "udev_list.c", + "udev_device.c", + "udev_monitor.c", + "udev_enumerate.c", + ], + generated_headers: [ + "libudev_zero_copy_headers", + ], + export_generated_headers: [ + "libudev_zero_copy_headers", + ], + cflags: [ + "-Wall", + "-Wextra", + "-Wpedantic", + "-Wmissing-prototypes", + "-Wstrict-prototypes", + "-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/METADATA b/METADATA new file mode 100644 index 0000000..626de45 --- /dev/null +++ b/METADATA @@ -0,0 +1,16 @@ +name: "libudev-zero" +description: + "Drop-in replacement for libudev intended to work with any device manager" + +third_party { +homepage: "https://github.com/illiliti/libudev-zero" + identifier { + type: "Git" + value: "https://github.com/illiliti/libudev-zero" + primary_source: true + version: "bbeb7ad51c1edb7ab3cf63f30a21e9bb383b7994" + } + version: "bbeb7ad51c1edb7ab3cf63f30a21e9bb383b7994" + last_upgrade_date { year: 2025 month: 4 day: 28 } + license_type: NOTICE +} \ No newline at end of file diff --git a/MODULE_LICENSE_ISC b/MODULE_LICENSE_ISC new file mode 100644 index 0000000..e69de29 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1fc5a1f --- /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 -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@|251|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/OWNERS b/OWNERS new file mode 100644 index 0000000..c956c29 --- /dev/null +++ b/OWNERS @@ -0,0 +1 @@ +include platform/system/core:main:/janitors/OWNERS \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..6cd5202 --- /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-1848802791) +* 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.pc.in b/libudev.pc.in new file mode 100644 index 0000000..2d3f0b0 --- /dev/null +++ b/libudev.pc.in @@ -0,0 +1,12 @@ +# 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 +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..3aaa327 --- /dev/null +++ b/udev.h @@ -0,0 +1,139 @@ +/* + * 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_current_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..b057796 --- /dev/null +++ b/udev_device.c @@ -0,0 +1,757 @@ +/* + * 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; +} + +/* XXX NOT IMPLEMENTED */ struct udev_list_entry *udev_device_get_current_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); + + file = fopen(path, "r"); + + if (!file) { + return NULL; + } + + if (fstat(fileno(file), &st) != 0 || !S_ISREG(st.st_mode)) { + fclose(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); + + file = fopen(path, "w"); + + if (!file) { + return -1; + } + + if (fstat(fileno(file), &st) != 0 || !S_ISREG(st.st_mode)) { + fclose(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 int set_properties_from_uevent(struct udev_device *udev_device, const char *syspath) +{ + char line[LINE_MAX], path[PATH_MAX + sizeof("/uevent")], devnode[PATH_MAX]; + FILE *file; + char *pos; + + snprintf(path, sizeof(path), "%s/uevent", syspath); + file = fopen(path, "r"); + + if (!file) { + return -1; + } + + 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, 0); + } + } + + fclose(file); + return 0; +} + +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_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); + } + } + } + else 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); + } + } + + 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; + } + + if (!realpath(syspath, path)) { + return NULL; + } + + 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); + + if (set_properties_from_uevent(udev_device, path) == -1) { + free(udev_device); + return NULL; + } + + 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"); + subsystem = read_symlink(path, "subsystem"); + + 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_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..6fd49c0 --- /dev/null +++ b/udev_enumerate.c @@ -0,0 +1,375 @@ +/* + * 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 "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; +}; + +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(struct udev_enumerate *udev_enumerate, const char *path) +{ + struct udev_device *udev_device; + + udev_device = udev_device_new_from_syspath(udev_enumerate->udev, path); + + if (!udev_device) { + return; + } + + if (!filter_subsystem(udev_enumerate, udev_device) || + !filter_sysname(udev_enumerate, udev_device) || + !filter_property(udev_enumerate, udev_device) || + !filter_sysattr(udev_enumerate, udev_device)) { + udev_device_unref(udev_device); + return; + } + + udev_list_entry_add(&udev_enumerate->devices, udev_device_get_syspath(udev_device), NULL, 0); + + udev_device_unref(udev_device); +} + +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) +{ + int i, cnt; + struct dirent **de; + + cnt = scandir(path, &de, filter_dot, NULL); + + if (cnt == -1) { + return 0; + } + + for (i = 0; i < cnt; i++) { + char device_path[PATH_MAX]; + snprintf(device_path, sizeof(device_path), "%s/%s", path, de[i]->d_name); + add_device(udev_enumerate, device_path); + } + + for (i = 0; i < cnt; i++) { + free(de[i]); + } + + free(de); + return 1; +} + +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; +}