X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=glib%2Fgspawn-win32.c;h=d98aba1269cfb779c9b04bf220c58a5b9e490aef;hb=20f6cc2a10ba26860e7a6d27c100deadb5497772;hp=3c2a944c43c32774de469ecae88d6d06670b4559;hpb=f4cb22c78d573d53dba62b7f6743cbe0a1d1e135;p=platform%2Fupstream%2Fglib.git diff --git a/glib/gspawn-win32.c b/glib/gspawn-win32.c index 3c2a944..d98aba1 100644 --- a/glib/gspawn-win32.c +++ b/glib/gspawn-win32.c @@ -30,7 +30,7 @@ * 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 + * 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 @@ -42,10 +42,13 @@ /* Define this to get some logging all the time */ /* #define G_SPAWN_WIN32_DEBUG */ -#include +#include "config.h" #include "glib.h" +#include "glib-private.h" #include "gprintfint.h" +#include "glibintl.h" +#include "gthread.h" #include #include @@ -57,13 +60,12 @@ #include #include #include +#include -#include "glibintl.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() \ @@ -79,6 +81,7 @@ } \ G_STMT_END #endif +#endif enum { @@ -89,6 +92,7 @@ enum enum { ARG_CHILD_ERR_REPORT = 1, + ARG_HELPER_SYNC, ARG_STDIN, ARG_STDOUT, ARG_STDERR, @@ -100,10 +104,80 @@ enum { ARG_COUNT = ARG_PROGRAM }; -/* Return codes from do_spawn() */ -#define DO_SPAWN_ERROR_HELPER -1 -#define DO_SPAWN_OK_NO_HELPER -2 -#define DO_SPAWN_ERROR_NO_HELPER -3 +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, @@ -128,134 +202,50 @@ protect_argv (gchar **argv, * without any quoting. */ for (i = 0; i < argc; i++) - { - gchar *p = argv[i]; - gchar *q; - gint len = 0; - gboolean need_dblquotes = FALSE; - while (*p) - { - if (*p == ' ' || *p == '\t') - need_dblquotes = TRUE; - else if (*p == '"') - len++; - else if (*p == '\\') - { - gchar *pp = p; - while (*pp && *pp == '\\') - pp++; - if (*pp == '"') - len++; - } - len++; - p++; - } - - q = (*new_argv)[i] = g_malloc (len + need_dblquotes*2 + 1); - p = argv[i]; + (*new_argv)[i] = protect_argv_string (argv[i]); - if (need_dblquotes) - *q++ = '"'; - - while (*p) - { - if (*p == '"') - *q++ = '\\'; - else if (*p == '\\') - { - gchar *pp = p; - while (*pp && *pp == '\\') - pp++; - if (*pp == '"') - *q++ = '\\'; - } - *q++ = *p; - p++; - } - - if (need_dblquotes) - *q++ = '"'; - *q++ = '\0'; - /* printf ("argv[%d]:%s, need_dblquotes:%s len:%d => %s\n", i, argv[i], need_dblquotes?"TRUE":"FALSE", len, (*new_argv)[i]); */ - } (*new_argv)[argc] = NULL; return argc; } -#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, - gboolean file_and_argv_zero, - GSpawnChildSetupFunc child_setup, - gpointer user_data, - GPid *child_handle, - 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; -} +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_spawn_async_utf8 (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); + return g_spawn_async_with_pipes_utf8 (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 gint +static void close_and_invalidate (gint *fd) { - gint ret; - if (*fd < 0) - return -1; - else - { - ret = close (*fd); - *fd = -1; - } + return; - return ret; + close (*fd); + *fd = -1; } typedef enum @@ -271,7 +261,7 @@ read_data (GString *str, GError **error) { GIOStatus giostatus; - gssize bytes; + gsize bytes; gchar buf[4096]; again: @@ -289,10 +279,8 @@ read_data (GString *str, 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")); + g_set_error_literal (error, G_SPAWN_ERROR, G_SPAWN_ERROR_READ, + _("Failed to read data from child process")); return READ_FAILED; } @@ -300,413 +288,359 @@ read_data (GString *str, 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) +static gboolean +make_pipe (gint p[2], + GError **error) { - gint outpipe = -1; - gint errpipe = -1; - GPid 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, - (flags & G_SPAWN_FILE_AND_ARGV_ZERO) != 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) + if (_pipe (p, 4096, _O_BINARY) < 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_win32_make_pollfd (outchannel, - G_IO_IN | G_IO_ERR | G_IO_HUP, - &outfd); - } - - 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_win32_make_pollfd (errchannel, - G_IO_IN | G_IO_ERR | G_IO_HUP, - &errfd); + 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; +} - /* Read data until we get EOF on both pipes. */ - while (!failed && - (outpipe >= 0 || - errpipe >= 0)) +/* 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) { - nfds = 0; - if (outpipe >= 0) - { - fds[nfds] = outfd; - outindex = nfds; - nfds++; - } - if (errpipe >= 0) - { - fds[nfds] = errfd; - errindex = nfds; - nfds++; - } + gint chunk; if (debug) - g_print ("%s:g_spawn_sync: calling g_io_channel_win32_poll, nfds=%d\n", - __FILE__, nfds); + g_print ("%s:read_helper_report: read %" G_GSIZE_FORMAT "...\n", + __FILE__, + sizeof(gintptr)*2 - bytes); - ret = g_io_channel_win32_poll (fds, nfds, -1); + chunk = read (fd, ((gchar*)report) + bytes, + sizeof(gintptr)*2 - bytes); - if (ret < 0) + if (debug) + g_print ("...got %d bytes\n", chunk); + + if (chunk < 0) { - failed = TRUE; + int errsv = errno; - 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; - } + /* 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)); - if (failed) - break; + 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 (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 (bytes < sizeof(gintptr)*2) + return FALSE; - if (failed) - break; - } + 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; + default: + g_assert_not_reached (); } +} - /* 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); - - g_spawn_close_pid(pid); +static gboolean +utf8_charv_to_wcharv (char **utf8_charv, + wchar_t ***wcharv, + int *error_index, + GError **error) +{ + wchar_t **retval = NULL; - if (failed) + *wcharv = NULL; + if (utf8_charv != NULL) { - if (outstr) - g_string_free (outstr, TRUE); - if (errstr) - g_string_free (errstr, TRUE); + int n = 0, i; - return FALSE; - } - else - { - if (exit_status) - *exit_status = status; - - if (standard_output) - *standard_output = g_string_free (outstr, FALSE); + while (utf8_charv[n]) + n++; + retval = g_new (wchar_t *, n + 1); - if (standard_error) - *standard_error = g_string_free (errstr, FALSE); - - return TRUE; + 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; } -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) +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) { - 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); + const int mode = (exit_status == NULL) ? P_NOWAIT : P_WAIT; + char **new_argv; + gintptr rc = -1; + int saved_errno; + 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; + } - 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, - (flags & G_SPAWN_FILE_AND_ARGV_ZERO) != 0, - child_setup, - user_data, - child_handle, - standard_input, - standard_output, - standard_error, - NULL, - error); -} + 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); -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; + return FALSE; + } - 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); + 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 retval; -} + return FALSE; + } -gboolean -g_spawn_command_line_async (const gchar *command_line, - GError **error) -{ - gboolean retval; - gchar **argv = 0; + 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); - g_return_val_if_fail (command_line != NULL, FALSE); + g_free (wargv0); + g_strfreev ((gchar **) wargv); + g_strfreev ((gchar **) wenvp); - 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); + saved_errno = errno; - return retval; -} + if (rc == -1 && saved_errno != 0) + { + g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, + _("Failed to execute child process (%s)"), + g_strerror (saved_errno)); + return FALSE; + } -/* This stinks, code reorg needed. Presumably do_spawn() should be - * inserted into its only caller, do_spawn_with_pipes(), and then code - * snippets from that function should be split out to separate - * functions if necessary. - */ + 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; -static gint shortcut_spawn_retval; + return TRUE; +} -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, - gboolean file_and_argv_zero, - GSpawnChildSetupFunc child_setup, - gpointer user_data) +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) { - gchar **protected_argv; - gchar **new_argv; - gchar args[ARG_COUNT][10]; - gint i; - int rc; + char **protected_argv; + char args[ARG_COUNT][10]; + char **new_argv; + int i; + gintptr rc = -1; + int saved_errno; int argc; - - SETUP_DEBUG(); + int stdin_pipe[2] = { -1, -1 }; + int stdout_pipe[2] = { -1, -1 }; + int stderr_pipe[2] = { -1, -1 }; + 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; + CONSOLE_CURSOR_INFO cursor_info; + 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 && + if (!standard_input && !standard_output && !standard_error && + (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) && - !close_descriptors && - !stdout_to_null && !stderr_to_null && - child_inherits_stdin) + (flags & G_SPAWN_LEAVE_DESCRIPTORS_OPEN)) { /* We can do without the helper process */ - int mode = dont_wait ? P_NOWAIT : P_WAIT; - - if (debug) - g_print ("doing without gspawn-win32-helper\n"); - - if (search_path) - rc = spawnvp (mode, argv[0], file_and_argv_zero ? protected_argv + 1 : protected_argv); - else - rc = spawnv (mode, argv[0], file_and_argv_zero ? protected_argv + 1 : protected_argv); + gboolean retval = + do_spawn_directly (exit_status, do_return_handle, flags, + argv, envp, protected_argv, + child_handle, error); + g_strfreev (protected_argv); + return retval; + } - for (i = 0; i < argc; i++) - g_free (protected_argv[i]); - g_free (protected_argv); - - if (rc == -1) - { - return DO_SPAWN_ERROR_NO_HELPER; - } - else - { - shortcut_spawn_retval = rc; - return DO_SPAWN_OK_NO_HELPER; - } + 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 (!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 = g_new (gchar *, argc + 1 + ARG_COUNT); + new_argv[0] = protect_argv_string (helper_process); - new_argv[0] = "gspawn-win32-helper"; - _g_sprintf (args[ARG_CHILD_ERR_REPORT], "%d", child_err_report_fd); + _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 (file_and_argv_zero) + 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 >= 0) + if (standard_input) { - _g_sprintf (args[ARG_STDIN], "%d", stdin_fd); + _g_sprintf (args[ARG_STDIN], "%d", stdin_pipe[0]); new_argv[ARG_STDIN] = args[ARG_STDIN]; } - else if (child_inherits_stdin) + else if (flags & G_SPAWN_CHILD_INHERITS_STDIN) { /* Let stdin be alone */ new_argv[ARG_STDIN] = "-"; @@ -716,13 +650,13 @@ do_spawn (gboolean dont_wait, /* Keep process from blocking on a read of stdin */ new_argv[ARG_STDIN] = "z"; } - - if (stdout_fd >= 0) + + if (standard_output) { - _g_sprintf (args[ARG_STDOUT], "%d", stdout_fd); + _g_sprintf (args[ARG_STDOUT], "%d", stdout_pipe[1]); new_argv[ARG_STDOUT] = args[ARG_STDOUT]; } - else if (stdout_to_null) + else if (flags & G_SPAWN_STDOUT_TO_DEV_NULL) { new_argv[ARG_STDOUT] = "z"; } @@ -730,13 +664,13 @@ do_spawn (gboolean dont_wait, { new_argv[ARG_STDOUT] = "-"; } - - if (stderr_fd >= 0) + + if (standard_error) { - _g_sprintf (args[ARG_STDERR], "%d", stderr_fd); + _g_sprintf (args[ARG_STDERR], "%d", stderr_pipe[1]); new_argv[ARG_STDERR] = args[ARG_STDERR]; } - else if (stderr_to_null) + else if (flags & G_SPAWN_STDERR_TO_DEV_NULL) { new_argv[ARG_STDERR] = "z"; } @@ -744,24 +678,23 @@ do_spawn (gboolean dont_wait, { new_argv[ARG_STDERR] = "-"; } - + if (working_directory && *working_directory) - /* The g_strdup() to lose the constness */ - new_argv[ARG_WORKING_DIRECTORY] = g_strdup (working_directory); + new_argv[ARG_WORKING_DIRECTORY] = protect_argv_string (working_directory); else new_argv[ARG_WORKING_DIRECTORY] = g_strdup ("-"); - - if (close_descriptors) + + if (!(flags & G_SPAWN_LEAVE_DESCRIPTORS_OPEN)) new_argv[ARG_CLOSE_DESCRIPTORS] = "y"; else new_argv[ARG_CLOSE_DESCRIPTORS] = "-"; - if (search_path) + if (flags & G_SPAWN_SEARCH_PATH) new_argv[ARG_USE_PATH] = "y"; else new_argv[ARG_USE_PATH] = "-"; - if (dont_wait) + if (exit_status == NULL) new_argv[ARG_WAIT] = "-"; else new_argv[ARG_WAIT] = "w"; @@ -769,232 +702,138 @@ do_spawn (gboolean dont_wait, for (i = 0; i <= argc; i++) new_argv[ARG_PROGRAM + i] = protected_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); - } + SETUP_DEBUG(); if (debug) { - g_print ("calling gspawn-win32-helper with argv:\n"); + 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 (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); - - for (i = 0; i < argc; i++) - g_free (protected_argv[i]); - g_free (protected_argv); - - 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) + if (!utf8_charv_to_wcharv (new_argv, &wargv, &conv_error_index, &conv_error)) { - gint chunk; + 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); - if (debug) - g_print ("%s:read_ints: trying to read %d bytes from pipe...\n", - __FILE__, - sizeof(gint)*n_ints_in_buf - bytes); + goto cleanup_and_fail; + } - chunk = read (fd, ((gchar*)buf) + bytes, - sizeof(gint)*n_ints_in_buf - bytes); + 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; + } - 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)); + whelper = g_utf8_to_utf16 (helper_process, -1, NULL, NULL, NULL); + g_free (helper_process); - return FALSE; - } - else if (chunk == 0) - break; /* EOF */ - else - bytes += chunk; - } + 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); - *n_ints_read = bytes/sizeof(gint); + saved_errno = errno; - return TRUE; -} + g_free (whelper); + g_strfreev ((gchar **) wargv); + g_strfreev ((gchar **) wenvp); -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, - gboolean file_and_argv_zero, - GSpawnChildSetupFunc child_setup, - gpointer user_data, - GPid *child_handle, - 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; + /* 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]); + close_and_invalidate (&stdin_pipe[0]); + close_and_invalidate (&stdout_pipe[1]); + close_and_invalidate (&stderr_pipe[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; + g_strfreev (protected_argv); - if (standard_error && !make_pipe (stderr_pipe, error)) - goto cleanup_and_fail; + g_free (new_argv[0]); + g_free (new_argv[ARG_WORKING_DIRECTORY]); + g_free (new_argv); - 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, - file_and_argv_zero, - child_setup, - user_data); - /* Check if gspawn-win32-helper couldn't be run */ - if (helper == DO_SPAWN_ERROR_HELPER) + if (rc == -1 && saved_errno != 0) { - g_set_error (error, - G_SPAWN_ERROR, - G_SPAWN_ERROR_FAILED, - _("Failed to execute helper program")); + g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, + _("Failed to execute helper program (%s)"), + g_strerror (saved_errno)); goto cleanup_and_fail; } - else if (helper == DO_SPAWN_OK_NO_HELPER) + if (exit_status != NULL) { - if (child_handle && dont_wait && !dont_return_handle) - *child_handle = (GPid) shortcut_spawn_retval; - else if (!dont_wait && exit_status) - *exit_status = shortcut_spawn_retval; - - close_and_invalidate (&child_err_report_pipe[0]); - close_and_invalidate (&child_err_report_pipe[1]); - - return TRUE; + /* 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 if (helper == DO_SPAWN_ERROR_NO_HELPER) + else { - g_set_error (error, - G_SPAWN_ERROR, - G_SPAWN_ERROR_FAILED, - _("Failed to execute child process (%s)"), - g_strerror (errno)); - helper = -1; - goto cleanup_and_fail; - } - - if (!read_ints (child_err_report_pipe[0], - buf, 2, &n_ints, - error) || - n_ints != 2) - goto cleanup_and_fail; + /* 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; - /* Error code from gspawn-win32-helper. */ - switch (buf[0]) - { - case CHILD_NO_ERROR: - if (child_handle && dont_wait && !dont_return_handle) + close_and_invalidate (&child_err_report_pipe[0]); + + switch (helper_report[0]) { - /* 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_handle, - 0, TRUE, DUPLICATE_SAME_ACCESS)) + 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; } - else if (child_handle) - *child_handle = 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 */ @@ -1005,49 +844,669 @@ do_spawn_with_pipes (gboolean dont_wait, *standard_output = stdout_pipe[0]; if (standard_error) *standard_error = stderr_pipe[0]; - if (exit_status) - *exit_status = buf[1]; - CloseHandle ((HANDLE) helper); + if (rc != -1) + CloseHandle ((HANDLE) rc); - close_and_invalidate (&child_err_report_pipe[0]); - 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]); + + 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]); + 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; } -static gboolean -make_pipe (gint p[2], - GError **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) { - if (pipe (p) < 0) + 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) { - g_set_error (error, - G_SPAWN_ERROR, - G_SPAWN_ERROR_FAILED, - _("Failed to create pipe for communicating with child process (%s)"), - g_strerror (errno)); + 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 - return TRUE; + { + 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_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) +{ + 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_command_line_sync_utf8 (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_utf8 (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_utf8 (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_utf8 (NULL, + argv, + NULL, + G_SPAWN_SEARCH_PATH, + NULL, + NULL, + NULL, + error); + g_strfreev (argv); + + return retval; } -#endif /* !GSPAWN_HELPER */ 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; +} + +#if !defined (_WIN64) + +/* Binary compatibility versions that take system codepage pathnames, + * argument vectors and environments. These get used only by code + * built against 2.8.1 or earlier. Code built against 2.8.2 or later + * will use the _utf8 versions above (see the #defines in gspawn.h). + */ + +#undef g_spawn_async +#undef g_spawn_async_with_pipes +#undef g_spawn_sync +#undef g_spawn_command_line_sync +#undef g_spawn_command_line_async + +static gboolean +setup_utf8_copies (const gchar *working_directory, + gchar **utf8_working_directory, + gchar **argv, + gchar ***utf8_argv, + gchar **envp, + gchar ***utf8_envp, + GError **error) +{ + gint i, argc, envc; + + if (working_directory == NULL) + *utf8_working_directory = NULL; + else + { + GError *conv_error = NULL; + + *utf8_working_directory = g_locale_to_utf8 (working_directory, -1, NULL, NULL, &conv_error); + if (*utf8_working_directory == NULL) + { + g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_CHDIR, + _("Invalid working directory: %s"), + conv_error->message); + g_error_free (conv_error); + return FALSE; + } + } + + argc = 0; + while (argv[argc]) + ++argc; + *utf8_argv = g_new (gchar *, argc + 1); + for (i = 0; i < argc; i++) + { + GError *conv_error = NULL; + + (*utf8_argv)[i] = g_locale_to_utf8 (argv[i], -1, NULL, NULL, &conv_error); + if ((*utf8_argv)[i] == NULL) + { + g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, + _("Invalid string in argument vector at %d: %s"), + i, conv_error->message); + g_error_free (conv_error); + + g_strfreev (*utf8_argv); + *utf8_argv = NULL; + + g_free (*utf8_working_directory); + *utf8_working_directory = NULL; + + return FALSE; + } + } + (*utf8_argv)[argc] = NULL; + + if (envp == NULL) + { + *utf8_envp = NULL; + } + else + { + envc = 0; + while (envp[envc]) + ++envc; + *utf8_envp = g_new (gchar *, envc + 1); + for (i = 0; i < envc; i++) + { + GError *conv_error = NULL; + + (*utf8_envp)[i] = g_locale_to_utf8 (envp[i], -1, NULL, NULL, &conv_error); + if ((*utf8_envp)[i] == NULL) + { + 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 (*utf8_envp); + *utf8_envp = NULL; + + g_strfreev (*utf8_argv); + *utf8_argv = NULL; + + g_free (*utf8_working_directory); + *utf8_working_directory = NULL; + + return FALSE; + } + } + (*utf8_envp)[envc] = NULL; + } + return TRUE; +} + +static void +free_utf8_copies (gchar *utf8_working_directory, + gchar **utf8_argv, + gchar **utf8_envp) +{ + g_free (utf8_working_directory); + g_strfreev (utf8_argv); + g_strfreev (utf8_envp); +} + +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) +{ + gchar *utf8_working_directory; + gchar **utf8_argv; + gchar **utf8_envp; + gboolean retval; + + if (!setup_utf8_copies (working_directory, &utf8_working_directory, + argv, &utf8_argv, + envp, &utf8_envp, + error)) + return FALSE; + + retval = g_spawn_async_with_pipes_utf8 (utf8_working_directory, + utf8_argv, utf8_envp, + flags, child_setup, user_data, + child_handle, + standard_input, standard_output, standard_error, + error); + + free_utf8_copies (utf8_working_directory, utf8_argv, utf8_envp); + + return retval; +} + +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) +{ + return g_spawn_async_with_pipes (working_directory, + argv, envp, + flags, + child_setup, + user_data, + child_handle, + NULL, NULL, NULL, + error); +} + +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) +{ + gchar *utf8_working_directory; + gchar **utf8_argv; + gchar **utf8_envp; + gboolean retval; + + if (!setup_utf8_copies (working_directory, &utf8_working_directory, + argv, &utf8_argv, + envp, &utf8_envp, + error)) + return FALSE; + + retval = g_spawn_sync_utf8 (utf8_working_directory, + utf8_argv, utf8_envp, + flags, child_setup, user_data, + standard_output, standard_error, exit_status, + error); + + free_utf8_copies (utf8_working_directory, utf8_argv, utf8_envp); + + return retval; +} + +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; +} + +#endif /* !_WIN64 */ + +#endif /* !GSPAWN_HELPER */