From 8d9fc8a777fb1b00ae371e276a14416a0987f4af Mon Sep 17 00:00:00 2001 From: David Zeuthen Date: Mon, 13 Apr 2009 14:20:15 -0400 Subject: [PATCH 11/13] =?utf-8?q?Bug=20576083=20=E2=80=93=20pre-unmount=20signals=20not=20being=20triggered?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Basically emit GVolumeMonitor::mount-pre-unmount on the volume monitor and retry unmount operation a couple of times. --- monitor/gdu/ggdumount.c | 177 +++++++++++++++++++++++++++++++++++++--------- 1 files changed, 142 insertions(+), 35 deletions(-) diff --git a/monitor/gdu/ggdumount.c b/monitor/gdu/ggdumount.c index e074a20..78088d5 100644 --- a/monitor/gdu/ggdumount.c +++ b/monitor/gdu/ggdumount.c @@ -38,6 +38,9 @@ #include "ggdumount.h" #include "ggduvolume.h" +#define BUSY_UNMOUNT_NUM_ATTEMPTS 5 +#define BUSY_UNMOUNT_MS_DELAY_BETWEEN_ATTEMPTS 500 + struct _GGduMount { GObject parent; @@ -451,26 +454,52 @@ g_gdu_mount_can_eject (GMount *_mount) typedef struct { GMount *mount; + + gchar **argv; + guint num_attempts_left; + GAsyncReadyCallback callback; gpointer user_data; GCancellable *cancellable; + int error_fd; GIOChannel *error_channel; guint error_channel_source_id; GString *error_string; -} UnmountEjectOp; + +} UnmountOp; + +static gboolean unmount_attempt (UnmountOp *data); static void -eject_unmount_cb (GPid pid, gint status, gpointer user_data) +unmount_cb (GPid pid, gint status, gpointer user_data) { - UnmountEjectOp *data = user_data; + UnmountOp *data = user_data; GSimpleAsyncResult *simple; + g_spawn_close_pid (pid); + if (WEXITSTATUS (status) != 0) { GError *error; + gint error_code; + + error_code = G_IO_ERROR_FAILED; + /* we may want to add more strstr() checks here depending on what unmount helper is being used etc... */ + if (data->error_string->str != NULL && strstr (data->error_string->str, "is busy") != NULL) + error_code = G_IO_ERROR_BUSY; + + if (error_code == G_IO_ERROR_BUSY && data->num_attempts_left > 0) + { + g_timeout_add (BUSY_UNMOUNT_MS_DELAY_BETWEEN_ATTEMPTS, + (GSourceFunc) unmount_attempt, + data); + data->num_attempts_left -= 1; + goto out; + } + error = g_error_new_literal (G_IO_ERROR, - G_IO_ERROR_FAILED, + error_code, data->error_string->str); simple = g_simple_async_result_new_from_error (G_OBJECT (data->mount), data->callback, @@ -493,16 +522,19 @@ eject_unmount_cb (GPid pid, gint status, gpointer user_data) g_io_channel_unref (data->error_channel); g_string_free (data->error_string, TRUE); close (data->error_fd); - g_spawn_close_pid (pid); + g_strfreev (data->argv); g_free (data); + + out: + ; } static gboolean -eject_unmount_read_error (GIOChannel *channel, - GIOCondition condition, - gpointer user_data) +unmount_read_error (GIOChannel *channel, + GIOCondition condition, + gpointer user_data) { - UnmountEjectOp *data = user_data; + UnmountOp *data = user_data; gchar buf[BUFSIZ]; gsize bytes_read; GError *error; @@ -532,26 +564,15 @@ read: return TRUE; } -static void -eject_unmount_do (GMount *mount, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data, - char **argv) +static gboolean +unmount_attempt (UnmountOp *data) { - UnmountEjectOp *data; GPid child_pid; GError *error; - data = g_new0 (UnmountEjectOp, 1); - data->mount = mount; - data->callback = callback; - data->user_data = user_data; - data->cancellable = cancellable; - error = NULL; if (!g_spawn_async_with_pipes (NULL, /* working dir */ - argv, + data->argv, NULL, /* envp */ G_SPAWN_DO_NOT_REAP_CHILD|G_SPAWN_SEARCH_PATH, NULL, /* child_setup */ @@ -572,8 +593,8 @@ eject_unmount_do (GMount *mount, if (error != NULL) goto handle_error; - data->error_channel_source_id = g_io_add_watch (data->error_channel, G_IO_IN, eject_unmount_read_error, data); - g_child_watch_add (child_pid, eject_unmount_cb, data); + data->error_channel_source_id = g_io_add_watch (data->error_channel, G_IO_IN, unmount_read_error, data); + g_child_watch_add (child_pid, unmount_cb, data); handle_error: @@ -596,6 +617,28 @@ handle_error: g_error_free (error); g_free (data); } + + return FALSE; +} + +static void +unmount_do (GMount *mount, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data, + char **argv) +{ + UnmountOp *data; + + data = g_new0 (UnmountOp, 1); + data->mount = mount; + data->callback = callback; + data->user_data = user_data; + data->cancellable = cancellable; + data->num_attempts_left = BUSY_UNMOUNT_NUM_ATTEMPTS; + data->argv = g_strdupv (argv); + + unmount_attempt (data); } /* ---------------------------------------------------------------------------------------------------- */ @@ -619,19 +662,54 @@ luks_lock_cb (GduDevice *device, g_object_unref (simple); } +static gboolean gdu_unmount_attempt (GSimpleAsyncResult *simple); + static void -unmount_cb (GduDevice *device, - GError *error, - gpointer user_data) +gdu_unmount_cb (GduDevice *device, + GError *error, + gpointer user_data) { GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data); if (error != NULL) { + gint error_code; + + /*g_debug ("domain=%s error_code=%d '%s'", g_quark_to_string (error->domain), error->code, error->message);*/ + + error_code = G_IO_ERROR_FAILED; + if (error->domain == GDU_ERROR && error->code == GDU_ERROR_BUSY) + error_code = G_IO_ERROR_BUSY; + /* We could handle PolicyKit integration here but this action is allowed by default * and this won't be needed when porting to PolicyKit 1.0 anyway */ - g_simple_async_result_set_from_error (simple, error); + + if (error_code == G_IO_ERROR_BUSY) + { + guint num_attempts_left; + + num_attempts_left = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (simple), "num-attempts-left")); + + if (num_attempts_left > 0) + { + num_attempts_left -= 1; + g_object_set_data (G_OBJECT (simple), + "num-attempts-left", + GUINT_TO_POINTER (num_attempts_left)); + + g_timeout_add (BUSY_UNMOUNT_MS_DELAY_BETWEEN_ATTEMPTS, + (GSourceFunc) gdu_unmount_attempt, + simple); + goto out; + } + } + + g_simple_async_result_set_error (simple, + G_IO_ERROR, + error_code, + "%s", + error->message); g_error_free (error); g_simple_async_result_complete (simple); g_object_unref (simple); @@ -687,6 +765,17 @@ unmount_cb (GduDevice *device, ; } +static gboolean +gdu_unmount_attempt (GSimpleAsyncResult *simple) +{ + GduDevice *device; + + /* TODO: honor flags */ + device = g_object_get_data (G_OBJECT (simple), "gdu-device"); + gdu_device_op_filesystem_unmount (device, gdu_unmount_cb, simple); + return FALSE; +} + static void g_gdu_mount_unmount (GMount *_mount, GMountUnmountFlags flags, @@ -698,6 +787,21 @@ g_gdu_mount_unmount (GMount *_mount, GSimpleAsyncResult *simple; GduPresentable *gdu_volume; + /* emit the ::mount-pre-unmount signal */ + g_signal_emit_by_name (mount->volume_monitor, "mount-pre-unmount", mount); + + /* If we end up with G_IO_ERROR_BUSY, try again BUSY_UNMOUNT_NUM_ATTEMPTS times + * waiting BUSY_UNMOUNT_MS_DELAY_BETWEEN_ATTEMPTS milliseconds between each + * attempt. + * + * This is to give other processes recieving the ::mount-pre-unmount signal some + * time to close file descriptors. + * + * TODO: Unfortunately this code is a bit messy because we take two different + * codepaths depending on whether we use GDU or the native unmount command. + * It would be good to clean this up. + */ + gdu_volume = NULL; if (mount->volume != NULL) gdu_volume = g_gdu_volume_get_presentable_with_cleartext (mount->volume); @@ -713,7 +817,7 @@ g_gdu_mount_unmount (GMount *_mount, else argv[1] = mount->device_file; - eject_unmount_do (_mount, cancellable, callback, user_data, argv); + unmount_do (_mount, cancellable, callback, user_data, argv); } else if (gdu_volume != NULL) { @@ -722,6 +826,10 @@ g_gdu_mount_unmount (GMount *_mount, user_data, NULL); + g_object_set_data (G_OBJECT (simple), + "num-attempts-left", + GUINT_TO_POINTER (BUSY_UNMOUNT_NUM_ATTEMPTS)); + if (mount->is_burn_mount) { /* burn mounts are really never mounted... */ @@ -731,12 +839,9 @@ g_gdu_mount_unmount (GMount *_mount, else { GduDevice *device; - - /* TODO: honor flags */ - device = gdu_presentable_get_device (gdu_volume); - gdu_device_op_filesystem_unmount (device, unmount_cb, simple); - g_object_unref (device); + g_object_set_data_full (G_OBJECT (simple), "gdu-device", device, g_object_unref); + gdu_unmount_attempt (simple); } } else @@ -760,6 +865,8 @@ g_gdu_mount_unmount_finish (GMount *mount, return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error); } +/* ---------------------------------------------------------------------------------------------------- */ + typedef struct { GObject *object; GAsyncReadyCallback callback; -- 1.6.2.2