mirror of
https://github.com/systemd/systemd.git
synced 2024-11-05 15:21:37 +03:00
529 lines
16 KiB
C
529 lines
16 KiB
C
/* -*- Mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
|
|
*
|
|
* Copyright (C) 2008-2010 David Zeuthen <davidz@redhat.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "gudevclient.h"
|
|
#include "gudevdevice.h"
|
|
#include "gudevmarshal.h"
|
|
#include "gudevprivate.h"
|
|
|
|
/**
|
|
* SECTION:gudevclient
|
|
* @short_description: Query devices and listen to uevents
|
|
*
|
|
* #GUdevClient is used to query information about devices on a Linux
|
|
* system from the Linux kernel and the udev device
|
|
* manager.
|
|
*
|
|
* Device information is retrieved from the kernel (through the
|
|
* <literal>sysfs</literal> filesystem) and the udev daemon (through a
|
|
* <literal>tmpfs</literal> filesystem) and presented through
|
|
* #GUdevDevice objects. This means that no blocking IO ever happens
|
|
* (in both cases, we are essentially just reading data from kernel
|
|
* memory) and as such there are no asynchronous versions of the
|
|
* provided methods.
|
|
*
|
|
* To get #GUdevDevice objects, use
|
|
* g_udev_client_query_by_subsystem(),
|
|
* g_udev_client_query_by_device_number(),
|
|
* g_udev_client_query_by_device_file(),
|
|
* g_udev_client_query_by_sysfs_path(),
|
|
* g_udev_client_query_by_subsystem_and_name()
|
|
* or the #GUdevEnumerator type.
|
|
*
|
|
* To listen to uevents, connect to the #GUdevClient::uevent signal.
|
|
*/
|
|
|
|
struct _GUdevClientPrivate
|
|
{
|
|
GSource *watch_source;
|
|
struct udev *udev;
|
|
struct udev_monitor *monitor;
|
|
|
|
gchar **subsystems;
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_SUBSYSTEMS,
|
|
};
|
|
|
|
enum
|
|
{
|
|
UEVENT_SIGNAL,
|
|
LAST_SIGNAL,
|
|
};
|
|
|
|
static guint signals[LAST_SIGNAL] = { 0 };
|
|
|
|
G_DEFINE_TYPE (GUdevClient, g_udev_client, G_TYPE_OBJECT)
|
|
|
|
/* ---------------------------------------------------------------------------------------------------- */
|
|
|
|
static gboolean
|
|
monitor_event (GIOChannel *source,
|
|
GIOCondition condition,
|
|
gpointer data)
|
|
{
|
|
GUdevClient *client = (GUdevClient *) data;
|
|
GUdevDevice *device;
|
|
struct udev_device *udevice;
|
|
|
|
if (client->priv->monitor == NULL)
|
|
goto out;
|
|
udevice = udev_monitor_receive_device (client->priv->monitor);
|
|
if (udevice == NULL)
|
|
goto out;
|
|
|
|
device = _g_udev_device_new (udevice);
|
|
udev_device_unref (udevice);
|
|
g_signal_emit (client,
|
|
signals[UEVENT_SIGNAL],
|
|
0,
|
|
g_udev_device_get_action (device),
|
|
device);
|
|
g_object_unref (device);
|
|
|
|
out:
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
g_udev_client_finalize (GObject *object)
|
|
{
|
|
GUdevClient *client = G_UDEV_CLIENT (object);
|
|
|
|
if (client->priv->watch_source != NULL)
|
|
{
|
|
g_source_destroy (client->priv->watch_source);
|
|
client->priv->watch_source = NULL;
|
|
}
|
|
|
|
if (client->priv->monitor != NULL)
|
|
{
|
|
udev_monitor_unref (client->priv->monitor);
|
|
client->priv->monitor = NULL;
|
|
}
|
|
|
|
if (client->priv->udev != NULL)
|
|
{
|
|
udev_unref (client->priv->udev);
|
|
client->priv->udev = NULL;
|
|
}
|
|
|
|
g_strfreev (client->priv->subsystems);
|
|
|
|
if (G_OBJECT_CLASS (g_udev_client_parent_class)->finalize != NULL)
|
|
G_OBJECT_CLASS (g_udev_client_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
g_udev_client_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GUdevClient *client = G_UDEV_CLIENT (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_SUBSYSTEMS:
|
|
if (client->priv->subsystems != NULL)
|
|
g_strfreev (client->priv->subsystems);
|
|
client->priv->subsystems = g_strdupv (g_value_get_boxed (value));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
g_udev_client_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GUdevClient *client = G_UDEV_CLIENT (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_SUBSYSTEMS:
|
|
g_value_set_boxed (value, client->priv->subsystems);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
g_udev_client_constructed (GObject *object)
|
|
{
|
|
GUdevClient *client = G_UDEV_CLIENT (object);
|
|
GIOChannel *channel;
|
|
guint n;
|
|
|
|
client->priv->udev = udev_new ();
|
|
|
|
/* connect to event source */
|
|
client->priv->monitor = udev_monitor_new_from_netlink (client->priv->udev, "udev");
|
|
|
|
//g_debug ("ss = %p", client->priv->subsystems);
|
|
|
|
if (client->priv->subsystems != NULL)
|
|
{
|
|
/* install subsystem filters to only wake up for certain events */
|
|
for (n = 0; client->priv->subsystems[n] != NULL; n++)
|
|
{
|
|
gchar *subsystem;
|
|
gchar *devtype;
|
|
gchar *s;
|
|
|
|
subsystem = g_strdup (client->priv->subsystems[n]);
|
|
devtype = NULL;
|
|
|
|
//g_debug ("s = '%s'", subsystem);
|
|
|
|
s = strstr (subsystem, "/");
|
|
if (s != NULL)
|
|
{
|
|
devtype = s + 1;
|
|
*s = '\0';
|
|
}
|
|
|
|
if (client->priv->monitor != NULL)
|
|
udev_monitor_filter_add_match_subsystem_devtype (client->priv->monitor, subsystem, devtype);
|
|
|
|
g_free (subsystem);
|
|
}
|
|
|
|
/* listen to events, and buffer them */
|
|
if (client->priv->monitor != NULL)
|
|
{
|
|
udev_monitor_enable_receiving (client->priv->monitor);
|
|
channel = g_io_channel_unix_new (udev_monitor_get_fd (client->priv->monitor));
|
|
client->priv->watch_source = g_io_create_watch (channel, G_IO_IN);
|
|
g_io_channel_unref (channel);
|
|
g_source_set_callback (client->priv->watch_source, (GSourceFunc) monitor_event, client, NULL);
|
|
g_source_attach (client->priv->watch_source, g_main_context_get_thread_default ());
|
|
g_source_unref (client->priv->watch_source);
|
|
}
|
|
else
|
|
{
|
|
client->priv->watch_source = NULL;
|
|
}
|
|
}
|
|
|
|
if (G_OBJECT_CLASS (g_udev_client_parent_class)->constructed != NULL)
|
|
G_OBJECT_CLASS (g_udev_client_parent_class)->constructed (object);
|
|
}
|
|
|
|
|
|
static void
|
|
g_udev_client_class_init (GUdevClientClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = (GObjectClass *) klass;
|
|
|
|
gobject_class->constructed = g_udev_client_constructed;
|
|
gobject_class->set_property = g_udev_client_set_property;
|
|
gobject_class->get_property = g_udev_client_get_property;
|
|
gobject_class->finalize = g_udev_client_finalize;
|
|
|
|
/**
|
|
* GUdevClient:subsystems:
|
|
*
|
|
* The subsystems to listen for uevents on.
|
|
*
|
|
* To listen for only a specific DEVTYPE for a given SUBSYSTEM, use
|
|
* "subsystem/devtype". For example, to only listen for uevents
|
|
* where SUBSYSTEM is usb and DEVTYPE is usb_interface, use
|
|
* "usb/usb_interface".
|
|
*
|
|
* If this property is %NULL, then no events will be reported. If
|
|
* it's the empty array, events from all subsystems will be
|
|
* reported.
|
|
*/
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_SUBSYSTEMS,
|
|
g_param_spec_boxed ("subsystems",
|
|
"The subsystems to listen for changes on",
|
|
"The subsystems to listen for changes on",
|
|
G_TYPE_STRV,
|
|
G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_READWRITE));
|
|
|
|
/**
|
|
* GUdevClient::uevent:
|
|
* @client: The #GUdevClient receiving the event.
|
|
* @action: The action for the uevent e.g. "add", "remove", "change", "move", etc.
|
|
* @device: Details about the #GUdevDevice the event is for.
|
|
*
|
|
* Emitted when @client receives an uevent.
|
|
*
|
|
* This signal is emitted in the
|
|
* <link linkend="g-main-context-push-thread-default">thread-default main loop</link>
|
|
* of the thread that @client was created in.
|
|
*/
|
|
signals[UEVENT_SIGNAL] = g_signal_new ("uevent",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GUdevClientClass, uevent),
|
|
NULL,
|
|
NULL,
|
|
g_udev_marshal_VOID__STRING_OBJECT,
|
|
G_TYPE_NONE,
|
|
2,
|
|
G_TYPE_STRING,
|
|
G_UDEV_TYPE_DEVICE);
|
|
|
|
g_type_class_add_private (klass, sizeof (GUdevClientPrivate));
|
|
}
|
|
|
|
static void
|
|
g_udev_client_init (GUdevClient *client)
|
|
{
|
|
client->priv = G_TYPE_INSTANCE_GET_PRIVATE (client,
|
|
G_UDEV_TYPE_CLIENT,
|
|
GUdevClientPrivate);
|
|
}
|
|
|
|
/**
|
|
* g_udev_client_new:
|
|
* @subsystems: (array zero-terminated=1) (element-type utf8) (transfer none) (allow-none): A %NULL terminated string array of subsystems to listen for uevents on, %NULL to not listen on uevents at all, or an empty array to listen to uevents on all subsystems. See the documentation for the #GUdevClient:subsystems property for details on this parameter.
|
|
*
|
|
* Constructs a #GUdevClient object that can be used to query
|
|
* information about devices. Connect to the #GUdevClient::uevent
|
|
* signal to listen for uevents. Note that signals are emitted in the
|
|
* <link linkend="g-main-context-push-thread-default">thread-default main loop</link>
|
|
* of the thread that you call this constructor from.
|
|
*
|
|
* Returns: A new #GUdevClient object. Free with g_object_unref().
|
|
*/
|
|
GUdevClient *
|
|
g_udev_client_new (const gchar * const *subsystems)
|
|
{
|
|
return G_UDEV_CLIENT (g_object_new (G_UDEV_TYPE_CLIENT, "subsystems", subsystems, NULL));
|
|
}
|
|
|
|
/**
|
|
* g_udev_client_query_by_subsystem:
|
|
* @client: A #GUdevClient.
|
|
* @subsystem: (allow-none): The subsystem to get devices for or %NULL to get all devices.
|
|
*
|
|
* Gets all devices belonging to @subsystem.
|
|
*
|
|
* Returns: (element-type GUdevDevice) (transfer full): A list of #GUdevDevice objects. The caller should free the result by using g_object_unref() on each element in the list and then g_list_free() on the list.
|
|
*/
|
|
GList *
|
|
g_udev_client_query_by_subsystem (GUdevClient *client,
|
|
const gchar *subsystem)
|
|
{
|
|
struct udev_enumerate *enumerate;
|
|
struct udev_list_entry *l, *devices;
|
|
GList *ret;
|
|
|
|
g_return_val_if_fail (G_UDEV_IS_CLIENT (client), NULL);
|
|
|
|
ret = NULL;
|
|
|
|
/* prepare a device scan */
|
|
enumerate = udev_enumerate_new (client->priv->udev);
|
|
|
|
/* filter for subsystem */
|
|
if (subsystem != NULL)
|
|
udev_enumerate_add_match_subsystem (enumerate, subsystem);
|
|
/* retrieve the list */
|
|
udev_enumerate_scan_devices (enumerate);
|
|
|
|
/* add devices to the list */
|
|
devices = udev_enumerate_get_list_entry (enumerate);
|
|
for (l = devices; l != NULL; l = udev_list_entry_get_next (l))
|
|
{
|
|
struct udev_device *udevice;
|
|
GUdevDevice *device;
|
|
|
|
udevice = udev_device_new_from_syspath (udev_enumerate_get_udev (enumerate),
|
|
udev_list_entry_get_name (l));
|
|
if (udevice == NULL)
|
|
continue;
|
|
device = _g_udev_device_new (udevice);
|
|
udev_device_unref (udevice);
|
|
ret = g_list_prepend (ret, device);
|
|
}
|
|
udev_enumerate_unref (enumerate);
|
|
|
|
ret = g_list_reverse (ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* g_udev_client_query_by_device_number:
|
|
* @client: A #GUdevClient.
|
|
* @type: A value from the #GUdevDeviceType enumeration.
|
|
* @number: A device number.
|
|
*
|
|
* Looks up a device for a type and device number.
|
|
*
|
|
* Returns: (transfer full): A #GUdevDevice object or %NULL if the device was not found. Free with g_object_unref().
|
|
*/
|
|
GUdevDevice *
|
|
g_udev_client_query_by_device_number (GUdevClient *client,
|
|
GUdevDeviceType type,
|
|
GUdevDeviceNumber number)
|
|
{
|
|
struct udev_device *udevice;
|
|
GUdevDevice *device;
|
|
|
|
g_return_val_if_fail (G_UDEV_IS_CLIENT (client), NULL);
|
|
|
|
device = NULL;
|
|
udevice = udev_device_new_from_devnum (client->priv->udev, type, number);
|
|
|
|
if (udevice == NULL)
|
|
goto out;
|
|
|
|
device = _g_udev_device_new (udevice);
|
|
udev_device_unref (udevice);
|
|
|
|
out:
|
|
return device;
|
|
}
|
|
|
|
/**
|
|
* g_udev_client_query_by_device_file:
|
|
* @client: A #GUdevClient.
|
|
* @device_file: A device file.
|
|
*
|
|
* Looks up a device for a device file.
|
|
*
|
|
* Returns: (transfer full): A #GUdevDevice object or %NULL if the device was not found. Free with g_object_unref().
|
|
*/
|
|
GUdevDevice *
|
|
g_udev_client_query_by_device_file (GUdevClient *client,
|
|
const gchar *device_file)
|
|
{
|
|
struct stat stat_buf;
|
|
GUdevDevice *device;
|
|
|
|
g_return_val_if_fail (G_UDEV_IS_CLIENT (client), NULL);
|
|
g_return_val_if_fail (device_file != NULL, NULL);
|
|
|
|
device = NULL;
|
|
|
|
if (stat (device_file, &stat_buf) != 0)
|
|
goto out;
|
|
|
|
if (stat_buf.st_rdev == 0)
|
|
goto out;
|
|
|
|
if (S_ISBLK (stat_buf.st_mode))
|
|
device = g_udev_client_query_by_device_number (client, G_UDEV_DEVICE_TYPE_BLOCK, stat_buf.st_rdev);
|
|
else if (S_ISCHR (stat_buf.st_mode))
|
|
device = g_udev_client_query_by_device_number (client, G_UDEV_DEVICE_TYPE_CHAR, stat_buf.st_rdev);
|
|
|
|
out:
|
|
return device;
|
|
}
|
|
|
|
/**
|
|
* g_udev_client_query_by_sysfs_path:
|
|
* @client: A #GUdevClient.
|
|
* @sysfs_path: A sysfs path.
|
|
*
|
|
* Looks up a device for a sysfs path.
|
|
*
|
|
* Returns: (transfer full): A #GUdevDevice object or %NULL if the device was not found. Free with g_object_unref().
|
|
*/
|
|
GUdevDevice *
|
|
g_udev_client_query_by_sysfs_path (GUdevClient *client,
|
|
const gchar *sysfs_path)
|
|
{
|
|
struct udev_device *udevice;
|
|
GUdevDevice *device;
|
|
|
|
g_return_val_if_fail (G_UDEV_IS_CLIENT (client), NULL);
|
|
g_return_val_if_fail (sysfs_path != NULL, NULL);
|
|
|
|
device = NULL;
|
|
udevice = udev_device_new_from_syspath (client->priv->udev, sysfs_path);
|
|
if (udevice == NULL)
|
|
goto out;
|
|
|
|
device = _g_udev_device_new (udevice);
|
|
udev_device_unref (udevice);
|
|
|
|
out:
|
|
return device;
|
|
}
|
|
|
|
/**
|
|
* g_udev_client_query_by_subsystem_and_name:
|
|
* @client: A #GUdevClient.
|
|
* @subsystem: A subsystem name.
|
|
* @name: The name of the device.
|
|
*
|
|
* Looks up a device for a subsystem and name.
|
|
*
|
|
* Returns: (transfer full): A #GUdevDevice object or %NULL if the device was not found. Free with g_object_unref().
|
|
*/
|
|
GUdevDevice *
|
|
g_udev_client_query_by_subsystem_and_name (GUdevClient *client,
|
|
const gchar *subsystem,
|
|
const gchar *name)
|
|
{
|
|
struct udev_device *udevice;
|
|
GUdevDevice *device;
|
|
|
|
g_return_val_if_fail (G_UDEV_IS_CLIENT (client), NULL);
|
|
g_return_val_if_fail (subsystem != NULL, NULL);
|
|
g_return_val_if_fail (name != NULL, NULL);
|
|
|
|
device = NULL;
|
|
udevice = udev_device_new_from_subsystem_sysname (client->priv->udev, subsystem, name);
|
|
if (udevice == NULL)
|
|
goto out;
|
|
|
|
device = _g_udev_device_new (udevice);
|
|
udev_device_unref (udevice);
|
|
|
|
out:
|
|
return device;
|
|
}
|
|
|
|
struct udev *
|
|
_g_udev_client_get_udev (GUdevClient *client)
|
|
{
|
|
g_return_val_if_fail (G_UDEV_IS_CLIENT (client), NULL);
|
|
return client->priv->udev;
|
|
}
|
|
|