Imported Upstream version 1.4.19
[platform/upstream/m4.git] / lib / spawn-pipe.c
index 87cebf7..cedd48a 100644 (file)
@@ -1,5 +1,5 @@
 /* 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
 
@@ -67,9 +99,10 @@ nonintr_close (int fd)
 
   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)
 {
@@ -103,34 +136,81 @@ 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)
@@ -147,6 +227,130 @@ create_pipe (const char *progname,
  *
  */
 
+  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);
@@ -154,7 +358,6 @@ create_pipe (const char *progname,
     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;
@@ -182,26 +385,19 @@ create_pipe (const char *progname,
     /* 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;
@@ -224,17 +420,19 @@ create_pipe (const char *progname,
     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)
@@ -320,18 +518,33 @@ create_pipe (const char *progname,
                                                           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)
@@ -340,9 +553,6 @@ create_pipe (const char *progname,
         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]);
@@ -353,8 +563,9 @@ create_pipe (const char *progname,
           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)
@@ -368,6 +579,7 @@ create_pipe (const char *progname,
     close (ofd[0]);
   if (pipe_stdout)
     close (ifd[1]);
+  free (prog_path_to_free);
 
   if (pipe_stdout)
     fd[0] = ifd[0];
@@ -376,6 +588,15 @@ create_pipe (const char *progname,
   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.
@@ -388,12 +609,13 @@ create_pipe (const char *progname,
  */
 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);
@@ -409,13 +631,14 @@ create_pipe_bidi (const char *progname,
  */
 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);
@@ -433,13 +656,14 @@ create_pipe_in (const char *progname,
  */
 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);