diff options
author | Edward Thomson <ethomson@edwardthomson.com> | 2022-01-15 20:08:10 +0000 |
---|---|---|
committer | Edward Thomson <ethomson@edwardthomson.com> | 2022-01-15 19:33:24 -0500 |
commit | f7fcf6c07bb5c0e104cb0ba8c70858b5cba4c846 (patch) | |
tree | edbb0d21c05e410253a2aa87067821e8f89320e2 | |
parent | 7c804ab4f183df581b4480b85727b1c661f4dff1 (diff) | |
download | libgit2-ethomson/find_executable.tar.gz |
path: introduce git_fs_path_find_executableethomson/find_executable
Provide a helper function to find an executable in the current process's
PATH.
-rw-r--r-- | src/fs_path.c | 56 | ||||
-rw-r--r-- | src/fs_path.h | 6 | ||||
-rw-r--r-- | src/win32/path_w32.c | 136 | ||||
-rw-r--r-- | src/win32/path_w32.h | 2 | ||||
-rw-r--r-- | tests/core/path.c | 84 |
5 files changed, 284 insertions, 0 deletions
diff --git a/src/fs_path.c b/src/fs_path.c index 957f389cc..9aa8213dc 100644 --- a/src/fs_path.c +++ b/src/fs_path.c @@ -1851,3 +1851,59 @@ cleanup: return ret; #endif } + +int git_fs_path_find_executable(git_str *fullpath, const char *executable) +{ +#ifdef GIT_WIN32 + git_win32_path fullpath_w, executable_w; + int error; + + if (git__utf8_to_16(executable_w, GIT_WIN_PATH_MAX, executable) < 0) + return -1; + + error = git_win32_path_find_executable(fullpath_w, executable_w); + + if (error == 0) + error = git_str_put_w(fullpath, fullpath_w, wcslen(fullpath_w)); + + return error; +#else + git_str path = GIT_STR_INIT; + const char *current_dir, *term; + bool found = false; + + if (git__getenv(&path, "PATH") < 0) + return -1; + + current_dir = path.ptr; + + while (*current_dir) { + if (! (term = strchr(current_dir, GIT_PATH_LIST_SEPARATOR))) + term = strchr(current_dir, '\0'); + + git_str_clear(fullpath); + if (git_str_put(fullpath, current_dir, (term - current_dir)) < 0 || + git_str_putc(fullpath, '/') < 0 || + git_str_puts(fullpath, executable) < 0) + return -1; + + if (git_fs_path_isfile(fullpath->ptr)) { + found = true; + break; + } + + current_dir = term; + + while (*current_dir == GIT_PATH_LIST_SEPARATOR) + current_dir++; + } + + git_str_dispose(&path); + + if (found) + return 0; + + git_str_clear(fullpath); + return GIT_ENOTFOUND; +#endif +} diff --git a/src/fs_path.h b/src/fs_path.h index 9720d34ce..222c44abc 100644 --- a/src/fs_path.h +++ b/src/fs_path.h @@ -743,4 +743,10 @@ bool git_fs_path_supports_symlinks(const char *dir); */ int git_fs_path_validate_system_file_ownership(const char *path); +/** + * Search the current PATH for the given executable, returning the full + * path if it is found. + */ +int git_fs_path_find_executable(git_str *fullpath, const char *executable); + #endif diff --git a/src/win32/path_w32.c b/src/win32/path_w32.c index 5d7ad11f6..b79f2f328 100644 --- a/src/win32/path_w32.c +++ b/src/win32/path_w32.c @@ -151,6 +151,142 @@ int git_win32_path_canonicalize(git_win32_path path) return (int)(to - path); } +static int git_win32_path_join_n( + git_win32_path dest, + wchar_t *one, + size_t one_len, + wchar_t *two, + size_t two_len) +{ + size_t backslash = 0; + + if (one_len && two_len && one[one_len - 1] != L'\\') + backslash = 1; + + if (one_len + two_len + backslash > MAX_PATH) { + git_error_set(GIT_ERROR_INVALID, "path too long"); + return -1; + } + + memmove(dest, one, one_len * sizeof(wchar_t)); + + if (backslash) + dest[one_len] = L'\\'; + + memcpy(dest + one_len + backslash, two, two_len * sizeof(wchar_t)); + dest[one_len + backslash + two_len] = L'\0'; + + return 0; +} + +static int git_win32_path_join(git_win32_path dest, wchar_t *one, wchar_t *two) +{ + return git_win32_path_join_n(dest, one, wcslen(one), two, wcslen(two)); +} + +struct win32_path_iter { + wchar_t *env; + const wchar_t *current_dir; +}; + +static int win32_path_iter_init(struct win32_path_iter *iter) +{ + DWORD len = GetEnvironmentVariableW(L"PATH", NULL, 0); + + if (len < 0 && GetLastError() == ERROR_ENVVAR_NOT_FOUND) { + iter->env = NULL; + iter->current_dir = NULL; + return 0; + } else if (len <= 0) { + git_error_set(GIT_ERROR_OS, "could not load PATH"); + return -1; + } + + iter->env = git__malloc(len * sizeof(wchar_t)); + GIT_ERROR_CHECK_ALLOC(iter->env); + + len = GetEnvironmentVariableW(L"PATH", iter->env, len); + + if (len < 0) { + git_error_set(GIT_ERROR_OS, "could not load PATH"); + return -1; + } + + iter->current_dir = iter->env; + return 0; +} + +static int win32_path_iter_next( + const wchar_t **out, + size_t *out_len, + struct win32_path_iter *iter) +{ + const wchar_t *start; + wchar_t term; + size_t len = 0; + + if (!iter->current_dir || !*iter->current_dir) + return GIT_ITEROVER; + + term = (*iter->current_dir == L'"') ? *iter->current_dir++ : L';'; + start = iter->current_dir; + + while (*iter->current_dir && *iter->current_dir != term) { + *iter->current_dir++; + len++; + } + + *out = start; + *out_len = len; + + if (term == L'"' && *iter->current_dir) + *iter->current_dir++; + + while (*iter->current_dir == L';') + *iter->current_dir++; + + return 0; +} + +static void win32_path_iter_dispose(struct win32_path_iter *iter) +{ + if (!iter) + return; + + git__free(iter->env); + iter->env = NULL; + iter->current_dir = NULL; +} + +int git_win32_path_find_executable(git_win32_path fullpath, wchar_t *exe) +{ + struct win32_path_iter path_iter; + wchar_t *dir; + size_t dir_len, exe_len = wcslen(exe); + bool found = false; + + 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_n(fullpath, dir, dir_len, exe, exe_len) < 0) + continue; + + if (_waccess(fullpath, 0) == 0) { + found = true; + break; + } + } + + win32_path_iter_dispose(&path_iter); + + if (found) + return 0; + + fullpath[0] = L'\0'; + return GIT_ENOTFOUND; +} + static int win32_path_cwd(wchar_t *out, size_t len) { int cwd_len; diff --git a/src/win32/path_w32.h b/src/win32/path_w32.h index 4fadf8d08..837b11ebd 100644 --- a/src/win32/path_w32.h +++ b/src/win32/path_w32.h @@ -86,4 +86,6 @@ size_t git_win32_path_trim_end(wchar_t *str, size_t len); */ size_t git_win32_path_remove_namespace(wchar_t *str, size_t len); +int git_win32_path_find_executable(git_win32_path fullpath, wchar_t* exe); + #endif diff --git a/tests/core/path.c b/tests/core/path.c index 563dcd2a3..e6260c866 100644 --- a/tests/core/path.c +++ b/tests/core/path.c @@ -2,6 +2,20 @@ #include "futils.h" #include "fs_path.h" +static char *path_save; + +void test_core_path__initialize(void) +{ + path_save = cl_getenv("PATH"); +} + +void test_core_path__cleanup(void) +{ + cl_setenv("PATH", path_save); + git__free(path_save); + path_save = NULL; +} + static void check_dirname(const char *A, const char *B) { @@ -60,6 +74,20 @@ check_joinpath_n( git_str_dispose(&joined_path); } +static void check_setenv(const char* name, const char* value) +{ + char* check; + + cl_git_pass(cl_setenv(name, value)); + check = cl_getenv(name); + + if (value) + cl_assert_equal_s(value, check); + else + cl_assert(check == NULL); + + git__free(check); +} /* get the dirname of a path */ void test_core_path__00_dirname(void) @@ -651,3 +679,59 @@ void test_core_path__16_resolve_relative(void) assert_common_dirlen(6, "a/b/c/foo.txt", "a/b/c/d/e/bar.txt"); assert_common_dirlen(7, "/a/b/c/foo.txt", "/a/b/c/d/e/bar.txt"); } + +static void fix_path(git_str* s) +{ +#ifndef GIT_WIN32 + GIT_UNUSED(s); +#else + char* c; + + for (c = s->ptr; *c; c++) { + if (*c == '/') + *c = '\\'; + } +#endif +} + +void test_core_path__find_exe_in_path(void) +{ + char* orig_path; + git_str sandbox_path = GIT_STR_INIT; + git_str new_path = GIT_STR_INIT, full_path = GIT_STR_INIT, dummy_path = GIT_STR_INIT; + +#ifdef GIT_WIN32 + static const char* bogus_path_1 = "c:\\does\\not\\exist\\"; + static const char* bogus_path_2 = "e:\\non\\existent"; +#else + static const char *bogus_path_1 = "/this/path/does/not/exist/"; + static const char *bogus_path_2 = "/non/existent"; +#endif + + orig_path = cl_getenv("PATH"); + + git_str_puts(&sandbox_path, clar_sandbox_path()); + git_str_joinpath(&dummy_path, sandbox_path.ptr, "dummmmmmmy_libgit2_file"); + cl_git_rewritefile(dummy_path.ptr, "this is a dummy file"); + + fix_path(&sandbox_path); + fix_path(&dummy_path); + + cl_git_pass(git_str_printf(&new_path, "%s%c%s%c%s%c%s", + bogus_path_1, GIT_PATH_LIST_SEPARATOR, + orig_path, GIT_PATH_LIST_SEPARATOR, + sandbox_path.ptr, GIT_PATH_LIST_SEPARATOR, + bogus_path_2)); + + check_setenv("PATH", new_path.ptr); + + cl_git_fail_with(GIT_ENOTFOUND, git_fs_path_find_executable(&full_path, "this_file_does_not_exist")); + cl_git_pass(git_fs_path_find_executable(&full_path, "dummmmmmmy_libgit2_file")); + + cl_assert_equal_s(full_path.ptr, dummy_path.ptr); + + git_str_dispose(&full_path); + git_str_dispose(&new_path); + git_str_dispose(&sandbox_path); + git__free(orig_path); +} |