* lib/windows-spawn.h (prepare_spawn): Add a second parameter.
* lib/windows-spawn.c: Don't include xalloc.h.
(quoted_arg_length, quoted_arg_string): New functions, extracted from
prepare_spawn.
(prepare_spawn): Use malloc instead of XNMALLOC. Allocate memory for all
elements of *new_argv together.
* modules/windows-spawn (Depends-on): Remove xalloc. Add malloc-posix.
* lib/os2-spawn.h (prepare_spawn): Add a second parameter.
* lib/os2-spawn.c: Don't include xalloc.h.
(prepare_spawn): Use malloc instead of XNMALLOC. Allocate memory for all
elements of *new_argv together.
* lib/execute.c: Include xalloc.h.
(execute): Check return value of prepare_spawn. Free the memory
allocated by prepare_spawn.
* modules/execute (Depends-on): Add xalloc-die.
* lib/spawn-pipe.c: Include xalloc.h.
(create_pipe): Check return value of prepare_spawn. Free the memory
allocated by prepare_spawn.
* modules/spawn-pipe (Depends-on): Add xalloc-die.
+2020-12-10 Bruno Haible <bruno@clisp.org>
+
+ execute, spawn-pipe: Fix memory leak on native Windows.
+ * lib/windows-spawn.h (prepare_spawn): Add a second parameter.
+ * lib/windows-spawn.c: Don't include xalloc.h.
+ (quoted_arg_length, quoted_arg_string): New functions, extracted from
+ prepare_spawn.
+ (prepare_spawn): Use malloc instead of XNMALLOC. Allocate memory for all
+ elements of *new_argv together.
+ * modules/windows-spawn (Depends-on): Remove xalloc. Add malloc-posix.
+ * lib/os2-spawn.h (prepare_spawn): Add a second parameter.
+ * lib/os2-spawn.c: Don't include xalloc.h.
+ (prepare_spawn): Use malloc instead of XNMALLOC. Allocate memory for all
+ elements of *new_argv together.
+ * lib/execute.c: Include xalloc.h.
+ (execute): Check return value of prepare_spawn. Free the memory
+ allocated by prepare_spawn.
+ * modules/execute (Depends-on): Add xalloc-die.
+ * lib/spawn-pipe.c: Include xalloc.h.
+ (create_pipe): Check return value of prepare_spawn. Free the memory
+ allocated by prepare_spawn.
+ * modules/spawn-pipe (Depends-on): Add xalloc-die.
+
2020-12-10 Bruno Haible <bruno@clisp.org>
findprog-in: Relicense under LGPLv2+.
#include "filename.h"
#include "findprog.h"
#include "wait-process.h"
+#include "xalloc.h"
#include "gettext.h"
#define _(str) gettext (str)
/* Native Windows API. */
- /* FIXME: Need to free memory allocated by prepare_spawn. */
- prog_argv = prepare_spawn (prog_argv);
+ char *prog_argv_mem_to_free;
+
+ prog_argv = prepare_spawn (prog_argv, &prog_argv_mem_to_free);
+ if (prog_argv == NULL)
+ xalloc_die ();
int exitcode = -1;
HANDLE stderr_handle =
(HANDLE) _get_osfhandle (null_stderr ? nulloutfd : STDERR_FILENO);
- exitcode = spawnpvech (P_WAIT, prog_path, (const char **) prog_argv,
+ exitcode = spawnpvech (P_WAIT, prog_path, (const char **) (prog_argv + 1),
(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;
+ prog_argv[1] = prog_path;
exitcode = spawnpvech (P_WAIT, prog_argv[0], (const char **) prog_argv,
(const char **) environ, directory,
stdin_handle, stdout_handle, stderr_handle);
close (nulloutfd);
if (nullinfd >= 0)
close (nullinfd);
+ free (prog_argv);
+ free (prog_argv_mem_to_free);
free (prog_path_to_free);
if (termsigp != NULL)
#include "cloexec.h"
#include "error.h"
-#include "xalloc.h"
#include "gettext.h"
#define _(str) gettext (str)
}
char **
-prepare_spawn (char **argv)
+prepare_spawn (char **argv, char **mem_to_free)
{
size_t argc;
char **new_argv;
;
/* Allocate new argument vector. */
- new_argv = XNMALLOC (1 + argc + 1, char *);
+ new_argv = (char **) malloc ((1 + argc + 1) * sizeof (char *));
+ if (new_argv == NULL)
+ return NULL;
/* Add an element upfront that can be used when argv[0] turns out to be a
script, not a program.
On Unix, this would be "/bin/sh". */
- *new_argv++ = "sh.exe";
+ new_argv[0] = "sh.exe";
/* Put quoted arguments into the new argument vector. */
+ size_t needed_size = 0;
+ for (i = 0; i < argc; i++)
+ {
+ const char *string = argv[i];
+ const char *quoted_string = (string[0] == '\0' ? "\"\"" : string);
+ size_t length = strlen (quoted_string);
+ needed_size += length + 1;
+ }
+
+ char *mem;
+ if (needed_size == 0)
+ mem = NULL;
+ else
+ {
+ mem = (char *) malloc (needed_size);
+ if (mem == NULL)
+ {
+ /* Memory allocation failure. */
+ free (new_argv);
+ errno = ENOMEM;
+ return NULL;
+ }
+ }
+ *mem_to_free = mem;
+
for (i = 0; i < argc; i++)
{
const char *string = argv[i];
- if (string[0] == '\0')
- new_argv[i] = xstrdup ("\"\"");
- else
- new_argv[i] = (char *) string;
+ new_argv[1 + i] = mem;
+ const char *quoted_string = (string[0] == '\0' ? "\"\"" : string);
+ size_t length = strlen (quoted_string);
+ memcpy (mem, quoted_string, length + 1);
+ mem += length + 1;
}
- new_argv[argc] = NULL;
+ new_argv[1 + argc] = NULL;
return new_argv;
}
extern void undup_safer_noinherit (int tempfd, int origfd);
/* Prepares an argument vector before calling spawn(). */
-extern char ** prepare_spawn (char **argv);
+extern char ** prepare_spawn (char **argv, char **mem_to_free);
#endif /* _OS2_SPAWN_H */
#include "findprog.h"
#include "unistd-safer.h"
#include "wait-process.h"
+#include "xalloc.h"
#include "gettext.h"
#define _(str) gettext (str)
using the low-level functions CreatePipe(), DuplicateHandle(),
CreateProcess() and _open_osfhandle(); see the GNU make and GNU clisp
and cvs source code. */
+ char *prog_argv_mem_to_free;
int ifd[2];
int ofd[2];
int child;
int stdinfd;
int stdoutfd;
- /* FIXME: Need to free memory allocated by prepare_spawn. */
- prog_argv = prepare_spawn (prog_argv);
+ prog_argv = prepare_spawn (prog_argv, &prog_argv_mem_to_free);
+ if (prog_argv == NULL)
+ xalloc_die ();
if (pipe_stdout)
if (pipe2_safer (ifd, O_BINARY | O_CLOEXEC) < 0)
HANDLE stderr_handle =
(HANDLE) _get_osfhandle (null_stderr ? nulloutfd : STDERR_FILENO);
- child = spawnpvech (P_NOWAIT, prog_path, (const char **) prog_argv,
+ child = spawnpvech (P_NOWAIT, prog_path, (const char **) (prog_argv + 1),
(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;
+ prog_argv[1] = prog_path;
child = spawnpvech (P_NOWAIT, prog_argv[0], (const char **) prog_argv,
(const char **) environ, directory,
stdin_handle, stdout_handle, stderr_handle);
but it inherits all open()ed or dup2()ed file handles (which is what
we want in the case of STD*_FILENO). */
{
- child = _spawnvpe (P_NOWAIT, prog_path, (const char **) prog_argv,
+ child = _spawnvpe (P_NOWAIT, prog_path, (const char **) (prog_argv + 1),
(const char **) environ);
if (child == -1 && errno == ENOEXEC)
{
/* prog is not a native executable. Try to execute it as a
shell script. Note that prepare_spawn() has already prepended
a hidden element "sh.exe" to prog_argv. */
- --prog_argv;
child = _spawnvpe (P_NOWAIT, prog_argv[0], (const char **) prog_argv,
(const char **) environ);
}
close (ifd[1]);
# endif
+ free (prog_argv);
+ free (prog_argv_mem_to_free);
free (prog_path_to_free);
if (child == -1)
#include <process.h>
#include "findprog.h"
-#include "xalloc.h"
/* Don't assume that UNICODE is not defined. */
#undef STARTUPINFO
#define SHELL_SPECIAL_CHARS "\"\\ \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037*?"
#define SHELL_SPACE_CHARS " \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037"
+
+/* Returns the length of a quoted argument string. */
+static size_t
+quoted_arg_length (const char *string)
+{
+ bool quote_around = (strpbrk (string, SHELL_SPACE_CHARS) != NULL);
+ size_t length;
+ unsigned int backslashes;
+ const char *s;
+
+ length = 0;
+ backslashes = 0;
+ if (quote_around)
+ length++;
+ for (s = string; *s != '\0'; s++)
+ {
+ char c = *s;
+ if (c == '"')
+ length += backslashes + 1;
+ length++;
+ if (c == '\\')
+ backslashes++;
+ else
+ backslashes = 0;
+ }
+ if (quote_around)
+ length += backslashes + 1;
+
+ return length;
+}
+
+/* Produces a quoted argument string.
+ Stores exactly quoted_arg_length (STRING) + 1 bytes, including the final
+ NUL byte, at MEM.
+ Returns a pointer past the stored quoted argument string. */
+static char *
+quoted_arg_string (const char *string, char *mem)
+{
+ bool quote_around = (strpbrk (string, SHELL_SPACE_CHARS) != NULL);
+ char *p;
+ unsigned int backslashes;
+ const char *s;
+
+ p = mem;
+ backslashes = 0;
+ if (quote_around)
+ *p++ = '"';
+ for (s = string; *s != '\0'; s++)
+ {
+ char c = *s;
+ if (c == '"')
+ {
+ unsigned int j;
+ for (j = backslashes + 1; j > 0; j--)
+ *p++ = '\\';
+ }
+ *p++ = c;
+ if (c == '\\')
+ backslashes++;
+ else
+ backslashes = 0;
+ }
+ if (quote_around)
+ {
+ unsigned int j;
+ for (j = backslashes; j > 0; j--)
+ *p++ = '\\';
+ *p++ = '"';
+ }
+ *p++ = '\0';
+
+ return p;
+}
+
char **
-prepare_spawn (char **argv)
+prepare_spawn (char **argv, char **mem_to_free)
{
size_t argc;
char **new_argv;
;
/* Allocate new argument vector. */
- new_argv = XNMALLOC (1 + argc + 1, char *);
+ new_argv = (char **) malloc ((1 + argc + 1) * sizeof (char *));
/* Add an element upfront that can be used when argv[0] turns out to be a
script, not a program.
"sh.exe". We have to omit the directory part and rely on the search in
PATH, because the mingw "mount points" are not visible inside Windows
CreateProcess(). */
- *new_argv++ = "sh.exe";
+ new_argv[0] = "sh.exe";
/* Put quoted arguments into the new argument vector. */
+ size_t needed_size = 0;
for (i = 0; i < argc; i++)
{
const char *string = argv[i];
+ size_t length;
if (string[0] == '\0')
- new_argv[i] = xstrdup ("\"\"");
+ length = strlen ("\"\"");
else if (strpbrk (string, SHELL_SPECIAL_CHARS) != NULL)
- {
- bool quote_around = (strpbrk (string, SHELL_SPACE_CHARS) != NULL);
- size_t length;
- unsigned int backslashes;
- const char *s;
- char *quoted_string;
- char *p;
-
- length = 0;
- backslashes = 0;
- if (quote_around)
- length++;
- for (s = string; *s != '\0'; s++)
- {
- char c = *s;
- if (c == '"')
- length += backslashes + 1;
- length++;
- if (c == '\\')
- backslashes++;
- else
- backslashes = 0;
- }
- if (quote_around)
- length += backslashes + 1;
+ length = quoted_arg_length (string);
+ else
+ length = strlen (string);
+ needed_size += length + 1;
+ }
- quoted_string = (char *) xmalloc (length + 1);
+ char *mem;
+ if (needed_size == 0)
+ mem = NULL;
+ else
+ {
+ mem = (char *) malloc (needed_size);
+ if (mem == NULL)
+ {
+ /* Memory allocation failure. */
+ free (new_argv);
+ errno = ENOMEM;
+ return NULL;
+ }
+ }
+ *mem_to_free = mem;
- p = quoted_string;
- backslashes = 0;
- if (quote_around)
- *p++ = '"';
- for (s = string; *s != '\0'; s++)
- {
- char c = *s;
- if (c == '"')
- {
- unsigned int j;
- for (j = backslashes + 1; j > 0; j--)
- *p++ = '\\';
- }
- *p++ = c;
- if (c == '\\')
- backslashes++;
- else
- backslashes = 0;
- }
- if (quote_around)
- {
- unsigned int j;
- for (j = backslashes; j > 0; j--)
- *p++ = '\\';
- *p++ = '"';
- }
- *p = '\0';
+ for (i = 0; i < argc; i++)
+ {
+ const char *string = argv[i];
- new_argv[i] = quoted_string;
+ new_argv[1 + i] = mem;
+ if (string[0] == '\0')
+ {
+ size_t length = strlen ("\"\"");
+ memcpy (mem, "\"\"", length + 1);
+ mem += length + 1;
+ }
+ else if (strpbrk (string, SHELL_SPECIAL_CHARS) != NULL)
+ {
+ mem = quoted_arg_string (string, mem);
}
else
- new_argv[i] = (char *) string;
+ {
+ size_t length = strlen (string);
+ memcpy (mem, string, length + 1);
+ mem += length + 1;
+ }
}
- new_argv[argc] = NULL;
+ new_argv[1 + argc] = NULL;
return new_argv;
}
#include <windows.h>
/* Prepares an argument vector before calling spawn().
+
Note that spawn() does not by itself call the command interpreter
(getenv ("COMSPEC") != NULL ? getenv ("COMSPEC") :
({ OSVERSIONINFO v; v.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
- programs that inspect GetCommandLine() and ignore argv,
- mingw programs that have a global variable 'int _CRT_glob = 0;',
- Cygwin programs, when invoked from a Cygwin program.
+
+ prepare_spawn creates and returns a new argument vector, where the arguments
+ are appropriately quoted and an additional argument "sh.exe" has been added
+ at the beginning. The new argument vector is freshly allocated. The memory
+ for all its elements is allocated within *MEM_TO_FREE, which is freshly
+ allocated as well. In case of memory allocation failure, NULL is returned,
+ with errno set.
*/
-extern char ** prepare_spawn (char **argv);
+extern char ** prepare_spawn (char **argv, char **mem_to_free);
/* Creates a subprocess.
MODE is either P_WAIT or P_NOWAIT.
unistd
wait-process
windows-spawn
+xalloc-die
configure.ac:
gl_EXECUTE
unistd-safer
wait-process
windows-spawn
+xalloc-die
configure.ac:
gl_SPAWN_PIPE
stdlib
strpbrk
unistd
-xalloc
+malloc-posix
configure.ac:
AC_REQUIRE([AC_CANONICAL_HOST])