[gvfs/wip/udisks2] Spawn helpers asynchronously, not synchronously



commit b9ccc6617f230a96938642ecb0aa65da3530e5d8
Author: David Zeuthen <davidz redhat com>
Date:   Thu Dec 22 14:24:46 2011 -0500

    Spawn helpers asynchronously, not synchronously
    
    Signed-off-by: David Zeuthen <davidz redhat com>

 monitor/udisks2/gvfsudisks2mount.c  |  343 +++++++++++++++++----------------
 monitor/udisks2/gvfsudisks2utils.c  |  358 +++++++++++++++++++++++++++++++++++
 monitor/udisks2/gvfsudisks2utils.h  |   13 ++
 monitor/udisks2/gvfsudisks2volume.c |   47 +++---
 4 files changed, 572 insertions(+), 189 deletions(-)
---
diff --git a/monitor/udisks2/gvfsudisks2mount.c b/monitor/udisks2/gvfsudisks2mount.c
index a38220d..ca1a075 100644
--- a/monitor/udisks2/gvfsudisks2mount.c
+++ b/monitor/udisks2/gvfsudisks2mount.c
@@ -511,76 +511,11 @@ gvfs_udisks2_mount_can_eject (GMount *_mount)
 
 /* ---------------------------------------------------------------------------------------------------- */
 
-static GArray *
-get_busy_processes (const gchar* const *mount_points)
-{
-  GArray *processes;
-  guint n;
-
-  processes = g_array_new (FALSE, FALSE, sizeof (GPid));
-
-  for (n = 0; mount_points != NULL && mount_points[n] != NULL; n++)
-    {
-      const gchar *mount_point = mount_points[n];
-      const gchar *lsof_argv[] = {"lsof", "-t", NULL, NULL};
-      gchar *standard_output = NULL;
-      const gchar *p;
-      gint exit_status;
-      GError *error;
-
-      lsof_argv[2] = mount_point;
-
-      error = NULL;
-      if (!g_spawn_sync (NULL,       /* working_directory */
-                         (gchar **) lsof_argv,
-                         NULL,       /* envp */
-                         G_SPAWN_SEARCH_PATH,
-                         NULL,       /* child_setup */
-                         NULL,       /* user_data */
-                         &standard_output,
-                         NULL,       /* standard_error */
-                         &exit_status,
-                         &error))
-        {
-          g_warning ("Error launching lsof(1): %s (%s, %d)",
-                     error->message, g_quark_to_string (error->domain), error->code);
-          goto cont_loop;
-        }
-      if (!(WIFEXITED (exit_status) && WEXITSTATUS (exit_status) == 0))
-        {
-          g_warning ("lsof(1) didn't exit normally");
-          goto cont_loop;
-        }
-
-      p = standard_output;
-      while (TRUE)
-        {
-          GPid pid;
-          gchar *endp;
-
-          if (*p == '\0')
-            break;
-
-          pid = strtol (p, &endp, 10);
-
-          if (pid == 0 && p == endp)
-            break;
-
-          g_array_append_val (processes, pid);
-
-          p = endp;
-        }
-    cont_loop:
-      g_free (standard_output);
-    }
-  return processes;
-}
-
-/* ---------------------------------------------------------------------------------------------------- */
-
 typedef struct
 {
+  volatile gint ref_count;
   GSimpleAsyncResult *simple;
+  gboolean completed;
 
   GVfsUDisks2Mount *mount;
 
@@ -588,43 +523,47 @@ typedef struct
   UDisksFilesystem *filesystem;
 
   GCancellable *cancellable;
-  gulong cancelled_handler_id;
-  gboolean is_cancelled;
 
   GMountOperation *mount_operation;
   GMountUnmountFlags flags;
 
   gulong mount_op_reply_handler_id;
   guint retry_unmount_timer_id;
-
 } UnmountData;
 
-static void
-unmount_data_free (UnmountData *data)
+static UnmountData *
+unmount_data_ref (UnmountData *data)
 {
-  g_object_unref (data->simple);
+  g_atomic_int_inc (&data->ref_count);
+  return data;
+}
 
-  if (data->mount_op_reply_handler_id > 0)
-    {
-      /* make the operation dialog go away */
-      g_signal_emit_by_name (data->mount_operation, "aborted");
-      g_signal_handler_disconnect (data->mount_operation, data->mount_op_reply_handler_id);
-    }
-  if (data->retry_unmount_timer_id > 0)
+static void
+unmount_data_unref (UnmountData *data)
+{
+  if (g_atomic_int_dec_and_test (&data->ref_count))
     {
-      g_source_remove (data->retry_unmount_timer_id);
-      data->retry_unmount_timer_id = 0;
-    }
+      g_object_unref (data->simple);
 
-  g_clear_object (&data->mount);
-  if (data->cancelled_handler_id != 0)
-    g_signal_handler_disconnect (data->cancellable, data->cancelled_handler_id);
-  g_clear_object (&data->cancellable);
-  g_clear_object (&data->mount_operation);
-  g_clear_object (&data->encrypted);
-  g_clear_object (&data->filesystem);
+      if (data->mount_op_reply_handler_id > 0)
+        {
+          /* make the operation dialog go away */
+          g_signal_emit_by_name (data->mount_operation, "aborted");
+          g_signal_handler_disconnect (data->mount_operation, data->mount_op_reply_handler_id);
+        }
+      if (data->retry_unmount_timer_id > 0)
+        {
+          g_source_remove (data->retry_unmount_timer_id);
+          data->retry_unmount_timer_id = 0;
+        }
 
-  g_free (data);
+      g_clear_object (&data->mount);
+      g_clear_object (&data->cancellable);
+      g_clear_object (&data->mount_operation);
+      g_clear_object (&data->encrypted);
+      g_clear_object (&data->filesystem);
+      g_free (data);
+    }
 }
 
 static void unmount_do (UnmountData *data, gboolean force);
@@ -673,7 +612,8 @@ on_mount_op_reply (GMountOperation       *mount_operation,
                                        "GMountOperation aborted (user should never see this "
                                        "error since it is G_IO_ERROR_FAILED_HANDLED)");
       g_simple_async_result_complete_in_idle (data->simple);
-      unmount_data_free (data);
+      data->completed = TRUE;
+      unmount_data_unref (data);
     }
   else if (result == G_MOUNT_OPERATION_HANDLED)
     {
@@ -690,46 +630,118 @@ on_mount_op_reply (GMountOperation       *mount_operation,
                                        G_IO_ERROR_BUSY,
                                        _("One or more programs are preventing the unmount operation."));
       g_simple_async_result_complete_in_idle (data->simple);
-      unmount_data_free (data);
+      data->completed = TRUE;
+      unmount_data_unref (data);
     }
 }
 
 static void
-unmount_show_busy (UnmountData        *data,
-                   const gchar *const *mount_points)
+lsof_command_cb (GObject       *source_object,
+                 GAsyncResult  *res,
+                 gpointer       user_data)
 {
+  UnmountData *data = user_data;
+  GError *error;
+  gint exit_status;
   GArray *processes;
   const gchar *choices[3] = {NULL, NULL, NULL};
   const gchar *message;
+  gchar *standard_output = NULL;
+  const gchar *p;
 
-  processes = get_busy_processes (mount_points);
+  processes = g_array_new (FALSE, FALSE, sizeof (GPid));
 
-  if (data->mount_op_reply_handler_id == 0)
+  error = NULL;
+  if (!gvfs_udisks2_utils_spawn_finish (res,
+                                        &exit_status,
+                                        &standard_output,
+                                        NULL, /* gchar **out_standard_error */
+                                        &error))
     {
-      data->mount_op_reply_handler_id = g_signal_connect (data->mount_operation,
-                                                          "reply",
-                                                          G_CALLBACK (on_mount_op_reply),
-                                                          data);
+      g_printerr ("Error launching lsof(1): %s (%s, %d)\n",
+                  error->message, g_quark_to_string (error->domain), error->code);
+      g_error_free (error);
+      goto out;
     }
-  choices[0] = _("Unmount Anyway");
-  choices[1] = _("Cancel");
-  message = _("Volume is busy\n"
-              "One or more applications are keeping the volume busy.");
-  g_signal_emit_by_name (data->mount_operation,
-                         "show-processes",
-                         message,
-                         processes,
-                         choices);
-  /* set up a timer to try unmounting every two seconds - this will also
-   * update the list of busy processes
-   */
-  if (data->retry_unmount_timer_id == 0)
+
+  if (!(WIFEXITED (exit_status) && WEXITSTATUS (exit_status) == 0))
+    {
+      g_printerr ("lsof(1) did not exit normally\n");
+      goto out;
+    }
+
+  p = standard_output;
+  while (TRUE)
     {
-      data->retry_unmount_timer_id = g_timeout_add_seconds (2,
-                                                            on_retry_timer_cb,
-                                                            data);
+      GPid pid;
+      gchar *endp;
+
+      if (*p == '\0')
+        break;
+
+      pid = strtol (p, &endp, 10);
+      if (pid == 0 && p == endp)
+        break;
+
+      g_array_append_val (processes, pid);
+
+      p = endp;
     }
-  g_array_free (processes, TRUE);
+
+ out:
+  if (!data->completed)
+    {
+      /* We want to emit the 'show-processes' signal even if launching
+       * lsof(1) failed or if it didn't return any PIDs. This is because
+       * it won't show e.g. root-owned processes operating on files
+       * on the mount point.
+       *
+       * (unfortunately there's no way to convey that it failed)
+       */
+      if (data->mount_op_reply_handler_id == 0)
+        {
+          data->mount_op_reply_handler_id = g_signal_connect (data->mount_operation,
+                                                              "reply",
+                                                              G_CALLBACK (on_mount_op_reply),
+                                                              data);
+        }
+      choices[0] = _("Unmount Anyway");
+      choices[1] = _("Cancel");
+      message = _("Volume is busy\n"
+                  "One or more applications are keeping the volume busy.");
+      g_signal_emit_by_name (data->mount_operation,
+                             "show-processes",
+                             message,
+                             processes,
+                             choices);
+      /* set up a timer to try unmounting every two seconds - this will also
+       * update the list of busy processes
+       */
+      if (data->retry_unmount_timer_id == 0)
+        {
+          data->retry_unmount_timer_id = g_timeout_add_seconds (2,
+                                                                on_retry_timer_cb,
+                                                                data);
+        }
+      g_array_free (processes, TRUE);
+      g_free (standard_output);
+    }
+  unmount_data_unref (data); /* return ref */
+}
+
+
+static void
+unmount_show_busy (UnmountData  *data,
+                   const gchar  *mount_point)
+{
+  gchar *escaped_mount_point;
+  escaped_mount_point = g_strescape (mount_point, NULL);
+  gvfs_udisks2_utils_spawn (data->cancellable,
+                            lsof_command_cb,
+                            unmount_data_ref (data),
+                            "lsof -t \"%s\"",
+                            escaped_mount_point);
+  g_free (escaped_mount_point);
 }
 
 static void
@@ -747,7 +759,8 @@ lock_cb (GObject       *source_object,
                                           &error))
     g_simple_async_result_take_error (data->simple, error);
   g_simple_async_result_complete (data->simple);
-  unmount_data_free (data);
+  data->completed = TRUE;
+  unmount_data_unref (data);
 }
 
 static void
@@ -769,14 +782,10 @@ unmount_cb (GObject       *source_object,
       /* if the user passed in a GMountOperation, then do the GMountOperation::show-processes dance ... */
       if (error->code == G_IO_ERROR_BUSY && data->mount_operation != NULL)
         {
-          unmount_show_busy (data, udisks_filesystem_get_mount_points (filesystem));
+          unmount_show_busy (data, udisks_filesystem_get_mount_points (filesystem)[0]);
           goto out;
         }
-      else
-        {
-          g_simple_async_result_take_error (data->simple, error);
-          g_simple_async_result_complete (data->simple);
-        }
+      g_simple_async_result_take_error (data->simple, error);
     }
   else
     {
@@ -790,53 +799,40 @@ unmount_cb (GObject       *source_object,
                                       data);
           goto out;
         }
-      else
-        {
-          g_simple_async_result_complete (data->simple);
-        }
     }
 
-  unmount_data_free (data);
+  g_simple_async_result_complete (data->simple);
+  data->completed = TRUE;
+  unmount_data_unref (data);
  out:
   ;
 }
 
+
+/* ------------------------------ */
+
 static void
-unmount_do_command (UnmountData *data,
-                    gboolean     force)
+umount_command_cb (GObject       *source_object,
+                   GAsyncResult  *res,
+                   gpointer       user_data)
 {
+  UnmountData *data = user_data;
   GError *error;
   gint exit_status;
   gchar *standard_error = NULL;
-  const gchar *umount_argv[4] = {"umount", NULL, NULL, NULL};
 
-  if (force)
-    {
-      umount_argv[1] = "-l";
-      umount_argv[2] = data->mount->mount_path;
-    }
-  else
-    {
-      umount_argv[1] = data->mount->mount_path;
-    }
-
-  /* TODO: we could do this async but it's probably not worth the effort */
   error = NULL;
-  if (!g_spawn_sync (NULL,            /* working dir */
-                     (gchar **) umount_argv,
-                     NULL,            /* envp */
-                     G_SPAWN_SEARCH_PATH,
-                     NULL,            /* child_setup */
-                     NULL,            /* user_data for child_setup */
-                     NULL,            /* standard_output */
-                     &standard_error, /* standard_error */
-                     &exit_status,
-                     &error))
+  if (!gvfs_udisks2_utils_spawn_finish (res,
+                                        &exit_status,
+                                        NULL, /* gchar **out_standard_output */
+                                        &standard_error,
+                                        &error))
     {
-      g_prefix_error (&error, "Error running umount: ");
+      g_simple_async_result_take_error (data->simple, error);
       g_simple_async_result_take_error (data->simple, error);
       g_simple_async_result_complete (data->simple);
-      unmount_data_free (data);
+      data->completed = TRUE;
+      unmount_data_unref (data);
       goto out;
     }
 
@@ -844,15 +840,14 @@ unmount_do_command (UnmountData *data,
     {
       gvfs_udisks2_volume_monitor_update (data->mount->monitor);
       g_simple_async_result_complete (data->simple);
-      unmount_data_free (data);
+      data->completed = TRUE;
+      unmount_data_unref (data);
       goto out;
     }
 
   if (standard_error != NULL && strstr (standard_error, "device is busy") != NULL)
     {
-      const gchar *mount_points[2] = {NULL, NULL};
-      mount_points[0] = data->mount->mount_path;
-      unmount_show_busy (data, mount_points);
+      unmount_show_busy (data, data->mount->mount_path);
       goto out;
     }
 
@@ -861,7 +856,8 @@ unmount_do_command (UnmountData *data,
                                    G_IO_ERROR_FAILED,
                                    standard_error);
   g_simple_async_result_complete (data->simple);
-  unmount_data_free (data);
+  data->completed = TRUE;
+  unmount_data_unref (data);
 
  out:
   g_free (standard_error);
@@ -873,9 +869,18 @@ unmount_do (UnmountData *data,
 {
   GVariantBuilder builder;
 
+  /* Use the umount(8) command if there is no block device / filesystem */
   if (data->filesystem == NULL)
     {
-      unmount_do_command (data, force);
+      gchar *escaped_mount_path;
+      escaped_mount_path = g_strescape (data->mount->mount_path, NULL);
+      gvfs_udisks2_utils_spawn (data->cancellable,
+                                umount_command_cb,
+                                data,
+                                "umount %s \"%s\"",
+                                force ? "-l " : "",
+                                escaped_mount_path);
+      g_free (escaped_mount_path);
       goto out;
     }
 
@@ -918,6 +923,7 @@ gvfs_udisks2_mount_unmount_with_operation (GMount              *_mount,
   g_signal_emit_by_name (mount->monitor, "mount-pre-unmount", mount);
 
   data = g_new0 (UnmountData, 1);
+  data->ref_count = 1;
   data->simple = g_simple_async_result_new (G_OBJECT (mount),
                                             callback,
                                             user_data,
@@ -931,7 +937,8 @@ gvfs_udisks2_mount_unmount_with_operation (GMount              *_mount,
     {
       /* burn mounts are really never mounted so complete successfully immediately */
       g_simple_async_result_complete_in_idle (data->simple);
-      unmount_data_free (data);
+      data->completed = TRUE;
+      unmount_data_unref (data);
       goto out;
     }
 
@@ -949,7 +956,9 @@ gvfs_udisks2_mount_unmount_with_operation (GMount              *_mount,
                                            G_IO_ERROR_FAILED,
                                            "No object for D-Bus interface");
           g_simple_async_result_complete (data->simple);
-              unmount_data_free (data);
+          data->completed = TRUE;
+          unmount_data_unref (data);
+          goto out;
         }
       data->filesystem = udisks_object_get_filesystem (UDISKS_OBJECT (object));
       if (data->filesystem == NULL)
@@ -964,7 +973,8 @@ gvfs_udisks2_mount_unmount_with_operation (GMount              *_mount,
                                                G_IO_ERROR_FAILED,
                                                "No filesystem or encrypted interface on D-Bus object");
               g_simple_async_result_complete (data->simple);
-              unmount_data_free (data);
+              data->completed = TRUE;
+              unmount_data_unref (data);
               goto out;
             }
 
@@ -981,7 +991,8 @@ gvfs_udisks2_mount_unmount_with_operation (GMount              *_mount,
                                                    G_IO_ERROR_FAILED,
                                                    "No filesystem interface on D-Bus object for cleartext device");
                   g_simple_async_result_complete (data->simple);
-                  unmount_data_free (data);
+                  data->completed = TRUE;
+                  unmount_data_unref (data);
                   goto out;
                 }
             }
diff --git a/monitor/udisks2/gvfsudisks2utils.c b/monitor/udisks2/gvfsudisks2utils.c
index 1906a00..213c2e7 100644
--- a/monitor/udisks2/gvfsudisks2utils.c
+++ b/monitor/udisks2/gvfsudisks2utils.c
@@ -105,3 +105,361 @@ gvfs_udisks2_utils_lookup_fstab_options_value (const gchar *fstab_options,
     }
   return ret;
 }
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  GSimpleAsyncResult *simple; /* borrowed reference */
+  GMainContext *main_context; /* may be NULL */
+
+  gchar *command_line;
+
+  GCancellable *cancellable;  /* may be NULL */
+  gulong cancellable_handler_id;
+
+  GPid child_pid;
+  gint child_stdout_fd;
+  gint child_stderr_fd;
+
+  GIOChannel *child_stdout_channel;
+  GIOChannel *child_stderr_channel;
+
+  GSource *child_watch_source;
+  GSource *child_stdout_source;
+  GSource *child_stderr_source;
+
+  GString *child_stdout;
+  GString *child_stderr;
+
+  gint exit_status;
+} SpawnData;
+
+static void
+child_watch_from_release_cb (GPid     pid,
+                             gint     status,
+                             gpointer user_data)
+{
+}
+
+static void
+spawn_data_free (SpawnData *data)
+{
+  /* Nuke the child, if necessary */
+  if (data->child_watch_source != NULL)
+    {
+      g_source_destroy (data->child_watch_source);
+      data->child_watch_source = NULL;
+    }
+
+  if (data->child_pid != 0)
+    {
+      GSource *source;
+      kill (data->child_pid, SIGTERM);
+      /* OK, we need to reap for the child ourselves - we don't want
+       * to use waitpid() because that might block the calling
+       * thread (the child might handle SIGTERM and use several
+       * seconds for cleanup/rollback).
+       *
+       * So we use GChildWatch instead.
+       *
+       * Avoid taking a references to ourselves. but note that we need
+       * to pass the GSource so we can nuke it once handled.
+       */
+      source = g_child_watch_source_new (data->child_pid);
+      g_source_set_callback (source,
+                             (GSourceFunc) child_watch_from_release_cb,
+                             source,
+                             (GDestroyNotify) g_source_destroy);
+      g_source_attach (source, data->main_context);
+      g_source_unref (source);
+      data->child_pid = 0;
+    }
+
+  if (data->child_stdout != NULL)
+    {
+      g_string_free (data->child_stdout, TRUE);
+      data->child_stdout = NULL;
+    }
+
+  if (data->child_stderr != NULL)
+    {
+      g_string_free (data->child_stderr, TRUE);
+      data->child_stderr = NULL;
+    }
+
+  if (data->child_stdout_channel != NULL)
+    {
+      g_io_channel_unref (data->child_stdout_channel);
+      data->child_stdout_channel = NULL;
+    }
+  if (data->child_stderr_channel != NULL)
+    {
+      g_io_channel_unref (data->child_stderr_channel);
+      data->child_stderr_channel = NULL;
+    }
+
+  if (data->child_stdout_source != NULL)
+    {
+      g_source_destroy (data->child_stdout_source);
+      data->child_stdout_source = NULL;
+    }
+  if (data->child_stderr_source != NULL)
+    {
+      g_source_destroy (data->child_stderr_source);
+      data->child_stderr_source = NULL;
+    }
+
+  if (data->child_stdout_fd != -1)
+    {
+      g_warn_if_fail (close (data->child_stdout_fd) == 0);
+      data->child_stdout_fd = -1;
+    }
+  if (data->child_stderr_fd != -1)
+    {
+      g_warn_if_fail (close (data->child_stderr_fd) == 0);
+      data->child_stderr_fd = -1;
+    }
+
+  if (data->cancellable_handler_id > 0)
+    {
+      g_cancellable_disconnect (data->cancellable, data->cancellable_handler_id);
+      data->cancellable_handler_id = 0;
+    }
+
+  if (data->main_context != NULL)
+    g_main_context_unref (data->main_context);
+
+  if (data->cancellable != NULL)
+    g_object_unref (data->cancellable);
+
+  g_free (data->command_line);
+
+  g_slice_free (SpawnData, data);
+}
+
+/* called in the thread where @cancellable was cancelled */
+static void
+on_cancelled (GCancellable *cancellable,
+              gpointer      user_data)
+{
+  SpawnData *data = user_data;
+  GError *error;
+
+  error = NULL;
+  g_warn_if_fail (g_cancellable_set_error_if_cancelled (cancellable, &error));
+  g_simple_async_result_take_error (data->simple, error);
+  g_simple_async_result_complete_in_idle (data->simple);
+  g_object_unref (data->simple);
+}
+
+static gboolean
+read_child_stderr (GIOChannel *channel,
+                   GIOCondition condition,
+                   gpointer user_data)
+{
+  SpawnData *data = user_data;
+  gchar buf[1024];
+  gsize bytes_read;
+
+  g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL);
+  g_string_append_len (data->child_stderr, buf, bytes_read);
+  return TRUE;
+}
+
+static gboolean
+read_child_stdout (GIOChannel *channel,
+                   GIOCondition condition,
+                   gpointer user_data)
+{
+  SpawnData *data = user_data;
+  gchar buf[1024];
+  gsize bytes_read;
+
+  g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL);
+  g_string_append_len (data->child_stdout, buf, bytes_read);
+  return TRUE;
+}
+
+static void
+child_watch_cb (GPid     pid,
+                gint     status,
+                gpointer user_data)
+{
+  SpawnData *data = user_data;
+  gchar *buf;
+  gsize buf_size;
+
+  if (g_io_channel_read_to_end (data->child_stdout_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL)
+    {
+      g_string_append_len (data->child_stdout, buf, buf_size);
+      g_free (buf);
+    }
+  if (g_io_channel_read_to_end (data->child_stderr_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL)
+    {
+      g_string_append_len (data->child_stderr, buf, buf_size);
+      g_free (buf);
+    }
+
+  data->exit_status = status;
+
+  /* ok, child watch is history, make sure we don't free it in spawn_data_free() */
+  data->child_pid = 0;
+  data->child_watch_source = NULL;
+
+  /* we're done */
+  g_simple_async_result_complete_in_idle (data->simple);
+  g_object_unref (data->simple);
+}
+
+void
+gvfs_udisks2_utils_spawn (GCancellable        *cancellable,
+                          GAsyncReadyCallback  callback,
+                          gpointer             user_data,
+                          const gchar         *command_line_format,
+                          ...)
+{
+  va_list var_args;
+  SpawnData *data;
+  GError *error;
+  gint child_argc;
+  gchar **child_argv = NULL;
+
+  data = g_slice_new0 (SpawnData);
+  data->simple = g_simple_async_result_new (NULL,
+                                            callback,
+                                            user_data,
+                                            gvfs_udisks2_utils_spawn);
+  data->main_context = g_main_context_get_thread_default ();
+  if (data->main_context != NULL)
+    g_main_context_ref (data->main_context);
+
+  data->cancellable = cancellable != NULL ? g_object_ref (cancellable) : NULL;
+
+  va_start (var_args, command_line_format);
+  data->command_line = g_strdup_vprintf (command_line_format, var_args);
+  va_end (var_args);
+
+  data->child_stdout = g_string_new (NULL);
+  data->child_stderr = g_string_new (NULL);
+  data->child_stdout_fd = -1;
+  data->child_stderr_fd = -1;
+
+  /* the life-cycle of SpawnData is tied to its GSimpleAsyncResult */
+  g_simple_async_result_set_op_res_gpointer (data->simple, data, (GDestroyNotify) spawn_data_free);
+
+  error = NULL;
+  if (data->cancellable != NULL)
+    {
+      /* could already be cancelled */
+      error = NULL;
+      if (g_cancellable_set_error_if_cancelled (data->cancellable, &error))
+        {
+          g_simple_async_result_take_error (data->simple, error);
+          g_simple_async_result_complete_in_idle (data->simple);
+          g_object_unref (data->simple);
+          goto out;
+        }
+
+      data->cancellable_handler_id = g_cancellable_connect (data->cancellable,
+                                                            G_CALLBACK (on_cancelled),
+                                                            data,
+                                                            NULL);
+    }
+
+  error = NULL;
+  if (!g_shell_parse_argv (data->command_line,
+                           &child_argc,
+                           &child_argv,
+                           &error))
+    {
+      g_prefix_error (&error,
+                      "Error parsing command-line `%s': ",
+                      data->command_line);
+      g_simple_async_result_take_error (data->simple, error);
+      g_simple_async_result_complete_in_idle (data->simple);
+      g_object_unref (data->simple);
+      goto out;
+    }
+
+  error = NULL;
+  if (!g_spawn_async_with_pipes (NULL, /* working directory */
+                                 child_argv,
+                                 NULL, /* envp */
+                                 G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
+                                 NULL, /* child_setup */
+                                 NULL, /* child_setup's user_data */
+                                 &(data->child_pid),
+                                 NULL, /* gint *stdin_fd */
+                                 &(data->child_stdout_fd),
+                                 &(data->child_stderr_fd),
+                                 &error))
+    {
+      g_prefix_error (&error,
+                      "Error spawning command-line `%s': ",
+                      data->command_line);
+      g_simple_async_result_take_error (data->simple, error);
+      g_simple_async_result_complete_in_idle (data->simple);
+      g_object_unref (data->simple);
+      goto out;
+    }
+
+  data->child_watch_source = g_child_watch_source_new (data->child_pid);
+  g_source_set_callback (data->child_watch_source, (GSourceFunc) child_watch_cb, data, NULL);
+  g_source_attach (data->child_watch_source, data->main_context);
+  g_source_unref (data->child_watch_source);
+
+  data->child_stdout_channel = g_io_channel_unix_new (data->child_stdout_fd);
+  g_io_channel_set_flags (data->child_stdout_channel, G_IO_FLAG_NONBLOCK, NULL);
+  data->child_stdout_source = g_io_create_watch (data->child_stdout_channel, G_IO_IN);
+  g_source_set_callback (data->child_stdout_source, (GSourceFunc) read_child_stdout, data, NULL);
+  g_source_attach (data->child_stdout_source, data->main_context);
+  g_source_unref (data->child_stdout_source);
+
+  data->child_stderr_channel = g_io_channel_unix_new (data->child_stderr_fd);
+  g_io_channel_set_flags (data->child_stderr_channel, G_IO_FLAG_NONBLOCK, NULL);
+  data->child_stderr_source = g_io_create_watch (data->child_stderr_channel, G_IO_IN);
+  g_source_set_callback (data->child_stderr_source, (GSourceFunc) read_child_stderr, data, NULL);
+  g_source_attach (data->child_stderr_source, data->main_context);
+  g_source_unref (data->child_stderr_source);
+
+ out:
+  g_strfreev (child_argv);
+}
+
+gboolean
+gvfs_udisks2_utils_spawn_finish (GAsyncResult   *res,
+                                 gint           *out_exit_status,
+                                 gchar         **out_standard_output,
+                                 gchar         **out_standard_error,
+                                 GError        **error)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+  SpawnData *data;
+  gboolean ret = FALSE;
+
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == gvfs_udisks2_utils_spawn);
+
+  if (g_simple_async_result_propagate_error (simple, error))
+    goto out;
+
+  data = g_simple_async_result_get_op_res_gpointer (simple);
+
+  if (out_exit_status != NULL)
+    *out_exit_status = data->exit_status;
+
+  if (out_standard_output != NULL)
+    *out_standard_output = g_strdup (data->child_stdout->str);
+
+  if (out_standard_error != NULL)
+    *out_standard_error = g_strdup (data->child_stderr->str);
+
+  ret = TRUE;
+
+ out:
+  return ret;
+}
+
diff --git a/monitor/udisks2/gvfsudisks2utils.h b/monitor/udisks2/gvfsudisks2utils.h
index ad3d6d7..7a6abe7 100644
--- a/monitor/udisks2/gvfsudisks2utils.h
+++ b/monitor/udisks2/gvfsudisks2utils.h
@@ -37,6 +37,19 @@ GIcon *gvfs_udisks2_utils_icon_from_fs_type (const gchar *fs_type);
 gchar *gvfs_udisks2_utils_lookup_fstab_options_value (const gchar *fstab_options,
                                                       const gchar *key);
 
+void     gvfs_udisks2_utils_spawn (GCancellable        *cancellable,
+                                   GAsyncReadyCallback  callback,
+                                   gpointer             user_data,
+                                   const gchar         *command_line_format,
+                                   ...);
+
+gboolean gvfs_udisks2_utils_spawn_finish (GAsyncResult   *res,
+                                          gint           *out_exit_status,
+                                          gchar         **out_standard_output,
+                                          gchar         **out_standard_error,
+                                          GError        **error);
+
+
 G_END_DECLS
 
 #endif /* __GVFS_UDISKS2_UTILS_H__ */
diff --git a/monitor/udisks2/gvfsudisks2volume.c b/monitor/udisks2/gvfsudisks2volume.c
index 2415452..8914b02 100644
--- a/monitor/udisks2/gvfsudisks2volume.c
+++ b/monitor/udisks2/gvfsudisks2volume.c
@@ -743,42 +743,35 @@ mount_cancel_pending_op (MountData *data)
 /* ------------------------------ */
 
 static void
-do_mount_command (MountData *data)
+mount_command_cb (GObject       *source_object,
+                  GAsyncResult  *res,
+                  gpointer       user_data)
 {
+  MountData *data = user_data;
   GError *error;
   gint exit_status;
   gchar *standard_error = NULL;
-  const gchar *mount_argv[3] = {"mount", NULL, NULL};
 
-  mount_argv[1] = g_unix_mount_point_get_mount_path (data->volume->mount_point);
+  /* TODO: for e.g. NFS and CIFS mounts we could do GMountOperation stuff and pipe a
+   * password to mount(8)'s stdin channel
+   *
+   * TODO: if this fails because the user is not authorized (e.g. EPERM), we could
+   * run it through a polkit-ified setuid root helper
+   */
 
-  /* TODO: we could do this async but it's probably not worth the effort */
   error = NULL;
-  if (!g_spawn_sync (NULL,            /* working dir */
-                     (gchar **) mount_argv,
-                     NULL,            /* envp */
-                     G_SPAWN_SEARCH_PATH,
-                     NULL,            /* child_setup */
-                     NULL,            /* user_data for child_setup */
-                     NULL,            /* standard_output */
-                     &standard_error, /* standard_error */
-                     &exit_status,
-                     &error))
+  if (!gvfs_udisks2_utils_spawn_finish (res,
+                                        &exit_status,
+                                        NULL, /* gchar **out_standard_output */
+                                        &standard_error,
+                                        &error))
     {
-      g_prefix_error (&error, "Error running mount: ");
       g_simple_async_result_take_error (data->simple, error);
       g_simple_async_result_complete (data->simple);
       mount_data_free (data);
       goto out;
     }
 
-  /* TODO: for e.g. NFS and CIFS mounts we could do GMountOperation stuff and pipe a
-   * password to mount(8)'s stdin channel
-   *
-   * TODO: if this fails because the user is not authorized (e.g. EPERM), we could
-   * run it through a polkit-ified setuid root helper
-   */
-
   if (WIFEXITED (exit_status) && WEXITSTATUS (exit_status) == 0)
     {
       gvfs_udisks2_volume_monitor_update (data->volume->monitor);
@@ -1057,9 +1050,17 @@ gvfs_udisks2_volume_mount (GVolume             *_volume,
     }
   volume->mount_pending_op = data;
 
+  /* Use the mount(8) command if there is no block device */
   if (volume->block == NULL)
     {
-      do_mount_command (data);
+      gchar *escaped_mount_path;
+      escaped_mount_path = g_strescape (g_unix_mount_point_get_mount_path (data->volume->mount_point), NULL);
+      gvfs_udisks2_utils_spawn (data->cancellable,
+                                mount_command_cb,
+                                data,
+                                "mount \"%s\"",
+                                escaped_mount_path);
+      g_free (escaped_mount_path);
       goto out;
     }
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]