mirror of
https://github.com/libgit2/libgit2.git
synced 2026-01-25 02:56:17 +00:00
Merge pull request #7163 from libgit2/ethomson/ssh_exec_updates
SSH exec updates
This commit is contained in:
@@ -120,7 +120,8 @@ GIT_INLINE(int) ensure_transport_state(
|
||||
}
|
||||
|
||||
static int get_ssh_cmdline(
|
||||
git_str *out,
|
||||
git_vector *args,
|
||||
bool *use_shell,
|
||||
ssh_exec_subtransport *transport,
|
||||
git_net_url *url,
|
||||
const char *command)
|
||||
@@ -128,7 +129,8 @@ static int get_ssh_cmdline(
|
||||
git_remote *remote = ((transport_smart *)transport->owner)->owner;
|
||||
git_repository *repo = remote->repo;
|
||||
git_config *cfg;
|
||||
git_str ssh_cmd = GIT_STR_INIT;
|
||||
git_str ssh_cmd = GIT_STR_INIT, url_and_host = GIT_STR_INIT,
|
||||
remote_cmd = GIT_STR_INIT;
|
||||
const char *default_ssh_cmd = "ssh";
|
||||
int error;
|
||||
|
||||
@@ -151,25 +153,57 @@ static int get_ssh_cmdline(
|
||||
if ((error = git_repository_config_snapshot(&cfg, repo)) < 0)
|
||||
return error;
|
||||
|
||||
if ((error = git__getenv(&ssh_cmd, "GIT_SSH")) == 0)
|
||||
;
|
||||
if ((error = git__getenv(&ssh_cmd, "GIT_SSH_COMMAND")) == 0)
|
||||
*use_shell = true;
|
||||
else if (error != GIT_ENOTFOUND)
|
||||
goto done;
|
||||
else if ((error = git__getenv(&ssh_cmd, "GIT_SSH")) == 0)
|
||||
*use_shell = false;
|
||||
else if (error != GIT_ENOTFOUND)
|
||||
goto done;
|
||||
else if ((error = git_config__get_string_buf(&ssh_cmd, cfg, "core.sshcommand")) < 0 && error != GIT_ENOTFOUND)
|
||||
goto done;
|
||||
|
||||
error = git_str_printf(out, "%s %s %s \"%s%s%s\" \"%s '%s'\"",
|
||||
ssh_cmd.size > 0 ? ssh_cmd.ptr : default_ssh_cmd,
|
||||
url->port_specified ? "-p" : "",
|
||||
url->port_specified ? url->port : "",
|
||||
url->username ? url->username : "",
|
||||
url->username ? "@" : "",
|
||||
url->host,
|
||||
command,
|
||||
url->path);
|
||||
git_error_clear();
|
||||
|
||||
if (!ssh_cmd.size &&
|
||||
git_str_puts(&ssh_cmd, default_ssh_cmd) < 0)
|
||||
goto done;
|
||||
|
||||
if ((error = git_vector_insert(args, git_str_detach(&ssh_cmd))) < 0)
|
||||
goto done;
|
||||
|
||||
if (url->port_specified) {
|
||||
char *p = git__strdup("-p");
|
||||
char *port = git__strdup(url->port);
|
||||
|
||||
if (!p || !port ||
|
||||
(error = git_vector_insert(args, p)) < 0 ||
|
||||
(error = git_vector_insert(args, port)) < 0)
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (url->username) {
|
||||
if ((error = git_str_puts(&url_and_host, url->username)) < 0 ||
|
||||
(error = git_str_putc(&url_and_host, '@')) < 0)
|
||||
goto done;
|
||||
}
|
||||
|
||||
if ((error = git_str_puts(&url_and_host, url->host)) < 0 ||
|
||||
(error = git_vector_insert(args, git_str_detach(&url_and_host))) < 0)
|
||||
goto done;
|
||||
|
||||
if ((error = git_str_puts(&remote_cmd, command)) < 0 ||
|
||||
(error = git_str_puts(&remote_cmd, " '")) < 0 ||
|
||||
(error = git_str_puts_escaped(&remote_cmd, url->path, "'!", "'\\", "'")) < 0 ||
|
||||
(error = git_str_puts(&remote_cmd, "'")) < 0 ||
|
||||
(error = git_vector_insert(args, git_str_detach(&remote_cmd))) < 0)
|
||||
goto done;
|
||||
|
||||
done:
|
||||
git_str_dispose(&ssh_cmd);
|
||||
git_str_dispose(&url_and_host);
|
||||
git_str_dispose(&remote_cmd);
|
||||
git_config_free(cfg);
|
||||
return error;
|
||||
}
|
||||
@@ -183,7 +217,8 @@ static int start_ssh(
|
||||
|
||||
git_process_options process_opts = GIT_PROCESS_OPTIONS_INIT;
|
||||
git_net_url url = GIT_NET_URL_INIT;
|
||||
git_str ssh_cmdline = GIT_STR_INIT;
|
||||
git_vector args = GIT_VECTOR_INIT;
|
||||
bool use_shell = false;
|
||||
const char *command;
|
||||
int error;
|
||||
|
||||
@@ -214,11 +249,15 @@ static int start_ssh(
|
||||
if (error < 0)
|
||||
goto done;
|
||||
|
||||
if ((error = get_ssh_cmdline(&ssh_cmdline, transport, &url, command)) < 0)
|
||||
if ((error = get_ssh_cmdline(&args, &use_shell,
|
||||
transport, &url, command)) < 0)
|
||||
goto done;
|
||||
|
||||
if ((error = git_process_new_from_cmdline(&transport->process,
|
||||
ssh_cmdline.ptr, env, ARRAY_SIZE(env), &process_opts)) < 0 ||
|
||||
process_opts.use_shell = use_shell;
|
||||
|
||||
if ((error = git_process_new(&transport->process,
|
||||
(const char **)args.contents, args.length,
|
||||
env, ARRAY_SIZE(env), &process_opts)) < 0 ||
|
||||
(error = git_process_start(transport->process)) < 0) {
|
||||
git_process_free(transport->process);
|
||||
transport->process = NULL;
|
||||
@@ -226,7 +265,7 @@ static int start_ssh(
|
||||
}
|
||||
|
||||
done:
|
||||
git_str_dispose(&ssh_cmdline);
|
||||
git_vector_dispose_deep(&args);
|
||||
git_net_url_dispose(&url);
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -611,6 +611,37 @@ 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;
|
||||
|
||||
GIT_ASSERT_ARG_WITH_RETVAL(path, false);
|
||||
if (p_stat(path, &st) < 0)
|
||||
return false;
|
||||
|
||||
return S_ISREG(st.st_mode) != 0 &&
|
||||
((st.st_mode & S_IXUSR) != 0);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool git_fs_path_islink(const char *path)
|
||||
{
|
||||
struct stat st;
|
||||
@@ -2020,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;
|
||||
|
||||
@@ -2035,9 +2067,15 @@ 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;
|
||||
|
||||
if (git__getenv(&path, "PATH") < 0)
|
||||
@@ -2049,20 +2087,28 @@ int git_fs_path_find_executable(git_str *fullpath, const char *executable)
|
||||
if (! (term = strchr(current_dir, GIT_PATH_LIST_SEPARATOR)))
|
||||
term = strchr(current_dir, '\0');
|
||||
|
||||
current_dirlen = term - current_dir;
|
||||
git_str_clear(fullpath);
|
||||
if (git_str_put(fullpath, current_dir, (term - current_dir)) < 0 ||
|
||||
git_str_putc(fullpath, '/') < 0 ||
|
||||
|
||||
/* An empty path segment is treated as '.' */
|
||||
if (current_dirlen == 0 && git_str_putc(fullpath, '.'))
|
||||
return -1;
|
||||
else if (current_dirlen != 0 &&
|
||||
git_str_put(fullpath, current_dir, current_dirlen) < 0)
|
||||
return -1;
|
||||
|
||||
if (git_str_putc(fullpath, '/') < 0 ||
|
||||
git_str_puts(fullpath, executable) < 0)
|
||||
return -1;
|
||||
|
||||
if (git_fs_path_isfile(fullpath->ptr)) {
|
||||
if (git_fs_path_isexecutable(fullpath->ptr)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
current_dir = term;
|
||||
|
||||
while (*current_dir == GIT_PATH_LIST_SEPARATOR)
|
||||
if (*current_dir == GIT_PATH_LIST_SEPARATOR)
|
||||
current_dir++;
|
||||
}
|
||||
|
||||
@@ -2073,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);
|
||||
}
|
||||
|
||||
@@ -204,6 +204,12 @@ extern bool git_fs_path_isdir(const char *path);
|
||||
*/
|
||||
extern bool git_fs_path_isfile(const char *path);
|
||||
|
||||
/**
|
||||
* Check if the given path points to an executable.
|
||||
* @return true or false
|
||||
*/
|
||||
extern bool git_fs_path_isexecutable(const char *path);
|
||||
|
||||
/**
|
||||
* Check if the given path points to a symbolic link.
|
||||
* @return true or false
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
typedef struct git_process git_process;
|
||||
|
||||
typedef struct {
|
||||
unsigned int capture_in : 1,
|
||||
unsigned int use_shell : 1,
|
||||
capture_in : 1,
|
||||
capture_out : 1,
|
||||
capture_err : 1,
|
||||
exclude_env : 1;
|
||||
|
||||
@@ -1065,10 +1065,13 @@ int git_str_puts_escaped(
|
||||
git_str *buf,
|
||||
const char *string,
|
||||
const char *esc_chars,
|
||||
const char *esc_with)
|
||||
const char *esc_prefix,
|
||||
const char *esc_suffix)
|
||||
{
|
||||
const char *scan;
|
||||
size_t total = 0, esc_len = strlen(esc_with), count, alloclen;
|
||||
size_t total = 0, count, alloclen;
|
||||
size_t esc_prefix_len = esc_prefix ? strlen(esc_prefix) : 0;
|
||||
size_t esc_suffix_len = esc_suffix ? strlen(esc_suffix) : 0;
|
||||
|
||||
if (!string)
|
||||
return 0;
|
||||
@@ -1080,7 +1083,7 @@ int git_str_puts_escaped(
|
||||
scan += count;
|
||||
/* count run of escaped characters */
|
||||
count = strspn(scan, esc_chars);
|
||||
total += count * (esc_len + 1);
|
||||
total += count * (esc_prefix_len + esc_suffix_len + 1);
|
||||
scan += count;
|
||||
}
|
||||
|
||||
@@ -1096,13 +1099,22 @@ int git_str_puts_escaped(
|
||||
buf->size += count;
|
||||
|
||||
for (count = strspn(scan, esc_chars); count > 0; --count) {
|
||||
/* copy escape sequence */
|
||||
memmove(buf->ptr + buf->size, esc_with, esc_len);
|
||||
buf->size += esc_len;
|
||||
/* copy escape prefix sequence */
|
||||
if (esc_prefix) {
|
||||
memmove(buf->ptr + buf->size, esc_prefix, esc_prefix_len);
|
||||
buf->size += esc_prefix_len;
|
||||
}
|
||||
|
||||
/* copy character to be escaped */
|
||||
buf->ptr[buf->size] = *scan;
|
||||
buf->size++;
|
||||
scan++;
|
||||
|
||||
/* copy escape suffix sequence */
|
||||
if (esc_suffix) {
|
||||
memmove(buf->ptr + buf->size, esc_suffix, esc_suffix_len);
|
||||
buf->size += esc_suffix_len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -268,21 +268,23 @@ int git_str_splice(
|
||||
* @param str String buffer to append data to
|
||||
* @param string String to escape and append
|
||||
* @param esc_chars Characters to be escaped
|
||||
* @param esc_with String to insert in from of each found character
|
||||
* @param esc_prefix String to insert as prefix of each found character
|
||||
* @param esc_suffix String to insert as suffix of each found character
|
||||
* @return 0 on success, <0 on failure (probably allocation problem)
|
||||
*/
|
||||
extern int git_str_puts_escaped(
|
||||
git_str *str,
|
||||
const char *string,
|
||||
const char *esc_chars,
|
||||
const char *esc_with);
|
||||
const char *esc_prefix,
|
||||
const char *esc_suffix);
|
||||
|
||||
/**
|
||||
* Append string escaping characters that are regex special
|
||||
*/
|
||||
GIT_INLINE(int) git_str_puts_escape_regex(git_str *str, const char *string)
|
||||
{
|
||||
return git_str_puts_escaped(str, string, "^.[]$()|*+?{}\\", "\\");
|
||||
return git_str_puts_escaped(str, string, "^.[]$()|*+?{}\\", "\\", NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "git2_util.h"
|
||||
#include "vector.h"
|
||||
#include "futils.h"
|
||||
#include "process.h"
|
||||
#include "strlist.h"
|
||||
|
||||
@@ -28,7 +29,8 @@ struct git_process {
|
||||
|
||||
char *cwd;
|
||||
|
||||
unsigned int capture_in : 1,
|
||||
unsigned int use_shell : 1,
|
||||
capture_in : 1,
|
||||
capture_out : 1,
|
||||
capture_err : 1;
|
||||
|
||||
@@ -50,6 +52,53 @@ GIT_INLINE(bool) is_delete_env(const char *env)
|
||||
return *(c+1) == '\0';
|
||||
}
|
||||
|
||||
GIT_INLINE(int) insert_dup(git_vector *v, const char *s)
|
||||
{
|
||||
char *dup = git__strdup(s);
|
||||
|
||||
GIT_ERROR_CHECK_ALLOC(dup);
|
||||
|
||||
return git_vector_insert(v, dup);
|
||||
}
|
||||
|
||||
static int setup_args(
|
||||
char ***out,
|
||||
const char **args,
|
||||
size_t args_len,
|
||||
bool use_shell)
|
||||
{
|
||||
git_vector prefixed = GIT_VECTOR_INIT;
|
||||
git_str first = GIT_STR_INIT;
|
||||
size_t cnt;
|
||||
|
||||
GIT_ASSERT(args && args_len);
|
||||
|
||||
if (use_shell) {
|
||||
if (git_str_puts(&first, args[0]) < 0 ||
|
||||
git_str_puts(&first, " \"$@\"") < 0 ||
|
||||
insert_dup(&prefixed, "/bin/sh") < 0 ||
|
||||
insert_dup(&prefixed, "-c") < 0 ||
|
||||
git_vector_insert(&prefixed, git_str_detach(&first)) < 0)
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
for (cnt = 0; args && cnt < args_len; cnt++) {
|
||||
if (insert_dup(&prefixed, args[cnt]) < 0)
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
git_vector_insert(&prefixed, NULL);
|
||||
|
||||
*out = (char **)prefixed.contents;
|
||||
|
||||
return 0;
|
||||
|
||||
on_error:
|
||||
git_str_dispose(&first);
|
||||
git_vector_dispose_deep(&prefixed);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int merge_env(
|
||||
char ***out,
|
||||
const char **env,
|
||||
@@ -57,7 +106,7 @@ static int merge_env(
|
||||
bool exclude_env)
|
||||
{
|
||||
git_vector merged = GIT_VECTOR_INIT;
|
||||
char **kv, *dup;
|
||||
char **kv;
|
||||
size_t max, cnt;
|
||||
int error = 0;
|
||||
|
||||
@@ -71,10 +120,7 @@ static int merge_env(
|
||||
if (is_delete_env(env[cnt]))
|
||||
continue;
|
||||
|
||||
dup = git__strdup(env[cnt]);
|
||||
GIT_ERROR_CHECK_ALLOC(dup);
|
||||
|
||||
if ((error = git_vector_insert(&merged, dup)) < 0)
|
||||
if (insert_dup(&merged, env[cnt]) < 0)
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
@@ -83,10 +129,7 @@ static int merge_env(
|
||||
if (env && git_strlist_contains_key(env, env_len, *kv, '='))
|
||||
continue;
|
||||
|
||||
dup = git__strdup(*kv);
|
||||
GIT_ERROR_CHECK_ALLOC(dup);
|
||||
|
||||
if ((error = git_vector_insert(&merged, dup)) < 0)
|
||||
if (insert_dup(&merged, *kv) < 0)
|
||||
goto on_error;
|
||||
}
|
||||
}
|
||||
@@ -125,13 +168,14 @@ int git_process_new(
|
||||
process = git__calloc(sizeof(git_process), 1);
|
||||
GIT_ERROR_CHECK_ALLOC(process);
|
||||
|
||||
if (git_strlist_copy_with_null(&process->args, args, args_len) < 0 ||
|
||||
if (setup_args(&process->args, args, args_len, opts ? opts->use_shell : false) < 0 ||
|
||||
merge_env(&process->env, env, env_len, opts ? opts->exclude_env : false) < 0) {
|
||||
git_process_free(process);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (opts) {
|
||||
process->use_shell = opts->use_shell;
|
||||
process->capture_in = opts->capture_in;
|
||||
process->capture_out = opts->capture_out;
|
||||
process->capture_err = opts->capture_err;
|
||||
@@ -158,10 +202,12 @@ extern int git_process_new_from_cmdline(
|
||||
size_t env_len,
|
||||
git_process_options *opts)
|
||||
{
|
||||
const char *args[] = { "/bin/sh", "-c", cmdline };
|
||||
git_process_options merged_opts = {0};
|
||||
|
||||
return git_process_new(out,
|
||||
args, ARRAY_SIZE(args), env, env_len, opts);
|
||||
memcpy(&merged_opts, opts, sizeof(git_process_options));
|
||||
merged_opts.use_shell = 1;
|
||||
|
||||
return git_process_new(out, &cmdline, 1, env, env_len, &merged_opts);
|
||||
}
|
||||
|
||||
#define CLOSE_FD(fd) \
|
||||
@@ -271,6 +317,33 @@ static void write_status(int fd, const char *fn, int error, int os_error)
|
||||
try_write_status(fd, fn, fn_len);
|
||||
}
|
||||
|
||||
static int resolve_path(git_process *process)
|
||||
{
|
||||
git_str full_path = GIT_STR_INIT;
|
||||
int error = 0;
|
||||
|
||||
/* The shell will resolve the path for us */
|
||||
if (process->use_shell)
|
||||
goto done;
|
||||
|
||||
error = git_fs_path_find_executable(&full_path, process->args[0]);
|
||||
|
||||
if (error == GIT_ENOTFOUND) {
|
||||
git_error_set(GIT_ERROR_SSH, "cannot run %s: No such file or directory", process->args[0]);
|
||||
error = -1;
|
||||
}
|
||||
|
||||
if (error)
|
||||
goto done;
|
||||
|
||||
git__free(process->args[0]);
|
||||
process->args[0] = git_str_detach(&full_path);
|
||||
|
||||
done:
|
||||
git_str_dispose(&full_path);
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_process_start(git_process *process)
|
||||
{
|
||||
int in[2] = { -1, -1 }, out[2] = { -1, -1 },
|
||||
@@ -278,6 +351,10 @@ int git_process_start(git_process *process)
|
||||
int fdflags, state, error;
|
||||
pid_t pid;
|
||||
|
||||
/* Locate the path (unless we're letting the shell do it for us) */
|
||||
if ((error = resolve_path(process)) < 0)
|
||||
goto on_error;
|
||||
|
||||
/* Set up the pipes to read from/write to the process */
|
||||
if ((process->capture_in && pipe(in) < 0) ||
|
||||
(process->capture_out && pipe(out) < 0) ||
|
||||
|
||||
@@ -269,13 +269,26 @@ int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix)
|
||||
return prefixcmp(str, str_n, prefix, true);
|
||||
}
|
||||
|
||||
int git__suffixcmp(const char *str, const char *suffix)
|
||||
static int suffixcmp(const char *str, const char *suffix, bool icase)
|
||||
{
|
||||
size_t a = strlen(str);
|
||||
size_t b = strlen(suffix);
|
||||
|
||||
if (a < b)
|
||||
return -1;
|
||||
return strcmp(str + (a - b), suffix);
|
||||
|
||||
return icase ? strcasecmp(str + (a - b), suffix) :
|
||||
strcmp(str + (a - b), suffix);
|
||||
}
|
||||
|
||||
int git__suffixcmp(const char *str, const char *suffix)
|
||||
{
|
||||
return suffixcmp(str, suffix, false);
|
||||
}
|
||||
|
||||
int git__suffixcmp_icase(const char *str, const char *suffix)
|
||||
{
|
||||
return suffixcmp(str, suffix, true);
|
||||
}
|
||||
|
||||
char *git__strtok(char **end, const char *sep)
|
||||
|
||||
@@ -57,6 +57,7 @@ extern int git__prefixcmp_icase(const char *str, const char *prefix);
|
||||
extern int git__prefixncmp(const char *str, size_t str_n, const char *prefix);
|
||||
extern int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix);
|
||||
extern int git__suffixcmp(const char *str, const char *suffix);
|
||||
extern int git__suffixcmp_icase(const char *str, const char *suffix);
|
||||
|
||||
GIT_INLINE(int) git__signum(int val)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -253,21 +257,92 @@ 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;
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(suffixes); i++) {
|
||||
struct executable_suffix *suffix = &suffixes[i];
|
||||
|
||||
if (!suffix->len)
|
||||
continue;
|
||||
|
||||
if (exe_len < suffix->len)
|
||||
continue;
|
||||
|
||||
if (memcmp(&exe[exe_len - suffix->len], suffix->suffix, suffix->len) == 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
wcscat(path, suffix->suffix);
|
||||
}
|
||||
|
||||
if (_waccess(path, 0) == 0)
|
||||
return true;
|
||||
|
||||
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 = wcslen(exe);
|
||||
bool found = false;
|
||||
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) {
|
||||
if (git_win32_path_join(fullpath, dir, dir_len, exe, exe_len) < 0)
|
||||
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 (_waccess(fullpath, 0) == 0) {
|
||||
if (is_executable(fullpath, fullpath_len, suffixed)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
@@ -275,6 +350,7 @@ int git_win32_path_find_executable(git_win32_path fullpath, wchar_t *exe)
|
||||
|
||||
win32_path_iter_dispose(&path_iter);
|
||||
|
||||
done:
|
||||
if (found)
|
||||
return 0;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -105,13 +105,13 @@ void test_online_clone__initialize(void)
|
||||
_orig_https_proxy = cl_getenv("HTTPS_PROXY");
|
||||
_orig_no_proxy = cl_getenv("NO_PROXY");
|
||||
|
||||
_orig_ssh_cmd = cl_getenv("GIT_SSH");
|
||||
_orig_ssh_cmd = cl_getenv("GIT_SSH_COMMAND");
|
||||
_ssh_cmd = cl_getenv("GITTEST_SSH_CMD");
|
||||
|
||||
if (_ssh_cmd)
|
||||
cl_setenv("GIT_SSH", _ssh_cmd);
|
||||
cl_setenv("GIT_SSH_COMMAND", _ssh_cmd);
|
||||
else
|
||||
cl_setenv("GIT_SSH", NULL);
|
||||
cl_setenv("GIT_SSH_COMMAND", NULL);
|
||||
|
||||
if (_remote_expectcontinue)
|
||||
git_libgit2_opts(GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, 1);
|
||||
@@ -174,7 +174,7 @@ void test_online_clone__cleanup(void)
|
||||
git__free(_orig_https_proxy);
|
||||
git__free(_orig_no_proxy);
|
||||
|
||||
cl_setenv("GIT_SSH", _orig_ssh_cmd);
|
||||
cl_setenv("GIT_SSH_COMMAND", _orig_ssh_cmd);
|
||||
git__free(_orig_ssh_cmd);
|
||||
|
||||
git__free(_ssh_cmd);
|
||||
|
||||
@@ -376,13 +376,13 @@ void test_online_push__initialize(void)
|
||||
_remote_push_options = cl_getenv("GITTEST_PUSH_OPTIONS");
|
||||
_remote = NULL;
|
||||
|
||||
_orig_ssh_cmd = cl_getenv("GIT_SSH");
|
||||
_orig_ssh_cmd = cl_getenv("GIT_SSH_COMMAND");
|
||||
_ssh_cmd = cl_getenv("GITTEST_SSH_CMD");
|
||||
|
||||
if (_ssh_cmd)
|
||||
cl_setenv("GIT_SSH", _ssh_cmd);
|
||||
cl_setenv("GIT_SSH_COMMAND", _ssh_cmd);
|
||||
else
|
||||
cl_setenv("GIT_SSH", NULL);
|
||||
cl_setenv("GIT_SSH_COMMAND", NULL);
|
||||
|
||||
/* Skip the test if we're missing the remote URL */
|
||||
if (!_remote_url)
|
||||
@@ -439,6 +439,7 @@ void test_online_push__cleanup(void)
|
||||
git__free(_remote_expectcontinue);
|
||||
git__free(_remote_push_options);
|
||||
|
||||
cl_setenv("GIT_SSH_COMMAND", _orig_ssh_cmd);
|
||||
git__free(_orig_ssh_cmd);
|
||||
git__free(_ssh_cmd);
|
||||
|
||||
|
||||
@@ -691,17 +691,33 @@ void test_gitstr__puts_escaped(void)
|
||||
git_str a = GIT_STR_INIT;
|
||||
|
||||
git_str_clear(&a);
|
||||
cl_git_pass(git_str_puts_escaped(&a, "this is a test", "", ""));
|
||||
cl_git_pass(git_str_puts_escaped(&a, "this is a test", "", "", ""));
|
||||
cl_assert_equal_s("this is a test", a.ptr);
|
||||
|
||||
git_str_clear(&a);
|
||||
cl_git_pass(git_str_puts_escaped(&a, "this is a test", "t", "\\"));
|
||||
cl_git_pass(git_str_puts_escaped(&a, "this is a test", "", NULL, NULL));
|
||||
cl_assert_equal_s("this is a test", a.ptr);
|
||||
|
||||
git_str_clear(&a);
|
||||
cl_git_pass(git_str_puts_escaped(&a, "this is a test", "t", "\\", ""));
|
||||
cl_assert_equal_s("\\this is a \\tes\\t", a.ptr);
|
||||
|
||||
git_str_clear(&a);
|
||||
cl_git_pass(git_str_puts_escaped(&a, "this is a test", "i ", "__"));
|
||||
cl_git_pass(git_str_puts_escaped(&a, "this is a test", "t", "\\", NULL));
|
||||
cl_assert_equal_s("\\this is a \\tes\\t", a.ptr);
|
||||
|
||||
git_str_clear(&a);
|
||||
cl_git_pass(git_str_puts_escaped(&a, "this is a test", "i ", "__", NULL));
|
||||
cl_assert_equal_s("th__is__ __is__ a__ test", a.ptr);
|
||||
|
||||
git_str_clear(&a);
|
||||
cl_git_pass(git_str_puts_escaped(&a, "this is a test", "i ", "__", "!!"));
|
||||
cl_assert_equal_s("th__i!!s__ !!__i!!s__ !!a__ !!test", a.ptr);
|
||||
|
||||
git_str_clear(&a);
|
||||
cl_git_pass(git_str_puts_escaped(&a, "this' is' an' escape! ", "'!", "'\\", "'"));
|
||||
cl_assert_equal_s("this'\\'' is'\\'' an'\\'' escape'\\!' ", a.ptr);
|
||||
|
||||
git_str_clear(&a);
|
||||
cl_git_pass(git_str_puts_escape_regex(&a, "^match\\s*[A-Z]+.*"));
|
||||
cl_assert_equal_s("\\^match\\\\s\\*\\[A-Z\\]\\+\\.\\*", a.ptr);
|
||||
|
||||
@@ -698,12 +698,20 @@ static void fix_path(git_str *s)
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
# define PATH_DELIMITER "\\"
|
||||
#else
|
||||
# define PATH_DELIMITER "/"
|
||||
#endif
|
||||
|
||||
void test_path_core__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;
|
||||
dummy_path_one = GIT_STR_INIT,
|
||||
dummy_path_two = GIT_STR_INIT,
|
||||
dummy_path_three = GIT_STR_INIT;
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
static const char *bogus_path_1 = "c:\\does\\not\\exist\\";
|
||||
@@ -716,28 +724,84 @@ void test_path_core__find_exe_in_path(void)
|
||||
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");
|
||||
|
||||
git_str_joinpath(&dummy_path_one, sandbox_path.ptr, "dummmmmmmy_libgit2_file_one");
|
||||
cl_git_rewritefile(dummy_path_one.ptr, "this is a dummy file");
|
||||
|
||||
git_str_joinpath(&dummy_path_two, sandbox_path.ptr, "dummmmmmmy_libgit2_file_two.exe");
|
||||
cl_git_rewritefile(dummy_path_two.ptr, "this is a dummy file");
|
||||
cl_must_pass(p_chmod("dummmmmmmy_libgit2_file_two.exe", 0777));
|
||||
|
||||
cl_must_pass(p_mkdir("sub", 0777));
|
||||
cl_must_pass(p_mkdir("sub/dir", 0777));
|
||||
git_str_joinpath(&dummy_path_three, sandbox_path.ptr, "sub/dir/dummmmmmmy_libgit2_file_three.exe");
|
||||
cl_git_rewritefile(dummy_path_three.ptr, "this is a dummy file");
|
||||
cl_must_pass(p_chmod("sub/dir/dummmmmmmy_libgit2_file_three.exe", 0777));
|
||||
|
||||
fix_path(&sandbox_path);
|
||||
fix_path(&dummy_path);
|
||||
fix_path(&dummy_path_one);
|
||||
fix_path(&dummy_path_two);
|
||||
fix_path(&dummy_path_three);
|
||||
|
||||
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);
|
||||
|
||||
/* ensure that we cannot find a nonexistent file */
|
||||
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);
|
||||
/* ensure that non-executable files are ignored */
|
||||
cl_git_fail_with(GIT_ENOTFOUND, git_fs_path_find_executable(&full_path, "dummmmmmmy_libgit2_file_one"));
|
||||
|
||||
/* find an executable in the path */
|
||||
git_str_clear(&full_path);
|
||||
cl_git_pass(git_fs_path_find_executable(&full_path, "dummmmmmmy_libgit2_file_two.exe"));
|
||||
cl_assert_equal_s(full_path.ptr, dummy_path_two.ptr);
|
||||
|
||||
/* find an executable in the current directory */
|
||||
git_str_clear(&new_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,
|
||||
".", GIT_PATH_LIST_SEPARATOR,
|
||||
bogus_path_2));
|
||||
check_setenv("PATH", new_path.ptr);
|
||||
|
||||
git_str_clear(&full_path);
|
||||
cl_git_pass(git_fs_path_find_executable(&full_path, "dummmmmmmy_libgit2_file_two.exe"));
|
||||
cl_assert_equal_s(full_path.ptr, "." PATH_DELIMITER "dummmmmmmy_libgit2_file_two.exe");
|
||||
|
||||
/* empty path is the same as current directory on POSIX */
|
||||
#ifndef GIT_WIN32
|
||||
git_str_clear(&new_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,
|
||||
"", GIT_PATH_LIST_SEPARATOR,
|
||||
bogus_path_2));
|
||||
check_setenv("PATH", new_path.ptr);
|
||||
|
||||
git_str_clear(&full_path);
|
||||
cl_git_pass(git_fs_path_find_executable(&full_path, "dummmmmmmy_libgit2_file_two.exe"));
|
||||
cl_assert_equal_s(full_path.ptr, "." PATH_DELIMITER "dummmmmmmy_libgit2_file_two.exe");
|
||||
#endif
|
||||
|
||||
/* don't look for qualified paths in PATH */
|
||||
cl_git_fail_with(GIT_ENOTFOUND, git_fs_path_find_executable(&full_path, "dir/dummmmmmmy_libgit2_file_three.exe"));
|
||||
|
||||
/* but do allow users to use paths relative to current dir */
|
||||
git_str_clear(&full_path);
|
||||
cl_git_pass(git_fs_path_find_executable(&full_path, "sub/dir/dummmmmmmy_libgit2_file_three.exe"));
|
||||
cl_assert_equal_s(full_path.ptr, "sub/dir/dummmmmmmy_libgit2_file_three.exe");
|
||||
|
||||
git_str_dispose(&full_path);
|
||||
git_str_dispose(&new_path);
|
||||
git_str_dispose(&dummy_path);
|
||||
git_str_dispose(&dummy_path_one);
|
||||
git_str_dispose(&dummy_path_two);
|
||||
git_str_dispose(&dummy_path_three);
|
||||
git_str_dispose(&sandbox_path);
|
||||
git__free(orig_path);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user