Make it possible to pass a string to the helper via stdin
authorDavid Zeuthen <davidz@redhat.com>
Wed, 8 Sep 2010 19:26:22 +0000 (15:26 -0400)
committerDavid Zeuthen <davidz@redhat.com>
Wed, 8 Sep 2010 19:26:22 +0000 (15:26 -0400)
This is needed for e.g. passing a pass-phrase to cryptsetup(8).

Signed-off-by: David Zeuthen <davidz@redhat.com>
src/tests/helper.c
src/tests/test.c
src/udisksdaemon.c
src/udisksdaemon.h
src/udisksfilesystemimpl.c
src/udisksspawnedjob.c
src/udisksspawnedjob.h

index 5cd9e64..388494d 100644 (file)
@@ -89,6 +89,21 @@ main (int argc, char *argv[])
       }
       break;
 
+    case 7:
+      /* read from stdin.. echo that back */
+      {
+        GString *s;
+        gint c;
+
+        s = g_string_new (NULL);
+        while ((c = fgetc (stdin)) != EOF)
+          g_string_append_c (s, c);
+        g_print ("Woah, you said `%s', partner!\n", s->str);
+        g_string_free (s, TRUE);
+        ret = 0;
+      }
+      break;
+
     default:
       g_assert_not_reached ();
       break;
index d347245..7704deb 100644 (file)
@@ -61,7 +61,7 @@ test_spawned_job_successful (void)
 {
   UDisksSpawnedJob *job;
 
-  job = udisks_spawned_job_new ("/bin/true", NULL);
+  job = udisks_spawned_job_new ("/bin/true", NULL, NULL);
   _g_assert_signal_received (job, "completed", G_CALLBACK (on_completed_expect_success), NULL);
   g_object_unref (job);
 }
@@ -73,7 +73,7 @@ test_spawned_job_failure (void)
 {
   UDisksSpawnedJob *job;
 
-  job = udisks_spawned_job_new ("/bin/false", NULL);
+  job = udisks_spawned_job_new ("/bin/false", NULL, NULL);
   _g_assert_signal_received (job, "completed", G_CALLBACK (on_completed_expect_failure),
                              "Command-line `/bin/false' exited with non-zero exit status 1.\n"
                              "stdout: `'\n"
@@ -88,7 +88,7 @@ test_spawned_job_missing_program (void)
 {
   UDisksSpawnedJob *job;
 
-  job = udisks_spawned_job_new ("/path/to/unknown/file", NULL);
+  job = udisks_spawned_job_new ("/path/to/unknown/file", NULL, NULL);
   _g_assert_signal_received (job, "completed", G_CALLBACK (on_completed_expect_failure),
                              "Failed to execute command-line `/path/to/unknown/file': Error spawning command-line `/path/to/unknown/file': Failed to execute child process \"/path/to/unknown/file\" (No such file or directory) (g-exec-error-quark, 8)");
   g_object_unref (job);
@@ -104,7 +104,7 @@ test_spawned_job_cancelled_at_start (void)
 
   cancellable = g_cancellable_new ();
   g_cancellable_cancel (cancellable);
-  job = udisks_spawned_job_new ("/bin/true", cancellable);
+  job = udisks_spawned_job_new ("/bin/true", NULL, cancellable);
   _g_assert_signal_received (job, "completed", G_CALLBACK (on_completed_expect_failure),
                              "Failed to execute command-line `/bin/true': Operation was cancelled (g-io-error-quark, 19)");
   g_object_unref (job);
@@ -129,7 +129,7 @@ test_spawned_job_cancelled_midway (void)
   GCancellable *cancellable;
 
   cancellable = g_cancellable_new ();
-  job = udisks_spawned_job_new ("/bin/sleep 0.5", cancellable);
+  job = udisks_spawned_job_new ("/bin/sleep 0.5", NULL, cancellable);
   g_timeout_add (10, on_timeout, cancellable); /* 10 msec */
   g_main_loop_run (loop);
   _g_assert_signal_received (job, "completed", G_CALLBACK (on_completed_expect_failure),
@@ -161,7 +161,7 @@ test_spawned_job_override_signal_handler (void)
   UDisksSpawnedJob *job;
   gboolean handler_ran;
 
-  job = udisks_spawned_job_new ("/path/to/unknown/file", NULL /* GCancellable */);
+  job = udisks_spawned_job_new ("/path/to/unknown/file", NULL, NULL /* GCancellable */);
   handler_ran = FALSE;
   g_signal_connect (job, "spawned-job-completed", G_CALLBACK (on_spawned_job_completed), &handler_ran);
   _g_assert_signal_received (job, "completed", G_CALLBACK (on_completed_expect_failure),
@@ -177,7 +177,7 @@ test_spawned_job_premature_termination (void)
 {
   UDisksSpawnedJob *job;
 
-  job = udisks_spawned_job_new ("/bin/sleep 1000", NULL /* GCancellable */);
+  job = udisks_spawned_job_new ("/bin/sleep 1000", NULL, NULL /* GCancellable */);
   g_object_unref (job);
 }
 
@@ -208,7 +208,7 @@ test_spawned_job_read_stdout (void)
   gchar *s;
 
   s = g_strdup_printf (UDISKS_TEST_DIR "/udisks-test-helper 0");
-  job = udisks_spawned_job_new (s, NULL);
+  job = udisks_spawned_job_new (s, NULL, NULL);
   _g_assert_signal_received (job, "spawned-job-completed", G_CALLBACK (read_stdout_on_spawned_job_completed), NULL);
   g_object_unref (job);
   g_free (s);
@@ -241,7 +241,7 @@ test_spawned_job_read_stderr (void)
   gchar *s;
 
   s = g_strdup_printf (UDISKS_TEST_DIR "/udisks-test-helper 1");
-  job = udisks_spawned_job_new (s, NULL);
+  job = udisks_spawned_job_new (s, NULL, NULL);
   _g_assert_signal_received (job, "spawned-job-completed", G_CALLBACK (read_stderr_on_spawned_job_completed), NULL);
   g_object_unref (job);
   g_free (s);
@@ -272,14 +272,14 @@ test_spawned_job_exit_status (void)
   gchar *s;
 
   s = g_strdup_printf (UDISKS_TEST_DIR "/udisks-test-helper 2");
-  job = udisks_spawned_job_new (s, NULL);
+  job = udisks_spawned_job_new (s, NULL, NULL);
   _g_assert_signal_received (job, "spawned-job-completed", G_CALLBACK (exit_status_on_spawned_job_completed),
                              GINT_TO_POINTER (1));
   g_object_unref (job);
   g_free (s);
 
   s = g_strdup_printf (UDISKS_TEST_DIR "/udisks-test-helper 3");
-  job = udisks_spawned_job_new (s, NULL);
+  job = udisks_spawned_job_new (s, NULL, NULL);
   _g_assert_signal_received (job, "spawned-job-completed", G_CALLBACK (exit_status_on_spawned_job_completed),
                              GINT_TO_POINTER (2));
   g_object_unref (job);
@@ -295,7 +295,7 @@ test_spawned_job_abnormal_termination (void)
   gchar *s;
 
   s = g_strdup_printf (UDISKS_TEST_DIR "/udisks-test-helper 4");
-  job = udisks_spawned_job_new (s, NULL);
+  job = udisks_spawned_job_new (s, NULL, NULL);
   _g_assert_signal_received (job, "completed", G_CALLBACK (on_completed_expect_failure),
                              "Command-line `./udisks-test-helper 4' was signaled with signal SIGSEGV (11).\n"
                              "stdout: `OK, deliberately causing a segfault\n"
@@ -305,7 +305,7 @@ test_spawned_job_abnormal_termination (void)
   g_free (s);
 
   s = g_strdup_printf (UDISKS_TEST_DIR "/udisks-test-helper 5");
-  job = udisks_spawned_job_new (s, NULL);
+  job = udisks_spawned_job_new (s, NULL, NULL);
   _g_assert_signal_received (job, "completed", G_CALLBACK (on_completed_expect_failure),
                              "Command-line `./udisks-test-helper 5' was signaled with signal SIGABRT (6).\n"
                              "stdout: `OK, deliberately abort()'ing\n"
@@ -348,7 +348,7 @@ test_spawned_job_binary_output (void)
   gchar *s;
 
   s = g_strdup_printf (UDISKS_TEST_DIR "/udisks-test-helper 6");
-  job = udisks_spawned_job_new (s, NULL);
+  job = udisks_spawned_job_new (s, NULL, NULL);
   _g_assert_signal_received (job, "spawned-job-completed", G_CALLBACK (binary_output_on_spawned_job_completed), NULL);
   g_object_unref (job);
   g_free (s);
@@ -356,6 +356,37 @@ test_spawned_job_binary_output (void)
 
 /* ---------------------------------------------------------------------------------------------------- */
 
+static gboolean
+input_string_on_spawned_job_completed (UDisksSpawnedJob *job,
+                                       GError           *error,
+                                       gint              status,
+                                       GString          *standard_output,
+                                       GString          *standard_error,
+                                       gpointer          user_data)
+{
+  g_assert_no_error (error);
+  g_assert_cmpstr (standard_error->str, ==, "");
+  g_assert (WIFEXITED (status));
+  g_assert (WEXITSTATUS (status) == 0);
+  g_assert_cmpstr (standard_output->str, ==, "Woah, you said `foobar', partner!\n");
+  return FALSE;
+}
+
+static void
+test_spawned_job_input_string (void)
+{
+  UDisksSpawnedJob *job;
+  gchar *s;
+
+  s = g_strdup_printf (UDISKS_TEST_DIR "/udisks-test-helper 7");
+  job = udisks_spawned_job_new (s, "foobar", NULL);
+  _g_assert_signal_received (job, "spawned-job-completed", G_CALLBACK (input_string_on_spawned_job_completed), NULL);
+  g_object_unref (job);
+  g_free (s);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
 int
 main (int    argc,
       char **argv)
@@ -379,6 +410,7 @@ main (int    argc,
   g_test_add_func ("/udisks/daemon/spawned_job/exit_status", test_spawned_job_exit_status);
   g_test_add_func ("/udisks/daemon/spawned_job/abnormal_termination", test_spawned_job_abnormal_termination);
   g_test_add_func ("/udisks/daemon/spawned_job/binary_output", test_spawned_job_binary_output);
+  g_test_add_func ("/udisks/daemon/spawned_job/input_string", test_spawned_job_input_string);
 
   ret = g_test_run();
 
index 0b8034d..c758bce 100644 (file)
@@ -265,6 +265,7 @@ on_job_completed (UDisksJob    *job,
  * udisks_daemon_launch_spawned_job:
  * @daemon: A #UDisksDaemon.
  * @cancellable: A #GCancellable or %NULL.
+ * @input_string: A string to write to stdin of the spawned program or %NULL.
  * @command_line_format: printf()-style format for the command line to spawn.
  * @...: Arguments for @command_line_format.
  *
@@ -279,6 +280,7 @@ on_job_completed (UDisksJob    *job,
 UDisksSpawnedJob *
 udisks_daemon_launch_spawned_job (UDisksDaemon    *daemon,
                                   GCancellable    *cancellable,
+                                  const gchar     *input_string,
                                   const gchar     *command_line_format,
                                   ...)
 {
@@ -296,7 +298,7 @@ udisks_daemon_launch_spawned_job (UDisksDaemon    *daemon,
   va_start (var_args, command_line_format);
   command_line = g_strdup_vprintf (command_line_format, var_args);
   va_end (var_args);
-  job = udisks_spawned_job_new (command_line, cancellable);
+  job = udisks_spawned_job_new (command_line, input_string, cancellable);
   g_free (command_line);
 
   /* TODO: protect job_id by a mutex */
index e9e04f6..933b47c 100644 (file)
@@ -36,8 +36,9 @@ GDBusObjectManager *udisks_daemon_get_object_manager (UDisksDaemon    *daemon);
 
 UDisksSpawnedJob   *udisks_daemon_launch_spawned_job (UDisksDaemon    *daemon,
                                                       GCancellable    *cancellable,
+                                                      const gchar     *input_string,
                                                       const gchar     *command_line_format,
-                                                      ...) G_GNUC_PRINTF (3, 4);
+                                                      ...) G_GNUC_PRINTF (4, 5);
 
 G_END_DECLS
 
index 3264aa9..74f1208 100644 (file)
@@ -120,6 +120,7 @@ handle_mount (UDisksFilesystem       *interface,
 
   job = UDISKS_JOB (udisks_daemon_launch_spawned_job (daemon,
                                                       NULL, /* GCancellable */
+                                                      NULL, /* input string */
                                                       "sleep %d", 2));
   /* this blows a little bit - would be nice to have an easier way to
    * get back to the object from the job
index 21705d9..beeea12 100644 (file)
@@ -32,7 +32,7 @@
  * @short_description: Job that spawns a command
  *
  * This type provides an implementation of the #UDisksJob interface
- * for jobs that are implemented by spawning a command.
+ * for jobs that are implemented by spawning a command line.
  */
 
 typedef struct _UDisksSpawnedJobClass   UDisksSpawnedJobClass;
@@ -53,14 +53,20 @@ struct _UDisksSpawnedJob
 
   GMainContext *main_context;
 
+  gchar *input_string;
+  const gchar *input_string_cursor;
+
   GPid child_pid;
+  gint child_stdin_fd;
   gint child_stdout_fd;
   gint child_stderr_fd;
 
+  GIOChannel *child_stdin_channel;
   GIOChannel *child_stdout_channel;
   GIOChannel *child_stderr_channel;
 
   GSource *child_watch_source;
+  GSource *child_stdin_source;
   GSource *child_stdout_source;
   GSource *child_stderr_source;
 
@@ -85,6 +91,7 @@ enum
 {
   PROP_0,
   PROP_COMMAND_LINE,
+  PROP_INPUT_STRING,
   PROP_CANCELLABLE
 };
 
@@ -119,6 +126,13 @@ udisks_spawned_job_finalize (GObject *object)
 
   g_free (job->command_line);
 
+  /* input string may contain key material - nuke contents */
+  if (job->input_string != NULL)
+    {
+      memset (job->input_string, '\0', strlen (job->input_string));
+      g_free (job->input_string);
+    }
+
   if (G_OBJECT_CLASS (udisks_spawned_job_parent_class)->finalize != NULL)
     G_OBJECT_CLASS (udisks_spawned_job_parent_class)->finalize (object);
 }
@@ -162,6 +176,11 @@ udisks_spawned_job_set_property (GObject      *object,
       job->command_line = g_value_dup_string (value);
       break;
 
+    case PROP_INPUT_STRING:
+      g_assert (job->input_string == NULL);
+      job->input_string = g_value_dup_string (value);
+      break;
+
     case PROP_CANCELLABLE:
       g_assert (job->cancellable == NULL);
       job->cancellable = g_value_dup_object (value);
@@ -266,6 +285,38 @@ read_child_stdout (GIOChannel *channel,
   return TRUE;
 }
 
+static gboolean
+write_child_stdin (GIOChannel *channel,
+                   GIOCondition condition,
+                   gpointer user_data)
+{
+  UDisksSpawnedJob *job = UDISKS_SPAWNED_JOB (user_data);
+  gsize bytes_written;
+
+  if (job->input_string_cursor == NULL || *job->input_string_cursor == '\0')
+    {
+      /* nothing left to write; close our end so the child will get EOF */
+      g_io_channel_unref (job->child_stdin_channel);
+      g_source_destroy (job->child_stdin_source);
+      g_warn_if_fail (close (job->child_stdin_fd) == 0);
+      job->child_stdin_channel = NULL;
+      job->child_stdin_source = NULL;
+      job->child_stdin_fd = -1;
+      return FALSE;
+    }
+
+  g_io_channel_write_chars (channel,
+                            job->input_string_cursor,
+                            strlen (job->input_string_cursor),
+                            &bytes_written,
+                            NULL);
+  g_io_channel_flush (channel, NULL);
+  job->input_string_cursor += bytes_written;
+
+  /* keep writing */
+  return TRUE;
+}
+
 static void
 child_watch_cb (GPid     pid,
                 gint     status,
@@ -355,7 +406,7 @@ udisks_spawned_job_constructed (GObject *object)
                                  NULL, /* child_setup */
                                  NULL, /* child_setup's user_data */
                                  &(job->child_pid),
-                                 NULL, // TODO:stdin: stdin_str != NULL ? &(job->stdin_fd) : NULL,
+                                 job->input_string != NULL ? &(job->child_stdin_fd) : NULL,
                                  &(job->child_stdout_fd),
                                  &(job->child_stderr_fd),
                                  &error))
@@ -373,6 +424,18 @@ udisks_spawned_job_constructed (GObject *object)
   g_source_attach (job->child_watch_source, job->main_context);
   g_source_unref (job->child_watch_source);
 
+  if (job->child_stdin_fd != -1)
+    {
+      job->input_string_cursor = job->input_string;
+
+      job->child_stdin_channel = g_io_channel_unix_new (job->child_stdin_fd);
+      g_io_channel_set_flags (job->child_stdin_channel, G_IO_FLAG_NONBLOCK, NULL);
+      job->child_stdin_source = g_io_create_watch (job->child_stdin_channel, G_IO_OUT);
+      g_source_set_callback (job->child_stdin_source, (GSourceFunc) write_child_stdin, job, NULL);
+      g_source_attach (job->child_stdin_source, job->main_context);
+      g_source_unref (job->child_stdin_source);
+    }
+
   job->child_stdout = g_string_new (NULL);
   job->child_stdout_channel = g_io_channel_unix_new (job->child_stdout_fd);
   g_io_channel_set_flags (job->child_stdout_channel, G_IO_FLAG_NONBLOCK, NULL);
@@ -397,8 +460,9 @@ udisks_spawned_job_constructed (GObject *object)
 static void
 udisks_spawned_job_init (UDisksSpawnedJob *job)
 {
-  job->child_stderr_fd = -1;
+  job->child_stdin_fd = -1;
   job->child_stdout_fd = -1;
+  job->child_stderr_fd = -1;
 }
 
 static void
@@ -431,6 +495,22 @@ udisks_spawned_job_class_init (UDisksSpawnedJobClass *klass)
                                                         G_PARAM_STATIC_STRINGS));
 
   /**
+   * UDisksSpawnedJob:input-string:
+   *
+   * String that will be written to stdin of the spawned program or
+   * %NULL to not write anything.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_INPUT_STRING,
+                                   g_param_spec_string ("input-string",
+                                                        "Input String",
+                                                        "String to write to stdin of the spawned program",
+                                                        NULL,
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_STRINGS));
+
+  /**
    * UDisksSpawnedJob:cancellable:
    *
    * The #GCancellable to use.
@@ -454,8 +534,8 @@ udisks_spawned_job_class_init (UDisksSpawnedJobClass *klass)
    * @standard_output: Standard output from the command line that was run.
    * @standard_error: Standard error output from the command line that was run.
    *
-   * Emitted when the spawned job has completed. If spawning the
-   * command failed or if the job was cancelled, @error will
+   * Emitted when the spawned job is complete. If spawning the command
+   * failed or if the job was cancelled, @error will
    * non-%NULL. Otherwise you can use macros such as WIFEXITED() and
    * WEXITSTATUS() on the @status integer to obtain more information.
    *
@@ -488,6 +568,7 @@ udisks_spawned_job_class_init (UDisksSpawnedJobClass *klass)
 /**
  * udisks_spawned_job_new:
  * @command_line: The command line to run.
+ * @input_string: A string to write to stdin of the spawned program or %NULL.
  * @cancellable: A #GCancellable or %NULL.
  *
  * Creates a new #UDisksSpawnedJob instance.
@@ -496,12 +577,14 @@ udisks_spawned_job_class_init (UDisksSpawnedJobClass *klass)
  */
 UDisksSpawnedJob *
 udisks_spawned_job_new (const gchar  *command_line,
+                        const gchar  *input_string,
                         GCancellable *cancellable)
 {
   g_return_val_if_fail (command_line != NULL, NULL);
   g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
   return UDISKS_SPAWNED_JOB (g_object_new (UDISKS_TYPE_SPAWNED_JOB,
                                            "command-line", command_line,
+                                           "input-string", input_string,
                                            "cancellable", cancellable,
                                            NULL));
 }
@@ -717,6 +800,11 @@ udisks_spawned_job_release_resources (UDisksSpawnedJob *job)
       job->child_stderr = NULL;
     }
 
+  if (job->child_stdin_channel != NULL)
+    {
+      g_io_channel_unref (job->child_stdin_channel);
+      job->child_stdin_channel = NULL;
+    }
   if (job->child_stdout_channel != NULL)
     {
       g_io_channel_unref (job->child_stdout_channel);
@@ -728,6 +816,11 @@ udisks_spawned_job_release_resources (UDisksSpawnedJob *job)
       job->child_stderr_channel = NULL;
     }
 
+  if (job->child_stdin_source != NULL)
+    {
+      g_source_destroy (job->child_stdin_source);
+      job->child_stdin_source = NULL;
+    }
   if (job->child_stdout_source != NULL)
     {
       g_source_destroy (job->child_stdout_source);
@@ -739,6 +832,11 @@ udisks_spawned_job_release_resources (UDisksSpawnedJob *job)
       job->child_stderr_source = NULL;
     }
 
+  if (job->child_stdin_fd != -1)
+    {
+      g_warn_if_fail (close (job->child_stdin_fd) == 0);
+      job->child_stdin_fd = -1;
+    }
   if (job->child_stdout_fd != -1)
     {
       g_warn_if_fail (close (job->child_stdout_fd) == 0);
index 1379b99..309971d 100644 (file)
@@ -30,7 +30,8 @@ G_BEGIN_DECLS
 #define UDISKS_IS_SPAWNED_JOB(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), UDISKS_TYPE_SPAWNED_JOB))
 
 GType              udisks_spawned_job_get_type         (void) G_GNUC_CONST;
-UDisksSpawnedJob  *udisks_spawned_job_new              (const gchar *command_line,
+UDisksSpawnedJob  *udisks_spawned_job_new              (const gchar  *command_line,
+                                                        const gchar  *input_string,
                                                         GCancellable *cancellable);
 const gchar       *udisks_spawned_job_get_command_line (UDisksSpawnedJob *job);