summaryrefslogtreecommitdiffstats
path: root/src/util
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/util/array.h35
-rw-r--r--src/util/ctype_compat.h70
-rw-r--r--src/util/date.c26
-rw-r--r--src/util/errors.c401
-rw-r--r--src/util/errors.h (renamed from src/libgit2/errors.h)38
-rw-r--r--src/util/fs_path.c19
-rw-r--r--src/util/fs_path.h23
-rw-r--r--src/util/futils.h2
-rw-r--r--src/util/git2_features.h.in8
-rw-r--r--src/util/git2_util.h2
-rw-r--r--src/util/integer.h4
-rw-r--r--src/util/net.c5
-rw-r--r--src/util/process.h222
-rw-r--r--src/util/rand.c8
-rw-r--r--src/util/regexp.c2
-rw-r--r--src/util/str.c4
-rw-r--r--src/util/strlist.c108
-rw-r--r--src/util/strlist.h36
-rw-r--r--src/util/unix/posix.h2
-rw-r--r--src/util/unix/process.c629
-rw-r--r--src/util/util.c4
-rw-r--r--src/util/util.h34
-rw-r--r--src/util/win32/posix_w32.c34
-rw-r--r--src/util/win32/process.c506
24 files changed, 2111 insertions, 111 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/libgit2/errors.h b/src/util/errors.h
index 772c7ba..8d58775 100644
--- a/src/libgit2/errors.h
+++ b/src/util/errors.h
@@ -8,7 +8,11 @@
#ifndef INCLUDE_errors_h__
#define INCLUDE_errors_h__
-#include "common.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.
@@ -16,6 +20,11 @@
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
@@ -27,9 +36,8 @@ GIT_INLINE(int) git_error_set_after_callback_function(
int error_code, const char *action)
{
if (error_code) {
- const git_error *e = git_error_last();
- if (!e || !e->message)
- git_error_set(e ? e->klass : GIT_ERROR_CALLBACK,
+ if (!git_error_exists())
+ git_error_set(GIT_ERROR_CALLBACK,
"%s callback returned %d", action, error_code);
}
return error_code;
@@ -54,27 +62,23 @@ int git_error_system_last(void);
void git_error_system_set(int code);
/**
- * Structure to preserve libgit2 error state
- */
-typedef struct {
- int error_code;
- unsigned int oom : 1;
- git_error error_msg;
-} git_error_state;
-
-/**
* 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_state_capture(git_error_state *state, int error_code);
+extern int git_error_save(git_error **out);
/**
- * Restore error state to a previous value, returning saved error code.
+ * 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_state_restore(git_error_state *state);
+extern int git_error_restore(git_error *error);
/** Free an error state. */
-extern void git_error_state_free(git_error_state *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);
+}