mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-02-25 11:42:10 +01:00
Merge branch '2097-spawn-fd-rewriting' into 'master'
Resolve "GSubprocessLauncher with FD assignment can clash with g_spawn_async internal pipe" Closes #2097 See merge request GNOME/glib!1690
This commit is contained in:
commit
51e964849b
@ -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
|
||||
|
@ -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,19 +392,21 @@ initable_init (GInitable *initable,
|
||||
spawn_flags |= G_SPAWN_DO_NOT_REAP_CHILD;
|
||||
spawn_flags |= G_SPAWN_CLOEXEC_PIPES;
|
||||
|
||||
#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,
|
||||
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_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,
|
||||
-1, -1, -1,
|
||||
NULL, NULL, 0,
|
||||
#endif
|
||||
&self->pid,
|
||||
pipe_ptrs[0], pipe_ptrs[1], pipe_ptrs[2],
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
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));
|
||||
}
|
||||
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_clear_pointer (&self->source_fds, g_array_unref);
|
||||
g_clear_pointer (&self->target_fds, g_array_unref);
|
||||
}
|
||||
|
||||
self->closed_fd = TRUE;
|
||||
|
@ -191,7 +191,7 @@ protect_argv_string (const gchar *string)
|
||||
}
|
||||
|
||||
static gint
|
||||
protect_argv (gchar **argv,
|
||||
protect_argv (const gchar * const *argv,
|
||||
gchar ***new_argv)
|
||||
{
|
||||
gint i;
|
||||
@ -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,7 +398,7 @@ set_child_error (gintptr report[2],
|
||||
}
|
||||
|
||||
static gboolean
|
||||
utf8_charv_to_wcharv (char **utf8_charv,
|
||||
utf8_charv_to_wcharv (const gchar * const *utf8_charv,
|
||||
wchar_t ***wcharv,
|
||||
int *error_index,
|
||||
GError **error)
|
||||
@ -438,14 +438,14 @@ static gboolean
|
||||
do_spawn_directly (gint *exit_status,
|
||||
gboolean do_return_handle,
|
||||
GSpawnFlags flags,
|
||||
gchar **argv,
|
||||
char **envp,
|
||||
char **protected_argv,
|
||||
GPid *child_handle,
|
||||
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,14 +531,18 @@ do_spawn_directly (gint *exit_status,
|
||||
}
|
||||
|
||||
static gboolean
|
||||
do_spawn_with_fds (gint *exit_status,
|
||||
fork_exec (gint *exit_status,
|
||||
gboolean do_return_handle,
|
||||
const gchar *working_directory,
|
||||
gchar **argv,
|
||||
char **envp,
|
||||
const gchar * const *argv,
|
||||
const gchar * const *envp,
|
||||
GSpawnFlags flags,
|
||||
GSpawnChildSetupFunc child_setup,
|
||||
GPid *child_handle,
|
||||
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,
|
||||
@ -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;
|
||||
@ -841,6 +873,20 @@ 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)
|
||||
@ -980,17 +962,21 @@ g_spawn_sync (const gchar *working_directory,
|
||||
if (standard_error)
|
||||
*standard_error = NULL;
|
||||
|
||||
if (!do_spawn_with_pipes (&status,
|
||||
if (!fork_exec (&status,
|
||||
FALSE,
|
||||
working_directory,
|
||||
argv,
|
||||
envp,
|
||||
(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;
|
||||
@ -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,
|
||||
@ -1200,17 +1186,21 @@ g_spawn_async_with_pipes (const gchar *working_directory,
|
||||
g_return_val_if_fail (standard_input == NULL ||
|
||||
!(flags & G_SPAWN_CHILD_INHERITS_STDIN), FALSE);
|
||||
|
||||
return do_spawn_with_pipes (NULL,
|
||||
return fork_exec (NULL,
|
||||
(flags & G_SPAWN_DO_NOT_REAP_CHILD),
|
||||
working_directory,
|
||||
argv,
|
||||
envp,
|
||||
(const gchar * const *) argv,
|
||||
(const gchar * const *) envp,
|
||||
flags,
|
||||
child_setup,
|
||||
child_handle,
|
||||
user_data,
|
||||
child_pid,
|
||||
standard_input,
|
||||
standard_output,
|
||||
standard_error,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
NULL,
|
||||
error);
|
||||
}
|
||||
@ -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,14 +1227,78 @@ 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,
|
||||
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,
|
||||
child_handle,
|
||||
user_data,
|
||||
child_pid_out,
|
||||
stdin_pipe_out,
|
||||
stdout_pipe_out,
|
||||
stderr_pipe_out,
|
||||
stdin_fd,
|
||||
stdout_fd,
|
||||
stderr_fd,
|
||||
@ -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,
|
||||
|
620
glib/gspawn.c
620
glib/gspawn.c
@ -167,10 +167,10 @@ 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,
|
||||
static gboolean fork_exec (gboolean intermediate_child,
|
||||
const gchar *working_directory,
|
||||
gchar **argv,
|
||||
gchar **envp,
|
||||
const gchar * const *argv,
|
||||
const gchar * const *envp,
|
||||
gboolean close_descriptors,
|
||||
gboolean search_path,
|
||||
gboolean search_path_from_envp,
|
||||
@ -182,30 +182,15 @@ static gboolean fork_exec_with_pipes (gboolean intermediate_child,
|
||||
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_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)
|
||||
@ -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,10 +397,10 @@ g_spawn_sync (const gchar *working_directory,
|
||||
if (standard_error)
|
||||
*standard_error = NULL;
|
||||
|
||||
if (!fork_exec_with_pipes (FALSE,
|
||||
if (!fork_exec (FALSE,
|
||||
working_directory,
|
||||
argv,
|
||||
envp,
|
||||
(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,
|
||||
@ -420,6 +415,8 @@ g_spawn_sync (const gchar *working_directory,
|
||||
NULL,
|
||||
standard_output ? &outpipe : NULL,
|
||||
standard_error ? &errpipe : NULL,
|
||||
-1, -1, -1,
|
||||
NULL, NULL, 0,
|
||||
error))
|
||||
return FALSE;
|
||||
|
||||
@ -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.
|
||||
@ -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.
|
||||
* 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,21 +804,21 @@ 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
|
||||
* 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
|
||||
* 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.
|
||||
*
|
||||
@ -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,36 +853,48 @@ 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,
|
||||
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,
|
||||
GPid *child_pid,
|
||||
gint *standard_input,
|
||||
gint *standard_output,
|
||||
gint *standard_error,
|
||||
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);
|
||||
/* 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_with_pipes (!(flags & G_SPAWN_DO_NOT_REAP_CHILD),
|
||||
return fork_exec (!(flags & G_SPAWN_DO_NOT_REAP_CHILD),
|
||||
working_directory,
|
||||
argv,
|
||||
envp,
|
||||
(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,
|
||||
@ -803,10 +905,16 @@ g_spawn_async_with_pipes (const gchar *working_directory,
|
||||
(flags & G_SPAWN_CLOEXEC_PIPES) != 0,
|
||||
child_setup,
|
||||
user_data,
|
||||
child_pid,
|
||||
standard_input,
|
||||
standard_output,
|
||||
standard_error,
|
||||
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,10 +961,10 @@ 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),
|
||||
return fork_exec (!(flags & G_SPAWN_DO_NOT_REAP_CHILD),
|
||||
working_directory,
|
||||
argv,
|
||||
envp,
|
||||
(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,
|
||||
@ -884,10 +976,11 @@ g_spawn_async_with_fds (const gchar *working_directory,
|
||||
child_setup,
|
||||
user_data,
|
||||
child_pid,
|
||||
NULL,
|
||||
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,20 +1576,22 @@ 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);
|
||||
|
||||
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,11 +1608,12 @@ 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);
|
||||
|
||||
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,11 +1629,12 @@ 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);
|
||||
|
||||
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);
|
||||
@ -1496,6 +1672,49 @@ do_exec (gint child_err_report_fd,
|
||||
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,10 +1960,10 @@ out_free_spawnattr:
|
||||
#endif /* POSIX_SPAWN_AVAILABLE */
|
||||
|
||||
static gboolean
|
||||
fork_exec_with_fds (gboolean intermediate_child,
|
||||
fork_exec (gboolean intermediate_child,
|
||||
const gchar *working_directory,
|
||||
gchar **argv,
|
||||
gchar **envp,
|
||||
const gchar * const *argv,
|
||||
const gchar * const *envp,
|
||||
gboolean close_descriptors,
|
||||
gboolean search_path,
|
||||
gboolean search_path_from_envp,
|
||||
@ -1756,10 +1975,15 @@ fork_exec_with_fds (gboolean intermediate_child,
|
||||
GSpawnChildSetupFunc child_setup,
|
||||
gpointer user_data,
|
||||
GPid *child_pid,
|
||||
gint *child_close_fds,
|
||||
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;
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -24,11 +24,16 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <locale.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#ifdef G_OS_UNIX
|
||||
#include <glib-unix.h>
|
||||
#include <glib/gstdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#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();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user