[glib: 1/7] gspawn: Add g_spawn_async_with_fds variant
- From: Philip Withnall <pwithnall src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib: 1/7] gspawn: Add g_spawn_async_with_fds variant
- Date: Thu, 21 Jun 2018 17:11:58 +0000 (UTC)
commit 3524de16e46350573536c610d776611d9834b554
Author: Daniel Drake <drake endlessm com>
Date: Mon May 28 10:09:21 2018 -0600
gspawn: Add g_spawn_async_with_fds variant
Add a new process spawning function variant which allows the caller
to pass specific file descriptors for stdin, stdout and stderr.
It is otherwise identical to g_spawn_async_with_pipes.
Allow the same fd to be passed in multiple parameters. To make this
workable, the child process logic that closes the fd after the first time
it has been dup2'ed needed tweaking; we now just set those fds to be
closed upon exec using the CLOEXEC flag. Add a test for this case.
This will be used by gnome-shell to avoid performing equivalent
dup2 actions in a child_setup function. Dropping use of child_setup will
enable use of an upcoming optimized process spawning codepath.
docs/reference/glib/glib-sections.txt | 1 +
glib/gspawn-win32.c | 163 +++++++++++++++-----
glib/gspawn.c | 279 +++++++++++++++++++++++++++-------
glib/gspawn.h | 13 ++
glib/tests/spawn-singlethread.c | 146 ++++++++++++++++++
5 files changed, 505 insertions(+), 97 deletions(-)
---
diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt
index 331d92c75..793f4b7f4 100644
--- a/docs/reference/glib/glib-sections.txt
+++ b/docs/reference/glib/glib-sections.txt
@@ -1232,6 +1232,7 @@ GSpawnError
G_SPAWN_ERROR
GSpawnFlags
GSpawnChildSetupFunc
+g_spawn_async_with_fds
g_spawn_async_with_pipes
g_spawn_async
g_spawn_sync
diff --git a/glib/gspawn-win32.c b/glib/gspawn-win32.c
index 0f5b8d034..b0cf5ab7a 100644
--- a/glib/gspawn-win32.c
+++ b/glib/gspawn-win32.c
@@ -520,19 +520,19 @@ do_spawn_directly (gint *exit_status,
}
static gboolean
-do_spawn_with_pipes (gint *exit_status,
- gboolean do_return_handle,
- const gchar *working_directory,
- gchar **argv,
- char **envp,
- GSpawnFlags flags,
- GSpawnChildSetupFunc child_setup,
- GPid *child_handle,
- gint *standard_input,
- gint *standard_output,
- gint *standard_error,
- gint *err_report,
- GError **error)
+do_spawn_with_fds (gint *exit_status,
+ gboolean do_return_handle,
+ const gchar *working_directory,
+ gchar **argv,
+ char **envp,
+ GSpawnFlags flags,
+ GSpawnChildSetupFunc child_setup,
+ GPid *child_handle,
+ gint stdin_fd,
+ gint stdout_fd,
+ gint stderr_fd,
+ gint *err_report,
+ GError **error)
{
char **protected_argv;
char args[ARG_COUNT][10];
@@ -541,9 +541,6 @@ do_spawn_with_pipes (gint *exit_status,
gintptr rc = -1;
int errsv;
int argc;
- int stdin_pipe[2] = { -1, -1 };
- int stdout_pipe[2] = { -1, -1 };
- int stderr_pipe[2] = { -1, -1 };
int child_err_report_pipe[2] = { -1, -1 };
int helper_sync_pipe[2] = { -1, -1 };
gintptr helper_report[2];
@@ -562,7 +559,7 @@ do_spawn_with_pipes (gint *exit_status,
argc = protect_argv (argv, &protected_argv);
- if (!standard_input && !standard_output && !standard_error &&
+ if (stdin_fd == -1 && stdout_fd == -1 && stderr_fd == -1 &&
(flags & G_SPAWN_CHILD_INHERITS_STDIN) &&
!(flags & G_SPAWN_STDOUT_TO_DEV_NULL) &&
!(flags & G_SPAWN_STDERR_TO_DEV_NULL) &&
@@ -578,15 +575,6 @@ do_spawn_with_pipes (gint *exit_status,
return retval;
}
- 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 (standard_error && !make_pipe (stderr_pipe, error))
- goto cleanup_and_fail;
-
if (!make_pipe (child_err_report_pipe, error))
goto cleanup_and_fail;
@@ -639,9 +627,9 @@ do_spawn_with_pipes (gint *exit_status,
*/
helper_sync_pipe[1] = dup_noninherited (helper_sync_pipe[1], _O_WRONLY);
- if (standard_input)
+ if (stdin_fd != -1)
{
- _g_sprintf (args[ARG_STDIN], "%d", stdin_pipe[0]);
+ _g_sprintf (args[ARG_STDIN], "%d", stdin_fd);
new_argv[ARG_STDIN] = args[ARG_STDIN];
}
else if (flags & G_SPAWN_CHILD_INHERITS_STDIN)
@@ -655,9 +643,9 @@ do_spawn_with_pipes (gint *exit_status,
new_argv[ARG_STDIN] = "z";
}
- if (standard_output)
+ if (stdout_fd != -1)
{
- _g_sprintf (args[ARG_STDOUT], "%d", stdout_pipe[1]);
+ _g_sprintf (args[ARG_STDOUT], "%d", stdout_fd);
new_argv[ARG_STDOUT] = args[ARG_STDOUT];
}
else if (flags & G_SPAWN_STDOUT_TO_DEV_NULL)
@@ -669,9 +657,9 @@ do_spawn_with_pipes (gint *exit_status,
new_argv[ARG_STDOUT] = "-";
}
- if (standard_error)
+ if (stdout_fd != -1)
{
- _g_sprintf (args[ARG_STDERR], "%d", stderr_pipe[1]);
+ _g_sprintf (args[ARG_STDERR], "%d", stderr_fd);
new_argv[ARG_STDERR] = args[ARG_STDERR];
}
else if (flags & G_SPAWN_STDERR_TO_DEV_NULL)
@@ -770,9 +758,6 @@ do_spawn_with_pipes (gint *exit_status,
*/
close_and_invalidate (&child_err_report_pipe[1]);
close_and_invalidate (&helper_sync_pipe[0]);
- close_and_invalidate (&stdin_pipe[0]);
- close_and_invalidate (&stdout_pipe[1]);
- close_and_invalidate (&stderr_pipe[1]);
g_strfreev (protected_argv);
@@ -842,12 +827,6 @@ do_spawn_with_pipes (gint *exit_status,
/* Success against all odds! return the information */
- if (standard_input)
- *standard_input = stdin_pipe[1];
- if (standard_output)
- *standard_output = stdout_pipe[0];
- if (standard_error)
- *standard_error = stderr_pipe[0];
if (rc != -1)
CloseHandle ((HANDLE) rc);
@@ -865,6 +844,71 @@ do_spawn_with_pipes (gint *exit_status,
close (helper_sync_pipe[0]);
if (helper_sync_pipe[1] != -1)
close (helper_sync_pipe[1]);
+
+ return FALSE;
+}
+
+static gboolean
+do_spawn_with_pipes (gint *exit_status,
+ gboolean do_return_handle,
+ const gchar *working_directory,
+ gchar **argv,
+ char **envp,
+ GSpawnFlags flags,
+ GSpawnChildSetupFunc child_setup,
+ GPid *child_handle,
+ gint *standard_input,
+ gint *standard_output,
+ gint *standard_error,
+ gint *err_report,
+ GError **error)
+{
+ int stdin_pipe[2] = { -1, -1 };
+ int stdout_pipe[2] = { -1, -1 };
+ int stderr_pipe[2] = { -1, -1 };
+
+ 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 (standard_error && !make_pipe (stderr_pipe, error))
+ goto cleanup_and_fail;
+
+ if (!do_spawn_with_fds (exit_status,
+ do_return_handle,
+ working_directory,
+ argv,
+ envp,
+ flags,
+ child_setup,
+ child_handle,
+ stdin_pipe[0],
+ stdout_pipe[1],
+ stderr_pipe[1],
+ err_report,
+ error))
+ goto cleanup_and_fail;
+
+ /* Close the other process's ends of the pipes in this process,
+ * otherwise the reader will never get EOF.
+ */
+ 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:
+
if (stdin_pipe[0] != -1)
close (stdin_pipe[0]);
if (stdin_pipe[1] != -1)
@@ -1160,6 +1204,43 @@ g_spawn_async_with_pipes (const gchar *working_directory,
error);
}
+gboolean
+g_spawn_async_with_fds (const gchar *working_directory,
+ gchar **argv,
+ gchar **envp,
+ GSpawnFlags flags,
+ GSpawnChildSetupFunc child_setup,
+ gpointer user_data,
+ GPid *child_handle,
+ gint stdin_fd,
+ gint stdout_fd,
+ gint stderr_fd,
+ GError **error)
+{
+ g_return_val_if_fail (argv != NULL, FALSE);
+ g_return_val_if_fail (stdin_fd == -1 ||
+ !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE);
+ g_return_val_if_fail (stderr_fd == -1 ||
+ !(flags & G_SPAWN_STDERR_TO_DEV_NULL), FALSE);
+ /* can't inherit stdin if we have an input pipe. */
+ g_return_val_if_fail (stdin_fd == -1 ||
+ !(flags & G_SPAWN_CHILD_INHERITS_STDIN), FALSE);
+
+ return do_spawn_with_fds (NULL,
+ (flags & G_SPAWN_DO_NOT_REAP_CHILD),
+ working_directory,
+ argv,
+ envp,
+ flags,
+ child_setup,
+ child_handle,
+ stdin_fd,
+ stdout_fd,
+ stderr_fd,
+ NULL,
+ error);
+}
+
gboolean
g_spawn_command_line_sync (const gchar *command_line,
gchar **standard_output,
diff --git a/glib/gspawn.c b/glib/gspawn.c
index 5e90d4c5b..bfcec5d78 100644
--- a/glib/gspawn.c
+++ b/glib/gspawn.c
@@ -142,6 +142,27 @@ static gboolean fork_exec_with_pipes (gboolean intermediate_child,
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);
+
G_DEFINE_QUARK (g-exec-error-quark, g_spawn_error)
G_DEFINE_QUARK (g-spawn-exit-error-quark, g_spawn_exit_error)
@@ -728,6 +749,87 @@ g_spawn_async_with_pipes (const gchar *working_directory,
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
+ * @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) (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
+ * @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).
+ *
+ * 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 (stdout_fd == -1 ||
+ !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE);
+ g_return_val_if_fail (stderr_fd == -1 ||
+ !(flags & G_SPAWN_STDERR_TO_DEV_NULL), FALSE);
+ /* can't inherit stdin if we have an input pipe. */
+ g_return_val_if_fail (stdin_fd == -1 ||
+ !(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);
+}
+
/**
* g_spawn_command_line_sync:
* @command_line: (type filename): a command line
@@ -1118,8 +1220,7 @@ do_exec (gint child_err_report_fd,
write_err_and_exit (child_err_report_fd,
CHILD_DUP2_FAILED);
- /* ignore this if it doesn't work */
- close_and_invalidate (&stdin_fd);
+ set_cloexec (GINT_TO_POINTER(0), stdin_fd);
}
else if (!child_inherits_stdin)
{
@@ -1138,8 +1239,7 @@ do_exec (gint child_err_report_fd,
write_err_and_exit (child_err_report_fd,
CHILD_DUP2_FAILED);
- /* ignore this if it doesn't work */
- close_and_invalidate (&stdout_fd);
+ set_cloexec (GINT_TO_POINTER(0), stdout_fd);
}
else if (stdout_to_null)
{
@@ -1157,8 +1257,7 @@ do_exec (gint child_err_report_fd,
write_err_and_exit (child_err_report_fd,
CHILD_DUP2_FAILED);
- /* ignore this if it doesn't work */
- close_and_invalidate (&stderr_fd);
+ set_cloexec (GINT_TO_POINTER(0), stderr_fd);
}
else if (stderr_to_null)
{
@@ -1232,30 +1331,28 @@ read_ints (int fd,
}
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)
+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)
{
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 };
guint pipe_flags = cloexec_pipes ? FD_CLOEXEC : 0;
@@ -1267,15 +1364,6 @@ fork_exec_with_pipes (gboolean intermediate_child,
if (intermediate_child && !g_unix_open_pipe (child_pid_report_pipe, pipe_flags, error))
goto cleanup_and_fail;
- 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;
-
pid = fork ();
if (pid < 0)
@@ -1313,9 +1401,12 @@ fork_exec_with_pipes (gboolean intermediate_child,
*/
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]);
+ if (child_close_fds != NULL)
+ {
+ int i = -1;
+ while (child_close_fds[++i] != -1)
+ close_and_invalidate (&child_close_fds[i]);
+ }
if (intermediate_child)
{
@@ -1341,9 +1432,9 @@ fork_exec_with_pipes (gboolean intermediate_child,
{
close_and_invalidate (&child_pid_report_pipe[1]);
do_exec (child_err_report_pipe[1],
- stdin_pipe[0],
- stdout_pipe[1],
- stderr_pipe[1],
+ stdin_fd,
+ stdout_fd,
+ stderr_fd,
working_directory,
argv,
envp,
@@ -1371,9 +1462,9 @@ fork_exec_with_pipes (gboolean intermediate_child,
*/
do_exec (child_err_report_pipe[1],
- stdin_pipe[0],
- stdout_pipe[1],
- stderr_pipe[1],
+ stdin_fd,
+ stdout_fd,
+ stderr_fd,
working_directory,
argv,
envp,
@@ -1398,9 +1489,6 @@ fork_exec_with_pipes (gboolean intermediate_child,
/* 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]);
/* If we had an intermediate child, reap it */
if (intermediate_child)
@@ -1513,13 +1601,6 @@ fork_exec_with_pipes (gboolean intermediate_child,
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;
}
@@ -1548,6 +1629,92 @@ fork_exec_with_pipes (gboolean intermediate_child,
close_and_invalidate (&child_err_report_pipe[1]);
close_and_invalidate (&child_pid_report_pipe[0]);
close_and_invalidate (&child_pid_report_pipe[1]);
+
+ 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]);
diff --git a/glib/gspawn.h b/glib/gspawn.h
index 055743ea2..d6b0be7d0 100644
--- a/glib/gspawn.h
+++ b/glib/gspawn.h
@@ -215,6 +215,19 @@ gboolean g_spawn_async_with_pipes (const gchar *working_directory,
gint *standard_error,
GError **error);
+/* Lets you provide fds for stdin/stdout/stderr */
+GLIB_AVAILABLE_IN_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);
/* If standard_output or standard_error are non-NULL, the full
* standard output or error of the command will be placed there.
diff --git a/glib/tests/spawn-singlethread.c b/glib/tests/spawn-singlethread.c
index 6a07df736..31c7e0760 100644
--- a/glib/tests/spawn-singlethread.c
+++ b/glib/tests/spawn-singlethread.c
@@ -25,8 +25,14 @@
#include <glib.h>
#include <string.h>
+#include <fcntl.h>
+
+#ifdef G_OS_UNIX
+#include <glib-unix.h>
+#endif
#ifdef G_OS_WIN32
+#include <io.h>
#define LINEEND "\r\n"
#else
#define LINEEND "\n"
@@ -156,6 +162,145 @@ test_spawn_async (void)
g_free (arg);
}
+/* Windows close() causes failure through the Invalid Parameter Handler
+ * Routine if the file descriptor does not exist.
+ */
+static void
+sane_close (int fd)
+{
+ if (fd >= 0)
+ close (fd);
+}
+
+/* Test g_spawn_async_with_fds() with a variety of different inputs */
+static void
+test_spawn_async_with_fds (void)
+{
+ int tnum = 1;
+ GPtrArray *argv;
+ char *arg;
+ int i;
+
+ /* Each test has 3 variable parameters: stdin, stdout, stderr */
+ enum fd_type {
+ NO_FD, /* don't pass a fd */
+ PIPE, /* pass fd of new/unique pipe */
+ STDOUT_PIPE, /* pass the same pipe as stdout */
+ } tests[][3] = {
+ { NO_FD, NO_FD, NO_FD }, /* Test with no fds passed */
+ { PIPE, PIPE, PIPE }, /* Test with unique fds passed */
+ { NO_FD, PIPE, STDOUT_PIPE }, /* Test the same fd for stdout + stderr */
+ };
+
+ arg = g_strdup_printf ("thread %d", tnum);
+
+ argv = g_ptr_array_new ();
+ g_ptr_array_add (argv, echo_prog_path);
+ g_ptr_array_add (argv, arg);
+ g_ptr_array_add (argv, NULL);
+
+ for (i = 0; i < G_N_ELEMENTS (tests); i++)
+ {
+ GError *error = NULL;
+ GPid pid;
+ GMainContext *context;
+ GMainLoop *loop;
+ GIOChannel *channel = NULL;
+ GSource *source;
+ SpawnAsyncMultithreadedData data;
+ enum fd_type *fd_info = tests[i];
+ gint test_pipe[3][2];
+ int j;
+
+ for (j = 0; j < 3; j++)
+ {
+ switch (fd_info[j])
+ {
+ case NO_FD:
+ test_pipe[j][0] = -1;
+ test_pipe[j][1] = -1;
+ break;
+ case PIPE:
+#ifdef G_OS_UNIX
+ g_unix_open_pipe (test_pipe[j], FD_CLOEXEC, &error);
+ g_assert_no_error (error);
+#else
+ g_assert_cmpint (_pipe (test_pipe[j], 4096, _O_BINARY), >=, 0);
+#endif
+ break;
+ case STDOUT_PIPE:
+ g_assert_cmpint (j, ==, 2); /* only works for stderr */
+ test_pipe[j][0] = test_pipe[1][0];
+ test_pipe[j][1] = test_pipe[1][1];
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+ }
+
+ context = g_main_context_new ();
+ loop = g_main_loop_new (context, TRUE);
+
+ g_spawn_async_with_fds (NULL, (char**)argv->pdata, NULL,
+ G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid,
+ test_pipe[0][0], test_pipe[1][1], test_pipe[2][1],
+ &error);
+ g_assert_no_error (error);
+ sane_close (test_pipe[0][0]);
+ sane_close (test_pipe[1][1]);
+ if (fd_info[2] != STDOUT_PIPE)
+ sane_close (test_pipe[2][1]);
+
+ data.loop = loop;
+ data.stdout_done = FALSE;
+ data.child_exited = FALSE;
+ data.stdout_buf = g_string_new (0);
+
+ source = g_child_watch_source_new (pid);
+ g_source_set_callback (source, (GSourceFunc)on_child_exited, &data, NULL);
+ g_source_attach (source, context);
+ g_source_unref (source);
+
+ if (test_pipe[1][0] != -1)
+ {
+ channel = g_io_channel_unix_new (test_pipe[1][0]);
+ source = g_io_create_watch (channel, G_IO_IN | G_IO_HUP | G_IO_ERR);
+ g_source_set_callback (source, (GSourceFunc)on_child_stdout,
+ &data, NULL);
+ g_source_attach (source, context);
+ g_source_unref (source);
+ }
+ else
+ {
+ /* Don't check stdout data if we didn't pass a fd */
+ data.stdout_done = TRUE;
+ }
+
+ g_main_loop_run (loop);
+
+ g_assert_true (data.child_exited);
+
+ if (test_pipe[1][0] != -1)
+ {
+ /* Check for echo on stdout */
+ g_assert_true (data.stdout_done);
+ g_assert_cmpstr (data.stdout_buf->str, ==, arg);
+ g_io_channel_unref (channel);
+ }
+ g_string_free (data.stdout_buf, TRUE);
+
+ g_main_context_unref (context);
+ g_main_loop_unref (loop);
+ sane_close (test_pipe[0][1]);
+ sane_close (test_pipe[1][0]);
+ if (fd_info[2] != STDOUT_PIPE)
+ sane_close (test_pipe[2][0]);
+ }
+
+ g_ptr_array_free (argv, TRUE);
+ g_free (arg);
+}
+
static void
test_spawn_sync (void)
{
@@ -251,6 +396,7 @@ main (int argc,
g_test_add_func ("/gthread/spawn-single-sync", test_spawn_sync);
g_test_add_func ("/gthread/spawn-single-async", test_spawn_async);
+ g_test_add_func ("/gthread/spawn-single-async-with-fds", test_spawn_async_with_fds);
g_test_add_func ("/gthread/spawn-script", test_spawn_script);
g_test_add_func ("/gthread/spawn/nonexistent", test_spawn_nonexistent);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]