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.
This commit is contained in:
Daniel Drake 2018-05-28 10:09:21 -06:00
parent c26558c1b1
commit 3524de16e4
5 changed files with 505 additions and 97 deletions

View File

@ -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

View File

@ -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,

View File

@ -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]);

View File

@ -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.

View File

@ -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);