diff options
Diffstat (limited to 'src/util')
-rw-r--r-- | src/util/array.h | 35 | ||||
-rw-r--r-- | src/util/ctype_compat.h | 70 | ||||
-rw-r--r-- | src/util/date.c | 26 | ||||
-rw-r--r-- | src/util/errors.c | 401 | ||||
-rw-r--r-- | src/util/errors.h | 84 | ||||
-rw-r--r-- | src/util/fs_path.c | 19 | ||||
-rw-r--r-- | src/util/fs_path.h | 23 | ||||
-rw-r--r-- | src/util/futils.h | 2 | ||||
-rw-r--r-- | src/util/git2_features.h.in | 8 | ||||
-rw-r--r-- | src/util/git2_util.h | 2 | ||||
-rw-r--r-- | src/util/integer.h | 4 | ||||
-rw-r--r-- | src/util/net.c | 5 | ||||
-rw-r--r-- | src/util/process.h | 222 | ||||
-rw-r--r-- | src/util/rand.c | 8 | ||||
-rw-r--r-- | src/util/regexp.c | 2 | ||||
-rw-r--r-- | src/util/str.c | 4 | ||||
-rw-r--r-- | src/util/strlist.c | 108 | ||||
-rw-r--r-- | src/util/strlist.h | 36 | ||||
-rw-r--r-- | src/util/unix/posix.h | 2 | ||||
-rw-r--r-- | src/util/unix/process.c | 629 | ||||
-rw-r--r-- | src/util/util.c | 4 | ||||
-rw-r--r-- | src/util/util.h | 34 | ||||
-rw-r--r-- | src/util/win32/posix_w32.c | 34 | ||||
-rw-r--r-- | src/util/win32/process.c | 506 |
24 files changed, 2174 insertions, 94 deletions
diff --git a/src/util/array.h b/src/util/array.h index 633d598..515e6e3 100644 --- a/src/util/array.h +++ b/src/util/array.h @@ -41,39 +41,40 @@ #define GIT_ERROR_CHECK_ARRAY(a) GIT_ERROR_CHECK_ALLOC((a).ptr) - -typedef git_array_t(char) git_array_generic_t; - -/* use a generic array for growth, return 0 on success */ -GIT_INLINE(int) git_array_grow(void *_a, size_t item_size) +GIT_INLINE(void *) git_array__alloc(void *arr, size_t *size, size_t *asize, size_t item_size) { - volatile git_array_generic_t *a = _a; size_t new_size; - char *new_array; + void *new_array; + + if (*size < *asize) + return arr; - if (a->size < 8) { + if (*size < 8) { new_size = 8; } else { - if (GIT_MULTIPLY_SIZET_OVERFLOW(&new_size, a->size, 3)) + if (GIT_MULTIPLY_SIZET_OVERFLOW(&new_size, *asize, 3)) goto on_oom; + new_size /= 2; } - if ((new_array = git__reallocarray(a->ptr, new_size, item_size)) == NULL) + if ((new_array = git__reallocarray(arr, new_size, item_size)) == NULL) goto on_oom; - a->ptr = new_array; - a->asize = new_size; - return 0; + *asize = new_size; + + return new_array; on_oom: - git_array_clear(*a); - return -1; + git__free(arr); + *size = 0; + *asize = 0; + return NULL; } #define git_array_alloc(a) \ - (((a).size < (a).asize || git_array_grow(&(a), sizeof(*(a).ptr)) == 0) ? \ - &(a).ptr[(a).size++] : (void *)NULL) + (((a).size < (a).asize || \ + ((a).ptr = git_array__alloc((a).ptr, &(a).size, &(a).asize, sizeof(*(a).ptr))) != NULL) ? &(a).ptr[(a).size++] : (void *)NULL) #define git_array_last(a) ((a).size ? &(a).ptr[(a).size - 1] : (void *)NULL) diff --git a/src/util/ctype_compat.h b/src/util/ctype_compat.h new file mode 100644 index 0000000..462c8a1 --- /dev/null +++ b/src/util/ctype_compat.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_ctype_compat_h__ +#define INCLUDE_ctype_compat_h__ + +/* + * The Microsoft C runtime (MSVCRT) may take a heavy lock on the + * locale in order to figure out how the `ctype` functions work. + * This is deeply slow. Provide our own to avoid that. + */ + +#ifdef GIT_WIN32 + +GIT_INLINE(int) git__tolower(int c) +{ + return (c >= 'A' && c <= 'Z') ? (c + 32) : c; +} + +GIT_INLINE(int) git__toupper(int c) +{ + return (c >= 'a' && c <= 'z') ? (c - 32) : c; +} + +GIT_INLINE(bool) git__isalpha(int c) +{ + return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')); +} + +GIT_INLINE(bool) git__isdigit(int c) +{ + return (c >= '0' && c <= '9'); +} + +GIT_INLINE(bool) git__isalnum(int c) +{ + return git__isalpha(c) || git__isdigit(c); +} + +GIT_INLINE(bool) git__isspace(int c) +{ + return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v'); +} + +GIT_INLINE(bool) git__isxdigit(int c) +{ + return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); +} + +GIT_INLINE(bool) git__isprint(int c) +{ + return (c >= ' ' && c <= '~'); +} + +#else +# define git__tolower(a) tolower((unsigned char)(a)) +# define git__toupper(a) toupper((unsigned char)(a)) + +# define git__isalpha(a) (!!isalpha((unsigned char)(a))) +# define git__isdigit(a) (!!isdigit((unsigned char)(a))) +# define git__isalnum(a) (!!isalnum((unsigned char)(a))) +# define git__isspace(a) (!!isspace((unsigned char)(a))) +# define git__isxdigit(a) (!!isxdigit((unsigned char)(a))) +# define git__isprint(a) (!!isprint((unsigned char)(a))) +#endif + +#endif diff --git a/src/util/date.c b/src/util/date.c index 4d757e2..872cb81 100644 --- a/src/util/date.c +++ b/src/util/date.c @@ -129,9 +129,9 @@ static size_t match_string(const char *date, const char *str) for (i = 0; *date; date++, str++, i++) { if (*date == *str) continue; - if (toupper(*date) == toupper(*str)) + if (git__toupper(*date) == git__toupper(*str)) continue; - if (!isalnum(*date)) + if (!git__isalnum(*date)) break; return 0; } @@ -143,7 +143,7 @@ static int skip_alpha(const char *date) int i = 0; do { i++; - } while (isalpha(date[i])); + } while (git__isalpha(date[i])); return i; } @@ -251,7 +251,7 @@ static size_t match_multi_number(unsigned long num, char c, const char *date, ch num2 = strtol(end+1, &end, 10); num3 = -1; - if (*end == c && isdigit(end[1])) + if (*end == c && git__isdigit(end[1])) num3 = strtol(end+1, &end, 10); /* Time? Date? */ @@ -349,7 +349,7 @@ static size_t match_digit(const char *date, struct tm *tm, int *offset, int *tm_ case '.': case '/': case '-': - if (isdigit(end[1])) { + if (git__isdigit(end[1])) { size_t match = match_multi_number(num, *end, date, end, tm); if (match) return match; @@ -364,7 +364,7 @@ static size_t match_digit(const char *date, struct tm *tm, int *offset, int *tm_ n = 0; do { n++; - } while (isdigit(date[n])); + } while (git__isdigit(date[n])); /* Four-digit year or a timezone? */ if (n == 4) { @@ -514,11 +514,11 @@ static int parse_date_basic(const char *date, git_time_t *timestamp, int *offset if (!c || c == '\n') break; - if (isalpha(c)) + if (git__isalpha(c)) match = match_alpha(date, &tm, offset); - else if (isdigit(c)) + else if (git__isdigit(c)) match = match_digit(date, &tm, offset, &tm_gmt); - else if ((c == '-' || c == '+') && isdigit(date[1])) + else if ((c == '-' || c == '+') && git__isdigit(date[1])) match = match_tz(date, offset); if (!match) { @@ -682,7 +682,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm const char *end = date; int i; - while (isalpha(*++end)) + while (git__isalpha(*++end)) /* scan to non-alpha */; for (i = 0; i < 12; i++) { @@ -783,7 +783,7 @@ static const char *approxidate_digit(const char *date, struct tm *tm, int *num) case '.': case '/': case '-': - if (isdigit(end[1])) { + if (git__isdigit(end[1])) { size_t match = match_multi_number(number, *end, date, end, tm); if (match) return date + match; @@ -843,13 +843,13 @@ static git_time_t approxidate_str(const char *date, if (!c) break; date++; - if (isdigit(c)) { + if (git__isdigit(c)) { pending_number(&tm, &number); date = approxidate_digit(date-1, &tm, &number); touched = 1; continue; } - if (isalpha(c)) + if (git__isalpha(c)) date = approxidate_alpha(date-1, &tm, &now, &number, &touched); } pending_number(&tm, &number); diff --git a/src/util/errors.c b/src/util/errors.c new file mode 100644 index 0000000..feed6a8 --- /dev/null +++ b/src/util/errors.c @@ -0,0 +1,401 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "git2_util.h" + +#include "errors.h" +#include "posix.h" +#include "str.h" +#include "runtime.h" + +/* + * Some static error data that is used when we're out of memory, TLS + * has not been setup, or TLS has failed. + */ + +static git_error oom_error = { + "Out of memory", + GIT_ERROR_NOMEMORY +}; + +static git_error uninitialized_error = { + "library has not been initialized", + GIT_ERROR_INVALID +}; + +static git_error tlsdata_error = { + "thread-local data initialization failure", + GIT_ERROR_THREAD +}; + +static git_error no_error = { + "no error", + GIT_ERROR_NONE +}; + +#define IS_STATIC_ERROR(err) \ + ((err) == &oom_error || (err) == &uninitialized_error || \ + (err) == &tlsdata_error || (err) == &no_error) + +/* Per-thread error state (TLS) */ + +static git_tlsdata_key tls_key; + +struct error_threadstate { + /* The error message buffer. */ + git_str message; + + /* Error information, set by `git_error_set` and friends. */ + git_error error; + + /* + * The last error to occur; points to the error member of this + * struct _or_ a static error. + */ + git_error *last; +}; + +static void threadstate_dispose(struct error_threadstate *threadstate) +{ + if (!threadstate) + return; + + git_str_dispose(&threadstate->message); +} + +static struct error_threadstate *threadstate_get(void) +{ + struct error_threadstate *threadstate; + + if ((threadstate = git_tlsdata_get(tls_key)) != NULL) + return threadstate; + + /* + * Avoid git__malloc here, since if it fails, it sets an error + * message, which requires thread state, which would allocate + * here, which would fail, which would set an error message... + */ + + if ((threadstate = git__allocator.gmalloc( + sizeof(struct error_threadstate), + __FILE__, __LINE__)) == NULL) + return NULL; + + memset(threadstate, 0, sizeof(struct error_threadstate)); + + if (git_str_init(&threadstate->message, 0) < 0) { + git__allocator.gfree(threadstate); + return NULL; + } + + git_tlsdata_set(tls_key, threadstate); + return threadstate; +} + +static void GIT_SYSTEM_CALL threadstate_free(void *threadstate) +{ + threadstate_dispose(threadstate); + git__free(threadstate); +} + +static void git_error_global_shutdown(void) +{ + struct error_threadstate *threadstate; + + threadstate = git_tlsdata_get(tls_key); + git_tlsdata_set(tls_key, NULL); + + threadstate_dispose(threadstate); + git__free(threadstate); + + git_tlsdata_dispose(tls_key); +} + +int git_error_global_init(void) +{ + if (git_tlsdata_init(&tls_key, &threadstate_free) != 0) + return -1; + + return git_runtime_shutdown_register(git_error_global_shutdown); +} + +static void set_error_from_buffer(int error_class) +{ + struct error_threadstate *threadstate = threadstate_get(); + git_error *error; + git_str *buf; + + if (!threadstate) + return; + + error = &threadstate->error; + buf = &threadstate->message; + + error->message = buf->ptr; + error->klass = error_class; + + threadstate->last = error; +} + +static void set_error(int error_class, char *string) +{ + struct error_threadstate *threadstate = threadstate_get(); + git_str *buf; + + if (!threadstate) + return; + + buf = &threadstate->message; + + git_str_clear(buf); + + if (string) + git_str_puts(buf, string); + + if (!git_str_oom(buf)) + set_error_from_buffer(error_class); +} + +void git_error_set_oom(void) +{ + struct error_threadstate *threadstate = threadstate_get(); + + if (!threadstate) + return; + + threadstate->last = &oom_error; +} + +void git_error_set(int error_class, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + git_error_vset(error_class, fmt, ap); + va_end(ap); +} + +void git_error_vset(int error_class, const char *fmt, va_list ap) +{ +#ifdef GIT_WIN32 + DWORD win32_error_code = (error_class == GIT_ERROR_OS) ? GetLastError() : 0; +#endif + + struct error_threadstate *threadstate = threadstate_get(); + int error_code = (error_class == GIT_ERROR_OS) ? errno : 0; + git_str *buf; + + if (!threadstate) + return; + + buf = &threadstate->message; + + git_str_clear(buf); + + if (fmt) { + git_str_vprintf(buf, fmt, ap); + if (error_class == GIT_ERROR_OS) + git_str_PUTS(buf, ": "); + } + + if (error_class == GIT_ERROR_OS) { +#ifdef GIT_WIN32 + char *win32_error = git_win32_get_error_message(win32_error_code); + if (win32_error) { + git_str_puts(buf, win32_error); + git__free(win32_error); + + SetLastError(0); + } + else +#endif + if (error_code) + git_str_puts(buf, strerror(error_code)); + + if (error_code) + errno = 0; + } + + if (!git_str_oom(buf)) + set_error_from_buffer(error_class); +} + +int git_error_set_str(int error_class, const char *string) +{ + struct error_threadstate *threadstate = threadstate_get(); + git_str *buf; + + GIT_ASSERT_ARG(string); + + if (!threadstate) + return -1; + + buf = &threadstate->message; + + git_str_clear(buf); + git_str_puts(buf, string); + + if (git_str_oom(buf)) + return -1; + + set_error_from_buffer(error_class); + return 0; +} + +void git_error_clear(void) +{ + struct error_threadstate *threadstate = threadstate_get(); + + if (!threadstate) + return; + + if (threadstate->last != NULL) { + set_error(0, NULL); + threadstate->last = NULL; + } + + errno = 0; +#ifdef GIT_WIN32 + SetLastError(0); +#endif +} + +bool git_error_exists(void) +{ + struct error_threadstate *threadstate; + + if ((threadstate = threadstate_get()) == NULL) + return true; + + return threadstate->last != NULL; +} + +const git_error *git_error_last(void) +{ + struct error_threadstate *threadstate; + + /* If the library is not initialized, return a static error. */ + if (!git_runtime_init_count()) + return &uninitialized_error; + + if ((threadstate = threadstate_get()) == NULL) + return &tlsdata_error; + + if (!threadstate->last) + return &no_error; + + return threadstate->last; +} + +int git_error_save(git_error **out) +{ + struct error_threadstate *threadstate = threadstate_get(); + git_error *error, *dup; + + if (!threadstate) { + *out = &tlsdata_error; + return -1; + } + + error = threadstate->last; + + if (!error || error == &no_error) { + *out = &no_error; + return 0; + } else if (IS_STATIC_ERROR(error)) { + *out = error; + return 0; + } + + if ((dup = git__malloc(sizeof(git_error))) == NULL) { + *out = &oom_error; + return -1; + } + + dup->klass = error->klass; + dup->message = git__strdup(error->message); + + if (!dup->message) { + *out = &oom_error; + return -1; + } + + *out = dup; + return 0; +} + +int git_error_restore(git_error *error) +{ + struct error_threadstate *threadstate = threadstate_get(); + + GIT_ASSERT_ARG(error); + + if (IS_STATIC_ERROR(error) && threadstate) + threadstate->last = error; + else + set_error(error->klass, error->message); + + git_error_free(error); + return 0; +} + +void git_error_free(git_error *error) +{ + if (!error) + return; + + if (IS_STATIC_ERROR(error)) + return; + + git__free(error->message); + git__free(error); +} + +int git_error_system_last(void) +{ +#ifdef GIT_WIN32 + return GetLastError(); +#else + return errno; +#endif +} + +void git_error_system_set(int code) +{ +#ifdef GIT_WIN32 + SetLastError(code); +#else + errno = code; +#endif +} + +/* Deprecated error values and functions */ + +#ifndef GIT_DEPRECATE_HARD + +#include "git2/deprecated.h" + +const git_error *giterr_last(void) +{ + return git_error_last(); +} + +void giterr_clear(void) +{ + git_error_clear(); +} + +void giterr_set_str(int error_class, const char *string) +{ + git_error_set_str(error_class, string); +} + +void giterr_set_oom(void) +{ + git_error_set_oom(); +} +#endif diff --git a/src/util/errors.h b/src/util/errors.h new file mode 100644 index 0000000..8d58775 --- /dev/null +++ b/src/util/errors.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_errors_h__ +#define INCLUDE_errors_h__ + +#include "git2_util.h" +#include "git2/sys/errors.h" + +/* Initialize the error thread-state. */ +int git_error_global_init(void); + +/* + * `vprintf`-style formatting for the error message for this thread. + */ +void git_error_vset(int error_class, const char *fmt, va_list ap); + +/** + * Determines whether an error exists. + */ +bool git_error_exists(void); + +/** + * Set error message for user callback if needed. + * + * If the error code in non-zero and no error message is set, this + * sets a generic error message. + * + * @return This always returns the `error_code` parameter. + */ +GIT_INLINE(int) git_error_set_after_callback_function( + int error_code, const char *action) +{ + if (error_code) { + if (!git_error_exists()) + git_error_set(GIT_ERROR_CALLBACK, + "%s callback returned %d", action, error_code); + } + return error_code; +} + +#ifdef GIT_WIN32 +#define git_error_set_after_callback(code) \ + git_error_set_after_callback_function((code), __FUNCTION__) +#else +#define git_error_set_after_callback(code) \ + git_error_set_after_callback_function((code), __func__) +#endif + +/** + * Gets the system error code for this thread. + */ +int git_error_system_last(void); + +/** + * Sets the system error code for this thread. + */ +void git_error_system_set(int code); + +/** + * Capture current error state to restore later, returning error code. + * If `error_code` is zero, this does not clear the current error state. + * You must either restore this error state, or free it. + * + * This function returns 0 on success, or -1 on failure. If the function + * fails, the `out` structure is set to the failure error message and + * the normal system error message is not updated. + */ +extern int git_error_save(git_error **out); + +/** + * Restore thread error state to the given value. The given value is + * freed and `git_error_free` need not be called on it. + */ +extern int git_error_restore(git_error *error); + +/** Free an error state. */ +extern void git_error_free(git_error *error); + +#endif diff --git a/src/util/fs_path.c b/src/util/fs_path.c index e03fcf7..9d5c99e 100644 --- a/src/util/fs_path.c +++ b/src/util/fs_path.c @@ -419,6 +419,16 @@ int git_fs_path_to_dir(git_str *path) return git_str_oom(path) ? -1 : 0; } +size_t git_fs_path_dirlen(const char *path) +{ + size_t len = strlen(path); + + while (len > 1 && path[len - 1] == '/') + len--; + + return len; +} + void git_fs_path_string_to_dir(char *path, size_t size) { size_t end = strlen(path); @@ -1938,12 +1948,13 @@ static int sudo_uid_lookup(uid_t *out) { git_str uid_str = GIT_STR_INIT; int64_t uid; - int error; + int error = -1; - if ((error = git__getenv(&uid_str, "SUDO_UID")) == 0 && - (error = git__strntol64(&uid, uid_str.ptr, uid_str.size, NULL, 10)) == 0 && - uid == (int64_t)((uid_t)uid)) { + if (git__getenv(&uid_str, "SUDO_UID") == 0 && + git__strntol64(&uid, uid_str.ptr, uid_str.size, NULL, 10) == 0 && + uid == (int64_t)((uid_t)uid)) { *out = (uid_t)uid; + error = 0; } git_str_dispose(&uid_str); diff --git a/src/util/fs_path.h b/src/util/fs_path.h index e5ca673..43f7951 100644 --- a/src/util/fs_path.h +++ b/src/util/fs_path.h @@ -87,6 +87,29 @@ extern int git_fs_path_to_dir(git_str *path); extern void git_fs_path_string_to_dir(char *path, size_t size); /** + * Provides the length of the given path string with no trailing + * slashes. + */ +size_t git_fs_path_dirlen(const char *path); + +/** + * Returns nonzero if the given path is a filesystem root; on Windows, this + * means a drive letter (eg `A:/`, `C:\`). On POSIX this is `/`. + */ +GIT_INLINE(int) git_fs_path_is_root(const char *name) +{ +#ifdef GIT_WIN32 + if (((name[0] >= 'A' && name[0] <= 'Z') || (name[0] >= 'a' && name[0] <= 'z')) && + name[1] == ':' && + (name[2] == '/' || name[2] == '\\') && + name[3] == '\0') + return 1; +#endif + + return (name[0] == '/' && name[1] == '\0'); +} + +/** * Taken from git.git; returns nonzero if the given path is "." or "..". */ GIT_INLINE(int) git_fs_path_is_dot_or_dotdot(const char *name) diff --git a/src/util/futils.h b/src/util/futils.h index 3f207af..53bcc55 100644 --- a/src/util/futils.h +++ b/src/util/futils.h @@ -25,7 +25,7 @@ extern int git_futils_readbuffer(git_str *obj, const char *path); extern int git_futils_readbuffer_updated( git_str *obj, const char *path, - unsigned char checksum[GIT_HASH_SHA1_SIZE], + unsigned char checksum[GIT_HASH_SHA256_SIZE], int *updated); extern int git_futils_readbuffer_fd_full(git_str *obj, git_file fd); extern int git_futils_readbuffer_fd(git_str *obj, git_file fd, size_t len); diff --git a/src/util/git2_features.h.in b/src/util/git2_features.h.in index a84ea89..52b7328 100644 --- a/src/util/git2_features.h.in +++ b/src/util/git2_features.h.in @@ -30,7 +30,9 @@ #cmakedefine GIT_QSORT_MSC #cmakedefine GIT_SSH 1 -#cmakedefine GIT_SSH_MEMORY_CREDENTIALS 1 +#cmakedefine GIT_SSH_EXEC 1 +#cmakedefine GIT_SSH_LIBSSH2 1 +#cmakedefine GIT_SSH_LIBSSH2_MEMORY_CREDENTIALS 1 #cmakedefine GIT_NTLM 1 #cmakedefine GIT_GSSAPI 1 @@ -44,6 +46,10 @@ #cmakedefine GIT_MBEDTLS 1 #cmakedefine GIT_SCHANNEL 1 +#cmakedefine GIT_HTTPPARSER_HTTPPARSER 1 +#cmakedefine GIT_HTTPPARSER_LLHTTP 1 +#cmakedefine GIT_HTTPPARSER_BUILTIN 1 + #cmakedefine GIT_SHA1_COLLISIONDETECT 1 #cmakedefine GIT_SHA1_WIN32 1 #cmakedefine GIT_SHA1_COMMON_CRYPTO 1 diff --git a/src/util/git2_util.h b/src/util/git2_util.h index c62dc24..5bf0981 100644 --- a/src/util/git2_util.h +++ b/src/util/git2_util.h @@ -12,6 +12,7 @@ #endif #include "git2/common.h" +#include "git2/sys/errors.h" #include "cc-compat.h" typedef struct git_str git_str; @@ -164,5 +165,6 @@ typedef struct git_str git_str; if (GIT_MULTIPLY_SIZET_OVERFLOW(out, nelem, elsize)) { return -1; } #include "util.h" +#include "ctype_compat.h" #endif diff --git a/src/util/integer.h b/src/util/integer.h index 6327717..a9e416c 100644 --- a/src/util/integer.h +++ b/src/util/integer.h @@ -89,7 +89,9 @@ GIT_INLINE(int) git__is_int(int64_t p) /* Use Microsoft's safe integer handling functions where available */ #elif defined(_MSC_VER) -# define ENABLE_INTSAFE_SIGNED_FUNCTIONS +# if !defined(ENABLE_INTSAFE_SIGNED_FUNCTIONS) +# define ENABLE_INTSAFE_SIGNED_FUNCTIONS +# endif # include <intsafe.h> # define git__add_sizet_overflow(out, one, two) \ diff --git a/src/util/net.c b/src/util/net.c index afd52ce..dede784 100644 --- a/src/util/net.c +++ b/src/util/net.c @@ -11,7 +11,6 @@ #include "posix.h" #include "str.h" -#include "http_parser.h" #include "runtime.h" #define DEFAULT_PORT_HTTP "80" @@ -22,7 +21,7 @@ #define GIT_NET_URL_PARSER_INIT { 0 } typedef struct { - int hierarchical : 1; + unsigned int hierarchical : 1; const char *scheme; const char *user; @@ -657,7 +656,7 @@ static bool has_at(const char *str) int git_net_url_parse_scp(git_net_url *url, const char *given) { const char *default_port = default_port_for_scheme("ssh"); - const char *c, *user, *host, *port, *path = NULL; + const char *c, *user, *host, *port = NULL, *path = NULL; size_t user_len = 0, host_len = 0, port_len = 0; unsigned short bracket = 0; diff --git a/src/util/process.h b/src/util/process.h new file mode 100644 index 0000000..3ada669 --- /dev/null +++ b/src/util/process.h @@ -0,0 +1,222 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_process_h__ +#define INCLUDE_process_h__ + +typedef struct git_process git_process; + +typedef struct { + unsigned int capture_in : 1, + capture_out : 1, + capture_err : 1, + exclude_env : 1; + + char *cwd; +} git_process_options; + +typedef enum { + GIT_PROCESS_STATUS_NONE, + GIT_PROCESS_STATUS_NORMAL, + GIT_PROCESS_STATUS_ERROR +} git_process_result_status; + +#define GIT_PROCESS_RESULT_INIT { GIT_PROCESS_STATUS_NONE } + +typedef struct { + git_process_result_status status; + int exitcode; + int signal; +} git_process_result; + +#define GIT_PROCESS_OPTIONS_INIT { 0 } + +#ifdef GIT_WIN32 +# define p_pid_t DWORD +#else +# define p_pid_t pid_t +#endif + +/** + * Create a new process. The command to run should be specified as the + * element of the `arg` array, execv-style. This should be the full path + * to the command to run, the PATH is not obeyed. + * + * This function will add the given environment variables (in `env`) + * to the current environment. Operations on environment variables + * are not thread safe, so you may not modify the environment during + * this call. You can avoid this by setting `exclude_env` in the + * options and providing the entire environment yourself. + * + * @param out location to store the process + * @param args the command (with arguments) to run + * @param args_len the length of the args array + * @param env environment variables to add (or NULL) + * @param env_len the length of the env len + * @param opts the options for creating the process + * @return 0 or an error code + */ +extern int git_process_new( + git_process **out, + const char **args, + size_t args_len, + const char **env, + size_t env_len, + git_process_options *opts); + +/** + * Create a new process. The command to run should be specified as the + * `cmdline` option - which is the full text of the command line as it + * would be specified or run by a user. The command to run will be + * looked up in the PATH. + * + * On Unix, this will be executed by the system's shell (`/bin/sh`) + * and may contain _Bourne-style_ shell quoting rules. On Windows, + * this will be passed to `CreateProcess`, and similarly, may + * contain _Windows-style_ shell quoting rules. + * + * This function will add the given environment variables (in `env`) + * to the current environment. Operations on environment variables + * are not thread safe, so you may not modify the environment during + * this call. You can avoid this by setting `exclude_env` in the + * options and providing the entire environment yourself. + */ +extern int git_process_new_from_cmdline( + git_process **out, + const char *cmdline, + const char **env, + size_t env_len, + git_process_options *opts); + +#ifdef GIT_WIN32 + +extern int git_process__appname( + git_str *out, + const char *cmdline); + +/* Windows path parsing is tricky; this helper function is for testing. */ +extern int git_process__cmdline( + git_str *out, + const char **in, + size_t in_len); + +#endif + +/* + * Whether the given string looks like a command line option (starts + * with a dash). This is useful for examining strings that will become + * cmdline arguments to ensure that they are not erroneously treated + * as an option. For example, arguments to `ssh`. + */ +GIT_INLINE(bool) git_process__is_cmdline_option(const char *str) +{ + return (str && str[0] == '-'); +} + +/** + * Start the process. + * + * @param process the process to start + * @return 0 or an error code + */ +extern int git_process_start(git_process *process); + +/** + * Returns the process id of the process. + * + * @param out pointer to a pid_t to store the process id + * @param process the process to query + * @return 0 or an error code + */ +extern int git_process_id(p_pid_t *out, git_process *process); + +/** + * Read from the process's stdout. The process must have been created with + * `capture_out` set to true. + * + * @param process the process to read from + * @param buf the buf to read into + * @param count maximum number of bytes to read + * @return number of bytes read or an error code + */ +extern ssize_t git_process_read(git_process *process, void *buf, size_t count); + +/** + * Read from the process's stderr. The process must have been created with + * `capture_err` set to true. + * + * @param process the process to read from + * @param buf the buf to read into + * @param count maximum number of bytes to read + * @return number of bytes read or an error code + */ +extern ssize_t git_process_read_err(git_process *process, void *buf, size_t count); + +/** + * Write to the process's stdin. The process must have been created with + * `capture_in` set to true. + * + * @param process the process to write to + * @param buf the buf to write + * @param count maximum number of bytes to write + * @return number of bytes written or an error code + */ +extern ssize_t git_process_write(git_process *process, const void *buf, size_t count); + +/** + * Wait for the process to finish. + * + * @param result the result of the process or NULL + * @param process the process to wait on + */ +extern int git_process_wait(git_process_result *result, git_process *process); + +/** + * Close the input pipe from the child. + * + * @param process the process to close the pipe on + */ +extern int git_process_close_in(git_process *process); + +/** + * Close the output pipe from the child. + * + * @param process the process to close the pipe on + */ +extern int git_process_close_out(git_process *process); + +/** + * Close the error pipe from the child. + * + * @param process the process to close the pipe on + */ +extern int git_process_close_err(git_process *process); + +/** + * Close all resources that are used by the process. This does not + * wait for the process to complete. + * + * @parma process the process to close + */ +extern int git_process_close(git_process *process); + +/** + * Place a human-readable error message in the given git buffer. + * + * @param msg the buffer to store the message + * @param result the process result that produced an error + */ +extern int git_process_result_msg(git_str *msg, git_process_result *result); + +/** + * Free a process structure + * + * @param process the process to free + */ +extern void git_process_free(git_process *process); + +#endif diff --git a/src/util/rand.c b/src/util/rand.c index 2ed0605..2b137a5 100644 --- a/src/util/rand.c +++ b/src/util/rand.c @@ -10,10 +10,6 @@ See <http://creativecommons.org/publicdomain/zero/1.0/>. */ #include "rand.h" #include "runtime.h" -#if defined(GIT_RAND_GETENTROPY) -# include <sys/random.h> -#endif - #if defined(GIT_WIN32) # include <wincrypt.h> #endif @@ -80,10 +76,10 @@ GIT_INLINE(int) getseed(uint64_t *seed) GIT_INLINE(int) getseed(uint64_t *seed) { struct timeval tv; - double loadavg[3]; int fd; # if defined(GIT_RAND_GETLOADAVG) + double loadavg[3]; bits convert; # endif @@ -129,8 +125,6 @@ GIT_INLINE(int) getseed(uint64_t *seed) convert.f = loadavg[0]; *seed ^= (convert.d >> 36); convert.f = loadavg[1]; *seed ^= (convert.d); convert.f = loadavg[2]; *seed ^= (convert.d >> 16); -# else - GIT_UNUSED(loadavg[0]); # endif *seed ^= git_time_monotonic(); diff --git a/src/util/regexp.c b/src/util/regexp.c index 0870088..eb45822 100644 --- a/src/util/regexp.c +++ b/src/util/regexp.c @@ -125,7 +125,7 @@ int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches, if ((data = pcre2_match_data_create(nmatches, NULL)) == NULL) { git_error_set_oom(); - goto out; + return -1; } if ((error = pcre2_match(*r, (const unsigned char *) string, strlen(string), diff --git a/src/util/str.c b/src/util/str.c index 0d405bf..0b07c81 100644 --- a/src/util/str.c +++ b/src/util/str.c @@ -485,8 +485,8 @@ int git_str_decode_percent( for (str_pos = 0; str_pos < str_len; buf->size++, str_pos++) { if (str[str_pos] == '%' && str_len > str_pos + 2 && - isxdigit(str[str_pos + 1]) && - isxdigit(str[str_pos + 2])) { + git__isxdigit(str[str_pos + 1]) && + git__isxdigit(str[str_pos + 2])) { buf->ptr[buf->size] = (HEX_DECODE(str[str_pos + 1]) << 4) + HEX_DECODE(str[str_pos + 2]); str_pos += 2; diff --git a/src/util/strlist.c b/src/util/strlist.c new file mode 100644 index 0000000..df5640c --- /dev/null +++ b/src/util/strlist.c @@ -0,0 +1,108 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include <stdio.h> + +#include "git2_util.h" +#include "vector.h" +#include "strlist.h" + +int git_strlist_copy(char ***out, const char **in, size_t len) +{ + char **dup; + size_t i; + + dup = git__calloc(len, sizeof(char *)); + GIT_ERROR_CHECK_ALLOC(dup); + + for (i = 0; i < len; i++) { + dup[i] = git__strdup(in[i]); + GIT_ERROR_CHECK_ALLOC(dup[i]); + } + + *out = dup; + return 0; +} + +int git_strlist_copy_with_null(char ***out, const char **in, size_t len) +{ + char **dup; + size_t new_len, i; + + GIT_ERROR_CHECK_ALLOC_ADD(&new_len, len, 1); + + dup = git__calloc(new_len, sizeof(char *)); + GIT_ERROR_CHECK_ALLOC(dup); + + for (i = 0; i < len; i++) { + dup[i] = git__strdup(in[i]); + GIT_ERROR_CHECK_ALLOC(dup[i]); + } + + *out = dup; + return 0; +} + +bool git_strlist_contains_prefix( + const char **strings, + size_t len, + const char *str, + size_t n) +{ + size_t i; + + for (i = 0; i < len; i++) { + if (strncmp(strings[i], str, n) == 0) + return true; + } + + return false; +} + +bool git_strlist_contains_key( + const char **strings, + size_t len, + const char *key, + char delimiter) +{ + const char *c; + + for (c = key; *c; c++) { + if (*c == delimiter) + break; + } + + return *c ? + git_strlist_contains_prefix(strings, len, key, (c - key)) : + false; +} + +void git_strlist_free(char **strings, size_t len) +{ + size_t i; + + if (!strings) + return; + + for (i = 0; i < len; i++) + git__free(strings[i]); + + git__free(strings); +} + +void git_strlist_free_with_null(char **strings) +{ + char **s; + + if (!strings) + return; + + for (s = strings; *s; s++) + git__free(*s); + + git__free(strings); +} diff --git a/src/util/strlist.h b/src/util/strlist.h new file mode 100644 index 0000000..68fbf8f --- /dev/null +++ b/src/util/strlist.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_runtime_h__ +#define INCLUDE_runtime_h__ + +#include "git2_util.h" + +extern int git_strlist_copy(char ***out, const char **in, size_t len); + +extern int git_strlist_copy_with_null( + char ***out, + const char **in, + size_t len); + +extern bool git_strlist_contains_prefix( + const char **strings, + size_t len, + const char *str, + size_t n); + +extern bool git_strlist_contains_key( + const char **strings, + size_t len, + const char *key, + char delimiter); + +extern void git_strlist_free(char **strings, size_t len); + +extern void git_strlist_free_with_null(char **strings); + +#endif diff --git a/src/util/unix/posix.h b/src/util/unix/posix.h index 778477e..60f27d3 100644 --- a/src/util/unix/posix.h +++ b/src/util/unix/posix.h @@ -54,8 +54,6 @@ GIT_INLINE(int) p_fsync(int fd) #define p_send(s,b,l,f) send(s,b,l,f) #define p_inet_pton(a, b, c) inet_pton(a, b, c) -#define p_strcasecmp(s1, s2) strcasecmp(s1, s2) -#define p_strncasecmp(s1, s2, c) strncasecmp(s1, s2, c) #define p_vsnprintf(b, c, f, a) vsnprintf(b, c, f, a) #define p_snprintf snprintf #define p_chdir(p) chdir(p) diff --git a/src/util/unix/process.c b/src/util/unix/process.c new file mode 100644 index 0000000..68c0384 --- /dev/null +++ b/src/util/unix/process.c @@ -0,0 +1,629 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include <stdio.h> +#include <sys/wait.h> +#include <signal.h> +#include <git2.h> + +#include "git2_util.h" +#include "vector.h" +#include "process.h" +#include "strlist.h" + +#ifdef __APPLE__ + #include <crt_externs.h> + #define environ (*_NSGetEnviron()) +#else + extern char **environ; +#endif + +struct git_process { + char **args; + char **env; + + char *cwd; + + unsigned int capture_in : 1, + capture_out : 1, + capture_err : 1; + + pid_t pid; + + int child_in; + int child_out; + int child_err; + git_process_result_status status; +}; + +GIT_INLINE(bool) is_delete_env(const char *env) +{ + char *c = strchr(env, '='); + + if (c == NULL) + return false; + + return *(c+1) == '\0'; +} + +static int merge_env( + char ***out, + const char **env, + size_t env_len, + bool exclude_env) +{ + git_vector merged = GIT_VECTOR_INIT; + char **kv, *dup; + size_t max, cnt; + int error = 0; + + for (max = env_len, kv = environ; !exclude_env && *kv; kv++) + max++; + + if ((error = git_vector_init(&merged, max, NULL)) < 0) + goto on_error; + + for (cnt = 0; env && cnt < env_len; cnt++) { + 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) + goto on_error; + } + + if (!exclude_env) { + for (kv = environ; *kv; kv++) { + 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) + goto on_error; + } + } + + if (merged.length == 0) { + *out = NULL; + error = 0; + goto on_error; + } + + git_vector_insert(&merged, NULL); + + *out = (char **)merged.contents; + + return 0; + +on_error: + git_vector_free_deep(&merged); + return error; +} + +int git_process_new( + git_process **out, + const char **args, + size_t args_len, + const char **env, + size_t env_len, + git_process_options *opts) +{ + git_process *process; + + GIT_ASSERT_ARG(out && args && args_len > 0); + + *out = NULL; + + process = git__calloc(sizeof(git_process), 1); + GIT_ERROR_CHECK_ALLOC(process); + + if (git_strlist_copy_with_null(&process->args, args, args_len) < 0 || + merge_env(&process->env, env, env_len, opts ? opts->exclude_env : false) < 0) { + git_process_free(process); + return -1; + } + + if (opts) { + process->capture_in = opts->capture_in; + process->capture_out = opts->capture_out; + process->capture_err = opts->capture_err; + + if (opts->cwd) { + process->cwd = git__strdup(opts->cwd); + GIT_ERROR_CHECK_ALLOC(process->cwd); + } + } + + process->child_in = -1; + process->child_out = -1; + process->child_err = -1; + process->status = -1; + + *out = process; + return 0; +} + +extern int git_process_new_from_cmdline( + git_process **out, + const char *cmdline, + const char **env, + size_t env_len, + git_process_options *opts) +{ + const char *args[] = { "/bin/sh", "-c", cmdline }; + + return git_process_new(out, + args, ARRAY_SIZE(args), env, env_len, opts); +} + +#define CLOSE_FD(fd) \ + if (fd >= 0) { \ + close(fd); \ + fd = -1; \ + } + +static int try_read_status(size_t *out, int fd, void *buf, size_t len) +{ + size_t read_len = 0; + int ret = -1; + + while (ret && read_len < len) { + ret = read(fd, buf + read_len, len - read_len); + + if (ret < 0 && errno != EAGAIN && errno != EINTR) { + git_error_set(GIT_ERROR_OS, "could not read child status"); + return -1; + } + + read_len += ret; + } + + *out = read_len; + return 0; +} + + +static int read_status(int fd) +{ + size_t status_len = sizeof(int) * 3, read_len = 0; + char buffer[status_len], fn[128]; + int error, fn_error, os_error, fn_len = 0; + + if ((error = try_read_status(&read_len, fd, buffer, status_len)) < 0) + return error; + + /* Immediate EOF indicates the exec succeeded. */ + if (read_len == 0) + return 0; + + if (read_len < status_len) { + git_error_set(GIT_ERROR_INVALID, "child status truncated"); + return -1; + } + + memcpy(&fn_error, &buffer[0], sizeof(int)); + memcpy(&os_error, &buffer[sizeof(int)], sizeof(int)); + memcpy(&fn_len, &buffer[sizeof(int) * 2], sizeof(int)); + + if (fn_len > 0) { + fn_len = min(fn_len, (int)(ARRAY_SIZE(fn) - 1)); + + if ((error = try_read_status(&read_len, fd, fn, fn_len)) < 0) + return error; + + fn[fn_len] = '\0'; + } else { + fn[0] = '\0'; + } + + if (fn_error) { + errno = os_error; + git_error_set(GIT_ERROR_OS, "could not %s", fn[0] ? fn : "(unknown)"); + } + + return fn_error; +} + +static bool try_write_status(int fd, const void *buf, size_t len) +{ + size_t write_len; + int ret; + + for (write_len = 0; write_len < len; ) { + ret = write(fd, buf + write_len, len - write_len); + + if (ret <= 0) + break; + + write_len += ret; + } + + return (len == write_len); +} + +static void write_status(int fd, const char *fn, int error, int os_error) +{ + size_t status_len = sizeof(int) * 3, fn_len; + char buffer[status_len]; + + fn_len = strlen(fn); + + if (fn_len > INT_MAX) + fn_len = INT_MAX; + + memcpy(&buffer[0], &error, sizeof(int)); + memcpy(&buffer[sizeof(int)], &os_error, sizeof(int)); + memcpy(&buffer[sizeof(int) * 2], &fn_len, sizeof(int)); + + /* Do our best effort to write all the status. */ + if (!try_write_status(fd, buffer, status_len)) + return; + + if (fn_len) + try_write_status(fd, fn, fn_len); +} + +int git_process_start(git_process *process) +{ + int in[2] = { -1, -1 }, out[2] = { -1, -1 }, + err[2] = { -1, -1 }, status[2] = { -1, -1 }; + int fdflags, state, error; + pid_t pid; + + /* Set up the pipes to read from/write to the process */ + if ((process->capture_in && pipe(in) < 0) || + (process->capture_out && pipe(out) < 0) || + (process->capture_err && pipe(err) < 0)) { + git_error_set(GIT_ERROR_OS, "could not create pipe"); + goto on_error; + } + + /* Set up a self-pipe for status from the forked process. */ + if (pipe(status) < 0 || + (fdflags = fcntl(status[1], F_GETFD)) < 0 || + fcntl(status[1], F_SETFD, fdflags | FD_CLOEXEC) < 0) { + git_error_set(GIT_ERROR_OS, "could not create pipe"); + goto on_error; + } + + switch (pid = fork()) { + case -1: + git_error_set(GIT_ERROR_OS, "could not fork"); + goto on_error; + + /* Child: start the process. */ + case 0: + /* Close the opposing side of the pipes */ + CLOSE_FD(status[0]); + + if (process->capture_in) { + CLOSE_FD(in[1]); + dup2(in[0], STDIN_FILENO); + } + + if (process->capture_out) { + CLOSE_FD(out[0]); + dup2(out[1], STDOUT_FILENO); + } + + if (process->capture_err) { + CLOSE_FD(err[0]); + dup2(err[1], STDERR_FILENO); + } + + if (process->cwd && (error = chdir(process->cwd)) < 0) { + write_status(status[1], "chdir", error, errno); + exit(0); + } + + /* + * Exec the process and write the results back if the + * call fails. If it succeeds, we'll close the status + * pipe (via CLOEXEC) and the parent will know. + */ + error = execve(process->args[0], + process->args, + process->env); + + write_status(status[1], "execve", error, errno); + exit(0); + + /* Parent: make sure the child process exec'd correctly. */ + default: + /* Close the opposing side of the pipes */ + CLOSE_FD(status[1]); + + if (process->capture_in) { + CLOSE_FD(in[0]); + process->child_in = in[1]; + } + + if (process->capture_out) { + CLOSE_FD(out[1]); + process->child_out = out[0]; + } + + if (process->capture_err) { + CLOSE_FD(err[1]); + process->child_err = err[0]; + } + + /* Try to read the status */ + process->status = status[0]; + if ((error = read_status(status[0])) < 0) { + waitpid(process->pid, &state, 0); + goto on_error; + } + + process->pid = pid; + return 0; + } + +on_error: + CLOSE_FD(in[0]); CLOSE_FD(in[1]); + CLOSE_FD(out[0]); CLOSE_FD(out[1]); + CLOSE_FD(err[0]); CLOSE_FD(err[1]); + CLOSE_FD(status[0]); CLOSE_FD(status[1]); + return -1; +} + +int git_process_id(p_pid_t *out, git_process *process) +{ + GIT_ASSERT(out && process); + + if (!process->pid) { + git_error_set(GIT_ERROR_INVALID, "process not running"); + return -1; + } + + *out = process->pid; + return 0; +} + +static ssize_t process_read(int fd, void *buf, size_t count) +{ + ssize_t ret; + + if (count > SSIZE_MAX) + count = SSIZE_MAX; + + if ((ret = read(fd, buf, count)) < 0) { + git_error_set(GIT_ERROR_OS, "could not read from child process"); + return -1; + } + + return ret; +} + +ssize_t git_process_read(git_process *process, void *buf, size_t count) +{ + GIT_ASSERT_ARG(process); + GIT_ASSERT(process->capture_out); + + return process_read(process->child_out, buf, count); +} + +ssize_t git_process_read_err(git_process *process, void *buf, size_t count) +{ + GIT_ASSERT_ARG(process); + GIT_ASSERT(process->capture_err); + + return process_read(process->child_err, buf, count); +} + +#ifdef GIT_THREADS + +# define signal_state sigset_t + +/* + * Since signal-handling is process-wide, we cannot simply use + * SIG_IGN to avoid SIGPIPE. Instead: http://www.microhowto.info:80/howto/ignore_sigpipe_without_affecting_other_threads_in_a_process.html + */ + +GIT_INLINE(int) disable_signals(sigset_t *saved_mask) +{ + sigset_t sigpipe_mask; + + sigemptyset(&sigpipe_mask); + sigaddset(&sigpipe_mask, SIGPIPE); + + if (pthread_sigmask(SIG_BLOCK, &sigpipe_mask, saved_mask) < 0) { + git_error_set(GIT_ERROR_OS, "could not configure signal mask"); + return -1; + } + + return 0; +} + +GIT_INLINE(int) restore_signals(sigset_t *saved_mask) +{ + sigset_t sigpipe_mask, pending; + int signal; + + sigemptyset(&sigpipe_mask); + sigaddset(&sigpipe_mask, SIGPIPE); + + if (sigpending(&pending) < 0) { + git_error_set(GIT_ERROR_OS, "could not examine pending signals"); + return -1; + } + + if (sigismember(&pending, SIGPIPE) == 1 && + sigwait(&sigpipe_mask, &signal) < 0) { + git_error_set(GIT_ERROR_OS, "could not wait for (blocking) signal delivery"); + return -1; + } + + if (pthread_sigmask(SIG_SETMASK, saved_mask, 0) < 0) { + git_error_set(GIT_ERROR_OS, "could not configure signal mask"); + return -1; + } + + return 0; +} + +#else + +# define signal_state struct sigaction + +GIT_INLINE(int) disable_signals(struct sigaction *saved_handler) +{ + struct sigaction ign_handler = { 0 }; + + ign_handler.sa_handler = SIG_IGN; + + if (sigaction(SIGPIPE, &ign_handler, saved_handler) < 0) { + git_error_set(GIT_ERROR_OS, "could not configure signal handler"); + return -1; + } + + return 0; +} + +GIT_INLINE(int) restore_signals(struct sigaction *saved_handler) +{ + if (sigaction(SIGPIPE, saved_handler, NULL) < 0) { + git_error_set(GIT_ERROR_OS, "could not configure signal handler"); + return -1; + } + + return 0; +} + +#endif + +ssize_t git_process_write(git_process *process, const void *buf, size_t count) +{ + signal_state saved_signal; + ssize_t ret; + + GIT_ASSERT_ARG(process); + GIT_ASSERT(process->capture_in); + + if (count > SSIZE_MAX) + count = SSIZE_MAX; + + if (disable_signals(&saved_signal) < 0) + return -1; + + if ((ret = write(process->child_in, buf, count)) < 0) + git_error_set(GIT_ERROR_OS, "could not write to child process"); + + if (restore_signals(&saved_signal) < 0) + return -1; + + return (ret < 0) ? -1 : ret; +} + +int git_process_close_in(git_process *process) +{ + if (!process->capture_in) { + git_error_set(GIT_ERROR_INVALID, "input is not open"); + return -1; + } + + CLOSE_FD(process->child_in); + return 0; +} + +int git_process_close_out(git_process *process) +{ + if (!process->capture_out) { + git_error_set(GIT_ERROR_INVALID, "output is not open"); + return -1; + } + + CLOSE_FD(process->child_out); + return 0; +} + +int git_process_close_err(git_process *process) +{ + if (!process->capture_err) { + git_error_set(GIT_ERROR_INVALID, "error is not open"); + return -1; + } + + CLOSE_FD(process->child_err); + return 0; +} + +int git_process_close(git_process *process) +{ + CLOSE_FD(process->child_in); + CLOSE_FD(process->child_out); + CLOSE_FD(process->child_err); + + return 0; +} + +int git_process_wait(git_process_result *result, git_process *process) +{ + int state; + + if (result) + memset(result, 0, sizeof(git_process_result)); + + if (!process->pid) { + git_error_set(GIT_ERROR_INVALID, "process is stopped"); + return -1; + } + + if (waitpid(process->pid, &state, 0) < 0) { + git_error_set(GIT_ERROR_OS, "could not wait for child"); + return -1; + } + + process->pid = 0; + + if (result) { + if (WIFEXITED(state)) { + result->status = GIT_PROCESS_STATUS_NORMAL; + result->exitcode = WEXITSTATUS(state); + } else if (WIFSIGNALED(state)) { + result->status = GIT_PROCESS_STATUS_ERROR; + result->signal = WTERMSIG(state); + } else { + result->status = GIT_PROCESS_STATUS_ERROR; + } + } + + return 0; +} + +int git_process_result_msg(git_str *out, git_process_result *result) +{ + if (result->status == GIT_PROCESS_STATUS_NONE) { + return git_str_puts(out, "process not started"); + } else if (result->status == GIT_PROCESS_STATUS_NORMAL) { + return git_str_printf(out, "process exited with code %d", + result->exitcode); + } else if (result->signal) { + return git_str_printf(out, "process exited on signal %d", + result->signal); + } + + return git_str_puts(out, "unknown error"); +} + +void git_process_free(git_process *process) +{ + if (!process) + return; + + if (process->pid) + git_process_close(process); + + git__free(process->cwd); + git_strlist_free_with_null(process->args); + git_strlist_free_with_null(process->env); + git__free(process); +} diff --git a/src/util/util.c b/src/util/util.c index c8e8303..e86bcee 100644 --- a/src/util/util.c +++ b/src/util/util.c @@ -623,12 +623,12 @@ int git__bsearch_r( */ int git__strcmp_cb(const void *a, const void *b) { - return strcmp((const char *)a, (const char *)b); + return git__strcmp((const char *)a, (const char *)b); } int git__strcasecmp_cb(const void *a, const void *b) { - return strcasecmp((const char *)a, (const char *)b); + return git__strcasecmp((const char *)a, (const char *)b); } int git__parse_bool(int *out, const char *value) diff --git a/src/util/util.h b/src/util/util.h index 7f178b1..2ed0051 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -83,15 +83,6 @@ extern char *git__strsep(char **end, const char *sep); extern void git__strntolower(char *str, size_t len); extern void git__strtolower(char *str); -#ifdef GIT_WIN32 -GIT_INLINE(int) git__tolower(int c) -{ - return (c >= 'A' && c <= 'Z') ? (c + 32) : c; -} -#else -# define git__tolower(a) tolower(a) -#endif - extern size_t git__linenlen(const char *buffer, size_t buffer_len); GIT_INLINE(const char *) git__next_line(const char *s) @@ -249,26 +240,6 @@ GIT_INLINE(size_t) git__size_t_powerof2(size_t v) return git__size_t_bitmask(v) + 1; } -GIT_INLINE(bool) git__isupper(int c) -{ - return (c >= 'A' && c <= 'Z'); -} - -GIT_INLINE(bool) git__isalpha(int c) -{ - return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')); -} - -GIT_INLINE(bool) git__isdigit(int c) -{ - return (c >= '0' && c <= '9'); -} - -GIT_INLINE(bool) git__isspace(int c) -{ - return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v'); -} - GIT_INLINE(bool) git__isspace_nonlf(int c) { return (c == ' ' || c == '\t' || c == '\f' || c == '\r' || c == '\v'); @@ -279,11 +250,6 @@ GIT_INLINE(bool) git__iswildcard(int c) return (c == '*' || c == '?' || c == '['); } -GIT_INLINE(bool) git__isxdigit(int c) -{ - return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); -} - /* * Parse a string value as a boolean, just like Core Git does. * diff --git a/src/util/win32/posix_w32.c b/src/util/win32/posix_w32.c index 3fec469..ace2320 100644 --- a/src/util/win32/posix_w32.c +++ b/src/util/win32/posix_w32.c @@ -787,13 +787,19 @@ int p_rmdir(const char *path) char *p_realpath(const char *orig_path, char *buffer) { git_win32_path orig_path_w, buffer_w; + DWORD long_len; if (git_win32_path_from_utf8(orig_path_w, orig_path) < 0) return NULL; - /* Note that if the path provided is a relative path, then the current directory + /* + * POSIX realpath performs two functions: first, it turns relative + * paths into absolute paths. For this, we need GetFullPathName. + * + * Note that if the path provided is a relative path, then the current directory * is used to resolve the path -- which is a concurrency issue because the current - * directory is a process-wide variable. */ + * directory is a process-wide variable. + */ if (!GetFullPathNameW(orig_path_w, GIT_WIN_PATH_UTF16, buffer_w, NULL)) { if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) errno = ENAMETOOLONG; @@ -803,9 +809,26 @@ char *p_realpath(const char *orig_path, char *buffer) return NULL; } - /* The path must exist. */ - if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) { - errno = ENOENT; + /* + * Then, the path is canonicalized. eg, on macOS, + * "/TMP" -> "/private/tmp". For this, we need GetLongPathName. + */ + if ((long_len = GetLongPathNameW(buffer_w, buffer_w, GIT_WIN_PATH_UTF16)) == 0) { + DWORD error = GetLastError(); + + if (error == ERROR_FILE_NOT_FOUND || + error == ERROR_PATH_NOT_FOUND) + errno = ENOENT; + else if (error == ERROR_ACCESS_DENIED) + errno = EPERM; + else + errno = EINVAL; + + return NULL; + } + + if (long_len > GIT_WIN_PATH_UTF16) { + errno = ENAMETOOLONG; return NULL; } @@ -821,7 +844,6 @@ char *p_realpath(const char *orig_path, char *buffer) return NULL; git_fs_path_mkposix(buffer); - return buffer; } diff --git a/src/util/win32/process.c b/src/util/win32/process.c new file mode 100644 index 0000000..bb52245 --- /dev/null +++ b/src/util/win32/process.c @@ -0,0 +1,506 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include <stdio.h> +#include <git2.h> + +#include "git2_util.h" +#include "process.h" +#include "strlist.h" + +#ifndef DWORD_MAX +# define DWORD_MAX INT32_MAX +#endif + +#define ENV_MAX 32767 + +struct git_process { + wchar_t *appname; + wchar_t *cmdline; + wchar_t *env; + + wchar_t *cwd; + + unsigned int capture_in : 1, + capture_out : 1, + capture_err : 1; + + PROCESS_INFORMATION process_info; + + HANDLE child_in; + HANDLE child_out; + HANDLE child_err; + + git_process_result_status status; +}; + +/* + * Windows processes have a single command-line that is split by the + * invoked application into arguments (instead of an array of + * command-line arguments). This command-line is split by space or + * tab delimiters, unless that whitespace is within a double quote. + * Literal double-quotes themselves can be escaped by a backslash, + * but only when not within double quotes. Literal backslashes can + * be escaped by a backslash. + * + * Effectively, this means that instead of thinking about quoting + * individual strings, think about double quotes as an escaping + * mechanism for whitespace. + * + * In other words (using ` as a string boundary): + * [ `foo`, `bar` ] => `foo bar` + * [ `foo bar` ] => `foo" "bar` + * [ `foo bar`, `foo bar` ] => `foo" "bar foo" "bar` + * [ `foo "bar" foo` ] => `foo" "\"bar\"" "foo` + */ +int git_process__cmdline( + git_str *out, + const char **in, + size_t in_len) +{ + bool quoted = false; + const char *c; + size_t i; + + for (i = 0; i < in_len; i++) { + /* Arguments are delimited by an unquoted space */ + if (i) + git_str_putc(out, ' '); + + for (c = in[i]; *c; c++) { + /* Start or stop quoting spaces within an argument */ + if ((*c == ' ' || *c == '\t') && !quoted) { + git_str_putc(out, '"'); + quoted = true; + } else if (*c != ' ' && *c != '\t' && quoted) { + git_str_putc(out, '"'); + quoted = false; + } + + /* Escape double-quotes and backslashes */ + if (*c == '"' || *c == '\\') + git_str_putc(out, '\\'); + + git_str_putc(out, *c); + } + } + + return git_str_oom(out) ? -1 : 0; +} + +GIT_INLINE(bool) is_delete_env(const char *env) +{ + char *c = strchr(env, '='); + + if (c == NULL) + return false; + + return *(c+1) == '\0'; +} + +static int merge_env(wchar_t **out, const char **in, size_t in_len, bool exclude_env) +{ + git_str merged = GIT_STR_INIT; + wchar_t *in16 = NULL, *env = NULL, *e; + char *e8 = NULL; + size_t e_len; + int ret = 0; + size_t i; + + *out = NULL; + + in16 = git__malloc(ENV_MAX * sizeof(wchar_t)); + GIT_ERROR_CHECK_ALLOC(in16); + + e8 = git__malloc(ENV_MAX); + GIT_ERROR_CHECK_ALLOC(e8); + + for (i = 0; in && i < in_len; i++) { + if (is_delete_env(in[i])) + continue; + + if ((ret = git_utf8_to_16(in16, ENV_MAX, in[i])) < 0) + goto done; + + git_str_put(&merged, (const char *)in16, ret * 2); + git_str_put(&merged, "\0\0", 2); + } + + if (!exclude_env) { + env = GetEnvironmentStringsW(); + + for (e = env; *e; e += (e_len + 1)) { + e_len = wcslen(e); + + if ((ret = git_utf8_from_16(e8, ENV_MAX, e)) < 0) + goto done; + + if (git_strlist_contains_key(in, in_len, e8, '=')) + continue; + + git_str_put(&merged, (const char *)e, e_len * 2); + git_str_put(&merged, "\0\0", 2); + } + } + + git_str_put(&merged, "\0\0", 2); + + *out = (wchar_t *)git_str_detach(&merged); + +done: + if (env) + FreeEnvironmentStringsW(env); + + git_str_dispose(&merged); + git__free(e8); + git__free(in16); + + return ret < 0 ? -1 : 0; +} + +static int process_new( + git_process **out, + const char *appname, + const char *cmdline, + const char **env, + size_t env_len, + git_process_options *opts) +{ + git_process *process; + int error = 0; + + *out = NULL; + + process = git__calloc(1, sizeof(git_process)); + GIT_ERROR_CHECK_ALLOC(process); + + if (appname && + git_utf8_to_16_alloc(&process->appname, appname) < 0) { + error = -1; + goto done; + } + + if (git_utf8_to_16_alloc(&process->cmdline, cmdline) < 0) { + error = -1; + goto done; + } + + if (opts && opts->cwd && + git_utf8_to_16_alloc(&process->cwd, opts->cwd) < 0) { + error = -1; + goto done; + } + + if (env && (error = merge_env(&process->env, env, env_len, opts && opts->exclude_env) < 0)) + goto done; + + if (opts) { + process->capture_in = opts->capture_in; + process->capture_out = opts->capture_out; + process->capture_err = opts->capture_err; + } + +done: + if (error) + git_process_free(process); + else + *out = process; + + return error; +} + +int git_process_new_from_cmdline( + git_process **out, + const char *cmdline, + const char **env, + size_t env_len, + git_process_options *opts) +{ + GIT_ASSERT_ARG(out && cmdline); + + return process_new(out, NULL, cmdline, env, env_len, opts); +} + +int git_process_new( + git_process **out, + const char **args, + size_t args_len, + const char **env, + size_t env_len, + git_process_options *opts) +{ + git_str cmdline = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(out && args && args_len > 0); + + if ((error = git_process__cmdline(&cmdline, args, args_len)) < 0) + goto done; + + error = process_new(out, args[0], cmdline.ptr, env, env_len, opts); + +done: + git_str_dispose(&cmdline); + return error; +} + +#define CLOSE_HANDLE(h) do { if ((h) != NULL) CloseHandle(h); } while(0) + +int git_process_start(git_process *process) +{ + STARTUPINFOW startup_info; + SECURITY_ATTRIBUTES security_attrs; + DWORD flags = CREATE_UNICODE_ENVIRONMENT; + HANDLE in[2] = { NULL, NULL }, + out[2] = { NULL, NULL }, + err[2] = { NULL, NULL }; + + memset(&security_attrs, 0, sizeof(SECURITY_ATTRIBUTES)); + security_attrs.bInheritHandle = TRUE; + + memset(&startup_info, 0, sizeof(STARTUPINFOW)); + startup_info.cb = sizeof(STARTUPINFOW); + startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE); + + if (process->capture_in) { + if (!CreatePipe(&in[0], &in[1], &security_attrs, 0) || + !SetHandleInformation(in[1], HANDLE_FLAG_INHERIT, 0)) { + git_error_set(GIT_ERROR_OS, "could not create pipe"); + goto on_error; + } + + startup_info.hStdInput = in[0]; + startup_info.dwFlags |= STARTF_USESTDHANDLES; + } + + if (process->capture_out) { + if (!CreatePipe(&out[0], &out[1], &security_attrs, 0) || + !SetHandleInformation(out[0], HANDLE_FLAG_INHERIT, 0)) { + git_error_set(GIT_ERROR_OS, "could not create pipe"); + goto on_error; + } + + startup_info.hStdOutput = out[1]; + startup_info.dwFlags |= STARTF_USESTDHANDLES; + } + + if (process->capture_err) { + if (!CreatePipe(&err[0], &err[1], &security_attrs, 0) || + !SetHandleInformation(err[0], HANDLE_FLAG_INHERIT, 0)) { + git_error_set(GIT_ERROR_OS, "could not create pipe"); + goto on_error; + } + + startup_info.hStdError = err[1]; + startup_info.dwFlags |= STARTF_USESTDHANDLES; + } + + memset(&process->process_info, 0, sizeof(PROCESS_INFORMATION)); + + if (!CreateProcessW(process->appname, process->cmdline, + NULL, NULL, TRUE, flags, process->env, + process->cwd, + &startup_info, + &process->process_info)) { + git_error_set(GIT_ERROR_OS, "could not create process"); + goto on_error; + } + + CLOSE_HANDLE(in[0]); process->child_in = in[1]; + CLOSE_HANDLE(out[1]); process->child_out = out[0]; + CLOSE_HANDLE(err[1]); process->child_err = err[0]; + + return 0; + +on_error: + CLOSE_HANDLE(in[0]); CLOSE_HANDLE(in[1]); + CLOSE_HANDLE(out[0]); CLOSE_HANDLE(out[1]); + CLOSE_HANDLE(err[0]); CLOSE_HANDLE(err[1]); + return -1; +} + +int git_process_id(p_pid_t *out, git_process *process) +{ + GIT_ASSERT(out && process); + + if (!process->process_info.dwProcessId) { + git_error_set(GIT_ERROR_INVALID, "process not running"); + return -1; + } + + *out = process->process_info.dwProcessId; + return 0; +} + +ssize_t git_process_read(git_process *process, void *buf, size_t count) +{ + DWORD ret; + + if (count > DWORD_MAX) + count = DWORD_MAX; + if (count > SSIZE_MAX) + count = SSIZE_MAX; + + if (!ReadFile(process->child_out, buf, (DWORD)count, &ret, NULL)) { + if (GetLastError() == ERROR_BROKEN_PIPE) + return 0; + + git_error_set(GIT_ERROR_OS, "could not read"); + return -1; + } + + return ret; +} + +ssize_t git_process_write(git_process *process, const void *buf, size_t count) +{ + DWORD ret; + + if (count > DWORD_MAX) + count = DWORD_MAX; + if (count > SSIZE_MAX) + count = SSIZE_MAX; + + if (!WriteFile(process->child_in, buf, (DWORD)count, &ret, NULL)) { + git_error_set(GIT_ERROR_OS, "could not write"); + return -1; + } + + return ret; +} + +int git_process_close_in(git_process *process) +{ + if (!process->capture_in) { + git_error_set(GIT_ERROR_INVALID, "input is not open"); + return -1; + } + + if (process->child_in) { + CloseHandle(process->child_in); + process->child_in = NULL; + } + + return 0; +} + +int git_process_close_out(git_process *process) +{ + if (!process->capture_out) { + git_error_set(GIT_ERROR_INVALID, "output is not open"); + return -1; + } + + if (process->child_out) { + CloseHandle(process->child_out); + process->child_out = NULL; + } + + return 0; +} + +int git_process_close_err(git_process *process) +{ + if (!process->capture_err) { + git_error_set(GIT_ERROR_INVALID, "error is not open"); + return -1; + } + + if (process->child_err) { + CloseHandle(process->child_err); + process->child_err = NULL; + } + + return 0; +} + +int git_process_close(git_process *process) +{ + if (process->child_in) { + CloseHandle(process->child_in); + process->child_in = NULL; + } + + if (process->child_out) { + CloseHandle(process->child_out); + process->child_out = NULL; + } + + if (process->child_err) { + CloseHandle(process->child_err); + process->child_err = NULL; + } + + CloseHandle(process->process_info.hProcess); + process->process_info.hProcess = NULL; + + CloseHandle(process->process_info.hThread); + process->process_info.hThread = NULL; + + return 0; +} + +int git_process_wait(git_process_result *result, git_process *process) +{ + DWORD exitcode; + + if (result) + memset(result, 0, sizeof(git_process_result)); + + if (!process->process_info.dwProcessId) { + git_error_set(GIT_ERROR_INVALID, "process is stopped"); + return -1; + } + + if (WaitForSingleObject(process->process_info.hProcess, INFINITE) == WAIT_FAILED) { + git_error_set(GIT_ERROR_OS, "could not wait for process"); + return -1; + } + + if (!GetExitCodeProcess(process->process_info.hProcess, &exitcode)) { + git_error_set(GIT_ERROR_OS, "could not get process exit code"); + return -1; + } + + result->status = GIT_PROCESS_STATUS_NORMAL; + result->exitcode = exitcode; + + memset(&process->process_info, 0, sizeof(PROCESS_INFORMATION)); + return 0; +} + +int git_process_result_msg(git_str *out, git_process_result *result) +{ + if (result->status == GIT_PROCESS_STATUS_NONE) { + return git_str_puts(out, "process not started"); + } else if (result->status == GIT_PROCESS_STATUS_NORMAL) { + return git_str_printf(out, "process exited with code %d", + result->exitcode); + } else if (result->signal) { + return git_str_printf(out, "process exited on signal %d", + result->signal); + } + + return git_str_puts(out, "unknown error"); +} + +void git_process_free(git_process *process) +{ + if (!process) + return; + + if (process->process_info.hProcess) + git_process_close(process); + + git__free(process->env); + git__free(process->cwd); + git__free(process->cmdline); + git__free(process->appname); + git__free(process); +} |