]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sd-device: introduce sd_device_monitor
authorYu Watanabe <watanabe.yu+github@gmail.com>
Mon, 8 Oct 2018 13:45:52 +0000 (22:45 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Tue, 16 Oct 2018 18:30:54 +0000 (03:30 +0900)
src/libsystemd/libsystemd.sym
src/libsystemd/meson.build
src/libsystemd/sd-device/device-monitor-private.h [new file with mode: 0644]
src/libsystemd/sd-device/device-monitor.c [new file with mode: 0644]
src/systemd/sd-device.h

index 8d0bebe2adf4e621f5c73189dc9b02c0c4840dc0..8d46081ec4036c6f170ce4b32134e820d42facca 100644 (file)
@@ -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;
index d95406e7acb74dc415d427a36866c5f28439158d..3cfc9753b1445ea321b3d11608aa739b3b2b0ff3 100644 (file)
@@ -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 (file)
index 0000000..2659cc3
--- /dev/null
@@ -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 (file)
index 0000000..6e94ff1
--- /dev/null
@@ -0,0 +1,755 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <errno.h>
+#include <linux/filter.h>
+#include <linux/netlink.h>
+#include <sys/socket.h>
+
+#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;
+}
index 406db8866dc7247a8032729a69db80acf6478bd6..43fb4a0c2a1e0fb84e03ec0bb61c19370d17ebd4 100644 (file)
 #include <sys/sysmacros.h>
 #include <sys/types.h>
 
+#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;