[geany/geany] 5f8791: Merge pull request #1095 from eht16/issue1076_win32_build_working_dir_locale

Colomban Wendling git-noreply at geany.org
Sun Nov 13 15:29:43 UTC 2016


Branch:      refs/heads/master
Author:      Colomban Wendling <ban at herbesfolles.org>
Committer:   Colomban Wendling <ban at herbesfolles.org>
Date:        Sun, 13 Nov 2016 15:29:43 UTC
Commit:      5f879126cecd443d2e1a4be4eacaf8ac1d43ffa8
             https://github.com/geany/geany/commit/5f879126cecd443d2e1a4be4eacaf8ac1d43ffa8

Log Message:
-----------
Merge pull request #1095 from eht16/issue1076_win32_build_working_dir_locale

Windows: Improve executing external commands on non-ASCII paths,
effectively fixing most known issues.


Modified Paths:
--------------
    geany.nsi.in
    src/Makefile.am
    src/build.c
    src/geany-run-helper.bat
    src/prefix.h
    src/spawn.c
    src/utils.c
    src/utils.h
    src/win32.c

Modified: geany.nsi.in
4 lines changed, 4 insertions(+), 0 deletions(-)
===================================================================
@@ -137,6 +137,9 @@ Section "!Program Files" SEC01
 	File "gtk\bin\libstdc++-*.dll"
 	File "gtk\bin\libwinpthread*.dll"
 
+	SetOutPath "$INSTDIR\libexec"
+	File /r "${RESOURCEDIR}\libexec\*"
+
 	SetOutPath "$INSTDIR\data"
 	File "${RESOURCEDIR}\data\GPL-2"
 	File "${RESOURCEDIR}\data\filetype_extensions.conf"
@@ -311,6 +314,7 @@ Section Uninstall
 	RMDir /r "$INSTDIR\etc"
 	RMDir /r "$INSTDIR\include"
 	RMDir /r "$INSTDIR\lib"
+	RMDir /r "$INSTDIR\libexec"
 	RMDir /r "$INSTDIR\share"
 	RMDir "$INSTDIR"
 


Modified: src/Makefile.am
7 lines changed, 7 insertions(+), 0 deletions(-)
===================================================================
@@ -138,6 +138,7 @@ AM_CPPFLAGS += \
 	-DGEANY_DATADIR=\"data\" \
 	-DGEANY_DOCDIR=\"\" \
 	-DGEANY_LIBDIR=\"\" \
+	-DGEANY_LIBEXECDIR=\"\" \
 	-DGEANY_LOCALEDIR=\"\" \
 	-DGEANY_PREFIX=\"\"
 
@@ -162,6 +163,7 @@ AM_CPPFLAGS += \
 	-DGEANY_DATADIR=\""$(datadir)"\" \
 	-DGEANY_DOCDIR=\""$(docdir)"\" \
 	-DGEANY_LIBDIR=\""$(libdir)"\" \
+	-DGEANY_LIBEXECDIR=\""$(libexecdir)"\" \
 	-DGEANY_LOCALEDIR=\""$(localedir)"\" \
 	-DGEANY_PREFIX=\""$(prefix)"\"
 
@@ -182,6 +184,11 @@ signallist.i: $(glade_file) Makefile
 
 CLEANFILES += signallist.i
 
+# install the run script
+if MINGW
+dist_pkglibexec_SCRIPTS = geany-run-helper.bat
+endif
+
 # Ubuntu ld has a bug so that libtool sees /usr/local/lib as a system path so
 # doesn't add RPATH, but ld requires explicit ldconfig there, unlike when
 # installing in /usr/lib.  So, workaround this by calling it explicitly when


Modified: src/build.c
55 lines changed, 38 insertions(+), 17 deletions(-)
===================================================================
@@ -79,9 +79,7 @@ typedef struct RunInfo
 
 static RunInfo *run_info;
 
-#ifdef G_OS_WIN32
-static const gchar RUN_SCRIPT_CMD[] = "geany_run_script_XXXXXX.bat";
-#else
+#ifndef G_OS_WIN32
 static const gchar RUN_SCRIPT_CMD[] = "geany_run_script_XXXXXX.sh";
 #endif
 
@@ -115,7 +113,9 @@ static guint build_items_count = 9;
 
 static void build_exit_cb(GPid pid, gint status, gpointer user_data);
 static void build_iofunc(GString *string, GIOCondition condition, gpointer data);
+#ifndef G_OS_WIN32
 static gchar *build_create_shellscript(const gchar *working_dir, const gchar *cmd, gboolean autoclose, GError **error);
+#endif
 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);
@@ -750,13 +750,15 @@ static void build_spawn_cmd(GeanyDocument *doc, const gchar *cmd, const gchar *d
 	msgwin_compiler_add(COLOR_BLUE, _("%s (in directory: %s)"), cmd, utf8_working_dir);
 	g_free(utf8_working_dir);
 
+#ifdef G_OS_UNIX
 	cmd_string = utils_get_locale_from_utf8(cmd);
 	argv[2] = cmd_string;
-
-#ifdef G_OS_UNIX
 	cmd = NULL;  /* under Unix, use argv to start cmd via sh for compatibility */
 #else
+	/* Expand environment variables like %blah%. */
+	cmd_string = win32_expand_environment_variables(cmd);
 	argv[0] = NULL;  /* under Windows, run cmd directly */
+	cmd = cmd_string;
 #endif
 
 	/* set the build info for the message window */
@@ -826,6 +828,17 @@ static gchar *prepare_run_cmd(GeanyDocument *doc, gchar **working_dir, guint cmd
 	}
 #endif
 
+#ifdef G_OS_WIN32
+	/* Expand environment variables like %blah%. */
+	SETPTR(cmd_string, win32_expand_environment_variables(cmd_string));
+
+	gchar *helper = g_build_filename(utils_resource_dir(RESOURCE_DIR_LIBEXEC), "geany-run-helper", NULL);
+	/* escape helper appropriately */
+	/* FIXME: check the Windows rules, but it should not matter too much here as \es and "es are not
+	 * allowed in paths anyway */
+	run_cmd = g_strdup_printf("\"%s\" \"%s\" %d %s", helper, *working_dir, autoclose ? 1 : 0, cmd_string);
+	g_free(helper);
+#else
 	run_cmd = build_create_shellscript(*working_dir, cmd_string, autoclose, &error);
 	if (!run_cmd)
 	{
@@ -834,7 +847,7 @@ static gchar *prepare_run_cmd(GeanyDocument *doc, gchar **working_dir, guint cmd
 		g_error_free(error);
 		g_free(*working_dir);
 	}
-
+#endif
 	utils_free_pointers(3, cmd_string_utf8, working_dir_utf8, cmd_string, NULL);
 	return run_cmd;
 }
@@ -892,6 +905,21 @@ static void build_run_cmd(GeanyDocument *doc, guint cmdindex)
 		gchar *locale_term_cmd = utils_get_locale_from_utf8(tool_prefs.term_cmd);
 		GError *error = NULL;
 
+#ifdef G_OS_WIN32
+		if (g_regex_match_simple("^[ \"]*cmd([.]exe)?[\" ]", locale_term_cmd, 0, 0))
+		{
+			/* if passing an argument to cmd.exe, respect its quoting rules */
+			GString *escaped_run_cmd = g_string_new(NULL);
+			for (gchar *p = run_cmd; *p; p++)
+			{
+				if (strchr("()%!^\"<>&| ", *p)) // cmd.exe metacharacters
+					g_string_append_c(escaped_run_cmd, '^');
+				g_string_append_c(escaped_run_cmd, *p);
+			}
+			SETPTR(run_cmd, g_string_free(escaped_run_cmd, FALSE));
+		}
+#endif
+
 		utils_str_replace_all(&locale_term_cmd, "%c", run_cmd);
 
 		if (spawn_async(working_dir, locale_term_cmd, NULL, NULL, &(run_info[cmdindex].pid),
@@ -908,7 +936,9 @@ static void build_run_cmd(GeanyDocument *doc, guint cmdindex)
 				"Check the Terminal setting in Preferences"), utf8_term_cmd, error->message);
 			g_free(utf8_term_cmd);
 			g_error_free(error);
+#ifndef G_OS_WIN32
 			g_unlink(run_cmd);
+#endif
 			run_info[cmdindex].pid = (GPid) 0;
 		}
 	}
@@ -1063,35 +1093,25 @@ static void run_exit_cb(GPid child_pid, gint status, gpointer user_data)
 /* write a little shellscript to call the executable (similar to anjuta_launcher but "internal")
  * working_dir and cmd are both in the locale encoding
  * it returns the full file name (including path) of the created script in the locale encoding */
+#ifndef G_OS_WIN32
 static gchar *build_create_shellscript(const gchar *working_dir, const gchar *cmd, gboolean autoclose, GError **error)
 {
 	gint fd;
 	gchar *str, *fname;
 	gboolean success = TRUE;
-#ifdef G_OS_WIN32
-	gchar *expanded_cmd;
-#else
 	gchar *escaped_dir;
-#endif
 	fd = g_file_open_tmp (RUN_SCRIPT_CMD, &fname, error);
 	if (fd < 0)
 		return NULL;
 	close(fd);
 
-#ifdef G_OS_WIN32
-	/* Expand environment variables like %blah%. */
-	expanded_cmd = win32_expand_environment_variables(cmd);
-	str = g_strdup_printf("cd \"%s\"\n\n%s\n\n%s\ndel \"%%0\"\n\npause\n", working_dir, expanded_cmd, (autoclose) ? "" : "pause");
-	g_free(expanded_cmd);
-#else
 	escaped_dir = g_shell_quote(working_dir);
 	str = g_strdup_printf(
 		"#!/bin/sh\n\nrm $0\n\ncd %s\n\n%s\n\necho \"\n\n------------------\n(program exited with code: $?)\" \
 		\n\n%s\n", escaped_dir, cmd, (autoclose) ? "" :
 		"\necho \"Press return to continue\"\n#to be more compatible with shells like "
 			"dash\ndummy_var=\"\"\nread dummy_var");
 	g_free(escaped_dir);
-#endif
 
 	if (!g_file_set_contents(fname, str, -1, error))
 		success = FALSE;
@@ -1119,6 +1139,7 @@ static gchar *build_create_shellscript(const gchar *working_dir, const gchar *cm
 
 	return fname;
 }
+#endif
 
 
 typedef void Callback(GtkWidget *w, gpointer u);


Modified: src/geany-run-helper.bat
30 lines changed, 30 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,30 @@
+REM USAGE: geany-run-helper DIRECTORY AUTOCLOSE COMMAND...
+
+REM unnecessary, but we get the directory
+cd %1
+shift
+REM save autoclose option and remove it
+set autoclose=%1
+shift
+
+REM spawn the child
+REM it's tricky because shift doesn't affect %*, so hack it out
+REM https://en.wikibooks.org/wiki/Windows_Batch_Scripting#Command-line_arguments
+set SPAWN=
+:argloop
+if -%1-==-- goto argloop_end
+	set SPAWN=%SPAWN% %1
+	shift
+goto argloop
+:argloop_end
+%SPAWN%
+
+REM show the result
+echo:
+echo:
+echo:------------------
+echo:(program exited with code: %ERRORLEVEL%)
+echo:
+
+REM and if wanted, wait on the user
+if not %autoclose%==1 pause


Modified: src/prefix.h
1 lines changed, 1 insertions(+), 0 deletions(-)
===================================================================
@@ -81,6 +81,7 @@ G_BEGIN_DECLS
 	#define GEANY_PREFIX		(br_thread_local_store (br_locate_prefix ((void *) "")))
 	#define GEANY_DATADIR		(br_thread_local_store (br_prepend_prefix ((void *) "", "/share")))
 	#define GEANY_LIBDIR		(br_thread_local_store (br_prepend_prefix ((void *) "", "/lib")))
+	#define GEANY_LIBEXECDIR	(br_thread_local_store (br_prepend_prefix ((void *) "", "/libexec/geany")))
 	#define GEANY_DOCDIR		(br_thread_local_store (br_prepend_prefix ((void *) "", "/share/doc/geany")))
 	#define GEANY_LOCALEDIR		(br_thread_local_store (br_prepend_prefix ((void *) "", "/share/locale")))
 #endif /* BR_NO_MACROS */


Modified: src/spawn.c
132 lines changed, 112 insertions(+), 20 deletions(-)
===================================================================
@@ -307,11 +307,11 @@ gboolean spawn_kill_process(GPid pid, GError **error)
 
 
 #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)
+static gchar *spawn_create_process_with_pipes(wchar_t *w_command_line, const wchar_t *w_working_directory,
+	void *w_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;
+	STARTUPINFOW startup;
 	PROCESS_INFORMATION process;
 	HANDLE hpipe[6] = { NULL, NULL, NULL, NULL, NULL, NULL };
 	int *fd[3] = { stdin_fd, stdout_fd, stderr_fd };
@@ -372,8 +372,9 @@ static gchar *spawn_create_process_with_pipes(char *command_line, const char *wo
 	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))
+	if (!CreateProcessW(NULL, w_command_line, NULL, NULL, TRUE,
+		CREATE_UNICODE_ENVIRONMENT | (pipe_io ? CREATE_NO_WINDOW : 0),
+		w_environment, w_working_directory, &startup, &process))
 	{
 		failed = "";  /* report the message only */
 		/* further errors will not be reported */
@@ -521,8 +522,10 @@ static gboolean spawn_async_with_pipes(const gchar *working_directory, const gch
 
 #ifdef G_OS_WIN32
 	GString *command;
-	GArray *environment;
-	gchar *failure;
+	GArray *w_environment;
+	wchar_t *w_working_directory = NULL;
+	wchar_t *w_command = NULL;
+	gboolean success = TRUE;
 
 	if (command_line)
 	{
@@ -553,7 +556,7 @@ static gboolean spawn_async_with_pipes(const gchar *working_directory, const gch
 	else
 		command = g_string_new(NULL);
 
-	environment = g_array_new(TRUE, FALSE, sizeof(char));
+	w_environment = g_array_new(TRUE, FALSE, sizeof(wchar_t));
 
 	while (argv && *argv)
 		spawn_append_argument(command, *argv++);
@@ -562,26 +565,115 @@ static gboolean spawn_async_with_pipes(const gchar *working_directory, const gch
 	g_message("full spawn command line: %s", command->str);
 #endif
 
-	while (envp && *envp)
+	while (envp && *envp && success)
 	{
-		g_array_append_vals(environment, *envp, strlen(*envp) + 1);
+		glong w_entry_len;
+		wchar_t *w_entry;
+		gchar *tmp = NULL;
+
+		// FIXME: remove this and rely on UTF-8 input
+		if (! g_utf8_validate(*envp, -1, NULL))
+		{
+			tmp = g_locale_to_utf8(*envp, -1, NULL, NULL, NULL);
+			if (tmp)
+				*envp = tmp;
+		}
+		/* TODO: better error message */
+		w_entry = g_utf8_to_utf16(*envp, -1, NULL, &w_entry_len, error);
+
+		if (! w_entry)
+			success = FALSE;
+		else
+		{
+			/* copy the entry, including NUL terminator */
+			g_array_append_vals(w_environment, w_entry, w_entry_len + 1);
+			g_free(w_entry);
+		}
+
+		g_free(tmp);
 		envp++;
 	}
 
-	failure = spawn_create_process_with_pipes(command->str, working_directory,
-		envp ? environment->data : NULL, child_pid, stdin_fd, stdout_fd, stderr_fd);
+	/* convert working directory into locale encoding */
+	if (success && working_directory)
+	{
+		GError *gerror = NULL;
+		const gchar *utf8_working_directory;
+		gchar *tmp = NULL;
 
-	g_string_free(command, TRUE);
-	g_array_free(environment, TRUE);
+		// FIXME: remove this and rely on UTF-8 input
+		if (! g_utf8_validate(working_directory, -1, NULL))
+		{
+			tmp = g_locale_to_utf8(working_directory, -1, NULL, NULL, NULL);
+			if (tmp)
+				utf8_working_directory = tmp;
+		}
+		else
+			utf8_working_directory = working_directory;
 
-	if (failure)
+		w_working_directory = g_utf8_to_utf16(utf8_working_directory, -1, NULL, NULL, &gerror);
+		if (! w_working_directory)
+		{
+			/* TODO use the code below post-1.28 as it introduces a new string
+			g_set_error(error, gerror->domain, gerror->code,
+				_("Failed to convert working directory into locale encoding: %s"), gerror->message);
+			*/
+			g_propagate_error(error, gerror);
+			success = FALSE;
+		}
+		g_free(tmp);
+	}
+	/* convert command into locale encoding */
+	if (success)
 	{
-		g_set_error_literal(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, failure);
-		g_free(failure);
-		return FALSE;
+		GError *gerror = NULL;
+		const gchar *utf8_cmd;
+		gchar *tmp = NULL;
+
+		// FIXME: remove this and rely on UTF-8 input
+		if (! g_utf8_validate(command->str, -1, NULL))
+		{
+			tmp = g_locale_to_utf8(command->str, -1, NULL, NULL, NULL);
+			if (tmp)
+				utf8_cmd = tmp;
+		}
+		else
+			utf8_cmd = command->str;
+
+		w_command = g_utf8_to_utf16(utf8_cmd, -1, NULL, NULL, &gerror);
+		if (! w_command)
+		{
+			/* TODO use the code below post-1.28 as it introduces a new string
+			g_set_error(error, gerror->domain, gerror->code,
+				_("Failed to convert command into locale encoding: %s"), gerror->message);
+			*/
+			g_propagate_error(error, gerror);
+			success = FALSE;
+		}
 	}
 
-	return TRUE;
+	if (success)
+	{
+		gchar *failure;
+
+		failure = spawn_create_process_with_pipes(w_command, w_working_directory,
+			envp ? w_environment->data : NULL, child_pid, stdin_fd, stdout_fd, stderr_fd);
+
+		if (failure)
+		{
+			g_set_error_literal(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, failure);
+			g_free(failure);
+		}
+
+		success = failure == NULL;
+	}
+
+	g_string_free(command, TRUE);
+	g_array_free(w_environment, TRUE);
+	g_free(w_working_directory);
+	g_free(w_command);
+
+	return success;
 #else  /* G_OS_WIN32 */
 	int cl_argc;
 	char **full_argv;
@@ -655,7 +747,7 @@ static gboolean spawn_async_with_pipes(const gchar *working_directory, const gch
 		#ifdef ENFILE
 			case G_SPAWN_ERROR_NFILE : en = ENFILE; break;
 		#endif
-		#ifdef EMFILE 
+		#ifdef EMFILE
 			case G_SPAWN_ERROR_MFILE : en = EMFILE; break;
 		#endif
 		#ifdef EINVAL


Modified: src/utils.c
3 lines changed, 3 insertions(+), 0 deletions(-)
===================================================================
@@ -2103,6 +2103,7 @@ const gchar *utils_resource_dir(GeanyResourceDirType type)
 		resdirs[RESOURCE_DIR_DOC] = g_build_filename(prefix, "share", "doc", "geany", "html", NULL);
 		resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(prefix, "share", "locale", NULL);
 		resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(prefix, "lib", "geany", NULL);
+		resdirs[RESOURCE_DIR_LIBEXEC] = g_build_filename(prefix, "libexec", "geany", NULL);
 		g_free(prefix);
 #else
 		if (is_osx_bundle())
@@ -2115,6 +2116,7 @@ const gchar *utils_resource_dir(GeanyResourceDirType type)
 			resdirs[RESOURCE_DIR_DOC] = g_build_filename(prefix, "share", "doc", "geany", "html", NULL);
 			resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(prefix, "share", "locale", NULL);
 			resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(prefix, "lib", "geany", NULL);
+			resdirs[RESOURCE_DIR_LIBEXEC] = g_build_filename(prefix, "libexec", "geany", NULL);
 			g_free(prefix);
 # endif
 		}
@@ -2125,6 +2127,7 @@ const gchar *utils_resource_dir(GeanyResourceDirType type)
 			resdirs[RESOURCE_DIR_DOC] = g_build_filename(GEANY_DOCDIR, "html", NULL);
 			resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(GEANY_LOCALEDIR, NULL);
 			resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(GEANY_LIBDIR, "geany", NULL);
+			resdirs[RESOURCE_DIR_LIBEXEC] = g_build_filename(GEANY_LIBEXECDIR, "geany", NULL);
 		}
 #endif
 	}


Modified: src/utils.h
1 lines changed, 1 insertions(+), 0 deletions(-)
===================================================================
@@ -221,6 +221,7 @@ typedef enum
 	RESOURCE_DIR_DOC,
 	RESOURCE_DIR_LOCALE,
 	RESOURCE_DIR_PLUGIN,
+	RESOURCE_DIR_LIBEXEC,
 
 	RESOURCE_DIR_COUNT
 } GeanyResourceDirType;


Modified: src/win32.c
15 lines changed, 10 insertions(+), 5 deletions(-)
===================================================================
@@ -891,14 +891,19 @@ void win32_init_debug_code(void)
 }
 
 
+/* expands environment placeholders in @str.  input and output is in UTF-8 */
 gchar *win32_expand_environment_variables(const gchar *str)
 {
-	gchar expCmdline[32768]; /* 32768 is the limit for ExpandEnvironmentStrings() */
+	wchar_t *cmdline = g_utf8_to_utf16(str, -1, NULL, NULL, NULL);
+	wchar_t expCmdline[32768]; /* 32768 is the limit for ExpandEnvironmentStrings() */
+	gchar *expanded = NULL;
 
-	if (ExpandEnvironmentStrings((LPCTSTR) str, (LPTSTR) expCmdline, sizeof(expCmdline)) != 0)
-		return g_strdup(expCmdline);
-	else
-		return g_strdup(str);
+	if (cmdline && ExpandEnvironmentStringsW(cmdline, expCmdline, sizeof(expCmdline)) != 0)
+		expanded = g_utf16_to_utf8(expCmdline, -1, NULL, NULL, NULL);
+
+	g_free(cmdline);
+
+	return expanded ? expanded : g_strdup(str);
 }
 
 



--------------
This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).


More information about the Commits mailing list