gspawn: Avoid considering out parameters as inputs

The g_spawn_async_with_pipes_and_fds() API changes its behavior
depending on the states of out parameters. Apart from the philosophical
inconsistency, this poses some real problems, e.g. in Lua bindings:

    https://github.com/lgi-devs/lgi/issues/277

This adds three new spawn flags to allow specifying the wanted behavior
explicitly and does not assert on the state of the output parameters.
This commit is contained in:
Nicola Fontana 2022-05-02 11:09:11 +02:00
parent ac2ab3d3c6
commit a84068d12a
2 changed files with 64 additions and 17 deletions

View File

@ -67,6 +67,10 @@
#include "glibintl.h" #include "glibintl.h"
#include "glib-unix.h" #include "glib-unix.h"
#define INHERITS_OR_NULL_STDIN (G_SPAWN_STDIN_FROM_DEV_NULL | G_SPAWN_CHILD_INHERITS_STDIN)
#define INHERITS_OR_NULL_STDOUT (G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_CHILD_INHERITS_STDOUT)
#define INHERITS_OR_NULL_STDERR (G_SPAWN_STDERR_TO_DEV_NULL | G_SPAWN_CHILD_INHERITS_STDERR)
/* posix_spawn() is assumed the fastest way to spawn, but glibc's /* posix_spawn() is assumed the fastest way to spawn, but glibc's
* implementation was buggy before glibc 2.24, so avoid it on old versions. * implementation was buggy before glibc 2.24, so avoid it on old versions.
*/ */
@ -728,17 +732,23 @@ g_spawn_async_with_pipes (const gchar *working_directory,
* @envp. If both %G_SPAWN_SEARCH_PATH and %G_SPAWN_SEARCH_PATH_FROM_ENVP * @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. * 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, @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, @stderr_pipe_out must be %NULL.
*
* %G_SPAWN_CHILD_INHERITS_STDIN means that the child will inherit the parent's * %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 * standard input (by default, the child's standard input is attached to
* `/dev/null`). If you use this flag, @stdin_pipe_out must be %NULL. * `/dev/null`). %G_SPAWN_STDIN_FROM_DEV_NULL explicitly imposes the default
* behavior. Both flags cannot be enabled at the same time and, in both cases,
* the @stdin_pipe_out argument is ignored.
*
* %G_SPAWN_STDOUT_TO_DEV_NULL means that the child's standard output
* will be discarded (by default, it goes to the same location as the parent's
* standard output). %G_SPAWN_CHILD_INHERITS_STDOUT explicitly imposes the
* default behavior. Both flags cannot be enabled at the same time and, in
* both cases, the @stdout_pipe_out argument is ignored.
*
* %G_SPAWN_STDERR_TO_DEV_NULL means that the child's standard error
* will be discarded (by default, it goes to the same location as the parent's
* standard error). %G_SPAWN_CHILD_INHERITS_STDERR explicitly imposes the
* default behavior. Both flags cannot be enabled at the same time and, in
* both cases, the @stderr_pipe_out argument is ignored.
* *
* It is valid to pass the same FD in multiple parameters (e.g. you can pass * 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 * a single FD for both @stdout_fd and @stderr_fd, and include it in
@ -862,18 +872,22 @@ g_spawn_async_with_pipes_and_fds (const gchar *working_directory,
{ {
g_return_val_if_fail (argv != NULL, FALSE); g_return_val_if_fail (argv != NULL, FALSE);
g_return_val_if_fail (argv[0] != NULL, FALSE); g_return_val_if_fail (argv[0] != NULL, FALSE);
g_return_val_if_fail (stdout_pipe_out == NULL || /* cant both inherit and set pipes to /dev/null */
!(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE); g_return_val_if_fail ((flags & INHERITS_OR_NULL_STDIN) != INHERITS_OR_NULL_STDIN, FALSE);
g_return_val_if_fail (stderr_pipe_out == NULL || g_return_val_if_fail ((flags & INHERITS_OR_NULL_STDOUT) != INHERITS_OR_NULL_STDOUT, FALSE);
!(flags & G_SPAWN_STDERR_TO_DEV_NULL), FALSE); g_return_val_if_fail ((flags & INHERITS_OR_NULL_STDERR) != INHERITS_OR_NULL_STDERR, 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);
/* cant use pipes and stdin/stdout/stderr FDs */ /* cant 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 (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 (stdout_pipe_out == NULL || stdout_fd < 0, FALSE);
g_return_val_if_fail (stderr_pipe_out == NULL || stderr_fd < 0, FALSE); g_return_val_if_fail (stderr_pipe_out == NULL || stderr_fd < 0, FALSE);
if ((flags & INHERITS_OR_NULL_STDIN) != 0)
stdin_pipe_out = NULL;
if ((flags & INHERITS_OR_NULL_STDOUT) != 0)
stdout_pipe_out = NULL;
if ((flags & INHERITS_OR_NULL_STDERR) != 0)
stderr_pipe_out = NULL;
return fork_exec (!(flags & G_SPAWN_DO_NOT_REAP_CHILD), return fork_exec (!(flags & G_SPAWN_DO_NOT_REAP_CHILD),
working_directory, working_directory,
(const gchar * const *) argv, (const gchar * const *) argv,

View File

@ -162,6 +162,12 @@ typedef void (* GSpawnChildSetupFunc) (gpointer user_data);
* Since: 2.34 * Since: 2.34
* @G_SPAWN_CLOEXEC_PIPES: create all pipes with the `O_CLOEXEC` flag set. * @G_SPAWN_CLOEXEC_PIPES: create all pipes with the `O_CLOEXEC` flag set.
* Since: 2.40 * Since: 2.40
* @G_SPAWN_CHILD_INHERITS_STDOUT: the child will inherit the parent's standard output.
* Since: 2.74
* @G_SPAWN_CHILD_INHERITS_STDERR: the child will inherit the parent's standard error.
* Since: 2.74
* @G_SPAWN_STDIN_FROM_DEV_NULL: the child's standard input is attached to `/dev/null`.
* Since: 2.74
* *
* Flags passed to g_spawn_sync(), g_spawn_async() and g_spawn_async_with_pipes(). * Flags passed to g_spawn_sync(), g_spawn_async() and g_spawn_async_with_pipes().
*/ */
@ -178,7 +184,34 @@ typedef enum
G_SPAWN_CHILD_INHERITS_STDIN = 1 << 5, G_SPAWN_CHILD_INHERITS_STDIN = 1 << 5,
G_SPAWN_FILE_AND_ARGV_ZERO = 1 << 6, G_SPAWN_FILE_AND_ARGV_ZERO = 1 << 6,
G_SPAWN_SEARCH_PATH_FROM_ENVP = 1 << 7, G_SPAWN_SEARCH_PATH_FROM_ENVP = 1 << 7,
G_SPAWN_CLOEXEC_PIPES = 1 << 8 G_SPAWN_CLOEXEC_PIPES = 1 << 8,
/**
* G_SPAWN_CHILD_INHERITS_STDOUT:
*
* The child will inherit the parent's standard output.
*
* Since: 2.74
*/
G_SPAWN_CHILD_INHERITS_STDOUT = 1 << 9,
/**
* G_SPAWN_CHILD_INHERITS_STDERR:
*
* The child will inherit the parent's standard error.
*
* Since: 2.74
*/
G_SPAWN_CHILD_INHERITS_STDERR = 1 << 10,
/**
* G_SPAWN_STDIN_FROM_DEV_NULL:
*
* The child's standard input is attached to `/dev/null`.
*
* Since: 2.74
*/
G_SPAWN_STDIN_FROM_DEV_NULL = 1 << 11
} GSpawnFlags; } GSpawnFlags;
GLIB_AVAILABLE_IN_ALL GLIB_AVAILABLE_IN_ALL