Blob Blame Raw
From 8d9fc8a777fb1b00ae371e276a14416a0987f4af Mon Sep 17 00:00:00 2001
From: David Zeuthen <davidz@redhat.com>
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