/* Creation of subprocesses, communicating via pipes.
- Copyright (C) 2001-2004, 2006-2016 Free Software Foundation, Inc.
+ Copyright (C) 2001-2004, 2006-2021 Free Software Foundation, Inc.
Written by Bruno Haible <haible@clisp.cons.org>, 2001.
This program is free software: you can redistribute it and/or modify
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>. */
+ along with this program. If not, see <https://www.gnu.org/licenses/>. */
+/* Tell clang not to warn about the 'child' variable, below. */
+#if defined __clang__
+# pragma clang diagnostic ignored "-Wconditional-uninitialized"
+#endif
+
#include <config.h>
/* Specification. */
#include <signal.h>
#include <unistd.h>
+#include "canonicalize.h"
#include "error.h"
#include "fatal-signal.h"
+#include "filename.h"
+#include "findprog.h"
#include "unistd-safer.h"
#include "wait-process.h"
+#include "xalloc.h"
#include "gettext.h"
#define _(str) gettext (str)
-#if (((defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__) \
- || defined __KLIBC__)
+
+/* Choice of implementation for native Windows.
+ - Define to 0 to use the posix_spawn facility (modules 'posix_spawn' and
+ 'posix_spawnp'), that is based on the module 'windows-spawn'.
+ - Define to 1 to use the older code, that uses the module 'windows-spawn'
+ directly.
+ You can set this macro from a Makefile or at configure time, from the
+ CPPFLAGS. */
+#ifndef SPAWN_PIPE_IMPL_AVOID_POSIX_SPAWN
+# define SPAWN_PIPE_IMPL_AVOID_POSIX_SPAWN 0
+#endif
+
+
+#if (defined _WIN32 && !defined __CYGWIN__) && SPAWN_PIPE_IMPL_AVOID_POSIX_SPAWN
/* Native Windows API. */
+# if GNULIB_MSVC_NOTHROW
+# include "msvc-nothrow.h"
+# else
+# include <io.h>
+# endif
+# include <process.h>
+# include "windows-spawn.h"
+
+#elif defined __KLIBC__
+
+/* OS/2 kLIBC API. */
# include <process.h>
-# include "w32spawn.h"
+# include "os2-spawn.h"
#else
return retval;
}
+#undef close /* avoid warning related to gnulib module unistd */
#define close nonintr_close
-#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+#if (defined _WIN32 && !defined __CYGWIN__) && SPAWN_PIPE_IMPL_AVOID_POSIX_SPAWN
static int
nonintr_open (const char *pathname, int oflag, mode_t mode)
{
*/
static pid_t
create_pipe (const char *progname,
- const char *prog_path, char **prog_argv,
+ const char *prog_path,
+ const char * const *prog_argv,
+ const char *directory,
bool pipe_stdin, bool pipe_stdout,
const char *prog_stdin, const char *prog_stdout,
bool null_stderr,
bool slave_process, bool exit_on_error,
int fd[2])
{
-#if (((defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__) \
- || defined __KLIBC__)
+ int saved_errno;
+ char *prog_path_to_free = NULL;
+
+ if (directory != NULL)
+ {
+ /* If a change of directory is requested, make sure PROG_PATH is absolute
+ before we do so. This is needed because
+ - posix_spawn and posix_spawnp are required to resolve a relative
+ PROG_PATH *after* changing the directory. See
+ <https://www.austingroupbugs.net/view.php?id=1208>:
+ "if this pathname does not start with a <slash> it shall be
+ interpreted relative to the working directory of the child
+ process _after_ all file_actions have been performed."
+ But this would be a surprising application behaviour, possibly
+ even security relevant.
+ - For the Windows CreateProcess() function, it is unspecified whether
+ a relative file name is interpreted to the parent's current
+ directory or to the specified directory. See
+ <https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa> */
+ if (! IS_ABSOLUTE_FILE_NAME (prog_path))
+ {
+ const char *resolved_prog =
+ find_in_given_path (prog_path, getenv ("PATH"), NULL, false);
+ if (resolved_prog == NULL)
+ goto fail_with_errno;
+ if (resolved_prog != prog_path)
+ prog_path_to_free = (char *) resolved_prog;
+ prog_path = resolved_prog;
+
+ if (! IS_ABSOLUTE_FILE_NAME (prog_path))
+ {
+ char *absolute_prog =
+ canonicalize_filename_mode (prog_path, CAN_MISSING | CAN_NOLINKS);
+ if (absolute_prog == NULL)
+ {
+ free (prog_path_to_free);
+ goto fail_with_errno;
+ }
+ free (prog_path_to_free);
+ prog_path_to_free = absolute_prog;
+ prog_path = absolute_prog;
+
+ if (! IS_ABSOLUTE_FILE_NAME (prog_path))
+ abort ();
+ }
+ }
+ }
+
+#if ((defined _WIN32 && !defined __CYGWIN__) && SPAWN_PIPE_IMPL_AVOID_POSIX_SPAWN) || defined __KLIBC__
/* Native Windows API.
- This uses _pipe(), dup2(), and spawnv(). It could also be implemented
+ This uses _pipe(), dup2(), and _spawnv(). It could also be implemented
using the low-level functions CreatePipe(), DuplicateHandle(),
CreateProcess() and _open_osfhandle(); see the GNU make and GNU clisp
and cvs source code. */
+ char *argv_mem_to_free;
int ifd[2];
int ofd[2];
- int orig_stdin;
- int orig_stdout;
- int orig_stderr;
int child;
int nulloutfd;
int stdinfd;
int stdoutfd;
- int saved_errno;
- /* FIXME: Need to free memory allocated by prepare_spawn. */
- prog_argv = prepare_spawn (prog_argv);
+ const char **argv = prepare_spawn (prog_argv, &argv_mem_to_free);
+ if (argv == NULL)
+ xalloc_die ();
if (pipe_stdout)
if (pipe2_safer (ifd, O_BINARY | O_CLOEXEC) < 0)
*
*/
+ child = -1;
+
+# if (defined _WIN32 && !defined __CYGWIN__) && SPAWN_PIPE_IMPL_AVOID_POSIX_SPAWN
+ bool must_close_ifd1 = pipe_stdout;
+ bool must_close_ofd0 = pipe_stdin;
+
+ /* Create standard file handles of child process. */
+ HANDLE stdin_handle = INVALID_HANDLE_VALUE;
+ HANDLE stdout_handle = INVALID_HANDLE_VALUE;
+ nulloutfd = -1;
+ stdinfd = -1;
+ stdoutfd = -1;
+ if ((!null_stderr
+ || (nulloutfd = open ("NUL", O_RDWR, 0)) >= 0)
+ && (pipe_stdin
+ || prog_stdin == NULL
+ || (stdinfd = open (prog_stdin, O_RDONLY, 0)) >= 0)
+ && (pipe_stdout
+ || prog_stdout == NULL
+ || (stdoutfd = open (prog_stdout, O_WRONLY, 0)) >= 0))
+ /* The child process doesn't inherit ifd[0], ifd[1], ofd[0], ofd[1],
+ but it inherits the three STD*_FILENO for which we pass the handles. */
+ /* Pass the environment explicitly. This is needed if the program has
+ modified the environment using putenv() or [un]setenv(). On Windows,
+ processes have two environments, one in the "environment block" of the
+ process and managed through SetEnvironmentVariable(), and one inside the
+ process, in the location retrieved by the 'environ' macro. If we were
+ to pass NULL, the child process would inherit a copy of the environment
+ block - ignoring the effects of putenv() and [un]setenv(). */
+ {
+ stdin_handle =
+ (HANDLE) _get_osfhandle (pipe_stdin ? ofd[0] :
+ prog_stdin == NULL ? STDIN_FILENO : stdinfd);
+ if (pipe_stdin)
+ {
+ HANDLE curr_process = GetCurrentProcess ();
+ HANDLE duplicate;
+ if (!DuplicateHandle (curr_process, stdin_handle,
+ curr_process, &duplicate,
+ 0, TRUE, DUPLICATE_SAME_ACCESS))
+ {
+ errno = EBADF; /* arbitrary */
+ goto failed;
+ }
+ must_close_ofd0 = false;
+ close (ofd[0]); /* implies CloseHandle (stdin_handle); */
+ stdin_handle = duplicate;
+ }
+ stdout_handle =
+ (HANDLE) _get_osfhandle (pipe_stdout ? ifd[1] :
+ prog_stdout == NULL ? STDOUT_FILENO : stdoutfd);
+ if (pipe_stdout)
+ {
+ HANDLE curr_process = GetCurrentProcess ();
+ HANDLE duplicate;
+ if (!DuplicateHandle (curr_process, stdout_handle,
+ curr_process, &duplicate,
+ 0, TRUE, DUPLICATE_SAME_ACCESS))
+ {
+ errno = EBADF; /* arbitrary */
+ goto failed;
+ }
+ must_close_ifd1 = false;
+ close (ifd[1]); /* implies CloseHandle (stdout_handle); */
+ stdout_handle = duplicate;
+ }
+ HANDLE stderr_handle =
+ (HANDLE) _get_osfhandle (null_stderr ? nulloutfd : STDERR_FILENO);
+
+ child = spawnpvech (P_NOWAIT, prog_path, argv + 1,
+ (const char * const *) environ, directory,
+ stdin_handle, stdout_handle, stderr_handle);
+# if 0 /* Executing arbitrary files as shell scripts is unsecure. */
+ if (child == -1 && errno == ENOEXEC)
+ {
+ /* prog is not a native executable. Try to execute it as a
+ shell script. Note that prepare_spawn() has already prepended
+ a hidden element "sh.exe" to argv. */
+ argv[1] = prog_path;
+ child = spawnpvech (P_NOWAIT, argv[0], argv,
+ (const char * const *) environ, directory,
+ stdin_handle, stdout_handle, stderr_handle);
+ }
+# endif
+ }
+ failed:
+ if (child == -1)
+ saved_errno = errno;
+ if (stdinfd >= 0)
+ close (stdinfd);
+ if (stdoutfd >= 0)
+ close (stdoutfd);
+ if (nulloutfd >= 0)
+ close (nulloutfd);
+
+ if (pipe_stdin)
+ {
+ if (must_close_ofd0)
+ close (ofd[0]);
+ else
+ if (stdin_handle != INVALID_HANDLE_VALUE)
+ CloseHandle (stdin_handle);
+ }
+ if (pipe_stdout)
+ {
+ if (must_close_ifd1)
+ close (ifd[1]);
+ else
+ if (stdout_handle != INVALID_HANDLE_VALUE)
+ CloseHandle (stdout_handle);
+ }
+
+# else /* __KLIBC__ */
+ if (!(directory == NULL || strcmp (directory, ".") == 0))
+ {
+ /* A directory argument is not supported in this implementation. */
+ saved_errno = EINVAL;
+ goto fail_with_saved_errno;
+ }
+
+ int orig_stdin;
+ int orig_stdout;
+ int orig_stderr;
+
/* Save standard file handles of parent process. */
if (pipe_stdin || prog_stdin != NULL)
orig_stdin = dup_safer_noinherit (STDIN_FILENO);
orig_stdout = dup_safer_noinherit (STDOUT_FILENO);
if (null_stderr)
orig_stderr = dup_safer_noinherit (STDERR_FILENO);
- child = -1;
/* Create standard file handles of child process. */
nulloutfd = -1;
/* The child process doesn't inherit ifd[0], ifd[1], ofd[0], ofd[1],
but it inherits all open()ed or dup2()ed file handles (which is what
we want in the case of STD*_FILENO). */
- /* Use spawnvpe and pass the environment explicitly. This is needed if
- the program has modified the environment using putenv() or [un]setenv().
- On Windows, programs have two environments, one in the "environment
- block" of the process and managed through SetEnvironmentVariable(), and
- one inside the process, in the location retrieved by the 'environ'
- macro. When using spawnvp() without 'e', the child process inherits a
- copy of the environment block - ignoring the effects of putenv() and
- [un]setenv(). */
{
- child = spawnvpe (P_NOWAIT, prog_path, (const char **) prog_argv,
- (const char **) environ);
- if (child < 0 && errno == ENOEXEC)
+ child = _spawnvpe (P_NOWAIT, prog_path, argv + 1,
+ (const char **) environ);
+# if 0 /* Executing arbitrary files as shell scripts is unsecure. */
+ if (child == -1 && errno == ENOEXEC)
{
/* prog is not a native executable. Try to execute it as a
shell script. Note that prepare_spawn() has already prepended
- a hidden element "sh.exe" to prog_argv. */
- --prog_argv;
- child = spawnvpe (P_NOWAIT, prog_argv[0], (const char **) prog_argv,
- (const char **) environ);
+ a hidden element "sh.exe" to argv. */
+ child = _spawnvpe (P_NOWAIT, argv[0], argv,
+ (const char **) environ);
}
+# endif
}
if (child == -1)
saved_errno = errno;
close (ofd[0]);
if (pipe_stdout)
close (ifd[1]);
+# endif
+
+ free (argv);
+ free (argv_mem_to_free);
+ free (prog_path_to_free);
+
if (child == -1)
{
- if (exit_on_error || !null_stderr)
- error (exit_on_error ? EXIT_FAILURE : 0, saved_errno,
- _("%s subprocess failed"), progname);
if (pipe_stdout)
close (ifd[0]);
if (pipe_stdin)
close (ofd[1]);
- errno = saved_errno;
- return -1;
+ goto fail_with_saved_errno;
}
if (pipe_stdout)
prog_stdout, O_WRONLY,
0))
!= 0)
+ || (directory != NULL
+ && (err = posix_spawn_file_actions_addchdir (&actions,
+ directory)))
|| (slave_process
&& ((err = posix_spawnattr_init (&attrs)) != 0
|| (attrs_allocated = true,
+# if defined _WIN32 && !defined __CYGWIN__
+ (err = posix_spawnattr_setpgroup (&attrs, 0)) != 0
+ || (err = posix_spawnattr_setflags (&attrs,
+ POSIX_SPAWN_SETPGROUP))
+ != 0
+# else
(err = posix_spawnattr_setsigmask (&attrs,
&blocked_signals))
!= 0
|| (err = posix_spawnattr_setflags (&attrs,
POSIX_SPAWN_SETSIGMASK))
- != 0)))
- || (err = posix_spawnp (&child, prog_path, &actions,
- attrs_allocated ? &attrs : NULL, prog_argv,
- environ))
+ != 0
+# endif
+ ) ) )
+ || (err = (directory != NULL
+ ? posix_spawn (&child, prog_path, &actions,
+ attrs_allocated ? &attrs : NULL,
+ (char * const *) prog_argv, environ)
+ : posix_spawnp (&child, prog_path, &actions,
+ attrs_allocated ? &attrs : NULL,
+ (char * const *) prog_argv, environ)))
!= 0))
{
if (actions_allocated)
posix_spawnattr_destroy (&attrs);
if (slave_process)
unblock_fatal_signals ();
- if (exit_on_error || !null_stderr)
- error (exit_on_error ? EXIT_FAILURE : 0, err,
- _("%s subprocess failed"), progname);
if (pipe_stdout)
{
close (ifd[0]);
close (ofd[0]);
close (ofd[1]);
}
- errno = err;
- return -1;
+ free (prog_path_to_free);
+ saved_errno = err;
+ goto fail_with_saved_errno;
}
posix_spawn_file_actions_destroy (&actions);
if (attrs_allocated)
close (ofd[0]);
if (pipe_stdout)
close (ifd[1]);
+ free (prog_path_to_free);
if (pipe_stdout)
fd[0] = ifd[0];
return child;
#endif
+
+ fail_with_errno:
+ saved_errno = errno;
+ fail_with_saved_errno:
+ if (exit_on_error || !null_stderr)
+ error (exit_on_error ? EXIT_FAILURE : 0, saved_errno,
+ _("%s subprocess failed"), progname);
+ errno = saved_errno;
+ return -1;
}
/* Open a bidirectional pipe.
*/
pid_t
create_pipe_bidi (const char *progname,
- const char *prog_path, char **prog_argv,
+ const char *prog_path, const char * const *prog_argv,
+ const char *directory,
bool null_stderr,
bool slave_process, bool exit_on_error,
int fd[2])
{
- pid_t result = create_pipe (progname, prog_path, prog_argv,
+ pid_t result = create_pipe (progname, prog_path, prog_argv, directory,
true, true, NULL, NULL,
null_stderr, slave_process, exit_on_error,
fd);
*/
pid_t
create_pipe_in (const char *progname,
- const char *prog_path, char **prog_argv,
+ const char *prog_path, const char * const *prog_argv,
+ const char *directory,
const char *prog_stdin, bool null_stderr,
bool slave_process, bool exit_on_error,
int fd[1])
{
int iofd[2];
- pid_t result = create_pipe (progname, prog_path, prog_argv,
+ pid_t result = create_pipe (progname, prog_path, prog_argv, directory,
false, true, prog_stdin, NULL,
null_stderr, slave_process, exit_on_error,
iofd);
*/
pid_t
create_pipe_out (const char *progname,
- const char *prog_path, char **prog_argv,
+ const char *prog_path, const char * const *prog_argv,
+ const char *directory,
const char *prog_stdout, bool null_stderr,
bool slave_process, bool exit_on_error,
int fd[1])
{
int iofd[2];
- pid_t result = create_pipe (progname, prog_path, prog_argv,
+ pid_t result = create_pipe (progname, prog_path, prog_argv, directory,
true, false, NULL, prog_stdout,
null_stderr, slave_process, exit_on_error,
iofd);