Files
glib/glib/gspawn-win32.c
Tor Lillqvist e40e038028 Do handle G_SPAWN_DO_NOT_REAP_CHILD after all, similarily as on Unix. If
2002-11-18  Tor Lillqvist  <tml@iki.fi>

	* glib/gspawn-win32.c (do_spawn_with_pipes): Do handle
	G_SPAWN_DO_NOT_REAP_CHILD after all, similarily as on Unix. If the
	flag is not set, don't call DuplicateHandle() on the handle
	returned by the helper process, and set the "child pid" as
	returned to the called to zero. Close the handle to the helper
	process in all cases.

	* glib/gspawn.c (g_spawn_async_with_pipes): Document Windows
	behaviour of G_SPAWN_DO_NOT_REAP_CHILD.
2002-11-18 09:40:51 +00:00

874 lines
23 KiB
C

/* gspawn-win32.c - Process launching on Win32
*
* Copyright 2000 Red Hat, Inc.
*
* GLib 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 of the
* License, or (at your option) any later version.
*
* GLib 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 GLib; see the file COPYING.LIB. If not, write
* to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/*
* Implementation details on Win32.
*
* - There is no way to set the no-inherit flag for
* a "file descriptor" in the MS C runtime. The flag is there,
* and the dospawn() function uses it, but unfortunately
* this flag can only be set when opening the file.
* - As there is no fork(), we cannot reliably change directory
* before starting the child process. (There might be several threads
* running, and the current directory is common for all threads.)
*
* Thus, we must in most cases use a helper program to handle closing
* of (inherited) file descriptors and changing of directory. In fact,
* we do it all the time.
*/
/* Define this to get some logging all the time */
/* #define G_SPAWN_WIN32_DEBUG */
#include <config.h>
#include "glib.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <windows.h>
#include <errno.h>
#include <fcntl.h>
#include <io.h>
#include <process.h>
#include <direct.h>
#include "glibintl.h"
#ifdef G_SPAWN_WIN32_DEBUG
static int debug = 1;
#define SETUP_DEBUG() /* empty */
#else
static int debug = -1;
#define SETUP_DEBUG() \
G_STMT_START \
{ \
if (debug == -1) \
{ \
if (getenv ("G_SPAWN_WIN32_DEBUG") != NULL) \
debug = 1; \
else \
debug = 0; \
} \
} \
G_STMT_END
#endif
enum
{
CHILD_NO_ERROR,
CHILD_CHDIR_FAILED,
CHILD_SPAWN_FAILED,
};
enum {
ARG_CHILD_ERR_REPORT = 1,
ARG_STDIN,
ARG_STDOUT,
ARG_STDERR,
ARG_WORKING_DIRECTORY,
ARG_CLOSE_DESCRIPTORS,
ARG_USE_PATH,
ARG_WAIT,
ARG_PROGRAM,
ARG_COUNT = ARG_PROGRAM
};
#ifndef GSPAWN_HELPER
static gboolean make_pipe (gint p[2],
GError **error);
static gboolean do_spawn_with_pipes (gboolean dont_wait,
gboolean dont_return_handle,
const gchar *working_directory,
gchar **argv,
gchar **envp,
gboolean close_descriptors,
gboolean search_path,
gboolean stdout_to_null,
gboolean stderr_to_null,
gboolean child_inherits_stdin,
GSpawnChildSetupFunc child_setup,
gpointer user_data,
gint *child_pid,
gint *standard_input,
gint *standard_output,
gint *standard_error,
gint *exit_status,
GError **error);
GQuark
g_spawn_error_quark (void)
{
static GQuark quark = 0;
if (quark == 0)
quark = g_quark_from_static_string ("g-exec-error-quark");
return quark;
}
gboolean
g_spawn_async (const gchar *working_directory,
gchar **argv,
gchar **envp,
GSpawnFlags flags,
GSpawnChildSetupFunc child_setup,
gpointer user_data,
gint *child_pid,
GError **error)
{
g_return_val_if_fail (argv != NULL, FALSE);
return g_spawn_async_with_pipes (working_directory,
argv, envp,
flags,
child_setup,
user_data,
child_pid,
NULL, NULL, NULL,
error);
}
/* Avoids a danger in threaded situations (calling close()
* on a file descriptor twice, and another thread has
* re-opened it since the first close)
*/
static gint
close_and_invalidate (gint *fd)
{
gint ret;
if (*fd < 0)
return -1;
else
{
ret = close (*fd);
*fd = -1;
}
return ret;
}
typedef enum
{
READ_FAILED = 0, /* FALSE */
READ_OK,
READ_EOF
} ReadResult;
static ReadResult
read_data (GString *str,
GIOChannel *iochannel,
GError **error)
{
GIOStatus giostatus;
gssize bytes;
gchar buf[4096];
again:
giostatus = g_io_channel_read_chars (iochannel, buf, sizeof (buf), &bytes, NULL);
if (bytes == 0)
return READ_EOF;
else if (bytes > 0)
{
g_string_append_len (str, buf, bytes);
return READ_OK;
}
else if (giostatus == G_IO_STATUS_AGAIN)
goto again;
else if (giostatus == G_IO_STATUS_ERROR)
{
g_set_error (error,
G_SPAWN_ERROR,
G_SPAWN_ERROR_READ,
_("Failed to read data from child process"));
return READ_FAILED;
}
else
return READ_OK;
}
gboolean
g_spawn_sync (const gchar *working_directory,
gchar **argv,
gchar **envp,
GSpawnFlags flags,
GSpawnChildSetupFunc child_setup,
gpointer user_data,
gchar **standard_output,
gchar **standard_error,
gint *exit_status,
GError **error)
{
gint outpipe = -1;
gint errpipe = -1;
gint pid;
GIOChannel *outchannel = NULL;
GIOChannel *errchannel = NULL;
GPollFD outfd, errfd;
GPollFD fds[2];
gint nfds;
gint outindex = -1;
gint errindex = -1;
gint ret;
GString *outstr = NULL;
GString *errstr = NULL;
gboolean failed;
gint status;
g_return_val_if_fail (argv != NULL, FALSE);
g_return_val_if_fail (!(flags & G_SPAWN_DO_NOT_REAP_CHILD), FALSE);
g_return_val_if_fail (standard_output == NULL ||
!(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE);
g_return_val_if_fail (standard_error == NULL ||
!(flags & G_SPAWN_STDERR_TO_DEV_NULL), FALSE);
/* Just to ensure segfaults if callers try to use
* these when an error is reported.
*/
if (standard_output)
*standard_output = NULL;
if (standard_error)
*standard_error = NULL;
if (!do_spawn_with_pipes (FALSE,
TRUE,
working_directory,
argv,
envp,
!(flags & G_SPAWN_LEAVE_DESCRIPTORS_OPEN),
(flags & G_SPAWN_SEARCH_PATH) != 0,
(flags & G_SPAWN_STDOUT_TO_DEV_NULL) != 0,
(flags & G_SPAWN_STDERR_TO_DEV_NULL) != 0,
(flags & G_SPAWN_CHILD_INHERITS_STDIN) != 0,
child_setup,
user_data,
&pid,
NULL,
standard_output ? &outpipe : NULL,
standard_error ? &errpipe : NULL,
&status,
error))
return FALSE;
/* Read data from child. */
failed = FALSE;
if (outpipe >= 0)
{
outstr = g_string_new ("");
outchannel = g_io_channel_win32_new_fd (outpipe);
g_io_channel_set_encoding (outchannel, NULL, NULL);
g_io_channel_win32_make_pollfd (outchannel,
G_IO_IN | G_IO_ERR | G_IO_HUP,
&outfd);
}
if (errpipe >= 0)
{
errstr = g_string_new ("");
errchannel = g_io_channel_win32_new_fd (errpipe);
g_io_channel_set_encoding (errchannel, NULL, NULL);
g_io_channel_win32_make_pollfd (errchannel,
G_IO_IN | G_IO_ERR | G_IO_HUP,
&errfd);
}
/* Read data until we get EOF on both pipes. */
while (!failed &&
(outpipe >= 0 ||
errpipe >= 0))
{
nfds = 0;
if (outpipe >= 0)
{
fds[nfds] = outfd;
outindex = nfds;
nfds++;
}
if (errpipe >= 0)
{
fds[nfds] = errfd;
errindex = nfds;
nfds++;
}
if (debug)
g_print ("%s:g_spawn_sync: calling g_io_channel_win32_poll, nfds=%d\n",
__FILE__, nfds);
ret = g_io_channel_win32_poll (fds, nfds, -1);
if (ret < 0)
{
failed = TRUE;
g_set_error (error,
G_SPAWN_ERROR,
G_SPAWN_ERROR_READ,
_("Unexpected error in g_io_channel_win32_poll() reading data from a child process"));
break;
}
if (outpipe >= 0 && (fds[outindex].revents & G_IO_IN))
{
switch (read_data (outstr, outchannel, error))
{
case READ_FAILED:
if (debug)
g_print ("g_spawn_sync: outchannel: READ_FAILED\n");
failed = TRUE;
break;
case READ_EOF:
if (debug)
g_print ("g_spawn_sync: outchannel: READ_EOF\n");
g_io_channel_unref (outchannel);
outchannel = NULL;
close_and_invalidate (&outpipe);
break;
default:
if (debug)
g_print ("g_spawn_sync: outchannel: OK\n");
break;
}
if (failed)
break;
}
if (errpipe >= 0 && (fds[errindex].revents & G_IO_IN))
{
switch (read_data (errstr, errchannel, error))
{
case READ_FAILED:
if (debug)
g_print ("g_spawn_sync: errchannel: READ_FAILED\n");
failed = TRUE;
break;
case READ_EOF:
if (debug)
g_print ("g_spawn_sync: errchannel: READ_EOF\n");
g_io_channel_unref (errchannel);
errchannel = NULL;
close_and_invalidate (&errpipe);
break;
default:
if (debug)
g_print ("g_spawn_sync: errchannel: OK\n");
break;
}
if (failed)
break;
}
}
/* These should only be open still if we had an error. */
if (outchannel != NULL)
g_io_channel_unref (outchannel);
if (errchannel != NULL)
g_io_channel_unref (errchannel);
if (outpipe >= 0)
close_and_invalidate (&outpipe);
if (errpipe >= 0)
close_and_invalidate (&errpipe);
if (failed)
{
if (outstr)
g_string_free (outstr, TRUE);
if (errstr)
g_string_free (errstr, TRUE);
return FALSE;
}
else
{
if (exit_status)
*exit_status = status;
if (standard_output)
*standard_output = g_string_free (outstr, FALSE);
if (standard_error)
*standard_error = g_string_free (errstr, FALSE);
return TRUE;
}
}
gboolean
g_spawn_async_with_pipes (const gchar *working_directory,
gchar **argv,
gchar **envp,
GSpawnFlags flags,
GSpawnChildSetupFunc child_setup,
gpointer user_data,
gint *child_pid,
gint *standard_input,
gint *standard_output,
gint *standard_error,
GError **error)
{
g_return_val_if_fail (argv != NULL, FALSE);
g_return_val_if_fail (standard_output == NULL ||
!(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE);
g_return_val_if_fail (standard_error == NULL ||
!(flags & G_SPAWN_STDERR_TO_DEV_NULL), FALSE);
/* can't inherit stdin if we have an input pipe. */
g_return_val_if_fail (standard_input == NULL ||
!(flags & G_SPAWN_CHILD_INHERITS_STDIN), FALSE);
return do_spawn_with_pipes (TRUE,
!(flags & G_SPAWN_DO_NOT_REAP_CHILD),
working_directory,
argv,
envp,
!(flags & G_SPAWN_LEAVE_DESCRIPTORS_OPEN),
(flags & G_SPAWN_SEARCH_PATH) != 0,
(flags & G_SPAWN_STDOUT_TO_DEV_NULL) != 0,
(flags & G_SPAWN_STDERR_TO_DEV_NULL) != 0,
(flags & G_SPAWN_CHILD_INHERITS_STDIN) != 0,
child_setup,
user_data,
child_pid,
standard_input,
standard_output,
standard_error,
NULL,
error);
}
gboolean
g_spawn_command_line_sync (const gchar *command_line,
gchar **standard_output,
gchar **standard_error,
gint *exit_status,
GError **error)
{
gboolean retval;
gchar **argv = 0;
g_return_val_if_fail (command_line != NULL, FALSE);
if (!g_shell_parse_argv (command_line,
NULL, &argv,
error))
return FALSE;
retval = g_spawn_sync (NULL,
argv,
NULL,
G_SPAWN_SEARCH_PATH,
NULL,
NULL,
standard_output,
standard_error,
exit_status,
error);
g_strfreev (argv);
return retval;
}
gboolean
g_spawn_command_line_async (const gchar *command_line,
GError **error)
{
gboolean retval;
gchar **argv = 0;
g_return_val_if_fail (command_line != NULL, FALSE);
if (!g_shell_parse_argv (command_line,
NULL, &argv,
error))
return FALSE;
retval = g_spawn_async (NULL,
argv,
NULL,
G_SPAWN_SEARCH_PATH,
NULL,
NULL,
NULL,
error);
g_strfreev (argv);
return retval;
}
static gint
do_spawn (gboolean dont_wait,
gint child_err_report_fd,
gint stdin_fd,
gint stdout_fd,
gint stderr_fd,
const gchar *working_directory,
gchar **argv,
gchar **envp,
gboolean close_descriptors,
gboolean search_path,
gboolean stdout_to_null,
gboolean stderr_to_null,
gboolean child_inherits_stdin,
GSpawnChildSetupFunc child_setup,
gpointer user_data)
{
gchar **new_argv;
gchar args[ARG_COUNT][10];
gint i;
int rc;
int argc = 0;
SETUP_DEBUG();
while (argv[argc])
++argc;
new_argv = g_new (gchar *, argc + 1 + ARG_COUNT);
new_argv[0] = "gspawn-win32-helper";
sprintf (args[ARG_CHILD_ERR_REPORT], "%d", child_err_report_fd);
new_argv[ARG_CHILD_ERR_REPORT] = args[ARG_CHILD_ERR_REPORT];
if (stdin_fd >= 0)
{
sprintf (args[ARG_STDIN], "%d", stdin_fd);
new_argv[ARG_STDIN] = args[ARG_STDIN];
}
else if (child_inherits_stdin)
{
/* Let stdin be alone */
new_argv[ARG_STDIN] = "-";
}
else
{
/* Keep process from blocking on a read of stdin */
new_argv[ARG_STDIN] = "z";
}
if (stdout_fd >= 0)
{
sprintf (args[ARG_STDOUT], "%d", stdout_fd);
new_argv[ARG_STDOUT] = args[ARG_STDOUT];
}
else if (stdout_to_null)
{
new_argv[ARG_STDOUT] = "z";
}
else
{
new_argv[ARG_STDOUT] = "-";
}
if (stderr_fd >= 0)
{
sprintf (args[ARG_STDERR], "%d", stderr_fd);
new_argv[ARG_STDERR] = args[ARG_STDERR];
}
else if (stderr_to_null)
{
new_argv[ARG_STDERR] = "z";
}
else
{
new_argv[ARG_STDERR] = "-";
}
if (working_directory && *working_directory)
new_argv[ARG_WORKING_DIRECTORY] = g_strdup (working_directory);
else
new_argv[ARG_WORKING_DIRECTORY] = "-";
if (close_descriptors)
new_argv[ARG_CLOSE_DESCRIPTORS] = "y";
else
new_argv[ARG_CLOSE_DESCRIPTORS] = "-";
if (search_path)
new_argv[ARG_USE_PATH] = "y";
else
new_argv[ARG_USE_PATH] = "-";
if (dont_wait)
new_argv[ARG_WAIT] = "-";
else
new_argv[ARG_WAIT] = "w";
for (i = 0; i <= argc; i++)
new_argv[ARG_PROGRAM + i] = argv[i];
/* Call user function just before we execute the helper program,
* which executes the program. Dunno what's the usefulness of this.
* A child setup function used on Unix probably isn't of much use
* as such on Win32, anyhow
*/
if (child_setup)
{
(* child_setup) (user_data);
}
if (debug)
{
g_print ("calling gspawn-win32-helper with argv:\n");
for (i = 0; i < argc + 1 + ARG_COUNT; i++)
g_print ("argv[%d]: %s\n", i, (new_argv[i] ? new_argv[i] : "NULL"));
}
if (envp != NULL)
/* Let's hope envp hasn't mucked with PATH so that
* gspawn-win32-helper.exe isn't found.
*/
rc = spawnvpe (P_NOWAIT, "gspawn-win32-helper", new_argv, envp);
else
rc = spawnvp (P_NOWAIT, "gspawn-win32-helper", new_argv);
/* Close the child_err_report_fd and the other process's ends of the
* pipes in this process, otherwise the reader will never get
* EOF.
*/
close (child_err_report_fd);
if (stdin_fd >= 0)
close (stdin_fd);
if (stdout_fd >= 0)
close (stdout_fd);
if (stderr_fd >= 0)
close (stderr_fd);
g_free (new_argv[ARG_WORKING_DIRECTORY]);
g_free (new_argv);
return rc;
}
static gboolean
read_ints (int fd,
gint* buf,
gint n_ints_in_buf,
gint *n_ints_read,
GError **error)
{
gint bytes = 0;
while (bytes < sizeof(gint)*n_ints_in_buf)
{
gint chunk;
if (debug)
g_print ("%s:read_ints: trying to read %d bytes from pipe...\n",
__FILE__,
sizeof(gint)*n_ints_in_buf - bytes);
chunk = read (fd, ((gchar*)buf) + bytes,
sizeof(gint)*n_ints_in_buf - bytes);
if (debug)
g_print ("... got %d bytes\n", chunk);
if (chunk < 0)
{
/* Some weird shit happened, bail out */
g_set_error (error,
G_SPAWN_ERROR,
G_SPAWN_ERROR_FAILED,
_("Failed to read from child pipe (%s)"),
g_strerror (errno));
return FALSE;
}
else if (chunk == 0)
break; /* EOF */
else
bytes += chunk;
}
*n_ints_read = bytes/sizeof(gint);
return TRUE;
}
static gboolean
do_spawn_with_pipes (gboolean dont_wait,
gboolean dont_return_handle,
const gchar *working_directory,
gchar **argv,
gchar **envp,
gboolean close_descriptors,
gboolean search_path,
gboolean stdout_to_null,
gboolean stderr_to_null,
gboolean child_inherits_stdin,
GSpawnChildSetupFunc child_setup,
gpointer user_data,
gint *child_pid,
gint *standard_input,
gint *standard_output,
gint *standard_error,
gint *exit_status,
GError **error)
{
gint stdin_pipe[2] = { -1, -1 };
gint stdout_pipe[2] = { -1, -1 };
gint stderr_pipe[2] = { -1, -1 };
gint child_err_report_pipe[2] = { -1, -1 };
gint helper = -1;
gint buf[2];
gint n_ints = 0;
if (!make_pipe (child_err_report_pipe, error))
return FALSE;
if (standard_input && !make_pipe (stdin_pipe, error))
goto cleanup_and_fail;
if (standard_output && !make_pipe (stdout_pipe, error))
goto cleanup_and_fail;
if (standard_error && !make_pipe (stderr_pipe, error))
goto cleanup_and_fail;
helper = do_spawn (dont_wait,
child_err_report_pipe[1],
stdin_pipe[0],
stdout_pipe[1],
stderr_pipe[1],
working_directory,
argv,
envp,
close_descriptors,
search_path,
stdout_to_null,
stderr_to_null,
child_inherits_stdin,
child_setup,
user_data);
/* do_spawn() returns -1 if gspawn-win32-helper couldn't be run */
if (helper == -1)
{
g_set_error (error,
G_SPAWN_ERROR,
G_SPAWN_ERROR_FAILED,
_("Failed to execute helper program"));
goto cleanup_and_fail;
}
if (!read_ints (child_err_report_pipe[0],
buf, 2, &n_ints,
error) ||
n_ints != 2)
goto cleanup_and_fail;
/* Error code from gspawn-win32-helper. */
switch (buf[0])
{
case CHILD_NO_ERROR:
if (child_pid && dont_wait && !dont_return_handle)
{
/* helper is our HANDLE for gspawn-win32-helper. It has
* told us the HANDLE of its child. Duplicate that into
* a HANDLE valid in this process.
*/
if (!DuplicateHandle ((HANDLE) helper, (HANDLE) buf[1],
GetCurrentProcess (), (LPHANDLE) child_pid,
0, TRUE, DUPLICATE_SAME_ACCESS))
*child_pid = 0;
}
else if (child_pid)
*child_pid = 0;
break;
case CHILD_CHDIR_FAILED:
g_set_error (error,
G_SPAWN_ERROR,
G_SPAWN_ERROR_CHDIR,
_("Failed to change to directory '%s' (%s)"),
working_directory,
g_strerror (buf[1]));
goto cleanup_and_fail;
case CHILD_SPAWN_FAILED:
g_set_error (error,
G_SPAWN_ERROR,
G_SPAWN_ERROR_FAILED,
_("Failed to execute child process (%s)"),
g_strerror (buf[1]));
goto cleanup_and_fail;
}
/* Success against all odds! return the information */
if (standard_input)
*standard_input = stdin_pipe[1];
if (standard_output)
*standard_output = stdout_pipe[0];
if (standard_error)
*standard_error = stderr_pipe[0];
if (exit_status)
*exit_status = buf[1];
CloseHandle ((HANDLE) helper);
return TRUE;
cleanup_and_fail:
if (helper != -1)
CloseHandle ((HANDLE) helper);
close_and_invalidate (&child_err_report_pipe[0]);
close_and_invalidate (&child_err_report_pipe[1]);
close_and_invalidate (&stdin_pipe[0]);
close_and_invalidate (&stdin_pipe[1]);
close_and_invalidate (&stdout_pipe[0]);
close_and_invalidate (&stdout_pipe[1]);
close_and_invalidate (&stderr_pipe[0]);
close_and_invalidate (&stderr_pipe[1]);
return FALSE;
}
static gboolean
make_pipe (gint p[2],
GError **error)
{
if (pipe (p) < 0)
{
g_set_error (error,
G_SPAWN_ERROR,
G_SPAWN_ERROR_FAILED,
_("Failed to create pipe for communicating with child process (%s)"),
g_strerror (errno));
return FALSE;
}
else
return TRUE;
}
#endif /* !GSPAWN_HELPER */