Bump to m4 1.4.19
[platform/upstream/m4.git] / lib / execute.c
index 554d3f0..472b171 100644 (file)
@@ -1,5 +1,5 @@
 /* Creation of autonomous subprocesses.
-   Copyright (C) 2001-2004, 2006-2013 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
@@ -13,7 +13,7 @@
    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/>.  */
 
 
 #include <config.h>
 #include <signal.h>
 #include <unistd.h>
 
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "canonicalize.h"
 #include "error.h"
 #include "fatal-signal.h"
+#include "filename.h"
+#include "findprog.h"
 #include "wait-process.h"
+#include "xalloc.h"
 #include "gettext.h"
 
 #define _(str) gettext (str)
 
-#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+
+/* 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 EXECUTE_IMPL_AVOID_POSIX_SPAWN
+# define EXECUTE_IMPL_AVOID_POSIX_SPAWN 0
+#endif
+
+
+#if (defined _WIN32 && !defined __CYGWIN__) && EXECUTE_IMPL_AVOID_POSIX_SPAWN
 
 /* Native Windows API.  */
+# if GNULIB_MSVC_NOTHROW
+#  include "msvc-nothrow.h"
+# else
+#  include <io.h>
+# endif
 # include <process.h>
-# include "w32spawn.h"
+# include "windows-spawn.h"
 
 #else
 
 
 #endif
 
-/* The results of open() in this file are not used with fchdir,
-   therefore save some unnecessary work in fchdir.c.  */
-#undef open
-#undef close
 
-
-#if defined EINTR && ((defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__)
+#if defined EINTR && (defined _WIN32 && !defined __CYGWIN__) && EXECUTE_IMPL_AVOID_POSIX_SPAWN
 
 /* EINTR handling for close(), open().
    These functions can return -1/EINTR even though we don't have any
@@ -71,6 +91,7 @@ nonintr_close (int fd)
 
   return retval;
 }
+#undef close /* avoid warning related to gnulib module unistd */
 #define close nonintr_close
 
 static int
@@ -90,104 +111,138 @@ nonintr_open (const char *pathname, int oflag, mode_t mode)
 #endif
 
 
-/* Execute a command, optionally redirecting any of the three standard file
-   descriptors to /dev/null.  Return its exit code.
-   If it didn't terminate correctly, exit if exit_on_error is true, otherwise
-   return 127.
-   If slave_process is true, the child process will be terminated when its
-   creator receives a catchable fatal signal.  */
 int
 execute (const char *progname,
-         const char *prog_path, char **prog_argv,
+         const char *prog_path, const char * const *prog_argv,
+         const char *directory,
          bool ignore_sigpipe,
          bool null_stdin, bool null_stdout, bool null_stderr,
          bool slave_process, bool exit_on_error,
          int *termsigp)
 {
-#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+  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__) && EXECUTE_IMPL_AVOID_POSIX_SPAWN
 
   /* Native Windows API.  */
-  int orig_stdin;
-  int orig_stdout;
-  int orig_stderr;
-  int exitcode;
-  int nullinfd;
-  int nulloutfd;
-
-  /* FIXME: Need to free memory allocated by prepare_spawn.  */
-  prog_argv = prepare_spawn (prog_argv);
-
-  /* Save standard file handles of parent process.  */
-  if (null_stdin)
-    orig_stdin = dup_safer_noinherit (STDIN_FILENO);
-  if (null_stdout)
-    orig_stdout = dup_safer_noinherit (STDOUT_FILENO);
-  if (null_stderr)
-    orig_stderr = dup_safer_noinherit (STDERR_FILENO);
-  exitcode = -1;
+
+  char *argv_mem_to_free;
+
+  const char **argv = prepare_spawn (prog_argv, &argv_mem_to_free);
+  if (argv == NULL)
+    xalloc_die ();
+
+  int exitcode = -1;
 
   /* Create standard file handles of child process.  */
-  nullinfd = -1;
-  nulloutfd = -1;
+  int nullinfd = -1;
+  int nulloutfd = -1;
   if ((!null_stdin
-       || ((nullinfd = open ("NUL", O_RDONLY, 0)) >= 0
-           && (nullinfd == STDIN_FILENO
-               || (dup2 (nullinfd, STDIN_FILENO) >= 0
-                   && close (nullinfd) >= 0))))
+       || (nullinfd = open ("NUL", O_RDONLY, 0)) >= 0)
       && (!(null_stdout || null_stderr)
-          || ((nulloutfd = open ("NUL", O_RDWR, 0)) >= 0
-              && (!null_stdout
-                  || nulloutfd == STDOUT_FILENO
-                  || dup2 (nulloutfd, STDOUT_FILENO) >= 0)
-              && (!null_stderr
-                  || nulloutfd == STDERR_FILENO
-                  || dup2 (nulloutfd, STDERR_FILENO) >= 0)
-              && ((null_stdout && nulloutfd == STDOUT_FILENO)
-                  || (null_stderr && nulloutfd == STDERR_FILENO)
-                  || close (nulloutfd) >= 0))))
-    /* 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().  */
+          || (nulloutfd = open ("NUL", O_RDWR, 0)) >= 0))
+    /* 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().  */
     {
-      exitcode = spawnvpe (P_WAIT, prog_path, (const char **) prog_argv,
-                           (const char **) environ);
-      if (exitcode < 0 && errno == ENOEXEC)
+      HANDLE stdin_handle =
+        (HANDLE) _get_osfhandle (null_stdin ? nullinfd : STDIN_FILENO);
+      HANDLE stdout_handle =
+        (HANDLE) _get_osfhandle (null_stdout ? nulloutfd : STDOUT_FILENO);
+      HANDLE stderr_handle =
+        (HANDLE) _get_osfhandle (null_stderr ? nulloutfd : STDERR_FILENO);
+
+      exitcode = spawnpvech (P_WAIT, 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 (exitcode == -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;
-          exitcode = spawnvpe (P_WAIT, prog_argv[0], (const char **) prog_argv,
-                               (const char **) environ);
+             a hidden element "sh.exe" to argv.  */
+          argv[1] = prog_path;
+          exitcode = spawnpvech (P_WAIT, argv[0], argv,
+                                 (const char * const *) environ, directory,
+                                 stdin_handle, stdout_handle, stderr_handle);
         }
+# endif
     }
+  if (exitcode == -1)
+    saved_errno = errno;
   if (nulloutfd >= 0)
     close (nulloutfd);
   if (nullinfd >= 0)
     close (nullinfd);
+  free (argv);
+  free (argv_mem_to_free);
+  free (prog_path_to_free);
 
-  /* Restore standard file handles of parent process.  */
-  if (null_stderr)
-    undup_safer_noinherit (orig_stderr, STDERR_FILENO);
-  if (null_stdout)
-    undup_safer_noinherit (orig_stdout, STDOUT_FILENO);
-  if (null_stdin)
-    undup_safer_noinherit (orig_stdin, STDIN_FILENO);
-
+  /* Treat failure and signalled child processes like wait_subprocess()
+     does.  */
   if (termsigp != NULL)
     *termsigp = 0;
 
   if (exitcode == -1)
+    goto fail_with_saved_errno;
+
+  if (WIFSIGNALED (exitcode))
     {
-      if (exit_on_error || !null_stderr)
-        error (exit_on_error ? EXIT_FAILURE : 0, errno,
-               _("%s subprocess failed"), progname);
-      return 127;
+      if (termsigp != NULL)
+        *termsigp = WTERMSIG (exitcode);
+      saved_errno = 0;
+      goto fail_with_saved_errno;
     }
 
   return exitcode;
@@ -235,6 +290,10 @@ execute (const char *progname,
                                                           "/dev/null", O_RDWR,
                                                           0))
                  != 0)
+          || (directory != NULL
+              && (err = posix_spawn_file_actions_addchdir (&actions,
+                                                           directory)))
+# if !(defined _WIN32 && !defined __CYGWIN__)
           || (slave_process
               && ((err = posix_spawnattr_init (&attrs)) != 0
                   || (attrs_allocated = true,
@@ -244,9 +303,14 @@ execute (const char *progname,
                       || (err = posix_spawnattr_setflags (&attrs,
                                                         POSIX_SPAWN_SETSIGMASK))
                          != 0)))
-          || (err = posix_spawnp (&child, prog_path, &actions,
-                                  attrs_allocated ? &attrs : NULL, prog_argv,
-                                  environ))
+# 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)
@@ -255,12 +319,11 @@ execute (const char *progname,
         posix_spawnattr_destroy (&attrs);
       if (slave_process)
         unblock_fatal_signals ();
+      free (prog_path_to_free);
       if (termsigp != NULL)
         *termsigp = 0;
-      if (exit_on_error || !null_stderr)
-        error (exit_on_error ? EXIT_FAILURE : 0, err,
-               _("%s subprocess failed"), progname);
-      return 127;
+      saved_errno = err;
+      goto fail_with_saved_errno;
     }
   posix_spawn_file_actions_destroy (&actions);
   if (attrs_allocated)
@@ -270,9 +333,18 @@ execute (const char *progname,
       register_slave_subprocess (child);
       unblock_fatal_signals ();
     }
+  free (prog_path_to_free);
 
   return wait_subprocess (child, progname, ignore_sigpipe, null_stderr,
                           slave_process, exit_on_error, termsigp);
 
 #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);
+  return 127;
 }