It has been noted [1] that when spawning a child process, it is useful to be
able to specify in which directory to execute this child process.

The "obvious" solution, to do a chdir() before spawning the child process
and chdir() or fchdir() back afterwards, has two drawbacks:
  - It is not multithread-safe.
  - It interferes with the search of the program in $PATH, if $PATH contains
    non-absolute directories (which is a bad practice for security reasons
    but not forbidden).

This patch adds a 'directory' parameter to the functions 'execute' and
create_pipe_*.

I need this, in particular, in order to complete the Ruby support for
GNU gettext.

Note the added NEWS entries:

Date        Modules         Changes

2020-12-02  spawn-pipe      The functions 'create_pipe_out', 'create_pipe_in',
                            'create_pipe_bidi' now take a 4th argument
                            'const char *directory'. To maintain the previous
                            behaviour, insert NULL as additional 4th argument.

2020-12-02  execute         The function 'execute' now takes a 4th argument
                            'const char *directory'. To maintain the previous
                            behaviour, insert NULL as additional 4th argument.


[1] https://www.austingroupbugs.net/view.php?id=1208


2020-12-02  Bruno Haible  <br...@clisp.org>

        spawn-pipe: Allow caller to specify directory for the subprocess.
        * lib/spawn-pipe.h (create_pipe_out, create_pipe_in, create_pipe_bidi):
        Add directory argument.
        * lib/spawn-pipe.c: Include canonicalize.h, filename.h, findprog.h.
        (create_pipe): Add directory argument. If specified, resolve the program
        file name and make it absolute, first. Pass the directory to spawnpvech
        and posix_spawn_file_actions_addchdir.
        (create_pipe_bidi, create_pipe_in, create_pipe_out): Add directory
        argument.
        * modules/spawn-pipe (Depends-on): Add canonicalize, filename,
        findprog-in, posix_spawn, posix_spawn_file_actions_addchdir.
        * tests/test-spawn-pipe-main.c (test_pipe): Update.
        * NEWS: Mention the change.
        * lib/csharpcomp.c (compile_csharp_using_mono,
        compile_csharp_using_sscli): Update.
        * lib/javacomp.c (is_envjavac_gcj, is_envjavac_gcj43, is_gcj_present,
        is_gcj_43): Update.
        * lib/javaversion.c (execute_and_read_line): Update.
        * lib/pipe-filter-gi.c (pipe_filter_gi_create): Update.
        * lib/pipe-filter-ii.c (pipe_filter_ii_execute): Update.

2020-12-02  Bruno Haible  <br...@clisp.org>

        execute: Allow caller to specify directory for the subprocess.
        * lib/execute.h (execute): Add directory argument.
        * lib/execute.c: Include canonicalize.h, filename.h, findprog.h.
        (execute): Add directory argument. If specified, resolve the program
        file name and make it absolute, first. Pass the directory to spawnpvech
        and posix_spawn_file_actions_addchdir.
        * modules/execute (Depends-on): Add canonicalize, filename, findprog-in,
        posix_spawn, posix_spawn_file_actions_addchdir.
        * tests/test-execute-main.c: Add test for passing a directory.
        * tests/test-execute-child.c: Likewise.
        * tests/test-execute.sh: Update.
        * modules/execute-tests (Depends-on): Add mkdir.
        * NEWS: Mention the change.
        * lib/csharpcomp.c (compile_csharp_using_sscli): Update.
        * lib/csharpexec.c (execute_csharp_using_mono,
        execute_csharp_using_sscli): Update.
        * lib/javacomp.c (compile_using_envjavac, compile_using_gcj,
        compile_using_javac, compile_using_jikes, is_javac_present,
        is_jikes_present): Update.
        * lib/javaexec.c (execute_java_class): Update.

>From 702cba00f4ff7b22f0684a23db0fb66aea2c4086 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 2 Dec 2020 17:44:04 +0100
Subject: [PATCH 1/2] execute: Allow caller to specify directory for the
 subprocess.

* lib/execute.h (execute): Add directory argument.
* lib/execute.c: Include canonicalize.h, filename.h, findprog.h.
(execute): Add directory argument. If specified, resolve the program
file name and make it absolute, first. Pass the directory to spawnpvech
and posix_spawn_file_actions_addchdir.
* modules/execute (Depends-on): Add canonicalize, filename, findprog-in,
posix_spawn, posix_spawn_file_actions_addchdir.
* tests/test-execute-main.c: Add test for passing a directory.
* tests/test-execute-child.c: Likewise.
* tests/test-execute.sh: Update.
* modules/execute-tests (Depends-on): Add mkdir.
* NEWS: Mention the change.
* lib/csharpcomp.c (compile_csharp_using_sscli): Update.
* lib/csharpexec.c (execute_csharp_using_mono,
execute_csharp_using_sscli): Update.
* lib/javacomp.c (compile_using_envjavac, compile_using_gcj,
compile_using_javac, compile_using_jikes, is_javac_present,
is_jikes_present): Update.
* lib/javaexec.c (execute_java_class): Update.
---
 ChangeLog                  | 23 +++++++++++
 NEWS                       |  4 ++
 lib/csharpcomp.c           |  3 +-
 lib/csharpexec.c           |  6 ++-
 lib/execute.c              | 98 ++++++++++++++++++++++++++++++++++++++--------
 lib/execute.h              |  4 ++
 lib/javacomp.c             | 22 +++++++----
 lib/javaexec.c             | 12 ++++--
 modules/execute            |  5 +++
 modules/execute-tests      |  1 +
 tests/test-execute-child.c | 20 +++++++++-
 tests/test-execute-main.c  | 66 ++++++++++++++++++++-----------
 tests/test-execute.sh      |  2 +-
 13 files changed, 210 insertions(+), 56 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 8e9e032..b5ea083 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,26 @@
+2020-12-02  Bruno Haible  <br...@clisp.org>
+
+	execute: Allow caller to specify directory for the subprocess.
+	* lib/execute.h (execute): Add directory argument.
+	* lib/execute.c: Include canonicalize.h, filename.h, findprog.h.
+	(execute): Add directory argument. If specified, resolve the program
+	file name and make it absolute, first. Pass the directory to spawnpvech
+	and posix_spawn_file_actions_addchdir.
+	* modules/execute (Depends-on): Add canonicalize, filename, findprog-in,
+	posix_spawn, posix_spawn_file_actions_addchdir.
+	* tests/test-execute-main.c: Add test for passing a directory.
+	* tests/test-execute-child.c: Likewise.
+	* tests/test-execute.sh: Update.
+	* modules/execute-tests (Depends-on): Add mkdir.
+	* NEWS: Mention the change.
+	* lib/csharpcomp.c (compile_csharp_using_sscli): Update.
+	* lib/csharpexec.c (execute_csharp_using_mono,
+	execute_csharp_using_sscli): Update.
+	* lib/javacomp.c (compile_using_envjavac, compile_using_gcj,
+	compile_using_javac, compile_using_jikes, is_javac_present,
+	is_jikes_present): Update.
+	* lib/javaexec.c (execute_java_class): Update.
+
 2020-12-01  Bruno Haible  <br...@clisp.org>
 
 	vma-iter: Add support for macOS11/arm64.
diff --git a/NEWS b/NEWS
index d76e3e7..445266d 100644
--- a/NEWS
+++ b/NEWS
@@ -60,6 +60,10 @@ User visible incompatible changes
 
 Date        Modules         Changes
 
+2020-12-02  execute         The function 'execute' now takes a 4th argument
+                            'const char *directory'. To maintain the previous
+                            behaviour, insert NULL as additional 4th argument.
+
 2020-10-16  hash            This module deprecates the 'hash_delete' function
                             using gcc's "deprecated" attribute.  Use the better-
                             named 'hash_remove' equivalent.
diff --git a/lib/csharpcomp.c b/lib/csharpcomp.c
index fac9a89..cf68c5a 100644
--- a/lib/csharpcomp.c
+++ b/lib/csharpcomp.c
@@ -378,7 +378,8 @@ compile_csharp_using_sscli (const char * const *sources,
           free (command);
         }
 
-      exitstatus = execute ("csc", "csc", argv, false, false, false, false,
+      exitstatus = execute ("csc", "csc", argv, NULL,
+                            false, false, false, false,
                             true, true, NULL);
 
       for (i = 2; i < 3 + libdirs_count + libraries_count; i++)
diff --git a/lib/csharpexec.c b/lib/csharpexec.c
index b54f14e..5e2aa2c 100644
--- a/lib/csharpexec.c
+++ b/lib/csharpexec.c
@@ -106,7 +106,8 @@ execute_csharp_using_mono (const char *assembly_path,
       argv[0] = "mono";
       argv[1] = "--version";
       argv[2] = NULL;
-      exitstatus = execute ("mono", "mono", argv, false, false, true, true,
+      exitstatus = execute ("mono", "mono", argv, NULL,
+                            false, false, true, true,
                             true, false, NULL);
       mono_present = (exitstatus == 0);
       mono_tested = true;
@@ -167,7 +168,8 @@ execute_csharp_using_sscli (const char *assembly_path,
 
       argv[0] = "clix";
       argv[1] = NULL;
-      exitstatus = execute ("clix", "clix", argv, false, false, true, true,
+      exitstatus = execute ("clix", "clix", argv, NULL,
+                            false, false, true, true,
                             true, false, NULL);
       clix_present = (exitstatus == 0 || exitstatus == 1);
       clix_tested = true;
diff --git a/lib/execute.c b/lib/execute.c
index 15556cf..03fb6ec 100644
--- a/lib/execute.c
+++ b/lib/execute.c
@@ -28,8 +28,11 @@
 #include <signal.h>
 #include <unistd.h>
 
+#include "canonicalize.h"
 #include "error.h"
 #include "fatal-signal.h"
+#include "filename.h"
+#include "findprog.h"
 #include "wait-process.h"
 #include "gettext.h"
 
@@ -94,17 +97,66 @@ nonintr_open (const char *pathname, int oflag, mode_t mode)
 int
 execute (const char *progname,
          const char *prog_path, char **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)
 {
+  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"), 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)
+                {
+                  saved_errno = errno;
+                  free (prog_path_to_free);
+                  goto fail_with_saved_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__
 
   /* Native Windows API.  */
 
-  int saved_errno;
-
   /* FIXME: Need to free memory allocated by prepare_spawn.  */
   prog_argv = prepare_spawn (prog_argv);
 
@@ -133,16 +185,17 @@ execute (const char *progname,
         (HANDLE) _get_osfhandle (null_stderr ? nulloutfd : STDERR_FILENO);
 
       exitcode = spawnpvech (P_WAIT, prog_path, (const char **) prog_argv,
-                             (const char **) environ, NULL,
+                             (const char **) environ, directory,
                              stdin_handle, stdout_handle, stderr_handle);
       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[0] = prog_path;
           --prog_argv;
           exitcode = spawnpvech (P_WAIT, prog_argv[0], (const char **) prog_argv,
-                                 (const char **) environ, NULL,
+                                 (const char **) environ, directory,
                                  stdin_handle, stdout_handle, stderr_handle);
         }
     }
@@ -152,17 +205,13 @@ execute (const char *progname,
     close (nulloutfd);
   if (nullinfd >= 0)
     close (nullinfd);
+  free (prog_path_to_free);
 
   if (termsigp != NULL)
     *termsigp = 0;
 
   if (exitcode == -1)
-    {
-      if (exit_on_error || !null_stderr)
-        error (exit_on_error ? EXIT_FAILURE : 0, saved_errno,
-               _("%s subprocess failed"), progname);
-      return 127;
-    }
+    goto fail_with_saved_errno;
 
   return exitcode;
 
@@ -209,6 +258,9 @@ execute (const char *progname,
                                                           "/dev/null", O_RDWR,
                                                           0))
                  != 0)
+          || (directory != NULL
+              && (err = posix_spawn_file_actions_addchdir (&actions,
+                                                           directory)))
           || (slave_process
               && ((err = posix_spawnattr_init (&attrs)) != 0
                   || (attrs_allocated = true,
@@ -218,9 +270,13 @@ 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))
+          || (err = (directory != NULL
+                     ? posix_spawn (&child, prog_path, &actions,
+                                    attrs_allocated ? &attrs : NULL, prog_argv,
+                                    environ)
+                     : posix_spawnp (&child, prog_path, &actions,
+                                     attrs_allocated ? &attrs : NULL, prog_argv,
+                                     environ)))
              != 0))
     {
       if (actions_allocated)
@@ -229,12 +285,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)
@@ -244,9 +299,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;
 }
diff --git a/lib/execute.h b/lib/execute.h
index b31c4d1..34fc989 100644
--- a/lib/execute.h
+++ b/lib/execute.h
@@ -32,6 +32,9 @@
    prog_argv is the array of strings that the subprocess shall receive in
    argv[].  It is a NULL-terminated array.  prog_argv[0] should normally be
    identical to prog_path.
+   If directory is not NULL, the command is executed in that directory.  If
+   prog_path is a relative file name, it resolved before changing to that
+   directory.  The current directory of the current process remains unchanged.
    If ignore_sigpipe is true, consider a subprocess termination due to SIGPIPE
    as equivalent to a success.  This is suitable for processes whose only
    purpose is to write to standard output.
@@ -44,6 +47,7 @@
    is called.  See spawn-pipe.h for the reason.  */
 extern int execute (const char *progname,
                     const char *prog_path, char **prog_argv,
+                    const char *directory,
                     bool ignore_sigpipe,
                     bool null_stdin, bool null_stdout, bool null_stderr,
                     bool slave_process, bool exit_on_error,
diff --git a/lib/javacomp.c b/lib/javacomp.c
index 63efc2d..fec5180 100644
--- a/lib/javacomp.c
+++ b/lib/javacomp.c
@@ -343,8 +343,9 @@ compile_using_envjavac (const char *javac,
   argv[1] = "-c";
   argv[2] = command;
   argv[3] = NULL;
-  exitstatus = execute (javac, BOURNE_SHELL, argv, false, false, false,
-                        null_stderr, true, true, NULL);
+  exitstatus = execute (javac, BOURNE_SHELL, argv, NULL,
+                        false, false, false, null_stderr,
+                        true, true, NULL);
   err = (exitstatus != 0);
 
   freea (command);
@@ -425,7 +426,8 @@ compile_using_gcj (const char * const *java_sources,
       free (command);
     }
 
-  exitstatus = execute ("gcj", "gcj", argv, false, false, false, null_stderr,
+  exitstatus = execute ("gcj", "gcj", argv, NULL,
+                        false, false, false, null_stderr,
                         true, true, NULL);
   err = (exitstatus != 0);
 
@@ -496,7 +498,8 @@ compile_using_javac (const char * const *java_sources,
       free (command);
     }
 
-  exitstatus = execute ("javac", "javac", argv, false, false, false,
+  exitstatus = execute ("javac", "javac", argv, NULL,
+                        false, false, false,
                         null_stderr, true, true, NULL);
   err = (exitstatus != 0);
 
@@ -551,8 +554,9 @@ compile_using_jikes (const char * const *java_sources,
       free (command);
     }
 
-  exitstatus = execute ("jikes", "jikes", argv, false, false, false,
-                        null_stderr, true, true, NULL);
+  exitstatus = execute ("jikes", "jikes", argv, NULL,
+                        false, false, false, null_stderr,
+                        true, true, NULL);
   err = (exitstatus != 0);
 
   freea (argv);
@@ -1872,7 +1876,8 @@ is_javac_present (void)
 
       argv[0] = "javac";
       argv[1] = NULL;
-      exitstatus = execute ("javac", "javac", argv, false, false, true, true,
+      exitstatus = execute ("javac", "javac", argv, NULL,
+                            false, false, true, true,
                             true, false, NULL);
       javac_present = (exitstatus == 0 || exitstatus == 1 || exitstatus == 2);
       javac_tested = true;
@@ -2138,7 +2143,8 @@ is_jikes_present (void)
 
       argv[0] = "jikes";
       argv[1] = NULL;
-      exitstatus = execute ("jikes", "jikes", argv, false, false, true, true,
+      exitstatus = execute ("jikes", "jikes", argv, NULL,
+                            false, false, true, true,
                             true, false, NULL);
       jikes_present = (exitstatus == 0 || exitstatus == 1);
       jikes_tested = true;
diff --git a/lib/javaexec.c b/lib/javaexec.c
index a374bff..d486fb8 100644
--- a/lib/javaexec.c
+++ b/lib/javaexec.c
@@ -208,7 +208,8 @@ execute_java_class (const char *class_name,
         argv[0] = "gij";
         argv[1] = "--version";
         argv[2] = NULL;
-        exitstatus = execute ("gij", "gij", argv, false, false, true, true,
+        exitstatus = execute ("gij", "gij", argv, NULL,
+                              false, false, true, true,
                               true, false, NULL);
         gij_present = (exitstatus == 0);
         gij_tested = true;
@@ -261,7 +262,8 @@ execute_java_class (const char *class_name,
         argv[0] = "java";
         argv[1] = "-version";
         argv[2] = NULL;
-        exitstatus = execute ("java", "java", argv, false, false, true, true,
+        exitstatus = execute ("java", "java", argv, NULL,
+                              false, false, true, true,
                               true, false, NULL);
         java_present = (exitstatus == 0);
         java_tested = true;
@@ -315,7 +317,8 @@ execute_java_class (const char *class_name,
 
         argv[0] = "jre";
         argv[1] = NULL;
-        exitstatus = execute ("jre", "jre", argv, false, false, true, true,
+        exitstatus = execute ("jre", "jre", argv, NULL,
+                              false, false, true, true,
                               true, false, NULL);
         jre_present = (exitstatus == 0 || exitstatus == 1);
         jre_tested = true;
@@ -372,7 +375,8 @@ execute_java_class (const char *class_name,
         argv[0] = "jview";
         argv[1] = "-?";
         argv[2] = NULL;
-        exitstatus = execute ("jview", "jview", argv, false, false, true, true,
+        exitstatus = execute ("jview", "jview", argv, NULL,
+                              false, false, true, true,
                               true, false, NULL);
         jview_present = (exitstatus == 0 || exitstatus == 1);
         jview_tested = true;
diff --git a/modules/execute b/modules/execute
index 52eee5f..cb04003 100644
--- a/modules/execute
+++ b/modules/execute
@@ -8,15 +8,20 @@ m4/execute.m4
 
 Depends-on:
 dup2
+canonicalize
 environ
 error
 fatal-signal
+filename
+findprog-in
 msvc-nothrow
 gettext-h
 spawn
+posix_spawn
 posix_spawnp
 posix_spawn_file_actions_init
 posix_spawn_file_actions_addopen
+posix_spawn_file_actions_addchdir
 posix_spawn_file_actions_destroy
 posix_spawnattr_init
 posix_spawnattr_setsigmask
diff --git a/modules/execute-tests b/modules/execute-tests
index 854b1ad..9562182 100644
--- a/modules/execute-tests
+++ b/modules/execute-tests
@@ -7,6 +7,7 @@ tests/macros.h
 Depends-on:
 dup2
 fcntl
+mkdir
 msvc-inval
 read-file
 stdint
diff --git a/tests/test-execute-child.c b/tests/test-execute-child.c
index 77f99ae..8197c30 100644
--- a/tests/test-execute-child.c
+++ b/tests/test-execute-child.c
@@ -29,7 +29,7 @@
 /* Get declarations of the native Windows API functions.  */
 # define WIN32_LEAN_AND_MEAN
 # include <windows.h>
-/* Get _get_osfhandle, _isatty.  */
+/* Get _get_osfhandle, _isatty, _chdir, _getcwd.  */
 # include <io.h>
 #endif
 
@@ -41,6 +41,7 @@
 #undef fprintf
 #undef fputs
 #undef fstat
+#undef getcwd
 #undef isatty
 #undef raise
 #undef read
@@ -201,6 +202,23 @@ main (int argc, char *argv[])
         return 4 + 2 * (isatty (10) != 0) + (isatty (11) != 0);
         #endif
       }
+    case 21:
+      /* Check execution in a different directory.  */
+      {
+        char cwd[1024];
+        #if defined _WIN32 && ! defined __CYGWIN__
+        if (_chdir ("..") != 0)
+          return 1;
+        if (_getcwd (cwd, sizeof (cwd)) == NULL)
+          return 2;
+        #else
+        if (chdir ("..") != 0)
+          return 1;
+        if (getcwd (cwd, sizeof (cwd)) == NULL)
+          return 2;
+        #endif
+        return (argc == 3 && strcmp (argv[2], cwd) == 0 ? 0 : 3);
+      }
     default:
       abort ();
     }
diff --git a/tests/test-execute-main.c b/tests/test-execute-main.c
index 62357c1..755209e 100644
--- a/tests/test-execute-main.c
+++ b/tests/test-execute-main.c
@@ -26,9 +26,10 @@
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <sys/stat.h>
 
 #if defined _WIN32 && ! defined __CYGWIN__
-/* Get _isatty.  */
+/* Get _isatty, _getcwd.  */
 # include <io.h>
 #endif
 
@@ -63,7 +64,7 @@ main (int argc, char *argv[])
       {
         /* Check an invocation without arguments.  Check the exit code.  */
         char *prog_argv[2] = { prog_path, NULL };
-        int ret = execute (progname, prog_argv[0], prog_argv,
+        int ret = execute (progname, prog_argv[0], prog_argv, NULL,
                            false, false, false, false, true, false, NULL);
         ASSERT (ret == 40);
       }
@@ -72,7 +73,7 @@ main (int argc, char *argv[])
       {
         /* Check an invocation of a non-existent program.  */
         char *prog_argv[3] = { "./non-existent", NULL };
-        int ret = execute (progname, prog_argv[0], prog_argv,
+        int ret = execute (progname, prog_argv[0], prog_argv, NULL,
                            false, false, false, false, true, false, NULL);
         ASSERT (ret == 127);
       }
@@ -96,7 +97,7 @@ main (int argc, char *argv[])
             (char *) "",
             NULL
           };
-        int ret = execute (progname, prog_argv[0], prog_argv,
+        int ret = execute (progname, prog_argv[0], prog_argv, NULL,
                            false, false, false, false, true, false, NULL);
         ASSERT (ret == 0);
       }
@@ -107,7 +108,7 @@ main (int argc, char *argv[])
         /* Check SIGPIPE handling with ignore_sigpipe = false.  */
         char *prog_argv[3] = { prog_path, (char *) "3", NULL };
         int termsig = 0xDEADBEEF;
-        int ret = execute (progname, prog_argv[0], prog_argv,
+        int ret = execute (progname, prog_argv[0], prog_argv, NULL,
                            false, false, false, false, true, false, &termsig);
         ASSERT (ret == 127);
         ASSERT (termsig == SIGPIPE);
@@ -120,7 +121,7 @@ main (int argc, char *argv[])
         /* Check SIGPIPE handling with ignore_sigpipe = true.  */
         char *prog_argv[3] = { prog_path, (char *) "4", NULL };
         int termsig = 0xDEADBEEF;
-        int ret = execute (progname, prog_argv[0], prog_argv,
+        int ret = execute (progname, prog_argv[0], prog_argv, NULL,
                            true, false, false, false, true, false, &termsig);
         ASSERT (ret == 0);
         ASSERT (termsig == SIGPIPE);
@@ -132,7 +133,7 @@ main (int argc, char *argv[])
         /* Check other signal.  */
         char *prog_argv[3] = { prog_path, (char *) "5", NULL };
         int termsig = 0xDEADBEEF;
-        int ret = execute (progname, prog_argv[0], prog_argv,
+        int ret = execute (progname, prog_argv[0], prog_argv, NULL,
                            false, false, false, false, true, false, &termsig);
         #if defined _WIN32 && !defined __CYGWIN__
         ASSERT (ret == 3);
@@ -154,7 +155,7 @@ main (int argc, char *argv[])
         ASSERT (fp != NULL);
 
         char *prog_argv[3] = { prog_path, (char *) "6", NULL };
-        int ret = execute (progname, prog_argv[0], prog_argv,
+        int ret = execute (progname, prog_argv[0], prog_argv, NULL,
                            false, false, false, false, true, false, NULL);
         ASSERT (ret == 0);
 
@@ -173,7 +174,7 @@ main (int argc, char *argv[])
         ASSERT (fp != NULL);
 
         char *prog_argv[3] = { prog_path, (char *) "7", NULL };
-        int ret = execute (progname, prog_argv[0], prog_argv,
+        int ret = execute (progname, prog_argv[0], prog_argv, NULL,
                            false, true, false, false, true, false, NULL);
         ASSERT (ret == 0);
 
@@ -188,7 +189,7 @@ main (int argc, char *argv[])
         ASSERT (fp != NULL);
 
         char *prog_argv[3] = { prog_path, (char *) "8", NULL };
-        int ret = execute (progname, prog_argv[0], prog_argv,
+        int ret = execute (progname, prog_argv[0], prog_argv, NULL,
                            false, false, false, false, true, false, NULL);
         ASSERT (ret == 0);
 
@@ -208,7 +209,7 @@ main (int argc, char *argv[])
         ASSERT (fp != NULL);
 
         char *prog_argv[3] = { prog_path, (char *) "9", NULL };
-        int ret = execute (progname, prog_argv[0], prog_argv,
+        int ret = execute (progname, prog_argv[0], prog_argv, NULL,
                            false, false, false, false, true, false, NULL);
         ASSERT (ret == 0);
       }
@@ -220,7 +221,7 @@ main (int argc, char *argv[])
         ASSERT (fp != NULL);
 
         char *prog_argv[3] = { prog_path, (char *) "10", NULL };
-        int ret = execute (progname, prog_argv[0], prog_argv,
+        int ret = execute (progname, prog_argv[0], prog_argv, NULL,
                            false, false, true, false, true, false, NULL);
         ASSERT (ret == 0);
 
@@ -240,7 +241,7 @@ main (int argc, char *argv[])
         ASSERT (fp != NULL);
 
         char *prog_argv[3] = { prog_path, (char *) "11", NULL };
-        int ret = execute (progname, prog_argv[0], prog_argv,
+        int ret = execute (progname, prog_argv[0], prog_argv, NULL,
                            false, false, false, false, true, false, NULL);
         ASSERT (ret == 0);
 
@@ -260,7 +261,7 @@ main (int argc, char *argv[])
         ASSERT (fp != NULL);
 
         char *prog_argv[3] = { prog_path, (char *) "12", NULL };
-        int ret = execute (progname, prog_argv[0], prog_argv,
+        int ret = execute (progname, prog_argv[0], prog_argv, NULL,
                            false, false, false, false, true, false, NULL);
         ASSERT (ret == 0);
       }
@@ -272,7 +273,7 @@ main (int argc, char *argv[])
         ASSERT (fp != NULL);
 
         char *prog_argv[3] = { prog_path, (char *) "13", NULL };
-        int ret = execute (progname, prog_argv[0], prog_argv,
+        int ret = execute (progname, prog_argv[0], prog_argv, NULL,
                            false, false, false, true, true, false, NULL);
         ASSERT (ret == 0);
 
@@ -290,7 +291,7 @@ main (int argc, char *argv[])
         /* Check file descriptors >= 3 can be inherited.  */
         ASSERT (dup2 (STDOUT_FILENO, 10) >= 0);
         char *prog_argv[3] = { prog_path, (char *) "14", NULL };
-        int ret = execute (progname, prog_argv[0], prog_argv,
+        int ret = execute (progname, prog_argv[0], prog_argv, NULL,
                            true, false, false, false, true, false, NULL);
         ASSERT (ret == 0);
       }
@@ -300,7 +301,7 @@ main (int argc, char *argv[])
         /* Check file descriptors >= 3 can be inherited.  */
         ASSERT (fcntl (STDOUT_FILENO, F_DUPFD, 10) >= 0);
         char *prog_argv[3] = { prog_path, (char *) "15", NULL };
-        int ret = execute (progname, prog_argv[0], prog_argv,
+        int ret = execute (progname, prog_argv[0], prog_argv, NULL,
                            true, false, false, false, true, false, NULL);
         ASSERT (ret == 0);
       }
@@ -310,7 +311,7 @@ main (int argc, char *argv[])
         /* Check file descriptors >= 3 with O_CLOEXEC bit are not inherited.  */
         ASSERT (fcntl (STDOUT_FILENO, F_DUPFD_CLOEXEC, 10) >= 0);
         char *prog_argv[3] = { prog_path, (char *) "16", NULL };
-        int ret = execute (progname, prog_argv[0], prog_argv,
+        int ret = execute (progname, prog_argv[0], prog_argv, NULL,
                            true, false, false, false, true, false, NULL);
         ASSERT (ret == 0);
       }
@@ -335,7 +336,7 @@ main (int argc, char *argv[])
         /* The file position is now 2.  */
 
         char *prog_argv[3] = { prog_path, (char *) "17", NULL };
-        int ret = execute (progname, prog_argv[0], prog_argv,
+        int ret = execute (progname, prog_argv[0], prog_argv, NULL,
                            false, false, false, false, true, false, NULL);
         ASSERT (ret == 0);
 
@@ -359,7 +360,7 @@ main (int argc, char *argv[])
         /* The file position is now 3.  */
 
         char *prog_argv[3] = { prog_path, (char *) "18", NULL };
-        int ret = execute (progname, prog_argv[0], prog_argv,
+        int ret = execute (progname, prog_argv[0], prog_argv, NULL,
                            false, false, false, false, true, false, NULL);
         ASSERT (ret == 0);
 
@@ -395,7 +396,7 @@ main (int argc, char *argv[])
         fd_out = 11;
 
         char *prog_argv[3] = { prog_path, (char *) "19", NULL };
-        int ret = execute (progname, prog_argv[0], prog_argv,
+        int ret = execute (progname, prog_argv[0], prog_argv, NULL,
                            false, false, false, false, true, false, NULL);
         #if defined _WIN32 && ! defined __CYGWIN__
         ASSERT (ret == 4 + 2 * (_isatty (10) != 0) + (_isatty (11) != 0));
@@ -419,7 +420,7 @@ main (int argc, char *argv[])
         int fd_out = 11;
 
         char *prog_argv[3] = { prog_path, (char *) "20", NULL };
-        int ret = execute (progname, prog_argv[0], prog_argv,
+        int ret = execute (progname, prog_argv[0], prog_argv, NULL,
                            false, false, false, false, true, false, NULL);
         #if defined _WIN32 && ! defined __CYGWIN__
         ASSERT (ret == 4 + 2 * (_isatty (10) != 0) + (_isatty (11) != 0));
@@ -431,6 +432,27 @@ main (int argc, char *argv[])
         close (fd_out);
       }
       break;
+    case 21:
+      {
+        /* Check execution in a different directory.  */
+        rmdir (BASE ".sub");
+        ASSERT (mkdir (BASE ".sub", 0700) == 0);
+
+        char cwd[1024];
+        #if defined _WIN32 && ! defined __CYGWIN__
+        ASSERT (_getcwd (cwd, sizeof (cwd)) != NULL);
+        #else
+        ASSERT (getcwd (cwd, sizeof (cwd)) != NULL);
+        #endif
+
+        char *prog_argv[4] = { prog_path, (char *) "21", cwd, NULL };
+        int ret = execute (progname, prog_argv[0], prog_argv, BASE ".sub",
+                           false, false, false, false, true, false, NULL);
+        ASSERT (ret == 0);
+
+        ASSERT (rmdir (BASE ".sub") == 0);
+      }
+      break;
     default:
       ASSERT (false);
     }
diff --git a/tests/test-execute.sh b/tests/test-execute.sh
index 1320e76..15c8b47 100755
--- a/tests/test-execute.sh
+++ b/tests/test-execute.sh
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 st=0
-for i in 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ; do
+for i in 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ; do
   ${CHECKER} ./test-execute-main${EXEEXT} ./test-execute-child${EXEEXT} $i \
     || { echo test-execute.sh: test case $i failed >&2; st=1; }
 done
-- 
2.7.4

>From 0949c47c03456dae91c15740731b1350296b0497 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 2 Dec 2020 17:52:00 +0100
Subject: [PATCH 2/2] spawn-pipe: Allow caller to specify directory for the
 subprocess.

* lib/spawn-pipe.h (create_pipe_out, create_pipe_in, create_pipe_bidi):
Add directory argument.
* lib/spawn-pipe.c: Include canonicalize.h, filename.h, findprog.h.
(create_pipe): Add directory argument. If specified, resolve the program
file name and make it absolute, first. Pass the directory to spawnpvech
and posix_spawn_file_actions_addchdir.
(create_pipe_bidi, create_pipe_in, create_pipe_out): Add directory
argument.
* modules/spawn-pipe (Depends-on): Add canonicalize, filename,
findprog-in, posix_spawn, posix_spawn_file_actions_addchdir.
* tests/test-spawn-pipe-main.c (test_pipe): Update.
* NEWS: Mention the change.
* lib/csharpcomp.c (compile_csharp_using_mono,
compile_csharp_using_sscli): Update.
* lib/javacomp.c (is_envjavac_gcj, is_envjavac_gcj43, is_gcj_present,
is_gcj_43): Update.
* lib/javaversion.c (execute_and_read_line): Update.
* lib/pipe-filter-gi.c (pipe_filter_gi_create): Update.
* lib/pipe-filter-ii.c (pipe_filter_ii_execute): Update.
---
 ChangeLog                    |  23 +++++++++
 NEWS                         |   5 ++
 lib/csharpcomp.c             |  11 +++--
 lib/javacomp.c               |  16 +++---
 lib/javaversion.c            |   4 +-
 lib/pipe-filter-gi.c         |   2 +-
 lib/pipe-filter-ii.c         |   2 +-
 lib/spawn-pipe.c             | 114 +++++++++++++++++++++++++++++++++++--------
 lib/spawn-pipe.h             |   7 +++
 modules/spawn-pipe           |   5 ++
 tests/test-spawn-pipe-main.c |   2 +-
 11 files changed, 154 insertions(+), 37 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index b5ea083..b32f190 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,28 @@
 2020-12-02  Bruno Haible  <br...@clisp.org>
 
+	spawn-pipe: Allow caller to specify directory for the subprocess.
+	* lib/spawn-pipe.h (create_pipe_out, create_pipe_in, create_pipe_bidi):
+	Add directory argument.
+	* lib/spawn-pipe.c: Include canonicalize.h, filename.h, findprog.h.
+	(create_pipe): Add directory argument. If specified, resolve the program
+	file name and make it absolute, first. Pass the directory to spawnpvech
+	and posix_spawn_file_actions_addchdir.
+	(create_pipe_bidi, create_pipe_in, create_pipe_out): Add directory
+	argument.
+	* modules/spawn-pipe (Depends-on): Add canonicalize, filename,
+	findprog-in, posix_spawn, posix_spawn_file_actions_addchdir.
+	* tests/test-spawn-pipe-main.c (test_pipe): Update.
+	* NEWS: Mention the change.
+	* lib/csharpcomp.c (compile_csharp_using_mono,
+	compile_csharp_using_sscli): Update.
+	* lib/javacomp.c (is_envjavac_gcj, is_envjavac_gcj43, is_gcj_present,
+	is_gcj_43): Update.
+	* lib/javaversion.c (execute_and_read_line): Update.
+	* lib/pipe-filter-gi.c (pipe_filter_gi_create): Update.
+	* lib/pipe-filter-ii.c (pipe_filter_ii_execute): Update.
+
+2020-12-02  Bruno Haible  <br...@clisp.org>
+
 	execute: Allow caller to specify directory for the subprocess.
 	* lib/execute.h (execute): Add directory argument.
 	* lib/execute.c: Include canonicalize.h, filename.h, findprog.h.
diff --git a/NEWS b/NEWS
index 445266d..747165a 100644
--- a/NEWS
+++ b/NEWS
@@ -60,6 +60,11 @@ User visible incompatible changes
 
 Date        Modules         Changes
 
+2020-12-02  spawn-pipe      The functions 'create_pipe_out', 'create_pipe_in',
+                            'create_pipe_bidi' now take a 4th argument
+                            'const char *directory'. To maintain the previous
+                            behaviour, insert NULL as additional 4th argument.
+
 2020-12-02  execute         The function 'execute' now takes a 4th argument
                             'const char *directory'. To maintain the previous
                             behaviour, insert NULL as additional 4th argument.
diff --git a/lib/csharpcomp.c b/lib/csharpcomp.c
index cf68c5a..57d2084 100644
--- a/lib/csharpcomp.c
+++ b/lib/csharpcomp.c
@@ -84,8 +84,8 @@ compile_csharp_using_mono (const char * const *sources,
       argv[0] = "mcs";
       argv[1] = "--version";
       argv[2] = NULL;
-      child = create_pipe_in ("mcs", "mcs", argv, DEV_NULL, true, true, false,
-                              fd);
+      child = create_pipe_in ("mcs", "mcs", argv, NULL,
+                              DEV_NULL, true, true, false, fd);
       mcs_present = false;
       if (child != -1)
         {
@@ -193,7 +193,8 @@ compile_csharp_using_mono (const char * const *sources,
           free (command);
         }
 
-      child = create_pipe_in ("mcs", "mcs", argv, NULL, false, true, true, fd);
+      child = create_pipe_in ("mcs", "mcs", argv, NULL,
+                              NULL, false, true, true, fd);
 
       /* Read the subprocess output, copying it to stderr.  Drop the last
          line if it starts with "Compilation succeeded".  */
@@ -270,8 +271,8 @@ compile_csharp_using_sscli (const char * const *sources,
       argv[0] = "csc";
       argv[1] = "-help";
       argv[2] = NULL;
-      child = create_pipe_in ("csc", "csc", argv, DEV_NULL, true, true, false,
-                              fd);
+      child = create_pipe_in ("csc", "csc", argv, NULL,
+                              DEV_NULL, true, true, false, fd);
       csc_present = false;
       if (child != -1)
         {
diff --git a/lib/javacomp.c b/lib/javacomp.c
index fec5180..c24bb69 100644
--- a/lib/javacomp.c
+++ b/lib/javacomp.c
@@ -664,8 +664,8 @@ is_envjavac_gcj (const char *javac)
       argv[1] = "-c";
       argv[2] = command;
       argv[3] = NULL;
-      child = create_pipe_in (javac, BOURNE_SHELL, argv, DEV_NULL, true, true,
-                              false, fd);
+      child = create_pipe_in (javac, BOURNE_SHELL, argv, NULL,
+                              DEV_NULL, true, true, false, fd);
       if (child == -1)
         goto failed;
 
@@ -746,8 +746,8 @@ is_envjavac_gcj43 (const char *javac)
       argv[1] = "-c";
       argv[2] = command;
       argv[3] = NULL;
-      child = create_pipe_in (javac, BOURNE_SHELL, argv, DEV_NULL, true, true,
-                              false, fd);
+      child = create_pipe_in (javac, BOURNE_SHELL, argv, NULL,
+                              DEV_NULL, true, true, false, fd);
       if (child == -1)
         goto failed;
 
@@ -1414,8 +1414,8 @@ is_gcj_present (void)
       argv[0] = "gcj";
       argv[1] = "--version";
       argv[2] = NULL;
-      child = create_pipe_in ("gcj", "gcj", argv, DEV_NULL, true, true,
-                              false, fd);
+      child = create_pipe_in ("gcj", "gcj", argv, NULL,
+                              DEV_NULL, true, true, false, fd);
       gcj_present = false;
       if (child != -1)
         {
@@ -1530,8 +1530,8 @@ is_gcj_43 (void)
       argv[0] = "gcj";
       argv[1] = "--version";
       argv[2] = NULL;
-      child = create_pipe_in ("gcj", "gcj", argv, DEV_NULL, true, true,
-                              false, fd);
+      child = create_pipe_in ("gcj", "gcj", argv, NULL,
+                              DEV_NULL, true, true, false, fd);
       gcj_43 = false;
       if (child != -1)
         {
diff --git a/lib/javaversion.c b/lib/javaversion.c
index fd99291..e50c499 100644
--- a/lib/javaversion.c
+++ b/lib/javaversion.c
@@ -65,8 +65,8 @@ execute_and_read_line (const char *progname,
   int exitstatus;
 
   /* Open a pipe to the JVM.  */
-  child = create_pipe_in (progname, prog_path, prog_argv, DEV_NULL, false,
-                          true, false, fd);
+  child = create_pipe_in (progname, prog_path, prog_argv, NULL,
+                          DEV_NULL, false, true, false, fd);
 
   if (child == -1)
     return false;
diff --git a/lib/pipe-filter-gi.c b/lib/pipe-filter-gi.c
index f7f8f6e..7528053 100644
--- a/lib/pipe-filter-gi.c
+++ b/lib/pipe-filter-gi.c
@@ -498,7 +498,7 @@ pipe_filter_gi_create (const char *progname,
 
   /* Open a bidirectional pipe to a subprocess.  */
   filter->child = create_pipe_bidi (progname, prog_path, (char **) prog_argv,
-                                    null_stderr, true, exit_on_error,
+                                    NULL, null_stderr, true, exit_on_error,
                                     filter->fd);
   filter->progname = progname;
   filter->null_stderr = null_stderr;
diff --git a/lib/pipe-filter-ii.c b/lib/pipe-filter-ii.c
index b29f802..384a59b 100644
--- a/lib/pipe-filter-ii.c
+++ b/lib/pipe-filter-ii.c
@@ -271,7 +271,7 @@ pipe_filter_ii_execute (const char *progname,
 
   /* Open a bidirectional pipe to a subprocess.  */
   child = create_pipe_bidi (progname, prog_path, (char **) prog_argv,
-                            null_stderr, true, exit_on_error,
+                            NULL, null_stderr, true, exit_on_error,
                             fd);
   if (child == -1)
     return -1;
diff --git a/lib/spawn-pipe.c b/lib/spawn-pipe.c
index a4c4d39..209bbf5 100644
--- a/lib/spawn-pipe.c
+++ b/lib/spawn-pipe.c
@@ -32,8 +32,11 @@
 #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 "gettext.h"
@@ -120,12 +123,62 @@ 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 *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])
 {
+  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"), 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)
+                {
+                  saved_errno = errno;
+                  free (prog_path_to_free);
+                  goto fail_with_saved_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__) || defined __KLIBC__
 
   /* Native Windows API.
@@ -139,7 +192,6 @@ create_pipe (const char *progname,
   int nulloutfd;
   int stdinfd;
   int stdoutfd;
-  int saved_errno;
 
   /* FIXME: Need to free memory allocated by prepare_spawn.  */
   prog_argv = prepare_spawn (prog_argv);
@@ -227,16 +279,17 @@ create_pipe (const char *progname,
         (HANDLE) _get_osfhandle (null_stderr ? nulloutfd : STDERR_FILENO);
 
       child = spawnpvech (P_NOWAIT, prog_path, (const char **) prog_argv,
-                          (const char **) environ, NULL,
+                          (const char **) environ, directory,
                           stdin_handle, stdout_handle, stderr_handle);
       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[0] = prog_path;
           --prog_argv;
           child = spawnpvech (P_NOWAIT, prog_argv[0], (const char **) prog_argv,
-                              (const char **) environ, NULL,
+                              (const char **) environ, directory,
                               stdin_handle, stdout_handle, stderr_handle);
         }
     }
@@ -256,6 +309,13 @@ create_pipe (const char *progname,
     close (ifd[1]);
 
 # 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;
@@ -330,17 +390,15 @@ create_pipe (const char *progname,
     close (ifd[1]);
 # endif
 
+  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)
@@ -426,6 +484,9 @@ 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,
@@ -435,9 +496,13 @@ create_pipe (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))
+          || (err = (directory != NULL
+                     ? posix_spawn (&child, prog_path, &actions,
+                                    attrs_allocated ? &attrs : NULL, prog_argv,
+                                    environ)
+                     : posix_spawnp (&child, prog_path, &actions,
+                                     attrs_allocated ? &attrs : NULL, prog_argv,
+                                     environ)))
              != 0))
     {
       if (actions_allocated)
@@ -446,9 +511,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]);
@@ -459,8 +521,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)
@@ -474,6 +537,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];
@@ -482,6 +546,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.
@@ -495,11 +568,12 @@ create_pipe (const char *progname,
 pid_t
 create_pipe_bidi (const char *progname,
                   const char *prog_path, char **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);
@@ -516,12 +590,13 @@ create_pipe_bidi (const char *progname,
 pid_t
 create_pipe_in (const char *progname,
                 const char *prog_path, char **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);
@@ -540,12 +615,13 @@ create_pipe_in (const char *progname,
 pid_t
 create_pipe_out (const char *progname,
                  const char *prog_path, char **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);
diff --git a/lib/spawn-pipe.h b/lib/spawn-pipe.h
index be0f1c8..02856a8 100644
--- a/lib/spawn-pipe.h
+++ b/lib/spawn-pipe.h
@@ -51,6 +51,10 @@ extern "C" {
    argv[].  It is a NULL-terminated array.  prog_argv[0] should normally be
    identical to prog_path.
 
+   If directory is not NULL, the subprocess is started in that directory.  If
+   prog_path is a relative file name, it resolved before changing to that
+   directory.  The current directory of the current process remains unchanged.
+
    If slave_process is true, the child process will be terminated when its
    creator receives a catchable fatal signal or exits normally.  If
    slave_process is false, the child process will continue running in this
@@ -93,6 +97,7 @@ extern "C" {
  */
 extern pid_t create_pipe_out (const char *progname,
                               const char *prog_path, char **prog_argv,
+                              const char *directory,
                               const char *prog_stdout, bool null_stderr,
                               bool slave_process, bool exit_on_error,
                               int fd[1]);
@@ -106,6 +111,7 @@ extern pid_t create_pipe_out (const char *progname,
  */
 extern pid_t create_pipe_in (const char *progname,
                              const char *prog_path, char **prog_argv,
+                             const char *directory,
                              const char *prog_stdin, bool null_stderr,
                              bool slave_process, bool exit_on_error,
                              int fd[1]);
@@ -134,6 +140,7 @@ extern pid_t create_pipe_in (const char *progname,
  */
 extern pid_t create_pipe_bidi (const char *progname,
                                const char *prog_path, char **prog_argv,
+                               const char *directory,
                                bool null_stderr,
                                bool slave_process, bool exit_on_error,
                                int fd[2]);
diff --git a/modules/spawn-pipe b/modules/spawn-pipe
index 2e78170..9d80193 100644
--- a/modules/spawn-pipe
+++ b/modules/spawn-pipe
@@ -10,20 +10,25 @@ m4/spawn-pipe.m4
 
 Depends-on:
 dup2
+canonicalize
 environ
 error
 fatal-signal
+filename
+findprog-in
 gettext-h
 msvc-nothrow
 open
 pipe2
 pipe2-safer
 spawn
+posix_spawn
 posix_spawnp
 posix_spawn_file_actions_init
 posix_spawn_file_actions_addclose
 posix_spawn_file_actions_adddup2
 posix_spawn_file_actions_addopen
+posix_spawn_file_actions_addchdir
 posix_spawn_file_actions_destroy
 posix_spawnattr_init
 posix_spawnattr_setsigmask
diff --git a/tests/test-spawn-pipe-main.c b/tests/test-spawn-pipe-main.c
index 946871b..fe4f94e 100644
--- a/tests/test-spawn-pipe-main.c
+++ b/tests/test-spawn-pipe-main.c
@@ -52,7 +52,7 @@ test_pipe (const char *prog, bool stderr_closed)
   argv[0] = (char *) prog;
   argv[1] = (char *) (stderr_closed ? "1" : "0");
   argv[2] = NULL;
-  pid = create_pipe_bidi (prog, prog, argv, false, true, true, fd);
+  pid = create_pipe_bidi (prog, prog, argv, NULL, false, true, true, fd);
   ASSERT (0 <= pid);
   ASSERT (STDERR_FILENO < fd[0]);
   ASSERT (STDERR_FILENO < fd[1]);
-- 
2.7.4

Reply via email to