Blob Blame History Raw
// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2017 Red Hat, Inc.
 */

#include "nm-default.h"

#include "nm-udev-utils.h"

#include <libudev.h>

struct _NMPUdevClient {
	char **subsystems;
	GSource *watch_source;
	struct udev *udev;
	struct udev_monitor *monitor;
	NMUdevClientEvent event_handler;
	gpointer event_user_data;
};

/*****************************************************************************/

gboolean
nm_udev_utils_property_as_boolean (const char *uproperty)
{
	/* taken from g_udev_device_get_property_as_boolean() */

	if (uproperty) {
		if (   strcmp (uproperty, "1") == 0
		    || g_ascii_strcasecmp (uproperty, "true") == 0)
			return TRUE;
	}
	return FALSE;
}

const char *
nm_udev_utils_property_decode (const char *uproperty, char **to_free)
{
	const char *p;
	char *unescaped = NULL;
	char *n = NULL;

	if (!uproperty) {
		*to_free = NULL;
		return NULL;
	}

	p = uproperty;
	while (*p) {
		int a, b;

		if (   p[0] == '\\'
		    && p[1] == 'x'
		    && (a = g_ascii_xdigit_value (p[2])) >= 0
		    && (b = g_ascii_xdigit_value (p[3])) >= 0
		    && (a || b)) {
			if (!n) {
				gssize l = p - uproperty;

				unescaped = g_malloc (l + strlen (p) + 1 - 3);
				memcpy (unescaped, uproperty, l);
				n = &unescaped[l];
			}
			*n++ = (a << 4) | b;
			p += 4;
		} else {
			if (n)
				*n++ = *p;
			p++;
		}
	}

	if (!n) {
		*to_free = NULL;
		return uproperty;
	}

	*n++ = '\0';
	return (*to_free = unescaped);
}

char *
nm_udev_utils_property_decode_cp (const char *uproperty)
{
	char *cpy;

	uproperty = nm_udev_utils_property_decode (uproperty, &cpy);
	return cpy ?: g_strdup (uproperty);
}

/*****************************************************************************/

static void
_subsystem_split (const char *subsystem_full,
                  const char **out_subsystem,
                  const char **out_devtype,
                  char **to_free)
{
	char *tmp, *s;

	nm_assert (subsystem_full);
	nm_assert (out_subsystem);
	nm_assert (out_devtype);
	nm_assert (to_free);

	s = strstr (subsystem_full, "/");
	if (s) {
		tmp = g_strdup (subsystem_full);
		s = &tmp[s - subsystem_full];
		*s = '\0';
		*out_subsystem = tmp;
		*out_devtype = &s[1];
		*to_free = tmp;
	} else {
		*out_subsystem = subsystem_full;
		*out_devtype = NULL;
		*to_free = NULL;
	}
}

static struct udev_enumerate *
nm_udev_utils_enumerate (struct udev *uclient,
                         const char *const*subsystems)
{
	struct udev_enumerate *enumerate;
	guint n;

	enumerate = udev_enumerate_new (uclient);

	if (subsystems) {
		for (n = 0; subsystems[n]; n++) {
			const char *subsystem;
			const char *devtype;
			gs_free char *to_free = NULL;

			_subsystem_split (subsystems[n], &subsystem, &devtype, &to_free);

			udev_enumerate_add_match_subsystem (enumerate, subsystem);

			if (devtype != NULL)
				udev_enumerate_add_match_property (enumerate, "DEVTYPE", devtype);
		}
	}

	return enumerate;
}

struct udev *
nm_udev_client_get_udev (NMUdevClient *self)
{
	g_return_val_if_fail (self, NULL);

	return self->udev;
}

struct udev_enumerate *
nm_udev_client_enumerate_new (NMUdevClient *self)
{
	g_return_val_if_fail (self, NULL);

	return nm_udev_utils_enumerate (self->udev, (const char *const*) self->subsystems);
}

/*****************************************************************************/

static gboolean
monitor_event (int fd,
               GIOCondition condition,
               gpointer user_data)
{
	NMUdevClient *self = user_data;
	struct udev_device *udevice;

	if (!self->monitor)
		goto out;

	udevice = udev_monitor_receive_device (self->monitor);
	if (udevice == NULL)
		goto out;

	self->event_handler (self,
	                     udevice,
	                     self->event_user_data);
	udev_device_unref (udevice);

out:
	return TRUE;
}

/**
 * nm_udev_client_new:
 * @subsystems: the subsystems
 * @event_handler: callback for events
 * @event_user_data: user-data for @event_handler
 *
 * Basically, it is g_udev_client_new(), and most notably
 * g_udev_client_constructed().
 *
 * Returns: a new NMUdevClient instance.
 */
NMUdevClient *
nm_udev_client_new (const char *const*subsystems,
                    NMUdevClientEvent event_handler,
                    gpointer event_user_data)
{
	NMUdevClient *self;
	guint n;

	self = g_slice_new0 (NMUdevClient);

	self->event_handler = event_handler;
	self->event_user_data = event_user_data;
	self->subsystems = subsystems && subsystems[0] ? g_strdupv ((char **) subsystems) : NULL;

	self->udev = udev_new ();
	if (!self->udev)
		goto fail;

	/* connect to event source */
	if (self->event_handler) {
		self->monitor = udev_monitor_new_from_netlink (self->udev, "udev");
		if (!self->monitor)
			goto fail;

		if (self->subsystems) {
			/* install subsystem filters to only wake up for certain events */
			for (n = 0; self->subsystems[n]; n++) {
				gs_free char *to_free = NULL;
				const char *subsystem;
				const char *devtype;

				_subsystem_split (self->subsystems[n], &subsystem, &devtype, &to_free);
				udev_monitor_filter_add_match_subsystem_devtype (self->monitor, subsystem, devtype);
			}

			/* listen to events, and buffer them */
			udev_monitor_set_receive_buffer_size (self->monitor, 4*1024*1024);
			udev_monitor_enable_receiving (self->monitor);

			self->watch_source = nm_g_unix_fd_source_new (udev_monitor_get_fd (self->monitor),
			                                              G_IO_IN,
			                                              G_PRIORITY_DEFAULT,
			                                              monitor_event,
			                                              self,
			                                              NULL);
			g_source_attach (self->watch_source, g_main_context_get_thread_default ());
		}
	}

	return self;

fail:
	return nm_udev_client_unref (self);
}

NMUdevClient *
nm_udev_client_unref (NMUdevClient *self)
{
	if (!self)
		return NULL;

	nm_clear_g_source_inst (&self->watch_source);

	udev_monitor_unref (self->monitor);
	self->monitor = NULL;
	udev_unref (self->udev);
	self->udev = NULL;

	g_strfreev (self->subsystems);

	g_slice_free (NMUdevClient, self);

	return NULL;
}