From 8d5c90a1f4bbe548a1ae361f8d69970669d6e72e Mon Sep 17 00:00:00 2001 From: Tomas Bzatek Date: Fri, 13 Nov 2020 16:52:11 +0100 Subject: [PATCH 1/2] udisksdaemonutil: Refactor udisks_daemon_util_trigger_uevent() out of UDisksLinuxBlockObject This decouples uevent triggering from UDisksLinuxBlockObject as sometimes we don't have a block object yet or it's outdated. --- doc/udisks2-sections.txt.daemon.sections.in | 2 + src/udisksdaemonutil.c | 230 ++++++++++++++++++++ src/udisksdaemonutil.h | 7 + src/udiskslinuxblockobject.c | 180 ++------------- 4 files changed, 253 insertions(+), 166 deletions(-) diff --git a/doc/udisks2-sections.txt.daemon.sections.in b/doc/udisks2-sections.txt.daemon.sections.in index 26c3c2cd..3030fa95 100644 --- a/doc/udisks2-sections.txt.daemon.sections.in +++ b/doc/udisks2-sections.txt.daemon.sections.in @@ -307,6 +307,8 @@ udisks_daemon_util_file_set_contents udisks_daemon_util_on_user_seat udisks_daemon_util_get_free_mdraid_device udisks_ata_identify_get_word +udisks_daemon_util_trigger_uevent +udisks_daemon_util_trigger_uevent_sync
diff --git a/src/udisksdaemonutil.c b/src/udisksdaemonutil.c index 75b877a0..d16c0766 100644 --- a/src/udisksdaemonutil.c +++ b/src/udisksdaemonutil.c @@ -34,10 +34,14 @@ #include #include +#include + #include "udisksdaemon.h" #include "udisksdaemonutil.h" #include "udisksstate.h" #include "udiskslogging.h" +#include "udiskslinuxdevice.h" +#include "udiskslinuxprovider.h" #include "udiskslinuxblockobject.h" #include "udiskslinuxdriveobject.h" @@ -1626,3 +1630,229 @@ udisks_ata_identify_get_word (const guchar *identify_data, guint word_number) out: return ret; } + + +/* ---------------------------------------------------------------------------------------------------- */ + +static volatile guint uevent_serial = 0; + +static gboolean +trigger_uevent (const gchar *path, const gchar *str) +{ + gint fd; + + fd = open (path, O_WRONLY); + if (fd < 0) + { + udisks_warning ("Error opening %s while triggering uevent: %m", path); + return FALSE; + } + + if (write (fd, str, strlen (str)) != (ssize_t) strlen (str)) + { + udisks_warning ("Error writing '%s' to file %s: %m", str, path); + close (fd); + return FALSE; + } + + close (fd); + return TRUE; +} + +typedef struct +{ + UDisksDaemon *daemon; + GMainLoop *main_loop; + guint serial; + gchar *uevent_path; + gboolean success; +} SynthUeventData; + +static gboolean +trigger_uevent_idle_cb (gpointer user_data) +{ + SynthUeventData *data = user_data; + gchar *str; + + str = g_strdup_printf ("change %s UDISKSSERIAL=%u", udisks_daemon_get_uuid (data->daemon), data->serial); + + if (! trigger_uevent (data->uevent_path, str)) + { + /* kernel refused our string, try simple "change" but don't wait for it */ + trigger_uevent (data->uevent_path, "change"); + data->success = FALSE; + g_main_loop_quit (data->main_loop); + } + g_free (str); + + /* remove the source */ + return FALSE; +} + +static gboolean +uevent_wait_timeout_cb (gpointer user_data) +{ + SynthUeventData *data = user_data; + + data->success = FALSE; + g_main_loop_quit (data->main_loop); + + /* remove the source */ + return FALSE; +} + +static void +uevent_probed_cb (UDisksLinuxProvider *provider, + const gchar *action, + UDisksLinuxDevice *device, + gpointer user_data) +{ + SynthUeventData *data = user_data; + const gchar *received_serial_str; + gint64 received_serial; + gchar *endptr; + + received_serial_str = g_udev_device_get_property (device->udev_device, "SYNTH_ARG_UDISKSSERIAL"); + if (received_serial_str != NULL) + { + endptr = (gchar *) received_serial_str; + received_serial = g_ascii_strtoll (received_serial_str, &endptr, 0); + if (endptr != received_serial_str && received_serial == data->serial) + { + data->success = TRUE; + g_main_loop_quit (data->main_loop); + } + } +} + +/** + * udisks_daemon_util_trigger_uevent: + * @daemon: A #UDisksDaemon. + * @device_path: Block device path. + * + * Triggers a 'change' uevent in the kernel. + * + * The triggered event will bubble up from the kernel through the udev + * stack and will eventually be received by the udisks daemon process + * itself. This method does not wait for the event to be received. + */ +void +udisks_daemon_util_trigger_uevent (UDisksDaemon *daemon, + const gchar *device_path) +{ + GUdevClient *gudev_client; + GUdevDevice *gudev_device; + gchar *path; + + g_return_if_fail (UDISKS_IS_DAEMON (daemon)); + g_return_if_fail (device_path != NULL); + + gudev_client = udisks_linux_provider_get_udev_client (udisks_daemon_get_linux_provider (daemon)); + gudev_device = g_udev_client_query_by_device_file (gudev_client, device_path); + if (gudev_device == NULL) + { + udisks_critical ("Device %s not found in udev database, skipping uevent trigger", device_path); + return; + } + + path = g_build_filename (g_udev_device_get_sysfs_path (gudev_device), "uevent", NULL); + trigger_uevent (path, "change"); + g_free (path); + + g_object_unref (gudev_device); +} + +/** + * udisks_daemon_util_trigger_uevent_sync: + * @daemon: A #UDisksDaemon. + * @device_path: Block device path. + * @timeout_seconds: Maximum time to wait for the uevent (in seconds). + * + * Triggers a 'change' uevent in the kernel and waits until it's received and + * processed by udisks. + * + * Unlike udisks_daemon_util_trigger_uevent() that just triggers + * a synthetic uevent to the kernel, this call will actually block and wait until + * the #UDisksLinuxProvider receives the uevent, performs probing and processes + * the uevent further down the UDisks object stack. Upon returning from this + * function call the caller may assume the event has been fully processed, all + * D-Bus objects are updated and settled. Typically used in busy wait for + * a particular D-Bus interface. + * + * Note that this uses synthetic uevent tagging and only works on linux kernel + * 4.13 and higher. In case an older kernel is detected this acts like the classic + * udisks_daemon_util_trigger_uevent() call and %FALSE is returned. + * + * Returns: %TRUE if the uevent has been successfully received, %FALSE otherwise + * or when the kernel version is too old. + */ +gboolean +udisks_daemon_util_trigger_uevent_sync (UDisksDaemon *daemon, + const gchar *device_path, + guint timeout_seconds) +{ + UDisksLinuxProvider *provider; + GUdevClient *gudev_client; + GUdevDevice *gudev_device; + SynthUeventData data; + GMainContext *main_context; + GSource *idle_source; + GSource *timeout_source; + + g_return_val_if_fail (UDISKS_IS_DAEMON (daemon), FALSE); + g_return_val_if_fail (device_path != NULL, FALSE); + + if (bd_utils_check_linux_version (4, 13, 0) < 0) + { + udisks_daemon_util_trigger_uevent (daemon, device_path); + return FALSE; + } + + provider = udisks_daemon_get_linux_provider (daemon); + gudev_client = udisks_linux_provider_get_udev_client (provider); + gudev_device = g_udev_client_query_by_device_file (gudev_client, device_path); + if (gudev_device == NULL) + { + udisks_critical ("Device %s not found in udev database, skipping uevent trigger", device_path); + return FALSE; + } + + data.daemon = daemon; + data.uevent_path = g_build_filename (g_udev_device_get_sysfs_path (gudev_device), "uevent", NULL); + data.serial = g_atomic_int_add (&uevent_serial, 1); + + main_context = g_main_context_new (); + g_main_context_push_thread_default (main_context); + data.main_loop = g_main_loop_new (main_context, FALSE); + + /* queue the actual trigger in the loop */ + idle_source = g_idle_source_new (); + g_source_set_callback (idle_source, (GSourceFunc) trigger_uevent_idle_cb, &data, NULL); + g_source_attach (idle_source, main_context); + g_source_unref (idle_source); + + /* add timeout as a fallback */ + timeout_source = g_timeout_source_new_seconds (timeout_seconds); + g_source_set_callback (timeout_source, (GSourceFunc) uevent_wait_timeout_cb, &data, NULL); + g_source_attach (timeout_source, main_context); + g_source_unref (timeout_source); + + /* catch incoming uevents */ + g_signal_connect (provider, "uevent-probed", G_CALLBACK (uevent_probed_cb), &data); + + data.success = FALSE; + g_main_loop_run (data.main_loop); + + g_signal_handlers_disconnect_by_func (provider, uevent_probed_cb, &data); + g_main_context_pop_thread_default (main_context); + + g_main_loop_unref (data.main_loop); + g_main_context_unref (main_context); + + g_free (data.uevent_path); + g_object_unref (gudev_device); + + return data.success; +} + +/* ---------------------------------------------------------------------------------------------------- */ diff --git a/src/udisksdaemonutil.h b/src/udisksdaemonutil.h index 4fe36214..2edf2122 100644 --- a/src/udisksdaemonutil.h +++ b/src/udisksdaemonutil.h @@ -51,6 +51,13 @@ guint64 udisks_daemon_util_block_get_size (GUdevDevice *device, gboolean *out_media_available, gboolean *out_media_change_detected); +void udisks_daemon_util_trigger_uevent (UDisksDaemon *daemon, + const gchar *device_path); + +gboolean udisks_daemon_util_trigger_uevent_sync (UDisksDaemon *daemon, + const gchar *device_path, + guint timeout_seconds); + gchar *udisks_daemon_util_resolve_link (const gchar *path, const gchar *name); diff --git a/src/udiskslinuxblockobject.c b/src/udiskslinuxblockobject.c index 42ab17d7..5a68c84b 100644 --- a/src/udiskslinuxblockobject.c +++ b/src/udiskslinuxblockobject.c @@ -38,8 +38,6 @@ #include #include -#include - #include "udiskslogging.h" #include "udisksdaemon.h" #include "udisksdaemonutil.h" @@ -959,122 +957,24 @@ on_mount_monitor_mount_removed (UDisksMountMonitor *monitor, /* ---------------------------------------------------------------------------------------------------- */ -static volatile guint uevent_serial = 0; - -static gboolean -trigger_uevent (const gchar *path, const gchar *str) -{ - gint fd; - - fd = open (path, O_WRONLY); - if (fd < 0) - { - udisks_warning ("Error opening %s while triggering uevent: %m", path); - return FALSE; - } - - if (write (fd, str, strlen (str)) != (ssize_t) strlen (str)) - { - udisks_warning ("Error writing '%s' to file %s: %m", str, path); - close (fd); - return FALSE; - } - - close (fd); - return TRUE; -} - -typedef struct -{ - UDisksLinuxBlockObject *object; - GMainLoop *main_loop; - guint serial; - gchar *uevent_path; - gboolean success; -} SynthUeventData; - -static gboolean -trigger_uevent_idle_cb (gpointer user_data) -{ - SynthUeventData *data = user_data; - gchar *str; - - str = g_strdup_printf ("change %s UDISKSSERIAL=%u", udisks_daemon_get_uuid (data->object->daemon), data->serial); - - if (! trigger_uevent (data->uevent_path, str)) - { - /* kernel refused our string, try simple "change" but don't wait for it */ - trigger_uevent (data->uevent_path, "change"); - data->success = FALSE; - g_main_loop_quit (data->main_loop); - } - g_free (str); - - /* remove the source */ - return FALSE; -} - -static gboolean -uevent_wait_timeout_cb (gpointer user_data) -{ - SynthUeventData *data = user_data; - - data->success = FALSE; - g_main_loop_quit (data->main_loop); - - /* remove the source */ - return FALSE; -} - -static void -uevent_probed_cb (UDisksLinuxProvider *provider, - const gchar *action, - UDisksLinuxDevice *device, - gpointer user_data) -{ - SynthUeventData *data = user_data; - const gchar *received_serial_str; - gint64 received_serial; - gchar *endptr; - - received_serial_str = g_udev_device_get_property (device->udev_device, "SYNTH_ARG_UDISKSSERIAL"); - if (received_serial_str != NULL) - { - endptr = (gchar *) received_serial_str; - received_serial = g_ascii_strtoll (received_serial_str, &endptr, 0); - if (endptr != received_serial_str && received_serial == data->serial) - { - data->success = TRUE; - g_main_loop_quit (data->main_loop); - } - } -} - /** * udisks_linux_block_object_trigger_uevent: * @object: A #UDisksLinuxBlockObject. * * Triggers a 'change' uevent in the kernel. * - * The triggered event will bubble up from the kernel through the udev - * stack and will eventually be received by the udisks daemon process - * itself. This method does not wait for the event to be received. + * Refer to udisks_daemon_util_trigger_uevent() for detailed description. */ void udisks_linux_block_object_trigger_uevent (UDisksLinuxBlockObject *object) { - UDisksLinuxDevice *device; - gchar *path; + gchar *device_file; g_return_if_fail (UDISKS_IS_LINUX_BLOCK_OBJECT (object)); - device = udisks_linux_block_object_get_device (object); - path = g_strconcat (g_udev_device_get_sysfs_path (device->udev_device), "/uevent", NULL); - - trigger_uevent (path, "change"); - - g_free (path); - g_object_unref (device); + device_file = udisks_linux_block_object_get_device_file (object); + udisks_daemon_util_trigger_uevent (object->daemon, device_file); + g_free (device_file); } /** @@ -1083,19 +983,10 @@ udisks_linux_block_object_trigger_uevent (UDisksLinuxBlockObject *object) * @timeout_seconds: Maximum time to wait for the uevent (in seconds). * * Triggers a 'change' uevent in the kernel and waits until it's received and - * processed by udisks. - * - * Unlike udisks_linux_block_object_trigger_uevent() that just triggers - * a synthetic uevent to the kernel, this call will actually block and wait until - * the #UDisksLinuxProvider receives the uevent, performs probing and processes - * the uevent further down the UDisks object stack. Upon returning from this - * function call the caller may assume the event has been fully processed, all - * D-Bus objects are updated and settled. Typically used in busy wait for - * a particular D-Bus interface. + * processed through the uevent queue. * - * Note that this uses synthetic uevent tagging and only works on linux kernel - * 4.13 and higher. In case an older kernel is detected this acts like the classic - * udisks_linux_block_object_trigger_uevent() call and %FALSE is returned. + * This is a convenient wrapper around udisks_daemon_util_trigger_uevent_sync(). + * Refer to this function for detailed documentation. * * Returns: %TRUE if the uevent has been successfully received, %FALSE otherwise * or when the kernel version is too old. @@ -1104,59 +995,16 @@ gboolean udisks_linux_block_object_trigger_uevent_sync (UDisksLinuxBlockObject *object, guint timeout_seconds) { - UDisksLinuxDevice *device; - UDisksLinuxProvider *provider; - SynthUeventData data; - GMainContext *main_context; - GSource *idle_source; - GSource *timeout_source; + gchar *device_file; + gboolean ret; g_return_val_if_fail (UDISKS_IS_LINUX_BLOCK_OBJECT (object), FALSE); - if (bd_utils_check_linux_version (4, 13, 0) < 0) - { - udisks_linux_block_object_trigger_uevent (object); - return FALSE; - } - - data.object = object; - device = udisks_linux_block_object_get_device (object); - data.uevent_path = g_strconcat (g_udev_device_get_sysfs_path (device->udev_device), "/uevent", NULL); - data.serial = g_atomic_int_add (&uevent_serial, 1); - - main_context = g_main_context_new (); - g_main_context_push_thread_default (main_context); - data.main_loop = g_main_loop_new (main_context, FALSE); - - /* queue the actual trigger in the loop */ - idle_source = g_idle_source_new (); - g_source_set_callback (idle_source, (GSourceFunc) trigger_uevent_idle_cb, &data, NULL); - g_source_attach (idle_source, main_context); - g_source_unref (idle_source); + device_file = udisks_linux_block_object_get_device_file (object); + ret = udisks_daemon_util_trigger_uevent_sync (object->daemon, device_file, timeout_seconds); + g_free (device_file); - /* add timeout as a fallback */ - timeout_source = g_timeout_source_new_seconds (timeout_seconds); - g_source_set_callback (timeout_source, (GSourceFunc) uevent_wait_timeout_cb, &data, NULL); - g_source_attach (timeout_source, main_context); - g_source_unref (timeout_source); - - /* catch incoming uevents */ - provider = udisks_daemon_get_linux_provider (object->daemon); - g_signal_connect (provider, "uevent-probed", G_CALLBACK (uevent_probed_cb), &data); - - data.success = FALSE; - g_main_loop_run (data.main_loop); - - g_signal_handlers_disconnect_by_func (provider, uevent_probed_cb, &data); - g_main_context_pop_thread_default (main_context); - - g_main_loop_unref (data.main_loop); - g_main_context_unref (main_context); - - g_free (data.uevent_path); - g_object_unref (device); - - return data.success; + return ret; } /* ---------------------------------------------------------------------------------------------------- */ -- 2.26.2