mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-04-29 02:20:43 +02:00
NMUdevClient does not actually implement ref-counting, because it's not used. Still, the destroy function was named nm_udev_client_unref(), because theoretically then we could later, as the need arises, make the type ref-counted. Then unref function already had the right name. However, NMUdevClient also has a callback function that emits monitor events. Again for simplicity, this callback function cannot be reset, it can only be set once (in the constructor) and can also not be unset nor disabled. When the user of NMUdevClient is done with the instance and calls "unref", then it must be sure that the callback is no longer invoked afterwards. In practice that is already the case, but "unref" makes it sound as if somebody else could also still hold a reference -- in which case the user would have to first unset/disable the callback. Rename the function to "destroy()", so that it's clear that the instance is gone afterwards and that the callback will not be invoked anymore.
265 lines
6.8 KiB
C
265 lines
6.8 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
/*
|
|
* 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_destroy(self);
|
|
}
|
|
|
|
NMUdevClient *
|
|
nm_udev_client_destroy(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;
|
|
}
|