Fix process spawning with static build on Windows

On Windows, process spawning needs an external helper exe which is found
relatively to the glib DLL file. If glib has been built statically this
file doesn't exist anymore and reference path is not the DLL path
anymore but the current executable path.

This patch searches for the helper exe taking as starting point the
current executable path, relative 'bin', 'lib', 'glib' and 'gio' folders
and then gets one level up until the root path. If this search doesn't
give result then the helper exe is searched using the PATH variable.
This commit is contained in:
Loic Le Page
2022-01-05 18:06:01 +01:00
parent 94f4ec85c6
commit 41f8bbd02d
6 changed files with 130 additions and 54 deletions

View File

@@ -1336,4 +1336,123 @@ g_crash_handler_win32_deinit (void)
WinVEH_handle = NULL;
}
/**
* g_win32_find_helper_executable_path:
* @executable_name: (transfer none): name of the helper executable to find
* (something like gspawn-win64-helper.exe or gdbus.exe for example).
* @dll_handle: handle of the DLL to use as searching base path. Pass NULL
* to take current process executable as searching base path.
*
* Find an external executable path and name starting in the same folder
* as a specified DLL or current process executable path. Helper executables
* (like gspawn-win64-helper.exe, gspawn-win64-helper-console.exe or
* gdbus.exe for example) are generally installed in the same folder as the
* corresponding DLL file.
*
* So, if package has been correctly installed, with a dynamic build of GLib,
* the helper executable should be in the same directory as the corresponding
* DLL file and searching should be straightforward.
*
* But if built statically, DLL handle is not available and we have to start
* searching from the directory holding current executable. It may be very
* different from the directory containing the helper program. In order to
* find the right helper program automatically in all common situations, we
* use this pattern:
*
* current directory
* |-- ???
* |-- bin
* | |-- ???
* |-- lib
* | |-- ???
* |-- glib
* | |-- ???
* |-- gio
* |-- ???
*
* starting at base searching path (DLL or current executable directory) and
* getting up until the root path. If we cannot still find the helper program,
* we'll rely on PATH as the last resort.
*
* Returns: (transfer full) (type filename) (nullable): the helper executable
* path and name in the GLib filename encoding or NULL in case of error. It
* should be deallocated with g_free().
*/
gchar *
g_win32_find_helper_executable_path (const gchar *executable_name, void *dll_handle)
{
static const gchar *const subdirs[] = { "", "bin", "lib", "glib", "gio" };
static const gsize nb_subdirs = G_N_ELEMENTS (subdirs);
DWORD module_path_len;
wchar_t module_path[MAX_PATH + 2] = { 0 };
gchar *base_searching_path;
gchar *p;
gchar *executable_path;
gsize i;
g_return_val_if_fail (executable_name && *executable_name, NULL);
module_path_len = GetModuleFileNameW (dll_handle, module_path, MAX_PATH + 1);
/* The > MAX_PATH check prevents truncated module path usage */
if (module_path_len == 0 || module_path_len > MAX_PATH)
return NULL;
base_searching_path = g_utf16_to_utf8 (module_path, -1, NULL, NULL, NULL);
if (base_searching_path == NULL)
return NULL;
p = strrchr (base_searching_path, G_DIR_SEPARATOR);
if (p == NULL)
{
g_free (base_searching_path);
return NULL;
}
*p = '\0';
for (;;)
{
/* Search in subdirectories */
for (i = 0; i < nb_subdirs; ++i)
{
/* As this function is exclusively used on Windows, the
* executable_path is always an absolute path. At worse, when
* reaching the root of the filesystem, base_searching_path may
* equal something like "[Drive letter]:" but never "/" like on
* Linux or Mac.
* For the peace of mind we still assert this, just in case that
* one day someone tries to use this function on Linux or Mac.
*/
executable_path = g_build_filename (base_searching_path, subdirs[i], executable_name, NULL);
g_assert (g_path_is_absolute (executable_path));
if (g_file_test (executable_path, G_FILE_TEST_IS_REGULAR))
break;
g_free (executable_path);
executable_path = NULL;
}
if (executable_path != NULL)
break;
/* Let's get one directory level up */
p = strrchr (base_searching_path, G_DIR_SEPARATOR);
if (p == NULL)
break;
*p = '\0';
}
g_free (base_searching_path);
if (executable_path == NULL)
{
/* Search in system PATH */
executable_path = g_find_program_in_path (executable_name);
if (executable_path == NULL)
executable_path = g_strdup (executable_name);
}
return executable_path;
}
#endif