* 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-02 Bruno Haible <bruno@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 <bruno@clisp.org>
vma-iter: Add support for macOS11/arm64.
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.
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++)
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;
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;
#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"
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);
(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);
}
}
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;
"/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,
|| (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)
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)
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;
}
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.
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,
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);
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);
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);
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);
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;
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;
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;
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;
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;
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;
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
Depends-on:
dup2
fcntl
+mkdir
msvc-inval
read-file
stdint
/* 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
#undef fprintf
#undef fputs
#undef fstat
+#undef getcwd
#undef isatty
#undef raise
#undef read
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 ();
}
#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
{
/* 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);
}
{
/* 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);
}
(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);
}
/* 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);
/* 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);
/* 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);
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);
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);
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);
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);
}
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);
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);
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);
}
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);
/* 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);
}
/* 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);
}
/* 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);
}
/* 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);
/* 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);
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));
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));
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);
}
#!/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