diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt index 1f27ec501..460a299bf 100644 --- a/docs/reference/glib/glib-sections.txt +++ b/docs/reference/glib/glib-sections.txt @@ -1527,6 +1527,7 @@ GSpawnFlags GSpawnChildSetupFunc g_spawn_async_with_fds g_spawn_async_with_pipes +g_spawn_async_with_pipes_and_fds g_spawn_async g_spawn_sync G_SPAWN_EXIT_ERROR diff --git a/gio/gsubprocess.c b/gio/gsubprocess.c index 665d729ed..77a23efc3 100644 --- a/gio/gsubprocess.c +++ b/gio/gsubprocess.c @@ -179,160 +179,6 @@ enum N_PROPS }; -#ifdef G_OS_UNIX -typedef struct -{ - gint fds[3]; - GSpawnChildSetupFunc child_setup_func; - gpointer child_setup_data; - GArray *basic_fd_assignments; - GArray *needdup_fd_assignments; -} ChildData; - -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); - } -} - -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; -} - -/* - * Based on code derived from - * gnome-terminal:src/terminal-screen.c:terminal_screen_child_setup(), - * used under the LGPLv2+ with permission from author. - */ -static void -child_setup (gpointer user_data) -{ - ChildData *child_data = user_data; - guint i; - gint result; - int errsv; - - /* We're on the child side now. "Rename" the file descriptors in - * child_data.fds[] to stdin/stdout/stderr. - * - * We don't close the originals. It's possible that the originals - * should not be closed and if they should be closed then they should - * have been created O_CLOEXEC. - */ - for (i = 0; i < 3; i++) - if (child_data->fds[i] != -1 && child_data->fds[i] != (gint) i) - { - do - { - result = dup2 (child_data->fds[i], i); - errsv = errno; - } - while (result == -1 && errsv == EINTR); - } - - /* Basic fd assignments we can just unset FD_CLOEXEC */ - if (child_data->basic_fd_assignments) - { - for (i = 0; i < child_data->basic_fd_assignments->len; i++) - { - gint fd = g_array_index (child_data->basic_fd_assignments, int, i); - - unset_cloexec (fd); - } - } - - /* 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. - */ - if (child_data->needdup_fd_assignments) - { - for (i = 0; i < child_data->needdup_fd_assignments->len; i += 2) - { - gint parent_fd = g_array_index (child_data->needdup_fd_assignments, int, i); - gint new_parent_fd; - - new_parent_fd = dupfd_cloexec (parent_fd); - - g_array_index (child_data->needdup_fd_assignments, int, i) = new_parent_fd; - } - for (i = 0; i < child_data->needdup_fd_assignments->len; i += 2) - { - gint parent_fd = g_array_index (child_data->needdup_fd_assignments, int, i); - gint child_fd = g_array_index (child_data->needdup_fd_assignments, int, i+1); - - if (parent_fd == child_fd) - { - unset_cloexec (parent_fd); - } - else - { - do - { - result = dup2 (parent_fd, child_fd); - errsv = errno; - } - while (result == -1 && errsv == EINTR); - (void) close (parent_fd); - } - } - } - - if (child_data->child_setup_func) - child_data->child_setup_func (child_data->child_setup_data); -} -#endif - static GInputStream * platform_input_stream_from_spawn_fd (gint fd) { @@ -450,12 +296,12 @@ initable_init (GInitable *initable, GError **error) { GSubprocess *self = G_SUBPROCESS (initable); -#ifdef G_OS_UNIX - ChildData child_data = { { -1, -1, -1 }, 0, NULL, NULL, NULL }; -#endif gint *pipe_ptrs[3] = { NULL, NULL, NULL }; gint pipe_fds[3] = { -1, -1, -1 }; gint close_fds[3] = { -1, -1, -1 }; +#ifdef G_OS_UNIX + gint stdin_fd = -1, stdout_fd = -1, stderr_fd = -1; +#endif GSpawnFlags spawn_flags = 0; gboolean success = FALSE; gint i; @@ -480,11 +326,11 @@ initable_init (GInitable *initable, else if (self->launcher) { if (self->launcher->stdin_fd != -1) - child_data.fds[0] = self->launcher->stdin_fd; + stdin_fd = self->launcher->stdin_fd; else if (self->launcher->stdin_path != NULL) { - child_data.fds[0] = close_fds[0] = unix_open_file (self->launcher->stdin_path, O_RDONLY, error); - if (child_data.fds[0] == -1) + stdin_fd = close_fds[0] = unix_open_file (self->launcher->stdin_path, O_RDONLY, error); + if (stdin_fd == -1) goto out; } } @@ -499,11 +345,11 @@ initable_init (GInitable *initable, else if (self->launcher) { if (self->launcher->stdout_fd != -1) - child_data.fds[1] = self->launcher->stdout_fd; + stdout_fd = self->launcher->stdout_fd; else if (self->launcher->stdout_path != NULL) { - child_data.fds[1] = close_fds[1] = unix_open_file (self->launcher->stdout_path, O_CREAT | O_WRONLY, error); - if (child_data.fds[1] == -1) + stdout_fd = close_fds[1] = unix_open_file (self->launcher->stdout_path, O_CREAT | O_WRONLY, error); + if (stdout_fd == -1) goto out; } } @@ -516,29 +362,21 @@ initable_init (GInitable *initable, pipe_ptrs[2] = &pipe_fds[2]; #ifdef G_OS_UNIX else if (self->flags & G_SUBPROCESS_FLAGS_STDERR_MERGE) - /* This will work because stderr gets setup after stdout. */ - child_data.fds[2] = 1; + /* This will work because stderr gets set up after stdout. */ + stderr_fd = 1; else if (self->launcher) { if (self->launcher->stderr_fd != -1) - child_data.fds[2] = self->launcher->stderr_fd; + stderr_fd = self->launcher->stderr_fd; else if (self->launcher->stderr_path != NULL) { - child_data.fds[2] = close_fds[2] = unix_open_file (self->launcher->stderr_path, O_CREAT | O_WRONLY, error); - if (child_data.fds[2] == -1) + stderr_fd = close_fds[2] = unix_open_file (self->launcher->stderr_path, O_CREAT | O_WRONLY, error); + if (stderr_fd == -1) goto out; } } #endif -#ifdef G_OS_UNIX - if (self->launcher) - { - child_data.basic_fd_assignments = self->launcher->basic_fd_assignments; - child_data.needdup_fd_assignments = self->launcher->needdup_fd_assignments; - } -#endif - /* argv0 has no '/' in it? We better do a PATH lookup. */ if (strchr (self->argv[0], G_DIR_SEPARATOR) == NULL) { @@ -554,23 +392,25 @@ initable_init (GInitable *initable, spawn_flags |= G_SPAWN_DO_NOT_REAP_CHILD; spawn_flags |= G_SPAWN_CLOEXEC_PIPES; + success = g_spawn_async_with_pipes_and_fds (self->launcher ? self->launcher->cwd : NULL, + (const gchar * const *) self->argv, + (const gchar * const *) (self->launcher ? self->launcher->envp : NULL), + spawn_flags, #ifdef G_OS_UNIX - child_data.child_setup_func = self->launcher ? self->launcher->child_setup_func : NULL; - child_data.child_setup_data = self->launcher ? self->launcher->child_setup_user_data : NULL; -#endif - - success = g_spawn_async_with_pipes (self->launcher ? self->launcher->cwd : NULL, - self->argv, - self->launcher ? self->launcher->envp : NULL, - spawn_flags, -#ifdef G_OS_UNIX - child_setup, &child_data, + self->launcher ? self->launcher->child_setup_func : NULL, + self->launcher ? self->launcher->child_setup_user_data : NULL, + stdin_fd, stdout_fd, stderr_fd, + self->launcher ? (const gint *) self->launcher->source_fds->data : NULL, + self->launcher ? (const gint *) self->launcher->target_fds->data : NULL, + self->launcher ? self->launcher->source_fds->len : 0, #else - NULL, NULL, + NULL, NULL, + -1, -1, -1, + NULL, NULL, 0, #endif - &self->pid, - pipe_ptrs[0], pipe_ptrs[1], pipe_ptrs[2], - error); + &self->pid, + pipe_ptrs[0], pipe_ptrs[1], pipe_ptrs[2], + error); g_assert (success == (self->pid != 0)); { diff --git a/gio/gsubprocesslauncher-private.h b/gio/gsubprocesslauncher-private.h index 95431a0ab..f8a6516c5 100644 --- a/gio/gsubprocesslauncher-private.h +++ b/gio/gsubprocesslauncher-private.h @@ -42,8 +42,8 @@ struct _GSubprocessLauncher gint stderr_fd; gchar *stderr_path; - GArray *basic_fd_assignments; - GArray *needdup_fd_assignments; + GArray *source_fds; + GArray *target_fds; /* always the same length as source_fds */ gboolean closed_fd; GSpawnChildSetupFunc child_setup_func; diff --git a/gio/gsubprocesslauncher.c b/gio/gsubprocesslauncher.c index 9e077bd20..b7257f453 100644 --- a/gio/gsubprocesslauncher.c +++ b/gio/gsubprocesslauncher.c @@ -164,8 +164,8 @@ g_subprocess_launcher_init (GSubprocessLauncher *self) self->stdin_fd = -1; self->stdout_fd = -1; self->stderr_fd = -1; - self->basic_fd_assignments = g_array_new (FALSE, 0, sizeof (int)); - self->needdup_fd_assignments = g_array_new (FALSE, 0, sizeof (int)); + self->source_fds = g_array_new (FALSE, 0, sizeof (int)); + self->target_fds = g_array_new (FALSE, 0, sizeof (int)); #endif } @@ -613,18 +613,10 @@ g_subprocess_launcher_take_fd (GSubprocessLauncher *self, gint source_fd, gint target_fd) { - if (source_fd == target_fd) + if (self->source_fds != NULL && self->target_fds != NULL) { - if (self->basic_fd_assignments) - g_array_append_val (self->basic_fd_assignments, source_fd); - } - else - { - if (self->needdup_fd_assignments) - { - g_array_append_val (self->needdup_fd_assignments, source_fd); - g_array_append_val (self->needdup_fd_assignments, target_fd); - } + g_array_append_val (self->source_fds, source_fd); + g_array_append_val (self->target_fds, target_fd); } } @@ -664,17 +656,18 @@ g_subprocess_launcher_close (GSubprocessLauncher *self) close (self->stderr_fd); self->stderr_fd = -1; - if (self->basic_fd_assignments) + if (self->source_fds) { - for (i = 0; i < self->basic_fd_assignments->len; i++) - (void) close (g_array_index (self->basic_fd_assignments, int, i)); - g_clear_pointer (&self->basic_fd_assignments, g_array_unref); - } - if (self->needdup_fd_assignments) - { - for (i = 0; i < self->needdup_fd_assignments->len; i += 2) - (void) close (g_array_index (self->needdup_fd_assignments, int, i)); - g_clear_pointer (&self->needdup_fd_assignments, g_array_unref); + g_assert (self->target_fds != NULL); + g_assert (self->source_fds->len == self->target_fds->len); + + for (i = 0; i < self->source_fds->len; i++) + { + (void) close (g_array_index (self->source_fds, int, i)); + (void) close (g_array_index (self->target_fds, int, i)); + } + g_clear_pointer (&self->source_fds, g_array_unref); + g_clear_pointer (&self->target_fds, g_array_unref); } self->closed_fd = TRUE; diff --git a/glib/gspawn-win32.c b/glib/gspawn-win32.c index e1ff7e041..0f6579eab 100644 --- a/glib/gspawn-win32.c +++ b/glib/gspawn-win32.c @@ -191,8 +191,8 @@ protect_argv_string (const gchar *string) } static gint -protect_argv (gchar **argv, - gchar ***new_argv) +protect_argv (const gchar * const *argv, + gchar ***new_argv) { gint i; gint argc = 0; @@ -230,7 +230,7 @@ g_spawn_async (const gchar *working_directory, GSpawnFlags flags, GSpawnChildSetupFunc child_setup, gpointer user_data, - GPid *child_handle, + GPid *child_pid, GError **error) { g_return_val_if_fail (argv != NULL, FALSE); @@ -240,7 +240,7 @@ g_spawn_async (const gchar *working_directory, flags, child_setup, user_data, - child_handle, + child_pid, NULL, NULL, NULL, error); } @@ -398,10 +398,10 @@ set_child_error (gintptr report[2], } static gboolean -utf8_charv_to_wcharv (char **utf8_charv, - wchar_t ***wcharv, - int *error_index, - GError **error) +utf8_charv_to_wcharv (const gchar * const *utf8_charv, + wchar_t ***wcharv, + int *error_index, + GError **error) { wchar_t **retval = NULL; @@ -436,16 +436,16 @@ utf8_charv_to_wcharv (char **utf8_charv, static gboolean do_spawn_directly (gint *exit_status, - gboolean do_return_handle, - GSpawnFlags flags, - gchar **argv, - char **envp, - char **protected_argv, - GPid *child_handle, - GError **error) + gboolean do_return_handle, + GSpawnFlags flags, + const gchar * const *argv, + const gchar * const *envp, + const gchar * const *protected_argv, + GPid *child_pid, + GError **error) { const int mode = (exit_status == NULL) ? P_NOWAIT : P_WAIT; - char **new_argv; + const gchar * const *new_argv; gintptr rc = -1; int errsv; GError *conv_error = NULL; @@ -515,13 +515,13 @@ do_spawn_directly (gint *exit_status, if (exit_status == NULL) { - if (child_handle && do_return_handle) - *child_handle = (GPid) rc; + if (child_pid && do_return_handle) + *child_pid = (GPid) rc; else { CloseHandle ((HANDLE) rc); - if (child_handle) - *child_handle = 0; + if (child_pid) + *child_pid = 0; } } else @@ -531,19 +531,23 @@ do_spawn_directly (gint *exit_status, } static gboolean -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) +fork_exec (gint *exit_status, + gboolean do_return_handle, + const gchar *working_directory, + const gchar * const *argv, + const gchar * const *envp, + GSpawnFlags flags, + 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, + gint *err_report, + GError **error) { char **protected_argv; char args[ARG_COUNT][10]; @@ -561,6 +565,13 @@ do_spawn_with_fds (gint *exit_status, gchar *helper_process; wchar_t *whelper, **wargv, **wenvp; gchar *glib_dll_directory; + int stdin_pipe[2] = { -1, -1 }; + int stdout_pipe[2] = { -1, -1 }; + int stderr_pipe[2] = { -1, -1 }; + + 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 (child_setup && !warned_about_child_setup) { @@ -568,6 +579,27 @@ do_spawn_with_fds (gint *exit_status, g_warning ("passing a child setup function to the g_spawn functions is pointless on Windows and it is ignored"); } + if (stdin_pipe_out != NULL) + { + if (!make_pipe (stdin_pipe, error)) + goto cleanup_and_fail; + stdin_fd = stdin_pipe[0]; + } + + if (stdout_pipe_out != NULL) + { + if (!make_pipe (stdout_pipe, error)) + goto cleanup_and_fail; + stdout_fd = stdout_pipe[1]; + } + + if (stderr_pipe_out != NULL) + { + if (!make_pipe (stderr_pipe, error)) + goto cleanup_and_fail; + stderr_fd = stderr_pipe[1]; + } + argc = protect_argv (argv, &protected_argv); if (stdin_fd == -1 && stdout_fd == -1 && stderr_fd == -1 && @@ -580,8 +612,8 @@ do_spawn_with_fds (gint *exit_status, /* We can do without the helper process */ gboolean retval = do_spawn_directly (exit_status, do_return_handle, flags, - argv, envp, protected_argv, - child_handle, error); + argv, envp, (const gchar * const *) protected_argv, + child_pid, error); g_strfreev (protected_argv); return retval; } @@ -714,7 +746,7 @@ do_spawn_with_fds (gint *exit_status, g_print ("argv[%d]: %s\n", i, (new_argv[i] ? new_argv[i] : "NULL")); } - if (!utf8_charv_to_wcharv (new_argv, &wargv, &conv_error_index, &conv_error)) + if (!utf8_charv_to_wcharv ((const gchar * const *) new_argv, &wargv, &conv_error_index, &conv_error)) { if (conv_error_index == ARG_WORKING_DIRECTORY) g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_CHDIR, @@ -807,23 +839,23 @@ do_spawn_with_fds (gint *exit_status, switch (helper_report[0]) { case CHILD_NO_ERROR: - if (child_handle && do_return_handle) + if (child_pid && do_return_handle) { /* rc is our HANDLE for gspawn-win32-helper. It has * told us the HANDLE of its child. Duplicate that into * a HANDLE valid in this process. */ if (!DuplicateHandle ((HANDLE) rc, (HANDLE) helper_report[1], - GetCurrentProcess (), (LPHANDLE) child_handle, + GetCurrentProcess (), (LPHANDLE) child_pid, 0, TRUE, DUPLICATE_SAME_ACCESS)) { char *emsg = g_win32_error_message (GetLastError ()); g_print("%s\n", emsg); - *child_handle = 0; + *child_pid = 0; } } - else if (child_handle) - *child_handle = 0; + else if (child_pid) + *child_pid = 0; write (helper_sync_pipe[1], " ", 1); close_and_invalidate (&helper_sync_pipe[1]); break; @@ -840,7 +872,21 @@ do_spawn_with_fds (gint *exit_status, if (rc != -1) CloseHandle ((HANDLE) rc); - + + /* 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 (stdin_pipe_out != NULL) + *stdin_pipe_out = stdin_pipe[1]; + if (stdout_pipe_out != NULL) + *stdout_pipe_out = stdout_pipe[0]; + if (stderr_pipe_out != NULL) + *stderr_pipe_out = stderr_pipe[0]; + return TRUE; cleanup_and_fail: @@ -856,70 +902,6 @@ do_spawn_with_fds (gint *exit_status, 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) @@ -979,20 +961,24 @@ g_spawn_sync (const gchar *working_directory, if (standard_error) *standard_error = NULL; - - if (!do_spawn_with_pipes (&status, - FALSE, - working_directory, - argv, - envp, - flags, - child_setup, - NULL, - NULL, - standard_output ? &outpipe : NULL, - standard_error ? &errpipe : NULL, - &reportpipe, - error)) + + if (!fork_exec (&status, + FALSE, + working_directory, + (const gchar * const *) argv, + (const gchar * const *) envp, + flags, + child_setup, + user_data, + NULL, + NULL, + standard_output ? &outpipe : NULL, + standard_error ? &errpipe : NULL, + -1, + -1, + -1, + &reportpipe, + error)) return FALSE; /* Read data from child. */ @@ -1185,7 +1171,7 @@ g_spawn_async_with_pipes (const gchar *working_directory, GSpawnFlags flags, GSpawnChildSetupFunc child_setup, gpointer user_data, - GPid *child_handle, + GPid *child_pid, gint *standard_input, gint *standard_output, gint *standard_error, @@ -1199,20 +1185,24 @@ g_spawn_async_with_pipes (const gchar *working_directory, /* 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 do_spawn_with_pipes (NULL, - (flags & G_SPAWN_DO_NOT_REAP_CHILD), - working_directory, - argv, - envp, - flags, - child_setup, - child_handle, - standard_input, - standard_output, - standard_error, - NULL, - error); + + return fork_exec (NULL, + (flags & G_SPAWN_DO_NOT_REAP_CHILD), + working_directory, + (const gchar * const *) argv, + (const gchar * const *) envp, + flags, + child_setup, + user_data, + child_pid, + standard_input, + standard_output, + standard_error, + -1, + -1, + -1, + NULL, + error); } gboolean @@ -1222,7 +1212,7 @@ g_spawn_async_with_fds (const gchar *working_directory, GSpawnFlags flags, GSpawnChildSetupFunc child_setup, gpointer user_data, - GPid *child_handle, + GPid *child_pid, gint stdin_fd, gint stdout_fd, gint stderr_fd, @@ -1237,19 +1227,83 @@ g_spawn_async_with_fds (const gchar *working_directory, 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); + return fork_exec (NULL, + (flags & G_SPAWN_DO_NOT_REAP_CHILD), + working_directory, + (const gchar * const *) argv, + (const gchar * const *) envp, + flags, + child_setup, + user_data, + child_pid, + NULL, + NULL, + NULL, + stdin_fd, + stdout_fd, + stderr_fd, + NULL, + error); + +} + +gboolean +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 (stdout_pipe_out == NULL || + !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE); + 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 (stdin_pipe_out == NULL || + !(flags & G_SPAWN_CHILD_INHERITS_STDIN), 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); + + /* source_fds/target_fds isn’t supported on Windows at the moment. */ + if (n_fds != 0) + { + g_set_error_literal (error, G_SPAWN_ERROR, G_SPAWN_ERROR_INVAL, + "FD redirection is not supported on Windows at the moment"); + return FALSE; + } + + return fork_exec (NULL, + (flags & G_SPAWN_DO_NOT_REAP_CHILD), + working_directory, + argv, + envp, + flags, + child_setup, + user_data, + child_pid_out, + stdin_pipe_out, + stdout_pipe_out, + stderr_pipe_out, + stdin_fd, + stdout_fd, + stderr_fd, + NULL, + error); } gboolean @@ -1384,7 +1438,7 @@ g_spawn_async_utf8 (const gchar *working_directory, GSpawnFlags flags, GSpawnChildSetupFunc child_setup, gpointer user_data, - GPid *child_handle, + GPid *child_pid, GError **error) { return g_spawn_async (working_directory, @@ -1393,7 +1447,7 @@ g_spawn_async_utf8 (const gchar *working_directory, flags, child_setup, user_data, - child_handle, + child_pid, error); } @@ -1404,7 +1458,7 @@ g_spawn_async_with_pipes_utf8 (const gchar *working_directory, GSpawnFlags flags, GSpawnChildSetupFunc child_setup, gpointer user_data, - GPid *child_handle, + GPid *child_pid, gint *standard_input, gint *standard_output, gint *standard_error, @@ -1416,7 +1470,7 @@ g_spawn_async_with_pipes_utf8 (const gchar *working_directory, flags, child_setup, user_data, - child_handle, + child_pid, standard_input, standard_output, standard_error, diff --git a/glib/gspawn.c b/glib/gspawn.c index 3ed437423..95f5b868e 100644 --- a/glib/gspawn.c +++ b/glib/gspawn.c @@ -167,46 +167,31 @@ static gint g_execute (const gchar *file, 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) @@ -264,6 +249,16 @@ g_spawn_async (const gchar *working_directory, 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) @@ -402,25 +397,27 @@ g_spawn_sync (const gchar *working_directory, if (standard_error) *standard_error = NULL; - if (!fork_exec_with_pipes (FALSE, - working_directory, - argv, - envp, - !(flags & G_SPAWN_LEAVE_DESCRIPTORS_OPEN), - (flags & G_SPAWN_SEARCH_PATH) != 0, - (flags & G_SPAWN_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. */ @@ -598,6 +595,82 @@ g_spawn_sync (const gchar *working_directory, * @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. @@ -607,7 +680,7 @@ g_spawn_sync (const gchar *working_directory, * 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 @@ -662,7 +735,7 @@ g_spawn_sync (const gchar *working_directory, * 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(). * @@ -676,15 +749,32 @@ g_spawn_sync (const gchar *working_directory, * 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() @@ -714,22 +804,22 @@ g_spawn_sync (const gchar *working_directory, * 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. @@ -739,8 +829,8 @@ g_spawn_sync (const gchar *working_directory, * 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(). @@ -763,51 +853,69 @@ g_spawn_sync (const gchar *working_directory, * 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); } /** @@ -819,29 +927,13 @@ g_spawn_async_with_pipes (const gchar *working_directory, * @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 * @@ -869,26 +961,27 @@ g_spawn_async_with_fds (const gchar *working_directory, 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); } /** @@ -1139,6 +1232,68 @@ set_cloexec (void *data, gint fd) return 0; } +/* 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 @@ -1345,6 +1500,20 @@ safe_closefrom (int lowfd) #endif } +/* 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 @@ -1388,11 +1557,14 @@ 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, + 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, @@ -1404,21 +1576,23 @@ do_exec (gint child_err_report_fd, 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) { @@ -1434,12 +1608,13 @@ do_exec (gint child_err_report_fd, 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) { @@ -1454,12 +1629,13 @@ do_exec (gint child_err_report_fd, 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) { @@ -1471,14 +1647,14 @@ do_exec (gint child_err_report_fd, 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); @@ -1495,7 +1671,50 @@ do_exec (gint child_err_report_fd, /* 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) { @@ -1503,9 +1722,9 @@ do_exec (gint child_err_report_fd, } 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, @@ -1563,8 +1782,8 @@ read_ints (int 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, @@ -1577,7 +1796,7 @@ do_posix_spawn (gchar **argv, 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]; @@ -1716,13 +1935,13 @@ do_posix_spawn (gchar **argv, 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; @@ -1741,26 +1960,31 @@ out_free_spawnattr: #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 }; @@ -1774,10 +1998,49 @@ fork_exec_with_fds (gboolean intermediate_child, 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", @@ -1796,7 +2059,7 @@ fork_exec_with_fds (gboolean intermediate_child, stdout_fd, stderr_fd); if (status == 0) - return TRUE; + goto success; if (status != ENOEXEC) { @@ -1806,7 +2069,7 @@ fork_exec_with_fds (gboolean intermediate_child, _("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 @@ -1833,7 +2096,7 @@ fork_exec_with_fds (gboolean intermediate_child, * 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"); @@ -1887,7 +2150,7 @@ fork_exec_with_fds (gboolean intermediate_child, /* 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 @@ -1900,12 +2163,13 @@ fork_exec_with_fds (gboolean intermediate_child, 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; @@ -1947,7 +2211,7 @@ fork_exec_with_fds (gboolean intermediate_child, */ 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) @@ -1981,6 +2245,9 @@ fork_exec_with_fds (gboolean intermediate_child, stdin_fd, stdout_fd, stderr_fd, + source_fds_copy, + target_fds, + n_fds, working_directory, argv, argv_buffer, @@ -2014,6 +2281,9 @@ fork_exec_with_fds (gboolean intermediate_child, stdin_fd, stdout_fd, stderr_fd, + source_fds_copy, + target_fds, + n_fds, working_directory, argv, argv_buffer, @@ -2053,8 +2323,7 @@ fork_exec_with_fds (gboolean intermediate_child, else if (errno == ECHILD) ; /* do nothing, child already reaped */ else - g_warning ("waitpid() should not fail in " - "'fork_exec_with_pipes'"); + g_warning ("waitpid() should not fail in 'fork_exec'"); } } @@ -2152,13 +2421,31 @@ fork_exec_with_fds (gboolean intermediate_child, 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 @@ -2175,104 +2462,10 @@ fork_exec_with_fds (gboolean intermediate_child, else if (errno == ECHILD) ; /* do nothing, child already reaped */ else - g_warning ("waitpid() should not fail in " - "'fork_exec_with_pipes'"); + g_warning ("waitpid() should not fail in 'fork_exec'"); } } - close_and_invalidate (&child_err_report_pipe[0]); - close_and_invalidate (&child_err_report_pipe[1]); - close_and_invalidate (&child_pid_report_pipe[0]); - close_and_invalidate (&child_pid_report_pipe[1]); - - 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]); @@ -2280,6 +2473,15 @@ cleanup_and_fail: 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; } diff --git a/glib/gspawn.h b/glib/gspawn.h index f91453a3c..e09dc2aec 100644 --- a/glib/gspawn.h +++ b/glib/gspawn.h @@ -213,6 +213,25 @@ gboolean g_spawn_async_with_pipes (const gchar *working_directory, gint *standard_error, GError **error); +GLIB_AVAILABLE_IN_2_68 +gboolean 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); + /* Lets you provide fds for stdin/stdout/stderr */ GLIB_AVAILABLE_IN_2_58 gboolean g_spawn_async_with_fds (const gchar *working_directory, diff --git a/glib/tests/spawn-singlethread.c b/glib/tests/spawn-singlethread.c index c4417fe19..9a18c0640 100644 --- a/glib/tests/spawn-singlethread.c +++ b/glib/tests/spawn-singlethread.c @@ -24,11 +24,16 @@ #include "config.h" #include +#include #include #include #ifdef G_OS_UNIX #include +#include +#include +#include +#include #endif #ifdef G_OS_WIN32 @@ -420,6 +425,72 @@ test_spawn_nonexistent (void) g_clear_error (&error); } +/* Test that FD assignments in a spawned process don’t overwrite and break the + * child_err_report_fd which is used to report error information back from the + * intermediate child process to the parent. + * + * https://gitlab.gnome.org/GNOME/glib/-/issues/2097 */ +static void +test_spawn_fd_assignment_clash (void) +{ +#ifdef G_OS_UNIX + int tmp_fd; + guint i; + const guint n_fds = 10; + gint source_fds[n_fds]; + gint target_fds[n_fds]; + const gchar *argv[] = { "/nonexistent" }; + gboolean retval; + GError *local_error = NULL; + struct stat statbuf; + + /* Open a temporary file and duplicate its FD several times so we have several + * FDs to remap in the child process. */ + tmp_fd = g_file_open_tmp ("glib-spawn-test-XXXXXX", NULL, NULL); + g_assert_cmpint (tmp_fd, >=, 0); + + for (i = 0; i < (n_fds - 1); ++i) + { + int source = fcntl (tmp_fd, F_DUPFD_CLOEXEC, 3); + g_assert_cmpint (source, >=, 0); + source_fds[i] = source; + target_fds[i] = source + n_fds; + } + + source_fds[i] = tmp_fd; + target_fds[i] = tmp_fd + n_fds; + + /* Print out the FD map. */ + g_test_message ("FD map:"); + for (i = 0; i < n_fds; i++) + g_test_message (" • %d → %d", source_fds[i], target_fds[i]); + + /* Spawn the subprocess. This should fail because the executable doesn’t + * exist. */ + retval = g_spawn_async_with_pipes_and_fds (NULL, argv, NULL, G_SPAWN_DEFAULT, + NULL, NULL, -1, -1, -1, + source_fds, target_fds, n_fds, + NULL, NULL, NULL, NULL, + &local_error); + g_assert_error (local_error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT); + g_assert_false (retval); + + g_clear_error (&local_error); + + /* Check nothing was written to the temporary file, as would happen if the FD + * mapping was messed up to conflict with the child process error reporting FD. + * See https://gitlab.gnome.org/GNOME/glib/-/issues/2097 */ + g_assert_no_errno (fstat (tmp_fd, &statbuf)); + g_assert_cmpuint (statbuf.st_size, ==, 0); + + /* Clean up. */ + for (i = 0; i < n_fds; i++) + g_close (source_fds[i], NULL); +#else /* !G_OS_UNIX */ + g_test_skip ("FD redirection only supported on Unix"); +#endif /* !G_OS_UNIX */ +} + int main (int argc, char *argv[]) @@ -427,6 +498,8 @@ main (int argc, char *dirname; int ret; + setlocale (LC_ALL, ""); + g_test_init (&argc, &argv, NULL); dirname = g_path_get_dirname (argv[0]); @@ -453,6 +526,7 @@ main (int argc, g_test_add_func ("/gthread/spawn-script", test_spawn_script); g_test_add_func ("/gthread/spawn/nonexistent", test_spawn_nonexistent); g_test_add_func ("/gthread/spawn-posix-spawn", test_posix_spawn); + g_test_add_func ("/gthread/spawn/fd-assignment-clash", test_spawn_fd_assignment_clash); ret = g_test_run();