/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * 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 of the License, 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: * * Copyright (C) 2016 Velocloud, Inc. * Copyright (C) 2020 Aleksander Morgado */ #define _GNU_SOURCE #include #include #include #define _LIBMM_INSIDE_MM #include #include #include "mm-kernel-device-generic.h" #include "mm-kernel-device-generic-rules.h" #include "mm-kernel-device-helpers.h" #include "mm-log-object.h" #include "mm-utils.h" #if !defined UDEVRULESDIR # error UDEVRULESDIR is not defined #endif static void initable_iface_init (GInitableIface *iface); G_DEFINE_TYPE_EXTENDED (MMKernelDeviceGeneric, mm_kernel_device_generic, MM_TYPE_KERNEL_DEVICE, 0, G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)) enum { PROP_0, PROP_PROPERTIES, PROP_RULES, PROP_LAST }; static GParamSpec *properties[PROP_LAST]; struct _MMKernelDeviceGenericPrivate { /* Input properties */ MMKernelEventProperties *properties; /* Rules to apply */ GArray *rules; /* Contents from sysfs */ gchar **drivers; gchar **subsystems; gchar *sysfs_path; gchar *wwandev_sysfs_path; gchar *interface_sysfs_path; guint8 interface_class; guint8 interface_subclass; guint8 interface_protocol; guint8 interface_number; gchar *interface_description; gchar *physdev_sysfs_path; guint16 physdev_vid; guint16 physdev_pid; guint16 physdev_subsystem_vid; guint16 physdev_revision; gchar *physdev_manufacturer; gchar *physdev_product; }; static gboolean has_sysfs_attribute (const gchar *path, const gchar *attribute) { g_autofree gchar *aux_filepath = NULL; aux_filepath = g_strdup_printf ("%s/%s", path, attribute); return g_file_test (aux_filepath, G_FILE_TEST_EXISTS); } static gchar * read_sysfs_attribute_as_string (const gchar *path, const gchar *attribute) { g_autofree gchar *aux = NULL; gchar *contents = NULL; aux = g_strdup_printf ("%s/%s", path, attribute); if (g_file_get_contents (aux, &contents, NULL, NULL)) { g_strdelimit (contents, "\r\n", ' '); g_strstrip (contents); } return contents; } static guint read_sysfs_attribute_as_hex (const gchar *path, const gchar *attribute) { g_autofree gchar *contents = NULL; guint val = 0; contents = read_sysfs_attribute_as_string (path, attribute); if (contents) mm_get_uint_from_hex_str (contents, &val); return val; } static gchar * read_sysfs_attribute_link_basename (const gchar *path, const gchar *attribute) { g_autofree gchar *aux_filepath = NULL; g_autofree gchar *canonicalized_path = NULL; aux_filepath = g_strdup_printf ("%s/%s", path, attribute); if (!g_file_test (aux_filepath, G_FILE_TEST_EXISTS)) return NULL; canonicalized_path = realpath (aux_filepath, NULL); return g_path_get_basename (canonicalized_path); } static gchar * lookup_sysfs_attribute_as_string (MMKernelDeviceGeneric *self, const gchar *attribute, gboolean iterate) { g_autofree gchar *iter = NULL; /* if there is no parent sysfs path set, we look for the attribute * only in the port sysfs path */ if (!self->priv->physdev_sysfs_path) return read_sysfs_attribute_as_string (self->priv->sysfs_path, attribute); iter = g_strdup (self->priv->sysfs_path); while (iter) { g_autofree gchar *parent = NULL; gchar *value; /* return first one found */ if ((value = read_sysfs_attribute_as_string (iter, attribute)) != NULL) return value; else if (!iterate) break; if (g_strcmp0 (iter, self->priv->physdev_sysfs_path) == 0) break; parent = g_path_get_dirname (iter); g_clear_pointer (&iter, g_free); iter = g_steal_pointer (&parent); } return NULL; } /*****************************************************************************/ /* Load contents */ static void preload_sysfs_path (MMKernelDeviceGeneric *self) { g_autofree gchar *tmp = NULL; if (self->priv->sysfs_path) return; /* sysfs can be built directly using subsystem and name; e.g. for subsystem * usbmisc and name cdc-wdm0: * $ realpath /sys/class/usbmisc/cdc-wdm0 * /sys/devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1.3/4-1.3:1.8/usbmisc/cdc-wdm0 */ tmp = g_strdup_printf ("/sys/class/%s/%s", mm_kernel_event_properties_get_subsystem (self->priv->properties), mm_kernel_event_properties_get_name (self->priv->properties)); self->priv->sysfs_path = realpath (tmp, NULL); if (!self->priv->sysfs_path || !g_file_test (self->priv->sysfs_path, G_FILE_TEST_EXISTS)) { mm_obj_warn (self, "invalid sysfs path read for %s/%s", mm_kernel_event_properties_get_subsystem (self->priv->properties), mm_kernel_event_properties_get_name (self->priv->properties)); g_clear_pointer (&self->priv->sysfs_path, g_free); } if (self->priv->sysfs_path) { const gchar *devpath; mm_obj_dbg (self, "sysfs path: %s", self->priv->sysfs_path); devpath = (g_str_has_prefix (self->priv->sysfs_path, "/sys") ? &self->priv->sysfs_path[4] : self->priv->sysfs_path); g_object_set_data_full (G_OBJECT (self), "DEVPATH", g_strdup (devpath), g_free); } } /*****************************************************************************/ static void preload_common_properties (MMKernelDeviceGeneric *self) { if (self->priv->interface_sysfs_path) { mm_obj_dbg (self, " ID_USB_INTERFACE_NUM: 0x%02x", self->priv->interface_number); g_object_set_data_full (G_OBJECT (self), "ID_USB_INTERFACE_NUM", g_strdup_printf ("%02x", self->priv->interface_number), g_free); } if (self->priv->physdev_product) { mm_obj_dbg (self, " ID_MODEL: %s", self->priv->physdev_product); g_object_set_data_full (G_OBJECT (self), "ID_MODEL", g_strdup (self->priv->physdev_product), g_free); } if (self->priv->physdev_manufacturer) { mm_obj_dbg (self, " ID_VENDOR: %s", self->priv->physdev_manufacturer); g_object_set_data_full (G_OBJECT (self), "ID_VENDOR", g_strdup (self->priv->physdev_manufacturer), g_free); } if (self->priv->physdev_sysfs_path) { mm_obj_dbg (self, " ID_VENDOR_ID: 0x%04x", self->priv->physdev_vid); g_object_set_data_full (G_OBJECT (self), "ID_VENDOR_ID", g_strdup_printf ("%04x", self->priv->physdev_vid), g_free); mm_obj_dbg (self, " ID_MODEL_ID: 0x%04x", self->priv->physdev_pid); g_object_set_data_full (G_OBJECT (self), "ID_MODEL_ID", g_strdup_printf ("%04x", self->priv->physdev_pid), g_free); mm_obj_dbg (self, " ID_REVISION: 0x%04x", self->priv->physdev_revision); g_object_set_data_full (G_OBJECT (self), "ID_REVISION", g_strdup_printf ("%04x", self->priv->physdev_revision), g_free); } } static void ptr_array_add_sysfs_attribute_link_basename (GPtrArray *array, const gchar *sysfs_path, const gchar *attribute, gchar **out_value) { g_autofree gchar *value = NULL; g_assert (array && sysfs_path && attribute); value = read_sysfs_attribute_link_basename (sysfs_path, attribute); if (out_value) *out_value = g_strdup (value); if (value && !g_ptr_array_find_with_equal_func (array, value, g_str_equal, NULL)) g_ptr_array_add (array, g_steal_pointer (&value)); } static void preload_contents_other (MMKernelDeviceGeneric *self) { g_autofree gchar *lower_device_name = NULL; GPtrArray *drivers; GPtrArray *subsystems; /* For any other kind of bus (or the absence of one, as in virtual devices), * assume this is a single port device and don't try to match multiple ports * together. Also, obviously, no vendor, product, revision or interface. */ drivers = g_ptr_array_sized_new (2); ptr_array_add_sysfs_attribute_link_basename (drivers, self->priv->sysfs_path, "driver", NULL); g_ptr_array_add (drivers, NULL); self->priv->drivers = (gchar **) g_ptr_array_free (drivers, FALSE); subsystems = g_ptr_array_sized_new (2); ptr_array_add_sysfs_attribute_link_basename (subsystems, self->priv->sysfs_path, "subsystem", NULL); g_ptr_array_add (subsystems, NULL); self->priv->subsystems = (gchar **) g_ptr_array_free (subsystems, FALSE); /* But look for a lower real physical device, as we may have one */ lower_device_name = mm_kernel_device_get_lower_device_name (self->priv->sysfs_path); if (lower_device_name) { g_autoptr(MMKernelDevice) lower_kernel_device = NULL; g_autoptr(MMKernelEventProperties) props = NULL; g_autoptr(GError) error = NULL; const gchar *subsystem; subsystem = mm_kernel_device_get_subsystem (MM_KERNEL_DEVICE (self)); props = mm_kernel_event_properties_new (); mm_kernel_event_properties_set_subsystem (props, subsystem); mm_kernel_event_properties_set_name (props, lower_device_name); lower_kernel_device = mm_kernel_device_generic_new (props, &error); if (!lower_kernel_device) { mm_obj_dbg (self, "couldn't find lower device: %s/%s", subsystem, lower_device_name); } else { mm_obj_dbg (self, "setting up lower device: %s/%s", subsystem, lower_device_name); g_object_set (self, "lower-device", lower_kernel_device, NULL); } } } static void preload_contents_platform (MMKernelDeviceGeneric *self, const gchar *platform) { g_autofree gchar *iter = NULL; GPtrArray *drivers; GPtrArray *subsystems; drivers = g_ptr_array_sized_new (3); subsystems = g_ptr_array_sized_new (3); iter = g_strdup (self->priv->sysfs_path); while (iter && (g_strcmp0 (iter, "/") != 0)) { gchar *parent; g_autofree gchar *current_subsystem = NULL; ptr_array_add_sysfs_attribute_link_basename (drivers, iter, "driver", NULL); ptr_array_add_sysfs_attribute_link_basename (subsystems, iter, "subsystem", ¤t_subsystem); /* Take first parent with the given platform subsystem as physical device */ current_subsystem = read_sysfs_attribute_link_basename (iter, "subsystem"); if (!self->priv->physdev_sysfs_path && (g_strcmp0 (current_subsystem, platform) == 0)) { self->priv->physdev_sysfs_path = g_strdup (iter); /* stop traversing as soon as the physical device is found */ break; } parent = g_path_get_dirname (iter); g_clear_pointer (&iter, g_free); iter = parent; } g_ptr_array_add (drivers, NULL); self->priv->drivers = (gchar **) g_ptr_array_free (drivers, FALSE); g_ptr_array_add (subsystems, NULL); self->priv->subsystems = (gchar **) g_ptr_array_free (subsystems, FALSE); } static void preload_contents_pcmcia (MMKernelDeviceGeneric *self) { g_autofree gchar *iter = NULL; GPtrArray *drivers; GPtrArray *subsystems; gboolean pcmcia_subsystem_found = FALSE; drivers = g_ptr_array_sized_new (3); subsystems = g_ptr_array_sized_new (3); iter = g_strdup (self->priv->sysfs_path); while (iter && (g_strcmp0 (iter, "/") != 0)) { g_autofree gchar *parent = NULL; g_autofree gchar *parent_subsystem = NULL; g_autofree gchar *current_subsystem = NULL; ptr_array_add_sysfs_attribute_link_basename (drivers, iter, "driver", NULL); ptr_array_add_sysfs_attribute_link_basename (subsystems, iter, "subsystem", ¤t_subsystem); if (g_strcmp0 (current_subsystem, "pcmcia") == 0) pcmcia_subsystem_found = TRUE; parent = g_path_get_dirname (iter); if (parent) parent_subsystem = read_sysfs_attribute_link_basename (parent, "subsystem"); if (pcmcia_subsystem_found && parent_subsystem && (g_strcmp0 (parent_subsystem, "pcmcia") != 0)) { self->priv->physdev_sysfs_path = g_strdup (iter); self->priv->physdev_vid = read_sysfs_attribute_as_hex (self->priv->physdev_sysfs_path, "manf_id"); self->priv->physdev_pid = read_sysfs_attribute_as_hex (self->priv->physdev_sysfs_path, "card_id"); /* stop traversing as soon as the physical device is found */ break; } g_clear_pointer (&iter, g_free); iter = g_steal_pointer (&parent); } g_ptr_array_add (drivers, NULL); self->priv->drivers = (gchar **) g_ptr_array_free (drivers, FALSE); g_ptr_array_add (subsystems, NULL); self->priv->subsystems = (gchar **) g_ptr_array_free (subsystems, FALSE); } static void preload_contents_pci (MMKernelDeviceGeneric *self) { g_autofree gchar *iter = NULL; GPtrArray *drivers; GPtrArray *subsystems; drivers = g_ptr_array_sized_new (4); subsystems = g_ptr_array_sized_new (4); iter = g_strdup (self->priv->sysfs_path); while (iter && (g_strcmp0 (iter, "/") != 0)) { g_autofree gchar *current_subsystem = NULL; gchar *parent; ptr_array_add_sysfs_attribute_link_basename (drivers, iter, "driver", NULL); ptr_array_add_sysfs_attribute_link_basename (subsystems, iter, "subsystem", ¤t_subsystem); /* the PCI channel specific devices have their own drivers and * subsystems, we can rely on the physical device being the first * one that reports the 'pci' subsystem */ if (!self->priv->physdev_sysfs_path && (g_strcmp0 (current_subsystem, "pci") == 0)) { self->priv->physdev_sysfs_path = g_strdup (iter); self->priv->physdev_vid = read_sysfs_attribute_as_hex (self->priv->physdev_sysfs_path, "vendor"); self->priv->physdev_pid = read_sysfs_attribute_as_hex (self->priv->physdev_sysfs_path, "device"); self->priv->physdev_subsystem_vid = read_sysfs_attribute_as_hex (self->priv->physdev_sysfs_path, "subsystem_vendor"); self->priv->physdev_revision = read_sysfs_attribute_as_hex (self->priv->physdev_sysfs_path, "revision"); /* stop traversing as soon as the physical device is found */ break; } parent = g_path_get_dirname (iter); g_clear_pointer (&iter, g_free); iter = parent; } g_ptr_array_add (drivers, NULL); self->priv->drivers = (gchar **) g_ptr_array_free (drivers, FALSE); g_ptr_array_add (subsystems, NULL); self->priv->subsystems = (gchar **) g_ptr_array_free (subsystems, FALSE); } static void preload_contents_usb (MMKernelDeviceGeneric *self) { g_autofree gchar *iter = NULL; GPtrArray *drivers; GPtrArray *subsystems; drivers = g_ptr_array_sized_new (4); subsystems = g_ptr_array_sized_new (4); iter = g_strdup (self->priv->sysfs_path); while (iter && (g_strcmp0 (iter, "/") != 0)) { gchar *parent; ptr_array_add_sysfs_attribute_link_basename (drivers, iter, "driver", NULL); ptr_array_add_sysfs_attribute_link_basename (subsystems, iter, "subsystem", NULL); /* is this the USB interface? */ if (!self->priv->interface_sysfs_path && has_sysfs_attribute (iter, "bInterfaceClass")) { self->priv->interface_sysfs_path = g_strdup (iter); self->priv->interface_class = read_sysfs_attribute_as_hex (self->priv->interface_sysfs_path, "bInterfaceClass"); self->priv->interface_subclass = read_sysfs_attribute_as_hex (self->priv->interface_sysfs_path, "bInterfaceSubClass"); self->priv->interface_protocol = read_sysfs_attribute_as_hex (self->priv->interface_sysfs_path, "bInterfaceProtocol"); self->priv->interface_number = read_sysfs_attribute_as_hex (self->priv->interface_sysfs_path, "bInterfaceNumber"); self->priv->interface_description = read_sysfs_attribute_as_string (self->priv->interface_sysfs_path, "interface"); } /* is this the USB physdev? */ else if (!self->priv->physdev_sysfs_path && has_sysfs_attribute (iter, "idVendor")) { self->priv->physdev_sysfs_path = g_strdup (iter); self->priv->physdev_vid = read_sysfs_attribute_as_hex (self->priv->physdev_sysfs_path, "idVendor"); self->priv->physdev_pid = read_sysfs_attribute_as_hex (self->priv->physdev_sysfs_path, "idProduct"); self->priv->physdev_revision = read_sysfs_attribute_as_hex (self->priv->physdev_sysfs_path, "bcdDevice"); self->priv->physdev_manufacturer = read_sysfs_attribute_as_string (self->priv->physdev_sysfs_path, "manufacturer"); self->priv->physdev_product = read_sysfs_attribute_as_string (self->priv->physdev_sysfs_path, "product"); /* stop traversing as soon as the physical device is found */ break; } parent = g_path_get_dirname (iter); g_clear_pointer (&iter, g_free); iter = parent; } g_ptr_array_add (drivers, NULL); self->priv->drivers = (gchar **) g_ptr_array_free (drivers, FALSE); g_ptr_array_add (subsystems, NULL); self->priv->subsystems = (gchar **) g_ptr_array_free (subsystems, FALSE); } static void preload_contents_wwan (MMKernelDeviceGeneric *self) { g_autofree gchar *iter = NULL; /* Find the first parent device subsystem */ iter = g_path_get_dirname(self->priv->sysfs_path); while (iter && (g_strcmp0 (iter, "/") != 0)) { g_autofree gchar *current_subsystem = NULL; gchar *parent; current_subsystem = read_sysfs_attribute_link_basename (iter, "subsystem"); if (current_subsystem) { if (g_strcmp0 (current_subsystem, "wwan") == 0) self->priv->wwandev_sysfs_path = g_strdup (iter); break; } parent = g_path_get_dirname (iter); g_clear_pointer (&iter, g_free); iter = parent; } } static gchar * find_device_bus_subsystem (MMKernelDeviceGeneric *self) { g_autofree gchar *iter = NULL; iter = g_strdup (self->priv->sysfs_path); while (iter && (g_strcmp0 (iter, "/") != 0)) { g_autofree gchar *subsys = NULL; gchar *parent; subsys = read_sysfs_attribute_link_basename (iter, "subsystem"); /* stop search as soon as we find a parent object * of one of the supported bus subsystems */ if (subsys && ((g_strcmp0 (subsys, "usb") == 0) || (g_strcmp0 (subsys, "pcmcia") == 0) || (g_strcmp0 (subsys, "pci") == 0) || (g_strcmp0 (subsys, "platform") == 0) || (g_strcmp0 (subsys, "pnp") == 0) || (g_strcmp0 (subsys, "sdio") == 0))) return g_steal_pointer (&subsys); parent = g_path_get_dirname (iter); g_clear_pointer (&iter, g_free); iter = parent; } /* no more parents to check */ return NULL; } static void preload_contents (MMKernelDeviceGeneric *self) { g_autofree gchar *bus_subsys = NULL; if (self->priv->sysfs_path) return; preload_sysfs_path (self); if (!self->priv->sysfs_path) return; bus_subsys = find_device_bus_subsystem (self); if (g_strcmp0 (bus_subsys, "usb") == 0) preload_contents_usb (self); else if (g_strcmp0 (bus_subsys, "pcmcia") == 0) preload_contents_pcmcia (self); else if (g_strcmp0 (bus_subsys, "pci") == 0) preload_contents_pci (self); else if ((g_strcmp0 (bus_subsys, "platform") == 0) || (g_strcmp0 (bus_subsys, "pnp") == 0) || (g_strcmp0 (bus_subsys, "sdio") == 0)) preload_contents_platform (self, bus_subsys); else preload_contents_other (self); preload_contents_wwan (self); /* wwan is bus agnostic class */ if (!bus_subsys) return; mm_obj_dbg (self, "port contents loaded:"); mm_obj_dbg (self, " bus: %s", bus_subsys ? bus_subsys : "n/a"); if (self->priv->interface_sysfs_path) { mm_obj_dbg (self, " interface: %s", self->priv->interface_sysfs_path); mm_obj_dbg (self, " interface class: %02x", self->priv->interface_class); mm_obj_dbg (self, " interface subclass: %02x", self->priv->interface_subclass); mm_obj_dbg (self, " interface protocol: %02x", self->priv->interface_protocol); mm_obj_dbg (self, " interface number: %02x", self->priv->interface_number); } if (self->priv->interface_description) mm_obj_dbg (self, " interface description: %s", self->priv->interface_description); if (self->priv->physdev_sysfs_path) mm_obj_dbg (self, " device: %s", self->priv->physdev_sysfs_path); if (self->priv->subsystems) { g_autofree gchar *subsystems_str = NULL; subsystems_str = g_strjoinv (", ", self->priv->subsystems); mm_obj_dbg (self, " subsystems: %s", subsystems_str); } if (self->priv->drivers) { g_autofree gchar *drivers_str = NULL; drivers_str = g_strjoinv (", ", self->priv->drivers); mm_obj_dbg (self, " drivers: %s", drivers_str); } if (self->priv->physdev_vid) mm_obj_dbg (self, " vendor: %04x", self->priv->physdev_vid); if (self->priv->physdev_pid) mm_obj_dbg (self, " product: %04x", self->priv->physdev_pid); if (self->priv->physdev_subsystem_vid) mm_obj_dbg (self, " subsystem vendor: %04x", self->priv->physdev_subsystem_vid); if (self->priv->physdev_revision) mm_obj_dbg (self, " revision: %04x", self->priv->physdev_revision); if (self->priv->physdev_manufacturer) mm_obj_dbg (self, " manufacturer: %s", self->priv->physdev_manufacturer); if (self->priv->physdev_product) mm_obj_dbg (self, " product: %s", self->priv->physdev_product); preload_common_properties (self); } /*****************************************************************************/ static const gchar * kernel_device_get_subsystem (MMKernelDevice *self) { return mm_kernel_event_properties_get_subsystem (MM_KERNEL_DEVICE_GENERIC (self)->priv->properties); } static const gchar * kernel_device_get_name (MMKernelDevice *self) { return mm_kernel_event_properties_get_name (MM_KERNEL_DEVICE_GENERIC (self)->priv->properties); } static const gchar * kernel_device_get_sysfs_path (MMKernelDevice *self) { return MM_KERNEL_DEVICE_GENERIC (self)->priv->sysfs_path; } static const gchar * kernel_device_get_wwandev_sysfs_path (MMKernelDevice *self) { return MM_KERNEL_DEVICE_GENERIC (self)->priv->wwandev_sysfs_path; } static gint kernel_device_get_interface_number (MMKernelDevice *self) { return (gint) MM_KERNEL_DEVICE_GENERIC (self)->priv->interface_number; } static gint kernel_device_get_interface_class (MMKernelDevice *self) { return (gint) MM_KERNEL_DEVICE_GENERIC (self)->priv->interface_class; } static gint kernel_device_get_interface_subclass (MMKernelDevice *self) { return (gint) MM_KERNEL_DEVICE_GENERIC (self)->priv->interface_subclass; } static gint kernel_device_get_interface_protocol (MMKernelDevice *self) { return (gint) MM_KERNEL_DEVICE_GENERIC (self)->priv->interface_protocol; } static const gchar * kernel_device_get_interface_sysfs_path (MMKernelDevice *self) { return MM_KERNEL_DEVICE_GENERIC (self)->priv->interface_sysfs_path; } static const gchar * kernel_device_get_interface_description (MMKernelDevice *self) { return MM_KERNEL_DEVICE_GENERIC (self)->priv->interface_description; } static const gchar * kernel_device_get_physdev_uid (MMKernelDevice *self) { const gchar *uid; /* Prefer the one coming in the properties, if any */ if ((uid = mm_kernel_event_properties_get_uid (MM_KERNEL_DEVICE_GENERIC (self)->priv->properties)) != NULL) return uid; /* Try to load from properties set */ if ((uid = mm_kernel_device_get_property (self, ID_MM_PHYSDEV_UID)) != NULL) return uid; /* Use physical device path, if any */ if (MM_KERNEL_DEVICE_GENERIC (self)->priv->physdev_sysfs_path) return MM_KERNEL_DEVICE_GENERIC (self)->priv->physdev_sysfs_path; /* If there is no physdev sysfs path, e.g. for platform ports, use the device sysfs itself */ return MM_KERNEL_DEVICE_GENERIC (self)->priv->sysfs_path; } static const gchar * kernel_device_get_driver (MMKernelDevice *_self) { MMKernelDeviceGeneric *self = MM_KERNEL_DEVICE_GENERIC (_self); return (self->priv->drivers ? self->priv->drivers[0] : NULL); } static guint16 kernel_device_get_physdev_vid (MMKernelDevice *self) { return MM_KERNEL_DEVICE_GENERIC (self)->priv->physdev_vid; } static guint16 kernel_device_get_physdev_pid (MMKernelDevice *self) { return MM_KERNEL_DEVICE_GENERIC (self)->priv->physdev_pid; } static guint16 kernel_device_get_physdev_subsystem_vid (MMKernelDevice *self) { return MM_KERNEL_DEVICE_GENERIC (self)->priv->physdev_subsystem_vid; } static guint16 kernel_device_get_physdev_revision (MMKernelDevice *self) { return MM_KERNEL_DEVICE_GENERIC (self)->priv->physdev_revision; } static const gchar * kernel_device_get_physdev_sysfs_path (MMKernelDevice *self) { return MM_KERNEL_DEVICE_GENERIC (self)->priv->physdev_sysfs_path; } static const gchar * kernel_device_get_physdev_subsystem (MMKernelDevice *_self) { MMKernelDeviceGeneric *self = MM_KERNEL_DEVICE_GENERIC (_self); guint len; len = (self->priv->subsystems ? g_strv_length (self->priv->subsystems) : 0); return (len > 0 ? self->priv->subsystems[len - 1] : NULL); } static const gchar * kernel_device_get_physdev_manufacturer (MMKernelDevice *self) { return MM_KERNEL_DEVICE_GENERIC (self)->priv->physdev_manufacturer; } static const gchar * kernel_device_get_physdev_product (MMKernelDevice *self) { return MM_KERNEL_DEVICE_GENERIC (self)->priv->physdev_product; } static gboolean kernel_device_cmp (MMKernelDevice *a, MMKernelDevice *b) { return (!g_strcmp0 (mm_kernel_device_get_subsystem (a), mm_kernel_device_get_subsystem (b)) && !g_strcmp0 (mm_kernel_device_get_name (a), mm_kernel_device_get_name (b))); } /*****************************************************************************/ static gboolean check_condition (MMKernelDeviceGeneric *self, MMUdevRuleMatch *match) { gboolean condition_equal; condition_equal = (match->type == MM_UDEV_RULE_MATCH_TYPE_EQUAL); /* We only apply 'add' rules */ if (g_str_equal (match->parameter, "ACTION")) return ((!!strstr (match->value, "add")) == condition_equal); /* Exact SUBSYSTEM match */ if (g_str_equal (match->parameter, "SUBSYSTEM")) return ((self->priv->subsystems && !g_strcmp0 (self->priv->subsystems[0], match->value)) == condition_equal); /* Loose SUBSYSTEMS match */ if (g_str_equal (match->parameter, "SUBSYSTEMS")) return ((self->priv->subsystems && g_strv_contains ((const gchar * const *) self->priv->subsystems, match->value)) == condition_equal); /* Exact DRIVER match */ if (g_str_equal (match->parameter, "DRIVER")) return ((self->priv->drivers && !g_strcmp0 (self->priv->drivers[0], match->value)) == condition_equal); /* Loose DRIVERS match */ if (g_str_equal (match->parameter, "DRIVERS")) return ((self->priv->drivers && g_strv_contains ((const gchar * const *) self->priv->drivers, match->value)) == condition_equal); /* Device name checks */ if (g_str_equal (match->parameter, "KERNEL")) return (mm_kernel_device_generic_string_match (mm_kernel_device_get_name (MM_KERNEL_DEVICE (self)), match->value, self) == condition_equal); /* Device sysfs path checks; we allow both a direct match and a prefix patch */ if (g_str_equal (match->parameter, "DEVPATH")) { g_autofree gchar *prefix_match = NULL; /* If sysfs path invalid (e.g. path doesn't exist), no match */ if (!self->priv->sysfs_path) return FALSE; /* If not already doing a prefix match, do an implicit one. This is so that * we can add properties to the usb_device owning all ports, and then apply * the property to all ports individually processed here. */ if (match->value[0] && match->value[strlen (match->value) - 1] != '*') prefix_match = g_strdup_printf ("%s/*", match->value); if ((mm_kernel_device_generic_string_match (self->priv->sysfs_path, match->value, self) == condition_equal) || (prefix_match && mm_kernel_device_generic_string_match (self->priv->sysfs_path, prefix_match, self) == condition_equal)) return TRUE; if (g_str_has_prefix (self->priv->sysfs_path, "/sys")) { if ((mm_kernel_device_generic_string_match (&self->priv->sysfs_path[4], match->value, self) == condition_equal) || (prefix_match && mm_kernel_device_generic_string_match (&self->priv->sysfs_path[4], prefix_match, self) == condition_equal)) return TRUE; } return FALSE; } /* Attributes checks */ if (g_str_has_prefix (match->parameter, "ATTR")) { gchar *attribute; gchar *contents = NULL; gboolean result = FALSE; guint val; attribute = g_strdup (&match->parameter[5]); g_strdelimit (attribute, "{}", ' '); g_strstrip (attribute); /* VID/PID/SUBSYSTEM VID directly from our API */ if (g_str_equal (attribute, "idVendor") || g_str_equal (attribute, "vendor")) result = ((mm_get_uint_from_hex_str (match->value, &val)) && ((mm_kernel_device_get_physdev_vid (MM_KERNEL_DEVICE (self)) == val) == condition_equal)); else if (g_str_equal (attribute, "idProduct") || g_str_equal (attribute, "device")) result = ((mm_get_uint_from_hex_str (match->value, &val)) && ((mm_kernel_device_get_physdev_pid (MM_KERNEL_DEVICE (self)) == val) == condition_equal)); else if (g_str_equal (attribute, "subsystem_vendor")) result = ((mm_get_uint_from_hex_str (match->value, &val)) && ((mm_kernel_device_get_physdev_subsystem_vid (MM_KERNEL_DEVICE (self)) == val) == condition_equal)); /* manufacturer in the physdev */ else if (g_str_equal (attribute, "manufacturer")) result = ((self->priv->physdev_manufacturer && g_str_equal (self->priv->physdev_manufacturer, match->value)) == condition_equal); /* product in the physdev */ else if (g_str_equal (attribute, "product")) result = ((self->priv->physdev_product && g_str_equal (self->priv->physdev_product, match->value)) == condition_equal); /* interface class/subclass/protocol/number in the interface */ else if (g_str_equal (attribute, "bInterfaceClass")) result = (g_str_equal (match->value, "?*") || ((mm_get_uint_from_hex_str (match->value, &val)) && ((self->priv->interface_class == val) == condition_equal))); else if (g_str_equal (attribute, "bInterfaceSubClass")) result = (g_str_equal (match->value, "?*") || ((mm_get_uint_from_hex_str (match->value, &val)) && ((self->priv->interface_subclass == val) == condition_equal))); else if (g_str_equal (attribute, "bInterfaceProtocol")) result = (g_str_equal (match->value, "?*") || ((mm_get_uint_from_hex_str (match->value, &val)) && ((self->priv->interface_protocol == val) == condition_equal))); else if (g_str_equal (attribute, "bInterfaceNumber")) result = (g_str_equal (match->value, "?*") || ((mm_get_uint_from_hex_str (match->value, &val)) && ((self->priv->interface_number == val) == condition_equal))); else { g_autofree gchar *found_value = NULL; found_value = lookup_sysfs_attribute_as_string (self, attribute, g_str_has_prefix (match->parameter, "ATTRS")); result = ((found_value && g_str_equal (found_value, match->value)) == condition_equal); } g_free (contents); g_free (attribute); return result; } /* Previously set property checks */ if (g_str_has_prefix (match->parameter, "ENV")) { gchar *property; gboolean result = FALSE; property = g_strdup (&match->parameter[3]); g_strdelimit (property, "{}", ' '); g_strstrip (property); result = ((!g_strcmp0 ((const gchar *) g_object_get_data (G_OBJECT (self), property), match->value)) == condition_equal); g_free (property); return result; } mm_obj_warn (self, "unknown match condition parameter: %s", match->parameter); return FALSE; } static guint check_rule (MMKernelDeviceGeneric *self, guint rule_i) { MMUdevRule *rule; gboolean apply = TRUE; g_assert (rule_i < self->priv->rules->len); rule = &g_array_index (self->priv->rules, MMUdevRule, rule_i); if (rule->conditions) { guint condition_i; for (condition_i = 0; condition_i < rule->conditions->len; condition_i++) { MMUdevRuleMatch *match; match = &g_array_index (rule->conditions, MMUdevRuleMatch, condition_i); if (!check_condition (self, match)) { apply = FALSE; break; } } } if (apply) { switch (rule->result.type) { case MM_UDEV_RULE_RESULT_TYPE_PROPERTY: { gchar *property_value_read = NULL; if (g_str_equal (rule->result.content.property.value, "$attr{bInterfaceClass}")) property_value_read = g_strdup_printf ("%02x", self->priv->interface_class); else if (g_str_equal (rule->result.content.property.value, "$attr{bInterfaceSubClass}")) property_value_read = g_strdup_printf ("%02x", self->priv->interface_subclass); else if (g_str_equal (rule->result.content.property.value, "$attr{bInterfaceProtocol}")) property_value_read = g_strdup_printf ("%02x", self->priv->interface_protocol); else if (g_str_equal (rule->result.content.property.value, "$attr{bInterfaceNumber}")) property_value_read = g_strdup_printf ("%02x", self->priv->interface_number); /* add new property */ mm_obj_dbg (self, "property added: %s=%s", rule->result.content.property.name, property_value_read ? property_value_read : rule->result.content.property.value); if (!property_value_read) /* NOTE: we keep a reference to the list of rules ourselves, so it isn't * an issue if we re-use the same string (i.e. without g_strdup-ing it) * as a property value. */ g_object_set_data (G_OBJECT (self), rule->result.content.property.name, rule->result.content.property.value); else g_object_set_data_full (G_OBJECT (self), rule->result.content.property.name, property_value_read, g_free); break; } case MM_UDEV_RULE_RESULT_TYPE_LABEL: /* noop */ break; case MM_UDEV_RULE_RESULT_TYPE_GOTO_INDEX: /* Jump to a new index */ return rule->result.content.index; case MM_UDEV_RULE_RESULT_TYPE_GOTO_TAG: case MM_UDEV_RULE_RESULT_TYPE_UNKNOWN: default: g_assert_not_reached (); } } /* Go to the next rule */ return rule_i + 1; } static void preload_rule_properties (MMKernelDeviceGeneric *self) { guint i; g_assert (self->priv->rules); g_assert (self->priv->rules->len > 0); /* Start to process rules */ i = 0; while (i < self->priv->rules->len) { guint next_rule; next_rule = check_rule (self, i); i = next_rule; } } static void check_preload (MMKernelDeviceGeneric *self) { /* Only preload when properties and rules are set */ if (!self->priv->properties || !self->priv->rules) return; /* Don't preload on "remove" actions, where we don't have the device any more */ if (g_strcmp0 (mm_kernel_event_properties_get_action (self->priv->properties), "remove") == 0) return; /* Don't preload for devices in the 'virtual' subsystem */ if (g_strcmp0 (mm_kernel_event_properties_get_subsystem (self->priv->properties), "virtual") == 0) return; mm_obj_dbg (self, "preloading contents and properties..."); preload_contents (self); preload_rule_properties (self); } static gboolean kernel_device_has_property (MMKernelDevice *self, const gchar *property) { return !!g_object_get_data (G_OBJECT (self), property); } static const gchar * kernel_device_get_property (MMKernelDevice *self, const gchar *property) { return g_object_get_data (G_OBJECT (self), property); } /*****************************************************************************/ static gchar * build_attribute_data_key (const gchar *attribute) { return g_strdup_printf ("ATTR:%s", attribute); } static gboolean kernel_device_has_attribute (MMKernelDevice *self, const gchar *attribute) { return has_sysfs_attribute (MM_KERNEL_DEVICE_GENERIC (self)->priv->sysfs_path, attribute); } static const gchar * kernel_device_get_attribute (MMKernelDevice *_self, const gchar *attribute) { MMKernelDeviceGeneric *self; g_autofree gchar *key = NULL; gchar *value = NULL; self = MM_KERNEL_DEVICE_GENERIC (_self); key = build_attribute_data_key (attribute); value = g_object_get_data (G_OBJECT (self), key); if (!value) { value = read_sysfs_attribute_as_string (self->priv->sysfs_path, attribute); if (value) g_object_set_data_full (G_OBJECT (self), key, value, g_free); } return (const gchar *) value; } /*****************************************************************************/ MMKernelDevice * mm_kernel_device_generic_new_with_rules (MMKernelEventProperties *props, GArray *rules, GError **error) { /* Note: we allow NULL rules, e.g. for virtual devices */ return MM_KERNEL_DEVICE (g_initable_new (MM_TYPE_KERNEL_DEVICE_GENERIC, NULL, error, "properties", props, "rules", rules, NULL)); } MMKernelDevice * mm_kernel_device_generic_new (MMKernelEventProperties *props, GError **error) { static GArray *rules = NULL; /* We only try to load the default list of rules once */ if (G_UNLIKELY (!rules)) { rules = mm_kernel_device_generic_rules_load (UDEVRULESDIR, error); if (!rules) return NULL; } return mm_kernel_device_generic_new_with_rules (props, rules, error); } /*****************************************************************************/ static void mm_kernel_device_generic_init (MMKernelDeviceGeneric *self) { /* Initialize private data */ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_KERNEL_DEVICE_GENERIC, MMKernelDeviceGenericPrivate); } static void set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { MMKernelDeviceGeneric *self = MM_KERNEL_DEVICE_GENERIC (object); switch (prop_id) { case PROP_PROPERTIES: g_assert (!self->priv->properties); self->priv->properties = g_value_dup_object (value); break; case PROP_RULES: g_assert (!self->priv->rules); self->priv->rules = g_value_dup_boxed (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { MMKernelDeviceGeneric *self = MM_KERNEL_DEVICE_GENERIC (object); switch (prop_id) { case PROP_PROPERTIES: g_value_set_object (value, self->priv->properties); break; case PROP_RULES: g_value_set_boxed (value, self->priv->rules); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static gboolean initable_init (GInitable *initable, GCancellable *cancellable, GError **error) { MMKernelDeviceGeneric *self = MM_KERNEL_DEVICE_GENERIC (initable); const gchar *subsystem; check_preload (self); subsystem = mm_kernel_device_get_subsystem (MM_KERNEL_DEVICE (self)); if (!subsystem) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "subsystem is mandatory in kernel device"); return FALSE; } if (!mm_kernel_device_get_name (MM_KERNEL_DEVICE (self))) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "name is mandatory in kernel device"); return FALSE; } /* sysfs path is mandatory as output, and will only be given if the * specified device exists; but only if this wasn't a 'remove' event * and not a virtual device. */ if (self->priv->properties && g_strcmp0 (mm_kernel_event_properties_get_action (self->priv->properties), "remove") && g_strcmp0 (mm_kernel_event_properties_get_subsystem (self->priv->properties), "virtual") && !self->priv->sysfs_path) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "device %s/%s not found", mm_kernel_event_properties_get_subsystem (self->priv->properties), mm_kernel_event_properties_get_name (self->priv->properties)); return FALSE; } return TRUE; } static void dispose (GObject *object) { MMKernelDeviceGeneric *self = MM_KERNEL_DEVICE_GENERIC (object); g_clear_pointer (&self->priv->physdev_product, g_free); g_clear_pointer (&self->priv->physdev_manufacturer, g_free); g_clear_pointer (&self->priv->physdev_sysfs_path, g_free); g_clear_pointer (&self->priv->interface_description, g_free); g_clear_pointer (&self->priv->interface_sysfs_path, g_free); g_clear_pointer (&self->priv->sysfs_path, g_free); g_clear_pointer (&self->priv->drivers, g_strfreev); g_clear_pointer (&self->priv->subsystems, g_strfreev); g_clear_pointer (&self->priv->rules, g_array_unref); g_clear_object (&self->priv->properties); G_OBJECT_CLASS (mm_kernel_device_generic_parent_class)->dispose (object); } static void initable_iface_init (GInitableIface *iface) { iface->init = initable_init; } static void mm_kernel_device_generic_class_init (MMKernelDeviceGenericClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); MMKernelDeviceClass *kernel_device_class = MM_KERNEL_DEVICE_CLASS (klass); g_type_class_add_private (object_class, sizeof (MMKernelDeviceGenericPrivate)); object_class->dispose = dispose; object_class->get_property = get_property; object_class->set_property = set_property; kernel_device_class->get_subsystem = kernel_device_get_subsystem; kernel_device_class->get_name = kernel_device_get_name; kernel_device_class->get_driver = kernel_device_get_driver; kernel_device_class->get_sysfs_path = kernel_device_get_sysfs_path; kernel_device_class->get_wwandev_sysfs_path = kernel_device_get_wwandev_sysfs_path; kernel_device_class->get_physdev_uid = kernel_device_get_physdev_uid; kernel_device_class->get_physdev_vid = kernel_device_get_physdev_vid; kernel_device_class->get_physdev_pid = kernel_device_get_physdev_pid; kernel_device_class->get_physdev_subsystem_vid = kernel_device_get_physdev_subsystem_vid; kernel_device_class->get_physdev_revision = kernel_device_get_physdev_revision; kernel_device_class->get_physdev_sysfs_path = kernel_device_get_physdev_sysfs_path; kernel_device_class->get_physdev_subsystem = kernel_device_get_physdev_subsystem; kernel_device_class->get_physdev_manufacturer = kernel_device_get_physdev_manufacturer; kernel_device_class->get_physdev_product = kernel_device_get_physdev_product; kernel_device_class->get_interface_number = kernel_device_get_interface_number; kernel_device_class->get_interface_class = kernel_device_get_interface_class; kernel_device_class->get_interface_subclass = kernel_device_get_interface_subclass; kernel_device_class->get_interface_protocol = kernel_device_get_interface_protocol; kernel_device_class->get_interface_sysfs_path = kernel_device_get_interface_sysfs_path; kernel_device_class->get_interface_description = kernel_device_get_interface_description; kernel_device_class->cmp = kernel_device_cmp; kernel_device_class->has_property = kernel_device_has_property; kernel_device_class->get_property = kernel_device_get_property; kernel_device_class->has_attribute = kernel_device_has_attribute; kernel_device_class->get_attribute = kernel_device_get_attribute; /* Device-wide properties are stored per-port in the generic backend */ kernel_device_class->has_global_property = kernel_device_has_property; kernel_device_class->get_global_property = kernel_device_get_property; properties[PROP_PROPERTIES] = g_param_spec_object ("properties", "Properties", "Generic kernel event properties", MM_TYPE_KERNEL_EVENT_PROPERTIES, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); g_object_class_install_property (object_class, PROP_PROPERTIES, properties[PROP_PROPERTIES]); properties[PROP_RULES] = g_param_spec_boxed ("rules", "Rules", "List of rules to apply", G_TYPE_ARRAY, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); g_object_class_install_property (object_class, PROP_RULES, properties[PROP_RULES]); }