// 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;
}