From 911d091866333508c4a658ef100f2dbb156d9ead Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Tue, 21 Feb 2023 14:48:45 +0100 Subject: [PATCH] Work around an UCRT issue with _wspawn() functions taking an environment block argument We were working around that with a call to chdir("."). Internally chdir() sets up cmd shell-related environment variables, but only if the current working directory belongs to a volume with a drive letter. For example, if the current working directory is on a UNC volume like a network share, the call to chdir() won't help. Reference: https://developercommunity.visualstudio.com/t/UCRT-Crash-in-_wspawne-functions/10262748 --- glib/gspawn-win32.c | 90 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 9 deletions(-) diff --git a/glib/gspawn-win32.c b/glib/gspawn-win32.c index e8bc10e9a..f14911b1b 100644 --- a/glib/gspawn-win32.c +++ b/glib/gspawn-win32.c @@ -65,8 +65,10 @@ #include #ifdef _MSC_VER +#ifdef HAVE_VCRUNTIME_H #include /* for _UCRT */ #endif +#endif #ifndef GSPAWN_HELPER #ifdef G_SPAWN_WIN32_DEBUG @@ -163,8 +165,85 @@ safe_wspawnvpe (int _Mode, #else -#define safe_wspawnve _wspawnve -#define safe_wspawnvpe _wspawnvpe +/**< private > + * ensure_cmd_environment: + * + * Workaround for an issue in the universal C Runtime library (UCRT). This adds + * a custom environment variable to this process's environment block that looks + * like the cmd.exe's shell-related environment variables, i.e the name starts + * with an equal sign character: '='. This is needed because the UCRT may crash + * if those environment variables are missing from the calling process's block. + * + * Reference: + * + * https://developercommunity.visualstudio.com/t/UCRT-Crash-in-_wspawne-functions/10262748 + */ +static void +ensure_cmd_environment (void) +{ + static gsize initialization_value = 0; + + if (g_once_init_enter (&initialization_value)) + { + wchar_t *block = GetEnvironmentStringsW (); + gboolean have_cmd_environment = FALSE; + + if (block) + { + const wchar_t *p = block; + + while (*p != L'\0') + { + if (*p == L'=') + { + have_cmd_environment = TRUE; + break; + } + + p += wcslen (p) + 1; + } + + if (!FreeEnvironmentStringsW (block)) + g_warning ("%s failed with error code %u", + "FreeEnvironmentStrings", + (guint) GetLastError ()); + } + + if (!have_cmd_environment) + { + if (!SetEnvironmentVariableW (L"=GLIB", L"GLIB")) + { + g_critical ("%s failed with error code %u", + "SetEnvironmentVariable", + (guint) GetLastError ()); + } + } + + g_once_init_leave (&initialization_value, 1); + } +} + +static intptr_t +safe_wspawnve (int _mode, + const wchar_t * _filename, + const wchar_t *const *_args, + const wchar_t *const *_env) +{ + ensure_cmd_environment (); + + return _wspawnve (_mode, _filename, _args, _env);; +} + +static intptr_t +safe_wspawnvpe (int _mode, + const wchar_t * _filename, + const wchar_t *const *_args, + const wchar_t *const *_env) +{ + ensure_cmd_environment (); + + return _wspawnvpe (_mode, _filename, _args, _env); +} #endif /* _UCRT */ @@ -686,13 +765,6 @@ fork_exec (gint *exit_status, argc = protect_argv (argv, &protected_argv); - /* - * FIXME: Workaround broken spawnvpe functions that SEGV when "=X:=" - * environment variables are missing. Calling chdir() will set the magic - * environment variable again. - */ - _chdir ("."); - if (stdin_fd == -1 && stdout_fd == -1 && stderr_fd == -1 && (flags & G_SPAWN_CHILD_INHERITS_STDIN) && !(flags & G_SPAWN_STDOUT_TO_DEV_NULL) &&