* 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 */
+#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 "gtestutils.h"
#include "gutils.h"
#include "glibintl.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.
* 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,
GPid *child_pid,
GError **error)
{
- g_return_val_if_fail (argv != NULL, FALSE);
-
return g_spawn_async_with_pipes (working_directory,
argv, envp,
flags,
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
gint fd,
GError **error)
{
- gssize bytes;
- gchar buf[4096];
+ gssize bytes;
+ gchar buf[4096];
again:
-
bytes = read (fd, buf, 4096);
if (bytes == 0)
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;
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,
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;
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);
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. */
(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))
{
failed = TRUE;
break;
case READ_EOF:
- close_and_invalidate (&outpipe);
- outpipe = -1;
+ g_clear_fd (&outpipe, NULL);
break;
default:
break;
break;
}
- if (errpipe >= 0 && FD_ISSET (errpipe, &fds))
+ if (errpipe >= 0 && fds[1].revents != 0)
{
switch (read_data (errstr, errpipe, error))
{
failed = TRUE;
break;
case READ_EOF:
- close_and_invalidate (&errpipe);
- errpipe = -1;
+ g_clear_fd (&errpipe, NULL);
break;
default:
break;
}
/* 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.
*/
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
}
else
{
- if (exit_status)
- *exit_status = status;
+ if (wait_status)
+ *wait_status = status;
if (standard_output)
*standard_output = g_string_free (outstr, FALSE);
/**
* 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
* 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;
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))
NULL,
standard_output,
standard_error,
- exit_status,
+ wait_status,
error);
g_strfreev (argv);
/**
* 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
*
* 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,
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))
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)
{
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)
{
_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,
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)
{
}
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,
return TRUE;
}
+#ifdef POSIX_SPAWN_AVAILABLE
+static gboolean
+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
-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)
+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;
- if (standard_error && !make_pipe (stderr_pipe, error))
+#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 (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)
/* 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
* 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)
{
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,
}
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);
}
/* 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,
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)
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;
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]));
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;
_("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;
}
{
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;
}
/* 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
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)
{
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)
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;
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
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:
*/
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;
}
}
* error.
*/
errno = EACCES;
-
- g_free (freeme);
}
/* Return the error from the last attempt (probably ENOENT). */