diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym index 8d0bebe2ad..8d46081ec4 100644 --- a/src/libsystemd/libsystemd.sym +++ b/src/libsystemd/libsystemd.sym @@ -647,4 +647,20 @@ global: sd_hwdb_enumerate; sd_id128_get_boot_app_specific; + + sd_device_monitor_new; + sd_device_monitor_ref; + sd_device_monitor_unref; + + sd_device_monitor_set_receive_buffer_size; + sd_device_monitor_attach_event; + sd_device_monitor_detach_event; + sd_device_monitor_get_event; + sd_device_monitor_start; + sd_device_monitor_stop; + + sd_device_monitor_filter_add_match_subsystem_devtype; + sd_device_monitor_filter_add_match_tag; + sd_device_monitor_filter_update; + sd_device_monitor_filter_remove; } LIBSYSTEMD_239; diff --git a/src/libsystemd/meson.build b/src/libsystemd/meson.build index d95406e7ac..3cfc9753b1 100644 --- a/src/libsystemd/meson.build +++ b/src/libsystemd/meson.build @@ -53,6 +53,8 @@ libsystemd_sources = files(''' sd-device/device-enumerator-private.h sd-device/device-enumerator.c sd-device/device-internal.h + sd-device/device-monitor-private.h + sd-device/device-monitor.c sd-device/device-private.c sd-device/device-private.h sd-device/device-util.h diff --git a/src/libsystemd/sd-device/device-monitor-private.h b/src/libsystemd/sd-device/device-monitor-private.h new file mode 100644 index 0000000000..2659cc3c46 --- /dev/null +++ b/src/libsystemd/sd-device/device-monitor-private.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include "sd-device.h" + +typedef enum MonitorNetlinkGroup { + MONITOR_GROUP_NONE, + MONITOR_GROUP_KERNEL, + MONITOR_GROUP_UDEV, + _MONITOR_NETLINK_GROUP_MAX, + _MONITOR_NETLINK_GROUP_INVALID = -1, +} MonitorNetlinkGroup; + +int device_monitor_new_full(sd_device_monitor **ret, MonitorNetlinkGroup group, int fd); +int device_monitor_disconnect(sd_device_monitor *m); +int device_monitor_allow_unicast_sender(sd_device_monitor *m, sd_device_monitor *sender); +int device_monitor_enable_receiving(sd_device_monitor *m); +int device_monitor_get_fd(sd_device_monitor *m); +int device_monitor_send_device(sd_device_monitor *m, sd_device_monitor *destination, sd_device *device); +int device_monitor_receive_device(sd_device_monitor *m, sd_device **ret); diff --git a/src/libsystemd/sd-device/device-monitor.c b/src/libsystemd/sd-device/device-monitor.c new file mode 100644 index 0000000000..6e94ff1fa0 --- /dev/null +++ b/src/libsystemd/sd-device/device-monitor.c @@ -0,0 +1,755 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include +#include +#include +#include + +#include "sd-device.h" +#include "sd-event.h" + +#include "MurmurHash2.h" +#include "alloc-util.h" +#include "device-monitor-private.h" +#include "device-private.h" +#include "device-util.h" +#include "fd-util.h" +#include "format-util.h" +#include "hashmap.h" +#include "missing.h" +#include "mount-util.h" +#include "set.h" +#include "socket-util.h" +#include "string-util.h" +#include "strv.h" + +struct sd_device_monitor { + unsigned n_ref; + + int sock; + union sockaddr_union snl; + union sockaddr_union snl_trusted_sender; + bool bound; + + Hashmap *subsystem_filter; + Set *tag_filter; + bool filter_uptodate; + + sd_event *event; + sd_event_source *event_source; + int64_t event_priority; + sd_device_monitor_handler_t callback; + void *userdata; +}; + +#define UDEV_MONITOR_MAGIC 0xfeedcafe + +typedef struct monitor_netlink_header { + /* "libudev" prefix to distinguish libudev and kernel messages */ + char prefix[8]; + /* Magic to protect against daemon <-> Library message format mismatch + * Used in the kernel from socket filter rules; needs to be stored in network order */ + unsigned magic; + /* Total length of header structure known to the sender */ + unsigned header_size; + /* Properties string buffer */ + unsigned properties_off; + unsigned properties_len; + /* Hashes of primary device properties strings, to let libudev subscribers + * use in-kernel socket filters; values need to be stored in network order */ + unsigned filter_subsystem_hash; + unsigned filter_devtype_hash; + unsigned filter_tag_bloom_hi; + unsigned filter_tag_bloom_lo; +} monitor_netlink_header; + +static int monitor_set_nl_address(sd_device_monitor *m) { + union sockaddr_union snl; + socklen_t addrlen; + + assert(m); + + /* Get the address the kernel has assigned us. + * It is usually, but not necessarily the pid. */ + addrlen = sizeof(struct sockaddr_nl); + if (getsockname(m->sock, &snl.sa, &addrlen) < 0) + return -errno; + + m->snl.nl.nl_pid = snl.nl.nl_pid; + return 0; +} + +int device_monitor_allow_unicast_sender(sd_device_monitor *m, sd_device_monitor *sender) { + assert_return(m, -EINVAL); + assert_return(sender, -EINVAL); + + m->snl_trusted_sender.nl.nl_pid = sender->snl.nl.nl_pid; + return 0; +} + +_public_ int sd_device_monitor_set_receive_buffer_size(sd_device_monitor *m, size_t size) { + int n = (int) size; + + assert_return(m, -EINVAL); + assert_return((size_t) n != size, -EINVAL); + + if (setsockopt(m->sock, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)) < 0 && + setsockopt(m->sock, SOL_SOCKET, SO_RCVBUFFORCE, &n, sizeof(n)) < 0) + return -errno; + + return 0; +} + +int device_monitor_disconnect(sd_device_monitor *m) { + assert(m); + + m->sock = safe_close(m->sock); + return 0; +} + +int device_monitor_get_fd(sd_device_monitor *m) { + assert_return(m, -EINVAL); + + return m->sock; +} + +int device_monitor_new_full(sd_device_monitor **ret, MonitorNetlinkGroup group, int fd) { + _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *m = NULL; + _cleanup_close_ int sock = -1; + int r; + + assert_return(ret, -EINVAL); + assert_return(group >= 0 && group < _MONITOR_NETLINK_GROUP_MAX, -EINVAL); + + if (group == MONITOR_GROUP_UDEV && + access("/run/udev/control", F_OK) < 0 && + dev_is_devtmpfs() <= 0) { + + /* + * We do not support subscribing to uevents if no instance of + * udev is running. Uevents would otherwise broadcast the + * processing data of the host into containers, which is not + * desired. + * + * Containers will currently not get any udev uevents, until + * a supporting infrastructure is available. + * + * We do not set a netlink multicast group here, so the socket + * will not receive any messages. + */ + + log_debug("The udev service seems not to be active, disabling the monitor"); + group = MONITOR_GROUP_NONE; + } + + if (fd < 0) { + sock = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_KOBJECT_UEVENT); + if (sock < 0) + return log_debug_errno(errno, "Failed to create socket: %m"); + } + + m = new(sd_device_monitor, 1); + if (!m) + return -ENOMEM; + + *m = (sd_device_monitor) { + .n_ref = 1, + .sock = fd >= 0 ? fd : TAKE_FD(sock), + .bound = fd >= 0, + .snl.nl.nl_family = AF_NETLINK, + .snl.nl.nl_groups = group, + }; + + if (fd >= 0) { + r = monitor_set_nl_address(m); + if (r < 0) + return log_debug_errno(r, "Failed to set netlink address: %m"); + } + + *ret = TAKE_PTR(m); + return 0; +} + +_public_ int sd_device_monitor_new(sd_device_monitor **ret) { + return device_monitor_new_full(ret, MONITOR_GROUP_UDEV, -1); +} + +_public_ int sd_device_monitor_stop(sd_device_monitor *m) { + assert_return(m, -EINVAL); + + m->event_source = sd_event_source_unref(m->event_source); + (void) device_monitor_disconnect(m); + + return 0; +} + +static int device_monitor_event_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + sd_device_monitor *m = userdata; + + assert(m); + + if (device_monitor_receive_device(m, &device) <= 0) + return 0; + + if (m->callback) + return m->callback(m, device, m->userdata); + + return 0; +} + +_public_ int sd_device_monitor_start(sd_device_monitor *m, sd_device_monitor_handler_t callback, void *userdata, const char *description) { + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; + int r; + + assert_return(m, -EINVAL); + + if (!m->event) { + r = sd_device_monitor_attach_event(m, NULL, 0); + if (r < 0) + return r; + } + + r = device_monitor_enable_receiving(m); + if (r < 0) + return r; + + m->callback = callback; + m->userdata = userdata; + + r = sd_event_add_io(m->event, &s, m->sock, EPOLLIN, device_monitor_event_handler, m); + if (r < 0) + return r; + + r = sd_event_source_set_priority(s, m->event_priority); + if (r < 0) + return r; + + if (description) { + r = sd_event_source_set_description(s, description); + if (r < 0) + return r; + } + + m->event_source = TAKE_PTR(s); + + return 0; +} + +_public_ int sd_device_monitor_detach_event(sd_device_monitor *m) { + assert_return(m, -EINVAL); + + (void) sd_device_monitor_stop(m); + m->event = sd_event_unref(m->event); + + return 0; +} + +_public_ int sd_device_monitor_attach_event(sd_device_monitor *m, sd_event *event, int64_t priority) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->event, -EBUSY); + + if (event) + m->event = sd_event_ref(event); + else { + r = sd_event_default(&m->event); + if (r < 0) + return 0; + } + + m->event_priority = priority; + + return 0; +} + +_public_ sd_event *sd_device_monitor_get_event(sd_device_monitor *m) { + assert_return(m, NULL); + + return m->event; +} + +int device_monitor_enable_receiving(sd_device_monitor *m) { + int r; + + assert_return(m, -EINVAL); + + if (!m->filter_uptodate) { + r = sd_device_monitor_filter_update(m); + if (r < 0) + return log_debug_errno(r, "Failed to update filter: %m"); + } + + if (!m->bound) { + if (bind(m->sock, &m->snl.sa, sizeof(struct sockaddr_nl)) < 0) + return log_debug_errno(errno, "Failed to bind monitoring socket to event source: %m"); + + m->bound = true; + } + + r = monitor_set_nl_address(m); + if (r < 0) + return log_debug_errno(r, "Failed to set address: %m"); + + /* enable receiving of sender credentials */ + if (setsockopt(m->sock, SOL_SOCKET, SO_PASSCRED, &const_int_one, sizeof(const_int_one)) < 0) + return log_debug_errno(errno, "Failed to set socket option SO_PASSCRED: %m"); + + return 0; +} + +static sd_device_monitor *device_monitor_free(sd_device_monitor *m) { + assert(m); + + (void) sd_device_monitor_detach_event(m); + + hashmap_free_free_free(m->subsystem_filter); + set_free_free(m->tag_filter); + + return mfree(m); +} + +DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_device_monitor, sd_device_monitor, device_monitor_free); + +static int passes_filter(sd_device_monitor *m, sd_device *device) { + const char *tag, *subsystem, *devtype, *s, *d = NULL; + Iterator i; + int r; + + assert_return(m, -EINVAL); + assert_return(device, -EINVAL); + + if (hashmap_isempty(m->subsystem_filter)) + goto tag; + + r = sd_device_get_subsystem(device, &s); + if (r < 0) + return r; + + r = sd_device_get_devtype(device, &d); + if (r < 0 && r != -ENOENT) + return r; + + HASHMAP_FOREACH_KEY(devtype, subsystem, m->subsystem_filter, i) { + if (!streq(s, subsystem)) + continue; + + if (!devtype) + goto tag; + + if (!d) + continue; + + if (streq(d, devtype)) + goto tag; + } + + return 0; + +tag: + if (set_isempty(m->tag_filter)) + return 1; + + SET_FOREACH(tag, m->tag_filter, i) + if (sd_device_has_tag(device, tag) > 0) + return 1; + + return 0; +} + +int device_monitor_receive_device(sd_device_monitor *m, sd_device **ret) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + union { + monitor_netlink_header nlh; + char raw[8192]; + } buf; + struct iovec iov = { + .iov_base = &buf, + .iov_len = sizeof(buf) + }; + char cred_msg[CMSG_SPACE(sizeof(struct ucred))]; + union sockaddr_union snl; + struct msghdr smsg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cred_msg, + .msg_controllen = sizeof(cred_msg), + .msg_name = &snl, + .msg_namelen = sizeof(snl), + }; + struct cmsghdr *cmsg; + struct ucred *cred; + ssize_t buflen, bufpos; + bool is_initialized = false; + int r; + + assert(ret); + + buflen = recvmsg(m->sock, &smsg, 0); + if (buflen < 0) { + if (errno != EINTR) + log_debug_errno(errno, "Failed to receive message: %m"); + return -errno; + } + + if (buflen < 32 || (smsg.msg_flags & MSG_TRUNC)) + return log_debug_errno(EINVAL, "Invalid message length."); + + if (snl.nl.nl_groups == MONITOR_GROUP_NONE) { + /* unicast message, check if we trust the sender */ + if (m->snl_trusted_sender.nl.nl_pid == 0 || + snl.nl.nl_pid != m->snl_trusted_sender.nl.nl_pid) + return log_debug_errno(EAGAIN, "Unicast netlink message ignored."); + + } else if (snl.nl.nl_groups == MONITOR_GROUP_KERNEL) { + if (snl.nl.nl_pid > 0) + return log_debug_errno(EAGAIN, "Multicast kernel netlink message from PID %"PRIu32" ignored.", snl.nl.nl_pid); + } + + cmsg = CMSG_FIRSTHDR(&smsg); + if (!cmsg || cmsg->cmsg_type != SCM_CREDENTIALS) + return log_debug_errno(EAGAIN, "No sender credentials received, message ignored."); + + cred = (struct ucred*) CMSG_DATA(cmsg); + if (cred->uid != 0) + return log_debug_errno(EAGAIN, "Sender uid="UID_FMT", message ignored.", cred->uid); + + if (streq(buf.raw, "libudev")) { + /* udev message needs proper version magic */ + if (buf.nlh.magic != htobe32(UDEV_MONITOR_MAGIC)) + return log_debug_errno(EAGAIN, "Invalid message signature (%x != %x)", + buf.nlh.magic, htobe32(UDEV_MONITOR_MAGIC)); + + if (buf.nlh.properties_off+32 > (size_t) buflen) + return log_debug_errno(EAGAIN, "Invalid message length (%u > %zd)", + buf.nlh.properties_off+32, buflen); + + bufpos = buf.nlh.properties_off; + + /* devices received from udev are always initialized */ + is_initialized = true; + + } else { + /* kernel message with header */ + bufpos = strlen(buf.raw) + 1; + if ((size_t) bufpos < sizeof("a@/d") || bufpos >= buflen) + return log_debug_errno(EAGAIN, "Invalid message length"); + + /* check message header */ + if (!strstr(buf.raw, "@/")) + return log_debug_errno(EAGAIN, "Invalid message header"); + } + + r = device_new_from_nulstr(&device, (uint8_t*) &buf.raw[bufpos], buflen - bufpos); + if (r < 0) + return log_debug_errno(r, "Failed to create device: %m"); + + if (is_initialized) + device_set_is_initialized(device); + + /* Skip device, if it does not pass the current filter */ + r = passes_filter(m, device); + if (r < 0) + return log_debug_errno(r, "Failed to check received device passing filter: %m"); + + if (r == 0) + log_debug("Received device does not pass filter, ignoring"); + else + *ret = TAKE_PTR(device); + + return r; +} + +static uint32_t string_hash32(const char *str) { + return MurmurHash2(str, strlen(str), 0); +} + +/* Get a bunch of bit numbers out of the hash, and set the bits in our bit field */ +static uint64_t string_bloom64(const char *str) { + uint64_t bits = 0; + uint32_t hash = string_hash32(str); + + bits |= 1LLU << (hash & 63); + bits |= 1LLU << ((hash >> 6) & 63); + bits |= 1LLU << ((hash >> 12) & 63); + bits |= 1LLU << ((hash >> 18) & 63); + return bits; +} + +int device_monitor_send_device( + sd_device_monitor *m, + sd_device_monitor *destination, + sd_device *device) { + + monitor_netlink_header nlh = { + .prefix = "libudev", + .magic = htobe32(UDEV_MONITOR_MAGIC), + .header_size = sizeof nlh, + }; + struct iovec iov[2] = { + { .iov_base = &nlh, .iov_len = sizeof nlh }, + }; + struct msghdr smsg = { + .msg_iov = iov, + .msg_iovlen = 2, + }; + /* default destination for sending */ + union sockaddr_union default_destination = { + .nl.nl_family = AF_NETLINK, + .nl.nl_groups = MONITOR_GROUP_UDEV, + }; + uint64_t tag_bloom_bits; + const char *buf, *val; + ssize_t count; + size_t blen; + int r; + + assert(m); + assert(device); + + r = device_get_properties_nulstr(device, (const uint8_t **) &buf, &blen); + if (r < 0) + return log_debug_errno(r, "Failed to get device properties: %m"); + if (blen < 32) { + log_debug("Device buffer is too small to contain a valid device"); + return -EINVAL; + } + + /* fill in versioned header */ + r = sd_device_get_subsystem(device, &val); + if (r < 0) + return log_debug_errno(r, "Failed to get device subsystem: %m"); + nlh.filter_subsystem_hash = htobe32(string_hash32(val)); + + if (sd_device_get_devtype(device, &val) >= 0 && val) + nlh.filter_devtype_hash = htobe32(string_hash32(val)); + + /* add tag bloom filter */ + tag_bloom_bits = 0; + FOREACH_DEVICE_TAG(device, val) + tag_bloom_bits |= string_bloom64(val); + + if (tag_bloom_bits > 0) { + nlh.filter_tag_bloom_hi = htobe32(tag_bloom_bits >> 32); + nlh.filter_tag_bloom_lo = htobe32(tag_bloom_bits & 0xffffffff); + } + + /* add properties list */ + nlh.properties_off = iov[0].iov_len; + nlh.properties_len = blen; + iov[1] = (struct iovec) { + .iov_base = (char*) buf, + .iov_len = blen, + }; + + /* + * Use custom address for target, or the default one. + * + * If we send to a multicast group, we will get + * ECONNREFUSED, which is expected. + */ + smsg.msg_name = destination ? &destination->snl : &default_destination; + smsg.msg_namelen = sizeof(struct sockaddr_nl); + count = sendmsg(m->sock, &smsg, 0); + if (count < 0) { + if (!destination && errno == ECONNREFUSED) { + log_debug("Passed device to netlink monitor"); + return 0; + } else + return log_debug_errno(errno, "Failed to send device to netlink monitor"); + } + + log_debug("Passed %zi byte device to netlink monitor", count); + return count; +} + +static void bpf_stmt(struct sock_filter *ins, unsigned *i, + unsigned short code, unsigned data) { + ins[(*i)++] = (struct sock_filter) { + .code = code, + .k = data, + }; +} + +static void bpf_jmp(struct sock_filter *ins, unsigned *i, + unsigned short code, unsigned data, + unsigned short jt, unsigned short jf) { + ins[(*i)++] = (struct sock_filter) { + .code = code, + .jt = jt, + .jf = jf, + .k = data, + }; +} + +_public_ int sd_device_monitor_filter_update(sd_device_monitor *m) { + struct sock_filter ins[512] = {}; + struct sock_fprog filter; + const char *subsystem, *devtype, *tag; + unsigned i = 0; + Iterator it; + + assert_return(m, -EINVAL); + + if (hashmap_isempty(m->subsystem_filter) && + set_isempty(m->tag_filter)) { + m->filter_uptodate = true; + return 0; + } + + /* load magic in A */ + bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(monitor_netlink_header, magic)); + /* jump if magic matches */ + bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, UDEV_MONITOR_MAGIC, 1, 0); + /* wrong magic, pass packet */ + bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff); + + if (!set_isempty(m->tag_filter)) { + int tag_matches = set_size(m->tag_filter); + + /* add all tags matches */ + SET_FOREACH(tag, m->tag_filter, it) { + uint64_t tag_bloom_bits = string_bloom64(tag); + uint32_t tag_bloom_hi = tag_bloom_bits >> 32; + uint32_t tag_bloom_lo = tag_bloom_bits & 0xffffffff; + + /* load device bloom bits in A */ + bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(monitor_netlink_header, filter_tag_bloom_hi)); + /* clear bits (tag bits & bloom bits) */ + bpf_stmt(ins, &i, BPF_ALU|BPF_AND|BPF_K, tag_bloom_hi); + /* jump to next tag if it does not match */ + bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, tag_bloom_hi, 0, 3); + + /* load device bloom bits in A */ + bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(monitor_netlink_header, filter_tag_bloom_lo)); + /* clear bits (tag bits & bloom bits) */ + bpf_stmt(ins, &i, BPF_ALU|BPF_AND|BPF_K, tag_bloom_lo); + /* jump behind end of tag match block if tag matches */ + tag_matches--; + bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, tag_bloom_lo, 1 + (tag_matches * 6), 0); + } + + /* nothing matched, drop packet */ + bpf_stmt(ins, &i, BPF_RET|BPF_K, 0); + } + + /* add all subsystem matches */ + if (!hashmap_isempty(m->subsystem_filter)) { + HASHMAP_FOREACH_KEY(devtype, subsystem, m->subsystem_filter, it) { + uint32_t hash = string_hash32(subsystem); + + /* load device subsystem value in A */ + bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(monitor_netlink_header, filter_subsystem_hash)); + if (!devtype) { + /* jump if subsystem does not match */ + bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 1); + } else { + hash = string_hash32(devtype); + + /* jump if subsystem does not match */ + bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 3); + /* load device devtype value in A */ + bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(monitor_netlink_header, filter_devtype_hash)); + /* jump if value does not match */ + bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 1); + } + + /* matched, pass packet */ + bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff); + + if (i+1 >= ELEMENTSOF(ins)) + return -E2BIG; + } + + /* nothing matched, drop packet */ + bpf_stmt(ins, &i, BPF_RET|BPF_K, 0); + } + + /* matched, pass packet */ + bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff); + + /* install filter */ + filter = (struct sock_fprog) { + .len = i, + .filter = ins, + }; + if (setsockopt(m->sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) < 0) + return -errno; + + m->filter_uptodate = true; + return 0; +} + +_public_ int sd_device_monitor_filter_add_match_subsystem_devtype(sd_device_monitor *m, const char *subsystem, const char *devtype) { + _cleanup_free_ char *s = NULL, *d = NULL; + int r; + + assert_return(m, -EINVAL); + assert_return(subsystem, -EINVAL); + + s = strdup(subsystem); + if (!s) + return -ENOMEM; + + if (devtype) { + d = strdup(devtype); + if (!d) + return -ENOMEM; + } + + r = hashmap_ensure_allocated(&m->subsystem_filter, NULL); + if (r < 0) + return r; + + r = hashmap_put(m->subsystem_filter, s, d); + if (r < 0) + return r; + + s = d = NULL; + m->filter_uptodate = false; + + return 0; +} + +_public_ int sd_device_monitor_filter_add_match_tag(sd_device_monitor *m, const char *tag) { + _cleanup_free_ char *t = NULL; + int r; + + assert_return(m, -EINVAL); + assert_return(tag, -EINVAL); + + t = strdup(tag); + if (!t) + return -ENOMEM; + + r = set_ensure_allocated(&m->tag_filter, &string_hash_ops); + if (r < 0) + return r; + + r = set_put(m->tag_filter, t); + if (r == -EEXIST) + return 0; + if (r < 0) + return r; + + TAKE_PTR(t); + m->filter_uptodate = false; + + return 0; +} + +_public_ int sd_device_monitor_filter_remove(sd_device_monitor *m) { + static const struct sock_fprog filter = { 0, NULL }; + + assert_return(m, -EINVAL); + + m->subsystem_filter = hashmap_free_free_free(m->subsystem_filter); + m->tag_filter = set_free_free(m->tag_filter); + + if (setsockopt(m->sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) < 0) + return -errno; + + m->filter_uptodate = true; + return 0; +} diff --git a/src/systemd/sd-device.h b/src/systemd/sd-device.h index 406db8866d..43fb4a0c2a 100644 --- a/src/systemd/sd-device.h +++ b/src/systemd/sd-device.h @@ -21,12 +21,19 @@ #include #include +#include "sd-event.h" + #include "_sd-common.h" _SD_BEGIN_DECLARATIONS; typedef struct sd_device sd_device; typedef struct sd_device_enumerator sd_device_enumerator; +typedef struct sd_device_monitor sd_device_monitor; + +/* callback */ + +typedef int (*sd_device_monitor_handler_t)(sd_device_monitor *m, sd_device *device, void *userdata); /* device */ @@ -89,8 +96,27 @@ int sd_device_enumerator_add_match_tag(sd_device_enumerator *enumerator, const c int sd_device_enumerator_add_match_parent(sd_device_enumerator *enumerator, sd_device *parent); int sd_device_enumerator_allow_uninitialized(sd_device_enumerator *enumerator); +/* device monitor */ + +int sd_device_monitor_new(sd_device_monitor **ret); +sd_device_monitor *sd_device_monitor_ref(sd_device_monitor *m); +sd_device_monitor *sd_device_monitor_unref(sd_device_monitor *m); + +int sd_device_monitor_set_receive_buffer_size(sd_device_monitor *m, size_t size); +int sd_device_monitor_attach_event(sd_device_monitor *m, sd_event *event, int64_t priority); +int sd_device_monitor_detach_event(sd_device_monitor *m); +sd_event *sd_device_monitor_get_event(sd_device_monitor *m); +int sd_device_monitor_start(sd_device_monitor *m, sd_device_monitor_handler_t callback, void *userdata, const char *description); +int sd_device_monitor_stop(sd_device_monitor *m); + +int sd_device_monitor_filter_add_match_subsystem_devtype(sd_device_monitor *m, const char *subsystem, const char *devtype); +int sd_device_monitor_filter_add_match_tag(sd_device_monitor *m, const char *tag); +int sd_device_monitor_filter_update(sd_device_monitor *m); +int sd_device_monitor_filter_remove(sd_device_monitor *m); + _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_device, sd_device_unref); _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_device_enumerator, sd_device_enumerator_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_device_monitor, sd_device_monitor_unref); _SD_END_DECLARATIONS;