kdbus: Fixup signal subscription
[platform/upstream/glib.git] / glib / gspawn.c
index 1425450..d2ef460 100644 (file)
@@ -4,20 +4,20 @@
  *  g_execvpe implementation based on GNU libc execvp:
  *   Copyright 1991, 92, 95, 96, 97, 98, 99 Free Software Foundation, Inc.
  *
- * GLib is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2 of the
- * License, or (at your option) any later version.
+ * SPDX-License-Identifier: LGPL-2.1-or-later
  *
- * GLib is distributed in the hope that it will be useful,
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * Lesser General Public License for more details.
  *
- * You should have received a copy of the GNU Lesser General Public
- * License along with GLib; see the file COPYING.LIB.  If not, write
- * to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
  */
 
 #include "config.h"
 #include <string.h>
 #include <stdlib.h>   /* for fdwalk */
 #include <dirent.h>
+#include <unistd.h>
+
+#ifdef HAVE_SPAWN_H
+#include <spawn.h>
+#endif /* HAVE_SPAWN_H */
+
+#ifdef HAVE_CRT_EXTERNS_H
+#include <crt_externs.h> /* for _NSGetEnviron */
+#endif
 
 #ifdef HAVE_SYS_SELECT_H
 #include <sys/select.h>
 #include <sys/resource.h>
 #endif /* HAVE_SYS_RESOURCE_H */
 
-#include "glib.h"
+#if defined(__linux__) || defined(__DragonFly__)
+#include <sys/syscall.h>  /* for syscall and SYS_getdents64 */
+#endif
+
+#include "gspawn.h"
+#include "gspawn-private.h"
+#include "gthread.h"
+#include "gtrace-private.h"
+#include "glib/gstdio.h"
+
+#include "genviron.h"
+#include "gmem.h"
+#include "gshell.h"
+#include "gstring.h"
+#include "gstrfuncs.h"
+#include "gtestutils.h"
+#include "gutils.h"
 #include "glibintl.h"
-#include "galias.h"
+#include "glib-unix.h"
+
+#if defined(__APPLE__) && defined(HAVE_LIBPROC_H)
+#include <libproc.h>
+#include <sys/proc_info.h>
+#endif
+
+#define INHERITS_OR_NULL_STDIN  (G_SPAWN_STDIN_FROM_DEV_NULL | G_SPAWN_CHILD_INHERITS_STDIN)
+#define INHERITS_OR_NULL_STDOUT (G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_CHILD_INHERITS_STDOUT)
+#define INHERITS_OR_NULL_STDERR (G_SPAWN_STDERR_TO_DEV_NULL | G_SPAWN_CHILD_INHERITS_STDERR)
+
+#define IS_STD_FILENO(_fd) ((_fd >= STDIN_FILENO) && (_fd <= STDERR_FILENO))
+#define IS_VALID_FILENO(_fd) (_fd >= 0)
+
+/* posix_spawn() is assumed the fastest way to spawn, but glibc's
+ * implementation was buggy before glibc 2.24, so avoid it on old versions.
+ */
+#ifdef HAVE_POSIX_SPAWN
+#ifdef __GLIBC__
+
+#if __GLIBC_PREREQ(2,24)
+#define POSIX_SPAWN_AVAILABLE
+#endif
+
+#else /* !__GLIBC__ */
+/* Assume that all non-glibc posix_spawn implementations are fine. */
+#define POSIX_SPAWN_AVAILABLE
+#endif /* __GLIBC__ */
+#endif /* HAVE_POSIX_SPAWN */
+
+#ifdef HAVE__NSGETENVIRON
+#define environ (*_NSGetEnviron())
+#else
+extern char **environ;
+#endif
+
+#ifndef O_CLOEXEC
+#define O_CLOEXEC 0
+#else
+#define HAVE_O_CLOEXEC 1
+#endif
 
 static gint g_execute (const gchar  *file,
-                       gchar **argv,
-                       gchar **envp,
-                       gboolean search_path);
-
-static gboolean make_pipe            (gint                  p[2],
-                                      GError              **error);
-static gboolean fork_exec_with_pipes (gboolean              intermediate_child,
-                                      const gchar          *working_directory,
-                                      gchar               **argv,
-                                      gchar               **envp,
-                                      gboolean              close_descriptors,
-                                      gboolean              search_path,
-                                      gboolean              stdout_to_null,
-                                      gboolean              stderr_to_null,
-                                      gboolean              child_inherits_stdin,
-                                      gboolean              file_and_argv_zero,
-                                      GSpawnChildSetupFunc  child_setup,
-                                      gpointer              user_data,
-                                      GPid                 *child_pid,
-                                      gint                 *standard_input,
-                                      gint                 *standard_output,
-                                      gint                 *standard_error,
-                                      GError              **error);
-
-GQuark
-g_spawn_error_quark (void)
-{
-  return g_quark_from_static_string ("g-exec-error-quark");
-}
+                       gchar       **argv,
+                       gchar       **argv_buffer,
+                       gsize         argv_buffer_len,
+                       gchar       **envp,
+                       const gchar  *search_path,
+                       gchar        *search_path_buffer,
+                       gsize         search_path_buffer_len);
+
+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)
 
 /**
  * g_spawn_async:
- * @working_directory: child's current working directory, or %NULL to inherit parent's
- * @argv: child's argument vector
- * @envp: child's environment, or %NULL to inherit parent's
+ * @working_directory: (type filename) (nullable): child's current working
+ *     directory, or %NULL to inherit parent's
+ * @argv: (array zero-terminated=1) (element-type filename):
+ *     child's argument vector
+ * @envp: (array zero-terminated=1) (element-type filename) (nullable):
+ *     child's environment, or %NULL to inherit parent's
  * @flags: flags from #GSpawnFlags
- * @child_setup: function to run in the child just before exec()
+ * @child_setup: (scope async) (closure user_data) (nullable): function to run
+ *     in the child just before `exec()`
  * @user_data: user data for @child_setup
- * @child_pid: return location for child process reference, or %NULL
+ * @child_pid: (out) (optional): return location for child process reference, or %NULL
  * @error: return location for error
+ *
+ * Executes a child program asynchronously.
  * 
  * See g_spawn_async_with_pipes() for a full description; this function
  * simply calls the g_spawn_async_with_pipes() without any pipes.
@@ -93,19 +171,16 @@ g_spawn_error_quark (void)
  * You should call g_spawn_close_pid() on the returned child process
  * reference when you don't need it any more.
  * 
- * <note><para>
- * If you are writing a GTK+ application, and the program you 
- * are spawning is a graphical application, too, then you may
- * want to use gdk_spawn_on_screen() instead to ensure that 
- * the spawned program opens its windows on the right screen.
- * </para></note>
- *
- * <note><para> Note that the returned @child_pid on Windows is a
- * handle to the child process and not its identifier. Process handles
- * and process identifiers are different concepts on Windows.
- * </para></note>
- *
- * Return value: %TRUE on success, %FALSE if error is set
+ * 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.
+ *
+ * Note that the returned @child_pid on Windows is a handle to the child
+ * process and not its identifier. Process handles and process identifiers
+ * are different concepts on Windows.
+ *
+ * Returns: %TRUE on success, %FALSE if error is set
  **/
 gboolean
 g_spawn_async (const gchar          *working_directory,
@@ -117,8 +192,6 @@ g_spawn_async (const gchar          *working_directory,
                GPid                 *child_pid,
                GError              **error)
 {
-  g_return_val_if_fail (argv != NULL, FALSE);
-  
   return g_spawn_async_with_pipes (working_directory,
                                    argv, envp,
                                    flags,
@@ -129,26 +202,6 @@ g_spawn_async (const gchar          *working_directory,
                                    error);
 }
 
-/* Avoids a danger in threaded situations (calling close()
- * on a file descriptor twice, and another thread has
- * re-opened it since the first close)
- */
-static gint
-close_and_invalidate (gint *fd)
-{
-  gint ret;
-
-  if (*fd < 0)
-    return -1;
-  else
-    {
-      ret = close (*fd);
-      *fd = -1;
-    }
-
-  return ret;
-}
-
 /* Some versions of OS X define READ_OK in public headers */
 #undef READ_OK
 
@@ -164,11 +217,10 @@ read_data (GString *str,
            gint     fd,
            GError **error)
 {
-  gssize bytes;        
-  gchar buf[4096];    
+  gssize bytes;
+  gchar buf[4096];
 
  again:
-  
   bytes = read (fd, buf, 4096);
 
   if (bytes == 0)
@@ -178,9 +230,9 @@ read_data (GString *str,
       g_string_append_len (str, buf, bytes);
       return READ_OK;
     }
-  else if (bytes < 0 && errno == EINTR)
+  else if (errno == EINTR)
     goto again;
-  else if (bytes < 0)
+  else
     {
       int errsv = errno;
 
@@ -189,45 +241,54 @@ read_data (GString *str,
                    G_SPAWN_ERROR_READ,
                    _("Failed to read data from child process (%s)"),
                    g_strerror (errsv));
-      
+
       return READ_FAILED;
     }
-  else
-    return READ_OK;
 }
 
 /**
  * g_spawn_sync:
- * @working_directory: child's current working directory, or %NULL to inherit parent's
- * @argv: child's argument vector
- * @envp: child's environment, or %NULL to inherit parent's
- * @flags: flags from #GSpawnFlags 
- * @child_setup: function to run in the child just before exec()
+ * @working_directory: (type filename) (nullable): child's current working
+ *     directory, or %NULL to inherit parent's
+ * @argv: (array zero-terminated=1) (element-type filename):
+ *     child's argument vector, which must be non-empty and %NULL-terminated
+ * @envp: (array zero-terminated=1) (element-type filename) (nullable):
+ *     child's environment, or %NULL to inherit parent's
+ * @flags: flags from #GSpawnFlags
+ * @child_setup: (scope call) (closure user_data) (nullable): function to run
+ *     in the child just before `exec()`
  * @user_data: user data for @child_setup
- * @standard_output: return location for child output, or %NULL
- * @standard_error: return location for child error messages, or %NULL
- * @exit_status: return location for child exit status, as returned by waitpid(), or %NULL
+ * @standard_output: (out) (array zero-terminated=1) (element-type guint8) (optional): return location for child output, or %NULL
+ * @standard_error: (out) (array zero-terminated=1) (element-type guint8) (optional): return location for child error messages, or %NULL
+ * @wait_status: (out) (optional): return location for child wait status, as returned by waitpid(), or %NULL
  * @error: return location for error, or %NULL
  *
  * Executes a child synchronously (waits for the child to exit before returning).
+ *
  * All output from the child is stored in @standard_output and @standard_error,
  * if those parameters are non-%NULL. Note that you must set the  
  * %G_SPAWN_STDOUT_TO_DEV_NULL and %G_SPAWN_STDERR_TO_DEV_NULL flags when
  * passing %NULL for @standard_output and @standard_error.
- * If @exit_status is non-%NULL, the exit status of the child is stored
- * there as it would be returned by waitpid(); standard UNIX macros such 
- * as WIFEXITED() and WEXITSTATUS() must be used to evaluate the exit status.
- * Note that this function call waitpid() even if @exit_status is %NULL, and
- * does not accept the %G_SPAWN_DO_NOT_REAP_CHILD flag.
- * If an error occurs, no data is returned in @standard_output, 
- * @standard_error, or @exit_status. 
+ *
+ * If @wait_status is non-%NULL, the platform-specific status of
+ * the child is stored there; see the documentation of
+ * g_spawn_check_wait_status() for how to use and interpret this.
+ * On Unix platforms, note that it is usually not equal
+ * to the integer passed to `exit()` or returned from `main()`.
+ *
+ * Note that it is invalid to pass %G_SPAWN_DO_NOT_REAP_CHILD in
+ * @flags, and on POSIX platforms, the same restrictions as for
+ * g_child_watch_source_new() apply.
+ *
+ * If an error occurs, no data is returned in @standard_output,
+ * @standard_error, or @wait_status.
  *
  * This function calls g_spawn_async_with_pipes() internally; see that
  * function for full details on the other parameters and details on
  * how these functions work on Windows.
  * 
- * Return value: %TRUE on success, %FALSE if an error was set.
- **/
+ * Returns: %TRUE on success, %FALSE if an error was set
+ */
 gboolean
 g_spawn_sync (const gchar          *working_directory,
               gchar               **argv,
@@ -237,13 +298,12 @@ g_spawn_sync (const gchar          *working_directory,
               gpointer              user_data,
               gchar               **standard_output,
               gchar               **standard_error,
-              gint                 *exit_status,
+              gint                 *wait_status,
               GError              **error)     
 {
   gint outpipe = -1;
   gint errpipe = -1;
   GPid pid;
-  fd_set fds;
   gint ret;
   GString *outstr = NULL;
   GString *errstr = NULL;
@@ -251,6 +311,7 @@ g_spawn_sync (const gchar          *working_directory,
   gint status;
   
   g_return_val_if_fail (argv != NULL, FALSE);
+  g_return_val_if_fail (argv[0] != NULL, FALSE);
   g_return_val_if_fail (!(flags & G_SPAWN_DO_NOT_REAP_CHILD), FALSE);
   g_return_val_if_fail (standard_output == NULL ||
                         !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE);
@@ -266,23 +327,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_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,
-                             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. */
@@ -304,35 +369,36 @@ g_spawn_sync (const gchar          *working_directory,
          (outpipe >= 0 ||
           errpipe >= 0))
     {
-      ret = 0;
-          
-      FD_ZERO (&fds);
-      if (outpipe >= 0)
-        FD_SET (outpipe, &fds);
-      if (errpipe >= 0)
-        FD_SET (errpipe, &fds);
-          
-      ret = select (MAX (outpipe, errpipe) + 1,
-                    &fds,
-                    NULL, NULL,
-                    NULL /* no timeout */);
+      /* Any negative FD in the array is ignored, so we can use a fixed length.
+       * We can use UNIX FDs here without worrying about Windows HANDLEs because
+       * the Windows implementation is entirely in gspawn-win32.c. */
+      GPollFD fds[] =
+        {
+          { outpipe, G_IO_IN | G_IO_HUP | G_IO_ERR, 0 },
+          { errpipe, G_IO_IN | G_IO_HUP | G_IO_ERR, 0 },
+        };
 
-      if (ret < 0 && errno != EINTR)
+      ret = g_poll (fds, G_N_ELEMENTS (fds), -1  /* no timeout */);
+
+      if (ret < 0)
         {
           int errsv = errno;
 
+         if (errno == EINTR)
+           continue;
+
           failed = TRUE;
 
           g_set_error (error,
                        G_SPAWN_ERROR,
                        G_SPAWN_ERROR_READ,
-                       _("Unexpected error in select() reading data from a child process (%s)"),
+                       _("Unexpected error in reading data from a child process (%s)"),
                        g_strerror (errsv));
               
           break;
         }
 
-      if (outpipe >= 0 && FD_ISSET (outpipe, &fds))
+      if (outpipe >= 0 && fds[0].revents != 0)
         {
           switch (read_data (outstr, outpipe, error))
             {
@@ -340,8 +406,7 @@ g_spawn_sync (const gchar          *working_directory,
               failed = TRUE;
               break;
             case READ_EOF:
-              close_and_invalidate (&outpipe);
-              outpipe = -1;
+              g_clear_fd (&outpipe, NULL);
               break;
             default:
               break;
@@ -351,7 +416,7 @@ g_spawn_sync (const gchar          *working_directory,
             break;
         }
 
-      if (errpipe >= 0 && FD_ISSET (errpipe, &fds))
+      if (errpipe >= 0 && fds[1].revents != 0)
         {
           switch (read_data (errstr, errpipe, error))
             {
@@ -359,8 +424,7 @@ g_spawn_sync (const gchar          *working_directory,
               failed = TRUE;
               break;
             case READ_EOF:
-              close_and_invalidate (&errpipe);
-              errpipe = -1;
+              g_clear_fd (&errpipe, NULL);
               break;
             default:
               break;
@@ -372,12 +436,9 @@ g_spawn_sync (const gchar          *working_directory,
     }
 
   /* These should only be open still if we had an error.  */
-  
-  if (outpipe >= 0)
-    close_and_invalidate (&outpipe);
-  if (errpipe >= 0)
-    close_and_invalidate (&errpipe);
-  
+  g_clear_fd (&outpipe, NULL);
+  g_clear_fd (&errpipe, NULL);
+
   /* Wait for child to exit, even if we have
    * an error pending.
    */
@@ -391,13 +452,13 @@ g_spawn_sync (const gchar          *working_directory,
         goto again;
       else if (errno == ECHILD)
         {
-          if (exit_status)
+          if (wait_status)
             {
-              g_warning ("In call to g_spawn_sync(), exit status of a child process was requested but SIGCHLD action was set to SIG_IGN and ECHILD was received by waitpid(), so exit status can't be returned. This is a bug in the program calling g_spawn_sync(); either don't request the exit status, or don't set the SIGCHLD action.");
+              g_warning ("In call to g_spawn_sync(), wait status of a child process was requested but ECHILD was received by waitpid(). See the documentation of g_child_watch_source_new() for possible causes.");
             }
           else
             {
-              /* We don't need the exit status. */
+              /* We don't need the wait status. */
             }
         }
       else
@@ -428,8 +489,8 @@ g_spawn_sync (const gchar          *working_directory,
     }
   else
     {
-      if (exit_status)
-        *exit_status = status;
+      if (wait_status)
+        *wait_status = status;
       
       if (standard_output)        
         *standard_output = g_string_free (outstr, FALSE);
@@ -443,230 +504,443 @@ g_spawn_sync (const gchar          *working_directory,
 
 /**
  * g_spawn_async_with_pipes:
- * @working_directory: child's current working directory, or %NULL to inherit parent's, in the GLib file name encoding
- * @argv: child's argument vector, in the GLib file name encoding
- * @envp: child's environment, or %NULL to inherit parent's, in the GLib file name encoding
+ * @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; it must be non-empty and %NULL-terminated
+ * @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) (closure user_data) (nullable): function to run
+ *     in the child just before `exec()`
+ * @user_data: user data for @child_setup
+ * @child_pid: (out) (optional): return location for child process ID, or %NULL
+ * @standard_input: (out) (optional): return location for file descriptor to write to child's stdin, or %NULL
+ * @standard_output: (out) (optional): return location for file descriptor to read child's stdout, or %NULL
+ * @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)
+{
+  return g_spawn_async_with_pipes_and_fds (working_directory,
+                                           (const gchar * const *) argv,
+                                           (const gchar * const *) envp,
+                                           flags,
+                                           child_setup, user_data,
+                                           -1, -1, -1,
+                                           NULL, NULL, 0,
+                                           child_pid,
+                                           standard_input,
+                                           standard_output,
+                                           standard_error,
+                                           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; it must be non-empty and %NULL-terminated
+ * @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: function to run in the child just before exec()
+ * @child_setup: (scope async) (closure user_data) (nullable): function to run
+ *     in the child just before `exec()`
  * @user_data: user data for @child_setup
- * @child_pid: return location for child process ID, or %NULL
- * @standard_input: return location for file descriptor to write to child's stdin, or %NULL
- * @standard_output: return location for file descriptor to read child's stdout, or %NULL
- * @standard_error: return location for file descriptor to read child's stderr, 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`
+ * @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. @argv
- * should be a %NULL-terminated array of strings, to be passed as the
- * argument vector for the child. The first string in @argv is of
- * course the name of the program to execute. By default, the name of
- * the program must be a full path; the <envar>PATH</envar> shell variable 
- * will only be searched if you pass the %G_SPAWN_SEARCH_PATH flag.
+ * block waiting for the child to exit).
+ *
+ * The child program is specified by the only argument that must be
+ * provided, @argv. @argv should be a %NULL-terminated array of strings,
+ * to be passed as the argument vector for the child. The first string
+ * in @argv is of course the name of the program to execute. By default,
+ * the 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 @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 the
+ * environment variable.
+ *
+ * If the program name is not a full path and %G_SPAWN_SEARCH_PATH flag
+ * is not used, then the program will be run from the current directory
+ * (or @working_directory, if specified); this might be unexpected or even
+ * dangerous in some cases when the current directory is world-writable.
  *
  * On Windows, note that all the string or string vector arguments to
- * this function and the other g_spawn*() functions are in UTF-8, the
+ * this function and the other `g_spawn*()` functions are in UTF-8, the
  * GLib file name encoding. Unicode characters that are not part of
  * the system codepage passed in these arguments will be correctly
  * available in the spawned program only if it uses wide character API
  * to retrieve its command line. For C programs built with Microsoft's
- * tools it is enough to make the program have a wmain() instead of
- * main(). wmain() has a wide character argument vector as parameter.
- *
- * At least currently, mingw doesn't support wmain(), so if you use
- * mingw to develop the spawned program, it will have to call the
- * undocumented function __wgetmainargs() to get the wide character
- * argument vector and environment. See gspawn-win32-helper.c in the
- * GLib sources or init.c in the mingw runtime sources for a prototype
- * for that function. Alternatively, you can retrieve the Win32 system
- * level wide character command line passed to the spawned program
- * using the GetCommandLineW() function.
- *
- * On Windows the low-level child process creation API
- * <function>CreateProcess()</function> doesn't use argument vectors,
- * but a command line. The C runtime library's
- * <function>spawn*()</function> family of functions (which
- * g_spawn_async_with_pipes() eventually calls) paste the argument
- * vector elements together into a command line, and the C runtime startup code
- * does a corresponding reconstruction of an argument vector from the
- * command line, to be passed to main(). Complications arise when you have
- * argument vector elements that contain spaces of double quotes. The
- * <function>spawn*()</function> functions don't do any quoting or
- * escaping, but on the other hand the startup code does do unquoting
- * and unescaping in order to enable receiving arguments with embedded
- * spaces or double quotes. To work around this asymmetry,
- * g_spawn_async_with_pipes() will do quoting and escaping on argument
- * vector elements that need it before calling the C runtime
- * spawn() function.
+ * tools it is enough to make the program have a `wmain()` instead of
+ * `main()`. `wmain()` has a wide character argument vector as parameter.
+ *
+ * At least currently, mingw doesn't support `wmain()`, so if you use
+ * mingw to develop the spawned program, it should call
+ * g_win32_get_command_line() to get arguments in UTF-8.
+ *
+ * On Windows the low-level child process creation API `CreateProcess()`
+ * doesn't use argument vectors, but a command line. The C runtime
+ * library's `spawn*()` family of functions (which g_spawn_async_with_pipes()
+ * eventually calls) paste the argument vector elements together into
+ * a command line, and the C runtime startup code does a corresponding
+ * reconstruction of an argument vector from the command line, to be
+ * passed to `main()`. Complications arise when you have argument vector
+ * elements that contain spaces or double quotes. The `spawn*()` functions
+ * don't do any quoting or escaping, but on the other hand the startup
+ * code does do unquoting and unescaping in order to enable receiving
+ * arguments with embedded spaces or double quotes. To work around this
+ * asymmetry, g_spawn_async_with_pipes() will do quoting and escaping on
+ * argument vector elements that need it before calling the C runtime
+ * `spawn()` function.
  *
  * The returned @child_pid on Windows is a handle to the child
  * process, not its identifier. Process handles and process
  * identifiers are different concepts on Windows.
  *
  * @envp is a %NULL-terminated array of strings, where each string
- * has the form <literal>KEY=VALUE</literal>. This will become
- * the child's environment. If @envp is %NULL, the child inherits its
- * parent's environment.
+ * has the form `KEY=VALUE`. This will become the child's environment.
+ * If @envp is %NULL, the child inherits its parent's environment.
  *
  * @flags should be the bitwise OR of any flags you want to affect the
- * function's behaviour. The %G_SPAWN_DO_NOT_REAP_CHILD means that 
- * the child will not automatically be reaped; you must use a
- * #GChildWatch source to be notified about the death of the child 
- * process. 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 #GChildWatch source is
- * equivalent to calling waitpid() or handling the %SIGCHLD signal 
- * manually. On Windows, calling g_spawn_close_pid() is equivalent
- * to calling CloseHandle() on the process handle returned in 
- * @child_pid).
- *
- * %G_SPAWN_LEAVE_DESCRIPTORS_OPEN means that the parent's open file
- * descriptors will be inherited by the child; otherwise all
- * descriptors except stdin/stdout/stderr will be closed before
- * calling exec() in the child. %G_SPAWN_SEARCH_PATH 
- * means that <literal>argv[0]</literal> need not be an absolute path, it
- * will be looked for in the user's <envar>PATH</envar>. 
- * %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.
- * %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.
+ * function's behaviour. The %G_SPAWN_DO_NOT_REAP_CHILD means that the
+ * child will not automatically be reaped; you must use a child watch
+ * (g_child_watch_add()) to be notified about the death of the child process,
+ * otherwise it will stay around as a zombie process until this process exits.
+ * 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()
+ * is equivalent to calling `CloseHandle()` on the process handle returned
+ * in @child_pid). See g_child_watch_add().
+ *
+ * Open UNIX file descriptors marked as `FD_CLOEXEC` will be automatically
+ * closed in the child process. %G_SPAWN_LEAVE_DESCRIPTORS_OPEN means that
+ * other open file descriptors will be inherited by the child; otherwise all
+ * descriptors except stdin/stdout/stderr will be closed before calling `exec()`
+ * in the child. %G_SPAWN_SEARCH_PATH means that @argv[0] need not be an
+ * absolute path, it will be looked for in the `PATH` environment
+ * variable. %G_SPAWN_SEARCH_PATH_FROM_ENVP means need not be an
+ * 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_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`). %G_SPAWN_STDIN_FROM_DEV_NULL explicitly imposes the default
+ * behavior. Both flags cannot be enabled at the same time and, in both cases,
+ * the @stdin_pipe_out argument is ignored.
+ *
+ * %G_SPAWN_STDOUT_TO_DEV_NULL means that the child's standard output
+ * will be discarded (by default, it goes to the same location as the parent's
+ * standard output). %G_SPAWN_CHILD_INHERITS_STDOUT explicitly imposes the
+ * default behavior. Both flags cannot be enabled at the same time and, in
+ * both cases, the @stdout_pipe_out argument is ignored.
+ *
+ * %G_SPAWN_STDERR_TO_DEV_NULL means that the child's standard error
+ * will be discarded (by default, it goes to the same location as the parent's
+ * standard error). %G_SPAWN_CHILD_INHERITS_STDERR explicitly imposes the
+ * default behavior. Both flags cannot be enabled at the same time and, in
+ * both cases, the @stderr_pipe_out argument is ignored.
+ *
+ * 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.
+ *
+ * @source_fds is supported on Windows since 2.72.
+ *
  * %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() uses @argv[0] as the file to execute, and
- * passes all of @argv to the child.
+ * the file to execute, while the remaining elements are the actual
+ * argument vector to pass to the file. Normally g_spawn_async_with_pipes()
+ * uses @argv[0] as the file to execute, and passes all of @argv to the child.
  *
  * @child_setup and @user_data are a function and user data. On POSIX
  * platforms, the function is called in the child after GLib has
  * performed all the setup it plans to perform (including creating
- * pipes, closing file descriptors, etc.) but before calling
- * exec(). That is, @child_setup is called just
- * before calling exec() in the child. Obviously
- * actions taken in this function will only affect the child, not the
- * parent.
- *
- * On Windows, there is no separate fork() and exec()
- * functionality. Child processes are created and run with a single
- * API call, CreateProcess(). There is no sensible thing @child_setup
+ * pipes, closing file descriptors, etc.) but before calling `exec()`.
+ * That is, @child_setup is called just before calling `exec()` in the
+ * child. Obviously actions taken in this function will only affect
+ * the child, not the parent.
+ *
+ * On Windows, there is no separate `fork()` and `exec()` functionality.
+ * Child processes are created and run with a single API call,
+ * `CreateProcess()`. There is no sensible thing @child_setup
  * could be used for on Windows so it is ignored and not called.
  *
  * If non-%NULL, @child_pid will on Unix be filled with the child's
- * process ID. You can use the process ID to send signals to the
- * child, or to use g_child_watch_add() (or waitpid()) if you specified the
+ * process ID. You can use the process ID to send signals to the child,
+ * or to use g_child_watch_add() (or `waitpid()`) if you specified the
  * %G_SPAWN_DO_NOT_REAP_CHILD flag. On Windows, @child_pid will be
  * filled with a handle to the child process only if you specified the
  * %G_SPAWN_DO_NOT_REAP_CHILD flag. You can then access the child
  * process using the Win32 API, for example wait for its termination
- * with the <function>WaitFor*()</function> functions, or examine its
- * exit code with GetExitCodeProcess(). You should close the handle 
- * with CloseHandle() or g_spawn_close_pid() when you no longer need it.
+ * with the `WaitFor*()` functions, or examine its exit code with
+ * `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.
+ * 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 
- * /dev/null unless %G_SPAWN_CHILD_INHERITS_STDIN is set.
+ * 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.
- * If an error is set, the function returns %FALSE. Errors
- * are reported even if they occur in the child (for example if the
- * executable in <literal>argv[0]</literal> is not found). Typically
- * the <literal>message</literal> field of returned errors should be displayed
- * to users. Possible errors are those from the #G_SPAWN_ERROR domain.
+ * If an error is set, the function returns %FALSE. Errors are reported
+ * even if they occur in the child (for example if the executable in
+ * `@argv[0]` is not found). Typically the `message` field of returned
+ * 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().
  *
- * <note><para>
- * If you are writing a GTK+ application, and the program you 
- * are spawning is a graphical application, too, then you may
- * want to use gdk_spawn_on_screen_with_pipes() instead to ensure that 
- * the spawned program opens its windows on the right screen.
- * </para></note>
- * 
- * Return value: %TRUE on success, %FALSE if an error was set
- **/
+ * On modern UNIX platforms, GLib can use an efficient process launching
+ * codepath driven internally by `posix_spawn()`. This has the advantage of
+ * avoiding the fork-time performance costs of cloning the parent process
+ * address space, and avoiding associated memory overcommit checks that are
+ * not relevant in the context of immediately executing a distinct process.
+ * This optimized codepath will be used provided that the following conditions
+ * are met:
+ *
+ * 1. %G_SPAWN_DO_NOT_REAP_CHILD is set
+ * 2. %G_SPAWN_LEAVE_DESCRIPTORS_OPEN is set
+ * 3. %G_SPAWN_SEARCH_PATH_FROM_ENVP is not set
+ * 4. @working_directory is %NULL
+ * 5. @child_setup is %NULL
+ * 6. The program is of a recognised binary format, or has a shebang.
+ *    Otherwise, GLib will have to execute the program through the
+ *    shell, which is not done using the optimized codepath.
+ *
+ * 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.
+ *
+ * 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 (argv[0] != NULL, FALSE);
+  /* can’t both inherit and set pipes to /dev/null */
+  g_return_val_if_fail ((flags & INHERITS_OR_NULL_STDIN) != INHERITS_OR_NULL_STDIN, FALSE);
+  g_return_val_if_fail ((flags & INHERITS_OR_NULL_STDOUT) != INHERITS_OR_NULL_STDOUT, FALSE);
+  g_return_val_if_fail ((flags & INHERITS_OR_NULL_STDERR) != INHERITS_OR_NULL_STDERR, FALSE);
+  /* 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);
+
+  if ((flags & INHERITS_OR_NULL_STDIN) != 0)
+    stdin_pipe_out = NULL;
+  if ((flags & INHERITS_OR_NULL_STDOUT) != 0)
+    stdout_pipe_out = NULL;
+  if ((flags & INHERITS_OR_NULL_STDERR) != 0)
+    stderr_pipe_out = NULL;
+
+  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);
+}
+
+/**
+ * g_spawn_async_with_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): child's argument vector, in the GLib file name encoding;
+ *   it must be non-empty and %NULL-terminated
+ * @envp: (array zero-terminated=1) (nullable): child's environment, or %NULL to inherit parent's, in the GLib file name encoding
+ * @flags: flags from #GSpawnFlags
+ * @child_setup: (scope async) (closure user_data) (nullable): function to run
+ *   in the child just before `exec()`
+ * @user_data: 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`
+ * @error: return location for error
+ *
+ * Executes a child program asynchronously.
+ *
+ * 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
+ *
+ * Since: 2.58
+ */
+gboolean
+g_spawn_async_with_fds (const gchar          *working_directory,
+                        gchar               **argv,
+                        gchar               **envp,
+                        GSpawnFlags           flags,
+                        GSpawnChildSetupFunc  child_setup,
+                        gpointer              user_data,
+                        GPid                 *child_pid,
+                        gint                  stdin_fd,
+                        gint                  stdout_fd,
+                        gint                  stderr_fd,
+                        GError              **error)
+{
+  g_return_val_if_fail (argv != NULL, FALSE);
+  g_return_val_if_fail (argv[0] != NULL, FALSE);
+  g_return_val_if_fail (stdout_fd < 0 ||
                         !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE);
-  g_return_val_if_fail (standard_error == NULL ||
+  g_return_val_if_fail (stderr_fd < 0 ||
                         !(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_fd < 0 ||
                         !(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_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,
-                               child_setup,
-                               user_data,
-                               child_pid,
-                               standard_input,
-                               standard_output,
-                               standard_error,
-                               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);
 }
 
 /**
  * g_spawn_command_line_sync:
- * @command_line: a command line 
- * @standard_output: return location for child output
- * @standard_error: return location for child errors
- * @exit_status: return location for child exit status, as returned by waitpid()
+ * @command_line: (type filename): a command line
+ * @standard_output: (out) (array zero-terminated=1) (element-type guint8) (optional): return location for child output
+ * @standard_error: (out) (array zero-terminated=1) (element-type guint8) (optional): return location for child errors
+ * @wait_status: (out) (optional): return location for child wait status, as returned by waitpid()
  * @error: return location for errors
  *
  * A simple version of g_spawn_sync() with little-used parameters
- * removed, taking a command line instead of an argument vector.  See
- * g_spawn_sync() for full details. @command_line will be parsed by
- * g_shell_parse_argv(). Unlike g_spawn_sync(), the %G_SPAWN_SEARCH_PATH flag
- * is enabled. Note that %G_SPAWN_SEARCH_PATH can have security
- * implications, so consider using g_spawn_sync() directly if
- * appropriate. Possible errors are those from g_spawn_sync() and those
+ * removed, taking a command line instead of an argument vector.
+ *
+ * See g_spawn_sync() for full details.
+ *
+ * The @command_line argument will be parsed by g_shell_parse_argv().
+ *
+ * Unlike g_spawn_sync(), the %G_SPAWN_SEARCH_PATH flag is enabled.
+ * Note that %G_SPAWN_SEARCH_PATH can have security implications, so
+ * consider using g_spawn_sync() directly if appropriate.
+ *
+ * Possible errors are those from g_spawn_sync() and those
  * from g_shell_parse_argv().
  *
- * If @exit_status is non-%NULL, the exit status of the child is stored there as
- * it would be returned by waitpid(); standard UNIX macros such as WIFEXITED()
- * and WEXITSTATUS() must be used to evaluate the exit status.
+ * If @wait_status is non-%NULL, the platform-specific status of
+ * the child is stored there; see the documentation of
+ * g_spawn_check_wait_status() for how to use and interpret this.
+ * On Unix platforms, note that it is usually not equal
+ * to the integer passed to `exit()` or returned from `main()`.
  * 
  * On Windows, please note the implications of g_shell_parse_argv()
  * parsing @command_line. Parsing is done according to Unix shell rules, not 
@@ -678,13 +952,13 @@ g_spawn_async_with_pipes (const gchar          *working_directory,
  * separator. You need to enclose such paths with single quotes, like
  * "'c:\\program files\\app\\app.exe' 'e:\\folder\\argument.txt'".
  *
- * Return value: %TRUE on success, %FALSE if an error was set
+ * Returns: %TRUE on success, %FALSE if an error was set
  **/
 gboolean
 g_spawn_command_line_sync (const gchar  *command_line,
                            gchar       **standard_output,
                            gchar       **standard_error,
-                           gint         *exit_status,
+                           gint         *wait_status,
                            GError      **error)
 {
   gboolean retval;
@@ -692,6 +966,7 @@ g_spawn_command_line_sync (const gchar  *command_line,
 
   g_return_val_if_fail (command_line != NULL, FALSE);
   
+  /* This will return a runtime error if @command_line is the empty string. */
   if (!g_shell_parse_argv (command_line,
                            NULL, &argv,
                            error))
@@ -705,7 +980,7 @@ g_spawn_command_line_sync (const gchar  *command_line,
                          NULL,
                          standard_output,
                          standard_error,
-                         exit_status,
+                         wait_status,
                          error);
   g_strfreev (argv);
 
@@ -714,12 +989,13 @@ g_spawn_command_line_sync (const gchar  *command_line,
 
 /**
  * g_spawn_command_line_async:
- * @command_line: a command line
+ * @command_line: (type filename): a command line
  * @error: return location for errors
  * 
  * A simple version of g_spawn_async() that parses a command line with
- * g_shell_parse_argv() and passes it to g_spawn_async(). Runs a
- * command line in the background. Unlike g_spawn_async(), the
+ * g_shell_parse_argv() and passes it to g_spawn_async().
+ *
+ * Runs a command line in the background. Unlike g_spawn_async(), the
  * %G_SPAWN_SEARCH_PATH flag is enabled, other flags are not. Note
  * that %G_SPAWN_SEARCH_PATH can have security implications, so
  * consider using g_spawn_async() directly if appropriate. Possible
@@ -727,7 +1003,7 @@ g_spawn_command_line_sync (const gchar  *command_line,
  * 
  * The same concerns on Windows apply as for g_spawn_command_line_sync().
  *
- * Return value: %TRUE on success, %FALSE if error is set.
+ * Returns: %TRUE on success, %FALSE if error is set
  **/
 gboolean
 g_spawn_command_line_async (const gchar *command_line,
@@ -738,6 +1014,7 @@ g_spawn_command_line_async (const gchar *command_line,
 
   g_return_val_if_fail (command_line != NULL, FALSE);
 
+  /* This will return a runtime error if @command_line is the empty string. */
   if (!g_shell_parse_argv (command_line,
                            NULL, &argv,
                            error))
@@ -756,113 +1033,128 @@ g_spawn_command_line_async (const gchar *command_line,
   return retval;
 }
 
-static gint
-exec_err_to_g_error (gint en)
+/**
+ * g_spawn_check_wait_status:
+ * @wait_status: A platform-specific wait status as returned from g_spawn_sync()
+ * @error: a #GError
+ *
+ * Set @error if @wait_status indicates the child exited abnormally
+ * (e.g. with a nonzero exit code, or via a fatal signal).
+ *
+ * The g_spawn_sync() and g_child_watch_add() family of APIs return the
+ * status of subprocesses encoded in a platform-specific way.
+ * On Unix, this is guaranteed to be in the same format waitpid() returns,
+ * and on Windows it is guaranteed to be the result of GetExitCodeProcess().
+ *
+ * Prior to the introduction of this function in GLib 2.34, interpreting
+ * @wait_status required use of platform-specific APIs, which is problematic
+ * for software using GLib as a cross-platform layer.
+ *
+ * Additionally, many programs simply want to determine whether or not
+ * the child exited successfully, and either propagate a #GError or
+ * print a message to standard error. In that common case, this function
+ * can be used. Note that the error message in @error will contain
+ * human-readable information about the wait status.
+ *
+ * The @domain and @code of @error have special semantics in the case
+ * where the process has an "exit code", as opposed to being killed by
+ * a signal. On Unix, this happens if WIFEXITED() would be true of
+ * @wait_status. On Windows, it is always the case.
+ *
+ * The special semantics are that the actual exit code will be the
+ * code set in @error, and the domain will be %G_SPAWN_EXIT_ERROR.
+ * This allows you to differentiate between different exit codes.
+ *
+ * If the process was terminated by some means other than an exit
+ * status (for example if it was killed by a signal), the domain will be
+ * %G_SPAWN_ERROR and the code will be %G_SPAWN_ERROR_FAILED.
+ *
+ * This function just offers convenience; you can of course also check
+ * the available platform via a macro such as %G_OS_UNIX, and use
+ * WIFEXITED() and WEXITSTATUS() on @wait_status directly. Do not attempt
+ * to scan or parse the error message string; it may be translated and/or
+ * change in future versions of GLib.
+ *
+ * Prior to version 2.70, g_spawn_check_exit_status() provides the same
+ * functionality, although under a misleading name.
+ *
+ * Returns: %TRUE if child exited successfully, %FALSE otherwise (and
+ *   @error will be set)
+ *
+ * Since: 2.70
+ */
+gboolean
+g_spawn_check_wait_status (gint      wait_status,
+                          GError  **error)
 {
-  switch (en)
-    {
-#ifdef EACCES
-    case EACCES:
-      return G_SPAWN_ERROR_ACCES;
-      break;
-#endif
-
-#ifdef EPERM
-    case EPERM:
-      return G_SPAWN_ERROR_PERM;
-      break;
-#endif
+  gboolean ret = FALSE;
 
-#ifdef E2BIG
-    case E2BIG:
-      return G_SPAWN_ERROR_2BIG;
-      break;
-#endif
-
-#ifdef ENOEXEC
-    case ENOEXEC:
-      return G_SPAWN_ERROR_NOEXEC;
-      break;
-#endif
-
-#ifdef ENAMETOOLONG
-    case ENAMETOOLONG:
-      return G_SPAWN_ERROR_NAMETOOLONG;
-      break;
-#endif
-
-#ifdef ENOENT
-    case ENOENT:
-      return G_SPAWN_ERROR_NOENT;
-      break;
-#endif
-
-#ifdef ENOMEM
-    case ENOMEM:
-      return G_SPAWN_ERROR_NOMEM;
-      break;
-#endif
-
-#ifdef ENOTDIR
-    case ENOTDIR:
-      return G_SPAWN_ERROR_NOTDIR;
-      break;
-#endif
-
-#ifdef ELOOP
-    case ELOOP:
-      return G_SPAWN_ERROR_LOOP;
-      break;
-#endif
-      
-#ifdef ETXTBUSY
-    case ETXTBUSY:
-      return G_SPAWN_ERROR_TXTBUSY;
-      break;
-#endif
-
-#ifdef EIO
-    case EIO:
-      return G_SPAWN_ERROR_IO;
-      break;
-#endif
-
-#ifdef ENFILE
-    case ENFILE:
-      return G_SPAWN_ERROR_NFILE;
-      break;
-#endif
-
-#ifdef EMFILE
-    case EMFILE:
-      return G_SPAWN_ERROR_MFILE;
-      break;
-#endif
-
-#ifdef EINVAL
-    case EINVAL:
-      return G_SPAWN_ERROR_INVAL;
-      break;
-#endif
+  if (WIFEXITED (wait_status))
+    {
+      if (WEXITSTATUS (wait_status) != 0)
+       {
+         g_set_error (error, G_SPAWN_EXIT_ERROR, WEXITSTATUS (wait_status),
+                      _("Child process exited with code %ld"),
+                      (long) WEXITSTATUS (wait_status));
+         goto out;
+       }
+    }
+  else if (WIFSIGNALED (wait_status))
+    {
+      g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED,
+                  _("Child process killed by signal %ld"),
+                  (long) WTERMSIG (wait_status));
+      goto out;
+    }
+  else if (WIFSTOPPED (wait_status))
+    {
+      g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED,
+                  _("Child process stopped by signal %ld"),
+                  (long) WSTOPSIG (wait_status));
+      goto out;
+    }
+  else
+    {
+      g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED,
+                  _("Child process exited abnormally"));
+      goto out;
+    }
 
-#ifdef EISDIR
-    case EISDIR:
-      return G_SPAWN_ERROR_ISDIR;
-      break;
-#endif
+  ret = TRUE;
+ out:
+  return ret;
+}
 
-#ifdef ELIBBAD
-    case ELIBBAD:
-      return G_SPAWN_ERROR_LIBBAD;
-      break;
-#endif
-      
-    default:
-      return G_SPAWN_ERROR_FAILED;
-      break;
-    }
+/**
+ * g_spawn_check_exit_status:
+ * @wait_status: A status as returned from g_spawn_sync()
+ * @error: a #GError
+ *
+ * An old name for g_spawn_check_wait_status(), deprecated because its
+ * name is misleading.
+ *
+ * Despite the name of the function, @wait_status must be the wait status
+ * as returned by g_spawn_sync(), g_subprocess_get_status(), `waitpid()`,
+ * etc. On Unix platforms, it is incorrect for it to be the exit status
+ * as passed to `exit()` or returned by g_subprocess_get_exit_status() or
+ * `WEXITSTATUS()`.
+ *
+ * Returns: %TRUE if child exited successfully, %FALSE otherwise (and
+ *     @error will be set)
+ *
+ * Since: 2.34
+ *
+ * Deprecated: 2.70: Use g_spawn_check_wait_status() instead, and check whether your code is conflating wait and exit statuses.
+ */
+gboolean
+g_spawn_check_exit_status (gint      wait_status,
+                           GError  **error)
+{
+  return g_spawn_check_wait_status (wait_status, error);
 }
 
+/* This function is called between fork() and exec() and hence must be
+ * async-signal-safe (see signal-safety(7)). */
 static gssize
 write_all (gint fd, gconstpointer vbuf, gsize to_write)
 {
@@ -886,7 +1178,9 @@ write_all (gint fd, gconstpointer vbuf, gsize to_write)
   return TRUE;
 }
 
-G_GNUC_NORETURN
+/* This function is called between fork() and exec() and hence must be
+ * async-signal-safe (see signal-safety(7)). */
+G_NORETURN
 static void
 write_err_and_exit (gint fd, gint msg)
 {
@@ -898,113 +1192,159 @@ write_err_and_exit (gint fd, gint msg)
   _exit (1);
 }
 
-static int
-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
+set_cloexec (int fd)
 {
-  if (fd >= GPOINTER_TO_INT (data))
-    fcntl (fd, F_SETFD, FD_CLOEXEC);
-
-  return 0;
+  fcntl (fd, F_SETFD, FD_CLOEXEC);
 }
 
-#ifndef HAVE_FDWALK
-static int
-fdwalk (int (*cb)(void *data, int fd), void *data)
+/* 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)
 {
-  gint open_max;
-  gint fd;
-  gint res = 0;
-  
-#ifdef HAVE_SYS_RESOURCE_H
-  struct rlimit rl;
-#endif
-
-#ifdef __linux__  
-  DIR *d;
-
-  if ((d = opendir("/proc/self/fd"))) {
-      struct dirent *de;
-
-      while ((de = readdir(d))) {
-          glong l;
-          gchar *e = NULL;
-
-          if (de->d_name[0] == '.')
-              continue;
-            
-          errno = 0;
-          l = strtol(de->d_name, &e, 10);
-          if (errno != 0 || !e || *e)
-              continue;
-
-          fd = (gint) l;
-
-          if ((glong) fd != l)
-              continue;
+  int flags;
+  int result;
 
-          if (fd == dirfd(d))
-              continue;
+  flags = fcntl (fd, F_GETFD, 0);
 
-          if ((res = cb (data, fd)) != 0)
-              break;
+  if (flags != -1)
+    {
+      int errsv;
+      flags &= (~FD_CLOEXEC);
+      do
+        {
+          result = fcntl (fd, F_SETFD, flags);
+          errsv = errno;
         }
-      
-      closedir(d);
-      return res;
-  }
-
-  /* If /proc is not mounted or not accessible we fall back to the old
-   * rlimit trick */
+      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 old_fd, int new_fd_min)
+{
+  int fd, errsv;
+#ifdef F_DUPFD_CLOEXEC
+  do
+    {
+      fd = fcntl (old_fd, F_DUPFD_CLOEXEC, new_fd_min);
+      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 (old_fd, F_DUPFD, new_fd_min);
+      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
-  
-#ifdef HAVE_SYS_RESOURCE_H
-      
-  if (getrlimit(RLIMIT_NOFILE, &rl) == 0 && rl.rlim_max != RLIM_INFINITY)
-      open_max = rl.rlim_max;
-  else
-#endif
-      open_max = sysconf (_SC_OPEN_MAX);
-
-  for (fd = 0; fd < open_max; fd++)
-      if ((res = cb (data, fd)) != 0)
-          break;
-
-  return res;
+  return fd;
 }
-#endif
 
+/* This function is called between fork() and exec() and hence must be
+ * async-signal-safe (see signal-safety(7)). */
 static gint
-sane_dup2 (gint fd1, gint fd2)
+safe_dup2 (gint fd1, gint fd2)
 {
   gint ret;
 
- retry:
-  ret = dup2 (fd1, fd2);
-  if (ret < 0 && errno == EINTR)
-    goto retry;
+  do
+    ret = dup2 (fd1, fd2);
+  while (ret < 0 && (errno == EINTR || errno == EBUSY));
 
   return ret;
 }
 
-enum
+/* This function is called between fork() and exec() and hence must be
+ * async-signal-safe (see signal-safety(7)). */
+static gboolean
+relocate_fd_out_of_standard_range (gint *fd)
 {
-  CHILD_CHDIR_FAILED,
+  gint ret = -1;
+  const int min_fileno = STDERR_FILENO + 1;
+
+  do
+    ret = fcntl (*fd, F_DUPFD, min_fileno);
+  while (ret < 0 && errno == EINTR);
+
+  /* Note we don't need to close the old fd, because the caller is expected
+   * to close fds in the standard range itself.
+   */
+  if (ret >= min_fileno)
+    {
+      *fd = ret;
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+/* This function is called between fork() and exec() and hence must be
+ * async-signal-safe (see signal-safety(7)). */
+static gint
+safe_open (const char *path, gint mode)
+{
+  gint ret;
+
+  do
+    ret = open (path, mode);
+  while (ret < 0 && errno == EINTR);
+
+  return ret;
+}
+
+enum
+{
+  CHILD_CHDIR_FAILED,
   CHILD_EXEC_FAILED,
-  CHILD_DUP2_FAILED,
-  CHILD_FORK_FAILED
+  CHILD_OPEN_FAILED,
+  CHILD_DUPFD_FAILED,
+  CHILD_FORK_FAILED,
+  CHILD_CLOSE_FAILED,
 };
 
+/* This function is called between fork() and exec() and hence must be
+ * async-signal-safe (see signal-safety(7)) until it calls exec().
+ *
+ * All callers must guarantee that @argv and @argv[0] are non-NULL. */
 static void
 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,
-         gchar               **envp,
+         const gchar * const  *argv,
+         gchar               **argv_buffer,
+         gsize                 argv_buffer_len,
+         const gchar * const  *envp,
          gboolean              close_descriptors,
-         gboolean              search_path,
+         const gchar          *search_path,
+         gchar                *search_path_buffer,
+         gsize                 search_path_buffer_len,
          gboolean              stdout_to_null,
          gboolean              stderr_to_null,
          gboolean              child_inherits_stdin,
@@ -1012,82 +1352,224 @@ do_exec (gint                  child_err_report_fd,
          GSpawnChildSetupFunc  child_setup,
          gpointer              user_data)
 {
+  gsize i;
+  gint max_target_fd = 0;
+
   if (working_directory && chdir (working_directory) < 0)
     write_err_and_exit (child_err_report_fd,
                         CHILD_CHDIR_FAILED);
 
-  /* Close all file descriptors but stdin stdout and stderr as
-   * soon as we exec. Note that this includes
-   * child_err_report_fd, which keeps the parent from blocking
-   * forever on the other end of that pipe.
+  /* It's possible the caller assigned stdin to an fd with a
+   * file number that is supposed to be reserved for
+   * stdout or stderr.
+   *
+   * If so, move it up out of the standard range, so it doesn't
+   * cause a conflict.
    */
-  if (close_descriptors)
+  if (IS_STD_FILENO (stdin_fd) && stdin_fd != STDIN_FILENO)
     {
-      fdwalk (set_cloexec, GINT_TO_POINTER(3));
-    }
-  else
-    {
-      /* We need to do child_err_report_fd anyway */
-      set_cloexec (GINT_TO_POINTER(0), child_err_report_fd);
+      int old_fd = stdin_fd;
+
+      if (!relocate_fd_out_of_standard_range (&stdin_fd))
+        write_err_and_exit (child_err_report_fd, CHILD_DUPFD_FAILED);
+
+      if (stdout_fd == old_fd)
+        stdout_fd = stdin_fd;
+
+      if (stderr_fd == old_fd)
+        stderr_fd = stdin_fd;
     }
-  
-  /* Redirect pipes as required */
-  
-  if (stdin_fd >= 0)
+
+  /* Redirect pipes as required
+   *
+   * There are two cases where we don't need to do the redirection
+   * 1. Where the associated file descriptor is cleared/invalid
+   * 2. When the associated file descriptor is already given the
+   * correct file number.
+   */
+  if (IS_VALID_FILENO (stdin_fd) && stdin_fd != STDIN_FILENO)
     {
-      /* dup2 can't actually fail here I don't think */
-          
-      if (sane_dup2 (stdin_fd, 0) < 0)
+      if (safe_dup2 (stdin_fd, 0) < 0)
         write_err_and_exit (child_err_report_fd,
-                            CHILD_DUP2_FAILED);
+                            CHILD_DUPFD_FAILED);
 
-      /* ignore this if it doesn't work */
-      close_and_invalidate (&stdin_fd);
+      set_cloexec (stdin_fd);
     }
   else if (!child_inherits_stdin)
     {
       /* Keep process from blocking on a read of stdin */
-      gint read_null = open ("/dev/null", O_RDONLY);
-      sane_dup2 (read_null, 0);
-      close_and_invalidate (&read_null);
+      gint read_null = safe_open ("/dev/null", O_RDONLY);
+      if (read_null < 0)
+        write_err_and_exit (child_err_report_fd,
+                            CHILD_OPEN_FAILED);
+      if (safe_dup2 (read_null, 0) < 0)
+        write_err_and_exit (child_err_report_fd,
+                            CHILD_DUPFD_FAILED);
+      g_clear_fd (&read_null, NULL);
     }
 
-  if (stdout_fd >= 0)
+  /* Like with stdin above, it's possible the caller assigned
+   * stdout to an fd with a file number that's intruding on the
+   * standard range.
+   *
+   * If so, move it out of the way, too.
+   */
+  if (IS_STD_FILENO (stdout_fd) && stdout_fd != STDOUT_FILENO)
     {
-      /* dup2 can't actually fail here I don't think */
-          
-      if (sane_dup2 (stdout_fd, 1) < 0)
+      int old_fd = stdout_fd;
+
+      if (!relocate_fd_out_of_standard_range (&stdout_fd))
+        write_err_and_exit (child_err_report_fd, CHILD_DUPFD_FAILED);
+
+      if (stderr_fd == old_fd)
+        stderr_fd = stdout_fd;
+    }
+
+  if (IS_VALID_FILENO (stdout_fd) && stdout_fd != STDOUT_FILENO)
+    {
+      if (safe_dup2 (stdout_fd, 1) < 0)
         write_err_and_exit (child_err_report_fd,
-                            CHILD_DUP2_FAILED);
+                            CHILD_DUPFD_FAILED);
 
-      /* ignore this if it doesn't work */
-      close_and_invalidate (&stdout_fd);
+      set_cloexec (stdout_fd);
     }
   else if (stdout_to_null)
     {
-      gint write_null = open ("/dev/null", O_WRONLY);
-      sane_dup2 (write_null, 1);
-      close_and_invalidate (&write_null);
+      gint write_null = safe_open ("/dev/null", O_WRONLY);
+      if (write_null < 0)
+        write_err_and_exit (child_err_report_fd,
+                            CHILD_OPEN_FAILED);
+      if (safe_dup2 (write_null, 1) < 0)
+        write_err_and_exit (child_err_report_fd,
+                            CHILD_DUPFD_FAILED);
+      g_clear_fd (&write_null, NULL);
     }
 
-  if (stderr_fd >= 0)
+  if (IS_STD_FILENO (stderr_fd) && stderr_fd != STDERR_FILENO)
     {
-      /* dup2 can't actually fail here I don't think */
-          
-      if (sane_dup2 (stderr_fd, 2) < 0)
+      if (!relocate_fd_out_of_standard_range (&stderr_fd))
+        write_err_and_exit (child_err_report_fd, CHILD_DUPFD_FAILED);
+    }
+
+  /* Like with stdin/stdout above, it's possible the caller assigned
+   * stderr to an fd with a file number that's intruding on the
+   * standard range.
+   *
+   * Make sure it's out of the way, also.
+   */
+  if (IS_VALID_FILENO (stderr_fd) && stderr_fd != STDERR_FILENO)
+    {
+      if (safe_dup2 (stderr_fd, 2) < 0)
         write_err_and_exit (child_err_report_fd,
-                            CHILD_DUP2_FAILED);
+                            CHILD_DUPFD_FAILED);
 
-      /* ignore this if it doesn't work */
-      close_and_invalidate (&stderr_fd);
+      set_cloexec (stderr_fd);
     }
   else if (stderr_to_null)
     {
-      gint write_null = open ("/dev/null", O_WRONLY);
-      sane_dup2 (write_null, 2);
-      close_and_invalidate (&write_null);
+      gint write_null = safe_open ("/dev/null", O_WRONLY);
+      if (write_null < 0)
+        write_err_and_exit (child_err_report_fd,
+                            CHILD_OPEN_FAILED);
+      if (safe_dup2 (write_null, 2) < 0)
+        write_err_and_exit (child_err_report_fd,
+                            CHILD_DUPFD_FAILED);
+      g_clear_fd (&write_null, NULL);
     }
-  
+
+  /* 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 && n_fds == 0)
+        {
+          if (safe_dup2 (child_err_report_fd, 3) < 0)
+            write_err_and_exit (child_err_report_fd, CHILD_DUPFD_FAILED);
+          set_cloexec (3);
+          if (g_closefrom (4) < 0)
+            write_err_and_exit (child_err_report_fd, CHILD_CLOSE_FAILED);
+          child_err_report_fd = 3;
+        }
+      else
+        {
+          if (g_fdwalk_set_cloexec (3) < 0)
+            write_err_and_exit (child_err_report_fd, CHILD_CLOSE_FAILED);
+        }
+    }
+  else
+    {
+      /* We need to do child_err_report_fd anyway */
+      set_cloexec (child_err_report_fd);
+    }
+
+  /*
+   * Work through the @source_fds and @target_fds mapping.
+   *
+   * Based on code originally derived from
+   * gnome-terminal:src/terminal-screen.c:terminal_screen_child_setup(),
+   * used under the LGPLv2+ with permission from author. (The code has
+   * since migrated to vte:src/spawn.cc:SpawnContext::exec and is no longer
+   * terribly similar to what we have here.)
+   */
+
+  if (n_fds > 0)
+    {
+      for (i = 0; i < n_fds; i++)
+        max_target_fd = MAX (max_target_fd, target_fds[i]);
+
+      if (max_target_fd == G_MAXINT)
+        {
+          errno = EINVAL;
+          write_err_and_exit (child_err_report_fd, CHILD_DUPFD_FAILED);
+        }
+
+      /* 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 all source fds, taking care to ensure the new
+       * fds are larger than any target fd to avoid introducing new conflicts.
+       */
+      for (i = 0; i < n_fds; i++)
+        {
+          if (source_fds[i] != target_fds[i])
+            {
+              source_fds[i] = dupfd_cloexec (source_fds[i], max_target_fd + 1);
+              if (source_fds[i] < 0)
+                write_err_and_exit (child_err_report_fd, CHILD_DUPFD_FAILED);
+            }
+        }
+
+      for (i = 0; i < n_fds; i++)
+        {
+          /* For basic fd assignments (where source == target), we can just
+           * unset FD_CLOEXEC.
+           */
+          if (source_fds[i] == target_fds[i])
+            {
+              unset_cloexec (source_fds[i]);
+            }
+          else
+            {
+              /* If any of the @target_fds conflict with @child_err_report_fd,
+               * dup it so it doesn’t get conflated.
+               */
+              if (target_fds[i] == child_err_report_fd)
+                {
+                  child_err_report_fd = dupfd_cloexec (child_err_report_fd, max_target_fd + 1);
+                  if (child_err_report_fd < 0)
+                    write_err_and_exit (child_err_report_fd, CHILD_DUPFD_FAILED);
+                }
+
+              if (safe_dup2 (source_fds[i], target_fds[i]) < 0)
+                write_err_and_exit (child_err_report_fd, CHILD_DUPFD_FAILED);
+
+              g_clear_fd (&source_fds[i], NULL);
+            }
+        }
+    }
+
   /* Call user function just before we exec */
   if (child_setup)
     {
@@ -1095,8 +1577,9 @@ do_exec (gint                  child_err_report_fd,
     }
 
   g_execute (argv[0],
-             file_and_argv_zero ? argv + 1 : argv,
-             envp, search_path);
+             (gchar **) (file_and_argv_zero ? argv + 1 : argv),
+             argv_buffer, argv_buffer_len,
+             (gchar **) envp, search_path, search_path_buffer, search_path_buffer_len);
 
   /* Exec failed */
   write_err_and_exit (child_err_report_fd,
@@ -1152,48 +1635,486 @@ read_ints (int      fd,
   return TRUE;
 }
 
+#ifdef POSIX_SPAWN_AVAILABLE
 static gboolean
-fork_exec_with_pipes (gboolean              intermediate_child,
-                      const gchar          *working_directory,
-                      gchar               **argv,
-                      gchar               **envp,
-                      gboolean              close_descriptors,
-                      gboolean              search_path,
-                      gboolean              stdout_to_null,
-                      gboolean              stderr_to_null,
-                      gboolean              child_inherits_stdin,
-                      gboolean              file_and_argv_zero,
-                      GSpawnChildSetupFunc  child_setup,
-                      gpointer              user_data,
-                      GPid                 *child_pid,
-                      gint                 *standard_input,
-                      gint                 *standard_output,
-                      gint                 *standard_error,
-                      GError              **error)     
+do_posix_spawn (const gchar * const *argv,
+                const gchar * const *envp,
+                gboolean    search_path,
+                gboolean    stdout_to_null,
+                gboolean    stderr_to_null,
+                gboolean    child_inherits_stdin,
+                gboolean    file_and_argv_zero,
+                GPid       *child_pid,
+                gint       *child_close_fds,
+                gint        stdin_fd,
+                gint        stdout_fd,
+                gint        stderr_fd,
+                const gint *source_fds,
+                const gint *target_fds,
+                gsize       n_fds)
+{
+  pid_t pid;
+  gint *duped_source_fds = NULL;
+  gint max_target_fd = 0;
+  const gchar * const *argv_pass;
+  posix_spawnattr_t attr;
+  posix_spawn_file_actions_t file_actions;
+  gint parent_close_fds[3];
+  gsize num_parent_close_fds = 0;
+  GSList *child_close = NULL;
+  GSList *elem;
+  sigset_t mask;
+  gsize i;
+  int r;
+
+  g_assert (argv != NULL && argv[0] != NULL);
+
+  if (*argv[0] == '\0')
+    {
+      /* We check the simple case first. */
+      return ENOENT;
+    }
+
+  r = posix_spawnattr_init (&attr);
+  if (r != 0)
+    return r;
+
+  if (child_close_fds)
+    {
+      int i = -1;
+      while (child_close_fds[++i] != -1)
+        child_close = g_slist_prepend (child_close,
+                                       GINT_TO_POINTER (child_close_fds[i]));
+    }
+
+  r = posix_spawnattr_setflags (&attr, POSIX_SPAWN_SETSIGDEF);
+  if (r != 0)
+    goto out_free_spawnattr;
+
+  /* Reset some signal handlers that we may use */
+  sigemptyset (&mask);
+  sigaddset (&mask, SIGCHLD);
+  sigaddset (&mask, SIGINT);
+  sigaddset (&mask, SIGTERM);
+  sigaddset (&mask, SIGHUP);
+
+  r = posix_spawnattr_setsigdefault (&attr, &mask);
+  if (r != 0)
+    goto out_free_spawnattr;
+
+  r = posix_spawn_file_actions_init (&file_actions);
+  if (r != 0)
+    goto out_free_spawnattr;
+
+  /* Redirect pipes as required */
+
+  if (stdin_fd >= 0)
+    {
+      r = posix_spawn_file_actions_adddup2 (&file_actions, stdin_fd, 0);
+      if (r != 0)
+        goto out_close_fds;
+
+      if (!g_slist_find (child_close, GINT_TO_POINTER (stdin_fd)))
+        child_close = g_slist_prepend (child_close, GINT_TO_POINTER (stdin_fd));
+    }
+  else if (!child_inherits_stdin)
+    {
+      /* Keep process from blocking on a read of stdin */
+      gint read_null = safe_open ("/dev/null", O_RDONLY | O_CLOEXEC);
+      g_assert (read_null != -1);
+      parent_close_fds[num_parent_close_fds++] = read_null;
+
+#ifndef HAVE_O_CLOEXEC
+      fcntl (read_null, F_SETFD, FD_CLOEXEC);
+#endif
+
+      r = posix_spawn_file_actions_adddup2 (&file_actions, read_null, 0);
+      if (r != 0)
+        goto out_close_fds;
+    }
+
+  if (stdout_fd >= 0)
+    {
+      r = posix_spawn_file_actions_adddup2 (&file_actions, stdout_fd, 1);
+      if (r != 0)
+        goto out_close_fds;
+
+      if (!g_slist_find (child_close, GINT_TO_POINTER (stdout_fd)))
+        child_close = g_slist_prepend (child_close, GINT_TO_POINTER (stdout_fd));
+    }
+  else if (stdout_to_null)
+    {
+      gint write_null = safe_open ("/dev/null", O_WRONLY | O_CLOEXEC);
+      g_assert (write_null != -1);
+      parent_close_fds[num_parent_close_fds++] = write_null;
+
+#ifndef HAVE_O_CLOEXEC
+      fcntl (write_null, F_SETFD, FD_CLOEXEC);
+#endif
+
+      r = posix_spawn_file_actions_adddup2 (&file_actions, write_null, 1);
+      if (r != 0)
+        goto out_close_fds;
+    }
+
+  if (stderr_fd >= 0)
+    {
+      r = posix_spawn_file_actions_adddup2 (&file_actions, stderr_fd, 2);
+      if (r != 0)
+        goto out_close_fds;
+
+      if (!g_slist_find (child_close, GINT_TO_POINTER (stderr_fd)))
+        child_close = g_slist_prepend (child_close, GINT_TO_POINTER (stderr_fd));
+    }
+  else if (stderr_to_null)
+    {
+      gint write_null = safe_open ("/dev/null", O_WRONLY | O_CLOEXEC);
+      g_assert (write_null != -1);
+      parent_close_fds[num_parent_close_fds++] = write_null;
+
+#ifndef HAVE_O_CLOEXEC
+      fcntl (write_null, F_SETFD, FD_CLOEXEC);
+#endif
+
+      r = posix_spawn_file_actions_adddup2 (&file_actions, write_null, 2);
+      if (r != 0)
+        goto out_close_fds;
+    }
+
+  /* If source_fds[i] != target_fds[i], 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, taking care to ensure the new fds are
+   * larger than any target fd to avoid introducing new conflicts.
+   *
+   * If source_fds[i] == target_fds[i], then we just need to leak
+   * the fd into the child process, which we *could* do by temporarily
+   * unsetting CLOEXEC and then setting it again after we spawn if
+   * it was originally set. POSIX requires that the addup2 action unset
+   * CLOEXEC if source and target are identical, so you'd think doing it
+   * manually wouldn't be needed, but unfortunately as of 2021 many
+   * libcs still don't do so. Example nonconforming libcs:
+   *  Bionic: https://android.googlesource.com/platform/bionic/+/f6e5b582604715729b09db3e36a7aeb8c24b36a4/libc/bionic/spawn.cpp#71
+   *  uclibc-ng: https://cgit.uclibc-ng.org/cgi/cgit/uclibc-ng.git/tree/librt/spawn.c?id=7c36bcae09d66bbaa35cbb02253ae0556f42677e#n88
+   *
+   * Anyway, unsetting CLOEXEC ourselves would open a small race window
+   * where the fd could be inherited into a child process if another
+   * thread spawns something at the same time, because we have not
+   * called fork() and are multithreaded here. This race is avoidable by
+   * using dupfd_cloexec, which we already have to do to handle the
+   * source_fds[i] != target_fds[i] case. So let's always do it!
+   */
+
+  for (i = 0; i < n_fds; i++)
+    max_target_fd = MAX (max_target_fd, target_fds[i]);
+
+  if (max_target_fd == G_MAXINT)
+    goto out_close_fds;
+
+  duped_source_fds = g_new (gint, n_fds);
+  for (i = 0; i < n_fds; i++)
+    duped_source_fds[i] = -1;  /* initialise in case dupfd_cloexec() fails below */
+  for (i = 0; i < n_fds; i++)
+    {
+      duped_source_fds[i] = dupfd_cloexec (source_fds[i], max_target_fd + 1);
+      if (duped_source_fds[i] < 0)
+        goto out_close_fds;
+    }
+
+  for (i = 0; i < n_fds; i++)
+    {
+      r = posix_spawn_file_actions_adddup2 (&file_actions, duped_source_fds[i], target_fds[i]);
+      if (r != 0)
+        goto out_close_fds;
+    }
+
+  /* Intentionally close the fds in the child as the last file action,
+   * having been careful not to add the same fd to this list twice.
+   *
+   * This is important to allow (e.g.) for the same fd to be passed as stdout
+   * and stderr (we must not close it before we have dupped it in both places,
+   * and we must not attempt to close it twice).
+   */
+  for (elem = child_close; elem != NULL; elem = elem->next)
+    {
+      r = posix_spawn_file_actions_addclose (&file_actions,
+                                             GPOINTER_TO_INT (elem->data));
+      if (r != 0)
+        goto out_close_fds;
+    }
+
+  argv_pass = file_and_argv_zero ? argv + 1 : argv;
+  if (envp == NULL)
+    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, (char * const *) argv_pass, (char * const *) envp);
+  else
+    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;
+
+out_close_fds:
+  for (i = 0; i < num_parent_close_fds; i++)
+    g_clear_fd (&parent_close_fds[i], NULL);
+
+  if (duped_source_fds != NULL)
+    {
+      for (i = 0; i < n_fds; i++)
+        g_clear_fd (&duped_source_fds[i], NULL);
+      g_free (duped_source_fds);
+    }
+
+  posix_spawn_file_actions_destroy (&file_actions);
+out_free_spawnattr:
+  posix_spawnattr_destroy (&attr);
+  g_slist_free (child_close);
+
+  return r;
+}
+#endif /* POSIX_SPAWN_AVAILABLE */
+
+static gboolean
+source_fds_collide_with_pipe (const GUnixPipe  *pipefd,
+                              const int        *source_fds,
+                              gsize             n_fds,
+                              GError          **error)
+{
+  return (_g_spawn_invalid_source_fd (pipefd->fds[G_UNIX_PIPE_END_READ], source_fds, n_fds, error) ||
+          _g_spawn_invalid_source_fd (pipefd->fds[G_UNIX_PIPE_END_WRITE], source_fds, n_fds, 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)
 {
   GPid pid = -1;
-  gint stdin_pipe[2] = { -1, -1 };
-  gint stdout_pipe[2] = { -1, -1 };
-  gint stderr_pipe[2] = { -1, -1 };
-  gint child_err_report_pipe[2] = { -1, -1 };
-  gint child_pid_report_pipe[2] = { -1, -1 };
+  GUnixPipe child_err_report_pipe = G_UNIX_PIPE_INIT;
+  GUnixPipe child_pid_report_pipe = G_UNIX_PIPE_INIT;
+  guint pipe_flags = cloexec_pipes ? O_CLOEXEC : 0;
   gint status;
-  
-  if (!make_pipe (child_err_report_pipe, error))
-    return FALSE;
+  const gchar *chosen_search_path;
+  gchar *search_path_buffer = NULL;
+  gchar *search_path_buffer_heap = NULL;
+  gsize search_path_buffer_len = 0;
+  gchar **argv_buffer = NULL;
+  gchar **argv_buffer_heap = NULL;
+  gsize argv_buffer_len = 0;
+  GUnixPipe stdin_pipe = G_UNIX_PIPE_INIT;
+  GUnixPipe stdout_pipe = G_UNIX_PIPE_INIT;
+  GUnixPipe stderr_pipe = G_UNIX_PIPE_INIT;
+  gint child_close_fds[4] = { -1, -1, -1, -1 };
+  gint n_child_close_fds = 0;
+  gint *source_fds_copy = NULL;
+
+  g_assert (argv != NULL && argv[0] != 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_pipe_open (&stdin_pipe, pipe_flags, error))
+        goto cleanup_and_fail;
+      if (source_fds_collide_with_pipe (&stdin_pipe, source_fds, n_fds, error))
+        goto cleanup_and_fail;
+      child_close_fds[n_child_close_fds++] = g_unix_pipe_get (&stdin_pipe, G_UNIX_PIPE_END_WRITE);
+      stdin_fd = g_unix_pipe_get (&stdin_pipe, G_UNIX_PIPE_END_READ);
+    }
 
-  if (intermediate_child && !make_pipe (child_pid_report_pipe, error))
-    goto cleanup_and_fail;
-  
-  if (standard_input && !make_pipe (stdin_pipe, error))
-    goto cleanup_and_fail;
-  
-  if (standard_output && !make_pipe (stdout_pipe, error))
-    goto cleanup_and_fail;
+  if (stdout_pipe_out != NULL)
+    {
+      if (!g_unix_pipe_open (&stdout_pipe, pipe_flags, error))
+        goto cleanup_and_fail;
+      if (source_fds_collide_with_pipe (&stdout_pipe, source_fds, n_fds, error))
+        goto cleanup_and_fail;
+      child_close_fds[n_child_close_fds++] = g_unix_pipe_get (&stdout_pipe, G_UNIX_PIPE_END_READ);
+      stdout_fd = g_unix_pipe_get (&stdout_pipe, G_UNIX_PIPE_END_WRITE);
+    }
+
+  if (stderr_pipe_out != NULL)
+    {
+      if (!g_unix_pipe_open (&stderr_pipe, pipe_flags, error))
+        goto cleanup_and_fail;
+      if (source_fds_collide_with_pipe (&stderr_pipe, source_fds, n_fds, error))
+        goto cleanup_and_fail;
+      child_close_fds[n_child_close_fds++] = g_unix_pipe_get (&stderr_pipe, G_UNIX_PIPE_END_READ);
+      stderr_fd = g_unix_pipe_get (&stderr_pipe, G_UNIX_PIPE_END_WRITE);
+    }
+
+  child_close_fds[n_child_close_fds++] = -1;
+
+#ifdef POSIX_SPAWN_AVAILABLE
+  if (!intermediate_child && working_directory == NULL && !close_descriptors &&
+      !search_path_from_envp && child_setup == NULL)
+    {
+      g_trace_mark (G_TRACE_CURRENT_TIME, 0,
+                    "GLib", "posix_spawn",
+                    "%s", argv[0]);
+
+      status = do_posix_spawn (argv,
+                               envp,
+                               search_path,
+                               stdout_to_null,
+                               stderr_to_null,
+                               child_inherits_stdin,
+                               file_and_argv_zero,
+                               child_pid,
+                               child_close_fds,
+                               stdin_fd,
+                               stdout_fd,
+                               stderr_fd,
+                               source_fds,
+                               target_fds,
+                               n_fds);
+      if (status == 0)
+        goto success;
+
+      if (status != ENOEXEC)
+        {
+          g_set_error (error,
+                       G_SPAWN_ERROR,
+                       G_SPAWN_ERROR_FAILED,
+                       _("Failed to spawn child process “%s” (%s)"),
+                       argv[0],
+                       g_strerror (status));
+          goto cleanup_and_fail;
+       }
+
+      /* posix_spawn is not intended to support script execution. It does in
+       * some situations on some glibc versions, but that will be fixed.
+       * So if it fails with ENOEXEC, we fall through to the regular
+       * gspawn codepath so that script execution can be attempted,
+       * per standard gspawn behaviour. */
+      g_debug ("posix_spawn failed (ENOEXEC), fall back to regular gspawn");
+    }
+  else
+    {
+      g_trace_mark (G_TRACE_CURRENT_TIME, 0,
+                    "GLib", "fork",
+                    "posix_spawn avoided %s%s%s%s%s",
+                    !intermediate_child ? "" : "(automatic reaping requested) ",
+                    working_directory == NULL ? "" : "(workdir specified) ",
+                    !close_descriptors ? "" : "(fd close requested) ",
+                    !search_path_from_envp ? "" : "(using envp for search path) ",
+                    child_setup == NULL ? "" : "(child_setup specified) ");
+    }
+#endif /* POSIX_SPAWN_AVAILABLE */
+
+  /* Choose a search path. This has to be done before calling fork()
+   * 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 ((gchar **) envp, "PATH");
+  if (search_path && chosen_search_path == NULL)
+    chosen_search_path = g_getenv ("PATH");
+
+  if ((search_path || search_path_from_envp) && chosen_search_path == NULL)
+    {
+      /* There is no 'PATH' in the environment.  The default
+       * * search path in libc is the current directory followed by
+       * * the path 'confstr' returns for '_CS_PATH'.
+       * */
+
+      /* In GLib we put . last, for security, and don't use the
+       * * unportable confstr(); UNIX98 does not actually specify
+       * * what to search if PATH is unset. POSIX may, dunno.
+       * */
+
+      chosen_search_path = "/bin:/usr/bin:.";
+    }
 
-  if (standard_error && !make_pipe (stderr_pipe, error))
+  if (search_path || search_path_from_envp)
+    g_assert (chosen_search_path != NULL);
+  else
+    g_assert (chosen_search_path == NULL);
+
+  /* Allocate a buffer which the fork()ed child can use to assemble potential
+   * paths for the binary to exec(), combining the argv[0] and elements from
+   * the chosen_search_path. This can’t be done in the child because malloc()
+   * (or alloca()) are not async-signal-safe (see `man 7 signal-safety`).
+   *
+   * Add 2 for the nul terminator and a leading `/`. */
+  if (chosen_search_path != NULL)
+    {
+      search_path_buffer_len = strlen (chosen_search_path) + strlen (argv[0]) + 2;
+      if (search_path_buffer_len < 4000)
+        {
+          /* Prefer small stack allocations to avoid valgrind leak warnings
+           * in forked child. The 4000B cutoff is arbitrary. */
+          search_path_buffer = g_alloca (search_path_buffer_len);
+        }
+      else
+        {
+          search_path_buffer_heap = g_malloc (search_path_buffer_len);
+          search_path_buffer = search_path_buffer_heap;
+        }
+    }
+
+  if (search_path || search_path_from_envp)
+    g_assert (search_path_buffer != NULL);
+  else
+    g_assert (search_path_buffer == NULL);
+
+  /* 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 ((gchar **) argv) + 2;
+  if (argv_buffer_len < 4000 / sizeof (gchar *))
+    {
+      /* Prefer small stack allocations to avoid valgrind leak warnings
+       * in forked child. The 4000B cutoff is arbitrary. */
+      argv_buffer = g_newa (gchar *, argv_buffer_len);
+    }
+  else
+    {
+      argv_buffer_heap = g_new (gchar *, argv_buffer_len);
+      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_pipe_open (&child_err_report_pipe, pipe_flags, error))
+    goto cleanup_and_fail;
+  if (source_fds_collide_with_pipe (&child_err_report_pipe, source_fds, n_fds, error))
     goto cleanup_and_fail;
 
+  if (intermediate_child)
+    {
+      if (!g_unix_pipe_open (&child_pid_report_pipe, pipe_flags, error))
+        goto cleanup_and_fail;
+      if (source_fds_collide_with_pipe (&child_pid_report_pipe, source_fds, n_fds, error))
+        goto cleanup_and_fail;
+    }
+  
   pid = fork ();
 
   if (pid < 0)
@@ -1213,6 +2134,12 @@ fork_exec_with_pipes (gboolean              intermediate_child,
       /* Immediate child. This may or may not be the child that
        * actually execs the new process.
        */
+
+      /* Reset some signal handlers that we may use */
+      signal (SIGCHLD, SIG_DFL);
+      signal (SIGINT, SIG_DFL);
+      signal (SIGTERM, SIG_DFL);
+      signal (SIGHUP, SIG_DFL);
       
       /* Be sure we crash if the parent exits
        * and we write to the err_report_pipe
@@ -1223,11 +2150,14 @@ fork_exec_with_pipes (gboolean              intermediate_child,
        * not needed in the close_descriptors case,
        * though
        */
-      close_and_invalidate (&child_err_report_pipe[0]);
-      close_and_invalidate (&child_pid_report_pipe[0]);
-      close_and_invalidate (&stdin_pipe[1]);
-      close_and_invalidate (&stdout_pipe[0]);
-      close_and_invalidate (&stderr_pipe[0]);
+      g_unix_pipe_close (&child_err_report_pipe, G_UNIX_PIPE_END_READ, NULL);
+      g_unix_pipe_close (&child_pid_report_pipe, G_UNIX_PIPE_END_READ, NULL);
+      if (child_close_fds[0] != -1)
+        {
+           int i = -1;
+           while (child_close_fds[++i] != -1)
+             g_clear_fd (&child_close_fds[i], NULL);
+        }
       
       if (intermediate_child)
         {
@@ -1243,23 +2173,31 @@ fork_exec_with_pipes (gboolean              intermediate_child,
           if (grandchild_pid < 0)
             {
               /* report -1 as child PID */
-              write_all (child_pid_report_pipe[1], &grandchild_pid,
-                         sizeof(grandchild_pid));
+              write_all (g_unix_pipe_get (&child_pid_report_pipe, G_UNIX_PIPE_END_WRITE),
+                         &grandchild_pid, sizeof(grandchild_pid));
               
-              write_err_and_exit (child_err_report_pipe[1],
+              write_err_and_exit (g_unix_pipe_get (&child_err_report_pipe, G_UNIX_PIPE_END_WRITE),
                                   CHILD_FORK_FAILED);              
             }
           else if (grandchild_pid == 0)
             {
-              do_exec (child_err_report_pipe[1],
-                       stdin_pipe[0],
-                       stdout_pipe[1],
-                       stderr_pipe[1],
+              g_unix_pipe_close (&child_pid_report_pipe, G_UNIX_PIPE_END_WRITE, NULL);
+              do_exec (g_unix_pipe_get (&child_err_report_pipe, G_UNIX_PIPE_END_WRITE),
+                       stdin_fd,
+                       stdout_fd,
+                       stderr_fd,
+                       source_fds_copy,
+                       target_fds,
+                       n_fds,
                        working_directory,
                        argv,
+                       argv_buffer,
+                       argv_buffer_len,
                        envp,
                        close_descriptors,
-                       search_path,
+                       chosen_search_path,
+                       search_path_buffer,
+                       search_path_buffer_len,
                        stdout_to_null,
                        stderr_to_null,
                        child_inherits_stdin,
@@ -1269,8 +2207,9 @@ fork_exec_with_pipes (gboolean              intermediate_child,
             }
           else
             {
-              write_all (child_pid_report_pipe[1], &grandchild_pid, sizeof(grandchild_pid));
-              close_and_invalidate (&child_pid_report_pipe[1]);
+              write_all (g_unix_pipe_get (&child_pid_report_pipe, G_UNIX_PIPE_END_WRITE),
+                         &grandchild_pid, sizeof(grandchild_pid));
+              g_unix_pipe_close (&child_pid_report_pipe, G_UNIX_PIPE_END_WRITE, NULL);
               
               _exit (0);
             }
@@ -1280,15 +2219,22 @@ fork_exec_with_pipes (gboolean              intermediate_child,
           /* Just run the child.
            */
 
-          do_exec (child_err_report_pipe[1],
-                   stdin_pipe[0],
-                   stdout_pipe[1],
-                   stderr_pipe[1],
+          do_exec (g_unix_pipe_get (&child_err_report_pipe, G_UNIX_PIPE_END_WRITE),
+                   stdin_fd,
+                   stdout_fd,
+                   stderr_fd,
+                   source_fds_copy,
+                   target_fds,
+                   n_fds,
                    working_directory,
                    argv,
+                   argv_buffer,
+                   argv_buffer_len,
                    envp,
                    close_descriptors,
-                   search_path,
+                   chosen_search_path,
+                   search_path_buffer,
+                   search_path_buffer_len,
                    stdout_to_null,
                    stderr_to_null,
                    child_inherits_stdin,
@@ -1305,11 +2251,8 @@ fork_exec_with_pipes (gboolean              intermediate_child,
       gint n_ints = 0;    
 
       /* Close the uncared-about ends of the pipes */
-      close_and_invalidate (&child_err_report_pipe[1]);
-      close_and_invalidate (&child_pid_report_pipe[1]);
-      close_and_invalidate (&stdin_pipe[0]);
-      close_and_invalidate (&stdout_pipe[1]);
-      close_and_invalidate (&stderr_pipe[1]);
+      g_unix_pipe_close (&child_err_report_pipe, G_UNIX_PIPE_END_WRITE, NULL);
+      g_unix_pipe_close (&child_pid_report_pipe, G_UNIX_PIPE_END_WRITE, NULL);
 
       /* If we had an intermediate child, reap it */
       if (intermediate_child)
@@ -1322,13 +2265,12 @@ fork_exec_with_pipes (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'");
             }
         }
       
 
-      if (!read_ints (child_err_report_pipe[0],
+      if (!read_ints (g_unix_pipe_get (&child_err_report_pipe, G_UNIX_PIPE_END_READ),
                       buf, 2, &n_ints,
                       error))
         goto cleanup_and_fail;
@@ -1343,7 +2285,7 @@ fork_exec_with_pipes (gboolean              intermediate_child,
               g_set_error (error,
                            G_SPAWN_ERROR,
                            G_SPAWN_ERROR_CHDIR,
-                           _("Failed to change to directory '%s' (%s)"),
+                           _("Failed to change to directory “%s” (%s)"),
                            working_directory,
                            g_strerror (buf[1]));
 
@@ -1352,18 +2294,26 @@ fork_exec_with_pipes (gboolean              intermediate_child,
             case CHILD_EXEC_FAILED:
               g_set_error (error,
                            G_SPAWN_ERROR,
-                           exec_err_to_g_error (buf[1]),
-                           _("Failed to execute child process \"%s\" (%s)"),
+                           _g_spawn_exec_err_to_g_error (buf[1]),
+                           _("Failed to execute child process “%s” (%s)"),
                            argv[0],
                            g_strerror (buf[1]));
 
               break;
-              
-            case CHILD_DUP2_FAILED:
+
+            case CHILD_OPEN_FAILED:
               g_set_error (error,
                            G_SPAWN_ERROR,
                            G_SPAWN_ERROR_FAILED,
-                           _("Failed to redirect output or input of child process (%s)"),
+                           _("Failed to open file to remap file descriptor (%s)"),
+                           g_strerror (buf[1]));
+              break;
+
+            case CHILD_DUPFD_FAILED:
+              g_set_error (error,
+                           G_SPAWN_ERROR,
+                           G_SPAWN_ERROR_FAILED,
+                           _("Failed to duplicate file descriptor for child process (%s)"),
                            g_strerror (buf[1]));
 
               break;
@@ -1375,12 +2325,20 @@ fork_exec_with_pipes (gboolean              intermediate_child,
                            _("Failed to fork child process (%s)"),
                            g_strerror (buf[1]));
               break;
-              
+
+            case CHILD_CLOSE_FAILED:
+              g_set_error (error,
+                           G_SPAWN_ERROR,
+                           G_SPAWN_ERROR_FAILED,
+                           _("Failed to close file descriptor for child process (%s)"),
+                           g_strerror (buf[1]));
+              break;
+
             default:
               g_set_error (error,
                            G_SPAWN_ERROR,
                            G_SPAWN_ERROR_FAILED,
-                           _("Unknown error executing child process \"%s\""),
+                           _("Unknown error executing child process “%s”"),
                            argv[0]);
               break;
             }
@@ -1393,7 +2351,7 @@ fork_exec_with_pipes (gboolean              intermediate_child,
         {
           n_ints = 0;
           
-          if (!read_ints (child_pid_report_pipe[0],
+          if (!read_ints (g_unix_pipe_get (&child_pid_report_pipe, G_UNIX_PIPE_END_READ),
                           buf, 1, &n_ints, error))
             goto cleanup_and_fail;
 
@@ -1416,22 +2374,36 @@ fork_exec_with_pipes (gboolean              intermediate_child,
         }
       
       /* Success against all odds! return the information */
-      close_and_invalidate (&child_err_report_pipe[0]);
-      close_and_invalidate (&child_pid_report_pipe[0]);
+      g_unix_pipe_close (&child_err_report_pipe, G_UNIX_PIPE_END_READ, NULL);
+      g_unix_pipe_close (&child_pid_report_pipe, G_UNIX_PIPE_END_READ, NULL);
+
+      g_free (search_path_buffer_heap);
+      g_free (argv_buffer_heap);
+      g_free (source_fds_copy);
+
       if (child_pid)
         *child_pid = pid;
 
-      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;
+      goto success;
     }
 
+success:
+  /* Close the uncared-about ends of the pipes */
+  g_unix_pipe_close (&stdin_pipe, G_UNIX_PIPE_END_READ, NULL);
+  g_unix_pipe_close (&stdout_pipe, G_UNIX_PIPE_END_WRITE, NULL);
+  g_unix_pipe_close (&stderr_pipe, G_UNIX_PIPE_END_WRITE, NULL);
+
+  if (stdin_pipe_out != NULL)
+    *stdin_pipe_out = g_unix_pipe_steal (&stdin_pipe, G_UNIX_PIPE_END_WRITE);
+
+  if (stdout_pipe_out != NULL)
+    *stdout_pipe_out = g_unix_pipe_steal (&stdout_pipe, G_UNIX_PIPE_END_READ);
+
+  if (stderr_pipe_out != NULL)
+    *stderr_pipe_out = g_unix_pipe_steal (&stderr_pipe, G_UNIX_PIPE_END_READ);
+
+  return TRUE;
+
  cleanup_and_fail:
 
   /* There was an error from the Child, reap the child to avoid it being
@@ -1448,80 +2420,62 @@ fork_exec_with_pipes (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]);
-  close_and_invalidate (&stdin_pipe[0]);
-  close_and_invalidate (&stdin_pipe[1]);
-  close_and_invalidate (&stdout_pipe[0]);
-  close_and_invalidate (&stdout_pipe[1]);
-  close_and_invalidate (&stderr_pipe[0]);
-  close_and_invalidate (&stderr_pipe[1]);
+  g_unix_pipe_clear (&stdin_pipe);
+  g_unix_pipe_clear (&stdout_pipe);
+  g_unix_pipe_clear (&stderr_pipe);
+  g_unix_pipe_clear (&child_err_report_pipe);
+  g_unix_pipe_clear (&child_pid_report_pipe);
 
-  return FALSE;
-}
+  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);
 
-static gboolean
-make_pipe (gint     p[2],
-           GError **error)
-{
-  if (pipe (p) < 0)
-    {
-      gint errsv = errno;
-      g_set_error (error,
-                   G_SPAWN_ERROR,
-                   G_SPAWN_ERROR_FAILED,
-                   _("Failed to create pipe for communicating with child process (%s)"),
-                   g_strerror (errsv));
-      return FALSE;
-    }
-  else
-    return TRUE;
+  return FALSE;
 }
 
 /* Based on execvp from GNU C Library */
 
-static void
+/* This function is called between fork() and exec() and hence must be
+ * async-signal-safe (see signal-safety(7)) until it calls exec(). */
+static gboolean
 script_execute (const gchar *file,
                 gchar      **argv,
-                gchar      **envp,
-                gboolean     search_path)
+                gchar      **argv_buffer,
+                gsize        argv_buffer_len,
+                gchar      **envp)
 {
   /* Count the arguments.  */
-  int argc = 0;
+  gsize argc = 0;
   while (argv[argc])
     ++argc;
-  
-  /* Construct an argument list for the shell.  */
-  {
-    gchar **new_argv;
-
-    new_argv = g_new0 (gchar*, argc + 2); /* /bin/sh and NULL */
-    
-    new_argv[0] = (char *) "/bin/sh";
-    new_argv[1] = (char *) file;
-    while (argc > 0)
-      {
-       new_argv[argc + 1] = argv[argc];
-       --argc;
-      }
-
-    /* Execute the shell. */
-    if (envp)
-      execve (new_argv[0], new_argv, envp);
-    else
-      execv (new_argv[0], new_argv);
-    
-    g_free (new_argv);
-  }
+
+  /* Construct an argument list for the shell. */
+  if (argc + 2 > argv_buffer_len)
+    return FALSE;
+
+  argv_buffer[0] = (char *) "/bin/sh";
+  argv_buffer[1] = (char *) file;
+  while (argc > 0)
+    {
+      argv_buffer[argc + 1] = argv[argc];
+      --argc;
+    }
+
+  /* Execute the shell. */
+  if (envp)
+    execve (argv_buffer[0], argv_buffer, envp);
+  else
+    execv (argv_buffer[0], argv_buffer);
+
+  return TRUE;
 }
 
+/* This function is called between fork() and exec() and hence must be
+ * async-signal-safe (see signal-safety(7)). */
 static gchar*
 my_strchrnul (const gchar *str, gchar c)
 {
@@ -1532,20 +2486,26 @@ my_strchrnul (const gchar *str, gchar c)
   return p;
 }
 
+/* This function is called between fork() and exec() and hence must be
+ * async-signal-safe (see signal-safety(7)) until it calls exec(). */
 static gint
-g_execute (const gchar *file,
-           gchar      **argv,
-           gchar      **envp,
-           gboolean     search_path)
+g_execute (const gchar  *file,
+           gchar       **argv,
+           gchar       **argv_buffer,
+           gsize         argv_buffer_len,
+           gchar       **envp,
+           const gchar  *search_path,
+           gchar        *search_path_buffer,
+           gsize         search_path_buffer_len)
 {
-  if (*file == '\0')
+  if (file == NULL || *file == '\0')
     {
       /* We check the simple case first. */
       errno = ENOENT;
       return -1;
     }
 
-  if (!search_path || strchr (file, '/') != NULL)
+  if (search_path == NULL || strchr (file, '/') != NULL)
     {
       /* Don't search when it contains a slash. */
       if (envp)
@@ -1553,37 +2513,32 @@ g_execute (const gchar *file,
       else
         execv (file, argv);
       
-      if (errno == ENOEXEC)
-       script_execute (file, argv, envp, FALSE);
+      if (errno == ENOEXEC &&
+          !script_execute (file, argv, argv_buffer, argv_buffer_len, envp))
+        {
+          errno = ENOMEM;
+          return -1;
+        }
     }
   else
     {
       gboolean got_eacces = 0;
       const gchar *path, *p;
-      gchar *name, *freeme;
+      gchar *name;
       gsize len;
       gsize pathlen;
 
-      path = g_getenv ("PATH");
-      if (path == NULL)
-       {
-         /* There is no `PATH' in the environment.  The default
-          * search path in libc is the current directory followed by
-          * the path `confstr' returns for `_CS_PATH'.
-           */
-
-          /* In GLib we put . last, for security, and don't use the
-           * unportable confstr(); UNIX98 does not actually specify
-           * what to search if PATH is unset. POSIX may, dunno.
-           */
-          
-          path = "/bin:/usr/bin:.";
-       }
-
+      path = search_path;
       len = strlen (file) + 1;
       pathlen = strlen (path);
-      freeme = name = g_malloc (pathlen + len + 1);
-      
+      name = search_path_buffer;
+
+      if (search_path_buffer_len < pathlen + len + 1)
+        {
+          errno = ENOMEM;
+          return -1;
+        }
+
       /* Copy the file name at the top, including '\0'  */
       memcpy (name + pathlen + 1, file, len);
       name = name + pathlen;
@@ -1600,7 +2555,7 @@ g_execute (const gchar *file,
 
          if (p == path)
            /* Two adjacent colons, or a colon at the beginning or the end
-             * of `PATH' means to search the current directory.
+             * of 'PATH' means to search the current directory.
              */
            startp = name + 1;
          else
@@ -1612,20 +2567,23 @@ g_execute (const gchar *file,
           else
             execv (startp, argv);
           
-         if (errno == ENOEXEC)
-           script_execute (startp, argv, envp, search_path);
+          if (errno == ENOEXEC &&
+              !script_execute (startp, argv, argv_buffer, argv_buffer_len, envp))
+            {
+              errno = ENOMEM;
+              return -1;
+            }
 
          switch (errno)
            {
            case EACCES:
-             /* Record the we got a `Permission denied' error.  If we end
+             /* Record the we got a 'Permission denied' error.  If we end
                * up finding no executable we can use, we want to diagnose
                * that we did find one but were denied access.
                */
              got_eacces = TRUE;
 
-              /* FALL THRU */
-              
+              G_GNUC_FALLTHROUGH;
            case ENOENT:
 #ifdef ESTALE
            case ESTALE:
@@ -1639,12 +2597,19 @@ g_execute (const gchar *file,
                */
              break;
 
+           case ENODEV:
+           case ETIMEDOUT:
+             /* Some strange filesystems like AFS return even
+              * stranger error numbers.  They cannot reasonably mean anything
+              * else so ignore those, too.
+              */
+             break;
+
            default:
              /* Some other error means we found an executable file, but
                * something went wrong executing it; return the error to our
                * caller.
                */
-              g_free (freeme);
              return -1;
            }
        }
@@ -1656,8 +2621,6 @@ g_execute (const gchar *file,
          * error.
          */
         errno = EACCES;
-
-      g_free (freeme);
     }
 
   /* Return the error from the last attempt (probably ENOENT).  */
@@ -1677,6 +2640,3 @@ void
 g_spawn_close_pid (GPid pid)
 {
 }
-
-#define __G_SPAWN_C__
-#include "galiasdef.c"