glib/glib/gspawn-win32.c
Daniel Drake 3524de16e4 gspawn: Add g_spawn_async_with_fds variant
Add a new process spawning function variant which allows the caller
to pass specific file descriptors for stdin, stdout and stderr.
It is otherwise identical to g_spawn_async_with_pipes.

Allow the same fd to be passed in multiple parameters. To make this
workable, the child process logic that closes the fd after the first time
it has been dup2'ed needed tweaking; we now just set those fds to be
closed upon exec using the CLOEXEC flag. Add a test for this case.

This will be used by gnome-shell to avoid performing equivalent
dup2 actions in a child_setup function. Dropping use of child_setup will
enable use of an upcoming optimized process spawning codepath.
2018-06-21 11:43:32 -05:00

1463 lines
41 KiB
C

/* gspawn-win32.c - Process launching on Win32
*
* Copyright 2000 Red Hat, Inc.
* Copyright 2003 Tor Lillqvist
*
* This library 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 library 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 library; if not, see <http://www.gnu.org/licenses/>.
*/
/*
* 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 many cases use a helper program to handle closing
* of (inherited) file descriptors and changing of directory. The
* helper process is also needed if the standard input, standard
* output, or standard error of the process to be run are supposed to
* be redirected somewhere.
*
* The structure of the source code in this file is a mess, I know.
*/
/* Define this to get some logging all the time */
/* #define G_SPAWN_WIN32_DEBUG */
#include "config.h"
#include "glib.h"
#include "glib-private.h"
#include "gprintfint.h"
#include "glibintl.h"
#include "gspawn-private.h"
#include "gthread.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 <wchar.h>
#ifndef GSPAWN_HELPER
#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
#endif
enum
{
CHILD_NO_ERROR,
CHILD_CHDIR_FAILED,
CHILD_SPAWN_FAILED,
CHILD_SPAWN_NOENT,
};
enum {
ARG_CHILD_ERR_REPORT = 1,
ARG_HELPER_SYNC,
ARG_STDIN,
ARG_STDOUT,
ARG_STDERR,
ARG_WORKING_DIRECTORY,
ARG_CLOSE_DESCRIPTORS,
ARG_USE_PATH,
ARG_WAIT,
ARG_PROGRAM,
ARG_COUNT = ARG_PROGRAM
};
static int
dup_noninherited (int fd,
int mode)
{
HANDLE filehandle;
DuplicateHandle (GetCurrentProcess (), (LPHANDLE) _get_osfhandle (fd),
GetCurrentProcess (), &filehandle,
0, FALSE, DUPLICATE_SAME_ACCESS);
close (fd);
return _open_osfhandle ((gintptr) filehandle, mode | _O_NOINHERIT);
}
#ifndef GSPAWN_HELPER
#ifdef _WIN64
#define HELPER_PROCESS "gspawn-win64-helper"
#else
#define HELPER_PROCESS "gspawn-win32-helper"
#endif
static gchar *
protect_argv_string (const gchar *string)
{
const gchar *p = string;
gchar *retval, *q;
gint len = 0;
gboolean need_dblquotes = FALSE;
while (*p)
{
if (*p == ' ' || *p == '\t')
need_dblquotes = TRUE;
else if (*p == '"')
len++;
else if (*p == '\\')
{
const gchar *pp = p;
while (*pp && *pp == '\\')
pp++;
if (*pp == '"')
len++;
}
len++;
p++;
}
q = retval = g_malloc (len + need_dblquotes*2 + 1);
p = string;
if (need_dblquotes)
*q++ = '"';
while (*p)
{
if (*p == '"')
*q++ = '\\';
else if (*p == '\\')
{
const gchar *pp = p;
while (*pp && *pp == '\\')
pp++;
if (*pp == '"')
*q++ = '\\';
}
*q++ = *p;
p++;
}
if (need_dblquotes)
*q++ = '"';
*q++ = '\0';
return retval;
}
static gint
protect_argv (gchar **argv,
gchar ***new_argv)
{
gint i;
gint argc = 0;
while (argv[argc])
++argc;
*new_argv = g_new (gchar *, argc+1);
/* Quote each argv element if necessary, so that it will get
* reconstructed correctly in the C runtime startup code. Note that
* the unquoting algorithm in the C runtime is really weird, and
* rather different than what Unix shells do. See stdargv.c in the C
* runtime sources (in the Platform SDK, in src/crt).
*
* Note that an new_argv[0] constructed by this function should
* *not* be passed as the filename argument to a spawn* or exec*
* family function. That argument should be the real file name
* without any quoting.
*/
for (i = 0; i < argc; i++)
(*new_argv)[i] = protect_argv_string (argv[i]);
(*new_argv)[argc] = NULL;
return argc;
}
G_DEFINE_QUARK (g-exec-error-quark, g_spawn_error)
G_DEFINE_QUARK (g-spawn-exit-error-quark, g_spawn_exit_error)
gboolean
g_spawn_async (const gchar *working_directory,
gchar **argv,
gchar **envp,
GSpawnFlags flags,
GSpawnChildSetupFunc child_setup,
gpointer user_data,
GPid *child_handle,
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_handle,
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 void
close_and_invalidate (gint *fd)
{
if (*fd < 0)
return;
close (*fd);
*fd = -1;
}
typedef enum
{
READ_FAILED = 0, /* FALSE */
READ_OK,
READ_EOF
} ReadResult;
static ReadResult
read_data (GString *str,
GIOChannel *iochannel,
GError **error)
{
GIOStatus giostatus;
gsize 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_literal (error, G_SPAWN_ERROR, G_SPAWN_ERROR_READ,
_("Failed to read data from child process"));
return READ_FAILED;
}
else
return READ_OK;
}
static gboolean
make_pipe (gint p[2],
GError **error)
{
if (_pipe (p, 4096, _O_BINARY) < 0)
{
int errsv = errno;
g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED,
_("Failed to create pipe for communicating with child process (%s)"),
g_strerror (errsv));
return FALSE;
}
else
return TRUE;
}
/* The helper process writes a status report back to us, through a
* pipe, consisting of two ints.
*/
static gboolean
read_helper_report (int fd,
gintptr report[2],
GError **error)
{
gint bytes = 0;
while (bytes < sizeof(gintptr)*2)
{
gint chunk;
int errsv;
if (debug)
g_print ("%s:read_helper_report: read %" G_GSIZE_FORMAT "...\n",
__FILE__,
sizeof(gintptr)*2 - bytes);
chunk = read (fd, ((gchar*)report) + bytes,
sizeof(gintptr)*2 - bytes);
errsv = errno;
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 (errsv));
return FALSE;
}
else if (chunk == 0)
{
g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED,
_("Failed to read from child pipe (%s)"),
"EOF");
break; /* EOF */
}
else
bytes += chunk;
}
if (bytes < sizeof(gintptr)*2)
return FALSE;
return TRUE;
}
static void
set_child_error (gintptr report[2],
const gchar *working_directory,
GError **error)
{
switch (report[0])
{
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 (report[1]));
break;
case CHILD_SPAWN_FAILED:
g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED,
_("Failed to execute child process (%s)"),
g_strerror (report[1]));
break;
case CHILD_SPAWN_NOENT:
g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT,
_("Failed to execute child process (%s)"),
g_strerror (report[1]));
break;
default:
g_assert_not_reached ();
}
}
static gboolean
utf8_charv_to_wcharv (char **utf8_charv,
wchar_t ***wcharv,
int *error_index,
GError **error)
{
wchar_t **retval = NULL;
*wcharv = NULL;
if (utf8_charv != NULL)
{
int n = 0, i;
while (utf8_charv[n])
n++;
retval = g_new (wchar_t *, n + 1);
for (i = 0; i < n; i++)
{
retval[i] = g_utf8_to_utf16 (utf8_charv[i], -1, NULL, NULL, error);
if (retval[i] == NULL)
{
if (error_index)
*error_index = i;
while (i)
g_free (retval[--i]);
g_free (retval);
return FALSE;
}
}
retval[n] = NULL;
}
*wcharv = retval;
return TRUE;
}
static gboolean
do_spawn_directly (gint *exit_status,
gboolean do_return_handle,
GSpawnFlags flags,
gchar **argv,
char **envp,
char **protected_argv,
GPid *child_handle,
GError **error)
{
const int mode = (exit_status == NULL) ? P_NOWAIT : P_WAIT;
char **new_argv;
gintptr rc = -1;
int errsv;
GError *conv_error = NULL;
gint conv_error_index;
wchar_t *wargv0, **wargv, **wenvp;
new_argv = (flags & G_SPAWN_FILE_AND_ARGV_ZERO) ? protected_argv + 1 : protected_argv;
wargv0 = g_utf8_to_utf16 (argv[0], -1, NULL, NULL, &conv_error);
if (wargv0 == NULL)
{
g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED,
_("Invalid program name: %s"),
conv_error->message);
g_error_free (conv_error);
return FALSE;
}
if (!utf8_charv_to_wcharv (new_argv, &wargv, &conv_error_index, &conv_error))
{
g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED,
_("Invalid string in argument vector at %d: %s"),
conv_error_index, conv_error->message);
g_error_free (conv_error);
g_free (wargv0);
return FALSE;
}
if (!utf8_charv_to_wcharv (envp, &wenvp, NULL, &conv_error))
{
g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED,
_("Invalid string in environment: %s"),
conv_error->message);
g_error_free (conv_error);
g_free (wargv0);
g_strfreev ((gchar **) wargv);
return FALSE;
}
if (flags & G_SPAWN_SEARCH_PATH)
if (wenvp != NULL)
rc = _wspawnvpe (mode, wargv0, (const wchar_t **) wargv, (const wchar_t **) wenvp);
else
rc = _wspawnvp (mode, wargv0, (const wchar_t **) wargv);
else
if (wenvp != NULL)
rc = _wspawnve (mode, wargv0, (const wchar_t **) wargv, (const wchar_t **) wenvp);
else
rc = _wspawnv (mode, wargv0, (const wchar_t **) wargv);
errsv = errno;
g_free (wargv0);
g_strfreev ((gchar **) wargv);
g_strfreev ((gchar **) wenvp);
if (rc == -1 && errsv != 0)
{
g_set_error (error, G_SPAWN_ERROR, _g_spawn_exec_err_to_g_error (errsv),
_("Failed to execute child process (%s)"),
g_strerror (errsv));
return FALSE;
}
if (exit_status == NULL)
{
if (child_handle && do_return_handle)
*child_handle = (GPid) rc;
else
{
CloseHandle ((HANDLE) rc);
if (child_handle)
*child_handle = 0;
}
}
else
*exit_status = rc;
return TRUE;
}
static gboolean
do_spawn_with_fds (gint *exit_status,
gboolean do_return_handle,
const gchar *working_directory,
gchar **argv,
char **envp,
GSpawnFlags flags,
GSpawnChildSetupFunc child_setup,
GPid *child_handle,
gint stdin_fd,
gint stdout_fd,
gint stderr_fd,
gint *err_report,
GError **error)
{
char **protected_argv;
char args[ARG_COUNT][10];
char **new_argv;
int i;
gintptr rc = -1;
int errsv;
int argc;
int child_err_report_pipe[2] = { -1, -1 };
int helper_sync_pipe[2] = { -1, -1 };
gintptr helper_report[2];
static gboolean warned_about_child_setup = FALSE;
GError *conv_error = NULL;
gint conv_error_index;
gchar *helper_process;
wchar_t *whelper, **wargv, **wenvp;
gchar *glib_dll_directory;
if (child_setup && !warned_about_child_setup)
{
warned_about_child_setup = TRUE;
g_warning ("passing a child setup function to the g_spawn functions is pointless on Windows and it is ignored");
}
argc = protect_argv (argv, &protected_argv);
if (stdin_fd == -1 && stdout_fd == -1 && stderr_fd == -1 &&
(flags & G_SPAWN_CHILD_INHERITS_STDIN) &&
!(flags & G_SPAWN_STDOUT_TO_DEV_NULL) &&
!(flags & G_SPAWN_STDERR_TO_DEV_NULL) &&
(working_directory == NULL || !*working_directory) &&
(flags & G_SPAWN_LEAVE_DESCRIPTORS_OPEN))
{
/* We can do without the helper process */
gboolean retval =
do_spawn_directly (exit_status, do_return_handle, flags,
argv, envp, protected_argv,
child_handle, error);
g_strfreev (protected_argv);
return retval;
}
if (!make_pipe (child_err_report_pipe, error))
goto cleanup_and_fail;
if (!make_pipe (helper_sync_pipe, error))
goto cleanup_and_fail;
new_argv = g_new (char *, argc + 1 + ARG_COUNT);
if (GetConsoleWindow () != NULL)
helper_process = HELPER_PROCESS "-console.exe";
else
helper_process = HELPER_PROCESS ".exe";
glib_dll_directory = _glib_get_dll_directory ();
if (glib_dll_directory != NULL)
{
helper_process = g_build_filename (glib_dll_directory, helper_process, NULL);
g_free (glib_dll_directory);
}
else
helper_process = g_strdup (helper_process);
new_argv[0] = protect_argv_string (helper_process);
_g_sprintf (args[ARG_CHILD_ERR_REPORT], "%d", child_err_report_pipe[1]);
new_argv[ARG_CHILD_ERR_REPORT] = args[ARG_CHILD_ERR_REPORT];
/* Make the read end of the child error report pipe
* noninherited. Otherwise it will needlessly be inherited by the
* helper process, and the started actual user process. As such that
* shouldn't harm, but it is unnecessary.
*/
child_err_report_pipe[0] = dup_noninherited (child_err_report_pipe[0], _O_RDONLY);
if (flags & G_SPAWN_FILE_AND_ARGV_ZERO)
{
/* Overload ARG_CHILD_ERR_REPORT to also encode the
* G_SPAWN_FILE_AND_ARGV_ZERO functionality.
*/
strcat (args[ARG_CHILD_ERR_REPORT], "#");
}
_g_sprintf (args[ARG_HELPER_SYNC], "%d", helper_sync_pipe[0]);
new_argv[ARG_HELPER_SYNC] = args[ARG_HELPER_SYNC];
/* Make the write end of the sync pipe noninherited. Otherwise the
* helper process will inherit it, and thus if this process happens
* to crash before writing the sync byte to the pipe, the helper
* process won't read but won't get any EOF either, as it has the
* write end open itself.
*/
helper_sync_pipe[1] = dup_noninherited (helper_sync_pipe[1], _O_WRONLY);
if (stdin_fd != -1)
{
_g_sprintf (args[ARG_STDIN], "%d", stdin_fd);
new_argv[ARG_STDIN] = args[ARG_STDIN];
}
else if (flags & G_SPAWN_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 != -1)
{
_g_sprintf (args[ARG_STDOUT], "%d", stdout_fd);
new_argv[ARG_STDOUT] = args[ARG_STDOUT];
}
else if (flags & G_SPAWN_STDOUT_TO_DEV_NULL)
{
new_argv[ARG_STDOUT] = "z";
}
else
{
new_argv[ARG_STDOUT] = "-";
}
if (stdout_fd != -1)
{
_g_sprintf (args[ARG_STDERR], "%d", stderr_fd);
new_argv[ARG_STDERR] = args[ARG_STDERR];
}
else if (flags & G_SPAWN_STDERR_TO_DEV_NULL)
{
new_argv[ARG_STDERR] = "z";
}
else
{
new_argv[ARG_STDERR] = "-";
}
if (working_directory && *working_directory)
new_argv[ARG_WORKING_DIRECTORY] = protect_argv_string (working_directory);
else
new_argv[ARG_WORKING_DIRECTORY] = g_strdup ("-");
if (!(flags & G_SPAWN_LEAVE_DESCRIPTORS_OPEN))
new_argv[ARG_CLOSE_DESCRIPTORS] = "y";
else
new_argv[ARG_CLOSE_DESCRIPTORS] = "-";
if (flags & G_SPAWN_SEARCH_PATH)
new_argv[ARG_USE_PATH] = "y";
else
new_argv[ARG_USE_PATH] = "-";
if (exit_status == NULL)
new_argv[ARG_WAIT] = "-";
else
new_argv[ARG_WAIT] = "w";
for (i = 0; i <= argc; i++)
new_argv[ARG_PROGRAM + i] = protected_argv[i];
SETUP_DEBUG();
if (debug)
{
g_print ("calling %s with argv:\n", helper_process);
for (i = 0; i < argc + 1 + ARG_COUNT; i++)
g_print ("argv[%d]: %s\n", i, (new_argv[i] ? new_argv[i] : "NULL"));
}
if (!utf8_charv_to_wcharv (new_argv, &wargv, &conv_error_index, &conv_error))
{
if (conv_error_index == ARG_WORKING_DIRECTORY)
g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_CHDIR,
_("Invalid working directory: %s"),
conv_error->message);
else
g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED,
_("Invalid string in argument vector at %d: %s"),
conv_error_index - ARG_PROGRAM, conv_error->message);
g_error_free (conv_error);
g_strfreev (protected_argv);
g_free (new_argv[0]);
g_free (new_argv[ARG_WORKING_DIRECTORY]);
g_free (new_argv);
g_free (helper_process);
goto cleanup_and_fail;
}
if (!utf8_charv_to_wcharv (envp, &wenvp, NULL, &conv_error))
{
g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED,
_("Invalid string in environment: %s"),
conv_error->message);
g_error_free (conv_error);
g_strfreev (protected_argv);
g_free (new_argv[0]);
g_free (new_argv[ARG_WORKING_DIRECTORY]);
g_free (new_argv);
g_free (helper_process);
g_strfreev ((gchar **) wargv);
goto cleanup_and_fail;
}
whelper = g_utf8_to_utf16 (helper_process, -1, NULL, NULL, NULL);
g_free (helper_process);
if (wenvp != NULL)
rc = _wspawnvpe (P_NOWAIT, whelper, (const wchar_t **) wargv, (const wchar_t **) wenvp);
else
rc = _wspawnvp (P_NOWAIT, whelper, (const wchar_t **) wargv);
errsv = errno;
g_free (whelper);
g_strfreev ((gchar **) wargv);
g_strfreev ((gchar **) wenvp);
/* Close the other process's ends of the pipes in this process,
* otherwise the reader will never get EOF.
*/
close_and_invalidate (&child_err_report_pipe[1]);
close_and_invalidate (&helper_sync_pipe[0]);
g_strfreev (protected_argv);
g_free (new_argv[0]);
g_free (new_argv[ARG_WORKING_DIRECTORY]);
g_free (new_argv);
/* Check if gspawn-win32-helper couldn't be run */
if (rc == -1 && errsv != 0)
{
g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED,
_("Failed to execute helper program (%s)"),
g_strerror (errsv));
goto cleanup_and_fail;
}
if (exit_status != NULL)
{
/* Synchronous case. Pass helper's report pipe back to caller,
* which takes care of reading it after the grandchild has
* finished.
*/
g_assert (err_report != NULL);
*err_report = child_err_report_pipe[0];
write (helper_sync_pipe[1], " ", 1);
close_and_invalidate (&helper_sync_pipe[1]);
}
else
{
/* Asynchronous case. We read the helper's report right away. */
if (!read_helper_report (child_err_report_pipe[0], helper_report, error))
goto cleanup_and_fail;
close_and_invalidate (&child_err_report_pipe[0]);
switch (helper_report[0])
{
case CHILD_NO_ERROR:
if (child_handle && do_return_handle)
{
/* rc 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) rc, (HANDLE) helper_report[1],
GetCurrentProcess (), (LPHANDLE) child_handle,
0, TRUE, DUPLICATE_SAME_ACCESS))
{
char *emsg = g_win32_error_message (GetLastError ());
g_print("%s\n", emsg);
*child_handle = 0;
}
}
else if (child_handle)
*child_handle = 0;
write (helper_sync_pipe[1], " ", 1);
close_and_invalidate (&helper_sync_pipe[1]);
break;
default:
write (helper_sync_pipe[1], " ", 1);
close_and_invalidate (&helper_sync_pipe[1]);
set_child_error (helper_report, working_directory, error);
goto cleanup_and_fail;
}
}
/* Success against all odds! return the information */
if (rc != -1)
CloseHandle ((HANDLE) rc);
return TRUE;
cleanup_and_fail:
if (rc != -1)
CloseHandle ((HANDLE) rc);
if (child_err_report_pipe[0] != -1)
close (child_err_report_pipe[0]);
if (child_err_report_pipe[1] != -1)
close (child_err_report_pipe[1]);
if (helper_sync_pipe[0] != -1)
close (helper_sync_pipe[0]);
if (helper_sync_pipe[1] != -1)
close (helper_sync_pipe[1]);
return FALSE;
}
static gboolean
do_spawn_with_pipes (gint *exit_status,
gboolean do_return_handle,
const gchar *working_directory,
gchar **argv,
char **envp,
GSpawnFlags flags,
GSpawnChildSetupFunc child_setup,
GPid *child_handle,
gint *standard_input,
gint *standard_output,
gint *standard_error,
gint *err_report,
GError **error)
{
int stdin_pipe[2] = { -1, -1 };
int stdout_pipe[2] = { -1, -1 };
int stderr_pipe[2] = { -1, -1 };
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;
if (!do_spawn_with_fds (exit_status,
do_return_handle,
working_directory,
argv,
envp,
flags,
child_setup,
child_handle,
stdin_pipe[0],
stdout_pipe[1],
stderr_pipe[1],
err_report,
error))
goto cleanup_and_fail;
/* Close the other process's ends of the pipes in this process,
* otherwise the reader will never get EOF.
*/
close_and_invalidate (&stdin_pipe[0]);
close_and_invalidate (&stdout_pipe[1]);
close_and_invalidate (&stderr_pipe[1]);
if (standard_input)
*standard_input = stdin_pipe[1];
if (standard_output)
*standard_output = stdout_pipe[0];
if (standard_error)
*standard_error = stderr_pipe[0];
return TRUE;
cleanup_and_fail:
if (stdin_pipe[0] != -1)
close (stdin_pipe[0]);
if (stdin_pipe[1] != -1)
close (stdin_pipe[1]);
if (stdout_pipe[0] != -1)
close (stdout_pipe[0]);
if (stdout_pipe[1] != -1)
close (stdout_pipe[1]);
if (stderr_pipe[0] != -1)
close (stderr_pipe[0]);
if (stderr_pipe[1] != -1)
close (stderr_pipe[1]);
return FALSE;
}
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 reportpipe = -1;
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 (&status,
FALSE,
working_directory,
argv,
envp,
flags,
child_setup,
NULL,
NULL,
standard_output ? &outpipe : NULL,
standard_error ? &errpipe : NULL,
&reportpipe,
error))
return FALSE;
/* Read data from child. */
failed = FALSE;
if (outpipe >= 0)
{
outstr = g_string_new (NULL);
outchannel = g_io_channel_win32_new_fd (outpipe);
g_io_channel_set_encoding (outchannel, NULL, NULL);
g_io_channel_set_buffered (outchannel, FALSE);
g_io_channel_win32_make_pollfd (outchannel,
G_IO_IN | G_IO_ERR | G_IO_HUP,
&outfd);
if (debug)
g_print ("outfd=%p\n", (HANDLE) outfd.fd);
}
if (errpipe >= 0)
{
errstr = g_string_new (NULL);
errchannel = g_io_channel_win32_new_fd (errpipe);
g_io_channel_set_encoding (errchannel, NULL, NULL);
g_io_channel_set_buffered (errchannel, FALSE);
g_io_channel_win32_make_pollfd (errchannel,
G_IO_IN | G_IO_ERR | G_IO_HUP,
&errfd);
if (debug)
g_print ("errfd=%p\n", (HANDLE) errfd.fd);
}
/* Read data until we get EOF on all 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 ("g_spawn_sync: calling g_io_channel_win32_poll, nfds=%d\n",
nfds);
ret = g_io_channel_win32_poll (fds, nfds, -1);
if (ret < 0)
{
failed = TRUE;
g_set_error_literal (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;
}
}
if (reportpipe == -1)
{
/* No helper process, exit status of actual spawned process
* already available.
*/
if (exit_status)
*exit_status = status;
}
else
{
/* Helper process was involved. Read its report now after the
* grandchild has finished.
*/
gintptr helper_report[2];
if (!read_helper_report (reportpipe, helper_report, error))
failed = TRUE;
else
{
switch (helper_report[0])
{
case CHILD_NO_ERROR:
if (exit_status)
*exit_status = helper_report[1];
break;
default:
set_child_error (helper_report, working_directory, error);
failed = TRUE;
break;
}
}
close_and_invalidate (&reportpipe);
}
/* 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 (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,
GPid *child_handle,
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 (NULL,
(flags & G_SPAWN_DO_NOT_REAP_CHILD),
working_directory,
argv,
envp,
flags,
child_setup,
child_handle,
standard_input,
standard_output,
standard_error,
NULL,
error);
}
gboolean
g_spawn_async_with_fds (const gchar *working_directory,
gchar **argv,
gchar **envp,
GSpawnFlags flags,
GSpawnChildSetupFunc child_setup,
gpointer user_data,
GPid *child_handle,
gint stdin_fd,
gint stdout_fd,
gint stderr_fd,
GError **error)
{
g_return_val_if_fail (argv != NULL, FALSE);
g_return_val_if_fail (stdin_fd == -1 ||
!(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE);
g_return_val_if_fail (stderr_fd == -1 ||
!(flags & G_SPAWN_STDERR_TO_DEV_NULL), FALSE);
/* can't inherit stdin if we have an input pipe. */
g_return_val_if_fail (stdin_fd == -1 ||
!(flags & G_SPAWN_CHILD_INHERITS_STDIN), FALSE);
return do_spawn_with_fds (NULL,
(flags & G_SPAWN_DO_NOT_REAP_CHILD),
working_directory,
argv,
envp,
flags,
child_setup,
child_handle,
stdin_fd,
stdout_fd,
stderr_fd,
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;
}
void
g_spawn_close_pid (GPid pid)
{
CloseHandle (pid);
}
gboolean
g_spawn_check_exit_status (gint exit_status,
GError **error)
{
gboolean ret = FALSE;
if (exit_status != 0)
{
g_set_error (error, G_SPAWN_EXIT_ERROR, exit_status,
_("Child process exited with code %ld"),
(long) exit_status);
goto out;
}
ret = TRUE;
out:
return ret;
}
#ifdef G_OS_WIN32
/* Binary compatibility versions. Not for newly compiled code. */
_GLIB_EXTERN gboolean g_spawn_async_utf8 (const gchar *working_directory,
gchar **argv,
gchar **envp,
GSpawnFlags flags,
GSpawnChildSetupFunc child_setup,
gpointer user_data,
GPid *child_pid,
GError **error);
_GLIB_EXTERN gboolean g_spawn_async_with_pipes_utf8 (const gchar *working_directory,
gchar **argv,
gchar **envp,
GSpawnFlags flags,
GSpawnChildSetupFunc child_setup,
gpointer user_data,
GPid *child_pid,
gint *standard_input,
gint *standard_output,
gint *standard_error,
GError **error);
_GLIB_EXTERN gboolean g_spawn_sync_utf8 (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);
_GLIB_EXTERN gboolean g_spawn_command_line_sync_utf8 (const gchar *command_line,
gchar **standard_output,
gchar **standard_error,
gint *exit_status,
GError **error);
_GLIB_EXTERN gboolean g_spawn_command_line_async_utf8 (const gchar *command_line,
GError **error);
gboolean
g_spawn_async_utf8 (const gchar *working_directory,
gchar **argv,
gchar **envp,
GSpawnFlags flags,
GSpawnChildSetupFunc child_setup,
gpointer user_data,
GPid *child_handle,
GError **error)
{
return g_spawn_async (working_directory,
argv,
envp,
flags,
child_setup,
user_data,
child_handle,
error);
}
gboolean
g_spawn_async_with_pipes_utf8 (const gchar *working_directory,
gchar **argv,
gchar **envp,
GSpawnFlags flags,
GSpawnChildSetupFunc child_setup,
gpointer user_data,
GPid *child_handle,
gint *standard_input,
gint *standard_output,
gint *standard_error,
GError **error)
{
return g_spawn_async_with_pipes (working_directory,
argv,
envp,
flags,
child_setup,
user_data,
child_handle,
standard_input,
standard_output,
standard_error,
error);
}
gboolean
g_spawn_sync_utf8 (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)
{
return g_spawn_sync (working_directory,
argv,
envp,
flags,
child_setup,
user_data,
standard_output,
standard_error,
exit_status,
error);
}
gboolean
g_spawn_command_line_sync_utf8 (const gchar *command_line,
gchar **standard_output,
gchar **standard_error,
gint *exit_status,
GError **error)
{
return g_spawn_command_line_sync (command_line,
standard_output,
standard_error,
exit_status,
error);
}
gboolean
g_spawn_command_line_async_utf8 (const gchar *command_line,
GError **error)
{
return g_spawn_command_line_async (command_line, error);
}
#endif /* G_OS_WIN32 */
#endif /* !GSPAWN_HELPER */