diff --git a/ChangeLog b/ChangeLog index 3836d44e1..b0d9efd80 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,35 @@ +2005-08-25 Tor Lillqvist + + Make also the g_spawn*() functions take parameters in the GLib + file name encoding, i.e. UTF-8, on Windows. Has no impact on Unix + API or ABI. Like the other GLib API that was earlier changed to + use UTF-8 on Windows, the names of the functions that take UTF-8 + have _utf8 suffixes added by using preprocessor macros in the + header file. The old names are kept for functions with the old + behaviour, taking parameters in the system codepage, for DLL ABI + stability. + + * glib/gspawn.h: On Win32 add the suffix _utf8 to the names of the + g_spawn*() functions. + + * glib/gspawn-win32.c: Use wide-char API on NT-based + Windows. Convert parameters from UTF-8 to wide chars (NT) or + system codepage (Win9x) and call the C library _wspawn*() or + spawn*() functions respectvely. Add DLL ABI stability versions + that take parameters in the system codepage. + + * glib/gspawn-win32-helper.c: On NT-based Windows use the + wide-char versions of argv and envp, and use wide-char API to + change directory and spawn the program to run. Remove the verbose + debugging output, it was too complex to modify for the wide-char + features. (Just add temporary debugging printouts if needed, no + need to have them permanently in the source.) + + * glib/gspawn.c: Corresponding documentation updates. + + * glib/glib.symbols: Corresponding changes: Mark the ABI stability + symbols as PRIVATE, add the new _utf8-suffixed ones. + 2005-08-24 Stepan Kasal * glib/gtypes.h (G_MININT64): Cast the constant to gint64; it is diff --git a/ChangeLog.pre-2-10 b/ChangeLog.pre-2-10 index 3836d44e1..b0d9efd80 100644 --- a/ChangeLog.pre-2-10 +++ b/ChangeLog.pre-2-10 @@ -1,3 +1,35 @@ +2005-08-25 Tor Lillqvist + + Make also the g_spawn*() functions take parameters in the GLib + file name encoding, i.e. UTF-8, on Windows. Has no impact on Unix + API or ABI. Like the other GLib API that was earlier changed to + use UTF-8 on Windows, the names of the functions that take UTF-8 + have _utf8 suffixes added by using preprocessor macros in the + header file. The old names are kept for functions with the old + behaviour, taking parameters in the system codepage, for DLL ABI + stability. + + * glib/gspawn.h: On Win32 add the suffix _utf8 to the names of the + g_spawn*() functions. + + * glib/gspawn-win32.c: Use wide-char API on NT-based + Windows. Convert parameters from UTF-8 to wide chars (NT) or + system codepage (Win9x) and call the C library _wspawn*() or + spawn*() functions respectvely. Add DLL ABI stability versions + that take parameters in the system codepage. + + * glib/gspawn-win32-helper.c: On NT-based Windows use the + wide-char versions of argv and envp, and use wide-char API to + change directory and spawn the program to run. Remove the verbose + debugging output, it was too complex to modify for the wide-char + features. (Just add temporary debugging printouts if needed, no + need to have them permanently in the source.) + + * glib/gspawn.c: Corresponding documentation updates. + + * glib/glib.symbols: Corresponding changes: Mark the ABI stability + symbols as PRIVATE, add the new _utf8-suffixed ones. + 2005-08-24 Stepan Kasal * glib/gtypes.h (G_MININT64): Cast the constant to gint64; it is diff --git a/ChangeLog.pre-2-12 b/ChangeLog.pre-2-12 index 3836d44e1..b0d9efd80 100644 --- a/ChangeLog.pre-2-12 +++ b/ChangeLog.pre-2-12 @@ -1,3 +1,35 @@ +2005-08-25 Tor Lillqvist + + Make also the g_spawn*() functions take parameters in the GLib + file name encoding, i.e. UTF-8, on Windows. Has no impact on Unix + API or ABI. Like the other GLib API that was earlier changed to + use UTF-8 on Windows, the names of the functions that take UTF-8 + have _utf8 suffixes added by using preprocessor macros in the + header file. The old names are kept for functions with the old + behaviour, taking parameters in the system codepage, for DLL ABI + stability. + + * glib/gspawn.h: On Win32 add the suffix _utf8 to the names of the + g_spawn*() functions. + + * glib/gspawn-win32.c: Use wide-char API on NT-based + Windows. Convert parameters from UTF-8 to wide chars (NT) or + system codepage (Win9x) and call the C library _wspawn*() or + spawn*() functions respectvely. Add DLL ABI stability versions + that take parameters in the system codepage. + + * glib/gspawn-win32-helper.c: On NT-based Windows use the + wide-char versions of argv and envp, and use wide-char API to + change directory and spawn the program to run. Remove the verbose + debugging output, it was too complex to modify for the wide-char + features. (Just add temporary debugging printouts if needed, no + need to have them permanently in the source.) + + * glib/gspawn.c: Corresponding documentation updates. + + * glib/glib.symbols: Corresponding changes: Mark the ABI stability + symbols as PRIVATE, add the new _utf8-suffixed ones. + 2005-08-24 Stepan Kasal * glib/gtypes.h (G_MININT64): Cast the constant to gint64; it is diff --git a/glib/glib.symbols b/glib/glib.symbols index 685357e22..bb205c6ec 100644 --- a/glib/glib.symbols +++ b/glib/glib.symbols @@ -851,13 +851,20 @@ g_slist_sort_with_data #if IN_HEADER(__G_SPAWN_H__) #if IN_FILE(__G_SPAWN_C__) -g_spawn_async -g_spawn_async_with_pipes +g_spawn_async PRIVATE +g_spawn_async_with_pipes PRIVATE g_spawn_close_pid -g_spawn_command_line_async -g_spawn_command_line_sync +g_spawn_command_line_async PRIVATE +g_spawn_command_line_sync PRIVATE g_spawn_error_quark -g_spawn_sync +g_spawn_sync PRIVATE +#ifdef G_OS_WIN32 +g_spawn_async_utf8 +g_spawn_async_with_pipes_utf8 +g_spawn_command_line_async_utf8 +g_spawn_command_line_sync_utf8 +g_spawn_sync_utf8 +#endif #endif #endif diff --git a/glib/gspawn-win32-helper.c b/glib/gspawn-win32-helper.c index 1e34aab71..1f79fa4c9 100644 --- a/glib/gspawn-win32-helper.c +++ b/glib/gspawn-win32-helper.c @@ -27,23 +27,12 @@ #include "gspawn-win32.c" /* For shared definitions */ -static GString *debugstring; - static void write_err_and_exit (gint fd, gint msg) { gint en = errno; - if (debug) - { - debugstring = g_string_new (NULL); - g_string_append (debugstring, - g_strdup_printf ("writing error code %d and errno %d", - msg, en)); - MessageBox (NULL, debugstring->str, "gspawn-win32-helper", 0); - } - write (fd, &msg, sizeof(msg)); write (fd, &en, sizeof(en)); @@ -63,6 +52,99 @@ write_err_and_exit (gint fd, * away in the global __argc and __argv by the C runtime startup code. */ +/* Info peeked from mingw runtime's source code. __wgetmainargs() is a + * function to get the program's argv in wide char format. + */ + +typedef struct { + int newmode; +} _startupinfo; + +extern void __wgetmainargs(int *argc, + wchar_t ***wargv, + wchar_t ***wenviron, + int expand_wildcards, + _startupinfo *startupinfo); + +/* Copy of protect_argv that handles wchar_t strings */ + +static gint +protect_wargv (wchar_t **wargv, + wchar_t ***new_wargv) +{ + gint i; + gint argc = 0; + + while (wargv[argc]) + ++argc; + *new_wargv = g_new (wchar_t *, 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_wargv[0] constructed by this function should + * *not* be passed as the filename argument to a _wspawn* or _wexec* + * family function. That argument should be the real file name + * without any quoting. + */ + for (i = 0; i < argc; i++) + { + wchar_t *p = wargv[i]; + wchar_t *q; + gint len = 0; + gboolean need_dblquotes = FALSE; + while (*p) + { + if (*p == ' ' || *p == '\t') + need_dblquotes = TRUE; + else if (*p == '"') + len++; + else if (*p == '\\') + { + wchar_t *pp = p; + while (*pp && *pp == '\\') + pp++; + if (*pp == '"') + len++; + } + len++; + p++; + } + + q = (*new_wargv)[i] = g_new (wchar_t, len + need_dblquotes*2 + 1); + p = wargv[i]; + + if (need_dblquotes) + *q++ = '"'; + + while (*p) + { + if (*p == '"') + *q++ = '\\'; + else if (*p == '\\') + { + wchar_t *pp = p; + while (*pp && *pp == '\\') + pp++; + if (*pp == '"') + *q++ = '\\'; + } + *q++ = *p; + p++; + } + + if (need_dblquotes) + *q++ = '"'; + *q++ = '\0'; + } + (*new_wargv)[argc] = NULL; + + return argc; +} + int _stdcall WinMain (struct HINSTANCE__ *hInstance, struct HINSTANCE__ *hPrevInstance, @@ -77,44 +159,43 @@ WinMain (struct HINSTANCE__ *hInstance, int saved_errno; int no_error = CHILD_NO_ERROR; int zero = 0; - gint file_and_argv_zero = 0; + gint argv_zero_offset = ARG_PROGRAM; gchar **new_argv; - - SETUP_DEBUG(); - - if (debug) - { - debugstring = g_string_new (NULL); - - g_string_append (debugstring, - g_strdup_printf ("g-spawn-win32-helper: " - "argc = %d, argv: ", - __argc)); - for (i = 0; i < __argc; i++) - { - if (i > 0) - g_string_append (debugstring, " "); - g_string_append (debugstring, __argv[i]); - } - - MessageBox (NULL, debugstring->str, "gspawn-win32-helper", 0); - } + wchar_t **new_wargv; + int argc; + wchar_t **wargv, **wenvp; + _startupinfo si = { 0 }; g_assert (__argc >= ARG_COUNT); - /* argv[ARG_CHILD_ERR_REPORT] is the file descriptor onto which - * write error messages. + if (G_WIN32_HAVE_WIDECHAR_API ()) + { + /* Fetch the wide-char argument vector */ + __wgetmainargs (&argc, &wargv, &wenvp, 0, &si); + + /* We still have the system codepage args in __argv. We can look + * at the first args in which gspawn-win32.c passes us flags and + * fd numbers in __argv, as we know those are just ASCII anyway. + */ + g_assert (argc == __argc); + } + + /* argv[ARG_CHILD_ERR_REPORT] is the file descriptor number onto + * which write error messages. */ child_err_report_fd = atoi (__argv[ARG_CHILD_ERR_REPORT]); - /* Hack to implement G_SPAWN_FILE_AND_ARGV_ZERO */ + /* Hack to implement G_SPAWN_FILE_AND_ARGV_ZERO. If + * argv[ARG_CHILD_ERR_REPORT] is suffixed with a '#' it means we get + * the program to run and its argv[0] separately. + */ if (__argv[ARG_CHILD_ERR_REPORT][strlen (__argv[ARG_CHILD_ERR_REPORT]) - 1] == '#') - file_and_argv_zero = 1; + argv_zero_offset++; - /* argv[ARG_STDIN..ARG_STDERR] are the file descriptors that should - * be dup2'd to stdin, stdout and stderr, '-' if the corresponding - * std* should be let alone, and 'z' if it should be connected to - * the bit bucket NUL:. + /* argv[ARG_STDIN..ARG_STDERR] are the file descriptor numbers that + * should be dup2'd to 0, 1 and 2. '-' if the corresponding fd + * should be left alone, and 'z' if it should be connected to the + * bit bucket NUL:. */ if (__argv[ARG_STDIN][0] == '-') ; /* Nothing */ @@ -185,21 +266,21 @@ WinMain (struct HINSTANCE__ *hInstance, if (__argv[ARG_WORKING_DIRECTORY][0] == '-' && __argv[ARG_WORKING_DIRECTORY][1] == 0) ; /* Nothing */ - else if (chdir (__argv[ARG_WORKING_DIRECTORY]) < 0) - write_err_and_exit (child_err_report_fd, - CHILD_CHDIR_FAILED); + else if ((G_WIN32_HAVE_WIDECHAR_API () && + _wchdir (wargv[ARG_WORKING_DIRECTORY]) < 0) || + (!G_WIN32_HAVE_WIDECHAR_API () && + chdir (__argv[ARG_WORKING_DIRECTORY]) < 0)) + write_err_and_exit (child_err_report_fd, CHILD_CHDIR_FAILED); /* __argv[ARG_CLOSE_DESCRIPTORS] is "y" if file descriptors from 3 * upwards should be closed */ - if (__argv[ARG_CLOSE_DESCRIPTORS][0] == 'y') for (i = 3; i < 1000; i++) /* FIXME real limit? */ if (i != child_err_report_fd) close (i); /* __argv[ARG_WAIT] is "w" to wait for the program to exit */ - if (__argv[ARG_WAIT][0] == 'w') mode = P_WAIT; else @@ -207,53 +288,36 @@ WinMain (struct HINSTANCE__ *hInstance, /* __argv[ARG_USE_PATH] is "y" to use PATH, otherwise not */ - /* __argv[ARG_PROGRAM] is program file to run, - * __argv[ARG_PROGRAM+1]... is its __argv. + /* __argv[ARG_PROGRAM] is executable file to run, + * __argv[argv_zero_offset]... is its argv. argv_zero_offset equals + * ARG_PROGRAM unless G_SPAWN_FILE_AND_ARGV_ZERO was used, in which + * case we have a separate executable name and argv[0]. */ - protect_argv (__argv, &new_argv); - /* For the program name passed to spawnv(), don't use the quoted - * version. */ - - if (debug) + * version. + */ + if (G_WIN32_HAVE_WIDECHAR_API ()) { - debugstring = g_string_new (NULL); - g_string_append (debugstring, - g_strdup_printf ("calling %s %s mode=%s argv: ", - (__argv[ARG_USE_PATH][0] == 'y' ? - "spawnvp" : "spawnv"), - __argv[ARG_PROGRAM], - (mode == P_WAIT ? - "P_WAIT" : "P_NOWAIT"))); - i = ARG_PROGRAM + 1 + file_and_argv_zero; - while (new_argv[i]) - { - g_string_append (debugstring, new_argv[i++]); - if (new_argv[i]) - g_string_append (debugstring, " "); - } - MessageBox (NULL, debugstring->str, "gspawn-win32-helper", 0); - } + protect_wargv (wargv + argv_zero_offset, &new_wargv); - if (new_argv[ARG_USE_PATH][0] == 'y') - handle = spawnvp (mode, __argv[ARG_PROGRAM], new_argv + ARG_PROGRAM + file_and_argv_zero); + if (__argv[ARG_USE_PATH][0] == 'y') + handle = _wspawnvp (mode, wargv[ARG_PROGRAM], (const wchar_t **) new_wargv); + else + handle = _wspawnv (mode, wargv[ARG_PROGRAM], (const wchar_t **) new_wargv); + } else - handle = spawnv (mode, __argv[ARG_PROGRAM], new_argv + ARG_PROGRAM + file_and_argv_zero); + { + protect_argv (__argv + argv_zero_offset, &new_argv); + + if (__argv[ARG_USE_PATH][0] == 'y') + handle = spawnvp (mode, __argv[ARG_PROGRAM], (const char **) new_argv); + else + handle = spawnv (mode, __argv[ARG_PROGRAM], (const char **) new_argv); + } saved_errno = errno; - if (debug) - { - debugstring = g_string_new (NULL); - g_string_append (debugstring, - g_strdup_printf ("%s returned %#x", - (__argv[ARG_USE_PATH][0] == 'y' ? - "spawnvp" : "spawnv"), - handle)); - MessageBox (NULL, debugstring->str, "gspawn-win32-helper", 0); - } - if (handle == -1 && saved_errno != 0) write_err_and_exit (child_err_report_fd, CHILD_SPAWN_FAILED); @@ -264,4 +328,3 @@ WinMain (struct HINSTANCE__ *hInstance, write (child_err_report_fd, &zero, sizeof (zero)); return 0; } - diff --git a/glib/gspawn-win32.c b/glib/gspawn-win32.c index 403a49d70..9e26df2f4 100644 --- a/glib/gspawn-win32.c +++ b/glib/gspawn-win32.c @@ -59,6 +59,14 @@ #include #include +#ifdef __MINGW32__ +/* Mingw doesn't have prototypes for these */ +int _wspawnvpe (int, const wchar_t *, const wchar_t **, const wchar_t **); +int _wspawnvp (int, const wchar_t *, const wchar_t **); +int _wspawnve (int, const wchar_t *, const wchar_t **, const wchar_t **); +int _wspawnv (int, const wchar_t *, const wchar_t **); +#endif + #include "glibintl.h" #ifdef G_SPAWN_WIN32_DEBUG @@ -100,6 +108,60 @@ enum { ARG_COUNT = ARG_PROGRAM }; +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) @@ -123,56 +185,8 @@ 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++; - } + (*new_argv)[i] = protect_argv_string (argv[i]); - q = (*new_argv)[i] = g_malloc (len + need_dblquotes*2 + 1); - p = 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; @@ -192,25 +206,25 @@ g_spawn_error_quark (void) } 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() @@ -258,9 +272,7 @@ read_data (GString *str, goto again; else if (giostatus == G_IO_STATUS_ERROR) { - g_set_error (error, - G_SPAWN_ERROR, - G_SPAWN_ERROR_READ, + g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_READ, _("Failed to read data from child process")); return READ_FAILED; @@ -275,9 +287,7 @@ make_pipe (gint p[2], { if (pipe (p) < 0) { - g_set_error (error, - G_SPAWN_ERROR, - G_SPAWN_ERROR_FAILED, + 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; @@ -315,9 +325,7 @@ read_helper_report (int fd, { /* Some weird shit happened, bail out */ - g_set_error (error, - G_SPAWN_ERROR, - G_SPAWN_ERROR_FAILED, + g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, _("Failed to read from child pipe (%s)"), g_strerror (errno)); @@ -343,17 +351,13 @@ set_child_error (gint report[2], switch (report[0]) { case CHILD_CHDIR_FAILED: - g_set_error (error, - G_SPAWN_ERROR, - G_SPAWN_ERROR_CHDIR, + 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, + g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, _("Failed to execute child process (%s)"), g_strerror (report[1])); break; @@ -362,6 +366,241 @@ set_child_error (gint report[2], } } +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 +utf8_charv_to_cp_charv (char **utf8_charv, + gchar ***cp_charv, + int *error_index, + GError **error) +{ + char **retval = NULL; + + *cp_charv = NULL; + if (utf8_charv != NULL) + { + int n = 0, i; + + while (utf8_charv[n]) + n++; + retval = g_new (char *, n + 1); + + for (i = 0; i < n; i++) + { + retval[i] = g_locale_from_utf8 (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; + } + + *cp_charv = retval; + return TRUE; +} + +static gboolean +do_spawn_directly (gboolean dont_wait, + gboolean dont_return_handle, + GSpawnFlags flags, + gchar **argv, + char **envp, + char **protected_argv, + GSpawnChildSetupFunc child_setup, + gpointer user_data, + GPid *child_handle, + gint *exit_status, + GError **error) +{ + int mode = dont_wait ? P_NOWAIT : P_WAIT; + char **new_argv; + int rc = -1; + int saved_errno; + GError *conv_error = NULL; + gint conv_error_index; + + new_argv = (flags & G_SPAWN_FILE_AND_ARGV_ZERO) ? protected_argv + 1 : protected_argv; + if (G_WIN32_HAVE_WIDECHAR_API ()) + { + wchar_t *wargv0, **wargv, **wenvp; + + 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 (child_setup) + (* child_setup) (user_data); + + 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_free (wargv0); + g_strfreev ((gchar **) wargv); + g_strfreev ((gchar **) wenvp); + } + else + { + char *cpargv0, **cpargv, **cpenvp; + + cpargv0 = g_locale_from_utf8 (argv[0], -1, NULL, NULL, &conv_error); + if (cpargv0 == 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_cp_charv (new_argv, &cpargv, &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 (cpargv0); + + return FALSE; + } + + if (!utf8_charv_to_cp_charv (envp, &cpenvp, 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 (cpargv0); + g_strfreev (cpargv); + + return FALSE; + } + + if (child_setup) + (* child_setup) (user_data); + + if (flags & G_SPAWN_SEARCH_PATH) + if (cpenvp != NULL) + rc = spawnvpe (mode, cpargv0, (const char **) cpargv, (const char **) cpenvp); + else + rc = spawnvp (mode, cpargv0, (const char **) cpargv); + else + if (envp != NULL) + rc = spawnve (mode, cpargv0, (const char **) cpargv, (const char **) cpenvp); + else + rc = spawnv (mode, cpargv0, (const char **) cpargv); + + g_free (cpargv0); + g_strfreev (cpargv); + g_strfreev (cpenvp); + } + + saved_errno = errno; + + 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; + } + + if (dont_wait) + { + if (child_handle && !dont_return_handle) + *child_handle = (GPid) rc; + else + { + CloseHandle ((HANDLE) rc); + if (child_handle) + *child_handle = 0; + } + } + else if (exit_status) + *exit_status = rc; + + return TRUE; +} + static gboolean do_spawn_with_pipes (gboolean dont_wait, gboolean dont_return_handle, @@ -391,9 +630,18 @@ do_spawn_with_pipes (gboolean dont_wait, int stderr_pipe[2] = { -1, -1 }; int child_err_report_pipe[2] = { -1, -1 }; int helper_report[2]; + static gboolean warned_about_child_setup = FALSE; + GError *conv_error = NULL; + gint conv_error_index; SETUP_DEBUG(); + 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 and dangerous on Win32"); + } + argc = protect_argv (argv, &protected_argv); if (!standard_input && !standard_output && !standard_error && @@ -404,65 +652,15 @@ do_spawn_with_pipes (gboolean dont_wait, (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 " HELPER_PROCESS "\n"); - - /* Call user function just before we spawn 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); - - new_argv = (flags & G_SPAWN_FILE_AND_ARGV_ZERO) ? protected_argv + 1 : protected_argv; - if (flags & G_SPAWN_SEARCH_PATH) - if (envp != NULL) - rc = spawnvpe (mode, argv[0], (const char **) new_argv, (const char **) envp); - else - rc = spawnvp (mode, argv[0], (const char **) new_argv); - else - if (envp != NULL) - rc = spawnve (mode, argv[0], (const char **) new_argv, (const char **) envp); - else - rc = spawnv (mode, argv[0], (const char **) new_argv); - - saved_errno = errno; - - for (i = 0; i < argc; i++) - g_free (protected_argv[i]); - g_free (protected_argv); - - 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 (errno)); - goto cleanup_and_fail; - } - - if (dont_wait) - { - if (child_handle && !dont_return_handle) - *child_handle = (GPid) rc; - else - { - CloseHandle (rc); - if (child_handle) - *child_handle = 0; - } - } - else if (exit_status) - *exit_status = rc; - - return TRUE; + gboolean retval = + do_spawn_directly (dont_wait, dont_return_handle, flags, + argv, envp, protected_argv, + child_setup, user_data, child_handle, + exit_status, error); + g_strfreev (protected_argv); + return retval; } - new_argv = g_new (char *, argc + 1 + ARG_COUNT); - if (standard_input && !make_pipe (stdin_pipe, error)) goto cleanup_and_fail; @@ -475,6 +673,7 @@ do_spawn_with_pipes (gboolean dont_wait, if (!make_pipe (child_err_report_pipe, error)) goto cleanup_and_fail; + new_argv = g_new (char *, argc + 1 + ARG_COUNT); new_argv[0] = 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]; @@ -532,8 +731,7 @@ do_spawn_with_pipes (gboolean dont_wait, } 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 ("-"); @@ -562,30 +760,121 @@ do_spawn_with_pipes (gboolean dont_wait, g_print ("argv[%d]: %s\n", i, (new_argv[i] ? new_argv[i] : "NULL")); } - if (child_setup) - (* child_setup) (user_data); + if (G_WIN32_HAVE_WIDECHAR_API ()) + { + wchar_t *whelper = g_utf8_to_utf16 (HELPER_PROCESS, -1, NULL, NULL, NULL); + wchar_t **wargv, **wenvp; - 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, HELPER_PROCESS, (const char **) new_argv, (const char **) envp); + 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[ARG_WORKING_DIRECTORY]); + g_free (new_argv); + g_free (whelper); + + 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_strfreev (protected_argv); + g_free (new_argv[ARG_WORKING_DIRECTORY]); + g_free (new_argv); + g_free (whelper); + g_strfreev ((gchar **) wargv); + + return FALSE; + } + + if (child_setup) + (* child_setup) (user_data); + + if (wenvp != NULL) + /* Let's hope envp hasn't mucked with PATH so that + * gspawn-win32-helper.exe isn't found. + */ + rc = _wspawnvpe (P_NOWAIT, whelper, (const wchar_t **) wargv, (const wchar_t **) wenvp); + else + rc = _wspawnvp (P_NOWAIT, whelper, (const wchar_t **) wargv); + + saved_errno = errno; + + g_free (whelper); + g_strfreev ((gchar **) wargv); + g_strfreev ((gchar **) wenvp); + } else - rc = spawnvp (P_NOWAIT, HELPER_PROCESS, (const char **) new_argv); + { + char **cpargv, **cpenvp; - saved_errno = errno; + if (!utf8_charv_to_cp_charv (new_argv, &cpargv, &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[ARG_WORKING_DIRECTORY]); + g_free (new_argv); + + return FALSE; + } + + if (!utf8_charv_to_cp_charv (envp, &cpenvp, 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[ARG_WORKING_DIRECTORY]); + g_free (new_argv); + g_strfreev (cpargv); + + return FALSE; + } - /* Close thed the other process's ends of the pipes in this - * process, otherwise the reader will never get EOF. + if (child_setup) + (* child_setup) (user_data); + + if (cpenvp != NULL) + rc = spawnvpe (P_NOWAIT, HELPER_PROCESS, (const char **) cpargv, (const char **) cpenvp); + else + rc = spawnvp (P_NOWAIT, HELPER_PROCESS, (const char **) cpargv); + + saved_errno = errno; + + g_strfreev (cpargv); + g_strfreev (cpenvp); + } + + /* 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 (&stdin_pipe[0]); close_and_invalidate (&stdout_pipe[1]); close_and_invalidate (&stderr_pipe[1]); - for (i = 0; i < argc; i++) - g_free (protected_argv[i]); - g_free (protected_argv); + g_strfreev (protected_argv); g_free (new_argv[ARG_WORKING_DIRECTORY]); g_free (new_argv); @@ -593,10 +882,9 @@ do_spawn_with_pipes (gboolean dont_wait, /* Check if gspawn-win32-helper couldn't be run */ 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; } @@ -680,16 +968,16 @@ do_spawn_with_pipes (gboolean dont_wait, } 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) +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) { gint outpipe = -1; gint errpipe = -1; @@ -791,9 +1079,7 @@ g_spawn_sync (const gchar *working_directory, { failed = TRUE; - g_set_error (error, - G_SPAWN_ERROR, - G_SPAWN_ERROR_READ, + 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; @@ -920,17 +1206,17 @@ g_spawn_sync (const gchar *working_directory, } 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_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 || @@ -958,12 +1244,283 @@ g_spawn_async_with_pipes (const gchar *working_directory, 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; +} + +void +g_spawn_close_pid (GPid pid) +{ + CloseHandle (pid); +} + +/* 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) + gchar **standard_output, + gchar **standard_error, + gint *exit_status, + GError **error) { gboolean retval; gchar **argv = 0; @@ -992,7 +1549,7 @@ g_spawn_command_line_sync (const gchar *command_line, gboolean g_spawn_command_line_async (const gchar *command_line, - GError **error) + GError **error) { gboolean retval; gchar **argv = 0; @@ -1019,11 +1576,5 @@ g_spawn_command_line_async (const gchar *command_line, #endif /* !GSPAWN_HELPER */ -void -g_spawn_close_pid (GPid pid) -{ - CloseHandle (pid); -} - #define __G_SPAWN_C__ #include "galiasdef.c" diff --git a/glib/gspawn.h b/glib/gspawn.h index 9ca187946..adb674173 100644 --- a/glib/gspawn.h +++ b/glib/gspawn.h @@ -71,6 +71,14 @@ typedef enum GQuark g_spawn_error_quark (void); +#ifdef G_OS_WIN32 +#define g_spawn_async g_spawn_async_utf8 +#define g_spawn_async_with_pipes g_spawn_async_with_pipes_utf8 +#define g_spawn_sync g_spawn_sync_utf8 +#define g_spawn_command_line_sync g_spawn_command_line_sync_utf8 +#define g_spawn_command_line_async g_spawn_command_line_async_utf8 +#endif + gboolean g_spawn_async (const gchar *working_directory, gchar **argv, gchar **envp,