Reported by Michele Locati <michele@locati.it>.
* lib/windows-path.h: New file.
* lib/windows-path.c: New file.
* lib/windows-spawn.h (compose_envblock): Add new_PATH parameter.
(spawnpvech): Add dll_dirs parameter. Call extended_PATH.
* lib/windows-spawn.c: Include windows-path.h.
(compose_envblock): Add new_PATH parameter.
* modules/windows-spawn (Description): Now applies to Cygwin as well.
(Files): Add lib/windows-path.h, lib/windows-path.c.
(configure.ac): Define GL_COND_OBJ_WINDOWS_PATH.
(Makefile.am): Conditionally compile windows-path.c.
(Include): Add windows-path.h.
* lib/spawni.c (__spawni): Update compose_envblock call.
* lib/execute.h (execute): Add dll_dirs parameter.
* lib/execute.c: Include windows-path.h.
(execute): Add dll_dirs parameter. Pass it down to spawnpvech. Call
extended_environ.
* lib/spawn-pipe.h (create_pipe_out, create_pipe_in, create_pipe_bidi):
Add dll_dirs parameter.
* lib/spawn-pipe.c: Include windows-path.h.
(create_pipe): Add dll_dirs parameter. Pass it down to spawnpvech. Call
extended_environ.
(create_pipe_bidi, create_pipe_in, create_pipe_out): Add dll_dirs
parameter.
* lib/javaexec.c (execute_java_class): Update execute invocations.
* lib/cygpath.c (execute_and_read_line): Update create_pipe_in
invocation.
* lib/javaversion.c (execute_and_read_line): Likewise.
* lib/csharpcomp.c (compile_csharp_using_mono,
compile_csharp_using_dotnet, compile_csharp_using_sscli): Update
execute, create_pipe_in invocations.
* lib/csharpexec.c (execute_csharp_using_mono,
execute_csharp_using_dotnet, execute_csharp_using_sscli): Likewise.
* lib/javacomp.c (compile_using_envjavac, compile_using_javac,
execute_and_read_line, is_javac_present): Likewise.
* lib/pipe-filter-gi.c (pipe_filter_gi_create): Update create_pipe_bidi
invocation.
* lib/pipe-filter-ii.c (pipe_filter_ii_execute): Likewise.
* tests/test-execute-main.c (main): Update execute invocations.
* tests/test-execute-script.c (main): Likewise.
* tests/test-spawn-pipe-main.c (main): Update create_pipe_bidi
invocation.
* tests/test-spawn-pipe-script.c (main): Update create_pipe_in
invocations.
* NEWS: Mention the changes.
+2024-10-22 Bruno Haible <bruno@clisp.org>
+
+ execute, spawn-pipe: Support DLL dependencies of Windows executables.
+ Reported by Michele Locati <michele@locati.it>.
+ * lib/windows-path.h: New file.
+ * lib/windows-path.c: New file.
+ * lib/windows-spawn.h (compose_envblock): Add new_PATH parameter.
+ (spawnpvech): Add dll_dirs parameter. Call extended_PATH.
+ * lib/windows-spawn.c: Include windows-path.h.
+ (compose_envblock): Add new_PATH parameter.
+ * modules/windows-spawn (Description): Now applies to Cygwin as well.
+ (Files): Add lib/windows-path.h, lib/windows-path.c.
+ (configure.ac): Define GL_COND_OBJ_WINDOWS_PATH.
+ (Makefile.am): Conditionally compile windows-path.c.
+ (Include): Add windows-path.h.
+ * lib/spawni.c (__spawni): Update compose_envblock call.
+ * lib/execute.h (execute): Add dll_dirs parameter.
+ * lib/execute.c: Include windows-path.h.
+ (execute): Add dll_dirs parameter. Pass it down to spawnpvech. Call
+ extended_environ.
+ * lib/spawn-pipe.h (create_pipe_out, create_pipe_in, create_pipe_bidi):
+ Add dll_dirs parameter.
+ * lib/spawn-pipe.c: Include windows-path.h.
+ (create_pipe): Add dll_dirs parameter. Pass it down to spawnpvech. Call
+ extended_environ.
+ (create_pipe_bidi, create_pipe_in, create_pipe_out): Add dll_dirs
+ parameter.
+ * lib/javaexec.c (execute_java_class): Update execute invocations.
+ * lib/cygpath.c (execute_and_read_line): Update create_pipe_in
+ invocation.
+ * lib/javaversion.c (execute_and_read_line): Likewise.
+ * lib/csharpcomp.c (compile_csharp_using_mono,
+ compile_csharp_using_dotnet, compile_csharp_using_sscli): Update
+ execute, create_pipe_in invocations.
+ * lib/csharpexec.c (execute_csharp_using_mono,
+ execute_csharp_using_dotnet, execute_csharp_using_sscli): Likewise.
+ * lib/javacomp.c (compile_using_envjavac, compile_using_javac,
+ execute_and_read_line, is_javac_present): Likewise.
+ * lib/pipe-filter-gi.c (pipe_filter_gi_create): Update create_pipe_bidi
+ invocation.
+ * lib/pipe-filter-ii.c (pipe_filter_ii_execute): Likewise.
+ * tests/test-execute-main.c (main): Update execute invocations.
+ * tests/test-execute-script.c (main): Likewise.
+ * tests/test-spawn-pipe-main.c (main): Update create_pipe_bidi
+ invocation.
+ * tests/test-spawn-pipe-script.c (main): Update create_pipe_in
+ invocations.
+ * NEWS: Mention the changes.
+
2024-10-21 Bruno Haible <bruno@clisp.org>
reallocarray: Don't assume unportable behaviour of realloc.
Date Modules Changes
+2024-10-22 spawn-pipe The functions 'create_pipe_out', 'create_pipe_in',
+ 'create_pipe_bidi' now take a 4th argument
+ 'const char * const *dll_dirs'. To maintain the
+ previous behaviour, insert NULL as additional 4th
+ argument.
+
+2024-10-22 execute The function 'execute' now takes a 4th argument
+ 'const char * const *dll_dirs'. To maintain the
+ previous behaviour, insert NULL as additional 4th
+ argument.
+
2024-10-02 file-has-acl The file_has_aclinfo function introduced 3 days ago
now has a different signature.
argv[0] = "mcs";
argv[1] = "--version";
argv[2] = NULL;
- child = create_pipe_in ("mcs", "mcs", argv, NULL,
+ child = create_pipe_in ("mcs", "mcs", argv, NULL, NULL,
DEV_NULL, true, true, false, fd);
mcs_present = false;
if (child != -1)
free (command);
}
- child = create_pipe_in ("mcs", "mcs", argv, NULL,
+ child = create_pipe_in ("mcs", "mcs", argv, NULL, NULL,
NULL, false, true, true, fd);
/* Read the subprocess output, copying it to stderr. Drop the last
argv[0] = "dotnet";
argv[1] = "--list-runtimes";
argv[2] = NULL;
- exitstatus = execute ("dotnet", "dotnet", argv, NULL,
+ exitstatus = execute ("dotnet", "dotnet", argv, NULL, NULL,
false, false, true, true,
true, false, NULL);
}
argv[0] = "dotnet";
argv[1] = "--list-sdks";
argv[2] = NULL;
- child = create_pipe_in ("dotnet", "dotnet", argv, NULL,
+ child = create_pipe_in ("dotnet", "dotnet", argv, NULL, NULL,
DEV_NULL, true, true, false, fd);
if (child != -1)
{
/* Open a pipe to the program. */
int fd[1];
- pid_t child = create_pipe_in ("dotnet", "dotnet", argv, NULL,
+ pid_t child = create_pipe_in ("dotnet", "dotnet", argv, NULL, NULL,
DEV_NULL, false, true, false, fd);
if (child == -1)
{
/* Open a pipe to the program. */
int fd[1];
- pid_t child = create_pipe_in ("dotnet", "dotnet", argv, NULL,
+ pid_t child = create_pipe_in ("dotnet", "dotnet", argv, NULL, NULL,
DEV_NULL, false, true, false, fd);
if (child == -1)
{
free (command);
}
- exitstatus = execute ("dotnet", "dotnet", argv, NULL,
+ exitstatus = execute ("dotnet", "dotnet", argv, NULL, NULL,
false, false, false, false,
true, true, NULL);
argv[0] = "csc";
argv[1] = "-help";
argv[2] = NULL;
- child = create_pipe_in ("csc", "csc", argv, NULL,
+ child = create_pipe_in ("csc", "csc", argv, NULL, NULL,
DEV_NULL, true, true, false, fd);
if (child != -1)
{
free (command);
}
- exitstatus = execute ("csc", "csc", argv, NULL,
+ exitstatus = execute ("csc", "csc", argv, NULL, NULL,
false, false, false, false,
true, true, NULL);
argv[0] = "csc";
argv[1] = "-help";
argv[2] = NULL;
- child = create_pipe_in ("csc", "csc", argv, NULL,
+ child = create_pipe_in ("csc", "csc", argv, NULL, NULL,
DEV_NULL, true, true, false, fd);
csc_present = false;
if (child != -1)
free (command);
}
- exitstatus = execute ("csc", "csc", argv, NULL,
+ exitstatus = execute ("csc", "csc", argv, NULL, NULL,
false, false, false, false,
true, true, NULL);
argv[0] = "mono";
argv[1] = "--version";
argv[2] = NULL;
- exitstatus = execute ("mono", "mono", argv, NULL,
+ exitstatus = execute ("mono", "mono", argv, NULL, NULL,
false, false, true, true,
true, false, NULL);
mono_present = (exitstatus == 0);
argv[0] = "dotnet";
argv[1] = "--list-runtimes";
argv[2] = NULL;
- exitstatus = execute ("dotnet", "dotnet", argv, NULL,
+ exitstatus = execute ("dotnet", "dotnet", argv, NULL, NULL,
false, false, true, true,
true, false, NULL);
dotnet_present = (exitstatus == 0);
/* Open a pipe to the program. */
int fd[1];
- pid_t child = create_pipe_in ("dotnet", "dotnet", argv, NULL,
+ pid_t child = create_pipe_in ("dotnet", "dotnet", argv, NULL, NULL,
DEV_NULL, false, true, false, fd);
if (child == -1)
{
argv[0] = "clix";
argv[1] = NULL;
- exitstatus = execute ("clix", "clix", argv, NULL,
+ exitstatus = execute ("clix", "clix", argv, NULL, NULL,
false, false, true, true,
true, false, NULL);
clix_present = (exitstatus == 0 || exitstatus == 1);
size_t linelen;
/* Open a pipe to the program. */
- child = create_pipe_in (progname, prog_path, prog_argv, NULL,
+ child = create_pipe_in (progname, prog_path, prog_argv, NULL, NULL,
DEV_NULL, false, true, false, fd);
if (child == -1)
#include "fatal-signal.h"
#include "filename.h"
#include "findprog.h"
+#include "windows-path.h"
#include "wait-process.h"
#include "xalloc.h"
#include "gettext.h"
int
execute (const char *progname,
const char *prog_path, const char * const *prog_argv,
+ const char * const *dll_dirs,
const char *directory,
bool ignore_sigpipe,
bool null_stdin, bool null_stdout, bool null_stderr,
(HANDLE) _get_osfhandle (null_stderr ? nulloutfd : STDERR_FILENO);
exitcode = spawnpvech (P_WAIT, prog_path, argv + 1,
- (const char * const *) environ, directory,
+ (const char * const *) environ, dll_dirs,
+ directory,
stdin_handle, stdout_handle, stderr_handle);
# if 0 /* Executing arbitrary files as shell scripts is unsecure. */
if (exitcode == -1 && errno == ENOEXEC)
a hidden element "sh.exe" to argv. */
argv[1] = prog_path;
exitcode = spawnpvech (P_WAIT, argv[0], argv,
- (const char * const *) environ, directory,
+ (const char * const *) environ, dll_dirs,
+ directory,
stdin_handle, stdout_handle, stderr_handle);
}
# endif
subprocess to exit with return code 127. It is implementation
dependent which error is reported which way. We treat both cases as
equivalent. */
+ char **child_environ;
+ char **malloced_environ;
sigset_t blocked_signals;
posix_spawn_file_actions_t actions;
bool actions_allocated;
int err;
pid_t child;
+ child_environ = environ;
+ malloced_environ = NULL;
+# if defined _WIN32 || defined __CYGWIN__
+ if (dll_dirs != NULL && dll_dirs[0] != NULL)
+ {
+ malloced_environ = extended_environ (dll_dirs);
+ if (malloced_environ == NULL)
+ goto fail_with_errno;
+ child_environ = malloced_environ;
+ }
+# endif
+
if (slave_process)
{
sigprocmask (SIG_SETMASK, NULL, &blocked_signals);
&blocked_signals))
!= 0
|| (err = posix_spawnattr_setflags (&attrs,
- POSIX_SPAWN_SETSIGMASK))
+ POSIX_SPAWN_SETSIGMASK))
!= 0)))
# endif
|| (err = (directory != NULL
? posix_spawn (&child, prog_path, &actions,
attrs_allocated ? &attrs : NULL,
- (char * const *) prog_argv, environ)
+ (char * const *) prog_argv,
+ child_environ)
: posix_spawnp (&child, prog_path, &actions,
attrs_allocated ? &attrs : NULL,
- (char * const *) prog_argv, environ)))
+ (char * const *) prog_argv,
+ child_environ)))
!= 0))
{
if (actions_allocated)
posix_spawnattr_destroy (&attrs);
if (slave_process)
unblock_fatal_signals ();
+ if (malloced_environ != NULL)
+ {
+ free (malloced_environ[0]);
+ free (malloced_environ);
+ }
free (prog_path_to_free);
if (termsigp != NULL)
*termsigp = 0;
register_slave_subprocess (child);
unblock_fatal_signals ();
}
+ if (malloced_environ != NULL)
+ {
+ free (malloced_environ[0]);
+ free (malloced_environ);
+ }
free (prog_path_to_free);
return wait_subprocess (child, progname, ignore_sigpipe, null_stderr,
descriptors to /dev/null. Return its exit code.
If it didn't terminate correctly, exit if exit_on_error is true, otherwise
return 127.
+
progname is the name of the program to be executed by the subprocess, used
for error messages.
prog_path is the file name of the program to be executed by the subprocess.
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.
+
+ dll_dirs is, on Windows platforms, a NULL-terminated list of directories
+ that contain DLLs needed to execute the program, or NULL if none is needed.
+ On other platforms, always pass NULL.
+
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.
+
If slave_process is true, the child process will be terminated when its
creator receives a catchable fatal signal.
+
If termsigp is not NULL, *termsig will be set to the signal that terminated
the subprocess (if supported by the platform: not on native Windows
platforms), otherwise 0.
+
It is recommended that no signal is blocked or ignored while execute()
is called. See spawn-pipe.h for the reason. */
extern int execute (const char *progname,
const char *prog_path, const char * const *prog_argv,
+ const char * const *dll_dirs,
const char *directory,
bool ignore_sigpipe,
bool null_stdin, bool null_stdout, bool null_stderr,
argv[1] = "-c";
argv[2] = command;
argv[3] = NULL;
- exitstatus = execute (javac, BOURNE_SHELL, argv, NULL,
+ exitstatus = execute (javac, BOURNE_SHELL, argv, NULL, NULL,
false, false, false, null_stderr,
true, true, NULL);
err = (exitstatus != 0);
free (command);
}
- exitstatus = execute ("javac", "javac", argv, NULL,
+ exitstatus = execute ("javac", "javac", argv, NULL, NULL,
false, false, false,
null_stderr, true, true, NULL);
err = (exitstatus != 0);
int exitstatus;
/* Open a pipe to the program. */
- child = create_pipe_in (progname, prog_path, prog_argv, NULL,
+ child = create_pipe_in (progname, prog_path, prog_argv, NULL, NULL,
DEV_NULL, false, true, false, fd);
if (child == -1)
argv[0] = "javac";
argv[1] = NULL;
- exitstatus = execute ("javac", "javac", argv, NULL,
+ exitstatus = execute ("javac", "javac", argv, NULL, NULL,
false, false, true, true,
true, false, NULL);
javac_present = (exitstatus == 0 || exitstatus == 1 || exitstatus == 2);
argv[0] = "java";
argv[1] = "-version";
argv[2] = NULL;
- exitstatus = execute ("java", "java", argv, NULL,
+ exitstatus = execute ("java", "java", argv, NULL, NULL,
false, false, true, true,
true, false, NULL);
java_present = (exitstatus == 0);
argv[0] = "jre";
argv[1] = NULL;
- exitstatus = execute ("jre", "jre", argv, NULL,
+ exitstatus = execute ("jre", "jre", argv, NULL, NULL,
false, false, true, true,
true, false, NULL);
jre_present = (exitstatus == 0 || exitstatus == 1);
size_t linelen;
/* Open a pipe to the JVM. */
- child = create_pipe_in (progname, prog_path, prog_argv, NULL,
+ child = create_pipe_in (progname, prog_path, prog_argv, NULL, NULL,
DEV_NULL, false, true, false, fd);
if (child == -1)
(struct pipe_filter_gi *) xmalloc (sizeof (struct pipe_filter_gi));
/* Open a bidirectional pipe to a subprocess. */
- filter->child = create_pipe_bidi (progname, prog_path, prog_argv,
+ filter->child = create_pipe_bidi (progname, prog_path, prog_argv, NULL,
NULL, null_stderr, true, exit_on_error,
filter->fd);
filter->progname = progname;
#endif
/* Open a bidirectional pipe to a subprocess. */
- child = create_pipe_bidi (progname, prog_path, prog_argv,
+ child = create_pipe_bidi (progname, prog_path, prog_argv, NULL,
NULL, null_stderr, true, exit_on_error,
fd);
if (child == -1)
#include "fatal-signal.h"
#include "filename.h"
#include "findprog.h"
+#include "windows-path.h"
#include "unistd-safer.h"
#include "wait-process.h"
#include "xalloc.h"
create_pipe (const char *progname,
const char *prog_path,
const char * const *prog_argv,
+ const char * const *dll_dirs,
const char *directory,
bool pipe_stdin, bool pipe_stdout,
const char *prog_stdin, const char *prog_stdout,
(HANDLE) _get_osfhandle (null_stderr ? nulloutfd : STDERR_FILENO);
child = spawnpvech (P_NOWAIT, prog_path, argv + 1,
- (const char * const *) environ, directory,
+ (const char * const *) environ, dll_dirs,
+ directory,
stdin_handle, stdout_handle, stderr_handle);
# if 0 /* Executing arbitrary files as shell scripts is unsecure. */
if (child == -1 && errno == ENOEXEC)
a hidden element "sh.exe" to argv. */
argv[1] = prog_path;
child = spawnpvech (P_NOWAIT, argv[0], argv,
- (const char * const *) environ, directory,
+ (const char * const *) environ, dll_dirs,
+ directory,
stdin_handle, stdout_handle, stderr_handle);
}
# endif
#else
/* Unix API. */
+ char **child_environ;
+ char **malloced_environ;
sigset_t blocked_signals;
posix_spawn_file_actions_t actions;
bool actions_allocated;
int err;
pid_t child;
+ child_environ = environ;
+ malloced_environ = NULL;
+# if defined _WIN32 || defined __CYGWIN__
+ if (dll_dirs != NULL && dll_dirs[0] != NULL)
+ {
+ malloced_environ = extended_environ (dll_dirs);
+ if (malloced_environ == NULL)
+ goto fail_with_errno;
+ child_environ = malloced_environ;
+ }
+# endif
+
if (slave_process)
{
sigprocmask (SIG_SETMASK, NULL, &blocked_signals);
# if defined _WIN32 && !defined __CYGWIN__
(err = posix_spawnattr_setpgroup (&attrs, 0)) != 0
|| (err = posix_spawnattr_setflags (&attrs,
- POSIX_SPAWN_SETPGROUP))
+ POSIX_SPAWN_SETPGROUP))
!= 0
# else
(err = posix_spawnattr_setsigmask (&attrs,
&blocked_signals))
!= 0
|| (err = posix_spawnattr_setflags (&attrs,
- POSIX_SPAWN_SETSIGMASK))
+ POSIX_SPAWN_SETSIGMASK))
!= 0
# endif
) ) )
|| (err = (directory != NULL
? posix_spawn (&child, prog_path, &actions,
attrs_allocated ? &attrs : NULL,
- (char * const *) prog_argv, environ)
+ (char * const *) prog_argv,
+ child_environ)
: posix_spawnp (&child, prog_path, &actions,
attrs_allocated ? &attrs : NULL,
- (char * const *) prog_argv, environ)))
+ (char * const *) prog_argv,
+ child_environ)))
!= 0))
{
if (actions_allocated)
posix_spawnattr_destroy (&attrs);
if (slave_process)
unblock_fatal_signals ();
+ if (malloced_environ != NULL)
+ {
+ free (malloced_environ[0]);
+ free (malloced_environ);
+ }
if (pipe_stdout)
{
close (ifd[0]);
register_slave_subprocess (child);
unblock_fatal_signals ();
}
+ if (malloced_environ != NULL)
+ {
+ free (malloced_environ[0]);
+ free (malloced_environ);
+ }
if (pipe_stdin)
close (ofd[0]);
if (pipe_stdout)
pid_t
create_pipe_bidi (const char *progname,
const char *prog_path, const char * const *prog_argv,
+ const char * const *dll_dirs,
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, directory,
+ pid_t result = create_pipe (progname, prog_path, prog_argv, dll_dirs,
+ directory,
true, true, NULL, NULL,
null_stderr, slave_process, exit_on_error,
fd);
pid_t
create_pipe_in (const char *progname,
const char *prog_path, const char * const *prog_argv,
+ const char * const *dll_dirs,
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, directory,
+ pid_t result = create_pipe (progname, prog_path, prog_argv, dll_dirs,
+ directory,
false, true, prog_stdin, NULL,
null_stderr, slave_process, exit_on_error,
iofd);
pid_t
create_pipe_out (const char *progname,
const char *prog_path, const char * const *prog_argv,
+ const char * const *dll_dirs,
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, directory,
+ pid_t result = create_pipe (progname, prog_path, prog_argv, dll_dirs,
+ directory,
true, false, NULL, prog_stdout,
null_stderr, slave_process, exit_on_error,
iofd);
argv[]. It is a NULL-terminated array. prog_argv[0] should normally be
identical to prog_path.
+ dll_dirs is, on Windows platforms, a NULL-terminated list of directories
+ that contain DLLs needed to execute the program, or NULL if none is needed.
+ On other platforms, always pass NULL.
+
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.
extern pid_t create_pipe_out (const char *progname,
const char *prog_path,
const char * const *prog_argv,
+ const char * const *dll_dirs,
const char *directory,
const char *prog_stdout, bool null_stderr,
bool slave_process, bool exit_on_error,
extern pid_t create_pipe_in (const char *progname,
const char *prog_path,
const char * const *prog_argv,
+ const char * const *dll_dirs,
const char *directory,
const char *prog_stdin, bool null_stderr,
bool slave_process, bool exit_on_error,
extern pid_t create_pipe_bidi (const char *progname,
const char *prog_path,
const char * const *prog_argv,
+ const char * const *dll_dirs,
const char *directory,
bool null_stderr,
bool slave_process, bool exit_on_error,
envblock = NULL;
else
{
- envblock = compose_envblock (envp);
+ envblock = compose_envblock (envp, NULL);
if (envblock == NULL)
{
free (command);
--- /dev/null
+/* Auxiliary functions for the creation of subprocesses on Windows.
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ Written by Bruno Haible <bruno@clisp.org>, 2024.
+
+ This file is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ This file is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+/* Specification. */
+#include "windows-path.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+char *
+extended_PATH (const char * const *dll_dirs)
+{
+ /* Create a string PATH=(dll_dirs[0];dll_dirs[1];...;)old_PATH */
+ const char *old_PATH = getenv ("PATH");
+ if (old_PATH == NULL)
+ old_PATH = "";
+ size_t size;
+ {
+ size = 5;
+ {
+ size_t i;
+ for (i = 0; dll_dirs[i] != NULL; i++)
+ size += strlen (dll_dirs[i]) + 1;
+ }
+ size += strlen (old_PATH) + 1;
+ }
+ char *new_PATH = (char *) malloc (size);
+ if (new_PATH == NULL)
+ return NULL;
+ {
+ char *p = new_PATH;
+ {
+ memcpy (p, "PATH=", 5);
+ p += 5;
+ }
+ {
+ size_t i;
+ for (i = 0; dll_dirs[i] != NULL; i++)
+ {
+ size_t l = strlen (dll_dirs[i]);
+ memcpy (p, dll_dirs[i], l);
+ p += l;
+#if defined _WIN32 && !defined __CYGWIN__
+ *p++ = ';';
+#else
+ *p++ = ':';
+#endif
+ }
+ }
+ {
+ size_t l = strlen (old_PATH);
+ memcpy (p, old_PATH, l);
+ p += l;
+ *p = '\0';
+ }
+ }
+ return new_PATH;
+}
+
+char **
+extended_environ (const char * const *dll_dirs)
+{
+ char *child_PATH = extended_PATH (dll_dirs);
+ if (child_PATH == NULL)
+ return NULL;
+
+ /* Create a shallow copy of environ, adding the child_PATH and removing
+ the original "PATH=..." string.
+ This is a bit hairy, because we don't have a lock that would prevent
+ other threads from making modifications in ENVP. So, just make sure
+ we don't crash; but if other threads are making modifications, part
+ of the result may be wrong. */
+ char **envp;
+
+#if defined _WIN32 && !defined __CYGWIN__
+ envp = _environ;
+#else
+ envp = environ;
+#endif
+
+ retry:
+ {
+ /* Guess the size of the needed block of memory.
+ The guess will be exact if other threads don't make modifications. */
+ size_t size = 0;
+ {
+ char **ep;
+ char *p;
+ for (ep = envp; (p = *ep) != NULL; ep++)
+ if (strncmp (p, "PATH=", 5) != 0)
+ size += 1;
+ }
+ char **new_environ = (char **) malloc ((1 + size + 1) * sizeof (char *));
+ if (new_environ == NULL)
+ {
+ free (child_PATH);
+ errno = ENOMEM;
+ return NULL;
+ }
+ char **nep = new_environ;
+ *nep++ = child_PATH;
+ {
+ size_t i = 0;
+ char **ep;
+ char *p;
+ for (ep = envp; (p = *ep) != NULL; ep++)
+ if (strncmp (p, "PATH=", 5) != 0)
+ {
+ if (i == size)
+ {
+ /* Other threads did modifications. Restart. */
+ free (new_environ);
+ goto retry;
+ }
+ *nep++ = p;
+ i += 1;
+ }
+ if (i < size)
+ {
+ /* Other threads did modifications. Restart. */
+ free (new_environ);
+ goto retry;
+ }
+ }
+ *nep = NULL;
+ return new_environ;
+ }
+}
--- /dev/null
+/* Auxiliary functions for the creation of subprocesses on Windows.
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ Written by Bruno Haible <bruno@clisp.org>, 2024.
+
+ This file is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ This file is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>. */
+
+#ifndef _WINDOWS_PATH_H
+#define _WINDOWS_PATH_H
+
+/* This file uses _GL_ATTRIBUTE_MALLOC. */
+#if !_GL_CONFIG_H_INCLUDED
+ #error "Please include config.h first."
+#endif
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/* Returns an augmented "PATH=..." string for the environment of a child process.
+ dll_dirs is a NULL-terminated list of directories that contain DLLs needed to
+ execute the program, or NULL if none is needed.
+ Returns a freshly allocated string. In case of memory allocation failure,
+ NULL is returned, with errno set. */
+extern char * extended_PATH (const char * const *dll_dirs)
+ _GL_ATTRIBUTE_MALLOC _GL_ATTRIBUTE_DEALLOC_FREE;
+
+/* Returns an augmented environment for a child process.
+ dll_dirs is a NULL-terminated list of directories that contain DLLs needed to
+ execute the program, or NULL if none is needed.
+ Returns a freshly allocated string array, with a freshly allocated first
+ string. In case of memory allocation failure, NULL is returned, with errno
+ set. */
+extern char ** extended_environ (const char * const *dll_dirs)
+ _GL_ATTRIBUTE_MALLOC _GL_ATTRIBUTE_DEALLOC_FREE;
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _WINDOWS_PATH_H */
#include <process.h>
#include "findprog.h"
+#include "windows-path.h"
/* Don't assume that UNICODE is not defined. */
#undef STARTUPINFO
}
char *
-compose_envblock (const char * const *envp)
+compose_envblock (const char * const *envp, const char *new_PATH)
{
/* This is a bit hairy, because we don't have a lock that would prevent other
threads from making modifications in ENVP. So, just make sure we don't
size_t total_size = 0;
const char * const *ep;
const char *p;
+ if (new_PATH != NULL)
+ total_size += strlen (new_PATH) + 1;
for (ep = envp; (p = *ep) != NULL; ep++)
- total_size += strlen (p) + 1;
+ if (!(new_PATH != NULL && strncmp (p, "PATH=", 5) == 0))
+ total_size += strlen (p) + 1;
size_t envblock_size = total_size;
/* Allocate the block of memory. */
return NULL;
}
size_t envblock_used = 0;
- for (ep = envp; (p = *ep) != NULL; ep++)
+ if (new_PATH != NULL)
{
- size_t size = strlen (p) + 1;
- if (envblock_used + size > envblock_size)
- {
- /* Other threads did modifications. Need more memory. */
- envblock_size += envblock_size / 2;
- if (envblock_used + size > envblock_size)
- envblock_size = envblock_used + size;
-
- char *new_envblock = (char *) realloc (envblock, envblock_size + 1);
- if (new_envblock == NULL)
- {
- free (envblock);
- errno = ENOMEM;
- return NULL;
- }
- envblock = new_envblock;
- }
- memcpy (envblock + envblock_used, p, size);
+ size_t size = strlen (new_PATH) + 1;
+ memcpy (envblock + envblock_used, new_PATH, size);
envblock_used += size;
- if (envblock[envblock_used - 1] != '\0')
- {
- /* Other threads did modifications. Restart. */
- free (envblock);
- goto retry;
- }
}
+ for (ep = envp; (p = *ep) != NULL; ep++)
+ if (!(new_PATH != NULL && strncmp (p, "PATH=", 5) == 0))
+ {
+ size_t size = strlen (p) + 1;
+ if (envblock_used + size > envblock_size)
+ {
+ /* Other threads did modifications. Need more memory. */
+ envblock_size += envblock_size / 2;
+ if (envblock_used + size > envblock_size)
+ envblock_size = envblock_used + size;
+
+ char *new_envblock =
+ (char *) realloc (envblock, envblock_size + 1);
+ if (new_envblock == NULL)
+ {
+ free (envblock);
+ errno = ENOMEM;
+ return NULL;
+ }
+ envblock = new_envblock;
+ }
+ memcpy (envblock + envblock_used, p, size);
+ envblock_used += size;
+ if (envblock[envblock_used - 1] != '\0')
+ {
+ /* Other threads did modifications. Restart. */
+ free (envblock);
+ goto retry;
+ }
+ }
envblock[envblock_used] = '\0';
return envblock;
}
spawnpvech (int mode,
const char *progname, const char * const *argv,
const char * const *envp,
+ const char * const *dll_dirs,
const char *currdir,
HANDLE stdin_handle, HANDLE stdout_handle, HANDLE stderr_handle)
{
/* Copy *ENVP into a contiguous block of memory. */
char *envblock;
- if (envp == NULL)
+ if (envp == NULL && !(dll_dirs != NULL && dll_dirs[0] != NULL))
envblock = NULL;
else
{
- envblock = compose_envblock (envp);
+ if (envp == NULL)
+ /* Documentation:
+ <https://learn.microsoft.com/en-us/cpp/c-runtime-library/environ-wenviron> */
+ envp = (const char **) _environ;
+ char *new_PATH = NULL;
+ if (dll_dirs != NULL && dll_dirs[0] != NULL)
+ {
+ new_PATH = extended_PATH (dll_dirs);
+ if (new_PATH == NULL)
+ goto out_of_memory_2;
+ }
+ envblock = compose_envblock (envp, new_PATH);
+ free (new_PATH);
if (envblock == NULL)
goto out_of_memory_2;
}
/* Composes the block of memory that contains the environment variables.
ENVP must contain an environment (a NULL-terminated array of string of the
form VARIABLE=VALUE).
+ NEW_PATH must be a string of the form PATH=VALUE, or NULL if the PATH
+ environment variable from this process is suitable for the child process.
Returns a freshly allocated block of memory. In case of memory allocation
failure, NULL is returned, with errno set. */
-extern char * compose_envblock (const char * const *envp)
+extern char * compose_envblock (const char * const *envp, const char *new_PATH)
_GL_ATTRIBUTE_MALLOC _GL_ATTRIBUTE_DEALLOC_FREE;
ENVP is the NULL-terminated set of environment variable assignments, or NULL
to inherit the initial environ variable assignments from the caller and
ignore all calls to putenv(), setenv(), unsetenv() done in the caller.
+ DLL_DIRS is, on Windows platforms, a NULL-terminated list of directories
+ that contain DLLs needed to execute the program, or NULL if none is needed.
+ On other platforms, always pass NULL.
CURRDIR is the directory in which to start the program, or NULL to inherit
the working directory from the caller.
STDIN_HANDLE, STDOUT_HANDLE, STDERR_HANDLE are the handles to use for the
extern intptr_t spawnpvech (int mode,
const char *progname, const char * const *argv,
const char * const *envp,
+ const char * const *dll_dirs,
const char *currdir,
HANDLE stdin_handle, HANDLE stdout_handle,
HANDLE stderr_handle);
Description:
-Auxiliary functions for the creation of subprocesses on native Windows.
+Auxiliary functions for the creation of subprocesses on Windows.
Files:
+lib/windows-path.h
+lib/windows-path.c
lib/windows-spawn.h
lib/windows-spawn.c
Depends-on:
+environ
findprog-in
msvc-nothrow
stdbool
configure.ac:
AC_REQUIRE([AC_CANONICAL_HOST])
+gl_CONDITIONAL([GL_COND_OBJ_WINDOWS_PATH],
+ [case "$host_os" in cygwin* | mingw* | windows*) true;; *) false;; esac])
gl_CONDITIONAL([GL_COND_OBJ_WINDOWS_SPAWN],
[case "$host_os" in mingw* | windows*) true;; *) false;; esac])
Makefile.am:
+if GL_COND_OBJ_WINDOWS_PATH
+lib_SOURCES += windows-path.c
+endif
if GL_COND_OBJ_WINDOWS_SPAWN
lib_SOURCES += windows-spawn.c
endif
Include:
+"windows-path.h"
"windows-spawn.h"
License:
{
/* Check an invocation without arguments. Check the exit code. */
const char *prog_argv[2] = { prog_path, NULL };
- int ret = execute (progname, prog_argv[0], prog_argv, NULL,
+ int ret = execute (progname, prog_argv[0], prog_argv, NULL, NULL,
false, false, false, false, true, false, NULL);
ASSERT (ret == 40);
}
{
/* Check an invocation of a nonexistent program. */
const char *prog_argv[3] = { "./nonexistent", NULL };
- int ret = execute (progname, prog_argv[0], prog_argv, NULL,
+ int ret = execute (progname, prog_argv[0], prog_argv, NULL, NULL,
false, false, false, false, true, false, NULL);
ASSERT (ret == 127);
}
"",
NULL
};
- int ret = execute (progname, prog_argv[0], prog_argv, NULL,
+ int ret = execute (progname, prog_argv[0], prog_argv, NULL, NULL,
false, false, false, false, true, false, NULL);
ASSERT (ret == 0);
}
/* Check SIGPIPE handling with ignore_sigpipe = false. */
const char *prog_argv[3] = { prog_path, "3", NULL };
int termsig = 0x7DEADBEE;
- int ret = execute (progname, prog_argv[0], prog_argv, NULL,
+ int ret = execute (progname, prog_argv[0], prog_argv, NULL, NULL,
false, false, false, false, true, false, &termsig);
ASSERT (ret == 127);
ASSERT (termsig == SIGPIPE);
/* Check SIGPIPE handling with ignore_sigpipe = true. */
const char *prog_argv[3] = { prog_path, "4", NULL };
int termsig = 0x7DEADBEE;
- int ret = execute (progname, prog_argv[0], prog_argv, NULL,
+ int ret = execute (progname, prog_argv[0], prog_argv, NULL, NULL,
true, false, false, false, true, false, &termsig);
ASSERT (ret == 0);
ASSERT (termsig == SIGPIPE);
/* Check other signal. */
const char *prog_argv[3] = { prog_path, "5", NULL };
int termsig = 0x7DEADBEE;
- int ret = execute (progname, prog_argv[0], prog_argv, NULL,
+ int ret = execute (progname, prog_argv[0], prog_argv, NULL, NULL,
false, false, false, false, true, false, &termsig);
ASSERT (ret == 127);
#if defined _WIN32 && !defined __CYGWIN__
ASSERT (fp != NULL);
const char *prog_argv[3] = { prog_path, "6", NULL };
- int ret = execute (progname, prog_argv[0], prog_argv, NULL,
+ int ret = execute (progname, prog_argv[0], prog_argv, NULL, NULL,
false, false, false, false, true, false, NULL);
ASSERT (ret == 0);
ASSERT (fp != NULL);
const char *prog_argv[3] = { prog_path, "7", NULL };
- int ret = execute (progname, prog_argv[0], prog_argv, NULL,
+ int ret = execute (progname, prog_argv[0], prog_argv, NULL, NULL,
false, true, false, false, true, false, NULL);
ASSERT (ret == 0);
ASSERT (fp != NULL);
const char *prog_argv[3] = { prog_path, "8", NULL };
- int ret = execute (progname, prog_argv[0], prog_argv, NULL,
+ int ret = execute (progname, prog_argv[0], prog_argv, NULL, NULL,
false, false, false, false, true, false, NULL);
ASSERT (ret == 0);
ASSERT (fp != NULL);
const char *prog_argv[3] = { prog_path, "9", NULL };
- int ret = execute (progname, prog_argv[0], prog_argv, NULL,
+ int ret = execute (progname, prog_argv[0], prog_argv, NULL, NULL,
false, false, false, false, true, false, NULL);
ASSERT (ret == 0);
}
ASSERT (fp != NULL);
const char *prog_argv[3] = { prog_path, "10", NULL };
- int ret = execute (progname, prog_argv[0], prog_argv, NULL,
+ int ret = execute (progname, prog_argv[0], prog_argv, NULL, NULL,
false, false, true, false, true, false, NULL);
ASSERT (ret == 0);
ASSERT (fp != NULL);
const char *prog_argv[3] = { prog_path, "11", NULL };
- int ret = execute (progname, prog_argv[0], prog_argv, NULL,
+ int ret = execute (progname, prog_argv[0], prog_argv, NULL, NULL,
false, false, false, false, true, false, NULL);
ASSERT (ret == 0);
ASSERT (fp != NULL);
const char *prog_argv[3] = { prog_path, "12", NULL };
- int ret = execute (progname, prog_argv[0], prog_argv, NULL,
+ int ret = execute (progname, prog_argv[0], prog_argv, NULL, NULL,
false, false, false, false, true, false, NULL);
ASSERT (ret == 0);
}
ASSERT (fp != NULL);
const char *prog_argv[3] = { prog_path, "13", NULL };
- int ret = execute (progname, prog_argv[0], prog_argv, NULL,
+ int ret = execute (progname, prog_argv[0], prog_argv, NULL, NULL,
false, false, false, true, true, false, NULL);
ASSERT (ret == 0);
/* Check file descriptors >= 3 can be inherited. */
ASSERT (dup2 (STDOUT_FILENO, 10) >= 0);
const char *prog_argv[3] = { prog_path, "14", NULL };
- int ret = execute (progname, prog_argv[0], prog_argv, NULL,
+ int ret = execute (progname, prog_argv[0], prog_argv, NULL, 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);
const char *prog_argv[3] = { prog_path, "15", NULL };
- int ret = execute (progname, prog_argv[0], prog_argv, NULL,
+ int ret = execute (progname, prog_argv[0], prog_argv, NULL, 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);
const char *prog_argv[3] = { prog_path, "16", NULL };
- int ret = execute (progname, prog_argv[0], prog_argv, NULL,
+ int ret = execute (progname, prog_argv[0], prog_argv, NULL, NULL,
true, false, false, false, true, false, NULL);
ASSERT (ret == 0);
}
/* The file position is now 2. */
const char *prog_argv[3] = { prog_path, "17", NULL };
- int ret = execute (progname, prog_argv[0], prog_argv, NULL,
+ int ret = execute (progname, prog_argv[0], prog_argv, NULL, NULL,
false, false, false, false, true, false, NULL);
ASSERT (ret == 0);
/* The file position is now 3. */
const char *prog_argv[3] = { prog_path, "18", NULL };
- int ret = execute (progname, prog_argv[0], prog_argv, NULL,
+ int ret = execute (progname, prog_argv[0], prog_argv, NULL, NULL,
false, false, false, false, true, false, NULL);
ASSERT (ret == 0);
fd_out = 16;
const char *prog_argv[3] = { prog_path, "19", NULL };
- int ret = execute (progname, prog_argv[0], prog_argv, NULL,
+ int ret = execute (progname, prog_argv[0], prog_argv, NULL, NULL,
false, false, false, false, true, false, NULL);
#if defined _WIN32 && ! defined __CYGWIN__
ASSERT (ret == 4 + 2 * (_isatty (15) != 0) + (_isatty (16) != 0));
int fd_out = 16;
const char *prog_argv[3] = { prog_path, "20", NULL };
- int ret = execute (progname, prog_argv[0], prog_argv, NULL,
+ int ret = execute (progname, prog_argv[0], prog_argv, NULL, NULL,
false, false, false, false, true, false, NULL);
#if defined _WIN32 && ! defined __CYGWIN__
ASSERT (ret == 4 + 2 * (_isatty (15) != 0) + (_isatty (16) != 0));
#endif
const char *prog_argv[4] = { prog_path, "21", cwd, NULL };
- int ret = execute (progname, prog_argv[0], prog_argv, BASE ".sub",
+ int ret = execute (progname, prog_argv[0], prog_argv, NULL, BASE ".sub",
false, false, false, false, true, false, NULL);
ASSERT (ret == 0);
(i == 0 ? SRCDIR "executable-script" : SRCDIR "executable-script.sh");
const char *prog_argv[2] = { prog_path, NULL };
- int ret = execute (progname, prog_argv[0], prog_argv, NULL,
+ int ret = execute (progname, prog_argv[0], prog_argv, NULL, NULL,
false, false, false, false, true, false, NULL);
ASSERT (ret == 127);
}
const char *prog_path = SRCDIR "executable-shell-script";
const char *prog_argv[2] = { prog_path, NULL };
- int ret = execute (progname, prog_argv[0], prog_argv, NULL,
+ int ret = execute (progname, prog_argv[0], prog_argv, NULL, NULL,
false, false, false, false, true, false, NULL);
ASSERT (ret == 0);
argv[0] = prog;
argv[1] = (stderr_closed ? "1" : "0");
argv[2] = NULL;
- pid = create_pipe_bidi (prog, prog, argv, NULL, false, true, true, fd);
+ pid = create_pipe_bidi (prog, prog, argv, NULL, NULL, false, true, true, fd);
ASSERT (0 <= pid);
ASSERT (STDERR_FILENO < fd[0]);
ASSERT (STDERR_FILENO < fd[1]);
(i == 0 ? SRCDIR "executable-script" : SRCDIR "executable-script.sh");
const char *prog_argv[2] = { prog_path, NULL };
- pid = create_pipe_in (progname, prog_argv[0], prog_argv, NULL,
+ pid = create_pipe_in (progname, prog_argv[0], prog_argv, NULL, NULL,
NULL, false, true, false, fd);
if (pid >= 0)
{
const char *prog_path = SRCDIR "executable-shell-script";
const char *prog_argv[2] = { prog_path, NULL };
- pid = create_pipe_in (progname, prog_argv[0], prog_argv, NULL,
+ pid = create_pipe_in (progname, prog_argv[0], prog_argv, NULL, NULL,
NULL, false, true, false, fd);
ASSERT (pid >= 0);
ASSERT (fd[0] > STDERR_FILENO);