/* -*- Mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- * * SPDX-FileCopyrightText: 2008-2010 David Zeuthen * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include "gudevclient.h" #include "gudevdevice.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 * sysfs filesystem) and the udev daemon (through a * tmpfs 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_WITH_CODE (GUdevClient, g_udev_client, G_TYPE_OBJECT, G_ADD_PRIVATE(GUdevClient)) /* ---------------------------------------------------------------------------------------------------- */ 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", * "online" or "offline" * @device: Details about the #GUdevDevice the event is for. * * Emitted when @client receives an uevent. * * Note that while you'll have access to all the device's properties and attributes * for the majority of actions, only the sysfs path will be available when the device * is removed. * * Also note that the action is an arbitrary string, controlled by device drivers. Other * values than those listed is possible, but unlikely. * * This signal is emitted in the * thread-default main loop * 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_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_STRING, G_UDEV_TYPE_DEVICE); } static void g_udev_client_init (GUdevClient *client) { client->priv = g_udev_client_get_instance_private (client); } /** * 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 * thread-default main loop * 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: (nullable) (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: (nullable) (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: (nullable) (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: (nullable) (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: (nullable) (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_enumerate * _g_udev_client_new_enumerate (GUdevClient *client) { struct udev_enumerate *enumerate; enumerate = udev_enumerate_new (client->priv->udev); if (client->priv->subsystems != NULL) { guint n; for (n = 0; client->priv->subsystems[n] != NULL; n++) { gchar *subsystem; gchar *devtype; gchar *s; subsystem = g_strdup (client->priv->subsystems[n]); devtype = NULL; s = strstr (subsystem, "/"); if (s != NULL) { devtype = s + 1; *s = '\0'; } udev_enumerate_add_match_subsystem (enumerate, subsystem); if (devtype != NULL) udev_enumerate_add_match_property (enumerate, "DEVTYPE", devtype); g_free (subsystem); } } return enumerate; }