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