Fix typo: Should be SOCKET_ERROR, not SO_ERROR. Noticed by Daniel
[platform/upstream/glib.git] / glib / gspawn-win32.c
index e0e9450..66cfe53 100644 (file)
 
 #include <config.h>
 
+#include "config.h"
+
 #include "glib.h"
+#include "gprintfint.h"
 
 #include <string.h>
 #include <stdlib.h>
@@ -94,11 +97,90 @@ enum {
   ARG_COUNT = ARG_PROGRAM
 };
 
+static gint
+protect_argv (gchar  **argv,
+             gchar ***new_argv)
+{
+  gint i;
+  gint argc = 0;
+  
+  while (argv[argc])
+    ++argc;
+  *new_argv = g_new (gchar *, argc+1);
+
+  /* Quote each argv element if necessary, so that it will get
+   * reconstructed correctly in the C runtime startup code.  Note that
+   * the unquoting algorithm in the C runtime is really weird, and
+   * rather different than what Unix shells do. See stdargv.c in the C
+   * runtime sources (in the Platform SDK, in src/crt).
+   *
+   * Note that an new_argv[0] constructed by this function should
+   * *not* be passed as the filename argument to a spawn* or exec*
+   * family function. That argument should be the real file name
+   * without any quoting.
+   */
+  for (i = 0; i < argc; i++)
+    {
+      gchar *p = argv[i];
+      gchar *q;
+      gint len = 0;
+      gboolean need_dblquotes = FALSE;
+      while (*p)
+       {
+         if (*p == ' ' || *p == '\t')
+           need_dblquotes = TRUE;
+         else if (*p == '"')
+           len++;
+         else if (*p == '\\')
+           {
+             gchar *pp = p;
+             while (*pp && *pp == '\\')
+               pp++;
+             if (*pp == '"')
+               len++;
+           }
+         len++;
+         p++;
+       }
+
+      q = (*new_argv)[i] = g_malloc (len + need_dblquotes*2 + 1);
+      p = argv[i];
+
+      if (need_dblquotes)
+       *q++ = '"';
+
+      while (*p)
+       {
+         if (*p == '"')
+           *q++ = '\\';
+         else if (*p == '\\')
+           {
+             gchar *pp = p;
+             while (*pp && *pp == '\\')
+               pp++;
+             if (*pp == '"')
+               *q++ = '\\';
+           }
+         *q++ = *p;
+         p++;
+       }
+
+      if (need_dblquotes)
+       *q++ = '"';
+      *q++ = '\0';
+      /* printf ("argv[%d]:%s, need_dblquotes:%s len:%d => %s\n", i, argv[i], need_dblquotes?"TRUE":"FALSE", len, (*new_argv)[i]); */
+    }
+  (*new_argv)[argc] = NULL;
+
+  return argc;
+}
+
 #ifndef GSPAWN_HELPER
 
 static gboolean make_pipe            (gint                  p[2],
                                       GError              **error);
 static gboolean do_spawn_with_pipes  (gboolean              dont_wait,
+                                     gboolean              dont_return_handle,
                                      const gchar          *working_directory,
                                       gchar               **argv,
                                       gchar               **envp,
@@ -109,6 +191,7 @@ static gboolean do_spawn_with_pipes  (gboolean              dont_wait,
                                       gboolean              child_inherits_stdin,
                                       GSpawnChildSetupFunc  child_setup,
                                       gpointer              user_data,
+                                      gint                 *child_pid,
                                       gint                 *standard_input,
                                       gint                 *standard_output,
                                       gint                 *standard_error,
@@ -222,6 +305,7 @@ g_spawn_sync (const gchar          *working_directory,
 {
   gint outpipe = -1;
   gint errpipe = -1;
+  gint pid;
   GIOChannel *outchannel = NULL;
   GIOChannel *errchannel = NULL;
   GPollFD outfd, errfd;
@@ -252,6 +336,7 @@ g_spawn_sync (const gchar          *working_directory,
     *standard_error = NULL;
   
   if (!do_spawn_with_pipes (FALSE,
+                           TRUE,
                            working_directory,
                            argv,
                            envp,
@@ -262,6 +347,7 @@ g_spawn_sync (const gchar          *working_directory,
                            (flags & G_SPAWN_CHILD_INHERITS_STDIN) != 0,
                            child_setup,
                            user_data,
+                           &pid,
                            NULL,
                            standard_output ? &outpipe : NULL,
                            standard_error ? &errpipe : NULL,
@@ -441,6 +527,7 @@ g_spawn_async_with_pipes (const gchar          *working_directory,
                         !(flags & G_SPAWN_CHILD_INHERITS_STDIN), FALSE);
   
   return do_spawn_with_pipes (TRUE,
+                             !(flags & G_SPAWN_DO_NOT_REAP_CHILD),
                              working_directory,
                              argv,
                              envp,
@@ -451,6 +538,7 @@ g_spawn_async_with_pipes (const gchar          *working_directory,
                              (flags & G_SPAWN_CHILD_INHERITS_STDIN) != 0,
                              child_setup,
                              user_data,
+                             child_pid,
                              standard_input,
                              standard_output,
                              standard_error,
@@ -537,6 +625,7 @@ do_spawn (gboolean              dont_wait,
   gchar **new_argv;
   gchar args[ARG_COUNT][10];
   gint i;
+  int rc;
   int argc = 0;
 
   SETUP_DEBUG();
@@ -547,12 +636,12 @@ do_spawn (gboolean              dont_wait,
   new_argv = g_new (gchar *, argc + 1 + ARG_COUNT);
 
   new_argv[0] = "gspawn-win32-helper";
-  sprintf (args[ARG_CHILD_ERR_REPORT], "%d", child_err_report_fd);
+  _g_sprintf (args[ARG_CHILD_ERR_REPORT], "%d", child_err_report_fd);
   new_argv[ARG_CHILD_ERR_REPORT] = args[ARG_CHILD_ERR_REPORT];
 
   if (stdin_fd >= 0)
     {
-      sprintf (args[ARG_STDIN], "%d", stdin_fd);
+      _g_sprintf (args[ARG_STDIN], "%d", stdin_fd);
       new_argv[ARG_STDIN] = args[ARG_STDIN];
     }
   else if (child_inherits_stdin)
@@ -568,7 +657,7 @@ do_spawn (gboolean              dont_wait,
 
   if (stdout_fd >= 0)
     {
-      sprintf (args[ARG_STDOUT], "%d", stdout_fd);
+      _g_sprintf (args[ARG_STDOUT], "%d", stdout_fd);
       new_argv[ARG_STDOUT] = args[ARG_STDOUT];
     }
   else if (stdout_to_null)
@@ -582,7 +671,7 @@ do_spawn (gboolean              dont_wait,
 
   if (stderr_fd >= 0)
     {
-      sprintf (args[ARG_STDERR], "%d", stderr_fd);
+      _g_sprintf (args[ARG_STDERR], "%d", stderr_fd);
       new_argv[ARG_STDERR] = args[ARG_STDERR];
     }
   else if (stderr_to_null)
@@ -595,9 +684,10 @@ do_spawn (gboolean              dont_wait,
     }
 
   if (working_directory && *working_directory)
-    new_argv[ARG_WORKING_DIRECTORY] = working_directory;
+    /* The g_strdup() to lose the constness */
+    new_argv[ARG_WORKING_DIRECTORY] = g_strdup (working_directory);
   else
-    new_argv[ARG_WORKING_DIRECTORY] = "-";
+    new_argv[ARG_WORKING_DIRECTORY] = g_strdup ("-");
 
   if (close_descriptors)
     new_argv[ARG_CLOSE_DESCRIPTORS] = "y";
@@ -638,11 +728,9 @@ do_spawn (gboolean              dont_wait,
     /* Let's hope envp hasn't mucked with PATH so that
      * gspawn-win32-helper.exe isn't found.
      */
-    spawnvpe (P_NOWAIT, "gspawn-win32-helper", new_argv, envp);
+    rc = spawnvpe (P_NOWAIT, "gspawn-win32-helper", new_argv, envp);
   else
-    spawnvp (P_NOWAIT, "gspawn-win32-helper", new_argv);
-
-  /* FIXME: What if gspawn-win32-helper.exe isn't found? */
+    rc = spawnvp (P_NOWAIT, "gspawn-win32-helper", new_argv);
 
   /* Close the child_err_report_fd and the other process's ends of the
    * pipes in this process, otherwise the reader will never get
@@ -656,9 +744,10 @@ do_spawn (gboolean              dont_wait,
   if (stderr_fd >= 0)
     close (stderr_fd);
 
+  g_free (new_argv[ARG_WORKING_DIRECTORY]);
   g_free (new_argv);
 
-  return 0;
+  return rc;
 }
 
 static gboolean
@@ -710,6 +799,7 @@ read_ints (int      fd,
 
 static gboolean
 do_spawn_with_pipes (gboolean              dont_wait,
+                    gboolean              dont_return_handle,
                     const gchar          *working_directory,
                     gchar               **argv,
                     gchar               **envp,
@@ -720,6 +810,7 @@ do_spawn_with_pipes (gboolean              dont_wait,
                     gboolean              child_inherits_stdin,
                     GSpawnChildSetupFunc  child_setup,
                     gpointer              user_data,
+                    gint                 *child_pid,
                     gint                 *standard_input,
                     gint                 *standard_output,
                     gint                 *standard_error,
@@ -730,9 +821,12 @@ do_spawn_with_pipes (gboolean              dont_wait,
   gint stdout_pipe[2] = { -1, -1 };
   gint stderr_pipe[2] = { -1, -1 };
   gint child_err_report_pipe[2] = { -1, -1 };
-  gint status;
+  gint helper = -1;
   gint buf[2];
   gint n_ints = 0;
+  gint i;
+  gint argc;
+  gchar **new_argv;
   
   if (!make_pipe (child_err_report_pipe, error))
     return FALSE;
@@ -746,13 +840,15 @@ do_spawn_with_pipes (gboolean              dont_wait,
   if (standard_error && !make_pipe (stderr_pipe, error))
     goto cleanup_and_fail;
 
-  status = do_spawn (dont_wait,
+  argc = protect_argv (argv, &new_argv);
+
+  helper = do_spawn (dont_wait,
                     child_err_report_pipe[1],
                     stdin_pipe[0],
                     stdout_pipe[1],
                     stderr_pipe[1],
                     working_directory,
-                    argv,
+                    new_argv,
                     envp,
                     close_descriptors,
                     search_path,
@@ -762,37 +858,61 @@ do_spawn_with_pipes (gboolean              dont_wait,
                     child_setup,
                     user_data);
       
+  for (i = 0; i < argc; i++)
+    g_free (new_argv[i]);
+  g_free (new_argv);
+
+  /* do_spawn() returns -1 if gspawn-win32-helper couldn't be run */
+  if (helper == -1)
+    {
+      g_set_error (error,
+                  G_SPAWN_ERROR,
+                  G_SPAWN_ERROR_FAILED,
+                  _("Failed to execute helper program"));
+      goto cleanup_and_fail;
+    }
+
   if (!read_ints (child_err_report_pipe[0],
                  buf, 2, &n_ints,
-                 error))
+                 error) ||
+      n_ints != 2)
     goto cleanup_and_fail;
         
-  if (n_ints == 2)
+  /* Error code from gspawn-win32-helper. */
+  switch (buf[0])
     {
-      /* Error from the child. */
-      
-      switch (buf[0])
+    case CHILD_NO_ERROR:
+      if (child_pid && dont_wait && !dont_return_handle)
        {
-       case CHILD_NO_ERROR:
-         break;
-         
-       case CHILD_CHDIR_FAILED:
-         g_set_error (error,
-                      G_SPAWN_ERROR,
-                      G_SPAWN_ERROR_CHDIR,
-                      _("Failed to change to directory '%s' (%s)"),
-                      working_directory,
-                      g_strerror (buf[1]));
-         goto cleanup_and_fail;
-         
-       case CHILD_SPAWN_FAILED:
-         g_set_error (error,
-                      G_SPAWN_ERROR,
-                      G_SPAWN_ERROR_FAILED,
-                      _("Failed to execute child process (%s)"),
-                      g_strerror (buf[1]));
-         goto cleanup_and_fail;
+         /* helper is our HANDLE for gspawn-win32-helper. It has
+          * told us the HANDLE of its child. Duplicate that into
+          * a HANDLE valid in this process.
+          */
+         if (!DuplicateHandle ((HANDLE) helper, (HANDLE) buf[1],
+                               GetCurrentProcess (), (LPHANDLE) child_pid,
+                               0, TRUE, DUPLICATE_SAME_ACCESS))
+           *child_pid = 0;
        }
+      else if (child_pid)
+       *child_pid = 0;
+      break;
+      
+    case CHILD_CHDIR_FAILED:
+      g_set_error (error,
+                  G_SPAWN_ERROR,
+                  G_SPAWN_ERROR_CHDIR,
+                  _("Failed to change to directory '%s' (%s)"),
+                  working_directory,
+                  g_strerror (buf[1]));
+      goto cleanup_and_fail;
+      
+    case CHILD_SPAWN_FAILED:
+      g_set_error (error,
+                  G_SPAWN_ERROR,
+                  G_SPAWN_ERROR_FAILED,
+                  _("Failed to execute child process (%s)"),
+                  g_strerror (buf[1]));
+      goto cleanup_and_fail;
     }
 
   /* Success against all odds! return the information */
@@ -804,11 +924,14 @@ do_spawn_with_pipes (gboolean              dont_wait,
   if (standard_error)
     *standard_error = stderr_pipe[0];
   if (exit_status)
-    *exit_status = status;
+    *exit_status = buf[1];
+  CloseHandle ((HANDLE) helper);
   
   return TRUE;
 
  cleanup_and_fail:
+  if (helper != -1)
+    CloseHandle ((HANDLE) helper);
   close_and_invalidate (&child_err_report_pipe[0]);
   close_and_invalidate (&child_err_report_pipe[1]);
   close_and_invalidate (&stdin_pipe[0]);