/* * System abstraction utility routines * * Authors: * Jon A. Cruz * * Copyright (C) 2004-2005 Authors * * Released under GNU GPL, read the file 'COPYING' for more information */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #if GLIB_CHECK_VERSION(2,6,0) #include #endif #include #include #include "preferences.h" #include "sys.h" #ifdef WIN32 #define BYPASS_GLIB_SPAWN 1 #ifdef BYPASS_GLIB_SPAWN #include // declares spawn functions #include // declares _wspawn functions #ifndef __MINGW32__ # ifdef __cplusplus extern "C" { # endif _CRTIMP int __cdecl __MINGW_NOTHROW _wspawnl (int, const wchar_t*, const wchar_t*, ...); _CRTIMP int __cdecl __MINGW_NOTHROW _wspawnle (int, const wchar_t*, const wchar_t*, ...); _CRTIMP int __cdecl __MINGW_NOTHROW _wspawnlp (int, const wchar_t*, const wchar_t*, ...); _CRTIMP int __cdecl __MINGW_NOTHROW _wspawnlpe (int, const wchar_t*, const wchar_t*, ...); _CRTIMP int __cdecl __MINGW_NOTHROW _wspawnv (int, const wchar_t*, const wchar_t* const*); _CRTIMP int __cdecl __MINGW_NOTHROW _wspawnve (int, const wchar_t*, const wchar_t* const*, const wchar_t* const*); _CRTIMP int __cdecl __MINGW_NOTHROW _wspawnvp (int, const wchar_t*, const wchar_t* const*); _CRTIMP int __cdecl __MINGW_NOTHROW _wspawnvpe (int, const wchar_t*, const wchar_t* const*, const wchar_t* const*); # ifdef __cplusplus } # endif #endif #include #include #include #include #endif // BYPASS_GLIB_SPAWN // For now to get at is_os_wide(). #include "extension/internal/win32.h" using Inkscape::Extension::Internal::PrintWin32; #endif // WIN32 //#define INK_DUMP_FILENAME_CONV 1 #undef INK_DUMP_FILENAME_CONV //#define INK_DUMP_FOPEN 1 #undef INK_DUMP_FOPEN void dump_str(gchar const *str, gchar const *prefix); void dump_ustr(Glib::ustring const &ustr); extern guint update_in_progress; #define DEBUG_MESSAGE(key, ...) \ {\ Inkscape::Preferences *prefs = Inkscape::Preferences::get(); \ gint dump = prefs->getBool("/options/bulia/" #key) ? 1 : 0;\ gint dumpD = prefs->getBool("/options/bulia/" #key"D") ? 1 : 0;\ gint dumpD2 = prefs->getBool("/options/bulia/" #key"D2") ? 1 : 0;\ dumpD &= ( (update_in_progress == 0) || dumpD2 );\ if ( dump )\ {\ g_message( __VA_ARGS__ );\ \ }\ if ( dumpD )\ {\ GtkWidget *dialog = gtk_message_dialog_new(NULL,\ GTK_DIALOG_DESTROY_WITH_PARENT, \ GTK_MESSAGE_INFO, \ GTK_BUTTONS_OK, \ __VA_ARGS__ \ );\ g_signal_connect_swapped(dialog, "response",\ G_CALLBACK(gtk_widget_destroy), \ dialog); \ gtk_widget_show_all( dialog );\ }\ } void Inkscape::IO::dump_fopen_call( char const *utf8name, char const *id ) { #ifdef INK_DUMP_FOPEN Glib::ustring str; for ( int i = 0; utf8name[i]; i++ ) { if ( utf8name[i] == '\\' ) { str += "\\\\"; } else if ( (utf8name[i] >= 0x20) && ((0x0ff & utf8name[i]) <= 0x7f) ) { str += utf8name[i]; } else { gchar tmp[32]; g_snprintf( tmp, sizeof(tmp), "\\x%02x", (0x0ff & utf8name[i]) ); str += tmp; } } g_message( "fopen call %s for [%s]", id, str.data() ); #else (void)utf8name; (void)id; #endif } FILE *Inkscape::IO::fopen_utf8name( char const *utf8name, char const *mode ) { static gint counter = 0; FILE* fp = NULL; DEBUG_MESSAGE( dumpOne, "entering fopen_utf8name( '%s', '%s' )[%d]", utf8name, mode, (counter++) ); #ifndef WIN32 DEBUG_MESSAGE( dumpOne, " STEP 0 ( '%s', '%s' )[%d]", utf8name, mode, (counter++) ); gchar *filename = g_filename_from_utf8( utf8name, -1, NULL, NULL, NULL ); if ( filename ) { DEBUG_MESSAGE( dumpOne, " STEP 1 ( '%s', '%s' )[%d]", utf8name, mode, (counter++) ); fp = std::fopen(filename, mode); DEBUG_MESSAGE( dumpOne, " STEP 2 ( '%s', '%s' )[%d]", utf8name, mode, (counter++) ); g_free(filename); DEBUG_MESSAGE( dumpOne, " STEP 3 ( '%s', '%s' )[%d]", utf8name, mode, (counter++) ); filename = 0; } #else Glib::ustring how( mode ); how.append("b"); DEBUG_MESSAGE( dumpOne, " calling is_os_wide() ( '%s', '%s' )[%d]", utf8name, mode, (counter++) ); fp = g_fopen(utf8name, how.c_str()); #endif DEBUG_MESSAGE( dumpOne, "leaving fopen_utf8name( '%s', '%s' )[%d]", utf8name, mode, (counter++) ); return fp; } int Inkscape::IO::mkdir_utf8name( char const *utf8name ) { static gint counter = 0; int retval = -1; DEBUG_MESSAGE( dumpMk, "entering mkdir_utf8name( '%s' )[%d]", utf8name, (counter++) ); #ifndef WIN32 DEBUG_MESSAGE( dumpMk, " STEP 0 ( '%s' )[%d]", utf8name, (counter++) ); gchar *filename = g_filename_from_utf8( utf8name, -1, NULL, NULL, NULL ); if ( filename ) { DEBUG_MESSAGE( dumpMk, " STEP 1 ( '%s' )[%d]", utf8name, (counter++) ); retval = ::mkdir(filename, S_IRWXU | S_IRGRP | S_IXGRP); DEBUG_MESSAGE( dumpMk, " STEP 2 ( '%s' )[%d]", utf8name, (counter++) ); g_free(filename); DEBUG_MESSAGE( dumpMk, " STEP 3 ( '%s' )[%d]", utf8name, (counter++) ); filename = 0; } #else DEBUG_MESSAGE( dumpMk, " calling is_os_wide() ( '%s' )[%d]", utf8name, (counter++) ); // Mode should be ingnored inside of glib on the way in retval = g_mkdir( utf8name, 0 ); #endif DEBUG_MESSAGE( dumpMk, "leaving mkdir_utf8name( '%s' )[%d]", utf8name, (counter++) ); return retval; } /* * Wrapper around Glib::file_open_tmp(). * Returns a handle to the temp file. * name_used contains the actual name used (a raw filename, not necessarily utf8). * * Returns: * A file handle (as from open()) to the file opened for reading and writing. * The file is opened in binary mode on platforms where there is a difference. * The file handle should be closed with close(). * * Note: * On Windows Vista Glib::file_open_tmp fails with the current version of glibmm * A special case is implemented for WIN32. This can be removed if the issue is fixed * in future versions of glibmm * */ int Inkscape::IO::file_open_tmp(std::string& name_used, const std::string& prefix) { #ifndef WIN32 return Glib::file_open_tmp(name_used, prefix); #else /* Special case for WIN32 due to a bug in glibmm * (only needed for Windows Vista, but since there is only one windows build all builds get the workaround) * The workaround can be removed if the bug is fixed in glibmm * * The code is mostly identical to the implementation in glibmm * http://svn.gnome.org/svn/glibmm/branches/glibmm-2-12/glib/src/fileutils.ccg * */ std::string basename_template (prefix); basename_template += "XXXXXX"; // this sillyness shouldn't be in the interface GError* error = 0; gchar *buf_name_used; gint fileno = g_file_open_tmp(basename_template.c_str(), &buf_name_used, &error); if(error) Glib::Error::throw_exception(error); name_used = g_strdup(buf_name_used); g_free(buf_name_used); return fileno; #endif } bool Inkscape::IO::file_test( char const *utf8name, GFileTest test ) { bool exists = false; if ( utf8name ) { gchar *filename = NULL; if (utf8name && !g_utf8_validate(utf8name, -1, NULL)) { /* FIXME: Trying to guess whether or not a filename is already in utf8 is unreliable. If any callers pass non-utf8 data (e.g. using g_get_home_dir), then change caller to use simple g_file_test. Then add g_return_val_if_fail(g_utf_validate(...), false) to beginning of this function. */ filename = g_strdup(utf8name); // Looks like g_get_home_dir isn't safe. //g_warning("invalid UTF-8 detected internally. HUNT IT DOWN AND KILL IT!!!"); } else { filename = g_filename_from_utf8 ( utf8name, -1, NULL, NULL, NULL ); } if ( filename ) { exists = g_file_test (filename, test); g_free(filename); filename = NULL; } else { g_warning( "Unable to convert filename in IO:file_test" ); } } return exists; } bool Inkscape::IO::file_is_writable( char const *utf8name) { bool success = true; if ( utf8name) { gchar *filename = NULL; if (utf8name && !g_utf8_validate(utf8name, -1, NULL)) { /* FIXME: Trying to guess whether or not a filename is already in utf8 is unreliable. If any callers pass non-utf8 data (e.g. using g_get_home_dir), then change caller to use simple g_file_test. Then add g_return_val_if_fail(g_utf_validate(...), false) to beginning of this function. */ filename = g_strdup(utf8name); // Looks like g_get_home_dir isn't safe. //g_warning("invalid UTF-8 detected internally. HUNT IT DOWN AND KILL IT!!!"); } else { filename = g_filename_from_utf8 ( utf8name, -1, NULL, NULL, NULL ); } if ( filename ) { struct stat st; if(g_lstat (filename, &st) == 0) { success = ((st.st_mode & S_IWRITE) != 0); } g_free(filename); filename = NULL; } else { g_warning( "Unable to convert filename in IO:file_test" ); } } return success; } /** Wrapper around g_dir_open, but taking a utf8name as first argument. */ GDir * Inkscape::IO::dir_open(gchar const *const utf8name, guint const flags, GError **const error) { gchar *const opsys_name = g_filename_from_utf8(utf8name, -1, NULL, NULL, error); if (opsys_name) { GDir *ret = g_dir_open(opsys_name, flags, error); g_free(opsys_name); return ret; } else { return NULL; } } /** * Like g_dir_read_name, but returns a utf8name (which must be freed, unlike g_dir_read_name). * * N.B. Skips over any dir entries that fail to convert to utf8. */ gchar * Inkscape::IO::dir_read_utf8name(GDir *dir) { for (;;) { gchar const *const opsys_name = g_dir_read_name(dir); if (!opsys_name) { return NULL; } gchar *utf8_name = g_filename_to_utf8(opsys_name, -1, NULL, NULL, NULL); if (utf8_name) { return utf8_name; } } } gchar* Inkscape::IO::locale_to_utf8_fallback( const gchar *opsysstring, gssize len, gsize *bytes_read, gsize *bytes_written, GError **error ) { gchar *result = NULL; if ( opsysstring ) { gchar *newFileName = g_locale_to_utf8( opsysstring, len, bytes_read, bytes_written, error ); if ( newFileName ) { if ( !g_utf8_validate(newFileName, -1, NULL) ) { g_warning( "input filename did not yield UTF-8" ); g_free( newFileName ); } else { result = newFileName; } newFileName = 0; } else if ( g_utf8_validate(opsysstring, -1, NULL) ) { // This *might* be a case that we want // g_warning( "input failed filename->utf8, fell back to original" ); // TODO handle cases when len >= 0 result = g_strdup( opsysstring ); } else { gchar const *charset = 0; g_get_charset(&charset); g_warning( "input filename conversion failed for file with locale charset '%s'", charset ); } } return result; } #ifdef BYPASS_GLIB_SPAWN /* this code was taken from the original glib sources */ #define GSPAWN_HELPER enum { CHILD_NO_ERROR, CHILD_CHDIR_FAILED, CHILD_SPAWN_FAILED, }; 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 debug = 0; #define HELPER_PROCESS "gspawn-win32-helper" 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(reinterpret_cast(filehandle), mode | _O_NOINHERIT); } /* The helper process writes a status report back to us, through a * pipe, consisting of two ints. */ static gboolean read_helper_report (int fd, gint report[2], GError **error) { gint bytes = 0; while (bytes < sizeof(gint)*2) { gint chunk; if (debug) g_print ("%s:read_helper_report: read %d...\n", __FILE__, sizeof(gint)*2 - bytes); chunk = read (fd, ((gchar*)report) + bytes, sizeof(gint)*2 - bytes); if (debug) g_print ("...got %d bytes\n", chunk); if (chunk < 0) { /* Some weird shit happened, bail out */ g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, _("Failed to read from child pipe (%s)"), g_strerror (errno)); return FALSE; } else if (chunk == 0) { 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(gint)*2) return FALSE; return TRUE; } static void set_child_error (gint 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 (); } } 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 = (gchar *)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; } 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] = (wchar_t *)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; } /* 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; } static gboolean do_spawn_directly (gint *exit_status, gboolean do_return_handle, GSpawnFlags flags, gchar **argv, char **envp, char **protected_argv, GSpawnChildSetupFunc child_setup, gpointer user_data, GPid *child_handle, GError **error) { const int mode = (exit_status == NULL) ? P_NOWAIT : P_WAIT; char **new_argv; int 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 = (wchar_t *)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); 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 (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 make_pipe (gint p[2], GError **error) { if (_pipe (p, 4096, _O_BINARY) < 0) { g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, _("Failed to create pipe for communicating with child process (%s)"), g_strerror (errno)); return FALSE; } else return TRUE; } 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, gpointer user_data, GPid *child_handle, gint *standard_input, gint *standard_output, gint *standard_error, gint *err_report, GError **error) { char **protected_argv; char args[ARG_COUNT][10]; char **new_argv; int i; int rc = -1; int saved_errno; int argc; 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 }; int 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; //extern gchar *_glib_get_installation_directory (void); gchar *glib_top; 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 && (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_setup, user_data, child_handle, error); g_strfreev (protected_argv); return retval; } 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 (GetConsoleCursorInfo (GetStdHandle (STD_OUTPUT_HANDLE), &cursor_info)) helper_process = HELPER_PROCESS "-console.exe"; else helper_process = HELPER_PROCESS ".exe"; glib_top = NULL; if (glib_top != NULL) { helper_process = g_build_filename (glib_top, "bin", helper_process, NULL); g_free (glib_top); } else helper_process = g_strdup (helper_process); new_argv[0] = protect_argv_string (helper_process); 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], "#"); } 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 (standard_input) { sprintf (args[ARG_STDIN], "%d", stdin_pipe[0]); 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 (standard_output) { sprintf (args[ARG_STDOUT], "%d", stdout_pipe[1]); 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 (standard_error) { sprintf (args[ARG_STDERR], "%d", stderr_pipe[1]); 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; } if (child_setup) (* child_setup) (user_data); whelper = (wchar_t *)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); saved_errno = 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]); close_and_invalidate (&stdin_pipe[0]); close_and_invalidate (&stdout_pipe[1]); close_and_invalidate (&stderr_pipe[1]); 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 && saved_errno != 0) { 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; } 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 (standard_input) *standard_input = stdin_pipe[1]; if (standard_output) *standard_output = stdout_pipe[0]; if (standard_error) *standard_error = stderr_pipe[0]; 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]); 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 my_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, user_data, child_handle, standard_input, standard_output, standard_error, NULL, error); } typedef GPid Pid; // _WRAP_ENUM(SpawnFlags, GSpawnFlags, NO_GTYPE) /* Helper callback to invoke the actual sigc++ slot. * We don't need to worry about (un)referencing, since the * child process gets its own copy of the parent's memory anyway. */ static void child_setup_callback(void* user_data) { #ifdef GLIBMM_EXCEPTIONS_ENABLED try { #endif //GLIBMM_EXCEPTIONS_ENABLED (*reinterpret_cast*>(user_data))(); #ifdef GLIBMM_EXCEPTIONS_ENABLED } catch(...) { Glib::exception_handlers_invoke(); } #endif //GLIBMM_EXCEPTIONS_ENABLED } void my_spawn_async_with_pipes(const std::string& working_directory, const Glib::ArrayHandle& argv, GSpawnFlags flags, const sigc::slot& child_setup, Pid* child_pid, int* standard_input, int* standard_output, int* standard_error) { const bool setup_slot = !child_setup.empty(); sigc::slot child_setup_ = child_setup; GError* error = 0; my_spawn_async_with_pipes_utf8( working_directory.c_str(), const_cast(argv.data()), 0, static_cast(unsigned(flags)), (setup_slot) ? &child_setup_callback : 0, (setup_slot) ? &child_setup_ : 0, child_pid, standard_input, standard_output, standard_error, &error); if(error) Glib::Error::throw_exception(error); } #endif void Inkscape::IO::spawn_async_with_pipes( const std::string& working_directory, const Glib::ArrayHandle& argv, Glib::SpawnFlags flags, const sigc::slot& child_setup, Glib::Pid* child_pid, int* standard_input, int* standard_output, int* standard_error) { #ifndef BYPASS_GLIB_SPAWN Glib::spawn_async_with_pipes(working_directory, argv, flags, child_setup, child_pid, standard_input, standard_output, standard_error); #else my_spawn_async_with_pipes(working_directory, argv, static_cast(flags), child_setup, child_pid, standard_input, standard_output, standard_error); #endif } gchar* Inkscape::IO::sanitizeString( gchar const * str ) { gchar *result = NULL; if ( str ) { if ( g_utf8_validate(str, -1, NULL) ) { result = g_strdup(str); } else { guchar scratch[8]; Glib::ustring buf; guchar const *ptr = (guchar const*)str; while ( *ptr ) { if ( *ptr == '\\' ) { buf.append("\\\\"); } else if ( *ptr < 0x80 ) { buf += (char)(*ptr); } else { g_snprintf((gchar*)scratch, sizeof(scratch), "\\x%02x", *ptr); buf.append((const char*)scratch); } ptr++; } result = g_strdup(buf.c_str()); } } return result; } /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :