/* -*- 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 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++) { 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); 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; }