NetworkManager/shared/nm-utils/nm-udev-utils.c
Beniamino Galvani 5780a2893b udev: increase receive buffer size
With the default 128KiB buffer size it is easy to lose events. For
example when 64 interfaces appear at the same time, we lose events for
the last 16. Increase the buffer size to 4MiB.

https://bugzilla.redhat.com/show_bug.cgi?id=1651578
2018-12-12 14:18:53 +01:00

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