fs: improve executable search on Windows

Ensure that when we look for an executable on Windows that we add
executable suffixes (`.exe`, `.cmd`). Without this, we would not support
looking for (eg) `ssh`, since we actually need to identify a file named
`ssh.exe` (or `ssh.cmd`) in `PATH`.
This commit is contained in:
Edward Thomson
2025-10-30 14:13:27 -07:00
parent 0f20ba29c1
commit 4d1e950326
2 changed files with 87 additions and 15 deletions

View File

@@ -611,6 +611,23 @@ bool git_fs_path_isfile(const char *path)
return S_ISREG(st.st_mode) != 0;
}
#ifdef GIT_WIN32
bool git_fs_path_isexecutable(const char *path)
{
struct stat st;
GIT_ASSERT_ARG_WITH_RETVAL(path, false);
if (git__suffixcmp_icase(path, ".exe") != 0 &&
git__suffixcmp_icase(path, ".cmd") != 0)
return false;
return (p_stat(path, &st) == 0);
}
#else
bool git_fs_path_isexecutable(const char *path)
{
struct stat st;
@@ -623,6 +640,8 @@ bool git_fs_path_isexecutable(const char *path)
((st.st_mode & S_IXUSR) != 0);
}
#endif
bool git_fs_path_islink(const char *path)
{
struct stat st;
@@ -2032,9 +2051,10 @@ int git_fs_path_owner_is_system(bool *out, const char *path)
return git_fs_path_owner_is(out, path, GIT_FS_PATH_OWNER_ADMINISTRATOR);
}
int git_fs_path_find_executable(git_str *fullpath, const char *executable)
{
#ifdef GIT_WIN32
static int find_executable(git_str *fullpath, const char *executable)
{
git_win32_path fullpath_w, executable_w;
int error;
@@ -2047,20 +2067,17 @@ int git_fs_path_find_executable(git_str *fullpath, const char *executable)
error = git_str_put_w(fullpath, fullpath_w, wcslen(fullpath_w));
return error;
}
#else
static int find_executable(git_str *fullpath, const char *executable)
{
git_str path = GIT_STR_INIT;
const char *current_dir, *term;
size_t current_dirlen;
bool found = false;
/* For qualified paths we do not look in PATH */
if (strchr(executable, '/') != NULL) {
if (!git_fs_path_isexecutable(executable))
return GIT_ENOTFOUND;
return git_str_puts(fullpath, executable);
}
if (git__getenv(&path, "PATH") < 0)
return -1;
@@ -2102,5 +2119,19 @@ int git_fs_path_find_executable(git_str *fullpath, const char *executable)
git_str_clear(fullpath);
return GIT_ENOTFOUND;
#endif
}
#endif
int git_fs_path_find_executable(git_str *fullpath, const char *executable)
{
/* For qualified paths we do not look in PATH */
if (strchr(executable, '/') != NULL) {
if (!git_fs_path_isexecutable(executable))
return GIT_ENOTFOUND;
return git_str_puts(fullpath, executable);
}
return find_executable(fullpath, executable);
}

View File

@@ -253,26 +253,67 @@ static void win32_path_iter_dispose(struct win32_path_iter *iter)
iter->current_dir = NULL;
}
struct executable_suffix {
const wchar_t *suffix;
size_t len;
};
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 = wcslen(exe);
bool found = false;
static struct executable_suffix suffixes[] = { { NULL, 0 }, { L".exe", 4 }, { L".cmd", 4 } };
size_t skip_bare = 1, i;
if (win32_path_iter_init(&path_iter) < 0)
return -1;
while (win32_path_iter_next(&dir, &dir_len, &path_iter) != GIT_ITEROVER) {
if (git_win32_path_join(fullpath, dir, dir_len, exe, exe_len) < 0)
/* 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];
if (!suffix->len)
continue;
if (_waccess(fullpath, 0) == 0) {
found = true;
if (exe_len < suffix->len)
continue;
if (memcmp(&exe[exe_len - suffix->len], suffix->suffix, suffix->len) == 0) {
skip_bare = 0;
break;
}
}
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];
if (git_win32_path_join(fullpath, dir, dir_len, exe, exe_len) < 0)
continue;
if (suffix->len) {
if (dir_len + exe_len + 1 + suffix->len > MAX_PATH)
continue;
wcscat(fullpath, suffix->suffix);
}
if (_waccess(fullpath, 0) == 0) {
found = true;
break;
}
}
}
win32_path_iter_dispose(&path_iter);
if (found)