/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuDeviceList" #include "config.h" #include #include #include "fu-device-list.h" #include "fu-device-private.h" #include "fu-mutex.h" #include "fwupd-error.h" /** * SECTION:fu-device-list * @short_description: a list of devices * * This list of devices provides a way to find a device using either the * device-id or a GUID. * * The device list will emit ::added and ::removed signals when the device list * has been changed. If the #FuDevice has changed during a device replug then * the ::changed signal will be emitted instead of ::added and then ::removed. * * See also: #FuDevice */ static void fu_device_list_finalize (GObject *obj); struct _FuDeviceList { GObject parent_instance; GPtrArray *devices; /* of FuDeviceItem */ GRWLock devices_mutex; GMainLoop *replug_loop; /* block waiting for replug */ guint replug_id; /* timeout the loop */ }; enum { SIGNAL_ADDED, SIGNAL_REMOVED, SIGNAL_CHANGED, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = { 0 }; typedef struct { FuDevice *device; FuDevice *device_old; FuDeviceList *self; /* no ref */ guint remove_id; } FuDeviceItem; G_DEFINE_TYPE (FuDeviceList, fu_device_list, G_TYPE_OBJECT) static void fu_device_list_emit_device_added (FuDeviceList *self, FuDevice *device) { g_debug ("::added %s", fu_device_get_id (device)); g_signal_emit (self, signals[SIGNAL_ADDED], 0, device); } static void fu_device_list_emit_device_removed (FuDeviceList *self, FuDevice *device) { g_debug ("::removed %s", fu_device_get_id (device)); g_signal_emit (self, signals[SIGNAL_REMOVED], 0, device); } static void fu_device_list_emit_device_changed (FuDeviceList *self, FuDevice *device) { g_debug ("::changed %s", fu_device_get_id (device)); g_signal_emit (self, signals[SIGNAL_CHANGED], 0, device); } /** * fu_device_list_get_all: * @self: A #FuDeviceList * * Returns all the devices that have been added to the device list. * This includes devices that are no longer active, for instance where a * different plugin has taken over responsibility of the #FuDevice. * * Returns: (transfer container) (element-type FuDevice): the devices * * Since: 1.0.2 **/ GPtrArray * fu_device_list_get_all (FuDeviceList *self) { GPtrArray *devices; g_return_val_if_fail (FU_IS_DEVICE_LIST (self), NULL); devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); g_rw_lock_reader_lock (&self->devices_mutex); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index (self->devices, i); g_ptr_array_add (devices, g_object_ref (item->device)); } for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index (self->devices, i); if (item->device_old == NULL) continue; g_ptr_array_add (devices, g_object_ref (item->device_old)); } g_rw_lock_reader_unlock (&self->devices_mutex); return devices; } /** * fu_device_list_get_active: * @self: A #FuDeviceList * * Returns all the active devices that have been added to the device list. * An active device is defined as a device that is currently connected and has * is owned by a plugin. * * Returns: (transfer container) (element-type FuDevice): the devices * * Since: 1.0.2 **/ GPtrArray * fu_device_list_get_active (FuDeviceList *self) { GPtrArray *devices; g_return_val_if_fail (FU_IS_DEVICE_LIST (self), NULL); devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); g_rw_lock_reader_lock (&self->devices_mutex); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index (self->devices, i); g_ptr_array_add (devices, g_object_ref (item->device)); } g_rw_lock_reader_unlock (&self->devices_mutex); return devices; } static FuDeviceItem * fu_device_list_find_by_device (FuDeviceList *self, FuDevice *device) { g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new (&self->devices_mutex); g_return_val_if_fail (locker != NULL, NULL); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index (self->devices, i); if (item->device == device) return item; } for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index (self->devices, i); if (item->device_old == device) return item; } return NULL; } static FuDeviceItem * fu_device_list_find_by_guid (FuDeviceList *self, const gchar *guid) { g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new (&self->devices_mutex); g_return_val_if_fail (locker != NULL, NULL); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index (self->devices, i); if (fu_device_has_guid (item->device, guid)) return item; } for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index (self->devices, i); if (item->device_old == NULL) continue; if (fu_device_has_guid (item->device_old, guid)) return item; } return NULL; } static FuDeviceItem * fu_device_list_find_by_connection (FuDeviceList *self, const gchar *physical_id, const gchar *logical_id) { g_autoptr(GRWLockReaderLocker) locker = NULL; if (physical_id == NULL) return NULL; locker = g_rw_lock_reader_locker_new (&self->devices_mutex); g_return_val_if_fail (locker != NULL, NULL); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item_tmp = g_ptr_array_index (self->devices, i); FuDevice *device = item_tmp->device; if (device != NULL && g_strcmp0 (fu_device_get_physical_id (device), physical_id) == 0 && g_strcmp0 (fu_device_get_logical_id (device), logical_id) == 0) return item_tmp; } for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item_tmp = g_ptr_array_index (self->devices, i); FuDevice *device = item_tmp->device_old; if (device != NULL && g_strcmp0 (fu_device_get_physical_id (device), physical_id) == 0 && g_strcmp0 (fu_device_get_logical_id (device), logical_id) == 0) return item_tmp; } return NULL; } static FuDeviceItem * fu_device_list_find_by_id (FuDeviceList *self, const gchar *device_id, gboolean *multiple_matches) { FuDeviceItem *item = NULL; gsize device_id_len; /* sanity check */ if (device_id == NULL) { g_critical ("device ID was NULL"); return NULL; } /* support abbreviated hashes */ device_id_len = strlen (device_id); g_rw_lock_reader_lock (&self->devices_mutex); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item_tmp = g_ptr_array_index (self->devices, i); const gchar *ids[] = { fu_device_get_id (item_tmp->device), fu_device_get_equivalent_id (item_tmp->device), NULL }; for (guint j = 0; ids[j] != NULL; j++) { if (strncmp (ids[j], device_id, device_id_len) == 0) { if (item != NULL && multiple_matches != NULL) *multiple_matches = TRUE; item = item_tmp; } } } g_rw_lock_reader_unlock (&self->devices_mutex); if (item != NULL) return item; /* only search old devices if we didn't find the active device */ g_rw_lock_reader_lock (&self->devices_mutex); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item_tmp = g_ptr_array_index (self->devices, i); const gchar *ids[3] = { NULL }; if (item_tmp->device_old == NULL) continue; ids[0] = fu_device_get_id (item_tmp->device_old); ids[1] = fu_device_get_equivalent_id (item_tmp->device_old); for (guint j = 0; ids[j] != NULL; j++) { if (strncmp (ids[j], device_id, device_id_len) == 0) { if (item != NULL && multiple_matches != NULL) *multiple_matches = TRUE; item = item_tmp; } } } g_rw_lock_reader_unlock (&self->devices_mutex); return item; } /** * fu_device_list_get_old: * @self: A #FuDeviceList * @device: A #FuDevice * * Returns the old device associated with the currently active device. * * Returns: (transfer full): the device, or %NULL if not found * * Since: 1.0.3 **/ FuDevice * fu_device_list_get_old (FuDeviceList *self, FuDevice *device) { FuDeviceItem *item = fu_device_list_find_by_device (self, device); if (item == NULL) return NULL; if (item->device_old == NULL) return NULL; return g_object_ref (item->device_old); } static FuDeviceItem * fu_device_list_get_by_guids (FuDeviceList *self, GPtrArray *guids) { g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new (&self->devices_mutex); g_return_val_if_fail (locker != NULL, NULL); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index (self->devices, i); for (guint j = 0; j < guids->len; j++) { const gchar *guid = g_ptr_array_index (guids, j); if (fu_device_has_guid (item->device, guid)) return item; } } for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index (self->devices, i); if (item->device_old == NULL) continue; for (guint j = 0; j < guids->len; j++) { const gchar *guid = g_ptr_array_index (guids, j); if (fu_device_has_guid (item->device_old, guid)) return item; } } return NULL; } static FuDeviceItem * fu_device_list_get_by_guids_removed (FuDeviceList *self, GPtrArray *guids) { g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new (&self->devices_mutex); g_return_val_if_fail (locker != NULL, NULL); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index (self->devices, i); if (item->remove_id == 0) continue; for (guint j = 0; j < guids->len; j++) { const gchar *guid = g_ptr_array_index (guids, j); if (fu_device_has_guid (item->device, guid)) return item; } } for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index (self->devices, i); if (item->device_old == NULL) continue; if (item->remove_id == 0) continue; for (guint j = 0; j < guids->len; j++) { const gchar *guid = g_ptr_array_index (guids, j); if (fu_device_has_guid (item->device_old, guid)) return item; } } return NULL; } static gboolean fu_device_list_device_delayed_remove_cb (gpointer user_data) { FuDeviceItem *item = (FuDeviceItem *) user_data; FuDeviceList *self = FU_DEVICE_LIST (item->self); GPtrArray *children; /* no longer valid */ item->remove_id = 0; /* remove any children associated with device */ children = fu_device_get_children (item->device); for (guint j = 0; j < children->len; j++) { FuDevice *child = g_ptr_array_index (children, j); fu_device_list_emit_device_removed (self, child); } /* just remove now */ g_debug ("doing delayed removal"); fu_device_list_emit_device_removed (self, item->device); g_rw_lock_writer_lock (&self->devices_mutex); g_ptr_array_remove (self->devices, item); g_rw_lock_writer_unlock (&self->devices_mutex); return G_SOURCE_REMOVE; } static void fu_device_list_remove_with_delay (FuDeviceItem *item) { /* we can't do anything with an unconnected device */ fu_device_remove_flag (item->device, FWUPD_DEVICE_FLAG_UPDATABLE); /* give the hardware time to re-enumerate or the user time to * re-insert the device with a magic button pressed */ g_debug ("waiting %ums for %s device removal", fu_device_get_remove_delay (item->device), fu_device_get_name (item->device)); item->remove_id = g_timeout_add (fu_device_get_remove_delay (item->device), fu_device_list_device_delayed_remove_cb, item); } /** * fu_device_list_remove: * @self: A #FuDeviceList * @device: A #FuDevice * * Removes a specific device from the list if it exists. * * If the @device has a remove-delay set then a timeout will be started. If * the exact same #FuDevice is added to the list with fu_device_list_add() * within the timeout then only a ::changed signal will be emitted. * * If there is no remove-delay set, the ::removed signal will be emitted * straight away. * * Since: 1.0.2 **/ void fu_device_list_remove (FuDeviceList *self, FuDevice *device) { FuDeviceItem *item; GPtrArray *children; g_return_if_fail (FU_IS_DEVICE_LIST (self)); g_return_if_fail (FU_IS_DEVICE (device)); /* check the device already exists */ item = fu_device_list_find_by_id (self, fu_device_get_id (device), NULL); if (item == NULL) { g_debug ("device %s not found", fu_device_get_id (device)); return; } /* ensure never fired if the remove delay is changed */ if (item->remove_id > 0) { g_source_remove (item->remove_id); item->remove_id = 0; } /* delay the removal and check for replug */ if (fu_device_get_remove_delay (item->device) > 0) { fu_device_list_remove_with_delay (item); return; } /* remove any children associated with device */ children = fu_device_get_children (device); for (guint j = 0; j < children->len; j++) { FuDevice *child = g_ptr_array_index (children, j); fu_device_list_emit_device_removed (self, child); } /* remove right now */ fu_device_list_emit_device_removed (self, item->device); g_rw_lock_writer_lock (&self->devices_mutex); g_ptr_array_remove (self->devices, item); g_rw_lock_writer_unlock (&self->devices_mutex); } static void fu_device_list_add_missing_guids (FuDevice *device_new, FuDevice *device_old) { GPtrArray *guids_old = fu_device_get_guids (device_old); for (guint i = 0; i < guids_old->len; i++) { const gchar *guid_tmp = g_ptr_array_index (guids_old, i); if (!fu_device_has_guid (device_new, guid_tmp)) { if (fu_device_has_flag (device_new, FWUPD_DEVICE_FLAG_ADD_COUNTERPART_GUIDS)) { g_debug ("adding GUID %s to device", guid_tmp); fu_device_add_counterpart_guid (device_new, guid_tmp); } else { g_debug ("not adding GUID %s to device, use " "FWUPD_DEVICE_FLAG_ADD_COUNTERPART_GUIDS if required", guid_tmp); } } } } static void fu_device_list_item_finalized_cb (gpointer data, GObject *where_the_object_was) { FuDeviceItem *item = (FuDeviceItem *) data; FuDeviceList *self = FU_DEVICE_LIST (item->self); g_critical ("FuDevice %p was finalized without being removed from " "FuDeviceList, removing item!", where_the_object_was); g_rw_lock_writer_lock (&self->devices_mutex); g_ptr_array_remove (self->devices, item); g_rw_lock_writer_unlock (&self->devices_mutex); } /* this should never be required, and yet here we are */ static void fu_device_list_item_set_device (FuDeviceItem *item, FuDevice *device) { if (item->device != NULL) { g_object_weak_unref (G_OBJECT (item->device), fu_device_list_item_finalized_cb, item); } if (device != NULL) { g_object_weak_ref (G_OBJECT (device), fu_device_list_item_finalized_cb, item); } g_set_object (&item->device, device); } static void fu_device_list_replace (FuDeviceList *self, FuDeviceItem *item, FuDevice *device) { /* clear timeout if scheduled */ if (item->remove_id != 0) { g_source_remove (item->remove_id); item->remove_id = 0; } /* copy over any GUIDs that used to exist */ fu_device_list_add_missing_guids (device, item->device); /* enforce the vendor ID if specified */ if (fu_device_get_vendor_id (item->device) != NULL && fu_device_get_vendor_id (device) == NULL) { const gchar *vendor_id = fu_device_get_vendor_id (item->device); g_debug ("copying old vendor ID %s to new device", vendor_id); fu_device_set_vendor_id (device, vendor_id); } /* copy over the version strings if not set */ if (fu_device_get_version (item->device) != NULL && fu_device_get_version (device) == NULL) { const gchar *version = fu_device_get_version (item->device); g_debug ("copying old version %s to new device", version); fu_device_set_version_format (device, fu_device_get_version_format (item->device)); fu_device_set_version (device, version); } /* always use the runtime version */ if (fu_device_has_flag (item->device, FWUPD_DEVICE_FLAG_USE_RUNTIME_VERSION) && fu_device_has_flag (item->device, FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER)) { const gchar *version = fu_device_get_version (item->device); g_debug ("forcing runtime version %s to new device", version); fu_device_set_version_format (device, fu_device_get_version_format (item->device)); fu_device_set_version (device, version); } /* allow another plugin to handle the write too */ if (fu_device_has_flag (item->device, FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED)) { g_debug ("copying another-write-required to new device"); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED); } /* device won't come back in right mode */ if (fu_device_has_flag (item->device, FWUPD_DEVICE_FLAG_WILL_DISAPPEAR)) { g_debug ("copying will-disappear to new device"); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WILL_DISAPPEAR); } /* copy the parent if not already set */ if (fu_device_get_parent (item->device) != NULL && fu_device_get_parent (device) == NULL) { FuDevice *parent = fu_device_get_parent (item->device); g_debug ("copying parent %s to new device", fu_device_get_id (parent)); fu_device_set_parent (device, parent); } /* assign the new device */ g_set_object (&item->device_old, item->device); fu_device_list_item_set_device (item, device); fu_device_list_emit_device_changed (self, device); /* we were waiting for this... */ if (fu_device_has_flag (item->device_old, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG) && g_main_loop_is_running (self->replug_loop)) { g_debug ("quitting replug loop"); fu_device_remove_flag (item->device_old, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); g_main_loop_quit (self->replug_loop); } } /** * fu_device_list_add: * @self: A #FuDeviceList * @device: A #FuDevice * * Adds a specific device to the device list if not already present. * * If the @device (or a compatible @device) has been previously removed within * the remove-timeout then only the ::changed signal will be emitted on calling * this function. Otherwise the ::added signal will be emitted straight away. * * Compatible devices are defined as #FuDevice objects that share at least one * device GUID. If a compatible device is matched then the vendor ID and * version will be copied to the new object if they are not already set. * * Any GUIDs present on the old device and not on the new device will be * inherited and do not have to be copied over by plugins manually. * * Returns: (transfer none): a device, or %NULL if not found * * Since: 1.0.2 **/ void fu_device_list_add (FuDeviceList *self, FuDevice *device) { FuDeviceItem *item; g_return_if_fail (FU_IS_DEVICE_LIST (self)); g_return_if_fail (FU_IS_DEVICE (device)); /* is the device waiting to be replugged? */ item = fu_device_list_find_by_id (self, fu_device_get_id (device), NULL); if (item != NULL && item->remove_id != 0) { g_debug ("found existing device %s, reusing item", fu_device_get_id (item->device)); if (item->remove_id != 0) { g_source_remove (item->remove_id); item->remove_id = 0; } fu_device_list_replace (self, item, device); return; } /* verify the device does not already exist */ if (item != NULL) { g_debug ("device %s already exists, ignoring", fu_device_get_id (item->device)); return; } /* verify a device with same connection does not already exist */ item = fu_device_list_find_by_connection (self, fu_device_get_physical_id (device), fu_device_get_logical_id (device)); if (item != NULL && item->remove_id != 0) { g_debug ("found physical device %s recently removed, reusing " "item from plugin %s for plugin %s", fu_device_get_id (item->device), fu_device_get_plugin (item->device), fu_device_get_plugin (device)); fu_device_list_replace (self, item, device); return; } /* verify a compatible device does not already exist */ item = fu_device_list_get_by_guids_removed (self, fu_device_get_guids (device)); if (item != NULL) { g_debug ("found compatible device %s recently removed, reusing " "item from plugin %s for plugin %s", fu_device_get_id (item->device), fu_device_get_plugin (item->device), fu_device_get_plugin (device)); fu_device_list_replace (self, item, device); return; } /* added the same device, supporting same protocol, from a different plugin */ item = fu_device_list_get_by_guids (self, fu_device_get_guids (device)); if (item != NULL && g_strcmp0 (fu_device_get_plugin (item->device), fu_device_get_plugin (device)) != 0 && g_strcmp0 (fu_device_get_protocol (item->device), fu_device_get_protocol (device)) == 0) { if (fu_device_get_priority (device) < fu_device_get_priority (item->device)) { g_debug ("ignoring device %s [%s] as better device %s [%s] already exists", fu_device_get_id (device), fu_device_get_plugin (device), fu_device_get_id (item->device), fu_device_get_plugin (item->device)); return; } if (fu_device_get_priority (device) == fu_device_get_priority (item->device)) { g_warning ("ignoring device %s [%s] existing device %s [%s] already exists", fu_device_get_id (device), fu_device_get_plugin (device), fu_device_get_id (item->device), fu_device_get_plugin (item->device)); return; } g_debug ("removing device %s [%s] as better device %s [%s] added", fu_device_get_id (item->device), fu_device_get_plugin (item->device), fu_device_get_id (device), fu_device_get_plugin (device)); fu_device_list_remove (self, item->device); } /* add helper */ item = g_new0 (FuDeviceItem, 1); item->self = self; /* no ref */ fu_device_list_item_set_device (item, device); g_rw_lock_writer_lock (&self->devices_mutex); g_ptr_array_add (self->devices, item); g_rw_lock_writer_unlock (&self->devices_mutex); fu_device_list_emit_device_added (self, device); } /** * fu_device_list_get_by_guid: * @self: A #FuDeviceList * @guid: A device GUID * @error: A #GError, or %NULL * * Finds a specific device that has the matching GUID. * * Returns: (transfer full): a device, or %NULL if not found * * Since: 1.0.2 **/ FuDevice * fu_device_list_get_by_guid (FuDeviceList *self, const gchar *guid, GError **error) { FuDeviceItem *item; g_return_val_if_fail (FU_IS_DEVICE_LIST (self), NULL); g_return_val_if_fail (guid != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); item = fu_device_list_find_by_guid (self, guid); if (item != NULL) return g_object_ref (item->device); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "GUID %s was not found", guid); return NULL; } static gboolean fu_device_list_replug_cb (gpointer user_data) { FuDeviceList *self = FU_DEVICE_LIST (user_data); /* no longer valid */ self->replug_id = 0; /* quit loop */ g_debug ("device did not replug"); g_main_loop_quit (self->replug_loop); return FALSE; } /** * fu_device_list_wait_for_replug: * @self: A #FuDeviceList * @device: A #FuDevice * @error: A #GError, or %NULL * * Waits for a specific device to replug if %FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG * is set. * * If the device does not exist this function returns without an error. * * Returns: %TRUE for success * * Since: 1.1.2 **/ gboolean fu_device_list_wait_for_replug (FuDeviceList *self, FuDevice *device, GError **error) { FuDeviceItem *item; guint remove_delay; g_return_val_if_fail (FU_IS_DEVICE_LIST (self), FALSE); g_return_val_if_fail (FU_IS_DEVICE (device), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); g_return_val_if_fail (self->replug_id == 0, FALSE); /* not found */ item = fu_device_list_find_by_device (self, device); if (item == NULL) return TRUE; /* not required, or possibly literally just happened */ if (!fu_device_has_flag (item->device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG)) { g_debug ("no replug or re-enumerate required"); return TRUE; } /* check that no other devices are waiting for replug too */ for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item_tmp = g_ptr_array_index (self->devices, i); if (item_tmp->device != device && fu_device_has_flag (item_tmp->device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG)) { g_warning ("%s is wait-for-replug when %s scheduled, unsetting", fu_device_get_id (item_tmp->device), fu_device_get_id (device)); fu_device_remove_flag (item_tmp->device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); } } /* plugin did not specify */ remove_delay = fu_device_get_remove_delay (device); if (remove_delay == 0) { remove_delay = FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE; g_warning ("plugin %s did not specify a remove delay for %s, " "so guessing we should wait %ums for replug", fu_device_get_plugin (device), fu_device_get_id (device), remove_delay); } else { g_debug ("waiting %ums for replug", remove_delay); } /* time to unplug and then re-plug */ self->replug_id = g_timeout_add (remove_delay, fu_device_list_replug_cb, self); g_main_loop_run (self->replug_loop); /* cancel timeout if still pending */ if (self->replug_id != 0) { g_source_remove (self->replug_id); self->replug_id = 0; } /* device was not added back to the device list */ if (fu_device_has_flag (item->device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "device %s did not come back", fu_device_get_id (device)); fu_device_remove_flag (item->device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return FALSE; } /* check that no other devices are waiting for replug instead */ for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item_tmp = g_ptr_array_index (self->devices, i); if (fu_device_has_flag (item_tmp->device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG)) { g_warning ("%s is wait-for-replug when %s performed", fu_device_get_id (item_tmp->device), fu_device_get_id (device)); } } /* the loop was quit without the timer */ g_debug ("waited for replug"); return TRUE; } /** * fu_device_list_get_by_id: * @self: A #FuDeviceList * @device_id: A device ID, typically a SHA1 hash * @error: A #GError, or %NULL * * Finds a specific device using the ID string. This function also supports * using abbreviated hashes. * * Returns: (transfer full): a device, or %NULL if not found * * Since: 1.0.2 **/ FuDevice * fu_device_list_get_by_id (FuDeviceList *self, const gchar *device_id, GError **error) { FuDeviceItem *item; gboolean multiple_matches = FALSE; g_return_val_if_fail (FU_IS_DEVICE_LIST (self), NULL); g_return_val_if_fail (device_id != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* multiple things matched */ item = fu_device_list_find_by_id (self, device_id, &multiple_matches); if (multiple_matches) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device ID %s was not unique", device_id); return NULL; } /* nothing at all matched */ if (item == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "device ID %s was not found", device_id); return NULL; } /* something found */ return g_object_ref (item->device); } static void fu_device_list_item_free (FuDeviceItem *item) { if (item->remove_id != 0) g_source_remove (item->remove_id); if (item->device_old != NULL) g_object_unref (item->device_old); fu_device_list_item_set_device (item, NULL); g_free (item); } static void fu_device_list_class_init (FuDeviceListClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_device_list_finalize; signals[SIGNAL_ADDED] = g_signal_new ("added", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); signals[SIGNAL_REMOVED] = g_signal_new ("removed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); signals[SIGNAL_CHANGED] = g_signal_new ("changed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); } static void fu_device_list_init (FuDeviceList *self) { self->devices = g_ptr_array_new_with_free_func ((GDestroyNotify) fu_device_list_item_free); self->replug_loop = g_main_loop_new (NULL, FALSE); g_rw_lock_init (&self->devices_mutex); } static void fu_device_list_finalize (GObject *obj) { FuDeviceList *self = FU_DEVICE_LIST (obj); if (self->replug_id != 0) g_source_remove (self->replug_id); g_ptr_array_unref (self->devices); g_main_loop_unref (self->replug_loop); g_rw_lock_clear (&self->devices_mutex); G_OBJECT_CLASS (fu_device_list_parent_class)->finalize (obj); } /** * fu_device_list_new: * * Creates a new device list. * * Returns: (transfer full): a #FuDeviceList * * Since: 1.0.2 **/ FuDeviceList * fu_device_list_new (void) { FuDeviceList *self; self = g_object_new (FU_TYPE_DEVICE_LIST, NULL); return FU_DEVICE_LIST (self); }