diff options
Diffstat (limited to 'src/kerneldevice/mm-kernel-device-generic.c')
-rw-r--r-- | src/kerneldevice/mm-kernel-device-generic.c | 772 |
1 files changed, 755 insertions, 17 deletions
diff --git a/src/kerneldevice/mm-kernel-device-generic.c b/src/kerneldevice/mm-kernel-device-generic.c index a6dbc2d7..32ac5ed1 100644 --- a/src/kerneldevice/mm-kernel-device-generic.c +++ b/src/kerneldevice/mm-kernel-device-generic.c @@ -22,8 +22,13 @@ #include <libmm-glib.h> #include "mm-kernel-device-generic.h" +#include "mm-kernel-device-generic-rules.h" #include "mm-log.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, @@ -32,6 +37,7 @@ G_DEFINE_TYPE_EXTENDED (MMKernelDeviceGeneric, mm_kernel_device_generic, MM_TYP enum { PROP_0, PROP_PROPERTIES, + PROP_RULES, PROP_LAST }; @@ -40,8 +46,330 @@ static GParamSpec *properties[PROP_LAST]; struct _MMKernelDeviceGenericPrivate { /* Input properties */ MMKernelEventProperties *properties; + /* Rules to apply */ + GArray *rules; + + /* Contents from sysfs */ + gchar *driver; + gchar *sysfs_path; + gchar *interface_sysfs_path; + guint8 interface_class; + guint8 interface_subclass; + guint8 interface_protocol; + guint8 interface_number; + gchar *physdev_sysfs_path; + guint16 physdev_vid; + guint16 physdev_pid; + gchar *physdev_manufacturer; + gchar *physdev_product; }; +static guint +read_sysfs_property_as_hex (const gchar *path, + const gchar *property) +{ + gchar *aux; + gchar *contents = NULL; + guint val = 0; + + aux = g_strdup_printf ("%s/%s", path, property); + if (g_file_get_contents (aux, &contents, NULL, NULL)) { + g_strdelimit (contents, "\r\n", ' '); + g_strstrip (contents); + mm_get_uint_from_hex_str (contents, &val); + } + g_free (contents); + g_free (aux); + return val; +} + +static gchar * +read_sysfs_property_as_string (const gchar *path, + const gchar *property) +{ + gchar *aux; + gchar *contents = NULL; + + aux = g_strdup_printf ("%s/%s", path, property); + if (g_file_get_contents (aux, &contents, NULL, NULL)) { + g_strdelimit (contents, "\r\n", ' '); + g_strstrip (contents); + } + g_free (aux); + return contents; +} + +/*****************************************************************************/ +/* Load contents */ + +static void +preload_sysfs_path (MMKernelDeviceGeneric *self) +{ + gchar *tmp; + + 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 = canonicalize_file_name (tmp); + if (!self->priv->sysfs_path || !g_file_test (self->priv->sysfs_path, G_FILE_TEST_EXISTS)) { + mm_warn ("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) + mm_dbg ("(%s/%s) sysfs path: %s", + mm_kernel_event_properties_get_subsystem (self->priv->properties), + mm_kernel_event_properties_get_name (self->priv->properties), + self->priv->sysfs_path); + g_free (tmp); +} + +static void +preload_interface_sysfs_path (MMKernelDeviceGeneric *self) +{ + gchar *dirpath; + gchar *aux; + + if (self->priv->interface_sysfs_path || !self->priv->sysfs_path) + return; + + /* parent sysfs can be built directly using subsystem and name; e.g. for + * subsystem usbmisc and name cdc-wdm0: + * $ realpath /sys/class/usbmisc/cdc-wdm0/device + * /sys/devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1.3/4-1.3:1.8 + * + * This sysfs path will be equal for all devices exported from within the + * same interface (e.g. a pair of cdc-wdm/wwan devices). + * + * The correct parent dir we want to have is the first one with "usb" subsystem. + */ + aux = g_strdup_printf ("%s/device", self->priv->sysfs_path); + dirpath = canonicalize_file_name (aux); + g_free (aux); + + while (dirpath) { + gchar *subsystem_filepath; + + /* Directory must exist */ + if (!g_file_test (dirpath, G_FILE_TEST_EXISTS)) + break; + + /* If subsystem file not found, keep looping */ + subsystem_filepath = g_strdup_printf ("%s/subsystem", dirpath); + if (g_file_test (subsystem_filepath, G_FILE_TEST_EXISTS)) { + gchar *canonicalized_subsystem; + gchar *subsystem_name; + + canonicalized_subsystem = canonicalize_file_name (subsystem_filepath); + g_free (subsystem_filepath); + + subsystem_name = g_path_get_basename (canonicalized_subsystem); + g_free (canonicalized_subsystem); + + if (subsystem_name && g_str_equal (subsystem_name, "usb")) { + self->priv->interface_sysfs_path = dirpath; + g_free (subsystem_name); + break; + } + } else + g_free (subsystem_filepath); + + /* Just in case */ + if (g_str_equal (dirpath, "/")) { + g_free (dirpath); + break; + } + + aux = g_path_get_dirname (dirpath); + g_free (dirpath); + dirpath = aux; + } + + if (self->priv->interface_sysfs_path) + mm_dbg ("(%s/%s) interface sysfs path: %s", + mm_kernel_event_properties_get_subsystem (self->priv->properties), + mm_kernel_event_properties_get_name (self->priv->properties), + self->priv->interface_sysfs_path); +} + +static void +preload_physdev_sysfs_path (MMKernelDeviceGeneric *self) +{ + /* physdev sysfs is the dirname of the parent sysfs path, e.g.: + * /sys/devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1.3 + * + * This sysfs path will be equal for all devices exported from the same + * physical device. + */ + if (!self->priv->physdev_sysfs_path && self->priv->interface_sysfs_path) + self->priv->physdev_sysfs_path = g_path_get_dirname (self->priv->interface_sysfs_path); + + if (self->priv->physdev_sysfs_path) + mm_dbg ("(%s/%s) physdev sysfs path: %s", + mm_kernel_event_properties_get_subsystem (self->priv->properties), + mm_kernel_event_properties_get_name (self->priv->properties), + self->priv->physdev_sysfs_path); +} + +static void +preload_driver (MMKernelDeviceGeneric *self) +{ + if (!self->priv->driver && self->priv->interface_sysfs_path) { + gchar *tmp; + gchar *tmp2; + + tmp = g_strdup_printf ("%s/driver", self->priv->interface_sysfs_path); + tmp2 = canonicalize_file_name (tmp); + if (tmp2 && g_file_test (tmp2, G_FILE_TEST_EXISTS)) + self->priv->driver = g_path_get_basename (tmp2); + g_free (tmp2); + g_free (tmp); + } + + if (self->priv->driver) + mm_dbg ("(%s/%s) driver: %s", + mm_kernel_event_properties_get_subsystem (self->priv->properties), + mm_kernel_event_properties_get_name (self->priv->properties), + self->priv->driver); +} + +static void +preload_physdev_vid (MMKernelDeviceGeneric *self) +{ + if (!self->priv->physdev_vid && self->priv->physdev_sysfs_path) { + guint val; + + val = read_sysfs_property_as_hex (self->priv->physdev_sysfs_path, "idVendor"); + if (val && val <= G_MAXUINT16) + self->priv->physdev_vid = val; + } + + if (self->priv->physdev_vid) + mm_dbg ("(%s/%s) vid: 0x%04x", + mm_kernel_event_properties_get_subsystem (self->priv->properties), + mm_kernel_event_properties_get_name (self->priv->properties), + self->priv->physdev_vid); + else + mm_dbg ("(%s/%s) vid: unknown", + mm_kernel_event_properties_get_subsystem (self->priv->properties), + mm_kernel_event_properties_get_name (self->priv->properties)); + +} + +static void +preload_physdev_pid (MMKernelDeviceGeneric *self) +{ + if (!self->priv->physdev_pid && self->priv->physdev_sysfs_path) { + guint val; + + val = read_sysfs_property_as_hex (self->priv->physdev_sysfs_path, "idProduct"); + if (val && val <= G_MAXUINT16) + self->priv->physdev_pid = val; + } + + if (self->priv->physdev_pid) + mm_dbg ("(%s/%s) pid: 0x%04x", + mm_kernel_event_properties_get_subsystem (self->priv->properties), + mm_kernel_event_properties_get_name (self->priv->properties), + self->priv->physdev_pid); + else + mm_dbg ("(%s/%s) pid: unknown", + mm_kernel_event_properties_get_subsystem (self->priv->properties), + mm_kernel_event_properties_get_name (self->priv->properties)); +} + +static void +preload_manufacturer (MMKernelDeviceGeneric *self) +{ + if (!self->priv->physdev_manufacturer) + self->priv->physdev_manufacturer = (self->priv->physdev_sysfs_path ? read_sysfs_property_as_string (self->priv->physdev_sysfs_path, "manufacturer") : NULL); + + mm_dbg ("(%s/%s) manufacturer: %s", + mm_kernel_event_properties_get_subsystem (self->priv->properties), + mm_kernel_event_properties_get_name (self->priv->properties), + self->priv->physdev_manufacturer ? self->priv->physdev_manufacturer : "unknown"); +} + +static void +preload_product (MMKernelDeviceGeneric *self) +{ + if (!self->priv->physdev_product) + self->priv->physdev_product = (self->priv->physdev_sysfs_path ? read_sysfs_property_as_string (self->priv->physdev_sysfs_path, "product") : NULL); + + mm_dbg ("(%s/%s) product: %s", + mm_kernel_event_properties_get_subsystem (self->priv->properties), + mm_kernel_event_properties_get_name (self->priv->properties), + self->priv->physdev_product ? self->priv->physdev_product : "unknown"); +} + +static void +preload_interface_class (MMKernelDeviceGeneric *self) +{ + self->priv->interface_class = (self->priv->interface_sysfs_path ? read_sysfs_property_as_hex (self->priv->interface_sysfs_path, "bInterfaceClass") : 0x00); + mm_dbg ("(%s/%s) interface class: 0x%02x", + mm_kernel_event_properties_get_subsystem (self->priv->properties), + mm_kernel_event_properties_get_name (self->priv->properties), + self->priv->interface_class); +} + +static void +preload_interface_subclass (MMKernelDeviceGeneric *self) +{ + self->priv->interface_subclass = (self->priv->interface_sysfs_path ? read_sysfs_property_as_hex (self->priv->interface_sysfs_path, "bInterfaceSubClass") : 0x00); + mm_dbg ("(%s/%s) interface subclass: 0x%02x", + mm_kernel_event_properties_get_subsystem (self->priv->properties), + mm_kernel_event_properties_get_name (self->priv->properties), + self->priv->interface_subclass); +} + +static void +preload_interface_protocol (MMKernelDeviceGeneric *self) +{ + self->priv->interface_protocol = (self->priv->interface_sysfs_path ? read_sysfs_property_as_hex (self->priv->interface_sysfs_path, "bInterfaceProtocol") : 0x00); + mm_dbg ("(%s/%s) interface protocol: 0x%02x", + mm_kernel_event_properties_get_subsystem (self->priv->properties), + mm_kernel_event_properties_get_name (self->priv->properties), + self->priv->interface_protocol); +} + +static void +preload_interface_number (MMKernelDeviceGeneric *self) +{ + self->priv->interface_number = (self->priv->interface_sysfs_path ? read_sysfs_property_as_hex (self->priv->interface_sysfs_path, "bInterfaceNumber") : 0x00); + mm_dbg ("(%s/%s) interface number: 0x%02x", + mm_kernel_event_properties_get_subsystem (self->priv->properties), + mm_kernel_event_properties_get_name (self->priv->properties), + self->priv->interface_number); +} + +static void +preload_contents (MMKernelDeviceGeneric *self) +{ + preload_sysfs_path (self); + preload_interface_sysfs_path (self); + preload_interface_class (self); + preload_interface_subclass (self); + preload_interface_protocol (self); + preload_interface_number (self); + preload_physdev_sysfs_path (self); + preload_manufacturer (self); + preload_product (self); + preload_driver (self); + preload_physdev_vid (self); + preload_physdev_pid (self); +} + /*****************************************************************************/ static const gchar * @@ -65,7 +393,7 @@ kernel_device_get_sysfs_path (MMKernelDevice *self) { g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), NULL); - return NULL; + return MM_KERNEL_DEVICE_GENERIC (self)->priv->sysfs_path; } static const gchar * @@ -73,16 +401,30 @@ kernel_device_get_parent_sysfs_path (MMKernelDevice *self) { g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), NULL); - return NULL; + return MM_KERNEL_DEVICE_GENERIC (self)->priv->interface_sysfs_path; } static const gchar * kernel_device_get_physdev_uid (MMKernelDevice *self) { + const gchar *uid; + g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), NULL); /* Prefer the one coming in the properties, if any */ - return mm_kernel_event_properties_get_uid (MM_KERNEL_DEVICE_GENERIC (self)->priv->properties); + 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 * @@ -90,7 +432,7 @@ kernel_device_get_driver (MMKernelDevice *self) { g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), NULL); - return NULL; + return MM_KERNEL_DEVICE_GENERIC (self)->priv->driver; } static guint16 @@ -98,7 +440,7 @@ kernel_device_get_physdev_vid (MMKernelDevice *self) { g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), 0); - return 0; + return MM_KERNEL_DEVICE_GENERIC (self)->priv->physdev_vid; } static guint16 @@ -106,15 +448,59 @@ kernel_device_get_physdev_pid (MMKernelDevice *self) { g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), 0); - return 0; + return MM_KERNEL_DEVICE_GENERIC (self)->priv->physdev_pid; } static gboolean kernel_device_is_candidate (MMKernelDevice *_self, gboolean manual_scan) { + MMKernelDeviceGeneric *self; + const gchar *name; + g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (_self), FALSE); + self = MM_KERNEL_DEVICE_GENERIC (_self); + + name = mm_kernel_event_properties_get_name (self->priv->properties); + + /* ignore VTs */ + if (strncmp (name, "tty", 3) == 0 && g_ascii_isdigit (name[3])) { + mm_dbg ("(%s/%s) VT ignored", + mm_kernel_event_properties_get_subsystem (self->priv->properties), + mm_kernel_event_properties_get_name (self->priv->properties)); + return FALSE; + } + + /* only ports tagged as candidate */ + if (!mm_kernel_device_get_property_as_boolean (_self, "ID_MM_CANDIDATE")) { + mm_dbg ("(%s/%s) device not flagged with ID_MM_CANDIDATE", + mm_kernel_event_properties_get_subsystem (self->priv->properties), + mm_kernel_event_properties_get_name (self->priv->properties)); + return FALSE; + } + + /* no devices without physical device */ + if (!self->priv->physdev_sysfs_path) { + mm_dbg ("(%s/%s) device without physdev sysfs path", + mm_kernel_event_properties_get_subsystem (self->priv->properties), + mm_kernel_event_properties_get_name (self->priv->properties)); + return FALSE; + } + + /* ignore ports explicitly ignored; note that in this case the property + * is set in this kernel device itself, unlike in the udev backend, that + * goes in the parent udev device */ + if (mm_kernel_device_get_property_as_boolean (_self, "ID_MM_DEVICE_IGNORE")) { + mm_dbg ("(%s/%s) device flagged with ID_MM_DEVICE_IGNORE", + mm_kernel_event_properties_get_subsystem (self->priv->properties), + mm_kernel_event_properties_get_name (self->priv->properties)); + return FALSE; + } + + mm_dbg ("(%s/%s) device is candidate", + mm_kernel_event_properties_get_subsystem (self->priv->properties), + mm_kernel_event_properties_get_name (self->priv->properties)); return TRUE; } @@ -129,13 +515,305 @@ kernel_device_cmp (MMKernelDevice *a, !g_strcmp0 (mm_kernel_device_get_name (a), mm_kernel_device_get_name (b))); } +/*****************************************************************************/ + +static gboolean +string_match (const gchar *str, + const gchar *original_pattern) +{ + gchar *pattern; + gchar *start; + gboolean open_prefix = FALSE; + gboolean open_suffix = FALSE; + gboolean match; + + pattern = g_strdup (original_pattern); + start = pattern; + + if (start[0] == '*') { + open_prefix = TRUE; + start++; + } + + if (start[strlen (start) - 1] == '*') { + open_suffix = TRUE; + start[strlen (start) - 1] = '\0'; + } + + if (open_suffix && !open_prefix) + match = g_str_has_prefix (str, start); + else if (!open_suffix && open_prefix) + match = g_str_has_suffix (str, start); + else if (open_suffix && open_prefix) + match = !!strstr (str, start); + else + match = g_str_equal (str, start); + + g_free (pattern); + return match; +} + +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); + + /* We look for the subsystem string in the whole sysfs path. + * + * Note that we're not really making a difference between "SUBSYSTEMS" + * (where the whole device tree is checked) and "SUBSYSTEM" (where just one + * single device is checked), because a lot of the MM udev rules are meant + * to just tag the physical device (e.g. with ID_MM_DEVICE_IGNORE) instead + * of the single ports. In our case with the custom parsing, we do tag all + * independent ports. + */ + if (g_str_equal (match->parameter, "SUBSYSTEMS") || g_str_equal (match->parameter, "SUBSYSTEM")) + return ((self->priv->sysfs_path && !!strstr (self->priv->sysfs_path, match->value)) == condition_equal); + + /* Exact DRIVER match? We also include the check for DRIVERS, even if we + * only apply it to this port driver. */ + if (g_str_equal (match->parameter, "DRIVER") || g_str_equal (match->parameter, "DRIVERS")) + return ((!g_strcmp0 (match->value, mm_kernel_device_get_driver (MM_KERNEL_DEVICE (self)))) == condition_equal); + + /* Device name checks */ + if (g_str_equal (match->parameter, "KERNEL")) + return (string_match (mm_kernel_device_get_name (MM_KERNEL_DEVICE (self)), match->value) == condition_equal); + + /* Device sysfs path checks; we allow both a direct match and a prefix patch */ + if (g_str_equal (match->parameter, "DEVPATH")) { + const gchar *sysfs_path; + gchar *prefix_match = NULL; + gboolean result = FALSE; + + sysfs_path = mm_kernel_device_get_sysfs_path (MM_KERNEL_DEVICE (self)); + + /* 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 (string_match (sysfs_path, match->value) == condition_equal) { + result = TRUE; + goto out; + } + + if (prefix_match && string_match (sysfs_path, prefix_match) == condition_equal) { + result = TRUE; + goto out; + } + + if (g_str_has_prefix (sysfs_path, "/sys")) { + if (string_match (&sysfs_path[4], match->value) == condition_equal) { + result = TRUE; + goto out; + } + if (prefix_match && string_match (&sysfs_path[4], prefix_match) == condition_equal) { + result = TRUE; + goto out; + } + } + out: + g_free (prefix_match); + return result; + } + + /* Attributes checks */ + if (g_str_has_prefix (match->parameter, "ATTRS")) { + gchar *attribute; + gchar *contents = NULL; + gboolean result = FALSE; + guint val; + + attribute = g_strdup (&match->parameter[5]); + g_strdelimit (attribute, "{}", ' '); + g_strstrip (attribute); + + /* VID/PID directly from our API */ + if (g_str_equal (attribute, "idVendor")) + 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")) + result = ((mm_get_uint_from_hex_str (match->value, &val)) && + ((mm_kernel_device_get_physdev_pid (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 + mm_warn ("Unknown attribute: %s", attribute); + + 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_warn ("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_dbg ("(%s/%s) property added: %s=%s", + mm_kernel_event_properties_get_subsystem (self->priv->properties), + mm_kernel_event_properties_get_name (self->priv->properties), + 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: + g_assert_not_reached (); + } + } + + /* Go to the next rule */ + return rule_i + 1; +} + +static void +preload_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_dbg ("(%s/%s) preloading contents and properties...", + mm_kernel_event_properties_get_subsystem (self->priv->properties), + mm_kernel_event_properties_get_name (self->priv->properties)); + preload_contents (self); + preload_properties (self); +} + static gboolean kernel_device_has_property (MMKernelDevice *self, const gchar *property) { g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), FALSE); - return FALSE; + return !!g_object_get_data (G_OBJECT (self), property); } static const gchar * @@ -144,42 +822,70 @@ kernel_device_get_property (MMKernelDevice *self, { g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), NULL); - return NULL; + return g_object_get_data (G_OBJECT (self), property); } static gboolean kernel_device_get_property_as_boolean (MMKernelDevice *self, const gchar *property) { + const gchar *value; + g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), FALSE); - return FALSE; + value = g_object_get_data (G_OBJECT (self), property); + return (value && mm_common_get_boolean_from_string (value, NULL)); } static gint kernel_device_get_property_as_int (MMKernelDevice *self, const gchar *property) { + const gchar *value; + gint aux = 0; + g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), -1); - return 0; + value = g_object_get_data (G_OBJECT (self), property); + return ((value && mm_get_int_from_str (value, &aux)) ? aux : 0); } /*****************************************************************************/ MMKernelDevice * -mm_kernel_device_generic_new (MMKernelEventProperties *properties, - GError **error) +mm_kernel_device_generic_new_with_rules (MMKernelEventProperties *properties, + GArray *rules, + GError **error) { g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (properties), NULL); + g_return_val_if_fail (rules != NULL, NULL); return MM_KERNEL_DEVICE (g_initable_new (MM_TYPE_KERNEL_DEVICE_GENERIC, NULL, error, "properties", properties, + "rules", rules, NULL)); } +MMKernelDevice * +mm_kernel_device_generic_new (MMKernelEventProperties *properties, + GError **error) +{ + static GArray *rules = NULL; + + g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (properties), 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 (properties, rules, error); +} + /*****************************************************************************/ static void @@ -201,6 +907,12 @@ set_property (GObject *object, case PROP_PROPERTIES: g_assert (!self->priv->properties); self->priv->properties = g_value_dup_object (value); + check_preload (self); + break; + case PROP_RULES: + g_assert (!self->priv->rules); + self->priv->rules = g_value_dup_boxed (value); + check_preload (self); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -220,6 +932,9 @@ get_property (GObject *object, 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; @@ -241,15 +956,24 @@ initable_init (GInitable *initable, return FALSE; } - if (!g_str_equal (subsystem, "virtual")) { + if (!mm_kernel_device_get_name (MM_KERNEL_DEVICE (self))) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, - "only virtual subsystem supported"); + "name is mandatory in kernel device"); return FALSE; } - if (!mm_kernel_device_get_name (MM_KERNEL_DEVICE (self))) { + /* 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, - "name is mandatory in kernel device"); + "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; } @@ -261,7 +985,13 @@ dispose (GObject *object) { MMKernelDeviceGeneric *self = MM_KERNEL_DEVICE_GENERIC (object); - g_clear_object (&self->priv->properties); + 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_sysfs_path, g_free); + g_clear_pointer (&self->priv->sysfs_path, g_free); + 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); } @@ -306,4 +1036,12 @@ mm_kernel_device_generic_class_init (MMKernelDeviceGenericClass *klass) 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]); } |