process: resolve paths for win32

When using `git_process_new` on win32, resolve the path to the
application in the same way that we do on POSIX.

Search `PATH` for command to execute (unless the given executable is
fully qualified). In addition, better match Windows executable lookup
behavior itself (allowing the command to be `foo`, and looking for a
matching `foo.exe` or `foo.cmd`.)
This commit is contained in:
Edward Thomson
2025-12-01 00:55:36 -08:00
parent 98ba974617
commit 2ad709e77c
3 changed files with 138 additions and 41 deletions

View File

@@ -153,6 +153,7 @@ int git_win32_path_canonicalize(git_win32_path path)
static int git_win32_path_join(
git_win32_path dest,
size_t *dest_len,
const wchar_t *one,
size_t one_len,
const wchar_t *two,
@@ -176,6 +177,9 @@ static int git_win32_path_join(
memcpy(dest + one_len + backslash, two, two_len * sizeof(wchar_t));
dest[one_len + backslash + two_len] = L'\0';
if (dest_len)
*dest_len = one_len + backslash + two_len;
return 0;
}
@@ -258,21 +262,12 @@ struct executable_suffix {
size_t len;
};
int git_win32_path_find_executable(git_win32_path fullpath, wchar_t *exe)
static struct executable_suffix suffixes[] = { { NULL, 0 }, { L".exe", 4 }, { L".cmd", 4 } };
static bool has_executable_suffix(wchar_t *exe, size_t exe_len)
{
struct win32_path_iter path_iter;
const wchar_t *dir;
size_t dir_len, exe_len = wcslen(exe);
bool found = false;
static struct executable_suffix suffixes[] = { { NULL, 0 }, { L".exe", 4 }, { L".cmd", 4 } };
size_t skip_bare = 1, i;
size_t i;
if (win32_path_iter_init(&path_iter) < 0)
return -1;
/* see if the given executable has an executable suffix; if so we will
* look for the explicit name directly, as well as with added suffixes.
*/
for (i = 0; i < ARRAY_SIZE(suffixes); i++) {
struct executable_suffix *suffix = &suffixes[i];
@@ -282,40 +277,80 @@ int git_win32_path_find_executable(git_win32_path fullpath, wchar_t *exe)
if (exe_len < suffix->len)
continue;
if (memcmp(&exe[exe_len - suffix->len], suffix->suffix, suffix->len) == 0) {
skip_bare = 0;
break;
}
if (memcmp(&exe[exe_len - suffix->len], suffix->suffix, suffix->len) == 0)
return true;
}
while (win32_path_iter_next(&dir, &dir_len, &path_iter) != GIT_ITEROVER && !found) {
/*
* if the given name has an executable suffix, then try looking for it
* directly. in all cases, append executable extensions
* (".exe", ".cmd"...)
*/
for (i = skip_bare; i < ARRAY_SIZE(suffixes); i++) {
struct executable_suffix *suffix = &suffixes[i];
return false;
}
if (git_win32_path_join(fullpath, dir, dir_len, exe, exe_len) < 0)
static int is_executable(git_win32_path path, size_t path_len, bool suffixed)
{
size_t i;
/*
* if the given name has an executable suffix, then try looking for it
* directly. in all cases, append executable extensions
* (".exe", ".cmd"...)
*/
for (i = suffixed ? 0 : 1; i < ARRAY_SIZE(suffixes); i++) {
struct executable_suffix *suffix = &suffixes[i];
if (suffix->len) {
if (path_len + suffix->len > MAX_PATH)
continue;
if (suffix->len) {
if (dir_len + exe_len + 1 + suffix->len > MAX_PATH)
continue;
wcscat(path, suffix->suffix);
}
wcscat(fullpath, suffix->suffix);
}
if (_waccess(path, 0) == 0)
return true;
if (_waccess(fullpath, 0) == 0) {
found = true;
break;
}
path[path_len] = L'\0';
}
return false;
}
int git_win32_path_find_executable(git_win32_path fullpath, wchar_t *exe)
{
struct win32_path_iter path_iter;
const wchar_t *dir;
size_t dir_len, exe_len, fullpath_len;
bool suffixed = false, found = false;
if ((exe_len = wcslen(exe)) > MAX_PATH)
goto done;
/* see if the given executable has an executable suffix; if so we will
* look for the explicit name directly, as well as with added suffixes.
*/
suffixed = has_executable_suffix(exe, exe_len);
/* For fully-qualified paths we do not look in PATH */
if (wcschr(exe, L'\\') != NULL || wcschr(exe, L'/') != NULL) {
if ((found = is_executable(exe, exe_len, suffixed)))
wcscpy(fullpath, exe);
goto done;
}
if (win32_path_iter_init(&path_iter) < 0)
return -1;
while (win32_path_iter_next(&dir, &dir_len, &path_iter) != GIT_ITEROVER && !found) {
if (git_win32_path_join(fullpath, &fullpath_len, dir, dir_len, exe, exe_len) < 0)
continue;
if (is_executable(fullpath, fullpath_len, suffixed)) {
found = true;
break;
}
}
win32_path_iter_dispose(&path_iter);
done:
if (found)
return 0;

View File

@@ -11,6 +11,7 @@
#include "git2_util.h"
#include "process.h"
#include "strlist.h"
#include "fs_path.h"
#ifndef DWORD_MAX
# define DWORD_MAX INT32_MAX
@@ -19,7 +20,8 @@
#define ENV_MAX 32767
struct git_process {
wchar_t *appname;
char *app_name;
wchar_t *app_path;
wchar_t *cmdline;
wchar_t *env;
@@ -178,8 +180,7 @@ static int process_new(
process = git__calloc(1, sizeof(git_process));
GIT_ERROR_CHECK_ALLOC(process);
if (appname &&
git_utf8_to_16_alloc(&process->appname, appname) < 0) {
if (appname && (process->app_name = git__strdup(appname)) == NULL) {
error = -1;
goto done;
}
@@ -233,7 +234,7 @@ int git_process_new(
size_t env_len,
git_process_options *opts)
{
git_str cmdline = GIT_STR_INIT;
git_str cmd_path = GIT_STR_INIT, cmdline = GIT_STR_INIT;
int error;
GIT_ASSERT_ARG(out && args && args_len > 0);
@@ -244,6 +245,7 @@ int git_process_new(
error = process_new(out, args[0], cmdline.ptr, env, env_len, opts);
done:
git_str_dispose(&cmd_path);
git_str_dispose(&cmdline);
return error;
}
@@ -259,6 +261,19 @@ int git_process_start(git_process *process)
out[2] = { NULL, NULL },
err[2] = { NULL, NULL };
if (process->app_name) {
git_str cmd_path = GIT_STR_INIT;
int error;
if ((error = git_fs_path_find_executable(&cmd_path, process->app_name)) == 0)
error = git_utf8_to_16_alloc(&process->app_path, cmd_path.ptr);
git_str_dispose(&cmd_path);
if (error < 0)
goto on_error;
}
memset(&security_attrs, 0, sizeof(SECURITY_ATTRIBUTES));
security_attrs.bInheritHandle = TRUE;
@@ -303,7 +318,7 @@ int git_process_start(git_process *process)
memset(&process->process_info, 0, sizeof(PROCESS_INFORMATION));
if (!CreateProcessW(process->appname, process->cmdline,
if (!CreateProcessW(process->app_path, process->cmdline,
NULL, NULL, TRUE, flags, process->env,
process->cwd,
&startup_info,
@@ -501,6 +516,7 @@ void git_process_free(git_process *process)
git__free(process->env);
git__free(process->cwd);
git__free(process->cmdline);
git__free(process->appname);
git__free(process->app_path);
git__free(process->app_name);
git__free(process);
}

View File

@@ -78,6 +78,52 @@ void test_process_start__not_found(void)
git_process_free(process);
}
void test_process_start__finds_in_path(void)
{
#ifdef GIT_WIN32
const char *args_array[] = { "cmd", "/c", "exit", "0" };
#else
const char *args_array[] = { "true" };
#endif
git_process *process;
git_process_options opts = GIT_PROCESS_OPTIONS_INIT;
git_process_result result = GIT_PROCESS_RESULT_INIT;
cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts));
cl_git_pass(git_process_start(process));
cl_git_pass(git_process_wait(&result, process));
cl_assert_equal_i(GIT_PROCESS_STATUS_NORMAL, result.status);
cl_assert_equal_i(0, result.exitcode);
cl_assert_equal_i(0, result.signal);
git_process_free(process);
}
void test_process_start__adds_suffix(void)
{
#ifdef GIT_WIN32
const char *args_array[] = { "C:\\Windows\\System32\\cmd", "/c", "exit", "0" };
git_process *process;
git_process_options opts = GIT_PROCESS_OPTIONS_INIT;
git_process_result result = GIT_PROCESS_RESULT_INIT;
cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts));
cl_git_pass(git_process_start(process));
cl_git_pass(git_process_wait(&result, process));
cl_assert_equal_i(GIT_PROCESS_STATUS_NORMAL, result.status);
cl_assert_equal_i(0, result.exitcode);
cl_assert_equal_i(0, result.signal);
git_process_free(process);
#else
cl_skip();
#endif
}
static void write_all(git_process *process, char *buf)
{
size_t buf_len = strlen(buf);