Imported Upstream version 2.67.4
[platform/upstream/glib.git] / glib / gspawn.c
index 3ed4374..95f5b86 100644 (file)
@@ -167,46 +167,31 @@ static gint g_execute (const gchar  *file,
                        gchar        *search_path_buffer,
                        gsize         search_path_buffer_len);
 
-static gboolean fork_exec_with_pipes (gboolean              intermediate_child,
-                                      const gchar          *working_directory,
-                                      gchar               **argv,
-                                      gchar               **envp,
-                                      gboolean              close_descriptors,
-                                      gboolean              search_path,
-                                      gboolean              search_path_from_envp,
-                                      gboolean              stdout_to_null,
-                                      gboolean              stderr_to_null,
-                                      gboolean              child_inherits_stdin,
-                                      gboolean              file_and_argv_zero,
-                                      gboolean              cloexec_pipes,
-                                      GSpawnChildSetupFunc  child_setup,
-                                      gpointer              user_data,
-                                      GPid                 *child_pid,
-                                      gint                 *standard_input,
-                                      gint                 *standard_output,
-                                      gint                 *standard_error,
-                                      GError              **error);
-
-static gboolean fork_exec_with_fds (gboolean              intermediate_child,
-                                    const gchar          *working_directory,
-                                    gchar               **argv,
-                                    gchar               **envp,
-                                    gboolean              close_descriptors,
-                                    gboolean              search_path,
-                                    gboolean              search_path_from_envp,
-                                    gboolean              stdout_to_null,
-                                    gboolean              stderr_to_null,
-                                    gboolean              child_inherits_stdin,
-                                    gboolean              file_and_argv_zero,
-                                    gboolean              cloexec_pipes,
-                                    GSpawnChildSetupFunc  child_setup,
-                                    gpointer              user_data,
-                                    GPid                 *child_pid,
-                                    gint                 *child_close_fds,
-                                    gint                  stdin_fd,
-                                    gint                  stdout_fd,
-                                    gint                  stderr_fd,
-                                    GError              **error);
+static gboolean fork_exec (gboolean              intermediate_child,
+                           const gchar          *working_directory,
+                           const gchar * const  *argv,
+                           const gchar * const  *envp,
+                           gboolean              close_descriptors,
+                           gboolean              search_path,
+                           gboolean              search_path_from_envp,
+                           gboolean              stdout_to_null,
+                           gboolean              stderr_to_null,
+                           gboolean              child_inherits_stdin,
+                           gboolean              file_and_argv_zero,
+                           gboolean              cloexec_pipes,
+                           GSpawnChildSetupFunc  child_setup,
+                           gpointer              user_data,
+                           GPid                 *child_pid,
+                           gint                 *stdin_pipe_out,
+                           gint                 *stdout_pipe_out,
+                           gint                 *stderr_pipe_out,
+                           gint                  stdin_fd,
+                           gint                  stdout_fd,
+                           gint                  stderr_fd,
+                           const gint           *source_fds,
+                           const gint           *target_fds,
+                           gsize                 n_fds,
+                           GError              **error);
 
 G_DEFINE_QUARK (g-exec-error-quark, g_spawn_error)
 G_DEFINE_QUARK (g-spawn-exit-error-quark, g_spawn_exit_error)
@@ -264,6 +249,16 @@ g_spawn_async (const gchar          *working_directory,
                                    error);
 }
 
+/* This function is called between fork() and exec() and hence must be
+ * async-signal-safe (see signal-safety(7)). */
+static gint
+steal_fd (gint *fd)
+{
+  gint fd_out = *fd;
+  *fd = -1;
+  return fd_out;
+}
+
 /* Avoids a danger in threaded situations (calling close()
  * on a file descriptor twice, and another thread has
  * re-opened it since the first close)
@@ -402,25 +397,27 @@ g_spawn_sync (const gchar          *working_directory,
   if (standard_error)
     *standard_error = NULL;
   
-  if (!fork_exec_with_pipes (FALSE,
-                             working_directory,
-                             argv,
-                             envp,
-                             !(flags & G_SPAWN_LEAVE_DESCRIPTORS_OPEN),
-                             (flags & G_SPAWN_SEARCH_PATH) != 0,
-                             (flags & G_SPAWN_SEARCH_PATH_FROM_ENVP) != 0,
-                             (flags & G_SPAWN_STDOUT_TO_DEV_NULL) != 0,
-                             (flags & G_SPAWN_STDERR_TO_DEV_NULL) != 0,
-                             (flags & G_SPAWN_CHILD_INHERITS_STDIN) != 0,
-                             (flags & G_SPAWN_FILE_AND_ARGV_ZERO) != 0,
-                             (flags & G_SPAWN_CLOEXEC_PIPES) != 0,
-                             child_setup,
-                             user_data,
-                             &pid,
-                             NULL,
-                             standard_output ? &outpipe : NULL,
-                             standard_error ? &errpipe : NULL,
-                             error))
+  if (!fork_exec (FALSE,
+                  working_directory,
+                  (const gchar * const *) argv,
+                  (const gchar * const *) envp,
+                  !(flags & G_SPAWN_LEAVE_DESCRIPTORS_OPEN),
+                  (flags & G_SPAWN_SEARCH_PATH) != 0,
+                  (flags & G_SPAWN_SEARCH_PATH_FROM_ENVP) != 0,
+                  (flags & G_SPAWN_STDOUT_TO_DEV_NULL) != 0,
+                  (flags & G_SPAWN_STDERR_TO_DEV_NULL) != 0,
+                  (flags & G_SPAWN_CHILD_INHERITS_STDIN) != 0,
+                  (flags & G_SPAWN_FILE_AND_ARGV_ZERO) != 0,
+                  (flags & G_SPAWN_CLOEXEC_PIPES) != 0,
+                  child_setup,
+                  user_data,
+                  &pid,
+                  NULL,
+                  standard_output ? &outpipe : NULL,
+                  standard_error ? &errpipe : NULL,
+                  -1, -1, -1,
+                  NULL, NULL, 0,
+                  error))
     return FALSE;
 
   /* Read data from child. */
@@ -598,6 +595,82 @@ g_spawn_sync (const gchar          *working_directory,
  * @standard_error: (out) (optional): return location for file descriptor to read child's stderr, or %NULL
  * @error: return location for error
  *
+ * Identical to g_spawn_async_with_pipes_and_fds() but with `n_fds` set to zero,
+ * so no FD assignments are used.
+ *
+ * Returns: %TRUE on success, %FALSE if an error was set
+ */
+gboolean
+g_spawn_async_with_pipes (const gchar          *working_directory,
+                          gchar               **argv,
+                          gchar               **envp,
+                          GSpawnFlags           flags,
+                          GSpawnChildSetupFunc  child_setup,
+                          gpointer              user_data,
+                          GPid                 *child_pid,
+                          gint                 *standard_input,
+                          gint                 *standard_output,
+                          gint                 *standard_error,
+                          GError              **error)
+{
+  g_return_val_if_fail (argv != NULL, FALSE);
+  g_return_val_if_fail (standard_output == NULL ||
+                        !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE);
+  g_return_val_if_fail (standard_error == NULL ||
+                        !(flags & G_SPAWN_STDERR_TO_DEV_NULL), FALSE);
+  /* can't inherit stdin if we have an input pipe. */
+  g_return_val_if_fail (standard_input == NULL ||
+                        !(flags & G_SPAWN_CHILD_INHERITS_STDIN), FALSE);
+
+  return fork_exec (!(flags & G_SPAWN_DO_NOT_REAP_CHILD),
+                    working_directory,
+                    (const gchar * const *) argv,
+                    (const gchar * const *) envp,
+                    !(flags & G_SPAWN_LEAVE_DESCRIPTORS_OPEN),
+                    (flags & G_SPAWN_SEARCH_PATH) != 0,
+                    (flags & G_SPAWN_SEARCH_PATH_FROM_ENVP) != 0,
+                    (flags & G_SPAWN_STDOUT_TO_DEV_NULL) != 0,
+                    (flags & G_SPAWN_STDERR_TO_DEV_NULL) != 0,
+                    (flags & G_SPAWN_CHILD_INHERITS_STDIN) != 0,
+                    (flags & G_SPAWN_FILE_AND_ARGV_ZERO) != 0,
+                    (flags & G_SPAWN_CLOEXEC_PIPES) != 0,
+                    child_setup,
+                    user_data,
+                    child_pid,
+                    standard_input,
+                    standard_output,
+                    standard_error,
+                    -1, -1, -1,
+                    NULL, NULL, 0,
+                    error);
+}
+
+/**
+ * g_spawn_async_with_pipes_and_fds:
+ * @working_directory: (type filename) (nullable): child's current working
+ *     directory, or %NULL to inherit parent's, in the GLib file name encoding
+ * @argv: (array zero-terminated=1) (element-type filename): child's argument
+ *     vector, in the GLib file name encoding
+ * @envp: (array zero-terminated=1) (element-type filename) (nullable):
+ *     child's environment, or %NULL to inherit parent's, in the GLib file
+ *     name encoding
+ * @flags: flags from #GSpawnFlags
+ * @child_setup: (scope async) (nullable): function to run in the child just before `exec()`
+ * @user_data: (closure): user data for @child_setup
+ * @stdin_fd: file descriptor to use for child's stdin, or `-1`
+ * @stdout_fd: file descriptor to use for child's stdout, or `-1`
+ * @stderr_fd: file descriptor to use for child's stderr, or `-1`
+ * @source_fds: (array length=n_fds) (nullable): array of FDs from the parent
+ *    process to make available in the child process
+ * @target_fds: (array length=n_fds) (nullable): array of FDs to remap
+ *    @source_fds to in the child process
+ * @n_fds: number of FDs in @source_fds and @target_fds
+ * @child_pid_out: (out) (optional): return location for child process ID, or %NULL
+ * @stdin_pipe_out: (out) (optional): return location for file descriptor to write to child's stdin, or %NULL
+ * @stdout_pipe_out: (out) (optional): return location for file descriptor to read child's stdout, or %NULL
+ * @stderr_pipe_out: (out) (optional): return location for file descriptor to read child's stderr, or %NULL
+ * @error: return location for error
+ *
  * Executes a child program asynchronously (your program will not
  * block waiting for the child to exit). The child program is
  * specified by the only argument that must be provided, @argv.
@@ -607,7 +680,7 @@ g_spawn_sync (const gchar          *working_directory,
  * name of the program must be a full path. If @flags contains the
  * %G_SPAWN_SEARCH_PATH flag, the `PATH` environment variable is
  * used to search for the executable. If @flags contains the
- * %G_SPAWN_SEARCH_PATH_FROM_ENVP flag, the `PATH` variable from 
+ * %G_SPAWN_SEARCH_PATH_FROM_ENVP flag, the `PATH` variable from
  * @envp is used to search for the executable. If both the
  * %G_SPAWN_SEARCH_PATH and %G_SPAWN_SEARCH_PATH_FROM_ENVP flags
  * are set, the `PATH` variable from @envp takes precedence over
@@ -662,7 +735,7 @@ g_spawn_sync (const gchar          *working_directory,
  * Eventually you must call g_spawn_close_pid() on the @child_pid, in order to
  * free resources which may be associated with the child process. (On Unix,
  * using a child watch is equivalent to calling waitpid() or handling
- * the %SIGCHLD signal manually. On Windows, calling g_spawn_close_pid()
+ * the `SIGCHLD` signal manually. On Windows, calling g_spawn_close_pid()
  * is equivalent to calling CloseHandle() on the process handle returned
  * in @child_pid). See g_child_watch_add().
  *
@@ -676,15 +749,32 @@ g_spawn_sync (const gchar          *working_directory,
  * absolute path, it will be looked for in the `PATH` variable from
  * @envp. If both %G_SPAWN_SEARCH_PATH and %G_SPAWN_SEARCH_PATH_FROM_ENVP
  * are used, the value from @envp takes precedence over the environment.
+ *
  * %G_SPAWN_STDOUT_TO_DEV_NULL means that the child's standard output
- * will be discarded, instead of going to the same location as the parent's 
- * standard output. If you use this flag, @standard_output must be %NULL.
+ * will be discarded, instead of going to the same location as the parent's
+ * standard output. If you use this flag, @stdout_pipe_out must be %NULL.
+ *
  * %G_SPAWN_STDERR_TO_DEV_NULL means that the child's standard error
  * will be discarded, instead of going to the same location as the parent's
- * standard error. If you use this flag, @standard_error must be %NULL.
+ * standard error. If you use this flag, @stderr_pipe_out must be %NULL.
+ *
  * %G_SPAWN_CHILD_INHERITS_STDIN means that the child will inherit the parent's
  * standard input (by default, the child's standard input is attached to
- * `/dev/null`). If you use this flag, @standard_input must be %NULL.
+ * `/dev/null`). If you use this flag, @stdin_pipe_out must be %NULL.
+ *
+ * It is valid to pass the same FD in multiple parameters (e.g. you can pass
+ * a single FD for both @stdout_fd and @stderr_fd, and include it in
+ * @source_fds too).
+ *
+ * @source_fds and @target_fds allow zero or more FDs from this process to be
+ * remapped to different FDs in the spawned process. If @n_fds is greater than
+ * zero, @source_fds and @target_fds must both be non-%NULL and the same length.
+ * Each FD in @source_fds is remapped to the FD number at the same index in
+ * @target_fds. The source and target FD may be equal to simply propagate an FD
+ * to the spawned process. FD remappings are processed after standard FDs, so
+ * any target FDs which equal @stdin_fd, @stdout_fd or @stderr_fd will overwrite
+ * them in the spawned process.
+ *
  * %G_SPAWN_FILE_AND_ARGV_ZERO means that the first element of @argv is
  * the file to execute, while the remaining elements are the actual
  * argument vector to pass to the file. Normally g_spawn_async_with_pipes()
@@ -714,22 +804,22 @@ g_spawn_sync (const gchar          *working_directory,
  * GetExitCodeProcess(). You should close the handle with CloseHandle()
  * or g_spawn_close_pid() when you no longer need it.
  *
- * If non-%NULL, the @standard_input, @standard_output, @standard_error
+ * If non-%NULL, the @stdin_pipe_out, @stdout_pipe_out, @stderr_pipe_out
  * locations will be filled with file descriptors for writing to the child's
  * standard input or reading from its standard output or standard error.
  * The caller of g_spawn_async_with_pipes() must close these file descriptors
  * when they are no longer in use. If these parameters are %NULL, the
  * corresponding pipe won't be created.
  *
- * If @standard_input is %NULL, the child's standard input is attached to
+ * If @stdin_pipe_out is %NULL, the child's standard input is attached to
  * `/dev/null` unless %G_SPAWN_CHILD_INHERITS_STDIN is set.
  *
- * If @standard_error is NULL, the child's standard error goes to the same 
- * location as the parent's standard error unless %G_SPAWN_STDERR_TO_DEV_NULL 
+ * If @stderr_pipe_out is NULL, the child's standard error goes to the same
+ * location as the parent's standard error unless %G_SPAWN_STDERR_TO_DEV_NULL
  * is set.
  *
- * If @standard_output is NULL, the child's standard output goes to the same 
- * location as the parent's standard output unless %G_SPAWN_STDOUT_TO_DEV_NULL 
+ * If @stdout_pipe_out is NULL, the child's standard output goes to the same
+ * location as the parent's standard output unless %G_SPAWN_STDOUT_TO_DEV_NULL
  * is set.
  *
  * @error can be %NULL to ignore errors, or non-%NULL to report errors.
@@ -739,8 +829,8 @@ g_spawn_sync (const gchar          *working_directory,
  * errors should be displayed to users. Possible errors are those from
  * the #G_SPAWN_ERROR domain.
  *
- * If an error occurs, @child_pid, @standard_input, @standard_output,
- * and @standard_error will not be filled with valid values.
+ * If an error occurs, @child_pid, @stdin_pipe_out, @stdout_pipe_out,
+ * and @stderr_pipe_out will not be filled with valid values.
  *
  * If @child_pid is not %NULL and an error does not occur then the returned
  * process reference must be closed using g_spawn_close_pid().
@@ -763,51 +853,69 @@ g_spawn_sync (const gchar          *working_directory,
  * If you are writing a GTK+ application, and the program you are spawning is a
  * graphical application too, then to ensure that the spawned program opens its
  * windows on the right screen, you may want to use #GdkAppLaunchContext,
- * #GAppLaunchContext, or set the %DISPLAY environment variable.
- * 
+ * #GAppLaunchContext, or set the `DISPLAY` environment variable.
+ *
  * Returns: %TRUE on success, %FALSE if an error was set
+ *
+ * Since: 2.68
  */
 gboolean
-g_spawn_async_with_pipes (const gchar          *working_directory,
-                          gchar               **argv,
-                          gchar               **envp,
-                          GSpawnFlags           flags,
-                          GSpawnChildSetupFunc  child_setup,
-                          gpointer              user_data,
-                          GPid                 *child_pid,
-                          gint                 *standard_input,
-                          gint                 *standard_output,
-                          gint                 *standard_error,
-                          GError              **error)
+g_spawn_async_with_pipes_and_fds (const gchar           *working_directory,
+                                  const gchar * const   *argv,
+                                  const gchar * const   *envp,
+                                  GSpawnFlags            flags,
+                                  GSpawnChildSetupFunc   child_setup,
+                                  gpointer               user_data,
+                                  gint                   stdin_fd,
+                                  gint                   stdout_fd,
+                                  gint                   stderr_fd,
+                                  const gint            *source_fds,
+                                  const gint            *target_fds,
+                                  gsize                  n_fds,
+                                  GPid                  *child_pid_out,
+                                  gint                  *stdin_pipe_out,
+                                  gint                  *stdout_pipe_out,
+                                  gint                  *stderr_pipe_out,
+                                  GError               **error)
 {
   g_return_val_if_fail (argv != NULL, FALSE);
-  g_return_val_if_fail (standard_output == NULL ||
+  g_return_val_if_fail (stdout_pipe_out == NULL ||
                         !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE);
-  g_return_val_if_fail (standard_error == NULL ||
+  g_return_val_if_fail (stderr_pipe_out == NULL ||
                         !(flags & G_SPAWN_STDERR_TO_DEV_NULL), FALSE);
   /* can't inherit stdin if we have an input pipe. */
-  g_return_val_if_fail (standard_input == NULL ||
+  g_return_val_if_fail (stdin_pipe_out == NULL ||
                         !(flags & G_SPAWN_CHILD_INHERITS_STDIN), FALSE);
-  
-  return fork_exec_with_pipes (!(flags & G_SPAWN_DO_NOT_REAP_CHILD),
-                               working_directory,
-                               argv,
-                               envp,
-                               !(flags & G_SPAWN_LEAVE_DESCRIPTORS_OPEN),
-                               (flags & G_SPAWN_SEARCH_PATH) != 0,
-                               (flags & G_SPAWN_SEARCH_PATH_FROM_ENVP) != 0,
-                               (flags & G_SPAWN_STDOUT_TO_DEV_NULL) != 0,
-                               (flags & G_SPAWN_STDERR_TO_DEV_NULL) != 0,
-                               (flags & G_SPAWN_CHILD_INHERITS_STDIN) != 0,
-                               (flags & G_SPAWN_FILE_AND_ARGV_ZERO) != 0,
-                               (flags & G_SPAWN_CLOEXEC_PIPES) != 0,
-                               child_setup,
-                               user_data,
-                               child_pid,
-                               standard_input,
-                               standard_output,
-                               standard_error,
-                               error);
+  /* can’t use pipes and stdin/stdout/stderr FDs */
+  g_return_val_if_fail (stdin_pipe_out == NULL || stdin_fd < 0, FALSE);
+  g_return_val_if_fail (stdout_pipe_out == NULL || stdout_fd < 0, FALSE);
+  g_return_val_if_fail (stderr_pipe_out == NULL || stderr_fd < 0, FALSE);
+
+  return fork_exec (!(flags & G_SPAWN_DO_NOT_REAP_CHILD),
+                    working_directory,
+                    (const gchar * const *) argv,
+                    (const gchar * const *) envp,
+                    !(flags & G_SPAWN_LEAVE_DESCRIPTORS_OPEN),
+                    (flags & G_SPAWN_SEARCH_PATH) != 0,
+                    (flags & G_SPAWN_SEARCH_PATH_FROM_ENVP) != 0,
+                    (flags & G_SPAWN_STDOUT_TO_DEV_NULL) != 0,
+                    (flags & G_SPAWN_STDERR_TO_DEV_NULL) != 0,
+                    (flags & G_SPAWN_CHILD_INHERITS_STDIN) != 0,
+                    (flags & G_SPAWN_FILE_AND_ARGV_ZERO) != 0,
+                    (flags & G_SPAWN_CLOEXEC_PIPES) != 0,
+                    child_setup,
+                    user_data,
+                    child_pid_out,
+                    stdin_pipe_out,
+                    stdout_pipe_out,
+                    stderr_pipe_out,
+                    stdin_fd,
+                    stdout_fd,
+                    stderr_fd,
+                    source_fds,
+                    target_fds,
+                    n_fds,
+                    error);
 }
 
 /**
@@ -819,29 +927,13 @@ g_spawn_async_with_pipes (const gchar          *working_directory,
  * @child_setup: (scope async) (nullable): function to run in the child just before exec()
  * @user_data: (closure): user data for @child_setup
  * @child_pid: (out) (optional): return location for child process ID, or %NULL
- * @stdin_fd: file descriptor to use for child's stdin, or -1
- * @stdout_fd: file descriptor to use for child's stdout, or -1
- * @stderr_fd: file descriptor to use for child's stderr, or -1
+ * @stdin_fd: file descriptor to use for child's stdin, or `-1`
+ * @stdout_fd: file descriptor to use for child's stdout, or `-1`
+ * @stderr_fd: file descriptor to use for child's stderr, or `-1`
  * @error: return location for error
  *
- * Identical to g_spawn_async_with_pipes() but instead of
- * creating pipes for the stdin/stdout/stderr, you can pass existing
- * file descriptors into this function through the @stdin_fd,
- * @stdout_fd and @stderr_fd parameters. The following @flags
- * also have their behaviour slightly tweaked as a result:
- *
- * %G_SPAWN_STDOUT_TO_DEV_NULL means that the child's standard output
- * will be discarded, instead of going to the same location as the parent's
- * standard output. If you use this flag, @standard_output must be -1.
- * %G_SPAWN_STDERR_TO_DEV_NULL means that the child's standard error
- * will be discarded, instead of going to the same location as the parent's
- * standard error. If you use this flag, @standard_error must be -1.
- * %G_SPAWN_CHILD_INHERITS_STDIN means that the child will inherit the parent's
- * standard input (by default, the child's standard input is attached to
- * /dev/null). If you use this flag, @standard_input must be -1.
- *
- * It is valid to pass the same fd in multiple parameters (e.g. you can pass
- * a single fd for both stdout and stderr).
+ * Identical to g_spawn_async_with_pipes_and_fds() but with `n_fds` set to zero,
+ * so no FD assignments are used.
  *
  * Returns: %TRUE on success, %FALSE if an error was set
  *
@@ -869,26 +961,27 @@ g_spawn_async_with_fds (const gchar          *working_directory,
   g_return_val_if_fail (stdin_fd < 0 ||
                         !(flags & G_SPAWN_CHILD_INHERITS_STDIN), FALSE);
 
-  return fork_exec_with_fds (!(flags & G_SPAWN_DO_NOT_REAP_CHILD),
-                               working_directory,
-                               argv,
-                               envp,
-                               !(flags & G_SPAWN_LEAVE_DESCRIPTORS_OPEN),
-                               (flags & G_SPAWN_SEARCH_PATH) != 0,
-                               (flags & G_SPAWN_SEARCH_PATH_FROM_ENVP) != 0,
-                               (flags & G_SPAWN_STDOUT_TO_DEV_NULL) != 0,
-                               (flags & G_SPAWN_STDERR_TO_DEV_NULL) != 0,
-                               (flags & G_SPAWN_CHILD_INHERITS_STDIN) != 0,
-                               (flags & G_SPAWN_FILE_AND_ARGV_ZERO) != 0,
-                               (flags & G_SPAWN_CLOEXEC_PIPES) != 0,
-                               child_setup,
-                               user_data,
-                               child_pid,
-                               NULL,
-                               stdin_fd,
-                               stdout_fd,
-                               stderr_fd,
-                               error);
+  return fork_exec (!(flags & G_SPAWN_DO_NOT_REAP_CHILD),
+                    working_directory,
+                    (const gchar * const *) argv,
+                    (const gchar * const *) envp,
+                    !(flags & G_SPAWN_LEAVE_DESCRIPTORS_OPEN),
+                    (flags & G_SPAWN_SEARCH_PATH) != 0,
+                    (flags & G_SPAWN_SEARCH_PATH_FROM_ENVP) != 0,
+                    (flags & G_SPAWN_STDOUT_TO_DEV_NULL) != 0,
+                    (flags & G_SPAWN_STDERR_TO_DEV_NULL) != 0,
+                    (flags & G_SPAWN_CHILD_INHERITS_STDIN) != 0,
+                    (flags & G_SPAWN_FILE_AND_ARGV_ZERO) != 0,
+                    (flags & G_SPAWN_CLOEXEC_PIPES) != 0,
+                    child_setup,
+                    user_data,
+                    child_pid,
+                    NULL, NULL, NULL,
+                    stdin_fd,
+                    stdout_fd,
+                    stderr_fd,
+                    NULL, NULL, 0,
+                    error);
 }
 
 /**
@@ -1141,6 +1234,68 @@ set_cloexec (void *data, gint fd)
 
 /* This function is called between fork() and exec() and hence must be
  * async-signal-safe (see signal-safety(7)). */
+static void
+unset_cloexec (int fd)
+{
+  int flags;
+  int result;
+
+  flags = fcntl (fd, F_GETFD, 0);
+
+  if (flags != -1)
+    {
+      int errsv;
+      flags &= (~FD_CLOEXEC);
+      do
+        {
+          result = fcntl (fd, F_SETFD, flags);
+          errsv = errno;
+        }
+      while (result == -1 && errsv == EINTR);
+    }
+}
+
+/* This function is called between fork() and exec() and hence must be
+ * async-signal-safe (see signal-safety(7)). */
+static int
+dupfd_cloexec (int parent_fd)
+{
+  int fd, errsv;
+#ifdef F_DUPFD_CLOEXEC
+  do
+    {
+      fd = fcntl (parent_fd, F_DUPFD_CLOEXEC, 3);
+      errsv = errno;
+    }
+  while (fd == -1 && errsv == EINTR);
+#else
+  /* OS X Snow Lion and earlier don't have F_DUPFD_CLOEXEC:
+   * https://bugzilla.gnome.org/show_bug.cgi?id=710962
+   */
+  int result, flags;
+  do
+    {
+      fd = fcntl (parent_fd, F_DUPFD, 3);
+      errsv = errno;
+    }
+  while (fd == -1 && errsv == EINTR);
+  flags = fcntl (fd, F_GETFD, 0);
+  if (flags != -1)
+    {
+      flags |= FD_CLOEXEC;
+      do
+        {
+          result = fcntl (fd, F_SETFD, flags);
+          errsv = errno;
+        }
+      while (result == -1 && errsv == EINTR);
+    }
+#endif
+  return fd;
+}
+
+/* This function is called between fork() and exec() and hence must be
+ * async-signal-safe (see signal-safety(7)). */
 static gint
 safe_close (gint fd)
 {
@@ -1348,6 +1503,20 @@ safe_closefrom (int lowfd)
 /* This function is called between fork() and exec() and hence must be
  * async-signal-safe (see signal-safety(7)). */
 static gint
+safe_dup (gint fd)
+{
+  gint ret;
+
+  do
+    ret = dup (fd);
+  while (ret < 0 && (errno == EINTR || errno == EBUSY));
+
+  return ret;
+}
+
+/* This function is called between fork() and exec() and hence must be
+ * async-signal-safe (see signal-safety(7)). */
+static gint
 safe_dup2 (gint fd1, gint fd2)
 {
   gint ret;
@@ -1388,11 +1557,14 @@ do_exec (gint                  child_err_report_fd,
          gint                  stdin_fd,
          gint                  stdout_fd,
          gint                  stderr_fd,
+         gint                 *source_fds,
+         const gint           *target_fds,
+         gsize                 n_fds,
          const gchar          *working_directory,
-         gchar               **argv,
+         const gchar * const  *argv,
          gchar               **argv_buffer,
          gsize                 argv_buffer_len,
-         gchar               **envp,
+         const gchar * const  *envp,
          gboolean              close_descriptors,
          const gchar          *search_path,
          gchar                *search_path_buffer,
@@ -1404,21 +1576,23 @@ do_exec (gint                  child_err_report_fd,
          GSpawnChildSetupFunc  child_setup,
          gpointer              user_data)
 {
+  gsize i;
+
   if (working_directory && chdir (working_directory) < 0)
     write_err_and_exit (child_err_report_fd,
                         CHILD_CHDIR_FAILED);
   
   /* Redirect pipes as required */
-  
   if (stdin_fd >= 0)
     {
       /* dup2 can't actually fail here I don't think */
-          
       if (safe_dup2 (stdin_fd, 0) < 0)
         write_err_and_exit (child_err_report_fd,
                             CHILD_DUP2_FAILED);
 
-      set_cloexec (GINT_TO_POINTER(0), stdin_fd);
+      if (!((stdout_fd >= 0 || stdout_to_null) && stdin_fd == 1) &&
+          !((stderr_fd >= 0 || stderr_to_null) && stdin_fd == 2))
+        set_cloexec (GINT_TO_POINTER(0), stdin_fd);
     }
   else if (!child_inherits_stdin)
     {
@@ -1434,12 +1608,13 @@ do_exec (gint                  child_err_report_fd,
   if (stdout_fd >= 0)
     {
       /* dup2 can't actually fail here I don't think */
-          
       if (safe_dup2 (stdout_fd, 1) < 0)
         write_err_and_exit (child_err_report_fd,
                             CHILD_DUP2_FAILED);
 
-      set_cloexec (GINT_TO_POINTER(0), stdout_fd);
+      if (!((stdin_fd >= 0 || !child_inherits_stdin) && stdout_fd == 0) &&
+          !((stderr_fd >= 0 || stderr_to_null) && stdout_fd == 2))
+        set_cloexec (GINT_TO_POINTER(0), stdout_fd);
     }
   else if (stdout_to_null)
     {
@@ -1454,12 +1629,13 @@ do_exec (gint                  child_err_report_fd,
   if (stderr_fd >= 0)
     {
       /* dup2 can't actually fail here I don't think */
-          
       if (safe_dup2 (stderr_fd, 2) < 0)
         write_err_and_exit (child_err_report_fd,
                             CHILD_DUP2_FAILED);
 
-      set_cloexec (GINT_TO_POINTER(0), stderr_fd);
+      if (!((stdin_fd >= 0 || !child_inherits_stdin) && stderr_fd == 0) &&
+          !((stdout_fd >= 0 || stdout_to_null) && stderr_fd == 1))
+        set_cloexec (GINT_TO_POINTER(0), stderr_fd);
     }
   else if (stderr_to_null)
     {
@@ -1471,14 +1647,14 @@ do_exec (gint                  child_err_report_fd,
       close_and_invalidate (&write_null);
     }
 
-  /* Close all file descriptors but stdin, stdout and stderr
+  /* Close all file descriptors but stdin, stdout and stderr, and any of source_fds,
    * before we exec. Note that this includes
    * child_err_report_fd, which keeps the parent from blocking
    * forever on the other end of that pipe.
    */
   if (close_descriptors)
     {
-      if (child_setup == NULL)
+      if (child_setup == NULL && n_fds == 0)
         {
           safe_dup2 (child_err_report_fd, 3);
           set_cloexec (GINT_TO_POINTER (0), 3);
@@ -1495,7 +1671,50 @@ do_exec (gint                  child_err_report_fd,
       /* We need to do child_err_report_fd anyway */
       set_cloexec (GINT_TO_POINTER (0), child_err_report_fd);
     }
-  
+
+  /*
+   * Work through the @source_fds and @target_fds mapping.
+   *
+   * Based on code derived from
+   * gnome-terminal:src/terminal-screen.c:terminal_screen_child_setup(),
+   * used under the LGPLv2+ with permission from author.
+   */
+
+  /* Basic fd assignments (where source == target) we can just unset FD_CLOEXEC
+   *
+   * If we're doing remapping fd assignments, we need to handle
+   * the case where the user has specified e.g.:
+   * 5 -> 4, 4 -> 6
+   *
+   * We do this by duping the source fds temporarily in a first pass.
+   *
+   * If any of the @target_fds conflict with @child_err_report_fd, dup the
+   * latter so it doesn’t get conflated.
+   */
+  if (n_fds > 0)
+    {
+      for (i = 0; i < n_fds; i++)
+        {
+          if (source_fds[i] != target_fds[i])
+            source_fds[i] = dupfd_cloexec (source_fds[i]);
+        }
+      for (i = 0; i < n_fds; i++)
+        {
+          if (source_fds[i] == target_fds[i])
+            {
+              unset_cloexec (source_fds[i]);
+            }
+          else
+            {
+              if (target_fds[i] == child_err_report_fd)
+                child_err_report_fd = safe_dup (child_err_report_fd);
+
+              safe_dup2 (source_fds[i], target_fds[i]);
+              (void) close (source_fds[i]);
+            }
+        }
+    }
+
   /* Call user function just before we exec */
   if (child_setup)
     {
@@ -1503,9 +1722,9 @@ do_exec (gint                  child_err_report_fd,
     }
 
   g_execute (argv[0],
-             file_and_argv_zero ? argv + 1 : argv,
+             (gchar **) (file_and_argv_zero ? argv + 1 : argv),
              argv_buffer, argv_buffer_len,
-             envp, search_path, search_path_buffer, search_path_buffer_len);
+             (gchar **) envp, search_path, search_path_buffer, search_path_buffer_len);
 
   /* Exec failed */
   write_err_and_exit (child_err_report_fd,
@@ -1563,8 +1782,8 @@ read_ints (int      fd,
 
 #ifdef POSIX_SPAWN_AVAILABLE
 static gboolean
-do_posix_spawn (gchar     **argv,
-                gchar     **envp,
+do_posix_spawn (const gchar * const *argv,
+                const gchar * const *envp,
                 gboolean    search_path,
                 gboolean    stdout_to_null,
                 gboolean    stderr_to_null,
@@ -1577,7 +1796,7 @@ do_posix_spawn (gchar     **argv,
                 gint        stderr_fd)
 {
   pid_t pid;
-  gchar **argv_pass;
+  const gchar * const *argv_pass;
   posix_spawnattr_t attr;
   posix_spawn_file_actions_t file_actions;
   gint parent_close_fds[3];
@@ -1716,13 +1935,13 @@ do_posix_spawn (gchar     **argv,
 
   argv_pass = file_and_argv_zero ? argv + 1 : argv;
   if (envp == NULL)
-    envp = environ;
+    envp = (const gchar * const *) environ;
 
   /* Don't search when it contains a slash. */
   if (!search_path || strchr (argv[0], '/') != NULL)
-    r = posix_spawn (&pid, argv[0], &file_actions, &attr, argv_pass, envp);
+    r = posix_spawn (&pid, argv[0], &file_actions, &attr, (char * const *) argv_pass, (char * const *) envp);
   else
-    r = posix_spawnp (&pid, argv[0], &file_actions, &attr, argv_pass, envp);
+    r = posix_spawnp (&pid, argv[0], &file_actions, &attr, (char * const *) argv_pass, (char * const *) envp);
 
   if (r == 0 && child_pid != NULL)
     *child_pid = pid;
@@ -1741,26 +1960,31 @@ out_free_spawnattr:
 #endif /* POSIX_SPAWN_AVAILABLE */
 
 static gboolean
-fork_exec_with_fds (gboolean              intermediate_child,
-                    const gchar          *working_directory,
-                    gchar               **argv,
-                    gchar               **envp,
-                    gboolean              close_descriptors,
-                    gboolean              search_path,
-                    gboolean              search_path_from_envp,
-                    gboolean              stdout_to_null,
-                    gboolean              stderr_to_null,
-                    gboolean              child_inherits_stdin,
-                    gboolean              file_and_argv_zero,
-                    gboolean              cloexec_pipes,
-                    GSpawnChildSetupFunc  child_setup,
-                    gpointer              user_data,
-                    GPid                 *child_pid,
-                    gint                 *child_close_fds,
-                    gint                  stdin_fd,
-                    gint                  stdout_fd,
-                    gint                  stderr_fd,
-                    GError              **error)
+fork_exec (gboolean              intermediate_child,
+           const gchar          *working_directory,
+           const gchar * const  *argv,
+           const gchar * const  *envp,
+           gboolean              close_descriptors,
+           gboolean              search_path,
+           gboolean              search_path_from_envp,
+           gboolean              stdout_to_null,
+           gboolean              stderr_to_null,
+           gboolean              child_inherits_stdin,
+           gboolean              file_and_argv_zero,
+           gboolean              cloexec_pipes,
+           GSpawnChildSetupFunc  child_setup,
+           gpointer              user_data,
+           GPid                 *child_pid,
+           gint                 *stdin_pipe_out,
+           gint                 *stdout_pipe_out,
+           gint                 *stderr_pipe_out,
+           gint                  stdin_fd,
+           gint                  stdout_fd,
+           gint                  stderr_fd,
+           const gint           *source_fds,
+           const gint           *target_fds,
+           gsize                 n_fds,
+           GError              **error)
 {
   GPid pid = -1;
   gint child_err_report_pipe[2] = { -1, -1 };
@@ -1774,10 +1998,49 @@ fork_exec_with_fds (gboolean              intermediate_child,
   gchar **argv_buffer = NULL;
   gchar **argv_buffer_heap = NULL;
   gsize argv_buffer_len = 0;
+  gint stdin_pipe[2] = { -1, -1 };
+  gint stdout_pipe[2] = { -1, -1 };
+  gint stderr_pipe[2] = { -1, -1 };
+  gint child_close_fds[4] = { -1, -1, -1, -1 };
+  gint n_child_close_fds = 0;
+  gint *source_fds_copy = NULL;
+
+  g_assert (stdin_pipe_out == NULL || stdin_fd < 0);
+  g_assert (stdout_pipe_out == NULL || stdout_fd < 0);
+  g_assert (stderr_pipe_out == NULL || stderr_fd < 0);
+
+  /* If pipes have been requested, open them */
+  if (stdin_pipe_out != NULL)
+    {
+      if (!g_unix_open_pipe (stdin_pipe, pipe_flags, error))
+        goto cleanup_and_fail;
+      child_close_fds[n_child_close_fds++] = stdin_pipe[1];
+      stdin_fd = stdin_pipe[0];
+    }
+
+  if (stdout_pipe_out != NULL)
+    {
+      if (!g_unix_open_pipe (stdout_pipe, pipe_flags, error))
+        goto cleanup_and_fail;
+      child_close_fds[n_child_close_fds++] = stdout_pipe[0];
+      stdout_fd = stdout_pipe[1];
+    }
+
+  if (stderr_pipe_out != NULL)
+    {
+      if (!g_unix_open_pipe (stderr_pipe, pipe_flags, error))
+        goto cleanup_and_fail;
+      child_close_fds[n_child_close_fds++] = stderr_pipe[0];
+      stderr_fd = stderr_pipe[1];
+    }
+
+  child_close_fds[n_child_close_fds++] = -1;
 
 #ifdef POSIX_SPAWN_AVAILABLE
+  /* FIXME: Handle @source_fds and @target_fds in do_posix_spawn() using the
+   * file actions API. */
   if (!intermediate_child && working_directory == NULL && !close_descriptors &&
-      !search_path_from_envp && child_setup == NULL)
+      !search_path_from_envp && child_setup == NULL && n_fds == 0)
     {
       g_trace_mark (G_TRACE_CURRENT_TIME, 0,
                     "GLib", "posix_spawn",
@@ -1796,7 +2059,7 @@ fork_exec_with_fds (gboolean              intermediate_child,
                                stdout_fd,
                                stderr_fd);
       if (status == 0)
-        return TRUE;
+        goto success;
 
       if (status != ENOEXEC)
         {
@@ -1806,7 +2069,7 @@ fork_exec_with_fds (gboolean              intermediate_child,
                        _("Failed to spawn child process “%s” (%s)"),
                        argv[0],
                        g_strerror (status));
-          return FALSE;
+          goto cleanup_and_fail;
        }
 
       /* posix_spawn is not intended to support script execution. It does in
@@ -1833,7 +2096,7 @@ fork_exec_with_fds (gboolean              intermediate_child,
    * as getenv() isn’t async-signal-safe (see `man 7 signal-safety`). */
   chosen_search_path = NULL;
   if (search_path_from_envp)
-    chosen_search_path = g_environ_getenv (envp, "PATH");
+    chosen_search_path = g_environ_getenv ((gchar **) envp, "PATH");
   if (search_path && chosen_search_path == NULL)
     chosen_search_path = g_getenv ("PATH");
 
@@ -1887,7 +2150,7 @@ fork_exec_with_fds (gboolean              intermediate_child,
   /* And allocate a buffer which is 2 elements longer than @argv, so that if
    * script_execute() has to be called later on, it can build a wrapper argv
    * array in this buffer. */
-  argv_buffer_len = g_strv_length (argv) + 2;
+  argv_buffer_len = g_strv_length ((gchar **) argv) + 2;
   if (argv_buffer_len < 4000 / sizeof (gchar *))
     {
       /* Prefer small stack allocations to avoid valgrind leak warnings
@@ -1900,12 +2163,13 @@ fork_exec_with_fds (gboolean              intermediate_child,
       argv_buffer = argv_buffer_heap;
     }
 
+  /* And one to hold a copy of @source_fds for later manipulation in do_exec(). */
+  source_fds_copy = g_new (int, n_fds);
+  if (n_fds > 0)
+    memcpy (source_fds_copy, source_fds, sizeof (*source_fds) * n_fds);
+
   if (!g_unix_open_pipe (child_err_report_pipe, pipe_flags, error))
-    {
-      g_free (search_path_buffer_heap);
-      g_free (argv_buffer_heap);
-      return FALSE;
-    }
+    goto cleanup_and_fail;
 
   if (intermediate_child && !g_unix_open_pipe (child_pid_report_pipe, pipe_flags, error))
     goto cleanup_and_fail;
@@ -1947,7 +2211,7 @@ fork_exec_with_fds (gboolean              intermediate_child,
        */
       close_and_invalidate (&child_err_report_pipe[0]);
       close_and_invalidate (&child_pid_report_pipe[0]);
-      if (child_close_fds != NULL)
+      if (child_close_fds[0] != -1)
         {
            int i = -1;
            while (child_close_fds[++i] != -1)
@@ -1981,6 +2245,9 @@ fork_exec_with_fds (gboolean              intermediate_child,
                        stdin_fd,
                        stdout_fd,
                        stderr_fd,
+                       source_fds_copy,
+                       target_fds,
+                       n_fds,
                        working_directory,
                        argv,
                        argv_buffer,
@@ -2014,6 +2281,9 @@ fork_exec_with_fds (gboolean              intermediate_child,
                    stdin_fd,
                    stdout_fd,
                    stderr_fd,
+                   source_fds_copy,
+                   target_fds,
+                   n_fds,
                    working_directory,
                    argv,
                    argv_buffer,
@@ -2053,8 +2323,7 @@ fork_exec_with_fds (gboolean              intermediate_child,
               else if (errno == ECHILD)
                 ; /* do nothing, child already reaped */
               else
-                g_warning ("waitpid() should not fail in "
-                          "'fork_exec_with_pipes'");
+                g_warning ("waitpid() should not fail in 'fork_exec'");
             }
         }
       
@@ -2152,13 +2421,31 @@ fork_exec_with_fds (gboolean              intermediate_child,
 
       g_free (search_path_buffer_heap);
       g_free (argv_buffer_heap);
+      g_free (source_fds_copy);
 
       if (child_pid)
         *child_pid = pid;
 
-      return TRUE;
+      goto success;
     }
 
+success:
+  /* Close the uncared-about ends of the pipes */
+  close_and_invalidate (&stdin_pipe[0]);
+  close_and_invalidate (&stdout_pipe[1]);
+  close_and_invalidate (&stderr_pipe[1]);
+
+  if (stdin_pipe_out != NULL)
+    *stdin_pipe_out = steal_fd (&stdin_pipe[1]);
+
+  if (stdout_pipe_out != NULL)
+    *stdout_pipe_out = steal_fd (&stdout_pipe[0]);
+
+  if (stderr_pipe_out != NULL)
+    *stderr_pipe_out = steal_fd (&stderr_pipe[0]);
+
+  return TRUE;
+
  cleanup_and_fail:
 
   /* There was an error from the Child, reap the child to avoid it being
@@ -2175,104 +2462,10 @@ fork_exec_with_fds (gboolean              intermediate_child,
           else if (errno == ECHILD)
             ; /* do nothing, child already reaped */
           else
-            g_warning ("waitpid() should not fail in "
-                       "'fork_exec_with_pipes'");
+            g_warning ("waitpid() should not fail in 'fork_exec'");
        }
    }
 
-  close_and_invalidate (&child_err_report_pipe[0]);
-  close_and_invalidate (&child_err_report_pipe[1]);
-  close_and_invalidate (&child_pid_report_pipe[0]);
-  close_and_invalidate (&child_pid_report_pipe[1]);
-
-  g_free (search_path_buffer_heap);
-  g_free (argv_buffer_heap);
-
-  return FALSE;
-}
-
-static gboolean
-fork_exec_with_pipes (gboolean              intermediate_child,
-                      const gchar          *working_directory,
-                      gchar               **argv,
-                      gchar               **envp,
-                      gboolean              close_descriptors,
-                      gboolean              search_path,
-                      gboolean              search_path_from_envp,
-                      gboolean              stdout_to_null,
-                      gboolean              stderr_to_null,
-                      gboolean              child_inherits_stdin,
-                      gboolean              file_and_argv_zero,
-                      gboolean              cloexec_pipes,
-                      GSpawnChildSetupFunc  child_setup,
-                      gpointer              user_data,
-                      GPid                 *child_pid,
-                      gint                 *standard_input,
-                      gint                 *standard_output,
-                      gint                 *standard_error,
-                      GError              **error)
-{
-  guint pipe_flags = cloexec_pipes ? FD_CLOEXEC : 0;
-  gint stdin_pipe[2] = { -1, -1 };
-  gint stdout_pipe[2] = { -1, -1 };
-  gint stderr_pipe[2] = { -1, -1 };
-  gint child_close_fds[4];
-  gboolean ret;
-
-  if (standard_input && !g_unix_open_pipe (stdin_pipe, pipe_flags, error))
-    goto cleanup_and_fail;
-
-  if (standard_output && !g_unix_open_pipe (stdout_pipe, pipe_flags, error))
-    goto cleanup_and_fail;
-
-  if (standard_error && !g_unix_open_pipe (stderr_pipe, FD_CLOEXEC, error))
-    goto cleanup_and_fail;
-
-  child_close_fds[0] = stdin_pipe[1];
-  child_close_fds[1] = stdout_pipe[0];
-  child_close_fds[2] = stderr_pipe[0];
-  child_close_fds[3] = -1;
-
-  ret = fork_exec_with_fds (intermediate_child,
-                            working_directory,
-                            argv,
-                            envp,
-                            close_descriptors,
-                            search_path,
-                            search_path_from_envp,
-                            stdout_to_null,
-                            stderr_to_null,
-                            child_inherits_stdin,
-                            file_and_argv_zero,
-                            pipe_flags,
-                            child_setup,
-                            user_data,
-                            child_pid,
-                            child_close_fds,
-                            stdin_pipe[0],
-                            stdout_pipe[1],
-                            stderr_pipe[1],
-                            error);
-  if (!ret)
-    goto cleanup_and_fail;
-
-  /* Close the uncared-about ends of the pipes */
-  close_and_invalidate (&stdin_pipe[0]);
-  close_and_invalidate (&stdout_pipe[1]);
-  close_and_invalidate (&stderr_pipe[1]);
-
-  if (standard_input)
-    *standard_input = stdin_pipe[1];
-
-  if (standard_output)
-    *standard_output = stdout_pipe[0];
-
-  if (standard_error)
-    *standard_error = stderr_pipe[0];
-
-  return TRUE;
-
-cleanup_and_fail:
   close_and_invalidate (&stdin_pipe[0]);
   close_and_invalidate (&stdin_pipe[1]);
   close_and_invalidate (&stdout_pipe[0]);
@@ -2280,6 +2473,15 @@ cleanup_and_fail:
   close_and_invalidate (&stderr_pipe[0]);
   close_and_invalidate (&stderr_pipe[1]);
 
+  close_and_invalidate (&child_err_report_pipe[0]);
+  close_and_invalidate (&child_err_report_pipe[1]);
+  close_and_invalidate (&child_pid_report_pipe[0]);
+  close_and_invalidate (&child_pid_report_pipe[1]);
+
+  g_clear_pointer (&search_path_buffer_heap, g_free);
+  g_clear_pointer (&argv_buffer_heap, g_free);
+  g_clear_pointer (&source_fds_copy, g_free);
+
   return FALSE;
 }