Branch: refs/heads/master Author: Colomban Wendling ban@herbesfolles.org Committer: Colomban Wendling ban@herbesfolles.org Date: Fri, 15 May 2015 16:54:23 UTC Commit: 826f6516d36ec7a7bed35116fda32d42449c1e5d https://github.com/geany/geany/commit/826f6516d36ec7a7bed35116fda32d42449c1e...
Log Message: ----------- Merge pull request #441 from zhekov/spawn
Add a spawn module for Geany
Thanks a lot for Dimitar's hard work, commitment and endless patience!
Closes #274, #441, and https://sourceforge.net/p/geany/bugs/943/ Should also fix https://sourceforge.net/p/geany/bugs/898/
Modified Paths: -------------- src/Makefile.am src/build.c src/callbacks.c src/keyfile.c src/makefile.win32 src/printing.c src/search.c src/spawn.c src/spawn.h src/templates.c src/tools.c src/utils.c src/win32.c src/win32.h wscript
Modified: src/Makefile.am 1 lines changed, 1 insertions(+), 0 deletions(-) =================================================================== @@ -89,6 +89,7 @@ libgeany_la_SOURCES = \ sciwrappers.c sciwrappers.h \ search.c search.h \ socket.c socket.h \ + spawn.c spawn.h \ stash.c stash.h \ support.h \ symbols.c symbols.h \
Modified: src/build.c 291 lines changed, 47 insertions(+), 244 deletions(-) =================================================================== @@ -44,6 +44,7 @@ #include "prefs.h" #include "projectprivate.h" #include "sciwrappers.h" +#include "spawn.h" #include "support.h" #include "toolbar.h" #include "ui_utils.h" @@ -60,20 +61,8 @@ #include <errno.h> #include <glib/gstdio.h>
-#ifdef G_OS_UNIX -# include <sys/types.h> -# include <sys/wait.h> -# include <signal.h> -#else -# include <windows.h> -#endif
-/* g_spawn_async_with_pipes doesn't work on Windows */ -#ifdef G_OS_WIN32 -#define SYNC_SPAWN -#endif - /* Number of editor indicators to draw - limited as this can affect performance */ #define GEANY_BUILD_ERR_HIGHLIGHT_MAX 50
@@ -124,12 +113,10 @@ widgets; static guint build_groups_count[GEANY_GBG_COUNT] = { 3, 4, 2 }; static guint build_items_count = 9;
-#ifndef SYNC_SPAWN -static void build_exit_cb(GPid child_pid, gint status, gpointer user_data); -static gboolean build_iofunc(GIOChannel *ioc, GIOCondition cond, gpointer data); -#endif +static void build_exit_cb(GPid pid, gint status, gpointer user_data); +static void build_iofunc(GString *string, GIOCondition condition, gpointer data); static gchar *build_create_shellscript(const gchar *working_dir, const gchar *cmd, gboolean autoclose, GError **error); -static GPid build_spawn_cmd(GeanyDocument *doc, const gchar *cmd, const gchar *dir); +static void build_spawn_cmd(GeanyDocument *doc, const gchar *cmd, const gchar *dir); static void set_stop_button(gboolean stop); static void run_exit_cb(GPid child_pid, gint status, gpointer user_data); static void on_set_build_commands_activate(GtkWidget *w, gpointer u); @@ -137,7 +124,7 @@ static void on_build_next_error(GtkWidget *menuitem, gpointer user_data); static void on_build_previous_error(GtkWidget *menuitem, gpointer user_data); static void kill_process(GPid *pid); static void show_build_result_message(gboolean failure); -static void process_build_output_line(const gchar *line, gint color); +static void process_build_output_line(gchar *msg, gint color); static void show_build_commands_dialog(void); static void on_build_menu_item(GtkWidget *w, gpointer user_data);
@@ -675,47 +662,6 @@ static void clear_all_errors(void) }
-#ifdef SYNC_SPAWN -static void parse_build_output(const gchar **output, gint status) -{ - guint x, i, len; - gchar *line, **lines; - - for (x = 0; x < 2; x++) - { - if (!EMPTY(output[x])) - { - lines = g_strsplit_set(output[x], "\r\n", -1); - len = g_strv_length(lines); - - for (i = 0; i < len; i++) - { - if (!EMPTY(lines[i])) - { - line = lines[i]; - while (*line != '\0') - { /* replace any control characters in the output */ - if (*line < 32) - *line = 32; - line++; - } - process_build_output_line(lines[i], COLOR_BLACK); - } - } - g_strfreev(lines); - } - } - - show_build_result_message(status != 0); - utils_beep(); - - build_info.pid = 0; - /* enable build items again */ - build_menu_update(NULL); -} -#endif - - /* Replaces occurrences of %e and %p with the appropriate filenames and * %l with current line number. %d and %p replacements should be in UTF8 */ static gchar *build_replace_placeholder(const GeanyDocument *doc, const gchar *src) @@ -784,43 +730,26 @@ static gchar *build_replace_placeholder(const GeanyDocument *doc, const gchar *s
/* dir is the UTF-8 working directory to run cmd in. It can be NULL to use the * idx document directory */ -static GPid build_spawn_cmd(GeanyDocument *doc, const gchar *cmd, const gchar *dir) +static void build_spawn_cmd(GeanyDocument *doc, const gchar *cmd, const gchar *dir) { GError *error = NULL; - gchar **argv; + gchar *argv[] = { "/bin/sh", "-c", (gchar *) cmd, NULL }; gchar *working_dir; gchar *utf8_working_dir; gchar *utf8_cmd_string; -#ifdef SYNC_SPAWN - gchar *output[2]; - gint status; -#else - gint stdout_fd; - gint stderr_fd; -#endif
- g_return_val_if_fail(doc == NULL || doc->is_valid, (GPid) -1); + g_return_if_fail(doc == NULL || doc->is_valid);
- if (!((doc != NULL && !EMPTY(doc->file_name)) || !EMPTY(dir))) + if ((doc == NULL || EMPTY(doc->file_name)) && EMPTY(dir)) { geany_debug("Failed to run command with no working directory"); ui_set_statusbar(TRUE, _("Process failed, no working directory")); - return (GPid) 1; + return; }
clear_all_errors(); SETPTR(current_dir_entered, NULL);
-#ifdef G_OS_WIN32 - argv = g_strsplit(cmd, " ", 0); -#else - argv = g_new0(gchar *, 4); - argv[0] = g_strdup("/bin/sh"); - argv[1] = g_strdup("-c"); - argv[2] = g_strdup(cmd); - argv[3] = NULL; -#endif - utf8_cmd_string = utils_get_utf8_from_locale(cmd); utf8_working_dir = !EMPTY(dir) ? g_strdup(dir) : g_path_get_dirname(doc->file_name); working_dir = utils_get_locale_from_utf8(utf8_working_dir); @@ -831,53 +760,28 @@ static GPid build_spawn_cmd(GeanyDocument *doc, const gchar *cmd, const gchar *d g_free(utf8_working_dir); g_free(utf8_cmd_string);
+#ifdef G_OS_UNIX + cmd = NULL; /* under Unix, use argv to start cmd via sh for compatibility */ +#else + argv[0] = NULL; /* under Windows, run cmd directly */ +#endif + /* set the build info for the message window */ g_free(build_info.dir); build_info.dir = g_strdup(working_dir); build_info.file_type_id = (doc == NULL) ? GEANY_FILETYPES_NONE : doc->file_type->id; build_info.message_count = 0;
-#ifdef SYNC_SPAWN - if (! utils_spawn_sync(working_dir, argv, NULL, G_SPAWN_SEARCH_PATH, - NULL, NULL, &output[0], &output[1], &status, &error)) -#else - if (! g_spawn_async_with_pipes(working_dir, argv, NULL, - G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, - &(build_info.pid), NULL, &stdout_fd, &stderr_fd, &error)) -#endif + if (!spawn_with_callbacks(working_dir, cmd, argv, NULL, 0, NULL, NULL, build_iofunc, + GINT_TO_POINTER(0), 0, build_iofunc, GINT_TO_POINTER(1), 0, build_exit_cb, NULL, + &build_info.pid, &error)) { geany_debug("build command spawning failed: %s", error->message); ui_set_statusbar(TRUE, _("Process failed (%s)"), error->message); - g_strfreev(argv); g_error_free(error); - g_free(working_dir); - error = NULL; - return (GPid) 0; - } - -#ifdef SYNC_SPAWN - parse_build_output((const gchar**) output, status); - g_free(output[0]); - g_free(output[1]); -#else - if (build_info.pid != 0) - { - g_child_watch_add(build_info.pid, (GChildWatchFunc) build_exit_cb, NULL); - build_menu_update(doc); - ui_progress_bar_start(NULL); }
- /* use GIOChannels to monitor stdout and stderr */ - utils_set_up_io_channel(stdout_fd, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL, - TRUE, build_iofunc, GINT_TO_POINTER(0)); - utils_set_up_io_channel(stderr_fd, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL, - TRUE, build_iofunc, GINT_TO_POINTER(1)); -#endif - - g_strfreev(argv); g_free(working_dir); - - return build_info.pid; }
@@ -903,13 +807,10 @@ static gchar *prepare_run_cmd(GeanyDocument *doc, gchar **working_dir, guint cmd working_dir_utf8 = build_replace_placeholder(doc, cmd_working_dir); *working_dir = utils_get_locale_from_utf8(working_dir_utf8);
- /* only test whether working dir exists, don't change it or else Windows support will break - * (gspawn-win32-helper.exe is used by GLib and must be in $PATH which means current working - * dir where geany.exe was started from, so we can't change it) */ if (EMPTY(*working_dir) || ! g_file_test(*working_dir, G_FILE_TEST_EXISTS) || ! g_file_test(*working_dir, G_FILE_TEST_IS_DIR)) { - ui_set_statusbar(TRUE, _("Failed to change the working directory to "%s""), + ui_set_statusbar(TRUE, _("Invalid working directory "%s""), !EMPTY(working_dir_utf8) ? working_dir_utf8 : "<NULL>" ); utils_free_pointers(3, cmd_string_utf8, working_dir_utf8, *working_dir, NULL); return NULL; @@ -945,18 +846,17 @@ static gchar *prepare_run_cmd(GeanyDocument *doc, gchar **working_dir, guint cmd }
-static GPid build_run_cmd(GeanyDocument *doc, guint cmdindex) +static void build_run_cmd(GeanyDocument *doc, guint cmdindex) { gchar *working_dir; gchar *run_cmd = NULL; - GError *error = NULL;
if (! DOC_VALID(doc) || doc->file_name == NULL) - return (GPid) 0; + return;
run_cmd = prepare_run_cmd(doc, &working_dir, cmdindex); if (run_cmd == NULL) - return (GPid) 0; + return;
run_info[cmdindex].file_type_id = doc->file_type->id;
@@ -990,97 +890,48 @@ static GPid build_run_cmd(GeanyDocument *doc, guint cmdindex) msgwin_show_hide(TRUE);
run_info[cmdindex].pid = 1; - g_free(vte_cmd); } else #endif { - gchar *locale_term_cmd = NULL; - gint argv_len, i; - gchar **argv = NULL; - gchar *script_path = NULL; + gchar *locale_term_cmd = utils_get_locale_from_utf8(tool_prefs.term_cmd); + GError *error = NULL;
- /* get the terminal path */ - locale_term_cmd = utils_get_locale_from_utf8(tool_prefs.term_cmd); - /* split the term_cmd, so arguments will work too */ - if (!g_shell_parse_argv(locale_term_cmd, &argv_len, &argv, NULL)) - { - ui_set_statusbar(TRUE, - _("Could not parse terminal command "%s" " - "(check Terminal tool setting in Preferences)"), tool_prefs.term_cmd); - run_info[cmdindex].pid = (GPid) 1; - g_unlink(run_cmd); - goto free_strings; - } + utils_str_replace_all(&locale_term_cmd, "%c", run_cmd);
- /* check that terminal exists (to prevent misleading error messages) */ - if (argv[0] != NULL) + if (spawn_async(working_dir, locale_term_cmd, NULL, NULL, &(run_info[cmdindex].pid), + &error)) { - gchar *tmp = argv[0]; - /* g_find_program_in_path checks whether tmp exists and is executable */ - argv[0] = g_find_program_in_path(tmp); - g_free(tmp); - } - if (argv[0] == NULL) - { - ui_set_statusbar(TRUE, - _("Could not find terminal "%s" " - "(check path for Terminal tool setting in Preferences)"), tool_prefs.term_cmd); - run_info[cmdindex].pid = (GPid) 1; - g_unlink(run_cmd); - goto free_strings; - } - - for (i = 0; i < argv_len; i++) - { - utils_str_replace_all(&(argv[i]), "%c", run_cmd); + g_child_watch_add(run_info[cmdindex].pid, (GChildWatchFunc) run_exit_cb, + (gpointer) &(run_info[cmdindex])); + build_menu_update(doc); } - - if (! g_spawn_async(working_dir, argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD, - NULL, NULL, &(run_info[cmdindex].pid), &error)) + else { - geany_debug("g_spawn_async() failed: %s", error->message); + geany_debug("spawn_async() failed: %s", error->message); ui_set_statusbar(TRUE, _("Process failed (%s)"), error->message); g_error_free(error); g_unlink(run_cmd); - error = NULL; run_info[cmdindex].pid = (GPid) 0; } - - if (run_info[cmdindex].pid != 0) - { - g_child_watch_add(run_info[cmdindex].pid, (GChildWatchFunc) run_exit_cb, - (gpointer)&(run_info[cmdindex])); - build_menu_update(doc); - } - free_strings: - g_strfreev(argv); - g_free(locale_term_cmd); - g_free(script_path); }
g_free(working_dir); g_free(run_cmd); - return run_info[cmdindex].pid; }
-static void process_build_output_line(const gchar *str, gint color) +static void process_build_output_line(gchar *msg, gint color) { - gchar *msg, *tmp; + gchar *tmp; gchar *filename; gint line;
- msg = g_strdup(str); - g_strchomp(msg);
if (EMPTY(msg)) - { - g_free(msg); return; - }
if (build_parse_make_dir(msg, &tmp)) { @@ -1106,33 +957,17 @@ static void process_build_output_line(const gchar *str, gint color) g_free(filename);
msgwin_compiler_add_string(color, msg); - g_free(msg); }
-#ifndef SYNC_SPAWN -static gboolean build_iofunc(GIOChannel *ioc, GIOCondition cond, gpointer data) +static void build_iofunc(GString *string, GIOCondition condition, gpointer data) { - if (cond & (G_IO_IN | G_IO_PRI)) + if (condition & (G_IO_IN | G_IO_PRI)) { - gchar *msg; - GIOStatus st; - - while ((st = g_io_channel_read_line(ioc, &msg, NULL, NULL, NULL)) == G_IO_STATUS_NORMAL && msg) - { - gint color = (GPOINTER_TO_INT(data)) ? COLOR_DARK_RED : COLOR_BLACK; - - process_build_output_line(msg, color); - g_free(msg); - } - if (st == G_IO_STATUS_ERROR || st == G_IO_STATUS_EOF) return FALSE; + process_build_output_line(string->str, + (GPOINTER_TO_INT(data)) ? COLOR_DARK_RED : COLOR_BLACK); } - if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) - return FALSE; - - return TRUE; } -#endif
gboolean build_parse_make_dir(const gchar *string, gchar **prefix) @@ -1205,40 +1040,16 @@ static void show_build_result_message(gboolean failure) }
-#ifndef SYNC_SPAWN static void build_exit_cb(GPid child_pid, gint status, gpointer user_data) { - gboolean failure = FALSE; - -#ifdef G_OS_WIN32 - failure = status; -#else - if (WIFEXITED(status)) - { - if (WEXITSTATUS(status) != EXIT_SUCCESS) - failure = TRUE; - } - else if (WIFSIGNALED(status)) - { - /* the terminating signal: WTERMSIG (status)); */ - failure = TRUE; - } - else - { /* any other failure occurred */ - failure = TRUE; - } -#endif - show_build_result_message(failure); - + show_build_result_message(!WIFEXITED(status) || WEXITSTATUS(status) != EXIT_SUCCESS); utils_beep(); - g_spawn_close_pid(child_pid);
build_info.pid = 0; /* enable build items again */ build_menu_update(NULL); ui_progress_bar_stop(); } -#endif
static void run_exit_cb(GPid child_pid, gint status, gpointer user_data) @@ -1790,26 +1601,18 @@ static void on_toolbutton_make_activate(GtkWidget *menuitem, gpointer user_data)
static void kill_process(GPid *pid) { - gint result; - -#ifdef G_OS_WIN32 - g_return_if_fail(*pid != NULL); - result = TerminateProcess(*pid, 0); - /* TerminateProcess() returns TRUE on success, for the check below we have to convert - * it to FALSE (and vice versa) */ - result = ! result; -#else - g_return_if_fail(*pid > 1); - result = kill(*pid, SIGTERM); -#endif + GError *error = NULL;
- if (result != 0) - ui_set_statusbar(TRUE, _("Process could not be stopped (%s)."), g_strerror(errno)); - else + if (spawn_kill_process(*pid, &error)) { *pid = 0; build_menu_update(NULL); } + else + { + ui_set_statusbar(TRUE, _("Process could not be stopped (%s)."), error->message); + g_error_free(error); + } }
Modified: src/callbacks.c 3 lines changed, 2 insertions(+), 1 deletions(-) =================================================================== @@ -51,6 +51,7 @@ #include "printing.h" #include "sciwrappers.h" #include "sidebar.h" +#include "spawn.h" #ifdef HAVE_SOCKET # include "socket.h" #endif @@ -1479,7 +1480,7 @@ void on_context_action1_activate(GtkMenuItem *menuitem, gpointer user_data) { utils_str_replace_all(&command, "%s", word);
- if (! g_spawn_command_line_async(command, &error)) + if (!spawn_async(NULL, command, NULL, NULL, NULL, &error)) { ui_set_statusbar(TRUE, "Context action command failed: %s", error->message); g_error_free(error);
Modified: src/keyfile.c 13 lines changed, 7 insertions(+), 6 deletions(-) =================================================================== @@ -941,19 +941,20 @@ static void load_dialog_prefs(GKeyFile *config)
/* printing */ tmp_string2 = g_find_program_in_path(GEANY_DEFAULT_TOOLS_PRINTCMD); -#ifdef G_OS_WIN32 + if (!EMPTY(tmp_string2)) { - /* single quote paths on Win32 for g_spawn_command_line_async */ - tmp_string = g_strconcat("'", tmp_string2, "' '%f'", NULL); + #ifdef G_OS_WIN32 + tmp_string = g_strconcat(GEANY_DEFAULT_TOOLS_PRINTCMD, " "%f"", NULL); + #else + tmp_string = g_strconcat(GEANY_DEFAULT_TOOLS_PRINTCMD, " '%f'", NULL); + #endif } else { tmp_string = g_strdup(""); } -#else - tmp_string = g_strconcat(tmp_string2, " %f", NULL); -#endif + printing_prefs.external_print_cmd = utils_get_setting_string(config, "printing", "print_cmd", tmp_string); g_free(tmp_string); g_free(tmp_string2);
Modified: src/makefile.win32 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -67,7 +67,7 @@ OBJS = about.o build.o callbacks.o dialogs.o document.o editor.o encodings.o fil geanyentryaction.o geanymenubuttonaction.o geanyobject.o geanywraplabel.o highlighting.o \ keybindings.o keyfile.o log.o main.o msgwindow.o navqueue.o notebook.o \ plugins.o pluginutils.o prefs.o printing.o project.o sciwrappers.o search.o \ - socket.o stash.o symbols.o templates.o toolbar.o tools.o sidebar.o \ + socket.o spawn.o stash.o symbols.o templates.o toolbar.o tools.o sidebar.o \ ui_utils.o utils.o win32.o
.c.o:
Modified: src/printing.c 19 lines changed, 9 insertions(+), 10 deletions(-) =================================================================== @@ -39,6 +39,7 @@ #include "highlighting.h" #include "msgwindow.h" #include "sciwrappers.h" +#include "spawn.h" #include "support.h" #include "utils.h" #include "ui_utils.h" @@ -600,16 +601,15 @@ static void print_external(GeanyDocument *doc) doc->file_name, cmdline)) { GError *error = NULL; - -#ifdef G_OS_WIN32 - gchar *tmp_cmdline = g_strdup(cmdline); -#else /* /bin/sh -c emulates the system() call and makes complex commands possible - * but only needed on non-win32 systems due to the lack of win32's shell capabilities */ - gchar *tmp_cmdline = g_strconcat("/bin/sh -c "", cmdline, """, NULL); -#endif - - if (! g_spawn_command_line_async(tmp_cmdline, &error)) + * but only on non-win32 systems due to the lack of win32's shell capabilities */ + #ifdef G_OS_UNIX + gchar *argv[] = { "/bin/sh", "-c", cmdline, NULL }; + + if (!spawn_async(NULL, NULL, argv, NULL, NULL, &error)) + #else + if (!spawn_async(NULL, cmdline, NULL, NULL, NULL, &error)) + #endif { dialogs_show_msgbox(GTK_MESSAGE_ERROR, _("Printing of "%s" failed (return code: %s)."), @@ -620,7 +620,6 @@ static void print_external(GeanyDocument *doc) { msgwin_status_add(_("File %s printed."), doc->file_name); } - g_free(tmp_cmdline); } g_free(cmdline); }
Modified: src/search.c 175 lines changed, 63 insertions(+), 112 deletions(-) =================================================================== @@ -37,6 +37,7 @@ #include "msgwindow.h" #include "prefs.h" #include "sciwrappers.h" +#include "spawn.h" #include "stash.h" #include "support.h" #include "toolbar.h" @@ -49,11 +50,6 @@ #include <string.h> #include <ctype.h>
-#ifdef G_OS_UNIX -# include <sys/types.h> -# include <sys/wait.h> -#endif - #include <gdk/gdkkeysyms.h>
enum @@ -149,10 +145,10 @@ static struct fif_dlg = {NULL, NULL, NULL, NULL, NULL, NULL, {0, 0}};
-static gboolean search_read_io(GIOChannel *source, GIOCondition condition, gpointer data); -static gboolean search_read_io_stderr(GIOChannel *source, GIOCondition condition, gpointer data); +static void search_read_io(GString *string, GIOCondition condition, gpointer data); +static void search_read_io_stderr(GString *string, GIOCondition condition, gpointer data);
-static void search_close_pid(GPid child_pid, gint status, gpointer user_data); +static void search_finished(GPid child_pid, gint status, gpointer user_data);
static gchar **search_get_argv(const gchar **argv_prefix, const gchar *dir);
@@ -1619,21 +1615,17 @@ on_find_in_files_dialog_response(GtkDialog *dialog, gint response, ui_set_statusbar(FALSE, _("Invalid directory for find in files.")); else if (!EMPTY(search_text)) { - gchar *locale_dir; GString *opts = get_grep_options(); const gchar *enc = (enc_idx == GEANY_ENCODING_UTF_8) ? NULL : encodings_get_charset_from_index(enc_idx);
- locale_dir = utils_get_locale_from_utf8(utf8_dir); - - if (search_find_in_files(search_text, locale_dir, opts->str, enc)) + if (search_find_in_files(search_text, utf8_dir, opts->str, enc)) { ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(search_combo), search_text, 0); ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(fif_dlg.files_combo), NULL, 0); ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(dir_combo), utf8_dir, 0); gtk_widget_hide(fif_dlg.dialog); } - g_free(locale_dir); g_string_free(opts, TRUE); } else @@ -1645,36 +1637,26 @@ on_find_in_files_dialog_response(GtkDialog *dialog, gint response,
static gboolean -search_find_in_files(const gchar *utf8_search_text, const gchar *dir, const gchar *opts, +search_find_in_files(const gchar *utf8_search_text, const gchar *utf8_dir, const gchar *opts, const gchar *enc) { - gchar **argv_prefix, **argv, **opts_argv; + gchar **argv_prefix, **argv; gchar *command_grep; + gchar *command_line, *dir; gchar *search_text = NULL; - gint opts_argv_len, i; - GPid child_pid; - gint stdout_fd; - gint stderr_fd; GError *error = NULL; gboolean ret = FALSE; gssize utf8_text_len;
- if (EMPTY(utf8_search_text) || ! dir) return TRUE; + if (EMPTY(utf8_search_text) || ! utf8_dir) return TRUE;
command_grep = g_find_program_in_path(tool_prefs.grep_cmd); if (command_grep == NULL) + command_line = g_strdup_printf("%s %s --", tool_prefs.grep_cmd, opts); + else { - ui_set_statusbar(TRUE, _("Cannot execute grep tool '%s';" - " check the path setting in Preferences."), tool_prefs.grep_cmd); - return FALSE; - } - - if (! g_shell_parse_argv(opts, &opts_argv_len, &opts_argv, &error)) - { - ui_set_statusbar(TRUE, _("Cannot parse extra options: %s"), error->message); - g_error_free(error); + command_line = g_strdup_printf(""%s" %s --", command_grep, opts); g_free(command_grep); - return FALSE; }
/* convert the search text in the preferred encoding (if the text is not valid UTF-8. assume @@ -1687,74 +1669,58 @@ search_find_in_files(const gchar *utf8_search_text, const gchar *dir, const gcha if (search_text == NULL) search_text = g_strdup(utf8_search_text);
- /* set grep command and options */ - argv_prefix = g_new0(gchar*, 1 + opts_argv_len + 3 + 1); /* last +1 for recursive arg */ - - argv_prefix[0] = command_grep; - for (i = 0; i < opts_argv_len; i++) - { - argv_prefix[i + 1] = g_strdup(opts_argv[i]); - } - g_strfreev(opts_argv); - - i++; /* correct for tool_prefs.grep_cmd */ - argv_prefix[i++] = g_strdup("--"); - argv_prefix[i++] = search_text; + argv_prefix = g_new(gchar*, 3); + argv_prefix[0] = search_text; + dir = utils_get_locale_from_utf8(utf8_dir);
/* finally add the arguments(files to be searched) */ - if (strstr(argv_prefix[1], "r")) /* recursive option set */ + if (settings.fif_recursive) /* recursive option set */ { /* Use '.' so we get relative paths in the output */ - argv_prefix[i++] = g_strdup("."); - argv_prefix[i++] = NULL; + argv_prefix[1] = g_strdup("."); + argv_prefix[2] = NULL; argv = argv_prefix; } else { - argv_prefix[i++] = NULL; + argv_prefix[1] = NULL; argv = search_get_argv((const gchar**)argv_prefix, dir); g_strfreev(argv_prefix); - }
- if (argv == NULL) /* no files */ - { - return FALSE; + if (argv == NULL) /* no files */ + { + g_free(command_line); + return FALSE; + } }
gtk_list_store_clear(msgwindow.store_msg); gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), MSG_MESSAGE);
- if (! g_spawn_async_with_pipes(dir, (gchar**)argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD, - NULL, NULL, &child_pid, - NULL, &stdout_fd, &stderr_fd, &error)) - { - geany_debug("%s: g_spawn_async_with_pipes() failed: %s", G_STRFUNC, error->message); - ui_set_statusbar(TRUE, _("Process failed (%s)"), error->message); - g_error_free(error); - ret = FALSE; + /* we can pass 'enc' without strdup'ing it here because it's a global const string and + * always exits longer than the lifetime of this function */ + if (spawn_with_callbacks(dir, command_line, argv, NULL, 0, NULL, NULL, search_read_io, + (gpointer) enc, 0, search_read_io_stderr, (gpointer) enc, 0, search_finished, NULL, + NULL, &error)) + { + gchar *utf8_str; + + ui_progress_bar_start(_("Searching...")); + msgwin_set_messages_dir(dir); + utf8_str = g_strdup_printf(_("%s %s -- %s (in directory: %s)"), + tool_prefs.grep_cmd, opts, utf8_search_text, utf8_dir); + msgwin_msg_add_string(COLOR_BLUE, -1, NULL, utf8_str); + g_free(utf8_str); + ret = TRUE; } else { - gchar *str, *utf8_str; - - ui_progress_bar_start(_("Searching...")); - - msgwin_set_messages_dir(dir); - /* we can pass 'enc' without strdup'ing it here because it's a global const string and - * always exits longer than the lifetime of this function */ - utils_set_up_io_channel(stdout_fd, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL, - TRUE, search_read_io, (gpointer) enc); - utils_set_up_io_channel(stderr_fd, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL, - TRUE, search_read_io_stderr, (gpointer) enc); - g_child_watch_add(child_pid, search_close_pid, NULL); - - str = g_strdup_printf(_("%s %s -- %s (in directory: %s)"), - tool_prefs.grep_cmd, opts, utf8_search_text, dir); - utf8_str = utils_get_utf8_from_locale(str); - msgwin_msg_add_string(COLOR_BLUE, -1, NULL, utf8_str); - utils_free_pointers(2, str, utf8_str, NULL); - ret = TRUE; + geany_debug("%s: spawn_with_callbacks() failed: %s", G_STRFUNC, error->message); + ui_set_statusbar(TRUE, _("Process failed (%s)"), error->message); + g_error_free(error); } + + utils_free_pointers(2, dir, command_line, NULL); g_strfreev(argv); return ret; } @@ -1837,61 +1803,47 @@ static gchar **search_get_argv(const gchar **argv_prefix, const gchar *dir) }
-static gboolean read_fif_io(GIOChannel *source, GIOCondition condition, gchar *enc, gint msg_color) +static void read_fif_io(gchar *msg, GIOCondition condition, gchar *enc, gint msg_color) { if (condition & (G_IO_IN | G_IO_PRI)) { - gchar *msg, *utf8_msg; - GIOStatus st; + gchar *utf8_msg = NULL;
- while ((st = g_io_channel_read_line(source, &msg, NULL, NULL, NULL)) != G_IO_STATUS_ERROR && - st != G_IO_STATUS_EOF && msg) + g_strstrip(msg); + /* enc is NULL when encoding is set to UTF-8, so we can skip any conversion */ + if (enc != NULL) { - utf8_msg = NULL; - - g_strstrip(msg); - /* enc is NULL when encoding is set to UTF-8, so we can skip any conversion */ - if (enc != NULL) + if (! g_utf8_validate(msg, -1, NULL)) { - if (! g_utf8_validate(msg, -1, NULL)) - { - utf8_msg = g_convert(msg, -1, "UTF-8", enc, NULL, NULL, NULL); - } - if (utf8_msg == NULL) - utf8_msg = msg; + utf8_msg = g_convert(msg, -1, "UTF-8", enc, NULL, NULL, NULL); } - else + if (utf8_msg == NULL) utf8_msg = msg; + } + else + utf8_msg = msg;
- msgwin_msg_add_string(msg_color, -1, NULL, utf8_msg); + msgwin_msg_add_string(msg_color, -1, NULL, utf8_msg);
- if (utf8_msg != msg) - g_free(utf8_msg); - g_free(msg); - } - if (st == G_IO_STATUS_ERROR || st == G_IO_STATUS_EOF) - return FALSE; + if (utf8_msg != msg) + g_free(utf8_msg); } - if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) - return FALSE; - - return TRUE; }
-static gboolean search_read_io(GIOChannel *source, GIOCondition condition, gpointer data) +static void search_read_io(GString *string, GIOCondition condition, gpointer data) { - return read_fif_io(source, condition, data, COLOR_BLACK); + return read_fif_io(string->str, condition, data, COLOR_BLACK); }
-static gboolean search_read_io_stderr(GIOChannel *source, GIOCondition condition, gpointer data) +static void search_read_io_stderr(GString *string, GIOCondition condition, gpointer data) { - return read_fif_io(source, condition, data, COLOR_DARK_RED); + return read_fif_io(string->str, condition, data, COLOR_DARK_RED); }
-static void search_close_pid(GPid child_pid, gint status, gpointer user_data) +static void search_finished(GPid child_pid, gint status, gpointer user_data) { const gchar *msg = _("Search failed."); #ifdef G_OS_UNIX @@ -1932,7 +1884,6 @@ static void search_close_pid(GPid child_pid, gint status, gpointer user_data) break; } utils_beep(); - g_spawn_close_pid(child_pid); ui_progress_bar_stop(); }
Modified: src/spawn.c 1341 lines changed, 1341 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,1341 @@ +/* + * spawn.c - this file is part of Geany, a fast and lightweight IDE + * + * Copyright 2013 Dimitar Toshkov Zhekov <dimitar(dot)zhekov(at)gmail(dot)com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* An ongoing effort to improve the tool spawning situation under Windows. + * In particular: + * - There is no g_shell_parse_argv() for windows. It's not hard to write one, + * but the command line recreated by mscvrt may be wrong. + * - GLib converts the argument vector to UNICODE. For non-UTF8 arguments, + * the result is often "Invalid string in argument vector at %d: %s: Invalid + * byte sequence in conversion input" (YMMV). Our tools (make, grep, gcc, ...) + * are "ANSI", so converting to UNICODE and then back only causes problems. + * - For various reasons, GLib uses an intermediate program to start children + * (see gspawn-win32.c), the result being that the grandchildren output (such + * as make -> gcc) is not captured. + * - With non-blocking pipes, the g_io_add_watch() callbacks are never invoked, + * while with blocking pipes, g_io_channel_read_line() blocks. + * - Some other problems are explained in separate comments below. + * + * Even under Unix, using g_io_channel_read_line() is not a good idea, since it may + * buffer lines of unlimited length. + * + * This module does not depend on Geany when compiled for testing (-DSPAWN_TEST). + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <errno.h> +#include <string.h> + +#include "spawn.h" + +#ifdef G_OS_WIN32 +# include <ctype.h> /* isspace() */ +# include <fcntl.h> /* _O_RDONLY, _O_WRONLY */ +# include <io.h> /* _open_osfhandle, _close */ +# include <windows.h> +#else /* G_OS_WIN32 */ +# include <signal.h> +#endif /* G_OS_WIN32 */ + +#ifdef SPAWN_TEST +# define _ +#else +# include "support.h" +#endif + +#ifdef G_OS_WIN32 +/* Each 4KB under Windows seem to come in 2 portions, so 2K + 2K is more + balanced than 4095 + 1. May be different on the latest Windows/glib? */ +# define DEFAULT_IO_LENGTH 2048 +#else +# define DEFAULT_IO_LENGTH 4096 +#endif + +#define G_IO_FAILURE (G_IO_ERR | G_IO_HUP | G_IO_NVAL) /* always used together */ + + +/** + * Checks whether a command line is syntactically valid and extracts the program name from it. + * + * All OS: + * - any leading spaces, tabs and new lines are skipped + * - an empty command is invalid + * Unix: + * - the standard shell quoting and escaping rules are used, see @c g_shell_parse_argv() + * - as a consequence, an unqouted # at the start of an argument comments to the end of line + * Windows: + * - leading carriage returns are skipped too + * - a quoted program name must be entirely inside the quotes. No "C:\Foo\Bar".pdf or + * "C:\Foo\Bar".bat, which would be executed by Windows as C:\Foo\Bar.exe + * - an unquoted program name may not contain spaces. Foo Bar Qux will not be considered + * "Foo Bar.exe" Qux or "Foo Bar Qux.exe", depending on what executables exist, as + * Windows normally does. + * - the program name must be separated from the arguments by at least one space or tab + * - the standard Windows quoting and escaping rules are used: double quote is escaped with + * backslash, and any literal backslashes before a double quote must be duplicated. + * + * @param command_line the command line to check and get the program name from. + * @param error return location for error. + * + * @return allocated string with the program name on success, @c NULL on error. + * + * @since 1.25 + **/ +GEANY_API_SYMBOL +gchar *spawn_get_program_name(const gchar *command_line, GError **error) +{ + gchar *program; + +#ifdef G_OS_WIN32 + gboolean open_quote = FALSE; + const gchar *s, *arguments; + + g_return_val_if_fail(command_line != NULL, FALSE); + + while (*command_line && strchr(" \t\r\n", *command_line)) + command_line++; + + if (!*command_line) + { + /* TL note: from glib */ + g_set_error(error, G_SHELL_ERROR, G_SHELL_ERROR_EMPTY_STRING, + _("Text was empty (or contained only whitespace)")); + return FALSE; + } + + /* To prevent Windows from doing something weird, we want to be 100% sure that the + character after the program name is a delimiter, so we allow space and tab only. */ + + if (*command_line == '"') + { + command_line++; + /* Windows allows "foo.exe, but we may have extra arguments */ + if ((s = strchr(command_line, '"')) == NULL) + { + /* TL note: from glib */ + g_set_error(error, G_SHELL_ERROR, G_SHELL_ERROR_BAD_QUOTING, + _("Text ended before matching quote was found for %c." + " (The text was '%s')"), '"', command_line); + return FALSE; + } + + if (!strchr(" \t", s[1])) /* strchr() catches s[1] == '\0' */ + { + g_set_error(error, G_SHELL_ERROR, G_SHELL_ERROR_BAD_QUOTING, + _("A quoted Windows program name must be entirely inside the quotes")); + return FALSE; + } + } + else + { + const gchar *quote = strchr(command_line, '"'); + + /* strchr() catches *s == '\0', and the for body is empty */ + for (s = command_line; !strchr(" \t", *s); s++); + + if (quote && quote < s) + { + g_set_error(error, G_SHELL_ERROR, G_SHELL_ERROR_BAD_QUOTING, + _("A quoted Windows program name must be entirely inside the quotes")); + return FALSE; + } + } + + program = g_strndup(command_line, s - command_line); + arguments = s + (*s == '"'); + + for (s = arguments; *s; s++) + { + if (*s == '"') + { + const char *slash; + + for (slash = s; slash > arguments && slash[-1] == '\'; slash--); + if ((s - slash) % 2 == 0) + open_quote ^= TRUE; + } + } + + if (open_quote) + { + /* TL note: from glib */ + g_set_error(error, G_SHELL_ERROR, G_SHELL_ERROR_BAD_QUOTING, + _("Text ended before matching quote was found for %c." + " (The text was '%s')"), '"', command_line); + g_free(program); + return FALSE; + } +#else /* G_OS_WIN32 */ + int argc; + char **argv; + + if (!g_shell_parse_argv(command_line, &argc, &argv, error)) + return FALSE; + + /* empty string results in parse error, so argv[0] is not NULL */ + program = g_strdup(argv[0]); + g_strfreev(argv); +#endif /* G_OS_WIN32 */ + + return program; +} + + +/** + * Checks whether a command line is valid. + * + * Checks if @a command_line is syntactically valid using @c spawn_get_program_name(). + * + * If @a execute is TRUE, also checks, using @c g_find_program_in_path(), if the program + * specified in @a command_line exists and is executable. + * + * @param command_line the command line to check. + * @param execute whether to check if the command line is really executable. + * @param error return location for error. + * + * @return @c TRUE on success, @c FALSE on error. + * + * @since 1.25 + **/ +GEANY_API_SYMBOL +gboolean spawn_check_command(const gchar *command_line, gboolean execute, GError **error) +{ + gchar *program = spawn_get_program_name(command_line, error); + + if (!program) + return FALSE; + + if (execute) + { + gchar *executable = g_find_program_in_path(program); + + if (!executable) + { + g_set_error(error, G_SHELL_ERROR, G_SHELL_ERROR_FAILED, /* or SPAWN error? */ + _("Program '%s' not found"), program); + g_free(program); + return FALSE; + } + + g_free(executable); + } + + g_free(program); + return TRUE; +} + + +/** + * Kills a process. + * + * @param pid id of the process to kill. + * @param error return location for error. + * + * On Unix, sends a SIGTERM to the process. + * + * On Windows, terminates the process with exit code 255 (used sometimes as "generic" + * error code, or for programs terminated with Ctrl+C / Ctrl+Break). + * + * @return @c TRUE on success, @c FALSE on error. + * + * @since 1.25 + **/ +GEANY_API_SYMBOL +gboolean spawn_kill_process(GPid pid, GError **error) +{ +#ifdef G_OS_WIN32 + if (!TerminateProcess(pid, 255)) + { + gchar *message = g_win32_error_message(GetLastError()); + + g_set_error(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, + _("TerminateProcess() failed: %s"), message); + g_free(message); + return FALSE; + } +#else + if (kill(pid, SIGTERM)) + { + g_set_error(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, "%s", g_strerror(errno)); + return FALSE; + } +#endif + return TRUE; +} + + +#ifdef G_OS_WIN32 +static gchar *spawn_create_process_with_pipes(char *command_line, const char *working_directory, + void *environment, HANDLE *hprocess, int *stdin_fd, int *stdout_fd, int *stderr_fd) +{ + enum { WRITE_STDIN, READ_STDOUT, READ_STDERR, READ_STDIN, WRITE_STDOUT, WRITE_STDERR }; + STARTUPINFO startup; + PROCESS_INFORMATION process; + HANDLE hpipe[6] = { NULL, NULL, NULL, NULL, NULL, NULL }; + int *fd[3] = { stdin_fd, stdout_fd, stderr_fd }; + const char *failed; /* failed WIN32/CRTL function, if any */ + gchar *message = NULL; /* glib WIN32/CTRL error message */ + gchar *failure = NULL; /* full error text */ + gboolean pipe_io; + int i; + + ZeroMemory(&startup, sizeof startup); + startup.cb = sizeof startup; + pipe_io = stdin_fd || stdout_fd || stderr_fd; + + if (pipe_io) + { + startup.dwFlags |= STARTF_USESTDHANDLES; + + /* not all programs accept mixed NULL and non-NULL hStd*, so we create all */ + for (i = 0; i < 3; i++) + { + static int pindex[3][2] = { { READ_STDIN, WRITE_STDIN }, + { READ_STDOUT, WRITE_STDOUT }, { READ_STDERR, WRITE_STDERR } }; + + if (!CreatePipe(&hpipe[pindex[i][0]], &hpipe[pindex[i][1]], NULL, 0)) + { + hpipe[pindex[i][0]] = hpipe[pindex[i][1]] = NULL; + failed = "CreatePipe"; + goto leave; + } + + if (fd[i]) + { + static int mode[3] = { _O_WRONLY, _O_RDONLY, _O_RDONLY }; + + if ((*fd[i] = _open_osfhandle((intptr_t) hpipe[i], mode[i])) == -1) + { + failed = "_open_osfhandle"; + message = g_strdup(g_strerror(errno)); + goto leave; + } + } + else if (!CloseHandle(hpipe[i])) + { + failed = "CloseHandle"; + goto leave; + } + + if (!SetHandleInformation(hpipe[i + 3], HANDLE_FLAG_INHERIT, + HANDLE_FLAG_INHERIT)) + { + failed = "SetHandleInformation"; + goto leave; + } + } + } + + startup.hStdInput = hpipe[READ_STDIN]; + startup.hStdOutput = hpipe[WRITE_STDOUT]; + startup.hStdError = hpipe[WRITE_STDERR]; + + if (!CreateProcess(NULL, command_line, NULL, NULL, TRUE, pipe_io ? CREATE_NO_WINDOW : 0, + environment, working_directory, &startup, &process)) + { + failed = "CreateProcess"; + /* further errors will not be reported */ + } + else + { + failed = NULL; + CloseHandle(process.hThread); /* we don't need this */ + *hprocess = process.hProcess; + } + +leave: + if (failed) + { + if (!message) + message = g_win32_error_message(GetLastError()); + + failure = g_strdup_printf("%s() failed: %s", failed, message); + g_free(message); + } + + if (pipe_io) + { + for (i = 0; i < 3; i++) + { + if (failed) + { + if (fd[i] && *fd[i] != -1) + _close(*fd[i]); + else if (hpipe[i]) + CloseHandle(hpipe[i]); + } + + if (hpipe[i + 3]) + CloseHandle(hpipe[i + 3]); + } + } + + return failure; +} + + +static void spawn_append_argument(GString *command, const char *text) +{ + const char *s; + + if (command->len) + g_string_append_c(command, ' '); + + for (s = text; *s; s++) + { + /* g_ascii_isspace() fails for '\v', and locale spaces (if any) will do no harm */ + if (*s == '"' || isspace(*s)) + break; + } + + if (*text && !*s) + g_string_append(command, text); + else + { + g_string_append_c(command, '"'); + + for (s = text; *s; s++) + { + const char *slash; + + for (slash = s; *slash == '\'; slash++); + + if (slash > s) + { + g_string_append_len(command, s, slash - s); + + if (!*slash || *slash == '"') + { + g_string_append_len(command, s, slash - s); + + if (!*slash) + break; + } + + s = slash; + } + + if (*s == '"') + g_string_append_c(command, '\'); + + g_string_append_c(command, *s); + } + + g_string_append_c(command, '"'); + } +} + + +static void spawn_close_pid(GPid pid, G_GNUC_UNUSED gint status, G_GNUC_UNUSED gpointer data) +{ + g_spawn_close_pid(pid); +} +#endif /* G_OS_WIN32 */ + + +/** + * Executes a child program asynchronously and setups pipes. + * + * This is the low-level spawning function. Please use @c spawn_with_callbacks() unless + * you need to setup specific event source(s). + * + * A command line or an argument vector must be passed. If both are present, the argument + * vector is appended to the command line. An empty command line is not allowed. + * + * Under Windows, if the child is a console application, and at least one file descriptor is + * specified, the new child console (if any) will be hidden. + * + * If a @a child_pid is passed, it's your responsibility to invoke @c g_spawn_close_pid(). + * + * @param working_directory child's current working directory, or @c NULL. + * @param command_line child program and arguments, or @c NULL. + * @param argv child's argument vector, or @c NULL. + * @param envp child's environment, or @c NULL. + * @param child_pid return location for child process ID, or @c NULL. + * @param stdin_fd return location for file descriptor to write to child's stdin, or @c NULL. + * @param stdout_fd return location for file descriptor to read child's stdout, or @c NULL. + * @param stderr_fd return location for file descriptor to read child's stderr, or @c NULL. + * @param error return location for error. + * + * @return @c TRUE on success, @c FALSE on error. + * + * @since 1.25 + **/ +GEANY_API_SYMBOL +gboolean spawn_async_with_pipes(const gchar *working_directory, const gchar *command_line, + gchar **argv, gchar **envp, GPid *child_pid, gint *stdin_fd, gint *stdout_fd, + gint *stderr_fd, GError **error) +{ + g_return_val_if_fail(command_line != NULL || argv != NULL, FALSE); + +#ifdef G_OS_WIN32 + GString *command; + GArray *environment; + GPid pid; + gchar *failure; + + if (command_line) + { + gchar *program = spawn_get_program_name(command_line, error); + const gchar *arguments; + + if (!program) + return FALSE; + + command = g_string_new(NULL); + arguments = strstr(command_line, program) + strlen(program); + + if (*arguments == '"') + { + g_string_append(command, program); + arguments++; + } + else + { + /* quote the first token, to avoid Windows attemps to run two or more + unquoted tokens as a program until an existing file name is found */ + g_string_printf(command, ""%s"", program); + } + + g_string_append(command, arguments); + g_free(program); + } + else + command = g_string_new(NULL); + + environment = g_array_new(TRUE, FALSE, sizeof(char)); + + while (argv && *argv) + spawn_append_argument(command, *argv++); + +#ifdef SPAWN_TEST + g_message("full spawn command line: %s\n", command->str); +#endif + + while (envp && *envp) + { + g_array_append_vals(environment, *envp, strlen(*envp) + 1); + envp++; + } + + failure = spawn_create_process_with_pipes(command->str, working_directory, + envp ? environment->data : NULL, child_pid ? child_pid : &pid, + stdin_fd, stdout_fd, stderr_fd); + + g_string_free(command, TRUE); + g_array_free(environment, TRUE); + + if (failure) + { + g_set_error(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, "%s", failure); + g_free(failure); + return FALSE; + } + else if (!child_pid) + g_child_watch_add(pid, spawn_close_pid, NULL); + + return TRUE; +#else /* G_OS_WIN32 */ + int cl_argc; + char **full_argv; + gboolean spawned; + + if (command_line) + { + int argc = 0; + char **cl_argv; + + if (!g_shell_parse_argv(command_line, &cl_argc, &cl_argv, error)) + return FALSE; + + if (argv) + for (argc = 0; argv[argc]; argc++); + + full_argv = g_renew(gchar *, cl_argv, cl_argc + argc + 1); + memcpy(full_argv + cl_argc, argv, argc * sizeof(gchar *)); + full_argv[cl_argc + argc] = NULL; + } + else + full_argv = argv; + + spawned = g_spawn_async_with_pipes(working_directory, full_argv, envp, + G_SPAWN_SEARCH_PATH | (child_pid ? G_SPAWN_DO_NOT_REAP_CHILD : 0), NULL, NULL, + child_pid, stdin_fd, stdout_fd, stderr_fd, error); + + if (full_argv != argv) + { + full_argv[cl_argc] = NULL; + g_strfreev(full_argv); + } + + return spawned; +#endif /* G_OS_WIN32 */ +} + + +/** + * Executes a child asynchronously. + * + * See @c spawn_async_with_pipes() for a full description; this function simply calls + * @c g_spawn_async_with_pipes() without any pipes. + * + * @since 1.25 + **/ +GEANY_API_SYMBOL +gboolean spawn_async(const gchar *working_directory, const gchar *command_line, gchar **argv, + gchar **envp, GPid *child_pid, GError **error) +{ + return spawn_async_with_pipes(working_directory, command_line, argv, envp, child_pid, + NULL, NULL, NULL, error); +} + + +/* + * Spawn with callbacks - general event sequence: + * + * - Launch the child. + * - Setup any I/O callbacks and a child watch callback. + * - On sync execution, run a main loop. + * - Wait for the child to terminate. + * - Check for active I/O sources. If any, add a timeout source to watch them, they should + * become inactive real soon now that the child is dead. Otherwise, finalize immediately. + * - In the timeout source: check for active I/O sources and finalize if none. + */ + +typedef struct _SpawnChannelData +{ + GIOChannel *channel; /* NULL if not created / already destroyed */ + union + { + GIOFunc write; + SpawnReadFunc read; + } cb; + gpointer cb_data; + /* stdout/stderr only */ + GString *buffer; /* NULL if recursive */ + GString *line_buffer; /* NULL if char buffered */ + gsize max_length; +} SpawnChannelData; + + +static void spawn_destroy_cb(gpointer data) +{ + SpawnChannelData *sc = (SpawnChannelData *) data; + + g_io_channel_shutdown(sc->channel, FALSE, NULL); + sc->channel = NULL; + + if (sc->buffer) + g_string_free(sc->buffer, TRUE); + + if (sc->line_buffer) + g_string_free(sc->line_buffer, TRUE); +} + + +static gboolean spawn_write_cb(GIOChannel *channel, GIOCondition condition, gpointer data) +{ + SpawnChannelData *sc = (SpawnChannelData *) data; + + if (!sc->cb.write(channel, condition, sc->cb_data)) + return FALSE; + + return !(condition & G_IO_FAILURE); +} + + +static gboolean spawn_read_cb(GIOChannel *channel, GIOCondition condition, gpointer data) +{ + SpawnChannelData *sc = (SpawnChannelData *) data; + GString *line_buffer = sc->line_buffer; + GString *buffer = sc->buffer ? sc->buffer : g_string_sized_new(sc->max_length); + GIOCondition input_cond = condition & (G_IO_IN | G_IO_PRI); + GIOCondition failure_cond = condition & G_IO_FAILURE; + /* + * - Normally, read only once. With IO watches, our data processing must be immediate, + * which may give the child time to emit more data, and a read loop may combine it into + * large stdout and stderr portions. Under Windows, looping blocks. + * - On failure, read in a loop. It won't block now, there will be no more data, and the + * IO watch is not guaranteed to be called again (under Windows this is the last call). + */ + if (input_cond) + { + gsize chars_read; + GIOStatus status; + + if (line_buffer) + { + gsize n = line_buffer->len; + + while ((status = g_io_channel_read_chars(channel, line_buffer->str + n, + DEFAULT_IO_LENGTH, &chars_read, NULL)) == G_IO_STATUS_NORMAL) + { + g_string_set_size(line_buffer, n + chars_read); + + while (n < line_buffer->len) + { + gsize line_len = 0; + + if (n == sc->max_length) + line_len = n; + else if (strchr("\n", line_buffer->str[n])) /* '\n' or '\0' */ + line_len = n + 1; + else if (n < line_buffer->len - 1 && line_buffer->str[n] == '\r') + line_len = n + 1 + (line_buffer->str[n + 1] == '\n'); + + if (!line_len) + n++; + else + { + g_string_append_len(buffer, line_buffer->str, line_len); + g_string_erase(line_buffer, 0, line_len); + /* input only, failures are reported separately below */ + sc->cb.read(buffer, input_cond, sc->cb_data); + g_string_truncate(buffer, 0); + n = 0; + } + } + + if (!failure_cond) + break; + } + } + else + { + while ((status = g_io_channel_read_chars(channel, buffer->str, sc->max_length, + &chars_read, NULL)) == G_IO_STATUS_NORMAL) + { + g_string_set_size(buffer, chars_read); + /* input only, failures are reported separately below */ + sc->cb.read(buffer, input_cond, sc->cb_data); + + if (!failure_cond) + break; + } + } + + /* Under OSX, after child death, the read watches receive input conditions instead + of error conditions, so we convert the termination statuses into conditions. + Should not hurt the other OS. */ + if (status == G_IO_STATUS_ERROR) + failure_cond |= G_IO_ERR; + else if (status == G_IO_STATUS_EOF) + failure_cond |= G_IO_HUP; + } + + if (failure_cond) /* we must signal the callback */ + { + if (line_buffer && line_buffer->len) /* flush the line buffer */ + { + g_string_append_len(buffer, line_buffer->str, line_buffer->len); + /* all data may be from a previous call */ + if (!input_cond) + input_cond = G_IO_IN; + } + else + { + input_cond = 0; + g_string_truncate(buffer, 0); + } + + sc->cb.read(buffer, input_cond | failure_cond, sc->cb_data); + } + + if (buffer != sc->buffer) + g_string_free(buffer, TRUE); + + return !failure_cond; +} + + +typedef struct _SpawnWatcherData +{ + SpawnChannelData sc[3]; /* stdin, stdout, stderr */ + GChildWatchFunc exit_cb; + gpointer exit_data; + GPid pid; + gint exit_status; + GMainContext *main_context; /* NULL if async execution */ + GMainLoop *main_loop; /* NULL if async execution */ +} SpawnWatcherData; + + +static void spawn_finalize(SpawnWatcherData *sw) +{ + if (sw->exit_cb) + sw->exit_cb(sw->pid, sw->exit_status, sw->exit_data); + + if (sw->main_loop) + { + g_main_loop_quit(sw->main_loop); + g_main_loop_unref(sw->main_loop); + } + + g_spawn_close_pid(sw->pid); + g_slice_free(SpawnWatcherData, sw); +} + + +static gboolean spawn_timeout_cb(gpointer data) +{ + SpawnWatcherData *sw = (SpawnWatcherData *) data; + int i; + + for (i = 0; i < 3; i++) + if (sw->sc[i].channel) + return TRUE; + + spawn_finalize(sw); + return FALSE; +} + + +static void spawn_watch_cb(GPid pid, gint status, gpointer data) +{ + SpawnWatcherData *sw = (SpawnWatcherData *) data; + int i; + + sw->pid = pid; + sw->exit_status = status; + + for (i = 0; i < 3; i++) + { + if (sw->sc[i].channel) + { + GSource *source = g_timeout_source_new(50); + + g_source_set_callback(source, spawn_timeout_cb, data, NULL); + g_source_attach(source, sw->main_context); + g_source_unref(source); + return; + } + } + + spawn_finalize(sw); +} + + +/** + * Executes a child program and setups callbacks. + * + * A command line or an argument vector must be passed. If both are present, the argument + * vector is appended to the command line. An empty command line is not allowed. + * + * The synchronous execution may not be combined with recursive callbacks. + * + * In line buffered mode, the child input is broken on '\n', "\r\n", '\r', '\0' and max length. + * + * All I/O callbacks are guaranteed to be invoked at least once with @c G_IO_ERR, @c G_IO_HUP + * or @c G_IO_NVAL set (except for a @a stdin_cb which returns @c FALSE before that). For the + * non-recursive callbacks, this is guaranteed to be the last call, and may be used to free any + * resources associated with the callback. + * + * The @a stdin_cb may write to @c channel only once per invocation, only if @c G_IO_OUT is + * set, and only a non-zero number of characters. + * + * @c stdout_cb and @c stderr_cb may modify the received strings in any way, but must not + * free them. + * + * The default max lengths are 24K for line buffered stdout, 8K for line buffered stderr, + * 4K for unbuffered input under Unix, and 2K for unbuffered input under Windows. + * + * @c exit_cb is always invoked last, after all I/O callbacks. + * + * The @a child_pid will be closed automatically, after @a exit_cb is invoked. + * + * @param working_directory child's current working directory, or @c NULL. + * @param command_line child program and arguments, or @c NULL. + * @param argv child's argument vector, or @c NULL. + * @param envp child's environment, or @c NULL. + * @param spawn_flags flags from SpawnFlags. + * @param stdin_cb callback to send data to childs's stdin, or @c NULL. + * @param stdin_data data to pass to @a stdin_cb. + * @param stdout_cb callback to receive child's stdout, or @c NULL. + * @param stdout_data data to pass to @a stdout_cb. + * @param stdout_max_length maximum data length to pass to stdout_cb, @c 0 = default. + * @param stderr_cb callback to receive child's stderr, or @c NULL. + * @param stderr_data data to pass to @a stderr_cb. + * @param stderr_max_length maximum data length to pass to stderr_cb, @c 0 = default. + * @param exit_cb callback to invoke when the child exits, or @c NULL. + * @param exit_data data to pass to @a exit_cb. + * @param child_pid return location for child process ID, or @c NULL. + * @param error return location for error. + * + * @return @c TRUE on success, @c FALSE on error. + * + * @since 1.25 + **/ +GEANY_API_SYMBOL +gboolean spawn_with_callbacks(const gchar *working_directory, const gchar *command_line, + gchar **argv, gchar **envp, SpawnFlags spawn_flags, GIOFunc stdin_cb, gpointer stdin_data, + SpawnReadFunc stdout_cb, gpointer stdout_data, gsize stdout_max_length, + SpawnReadFunc stderr_cb, gpointer stderr_data, gsize stderr_max_length, + GChildWatchFunc exit_cb, gpointer exit_data, GPid *child_pid, GError **error) +{ + GPid pid; + int pipe[3] = { -1, -1, -1 }; + + g_return_val_if_fail(!(spawn_flags & SPAWN_RECURSIVE) || !(spawn_flags & SPAWN_SYNC), + FALSE); + + if (spawn_async_with_pipes(working_directory, command_line, argv, envp, &pid, + stdin_cb ? &pipe[0] : NULL, stdout_cb ? &pipe[1] : NULL, + stderr_cb ? &pipe[2] : NULL, error)) + { + SpawnWatcherData *sw = g_slice_new0(SpawnWatcherData); + gpointer cb_data[3] = { stdin_data, stdout_data, stderr_data }; + GSource *source; + int i; + + sw->main_context = spawn_flags & SPAWN_SYNC ? g_main_context_new() : NULL; + + if (child_pid) + *child_pid = pid; + + for (i = 0; i < 3; i++) + { + SpawnChannelData *sc = &sw->sc[i]; + GIOCondition condition; + GSourceFunc callback; + + if (pipe[i] == -1) + continue; + + #ifdef G_OS_WIN32 + sc->channel = g_io_channel_win32_new_fd(pipe[i]); + #else + sc->channel = g_io_channel_unix_new(pipe[i]); + g_io_channel_set_flags(sc->channel, G_IO_FLAG_NONBLOCK, NULL); + #endif + g_io_channel_set_encoding(sc->channel, NULL, NULL); + /* we have our own buffers, and GIO buffering blocks under Windows */ + g_io_channel_set_buffered(sc->channel, FALSE); + sc->cb_data = cb_data[i]; + + if (i == 0) + { + sc->cb.write = stdin_cb; + condition = G_IO_OUT | G_IO_FAILURE; + callback = (GSourceFunc) spawn_write_cb; + } + else + { + gboolean line_buffered = !(spawn_flags & + ((SPAWN_STDOUT_UNBUFFERED >> 1) << i)); + + condition = G_IO_IN | G_IO_PRI | G_IO_FAILURE; + callback = (GSourceFunc) spawn_read_cb; + + if (i == 1) + { + sc->cb.read = stdout_cb; + sc->max_length = stdout_max_length ? stdout_max_length : + line_buffered ? 24576 : DEFAULT_IO_LENGTH; + } + else + { + sc->cb.read = stderr_cb; + sc->max_length = stderr_max_length ? stderr_max_length : + line_buffered ? 8192 : DEFAULT_IO_LENGTH; + } + + if (line_buffered) + { + sc->line_buffer = g_string_sized_new(sc->max_length + + DEFAULT_IO_LENGTH); + } + } + + source = g_io_create_watch(sc->channel, condition); + g_io_channel_unref(sc->channel); + + if (spawn_flags & (SPAWN_STDIN_RECURSIVE << i)) + g_source_set_can_recurse(source, TRUE); + else if (i) /* to avoid new string on each call */ + sc->buffer = g_string_sized_new(sc->max_length); + + g_source_set_callback(source, callback, sc, spawn_destroy_cb); + g_source_attach(source, sw->main_context); + g_source_unref(source); + } + + sw->exit_cb = exit_cb; + sw->exit_data = exit_data; + source = g_child_watch_source_new(pid); + g_source_set_callback(source, (GSourceFunc) spawn_watch_cb, sw, NULL); + g_source_attach(source, sw->main_context); + g_source_unref(source); + + if (spawn_flags & SPAWN_SYNC) + { + sw->main_loop = g_main_loop_new(sw->main_context, FALSE); + g_main_context_unref(sw->main_context); + g_main_loop_run(sw->main_loop); + } + + return TRUE; + } + + return FALSE; +} + + +/** + * Writes (a portion of) the data pointed by @a data->ptr to the @a channel. + * + * If @c G_IO_OUT in @a condition is set, and the @a data->size is > 0, attempts to write + * @a data->ptr (or a portion of it, depending on the size) to the @a channel. On success, + * increases ptr and decreases size with the number of characters written. + * + * This function may converted to @c GIOFunc and passed to @c spawn_with_callbacks() as + * @c stdin_cb, together with a @c SpawnWriteData for @c stdin_data. As with any other + * callback data, make sure that @c stdin_data exists while the child is being executed. + * (For example, on asynchronous execution, you can allocate the data in the heap, and free + * it in your @c spawn_with_callbacks() @c exit_cb callback.) + * + * @return @c TRUE if the remaining size is > 0 and @a condition does not indicate any error, + * @c FALSE otherwise. + * + * @since 1.25 + **/ +GEANY_API_SYMBOL +gboolean spawn_write_data(GIOChannel *channel, GIOCondition condition, SpawnWriteData *data) +{ + if ((condition & G_IO_OUT) && data->size) + { + gsize chars_written = 0; + + g_io_channel_write_chars(channel, data->ptr, data->size < DEFAULT_IO_LENGTH ? + data->size : DEFAULT_IO_LENGTH, &chars_written, NULL); + + /* "This can be nonzero even if the return value is not G_IO_STATUS_NORMAL." */ + if (chars_written) + { + data->ptr += chars_written; + data->size -= chars_written; + } + } + + return data->size > 0 && !(condition & G_IO_FAILURE); +} + + +static void spawn_append_gstring_cb(GString *string, GIOCondition condition, gpointer data) +{ + if (condition & (G_IO_IN | G_IO_PRI)) + g_string_append_len((GString *) data, string->str, string->len); +} + + +/** + * Convinience @c GChildWatchFunc callback that copies the child exit status into a gint + * pointed by @a exit_status. + * + * @since 1.25 + **/ +GEANY_API_SYMBOL +void spawn_get_exit_status_cb(G_GNUC_UNUSED GPid pid, gint status, gpointer exit_status) +{ + *(gint *) exit_status = status; +} + + +/** + * Executes a child synchronously. + * + * A command line or an argument vector must be passed. If both are present, the argument + * vector is appended to the command line. An empty command line is not allowed. + * + * The @a stdin_data is sent to the child with @c spawn_write_data(). + * + * All output from the child, including the nul characters, is stored in @a stdout_data and + * @a stderr_data (if non-NULL). Any existing data in these strings will be erased. + * + * @param working_directory child's current working directory, or @c NULL. + * @param command_line child program and arguments, or @c NULL. + * @param argv child's argument vector, or @c NULL. + * @param envp child's environment, or @c NULL. + * @param stdin_data data to send to childs's stdin, or @c NULL. + * @param stdout_data GString location to receive the child's stdout, or NULL. + * @param stderr_data GString location to receive the child's stderr, or NULL. + * @param exit_status return location for the child exit code, or NULL. + * @param error return location for error. + * + * @return @c TRUE on success, @c FALSE on error. + * + * @since 1.25 + **/ +GEANY_API_SYMBOL +gboolean spawn_sync(const gchar *working_directory, const gchar *command_line, gchar **argv, + gchar **envp, SpawnWriteData *stdin_data, GString *stdout_data, GString *stderr_data, + gint *exit_status, GError **error) +{ + g_string_truncate(stdout_data, 0); + g_string_truncate(stderr_data, 0); + + return spawn_with_callbacks(working_directory, command_line, argv, envp, SPAWN_SYNC | + SPAWN_UNBUFFERED, stdin_data ? (GIOFunc) spawn_write_data : NULL, stdin_data, + stdout_data ? spawn_append_gstring_cb : NULL, stdout_data, 0, + stderr_data ? spawn_append_gstring_cb : NULL, stderr_data, 0, + exit_status ? spawn_get_exit_status_cb : NULL, exit_status, NULL, error); +} + + +/* tests, not part of the API */ +#ifdef SPAWN_TEST +#include <stdio.h> + + +static gboolean read_line(const char *prompt, char *buffer, size_t size) +{ + fputs(prompt, stderr); + *buffer = '\0'; + + if (fgets(buffer, size, stdin)) + { + char *s = strchr(buffer, '\n'); + + if (s) + *s = '\0'; + } + + return *buffer; +} + + +static GString *read_string(const char *prompt) +{ + char buffer[0x1000]; /* larger portions for spawn < file */ + GString *string = g_string_sized_new(sizeof buffer); + + while (read_line(prompt, buffer, sizeof buffer)) + { + if (string->len) + g_string_append_c(string, '\n'); + + g_string_append(string, buffer); + } + + if (!string->len) + { + g_string_free(string, TRUE); + string = NULL; + } + + return string; +} + + +static void print_cb(GString *string, GIOCondition condition, gpointer data) +{ + if (condition & (G_IO_IN | G_IO_PRI)) + { + gsize i; + + printf("%s: ", (const gchar *) data); + /*fputs(string->str, stdout);*/ + for (i = 0; i < string->len; i++) + { + unsigned char c = (unsigned char) string->str[i]; + printf(c >= ' ' && c < 0x80 ? "%c" : "\x%02x", c); + } + putchar('\n'); + } +} + + +static void print_status(gint status) +{ + fputs("finished, ", stderr); + + if (WIFEXITED(status)) + fprintf(stderr, "exit code %d\n", WEXITSTATUS(status)); + else + fputs("abnormal termination\n", stderr); +} + + +static void exit_cb(GPid pid, gint status, G_GNUC_UNUSED gpointer data) +{ + fprintf(stderr, "process %u ", (guint) pid); + print_status(status); +} + + +static void watch_cb(GPid pid, gint status, gpointer data) +{ + g_spawn_close_pid(pid); + exit_cb(pid, status, NULL); + g_main_loop_quit((GMainLoop *) data); +} + + +int main(int argc, char **argv) +{ + char *test_type; + + if (argc != 2) + { + fputs("usage: spawn <test-type>\n", stderr); + return 1; + } + + test_type = argv[1]; + + if (!strcmp(test_type, "syntax") || !strcmp(test_type, "syntexec")) + { + char command_line[0x100]; + + while (read_line("command line: ", command_line, sizeof command_line)) + { + GError *error = NULL; + + if (spawn_check_command(command_line, argv[1][4] == 'e', &error)) + fputs("valid\n", stderr); + else + { + fprintf(stderr, "error: %s\n", error->message); + g_error_free(error); + } + } + } + else if (!strcmp(test_type, "execute")) + { + char command_line[0x100]; + + while (read_line("command line: ", command_line, sizeof command_line)) + { + char working_directory[0x100]; + char args[4][0x100]; + char envs[4][0x100]; + char *argv[] = { args[0], args[1], args[2], args[3], NULL }; + char *envp[] = { envs[0], envs[1], envs[2], envs[3], NULL }; + int i; + GPid pid; + GError *error = NULL; + + read_line("working directory: ", working_directory, sizeof working_directory); + + fputs("up to 4 arguments\n", stderr); + for (i = 0; i < 4 && read_line("argument: ", args[i], sizeof args[i]); i++); + argv[i] = NULL; + + fputs("up to 4 variables, or empty line for parent environment\n", stderr); + for (i = 0; i < 4 && read_line("variable: ", envs[i], sizeof envs[i]); i++); + envp[i] = NULL; + + if (spawn_async_with_pipes(*working_directory ? working_directory : NULL, + *command_line ? command_line : NULL, argv, i ? envp : NULL, &pid, NULL, + NULL, NULL, &error)) + { + GMainLoop *loop = g_main_loop_new(NULL, TRUE); + + g_child_watch_add(pid, watch_cb, loop); + g_main_loop_run(loop); + g_main_loop_unref(loop); + } + else + { + fprintf(stderr, "error: %s\n", error->message); + g_error_free(error); + } + } + } + else if (!strcmp(test_type, "redirect") || !strcmp(test_type, "redinput")) + { + char command_line[0x100]; + gboolean output = test_type[4] == 'r'; + + while (read_line("command line: ", command_line, sizeof command_line)) + { + GString *stdin_text = read_string("text to send: "); + SpawnWriteData stdin_data; + GError *error = NULL; + + if (stdin_text) + { + stdin_data.ptr = stdin_text->str; + stdin_data.size = stdin_text->len; + } + + if (!spawn_with_callbacks(NULL, command_line, NULL, NULL, SPAWN_SYNC, + stdin_text ? (GIOFunc) spawn_write_data : NULL, &stdin_data, + output ? print_cb : NULL, "stdout", 0, output ? print_cb : NULL, + "stderr", 0, exit_cb, NULL, NULL, &error)) + { + fprintf(stderr, "error: %s\n", error->message); + g_error_free(error); + } + + if (stdin_text) + g_string_free(stdin_text, TRUE); + } + } + else if (!strcmp(test_type, "capture")) + { + char command_line[0x100]; + + while (read_line("command line: ", command_line, sizeof command_line)) + { + GString *stdin_text = read_string("text to send: "); + SpawnWriteData stdin_data = { NULL, 0 }; + GString *stdout_data = g_string_sized_new(0x10000); /* may grow */ + GString *stderr_data = g_string_sized_new(0x1000); /* may grow */ + gint exit_status; + GError *error = NULL; + + if (stdin_text) + { + stdin_data.ptr = stdin_text->str; + stdin_data.size = stdin_text->len; + } + + if (spawn_sync(NULL, command_line, NULL, NULL, &stdin_data, stdout_data, + stderr_data, &exit_status, &error)) + { + printf("stdout: %s\n", stdout_data->str); + printf("stderr: %s\n", stderr_data->str); + print_status(exit_status); + } + else + { + fprintf(stderr, "error: %s\n", error->message); + g_error_free(error); + } + + if (stdin_text) + g_string_free(stdin_text, TRUE); + + g_string_free(stdout_data, TRUE); + g_string_free(stderr_data, TRUE); + } + } + else + { + fprintf(stderr, "spawn: unknown test type '%s'", argv[1]); + return 1; + } + + return 0; +} +#endif /* SPAWN_TEST */
Modified: src/spawn.h 107 lines changed, 107 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,107 @@ +/* + * spawn.h - this file is part of Geany, a fast and lightweight IDE + * + * Copyright 2013 Dimitar Toshkov Zhekov <dimitar(dot)zhekov(at)gmail(dot)com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + + +#ifndef GEANY_SPAWN_H +#define GEANY_SPAWN_H 1 + +#include <glib.h> + +#ifdef G_OS_WIN32 +# define WIFEXITED(status) TRUE +# define WEXITSTATUS(status) (status) +# define WIFSIGNALLED(status) FALSE +#else +# include <sys/types.h> +# include <sys/wait.h> +#endif + +gchar *spawn_get_program_name(const gchar *command_line, GError **error); + +gboolean spawn_check_command(const gchar *command_line, gboolean execute, GError **error); + +gboolean spawn_kill_process(GPid pid, GError **error); + +gboolean spawn_async_with_pipes(const gchar *working_directory, const gchar *command_line, + gchar **argv, gchar **envp, GPid *child_pid, gint *stdin_fd, gint *stdout_fd, + gint *stderr_fd, GError **error); + +gboolean spawn_async(const gchar *working_directory, const gchar *command_line, gchar **argv, + gchar **envp, GPid *child_pid, GError **error); + +/** Flags passed to @c spawn_with_callbacks(), which see. */ +typedef enum +{ + SPAWN_ASYNC = 0x00, /**< Asynchronous execution [default]. */ + SPAWN_SYNC = 0x01, /**< Synchronous execution. */ + /* buffering modes */ + SPAWN_LINE_BUFFERED = 0x00, /**< stdout/stderr are line buffered [default]. */ + SPAWN_STDOUT_UNBUFFERED = 0x02, /**< stdout is not buffered. */ + SPAWN_STDERR_UNBUFFERED = 0x04, /**< stderr is not buffered. */ + SPAWN_UNBUFFERED = 0x06, /**< stdout/stderr are not buffered. */ + /* recursive modes */ + SPAWN_STDIN_RECURSIVE = 0x08, /**< The stdin callback is recursive. */ + SPAWN_STDOUT_RECURSIVE = 0x10, /**< The stdout callback is recursive. */ + SPAWN_STDERR_RECURSIVE = 0x20, /**< The stderr callback is recursive. */ + SPAWN_RECURSIVE = 0x38 /**< All callbacks are recursive. */ +} SpawnFlags; + +/** + * Specifies the type of function passed to @c spawn_with_callbacks() as stdout or stderr + * callback. + * + * In unbuffered mode, the @a string may contain nuls, while in line buffered mode, it may + * contain only a single nul as a line termination character at @a string->len - 1. In all + * cases, the @a string will be terminated with a nul character that is not part of the data + * at @a string->len. + * + * If @c G_IO_IN or @c G_IO_PRI are set, the @a string will contain at least one character. + * + * @param string contains the child data if @c G_IO_IN or @c G_IO_PRI are set. + * @param condition the I/O condition which has been satisfied. + * @param data the passed to @c spawn_with_callbacks() with the callback. + */ +typedef void (*SpawnReadFunc)(GString *string, GIOCondition condition, gpointer data); + +gboolean spawn_with_callbacks(const gchar *working_directory, const gchar *command_line, + gchar **argv, gchar **envp, SpawnFlags spawn_flags, GIOFunc stdin_cb, gpointer stdin_data, + SpawnReadFunc stdout_cb, gpointer stdout_data, gsize stdout_max_length, + SpawnReadFunc stderr_cb, gpointer stderr_data, gsize stderr_max_length, + GChildWatchFunc exit_cb, gpointer exit_data, GPid *child_pid, GError **error); + +/** + * A simple structure used by @c spawn_write_data() to write data to a channel. + * See @c spawn_write_data() for more information. + */ +typedef struct _SpawnWriteData +{ + const gchar *ptr; /**< Pointer to the data. May be NULL if the size is 0. */ + gsize size; /**< Size of the data. */ +} SpawnWriteData; + +gboolean spawn_write_data(GIOChannel *channel, GIOCondition condition, SpawnWriteData *data); + +void spawn_get_exit_status_cb(GPid pid, gint status, gpointer exit_status); + +gboolean spawn_sync(const gchar *working_directory, const gchar *command_line, gchar **argv, + gchar **envp, SpawnWriteData *stdin_data, GString *stdout_data, GString *stderr_data, + gint *exit_status, GError **error); + +#endif /* GEANY_SPAWN_H */
Modified: src/templates.c 44 lines changed, 22 insertions(+), 22 deletions(-) =================================================================== @@ -37,6 +37,7 @@ #include "geany.h" #include "geanymenubuttonaction.h" #include "geanyobject.h" +#include "spawn.h" #include "support.h" #include "toolbar.h" #include "ui_utils.h" @@ -595,33 +596,32 @@ static void templates_replace_default_dates(GString *text) static gchar *run_command(const gchar *command, const gchar *file_name, const gchar *file_type, const gchar *func_name) { + GString *output = g_string_new(NULL); gchar *result = NULL; - gchar **argv; + GError *error = NULL; + gchar **env;
- if (g_shell_parse_argv(command, NULL, &argv, NULL)) - { - GError *error = NULL; - gchar **env; + file_name = (file_name != NULL) ? file_name : ""; + file_type = (file_type != NULL) ? file_type : ""; + func_name = (func_name != NULL) ? func_name : "";
- file_name = (file_name != NULL) ? file_name : ""; - file_type = (file_type != NULL) ? file_type : ""; - func_name = (func_name != NULL) ? func_name : ""; + env = utils_copy_environment(NULL, + "GEANY_FILENAME", file_name, + "GEANY_FILETYPE", file_type, + "GEANY_FUNCNAME", func_name, + NULL);
- env = utils_copy_environment(NULL, - "GEANY_FILENAME", file_name, - "GEANY_FILETYPE", file_type, - "GEANY_FUNCNAME", func_name, - NULL); - if (! utils_spawn_sync(NULL, argv, env, G_SPAWN_SEARCH_PATH, - NULL, NULL, &result, NULL, NULL, &error)) - { - g_warning("templates_replace_command: %s", error->message); - g_error_free(error); - result = NULL; - } - g_strfreev(argv); - g_strfreev(env); + if (spawn_sync(NULL, command, NULL, env, NULL, output, NULL, NULL, &error)) + { + result = g_string_free(output, FALSE); } + else + { + g_warning("templates_replace_command: %s", error->message); + g_error_free(error); + } + + g_strfreev(env); return result; }
Modified: src/tools.c 264 lines changed, 36 insertions(+), 228 deletions(-) =================================================================== @@ -33,6 +33,7 @@ #include "document.h" #include "keybindings.h" #include "sciwrappers.h" +#include "spawn.h" #include "support.h" #include "ui_utils.h" #include "utils.h" @@ -45,12 +46,6 @@ #include <string.h> #include <errno.h>
-#ifdef G_OS_UNIX -# include <sys/types.h> -# include <sys/wait.h> -# include <signal.h> -#endif -
enum { @@ -76,26 +71,6 @@ struct cc_dialog GtkWidget *button_down; };
-/* data required by the custom command callbacks */ -struct cc_data -{ - const gchar *command; /* command launched */ - GeanyDocument *doc; /* document in which replace the selection */ - GString *buffer; /* buffer holding stdout content, or NULL */ - gboolean error; /* whether and error occurred */ - gboolean finished; /* whether the command has finished */ -}; - - -static gboolean cc_exists_command(const gchar *command) -{ - gchar *path = g_find_program_in_path(command); - - g_free(path); - - return path != NULL; -} -
/* update STATUS and TOOLTIP columns according to cmd */ static void cc_dialog_update_row_status(GtkListStore *store, GtkTreeIter *iter, const gchar *cmd) @@ -103,19 +78,9 @@ static void cc_dialog_update_row_status(GtkListStore *store, GtkTreeIter *iter, GError *err = NULL; const gchar *stock_id = GTK_STOCK_NO; gchar *tooltip = NULL; - gint argc; - gchar **argv;
- if (EMPTY(cmd)) + if (EMPTY(cmd) || spawn_check_command(cmd, TRUE, &err)) stock_id = GTK_STOCK_YES; - else if (g_shell_parse_argv(cmd, &argc, &argv, &err)) - { - if (argc > 0 && cc_exists_command(argv[0])) - stock_id = GTK_STOCK_YES; - else - tooltip = g_strdup_printf(_("Invalid command: %s"), _("Command not found")); - g_strfreev(argv); - } else { tooltip = g_strdup_printf(_("Invalid command: %s"), err->message); @@ -227,218 +192,61 @@ static void cc_on_dialog_move_down_clicked(GtkButton *button, struct cc_dialog * }
-static gboolean cc_iofunc(GIOChannel *ioc, GIOCondition cond, gpointer user_data) +/* Executes command (which should include all necessary command line args) and passes the current + * selection through the standard input of command. The whole output of command replaces the + * current selection. */ +void tools_execute_custom_command(GeanyDocument *doc, const gchar *command) { - struct cc_data *data = user_data; - - if (cond & (G_IO_IN | G_IO_PRI)) - { - gchar *msg = NULL; - GIOStatus rv; - GError *err = NULL; - - if (! data->buffer) - data->buffer = g_string_sized_new(256); - - do - { - rv = g_io_channel_read_line(ioc, &msg, NULL, NULL, &err); - if (msg != NULL) - { - g_string_append(data->buffer, msg); - g_free(msg); - } - if (G_UNLIKELY(err != NULL)) - { - geany_debug("%s: %s", G_STRFUNC, err->message); - g_error_free(err); - err = NULL; - } - } while (rv == G_IO_STATUS_NORMAL || rv == G_IO_STATUS_AGAIN); - - if (G_UNLIKELY(rv != G_IO_STATUS_EOF)) - { /* Something went wrong? */ - g_warning("%s: %s\n", G_STRFUNC, "Incomplete command output"); - } - } - return FALSE; -} - + GError *error = NULL; + gchar *sel; + SpawnWriteData input; + GString *output; + GString *errors; + gint status; + + g_return_if_fail(doc != NULL && command != NULL); + + if (! sci_has_selection(doc->editor->sci)) + editor_select_lines(doc->editor, FALSE);
-static gboolean cc_iofunc_err(GIOChannel *ioc, GIOCondition cond, gpointer user_data) -{ - struct cc_data *data = user_data; + sel = sci_get_selection_contents(doc->editor->sci); + input.ptr = sel; + input.size = strlen(sel); + output = g_string_sized_new(256); + errors = g_string_new(NULL); + ui_set_statusbar(TRUE, _("Passing data and executing custom command: %s"), command);
- if (cond & (G_IO_IN | G_IO_PRI)) + if (spawn_sync(NULL, command, NULL, NULL, &input, output, errors, &status, &error)) { - gchar *msg = NULL; - GString *str = g_string_sized_new(256); - GIOStatus rv; - - do - { - rv = g_io_channel_read_line(ioc, &msg, NULL, NULL, NULL); - if (msg != NULL) - { - g_string_append(str, msg); - g_free(msg); - } - } while (rv == G_IO_STATUS_NORMAL || rv == G_IO_STATUS_AGAIN); - - if (!EMPTY(str->str)) + if (errors->len > 0) { - g_warning("%s: %s\n", data->command, str->str); + g_warning("%s: %s\n", command, errors->str); ui_set_statusbar(TRUE, _("The executed custom command returned an error. " "Your selection was not changed. Error message: %s"), - str->str); - data->error = TRUE; - + errors->str); } - g_string_free(str, TRUE); - } - data->finished = TRUE; - return FALSE; -} - - -static gboolean cc_replace_sel_cb(gpointer user_data) -{ - struct cc_data *data = user_data; - - if (! data->finished) - { /* keep this function in the main loop until cc_iofunc_err() has finished */ - return TRUE; - } - - if (! data->error && data->buffer != NULL && DOC_VALID(data->doc)) - { /* Command completed successfully */ - sci_replace_sel(data->doc->editor->sci, data->buffer->str); - } - - if (data->buffer) - g_string_free(data->buffer, TRUE); - g_slice_free1(sizeof *data, data); - - return FALSE; -} - - -/* check whether the executed command failed and if so do nothing. - * If it returned with a successful exit code, replace the selection. */ -static void cc_exit_cb(GPid child_pid, gint status, gpointer user_data) -{ - struct cc_data *data = user_data; - - /* if there was already an error, skip further checks */ - if (! data->error) - { -#ifdef G_OS_UNIX - if (WIFEXITED(status)) + else if (!WIFEXITED(status) || WEXITSTATUS(status) != EXIT_SUCCESS) { - if (WEXITSTATUS(status) != EXIT_SUCCESS) - data->error = TRUE; - } - else if (WIFSIGNALED(status)) - { /* the terminating signal: WTERMSIG (status)); */ - data->error = TRUE; - } - else - { /* any other failure occurred */ - data->error = TRUE; - } -#else - data->error = ! win32_get_exit_status(child_pid); -#endif - - if (data->error) - { /* here we are sure data->error was set due to an unsuccessful exit code - * and so we add an error message */ /* TODO maybe include the exit code in the error message */ ui_set_statusbar(TRUE, _("The executed custom command exited with an unsuccessful exit code.")); } - } - - g_idle_add(cc_replace_sel_cb, data); - g_spawn_close_pid(child_pid); -} - - -/* Executes command (which should include all necessary command line args) and passes the current - * selection through the standard input of command. The whole output of command replaces the - * current selection. */ -void tools_execute_custom_command(GeanyDocument *doc, const gchar *command) -{ - GError *error = NULL; - GPid pid; - gchar **argv; - gint stdin_fd; - gint stdout_fd; - gint stderr_fd; - - g_return_if_fail(DOC_VALID(doc) && command != NULL); - - if (! sci_has_selection(doc->editor->sci)) - editor_select_lines(doc->editor, FALSE); - - if (!g_shell_parse_argv(command, NULL, &argv, &error)) - { - ui_set_statusbar(TRUE, _("Custom command failed: %s"), error->message); - g_error_free(error); - return; - } - ui_set_statusbar(TRUE, _("Passing data and executing custom command: %s"), command); - - if (g_spawn_async_with_pipes(NULL, argv, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, - NULL, NULL, &pid, &stdin_fd, &stdout_fd, &stderr_fd, &error)) - { - gchar *sel; - gint remaining, wrote; - struct cc_data *data = g_slice_alloc(sizeof *data); - - data->error = FALSE; - data->finished = FALSE; - data->buffer = NULL; - data->doc = doc; - data->command = command; - - g_child_watch_add(pid, cc_exit_cb, data); - - /* use GIOChannel to monitor stdout */ - utils_set_up_io_channel(stdout_fd, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL, - FALSE, cc_iofunc, data); - /* copy program's stderr to Geany's stdout to help error tracking */ - utils_set_up_io_channel(stderr_fd, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL, - FALSE, cc_iofunc_err, data); - - /* get selection */ - sel = sci_get_selection_contents(doc->editor->sci); - - /* write data to the command */ - remaining = strlen(sel); - do - { - wrote = write(stdin_fd, sel, remaining); - if (G_UNLIKELY(wrote < 0)) - { - g_warning("%s: %s: %s\n", G_STRFUNC, "Failed sending data to command", - g_strerror(errno)); - break; - } - remaining -= wrote; - } while (remaining > 0); - close(stdin_fd); - g_free(sel); + else + { /* Command completed successfully */ + sci_replace_sel(doc->editor->sci, output->str); + } } else { - geany_debug("g_spawn_async_with_pipes() failed: %s", error->message); + geany_debug("spawn_sync() failed: %s", error->message); ui_set_statusbar(TRUE, _("Custom command failed: %s"), error->message); g_error_free(error); }
- g_strfreev(argv); + g_string_free(output, TRUE); + g_string_free(errors, TRUE); + g_free(sel); }
Modified: src/utils.c 80 lines changed, 24 insertions(+), 56 deletions(-) =================================================================== @@ -34,6 +34,7 @@ #include "document.h" #include "prefs.h" #include "sciwrappers.h" +#include "spawn.h" #include "support.h" #include "templates.h" #include "ui_utils.h" @@ -76,29 +77,21 @@ void utils_open_browser(const gchar *uri) g_return_if_fail(uri != NULL); win32_open_browser(uri); #else - gboolean again = TRUE; + gchar *argv[2] = { (gchar *) uri, NULL };
g_return_if_fail(uri != NULL);
- while (again) + while (!spawn_async(NULL, tool_prefs.browser_cmd, argv, NULL, NULL, NULL)) { - gchar *cmdline = g_strconcat(tool_prefs.browser_cmd, " "", uri, """, NULL); + gchar *new_cmd = dialogs_show_input(_("Select Browser"), GTK_WINDOW(main_widgets.window), + _("Failed to spawn the configured browser command. " + "Please correct it or enter another one."), + tool_prefs.browser_cmd);
- if (g_spawn_command_line_async(cmdline, NULL)) - again = FALSE; - else - { - gchar *new_cmd = dialogs_show_input(_("Select Browser"), GTK_WINDOW(main_widgets.window), - _("Failed to spawn the configured browser command. " - "Please correct it or enter another one."), - tool_prefs.browser_cmd); + if (new_cmd == NULL) /* user canceled */ + break;
- if (new_cmd == NULL) /* user canceled */ - again = FALSE; - else - SETPTR(tool_prefs.browser_cmd, new_cmd); - } - g_free(cmdline); + SETPTR(tool_prefs.browser_cmd, new_cmd); } #endif } @@ -1651,15 +1644,14 @@ const gchar *utils_get_default_dir_utf8(void)
/** - * Wraps g_spawn_sync() and internally calls this function on Unix-like - * systems. On Win32 platforms, it uses the Windows API. + * Wraps @c spawn_sync(), which see. * * @param dir The child's current working directory, or @a NULL to inherit parent's. * @param argv The child's argument vector. * @param env The child's environment, or @a NULL to inherit parent's. - * @param flags Flags from GSpawnFlags. - * @param child_setup A function to run in the child just before exec(). - * @param user_data The user data for child_setup. + * @param flags Ignored. + * @param child_setup Ignored. + * @param user_data Ignored. * @param std_out The return location for child output, or @a NULL. * @param std_err The return location for child error messages, or @a NULL. * @param exit_status The child exit status, as returned by waitpid(), or @a NULL. @@ -1672,40 +1664,29 @@ gboolean utils_spawn_sync(const gchar *dir, gchar **argv, gchar **env, GSpawnFla GSpawnChildSetupFunc child_setup, gpointer user_data, gchar **std_out, gchar **std_err, gint *exit_status, GError **error) { - gboolean result; - - if (argv == NULL) - { - g_set_error(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, "argv must not be NULL"); - return FALSE; - } + GString *output = std_out ? g_string_new(NULL) : NULL; + GString *errors = std_err ? g_string_new(NULL) : NULL; + gboolean result = spawn_sync(dir, NULL, argv, env, NULL, output, errors, exit_status, error);
if (std_out) - *std_out = NULL; + *std_out = g_string_free(output, !result);
if (std_err) - *std_err = NULL; - -#ifdef G_OS_WIN32 - result = win32_spawn(dir, argv, env, flags, std_out, std_err, exit_status, error); -#else - result = g_spawn_sync(dir, argv, env, flags, NULL, NULL, std_out, std_err, exit_status, error); -#endif + *std_err = g_string_free(errors, !result);
return result; }
/** - * Wraps g_spawn_async() and internally calls this function on Unix-like - * systems. On Win32 platforms, it uses the Windows API. + * Wraps @c spawn_async(), which see. * * @param dir The child's current working directory, or @a NULL to inherit parent's. * @param argv The child's argument vector. * @param env The child's environment, or @a NULL to inherit parent's. - * @param flags Flags from GSpawnFlags. - * @param child_setup A function to run in the child just before exec(). - * @param user_data The user data for child_setup. + * @param flags Ignored. + * @param child_setup Ignored. + * @param user_data Ignored. * @param child_pid The return location for child process ID, or NULL. * @param error The return location for error or @a NULL. * @@ -1716,20 +1697,7 @@ gboolean utils_spawn_async(const gchar *dir, gchar **argv, gchar **env, GSpawnFl GSpawnChildSetupFunc child_setup, gpointer user_data, GPid *child_pid, GError **error) { - gboolean result; - - if (argv == NULL) - { - g_set_error(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, "argv must not be NULL"); - return FALSE; - } - -#ifdef G_OS_WIN32 - result = win32_spawn(dir, argv, env, flags, NULL, NULL, NULL, error); -#else - result = g_spawn_async(dir, argv, env, flags, NULL, NULL, child_pid, error); -#endif - return result; + return spawn_async(dir, NULL, argv, env, child_pid, error); }
Modified: src/win32.c 493 lines changed, 0 insertions(+), 493 deletions(-) =================================================================== @@ -57,31 +57,6 @@ #include <glib/gstdio.h> #include <gdk/gdkwin32.h>
-#define BUFSIZE 4096 -#define CMDSIZE 32768 - -struct _geany_win32_spawn -{ - HANDLE hChildStdinRd; - HANDLE hChildStdinWr; - HANDLE hChildStdoutRd; - HANDLE hChildStdoutWr; - HANDLE hChildStderrRd; - HANDLE hChildStderrWr; - HANDLE hInputFile; - HANDLE hStdout; - HANDLE hStderr; - HANDLE processId; - DWORD dwExitCode; -}; -typedef struct _geany_win32_spawn geany_win32_spawn; - -static gboolean GetContentFromHandle(HANDLE hFile, gchar **content, GError **error); -static HANDLE GetTempFileHandle(GError **error); -static gboolean CreateChildProcess(geany_win32_spawn *gw_spawn, TCHAR *szCmdline, - const TCHAR *dir, GError **error); -static VOID ReadFromPipe(HANDLE hRead, HANDLE hWrite, HANDLE hFile, GError **error); -
/* The timer handle used to refresh windows below modal native dialogs. If * ever more than one dialog can be shown at a time, this needs to be changed @@ -837,17 +812,6 @@ void win32_open_browser(const gchar *uri) }
-/* Returns TRUE if the command, which child_pid refers to, returned with a successful exit code, - * otherwise FALSE. */ -gboolean win32_get_exit_status(GPid child_pid) -{ - DWORD exit_code; - GetExitCodeProcess(child_pid, &exit_code); - - return (exit_code == 0); -} - - static FILE *open_std_handle(DWORD handle, const char *mode) { HANDLE lStdHandle; @@ -929,299 +893,7 @@ void win32_init_debug_code(void) /* create a console window to get log messages on Windows, * especially useful when generating tags files */ debug_setup_console(); - /* Enable GLib process spawn debug mode when Geany was started with the debug flag */ - g_setenv("G_SPAWN_WIN32_DEBUG", "1", FALSE); - } -} - - -static gchar *create_temp_file(void) -{ - gchar *name; - gint fd; - - fd = g_file_open_tmp("tmp_XXXXXX", &name, NULL); - if (fd == -1) - name = NULL; - else - close(fd); - - return name; -} - - -/* Sometimes this blocks for 30s before aborting when there are several - * pages of (error) output and sometimes hangs - see the FIXME. - * Also gw_spawn.dwExitCode seems to be not set properly. */ -/* Process spawning implementation for Windows, by Pierre Joye. - * Don't call this function directly, use utils_spawn_[a]sync() instead. */ -static -gboolean _broken_win32_spawn(const gchar *dir, gchar **argv, gchar **env, GSpawnFlags flags, - gchar **std_out, gchar **std_err, gint *exit_status, GError **error) -{ - TCHAR buffer[CMDSIZE]=TEXT(""); - TCHAR cmdline[CMDSIZE] = TEXT(""); - TCHAR* lpPart[CMDSIZE]={NULL}; - DWORD retval = 0; - gint argc = 0, i; - gint cmdpos = 0; - - SECURITY_ATTRIBUTES saAttr; - BOOL fSuccess; - geany_win32_spawn gw_spawn; - - /* Temp file */ - HANDLE hStdoutTempFile = NULL; - HANDLE hStderrTempFile = NULL; - - gchar *stdout_content = NULL; - gchar *stderr_content = NULL; - - while (argv[argc]) - { - ++argc; - } - g_return_val_if_fail (std_out == NULL || - !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE); - g_return_val_if_fail (std_err == NULL || - !(flags & G_SPAWN_STDERR_TO_DEV_NULL), FALSE); - - if (flags & G_SPAWN_SEARCH_PATH) - { - retval = SearchPath(NULL, argv[0], ".exe", sizeof(buffer), buffer, lpPart); - if (retval > 0) - g_snprintf(cmdline, sizeof(cmdline), ""%s"", buffer); - else - g_strlcpy(cmdline, argv[0], sizeof(cmdline)); - cmdpos = 1; - } - - for (i = cmdpos; i < argc; i++) - { - g_snprintf(cmdline, sizeof(cmdline), "%s %s", cmdline, argv[i]); - /*MessageBox(NULL, cmdline, cmdline, MB_OK);*/ - } - - if (std_err != NULL) - { - hStderrTempFile = GetTempFileHandle(error); - if (hStderrTempFile == INVALID_HANDLE_VALUE) - { - gchar *msg = g_win32_error_message(GetLastError()); - geany_debug("win32_spawn: Second CreateFile failed (%d)", (gint) GetLastError()); - g_set_error(error, G_SPAWN_ERROR, G_FILE_ERROR, "%s", msg); - g_free(msg); - return FALSE; - } - } - - if (std_out != NULL) - { - hStdoutTempFile = GetTempFileHandle(error); - if (hStdoutTempFile == INVALID_HANDLE_VALUE) - { - gchar *msg = g_win32_error_message(GetLastError()); - geany_debug("win32_spawn: Second CreateFile failed (%d)", (gint) GetLastError()); - g_set_error(error, G_SPAWN_ERROR, G_FILE_ERROR, "%s", msg); - g_free(msg); - return FALSE; - } - } - - /* Set the bInheritHandle flag so pipe handles are inherited. */ - saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); - saAttr.bInheritHandle = TRUE; - saAttr.lpSecurityDescriptor = NULL; - - /* Get the handle to the current STDOUT and STDERR. */ - gw_spawn.hStdout = GetStdHandle(STD_OUTPUT_HANDLE); - gw_spawn.hStderr = GetStdHandle(STD_ERROR_HANDLE); - gw_spawn.dwExitCode = 0; - - /* Create a pipe for the child process's STDOUT. */ - if (! CreatePipe(&(gw_spawn.hChildStdoutRd), &(gw_spawn.hChildStdoutWr), &saAttr, 0)) - { - gchar *msg = g_win32_error_message(GetLastError()); - geany_debug("win32_spawn: Stdout pipe creation failed (%d)", (gint) GetLastError()); - g_set_error(error, G_SPAWN_ERROR, G_FILE_ERROR_PIPE, "%s", msg); - g_free(msg); - return FALSE; - } - - /* Ensure that the read handle to the child process's pipe for STDOUT is not inherited.*/ - SetHandleInformation(gw_spawn.hChildStdoutRd, HANDLE_FLAG_INHERIT, 0); - - /* Create a pipe for the child process's STDERR. */ - if (! CreatePipe(&(gw_spawn.hChildStderrRd), &(gw_spawn.hChildStderrWr), &saAttr, 0)) - { - gchar *msg = g_win32_error_message(GetLastError()); - geany_debug("win32_spawn: Stderr pipe creation failed"); - g_set_error(error, G_SPAWN_ERROR, G_FILE_ERROR_PIPE, "%s", msg); - g_free(msg); - return FALSE; - } - - /* Ensure that the read handle to the child process's pipe for STDOUT is not inherited.*/ - SetHandleInformation(gw_spawn.hChildStderrRd, HANDLE_FLAG_INHERIT, 0); - - /* Create a pipe for the child process's STDIN. */ - if (! CreatePipe(&(gw_spawn.hChildStdinRd), &(gw_spawn.hChildStdinWr), &saAttr, 0)) - { - gchar *msg = g_win32_error_message(GetLastError()); - geany_debug("win32_spawn: Stdin pipe creation failed"); - g_set_error(error, G_SPAWN_ERROR, G_FILE_ERROR_PIPE, "%s", msg); - g_free(msg); - return FALSE; - } - - /* Ensure that the write handle to the child process's pipe for STDIN is not inherited. */ - SetHandleInformation(gw_spawn.hChildStdinWr, HANDLE_FLAG_INHERIT, 0); - - /* Now create the child process. */ - fSuccess = CreateChildProcess(&gw_spawn, cmdline, dir, error); - if (exit_status) - { - *exit_status = gw_spawn.dwExitCode; - } - - if (! fSuccess) - { - geany_debug("win32_spawn: Create process failed"); - return FALSE; - } - - /* Read from pipe that is the standard output for child process. */ - if (std_out != NULL) - { - ReadFromPipe(gw_spawn.hChildStdoutRd, gw_spawn.hChildStdoutWr, hStdoutTempFile, error); - if (! GetContentFromHandle(hStdoutTempFile, &stdout_content, error)) - { - return FALSE; - } - *std_out = stdout_content; - } - - if (std_err != NULL) - { - ReadFromPipe(gw_spawn.hChildStderrRd, gw_spawn.hChildStderrWr, hStderrTempFile, error); - if (! GetContentFromHandle(hStderrTempFile, &stderr_content, error)) - { - return FALSE; - } - *std_err = stderr_content; - } - return TRUE; -} - - -/* Note: g_spawn is broken for receiving both stdio and stderr e.g. when - * running make and there are compile errors. See glib/giowin32.c header - * comment about Windows bugs, e.g. #338943 */ -/* Simple replacement for _broken_win32_spawn(). - * flags is ignored, G_SPAWN_SEARCH_PATH is implied. - * Don't call this function directly, use utils_spawn_[a]sync() instead. - * Adapted from tm_workspace_create_global_tags(). */ -gboolean win32_spawn(const gchar *dir, gchar **argv, gchar **env, GSpawnFlags flags, - gchar **std_out, gchar **std_err, gint *exit_status, GError **error) -{ - gint ret; - gboolean fail; - gchar *tmp_file = create_temp_file(); - gchar *tmp_errfile = create_temp_file(); - gchar *command; - gchar *locale_command; - - if (env != NULL) - { - return _broken_win32_spawn(dir, argv, env, flags, std_out, std_err, - exit_status, error); - } - if (!tmp_file || !tmp_errfile) - { - g_warning("%s: Could not create temporary files!", G_STRFUNC); - return FALSE; - } - command = g_strjoinv(" ", argv); - SETPTR(command, g_strdup_printf("cmd.exe /S /C "%s >%s 2>%s"", - command, tmp_file, tmp_errfile)); - locale_command = g_locale_from_utf8(command, -1, NULL, NULL, NULL); - if (! locale_command) - locale_command = g_strdup(command); - geany_debug("WIN32: actually running command:\n%s", command); - g_chdir(dir); - errno = 0; - ret = system(locale_command); - /* the command can return -1 as an exit code, so check errno also */ - fail = ret == -1 && errno; - if (!fail) - { - if (std_out != NULL) - g_file_get_contents(tmp_file, std_out, NULL, NULL); - if (std_err != NULL) - g_file_get_contents(tmp_errfile, std_err, NULL, NULL); - } - else if (error) - g_set_error_literal(error, G_SPAWN_ERROR, errno, g_strerror(errno)); - - g_free(command); - g_free(locale_command); - g_unlink(tmp_file); - g_free(tmp_file); - g_unlink(tmp_errfile); - g_free(tmp_errfile); - if (exit_status) - *exit_status = ret; - - return !fail; -} - - -static gboolean GetContentFromHandle(HANDLE hFile, gchar **content, GError **error) -{ - DWORD filesize; - gchar * buffer; - DWORD dwRead; - - filesize = GetFileSize(hFile, NULL); - if (filesize < 1) - { - *content = NULL; - return TRUE; - } - - buffer = g_malloc(filesize + 1); - if (! buffer) - { - gchar *msg = g_win32_error_message(GetLastError()); - geany_debug("GetContentFromHandle: Alloc failed"); - g_set_error(error, G_SPAWN_ERROR, G_SPAWN_ERROR, "%s", msg); - g_free(msg); - return FALSE; - } - - SetFilePointer(hFile, 0, NULL, FILE_BEGIN); - if (! ReadFile(hFile, buffer, filesize, &dwRead, NULL) || dwRead == 0) - { - gchar *msg = g_win32_error_message(GetLastError()); - geany_debug("GetContentFromHandle: Cannot read tempfile"); - g_set_error(error, G_SPAWN_ERROR, G_FILE_ERROR_FAILED, "%s", msg); - g_free(msg); - return FALSE; - } - - if (! CloseHandle(hFile)) - { - gchar *msg = g_win32_error_message(GetLastError()); - geany_debug("GetContentFromHandle: CloseHandle failed (%d)", (gint) GetLastError()); - g_set_error(error, G_SPAWN_ERROR, G_FILE_ERROR_FAILED, "%s", msg); - g_free(msg); - g_free(buffer); - *content = NULL; - return FALSE; } - buffer[filesize] = '\0'; - *content = buffer; - return TRUE; }
@@ -1236,171 +908,6 @@ gchar *win32_expand_environment_variables(const gchar *str) }
-static gboolean CreateChildProcess(geany_win32_spawn *gw_spawn, TCHAR *szCmdline, - const TCHAR *dir, GError **error) -{ - PROCESS_INFORMATION piProcInfo; - STARTUPINFOW siStartInfo; - BOOL bFuncRetn = FALSE; - gchar *expandedCmdline; - wchar_t w_commandline[CMDSIZE]; - wchar_t w_dir[MAX_PATH]; - - /* Set up members of the PROCESS_INFORMATION structure. */ - ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); - - /* Set up members of the STARTUPINFO structure.*/ - ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); - - siStartInfo.cb = sizeof(STARTUPINFO); - siStartInfo.hStdError = gw_spawn->hChildStderrWr; - siStartInfo.hStdOutput = gw_spawn->hChildStdoutWr; - siStartInfo.hStdInput = gw_spawn->hChildStdinRd; - siStartInfo.dwFlags |= STARTF_USESTDHANDLES; - - /* Expand environment variables like %blah%. */ - expandedCmdline = win32_expand_environment_variables(szCmdline); - - MultiByteToWideChar(CP_UTF8, 0, expandedCmdline, -1, w_commandline, G_N_ELEMENTS(w_commandline)); - MultiByteToWideChar(CP_UTF8, 0, dir, -1, w_dir, G_N_ELEMENTS(w_dir)); - - /* Create the child process. */ - bFuncRetn = CreateProcessW(NULL, - w_commandline, /* command line */ - NULL, /* process sec@@ Diff output truncated at 100000 characters. @@
-------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).