summaryrefslogtreecommitdiffstats
path: root/src/libudev/libudev-monitor.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libudev/libudev-monitor.c')
-rw-r--r--src/libudev/libudev-monitor.c309
1 files changed, 309 insertions, 0 deletions
diff --git a/src/libudev/libudev-monitor.c b/src/libudev/libudev-monitor.c
new file mode 100644
index 0000000..70036f5
--- /dev/null
+++ b/src/libudev/libudev-monitor.c
@@ -0,0 +1,309 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <errno.h>
+#include <poll.h>
+
+#include "libudev.h"
+
+#include "alloc-util.h"
+#include "device-monitor-private.h"
+#include "device-private.h"
+#include "device-util.h"
+#include "libudev-device-internal.h"
+#include "string-util.h"
+
+/**
+ * SECTION:libudev-monitor
+ * @short_description: device event source
+ *
+ * Connects to a device event source.
+ */
+
+/**
+ * udev_monitor:
+ *
+ * Opaque object handling an event source.
+ */
+struct udev_monitor {
+ struct udev *udev;
+ unsigned n_ref;
+ sd_device_monitor *monitor;
+};
+
+static MonitorNetlinkGroup monitor_netlink_group_from_string(const char *name) {
+ if (!name)
+ return MONITOR_GROUP_NONE;
+ if (streq(name, "udev"))
+ return MONITOR_GROUP_UDEV;
+ if (streq(name, "kernel"))
+ return MONITOR_GROUP_KERNEL;
+ return _MONITOR_NETLINK_GROUP_INVALID;
+}
+
+/**
+ * udev_monitor_new_from_netlink:
+ * @udev: udev library context
+ * @name: name of event source
+ *
+ * Create new udev monitor and connect to a specified event
+ * source. Valid sources identifiers are "udev" and "kernel".
+ *
+ * Applications should usually not connect directly to the
+ * "kernel" events, because the devices might not be useable
+ * at that time, before udev has configured them, and created
+ * device nodes. Accessing devices at the same time as udev,
+ * might result in unpredictable behavior. The "udev" events
+ * are sent out after udev has finished its event processing,
+ * all rules have been processed, and needed device nodes are
+ * created.
+ *
+ * The initial refcount is 1, and needs to be decremented to
+ * release the resources of the udev monitor.
+ *
+ * Returns: a new udev monitor, or #NULL, in case of an error
+ **/
+_public_ struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char *name) {
+ _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *m = NULL;
+ struct udev_monitor *udev_monitor;
+ MonitorNetlinkGroup g;
+ int r;
+
+ g = monitor_netlink_group_from_string(name);
+ if (g < 0)
+ return_with_errno(NULL, EINVAL);
+
+ r = device_monitor_new_full(&m, g, -1);
+ if (r < 0)
+ return_with_errno(NULL, r);
+
+ udev_monitor = new(struct udev_monitor, 1);
+ if (!udev_monitor)
+ return_with_errno(NULL, ENOMEM);
+
+ *udev_monitor = (struct udev_monitor) {
+ .udev = udev,
+ .n_ref = 1,
+ .monitor = TAKE_PTR(m),
+ };
+
+ return udev_monitor;
+}
+
+/**
+ * udev_monitor_filter_update:
+ * @udev_monitor: monitor
+ *
+ * Update the installed socket filter. This is only needed,
+ * if the filter was removed or changed.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+_public_ int udev_monitor_filter_update(struct udev_monitor *udev_monitor) {
+ assert_return(udev_monitor, -EINVAL);
+
+ return sd_device_monitor_filter_update(udev_monitor->monitor);
+}
+
+/**
+ * udev_monitor_enable_receiving:
+ * @udev_monitor: the monitor which should receive events
+ *
+ * Binds the @udev_monitor socket to the event source.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+_public_ int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor) {
+ assert_return(udev_monitor, -EINVAL);
+
+ return device_monitor_enable_receiving(udev_monitor->monitor);
+}
+
+/**
+ * udev_monitor_set_receive_buffer_size:
+ * @udev_monitor: the monitor which should receive events
+ * @size: the size in bytes
+ *
+ * Set the size of the kernel socket buffer. This call needs the
+ * appropriate privileges to succeed.
+ *
+ * Returns: 0 on success, otherwise -1 on error.
+ */
+_public_ int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int size) {
+ assert_return(udev_monitor, -EINVAL);
+
+ return sd_device_monitor_set_receive_buffer_size(udev_monitor->monitor, (size_t) size);
+}
+
+static struct udev_monitor *udev_monitor_free(struct udev_monitor *udev_monitor) {
+ assert(udev_monitor);
+
+ sd_device_monitor_unref(udev_monitor->monitor);
+ return mfree(udev_monitor);
+}
+
+/**
+ * udev_monitor_ref:
+ * @udev_monitor: udev monitor
+ *
+ * Take a reference of a udev monitor.
+ *
+ * Returns: the passed udev monitor
+ **/
+
+/**
+ * udev_monitor_unref:
+ * @udev_monitor: udev monitor
+ *
+ * Drop a reference of a udev monitor. If the refcount reaches zero,
+ * the bound socket will be closed, and the resources of the monitor
+ * will be released.
+ *
+ * Returns: #NULL
+ **/
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(struct udev_monitor, udev_monitor, udev_monitor_free);
+
+/**
+ * udev_monitor_get_udev:
+ * @udev_monitor: udev monitor
+ *
+ * Retrieve the udev library context the monitor was created with.
+ *
+ * Returns: the udev library context
+ **/
+_public_ struct udev *udev_monitor_get_udev(struct udev_monitor *udev_monitor) {
+ assert_return(udev_monitor, NULL);
+
+ return udev_monitor->udev;
+}
+
+/**
+ * udev_monitor_get_fd:
+ * @udev_monitor: udev monitor
+ *
+ * Retrieve the socket file descriptor associated with the monitor.
+ *
+ * Returns: the socket file descriptor
+ **/
+_public_ int udev_monitor_get_fd(struct udev_monitor *udev_monitor) {
+ assert_return(udev_monitor, -EINVAL);
+
+ return device_monitor_get_fd(udev_monitor->monitor);
+}
+
+static int udev_monitor_receive_sd_device(struct udev_monitor *udev_monitor, sd_device **ret) {
+ struct pollfd pfd;
+ int r;
+
+ assert(udev_monitor);
+ assert(ret);
+
+ pfd = (struct pollfd) {
+ .fd = device_monitor_get_fd(udev_monitor->monitor),
+ .events = POLLIN,
+ };
+
+ for (;;) {
+ /* r == 0 means a device is received but it does not pass the current filter. */
+ r = device_monitor_receive_device(udev_monitor->monitor, ret);
+ if (r != 0)
+ return r;
+
+ for (;;) {
+ /* wait next message */
+ r = poll(&pfd, 1, 0);
+ if (r < 0) {
+ if (IN_SET(errno, EINTR, EAGAIN))
+ continue;
+
+ return -errno;
+ } else if (r == 0)
+ return -EAGAIN;
+
+ /* receive next message */
+ break;
+ }
+ }
+}
+
+/**
+ * udev_monitor_receive_device:
+ * @udev_monitor: udev monitor
+ *
+ * Receive data from the udev monitor socket, allocate a new udev
+ * device, fill in the received data, and return the device.
+ *
+ * Only socket connections with uid=0 are accepted.
+ *
+ * The monitor socket is by default set to NONBLOCK. A variant of poll() on
+ * the file descriptor returned by udev_monitor_get_fd() should to be used to
+ * wake up when new devices arrive, or alternatively the file descriptor
+ * switched into blocking mode.
+ *
+ * The initial refcount is 1, and needs to be decremented to
+ * release the resources of the udev device.
+ *
+ * Returns: a new udev device, or #NULL, in case of an error
+ **/
+_public_ struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monitor) {
+ _cleanup_(sd_device_unrefp) sd_device *device = NULL;
+ int r;
+
+ assert_return(udev_monitor, NULL);
+
+ r = udev_monitor_receive_sd_device(udev_monitor, &device);
+ if (r < 0)
+ return_with_errno(NULL, r);
+
+ return udev_device_new(udev_monitor->udev, device);
+}
+
+/**
+ * udev_monitor_filter_add_match_subsystem_devtype:
+ * @udev_monitor: the monitor
+ * @subsystem: the subsystem value to match the incoming devices against
+ * @devtype: the devtype value to match the incoming devices against
+ *
+ * This filter is efficiently executed inside the kernel, and libudev subscribers
+ * will usually not be woken up for devices which do not match.
+ *
+ * The filter must be installed before the monitor is switched to listening mode.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+_public_ int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_monitor, const char *subsystem, const char *devtype) {
+ assert_return(udev_monitor, -EINVAL);
+
+ return sd_device_monitor_filter_add_match_subsystem_devtype(udev_monitor->monitor, subsystem, devtype);
+}
+
+/**
+ * udev_monitor_filter_add_match_tag:
+ * @udev_monitor: the monitor
+ * @tag: the name of a tag
+ *
+ * This filter is efficiently executed inside the kernel, and libudev subscribers
+ * will usually not be woken up for devices which do not match.
+ *
+ * The filter must be installed before the monitor is switched to listening mode.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+_public_ int udev_monitor_filter_add_match_tag(struct udev_monitor *udev_monitor, const char *tag) {
+ assert_return(udev_monitor, -EINVAL);
+
+ return sd_device_monitor_filter_add_match_tag(udev_monitor->monitor, tag);
+}
+
+/**
+ * udev_monitor_filter_remove:
+ * @udev_monitor: monitor
+ *
+ * Remove all filters from monitor.
+ *
+ * Returns: 0 on success, otherwise a negative error value.
+ */
+_public_ int udev_monitor_filter_remove(struct udev_monitor *udev_monitor) {
+ assert_return(udev_monitor, -EINVAL);
+
+ return sd_device_monitor_filter_remove(udev_monitor->monitor);
+}