
GCC 8.0's -Wcast-function-type objects casting function pointers to ones with incompatible prototypes. Sometimes we do that on purpose though. Notably, the g_source_set_callback()'s func argument can point to functions of various prototypes. Also, libnm-glib/nm-remote-connection is perhaps just not worth reworking, that would just be a waste of time. A cast to void(*)(void) avoids the GCC warning, let's use it.
295 lines
6.8 KiB
C
295 lines
6.8 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
|
|
/* nm-udev-utils.c - udev utils functions
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* 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 (GIOChannel *source,
|
|
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;
|
|
GIOChannel *channel;
|
|
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++) {
|
|
if (self->monitor) {
|
|
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 */
|
|
if (self->monitor) {
|
|
udev_monitor_enable_receiving (self->monitor);
|
|
channel = g_io_channel_unix_new (udev_monitor_get_fd (self->monitor));
|
|
self->watch_source = g_io_create_watch (channel, G_IO_IN);
|
|
g_io_channel_unref (channel);
|
|
g_source_set_callback (self->watch_source, (GSourceFunc)(void (*) (void)) monitor_event, self, NULL);
|
|
g_source_attach (self->watch_source, g_main_context_get_thread_default ());
|
|
g_source_unref (self->watch_source);
|
|
}
|
|
}
|
|
}
|
|
|
|
return self;
|
|
|
|
fail:
|
|
return nm_udev_client_unref (self);
|
|
}
|
|
|
|
NMUdevClient *
|
|
nm_udev_client_unref (NMUdevClient *self)
|
|
{
|
|
if (!self)
|
|
return NULL;
|
|
|
|
if (self->watch_source) {
|
|
g_source_destroy (self->watch_source);
|
|
self->watch_source = NULL;
|
|
}
|
|
|
|
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;
|
|
}
|