summaryrefslogtreecommitdiffstats
path: root/src/util
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/util/CMakeLists.txt79
-rw-r--r--src/util/alloc.c115
-rw-r--r--src/util/alloc.h65
-rw-r--r--src/util/allocators/failalloc.c32
-rw-r--r--src/util/allocators/failalloc.h17
-rw-r--r--src/util/allocators/stdalloc.c47
-rw-r--r--src/util/allocators/stdalloc.h17
-rw-r--r--src/util/allocators/win32_leakcheck.c50
-rw-r--r--src/util/allocators/win32_leakcheck.h17
-rw-r--r--src/util/array.h129
-rw-r--r--src/util/assert_safe.h74
-rw-r--r--src/util/bitvec.h75
-rw-r--r--src/util/cc-compat.h108
-rw-r--r--src/util/date.c899
-rw-r--r--src/util/date.h33
-rw-r--r--src/util/filebuf.c600
-rw-r--r--src/util/filebuf.h107
-rw-r--r--src/util/fs_path.c2066
-rw-r--r--src/util/fs_path.h790
-rw-r--r--src/util/futils.c1236
-rw-r--r--src/util/futils.h403
-rw-r--r--src/util/git2_features.h.in68
-rw-r--r--src/util/git2_util.h168
-rw-r--r--src/util/hash.c158
-rw-r--r--src/util/hash.h61
-rw-r--r--src/util/hash/builtin.c53
-rw-r--r--src/util/hash/builtin.h19
-rw-r--r--src/util/hash/collisiondetect.c48
-rw-r--r--src/util/hash/collisiondetect.h19
-rw-r--r--src/util/hash/common_crypto.c112
-rw-r--r--src/util/hash/common_crypto.h27
-rw-r--r--src/util/hash/mbedtls.c92
-rw-r--r--src/util/hash/mbedtls.h29
-rw-r--r--src/util/hash/openssl.c195
-rw-r--r--src/util/hash/openssl.h45
-rw-r--r--src/util/hash/rfc6234/sha.h243
-rw-r--r--src/util/hash/rfc6234/sha224-256.c601
-rw-r--r--src/util/hash/sha.h70
-rw-r--r--src/util/hash/sha1dc/sha1.c1909
-rw-r--r--src/util/hash/sha1dc/sha1.h110
-rw-r--r--src/util/hash/sha1dc/ubc_check.c372
-rw-r--r--src/util/hash/sha1dc/ubc_check.h52
-rw-r--r--src/util/hash/win32.c549
-rw-r--r--src/util/hash/win32.h60
-rw-r--r--src/util/integer.h218
-rw-r--r--src/util/khash.h615
-rw-r--r--src/util/map.h46
-rw-r--r--src/util/net.c1154
-rw-r--r--src/util/net.h110
-rw-r--r--src/util/pool.c260
-rw-r--r--src/util/pool.h146
-rw-r--r--src/util/posix.c357
-rw-r--r--src/util/posix.h220
-rw-r--r--src/util/pqueue.c125
-rw-r--r--src/util/pqueue.h59
-rw-r--r--src/util/rand.c236
-rw-r--r--src/util/rand.h37
-rw-r--r--src/util/regexp.c221
-rw-r--r--src/util/regexp.h97
-rw-r--r--src/util/runtime.c162
-rw-r--r--src/util/runtime.h62
-rw-r--r--src/util/sortedcache.c380
-rw-r--r--src/util/sortedcache.h182
-rw-r--r--src/util/staticstr.h66
-rw-r--r--src/util/str.c1372
-rw-r--r--src/util/str.h357
-rw-r--r--src/util/strmap.c100
-rw-r--r--src/util/strmap.h131
-rw-r--r--src/util/strnlen.h24
-rw-r--r--src/util/thread.c140
-rw-r--r--src/util/thread.h480
-rw-r--r--src/util/tsort.c382
-rw-r--r--src/util/unix/map.c76
-rw-r--r--src/util/unix/posix.h104
-rw-r--r--src/util/unix/pthread.h57
-rw-r--r--src/util/unix/realpath.c32
-rw-r--r--src/util/utf8.c150
-rw-r--r--src/util/utf8.h52
-rw-r--r--src/util/util.c824
-rw-r--r--src/util/util.h396
-rw-r--r--src/util/varint.c43
-rw-r--r--src/util/varint.h17
-rw-r--r--src/util/vector.c431
-rw-r--r--src/util/vector.h128
-rw-r--r--src/util/wildmatch.c320
-rw-r--r--src/util/wildmatch.h23
-rw-r--r--src/util/win32/dir.c122
-rw-r--r--src/util/win32/dir.h44
-rw-r--r--src/util/win32/error.c53
-rw-r--r--src/util/win32/error.h15
-rw-r--r--src/util/win32/map.c141
-rw-r--r--src/util/win32/mingw-compat.h23
-rw-r--r--src/util/win32/msvc-compat.h36
-rw-r--r--src/util/win32/path_w32.c642
-rw-r--r--src/util/win32/path_w32.h91
-rw-r--r--src/util/win32/posix.h62
-rw-r--r--src/util/win32/posix_w32.c1047
-rw-r--r--src/util/win32/precompiled.c1
-rw-r--r--src/util/win32/precompiled.h21
-rw-r--r--src/util/win32/reparse.h57
-rw-r--r--src/util/win32/thread.c262
-rw-r--r--src/util/win32/thread.h64
-rw-r--r--src/util/win32/utf-conv.c144
-rw-r--r--src/util/win32/utf-conv.h127
-rw-r--r--src/util/win32/version.h37
-rw-r--r--src/util/win32/w32_buffer.c57
-rw-r--r--src/util/win32/w32_buffer.h19
-rw-r--r--src/util/win32/w32_common.h48
-rw-r--r--src/util/win32/w32_leakcheck.c581
-rw-r--r--src/util/win32/w32_leakcheck.h222
-rw-r--r--src/util/win32/w32_util.c126
-rw-r--r--src/util/win32/w32_util.h144
-rw-r--r--src/util/win32/win32-compat.h52
-rw-r--r--src/util/zstream.c210
-rw-r--r--src/util/zstream.h54
115 files changed, 26913 insertions, 0 deletions
diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt
new file mode 100644
index 0000000..ee35eb9
--- /dev/null
+++ b/src/util/CMakeLists.txt
@@ -0,0 +1,79 @@
+# util: a shared library for common utility functions for libgit2 projects
+
+add_library(util OBJECT)
+set_target_properties(util PROPERTIES C_STANDARD 90)
+set_target_properties(util PROPERTIES C_EXTENSIONS OFF)
+
+configure_file(git2_features.h.in git2_features.h)
+
+set(UTIL_INCLUDES
+ "${PROJECT_BINARY_DIR}/src/util"
+ "${PROJECT_BINARY_DIR}/include"
+ "${PROJECT_SOURCE_DIR}/src/util"
+ "${PROJECT_SOURCE_DIR}/include")
+
+file(GLOB UTIL_SRC *.c *.h allocators/*.c allocators/*.h hash.h)
+list(SORT UTIL_SRC)
+
+#
+# Platform specific sources
+#
+
+if(WIN32 AND NOT CYGWIN)
+ file(GLOB UTIL_SRC_OS win32/*.c win32/*.h)
+ list(SORT UTIL_SRC_OS)
+elseif(NOT AMIGA)
+ file(GLOB UTIL_SRC_OS unix/*.c unix/*.h)
+ list(SORT UTIL_SRC_OS)
+endif()
+
+#
+# Hash backend selection
+#
+
+if(USE_SHA1 STREQUAL "CollisionDetection")
+ file(GLOB UTIL_SRC_SHA1 hash/collisiondetect.* hash/sha1dc/*)
+ target_compile_definitions(util PRIVATE SHA1DC_NO_STANDARD_INCLUDES=1)
+ target_compile_definitions(util PRIVATE SHA1DC_CUSTOM_INCLUDE_SHA1_C=\"git2_util.h\")
+ target_compile_definitions(util PRIVATE SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C=\"git2_util.h\")
+elseif(USE_SHA1 STREQUAL "OpenSSL" OR USE_SHA1 STREQUAL "OpenSSL-Dynamic")
+ add_definitions(-DOPENSSL_API_COMPAT=0x10100000L)
+ file(GLOB UTIL_SRC_SHA1 hash/openssl.*)
+elseif(USE_SHA1 STREQUAL "CommonCrypto")
+ file(GLOB UTIL_SRC_SHA1 hash/common_crypto.*)
+elseif(USE_SHA1 STREQUAL "mbedTLS")
+ file(GLOB UTIL_SRC_SHA1 hash/mbedtls.*)
+elseif(USE_SHA1 STREQUAL "Win32")
+ file(GLOB UTIL_SRC_SHA1 hash/win32.*)
+else()
+ message(FATAL_ERROR "Asked for unknown SHA1 backend: ${USE_SHA1}")
+endif()
+
+list(SORT UTIL_SRC_SHA1)
+
+if(USE_SHA256 STREQUAL "Builtin")
+ file(GLOB UTIL_SRC_SHA256 hash/builtin.* hash/rfc6234/*)
+elseif(USE_SHA256 STREQUAL "OpenSSL" OR USE_SHA256 STREQUAL "OpenSSL-Dynamic")
+ add_definitions(-DOPENSSL_API_COMPAT=0x10100000L)
+ file(GLOB UTIL_SRC_SHA256 hash/openssl.*)
+elseif(USE_SHA256 STREQUAL "CommonCrypto")
+ file(GLOB UTIL_SRC_SHA256 hash/common_crypto.*)
+elseif(USE_SHA256 STREQUAL "mbedTLS")
+ file(GLOB UTIL_SRC_SHA256 hash/mbedtls.*)
+elseif(USE_SHA256 STREQUAL "Win32")
+ file(GLOB UTIL_SRC_SHA256 hash/win32.*)
+else()
+ message(FATAL_ERROR "Asked for unknown SHA256 backend: ${USE_SHA256}")
+endif()
+
+list(SORT UTIL_SRC_SHA256)
+
+#
+# Build the library
+#
+
+target_sources(util PRIVATE ${UTIL_SRC} ${UTIL_SRC_OS} ${UTIL_SRC_SHA1} ${UTIL_SRC_SHA256})
+ide_split_sources(util)
+
+target_include_directories(util PRIVATE ${UTIL_INCLUDES} ${LIBGIT2_DEPENDENCY_INCLUDES} PUBLIC ${libgit2_SOURCE_DIR}/include)
+target_include_directories(util SYSTEM PRIVATE ${LIBGIT2_SYSTEM_INCLUDES})
diff --git a/src/util/alloc.c b/src/util/alloc.c
new file mode 100644
index 0000000..6ec173d
--- /dev/null
+++ b/src/util/alloc.c
@@ -0,0 +1,115 @@
+/*
+ * 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 "alloc.h"
+#include "runtime.h"
+
+#include "allocators/failalloc.h"
+#include "allocators/stdalloc.h"
+#include "allocators/win32_leakcheck.h"
+
+/* Fail any allocation until git_libgit2_init is called. */
+git_allocator git__allocator = {
+ git_failalloc_malloc,
+ git_failalloc_realloc,
+ git_failalloc_free
+};
+
+void *git__calloc(size_t nelem, size_t elsize)
+{
+ size_t newsize;
+ void *ptr;
+
+ if (GIT_MULTIPLY_SIZET_OVERFLOW(&newsize, nelem, elsize))
+ return NULL;
+
+ if ((ptr = git__malloc(newsize)))
+ memset(ptr, 0, newsize);
+
+ return ptr;
+}
+
+void *git__reallocarray(void *ptr, size_t nelem, size_t elsize)
+{
+ size_t newsize;
+
+ if (GIT_MULTIPLY_SIZET_OVERFLOW(&newsize, nelem, elsize))
+ return NULL;
+
+ return git__realloc(ptr, newsize);
+}
+
+void *git__mallocarray(size_t nelem, size_t elsize)
+{
+ return git__reallocarray(NULL, nelem, elsize);
+}
+
+char *git__strdup(const char *str)
+{
+ size_t len = strlen(str) + 1;
+ void *ptr = git__malloc(len);
+
+ if (ptr)
+ memcpy(ptr, str, len);
+
+ return ptr;
+}
+
+char *git__strndup(const char *str, size_t n)
+{
+ size_t len = p_strnlen(str, n);
+ char *ptr = git__malloc(len + 1);
+
+ if (ptr) {
+ memcpy(ptr, str, len);
+ ptr[len] = '\0';
+ }
+
+ return ptr;
+}
+
+char *git__substrdup(const char *str, size_t n)
+{
+ char *ptr = git__malloc(n + 1);
+
+ if (ptr) {
+ memcpy(ptr, str, n);
+ ptr[n] = '\0';
+ }
+
+ return ptr;
+}
+
+static int setup_default_allocator(void)
+{
+#if defined(GIT_WIN32_LEAKCHECK)
+ return git_win32_leakcheck_init_allocator(&git__allocator);
+#else
+ return git_stdalloc_init_allocator(&git__allocator);
+#endif
+}
+
+int git_allocator_global_init(void)
+{
+ /*
+ * We don't want to overwrite any allocator which has been set
+ * before the init function is called.
+ */
+ if (git__allocator.gmalloc != git_failalloc_malloc)
+ return 0;
+
+ return setup_default_allocator();
+}
+
+int git_allocator_setup(git_allocator *allocator)
+{
+ if (!allocator)
+ return setup_default_allocator();
+
+ memcpy(&git__allocator, allocator, sizeof(*allocator));
+ return 0;
+}
diff --git a/src/util/alloc.h b/src/util/alloc.h
new file mode 100644
index 0000000..32b614b
--- /dev/null
+++ b/src/util/alloc.h
@@ -0,0 +1,65 @@
+/*
+ * 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_alloc_h__
+#define INCLUDE_alloc_h__
+
+#include "git2/sys/alloc.h"
+
+#include "git2_util.h"
+
+extern git_allocator git__allocator;
+
+GIT_INLINE(void *) git__malloc(size_t len)
+{
+ void *p = git__allocator.gmalloc(len, __FILE__, __LINE__);
+
+ if (!p)
+ git_error_set_oom();
+
+ return p;
+}
+
+GIT_INLINE(void *) git__realloc(void *ptr, size_t size)
+{
+ void *p = git__allocator.grealloc(ptr, size, __FILE__, __LINE__);
+
+ if (!p)
+ git_error_set_oom();
+
+ return p;
+}
+
+GIT_INLINE(void) git__free(void *ptr)
+{
+ git__allocator.gfree(ptr);
+}
+
+extern void *git__calloc(size_t nelem, size_t elsize);
+extern void *git__mallocarray(size_t nelem, size_t elsize);
+extern void *git__reallocarray(void *ptr, size_t nelem, size_t elsize);
+
+extern char *git__strdup(const char *str);
+extern char *git__strndup(const char *str, size_t n);
+extern char *git__substrdup(const char *str, size_t n);
+
+/**
+ * This function is being called by our global setup routines to
+ * initialize the standard allocator.
+ */
+int git_allocator_global_init(void);
+
+/**
+ * Switch out libgit2's global memory allocator
+ *
+ * @param allocator The new allocator that should be used. All function pointers
+ * of it need to be set correctly.
+ * @return An error code or 0.
+ */
+int git_allocator_setup(git_allocator *allocator);
+
+#endif
diff --git a/src/util/allocators/failalloc.c b/src/util/allocators/failalloc.c
new file mode 100644
index 0000000..c1025e3
--- /dev/null
+++ b/src/util/allocators/failalloc.c
@@ -0,0 +1,32 @@
+/*
+ * 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 "failalloc.h"
+
+void *git_failalloc_malloc(size_t len, const char *file, int line)
+{
+ GIT_UNUSED(len);
+ GIT_UNUSED(file);
+ GIT_UNUSED(line);
+
+ return NULL;
+}
+
+void *git_failalloc_realloc(void *ptr, size_t size, const char *file, int line)
+{
+ GIT_UNUSED(ptr);
+ GIT_UNUSED(size);
+ GIT_UNUSED(file);
+ GIT_UNUSED(line);
+
+ return NULL;
+}
+
+void git_failalloc_free(void *ptr)
+{
+ GIT_UNUSED(ptr);
+}
diff --git a/src/util/allocators/failalloc.h b/src/util/allocators/failalloc.h
new file mode 100644
index 0000000..a3788e6
--- /dev/null
+++ b/src/util/allocators/failalloc.h
@@ -0,0 +1,17 @@
+/*
+ * 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_allocators_failalloc_h__
+#define INCLUDE_allocators_failalloc_h__
+
+#include "git2_util.h"
+
+extern void *git_failalloc_malloc(size_t len, const char *file, int line);
+extern void *git_failalloc_realloc(void *ptr, size_t size, const char *file, int line);
+extern void git_failalloc_free(void *ptr);
+
+#endif
diff --git a/src/util/allocators/stdalloc.c b/src/util/allocators/stdalloc.c
new file mode 100644
index 0000000..f2d72a7
--- /dev/null
+++ b/src/util/allocators/stdalloc.c
@@ -0,0 +1,47 @@
+/*
+ * 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 "stdalloc.h"
+
+static void *stdalloc__malloc(size_t len, const char *file, int line)
+{
+ GIT_UNUSED(file);
+ GIT_UNUSED(line);
+
+#ifdef GIT_DEBUG_STRICT_ALLOC
+ if (!len)
+ return NULL;
+#endif
+
+ return malloc(len);
+}
+
+static void *stdalloc__realloc(void *ptr, size_t size, const char *file, int line)
+{
+ GIT_UNUSED(file);
+ GIT_UNUSED(line);
+
+#ifdef GIT_DEBUG_STRICT_ALLOC
+ if (!size)
+ return NULL;
+#endif
+
+ return realloc(ptr, size);
+}
+
+static void stdalloc__free(void *ptr)
+{
+ free(ptr);
+}
+
+int git_stdalloc_init_allocator(git_allocator *allocator)
+{
+ allocator->gmalloc = stdalloc__malloc;
+ allocator->grealloc = stdalloc__realloc;
+ allocator->gfree = stdalloc__free;
+ return 0;
+}
diff --git a/src/util/allocators/stdalloc.h b/src/util/allocators/stdalloc.h
new file mode 100644
index 0000000..955038c
--- /dev/null
+++ b/src/util/allocators/stdalloc.h
@@ -0,0 +1,17 @@
+/*
+ * 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_allocators_stdalloc_h__
+#define INCLUDE_allocators_stdalloc_h__
+
+#include "git2_util.h"
+
+#include "alloc.h"
+
+int git_stdalloc_init_allocator(git_allocator *allocator);
+
+#endif
diff --git a/src/util/allocators/win32_leakcheck.c b/src/util/allocators/win32_leakcheck.c
new file mode 100644
index 0000000..cdf16d3
--- /dev/null
+++ b/src/util/allocators/win32_leakcheck.c
@@ -0,0 +1,50 @@
+/*
+ * 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 "win32_leakcheck.h"
+
+#if defined(GIT_WIN32_LEAKCHECK)
+
+#include "win32/w32_leakcheck.h"
+
+static void *leakcheck_malloc(size_t len, const char *file, int line)
+{
+ void *ptr = _malloc_dbg(len, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line);
+ if (!ptr) git_error_set_oom();
+ return ptr;
+}
+
+static void *leakcheck_realloc(void *ptr, size_t size, const char *file, int line)
+{
+ void *new_ptr = _realloc_dbg(ptr, size, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line);
+ if (!new_ptr) git_error_set_oom();
+ return new_ptr;
+}
+
+static void leakcheck_free(void *ptr)
+{
+ free(ptr);
+}
+
+int git_win32_leakcheck_init_allocator(git_allocator *allocator)
+{
+ allocator->gmalloc = leakcheck_malloc;
+ allocator->grealloc = leakcheck_realloc;
+ allocator->gfree = leakcheck_free;
+ return 0;
+}
+
+#else
+
+int git_win32_leakcheck_init_allocator(git_allocator *allocator)
+{
+ GIT_UNUSED(allocator);
+ git_error_set(GIT_EINVALID, "leakcheck memory allocator not available");
+ return -1;
+}
+
+#endif
diff --git a/src/util/allocators/win32_leakcheck.h b/src/util/allocators/win32_leakcheck.h
new file mode 100644
index 0000000..edcd930
--- /dev/null
+++ b/src/util/allocators/win32_leakcheck.h
@@ -0,0 +1,17 @@
+/*
+ * 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_allocators_win32_leakcheck_h
+#define INCLUDE_allocators_win32_leakcheck_h
+
+#include "git2_util.h"
+
+#include "alloc.h"
+
+int git_win32_leakcheck_init_allocator(git_allocator *allocator);
+
+#endif
diff --git a/src/util/array.h b/src/util/array.h
new file mode 100644
index 0000000..633d598
--- /dev/null
+++ b/src/util/array.h
@@ -0,0 +1,129 @@
+/*
+ * 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_array_h__
+#define INCLUDE_array_h__
+
+#include "git2_util.h"
+
+/*
+ * Use this to declare a typesafe resizable array of items, a la:
+ *
+ * git_array_t(int) my_ints = GIT_ARRAY_INIT;
+ * ...
+ * int *i = git_array_alloc(my_ints);
+ * GIT_ERROR_CHECK_ALLOC(i);
+ * ...
+ * git_array_clear(my_ints);
+ *
+ * You may also want to do things like:
+ *
+ * typedef git_array_t(my_struct) my_struct_array_t;
+ */
+#define git_array_t(type) struct { type *ptr; size_t size, asize; }
+
+#define GIT_ARRAY_INIT { NULL, 0, 0 }
+
+#define git_array_init(a) \
+ do { (a).size = (a).asize = 0; (a).ptr = NULL; } while (0)
+
+#define git_array_init_to_size(a, desired) \
+ do { (a).size = 0; (a).asize = desired; (a).ptr = git__calloc(desired, sizeof(*(a).ptr)); } while (0)
+
+#define git_array_dispose(a) \
+ do { git__free((a).ptr); } while (0)
+
+#define git_array_clear(a) \
+ do { git__free((a).ptr); git_array_init(a); } while (0)
+
+#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)
+{
+ volatile git_array_generic_t *a = _a;
+ size_t new_size;
+ char *new_array;
+
+ if (a->size < 8) {
+ new_size = 8;
+ } else {
+ if (GIT_MULTIPLY_SIZET_OVERFLOW(&new_size, a->size, 3))
+ goto on_oom;
+ new_size /= 2;
+ }
+
+ if ((new_array = git__reallocarray(a->ptr, new_size, item_size)) == NULL)
+ goto on_oom;
+
+ a->ptr = new_array;
+ a->asize = new_size;
+ return 0;
+
+on_oom:
+ git_array_clear(*a);
+ return -1;
+}
+
+#define git_array_alloc(a) \
+ (((a).size < (a).asize || git_array_grow(&(a), sizeof(*(a).ptr)) == 0) ? \
+ &(a).ptr[(a).size++] : (void *)NULL)
+
+#define git_array_last(a) ((a).size ? &(a).ptr[(a).size - 1] : (void *)NULL)
+
+#define git_array_pop(a) ((a).size ? &(a).ptr[--(a).size] : (void *)NULL)
+
+#define git_array_get(a, i) (((i) < (a).size) ? &(a).ptr[(i)] : (void *)NULL)
+
+#define git_array_size(a) (a).size
+
+#define git_array_valid_index(a, i) ((i) < (a).size)
+
+#define git_array_foreach(a, i, element) \
+ for ((i) = 0; (i) < (a).size && ((element) = &(a).ptr[(i)]); (i)++)
+
+typedef int (*git_array_compare_cb)(const void *, const void *);
+
+GIT_INLINE(int) git_array__search(
+ size_t *out,
+ void *array_ptr,
+ size_t item_size,
+ size_t array_len,
+ git_array_compare_cb compare,
+ const void *key)
+{
+ size_t lim;
+ unsigned char *part, *array = array_ptr, *base = array_ptr;
+ int cmp = -1;
+
+ for (lim = array_len; lim != 0; lim >>= 1) {
+ part = base + (lim >> 1) * item_size;
+ cmp = (*compare)(key, part);
+
+ if (cmp == 0) {
+ base = part;
+ break;
+ }
+ if (cmp > 0) { /* key > p; take right partition */
+ base = part + 1 * item_size;
+ lim--;
+ } /* else take left partition */
+ }
+
+ if (out)
+ *out = (base - array) / item_size;
+
+ return (cmp == 0) ? 0 : GIT_ENOTFOUND;
+}
+
+#define git_array_search(out, a, cmp, key) \
+ git_array__search(out, (a).ptr, sizeof(*(a).ptr), (a).size, \
+ (cmp), (key))
+
+#endif
diff --git a/src/util/assert_safe.h b/src/util/assert_safe.h
new file mode 100644
index 0000000..cc0bac5
--- /dev/null
+++ b/src/util/assert_safe.h
@@ -0,0 +1,74 @@
+/*
+ * 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_assert_safe_h__
+#define INCLUDE_assert_safe_h__
+
+/*
+ * In a debug build, we'll assert(3) for aide in debugging. In release
+ * builds, we will provide macros that will set an error message that
+ * indicate a failure and return. Note that memory leaks can occur in
+ * a release-mode assertion failure -- it is impractical to provide
+ * safe clean up routines in these very extreme failures, but care
+ * should be taken to not leak very large objects.
+ */
+
+#if (defined(_DEBUG) || defined(GIT_ASSERT_HARD)) && GIT_ASSERT_HARD != 0
+# include <assert.h>
+
+# define GIT_ASSERT(expr) assert(expr)
+# define GIT_ASSERT_ARG(expr) assert(expr)
+
+# define GIT_ASSERT_WITH_RETVAL(expr, fail) assert(expr)
+# define GIT_ASSERT_ARG_WITH_RETVAL(expr, fail) assert(expr)
+
+# define GIT_ASSERT_WITH_CLEANUP(expr, cleanup) assert(expr)
+#else
+
+/** Internal consistency check to stop the function. */
+# define GIT_ASSERT(expr) GIT_ASSERT_WITH_RETVAL(expr, -1)
+
+/**
+ * Assert that a consumer-provided argument is valid, setting an
+ * actionable error message and returning -1 if it is not.
+ */
+# define GIT_ASSERT_ARG(expr) GIT_ASSERT_ARG_WITH_RETVAL(expr, -1)
+
+/** Internal consistency check to return the `fail` param on failure. */
+# define GIT_ASSERT_WITH_RETVAL(expr, fail) \
+ GIT_ASSERT__WITH_RETVAL(expr, GIT_ERROR_INTERNAL, "unrecoverable internal error", fail)
+
+/**
+ * Assert that a consumer-provided argument is valid, setting an
+ * actionable error message and returning the `fail` param if not.
+ */
+# define GIT_ASSERT_ARG_WITH_RETVAL(expr, fail) \
+ GIT_ASSERT__WITH_RETVAL(expr, GIT_ERROR_INVALID, "invalid argument", fail)
+
+# define GIT_ASSERT__WITH_RETVAL(expr, code, msg, fail) do { \
+ if (!(expr)) { \
+ git_error_set(code, "%s: '%s'", msg, #expr); \
+ return fail; \
+ } \
+ } while(0)
+
+/**
+ * Go to to the given label on assertion failures; useful when you have
+ * taken a lock or otherwise need to release a resource.
+ */
+# define GIT_ASSERT_WITH_CLEANUP(expr, cleanup) \
+ GIT_ASSERT__WITH_CLEANUP(expr, GIT_ERROR_INTERNAL, "unrecoverable internal error", cleanup)
+
+# define GIT_ASSERT__WITH_CLEANUP(expr, code, msg, cleanup) do { \
+ if (!(expr)) { \
+ git_error_set(code, "%s: '%s'", msg, #expr); \
+ cleanup; \
+ } \
+ } while(0)
+
+#endif /* GIT_ASSERT_HARD */
+
+#endif
diff --git a/src/util/bitvec.h b/src/util/bitvec.h
new file mode 100644
index 0000000..544832d
--- /dev/null
+++ b/src/util/bitvec.h
@@ -0,0 +1,75 @@
+/*
+ * 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_bitvec_h__
+#define INCLUDE_bitvec_h__
+
+#include "common.h"
+
+/*
+ * This is a silly little fixed length bit vector type that will store
+ * vectors of 64 bits or less directly in the structure and allocate
+ * memory for vectors longer than 64 bits. You can use the two versions
+ * transparently through the API and avoid heap allocation completely when
+ * using a short bit vector as a result.
+ */
+typedef struct {
+ size_t length;
+ union {
+ uint64_t *words;
+ uint64_t bits;
+ } u;
+} git_bitvec;
+
+GIT_INLINE(int) git_bitvec_init(git_bitvec *bv, size_t capacity)
+{
+ memset(bv, 0x0, sizeof(*bv));
+
+ if (capacity >= 64) {
+ bv->length = (capacity / 64) + 1;
+ bv->u.words = git__calloc(bv->length, sizeof(uint64_t));
+ if (!bv->u.words)
+ return -1;
+ }
+
+ return 0;
+}
+
+#define GIT_BITVEC_MASK(BIT) ((uint64_t)1 << (BIT % 64))
+#define GIT_BITVEC_WORD(BV, BIT) (BV->length ? &BV->u.words[BIT / 64] : &BV->u.bits)
+
+GIT_INLINE(void) git_bitvec_set(git_bitvec *bv, size_t bit, bool on)
+{
+ uint64_t *word = GIT_BITVEC_WORD(bv, bit);
+ uint64_t mask = GIT_BITVEC_MASK(bit);
+
+ if (on)
+ *word |= mask;
+ else
+ *word &= ~mask;
+}
+
+GIT_INLINE(bool) git_bitvec_get(git_bitvec *bv, size_t bit)
+{
+ uint64_t *word = GIT_BITVEC_WORD(bv, bit);
+ return (*word & GIT_BITVEC_MASK(bit)) != 0;
+}
+
+GIT_INLINE(void) git_bitvec_clear(git_bitvec *bv)
+{
+ if (!bv->length)
+ bv->u.bits = 0;
+ else
+ memset(bv->u.words, 0x0, bv->length * sizeof(uint64_t));
+}
+
+GIT_INLINE(void) git_bitvec_free(git_bitvec *bv)
+{
+ if (bv->length)
+ git__free(bv->u.words);
+}
+
+#endif
diff --git a/src/util/cc-compat.h b/src/util/cc-compat.h
new file mode 100644
index 0000000..ede6e9a
--- /dev/null
+++ b/src/util/cc-compat.h
@@ -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.
+ */
+#ifndef INCLUDE_cc_compat_h__
+#define INCLUDE_cc_compat_h__
+
+#include <stdarg.h>
+
+/*
+ * See if our compiler is known to support flexible array members.
+ */
+#ifndef GIT_FLEX_ARRAY
+# if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
+# define GIT_FLEX_ARRAY /* empty */
+# elif defined(__GNUC__)
+# if (__GNUC__ >= 3)
+# define GIT_FLEX_ARRAY /* empty */
+# else
+# define GIT_FLEX_ARRAY 0 /* older GNU extension */
+# endif
+# endif
+
+/* Default to safer but a bit wasteful traditional style */
+# ifndef GIT_FLEX_ARRAY
+# define GIT_FLEX_ARRAY 1
+# endif
+#endif
+
+#if defined(__GNUC__)
+# define GIT_ALIGN(x,size) x __attribute__ ((aligned(size)))
+#elif defined(_MSC_VER)
+# define GIT_ALIGN(x,size) __declspec(align(size)) x
+#else
+# define GIT_ALIGN(x,size) x
+#endif
+
+#if defined(__GNUC__)
+# define GIT_UNUSED(x) \
+ do { \
+ __typeof__(x) _unused __attribute__((unused)); \
+ _unused = (x); \
+ } while (0)
+# define GIT_UNUSED_ARG __attribute__((unused))
+#else
+# define GIT_UNUSED(x) ((void)(x))
+# define GIT_UNUSED_ARG
+#endif
+
+/* Define the printf format specifier to use for size_t output */
+#if defined(_MSC_VER) || defined(__MINGW32__)
+
+/* Visual Studio 2012 and prior lack PRId64 entirely */
+# ifndef PRId64
+# define PRId64 "I64d"
+# endif
+
+/* The first block is needed to avoid warnings on MingW amd64 */
+# if (SIZE_MAX == ULLONG_MAX)
+# define PRIuZ "I64u"
+# define PRIxZ "I64x"
+# define PRIXZ "I64X"
+# define PRIdZ "I64d"
+# else
+# define PRIuZ "Iu"
+# define PRIxZ "Ix"
+# define PRIXZ "IX"
+# define PRIdZ "Id"
+# endif
+
+#else
+# define PRIuZ "zu"
+# define PRIxZ "zx"
+# define PRIXZ "zX"
+# define PRIdZ "zd"
+#endif
+
+/* Microsoft Visual C/C++ */
+#if defined(_MSC_VER)
+/* disable "deprecated function" warnings */
+# pragma warning ( disable : 4996 )
+/* disable "conditional expression is constant" level 4 warnings */
+# pragma warning ( disable : 4127 )
+#endif
+
+#if defined (_MSC_VER)
+ typedef unsigned char bool;
+# ifndef true
+# define true 1
+# endif
+# ifndef false
+# define false 0
+# endif
+#else
+# include <stdbool.h>
+#endif
+
+#ifndef va_copy
+# ifdef __va_copy
+# define va_copy(dst, src) __va_copy(dst, src)
+# else
+# define va_copy(dst, src) ((dst) = (src))
+# endif
+#endif
+
+#endif
diff --git a/src/util/date.c b/src/util/date.c
new file mode 100644
index 0000000..4d757e2
--- /dev/null
+++ b/src/util/date.c
@@ -0,0 +1,899 @@
+/*
+ * 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"
+
+#ifndef GIT_WIN32
+#include <sys/time.h>
+#endif
+
+#include "util.h"
+#include "posix.h"
+#include "date.h"
+
+#include <ctype.h>
+#include <time.h>
+
+typedef enum {
+ DATE_NORMAL = 0,
+ DATE_RELATIVE,
+ DATE_SHORT,
+ DATE_LOCAL,
+ DATE_ISO8601,
+ DATE_RFC2822,
+ DATE_RAW
+} date_mode;
+
+/*
+ * This is like mktime, but without normalization of tm_wday and tm_yday.
+ */
+static git_time_t tm_to_time_t(const struct tm *tm)
+{
+ static const int mdays[] = {
+ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
+ };
+ int year = tm->tm_year - 70;
+ int month = tm->tm_mon;
+ int day = tm->tm_mday;
+
+ if (year < 0 || year > 129) /* algo only works for 1970-2099 */
+ return -1;
+ if (month < 0 || month > 11) /* array bounds */
+ return -1;
+ if (month < 2 || (year + 2) % 4)
+ day--;
+ if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_sec < 0)
+ return -1;
+ return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24*60*60UL +
+ tm->tm_hour * 60*60 + tm->tm_min * 60 + tm->tm_sec;
+}
+
+static const char *month_names[] = {
+ "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"
+};
+
+static const char *weekday_names[] = {
+ "Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays"
+};
+
+
+
+/*
+ * Check these. And note how it doesn't do the summer-time conversion.
+ *
+ * In my world, it's always summer, and things are probably a bit off
+ * in other ways too.
+ */
+static const struct {
+ const char *name;
+ int offset;
+ int dst;
+} timezone_names[] = {
+ { "IDLW", -12, 0, }, /* International Date Line West */
+ { "NT", -11, 0, }, /* Nome */
+ { "CAT", -10, 0, }, /* Central Alaska */
+ { "HST", -10, 0, }, /* Hawaii Standard */
+ { "HDT", -10, 1, }, /* Hawaii Daylight */
+ { "YST", -9, 0, }, /* Yukon Standard */
+ { "YDT", -9, 1, }, /* Yukon Daylight */
+ { "PST", -8, 0, }, /* Pacific Standard */
+ { "PDT", -8, 1, }, /* Pacific Daylight */
+ { "MST", -7, 0, }, /* Mountain Standard */
+ { "MDT", -7, 1, }, /* Mountain Daylight */
+ { "CST", -6, 0, }, /* Central Standard */
+ { "CDT", -6, 1, }, /* Central Daylight */
+ { "EST", -5, 0, }, /* Eastern Standard */
+ { "EDT", -5, 1, }, /* Eastern Daylight */
+ { "AST", -3, 0, }, /* Atlantic Standard */
+ { "ADT", -3, 1, }, /* Atlantic Daylight */
+ { "WAT", -1, 0, }, /* West Africa */
+
+ { "GMT", 0, 0, }, /* Greenwich Mean */
+ { "UTC", 0, 0, }, /* Universal (Coordinated) */
+ { "Z", 0, 0, }, /* Zulu, alias for UTC */
+
+ { "WET", 0, 0, }, /* Western European */
+ { "BST", 0, 1, }, /* British Summer */
+ { "CET", +1, 0, }, /* Central European */
+ { "MET", +1, 0, }, /* Middle European */
+ { "MEWT", +1, 0, }, /* Middle European Winter */
+ { "MEST", +1, 1, }, /* Middle European Summer */
+ { "CEST", +1, 1, }, /* Central European Summer */
+ { "MESZ", +1, 1, }, /* Middle European Summer */
+ { "FWT", +1, 0, }, /* French Winter */
+ { "FST", +1, 1, }, /* French Summer */
+ { "EET", +2, 0, }, /* Eastern Europe */
+ { "EEST", +2, 1, }, /* Eastern European Daylight */
+ { "WAST", +7, 0, }, /* West Australian Standard */
+ { "WADT", +7, 1, }, /* West Australian Daylight */
+ { "CCT", +8, 0, }, /* China Coast */
+ { "JST", +9, 0, }, /* Japan Standard */
+ { "EAST", +10, 0, }, /* Eastern Australian Standard */
+ { "EADT", +10, 1, }, /* Eastern Australian Daylight */
+ { "GST", +10, 0, }, /* Guam Standard */
+ { "NZT", +12, 0, }, /* New Zealand */
+ { "NZST", +12, 0, }, /* New Zealand Standard */
+ { "NZDT", +12, 1, }, /* New Zealand Daylight */
+ { "IDLE", +12, 0, }, /* International Date Line East */
+};
+
+static size_t match_string(const char *date, const char *str)
+{
+ size_t i = 0;
+
+ for (i = 0; *date; date++, str++, i++) {
+ if (*date == *str)
+ continue;
+ if (toupper(*date) == toupper(*str))
+ continue;
+ if (!isalnum(*date))
+ break;
+ return 0;
+ }
+ return i;
+}
+
+static int skip_alpha(const char *date)
+{
+ int i = 0;
+ do {
+ i++;
+ } while (isalpha(date[i]));
+ return i;
+}
+
+/*
+* Parse month, weekday, or timezone name
+*/
+static size_t match_alpha(const char *date, struct tm *tm, int *offset)
+{
+ unsigned int i;
+
+ for (i = 0; i < 12; i++) {
+ size_t match = match_string(date, month_names[i]);
+ if (match >= 3) {
+ tm->tm_mon = i;
+ return match;
+ }
+ }
+
+ for (i = 0; i < 7; i++) {
+ size_t match = match_string(date, weekday_names[i]);
+ if (match >= 3) {
+ tm->tm_wday = i;
+ return match;
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(timezone_names); i++) {
+ size_t match = match_string(date, timezone_names[i].name);
+ if (match >= 3 || match == strlen(timezone_names[i].name)) {
+ int off = timezone_names[i].offset;
+
+ /* This is bogus, but we like summer */
+ off += timezone_names[i].dst;
+
+ /* Only use the tz name offset if we don't have anything better */
+ if (*offset == -1)
+ *offset = 60*off;
+
+ return match;
+ }
+ }
+
+ if (match_string(date, "PM") == 2) {
+ tm->tm_hour = (tm->tm_hour % 12) + 12;
+ return 2;
+ }
+
+ if (match_string(date, "AM") == 2) {
+ tm->tm_hour = (tm->tm_hour % 12) + 0;
+ return 2;
+ }
+
+ /* BAD */
+ return skip_alpha(date);
+}
+
+static int is_date(int year, int month, int day, struct tm *now_tm, time_t now, struct tm *tm)
+{
+ if (month > 0 && month < 13 && day > 0 && day < 32) {
+ struct tm check = *tm;
+ struct tm *r = (now_tm ? &check : tm);
+ git_time_t specified;
+
+ r->tm_mon = month - 1;
+ r->tm_mday = day;
+ if (year == -1) {
+ if (!now_tm)
+ return 1;
+ r->tm_year = now_tm->tm_year;
+ }
+ else if (year >= 1970 && year < 2100)
+ r->tm_year = year - 1900;
+ else if (year > 70 && year < 100)
+ r->tm_year = year;
+ else if (year < 38)
+ r->tm_year = year + 100;
+ else
+ return 0;
+ if (!now_tm)
+ return 1;
+
+ specified = tm_to_time_t(r);
+
+ /* Be it commit time or author time, it does not make
+ * sense to specify timestamp way into the future. Make
+ * sure it is not later than ten days from now...
+ */
+ if (now + 10*24*3600 < specified)
+ return 0;
+ tm->tm_mon = r->tm_mon;
+ tm->tm_mday = r->tm_mday;
+ if (year != -1)
+ tm->tm_year = r->tm_year;
+ return 1;
+ }
+ return 0;
+}
+
+static size_t match_multi_number(unsigned long num, char c, const char *date, char *end, struct tm *tm)
+{
+ time_t now;
+ struct tm now_tm;
+ struct tm *refuse_future;
+ long num2, num3;
+
+ num2 = strtol(end+1, &end, 10);
+ num3 = -1;
+ if (*end == c && isdigit(end[1]))
+ num3 = strtol(end+1, &end, 10);
+
+ /* Time? Date? */
+ switch (c) {
+ case ':':
+ if (num3 < 0)
+ num3 = 0;
+ if (num < 25 && num2 >= 0 && num2 < 60 && num3 >= 0 && num3 <= 60) {
+ tm->tm_hour = num;
+ tm->tm_min = num2;
+ tm->tm_sec = num3;
+ break;
+ }
+ return 0;
+
+ case '-':
+ case '/':
+ case '.':
+ now = time(NULL);
+ refuse_future = NULL;
+ if (p_gmtime_r(&now, &now_tm))
+ refuse_future = &now_tm;
+
+ if (num > 70) {
+ /* yyyy-mm-dd? */
+ if (is_date(num, num2, num3, refuse_future, now, tm))
+ break;
+ /* yyyy-dd-mm? */
+ if (is_date(num, num3, num2, refuse_future, now, tm))
+ break;
+ }
+ /* Our eastern European friends say dd.mm.yy[yy]
+ * is the norm there, so giving precedence to
+ * mm/dd/yy[yy] form only when separator is not '.'
+ */
+ if (c != '.' &&
+ is_date(num3, num, num2, refuse_future, now, tm))
+ break;
+ /* European dd.mm.yy[yy] or funny US dd/mm/yy[yy] */
+ if (is_date(num3, num2, num, refuse_future, now, tm))
+ break;
+ /* Funny European mm.dd.yy */
+ if (c == '.' &&
+ is_date(num3, num, num2, refuse_future, now, tm))
+ break;
+ return 0;
+ }
+ return end - date;
+}
+
+/*
+ * Have we filled in any part of the time/date yet?
+ * We just do a binary 'and' to see if the sign bit
+ * is set in all the values.
+ */
+static int nodate(struct tm *tm)
+{
+ return (tm->tm_year &
+ tm->tm_mon &
+ tm->tm_mday &
+ tm->tm_hour &
+ tm->tm_min &
+ tm->tm_sec) < 0;
+}
+
+/*
+ * We've seen a digit. Time? Year? Date?
+ */
+static size_t match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt)
+{
+ size_t n;
+ char *end;
+ unsigned long num;
+
+ num = strtoul(date, &end, 10);
+
+ /*
+ * Seconds since 1970? We trigger on that for any numbers with
+ * more than 8 digits. This is because we don't want to rule out
+ * numbers like 20070606 as a YYYYMMDD date.
+ */
+ if (num >= 100000000 && nodate(tm)) {
+ time_t time = num;
+ if (p_gmtime_r(&time, tm)) {
+ *tm_gmt = 1;
+ return end - date;
+ }
+ }
+
+ /*
+ * Check for special formats: num[-.:/]num[same]num
+ */
+ switch (*end) {
+ case ':':
+ case '.':
+ case '/':
+ case '-':
+ if (isdigit(end[1])) {
+ size_t match = match_multi_number(num, *end, date, end, tm);
+ if (match)
+ return match;
+ }
+ }
+
+ /*
+ * None of the special formats? Try to guess what
+ * the number meant. We use the number of digits
+ * to make a more educated guess..
+ */
+ n = 0;
+ do {
+ n++;
+ } while (isdigit(date[n]));
+
+ /* Four-digit year or a timezone? */
+ if (n == 4) {
+ if (num <= 1400 && *offset == -1) {
+ unsigned int minutes = num % 100;
+ unsigned int hours = num / 100;
+ *offset = hours*60 + minutes;
+ } else if (num > 1900 && num < 2100)
+ tm->tm_year = num - 1900;
+ return n;
+ }
+
+ /*
+ * Ignore lots of numerals. We took care of 4-digit years above.
+ * Days or months must be one or two digits.
+ */
+ if (n > 2)
+ return n;
+
+ /*
+ * NOTE! We will give precedence to day-of-month over month or
+ * year numbers in the 1-12 range. So 05 is always "mday 5",
+ * unless we already have a mday..
+ *
+ * IOW, 01 Apr 05 parses as "April 1st, 2005".
+ */
+ if (num > 0 && num < 32 && tm->tm_mday < 0) {
+ tm->tm_mday = num;
+ return n;
+ }
+
+ /* Two-digit year? */
+ if (n == 2 && tm->tm_year < 0) {
+ if (num < 10 && tm->tm_mday >= 0) {
+ tm->tm_year = num + 100;
+ return n;
+ }
+ if (num >= 70) {
+ tm->tm_year = num;
+ return n;
+ }
+ }
+
+ if (num > 0 && num < 13 && tm->tm_mon < 0)
+ tm->tm_mon = num-1;
+
+ return n;
+}
+
+static size_t match_tz(const char *date, int *offp)
+{
+ char *end;
+ int hour = strtoul(date + 1, &end, 10);
+ size_t n = end - (date + 1);
+ int min = 0;
+
+ if (n == 4) {
+ /* hhmm */
+ min = hour % 100;
+ hour = hour / 100;
+ } else if (n != 2) {
+ min = 99; /* random stuff */
+ } else if (*end == ':') {
+ /* hh:mm? */
+ min = strtoul(end + 1, &end, 10);
+ if (end - (date + 1) != 5)
+ min = 99; /* random stuff */
+ } /* otherwise we parsed "hh" */
+
+ /*
+ * Don't accept any random stuff. Even though some places have
+ * offset larger than 12 hours (e.g. Pacific/Kiritimati is at
+ * UTC+14), there is something wrong if hour part is much
+ * larger than that. We might also want to check that the
+ * minutes are divisible by 15 or something too. (Offset of
+ * Kathmandu, Nepal is UTC+5:45)
+ */
+ if (min < 60 && hour < 24) {
+ int offset = hour * 60 + min;
+ if (*date == '-')
+ offset = -offset;
+ *offp = offset;
+ }
+ return end - date;
+}
+
+/*
+ * Parse a string like "0 +0000" as ancient timestamp near epoch, but
+ * only when it appears not as part of any other string.
+ */
+static int match_object_header_date(const char *date, git_time_t *timestamp, int *offset)
+{
+ char *end;
+ unsigned long stamp;
+ int ofs;
+
+ if (*date < '0' || '9' <= *date)
+ return -1;
+ stamp = strtoul(date, &end, 10);
+ if (*end != ' ' || stamp == ULONG_MAX || (end[1] != '+' && end[1] != '-'))
+ return -1;
+ date = end + 2;
+ ofs = strtol(date, &end, 10);
+ if ((*end != '\0' && (*end != '\n')) || end != date + 4)
+ return -1;
+ ofs = (ofs / 100) * 60 + (ofs % 100);
+ if (date[-1] == '-')
+ ofs = -ofs;
+ *timestamp = stamp;
+ *offset = ofs;
+ return 0;
+}
+
+/* Gr. strptime is crap for this; it doesn't have a way to require RFC2822
+ (i.e. English) day/month names, and it doesn't work correctly with %z. */
+static int parse_date_basic(const char *date, git_time_t *timestamp, int *offset)
+{
+ struct tm tm;
+ int tm_gmt;
+ git_time_t dummy_timestamp;
+ int dummy_offset;
+
+ if (!timestamp)
+ timestamp = &dummy_timestamp;
+ if (!offset)
+ offset = &dummy_offset;
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_year = -1;
+ tm.tm_mon = -1;
+ tm.tm_mday = -1;
+ tm.tm_isdst = -1;
+ tm.tm_hour = -1;
+ tm.tm_min = -1;
+ tm.tm_sec = -1;
+ *offset = -1;
+ tm_gmt = 0;
+
+ if (*date == '@' &&
+ !match_object_header_date(date + 1, timestamp, offset))
+ return 0; /* success */
+ for (;;) {
+ size_t match = 0;
+ unsigned char c = *date;
+
+ /* Stop at end of string or newline */
+ if (!c || c == '\n')
+ break;
+
+ if (isalpha(c))
+ match = match_alpha(date, &tm, offset);
+ else if (isdigit(c))
+ match = match_digit(date, &tm, offset, &tm_gmt);
+ else if ((c == '-' || c == '+') && isdigit(date[1]))
+ match = match_tz(date, offset);
+
+ if (!match) {
+ /* BAD */
+ match = 1;
+ }
+
+ date += match;
+ }
+
+ /* mktime uses local timezone */
+ *timestamp = tm_to_time_t(&tm);
+ if (*offset == -1)
+ *offset = (int)((time_t)*timestamp - mktime(&tm)) / 60;
+
+ if (*timestamp == (git_time_t)-1)
+ return -1;
+
+ if (!tm_gmt)
+ *timestamp -= *offset * 60;
+ return 0; /* success */
+}
+
+
+/*
+ * Relative time update (eg "2 days ago"). If we haven't set the time
+ * yet, we need to set it from current time.
+ */
+static git_time_t update_tm(struct tm *tm, struct tm *now, unsigned long sec)
+{
+ time_t n;
+
+ if (tm->tm_mday < 0)
+ tm->tm_mday = now->tm_mday;
+ if (tm->tm_mon < 0)
+ tm->tm_mon = now->tm_mon;
+ if (tm->tm_year < 0) {
+ tm->tm_year = now->tm_year;
+ if (tm->tm_mon > now->tm_mon)
+ tm->tm_year--;
+ }
+
+ n = mktime(tm) - sec;
+ p_localtime_r(&n, tm);
+ return n;
+}
+
+static void date_now(struct tm *tm, struct tm *now, int *num)
+{
+ GIT_UNUSED(num);
+ update_tm(tm, now, 0);
+}
+
+static void date_yesterday(struct tm *tm, struct tm *now, int *num)
+{
+ GIT_UNUSED(num);
+ update_tm(tm, now, 24*60*60);
+}
+
+static void date_time(struct tm *tm, struct tm *now, int hour)
+{
+ if (tm->tm_hour < hour)
+ date_yesterday(tm, now, NULL);
+ tm->tm_hour = hour;
+ tm->tm_min = 0;
+ tm->tm_sec = 0;
+}
+
+static void date_midnight(struct tm *tm, struct tm *now, int *num)
+{
+ GIT_UNUSED(num);
+ date_time(tm, now, 0);
+}
+
+static void date_noon(struct tm *tm, struct tm *now, int *num)
+{
+ GIT_UNUSED(num);
+ date_time(tm, now, 12);
+}
+
+static void date_tea(struct tm *tm, struct tm *now, int *num)
+{
+ GIT_UNUSED(num);
+ date_time(tm, now, 17);
+}
+
+static void date_pm(struct tm *tm, struct tm *now, int *num)
+{
+ int hour, n = *num;
+ *num = 0;
+ GIT_UNUSED(now);
+
+ hour = tm->tm_hour;
+ if (n) {
+ hour = n;
+ tm->tm_min = 0;
+ tm->tm_sec = 0;
+ }
+ tm->tm_hour = (hour % 12) + 12;
+}
+
+static void date_am(struct tm *tm, struct tm *now, int *num)
+{
+ int hour, n = *num;
+ *num = 0;
+ GIT_UNUSED(now);
+
+ hour = tm->tm_hour;
+ if (n) {
+ hour = n;
+ tm->tm_min = 0;
+ tm->tm_sec = 0;
+ }
+ tm->tm_hour = (hour % 12);
+}
+
+static void date_never(struct tm *tm, struct tm *now, int *num)
+{
+ time_t n = 0;
+ GIT_UNUSED(now);
+ GIT_UNUSED(num);
+ p_localtime_r(&n, tm);
+}
+
+static const struct special {
+ const char *name;
+ void (*fn)(struct tm *, struct tm *, int *);
+} special[] = {
+ { "yesterday", date_yesterday },
+ { "noon", date_noon },
+ { "midnight", date_midnight },
+ { "tea", date_tea },
+ { "PM", date_pm },
+ { "AM", date_am },
+ { "never", date_never },
+ { "now", date_now },
+ { NULL }
+};
+
+static const char *number_name[] = {
+ "zero", "one", "two", "three", "four",
+ "five", "six", "seven", "eight", "nine", "ten",
+};
+
+static const struct typelen {
+ const char *type;
+ int length;
+} typelen[] = {
+ { "seconds", 1 },
+ { "minutes", 60 },
+ { "hours", 60*60 },
+ { "days", 24*60*60 },
+ { "weeks", 7*24*60*60 },
+ { NULL }
+};
+
+static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm *now, int *num, int *touched)
+{
+ const struct typelen *tl;
+ const struct special *s;
+ const char *end = date;
+ int i;
+
+ while (isalpha(*++end))
+ /* scan to non-alpha */;
+
+ for (i = 0; i < 12; i++) {
+ size_t match = match_string(date, month_names[i]);
+ if (match >= 3) {
+ tm->tm_mon = i;
+ *touched = 1;
+ return end;
+ }
+ }
+
+ for (s = special; s->name; s++) {
+ size_t len = strlen(s->name);
+ if (match_string(date, s->name) == len) {
+ s->fn(tm, now, num);
+ *touched = 1;
+ return end;
+ }
+ }
+
+ if (!*num) {
+ for (i = 1; i < 11; i++) {
+ size_t len = strlen(number_name[i]);
+ if (match_string(date, number_name[i]) == len) {
+ *num = i;
+ *touched = 1;
+ return end;
+ }
+ }
+ if (match_string(date, "last") == 4) {
+ *num = 1;
+ *touched = 1;
+ }
+ return end;
+ }
+
+ tl = typelen;
+ while (tl->type) {
+ size_t len = strlen(tl->type);
+ if (match_string(date, tl->type) >= len-1) {
+ update_tm(tm, now, tl->length * (unsigned long)*num);
+ *num = 0;
+ *touched = 1;
+ return end;
+ }
+ tl++;
+ }
+
+ for (i = 0; i < 7; i++) {
+ size_t match = match_string(date, weekday_names[i]);
+ if (match >= 3) {
+ int diff, n = *num -1;
+ *num = 0;
+
+ diff = tm->tm_wday - i;
+ if (diff <= 0)
+ n++;
+ diff += 7*n;
+
+ update_tm(tm, now, diff * 24 * 60 * 60);
+ *touched = 1;
+ return end;
+ }
+ }
+
+ if (match_string(date, "months") >= 5) {
+ int n;
+ update_tm(tm, now, 0); /* fill in date fields if needed */
+ n = tm->tm_mon - *num;
+ *num = 0;
+ while (n < 0) {
+ n += 12;
+ tm->tm_year--;
+ }
+ tm->tm_mon = n;
+ *touched = 1;
+ return end;
+ }
+
+ if (match_string(date, "years") >= 4) {
+ update_tm(tm, now, 0); /* fill in date fields if needed */
+ tm->tm_year -= *num;
+ *num = 0;
+ *touched = 1;
+ return end;
+ }
+
+ return end;
+}
+
+static const char *approxidate_digit(const char *date, struct tm *tm, int *num)
+{
+ char *end;
+ unsigned long number = strtoul(date, &end, 10);
+
+ switch (*end) {
+ case ':':
+ case '.':
+ case '/':
+ case '-':
+ if (isdigit(end[1])) {
+ size_t match = match_multi_number(number, *end, date, end, tm);
+ if (match)
+ return date + match;
+ }
+ }
+
+ /* Accept zero-padding only for small numbers ("Dec 02", never "Dec 0002") */
+ if (date[0] != '0' || end - date <= 2)
+ *num = number;
+ return end;
+}
+
+/*
+ * Do we have a pending number at the end, or when
+ * we see a new one? Let's assume it's a month day,
+ * as in "Dec 6, 1992"
+ */
+static void pending_number(struct tm *tm, int *num)
+{
+ int number = *num;
+
+ if (number) {
+ *num = 0;
+ if (tm->tm_mday < 0 && number < 32)
+ tm->tm_mday = number;
+ else if (tm->tm_mon < 0 && number < 13)
+ tm->tm_mon = number-1;
+ else if (tm->tm_year < 0) {
+ if (number > 1969 && number < 2100)
+ tm->tm_year = number - 1900;
+ else if (number > 69 && number < 100)
+ tm->tm_year = number;
+ else if (number < 38)
+ tm->tm_year = 100 + number;
+ /* We mess up for number = 00 ? */
+ }
+ }
+}
+
+static git_time_t approxidate_str(const char *date,
+ time_t time_sec,
+ int *error_ret)
+{
+ int number = 0;
+ int touched = 0;
+ struct tm tm = {0}, now;
+
+ p_localtime_r(&time_sec, &tm);
+ now = tm;
+
+ tm.tm_year = -1;
+ tm.tm_mon = -1;
+ tm.tm_mday = -1;
+
+ for (;;) {
+ unsigned char c = *date;
+ if (!c)
+ break;
+ date++;
+ if (isdigit(c)) {
+ pending_number(&tm, &number);
+ date = approxidate_digit(date-1, &tm, &number);
+ touched = 1;
+ continue;
+ }
+ if (isalpha(c))
+ date = approxidate_alpha(date-1, &tm, &now, &number, &touched);
+ }
+ pending_number(&tm, &number);
+ if (!touched)
+ *error_ret = -1;
+ return update_tm(&tm, &now, 0);
+}
+
+int git_date_parse(git_time_t *out, const char *date)
+{
+ time_t time_sec;
+ git_time_t timestamp;
+ int offset, error_ret=0;
+
+ if (!parse_date_basic(date, &timestamp, &offset)) {
+ *out = timestamp;
+ return 0;
+ }
+
+ if (time(&time_sec) == -1)
+ return -1;
+
+ *out = approxidate_str(date, time_sec, &error_ret);
+ return error_ret;
+}
+
+int git_date_rfc2822_fmt(git_str *out, git_time_t time, int offset)
+{
+ time_t t;
+ struct tm gmt;
+
+ GIT_ASSERT_ARG(out);
+
+ t = (time_t) (time + offset * 60);
+
+ if (p_gmtime_r(&t, &gmt) == NULL)
+ return -1;
+
+ return git_str_printf(out, "%.3s, %u %.3s %.4u %02u:%02u:%02u %+03d%02d",
+ weekday_names[gmt.tm_wday],
+ gmt.tm_mday,
+ month_names[gmt.tm_mon],
+ gmt.tm_year + 1900,
+ gmt.tm_hour, gmt.tm_min, gmt.tm_sec,
+ offset / 60, offset % 60);
+}
+
diff --git a/src/util/date.h b/src/util/date.h
new file mode 100644
index 0000000..7ebd3c3
--- /dev/null
+++ b/src/util/date.h
@@ -0,0 +1,33 @@
+/*
+ * 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_date_h__
+#define INCLUDE_date_h__
+
+#include "util.h"
+#include "str.h"
+
+/*
+ * Parse a string into a value as a git_time_t.
+ *
+ * Sample valid input:
+ * - "yesterday"
+ * - "July 17, 2003"
+ * - "2003-7-17 08:23"
+ */
+extern int git_date_parse(git_time_t *out, const char *date);
+
+/*
+ * Format a git_time as a RFC2822 string
+ *
+ * @param out buffer to store formatted date
+ * @param time the time to be formatted
+ * @param offset the timezone offset
+ * @return 0 if successful; -1 on error
+ */
+extern int git_date_rfc2822_fmt(git_str *out, git_time_t time, int offset);
+
+#endif
diff --git a/src/util/filebuf.c b/src/util/filebuf.c
new file mode 100644
index 0000000..7afb76b
--- /dev/null
+++ b/src/util/filebuf.c
@@ -0,0 +1,600 @@
+/*
+ * 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 "filebuf.h"
+
+#include "futils.h"
+
+static const size_t WRITE_BUFFER_SIZE = (4096 * 2);
+
+enum buferr_t {
+ BUFERR_OK = 0,
+ BUFERR_WRITE,
+ BUFERR_ZLIB,
+ BUFERR_MEM
+};
+
+#define ENSURE_BUF_OK(buf) if ((buf)->last_error != BUFERR_OK) { return -1; }
+
+static int verify_last_error(git_filebuf *file)
+{
+ switch (file->last_error) {
+ case BUFERR_WRITE:
+ git_error_set(GIT_ERROR_OS, "failed to write out file");
+ return -1;
+
+ case BUFERR_MEM:
+ git_error_set_oom();
+ return -1;
+
+ case BUFERR_ZLIB:
+ git_error_set(GIT_ERROR_ZLIB,
+ "Buffer error when writing out ZLib data");
+ return -1;
+
+ default:
+ return 0;
+ }
+}
+
+static int lock_file(git_filebuf *file, int flags, mode_t mode)
+{
+ if (git_fs_path_exists(file->path_lock) == true) {
+ git_error_clear(); /* actual OS error code just confuses */
+ git_error_set(GIT_ERROR_OS,
+ "failed to lock file '%s' for writing", file->path_lock);
+ return GIT_ELOCKED;
+ }
+
+ /* create path to the file buffer is required */
+ if (flags & GIT_FILEBUF_CREATE_LEADING_DIRS) {
+ /* XXX: Should dirmode here be configurable? Or is 0777 always fine? */
+ file->fd = git_futils_creat_locked_withpath(file->path_lock, 0777, mode);
+ } else {
+ file->fd = git_futils_creat_locked(file->path_lock, mode);
+ }
+
+ if (file->fd < 0)
+ return file->fd;
+
+ file->fd_is_open = true;
+
+ if ((flags & GIT_FILEBUF_APPEND) && git_fs_path_exists(file->path_original) == true) {
+ git_file source;
+ char buffer[GIT_BUFSIZE_FILEIO];
+ ssize_t read_bytes;
+ int error = 0;
+
+ source = p_open(file->path_original, O_RDONLY);
+ if (source < 0) {
+ git_error_set(GIT_ERROR_OS,
+ "failed to open file '%s' for reading",
+ file->path_original);
+ return -1;
+ }
+
+ while ((read_bytes = p_read(source, buffer, sizeof(buffer))) > 0) {
+ if ((error = p_write(file->fd, buffer, read_bytes)) < 0)
+ break;
+ if (file->compute_digest)
+ git_hash_update(&file->digest, buffer, read_bytes);
+ }
+
+ p_close(source);
+
+ if (read_bytes < 0) {
+ git_error_set(GIT_ERROR_OS, "failed to read file '%s'", file->path_original);
+ return -1;
+ } else if (error < 0) {
+ git_error_set(GIT_ERROR_OS, "failed to write file '%s'", file->path_lock);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+void git_filebuf_cleanup(git_filebuf *file)
+{
+ if (file->fd_is_open && file->fd >= 0)
+ p_close(file->fd);
+
+ if (file->created_lock && !file->did_rename && file->path_lock && git_fs_path_exists(file->path_lock))
+ p_unlink(file->path_lock);
+
+ if (file->compute_digest) {
+ git_hash_ctx_cleanup(&file->digest);
+ file->compute_digest = 0;
+ }
+
+ if (file->buffer)
+ git__free(file->buffer);
+
+ /* use the presence of z_buf to decide if we need to deflateEnd */
+ if (file->z_buf) {
+ git__free(file->z_buf);
+ deflateEnd(&file->zs);
+ }
+
+ if (file->path_original)
+ git__free(file->path_original);
+ if (file->path_lock)
+ git__free(file->path_lock);
+
+ memset(file, 0x0, sizeof(git_filebuf));
+ file->fd = -1;
+}
+
+GIT_INLINE(int) flush_buffer(git_filebuf *file)
+{
+ int result = file->write(file, file->buffer, file->buf_pos);
+ file->buf_pos = 0;
+ return result;
+}
+
+int git_filebuf_flush(git_filebuf *file)
+{
+ return flush_buffer(file);
+}
+
+static int write_normal(git_filebuf *file, void *source, size_t len)
+{
+ if (len > 0) {
+ if (p_write(file->fd, (void *)source, len) < 0) {
+ file->last_error = BUFERR_WRITE;
+ return -1;
+ }
+
+ if (file->compute_digest)
+ git_hash_update(&file->digest, source, len);
+ }
+
+ return 0;
+}
+
+static int write_deflate(git_filebuf *file, void *source, size_t len)
+{
+ z_stream *zs = &file->zs;
+
+ if (len > 0 || file->flush_mode == Z_FINISH) {
+ zs->next_in = source;
+ zs->avail_in = (uInt)len;
+
+ do {
+ size_t have;
+
+ zs->next_out = file->z_buf;
+ zs->avail_out = (uInt)file->buf_size;
+
+ if (deflate(zs, file->flush_mode) == Z_STREAM_ERROR) {
+ file->last_error = BUFERR_ZLIB;
+ return -1;
+ }
+
+ have = file->buf_size - (size_t)zs->avail_out;
+
+ if (p_write(file->fd, file->z_buf, have) < 0) {
+ file->last_error = BUFERR_WRITE;
+ return -1;
+ }
+
+ } while (zs->avail_out == 0);
+
+ GIT_ASSERT(zs->avail_in == 0);
+
+ if (file->compute_digest)
+ git_hash_update(&file->digest, source, len);
+ }
+
+ return 0;
+}
+
+#define MAX_SYMLINK_DEPTH 5
+
+static int resolve_symlink(git_str *out, const char *path)
+{
+ int i, error, root;
+ ssize_t ret;
+ struct stat st;
+ git_str curpath = GIT_STR_INIT, target = GIT_STR_INIT;
+
+ if ((error = git_str_grow(&target, GIT_PATH_MAX + 1)) < 0 ||
+ (error = git_str_puts(&curpath, path)) < 0)
+ return error;
+
+ for (i = 0; i < MAX_SYMLINK_DEPTH; i++) {
+ error = p_lstat(curpath.ptr, &st);
+ if (error < 0 && errno == ENOENT) {
+ error = git_str_puts(out, curpath.ptr);
+ goto cleanup;
+ }
+
+ if (error < 0) {
+ git_error_set(GIT_ERROR_OS, "failed to stat '%s'", curpath.ptr);
+ error = -1;
+ goto cleanup;
+ }
+
+ if (!S_ISLNK(st.st_mode)) {
+ error = git_str_puts(out, curpath.ptr);
+ goto cleanup;
+ }
+
+ ret = p_readlink(curpath.ptr, target.ptr, GIT_PATH_MAX);
+ if (ret < 0) {
+ git_error_set(GIT_ERROR_OS, "failed to read symlink '%s'", curpath.ptr);
+ error = -1;
+ goto cleanup;
+ }
+
+ if (ret == GIT_PATH_MAX) {
+ git_error_set(GIT_ERROR_INVALID, "symlink target too long");
+ error = -1;
+ goto cleanup;
+ }
+
+ /* readlink(2) won't NUL-terminate for us */
+ target.ptr[ret] = '\0';
+ target.size = ret;
+
+ root = git_fs_path_root(target.ptr);
+ if (root >= 0) {
+ if ((error = git_str_sets(&curpath, target.ptr)) < 0)
+ goto cleanup;
+ } else {
+ git_str dir = GIT_STR_INIT;
+
+ if ((error = git_fs_path_dirname_r(&dir, curpath.ptr)) < 0)
+ goto cleanup;
+
+ git_str_swap(&curpath, &dir);
+ git_str_dispose(&dir);
+
+ if ((error = git_fs_path_apply_relative(&curpath, target.ptr)) < 0)
+ goto cleanup;
+ }
+ }
+
+ git_error_set(GIT_ERROR_INVALID, "maximum symlink depth reached");
+ error = -1;
+
+cleanup:
+ git_str_dispose(&curpath);
+ git_str_dispose(&target);
+ return error;
+}
+
+int git_filebuf_open(git_filebuf *file, const char *path, int flags, mode_t mode)
+{
+ return git_filebuf_open_withsize(file, path, flags, mode, WRITE_BUFFER_SIZE);
+}
+
+int git_filebuf_open_withsize(git_filebuf *file, const char *path, int flags, mode_t mode, size_t size)
+{
+ int compression, error = -1;
+ size_t path_len, alloc_len;
+
+ GIT_ASSERT_ARG(file);
+ GIT_ASSERT_ARG(path);
+ GIT_ASSERT(file->buffer == NULL);
+
+ memset(file, 0x0, sizeof(git_filebuf));
+
+ if (flags & GIT_FILEBUF_DO_NOT_BUFFER)
+ file->do_not_buffer = true;
+
+ if (flags & GIT_FILEBUF_FSYNC)
+ file->do_fsync = true;
+
+ file->buf_size = size;
+ file->buf_pos = 0;
+ file->fd = -1;
+ file->last_error = BUFERR_OK;
+
+ /* Allocate the main cache buffer */
+ if (!file->do_not_buffer) {
+ file->buffer = git__malloc(file->buf_size);
+ GIT_ERROR_CHECK_ALLOC(file->buffer);
+ }
+
+ /* If we are hashing on-write, allocate a new hash context */
+ if (flags & GIT_FILEBUF_HASH_SHA1) {
+ file->compute_digest = 1;
+
+ if (git_hash_ctx_init(&file->digest, GIT_HASH_ALGORITHM_SHA1) < 0)
+ goto cleanup;
+ } else if (flags & GIT_FILEBUF_HASH_SHA256) {
+ file->compute_digest = 1;
+
+ if (git_hash_ctx_init(&file->digest, GIT_HASH_ALGORITHM_SHA256) < 0)
+ goto cleanup;
+ }
+
+ compression = flags >> GIT_FILEBUF_DEFLATE_SHIFT;
+
+ /* If we are deflating on-write, */
+ if (compression != 0) {
+ /* Initialize the ZLib stream */
+ if (deflateInit(&file->zs, compression) != Z_OK) {
+ git_error_set(GIT_ERROR_ZLIB, "failed to initialize zlib");
+ goto cleanup;
+ }
+
+ /* Allocate the Zlib cache buffer */
+ file->z_buf = git__malloc(file->buf_size);
+ GIT_ERROR_CHECK_ALLOC(file->z_buf);
+
+ /* Never flush */
+ file->flush_mode = Z_NO_FLUSH;
+ file->write = &write_deflate;
+ } else {
+ file->write = &write_normal;
+ }
+
+ /* If we are writing to a temp file */
+ if (flags & GIT_FILEBUF_TEMPORARY) {
+ git_str tmp_path = GIT_STR_INIT;
+
+ /* Open the file as temporary for locking */
+ file->fd = git_futils_mktmp(&tmp_path, path, mode);
+
+ if (file->fd < 0) {
+ git_str_dispose(&tmp_path);
+ goto cleanup;
+ }
+ file->fd_is_open = true;
+ file->created_lock = true;
+
+ /* No original path */
+ file->path_original = NULL;
+ file->path_lock = git_str_detach(&tmp_path);
+ GIT_ERROR_CHECK_ALLOC(file->path_lock);
+ } else {
+ git_str resolved_path = GIT_STR_INIT;
+
+ if ((error = resolve_symlink(&resolved_path, path)) < 0)
+ goto cleanup;
+
+ /* Save the original path of the file */
+ path_len = resolved_path.size;
+ file->path_original = git_str_detach(&resolved_path);
+
+ /* create the locking path by appending ".lock" to the original */
+ GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, path_len, GIT_FILELOCK_EXTLENGTH);
+ file->path_lock = git__malloc(alloc_len);
+ GIT_ERROR_CHECK_ALLOC(file->path_lock);
+
+ memcpy(file->path_lock, file->path_original, path_len);
+ memcpy(file->path_lock + path_len, GIT_FILELOCK_EXTENSION, GIT_FILELOCK_EXTLENGTH);
+
+ if (git_fs_path_isdir(file->path_original)) {
+ git_error_set(GIT_ERROR_FILESYSTEM, "path '%s' is a directory", file->path_original);
+ error = GIT_EDIRECTORY;
+ goto cleanup;
+ }
+
+ /* open the file for locking */
+ if ((error = lock_file(file, flags, mode)) < 0)
+ goto cleanup;
+
+ file->created_lock = true;
+ }
+
+ return 0;
+
+cleanup:
+ git_filebuf_cleanup(file);
+ return error;
+}
+
+int git_filebuf_hash(unsigned char *out, git_filebuf *file)
+{
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(file);
+ GIT_ASSERT_ARG(file->compute_digest);
+
+ flush_buffer(file);
+
+ if (verify_last_error(file) < 0)
+ return -1;
+
+ git_hash_final(out, &file->digest);
+ git_hash_ctx_cleanup(&file->digest);
+ file->compute_digest = 0;
+
+ return 0;
+}
+
+int git_filebuf_commit_at(git_filebuf *file, const char *path)
+{
+ git__free(file->path_original);
+ file->path_original = git__strdup(path);
+ GIT_ERROR_CHECK_ALLOC(file->path_original);
+
+ return git_filebuf_commit(file);
+}
+
+int git_filebuf_commit(git_filebuf *file)
+{
+ /* temporary files cannot be committed */
+ GIT_ASSERT_ARG(file);
+ GIT_ASSERT(file->path_original);
+
+ file->flush_mode = Z_FINISH;
+ flush_buffer(file);
+
+ if (verify_last_error(file) < 0)
+ goto on_error;
+
+ file->fd_is_open = false;
+
+ if (file->do_fsync && p_fsync(file->fd) < 0) {
+ git_error_set(GIT_ERROR_OS, "failed to fsync '%s'", file->path_lock);
+ goto on_error;
+ }
+
+ if (p_close(file->fd) < 0) {
+ git_error_set(GIT_ERROR_OS, "failed to close file at '%s'", file->path_lock);
+ goto on_error;
+ }
+
+ file->fd = -1;
+
+ if (p_rename(file->path_lock, file->path_original) < 0) {
+ git_error_set(GIT_ERROR_OS, "failed to rename lockfile to '%s'", file->path_original);
+ goto on_error;
+ }
+
+ if (file->do_fsync && git_futils_fsync_parent(file->path_original) < 0)
+ goto on_error;
+
+ file->did_rename = true;
+
+ git_filebuf_cleanup(file);
+ return 0;
+
+on_error:
+ git_filebuf_cleanup(file);
+ return -1;
+}
+
+GIT_INLINE(void) add_to_cache(git_filebuf *file, const void *buf, size_t len)
+{
+ memcpy(file->buffer + file->buf_pos, buf, len);
+ file->buf_pos += len;
+}
+
+int git_filebuf_write(git_filebuf *file, const void *buff, size_t len)
+{
+ const unsigned char *buf = buff;
+
+ ENSURE_BUF_OK(file);
+
+ if (file->do_not_buffer)
+ return file->write(file, (void *)buff, len);
+
+ for (;;) {
+ size_t space_left = file->buf_size - file->buf_pos;
+
+ /* cache if it's small */
+ if (space_left > len) {
+ add_to_cache(file, buf, len);
+ return 0;
+ }
+
+ add_to_cache(file, buf, space_left);
+ if (flush_buffer(file) < 0)
+ return -1;
+
+ len -= space_left;
+ buf += space_left;
+ }
+}
+
+int git_filebuf_reserve(git_filebuf *file, void **buffer, size_t len)
+{
+ size_t space_left = file->buf_size - file->buf_pos;
+
+ *buffer = NULL;
+
+ ENSURE_BUF_OK(file);
+
+ if (len > file->buf_size) {
+ file->last_error = BUFERR_MEM;
+ return -1;
+ }
+
+ if (space_left <= len) {
+ if (flush_buffer(file) < 0)
+ return -1;
+ }
+
+ *buffer = (file->buffer + file->buf_pos);
+ file->buf_pos += len;
+
+ return 0;
+}
+
+int git_filebuf_printf(git_filebuf *file, const char *format, ...)
+{
+ va_list arglist;
+ size_t space_left, len, alloclen;
+ int written, res;
+ char *tmp_buffer;
+
+ ENSURE_BUF_OK(file);
+
+ space_left = file->buf_size - file->buf_pos;
+
+ do {
+ va_start(arglist, format);
+ written = p_vsnprintf((char *)file->buffer + file->buf_pos, space_left, format, arglist);
+ va_end(arglist);
+
+ if (written < 0) {
+ file->last_error = BUFERR_MEM;
+ return -1;
+ }
+
+ len = written;
+ if (len + 1 <= space_left) {
+ file->buf_pos += len;
+ return 0;
+ }
+
+ if (flush_buffer(file) < 0)
+ return -1;
+
+ space_left = file->buf_size - file->buf_pos;
+
+ } while (len + 1 <= space_left);
+
+ if (GIT_ADD_SIZET_OVERFLOW(&alloclen, len, 1) ||
+ !(tmp_buffer = git__malloc(alloclen))) {
+ file->last_error = BUFERR_MEM;
+ return -1;
+ }
+
+ va_start(arglist, format);
+ written = p_vsnprintf(tmp_buffer, len + 1, format, arglist);
+ va_end(arglist);
+
+ if (written < 0) {
+ git__free(tmp_buffer);
+ file->last_error = BUFERR_MEM;
+ return -1;
+ }
+
+ res = git_filebuf_write(file, tmp_buffer, len);
+ git__free(tmp_buffer);
+
+ return res;
+}
+
+int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file)
+{
+ int res;
+ struct stat st;
+
+ if (file->fd_is_open)
+ res = p_fstat(file->fd, &st);
+ else
+ res = p_stat(file->path_original, &st);
+
+ if (res < 0) {
+ git_error_set(GIT_ERROR_OS, "could not get stat info for '%s'",
+ file->path_original);
+ return res;
+ }
+
+ if (mtime)
+ *mtime = st.st_mtime;
+ if (size)
+ *size = (size_t)st.st_size;
+
+ return 0;
+}
diff --git a/src/util/filebuf.h b/src/util/filebuf.h
new file mode 100644
index 0000000..e23b9ed
--- /dev/null
+++ b/src/util/filebuf.h
@@ -0,0 +1,107 @@
+/*
+ * 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_filebuf_h__
+#define INCLUDE_filebuf_h__
+
+#include "git2_util.h"
+
+#include "futils.h"
+#include "hash.h"
+#include <zlib.h>
+
+#ifdef GIT_THREADS
+# define GIT_FILEBUF_THREADS
+#endif
+
+#define GIT_FILEBUF_HASH_SHA1 (1 << 0)
+#define GIT_FILEBUF_HASH_SHA256 (1 << 1)
+#define GIT_FILEBUF_APPEND (1 << 2)
+#define GIT_FILEBUF_CREATE_LEADING_DIRS (1 << 3)
+#define GIT_FILEBUF_TEMPORARY (1 << 4)
+#define GIT_FILEBUF_DO_NOT_BUFFER (1 << 5)
+#define GIT_FILEBUF_FSYNC (1 << 6)
+#define GIT_FILEBUF_DEFLATE_SHIFT (7)
+
+#define GIT_FILELOCK_EXTENSION ".lock\0"
+#define GIT_FILELOCK_EXTLENGTH 6
+
+typedef struct git_filebuf git_filebuf;
+struct git_filebuf {
+ char *path_original;
+ char *path_lock;
+
+ int (*write)(git_filebuf *file, void *source, size_t len);
+
+ bool compute_digest;
+ git_hash_ctx digest;
+
+ unsigned char *buffer;
+ unsigned char *z_buf;
+
+ z_stream zs;
+ int flush_mode;
+
+ size_t buf_size, buf_pos;
+ git_file fd;
+ bool fd_is_open;
+ bool created_lock;
+ bool did_rename;
+ bool do_not_buffer;
+ bool do_fsync;
+ int last_error;
+};
+
+#define GIT_FILEBUF_INIT {0}
+
+/*
+ * The git_filebuf object lifecycle is:
+ * - Allocate git_filebuf, preferably using GIT_FILEBUF_INIT.
+ *
+ * - Call git_filebuf_open() to initialize the filebuf for use.
+ *
+ * - Make as many calls to git_filebuf_write(), git_filebuf_printf(),
+ * git_filebuf_reserve() as you like. The error codes for these
+ * functions don't need to be checked. They are stored internally
+ * by the file buffer.
+ *
+ * - While you are writing, you may call git_filebuf_hash() to get
+ * the hash of all you have written so far. This function will
+ * fail if any of the previous writes to the buffer failed.
+ *
+ * - To close the git_filebuf, you may call git_filebuf_commit() or
+ * git_filebuf_commit_at() to save the file, or
+ * git_filebuf_cleanup() to abandon the file. All of these will
+ * free the git_filebuf object. Likewise, all of these will fail
+ * if any of the previous writes to the buffer failed, and set
+ * an error code accordingly.
+ */
+int git_filebuf_write(git_filebuf *lock, const void *buff, size_t len);
+int git_filebuf_reserve(git_filebuf *file, void **buff, size_t len);
+int git_filebuf_printf(git_filebuf *file, const char *format, ...) GIT_FORMAT_PRINTF(2, 3);
+
+int git_filebuf_open(git_filebuf *lock, const char *path, int flags, mode_t mode);
+int git_filebuf_open_withsize(git_filebuf *file, const char *path, int flags, mode_t mode, size_t size);
+int git_filebuf_commit(git_filebuf *lock);
+int git_filebuf_commit_at(git_filebuf *lock, const char *path);
+void git_filebuf_cleanup(git_filebuf *lock);
+int git_filebuf_hash(unsigned char *out, git_filebuf *file);
+int git_filebuf_flush(git_filebuf *file);
+int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file);
+
+GIT_INLINE(int) git_filebuf_hash_flags(git_hash_algorithm_t algorithm)
+{
+ switch (algorithm) {
+ case GIT_HASH_ALGORITHM_SHA1:
+ return GIT_FILEBUF_HASH_SHA1;
+ case GIT_HASH_ALGORITHM_SHA256:
+ return GIT_FILEBUF_HASH_SHA256;
+ default:
+ return 0;
+ }
+}
+
+#endif
diff --git a/src/util/fs_path.c b/src/util/fs_path.c
new file mode 100644
index 0000000..e03fcf7
--- /dev/null
+++ b/src/util/fs_path.c
@@ -0,0 +1,2066 @@
+/*
+ * 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 "fs_path.h"
+
+#include "git2_util.h"
+#include "futils.h"
+#include "posix.h"
+#ifdef GIT_WIN32
+#include "win32/posix.h"
+#include "win32/w32_buffer.h"
+#include "win32/w32_util.h"
+#include "win32/version.h"
+#include <aclapi.h>
+#else
+#include <dirent.h>
+#endif
+#include <stdio.h>
+#include <ctype.h>
+
+#define ensure_error_set(code) do { \
+ const git_error *e = git_error_last(); \
+ if (!e || !e->message) \
+ git_error_set(e ? e->klass : GIT_ERROR_CALLBACK, \
+ "filesystem callback returned %d", code); \
+ } while(0)
+
+static int dos_drive_prefix_length(const char *path)
+{
+ int i;
+
+ /*
+ * Does it start with an ASCII letter (i.e. highest bit not set),
+ * followed by a colon?
+ */
+ if (!(0x80 & (unsigned char)*path))
+ return *path && path[1] == ':' ? 2 : 0;
+
+ /*
+ * While drive letters must be letters of the English alphabet, it is
+ * possible to assign virtually _any_ Unicode character via `subst` as
+ * a drive letter to "virtual drives". Even `1`, or `ä`. Or fun stuff
+ * like this:
+ *
+ * subst ֍: %USERPROFILE%\Desktop
+ */
+ for (i = 1; i < 4 && (0x80 & (unsigned char)path[i]); i++)
+ ; /* skip first UTF-8 character */
+ return path[i] == ':' ? i + 1 : 0;
+}
+
+#ifdef GIT_WIN32
+static bool looks_like_network_computer_name(const char *path, int pos)
+{
+ if (pos < 3)
+ return false;
+
+ if (path[0] != '/' || path[1] != '/')
+ return false;
+
+ while (pos-- > 2) {
+ if (path[pos] == '/')
+ return false;
+ }
+
+ return true;
+}
+#endif
+
+/*
+ * Based on the Android implementation, BSD licensed.
+ * http://android.git.kernel.org/
+ *
+ * Copyright (C) 2008 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+int git_fs_path_basename_r(git_str *buffer, const char *path)
+{
+ const char *endp, *startp;
+ int len, result;
+
+ /* Empty or NULL string gets treated as "." */
+ if (path == NULL || *path == '\0') {
+ startp = ".";
+ len = 1;
+ goto Exit;
+ }
+
+ /* Strip trailing slashes */
+ endp = path + strlen(path) - 1;
+ while (endp > path && *endp == '/')
+ endp--;
+
+ /* All slashes becomes "/" */
+ if (endp == path && *endp == '/') {
+ startp = "/";
+ len = 1;
+ goto Exit;
+ }
+
+ /* Find the start of the base */
+ startp = endp;
+ while (startp > path && *(startp - 1) != '/')
+ startp--;
+
+ /* Cast is safe because max path < max int */
+ len = (int)(endp - startp + 1);
+
+Exit:
+ result = len;
+
+ if (buffer != NULL && git_str_set(buffer, startp, len) < 0)
+ return -1;
+
+ return result;
+}
+
+/*
+ * Determine if the path is a Windows prefix and, if so, returns
+ * its actual length. If it is not a prefix, returns -1.
+ */
+static int win32_prefix_length(const char *path, int len)
+{
+#ifndef GIT_WIN32
+ GIT_UNUSED(path);
+ GIT_UNUSED(len);
+#else
+ /*
+ * Mimic unix behavior where '/.git' returns '/': 'C:/.git'
+ * will return 'C:/' here
+ */
+ if (dos_drive_prefix_length(path) == len)
+ return len;
+
+ /*
+ * Similarly checks if we're dealing with a network computer name
+ * '//computername/.git' will return '//computername/'
+ */
+ if (looks_like_network_computer_name(path, len))
+ return len;
+#endif
+
+ return -1;
+}
+
+/*
+ * Based on the Android implementation, BSD licensed.
+ * Check http://android.git.kernel.org/
+ */
+int git_fs_path_dirname_r(git_str *buffer, const char *path)
+{
+ const char *endp;
+ int is_prefix = 0, len;
+
+ /* Empty or NULL string gets treated as "." */
+ if (path == NULL || *path == '\0') {
+ path = ".";
+ len = 1;
+ goto Exit;
+ }
+
+ /* Strip trailing slashes */
+ endp = path + strlen(path) - 1;
+ while (endp > path && *endp == '/')
+ endp--;
+
+ if (endp - path + 1 > INT_MAX) {
+ git_error_set(GIT_ERROR_INVALID, "path too long");
+ return -1;
+ }
+
+ if ((len = win32_prefix_length(path, (int)(endp - path + 1))) > 0) {
+ is_prefix = 1;
+ goto Exit;
+ }
+
+ /* Find the start of the dir */
+ while (endp > path && *endp != '/')
+ endp--;
+
+ /* Either the dir is "/" or there are no slashes */
+ if (endp == path) {
+ path = (*endp == '/') ? "/" : ".";
+ len = 1;
+ goto Exit;
+ }
+
+ do {
+ endp--;
+ } while (endp > path && *endp == '/');
+
+ if (endp - path + 1 > INT_MAX) {
+ git_error_set(GIT_ERROR_INVALID, "path too long");
+ return -1;
+ }
+
+ if ((len = win32_prefix_length(path, (int)(endp - path + 1))) > 0) {
+ is_prefix = 1;
+ goto Exit;
+ }
+
+ /* Cast is safe because max path < max int */
+ len = (int)(endp - path + 1);
+
+Exit:
+ if (buffer) {
+ if (git_str_set(buffer, path, len) < 0)
+ return -1;
+ if (is_prefix && git_str_putc(buffer, '/') < 0)
+ return -1;
+ }
+
+ return len;
+}
+
+
+char *git_fs_path_dirname(const char *path)
+{
+ git_str buf = GIT_STR_INIT;
+ char *dirname;
+
+ git_fs_path_dirname_r(&buf, path);
+ dirname = git_str_detach(&buf);
+ git_str_dispose(&buf); /* avoid memleak if error occurs */
+
+ return dirname;
+}
+
+char *git_fs_path_basename(const char *path)
+{
+ git_str buf = GIT_STR_INIT;
+ char *basename;
+
+ git_fs_path_basename_r(&buf, path);
+ basename = git_str_detach(&buf);
+ git_str_dispose(&buf); /* avoid memleak if error occurs */
+
+ return basename;
+}
+
+size_t git_fs_path_basename_offset(git_str *buffer)
+{
+ ssize_t slash;
+
+ if (!buffer || buffer->size <= 0)
+ return 0;
+
+ slash = git_str_rfind_next(buffer, '/');
+
+ if (slash >= 0 && buffer->ptr[slash] == '/')
+ return (size_t)(slash + 1);
+
+ return 0;
+}
+
+int git_fs_path_root(const char *path)
+{
+ int offset = 0, prefix_len;
+
+ /* Does the root of the path look like a windows drive ? */
+ if ((prefix_len = dos_drive_prefix_length(path)))
+ offset += prefix_len;
+
+#ifdef GIT_WIN32
+ /* Are we dealing with a windows network path? */
+ else if ((path[0] == '/' && path[1] == '/' && path[2] != '/') ||
+ (path[0] == '\\' && path[1] == '\\' && path[2] != '\\'))
+ {
+ offset += 2;
+
+ /* Skip the computer name segment */
+ while (path[offset] && path[offset] != '/' && path[offset] != '\\')
+ offset++;
+ }
+
+ if (path[offset] == '\\')
+ return offset;
+#endif
+
+ if (path[offset] == '/')
+ return offset;
+
+ return -1; /* Not a real error - signals that path is not rooted */
+}
+
+static void path_trim_slashes(git_str *path)
+{
+ int ceiling = git_fs_path_root(path->ptr) + 1;
+
+ if (ceiling < 0)
+ return;
+
+ while (path->size > (size_t)ceiling) {
+ if (path->ptr[path->size-1] != '/')
+ break;
+
+ path->ptr[path->size-1] = '\0';
+ path->size--;
+ }
+}
+
+int git_fs_path_join_unrooted(
+ git_str *path_out, const char *path, const char *base, ssize_t *root_at)
+{
+ ssize_t root;
+
+ GIT_ASSERT_ARG(path_out);
+ GIT_ASSERT_ARG(path);
+
+ root = (ssize_t)git_fs_path_root(path);
+
+ if (base != NULL && root < 0) {
+ if (git_str_joinpath(path_out, base, path) < 0)
+ return -1;
+
+ root = (ssize_t)strlen(base);
+ } else {
+ if (git_str_sets(path_out, path) < 0)
+ return -1;
+
+ if (root < 0)
+ root = 0;
+ else if (base)
+ git_fs_path_equal_or_prefixed(base, path, &root);
+ }
+
+ if (root_at)
+ *root_at = root;
+
+ return 0;
+}
+
+void git_fs_path_squash_slashes(git_str *path)
+{
+ char *p, *q;
+
+ if (path->size == 0)
+ return;
+
+ for (p = path->ptr, q = path->ptr; *q; p++, q++) {
+ *p = *q;
+
+ while (*q == '/' && *(q+1) == '/') {
+ path->size--;
+ q++;
+ }
+ }
+
+ *p = '\0';
+}
+
+int git_fs_path_prettify(git_str *path_out, const char *path, const char *base)
+{
+ char buf[GIT_PATH_MAX];
+
+ GIT_ASSERT_ARG(path_out);
+ GIT_ASSERT_ARG(path);
+
+ /* construct path if needed */
+ if (base != NULL && git_fs_path_root(path) < 0) {
+ if (git_str_joinpath(path_out, base, path) < 0)
+ return -1;
+ path = path_out->ptr;
+ }
+
+ if (p_realpath(path, buf) == NULL) {
+ /* git_error_set resets the errno when dealing with a GIT_ERROR_OS kind of error */
+ int error = (errno == ENOENT || errno == ENOTDIR) ? GIT_ENOTFOUND : -1;
+ git_error_set(GIT_ERROR_OS, "failed to resolve path '%s'", path);
+
+ git_str_clear(path_out);
+
+ return error;
+ }
+
+ return git_str_sets(path_out, buf);
+}
+
+int git_fs_path_prettify_dir(git_str *path_out, const char *path, const char *base)
+{
+ int error = git_fs_path_prettify(path_out, path, base);
+ return (error < 0) ? error : git_fs_path_to_dir(path_out);
+}
+
+int git_fs_path_to_dir(git_str *path)
+{
+ if (path->asize > 0 &&
+ git_str_len(path) > 0 &&
+ path->ptr[git_str_len(path) - 1] != '/')
+ git_str_putc(path, '/');
+
+ return git_str_oom(path) ? -1 : 0;
+}
+
+void git_fs_path_string_to_dir(char *path, size_t size)
+{
+ size_t end = strlen(path);
+
+ if (end && path[end - 1] != '/' && end < size) {
+ path[end] = '/';
+ path[end + 1] = '\0';
+ }
+}
+
+int git__percent_decode(git_str *decoded_out, const char *input)
+{
+ int len, hi, lo, i;
+
+ GIT_ASSERT_ARG(decoded_out);
+ GIT_ASSERT_ARG(input);
+
+ len = (int)strlen(input);
+ git_str_clear(decoded_out);
+
+ for(i = 0; i < len; i++)
+ {
+ char c = input[i];
+
+ if (c != '%')
+ goto append;
+
+ if (i >= len - 2)
+ goto append;
+
+ hi = git__fromhex(input[i + 1]);
+ lo = git__fromhex(input[i + 2]);
+
+ if (hi < 0 || lo < 0)
+ goto append;
+
+ c = (char)(hi << 4 | lo);
+ i += 2;
+
+append:
+ if (git_str_putc(decoded_out, c) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+static int error_invalid_local_file_uri(const char *uri)
+{
+ git_error_set(GIT_ERROR_CONFIG, "'%s' is not a valid local file URI", uri);
+ return -1;
+}
+
+static int local_file_url_prefixlen(const char *file_url)
+{
+ int len = -1;
+
+ if (git__prefixcmp(file_url, "file://") == 0) {
+ if (file_url[7] == '/')
+ len = 8;
+ else if (git__prefixcmp(file_url + 7, "localhost/") == 0)
+ len = 17;
+ }
+
+ return len;
+}
+
+bool git_fs_path_is_local_file_url(const char *file_url)
+{
+ return (local_file_url_prefixlen(file_url) > 0);
+}
+
+int git_fs_path_fromurl(git_str *local_path_out, const char *file_url)
+{
+ int offset;
+
+ GIT_ASSERT_ARG(local_path_out);
+ GIT_ASSERT_ARG(file_url);
+
+ if ((offset = local_file_url_prefixlen(file_url)) < 0 ||
+ file_url[offset] == '\0' || file_url[offset] == '/')
+ return error_invalid_local_file_uri(file_url);
+
+#ifndef GIT_WIN32
+ offset--; /* A *nix absolute path starts with a forward slash */
+#endif
+
+ git_str_clear(local_path_out);
+ return git__percent_decode(local_path_out, file_url + offset);
+}
+
+int git_fs_path_walk_up(
+ git_str *path,
+ const char *ceiling,
+ int (*cb)(void *data, const char *),
+ void *data)
+{
+ int error = 0;
+ git_str iter;
+ ssize_t stop = 0, scan;
+ char oldc = '\0';
+
+ GIT_ASSERT_ARG(path);
+ GIT_ASSERT_ARG(cb);
+
+ if (ceiling != NULL) {
+ if (git__prefixcmp(path->ptr, ceiling) == 0)
+ stop = (ssize_t)strlen(ceiling);
+ else
+ stop = git_str_len(path);
+ }
+ scan = git_str_len(path);
+
+ /* empty path: yield only once */
+ if (!scan) {
+ error = cb(data, "");
+ if (error)
+ ensure_error_set(error);
+ return error;
+ }
+
+ iter.ptr = path->ptr;
+ iter.size = git_str_len(path);
+ iter.asize = path->asize;
+
+ while (scan >= stop) {
+ error = cb(data, iter.ptr);
+ iter.ptr[scan] = oldc;
+
+ if (error) {
+ ensure_error_set(error);
+ break;
+ }
+
+ scan = git_str_rfind_next(&iter, '/');
+ if (scan >= 0) {
+ scan++;
+ oldc = iter.ptr[scan];
+ iter.size = scan;
+ iter.ptr[scan] = '\0';
+ }
+ }
+
+ if (scan >= 0)
+ iter.ptr[scan] = oldc;
+
+ /* relative path: yield for the last component */
+ if (!error && stop == 0 && iter.ptr[0] != '/') {
+ error = cb(data, "");
+ if (error)
+ ensure_error_set(error);
+ }
+
+ return error;
+}
+
+bool git_fs_path_exists(const char *path)
+{
+ GIT_ASSERT_ARG_WITH_RETVAL(path, false);
+ return p_access(path, F_OK) == 0;
+}
+
+bool git_fs_path_isdir(const char *path)
+{
+ struct stat st;
+ if (p_stat(path, &st) < 0)
+ return false;
+
+ return S_ISDIR(st.st_mode) != 0;
+}
+
+bool git_fs_path_isfile(const char *path)
+{
+ struct stat st;
+
+ GIT_ASSERT_ARG_WITH_RETVAL(path, false);
+ if (p_stat(path, &st) < 0)
+ return false;
+
+ return S_ISREG(st.st_mode) != 0;
+}
+
+bool git_fs_path_islink(const char *path)
+{
+ struct stat st;
+
+ GIT_ASSERT_ARG_WITH_RETVAL(path, false);
+ if (p_lstat(path, &st) < 0)
+ return false;
+
+ return S_ISLNK(st.st_mode) != 0;
+}
+
+#ifdef GIT_WIN32
+
+bool git_fs_path_is_empty_dir(const char *path)
+{
+ git_win32_path filter_w;
+ bool empty = false;
+
+ if (git_win32__findfirstfile_filter(filter_w, path)) {
+ WIN32_FIND_DATAW findData;
+ HANDLE hFind = FindFirstFileW(filter_w, &findData);
+
+ /* FindFirstFile will fail if there are no children to the given
+ * path, which can happen if the given path is a file (and obviously
+ * has no children) or if the given path is an empty mount point.
+ * (Most directories have at least directory entries '.' and '..',
+ * but ridiculously another volume mounted in another drive letter's
+ * path space do not, and thus have nothing to enumerate.) If
+ * FindFirstFile fails, check if this is a directory-like thing
+ * (a mount point).
+ */
+ if (hFind == INVALID_HANDLE_VALUE)
+ return git_fs_path_isdir(path);
+
+ /* If the find handle was created successfully, then it's a directory */
+ empty = true;
+
+ do {
+ /* Allow the enumeration to return . and .. and still be considered
+ * empty. In the special case of drive roots (i.e. C:\) where . and
+ * .. do not occur, we can still consider the path to be an empty
+ * directory if there's nothing there. */
+ if (!git_fs_path_is_dot_or_dotdotW(findData.cFileName)) {
+ empty = false;
+ break;
+ }
+ } while (FindNextFileW(hFind, &findData));
+
+ FindClose(hFind);
+ }
+
+ return empty;
+}
+
+#else
+
+static int path_found_entry(void *payload, git_str *path)
+{
+ GIT_UNUSED(payload);
+ return !git_fs_path_is_dot_or_dotdot(path->ptr);
+}
+
+bool git_fs_path_is_empty_dir(const char *path)
+{
+ int error;
+ git_str dir = GIT_STR_INIT;
+
+ if (!git_fs_path_isdir(path))
+ return false;
+
+ if ((error = git_str_sets(&dir, path)) != 0)
+ git_error_clear();
+ else
+ error = git_fs_path_direach(&dir, 0, path_found_entry, NULL);
+
+ git_str_dispose(&dir);
+
+ return !error;
+}
+
+#endif
+
+int git_fs_path_set_error(int errno_value, const char *path, const char *action)
+{
+ switch (errno_value) {
+ case ENOENT:
+ case ENOTDIR:
+ git_error_set(GIT_ERROR_OS, "could not find '%s' to %s", path, action);
+ return GIT_ENOTFOUND;
+
+ case EINVAL:
+ case ENAMETOOLONG:
+ git_error_set(GIT_ERROR_OS, "invalid path for filesystem '%s'", path);
+ return GIT_EINVALIDSPEC;
+
+ case EEXIST:
+ git_error_set(GIT_ERROR_OS, "failed %s - '%s' already exists", action, path);
+ return GIT_EEXISTS;
+
+ case EACCES:
+ git_error_set(GIT_ERROR_OS, "failed %s - '%s' is locked", action, path);
+ return GIT_ELOCKED;
+
+ default:
+ git_error_set(GIT_ERROR_OS, "could not %s '%s'", action, path);
+ return -1;
+ }
+}
+
+int git_fs_path_lstat(const char *path, struct stat *st)
+{
+ if (p_lstat(path, st) == 0)
+ return 0;
+
+ return git_fs_path_set_error(errno, path, "stat");
+}
+
+static bool _check_dir_contents(
+ git_str *dir,
+ const char *sub,
+ bool (*predicate)(const char *))
+{
+ bool result;
+ size_t dir_size = git_str_len(dir);
+ size_t sub_size = strlen(sub);
+ size_t alloc_size;
+
+ /* leave base valid even if we could not make space for subdir */
+ if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, dir_size, sub_size) ||
+ GIT_ADD_SIZET_OVERFLOW(&alloc_size, alloc_size, 2) ||
+ git_str_try_grow(dir, alloc_size, false) < 0)
+ return false;
+
+ /* save excursion */
+ if (git_str_joinpath(dir, dir->ptr, sub) < 0)
+ return false;
+
+ result = predicate(dir->ptr);
+
+ /* restore path */
+ git_str_truncate(dir, dir_size);
+ return result;
+}
+
+bool git_fs_path_contains(git_str *dir, const char *item)
+{
+ return _check_dir_contents(dir, item, &git_fs_path_exists);
+}
+
+bool git_fs_path_contains_dir(git_str *base, const char *subdir)
+{
+ return _check_dir_contents(base, subdir, &git_fs_path_isdir);
+}
+
+bool git_fs_path_contains_file(git_str *base, const char *file)
+{
+ return _check_dir_contents(base, file, &git_fs_path_isfile);
+}
+
+int git_fs_path_find_dir(git_str *dir)
+{
+ int error = 0;
+ char buf[GIT_PATH_MAX];
+
+ if (p_realpath(dir->ptr, buf) != NULL)
+ error = git_str_sets(dir, buf);
+
+ /* call dirname if this is not a directory */
+ if (!error) /* && git_fs_path_isdir(dir->ptr) == false) */
+ error = (git_fs_path_dirname_r(dir, dir->ptr) < 0) ? -1 : 0;
+
+ if (!error)
+ error = git_fs_path_to_dir(dir);
+
+ return error;
+}
+
+int git_fs_path_resolve_relative(git_str *path, size_t ceiling)
+{
+ char *base, *to, *from, *next;
+ size_t len;
+
+ GIT_ERROR_CHECK_ALLOC_STR(path);
+
+ if (ceiling > path->size)
+ ceiling = path->size;
+
+ /* recognize drive prefixes, etc. that should not be backed over */
+ if (ceiling == 0)
+ ceiling = git_fs_path_root(path->ptr) + 1;
+
+ /* recognize URL prefixes that should not be backed over */
+ if (ceiling == 0) {
+ for (next = path->ptr; *next && git__isalpha(*next); ++next);
+ if (next[0] == ':' && next[1] == '/' && next[2] == '/')
+ ceiling = (next + 3) - path->ptr;
+ }
+
+ base = to = from = path->ptr + ceiling;
+
+ while (*from) {
+ for (next = from; *next && *next != '/'; ++next);
+
+ len = next - from;
+
+ if (len == 1 && from[0] == '.')
+ /* do nothing with singleton dot */;
+
+ else if (len == 2 && from[0] == '.' && from[1] == '.') {
+ /* error out if trying to up one from a hard base */
+ if (to == base && ceiling != 0) {
+ git_error_set(GIT_ERROR_INVALID,
+ "cannot strip root component off url");
+ return -1;
+ }
+
+ /* no more path segments to strip,
+ * use '../' as a new base path */
+ if (to == base) {
+ if (*next == '/')
+ len++;
+
+ if (to != from)
+ memmove(to, from, len);
+
+ to += len;
+ /* this is now the base, can't back up from a
+ * relative prefix */
+ base = to;
+ } else {
+ /* back up a path segment */
+ while (to > base && to[-1] == '/') to--;
+ while (to > base && to[-1] != '/') to--;
+ }
+ } else {
+ if (*next == '/' && *from != '/')
+ len++;
+
+ if (to != from)
+ memmove(to, from, len);
+
+ to += len;
+ }
+
+ from += len;
+
+ while (*from == '/') from++;
+ }
+
+ *to = '\0';
+
+ path->size = to - path->ptr;
+
+ return 0;
+}
+
+int git_fs_path_apply_relative(git_str *target, const char *relpath)
+{
+ return git_str_joinpath(target, git_str_cstr(target), relpath) ||
+ git_fs_path_resolve_relative(target, 0);
+}
+
+int git_fs_path_cmp(
+ const char *name1, size_t len1, int isdir1,
+ const char *name2, size_t len2, int isdir2,
+ int (*compare)(const char *, const char *, size_t))
+{
+ unsigned char c1, c2;
+ size_t len = len1 < len2 ? len1 : len2;
+ int cmp;
+
+ cmp = compare(name1, name2, len);
+ if (cmp)
+ return cmp;
+
+ c1 = name1[len];
+ c2 = name2[len];
+
+ if (c1 == '\0' && isdir1)
+ c1 = '/';
+
+ if (c2 == '\0' && isdir2)
+ c2 = '/';
+
+ return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;
+}
+
+size_t git_fs_path_common_dirlen(const char *one, const char *two)
+{
+ const char *p, *q, *dirsep = NULL;
+
+ for (p = one, q = two; *p && *q; p++, q++) {
+ if (*p == '/' && *q == '/')
+ dirsep = p;
+ else if (*p != *q)
+ break;
+ }
+
+ return dirsep ? (dirsep - one) + 1 : 0;
+}
+
+int git_fs_path_make_relative(git_str *path, const char *parent)
+{
+ const char *p, *q, *p_dirsep, *q_dirsep;
+ size_t plen = path->size, newlen, alloclen, depth = 1, i, offset;
+
+ for (p_dirsep = p = path->ptr, q_dirsep = q = parent; *p && *q; p++, q++) {
+ if (*p == '/' && *q == '/') {
+ p_dirsep = p;
+ q_dirsep = q;
+ }
+ else if (*p != *q)
+ break;
+ }
+
+ /* need at least 1 common path segment */
+ if ((p_dirsep == path->ptr || q_dirsep == parent) &&
+ (*p_dirsep != '/' || *q_dirsep != '/')) {
+ git_error_set(GIT_ERROR_INVALID,
+ "%s is not a parent of %s", parent, path->ptr);
+ return GIT_ENOTFOUND;
+ }
+
+ if (*p == '/' && !*q)
+ p++;
+ else if (!*p && *q == '/')
+ q++;
+ else if (!*p && !*q)
+ return git_str_clear(path), 0;
+ else {
+ p = p_dirsep + 1;
+ q = q_dirsep + 1;
+ }
+
+ plen -= (p - path->ptr);
+
+ if (!*q)
+ return git_str_set(path, p, plen);
+
+ for (; (q = strchr(q, '/')) && *(q + 1); q++)
+ depth++;
+
+ GIT_ERROR_CHECK_ALLOC_MULTIPLY(&newlen, depth, 3);
+ GIT_ERROR_CHECK_ALLOC_ADD(&newlen, newlen, plen);
+
+ GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, newlen, 1);
+
+ /* save the offset as we might realllocate the pointer */
+ offset = p - path->ptr;
+ if (git_str_try_grow(path, alloclen, 1) < 0)
+ return -1;
+ p = path->ptr + offset;
+
+ memmove(path->ptr + (depth * 3), p, plen + 1);
+
+ for (i = 0; i < depth; i++)
+ memcpy(path->ptr + (i * 3), "../", 3);
+
+ path->size = newlen;
+ return 0;
+}
+
+bool git_fs_path_has_non_ascii(const char *path, size_t pathlen)
+{
+ const uint8_t *scan = (const uint8_t *)path, *end;
+
+ for (end = scan + pathlen; scan < end; ++scan)
+ if (*scan & 0x80)
+ return true;
+
+ return false;
+}
+
+#ifdef GIT_USE_ICONV
+
+int git_fs_path_iconv_init_precompose(git_fs_path_iconv_t *ic)
+{
+ git_str_init(&ic->buf, 0);
+ ic->map = iconv_open(GIT_PATH_REPO_ENCODING, GIT_PATH_NATIVE_ENCODING);
+ return 0;
+}
+
+void git_fs_path_iconv_clear(git_fs_path_iconv_t *ic)
+{
+ if (ic) {
+ if (ic->map != (iconv_t)-1)
+ iconv_close(ic->map);
+ git_str_dispose(&ic->buf);
+ }
+}
+
+int git_fs_path_iconv(git_fs_path_iconv_t *ic, const char **in, size_t *inlen)
+{
+ char *nfd = (char*)*in, *nfc;
+ size_t nfdlen = *inlen, nfclen, wantlen = nfdlen, alloclen, rv;
+ int retry = 1;
+
+ if (!ic || ic->map == (iconv_t)-1 ||
+ !git_fs_path_has_non_ascii(*in, *inlen))
+ return 0;
+
+ git_str_clear(&ic->buf);
+
+ while (1) {
+ GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, wantlen, 1);
+ if (git_str_grow(&ic->buf, alloclen) < 0)
+ return -1;
+
+ nfc = ic->buf.ptr + ic->buf.size;
+ nfclen = ic->buf.asize - ic->buf.size;
+
+ rv = iconv(ic->map, &nfd, &nfdlen, &nfc, &nfclen);
+
+ ic->buf.size = (nfc - ic->buf.ptr);
+
+ if (rv != (size_t)-1)
+ break;
+
+ /* if we cannot convert the data (probably because iconv thinks
+ * it is not valid UTF-8 source data), then use original data
+ */
+ if (errno != E2BIG)
+ return 0;
+
+ /* make space for 2x the remaining data to be converted
+ * (with per retry overhead to avoid infinite loops)
+ */
+ wantlen = ic->buf.size + max(nfclen, nfdlen) * 2 + (size_t)(retry * 4);
+
+ if (retry++ > 4)
+ goto fail;
+ }
+
+ ic->buf.ptr[ic->buf.size] = '\0';
+
+ *in = ic->buf.ptr;
+ *inlen = ic->buf.size;
+
+ return 0;
+
+fail:
+ git_error_set(GIT_ERROR_OS, "unable to convert unicode path data");
+ return -1;
+}
+
+static const char *nfc_file = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D";
+static const char *nfd_file = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D";
+
+/* Check if the platform is decomposing unicode data for us. We will
+ * emulate core Git and prefer to use precomposed unicode data internally
+ * on these platforms, composing the decomposed unicode on the fly.
+ *
+ * This mainly happens on the Mac where HDFS stores filenames as
+ * decomposed unicode. Even on VFAT and SAMBA file systems, the Mac will
+ * return decomposed unicode from readdir() even when the actual
+ * filesystem is storing precomposed unicode.
+ */
+bool git_fs_path_does_decompose_unicode(const char *root)
+{
+ git_str nfc_path = GIT_STR_INIT;
+ git_str nfd_path = GIT_STR_INIT;
+ int fd;
+ bool found_decomposed = false;
+ size_t orig_len;
+ const char *trailer;
+
+ /* Create a file using a precomposed path and then try to find it
+ * using the decomposed name. If the lookup fails, then we will mark
+ * that we should precompose unicode for this repository.
+ */
+ if (git_str_joinpath(&nfc_path, root, nfc_file) < 0)
+ goto done;
+
+ /* record original path length before trailer */
+ orig_len = nfc_path.size;
+
+ if ((fd = git_futils_mktmp(&nfc_path, nfc_path.ptr, 0666)) < 0)
+ goto done;
+ p_close(fd);
+
+ trailer = nfc_path.ptr + orig_len;
+
+ /* try to look up as NFD path */
+ if (git_str_joinpath(&nfd_path, root, nfd_file) < 0 ||
+ git_str_puts(&nfd_path, trailer) < 0)
+ goto done;
+
+ found_decomposed = git_fs_path_exists(nfd_path.ptr);
+
+ /* remove temporary file (using original precomposed path) */
+ (void)p_unlink(nfc_path.ptr);
+
+done:
+ git_str_dispose(&nfc_path);
+ git_str_dispose(&nfd_path);
+ return found_decomposed;
+}
+
+#else
+
+bool git_fs_path_does_decompose_unicode(const char *root)
+{
+ GIT_UNUSED(root);
+ return false;
+}
+
+#endif
+
+#if defined(__sun) || defined(__GNU__)
+typedef char path_dirent_data[sizeof(struct dirent) + FILENAME_MAX + 1];
+#else
+typedef struct dirent path_dirent_data;
+#endif
+
+int git_fs_path_direach(
+ git_str *path,
+ uint32_t flags,
+ int (*fn)(void *, git_str *),
+ void *arg)
+{
+ int error = 0;
+ ssize_t wd_len;
+ DIR *dir;
+ struct dirent *de;
+
+#ifdef GIT_USE_ICONV
+ git_fs_path_iconv_t ic = GIT_PATH_ICONV_INIT;
+#endif
+
+ GIT_UNUSED(flags);
+
+ if (git_fs_path_to_dir(path) < 0)
+ return -1;
+
+ wd_len = git_str_len(path);
+
+ if ((dir = opendir(path->ptr)) == NULL) {
+ git_error_set(GIT_ERROR_OS, "failed to open directory '%s'", path->ptr);
+ if (errno == ENOENT)
+ return GIT_ENOTFOUND;
+
+ return -1;
+ }
+
+#ifdef GIT_USE_ICONV
+ if ((flags & GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE) != 0)
+ (void)git_fs_path_iconv_init_precompose(&ic);
+#endif
+
+ while ((de = readdir(dir)) != NULL) {
+ const char *de_path = de->d_name;
+ size_t de_len = strlen(de_path);
+
+ if (git_fs_path_is_dot_or_dotdot(de_path))
+ continue;
+
+#ifdef GIT_USE_ICONV
+ if ((error = git_fs_path_iconv(&ic, &de_path, &de_len)) < 0)
+ break;
+#endif
+
+ if ((error = git_str_put(path, de_path, de_len)) < 0)
+ break;
+
+ git_error_clear();
+ error = fn(arg, path);
+
+ git_str_truncate(path, wd_len); /* restore path */
+
+ /* Only set our own error if the callback did not set one already */
+ if (error != 0) {
+ if (!git_error_last())
+ ensure_error_set(error);
+
+ break;
+ }
+ }
+
+ closedir(dir);
+
+#ifdef GIT_USE_ICONV
+ git_fs_path_iconv_clear(&ic);
+#endif
+
+ return error;
+}
+
+#if defined(GIT_WIN32) && !defined(__MINGW32__)
+
+/* Using _FIND_FIRST_EX_LARGE_FETCH may increase performance in Windows 7
+ * and better.
+ */
+#ifndef FIND_FIRST_EX_LARGE_FETCH
+# define FIND_FIRST_EX_LARGE_FETCH 2
+#endif
+
+int git_fs_path_diriter_init(
+ git_fs_path_diriter *diriter,
+ const char *path,
+ unsigned int flags)
+{
+ git_win32_path path_filter;
+
+ static int is_win7_or_later = -1;
+ if (is_win7_or_later < 0)
+ is_win7_or_later = git_has_win32_version(6, 1, 0);
+
+ GIT_ASSERT_ARG(diriter);
+ GIT_ASSERT_ARG(path);
+
+ memset(diriter, 0, sizeof(git_fs_path_diriter));
+ diriter->handle = INVALID_HANDLE_VALUE;
+
+ if (git_str_puts(&diriter->path_utf8, path) < 0)
+ return -1;
+
+ path_trim_slashes(&diriter->path_utf8);
+
+ if (diriter->path_utf8.size == 0) {
+ git_error_set(GIT_ERROR_FILESYSTEM, "could not open directory '%s'", path);
+ return -1;
+ }
+
+ if ((diriter->parent_len = git_win32_path_from_utf8(diriter->path, diriter->path_utf8.ptr)) < 0 ||
+ !git_win32__findfirstfile_filter(path_filter, diriter->path_utf8.ptr)) {
+ git_error_set(GIT_ERROR_OS, "could not parse the directory path '%s'", path);
+ return -1;
+ }
+
+ diriter->handle = FindFirstFileExW(
+ path_filter,
+ is_win7_or_later ? FindExInfoBasic : FindExInfoStandard,
+ &diriter->current,
+ FindExSearchNameMatch,
+ NULL,
+ is_win7_or_later ? FIND_FIRST_EX_LARGE_FETCH : 0);
+
+ if (diriter->handle == INVALID_HANDLE_VALUE) {
+ git_error_set(GIT_ERROR_OS, "could not open directory '%s'", path);
+ return -1;
+ }
+
+ diriter->parent_utf8_len = diriter->path_utf8.size;
+ diriter->flags = flags;
+ return 0;
+}
+
+static int diriter_update_paths(git_fs_path_diriter *diriter)
+{
+ size_t filename_len, path_len;
+
+ filename_len = wcslen(diriter->current.cFileName);
+
+ if (GIT_ADD_SIZET_OVERFLOW(&path_len, diriter->parent_len, filename_len) ||
+ GIT_ADD_SIZET_OVERFLOW(&path_len, path_len, 2))
+ return -1;
+
+ if (path_len > GIT_WIN_PATH_UTF16) {
+ git_error_set(GIT_ERROR_FILESYSTEM,
+ "invalid path '%.*ls\\%ls' (path too long)",
+ diriter->parent_len, diriter->path, diriter->current.cFileName);
+ return -1;
+ }
+
+ diriter->path[diriter->parent_len] = L'\\';
+ memcpy(&diriter->path[diriter->parent_len+1],
+ diriter->current.cFileName, filename_len * sizeof(wchar_t));
+ diriter->path[path_len-1] = L'\0';
+
+ git_str_truncate(&diriter->path_utf8, diriter->parent_utf8_len);
+
+ if (diriter->parent_utf8_len > 0 &&
+ diriter->path_utf8.ptr[diriter->parent_utf8_len-1] != '/')
+ git_str_putc(&diriter->path_utf8, '/');
+
+ git_str_put_w(&diriter->path_utf8, diriter->current.cFileName, filename_len);
+
+ if (git_str_oom(&diriter->path_utf8))
+ return -1;
+
+ return 0;
+}
+
+int git_fs_path_diriter_next(git_fs_path_diriter *diriter)
+{
+ bool skip_dot = !(diriter->flags & GIT_FS_PATH_DIR_INCLUDE_DOT_AND_DOTDOT);
+
+ do {
+ /* Our first time through, we already have the data from
+ * FindFirstFileW. Use it, otherwise get the next file.
+ */
+ if (!diriter->needs_next)
+ diriter->needs_next = 1;
+ else if (!FindNextFileW(diriter->handle, &diriter->current))
+ return GIT_ITEROVER;
+ } while (skip_dot && git_fs_path_is_dot_or_dotdotW(diriter->current.cFileName));
+
+ if (diriter_update_paths(diriter) < 0)
+ return -1;
+
+ return 0;
+}
+
+int git_fs_path_diriter_filename(
+ const char **out,
+ size_t *out_len,
+ git_fs_path_diriter *diriter)
+{
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(out_len);
+ GIT_ASSERT_ARG(diriter);
+ GIT_ASSERT(diriter->path_utf8.size > diriter->parent_utf8_len);
+
+ *out = &diriter->path_utf8.ptr[diriter->parent_utf8_len+1];
+ *out_len = diriter->path_utf8.size - diriter->parent_utf8_len - 1;
+ return 0;
+}
+
+int git_fs_path_diriter_fullpath(
+ const char **out,
+ size_t *out_len,
+ git_fs_path_diriter *diriter)
+{
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(out_len);
+ GIT_ASSERT_ARG(diriter);
+
+ *out = diriter->path_utf8.ptr;
+ *out_len = diriter->path_utf8.size;
+ return 0;
+}
+
+int git_fs_path_diriter_stat(struct stat *out, git_fs_path_diriter *diriter)
+{
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(diriter);
+
+ return git_win32__file_attribute_to_stat(out,
+ (WIN32_FILE_ATTRIBUTE_DATA *)&diriter->current,
+ diriter->path);
+}
+
+void git_fs_path_diriter_free(git_fs_path_diriter *diriter)
+{
+ if (diriter == NULL)
+ return;
+
+ git_str_dispose(&diriter->path_utf8);
+
+ if (diriter->handle != INVALID_HANDLE_VALUE) {
+ FindClose(diriter->handle);
+ diriter->handle = INVALID_HANDLE_VALUE;
+ }
+}
+
+#else
+
+int git_fs_path_diriter_init(
+ git_fs_path_diriter *diriter,
+ const char *path,
+ unsigned int flags)
+{
+ GIT_ASSERT_ARG(diriter);
+ GIT_ASSERT_ARG(path);
+
+ memset(diriter, 0, sizeof(git_fs_path_diriter));
+
+ if (git_str_puts(&diriter->path, path) < 0)
+ return -1;
+
+ path_trim_slashes(&diriter->path);
+
+ if (diriter->path.size == 0) {
+ git_error_set(GIT_ERROR_FILESYSTEM, "could not open directory '%s'", path);
+ return -1;
+ }
+
+ if ((diriter->dir = opendir(diriter->path.ptr)) == NULL) {
+ git_str_dispose(&diriter->path);
+
+ git_error_set(GIT_ERROR_OS, "failed to open directory '%s'", path);
+ return -1;
+ }
+
+#ifdef GIT_USE_ICONV
+ if ((flags & GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE) != 0)
+ (void)git_fs_path_iconv_init_precompose(&diriter->ic);
+#endif
+
+ diriter->parent_len = diriter->path.size;
+ diriter->flags = flags;
+
+ return 0;
+}
+
+int git_fs_path_diriter_next(git_fs_path_diriter *diriter)
+{
+ struct dirent *de;
+ const char *filename;
+ size_t filename_len;
+ bool skip_dot = !(diriter->flags & GIT_FS_PATH_DIR_INCLUDE_DOT_AND_DOTDOT);
+ int error = 0;
+
+ GIT_ASSERT_ARG(diriter);
+
+ errno = 0;
+
+ do {
+ if ((de = readdir(diriter->dir)) == NULL) {
+ if (!errno)
+ return GIT_ITEROVER;
+
+ git_error_set(GIT_ERROR_OS,
+ "could not read directory '%s'", diriter->path.ptr);
+ return -1;
+ }
+ } while (skip_dot && git_fs_path_is_dot_or_dotdot(de->d_name));
+
+ filename = de->d_name;
+ filename_len = strlen(filename);
+
+#ifdef GIT_USE_ICONV
+ if ((diriter->flags & GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE) != 0 &&
+ (error = git_fs_path_iconv(&diriter->ic, &filename, &filename_len)) < 0)
+ return error;
+#endif
+
+ git_str_truncate(&diriter->path, diriter->parent_len);
+
+ if (diriter->parent_len > 0 &&
+ diriter->path.ptr[diriter->parent_len-1] != '/')
+ git_str_putc(&diriter->path, '/');
+
+ git_str_put(&diriter->path, filename, filename_len);
+
+ if (git_str_oom(&diriter->path))
+ return -1;
+
+ return error;
+}
+
+int git_fs_path_diriter_filename(
+ const char **out,
+ size_t *out_len,
+ git_fs_path_diriter *diriter)
+{
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(out_len);
+ GIT_ASSERT_ARG(diriter);
+ GIT_ASSERT(diriter->path.size > diriter->parent_len);
+
+ *out = &diriter->path.ptr[diriter->parent_len+1];
+ *out_len = diriter->path.size - diriter->parent_len - 1;
+ return 0;
+}
+
+int git_fs_path_diriter_fullpath(
+ const char **out,
+ size_t *out_len,
+ git_fs_path_diriter *diriter)
+{
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(out_len);
+ GIT_ASSERT_ARG(diriter);
+
+ *out = diriter->path.ptr;
+ *out_len = diriter->path.size;
+ return 0;
+}
+
+int git_fs_path_diriter_stat(struct stat *out, git_fs_path_diriter *diriter)
+{
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(diriter);
+
+ return git_fs_path_lstat(diriter->path.ptr, out);
+}
+
+void git_fs_path_diriter_free(git_fs_path_diriter *diriter)
+{
+ if (diriter == NULL)
+ return;
+
+ if (diriter->dir) {
+ closedir(diriter->dir);
+ diriter->dir = NULL;
+ }
+
+#ifdef GIT_USE_ICONV
+ git_fs_path_iconv_clear(&diriter->ic);
+#endif
+
+ git_str_dispose(&diriter->path);
+}
+
+#endif
+
+int git_fs_path_dirload(
+ git_vector *contents,
+ const char *path,
+ size_t prefix_len,
+ uint32_t flags)
+{
+ git_fs_path_diriter iter = GIT_FS_PATH_DIRITER_INIT;
+ const char *name;
+ size_t name_len;
+ char *dup;
+ int error;
+
+ GIT_ASSERT_ARG(contents);
+ GIT_ASSERT_ARG(path);
+
+ if ((error = git_fs_path_diriter_init(&iter, path, flags)) < 0)
+ return error;
+
+ while ((error = git_fs_path_diriter_next(&iter)) == 0) {
+ if ((error = git_fs_path_diriter_fullpath(&name, &name_len, &iter)) < 0)
+ break;
+
+ GIT_ASSERT(name_len > prefix_len);
+
+ dup = git__strndup(name + prefix_len, name_len - prefix_len);
+ GIT_ERROR_CHECK_ALLOC(dup);
+
+ if ((error = git_vector_insert(contents, dup)) < 0)
+ break;
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ git_fs_path_diriter_free(&iter);
+ return error;
+}
+
+int git_fs_path_from_url_or_path(git_str *local_path_out, const char *url_or_path)
+{
+ if (git_fs_path_is_local_file_url(url_or_path))
+ return git_fs_path_fromurl(local_path_out, url_or_path);
+ else
+ return git_str_sets(local_path_out, url_or_path);
+}
+
+/* Reject paths like AUX or COM1, or those versions that end in a dot or
+ * colon. ("AUX." or "AUX:")
+ */
+GIT_INLINE(bool) validate_dospath(
+ const char *component,
+ size_t len,
+ const char dospath[3],
+ bool trailing_num)
+{
+ size_t last = trailing_num ? 4 : 3;
+
+ if (len < last || git__strncasecmp(component, dospath, 3) != 0)
+ return true;
+
+ if (trailing_num && (component[3] < '1' || component[3] > '9'))
+ return true;
+
+ return (len > last &&
+ component[last] != '.' &&
+ component[last] != ':');
+}
+
+GIT_INLINE(bool) validate_char(unsigned char c, unsigned int flags)
+{
+ if ((flags & GIT_FS_PATH_REJECT_BACKSLASH) && c == '\\')
+ return false;
+
+ if ((flags & GIT_FS_PATH_REJECT_SLASH) && c == '/')
+ return false;
+
+ if (flags & GIT_FS_PATH_REJECT_NT_CHARS) {
+ if (c < 32)
+ return false;
+
+ switch (c) {
+ case '<':
+ case '>':
+ case ':':
+ case '"':
+ case '|':
+ case '?':
+ case '*':
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/*
+ * We fundamentally don't like some paths when dealing with user-inputted
+ * strings (to avoid escaping a sandbox): we don't want dot or dot-dot
+ * anywhere, we want to avoid writing weird paths on Windows that can't
+ * be handled by tools that use the non-\\?\ APIs, we don't want slashes
+ * or double slashes at the end of paths that can make them ambiguous.
+ *
+ * For checkout, we don't want to recurse into ".git" either.
+ */
+static bool validate_component(
+ const char *component,
+ size_t len,
+ unsigned int flags)
+{
+ if (len == 0)
+ return !(flags & GIT_FS_PATH_REJECT_EMPTY_COMPONENT);
+
+ if ((flags & GIT_FS_PATH_REJECT_TRAVERSAL) &&
+ len == 1 && component[0] == '.')
+ return false;
+
+ if ((flags & GIT_FS_PATH_REJECT_TRAVERSAL) &&
+ len == 2 && component[0] == '.' && component[1] == '.')
+ return false;
+
+ if ((flags & GIT_FS_PATH_REJECT_TRAILING_DOT) &&
+ component[len - 1] == '.')
+ return false;
+
+ if ((flags & GIT_FS_PATH_REJECT_TRAILING_SPACE) &&
+ component[len - 1] == ' ')
+ return false;
+
+ if ((flags & GIT_FS_PATH_REJECT_TRAILING_COLON) &&
+ component[len - 1] == ':')
+ return false;
+
+ if (flags & GIT_FS_PATH_REJECT_DOS_PATHS) {
+ if (!validate_dospath(component, len, "CON", false) ||
+ !validate_dospath(component, len, "PRN", false) ||
+ !validate_dospath(component, len, "AUX", false) ||
+ !validate_dospath(component, len, "NUL", false) ||
+ !validate_dospath(component, len, "COM", true) ||
+ !validate_dospath(component, len, "LPT", true))
+ return false;
+ }
+
+ return true;
+}
+
+#ifdef GIT_WIN32
+GIT_INLINE(bool) validate_length(
+ const char *path,
+ size_t len,
+ size_t utf8_char_len)
+{
+ GIT_UNUSED(path);
+ GIT_UNUSED(len);
+
+ return (utf8_char_len <= MAX_PATH);
+}
+#endif
+
+bool git_fs_path_str_is_valid_ext(
+ const git_str *path,
+ unsigned int flags,
+ bool (*validate_char_cb)(char ch, void *payload),
+ bool (*validate_component_cb)(const char *component, size_t len, void *payload),
+ bool (*validate_length_cb)(const char *path, size_t len, size_t utf8_char_len),
+ void *payload)
+{
+ const char *start, *c;
+ size_t len = 0;
+
+ if (!flags)
+ return true;
+
+ for (start = c = path->ptr; *c && len < path->size; c++, len++) {
+ if (!validate_char(*c, flags))
+ return false;
+
+ if (validate_char_cb && !validate_char_cb(*c, payload))
+ return false;
+
+ if (*c != '/')
+ continue;
+
+ if (!validate_component(start, (c - start), flags))
+ return false;
+
+ if (validate_component_cb &&
+ !validate_component_cb(start, (c - start), payload))
+ return false;
+
+ start = c + 1;
+ }
+
+ /*
+ * We want to support paths specified as either `const char *`
+ * or `git_str *`; we pass size as `SIZE_MAX` when we use a
+ * `const char *` to avoid a `strlen`. Ensure that we didn't
+ * have a NUL in the buffer if there was a non-SIZE_MAX length.
+ */
+ if (path->size != SIZE_MAX && len != path->size)
+ return false;
+
+ if (!validate_component(start, (c - start), flags))
+ return false;
+
+ if (validate_component_cb &&
+ !validate_component_cb(start, (c - start), payload))
+ return false;
+
+#ifdef GIT_WIN32
+ if ((flags & GIT_FS_PATH_REJECT_LONG_PATHS) != 0) {
+ size_t utf8_len = git_utf8_char_length(path->ptr, len);
+
+ if (!validate_length(path->ptr, len, utf8_len))
+ return false;
+
+ if (validate_length_cb &&
+ !validate_length_cb(path->ptr, len, utf8_len))
+ return false;
+ }
+#else
+ GIT_UNUSED(validate_length_cb);
+#endif
+
+ return true;
+}
+
+int git_fs_path_validate_str_length_with_suffix(
+ git_str *path,
+ size_t suffix_len)
+{
+#ifdef GIT_WIN32
+ size_t utf8_len = git_utf8_char_length(path->ptr, path->size);
+ size_t total_len;
+
+ if (GIT_ADD_SIZET_OVERFLOW(&total_len, utf8_len, suffix_len) ||
+ total_len > MAX_PATH) {
+
+ git_error_set(GIT_ERROR_FILESYSTEM, "path too long: '%.*s'",
+ (int)path->size, path->ptr);
+ return -1;
+ }
+#else
+ GIT_UNUSED(path);
+ GIT_UNUSED(suffix_len);
+#endif
+
+ return 0;
+}
+
+int git_fs_path_normalize_slashes(git_str *out, const char *path)
+{
+ int error;
+ char *p;
+
+ if ((error = git_str_puts(out, path)) < 0)
+ return error;
+
+ for (p = out->ptr; *p; p++) {
+ if (*p == '\\')
+ *p = '/';
+ }
+
+ return 0;
+}
+
+bool git_fs_path_supports_symlinks(const char *dir)
+{
+ git_str path = GIT_STR_INIT;
+ bool supported = false;
+ struct stat st;
+ int fd;
+
+ if ((fd = git_futils_mktmp(&path, dir, 0666)) < 0 ||
+ p_close(fd) < 0 ||
+ p_unlink(path.ptr) < 0 ||
+ p_symlink("testing", path.ptr) < 0 ||
+ p_lstat(path.ptr, &st) < 0)
+ goto done;
+
+ supported = (S_ISLNK(st.st_mode) != 0);
+done:
+ if (path.size)
+ (void)p_unlink(path.ptr);
+ git_str_dispose(&path);
+ return supported;
+}
+
+static git_fs_path_owner_t mock_owner = GIT_FS_PATH_OWNER_NONE;
+
+void git_fs_path__set_owner(git_fs_path_owner_t owner)
+{
+ mock_owner = owner;
+}
+
+#ifdef GIT_WIN32
+static PSID *sid_dup(PSID sid)
+{
+ DWORD len;
+ PSID dup;
+
+ len = GetLengthSid(sid);
+
+ if ((dup = git__malloc(len)) == NULL)
+ return NULL;
+
+ if (!CopySid(len, dup, sid)) {
+ git_error_set(GIT_ERROR_OS, "could not duplicate sid");
+ git__free(dup);
+ return NULL;
+ }
+
+ return dup;
+}
+
+static int current_user_sid(PSID *out)
+{
+ TOKEN_USER *info = NULL;
+ HANDLE token = NULL;
+ DWORD len = 0;
+ int error = -1;
+
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
+ git_error_set(GIT_ERROR_OS, "could not lookup process information");
+ goto done;
+ }
+
+ if (GetTokenInformation(token, TokenUser, NULL, 0, &len) ||
+ GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ git_error_set(GIT_ERROR_OS, "could not lookup token metadata");
+ goto done;
+ }
+
+ info = git__malloc(len);
+ GIT_ERROR_CHECK_ALLOC(info);
+
+ if (!GetTokenInformation(token, TokenUser, info, len, &len)) {
+ git_error_set(GIT_ERROR_OS, "could not lookup current user");
+ goto done;
+ }
+
+ if ((*out = sid_dup(info->User.Sid)))
+ error = 0;
+
+done:
+ if (token)
+ CloseHandle(token);
+
+ git__free(info);
+ return error;
+}
+
+static int file_owner_sid(PSID *out, const char *path)
+{
+ git_win32_path path_w32;
+ PSECURITY_DESCRIPTOR descriptor = NULL;
+ PSID owner_sid;
+ DWORD ret;
+ int error = GIT_EINVALID;
+
+ if (git_win32_path_from_utf8(path_w32, path) < 0)
+ return -1;
+
+ ret = GetNamedSecurityInfoW(path_w32, SE_FILE_OBJECT,
+ OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
+ &owner_sid, NULL, NULL, NULL, &descriptor);
+
+ if (ret == ERROR_FILE_NOT_FOUND || ret == ERROR_PATH_NOT_FOUND)
+ error = GIT_ENOTFOUND;
+ else if (ret != ERROR_SUCCESS)
+ git_error_set(GIT_ERROR_OS, "failed to get security information");
+ else if (!IsValidSid(owner_sid))
+ git_error_set(GIT_ERROR_OS, "file owner is not valid");
+ else if ((*out = sid_dup(owner_sid)))
+ error = 0;
+
+ if (descriptor)
+ LocalFree(descriptor);
+
+ return error;
+}
+
+int git_fs_path_owner_is(
+ bool *out,
+ const char *path,
+ git_fs_path_owner_t owner_type)
+{
+ PSID owner_sid = NULL, user_sid = NULL;
+ BOOL is_admin, admin_owned;
+ int error;
+
+ if (mock_owner) {
+ *out = ((mock_owner & owner_type) != 0);
+ return 0;
+ }
+
+ if ((error = file_owner_sid(&owner_sid, path)) < 0)
+ goto done;
+
+ if ((owner_type & GIT_FS_PATH_OWNER_CURRENT_USER) != 0) {
+ if ((error = current_user_sid(&user_sid)) < 0)
+ goto done;
+
+ if (EqualSid(owner_sid, user_sid)) {
+ *out = true;
+ goto done;
+ }
+ }
+
+ admin_owned =
+ IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) ||
+ IsWellKnownSid(owner_sid, WinLocalSystemSid);
+
+ if (admin_owned &&
+ (owner_type & GIT_FS_PATH_OWNER_ADMINISTRATOR) != 0) {
+ *out = true;
+ goto done;
+ }
+
+ if (admin_owned &&
+ (owner_type & GIT_FS_PATH_USER_IS_ADMINISTRATOR) != 0 &&
+ CheckTokenMembership(NULL, owner_sid, &is_admin) &&
+ is_admin) {
+ *out = true;
+ goto done;
+ }
+
+ *out = false;
+
+done:
+ git__free(owner_sid);
+ git__free(user_sid);
+ return error;
+}
+
+#else
+
+static int sudo_uid_lookup(uid_t *out)
+{
+ git_str uid_str = GIT_STR_INIT;
+ int64_t uid;
+ int error;
+
+ 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)) {
+ *out = (uid_t)uid;
+ }
+
+ git_str_dispose(&uid_str);
+ return error;
+}
+
+int git_fs_path_owner_is(
+ bool *out,
+ const char *path,
+ git_fs_path_owner_t owner_type)
+{
+ struct stat st;
+ uid_t euid, sudo_uid;
+
+ if (mock_owner) {
+ *out = ((mock_owner & owner_type) != 0);
+ return 0;
+ }
+
+ euid = geteuid();
+
+ if (p_lstat(path, &st) != 0) {
+ if (errno == ENOENT)
+ return GIT_ENOTFOUND;
+
+ git_error_set(GIT_ERROR_OS, "could not stat '%s'", path);
+ return -1;
+ }
+
+ if ((owner_type & GIT_FS_PATH_OWNER_CURRENT_USER) != 0 &&
+ st.st_uid == euid) {
+ *out = true;
+ return 0;
+ }
+
+ if ((owner_type & GIT_FS_PATH_OWNER_ADMINISTRATOR) != 0 &&
+ st.st_uid == 0) {
+ *out = true;
+ return 0;
+ }
+
+ if ((owner_type & GIT_FS_PATH_OWNER_RUNNING_SUDO) != 0 &&
+ euid == 0 &&
+ sudo_uid_lookup(&sudo_uid) == 0 &&
+ st.st_uid == sudo_uid) {
+ *out = true;
+ return 0;
+ }
+
+ *out = false;
+ return 0;
+}
+
+#endif
+
+int git_fs_path_owner_is_current_user(bool *out, const char *path)
+{
+ return git_fs_path_owner_is(out, path, GIT_FS_PATH_OWNER_CURRENT_USER);
+}
+
+int git_fs_path_owner_is_system(bool *out, const char *path)
+{
+ return git_fs_path_owner_is(out, path, GIT_FS_PATH_OWNER_ADMINISTRATOR);
+}
+
+int git_fs_path_find_executable(git_str *fullpath, const char *executable)
+{
+#ifdef GIT_WIN32
+ git_win32_path fullpath_w, executable_w;
+ int error;
+
+ if (git_utf8_to_16(executable_w, GIT_WIN_PATH_MAX, executable) < 0)
+ return -1;
+
+ error = git_win32_path_find_executable(fullpath_w, executable_w);
+
+ if (error == 0)
+ error = git_str_put_w(fullpath, fullpath_w, wcslen(fullpath_w));
+
+ return error;
+#else
+ git_str path = GIT_STR_INIT;
+ const char *current_dir, *term;
+ bool found = false;
+
+ if (git__getenv(&path, "PATH") < 0)
+ return -1;
+
+ current_dir = path.ptr;
+
+ while (*current_dir) {
+ if (! (term = strchr(current_dir, GIT_PATH_LIST_SEPARATOR)))
+ term = strchr(current_dir, '\0');
+
+ git_str_clear(fullpath);
+ if (git_str_put(fullpath, current_dir, (term - current_dir)) < 0 ||
+ git_str_putc(fullpath, '/') < 0 ||
+ git_str_puts(fullpath, executable) < 0)
+ return -1;
+
+ if (git_fs_path_isfile(fullpath->ptr)) {
+ found = true;
+ break;
+ }
+
+ current_dir = term;
+
+ while (*current_dir == GIT_PATH_LIST_SEPARATOR)
+ current_dir++;
+ }
+
+ git_str_dispose(&path);
+
+ if (found)
+ return 0;
+
+ git_str_clear(fullpath);
+ return GIT_ENOTFOUND;
+#endif
+}
diff --git a/src/util/fs_path.h b/src/util/fs_path.h
new file mode 100644
index 0000000..e5ca673
--- /dev/null
+++ b/src/util/fs_path.h
@@ -0,0 +1,790 @@
+/*
+ * 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_fs_path_h__
+#define INCLUDE_fs_path_h__
+
+#include "git2_util.h"
+
+#include "posix.h"
+#include "str.h"
+#include "vector.h"
+#include "utf8.h"
+
+/**
+ * Path manipulation utils
+ *
+ * These are path utilities that munge paths without actually
+ * looking at the real filesystem.
+ */
+
+/*
+ * The dirname() function shall take a pointer to a character string
+ * that contains a pathname, and return a pointer to a string that is a
+ * pathname of the parent directory of that file. Trailing '/' characters
+ * in the path are not counted as part of the path.
+ *
+ * If path does not contain a '/', then dirname() shall return a pointer to
+ * the string ".". If path is a null pointer or points to an empty string,
+ * dirname() shall return a pointer to the string "." .
+ *
+ * The `git_fs_path_dirname` implementation is thread safe. The returned
+ * string must be manually free'd.
+ *
+ * The `git_fs_path_dirname_r` implementation writes the dirname to a `git_str`
+ * if the buffer pointer is not NULL.
+ * It returns an error code < 0 if there is an allocation error, otherwise
+ * the length of the dirname (which will be > 0).
+ */
+extern char *git_fs_path_dirname(const char *path);
+extern int git_fs_path_dirname_r(git_str *buffer, const char *path);
+
+/*
+ * This function returns the basename of the file, which is the last
+ * part of its full name given by fname, with the drive letter and
+ * leading directories stripped off. For example, the basename of
+ * c:/foo/bar/file.ext is file.ext, and the basename of a:foo is foo.
+ *
+ * Trailing slashes and backslashes are significant: the basename of
+ * c:/foo/bar/ is an empty string after the rightmost slash.
+ *
+ * The `git_fs_path_basename` implementation is thread safe. The returned
+ * string must be manually free'd.
+ *
+ * The `git_fs_path_basename_r` implementation writes the basename to a `git_str`.
+ * It returns an error code < 0 if there is an allocation error, otherwise
+ * the length of the basename (which will be >= 0).
+ */
+extern char *git_fs_path_basename(const char *path);
+extern int git_fs_path_basename_r(git_str *buffer, const char *path);
+
+/* Return the offset of the start of the basename. Unlike the other
+ * basename functions, this returns 0 if the path is empty.
+ */
+extern size_t git_fs_path_basename_offset(git_str *buffer);
+
+/**
+ * Find offset to root of path if path has one.
+ *
+ * This will return a number >= 0 which is the offset to the start of the
+ * path, if the path is rooted (i.e. "/rooted/path" returns 0 and
+ * "c:/windows/rooted/path" returns 2). If the path is not rooted, this
+ * returns -1.
+ */
+extern int git_fs_path_root(const char *path);
+
+/**
+ * Ensure path has a trailing '/'.
+ */
+extern int git_fs_path_to_dir(git_str *path);
+
+/**
+ * Ensure string has a trailing '/' if there is space for it.
+ */
+extern void git_fs_path_string_to_dir(char *path, size_t size);
+
+/**
+ * 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)
+{
+ return (name[0] == '.' &&
+ (name[1] == '\0' ||
+ (name[1] == '.' && name[2] == '\0')));
+}
+
+#ifdef GIT_WIN32
+GIT_INLINE(int) git_fs_path_is_dot_or_dotdotW(const wchar_t *name)
+{
+ return (name[0] == L'.' &&
+ (name[1] == L'\0' ||
+ (name[1] == L'.' && name[2] == L'\0')));
+}
+
+#define git_fs_path_is_absolute(p) \
+ (git__isalpha((p)[0]) && (p)[1] == ':' && ((p)[2] == '\\' || (p)[2] == '/'))
+
+#define git_fs_path_is_dirsep(p) \
+ ((p) == '/' || (p) == '\\')
+
+/**
+ * Convert backslashes in path to forward slashes.
+ */
+GIT_INLINE(void) git_fs_path_mkposix(char *path)
+{
+ while (*path) {
+ if (*path == '\\')
+ *path = '/';
+
+ path++;
+ }
+}
+#else
+# define git_fs_path_mkposix(p) /* blank */
+
+#define git_fs_path_is_absolute(p) \
+ ((p)[0] == '/')
+
+#define git_fs_path_is_dirsep(p) \
+ ((p) == '/')
+
+#endif
+
+/**
+ * Check if string is a relative path (i.e. starts with "./" or "../")
+ */
+GIT_INLINE(int) git_fs_path_is_relative(const char *p)
+{
+ return (p[0] == '.' && (p[1] == '/' || (p[1] == '.' && p[2] == '/')));
+}
+
+/**
+ * Check if string is at end of path segment (i.e. looking at '/' or '\0')
+ */
+GIT_INLINE(int) git_fs_path_at_end_of_segment(const char *p)
+{
+ return !*p || *p == '/';
+}
+
+extern int git__percent_decode(git_str *decoded_out, const char *input);
+
+/**
+ * Extract path from file:// URL.
+ */
+extern int git_fs_path_fromurl(git_str *local_path_out, const char *file_url);
+
+
+/**
+ * Path filesystem utils
+ *
+ * These are path utilities that actually access the filesystem.
+ */
+
+/**
+ * Check if a file exists and can be accessed.
+ * @return true or false
+ */
+extern bool git_fs_path_exists(const char *path);
+
+/**
+ * Check if the given path points to a directory.
+ * @return true or false
+ */
+extern bool git_fs_path_isdir(const char *path);
+
+/**
+ * Check if the given path points to a regular file.
+ * @return true or false
+ */
+extern bool git_fs_path_isfile(const char *path);
+
+/**
+ * Check if the given path points to a symbolic link.
+ * @return true or false
+ */
+extern bool git_fs_path_islink(const char *path);
+
+/**
+ * Check if the given path is a directory, and is empty.
+ */
+extern bool git_fs_path_is_empty_dir(const char *path);
+
+/**
+ * Stat a file and/or link and set error if needed.
+ */
+extern int git_fs_path_lstat(const char *path, struct stat *st);
+
+/**
+ * Check if the parent directory contains the item.
+ *
+ * @param dir Directory to check.
+ * @param item Item that might be in the directory.
+ * @return 0 if item exists in directory, <0 otherwise.
+ */
+extern bool git_fs_path_contains(git_str *dir, const char *item);
+
+/**
+ * Check if the given path contains the given subdirectory.
+ *
+ * @param parent Directory path that might contain subdir
+ * @param subdir Subdirectory name to look for in parent
+ * @return true if subdirectory exists, false otherwise.
+ */
+extern bool git_fs_path_contains_dir(git_str *parent, const char *subdir);
+
+/**
+ * Determine the common directory length between two paths, including
+ * the final path separator. For example, given paths 'a/b/c/1.txt
+ * and 'a/b/c/d/2.txt', the common directory is 'a/b/c/', and this
+ * will return the length of the string 'a/b/c/', which is 6.
+ *
+ * @param one The first path
+ * @param two The second path
+ * @return The length of the common directory
+ */
+extern size_t git_fs_path_common_dirlen(const char *one, const char *two);
+
+/**
+ * Make the path relative to the given parent path.
+ *
+ * @param path The path to make relative
+ * @param parent The parent path to make path relative to
+ * @return 0 if path was made relative, GIT_ENOTFOUND
+ * if there was not common root between the paths,
+ * or <0.
+ */
+extern int git_fs_path_make_relative(git_str *path, const char *parent);
+
+/**
+ * Check if the given path contains the given file.
+ *
+ * @param dir Directory path that might contain file
+ * @param file File name to look for in parent
+ * @return true if file exists, false otherwise.
+ */
+extern bool git_fs_path_contains_file(git_str *dir, const char *file);
+
+/**
+ * Prepend base to unrooted path or just copy path over.
+ *
+ * This will optionally return the index into the path where the "root"
+ * is, either the end of the base directory prefix or the path root.
+ */
+extern int git_fs_path_join_unrooted(
+ git_str *path_out, const char *path, const char *base, ssize_t *root_at);
+
+/**
+ * Removes multiple occurrences of '/' in a row, squashing them into a
+ * single '/'.
+ */
+extern void git_fs_path_squash_slashes(git_str *path);
+
+/**
+ * Clean up path, prepending base if it is not already rooted.
+ */
+extern int git_fs_path_prettify(git_str *path_out, const char *path, const char *base);
+
+/**
+ * Clean up path, prepending base if it is not already rooted and
+ * appending a slash.
+ */
+extern int git_fs_path_prettify_dir(git_str *path_out, const char *path, const char *base);
+
+/**
+ * Get a directory from a path.
+ *
+ * If path is a directory, this acts like `git_fs_path_prettify_dir`
+ * (cleaning up path and appending a '/'). If path is a normal file,
+ * this prettifies it, then removed the filename a la dirname and
+ * appends the trailing '/'. If the path does not exist, it is
+ * treated like a regular filename.
+ */
+extern int git_fs_path_find_dir(git_str *dir);
+
+/**
+ * Resolve relative references within a path.
+ *
+ * This eliminates "./" and "../" relative references inside a path,
+ * as well as condensing multiple slashes into single ones. It will
+ * not touch the path before the "ceiling" length.
+ *
+ * Additionally, this will recognize an "c:/" drive prefix or a "xyz://" URL
+ * prefix and not touch that part of the path.
+ */
+extern int git_fs_path_resolve_relative(git_str *path, size_t ceiling);
+
+/**
+ * Apply a relative path to base path.
+ *
+ * Note that the base path could be a filename or a URL and this
+ * should still work. The relative path is walked segment by segment
+ * with three rules: series of slashes will be condensed to a single
+ * slash, "." will be eaten with no change, and ".." will remove a
+ * segment from the base path.
+ */
+extern int git_fs_path_apply_relative(git_str *target, const char *relpath);
+
+enum {
+ GIT_FS_PATH_DIR_IGNORE_CASE = (1u << 0),
+ GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE = (1u << 1),
+ GIT_FS_PATH_DIR_INCLUDE_DOT_AND_DOTDOT = (1u << 2),
+};
+
+/**
+ * Walk each directory entry, except '.' and '..', calling fn(state).
+ *
+ * @param pathbuf Buffer the function reads the initial directory
+ * path from, and updates with each successive entry's name.
+ * @param flags Combination of GIT_FS_PATH_DIR flags.
+ * @param callback Callback for each entry. Passed the `payload` and each
+ * successive path inside the directory as a full path. This may
+ * safely append text to the pathbuf if needed. Return non-zero to
+ * cancel iteration (and return value will be propagated back).
+ * @param payload Passed to callback as first argument.
+ * @return 0 on success or error code from OS error or from callback
+ */
+extern int git_fs_path_direach(
+ git_str *pathbuf,
+ uint32_t flags,
+ int (*callback)(void *payload, git_str *path),
+ void *payload);
+
+/**
+ * Sort function to order two paths
+ */
+extern int git_fs_path_cmp(
+ const char *name1, size_t len1, int isdir1,
+ const char *name2, size_t len2, int isdir2,
+ int (*compare)(const char *, const char *, size_t));
+
+/**
+ * Invoke callback up path directory by directory until the ceiling is
+ * reached (inclusive of a final call at the root_path).
+ *
+ * Returning anything other than 0 from the callback function
+ * will stop the iteration and propagate the error to the caller.
+ *
+ * @param pathbuf Buffer the function reads the directory from and
+ * and updates with each successive name.
+ * @param ceiling Prefix of path at which to stop walking up. If NULL,
+ * this will walk all the way up to the root. If not a prefix of
+ * pathbuf, the callback will be invoked a single time on the
+ * original input path.
+ * @param callback Function to invoke on each path. Passed the `payload`
+ * and the buffer containing the current path. The path should not
+ * be modified in any way. Return non-zero to stop iteration.
+ * @param payload Passed to fn as the first ath.
+ */
+extern int git_fs_path_walk_up(
+ git_str *pathbuf,
+ const char *ceiling,
+ int (*callback)(void *payload, const char *path),
+ void *payload);
+
+
+enum {
+ GIT_FS_PATH_NOTEQUAL = 0,
+ GIT_FS_PATH_EQUAL = 1,
+ GIT_FS_PATH_PREFIX = 2
+};
+
+/*
+ * Determines if a path is equal to or potentially a child of another.
+ * @param parent The possible parent
+ * @param child The possible child
+ */
+GIT_INLINE(int) git_fs_path_equal_or_prefixed(
+ const char *parent,
+ const char *child,
+ ssize_t *prefixlen)
+{
+ const char *p = parent, *c = child;
+ int lastslash = 0;
+
+ while (*p && *c) {
+ lastslash = (*p == '/');
+
+ if (*p++ != *c++)
+ return GIT_FS_PATH_NOTEQUAL;
+ }
+
+ if (*p != '\0')
+ return GIT_FS_PATH_NOTEQUAL;
+
+ if (*c == '\0') {
+ if (prefixlen)
+ *prefixlen = p - parent;
+
+ return GIT_FS_PATH_EQUAL;
+ }
+
+ if (*c == '/' || lastslash) {
+ if (prefixlen)
+ *prefixlen = (p - parent) - lastslash;
+
+ return GIT_FS_PATH_PREFIX;
+ }
+
+ return GIT_FS_PATH_NOTEQUAL;
+}
+
+/* translate errno to libgit2 error code and set error message */
+extern int git_fs_path_set_error(
+ int errno_value, const char *path, const char *action);
+
+/* check if non-ascii characters are present in filename */
+extern bool git_fs_path_has_non_ascii(const char *path, size_t pathlen);
+
+#define GIT_PATH_REPO_ENCODING "UTF-8"
+
+#ifdef __APPLE__
+#define GIT_PATH_NATIVE_ENCODING "UTF-8-MAC"
+#else
+#define GIT_PATH_NATIVE_ENCODING "UTF-8"
+#endif
+
+#ifdef GIT_USE_ICONV
+
+#include <iconv.h>
+
+typedef struct {
+ iconv_t map;
+ git_str buf;
+} git_fs_path_iconv_t;
+
+#define GIT_PATH_ICONV_INIT { (iconv_t)-1, GIT_STR_INIT }
+
+/* Init iconv data for converting decomposed UTF-8 to precomposed */
+extern int git_fs_path_iconv_init_precompose(git_fs_path_iconv_t *ic);
+
+/* Clear allocated iconv data */
+extern void git_fs_path_iconv_clear(git_fs_path_iconv_t *ic);
+
+/*
+ * Rewrite `in` buffer using iconv map if necessary, replacing `in`
+ * pointer internal iconv buffer if rewrite happened. The `in` pointer
+ * will be left unchanged if no rewrite was needed.
+ */
+extern int git_fs_path_iconv(git_fs_path_iconv_t *ic, const char **in, size_t *inlen);
+
+#endif /* GIT_USE_ICONV */
+
+extern bool git_fs_path_does_decompose_unicode(const char *root);
+
+
+typedef struct git_fs_path_diriter git_fs_path_diriter;
+
+#if defined(GIT_WIN32) && !defined(__MINGW32__)
+
+struct git_fs_path_diriter
+{
+ git_win32_path path;
+ size_t parent_len;
+
+ git_str path_utf8;
+ size_t parent_utf8_len;
+
+ HANDLE handle;
+
+ unsigned int flags;
+
+ WIN32_FIND_DATAW current;
+ unsigned int needs_next;
+};
+
+#define GIT_FS_PATH_DIRITER_INIT { {0}, 0, GIT_STR_INIT, 0, INVALID_HANDLE_VALUE }
+
+#else
+
+struct git_fs_path_diriter
+{
+ git_str path;
+ size_t parent_len;
+
+ unsigned int flags;
+
+ DIR *dir;
+
+#ifdef GIT_USE_ICONV
+ git_fs_path_iconv_t ic;
+#endif
+};
+
+#define GIT_FS_PATH_DIRITER_INIT { GIT_STR_INIT }
+
+#endif
+
+/**
+ * Initialize a directory iterator.
+ *
+ * @param diriter Pointer to a diriter structure that will be setup.
+ * @param path The path that will be iterated over
+ * @param flags Directory reader flags
+ * @return 0 or an error code
+ */
+extern int git_fs_path_diriter_init(
+ git_fs_path_diriter *diriter,
+ const char *path,
+ unsigned int flags);
+
+/**
+ * Advance the directory iterator. Will return GIT_ITEROVER when
+ * the iteration has completed successfully.
+ *
+ * @param diriter The directory iterator
+ * @return 0, GIT_ITEROVER, or an error code
+ */
+extern int git_fs_path_diriter_next(git_fs_path_diriter *diriter);
+
+/**
+ * Returns the file name of the current item in the iterator.
+ *
+ * @param out Pointer to store the path in
+ * @param out_len Pointer to store the length of the path in
+ * @param diriter The directory iterator
+ * @return 0 or an error code
+ */
+extern int git_fs_path_diriter_filename(
+ const char **out,
+ size_t *out_len,
+ git_fs_path_diriter *diriter);
+
+/**
+ * Returns the full path of the current item in the iterator; that
+ * is the current filename plus the path of the directory that the
+ * iterator was constructed with.
+ *
+ * @param out Pointer to store the path in
+ * @param out_len Pointer to store the length of the path in
+ * @param diriter The directory iterator
+ * @return 0 or an error code
+ */
+extern int git_fs_path_diriter_fullpath(
+ const char **out,
+ size_t *out_len,
+ git_fs_path_diriter *diriter);
+
+/**
+ * Performs an `lstat` on the current item in the iterator.
+ *
+ * @param out Pointer to store the stat data in
+ * @param diriter The directory iterator
+ * @return 0 or an error code
+ */
+extern int git_fs_path_diriter_stat(struct stat *out, git_fs_path_diriter *diriter);
+
+/**
+ * Closes the directory iterator.
+ *
+ * @param diriter The directory iterator
+ */
+extern void git_fs_path_diriter_free(git_fs_path_diriter *diriter);
+
+/**
+ * Load all directory entries (except '.' and '..') into a vector.
+ *
+ * For cases where `git_fs_path_direach()` is not appropriate, this
+ * allows you to load the filenames in a directory into a vector
+ * of strings. That vector can then be sorted, iterated, or whatever.
+ * Remember to free alloc of the allocated strings when you are done.
+ *
+ * @param contents Vector to fill with directory entry names.
+ * @param path The directory to read from.
+ * @param prefix_len When inserting entries, the trailing part of path
+ * will be prefixed after this length. I.e. given path "/a/b" and
+ * prefix_len 3, the entries will look like "b/e1", "b/e2", etc.
+ * @param flags Combination of GIT_FS_PATH_DIR flags.
+ */
+extern int git_fs_path_dirload(
+ git_vector *contents,
+ const char *path,
+ size_t prefix_len,
+ uint32_t flags);
+
+
+/* Used for paths to repositories on the filesystem */
+extern bool git_fs_path_is_local_file_url(const char *file_url);
+extern int git_fs_path_from_url_or_path(git_str *local_path_out, const char *url_or_path);
+
+/* Flags to determine path validity in `git_fs_path_isvalid` */
+#define GIT_FS_PATH_REJECT_EMPTY_COMPONENT (1 << 0)
+#define GIT_FS_PATH_REJECT_TRAVERSAL (1 << 1)
+#define GIT_FS_PATH_REJECT_SLASH (1 << 2)
+#define GIT_FS_PATH_REJECT_BACKSLASH (1 << 3)
+#define GIT_FS_PATH_REJECT_TRAILING_DOT (1 << 4)
+#define GIT_FS_PATH_REJECT_TRAILING_SPACE (1 << 5)
+#define GIT_FS_PATH_REJECT_TRAILING_COLON (1 << 6)
+#define GIT_FS_PATH_REJECT_DOS_PATHS (1 << 7)
+#define GIT_FS_PATH_REJECT_NT_CHARS (1 << 8)
+#define GIT_FS_PATH_REJECT_LONG_PATHS (1 << 9)
+
+#define GIT_FS_PATH_REJECT_MAX (1 << 9)
+
+/* Default path safety for writing files to disk: since we use the
+ * Win32 "File Namespace" APIs ("\\?\") we need to protect from
+ * paths that the normal Win32 APIs would not write.
+ */
+#ifdef GIT_WIN32
+# define GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS \
+ GIT_FS_PATH_REJECT_EMPTY_COMPONENT | \
+ GIT_FS_PATH_REJECT_TRAVERSAL | \
+ GIT_FS_PATH_REJECT_BACKSLASH | \
+ GIT_FS_PATH_REJECT_TRAILING_DOT | \
+ GIT_FS_PATH_REJECT_TRAILING_SPACE | \
+ GIT_FS_PATH_REJECT_TRAILING_COLON | \
+ GIT_FS_PATH_REJECT_DOS_PATHS | \
+ GIT_FS_PATH_REJECT_NT_CHARS
+#else
+# define GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS \
+ GIT_FS_PATH_REJECT_EMPTY_COMPONENT | \
+ GIT_FS_PATH_REJECT_TRAVERSAL
+#endif
+
+/**
+ * Validate a filesystem path; with custom callbacks per-character and
+ * per-path component.
+ */
+extern bool git_fs_path_str_is_valid_ext(
+ const git_str *path,
+ unsigned int flags,
+ bool (*validate_char_cb)(char ch, void *payload),
+ bool (*validate_component_cb)(const char *component, size_t len, void *payload),
+ bool (*validate_length_cb)(const char *component, size_t len, size_t utf8_char_len),
+ void *payload);
+
+GIT_INLINE(bool) git_fs_path_is_valid_ext(
+ const char *path,
+ unsigned int flags,
+ bool (*validate_char_cb)(char ch, void *payload),
+ bool (*validate_component_cb)(const char *component, size_t len, void *payload),
+ bool (*validate_length_cb)(const char *component, size_t len, size_t utf8_char_len),
+ void *payload)
+{
+ const git_str str = GIT_STR_INIT_CONST(path, SIZE_MAX);
+ return git_fs_path_str_is_valid_ext(
+ &str,
+ flags,
+ validate_char_cb,
+ validate_component_cb,
+ validate_length_cb,
+ payload);
+}
+
+/**
+ * Validate a filesystem path. This ensures that the given path is legal
+ * and does not contain any "unsafe" components like path traversal ('.'
+ * or '..'), characters that are inappropriate for lesser filesystems
+ * (trailing ' ' or ':' characters), or filenames ("component names")
+ * that are not supported ('AUX', 'COM1").
+ */
+GIT_INLINE(bool) git_fs_path_is_valid(
+ const char *path,
+ unsigned int flags)
+{
+ const git_str str = GIT_STR_INIT_CONST(path, SIZE_MAX);
+ return git_fs_path_str_is_valid_ext(&str, flags, NULL, NULL, NULL, NULL);
+}
+
+/** Validate a filesystem path in a `git_str`. */
+GIT_INLINE(bool) git_fs_path_str_is_valid(
+ const git_str *path,
+ unsigned int flags)
+{
+ return git_fs_path_str_is_valid_ext(path, flags, NULL, NULL, NULL, NULL);
+}
+
+extern int git_fs_path_validate_str_length_with_suffix(
+ git_str *path,
+ size_t suffix_len);
+
+/**
+ * Validate an on-disk path, taking into account that it will have a
+ * suffix appended (eg, `.lock`).
+ */
+GIT_INLINE(int) git_fs_path_validate_filesystem_with_suffix(
+ const char *path,
+ size_t path_len,
+ size_t suffix_len)
+{
+#ifdef GIT_WIN32
+ size_t path_chars, total_chars;
+
+ path_chars = git_utf8_char_length(path, path_len);
+
+ if (GIT_ADD_SIZET_OVERFLOW(&total_chars, path_chars, suffix_len) ||
+ total_chars > MAX_PATH) {
+ git_error_set(GIT_ERROR_FILESYSTEM, "path too long: '%s'", path);
+ return -1;
+ }
+ return 0;
+#else
+ GIT_UNUSED(path);
+ GIT_UNUSED(path_len);
+ GIT_UNUSED(suffix_len);
+ return 0;
+#endif
+}
+
+/**
+ * Validate an path on the filesystem. This ensures that the given
+ * path is valid for the operating system/platform; for example, this
+ * will ensure that the given absolute path is smaller than MAX_PATH on
+ * Windows.
+ *
+ * For paths within the working directory, you should use ensure that
+ * `core.longpaths` is obeyed. Use `git_fs_path_validate_workdir`.
+ */
+GIT_INLINE(int) git_fs_path_validate_filesystem(
+ const char *path,
+ size_t path_len)
+{
+ return git_fs_path_validate_filesystem_with_suffix(path, path_len, 0);
+}
+
+/**
+ * Convert any backslashes into slashes
+ */
+int git_fs_path_normalize_slashes(git_str *out, const char *path);
+
+bool git_fs_path_supports_symlinks(const char *dir);
+
+typedef enum {
+ GIT_FS_PATH_OWNER_NONE = 0,
+
+ /** The file must be owned by the current user. */
+ GIT_FS_PATH_OWNER_CURRENT_USER = (1 << 0),
+
+ /** The file must be owned by the system account. */
+ GIT_FS_PATH_OWNER_ADMINISTRATOR = (1 << 1),
+
+ /**
+ * The file may be owned by a system account if the current
+ * user is in an administrator group. Windows only; this is
+ * a noop on non-Windows systems.
+ */
+ GIT_FS_PATH_USER_IS_ADMINISTRATOR = (1 << 2),
+
+ /**
+ * The file is owned by the current user, who is running `sudo`.
+ */
+ GIT_FS_PATH_OWNER_RUNNING_SUDO = (1 << 3),
+
+ /** The file may be owned by another user. */
+ GIT_FS_PATH_OWNER_OTHER = (1 << 4)
+} git_fs_path_owner_t;
+
+/**
+ * Sets the mock ownership for files; subsequent calls to
+ * `git_fs_path_owner_is_*` functions will return this data until
+ * cleared with `GIT_FS_PATH_OWNER_NONE`.
+ */
+void git_fs_path__set_owner(git_fs_path_owner_t owner);
+
+/** Verify that the file in question is owned by the given owner. */
+int git_fs_path_owner_is(
+ bool *out,
+ const char *path,
+ git_fs_path_owner_t owner_type);
+
+/**
+ * Verify that the file in question is owned by an administrator or system
+ * account.
+ */
+int git_fs_path_owner_is_system(bool *out, const char *path);
+
+/**
+ * Verify that the file in question is owned by the current user;
+ */
+
+int git_fs_path_owner_is_current_user(bool *out, const char *path);
+
+/**
+ * Search the current PATH for the given executable, returning the full
+ * path if it is found.
+ */
+int git_fs_path_find_executable(git_str *fullpath, const char *executable);
+
+#endif
diff --git a/src/util/futils.c b/src/util/futils.c
new file mode 100644
index 0000000..7b5a24b
--- /dev/null
+++ b/src/util/futils.c
@@ -0,0 +1,1236 @@
+/*
+ * 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 "futils.h"
+
+#include "runtime.h"
+#include "strmap.h"
+#include "hash.h"
+#include "rand.h"
+
+#include <ctype.h>
+
+#define GIT_FILEMODE_DEFAULT 0100666
+
+int git_futils_mkpath2file(const char *file_path, const mode_t mode)
+{
+ return git_futils_mkdir(
+ file_path, mode,
+ GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR);
+}
+
+int git_futils_mktmp(git_str *path_out, const char *filename, mode_t mode)
+{
+ const int open_flags = O_RDWR | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC;
+ unsigned int tries = 32;
+ int fd;
+
+ while (tries--) {
+ uint64_t rand = git_rand_next();
+
+ git_str_sets(path_out, filename);
+ git_str_puts(path_out, "_git2_");
+ git_str_encode_hexstr(path_out, (void *)&rand, sizeof(uint64_t));
+
+ if (git_str_oom(path_out))
+ return -1;
+
+ /* Note that we open with O_CREAT | O_EXCL */
+ if ((fd = p_open(path_out->ptr, open_flags, mode)) >= 0)
+ return fd;
+ }
+
+ git_error_set(GIT_ERROR_OS,
+ "failed to create temporary file '%s'", path_out->ptr);
+ git_str_dispose(path_out);
+ return -1;
+}
+
+int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode)
+{
+ int fd;
+
+ if (git_futils_mkpath2file(path, dirmode) < 0)
+ return -1;
+
+ fd = p_creat(path, mode);
+ if (fd < 0) {
+ git_error_set(GIT_ERROR_OS, "failed to create file '%s'", path);
+ return -1;
+ }
+
+ return fd;
+}
+
+int git_futils_creat_locked(const char *path, const mode_t mode)
+{
+ int fd = p_open(path, O_WRONLY | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC,
+ mode);
+
+ if (fd < 0) {
+ int error = errno;
+ git_error_set(GIT_ERROR_OS, "failed to create locked file '%s'", path);
+ switch (error) {
+ case EEXIST:
+ return GIT_ELOCKED;
+ case ENOENT:
+ return GIT_ENOTFOUND;
+ default:
+ return -1;
+ }
+ }
+
+ return fd;
+}
+
+int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode)
+{
+ if (git_futils_mkpath2file(path, dirmode) < 0)
+ return -1;
+
+ return git_futils_creat_locked(path, mode);
+}
+
+int git_futils_open_ro(const char *path)
+{
+ int fd = p_open(path, O_RDONLY);
+ if (fd < 0)
+ return git_fs_path_set_error(errno, path, "open");
+ return fd;
+}
+
+int git_futils_truncate(const char *path, int mode)
+{
+ int fd = p_open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode);
+ if (fd < 0)
+ return git_fs_path_set_error(errno, path, "open");
+
+ close(fd);
+ return 0;
+}
+
+int git_futils_filesize(uint64_t *out, git_file fd)
+{
+ struct stat sb;
+
+ if (p_fstat(fd, &sb)) {
+ git_error_set(GIT_ERROR_OS, "failed to stat file descriptor");
+ return -1;
+ }
+
+ if (sb.st_size < 0) {
+ git_error_set(GIT_ERROR_INVALID, "invalid file size");
+ return -1;
+ }
+
+ *out = sb.st_size;
+ return 0;
+}
+
+mode_t git_futils_canonical_mode(mode_t raw_mode)
+{
+ if (S_ISREG(raw_mode))
+ return S_IFREG | GIT_PERMS_CANONICAL(raw_mode);
+ else if (S_ISLNK(raw_mode))
+ return S_IFLNK;
+ else if (S_ISGITLINK(raw_mode))
+ return S_IFGITLINK;
+ else if (S_ISDIR(raw_mode))
+ return S_IFDIR;
+ else
+ return 0;
+}
+
+int git_futils_readbuffer_fd(git_str *buf, git_file fd, size_t len)
+{
+ ssize_t read_size = 0;
+ size_t alloc_len;
+
+ git_str_clear(buf);
+
+ if (!git__is_ssizet(len)) {
+ git_error_set(GIT_ERROR_INVALID, "read too large");
+ return -1;
+ }
+
+ GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, len, 1);
+ if (git_str_grow(buf, alloc_len) < 0)
+ return -1;
+
+ /* p_read loops internally to read len bytes */
+ read_size = p_read(fd, buf->ptr, len);
+
+ if (read_size < 0) {
+ git_error_set(GIT_ERROR_OS, "failed to read descriptor");
+ git_str_dispose(buf);
+ return -1;
+ }
+
+ if ((size_t)read_size != len) {
+ git_error_set(GIT_ERROR_FILESYSTEM, "could not read (expected %" PRIuZ " bytes, read %" PRIuZ ")", len, (size_t)read_size);
+ git_str_dispose(buf);
+ return -1;
+ }
+
+ buf->ptr[read_size] = '\0';
+ buf->size = read_size;
+
+ return 0;
+}
+
+int git_futils_readbuffer_fd_full(git_str *buf, git_file fd)
+{
+ static size_t blocksize = 10240;
+ size_t alloc_len = 0, total_size = 0;
+ ssize_t read_size = 0;
+
+ git_str_clear(buf);
+
+ while (true) {
+ GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, blocksize);
+
+ if (git_str_grow(buf, alloc_len) < 0)
+ return -1;
+
+ /* p_read loops internally to read blocksize bytes */
+ read_size = p_read(fd, buf->ptr, blocksize);
+
+ if (read_size < 0) {
+ git_error_set(GIT_ERROR_OS, "failed to read descriptor");
+ git_str_dispose(buf);
+ return -1;
+ }
+
+ total_size += read_size;
+
+ if ((size_t)read_size < blocksize) {
+ break;
+ }
+ }
+
+ buf->ptr[total_size] = '\0';
+ buf->size = total_size;
+
+ return 0;
+}
+
+int git_futils_readbuffer_updated(
+ git_str *out,
+ const char *path,
+ unsigned char checksum[GIT_HASH_SHA256_SIZE],
+ int *updated)
+{
+ int error;
+ git_file fd;
+ struct stat st;
+ git_str buf = GIT_STR_INIT;
+ unsigned char checksum_new[GIT_HASH_SHA256_SIZE];
+
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(path && *path);
+
+ if (updated != NULL)
+ *updated = 0;
+
+ if (p_stat(path, &st) < 0)
+ return git_fs_path_set_error(errno, path, "stat");
+
+
+ if (S_ISDIR(st.st_mode)) {
+ git_error_set(GIT_ERROR_INVALID, "requested file is a directory");
+ return GIT_ENOTFOUND;
+ }
+
+ if (!git__is_sizet(st.st_size+1)) {
+ git_error_set(GIT_ERROR_OS, "invalid regular file stat for '%s'", path);
+ return -1;
+ }
+
+ if ((fd = git_futils_open_ro(path)) < 0)
+ return fd;
+
+ if (git_futils_readbuffer_fd(&buf, fd, (size_t)st.st_size) < 0) {
+ p_close(fd);
+ return -1;
+ }
+
+ p_close(fd);
+
+ if (checksum) {
+ error = git_hash_buf(checksum_new, buf.ptr,
+ buf.size, GIT_HASH_ALGORITHM_SHA256);
+
+ if (error < 0) {
+ git_str_dispose(&buf);
+ return error;
+ }
+
+ /*
+ * If we were given a checksum, we only want to use it if it's different
+ */
+ if (!memcmp(checksum, checksum_new, GIT_HASH_SHA256_SIZE)) {
+ git_str_dispose(&buf);
+ if (updated)
+ *updated = 0;
+
+ return 0;
+ }
+
+ memcpy(checksum, checksum_new, GIT_HASH_SHA256_SIZE);
+ }
+
+ /*
+ * If we're here, the file did change, or the user didn't have an old version
+ */
+ if (updated != NULL)
+ *updated = 1;
+
+ git_str_swap(out, &buf);
+ git_str_dispose(&buf);
+
+ return 0;
+}
+
+int git_futils_readbuffer(git_str *buf, const char *path)
+{
+ return git_futils_readbuffer_updated(buf, path, NULL, NULL);
+}
+
+int git_futils_writebuffer(
+ const git_str *buf, const char *path, int flags, mode_t mode)
+{
+ int fd, do_fsync = 0, error = 0;
+
+ if (!flags)
+ flags = O_CREAT | O_TRUNC | O_WRONLY;
+
+ if ((flags & O_FSYNC) != 0)
+ do_fsync = 1;
+
+ flags &= ~O_FSYNC;
+
+ if (!mode)
+ mode = GIT_FILEMODE_DEFAULT;
+
+ if ((fd = p_open(path, flags, mode)) < 0) {
+ git_error_set(GIT_ERROR_OS, "could not open '%s' for writing", path);
+ return fd;
+ }
+
+ if ((error = p_write(fd, git_str_cstr(buf), git_str_len(buf))) < 0) {
+ git_error_set(GIT_ERROR_OS, "could not write to '%s'", path);
+ (void)p_close(fd);
+ return error;
+ }
+
+ if (do_fsync && (error = p_fsync(fd)) < 0) {
+ git_error_set(GIT_ERROR_OS, "could not fsync '%s'", path);
+ p_close(fd);
+ return error;
+ }
+
+ if ((error = p_close(fd)) < 0) {
+ git_error_set(GIT_ERROR_OS, "error while closing '%s'", path);
+ return error;
+ }
+
+ if (do_fsync && (flags & O_CREAT))
+ error = git_futils_fsync_parent(path);
+
+ return error;
+}
+
+int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode)
+{
+ if (git_futils_mkpath2file(to, dirmode) < 0)
+ return -1;
+
+ if (p_rename(from, to) < 0) {
+ git_error_set(GIT_ERROR_OS, "failed to rename '%s' to '%s'", from, to);
+ return -1;
+ }
+
+ return 0;
+}
+
+int git_futils_mmap_ro(git_map *out, git_file fd, off64_t begin, size_t len)
+{
+ return p_mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin);
+}
+
+int git_futils_mmap_ro_file(git_map *out, const char *path)
+{
+ git_file fd = git_futils_open_ro(path);
+ uint64_t len;
+ int result;
+
+ if (fd < 0)
+ return fd;
+
+ if ((result = git_futils_filesize(&len, fd)) < 0)
+ goto out;
+
+ if (!git__is_sizet(len)) {
+ git_error_set(GIT_ERROR_OS, "file `%s` too large to mmap", path);
+ result = -1;
+ goto out;
+ }
+
+ result = git_futils_mmap_ro(out, fd, 0, (size_t)len);
+out:
+ p_close(fd);
+ return result;
+}
+
+void git_futils_mmap_free(git_map *out)
+{
+ p_munmap(out);
+}
+
+GIT_INLINE(int) mkdir_validate_dir(
+ const char *path,
+ struct stat *st,
+ mode_t mode,
+ uint32_t flags,
+ struct git_futils_mkdir_options *opts)
+{
+ /* with exclusive create, existing dir is an error */
+ if ((flags & GIT_MKDIR_EXCL) != 0) {
+ git_error_set(GIT_ERROR_FILESYSTEM,
+ "failed to make directory '%s': directory exists", path);
+ return GIT_EEXISTS;
+ }
+
+ if ((S_ISREG(st->st_mode) && (flags & GIT_MKDIR_REMOVE_FILES)) ||
+ (S_ISLNK(st->st_mode) && (flags & GIT_MKDIR_REMOVE_SYMLINKS))) {
+ if (p_unlink(path) < 0) {
+ git_error_set(GIT_ERROR_OS, "failed to remove %s '%s'",
+ S_ISLNK(st->st_mode) ? "symlink" : "file", path);
+ return GIT_EEXISTS;
+ }
+
+ opts->perfdata.mkdir_calls++;
+
+ if (p_mkdir(path, mode) < 0) {
+ git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", path);
+ return GIT_EEXISTS;
+ }
+ }
+
+ else if (S_ISLNK(st->st_mode)) {
+ /* Re-stat the target, make sure it's a directory */
+ opts->perfdata.stat_calls++;
+
+ if (p_stat(path, st) < 0) {
+ git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", path);
+ return GIT_EEXISTS;
+ }
+ }
+
+ else if (!S_ISDIR(st->st_mode)) {
+ git_error_set(GIT_ERROR_FILESYSTEM,
+ "failed to make directory '%s': directory exists", path);
+ return GIT_EEXISTS;
+ }
+
+ return 0;
+}
+
+GIT_INLINE(int) mkdir_validate_mode(
+ const char *path,
+ struct stat *st,
+ bool terminal_path,
+ mode_t mode,
+ uint32_t flags,
+ struct git_futils_mkdir_options *opts)
+{
+ if (((terminal_path && (flags & GIT_MKDIR_CHMOD) != 0) ||
+ (flags & GIT_MKDIR_CHMOD_PATH) != 0) && st->st_mode != mode) {
+
+ opts->perfdata.chmod_calls++;
+
+ if (p_chmod(path, mode) < 0) {
+ git_error_set(GIT_ERROR_OS, "failed to set permissions on '%s'", path);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+GIT_INLINE(int) mkdir_canonicalize(
+ git_str *path,
+ uint32_t flags)
+{
+ ssize_t root_len;
+
+ if (path->size == 0) {
+ git_error_set(GIT_ERROR_OS, "attempt to create empty path");
+ return -1;
+ }
+
+ /* Trim trailing slashes (except the root) */
+ if ((root_len = git_fs_path_root(path->ptr)) < 0)
+ root_len = 0;
+ else
+ root_len++;
+
+ while (path->size > (size_t)root_len && path->ptr[path->size - 1] == '/')
+ path->ptr[--path->size] = '\0';
+
+ /* if we are not supposed to made the last element, truncate it */
+ if ((flags & GIT_MKDIR_SKIP_LAST2) != 0) {
+ git_fs_path_dirname_r(path, path->ptr);
+ flags |= GIT_MKDIR_SKIP_LAST;
+ }
+ if ((flags & GIT_MKDIR_SKIP_LAST) != 0) {
+ git_fs_path_dirname_r(path, path->ptr);
+ }
+
+ /* We were either given the root path (or trimmed it to
+ * the root), we don't have anything to do.
+ */
+ if (path->size <= (size_t)root_len)
+ git_str_clear(path);
+
+ return 0;
+}
+
+int git_futils_mkdir(
+ const char *path,
+ mode_t mode,
+ uint32_t flags)
+{
+ git_str make_path = GIT_STR_INIT, parent_path = GIT_STR_INIT;
+ const char *relative;
+ struct git_futils_mkdir_options opts = { 0 };
+ struct stat st;
+ size_t depth = 0;
+ int len = 0, root_len, error;
+
+ if ((error = git_str_puts(&make_path, path)) < 0 ||
+ (error = mkdir_canonicalize(&make_path, flags)) < 0 ||
+ (error = git_str_puts(&parent_path, make_path.ptr)) < 0 ||
+ make_path.size == 0)
+ goto done;
+
+ root_len = git_fs_path_root(make_path.ptr);
+
+ /* find the first parent directory that exists. this will be used
+ * as the base to dirname_relative.
+ */
+ for (relative = make_path.ptr; parent_path.size; ) {
+ error = p_lstat(parent_path.ptr, &st);
+
+ if (error == 0) {
+ break;
+ } else if (errno != ENOENT) {
+ git_error_set(GIT_ERROR_OS, "failed to stat '%s'", parent_path.ptr);
+ error = -1;
+ goto done;
+ }
+
+ depth++;
+
+ /* examine the parent of the current path */
+ if ((len = git_fs_path_dirname_r(&parent_path, parent_path.ptr)) < 0) {
+ error = len;
+ goto done;
+ }
+
+ GIT_ASSERT(len);
+
+ /*
+ * We've walked all the given path's parents and it's either relative
+ * (the parent is simply '.') or rooted (the length is less than or
+ * equal to length of the root path). The path may be less than the
+ * root path length on Windows, where `C:` == `C:/`.
+ */
+ if ((len == 1 && parent_path.ptr[0] == '.') ||
+ (len == 1 && parent_path.ptr[0] == '/') ||
+ len <= root_len) {
+ relative = make_path.ptr;
+ break;
+ }
+
+ relative = make_path.ptr + len + 1;
+
+ /* not recursive? just make this directory relative to its parent. */
+ if ((flags & GIT_MKDIR_PATH) == 0)
+ break;
+ }
+
+ /* we found an item at the location we're trying to create,
+ * validate it.
+ */
+ if (depth == 0) {
+ error = mkdir_validate_dir(make_path.ptr, &st, mode, flags, &opts);
+
+ if (!error)
+ error = mkdir_validate_mode(
+ make_path.ptr, &st, true, mode, flags, &opts);
+
+ goto done;
+ }
+
+ /* we already took `SKIP_LAST` and `SKIP_LAST2` into account when
+ * canonicalizing `make_path`.
+ */
+ flags &= ~(GIT_MKDIR_SKIP_LAST2 | GIT_MKDIR_SKIP_LAST);
+
+ error = git_futils_mkdir_relative(relative,
+ parent_path.size ? parent_path.ptr : NULL, mode, flags, &opts);
+
+done:
+ git_str_dispose(&make_path);
+ git_str_dispose(&parent_path);
+ return error;
+}
+
+int git_futils_mkdir_r(const char *path, const mode_t mode)
+{
+ return git_futils_mkdir(path, mode, GIT_MKDIR_PATH);
+}
+
+int git_futils_mkdir_relative(
+ const char *relative_path,
+ const char *base,
+ mode_t mode,
+ uint32_t flags,
+ struct git_futils_mkdir_options *opts)
+{
+ git_str make_path = GIT_STR_INIT;
+ ssize_t root = 0, min_root_len;
+ char lastch = '/', *tail;
+ struct stat st;
+ struct git_futils_mkdir_options empty_opts = {0};
+ int error;
+
+ if (!opts)
+ opts = &empty_opts;
+
+ /* build path and find "root" where we should start calling mkdir */
+ if (git_fs_path_join_unrooted(&make_path, relative_path, base, &root) < 0)
+ return -1;
+
+ if ((error = mkdir_canonicalize(&make_path, flags)) < 0 ||
+ make_path.size == 0)
+ goto done;
+
+ /* if we are not supposed to make the whole path, reset root */
+ if ((flags & GIT_MKDIR_PATH) == 0)
+ root = git_str_rfind(&make_path, '/');
+
+ /* advance root past drive name or network mount prefix */
+ min_root_len = git_fs_path_root(make_path.ptr);
+ if (root < min_root_len)
+ root = min_root_len;
+ while (root >= 0 && make_path.ptr[root] == '/')
+ ++root;
+
+ /* clip root to make_path length */
+ if (root > (ssize_t)make_path.size)
+ root = (ssize_t)make_path.size; /* i.e. NUL byte of string */
+ if (root < 0)
+ root = 0;
+
+ /* walk down tail of path making each directory */
+ for (tail = &make_path.ptr[root]; *tail; *tail = lastch) {
+ bool mkdir_attempted = false;
+
+ /* advance tail to include next path component */
+ while (*tail == '/')
+ tail++;
+ while (*tail && *tail != '/')
+ tail++;
+
+ /* truncate path at next component */
+ lastch = *tail;
+ *tail = '\0';
+ st.st_mode = 0;
+
+ if (opts->dir_map && git_strmap_exists(opts->dir_map, make_path.ptr))
+ continue;
+
+ /* See what's going on with this path component */
+ opts->perfdata.stat_calls++;
+
+retry_lstat:
+ if (p_lstat(make_path.ptr, &st) < 0) {
+ if (mkdir_attempted || errno != ENOENT) {
+ git_error_set(GIT_ERROR_OS, "cannot access component in path '%s'", make_path.ptr);
+ error = -1;
+ goto done;
+ }
+
+ git_error_clear();
+ opts->perfdata.mkdir_calls++;
+ mkdir_attempted = true;
+ if (p_mkdir(make_path.ptr, mode) < 0) {
+ if (errno == EEXIST)
+ goto retry_lstat;
+ git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", make_path.ptr);
+ error = -1;
+ goto done;
+ }
+ } else {
+ if ((error = mkdir_validate_dir(
+ make_path.ptr, &st, mode, flags, opts)) < 0)
+ goto done;
+ }
+
+ /* chmod if requested and necessary */
+ if ((error = mkdir_validate_mode(
+ make_path.ptr, &st, (lastch == '\0'), mode, flags, opts)) < 0)
+ goto done;
+
+ if (opts->dir_map && opts->pool) {
+ char *cache_path;
+ size_t alloc_size;
+
+ GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, make_path.size, 1);
+ cache_path = git_pool_malloc(opts->pool, alloc_size);
+ GIT_ERROR_CHECK_ALLOC(cache_path);
+
+ memcpy(cache_path, make_path.ptr, make_path.size + 1);
+
+ if ((error = git_strmap_set(opts->dir_map, cache_path, cache_path)) < 0)
+ goto done;
+ }
+ }
+
+ error = 0;
+
+ /* check that full path really is a directory if requested & needed */
+ if ((flags & GIT_MKDIR_VERIFY_DIR) != 0 &&
+ lastch != '\0') {
+ opts->perfdata.stat_calls++;
+
+ if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) {
+ git_error_set(GIT_ERROR_OS, "path is not a directory '%s'",
+ make_path.ptr);
+ error = GIT_ENOTFOUND;
+ }
+ }
+
+done:
+ git_str_dispose(&make_path);
+ return error;
+}
+
+typedef struct {
+ const char *base;
+ size_t baselen;
+ uint32_t flags;
+ int depth;
+} futils__rmdir_data;
+
+#define FUTILS_MAX_DEPTH 100
+
+static int futils__error_cannot_rmdir(const char *path, const char *filemsg)
+{
+ if (filemsg)
+ git_error_set(GIT_ERROR_OS, "could not remove directory '%s': %s",
+ path, filemsg);
+ else
+ git_error_set(GIT_ERROR_OS, "could not remove directory '%s'", path);
+
+ return -1;
+}
+
+static int futils__rm_first_parent(git_str *path, const char *ceiling)
+{
+ int error = GIT_ENOTFOUND;
+ struct stat st;
+
+ while (error == GIT_ENOTFOUND) {
+ git_str_rtruncate_at_char(path, '/');
+
+ if (!path->size || git__prefixcmp(path->ptr, ceiling) != 0)
+ error = 0;
+ else if (p_lstat_posixly(path->ptr, &st) == 0) {
+ if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
+ error = p_unlink(path->ptr);
+ else if (!S_ISDIR(st.st_mode))
+ error = -1; /* fail to remove non-regular file */
+ } else if (errno != ENOTDIR)
+ error = -1;
+ }
+
+ if (error)
+ futils__error_cannot_rmdir(path->ptr, "cannot remove parent");
+
+ return error;
+}
+
+static int futils__rmdir_recurs_foreach(void *opaque, git_str *path)
+{
+ int error = 0;
+ futils__rmdir_data *data = opaque;
+ struct stat st;
+
+ if (data->depth > FUTILS_MAX_DEPTH)
+ error = futils__error_cannot_rmdir(
+ path->ptr, "directory nesting too deep");
+
+ else if ((error = p_lstat_posixly(path->ptr, &st)) < 0) {
+ if (errno == ENOENT)
+ error = 0;
+ else if (errno == ENOTDIR) {
+ /* asked to remove a/b/c/d/e and a/b is a normal file */
+ if ((data->flags & GIT_RMDIR_REMOVE_BLOCKERS) != 0)
+ error = futils__rm_first_parent(path, data->base);
+ else
+ futils__error_cannot_rmdir(
+ path->ptr, "parent is not directory");
+ }
+ else
+ error = git_fs_path_set_error(errno, path->ptr, "rmdir");
+ }
+
+ else if (S_ISDIR(st.st_mode)) {
+ data->depth++;
+
+ error = git_fs_path_direach(path, 0, futils__rmdir_recurs_foreach, data);
+
+ data->depth--;
+
+ if (error < 0)
+ return error;
+
+ if (data->depth == 0 && (data->flags & GIT_RMDIR_SKIP_ROOT) != 0)
+ return error;
+
+ if ((error = p_rmdir(path->ptr)) < 0) {
+ if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 &&
+ (errno == ENOTEMPTY || errno == EEXIST || errno == EBUSY))
+ error = 0;
+ else
+ error = git_fs_path_set_error(errno, path->ptr, "rmdir");
+ }
+ }
+
+ else if ((data->flags & GIT_RMDIR_REMOVE_FILES) != 0) {
+ if (p_unlink(path->ptr) < 0)
+ error = git_fs_path_set_error(errno, path->ptr, "remove");
+ }
+
+ else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0)
+ error = futils__error_cannot_rmdir(path->ptr, "still present");
+
+ return error;
+}
+
+static int futils__rmdir_empty_parent(void *opaque, const char *path)
+{
+ futils__rmdir_data *data = opaque;
+ int error = 0;
+
+ if (strlen(path) <= data->baselen)
+ error = GIT_ITEROVER;
+
+ else if (p_rmdir(path) < 0) {
+ int en = errno;
+
+ if (en == ENOENT || en == ENOTDIR) {
+ /* do nothing */
+ } else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0 &&
+ en == EBUSY) {
+ error = git_fs_path_set_error(errno, path, "rmdir");
+ } else if (en == ENOTEMPTY || en == EEXIST || en == EBUSY) {
+ error = GIT_ITEROVER;
+ } else {
+ error = git_fs_path_set_error(errno, path, "rmdir");
+ }
+ }
+
+ return error;
+}
+
+int git_futils_rmdir_r(
+ const char *path, const char *base, uint32_t flags)
+{
+ int error;
+ git_str fullpath = GIT_STR_INIT;
+ futils__rmdir_data data;
+
+ /* build path and find "root" where we should start calling mkdir */
+ if (git_fs_path_join_unrooted(&fullpath, path, base, NULL) < 0)
+ return -1;
+
+ memset(&data, 0, sizeof(data));
+ data.base = base ? base : "";
+ data.baselen = base ? strlen(base) : 0;
+ data.flags = flags;
+
+ error = futils__rmdir_recurs_foreach(&data, &fullpath);
+
+ /* remove now-empty parents if requested */
+ if (!error && (flags & GIT_RMDIR_EMPTY_PARENTS) != 0)
+ error = git_fs_path_walk_up(
+ &fullpath, base, futils__rmdir_empty_parent, &data);
+
+ if (error == GIT_ITEROVER) {
+ git_error_clear();
+ error = 0;
+ }
+
+ git_str_dispose(&fullpath);
+
+ return error;
+}
+
+int git_futils_fake_symlink(const char *target, const char *path)
+{
+ int retcode = GIT_ERROR;
+ int fd = git_futils_creat_withpath(path, 0755, 0644);
+ if (fd >= 0) {
+ retcode = p_write(fd, target, strlen(target));
+ p_close(fd);
+ }
+ return retcode;
+}
+
+static int cp_by_fd(int ifd, int ofd, bool close_fd_when_done)
+{
+ int error = 0;
+ char buffer[GIT_BUFSIZE_FILEIO];
+ ssize_t len = 0;
+
+ while (!error && (len = p_read(ifd, buffer, sizeof(buffer))) > 0)
+ /* p_write() does not have the same semantics as write(). It loops
+ * internally and will return 0 when it has completed writing.
+ */
+ error = p_write(ofd, buffer, len);
+
+ if (len < 0) {
+ git_error_set(GIT_ERROR_OS, "read error while copying file");
+ error = (int)len;
+ }
+
+ if (error < 0)
+ git_error_set(GIT_ERROR_OS, "write error while copying file");
+
+ if (close_fd_when_done) {
+ p_close(ifd);
+ p_close(ofd);
+ }
+
+ return error;
+}
+
+int git_futils_cp(const char *from, const char *to, mode_t filemode)
+{
+ int ifd, ofd;
+
+ if ((ifd = git_futils_open_ro(from)) < 0)
+ return ifd;
+
+ if ((ofd = p_open(to, O_WRONLY | O_CREAT | O_EXCL, filemode)) < 0) {
+ p_close(ifd);
+ return git_fs_path_set_error(errno, to, "open for writing");
+ }
+
+ return cp_by_fd(ifd, ofd, true);
+}
+
+int git_futils_touch(const char *path, time_t *when)
+{
+ struct p_timeval times[2];
+ int ret;
+
+ times[0].tv_sec = times[1].tv_sec = when ? *when : time(NULL);
+ times[0].tv_usec = times[1].tv_usec = 0;
+
+ ret = p_utimes(path, times);
+
+ return (ret < 0) ? git_fs_path_set_error(errno, path, "touch") : 0;
+}
+
+static int cp_link(const char *from, const char *to, size_t link_size)
+{
+ int error = 0;
+ ssize_t read_len;
+ char *link_data;
+ size_t alloc_size;
+
+ GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, link_size, 1);
+ link_data = git__malloc(alloc_size);
+ GIT_ERROR_CHECK_ALLOC(link_data);
+
+ read_len = p_readlink(from, link_data, link_size);
+ if (read_len != (ssize_t)link_size) {
+ git_error_set(GIT_ERROR_OS, "failed to read symlink data for '%s'", from);
+ error = -1;
+ }
+ else {
+ link_data[read_len] = '\0';
+
+ if (p_symlink(link_data, to) < 0) {
+ git_error_set(GIT_ERROR_OS, "could not symlink '%s' as '%s'",
+ link_data, to);
+ error = -1;
+ }
+ }
+
+ git__free(link_data);
+ return error;
+}
+
+typedef struct {
+ const char *to_root;
+ git_str to;
+ ssize_t from_prefix;
+ uint32_t flags;
+ uint32_t mkdir_flags;
+ mode_t dirmode;
+} cp_r_info;
+
+#define GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT (1u << 10)
+
+static int _cp_r_mkdir(cp_r_info *info, git_str *from)
+{
+ int error = 0;
+
+ /* create root directory the first time we need to create a directory */
+ if ((info->flags & GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT) == 0) {
+ error = git_futils_mkdir(
+ info->to_root, info->dirmode,
+ (info->flags & GIT_CPDIR_CHMOD_DIRS) ? GIT_MKDIR_CHMOD : 0);
+
+ info->flags |= GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT;
+ }
+
+ /* create directory with root as base to prevent excess chmods */
+ if (!error)
+ error = git_futils_mkdir_relative(
+ from->ptr + info->from_prefix, info->to_root,
+ info->dirmode, info->mkdir_flags, NULL);
+
+ return error;
+}
+
+static int _cp_r_callback(void *ref, git_str *from)
+{
+ int error = 0;
+ cp_r_info *info = ref;
+ struct stat from_st, to_st;
+ bool exists = false;
+
+ if ((info->flags & GIT_CPDIR_COPY_DOTFILES) == 0 &&
+ from->ptr[git_fs_path_basename_offset(from)] == '.')
+ return 0;
+
+ if ((error = git_str_joinpath(
+ &info->to, info->to_root, from->ptr + info->from_prefix)) < 0)
+ return error;
+
+ if (!(error = git_fs_path_lstat(info->to.ptr, &to_st)))
+ exists = true;
+ else if (error != GIT_ENOTFOUND)
+ return error;
+ else {
+ git_error_clear();
+ error = 0;
+ }
+
+ if ((error = git_fs_path_lstat(from->ptr, &from_st)) < 0)
+ return error;
+
+ if (S_ISDIR(from_st.st_mode)) {
+ mode_t oldmode = info->dirmode;
+
+ /* if we are not chmod'ing, then overwrite dirmode */
+ if ((info->flags & GIT_CPDIR_CHMOD_DIRS) == 0)
+ info->dirmode = from_st.st_mode;
+
+ /* make directory now if CREATE_EMPTY_DIRS is requested and needed */
+ if (!exists && (info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) != 0)
+ error = _cp_r_mkdir(info, from);
+
+ /* recurse onto target directory */
+ if (!error && (!exists || S_ISDIR(to_st.st_mode)))
+ error = git_fs_path_direach(from, 0, _cp_r_callback, info);
+
+ if (oldmode != 0)
+ info->dirmode = oldmode;
+
+ return error;
+ }
+
+ if (exists) {
+ if ((info->flags & GIT_CPDIR_OVERWRITE) == 0)
+ return 0;
+
+ if (p_unlink(info->to.ptr) < 0) {
+ git_error_set(GIT_ERROR_OS, "cannot overwrite existing file '%s'",
+ info->to.ptr);
+ return GIT_EEXISTS;
+ }
+ }
+
+ /* Done if this isn't a regular file or a symlink */
+ if (!S_ISREG(from_st.st_mode) &&
+ (!S_ISLNK(from_st.st_mode) ||
+ (info->flags & GIT_CPDIR_COPY_SYMLINKS) == 0))
+ return 0;
+
+ /* Make container directory on demand if needed */
+ if ((info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0 &&
+ (error = _cp_r_mkdir(info, from)) < 0)
+ return error;
+
+ /* make symlink or regular file */
+ if (info->flags & GIT_CPDIR_LINK_FILES) {
+ if ((error = p_link(from->ptr, info->to.ptr)) < 0)
+ git_error_set(GIT_ERROR_OS, "failed to link '%s'", from->ptr);
+ } else if (S_ISLNK(from_st.st_mode)) {
+ error = cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size);
+ } else {
+ mode_t usemode = from_st.st_mode;
+
+ if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0)
+ usemode = GIT_PERMS_FOR_WRITE(usemode);
+
+ error = git_futils_cp(from->ptr, info->to.ptr, usemode);
+ }
+
+ return error;
+}
+
+int git_futils_cp_r(
+ const char *from,
+ const char *to,
+ uint32_t flags,
+ mode_t dirmode)
+{
+ int error;
+ git_str path = GIT_STR_INIT;
+ cp_r_info info;
+
+ if (git_str_joinpath(&path, from, "") < 0) /* ensure trailing slash */
+ return -1;
+
+ memset(&info, 0, sizeof(info));
+ info.to_root = to;
+ info.flags = flags;
+ info.dirmode = dirmode;
+ info.from_prefix = path.size;
+ git_str_init(&info.to, 0);
+
+ /* precalculate mkdir flags */
+ if ((flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0) {
+ /* if not creating empty dirs, then use mkdir to create the path on
+ * demand right before files are copied.
+ */
+ info.mkdir_flags = GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST;
+ if ((flags & GIT_CPDIR_CHMOD_DIRS) != 0)
+ info.mkdir_flags |= GIT_MKDIR_CHMOD_PATH;
+ } else {
+ /* otherwise, we will do simple mkdir as directories are encountered */
+ info.mkdir_flags =
+ ((flags & GIT_CPDIR_CHMOD_DIRS) != 0) ? GIT_MKDIR_CHMOD : 0;
+ }
+
+ error = _cp_r_callback(&info, &path);
+
+ git_str_dispose(&path);
+ git_str_dispose(&info.to);
+
+ return error;
+}
+
+int git_futils_filestamp_check(
+ git_futils_filestamp *stamp, const char *path)
+{
+ struct stat st;
+
+ /* if the stamp is NULL, then always reload */
+ if (stamp == NULL)
+ return 1;
+
+ if (p_stat(path, &st) < 0)
+ return GIT_ENOTFOUND;
+
+ if (stamp->mtime.tv_sec == st.st_mtime &&
+#if defined(GIT_USE_NSEC)
+ stamp->mtime.tv_nsec == st.st_mtime_nsec &&
+#endif
+ stamp->size == (uint64_t)st.st_size &&
+ stamp->ino == (unsigned int)st.st_ino)
+ return 0;
+
+ stamp->mtime.tv_sec = st.st_mtime;
+#if defined(GIT_USE_NSEC)
+ stamp->mtime.tv_nsec = st.st_mtime_nsec;
+#endif
+ stamp->size = (uint64_t)st.st_size;
+ stamp->ino = (unsigned int)st.st_ino;
+
+ return 1;
+}
+
+void git_futils_filestamp_set(
+ git_futils_filestamp *target, const git_futils_filestamp *source)
+{
+ if (source)
+ memcpy(target, source, sizeof(*target));
+ else
+ memset(target, 0, sizeof(*target));
+}
+
+
+void git_futils_filestamp_set_from_stat(
+ git_futils_filestamp *stamp, struct stat *st)
+{
+ if (st) {
+ stamp->mtime.tv_sec = st->st_mtime;
+#if defined(GIT_USE_NSEC)
+ stamp->mtime.tv_nsec = st->st_mtime_nsec;
+#else
+ stamp->mtime.tv_nsec = 0;
+#endif
+ stamp->size = (uint64_t)st->st_size;
+ stamp->ino = (unsigned int)st->st_ino;
+ } else {
+ memset(stamp, 0, sizeof(*stamp));
+ }
+}
+
+int git_futils_fsync_dir(const char *path)
+{
+#ifdef GIT_WIN32
+ GIT_UNUSED(path);
+ return 0;
+#else
+ int fd, error = -1;
+
+ if ((fd = p_open(path, O_RDONLY)) < 0) {
+ git_error_set(GIT_ERROR_OS, "failed to open directory '%s' for fsync", path);
+ return -1;
+ }
+
+ if ((error = p_fsync(fd)) < 0)
+ git_error_set(GIT_ERROR_OS, "failed to fsync directory '%s'", path);
+
+ p_close(fd);
+ return error;
+#endif
+}
+
+int git_futils_fsync_parent(const char *path)
+{
+ char *parent;
+ int error;
+
+ if ((parent = git_fs_path_dirname(path)) == NULL)
+ return -1;
+
+ error = git_futils_fsync_dir(parent);
+ git__free(parent);
+ return error;
+}
diff --git a/src/util/futils.h b/src/util/futils.h
new file mode 100644
index 0000000..3f207af
--- /dev/null
+++ b/src/util/futils.h
@@ -0,0 +1,403 @@
+/*
+ * 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_futils_h__
+#define INCLUDE_futils_h__
+
+#include "git2_util.h"
+
+#include "map.h"
+#include "posix.h"
+#include "fs_path.h"
+#include "pool.h"
+#include "strmap.h"
+#include "hash.h"
+
+/**
+ * Filebuffer methods
+ *
+ * Read whole files into an in-memory buffer for processing
+ */
+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],
+ 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);
+
+/* Additional constants for `git_futils_writebuffer`'s `open_flags`. We
+ * support these internally and they will be removed before the `open` call.
+ */
+#ifndef O_FSYNC
+# define O_FSYNC (1 << 31)
+#endif
+
+extern int git_futils_writebuffer(
+ const git_str *buf, const char *path, int open_flags, mode_t mode);
+
+/**
+ * File utils
+ *
+ * These are custom filesystem-related helper methods. They are
+ * rather high level, and wrap the underlying POSIX methods
+ *
+ * All these methods return 0 on success,
+ * or an error code on failure and an error message is set.
+ */
+
+/**
+ * Create and open a file, while also
+ * creating all the folders in its path
+ */
+extern int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode);
+
+/**
+ * Create and open a process-locked file
+ */
+extern int git_futils_creat_locked(const char *path, const mode_t mode);
+
+/**
+ * Create and open a process-locked file, while
+ * also creating all the folders in its path
+ */
+extern int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode);
+
+/**
+ * Create a path recursively.
+ */
+extern int git_futils_mkdir_r(const char *path, const mode_t mode);
+
+/**
+ * Flags to pass to `git_futils_mkdir`.
+ *
+ * * GIT_MKDIR_EXCL is "exclusive" - i.e. generate an error if dir exists.
+ * * GIT_MKDIR_PATH says to make all components in the path.
+ * * GIT_MKDIR_CHMOD says to chmod the final directory entry after creation
+ * * GIT_MKDIR_CHMOD_PATH says to chmod each directory component in the path
+ * * GIT_MKDIR_SKIP_LAST says to leave off the last element of the path
+ * * GIT_MKDIR_SKIP_LAST2 says to leave off the last 2 elements of the path
+ * * GIT_MKDIR_VERIFY_DIR says confirm final item is a dir, not just EEXIST
+ * * GIT_MKDIR_REMOVE_FILES says to remove files and recreate dirs
+ * * GIT_MKDIR_REMOVE_SYMLINKS says to remove symlinks and recreate dirs
+ *
+ * Note that the chmod options will be executed even if the directory already
+ * exists, unless GIT_MKDIR_EXCL is given.
+ */
+typedef enum {
+ GIT_MKDIR_EXCL = 1,
+ GIT_MKDIR_PATH = 2,
+ GIT_MKDIR_CHMOD = 4,
+ GIT_MKDIR_CHMOD_PATH = 8,
+ GIT_MKDIR_SKIP_LAST = 16,
+ GIT_MKDIR_SKIP_LAST2 = 32,
+ GIT_MKDIR_VERIFY_DIR = 64,
+ GIT_MKDIR_REMOVE_FILES = 128,
+ GIT_MKDIR_REMOVE_SYMLINKS = 256
+} git_futils_mkdir_flags;
+
+struct git_futils_mkdir_perfdata
+{
+ size_t stat_calls;
+ size_t mkdir_calls;
+ size_t chmod_calls;
+};
+
+struct git_futils_mkdir_options
+{
+ git_strmap *dir_map;
+ git_pool *pool;
+ struct git_futils_mkdir_perfdata perfdata;
+};
+
+/**
+ * Create a directory or entire path.
+ *
+ * This makes a directory (and the entire path leading up to it if requested),
+ * and optionally chmods the directory immediately after (or each part of the
+ * path if requested).
+ *
+ * @param path The path to create, relative to base.
+ * @param base Root for relative path. These directories will never be made.
+ * @param mode The mode to use for created directories.
+ * @param flags Combination of the mkdir flags above.
+ * @param opts Extended options, or null.
+ * @return 0 on success, else error code
+ */
+extern int git_futils_mkdir_relative(const char *path, const char *base, mode_t mode, uint32_t flags, struct git_futils_mkdir_options *opts);
+
+/**
+ * Create a directory or entire path. Similar to `git_futils_mkdir_relative`
+ * without performance data.
+ */
+extern int git_futils_mkdir(const char *path, mode_t mode, uint32_t flags);
+
+/**
+ * Create all the folders required to contain
+ * the full path of a file
+ */
+extern int git_futils_mkpath2file(const char *path, const mode_t mode);
+
+/**
+ * Flags to pass to `git_futils_rmdir_r`.
+ *
+ * * GIT_RMDIR_EMPTY_HIERARCHY - the default; remove hierarchy of empty
+ * dirs and generate error if any files are found.
+ * * GIT_RMDIR_REMOVE_FILES - attempt to remove files in the hierarchy.
+ * * GIT_RMDIR_SKIP_NONEMPTY - skip non-empty directories with no error.
+ * * GIT_RMDIR_EMPTY_PARENTS - remove containing directories up to base
+ * if removing this item leaves them empty
+ * * GIT_RMDIR_REMOVE_BLOCKERS - remove blocking file that causes ENOTDIR
+ * * GIT_RMDIR_SKIP_ROOT - don't remove root directory itself
+ */
+typedef enum {
+ GIT_RMDIR_EMPTY_HIERARCHY = 0,
+ GIT_RMDIR_REMOVE_FILES = (1 << 0),
+ GIT_RMDIR_SKIP_NONEMPTY = (1 << 1),
+ GIT_RMDIR_EMPTY_PARENTS = (1 << 2),
+ GIT_RMDIR_REMOVE_BLOCKERS = (1 << 3),
+ GIT_RMDIR_SKIP_ROOT = (1 << 4)
+} git_futils_rmdir_flags;
+
+/**
+ * Remove path and any files and directories beneath it.
+ *
+ * @param path Path to the top level directory to process.
+ * @param base Root for relative path.
+ * @param flags Combination of git_futils_rmdir_flags values
+ * @return 0 on success; -1 on error.
+ */
+extern int git_futils_rmdir_r(const char *path, const char *base, uint32_t flags);
+
+/**
+ * Create and open a temporary file with a `_git2_` suffix in a
+ * protected directory; the file created will created will honor
+ * the current `umask`. Writes the filename into path_out.
+ *
+ * This function uses a high-quality PRNG seeded by the system's
+ * entropy pool _where available_ and falls back to a simple seed
+ * (time plus system information) when not. This is suitable for
+ * writing within a protected directory, but the system's safe
+ * temporary file creation functions should be preferred where
+ * available when writing into world-writable (temp) directories.
+ *
+ * @return On success, an open file descriptor, else an error code < 0.
+ */
+extern int git_futils_mktmp(git_str *path_out, const char *filename, mode_t mode);
+
+/**
+ * Move a file on the filesystem, create the
+ * destination path if it doesn't exist
+ */
+extern int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode);
+
+/**
+ * Copy a file
+ *
+ * The filemode will be used for the newly created file.
+ */
+extern int git_futils_cp(
+ const char *from,
+ const char *to,
+ mode_t filemode);
+
+/**
+ * Set the files atime and mtime to the given time, or the current time
+ * if `ts` is NULL.
+ */
+extern int git_futils_touch(const char *path, time_t *when);
+
+/**
+ * Flags that can be passed to `git_futils_cp_r`.
+ *
+ * - GIT_CPDIR_CREATE_EMPTY_DIRS: create directories even if there are no
+ * files under them (otherwise directories will only be created lazily
+ * when a file inside them is copied).
+ * - GIT_CPDIR_COPY_SYMLINKS: copy symlinks, otherwise they are ignored.
+ * - GIT_CPDIR_COPY_DOTFILES: copy files with leading '.', otherwise ignored.
+ * - GIT_CPDIR_OVERWRITE: overwrite pre-existing files with source content,
+ * otherwise they are silently skipped.
+ * - GIT_CPDIR_CHMOD_DIRS: explicitly chmod directories to `dirmode`
+ * - GIT_CPDIR_SIMPLE_TO_MODE: default tries to replicate the mode of the
+ * source file to the target; with this flag, always use 0666 (or 0777 if
+ * source has exec bits set) for target.
+ * - GIT_CPDIR_LINK_FILES will try to use hardlinks for the files
+ */
+typedef enum {
+ GIT_CPDIR_CREATE_EMPTY_DIRS = (1u << 0),
+ GIT_CPDIR_COPY_SYMLINKS = (1u << 1),
+ GIT_CPDIR_COPY_DOTFILES = (1u << 2),
+ GIT_CPDIR_OVERWRITE = (1u << 3),
+ GIT_CPDIR_CHMOD_DIRS = (1u << 4),
+ GIT_CPDIR_SIMPLE_TO_MODE = (1u << 5),
+ GIT_CPDIR_LINK_FILES = (1u << 6)
+} git_futils_cpdir_flags;
+
+/**
+ * Copy a directory tree.
+ *
+ * This copies directories and files from one root to another. You can
+ * pass a combination of GIT_CPDIR flags as defined above.
+ *
+ * If you pass the CHMOD flag, then the dirmode will be applied to all
+ * directories that are created during the copy, overriding the natural
+ * permissions. If you do not pass the CHMOD flag, then the dirmode
+ * will actually be copied from the source files and the `dirmode` arg
+ * will be ignored.
+ */
+extern int git_futils_cp_r(
+ const char *from,
+ const char *to,
+ uint32_t flags,
+ mode_t dirmode);
+
+/**
+ * Open a file readonly and set error if needed.
+ */
+extern int git_futils_open_ro(const char *path);
+
+/**
+ * Truncate a file, creating it if it doesn't exist.
+ */
+extern int git_futils_truncate(const char *path, int mode);
+
+/**
+ * Get the filesize in bytes of a file
+ */
+extern int git_futils_filesize(uint64_t *out, git_file fd);
+
+#define GIT_PERMS_IS_EXEC(MODE) (((MODE) & 0100) != 0)
+#define GIT_PERMS_CANONICAL(MODE) (GIT_PERMS_IS_EXEC(MODE) ? 0755 : 0644)
+#define GIT_PERMS_FOR_WRITE(MODE) (GIT_PERMS_IS_EXEC(MODE) ? 0777 : 0666)
+
+#define GIT_MODE_PERMS_MASK 0777
+#define GIT_MODE_TYPE_MASK 0170000
+#define GIT_MODE_TYPE(MODE) ((MODE) & GIT_MODE_TYPE_MASK)
+#define GIT_MODE_ISBLOB(MODE) (GIT_MODE_TYPE(MODE) == GIT_MODE_TYPE(GIT_FILEMODE_BLOB))
+
+/**
+ * Convert a mode_t from the OS to a legal git mode_t value.
+ */
+extern mode_t git_futils_canonical_mode(mode_t raw_mode);
+
+
+/**
+ * Read-only map all or part of a file into memory.
+ * When possible this function should favor a virtual memory
+ * style mapping over some form of malloc()+read(), as the
+ * data access will be random and is not likely to touch the
+ * majority of the region requested.
+ *
+ * @param out buffer to populate with the mapping information.
+ * @param fd open descriptor to configure the mapping from.
+ * @param begin first byte to map, this should be page aligned.
+ * @param len number of bytes to map.
+ * @return
+ * - 0 on success;
+ * - -1 on error.
+ */
+extern int git_futils_mmap_ro(
+ git_map *out,
+ git_file fd,
+ off64_t begin,
+ size_t len);
+
+/**
+ * Read-only map an entire file.
+ *
+ * @param out buffer to populate with the mapping information.
+ * @param path path to file to be opened.
+ * @return
+ * - 0 on success;
+ * - GIT_ENOTFOUND if not found;
+ * - -1 on an unspecified OS related error.
+ */
+extern int git_futils_mmap_ro_file(
+ git_map *out,
+ const char *path);
+
+/**
+ * Release the memory associated with a previous memory mapping.
+ * @param map the mapping description previously configured.
+ */
+extern void git_futils_mmap_free(git_map *map);
+
+/**
+ * Create a "fake" symlink (text file containing the target path).
+ *
+ * @param target original symlink target
+ * @param path symlink file to be created
+ * @return 0 on success, -1 on error
+ */
+extern int git_futils_fake_symlink(const char *target, const char *path);
+
+/**
+ * A file stamp represents a snapshot of information about a file that can
+ * be used to test if the file changes. This portable implementation is
+ * based on stat data about that file, but it is possible that OS specific
+ * versions could be implemented in the future.
+ */
+typedef struct {
+ struct timespec mtime;
+ uint64_t size;
+ unsigned int ino;
+} git_futils_filestamp;
+
+/**
+ * Compare stat information for file with reference info.
+ *
+ * This function updates the file stamp to current data for the given path
+ * and returns 0 if the file is up-to-date relative to the prior setting,
+ * 1 if the file has been changed, or GIT_ENOTFOUND if the file doesn't
+ * exist. This will not call git_error_set, so you must set the error if you
+ * plan to return an error.
+ *
+ * @param stamp File stamp to be checked
+ * @param path Path to stat and check if changed
+ * @return 0 if up-to-date, 1 if out-of-date, GIT_ENOTFOUND if cannot stat
+ */
+extern int git_futils_filestamp_check(
+ git_futils_filestamp *stamp, const char *path);
+
+/**
+ * Set or reset file stamp data
+ *
+ * This writes the target file stamp. If the source is NULL, this will set
+ * the target stamp to values that will definitely be out of date. If the
+ * source is not NULL, this copies the source values to the target.
+ *
+ * @param tgt File stamp to write to
+ * @param src File stamp to copy from or NULL to clear the target
+ */
+extern void git_futils_filestamp_set(
+ git_futils_filestamp *tgt, const git_futils_filestamp *src);
+
+/**
+ * Set file stamp data from stat structure
+ */
+extern void git_futils_filestamp_set_from_stat(
+ git_futils_filestamp *stamp, struct stat *st);
+
+/**
+ * `fsync` the parent directory of the given path, if `fsync` is
+ * supported for directories on this platform.
+ *
+ * @param path Path of the directory to sync.
+ * @return 0 on success, -1 on error
+ */
+extern int git_futils_fsync_dir(const char *path);
+
+/**
+ * `fsync` the parent directory of the given path, if `fsync` is
+ * supported for directories on this platform.
+ *
+ * @param path Path of the file whose parent directory should be synced.
+ * @return 0 on success, -1 on error
+ */
+extern int git_futils_fsync_parent(const char *path);
+
+#endif
diff --git a/src/util/git2_features.h.in b/src/util/git2_features.h.in
new file mode 100644
index 0000000..a84ea89
--- /dev/null
+++ b/src/util/git2_features.h.in
@@ -0,0 +1,68 @@
+#ifndef INCLUDE_features_h__
+#define INCLUDE_features_h__
+
+#cmakedefine GIT_DEBUG_POOL 1
+#cmakedefine GIT_DEBUG_STRICT_ALLOC 1
+#cmakedefine GIT_DEBUG_STRICT_OPEN 1
+
+#cmakedefine GIT_THREADS 1
+#cmakedefine GIT_WIN32_LEAKCHECK 1
+
+#cmakedefine GIT_ARCH_64 1
+#cmakedefine GIT_ARCH_32 1
+
+#cmakedefine GIT_USE_ICONV 1
+#cmakedefine GIT_USE_NSEC 1
+#cmakedefine GIT_USE_STAT_MTIM 1
+#cmakedefine GIT_USE_STAT_MTIMESPEC 1
+#cmakedefine GIT_USE_STAT_MTIME_NSEC 1
+#cmakedefine GIT_USE_FUTIMENS 1
+
+#cmakedefine GIT_REGEX_REGCOMP_L
+#cmakedefine GIT_REGEX_REGCOMP
+#cmakedefine GIT_REGEX_PCRE
+#cmakedefine GIT_REGEX_PCRE2
+#cmakedefine GIT_REGEX_BUILTIN 1
+
+#cmakedefine GIT_QSORT_BSD
+#cmakedefine GIT_QSORT_GNU
+#cmakedefine GIT_QSORT_C11
+#cmakedefine GIT_QSORT_MSC
+
+#cmakedefine GIT_SSH 1
+#cmakedefine GIT_SSH_MEMORY_CREDENTIALS 1
+
+#cmakedefine GIT_NTLM 1
+#cmakedefine GIT_GSSAPI 1
+#cmakedefine GIT_GSSFRAMEWORK 1
+
+#cmakedefine GIT_WINHTTP 1
+#cmakedefine GIT_HTTPS 1
+#cmakedefine GIT_OPENSSL 1
+#cmakedefine GIT_OPENSSL_DYNAMIC 1
+#cmakedefine GIT_SECURE_TRANSPORT 1
+#cmakedefine GIT_MBEDTLS 1
+#cmakedefine GIT_SCHANNEL 1
+
+#cmakedefine GIT_SHA1_COLLISIONDETECT 1
+#cmakedefine GIT_SHA1_WIN32 1
+#cmakedefine GIT_SHA1_COMMON_CRYPTO 1
+#cmakedefine GIT_SHA1_OPENSSL 1
+#cmakedefine GIT_SHA1_OPENSSL_DYNAMIC 1
+#cmakedefine GIT_SHA1_MBEDTLS 1
+
+#cmakedefine GIT_SHA256_BUILTIN 1
+#cmakedefine GIT_SHA256_WIN32 1
+#cmakedefine GIT_SHA256_COMMON_CRYPTO 1
+#cmakedefine GIT_SHA256_OPENSSL 1
+#cmakedefine GIT_SHA256_OPENSSL_DYNAMIC 1
+#cmakedefine GIT_SHA256_MBEDTLS 1
+
+#cmakedefine GIT_RAND_GETENTROPY 1
+#cmakedefine GIT_RAND_GETLOADAVG 1
+
+#cmakedefine GIT_IO_POLL 1
+#cmakedefine GIT_IO_WSAPOLL 1
+#cmakedefine GIT_IO_SELECT 1
+
+#endif
diff --git a/src/util/git2_util.h b/src/util/git2_util.h
new file mode 100644
index 0000000..c62dc24
--- /dev/null
+++ b/src/util/git2_util.h
@@ -0,0 +1,168 @@
+/*
+ * 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_git2_util_h__
+#define INCLUDE_git2_util_h__
+
+#if !defined(LIBGIT2_NO_FEATURES_H)
+# include "git2_features.h"
+#endif
+
+#include "git2/common.h"
+#include "cc-compat.h"
+
+typedef struct git_str git_str;
+
+/** Declare a function as always inlined. */
+#if defined(_MSC_VER)
+# define GIT_INLINE(type) static __inline type
+#elif defined(__GNUC__)
+# define GIT_INLINE(type) static __inline__ type
+#else
+# define GIT_INLINE(type) static type
+#endif
+
+/** Support for gcc/clang __has_builtin intrinsic */
+#ifndef __has_builtin
+# define __has_builtin(x) 0
+#endif
+
+/**
+ * Declare that a function's return value must be used.
+ *
+ * Used mostly to guard against potential silent bugs at runtime. This is
+ * recommended to be added to functions that:
+ *
+ * - Allocate / reallocate memory. This prevents memory leaks or errors where
+ * buffers are expected to have grown to a certain size, but could not be
+ * resized.
+ * - Acquire locks. When a lock cannot be acquired, that will almost certainly
+ * cause a data race / undefined behavior.
+ */
+#if defined(__GNUC__)
+# define GIT_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
+#else
+# define GIT_WARN_UNUSED_RESULT
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef GIT_WIN32
+
+# include <io.h>
+# include <direct.h>
+# include <winsock2.h>
+# include <windows.h>
+# include <ws2tcpip.h>
+# include "win32/msvc-compat.h"
+# include "win32/mingw-compat.h"
+# include "win32/win32-compat.h"
+# include "win32/w32_common.h"
+# include "win32/version.h"
+# include "win32/error.h"
+# ifdef GIT_THREADS
+# include "win32/thread.h"
+# endif
+
+#else
+
+# include <unistd.h>
+# include <strings.h>
+# ifdef GIT_THREADS
+# include <pthread.h>
+# include <sched.h>
+# endif
+
+#define GIT_LIBGIT2_CALL
+#define GIT_SYSTEM_CALL
+
+#ifdef GIT_USE_STAT_ATIMESPEC
+# define st_atim st_atimespec
+# define st_ctim st_ctimespec
+# define st_mtim st_mtimespec
+#endif
+
+# include <arpa/inet.h>
+
+#endif
+
+#include "git2/types.h"
+#include "git2/errors.h"
+#include "thread.h"
+#include "integer.h"
+#include "assert_safe.h"
+
+#include "posix.h"
+
+#define GIT_BUFSIZE_DEFAULT 65536
+#define GIT_BUFSIZE_FILEIO GIT_BUFSIZE_DEFAULT
+#define GIT_BUFSIZE_FILTERIO GIT_BUFSIZE_DEFAULT
+#define GIT_BUFSIZE_NETIO GIT_BUFSIZE_DEFAULT
+
+
+/**
+ * Check a pointer allocation result, returning -1 if it failed.
+ */
+#define GIT_ERROR_CHECK_ALLOC(ptr) do { \
+ if ((ptr) == NULL) { return -1; } \
+ } while(0)
+
+/**
+ * Check a buffer allocation result, returning -1 if it failed.
+ */
+#define GIT_ERROR_CHECK_ALLOC_STR(buf) do { \
+ if ((void *)(buf) == NULL || git_str_oom(buf)) { return -1; } \
+ } while(0)
+
+/**
+ * Check a return value and propagate result if non-zero.
+ */
+#define GIT_ERROR_CHECK_ERROR(code) \
+ do { int _err = (code); if (_err) return _err; } while (0)
+
+
+/** Check for additive overflow, setting an error if would occur. */
+#define GIT_ADD_SIZET_OVERFLOW(out, one, two) \
+ (git__add_sizet_overflow(out, one, two) ? (git_error_set_oom(), 1) : 0)
+
+/** Check for additive overflow, setting an error if would occur. */
+#define GIT_MULTIPLY_SIZET_OVERFLOW(out, nelem, elsize) \
+ (git__multiply_sizet_overflow(out, nelem, elsize) ? (git_error_set_oom(), 1) : 0)
+
+/** Check for additive overflow, failing if it would occur. */
+#define GIT_ERROR_CHECK_ALLOC_ADD(out, one, two) \
+ if (GIT_ADD_SIZET_OVERFLOW(out, one, two)) { return -1; }
+
+#define GIT_ERROR_CHECK_ALLOC_ADD3(out, one, two, three) \
+ if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \
+ GIT_ADD_SIZET_OVERFLOW(out, *(out), three)) { return -1; }
+
+#define GIT_ERROR_CHECK_ALLOC_ADD4(out, one, two, three, four) \
+ if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \
+ GIT_ADD_SIZET_OVERFLOW(out, *(out), three) || \
+ GIT_ADD_SIZET_OVERFLOW(out, *(out), four)) { return -1; }
+
+#define GIT_ERROR_CHECK_ALLOC_ADD5(out, one, two, three, four, five) \
+ if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \
+ GIT_ADD_SIZET_OVERFLOW(out, *(out), three) || \
+ GIT_ADD_SIZET_OVERFLOW(out, *(out), four) || \
+ GIT_ADD_SIZET_OVERFLOW(out, *(out), five)) { return -1; }
+
+/** Check for multiplicative overflow, failing if it would occur. */
+#define GIT_ERROR_CHECK_ALLOC_MULTIPLY(out, nelem, elsize) \
+ if (GIT_MULTIPLY_SIZET_OVERFLOW(out, nelem, elsize)) { return -1; }
+
+#include "util.h"
+
+#endif
diff --git a/src/util/hash.c b/src/util/hash.c
new file mode 100644
index 0000000..ff900ce
--- /dev/null
+++ b/src/util/hash.c
@@ -0,0 +1,158 @@
+/*
+ * 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 "hash.h"
+
+int git_hash_global_init(void)
+{
+ if (git_hash_sha1_global_init() < 0 ||
+ git_hash_sha256_global_init() < 0)
+ return -1;
+
+ return 0;
+}
+
+int git_hash_ctx_init(git_hash_ctx *ctx, git_hash_algorithm_t algorithm)
+{
+ int error;
+
+ switch (algorithm) {
+ case GIT_HASH_ALGORITHM_SHA1:
+ error = git_hash_sha1_ctx_init(&ctx->ctx.sha1);
+ break;
+ case GIT_HASH_ALGORITHM_SHA256:
+ error = git_hash_sha256_ctx_init(&ctx->ctx.sha256);
+ break;
+ default:
+ git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm");
+ error = -1;
+ }
+
+ ctx->algorithm = algorithm;
+ return error;
+}
+
+void git_hash_ctx_cleanup(git_hash_ctx *ctx)
+{
+ switch (ctx->algorithm) {
+ case GIT_HASH_ALGORITHM_SHA1:
+ git_hash_sha1_ctx_cleanup(&ctx->ctx.sha1);
+ return;
+ case GIT_HASH_ALGORITHM_SHA256:
+ git_hash_sha256_ctx_cleanup(&ctx->ctx.sha256);
+ return;
+ default:
+ /* unreachable */ ;
+ }
+}
+
+int git_hash_init(git_hash_ctx *ctx)
+{
+ switch (ctx->algorithm) {
+ case GIT_HASH_ALGORITHM_SHA1:
+ return git_hash_sha1_init(&ctx->ctx.sha1);
+ case GIT_HASH_ALGORITHM_SHA256:
+ return git_hash_sha256_init(&ctx->ctx.sha256);
+ default:
+ /* unreachable */ ;
+ }
+
+ git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm");
+ return -1;
+}
+
+int git_hash_update(git_hash_ctx *ctx, const void *data, size_t len)
+{
+ switch (ctx->algorithm) {
+ case GIT_HASH_ALGORITHM_SHA1:
+ return git_hash_sha1_update(&ctx->ctx.sha1, data, len);
+ case GIT_HASH_ALGORITHM_SHA256:
+ return git_hash_sha256_update(&ctx->ctx.sha256, data, len);
+ default:
+ /* unreachable */ ;
+ }
+
+ git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm");
+ return -1;
+}
+
+int git_hash_final(unsigned char *out, git_hash_ctx *ctx)
+{
+ switch (ctx->algorithm) {
+ case GIT_HASH_ALGORITHM_SHA1:
+ return git_hash_sha1_final(out, &ctx->ctx.sha1);
+ case GIT_HASH_ALGORITHM_SHA256:
+ return git_hash_sha256_final(out, &ctx->ctx.sha256);
+ default:
+ /* unreachable */ ;
+ }
+
+ git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm");
+ return -1;
+}
+
+int git_hash_buf(
+ unsigned char *out,
+ const void *data,
+ size_t len,
+ git_hash_algorithm_t algorithm)
+{
+ git_hash_ctx ctx;
+ int error = 0;
+
+ if (git_hash_ctx_init(&ctx, algorithm) < 0)
+ return -1;
+
+ if ((error = git_hash_update(&ctx, data, len)) >= 0)
+ error = git_hash_final(out, &ctx);
+
+ git_hash_ctx_cleanup(&ctx);
+
+ return error;
+}
+
+int git_hash_vec(
+ unsigned char *out,
+ git_str_vec *vec,
+ size_t n,
+ git_hash_algorithm_t algorithm)
+{
+ git_hash_ctx ctx;
+ size_t i;
+ int error = 0;
+
+ if (git_hash_ctx_init(&ctx, algorithm) < 0)
+ return -1;
+
+ for (i = 0; i < n; i++) {
+ if ((error = git_hash_update(&ctx, vec[i].data, vec[i].len)) < 0)
+ goto done;
+ }
+
+ error = git_hash_final(out, &ctx);
+
+done:
+ git_hash_ctx_cleanup(&ctx);
+
+ return error;
+}
+
+int git_hash_fmt(char *out, unsigned char *hash, size_t hash_len)
+{
+ static char hex[] = "0123456789abcdef";
+ char *str = out;
+ size_t i;
+
+ for (i = 0; i < hash_len; i++) {
+ *str++ = hex[hash[i] >> 4];
+ *str++ = hex[hash[i] & 0x0f];
+ }
+
+ *str++ = '\0';
+
+ return 0;
+}
diff --git a/src/util/hash.h b/src/util/hash.h
new file mode 100644
index 0000000..21fcaf0
--- /dev/null
+++ b/src/util/hash.h
@@ -0,0 +1,61 @@
+/*
+ * 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_hash_h__
+#define INCLUDE_hash_h__
+
+#include "git2_util.h"
+
+#include "hash/sha.h"
+
+typedef struct {
+ void *data;
+ size_t len;
+} git_str_vec;
+
+typedef enum {
+ GIT_HASH_ALGORITHM_NONE = 0,
+ GIT_HASH_ALGORITHM_SHA1,
+ GIT_HASH_ALGORITHM_SHA256
+} git_hash_algorithm_t;
+
+#define GIT_HASH_MAX_SIZE GIT_HASH_SHA256_SIZE
+
+typedef struct git_hash_ctx {
+ union {
+ git_hash_sha1_ctx sha1;
+ git_hash_sha256_ctx sha256;
+ } ctx;
+ git_hash_algorithm_t algorithm;
+} git_hash_ctx;
+
+int git_hash_global_init(void);
+
+int git_hash_ctx_init(git_hash_ctx *ctx, git_hash_algorithm_t algorithm);
+void git_hash_ctx_cleanup(git_hash_ctx *ctx);
+
+int git_hash_init(git_hash_ctx *c);
+int git_hash_update(git_hash_ctx *c, const void *data, size_t len);
+int git_hash_final(unsigned char *out, git_hash_ctx *c);
+
+int git_hash_buf(unsigned char *out, const void *data, size_t len, git_hash_algorithm_t algorithm);
+int git_hash_vec(unsigned char *out, git_str_vec *vec, size_t n, git_hash_algorithm_t algorithm);
+
+int git_hash_fmt(char *out, unsigned char *hash, size_t hash_len);
+
+GIT_INLINE(size_t) git_hash_size(git_hash_algorithm_t algorithm) {
+ switch (algorithm) {
+ case GIT_HASH_ALGORITHM_SHA1:
+ return GIT_HASH_SHA1_SIZE;
+ case GIT_HASH_ALGORITHM_SHA256:
+ return GIT_HASH_SHA256_SIZE;
+ default:
+ return 0;
+ }
+}
+
+#endif
diff --git a/src/util/hash/builtin.c b/src/util/hash/builtin.c
new file mode 100644
index 0000000..cc4aa58
--- /dev/null
+++ b/src/util/hash/builtin.c
@@ -0,0 +1,53 @@
+/*
+ * 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 "builtin.h"
+
+int git_hash_sha256_global_init(void)
+{
+ return 0;
+}
+
+int git_hash_sha256_ctx_init(git_hash_sha256_ctx *ctx)
+{
+ return git_hash_sha256_init(ctx);
+}
+
+void git_hash_sha256_ctx_cleanup(git_hash_sha256_ctx *ctx)
+{
+ GIT_UNUSED(ctx);
+}
+
+int git_hash_sha256_init(git_hash_sha256_ctx *ctx)
+{
+ GIT_ASSERT_ARG(ctx);
+ if (SHA256Reset(&ctx->c)) {
+ git_error_set(GIT_ERROR_SHA, "SHA256 error");
+ return -1;
+ }
+ return 0;
+}
+
+int git_hash_sha256_update(git_hash_sha256_ctx *ctx, const void *data, size_t len)
+{
+ GIT_ASSERT_ARG(ctx);
+ if (SHA256Input(&ctx->c, data, len)) {
+ git_error_set(GIT_ERROR_SHA, "SHA256 error");
+ return -1;
+ }
+ return 0;
+}
+
+int git_hash_sha256_final(unsigned char *out, git_hash_sha256_ctx *ctx)
+{
+ GIT_ASSERT_ARG(ctx);
+ if (SHA256Result(&ctx->c, out)) {
+ git_error_set(GIT_ERROR_SHA, "SHA256 error");
+ return -1;
+ }
+ return 0;
+}
diff --git a/src/util/hash/builtin.h b/src/util/hash/builtin.h
new file mode 100644
index 0000000..769df1a
--- /dev/null
+++ b/src/util/hash/builtin.h
@@ -0,0 +1,19 @@
+/*
+ * 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_hash_builtin_h__
+#define INCLUDE_hash_builtin_h__
+
+#include "hash/sha.h"
+
+#include "rfc6234/sha.h"
+
+struct git_hash_sha256_ctx {
+ SHA256Context c;
+};
+
+#endif
diff --git a/src/util/hash/collisiondetect.c b/src/util/hash/collisiondetect.c
new file mode 100644
index 0000000..c51a402
--- /dev/null
+++ b/src/util/hash/collisiondetect.c
@@ -0,0 +1,48 @@
+/*
+ * 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 "collisiondetect.h"
+
+int git_hash_sha1_global_init(void)
+{
+ return 0;
+}
+
+int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx)
+{
+ return git_hash_sha1_init(ctx);
+}
+
+void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx)
+{
+ GIT_UNUSED(ctx);
+}
+
+int git_hash_sha1_init(git_hash_sha1_ctx *ctx)
+{
+ GIT_ASSERT_ARG(ctx);
+ SHA1DCInit(&ctx->c);
+ return 0;
+}
+
+int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len)
+{
+ GIT_ASSERT_ARG(ctx);
+ SHA1DCUpdate(&ctx->c, data, len);
+ return 0;
+}
+
+int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx)
+{
+ GIT_ASSERT_ARG(ctx);
+ if (SHA1DCFinal(out, &ctx->c)) {
+ git_error_set(GIT_ERROR_SHA, "SHA1 collision attack detected");
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/src/util/hash/collisiondetect.h b/src/util/hash/collisiondetect.h
new file mode 100644
index 0000000..8de5502
--- /dev/null
+++ b/src/util/hash/collisiondetect.h
@@ -0,0 +1,19 @@
+/*
+ * 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_hash_collisiondetect_h__
+#define INCLUDE_hash_collisiondetect_h__
+
+#include "hash/sha.h"
+
+#include "sha1dc/sha1.h"
+
+struct git_hash_sha1_ctx {
+ SHA1_CTX c;
+};
+
+#endif
diff --git a/src/util/hash/common_crypto.c b/src/util/hash/common_crypto.c
new file mode 100644
index 0000000..b327ba9
--- /dev/null
+++ b/src/util/hash/common_crypto.c
@@ -0,0 +1,112 @@
+/*
+ * 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 "common_crypto.h"
+
+#define CC_LONG_MAX ((CC_LONG)-1)
+
+#ifdef GIT_SHA1_COMMON_CRYPTO
+
+int git_hash_sha1_global_init(void)
+{
+ return 0;
+}
+
+int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx)
+{
+ return git_hash_sha1_init(ctx);
+}
+
+void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx)
+{
+ GIT_UNUSED(ctx);
+}
+
+int git_hash_sha1_init(git_hash_sha1_ctx *ctx)
+{
+ GIT_ASSERT_ARG(ctx);
+ CC_SHA1_Init(&ctx->c);
+ return 0;
+}
+
+int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *_data, size_t len)
+{
+ const unsigned char *data = _data;
+
+ GIT_ASSERT_ARG(ctx);
+
+ while (len > 0) {
+ CC_LONG chunk = (len > CC_LONG_MAX) ? CC_LONG_MAX : (CC_LONG)len;
+
+ CC_SHA1_Update(&ctx->c, data, chunk);
+
+ data += chunk;
+ len -= chunk;
+ }
+
+ return 0;
+}
+
+int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx)
+{
+ GIT_ASSERT_ARG(ctx);
+ CC_SHA1_Final(out, &ctx->c);
+ return 0;
+}
+
+#endif
+
+#ifdef GIT_SHA256_COMMON_CRYPTO
+
+int git_hash_sha256_global_init(void)
+{
+ return 0;
+}
+
+int git_hash_sha256_ctx_init(git_hash_sha256_ctx *ctx)
+{
+ return git_hash_sha256_init(ctx);
+}
+
+void git_hash_sha256_ctx_cleanup(git_hash_sha256_ctx *ctx)
+{
+ GIT_UNUSED(ctx);
+}
+
+int git_hash_sha256_init(git_hash_sha256_ctx *ctx)
+{
+ GIT_ASSERT_ARG(ctx);
+ CC_SHA256_Init(&ctx->c);
+ return 0;
+}
+
+int git_hash_sha256_update(git_hash_sha256_ctx *ctx, const void *_data, size_t len)
+{
+ const unsigned char *data = _data;
+
+ GIT_ASSERT_ARG(ctx);
+
+ while (len > 0) {
+ CC_LONG chunk = (len > CC_LONG_MAX) ? CC_LONG_MAX : (CC_LONG)len;
+
+ CC_SHA256_Update(&ctx->c, data, chunk);
+
+ data += chunk;
+ len -= chunk;
+ }
+
+ return 0;
+}
+
+int git_hash_sha256_final(unsigned char *out, git_hash_sha256_ctx *ctx)
+{
+ GIT_ASSERT_ARG(ctx);
+ CC_SHA256_Final(out, &ctx->c);
+ return 0;
+}
+
+#endif
diff --git a/src/util/hash/common_crypto.h b/src/util/hash/common_crypto.h
new file mode 100644
index 0000000..157712b
--- /dev/null
+++ b/src/util/hash/common_crypto.h
@@ -0,0 +1,27 @@
+/*
+ * 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_hash_common_crypto_h__
+#define INCLUDE_hash_common_crypto_h__
+
+#include "hash/sha.h"
+
+#include <CommonCrypto/CommonDigest.h>
+
+#ifdef GIT_SHA1_COMMON_CRYPTO
+struct git_hash_sha1_ctx {
+ CC_SHA1_CTX c;
+};
+#endif
+
+#ifdef GIT_SHA256_COMMON_CRYPTO
+struct git_hash_sha256_ctx {
+ CC_SHA256_CTX c;
+};
+#endif
+
+#endif
diff --git a/src/util/hash/mbedtls.c b/src/util/hash/mbedtls.c
new file mode 100644
index 0000000..ecdfb78
--- /dev/null
+++ b/src/util/hash/mbedtls.c
@@ -0,0 +1,92 @@
+/*
+ * 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 "mbedtls.h"
+
+#ifdef GIT_SHA1_MBEDTLS
+
+int git_hash_sha1_global_init(void)
+{
+ return 0;
+}
+
+int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx)
+{
+ return git_hash_sha1_init(ctx);
+}
+
+void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx)
+{
+ if (ctx)
+ mbedtls_sha1_free(&ctx->c);
+}
+
+int git_hash_sha1_init(git_hash_sha1_ctx *ctx)
+{
+ GIT_ASSERT_ARG(ctx);
+ mbedtls_sha1_init(&ctx->c);
+ mbedtls_sha1_starts(&ctx->c);
+ return 0;
+}
+
+int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len)
+{
+ GIT_ASSERT_ARG(ctx);
+ mbedtls_sha1_update(&ctx->c, data, len);
+ return 0;
+}
+
+int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx)
+{
+ GIT_ASSERT_ARG(ctx);
+ mbedtls_sha1_finish(&ctx->c, out);
+ return 0;
+}
+
+#endif
+
+#ifdef GIT_SHA256_MBEDTLS
+
+int git_hash_sha256_global_init(void)
+{
+ return 0;
+}
+
+int git_hash_sha256_ctx_init(git_hash_sha256_ctx *ctx)
+{
+ return git_hash_sha256_init(ctx);
+}
+
+void git_hash_sha256_ctx_cleanup(git_hash_sha256_ctx *ctx)
+{
+ if (ctx)
+ mbedtls_sha256_free(&ctx->c);
+}
+
+int git_hash_sha256_init(git_hash_sha256_ctx *ctx)
+{
+ GIT_ASSERT_ARG(ctx);
+ mbedtls_sha256_init(&ctx->c);
+ mbedtls_sha256_starts(&ctx->c, 0);
+ return 0;
+}
+
+int git_hash_sha256_update(git_hash_sha256_ctx *ctx, const void *data, size_t len)
+{
+ GIT_ASSERT_ARG(ctx);
+ mbedtls_sha256_update(&ctx->c, data, len);
+ return 0;
+}
+
+int git_hash_sha256_final(unsigned char *out, git_hash_sha256_ctx *ctx)
+{
+ GIT_ASSERT_ARG(ctx);
+ mbedtls_sha256_finish(&ctx->c, out);
+ return 0;
+}
+
+#endif
diff --git a/src/util/hash/mbedtls.h b/src/util/hash/mbedtls.h
new file mode 100644
index 0000000..05fb38b
--- /dev/null
+++ b/src/util/hash/mbedtls.h
@@ -0,0 +1,29 @@
+/*
+ * 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_hash_mbedtls_h__
+#define INCLUDE_hash_mbedtls_h__
+
+#include "hash/sha.h"
+
+#ifdef GIT_SHA1_MBEDTLS
+# include <mbedtls/sha1.h>
+
+struct git_hash_sha1_ctx {
+ mbedtls_sha1_context c;
+};
+#endif
+
+#ifdef GIT_SHA256_MBEDTLS
+# include <mbedtls/sha256.h>
+
+struct git_hash_sha256_ctx {
+ mbedtls_sha256_context c;
+};
+#endif
+
+#endif /* INCLUDE_hash_sha1_mbedtls_h__ */
diff --git a/src/util/hash/openssl.c b/src/util/hash/openssl.c
new file mode 100644
index 0000000..eaf91e7
--- /dev/null
+++ b/src/util/hash/openssl.c
@@ -0,0 +1,195 @@
+/*
+ * 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 "openssl.h"
+
+#ifdef GIT_OPENSSL_DYNAMIC
+# include <dlfcn.h>
+
+static int handle_count;
+static void *openssl_handle;
+
+static int git_hash_openssl_global_shutdown(void)
+{
+ if (--handle_count == 0) {
+ dlclose(openssl_handle);
+ openssl_handle = NULL;
+ }
+
+ return 0;
+}
+
+static int git_hash_openssl_global_init(void)
+{
+ if (!handle_count) {
+ if ((openssl_handle = dlopen("libssl.so.1.1", RTLD_NOW)) == NULL &&
+ (openssl_handle = dlopen("libssl.1.1.dylib", RTLD_NOW)) == NULL &&
+ (openssl_handle = dlopen("libssl.so.1.0.0", RTLD_NOW)) == NULL &&
+ (openssl_handle = dlopen("libssl.1.0.0.dylib", RTLD_NOW)) == NULL &&
+ (openssl_handle = dlopen("libssl.so.10", RTLD_NOW)) == NULL &&
+ (openssl_handle = dlopen("libssl.so.3", RTLD_NOW)) == NULL) {
+ git_error_set(GIT_ERROR_SSL, "could not load ssl libraries");
+ return -1;
+ }
+ }
+
+ if (git_hash_openssl_global_shutdown() < 0)
+ return -1;
+
+ handle_count++;
+ return 0;
+}
+
+#endif
+
+#ifdef GIT_SHA1_OPENSSL
+
+# ifdef GIT_OPENSSL_DYNAMIC
+static int (*SHA1_Init)(SHA_CTX *c);
+static int (*SHA1_Update)(SHA_CTX *c, const void *data, size_t len);
+static int (*SHA1_Final)(unsigned char *md, SHA_CTX *c);
+# endif
+
+int git_hash_sha1_global_init(void)
+{
+#ifdef GIT_OPENSSL_DYNAMIC
+ if (git_hash_openssl_global_init() < 0)
+ return -1;
+
+ if ((SHA1_Init = dlsym(openssl_handle, "SHA1_Init")) == NULL ||
+ (SHA1_Update = dlsym(openssl_handle, "SHA1_Update")) == NULL ||
+ (SHA1_Final = dlsym(openssl_handle, "SHA1_Final")) == NULL) {
+ const char *msg = dlerror();
+ git_error_set(GIT_ERROR_SSL, "could not load hash function: %s", msg ? msg : "unknown error");
+ return -1;
+ }
+#endif
+
+ return 0;
+}
+
+int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx)
+{
+ return git_hash_sha1_init(ctx);
+}
+
+void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx)
+{
+ GIT_UNUSED(ctx);
+}
+
+int git_hash_sha1_init(git_hash_sha1_ctx *ctx)
+{
+ GIT_ASSERT_ARG(ctx);
+
+ if (SHA1_Init(&ctx->c) != 1) {
+ git_error_set(GIT_ERROR_SHA, "failed to initialize sha1 context");
+ return -1;
+ }
+
+ return 0;
+}
+
+int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len)
+{
+ GIT_ASSERT_ARG(ctx);
+
+ if (SHA1_Update(&ctx->c, data, len) != 1) {
+ git_error_set(GIT_ERROR_SHA, "failed to update sha1");
+ return -1;
+ }
+
+ return 0;
+}
+
+int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx)
+{
+ GIT_ASSERT_ARG(ctx);
+
+ if (SHA1_Final(out, &ctx->c) != 1) {
+ git_error_set(GIT_ERROR_SHA, "failed to finalize sha1");
+ return -1;
+ }
+
+ return 0;
+}
+
+#endif
+
+#ifdef GIT_SHA256_OPENSSL
+
+# ifdef GIT_OPENSSL_DYNAMIC
+static int (*SHA256_Init)(SHA256_CTX *c);
+static int (*SHA256_Update)(SHA256_CTX *c, const void *data, size_t len);
+static int (*SHA256_Final)(unsigned char *md, SHA256_CTX *c);
+#endif
+
+int git_hash_sha256_global_init(void)
+{
+#ifdef GIT_OPENSSL_DYNAMIC
+ if (git_hash_openssl_global_init() < 0)
+ return -1;
+
+ if ((SHA256_Init = dlsym(openssl_handle, "SHA256_Init")) == NULL ||
+ (SHA256_Update = dlsym(openssl_handle, "SHA256_Update")) == NULL ||
+ (SHA256_Final = dlsym(openssl_handle, "SHA256_Final")) == NULL) {
+ const char *msg = dlerror();
+ git_error_set(GIT_ERROR_SSL, "could not load hash function: %s", msg ? msg : "unknown error");
+ return -1;
+ }
+#endif
+
+ return 0;
+}
+
+int git_hash_sha256_ctx_init(git_hash_sha256_ctx *ctx)
+{
+ return git_hash_sha256_init(ctx);
+}
+
+void git_hash_sha256_ctx_cleanup(git_hash_sha256_ctx *ctx)
+{
+ GIT_UNUSED(ctx);
+}
+
+int git_hash_sha256_init(git_hash_sha256_ctx *ctx)
+{
+ GIT_ASSERT_ARG(ctx);
+
+ if (SHA256_Init(&ctx->c) != 1) {
+ git_error_set(GIT_ERROR_SHA, "failed to initialize sha256 context");
+ return -1;
+ }
+
+ return 0;
+}
+
+int git_hash_sha256_update(git_hash_sha256_ctx *ctx, const void *data, size_t len)
+{
+ GIT_ASSERT_ARG(ctx);
+
+ if (SHA256_Update(&ctx->c, data, len) != 1) {
+ git_error_set(GIT_ERROR_SHA, "failed to update sha256");
+ return -1;
+ }
+
+ return 0;
+}
+
+int git_hash_sha256_final(unsigned char *out, git_hash_sha256_ctx *ctx)
+{
+ GIT_ASSERT_ARG(ctx);
+
+ if (SHA256_Final(out, &ctx->c) != 1) {
+ git_error_set(GIT_ERROR_SHA, "failed to finalize sha256");
+ return -1;
+ }
+
+ return 0;
+}
+
+#endif
diff --git a/src/util/hash/openssl.h b/src/util/hash/openssl.h
new file mode 100644
index 0000000..7cb089a
--- /dev/null
+++ b/src/util/hash/openssl.h
@@ -0,0 +1,45 @@
+/*
+ * 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_hash_openssl_h__
+#define INCLUDE_hash_openssl_h__
+
+#include "hash/sha.h"
+
+#ifndef GIT_OPENSSL_DYNAMIC
+# include <openssl/sha.h>
+#else
+
+typedef struct {
+ unsigned int h0, h1, h2, h3, h4;
+ unsigned int Nl, Nh;
+ unsigned int data[16];
+ unsigned int num;
+} SHA_CTX;
+
+typedef struct {
+ unsigned int h[8];
+ unsigned int Nl, Nh;
+ unsigned int data[16];
+ unsigned int num, md_len;
+} SHA256_CTX;
+
+#endif
+
+#ifdef GIT_SHA1_OPENSSL
+struct git_hash_sha1_ctx {
+ SHA_CTX c;
+};
+#endif
+
+#ifdef GIT_SHA256_OPENSSL
+struct git_hash_sha256_ctx {
+ SHA256_CTX c;
+};
+#endif
+
+#endif
diff --git a/src/util/hash/rfc6234/sha.h b/src/util/hash/rfc6234/sha.h
new file mode 100644
index 0000000..0fbcc50
--- /dev/null
+++ b/src/util/hash/rfc6234/sha.h
@@ -0,0 +1,243 @@
+/**************************** sha.h ****************************/
+/***************** See RFC 6234 for details. *******************/
+/*
+ Copyright (c) 2011 IETF Trust and the persons identified as
+ authors of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or
+ without modification, are permitted provided that the following
+ conditions are met:
+
+ - Redistributions of source code must retain the above
+ copyright notice, this list of conditions and
+ the following disclaimer.
+
+ - Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+ - Neither the name of Internet Society, IETF or IETF Trust, nor
+ the names of specific contributors, may be used to endorse or
+ promote products derived from this software without specific
+ prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+#ifndef _SHA_H_
+#define _SHA_H_
+
+/*
+ * Description:
+ * This file implements the Secure Hash Algorithms
+ * as defined in the U.S. National Institute of Standards
+ * and Technology Federal Information Processing Standards
+ * Publication (FIPS PUB) 180-3 published in October 2008
+ * and formerly defined in its predecessors, FIPS PUB 180-1
+ * and FIP PUB 180-2.
+ *
+ * A combined document showing all algorithms is available at
+ * http://csrc.nist.gov/publications/fips/
+ * fips180-3/fips180-3_final.pdf
+ *
+ * The five hashes are defined in these sizes:
+ * SHA-1 20 byte / 160 bit
+ * SHA-224 28 byte / 224 bit
+ * SHA-256 32 byte / 256 bit
+ * SHA-384 48 byte / 384 bit
+ * SHA-512 64 byte / 512 bit
+ *
+ * Compilation Note:
+ * These files may be compiled with two options:
+ * USE_32BIT_ONLY - use 32-bit arithmetic only, for systems
+ * without 64-bit integers
+ *
+ * USE_MODIFIED_MACROS - use alternate form of the SHA_Ch()
+ * and SHA_Maj() macros that are equivalent
+ * and potentially faster on many systems
+ *
+ */
+
+#include <stdint.h>
+/*
+ * If you do not have the ISO standard stdint.h header file, then you
+ * must typedef the following:
+ * name meaning
+ * uint64_t unsigned 64-bit integer
+ * uint32_t unsigned 32-bit integer
+ * uint8_t unsigned 8-bit integer (i.e., unsigned char)
+ * int_least16_t integer of >= 16 bits
+ *
+ * See stdint-example.h
+ */
+
+#ifndef _SHA_enum_
+#define _SHA_enum_
+/*
+ * All SHA functions return one of these values.
+ */
+enum {
+ shaSuccess = 0,
+ shaNull, /* Null pointer parameter */
+ shaInputTooLong, /* input data too long */
+ shaStateError, /* called Input after FinalBits or Result */
+ shaBadParam /* passed a bad parameter */
+};
+#endif /* _SHA_enum_ */
+
+/*
+ * These constants hold size information for each of the SHA
+ * hashing operations
+ */
+enum {
+ SHA1_Message_Block_Size = 64, SHA224_Message_Block_Size = 64,
+ SHA256_Message_Block_Size = 64, SHA384_Message_Block_Size = 128,
+ SHA512_Message_Block_Size = 128,
+ USHA_Max_Message_Block_Size = SHA512_Message_Block_Size,
+ SHA1HashSize = 20, SHA224HashSize = 28, SHA256HashSize = 32,
+ SHA384HashSize = 48, SHA512HashSize = 64,
+ USHAMaxHashSize = SHA512HashSize,
+
+ SHA1HashSizeBits = 160, SHA224HashSizeBits = 224,
+ SHA256HashSizeBits = 256, SHA384HashSizeBits = 384,
+ SHA512HashSizeBits = 512, USHAMaxHashSizeBits = SHA512HashSizeBits
+};
+
+/*
+ * These constants are used in the USHA (Unified SHA) functions.
+ */
+typedef enum SHAversion {
+ SHA1, SHA224, SHA256, SHA384, SHA512
+} SHAversion;
+
+/*
+ * This structure will hold context information for the SHA-1
+ * hashing operation.
+ */
+typedef struct SHA1Context {
+ uint32_t Intermediate_Hash[SHA1HashSize/4]; /* Message Digest */
+
+ uint32_t Length_High; /* Message length in bits */
+ uint32_t Length_Low; /* Message length in bits */
+
+ int_least16_t Message_Block_Index; /* Message_Block array index */
+ /* 512-bit message blocks */
+ uint8_t Message_Block[SHA1_Message_Block_Size];
+
+ int Computed; /* Is the hash computed? */
+ int Corrupted; /* Cumulative corruption code */
+} SHA1Context;
+
+/*
+ * This structure will hold context information for the SHA-256
+ * hashing operation.
+ */
+typedef struct SHA256Context {
+ uint32_t Intermediate_Hash[SHA256HashSize/4]; /* Message Digest */
+
+ uint32_t Length_High; /* Message length in bits */
+ uint32_t Length_Low; /* Message length in bits */
+
+ int_least16_t Message_Block_Index; /* Message_Block array index */
+ /* 512-bit message blocks */
+ uint8_t Message_Block[SHA256_Message_Block_Size];
+
+ int Computed; /* Is the hash computed? */
+ int Corrupted; /* Cumulative corruption code */
+} SHA256Context;
+
+/*
+ * This structure will hold context information for the SHA-512
+ * hashing operation.
+ */
+typedef struct SHA512Context {
+#ifdef USE_32BIT_ONLY
+ uint32_t Intermediate_Hash[SHA512HashSize/4]; /* Message Digest */
+ uint32_t Length[4]; /* Message length in bits */
+#else /* !USE_32BIT_ONLY */
+ uint64_t Intermediate_Hash[SHA512HashSize/8]; /* Message Digest */
+ uint64_t Length_High, Length_Low; /* Message length in bits */
+#endif /* USE_32BIT_ONLY */
+
+ int_least16_t Message_Block_Index; /* Message_Block array index */
+ /* 1024-bit message blocks */
+ uint8_t Message_Block[SHA512_Message_Block_Size];
+
+ int Computed; /* Is the hash computed?*/
+ int Corrupted; /* Cumulative corruption code */
+} SHA512Context;
+
+/*
+ * This structure will hold context information for the SHA-224
+ * hashing operation. It uses the SHA-256 structure for computation.
+ */
+typedef struct SHA256Context SHA224Context;
+
+/*
+ * This structure will hold context information for the SHA-384
+ * hashing operation. It uses the SHA-512 structure for computation.
+ */
+typedef struct SHA512Context SHA384Context;
+
+/*
+ * Function Prototypes
+ */
+
+/* SHA-1 */
+extern int SHA1Reset(SHA1Context *);
+extern int SHA1Input(SHA1Context *, const uint8_t *bytes,
+ unsigned int bytecount);
+extern int SHA1FinalBits(SHA1Context *, uint8_t bits,
+ unsigned int bit_count);
+extern int SHA1Result(SHA1Context *,
+ uint8_t Message_Digest[SHA1HashSize]);
+
+/* SHA-224 */
+extern int SHA224Reset(SHA224Context *);
+extern int SHA224Input(SHA224Context *, const uint8_t *bytes,
+ unsigned int bytecount);
+extern int SHA224FinalBits(SHA224Context *, uint8_t bits,
+ unsigned int bit_count);
+extern int SHA224Result(SHA224Context *,
+ uint8_t Message_Digest[SHA224HashSize]);
+
+/* SHA-256 */
+extern int SHA256Reset(SHA256Context *);
+extern int SHA256Input(SHA256Context *, const uint8_t *bytes,
+ unsigned int bytecount);
+extern int SHA256FinalBits(SHA256Context *, uint8_t bits,
+ unsigned int bit_count);
+extern int SHA256Result(SHA256Context *,
+ uint8_t Message_Digest[SHA256HashSize]);
+
+/* SHA-384 */
+extern int SHA384Reset(SHA384Context *);
+extern int SHA384Input(SHA384Context *, const uint8_t *bytes,
+ unsigned int bytecount);
+extern int SHA384FinalBits(SHA384Context *, uint8_t bits,
+ unsigned int bit_count);
+extern int SHA384Result(SHA384Context *,
+ uint8_t Message_Digest[SHA384HashSize]);
+
+/* SHA-512 */
+extern int SHA512Reset(SHA512Context *);
+extern int SHA512Input(SHA512Context *, const uint8_t *bytes,
+ unsigned int bytecount);
+extern int SHA512FinalBits(SHA512Context *, uint8_t bits,
+ unsigned int bit_count);
+extern int SHA512Result(SHA512Context *,
+ uint8_t Message_Digest[SHA512HashSize]);
+
+#endif /* _SHA_H_ */
diff --git a/src/util/hash/rfc6234/sha224-256.c b/src/util/hash/rfc6234/sha224-256.c
new file mode 100644
index 0000000..c8e0cf8
--- /dev/null
+++ b/src/util/hash/rfc6234/sha224-256.c
@@ -0,0 +1,601 @@
+/************************* sha224-256.c ************************/
+/***************** See RFC 6234 for details. *******************/
+/* Copyright (c) 2011 IETF Trust and the persons identified as */
+/* authors of the code. All rights reserved. */
+/* See sha.h for terms of use and redistribution. */
+
+/*
+ * Description:
+ * This file implements the Secure Hash Algorithms SHA-224 and
+ * SHA-256 as defined in the U.S. National Institute of Standards
+ * and Technology Federal Information Processing Standards
+ * Publication (FIPS PUB) 180-3 published in October 2008
+ * and formerly defined in its predecessors, FIPS PUB 180-1
+ * and FIP PUB 180-2.
+ *
+ * A combined document showing all algorithms is available at
+ * http://csrc.nist.gov/publications/fips/
+ * fips180-3/fips180-3_final.pdf
+ *
+ * The SHA-224 and SHA-256 algorithms produce 224-bit and 256-bit
+ * message digests for a given data stream. It should take about
+ * 2**n steps to find a message with the same digest as a given
+ * message and 2**(n/2) to find any two messages with the same
+ * digest, when n is the digest size in bits. Therefore, this
+ * algorithm can serve as a means of providing a
+ * "fingerprint" for a message.
+ *
+ * Portability Issues:
+ * SHA-224 and SHA-256 are defined in terms of 32-bit "words".
+ * This code uses <stdint.h> (included via "sha.h") to define 32-
+ * and 8-bit unsigned integer types. If your C compiler does not
+ * support 32-bit unsigned integers, this code is not
+ * appropriate.
+ *
+ * Caveats:
+ * SHA-224 and SHA-256 are designed to work with messages less
+ * than 2^64 bits long. This implementation uses SHA224/256Input()
+ * to hash the bits that are a multiple of the size of an 8-bit
+ * octet, and then optionally uses SHA224/256FinalBits()
+ * to hash the final few bits of the input.
+ */
+
+#include "sha.h"
+
+/*
+ * These definitions are defined in FIPS 180-3, section 4.1.
+ * Ch() and Maj() are defined identically in sections 4.1.1,
+ * 4.1.2, and 4.1.3.
+ *
+ * The definitions used in FIPS 180-3 are as follows:
+ */
+#ifndef USE_MODIFIED_MACROS
+#define SHA_Ch(x,y,z) (((x) & (y)) ^ ((~(x)) & (z)))
+#define SHA_Maj(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
+#else /* USE_MODIFIED_MACROS */
+/*
+ * The following definitions are equivalent and potentially faster.
+ */
+#define SHA_Ch(x, y, z) (((x) & ((y) ^ (z))) ^ (z))
+#define SHA_Maj(x, y, z) (((x) & ((y) | (z))) | ((y) & (z)))
+#endif /* USE_MODIFIED_MACROS */
+
+#define SHA_Parity(x, y, z) ((x) ^ (y) ^ (z))
+
+/* Define the SHA shift, rotate left, and rotate right macros */
+#define SHA256_SHR(bits,word) ((word) >> (bits))
+#define SHA256_ROTL(bits,word) \
+ (((word) << (bits)) | ((word) >> (32-(bits))))
+#define SHA256_ROTR(bits,word) \
+ (((word) >> (bits)) | ((word) << (32-(bits))))
+
+/* Define the SHA SIGMA and sigma macros */
+#define SHA256_SIGMA0(word) \
+ (SHA256_ROTR( 2,word) ^ SHA256_ROTR(13,word) ^ SHA256_ROTR(22,word))
+#define SHA256_SIGMA1(word) \
+ (SHA256_ROTR( 6,word) ^ SHA256_ROTR(11,word) ^ SHA256_ROTR(25,word))
+#define SHA256_sigma0(word) \
+ (SHA256_ROTR( 7,word) ^ SHA256_ROTR(18,word) ^ SHA256_SHR( 3,word))
+#define SHA256_sigma1(word) \
+ (SHA256_ROTR(17,word) ^ SHA256_ROTR(19,word) ^ SHA256_SHR(10,word))
+
+/*
+ * Add "length" to the length.
+ * Set Corrupted when overflow has occurred.
+ */
+static uint32_t addTemp;
+#define SHA224_256AddLength(context, length) \
+ (addTemp = (context)->Length_Low, (context)->Corrupted = \
+ (((context)->Length_Low += (length)) < addTemp) && \
+ (++(context)->Length_High == 0) ? shaInputTooLong : \
+ (context)->Corrupted )
+
+/* Local Function Prototypes */
+static int SHA224_256Reset(SHA256Context *context, uint32_t *H0);
+static void SHA224_256ProcessMessageBlock(SHA256Context *context);
+static void SHA224_256Finalize(SHA256Context *context,
+ uint8_t Pad_Byte);
+static void SHA224_256PadMessage(SHA256Context *context,
+ uint8_t Pad_Byte);
+static int SHA224_256ResultN(SHA256Context *context,
+ uint8_t Message_Digest[ ], int HashSize);
+
+/* Initial Hash Values: FIPS 180-3 section 5.3.2 */
+static uint32_t SHA224_H0[SHA256HashSize/4] = {
+ 0xC1059ED8, 0x367CD507, 0x3070DD17, 0xF70E5939,
+ 0xFFC00B31, 0x68581511, 0x64F98FA7, 0xBEFA4FA4
+};
+
+/* Initial Hash Values: FIPS 180-3 section 5.3.3 */
+static uint32_t SHA256_H0[SHA256HashSize/4] = {
+ 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A,
+ 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19
+};
+
+/*
+ * SHA224Reset
+ *
+ * Description:
+ * This function will initialize the SHA224Context in preparation
+ * for computing a new SHA224 message digest.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to reset.
+ *
+ * Returns:
+ * sha Error Code.
+ */
+int SHA224Reset(SHA224Context *context)
+{
+ return SHA224_256Reset(context, SHA224_H0);
+}
+
+/*
+ * SHA224Input
+ *
+ * Description:
+ * This function accepts an array of octets as the next portion
+ * of the message.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The SHA context to update.
+ * message_array[ ]: [in]
+ * An array of octets representing the next portion of
+ * the message.
+ * length: [in]
+ * The length of the message in message_array.
+ *
+ * Returns:
+ * sha Error Code.
+ *
+ */
+int SHA224Input(SHA224Context *context, const uint8_t *message_array,
+ unsigned int length)
+{
+ return SHA256Input(context, message_array, length);
+}
+
+/*
+ * SHA224FinalBits
+ *
+ * Description:
+ * This function will add in any final bits of the message.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The SHA context to update.
+ * message_bits: [in]
+ * The final bits of the message, in the upper portion of the
+ * byte. (Use 0b###00000 instead of 0b00000### to input the
+ * three bits ###.)
+ * length: [in]
+ * The number of bits in message_bits, between 1 and 7.
+ *
+ * Returns:
+ * sha Error Code.
+ */
+int SHA224FinalBits(SHA224Context *context,
+ uint8_t message_bits, unsigned int length)
+{
+ return SHA256FinalBits(context, message_bits, length);
+}
+
+/*
+ * SHA224Result
+ *
+ * Description:
+ * This function will return the 224-bit message digest
+ * into the Message_Digest array provided by the caller.
+ * NOTE:
+ * The first octet of hash is stored in the element with index 0,
+ * the last octet of hash in the element with index 27.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to use to calculate the SHA hash.
+ * Message_Digest[ ]: [out]
+ * Where the digest is returned.
+ *
+ * Returns:
+ * sha Error Code.
+ */
+int SHA224Result(SHA224Context *context,
+ uint8_t Message_Digest[SHA224HashSize])
+{
+ return SHA224_256ResultN(context, Message_Digest, SHA224HashSize);
+}
+
+/*
+ * SHA256Reset
+ *
+ * Description:
+ * This function will initialize the SHA256Context in preparation
+ * for computing a new SHA256 message digest.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to reset.
+ *
+ * Returns:
+ * sha Error Code.
+ */
+int SHA256Reset(SHA256Context *context)
+{
+ return SHA224_256Reset(context, SHA256_H0);
+}
+
+/*
+ * SHA256Input
+ *
+ * Description:
+ * This function accepts an array of octets as the next portion
+ * of the message.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The SHA context to update.
+ * message_array[ ]: [in]
+ * An array of octets representing the next portion of
+ * the message.
+ * length: [in]
+ * The length of the message in message_array.
+ *
+ * Returns:
+ * sha Error Code.
+ */
+int SHA256Input(SHA256Context *context, const uint8_t *message_array,
+ unsigned int length)
+{
+ if (!context) return shaNull;
+ if (!length) return shaSuccess;
+ if (!message_array) return shaNull;
+ if (context->Computed) return context->Corrupted = shaStateError;
+ if (context->Corrupted) return context->Corrupted;
+
+ while (length--) {
+ context->Message_Block[context->Message_Block_Index++] =
+ *message_array;
+
+ if ((SHA224_256AddLength(context, 8) == shaSuccess) &&
+ (context->Message_Block_Index == SHA256_Message_Block_Size))
+ SHA224_256ProcessMessageBlock(context);
+
+ message_array++;
+ }
+
+ return context->Corrupted;
+
+}
+
+/*
+ * SHA256FinalBits
+ *
+ * Description:
+ * This function will add in any final bits of the message.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The SHA context to update.
+ * message_bits: [in]
+ * The final bits of the message, in the upper portion of the
+ * byte. (Use 0b###00000 instead of 0b00000### to input the
+ * three bits ###.)
+ * length: [in]
+ * The number of bits in message_bits, between 1 and 7.
+ *
+ * Returns:
+ * sha Error Code.
+ */
+int SHA256FinalBits(SHA256Context *context,
+ uint8_t message_bits, unsigned int length)
+{
+ static uint8_t masks[8] = {
+ /* 0 0b00000000 */ 0x00, /* 1 0b10000000 */ 0x80,
+ /* 2 0b11000000 */ 0xC0, /* 3 0b11100000 */ 0xE0,
+ /* 4 0b11110000 */ 0xF0, /* 5 0b11111000 */ 0xF8,
+ /* 6 0b11111100 */ 0xFC, /* 7 0b11111110 */ 0xFE
+ };
+ static uint8_t markbit[8] = {
+ /* 0 0b10000000 */ 0x80, /* 1 0b01000000 */ 0x40,
+ /* 2 0b00100000 */ 0x20, /* 3 0b00010000 */ 0x10,
+ /* 4 0b00001000 */ 0x08, /* 5 0b00000100 */ 0x04,
+ /* 6 0b00000010 */ 0x02, /* 7 0b00000001 */ 0x01
+ };
+
+ if (!context) return shaNull;
+ if (!length) return shaSuccess;
+ if (context->Corrupted) return context->Corrupted;
+ if (context->Computed) return context->Corrupted = shaStateError;
+ if (length >= 8) return context->Corrupted = shaBadParam;
+
+ SHA224_256AddLength(context, length);
+ SHA224_256Finalize(context, (uint8_t)
+ ((message_bits & masks[length]) | markbit[length]));
+
+ return context->Corrupted;
+}
+
+/*
+ * SHA256Result
+ *
+ * Description:
+ * This function will return the 256-bit message digest
+ * into the Message_Digest array provided by the caller.
+ * NOTE:
+ * The first octet of hash is stored in the element with index 0,
+ * the last octet of hash in the element with index 31.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to use to calculate the SHA hash.
+ * Message_Digest[ ]: [out]
+ * Where the digest is returned.
+ *
+ * Returns:
+ * sha Error Code.
+ */
+int SHA256Result(SHA256Context *context,
+ uint8_t Message_Digest[SHA256HashSize])
+{
+ return SHA224_256ResultN(context, Message_Digest, SHA256HashSize);
+}
+
+/*
+ * SHA224_256Reset
+ *
+ * Description:
+ * This helper function will initialize the SHA256Context in
+ * preparation for computing a new SHA-224 or SHA-256 message digest.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to reset.
+ * H0[ ]: [in]
+ * The initial hash value array to use.
+ *
+ * Returns:
+ * sha Error Code.
+ */
+static int SHA224_256Reset(SHA256Context *context, uint32_t *H0)
+{
+ if (!context) return shaNull;
+
+ context->Length_High = context->Length_Low = 0;
+ context->Message_Block_Index = 0;
+
+ context->Intermediate_Hash[0] = H0[0];
+ context->Intermediate_Hash[1] = H0[1];
+ context->Intermediate_Hash[2] = H0[2];
+ context->Intermediate_Hash[3] = H0[3];
+ context->Intermediate_Hash[4] = H0[4];
+ context->Intermediate_Hash[5] = H0[5];
+ context->Intermediate_Hash[6] = H0[6];
+ context->Intermediate_Hash[7] = H0[7];
+
+ context->Computed = 0;
+ context->Corrupted = shaSuccess;
+
+ return shaSuccess;
+}
+
+/*
+ * SHA224_256ProcessMessageBlock
+ *
+ * Description:
+ * This helper function will process the next 512 bits of the
+ * message stored in the Message_Block array.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The SHA context to update.
+ *
+ * Returns:
+ * Nothing.
+ *
+ * Comments:
+ * Many of the variable names in this code, especially the
+ * single character names, were used because those were the
+ * names used in the Secure Hash Standard.
+ */
+static void SHA224_256ProcessMessageBlock(SHA256Context *context)
+{
+ /* Constants defined in FIPS 180-3, section 4.2.2 */
+ static const uint32_t K[64] = {
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b,
+ 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01,
+ 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7,
+ 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
+ 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152,
+ 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
+ 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
+ 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819,
+ 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08,
+ 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f,
+ 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+ };
+ int t, t4; /* Loop counter */
+ uint32_t temp1, temp2; /* Temporary word value */
+ uint32_t W[64]; /* Word sequence */
+ uint32_t A, B, C, D, E, F, G, H; /* Word buffers */
+
+ /*
+ * Initialize the first 16 words in the array W
+ */
+ for (t = t4 = 0; t < 16; t++, t4 += 4)
+ W[t] = (((uint32_t)context->Message_Block[t4]) << 24) |
+ (((uint32_t)context->Message_Block[t4 + 1]) << 16) |
+ (((uint32_t)context->Message_Block[t4 + 2]) << 8) |
+ (((uint32_t)context->Message_Block[t4 + 3]));
+
+ for (t = 16; t < 64; t++)
+ W[t] = SHA256_sigma1(W[t-2]) + W[t-7] +
+ SHA256_sigma0(W[t-15]) + W[t-16];
+
+ A = context->Intermediate_Hash[0];
+ B = context->Intermediate_Hash[1];
+ C = context->Intermediate_Hash[2];
+ D = context->Intermediate_Hash[3];
+ E = context->Intermediate_Hash[4];
+ F = context->Intermediate_Hash[5];
+ G = context->Intermediate_Hash[6];
+ H = context->Intermediate_Hash[7];
+
+ for (t = 0; t < 64; t++) {
+ temp1 = H + SHA256_SIGMA1(E) + SHA_Ch(E,F,G) + K[t] + W[t];
+ temp2 = SHA256_SIGMA0(A) + SHA_Maj(A,B,C);
+ H = G;
+ G = F;
+ F = E;
+ E = D + temp1;
+ D = C;
+ C = B;
+ B = A;
+ A = temp1 + temp2;
+ }
+
+ context->Intermediate_Hash[0] += A;
+ context->Intermediate_Hash[1] += B;
+ context->Intermediate_Hash[2] += C;
+ context->Intermediate_Hash[3] += D;
+ context->Intermediate_Hash[4] += E;
+ context->Intermediate_Hash[5] += F;
+ context->Intermediate_Hash[6] += G;
+ context->Intermediate_Hash[7] += H;
+
+ context->Message_Block_Index = 0;
+}
+
+/*
+ * SHA224_256Finalize
+ *
+ * Description:
+ * This helper function finishes off the digest calculations.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The SHA context to update.
+ * Pad_Byte: [in]
+ * The last byte to add to the message block before the 0-padding
+ * and length. This will contain the last bits of the message
+ * followed by another single bit. If the message was an
+ * exact multiple of 8-bits long, Pad_Byte will be 0x80.
+ *
+ * Returns:
+ * sha Error Code.
+ */
+static void SHA224_256Finalize(SHA256Context *context,
+ uint8_t Pad_Byte)
+{
+ int i;
+ SHA224_256PadMessage(context, Pad_Byte);
+ /* message may be sensitive, so clear it out */
+ for (i = 0; i < SHA256_Message_Block_Size; ++i)
+ context->Message_Block[i] = 0;
+ context->Length_High = 0; /* and clear length */
+ context->Length_Low = 0;
+ context->Computed = 1;
+}
+
+/*
+ * SHA224_256PadMessage
+ *
+ * Description:
+ * According to the standard, the message must be padded to the next
+ * even multiple of 512 bits. The first padding bit must be a '1'.
+ * The last 64 bits represent the length of the original message.
+ * All bits in between should be 0. This helper function will pad
+ * the message according to those rules by filling the
+ * Message_Block array accordingly. When it returns, it can be
+ * assumed that the message digest has been computed.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to pad.
+ * Pad_Byte: [in]
+ * The last byte to add to the message block before the 0-padding
+ * and length. This will contain the last bits of the message
+ * followed by another single bit. If the message was an
+ * exact multiple of 8-bits long, Pad_Byte will be 0x80.
+ *
+ * Returns:
+ * Nothing.
+ */
+static void SHA224_256PadMessage(SHA256Context *context,
+ uint8_t Pad_Byte)
+{
+ /*
+ * Check to see if the current message block is too small to hold
+ * the initial padding bits and length. If so, we will pad the
+ * block, process it, and then continue padding into a second
+ * block.
+ */
+ if (context->Message_Block_Index >= (SHA256_Message_Block_Size-8)) {
+ context->Message_Block[context->Message_Block_Index++] = Pad_Byte;
+ while (context->Message_Block_Index < SHA256_Message_Block_Size)
+ context->Message_Block[context->Message_Block_Index++] = 0;
+ SHA224_256ProcessMessageBlock(context);
+ } else
+ context->Message_Block[context->Message_Block_Index++] = Pad_Byte;
+
+ while (context->Message_Block_Index < (SHA256_Message_Block_Size-8))
+ context->Message_Block[context->Message_Block_Index++] = 0;
+
+ /*
+ * Store the message length as the last 8 octets
+ */
+ context->Message_Block[56] = (uint8_t)(context->Length_High >> 24);
+ context->Message_Block[57] = (uint8_t)(context->Length_High >> 16);
+ context->Message_Block[58] = (uint8_t)(context->Length_High >> 8);
+ context->Message_Block[59] = (uint8_t)(context->Length_High);
+ context->Message_Block[60] = (uint8_t)(context->Length_Low >> 24);
+ context->Message_Block[61] = (uint8_t)(context->Length_Low >> 16);
+ context->Message_Block[62] = (uint8_t)(context->Length_Low >> 8);
+ context->Message_Block[63] = (uint8_t)(context->Length_Low);
+
+ SHA224_256ProcessMessageBlock(context);
+}
+
+/*
+ * SHA224_256ResultN
+ *
+ * Description:
+ * This helper function will return the 224-bit or 256-bit message
+ * digest into the Message_Digest array provided by the caller.
+ * NOTE:
+ * The first octet of hash is stored in the element with index 0,
+ * the last octet of hash in the element with index 27/31.
+ *
+ * Parameters:
+ * context: [in/out]
+ * The context to use to calculate the SHA hash.
+ * Message_Digest[ ]: [out]
+ * Where the digest is returned.
+ * HashSize: [in]
+ * The size of the hash, either 28 or 32.
+ *
+ * Returns:
+ * sha Error Code.
+ */
+static int SHA224_256ResultN(SHA256Context *context,
+ uint8_t Message_Digest[ ], int HashSize)
+{
+ int i;
+
+ if (!context) return shaNull;
+ if (!Message_Digest) return shaNull;
+ if (context->Corrupted) return context->Corrupted;
+
+ if (!context->Computed)
+ SHA224_256Finalize(context, 0x80);
+
+ for (i = 0; i < HashSize; ++i)
+ Message_Digest[i] = (uint8_t)
+ (context->Intermediate_Hash[i>>2] >> 8 * ( 3 - ( i & 0x03 ) ));
+
+ return shaSuccess;
+}
+
diff --git a/src/util/hash/sha.h b/src/util/hash/sha.h
new file mode 100644
index 0000000..4f59623
--- /dev/null
+++ b/src/util/hash/sha.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_hash_sha_h__
+#define INCLUDE_hash_sha_h__
+
+#include "git2_util.h"
+
+typedef struct git_hash_sha1_ctx git_hash_sha1_ctx;
+typedef struct git_hash_sha256_ctx git_hash_sha256_ctx;
+
+#if defined(GIT_SHA1_COMMON_CRYPTO) || defined(GIT_SHA256_COMMON_CRYPTO)
+# include "common_crypto.h"
+#endif
+
+#if defined(GIT_SHA1_OPENSSL) || defined(GIT_SHA256_OPENSSL)
+# include "openssl.h"
+#endif
+
+#if defined(GIT_SHA1_WIN32) || defined(GIT_SHA256_WIN32)
+# include "win32.h"
+#endif
+
+#if defined(GIT_SHA1_MBEDTLS) || defined(GIT_SHA256_MBEDTLS)
+# include "mbedtls.h"
+#endif
+
+#if defined(GIT_SHA1_COLLISIONDETECT)
+# include "collisiondetect.h"
+#endif
+
+#if defined(GIT_SHA256_BUILTIN)
+# include "builtin.h"
+#endif
+
+/*
+ * SHA1
+ */
+
+#define GIT_HASH_SHA1_SIZE 20
+
+int git_hash_sha1_global_init(void);
+
+int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx);
+void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx);
+
+int git_hash_sha1_init(git_hash_sha1_ctx *c);
+int git_hash_sha1_update(git_hash_sha1_ctx *c, const void *data, size_t len);
+int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *c);
+
+/*
+ * SHA256
+ */
+
+#define GIT_HASH_SHA256_SIZE 32
+
+int git_hash_sha256_global_init(void);
+
+int git_hash_sha256_ctx_init(git_hash_sha256_ctx *ctx);
+void git_hash_sha256_ctx_cleanup(git_hash_sha256_ctx *ctx);
+
+int git_hash_sha256_init(git_hash_sha256_ctx *c);
+int git_hash_sha256_update(git_hash_sha256_ctx *c, const void *data, size_t len);
+int git_hash_sha256_final(unsigned char *out, git_hash_sha256_ctx *c);
+
+#endif
diff --git a/src/util/hash/sha1dc/sha1.c b/src/util/hash/sha1dc/sha1.c
new file mode 100644
index 0000000..9298227
--- /dev/null
+++ b/src/util/hash/sha1dc/sha1.c
@@ -0,0 +1,1909 @@
+/***
+* Copyright 2017 Marc Stevens <marc@marc-stevens.nl>, Dan Shumow (danshu@microsoft.com)
+* Distributed under the MIT Software License.
+* See accompanying file LICENSE.txt or copy at
+* https://opensource.org/licenses/MIT
+***/
+
+#ifndef SHA1DC_NO_STANDARD_INCLUDES
+#include <string.h>
+#include <memory.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h> /* make sure macros like _BIG_ENDIAN visible */
+#endif
+
+#ifdef SHA1DC_CUSTOM_INCLUDE_SHA1_C
+#include SHA1DC_CUSTOM_INCLUDE_SHA1_C
+#endif
+
+#ifndef SHA1DC_INIT_SAFE_HASH_DEFAULT
+#define SHA1DC_INIT_SAFE_HASH_DEFAULT 1
+#endif
+
+#include "sha1.h"
+#include "ubc_check.h"
+
+#if (defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || \
+ defined(i386) || defined(__i386) || defined(__i386__) || defined(__i486__) || \
+ defined(__i586__) || defined(__i686__) || defined(_M_IX86) || defined(__X86__) || \
+ defined(_X86_) || defined(__THW_INTEL__) || defined(__I86__) || defined(__INTEL__) || \
+ defined(__386) || defined(_M_X64) || defined(_M_AMD64))
+#define SHA1DC_ON_INTEL_LIKE_PROCESSOR
+#endif
+
+/*
+ Because Little-Endian architectures are most common,
+ we only set SHA1DC_BIGENDIAN if one of these conditions is met.
+ Note that all MSFT platforms are little endian,
+ so none of these will be defined under the MSC compiler.
+ If you are compiling on a big endian platform and your compiler does not define one of these,
+ you will have to add whatever macros your tool chain defines to indicate Big-Endianness.
+ */
+
+#if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__)
+/*
+ * Should detect Big Endian under GCC since at least 4.6.0 (gcc svn
+ * rev #165881). See
+ * https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html
+ *
+ * This also works under clang since 3.2, it copied the GCC-ism. See
+ * clang.git's 3b198a97d2 ("Preprocessor: add __BYTE_ORDER__
+ * predefined macro", 2012-07-27)
+ */
+#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+#define SHA1DC_BIGENDIAN
+#endif
+
+/* Not under GCC-alike */
+#elif defined(__BYTE_ORDER) && defined(__BIG_ENDIAN)
+/*
+ * Should detect Big Endian under glibc.git since 14245eb70e ("entered
+ * into RCS", 1992-11-25). Defined in <endian.h> which will have been
+ * brought in by standard headers. See glibc.git and
+ * https://sourceforge.net/p/predef/wiki/Endianness/
+ */
+#if __BYTE_ORDER == __BIG_ENDIAN
+#define SHA1DC_BIGENDIAN
+#endif
+
+/* Not under GCC-alike or glibc */
+#elif defined(_BYTE_ORDER) && defined(_BIG_ENDIAN) && defined(_LITTLE_ENDIAN)
+/*
+ * *BSD and newlib (embedded linux, cygwin, etc).
+ * the defined(_BIG_ENDIAN) && defined(_LITTLE_ENDIAN) part prevents
+ * this condition from matching with Solaris/sparc.
+ * (Solaris defines only one endian macro)
+ */
+#if _BYTE_ORDER == _BIG_ENDIAN
+#define SHA1DC_BIGENDIAN
+#endif
+
+/* Not under GCC-alike or glibc or *BSD or newlib */
+#elif (defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || \
+ defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || \
+ defined(__sparc))
+/*
+ * Should define Big Endian for a whitelist of known processors. See
+ * https://sourceforge.net/p/predef/wiki/Endianness/ and
+ * http://www.oracle.com/technetwork/server-storage/solaris/portingtosolaris-138514.html
+ */
+#define SHA1DC_BIGENDIAN
+
+/* Not under GCC-alike or glibc or *BSD or newlib or <processor whitelist> */
+#elif (defined(_AIX) || defined(__hpux))
+
+/*
+ * Defines Big Endian on a whitelist of OSs that are known to be Big
+ * Endian-only. See
+ * https://public-inbox.org/git/93056823-2740-d072-1ebd-46b440b33d7e@felt.demon.nl/
+ */
+#define SHA1DC_BIGENDIAN
+
+/* Not under GCC-alike or glibc or *BSD or newlib or <processor whitelist> or <os whitelist> */
+#elif defined(SHA1DC_ON_INTEL_LIKE_PROCESSOR)
+/*
+ * As a last resort before we do anything else we're not 100% sure
+ * about below, we blacklist specific processors here. We could add
+ * more, see e.g. https://wiki.debian.org/ArchitectureSpecificsMemo
+ */
+#else /* Not under GCC-alike or glibc or *BSD or newlib or <processor whitelist> or <os whitelist> or <processor blacklist> */
+
+/* We do nothing more here for now */
+/*#error "Uncomment this to see if you fall through all the detection"*/
+
+#endif /* Big Endian detection */
+
+#if (defined(SHA1DC_FORCE_LITTLEENDIAN) && defined(SHA1DC_BIGENDIAN))
+#undef SHA1DC_BIGENDIAN
+#endif
+#if (defined(SHA1DC_FORCE_BIGENDIAN) && !defined(SHA1DC_BIGENDIAN))
+#define SHA1DC_BIGENDIAN
+#endif
+/*ENDIANNESS SELECTION*/
+
+#ifndef SHA1DC_FORCE_ALIGNED_ACCESS
+#if defined(SHA1DC_FORCE_UNALIGNED_ACCESS) || defined(SHA1DC_ON_INTEL_LIKE_PROCESSOR)
+#define SHA1DC_ALLOW_UNALIGNED_ACCESS
+#endif /*UNALIGNED ACCESS DETECTION*/
+#endif /*FORCE ALIGNED ACCESS*/
+
+#define rotate_right(x,n) (((x)>>(n))|((x)<<(32-(n))))
+#define rotate_left(x,n) (((x)<<(n))|((x)>>(32-(n))))
+
+#define sha1_bswap32(x) \
+ {x = ((x << 8) & 0xFF00FF00) | ((x >> 8) & 0xFF00FF); x = (x << 16) | (x >> 16);}
+
+#define sha1_mix(W, t) (rotate_left(W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16], 1))
+
+#ifdef SHA1DC_BIGENDIAN
+ #define sha1_load(m, t, temp) { temp = m[t]; }
+#else
+ #define sha1_load(m, t, temp) { temp = m[t]; sha1_bswap32(temp); }
+#endif
+
+#define sha1_store(W, t, x) *(volatile uint32_t *)&W[t] = x
+
+#define sha1_f1(b,c,d) ((d)^((b)&((c)^(d))))
+#define sha1_f2(b,c,d) ((b)^(c)^(d))
+#define sha1_f3(b,c,d) (((b)&(c))+((d)&((b)^(c))))
+#define sha1_f4(b,c,d) ((b)^(c)^(d))
+
+#define HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, m, t) \
+ { e += rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999 + m[t]; b = rotate_left(b, 30); }
+#define HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, m, t) \
+ { e += rotate_left(a, 5) + sha1_f2(b,c,d) + 0x6ED9EBA1 + m[t]; b = rotate_left(b, 30); }
+#define HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, m, t) \
+ { e += rotate_left(a, 5) + sha1_f3(b,c,d) + 0x8F1BBCDC + m[t]; b = rotate_left(b, 30); }
+#define HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, m, t) \
+ { e += rotate_left(a, 5) + sha1_f4(b,c,d) + 0xCA62C1D6 + m[t]; b = rotate_left(b, 30); }
+
+#define HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, m, t) \
+ { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999 + m[t]; }
+#define HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, m, t) \
+ { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f2(b,c,d) + 0x6ED9EBA1 + m[t]; }
+#define HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, m, t) \
+ { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f3(b,c,d) + 0x8F1BBCDC + m[t]; }
+#define HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, m, t) \
+ { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f4(b,c,d) + 0xCA62C1D6 + m[t]; }
+
+#define SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, t, temp) \
+ {sha1_load(m, t, temp); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999; b = rotate_left(b, 30);}
+
+#define SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(a, b, c, d, e, W, t, temp) \
+ {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999; b = rotate_left(b, 30); }
+
+#define SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, t, temp) \
+ {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f2(b,c,d) + 0x6ED9EBA1; b = rotate_left(b, 30); }
+
+#define SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, t, temp) \
+ {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f3(b,c,d) + 0x8F1BBCDC; b = rotate_left(b, 30); }
+
+#define SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, t, temp) \
+ {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f4(b,c,d) + 0xCA62C1D6; b = rotate_left(b, 30); }
+
+
+#define SHA1_STORE_STATE(i) states[i][0] = a; states[i][1] = b; states[i][2] = c; states[i][3] = d; states[i][4] = e;
+
+#ifdef BUILDNOCOLLDETECTSHA1COMPRESSION
+void sha1_compression(uint32_t ihv[5], const uint32_t m[16])
+{
+ uint32_t W[80];
+ uint32_t a,b,c,d,e;
+ unsigned i;
+
+ memcpy(W, m, 16 * 4);
+ for (i = 16; i < 80; ++i)
+ W[i] = sha1_mix(W, i);
+
+ a = ihv[0]; b = ihv[1]; c = ihv[2]; d = ihv[3]; e = ihv[4];
+
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 0);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 1);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 2);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 3);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 4);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 5);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 6);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 7);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 8);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 9);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 10);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 11);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 12);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 13);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 14);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 15);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 16);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 17);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 18);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 19);
+
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 20);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 21);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 22);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 23);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 24);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 25);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 26);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 27);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 28);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 29);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 30);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 31);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 32);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 33);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 34);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 35);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 36);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 37);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 38);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 39);
+
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 40);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 41);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 42);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 43);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 44);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 45);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 46);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 47);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 48);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 49);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 50);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 51);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 52);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 53);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 54);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 55);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 56);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 57);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 58);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 59);
+
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 60);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 61);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 62);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 63);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 64);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 65);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 66);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 67);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 68);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 69);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 70);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 71);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 72);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 73);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 74);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 75);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 76);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 77);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 78);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 79);
+
+ ihv[0] += a; ihv[1] += b; ihv[2] += c; ihv[3] += d; ihv[4] += e;
+}
+#endif /*BUILDNOCOLLDETECTSHA1COMPRESSION*/
+
+
+static void sha1_compression_W(uint32_t ihv[5], const uint32_t W[80])
+{
+ uint32_t a = ihv[0], b = ihv[1], c = ihv[2], d = ihv[3], e = ihv[4];
+
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 0);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 1);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 2);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 3);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 4);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 5);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 6);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 7);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 8);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 9);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 10);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 11);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 12);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 13);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 14);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 15);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 16);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 17);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 18);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 19);
+
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 20);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 21);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 22);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 23);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 24);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 25);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 26);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 27);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 28);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 29);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 30);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 31);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 32);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 33);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 34);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 35);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 36);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 37);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 38);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 39);
+
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 40);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 41);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 42);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 43);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 44);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 45);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 46);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 47);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 48);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 49);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 50);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 51);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 52);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 53);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 54);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 55);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 56);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 57);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 58);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 59);
+
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 60);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 61);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 62);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 63);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 64);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 65);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 66);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 67);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 68);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 69);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 70);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 71);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 72);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 73);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 74);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 75);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 76);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 77);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 78);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 79);
+
+ ihv[0] += a; ihv[1] += b; ihv[2] += c; ihv[3] += d; ihv[4] += e;
+}
+
+
+
+void sha1_compression_states(uint32_t ihv[5], const uint32_t m[16], uint32_t W[80], uint32_t states[80][5])
+{
+ uint32_t a = ihv[0], b = ihv[1], c = ihv[2], d = ihv[3], e = ihv[4];
+ uint32_t temp;
+
+#ifdef DOSTORESTATE00
+ SHA1_STORE_STATE(0)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 0, temp);
+
+#ifdef DOSTORESTATE01
+ SHA1_STORE_STATE(1)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 1, temp);
+
+#ifdef DOSTORESTATE02
+ SHA1_STORE_STATE(2)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 2, temp);
+
+#ifdef DOSTORESTATE03
+ SHA1_STORE_STATE(3)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 3, temp);
+
+#ifdef DOSTORESTATE04
+ SHA1_STORE_STATE(4)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 4, temp);
+
+#ifdef DOSTORESTATE05
+ SHA1_STORE_STATE(5)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 5, temp);
+
+#ifdef DOSTORESTATE06
+ SHA1_STORE_STATE(6)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 6, temp);
+
+#ifdef DOSTORESTATE07
+ SHA1_STORE_STATE(7)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 7, temp);
+
+#ifdef DOSTORESTATE08
+ SHA1_STORE_STATE(8)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 8, temp);
+
+#ifdef DOSTORESTATE09
+ SHA1_STORE_STATE(9)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 9, temp);
+
+#ifdef DOSTORESTATE10
+ SHA1_STORE_STATE(10)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 10, temp);
+
+#ifdef DOSTORESTATE11
+ SHA1_STORE_STATE(11)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 11, temp);
+
+#ifdef DOSTORESTATE12
+ SHA1_STORE_STATE(12)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 12, temp);
+
+#ifdef DOSTORESTATE13
+ SHA1_STORE_STATE(13)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 13, temp);
+
+#ifdef DOSTORESTATE14
+ SHA1_STORE_STATE(14)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 14, temp);
+
+#ifdef DOSTORESTATE15
+ SHA1_STORE_STATE(15)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 15, temp);
+
+#ifdef DOSTORESTATE16
+ SHA1_STORE_STATE(16)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(e, a, b, c, d, W, 16, temp);
+
+#ifdef DOSTORESTATE17
+ SHA1_STORE_STATE(17)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(d, e, a, b, c, W, 17, temp);
+
+#ifdef DOSTORESTATE18
+ SHA1_STORE_STATE(18)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(c, d, e, a, b, W, 18, temp);
+
+#ifdef DOSTORESTATE19
+ SHA1_STORE_STATE(19)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(b, c, d, e, a, W, 19, temp);
+
+
+
+#ifdef DOSTORESTATE20
+ SHA1_STORE_STATE(20)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 20, temp);
+
+#ifdef DOSTORESTATE21
+ SHA1_STORE_STATE(21)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 21, temp);
+
+#ifdef DOSTORESTATE22
+ SHA1_STORE_STATE(22)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 22, temp);
+
+#ifdef DOSTORESTATE23
+ SHA1_STORE_STATE(23)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 23, temp);
+
+#ifdef DOSTORESTATE24
+ SHA1_STORE_STATE(24)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 24, temp);
+
+#ifdef DOSTORESTATE25
+ SHA1_STORE_STATE(25)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 25, temp);
+
+#ifdef DOSTORESTATE26
+ SHA1_STORE_STATE(26)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 26, temp);
+
+#ifdef DOSTORESTATE27
+ SHA1_STORE_STATE(27)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 27, temp);
+
+#ifdef DOSTORESTATE28
+ SHA1_STORE_STATE(28)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 28, temp);
+
+#ifdef DOSTORESTATE29
+ SHA1_STORE_STATE(29)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 29, temp);
+
+#ifdef DOSTORESTATE30
+ SHA1_STORE_STATE(30)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 30, temp);
+
+#ifdef DOSTORESTATE31
+ SHA1_STORE_STATE(31)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 31, temp);
+
+#ifdef DOSTORESTATE32
+ SHA1_STORE_STATE(32)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 32, temp);
+
+#ifdef DOSTORESTATE33
+ SHA1_STORE_STATE(33)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 33, temp);
+
+#ifdef DOSTORESTATE34
+ SHA1_STORE_STATE(34)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 34, temp);
+
+#ifdef DOSTORESTATE35
+ SHA1_STORE_STATE(35)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 35, temp);
+
+#ifdef DOSTORESTATE36
+ SHA1_STORE_STATE(36)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 36, temp);
+
+#ifdef DOSTORESTATE37
+ SHA1_STORE_STATE(37)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 37, temp);
+
+#ifdef DOSTORESTATE38
+ SHA1_STORE_STATE(38)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 38, temp);
+
+#ifdef DOSTORESTATE39
+ SHA1_STORE_STATE(39)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 39, temp);
+
+
+
+#ifdef DOSTORESTATE40
+ SHA1_STORE_STATE(40)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 40, temp);
+
+#ifdef DOSTORESTATE41
+ SHA1_STORE_STATE(41)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 41, temp);
+
+#ifdef DOSTORESTATE42
+ SHA1_STORE_STATE(42)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 42, temp);
+
+#ifdef DOSTORESTATE43
+ SHA1_STORE_STATE(43)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 43, temp);
+
+#ifdef DOSTORESTATE44
+ SHA1_STORE_STATE(44)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 44, temp);
+
+#ifdef DOSTORESTATE45
+ SHA1_STORE_STATE(45)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 45, temp);
+
+#ifdef DOSTORESTATE46
+ SHA1_STORE_STATE(46)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 46, temp);
+
+#ifdef DOSTORESTATE47
+ SHA1_STORE_STATE(47)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 47, temp);
+
+#ifdef DOSTORESTATE48
+ SHA1_STORE_STATE(48)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 48, temp);
+
+#ifdef DOSTORESTATE49
+ SHA1_STORE_STATE(49)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 49, temp);
+
+#ifdef DOSTORESTATE50
+ SHA1_STORE_STATE(50)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 50, temp);
+
+#ifdef DOSTORESTATE51
+ SHA1_STORE_STATE(51)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 51, temp);
+
+#ifdef DOSTORESTATE52
+ SHA1_STORE_STATE(52)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 52, temp);
+
+#ifdef DOSTORESTATE53
+ SHA1_STORE_STATE(53)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 53, temp);
+
+#ifdef DOSTORESTATE54
+ SHA1_STORE_STATE(54)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 54, temp);
+
+#ifdef DOSTORESTATE55
+ SHA1_STORE_STATE(55)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 55, temp);
+
+#ifdef DOSTORESTATE56
+ SHA1_STORE_STATE(56)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 56, temp);
+
+#ifdef DOSTORESTATE57
+ SHA1_STORE_STATE(57)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 57, temp);
+
+#ifdef DOSTORESTATE58
+ SHA1_STORE_STATE(58)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 58, temp);
+
+#ifdef DOSTORESTATE59
+ SHA1_STORE_STATE(59)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 59, temp);
+
+
+
+
+#ifdef DOSTORESTATE60
+ SHA1_STORE_STATE(60)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 60, temp);
+
+#ifdef DOSTORESTATE61
+ SHA1_STORE_STATE(61)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 61, temp);
+
+#ifdef DOSTORESTATE62
+ SHA1_STORE_STATE(62)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 62, temp);
+
+#ifdef DOSTORESTATE63
+ SHA1_STORE_STATE(63)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 63, temp);
+
+#ifdef DOSTORESTATE64
+ SHA1_STORE_STATE(64)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 64, temp);
+
+#ifdef DOSTORESTATE65
+ SHA1_STORE_STATE(65)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 65, temp);
+
+#ifdef DOSTORESTATE66
+ SHA1_STORE_STATE(66)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 66, temp);
+
+#ifdef DOSTORESTATE67
+ SHA1_STORE_STATE(67)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 67, temp);
+
+#ifdef DOSTORESTATE68
+ SHA1_STORE_STATE(68)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 68, temp);
+
+#ifdef DOSTORESTATE69
+ SHA1_STORE_STATE(69)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 69, temp);
+
+#ifdef DOSTORESTATE70
+ SHA1_STORE_STATE(70)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 70, temp);
+
+#ifdef DOSTORESTATE71
+ SHA1_STORE_STATE(71)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 71, temp);
+
+#ifdef DOSTORESTATE72
+ SHA1_STORE_STATE(72)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 72, temp);
+
+#ifdef DOSTORESTATE73
+ SHA1_STORE_STATE(73)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 73, temp);
+
+#ifdef DOSTORESTATE74
+ SHA1_STORE_STATE(74)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 74, temp);
+
+#ifdef DOSTORESTATE75
+ SHA1_STORE_STATE(75)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 75, temp);
+
+#ifdef DOSTORESTATE76
+ SHA1_STORE_STATE(76)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 76, temp);
+
+#ifdef DOSTORESTATE77
+ SHA1_STORE_STATE(77)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 77, temp);
+
+#ifdef DOSTORESTATE78
+ SHA1_STORE_STATE(78)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 78, temp);
+
+#ifdef DOSTORESTATE79
+ SHA1_STORE_STATE(79)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 79, temp);
+
+
+
+ ihv[0] += a; ihv[1] += b; ihv[2] += c; ihv[3] += d; ihv[4] += e;
+}
+
+
+
+
+#define SHA1_RECOMPRESS(t) \
+static void sha1recompress_fast_ ## t (uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]) \
+{ \
+ uint32_t a = state[0], b = state[1], c = state[2], d = state[3], e = state[4]; \
+ if (t > 79) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 79); \
+ if (t > 78) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 78); \
+ if (t > 77) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 77); \
+ if (t > 76) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 76); \
+ if (t > 75) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 75); \
+ if (t > 74) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 74); \
+ if (t > 73) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 73); \
+ if (t > 72) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 72); \
+ if (t > 71) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 71); \
+ if (t > 70) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 70); \
+ if (t > 69) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 69); \
+ if (t > 68) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 68); \
+ if (t > 67) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 67); \
+ if (t > 66) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 66); \
+ if (t > 65) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 65); \
+ if (t > 64) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 64); \
+ if (t > 63) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 63); \
+ if (t > 62) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 62); \
+ if (t > 61) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 61); \
+ if (t > 60) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 60); \
+ if (t > 59) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 59); \
+ if (t > 58) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 58); \
+ if (t > 57) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 57); \
+ if (t > 56) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 56); \
+ if (t > 55) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 55); \
+ if (t > 54) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 54); \
+ if (t > 53) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 53); \
+ if (t > 52) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 52); \
+ if (t > 51) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 51); \
+ if (t > 50) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 50); \
+ if (t > 49) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 49); \
+ if (t > 48) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 48); \
+ if (t > 47) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 47); \
+ if (t > 46) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 46); \
+ if (t > 45) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 45); \
+ if (t > 44) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 44); \
+ if (t > 43) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 43); \
+ if (t > 42) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 42); \
+ if (t > 41) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 41); \
+ if (t > 40) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 40); \
+ if (t > 39) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 39); \
+ if (t > 38) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 38); \
+ if (t > 37) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 37); \
+ if (t > 36) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 36); \
+ if (t > 35) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 35); \
+ if (t > 34) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 34); \
+ if (t > 33) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 33); \
+ if (t > 32) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 32); \
+ if (t > 31) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 31); \
+ if (t > 30) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 30); \
+ if (t > 29) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 29); \
+ if (t > 28) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 28); \
+ if (t > 27) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 27); \
+ if (t > 26) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 26); \
+ if (t > 25) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 25); \
+ if (t > 24) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 24); \
+ if (t > 23) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 23); \
+ if (t > 22) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 22); \
+ if (t > 21) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 21); \
+ if (t > 20) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 20); \
+ if (t > 19) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 19); \
+ if (t > 18) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 18); \
+ if (t > 17) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 17); \
+ if (t > 16) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 16); \
+ if (t > 15) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 15); \
+ if (t > 14) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 14); \
+ if (t > 13) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 13); \
+ if (t > 12) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 12); \
+ if (t > 11) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 11); \
+ if (t > 10) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 10); \
+ if (t > 9) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 9); \
+ if (t > 8) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 8); \
+ if (t > 7) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 7); \
+ if (t > 6) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 6); \
+ if (t > 5) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 5); \
+ if (t > 4) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 4); \
+ if (t > 3) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 3); \
+ if (t > 2) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 2); \
+ if (t > 1) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 1); \
+ if (t > 0) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 0); \
+ ihvin[0] = a; ihvin[1] = b; ihvin[2] = c; ihvin[3] = d; ihvin[4] = e; \
+ a = state[0]; b = state[1]; c = state[2]; d = state[3]; e = state[4]; \
+ if (t <= 0) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 0); \
+ if (t <= 1) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 1); \
+ if (t <= 2) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 2); \
+ if (t <= 3) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 3); \
+ if (t <= 4) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 4); \
+ if (t <= 5) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 5); \
+ if (t <= 6) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 6); \
+ if (t <= 7) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 7); \
+ if (t <= 8) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 8); \
+ if (t <= 9) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 9); \
+ if (t <= 10) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 10); \
+ if (t <= 11) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 11); \
+ if (t <= 12) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 12); \
+ if (t <= 13) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 13); \
+ if (t <= 14) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 14); \
+ if (t <= 15) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 15); \
+ if (t <= 16) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 16); \
+ if (t <= 17) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 17); \
+ if (t <= 18) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 18); \
+ if (t <= 19) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 19); \
+ if (t <= 20) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 20); \
+ if (t <= 21) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 21); \
+ if (t <= 22) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 22); \
+ if (t <= 23) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 23); \
+ if (t <= 24) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 24); \
+ if (t <= 25) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 25); \
+ if (t <= 26) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 26); \
+ if (t <= 27) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 27); \
+ if (t <= 28) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 28); \
+ if (t <= 29) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 29); \
+ if (t <= 30) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 30); \
+ if (t <= 31) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 31); \
+ if (t <= 32) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 32); \
+ if (t <= 33) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 33); \
+ if (t <= 34) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 34); \
+ if (t <= 35) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 35); \
+ if (t <= 36) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 36); \
+ if (t <= 37) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 37); \
+ if (t <= 38) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 38); \
+ if (t <= 39) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 39); \
+ if (t <= 40) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 40); \
+ if (t <= 41) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 41); \
+ if (t <= 42) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 42); \
+ if (t <= 43) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 43); \
+ if (t <= 44) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 44); \
+ if (t <= 45) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 45); \
+ if (t <= 46) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 46); \
+ if (t <= 47) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 47); \
+ if (t <= 48) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 48); \
+ if (t <= 49) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 49); \
+ if (t <= 50) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 50); \
+ if (t <= 51) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 51); \
+ if (t <= 52) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 52); \
+ if (t <= 53) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 53); \
+ if (t <= 54) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 54); \
+ if (t <= 55) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 55); \
+ if (t <= 56) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 56); \
+ if (t <= 57) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 57); \
+ if (t <= 58) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 58); \
+ if (t <= 59) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 59); \
+ if (t <= 60) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 60); \
+ if (t <= 61) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 61); \
+ if (t <= 62) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 62); \
+ if (t <= 63) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 63); \
+ if (t <= 64) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 64); \
+ if (t <= 65) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 65); \
+ if (t <= 66) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 66); \
+ if (t <= 67) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 67); \
+ if (t <= 68) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 68); \
+ if (t <= 69) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 69); \
+ if (t <= 70) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 70); \
+ if (t <= 71) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 71); \
+ if (t <= 72) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 72); \
+ if (t <= 73) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 73); \
+ if (t <= 74) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 74); \
+ if (t <= 75) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 75); \
+ if (t <= 76) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 76); \
+ if (t <= 77) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 77); \
+ if (t <= 78) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 78); \
+ if (t <= 79) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 79); \
+ ihvout[0] = ihvin[0] + a; ihvout[1] = ihvin[1] + b; ihvout[2] = ihvin[2] + c; ihvout[3] = ihvin[3] + d; ihvout[4] = ihvin[4] + e; \
+}
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable: 4127) /* Compiler complains about the checks in the above macro being constant. */
+#endif
+
+#ifdef DOSTORESTATE0
+SHA1_RECOMPRESS(0)
+#endif
+
+#ifdef DOSTORESTATE1
+SHA1_RECOMPRESS(1)
+#endif
+
+#ifdef DOSTORESTATE2
+SHA1_RECOMPRESS(2)
+#endif
+
+#ifdef DOSTORESTATE3
+SHA1_RECOMPRESS(3)
+#endif
+
+#ifdef DOSTORESTATE4
+SHA1_RECOMPRESS(4)
+#endif
+
+#ifdef DOSTORESTATE5
+SHA1_RECOMPRESS(5)
+#endif
+
+#ifdef DOSTORESTATE6
+SHA1_RECOMPRESS(6)
+#endif
+
+#ifdef DOSTORESTATE7
+SHA1_RECOMPRESS(7)
+#endif
+
+#ifdef DOSTORESTATE8
+SHA1_RECOMPRESS(8)
+#endif
+
+#ifdef DOSTORESTATE9
+SHA1_RECOMPRESS(9)
+#endif
+
+#ifdef DOSTORESTATE10
+SHA1_RECOMPRESS(10)
+#endif
+
+#ifdef DOSTORESTATE11
+SHA1_RECOMPRESS(11)
+#endif
+
+#ifdef DOSTORESTATE12
+SHA1_RECOMPRESS(12)
+#endif
+
+#ifdef DOSTORESTATE13
+SHA1_RECOMPRESS(13)
+#endif
+
+#ifdef DOSTORESTATE14
+SHA1_RECOMPRESS(14)
+#endif
+
+#ifdef DOSTORESTATE15
+SHA1_RECOMPRESS(15)
+#endif
+
+#ifdef DOSTORESTATE16
+SHA1_RECOMPRESS(16)
+#endif
+
+#ifdef DOSTORESTATE17
+SHA1_RECOMPRESS(17)
+#endif
+
+#ifdef DOSTORESTATE18
+SHA1_RECOMPRESS(18)
+#endif
+
+#ifdef DOSTORESTATE19
+SHA1_RECOMPRESS(19)
+#endif
+
+#ifdef DOSTORESTATE20
+SHA1_RECOMPRESS(20)
+#endif
+
+#ifdef DOSTORESTATE21
+SHA1_RECOMPRESS(21)
+#endif
+
+#ifdef DOSTORESTATE22
+SHA1_RECOMPRESS(22)
+#endif
+
+#ifdef DOSTORESTATE23
+SHA1_RECOMPRESS(23)
+#endif
+
+#ifdef DOSTORESTATE24
+SHA1_RECOMPRESS(24)
+#endif
+
+#ifdef DOSTORESTATE25
+SHA1_RECOMPRESS(25)
+#endif
+
+#ifdef DOSTORESTATE26
+SHA1_RECOMPRESS(26)
+#endif
+
+#ifdef DOSTORESTATE27
+SHA1_RECOMPRESS(27)
+#endif
+
+#ifdef DOSTORESTATE28
+SHA1_RECOMPRESS(28)
+#endif
+
+#ifdef DOSTORESTATE29
+SHA1_RECOMPRESS(29)
+#endif
+
+#ifdef DOSTORESTATE30
+SHA1_RECOMPRESS(30)
+#endif
+
+#ifdef DOSTORESTATE31
+SHA1_RECOMPRESS(31)
+#endif
+
+#ifdef DOSTORESTATE32
+SHA1_RECOMPRESS(32)
+#endif
+
+#ifdef DOSTORESTATE33
+SHA1_RECOMPRESS(33)
+#endif
+
+#ifdef DOSTORESTATE34
+SHA1_RECOMPRESS(34)
+#endif
+
+#ifdef DOSTORESTATE35
+SHA1_RECOMPRESS(35)
+#endif
+
+#ifdef DOSTORESTATE36
+SHA1_RECOMPRESS(36)
+#endif
+
+#ifdef DOSTORESTATE37
+SHA1_RECOMPRESS(37)
+#endif
+
+#ifdef DOSTORESTATE38
+SHA1_RECOMPRESS(38)
+#endif
+
+#ifdef DOSTORESTATE39
+SHA1_RECOMPRESS(39)
+#endif
+
+#ifdef DOSTORESTATE40
+SHA1_RECOMPRESS(40)
+#endif
+
+#ifdef DOSTORESTATE41
+SHA1_RECOMPRESS(41)
+#endif
+
+#ifdef DOSTORESTATE42
+SHA1_RECOMPRESS(42)
+#endif
+
+#ifdef DOSTORESTATE43
+SHA1_RECOMPRESS(43)
+#endif
+
+#ifdef DOSTORESTATE44
+SHA1_RECOMPRESS(44)
+#endif
+
+#ifdef DOSTORESTATE45
+SHA1_RECOMPRESS(45)
+#endif
+
+#ifdef DOSTORESTATE46
+SHA1_RECOMPRESS(46)
+#endif
+
+#ifdef DOSTORESTATE47
+SHA1_RECOMPRESS(47)
+#endif
+
+#ifdef DOSTORESTATE48
+SHA1_RECOMPRESS(48)
+#endif
+
+#ifdef DOSTORESTATE49
+SHA1_RECOMPRESS(49)
+#endif
+
+#ifdef DOSTORESTATE50
+SHA1_RECOMPRESS(50)
+#endif
+
+#ifdef DOSTORESTATE51
+SHA1_RECOMPRESS(51)
+#endif
+
+#ifdef DOSTORESTATE52
+SHA1_RECOMPRESS(52)
+#endif
+
+#ifdef DOSTORESTATE53
+SHA1_RECOMPRESS(53)
+#endif
+
+#ifdef DOSTORESTATE54
+SHA1_RECOMPRESS(54)
+#endif
+
+#ifdef DOSTORESTATE55
+SHA1_RECOMPRESS(55)
+#endif
+
+#ifdef DOSTORESTATE56
+SHA1_RECOMPRESS(56)
+#endif
+
+#ifdef DOSTORESTATE57
+SHA1_RECOMPRESS(57)
+#endif
+
+#ifdef DOSTORESTATE58
+SHA1_RECOMPRESS(58)
+#endif
+
+#ifdef DOSTORESTATE59
+SHA1_RECOMPRESS(59)
+#endif
+
+#ifdef DOSTORESTATE60
+SHA1_RECOMPRESS(60)
+#endif
+
+#ifdef DOSTORESTATE61
+SHA1_RECOMPRESS(61)
+#endif
+
+#ifdef DOSTORESTATE62
+SHA1_RECOMPRESS(62)
+#endif
+
+#ifdef DOSTORESTATE63
+SHA1_RECOMPRESS(63)
+#endif
+
+#ifdef DOSTORESTATE64
+SHA1_RECOMPRESS(64)
+#endif
+
+#ifdef DOSTORESTATE65
+SHA1_RECOMPRESS(65)
+#endif
+
+#ifdef DOSTORESTATE66
+SHA1_RECOMPRESS(66)
+#endif
+
+#ifdef DOSTORESTATE67
+SHA1_RECOMPRESS(67)
+#endif
+
+#ifdef DOSTORESTATE68
+SHA1_RECOMPRESS(68)
+#endif
+
+#ifdef DOSTORESTATE69
+SHA1_RECOMPRESS(69)
+#endif
+
+#ifdef DOSTORESTATE70
+SHA1_RECOMPRESS(70)
+#endif
+
+#ifdef DOSTORESTATE71
+SHA1_RECOMPRESS(71)
+#endif
+
+#ifdef DOSTORESTATE72
+SHA1_RECOMPRESS(72)
+#endif
+
+#ifdef DOSTORESTATE73
+SHA1_RECOMPRESS(73)
+#endif
+
+#ifdef DOSTORESTATE74
+SHA1_RECOMPRESS(74)
+#endif
+
+#ifdef DOSTORESTATE75
+SHA1_RECOMPRESS(75)
+#endif
+
+#ifdef DOSTORESTATE76
+SHA1_RECOMPRESS(76)
+#endif
+
+#ifdef DOSTORESTATE77
+SHA1_RECOMPRESS(77)
+#endif
+
+#ifdef DOSTORESTATE78
+SHA1_RECOMPRESS(78)
+#endif
+
+#ifdef DOSTORESTATE79
+SHA1_RECOMPRESS(79)
+#endif
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+static void sha1_recompression_step(uint32_t step, uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5])
+{
+ switch (step)
+ {
+#ifdef DOSTORESTATE0
+ case 0:
+ sha1recompress_fast_0(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE1
+ case 1:
+ sha1recompress_fast_1(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE2
+ case 2:
+ sha1recompress_fast_2(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE3
+ case 3:
+ sha1recompress_fast_3(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE4
+ case 4:
+ sha1recompress_fast_4(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE5
+ case 5:
+ sha1recompress_fast_5(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE6
+ case 6:
+ sha1recompress_fast_6(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE7
+ case 7:
+ sha1recompress_fast_7(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE8
+ case 8:
+ sha1recompress_fast_8(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE9
+ case 9:
+ sha1recompress_fast_9(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE10
+ case 10:
+ sha1recompress_fast_10(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE11
+ case 11:
+ sha1recompress_fast_11(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE12
+ case 12:
+ sha1recompress_fast_12(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE13
+ case 13:
+ sha1recompress_fast_13(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE14
+ case 14:
+ sha1recompress_fast_14(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE15
+ case 15:
+ sha1recompress_fast_15(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE16
+ case 16:
+ sha1recompress_fast_16(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE17
+ case 17:
+ sha1recompress_fast_17(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE18
+ case 18:
+ sha1recompress_fast_18(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE19
+ case 19:
+ sha1recompress_fast_19(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE20
+ case 20:
+ sha1recompress_fast_20(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE21
+ case 21:
+ sha1recompress_fast_21(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE22
+ case 22:
+ sha1recompress_fast_22(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE23
+ case 23:
+ sha1recompress_fast_23(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE24
+ case 24:
+ sha1recompress_fast_24(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE25
+ case 25:
+ sha1recompress_fast_25(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE26
+ case 26:
+ sha1recompress_fast_26(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE27
+ case 27:
+ sha1recompress_fast_27(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE28
+ case 28:
+ sha1recompress_fast_28(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE29
+ case 29:
+ sha1recompress_fast_29(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE30
+ case 30:
+ sha1recompress_fast_30(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE31
+ case 31:
+ sha1recompress_fast_31(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE32
+ case 32:
+ sha1recompress_fast_32(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE33
+ case 33:
+ sha1recompress_fast_33(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE34
+ case 34:
+ sha1recompress_fast_34(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE35
+ case 35:
+ sha1recompress_fast_35(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE36
+ case 36:
+ sha1recompress_fast_36(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE37
+ case 37:
+ sha1recompress_fast_37(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE38
+ case 38:
+ sha1recompress_fast_38(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE39
+ case 39:
+ sha1recompress_fast_39(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE40
+ case 40:
+ sha1recompress_fast_40(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE41
+ case 41:
+ sha1recompress_fast_41(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE42
+ case 42:
+ sha1recompress_fast_42(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE43
+ case 43:
+ sha1recompress_fast_43(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE44
+ case 44:
+ sha1recompress_fast_44(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE45
+ case 45:
+ sha1recompress_fast_45(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE46
+ case 46:
+ sha1recompress_fast_46(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE47
+ case 47:
+ sha1recompress_fast_47(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE48
+ case 48:
+ sha1recompress_fast_48(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE49
+ case 49:
+ sha1recompress_fast_49(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE50
+ case 50:
+ sha1recompress_fast_50(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE51
+ case 51:
+ sha1recompress_fast_51(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE52
+ case 52:
+ sha1recompress_fast_52(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE53
+ case 53:
+ sha1recompress_fast_53(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE54
+ case 54:
+ sha1recompress_fast_54(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE55
+ case 55:
+ sha1recompress_fast_55(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE56
+ case 56:
+ sha1recompress_fast_56(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE57
+ case 57:
+ sha1recompress_fast_57(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE58
+ case 58:
+ sha1recompress_fast_58(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE59
+ case 59:
+ sha1recompress_fast_59(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE60
+ case 60:
+ sha1recompress_fast_60(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE61
+ case 61:
+ sha1recompress_fast_61(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE62
+ case 62:
+ sha1recompress_fast_62(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE63
+ case 63:
+ sha1recompress_fast_63(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE64
+ case 64:
+ sha1recompress_fast_64(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE65
+ case 65:
+ sha1recompress_fast_65(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE66
+ case 66:
+ sha1recompress_fast_66(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE67
+ case 67:
+ sha1recompress_fast_67(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE68
+ case 68:
+ sha1recompress_fast_68(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE69
+ case 69:
+ sha1recompress_fast_69(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE70
+ case 70:
+ sha1recompress_fast_70(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE71
+ case 71:
+ sha1recompress_fast_71(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE72
+ case 72:
+ sha1recompress_fast_72(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE73
+ case 73:
+ sha1recompress_fast_73(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE74
+ case 74:
+ sha1recompress_fast_74(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE75
+ case 75:
+ sha1recompress_fast_75(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE76
+ case 76:
+ sha1recompress_fast_76(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE77
+ case 77:
+ sha1recompress_fast_77(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE78
+ case 78:
+ sha1recompress_fast_78(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE79
+ case 79:
+ sha1recompress_fast_79(ihvin, ihvout, me2, state);
+ break;
+#endif
+ default:
+ abort();
+ }
+
+}
+
+
+
+static void sha1_process(SHA1_CTX *ctx, const uint32_t block[16])
+{
+ unsigned i, j;
+ uint32_t ubc_dv_mask[DVMASKSIZE] = { 0xFFFFFFFF };
+ uint32_t ihvtmp[5];
+
+ ctx->ihv1[0] = ctx->ihv[0];
+ ctx->ihv1[1] = ctx->ihv[1];
+ ctx->ihv1[2] = ctx->ihv[2];
+ ctx->ihv1[3] = ctx->ihv[3];
+ ctx->ihv1[4] = ctx->ihv[4];
+
+ sha1_compression_states(ctx->ihv, block, ctx->m1, ctx->states);
+
+ if (ctx->detect_coll)
+ {
+ if (ctx->ubc_check)
+ {
+ ubc_check(ctx->m1, ubc_dv_mask);
+ }
+
+ if (ubc_dv_mask[0] != 0)
+ {
+ for (i = 0; sha1_dvs[i].dvType != 0; ++i)
+ {
+ if (ubc_dv_mask[0] & ((uint32_t)(1) << sha1_dvs[i].maskb))
+ {
+ for (j = 0; j < 80; ++j)
+ ctx->m2[j] = ctx->m1[j] ^ sha1_dvs[i].dm[j];
+
+ sha1_recompression_step(sha1_dvs[i].testt, ctx->ihv2, ihvtmp, ctx->m2, ctx->states[sha1_dvs[i].testt]);
+
+ /* to verify SHA-1 collision detection code with collisions for reduced-step SHA-1 */
+ if ((0 == ((ihvtmp[0] ^ ctx->ihv[0]) | (ihvtmp[1] ^ ctx->ihv[1]) | (ihvtmp[2] ^ ctx->ihv[2]) | (ihvtmp[3] ^ ctx->ihv[3]) | (ihvtmp[4] ^ ctx->ihv[4])))
+ || (ctx->reduced_round_coll && 0==((ctx->ihv1[0] ^ ctx->ihv2[0]) | (ctx->ihv1[1] ^ ctx->ihv2[1]) | (ctx->ihv1[2] ^ ctx->ihv2[2]) | (ctx->ihv1[3] ^ ctx->ihv2[3]) | (ctx->ihv1[4] ^ ctx->ihv2[4]))))
+ {
+ ctx->found_collision = 1;
+
+ if (ctx->safe_hash)
+ {
+ sha1_compression_W(ctx->ihv, ctx->m1);
+ sha1_compression_W(ctx->ihv, ctx->m1);
+ }
+
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+void SHA1DCInit(SHA1_CTX *ctx)
+{
+ ctx->total = 0;
+ ctx->ihv[0] = 0x67452301;
+ ctx->ihv[1] = 0xEFCDAB89;
+ ctx->ihv[2] = 0x98BADCFE;
+ ctx->ihv[3] = 0x10325476;
+ ctx->ihv[4] = 0xC3D2E1F0;
+ ctx->found_collision = 0;
+ ctx->safe_hash = SHA1DC_INIT_SAFE_HASH_DEFAULT;
+ ctx->ubc_check = 1;
+ ctx->detect_coll = 1;
+ ctx->reduced_round_coll = 0;
+ ctx->callback = NULL;
+}
+
+void SHA1DCSetSafeHash(SHA1_CTX *ctx, int safehash)
+{
+ if (safehash)
+ ctx->safe_hash = 1;
+ else
+ ctx->safe_hash = 0;
+}
+
+
+void SHA1DCSetUseUBC(SHA1_CTX *ctx, int ubc_check)
+{
+ if (ubc_check)
+ ctx->ubc_check = 1;
+ else
+ ctx->ubc_check = 0;
+}
+
+void SHA1DCSetUseDetectColl(SHA1_CTX *ctx, int detect_coll)
+{
+ if (detect_coll)
+ ctx->detect_coll = 1;
+ else
+ ctx->detect_coll = 0;
+}
+
+void SHA1DCSetDetectReducedRoundCollision(SHA1_CTX *ctx, int reduced_round_coll)
+{
+ if (reduced_round_coll)
+ ctx->reduced_round_coll = 1;
+ else
+ ctx->reduced_round_coll = 0;
+}
+
+void SHA1DCSetCallback(SHA1_CTX *ctx, collision_block_callback callback)
+{
+ ctx->callback = callback;
+}
+
+void SHA1DCUpdate(SHA1_CTX *ctx, const char *buf, size_t len)
+{
+ unsigned left, fill;
+
+ if (len == 0)
+ return;
+
+ left = ctx->total & 63;
+ fill = 64 - left;
+
+ if (left && len >= fill)
+ {
+ ctx->total += fill;
+ memcpy(ctx->buffer + left, buf, fill);
+ sha1_process(ctx, (uint32_t*)(ctx->buffer));
+ buf += fill;
+ len -= fill;
+ left = 0;
+ }
+ while (len >= 64)
+ {
+ ctx->total += 64;
+
+#if defined(SHA1DC_ALLOW_UNALIGNED_ACCESS)
+ sha1_process(ctx, (uint32_t*)(buf));
+#else
+ memcpy(ctx->buffer, buf, 64);
+ sha1_process(ctx, (uint32_t*)(ctx->buffer));
+#endif /* defined(SHA1DC_ALLOW_UNALIGNED_ACCESS) */
+ buf += 64;
+ len -= 64;
+ }
+ if (len > 0)
+ {
+ ctx->total += len;
+ memcpy(ctx->buffer + left, buf, len);
+ }
+}
+
+static const unsigned char sha1_padding[64] =
+{
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+int SHA1DCFinal(unsigned char output[20], SHA1_CTX *ctx)
+{
+ uint32_t last = ctx->total & 63;
+ uint32_t padn = (last < 56) ? (56 - last) : (120 - last);
+ uint64_t total;
+ SHA1DCUpdate(ctx, (const char*)(sha1_padding), padn);
+
+ total = ctx->total - padn;
+ total <<= 3;
+ ctx->buffer[56] = (unsigned char)(total >> 56);
+ ctx->buffer[57] = (unsigned char)(total >> 48);
+ ctx->buffer[58] = (unsigned char)(total >> 40);
+ ctx->buffer[59] = (unsigned char)(total >> 32);
+ ctx->buffer[60] = (unsigned char)(total >> 24);
+ ctx->buffer[61] = (unsigned char)(total >> 16);
+ ctx->buffer[62] = (unsigned char)(total >> 8);
+ ctx->buffer[63] = (unsigned char)(total);
+ sha1_process(ctx, (uint32_t*)(ctx->buffer));
+ output[0] = (unsigned char)(ctx->ihv[0] >> 24);
+ output[1] = (unsigned char)(ctx->ihv[0] >> 16);
+ output[2] = (unsigned char)(ctx->ihv[0] >> 8);
+ output[3] = (unsigned char)(ctx->ihv[0]);
+ output[4] = (unsigned char)(ctx->ihv[1] >> 24);
+ output[5] = (unsigned char)(ctx->ihv[1] >> 16);
+ output[6] = (unsigned char)(ctx->ihv[1] >> 8);
+ output[7] = (unsigned char)(ctx->ihv[1]);
+ output[8] = (unsigned char)(ctx->ihv[2] >> 24);
+ output[9] = (unsigned char)(ctx->ihv[2] >> 16);
+ output[10] = (unsigned char)(ctx->ihv[2] >> 8);
+ output[11] = (unsigned char)(ctx->ihv[2]);
+ output[12] = (unsigned char)(ctx->ihv[3] >> 24);
+ output[13] = (unsigned char)(ctx->ihv[3] >> 16);
+ output[14] = (unsigned char)(ctx->ihv[3] >> 8);
+ output[15] = (unsigned char)(ctx->ihv[3]);
+ output[16] = (unsigned char)(ctx->ihv[4] >> 24);
+ output[17] = (unsigned char)(ctx->ihv[4] >> 16);
+ output[18] = (unsigned char)(ctx->ihv[4] >> 8);
+ output[19] = (unsigned char)(ctx->ihv[4]);
+ return ctx->found_collision;
+}
+
+#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C
+#include SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C
+#endif
diff --git a/src/util/hash/sha1dc/sha1.h b/src/util/hash/sha1dc/sha1.h
new file mode 100644
index 0000000..1e4e94b
--- /dev/null
+++ b/src/util/hash/sha1dc/sha1.h
@@ -0,0 +1,110 @@
+/***
+* Copyright 2017 Marc Stevens <marc@marc-stevens.nl>, Dan Shumow <danshu@microsoft.com>
+* Distributed under the MIT Software License.
+* See accompanying file LICENSE.txt or copy at
+* https://opensource.org/licenses/MIT
+***/
+
+#ifndef SHA1DC_SHA1_H
+#define SHA1DC_SHA1_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#ifndef SHA1DC_NO_STANDARD_INCLUDES
+#include <stdint.h>
+#endif
+
+/* sha-1 compression function that takes an already expanded message, and additionally store intermediate states */
+/* only stores states ii (the state between step ii-1 and step ii) when DOSTORESTATEii is defined in ubc_check.h */
+void sha1_compression_states(uint32_t[5], const uint32_t[16], uint32_t[80], uint32_t[80][5]);
+
+/*
+// Function type for sha1_recompression_step_T (uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]).
+// Where 0 <= T < 80
+// me2 is an expanded message (the expansion of an original message block XOR'ed with a disturbance vector's message block difference.)
+// state is the internal state (a,b,c,d,e) before step T of the SHA-1 compression function while processing the original message block.
+// The function will return:
+// ihvin: The reconstructed input chaining value.
+// ihvout: The reconstructed output chaining value.
+*/
+typedef void(*sha1_recompression_type)(uint32_t*, uint32_t*, const uint32_t*, const uint32_t*);
+
+/* A callback function type that can be set to be called when a collision block has been found: */
+/* void collision_block_callback(uint64_t byteoffset, const uint32_t ihvin1[5], const uint32_t ihvin2[5], const uint32_t m1[80], const uint32_t m2[80]) */
+typedef void(*collision_block_callback)(uint64_t, const uint32_t*, const uint32_t*, const uint32_t*, const uint32_t*);
+
+/* The SHA-1 context. */
+typedef struct {
+ uint64_t total;
+ uint32_t ihv[5];
+ unsigned char buffer[64];
+ int found_collision;
+ int safe_hash;
+ int detect_coll;
+ int ubc_check;
+ int reduced_round_coll;
+ collision_block_callback callback;
+
+ uint32_t ihv1[5];
+ uint32_t ihv2[5];
+ uint32_t m1[80];
+ uint32_t m2[80];
+ uint32_t states[80][5];
+} SHA1_CTX;
+
+/* Initialize SHA-1 context. */
+void SHA1DCInit(SHA1_CTX*);
+
+/*
+ Function to enable safe SHA-1 hashing:
+ Collision attacks are thwarted by hashing a detected near-collision block 3 times.
+ Think of it as extending SHA-1 from 80-steps to 240-steps for such blocks:
+ The best collision attacks against SHA-1 have complexity about 2^60,
+ thus for 240-steps an immediate lower-bound for the best cryptanalytic attacks would be 2^180.
+ An attacker would be better off using a generic birthday search of complexity 2^80.
+
+ Enabling safe SHA-1 hashing will result in the correct SHA-1 hash for messages where no collision attack was detected,
+ but it will result in a different SHA-1 hash for messages where a collision attack was detected.
+ This will automatically invalidate SHA-1 based digital signature forgeries.
+ Enabled by default.
+*/
+void SHA1DCSetSafeHash(SHA1_CTX*, int);
+
+/*
+ Function to disable or enable the use of Unavoidable Bitconditions (provides a significant speed up).
+ Enabled by default
+ */
+void SHA1DCSetUseUBC(SHA1_CTX*, int);
+
+/*
+ Function to disable or enable the use of Collision Detection.
+ Enabled by default.
+ */
+void SHA1DCSetUseDetectColl(SHA1_CTX*, int);
+
+/* function to disable or enable the detection of reduced-round SHA-1 collisions */
+/* disabled by default */
+void SHA1DCSetDetectReducedRoundCollision(SHA1_CTX*, int);
+
+/* function to set a callback function, pass NULL to disable */
+/* by default no callback set */
+void SHA1DCSetCallback(SHA1_CTX*, collision_block_callback);
+
+/* update SHA-1 context with buffer contents */
+void SHA1DCUpdate(SHA1_CTX*, const char*, size_t);
+
+/* obtain SHA-1 hash from SHA-1 context */
+/* returns: 0 = no collision detected, otherwise = collision found => warn user for active attack */
+int SHA1DCFinal(unsigned char[20], SHA1_CTX*);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H
+#include SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H
+#endif
+
+#endif
diff --git a/src/util/hash/sha1dc/ubc_check.c b/src/util/hash/sha1dc/ubc_check.c
new file mode 100644
index 0000000..b3beff2
--- /dev/null
+++ b/src/util/hash/sha1dc/ubc_check.c
@@ -0,0 +1,372 @@
+/***
+* Copyright 2017 Marc Stevens <marc@marc-stevens.nl>, Dan Shumow <danshu@microsoft.com>
+* Distributed under the MIT Software License.
+* See accompanying file LICENSE.txt or copy at
+* https://opensource.org/licenses/MIT
+***/
+
+/*
+// this file was generated by the 'parse_bitrel' program in the tools section
+// using the data files from directory 'tools/data/3565'
+//
+// sha1_dvs contains a list of SHA-1 Disturbance Vectors (DV) to check
+// dvType, dvK and dvB define the DV: I(K,B) or II(K,B) (see the paper)
+// dm[80] is the expanded message block XOR-difference defined by the DV
+// testt is the step to do the recompression from for collision detection
+// maski and maskb define the bit to check for each DV in the dvmask returned by ubc_check
+//
+// ubc_check takes as input an expanded message block and verifies the unavoidable bitconditions for all listed DVs
+// it returns a dvmask where each bit belonging to a DV is set if all unavoidable bitconditions for that DV have been met
+// thus one needs to do the recompression check for each DV that has its bit set
+//
+// ubc_check is programmatically generated and the unavoidable bitconditions have been hardcoded
+// a directly verifiable version named ubc_check_verify can be found in ubc_check_verify.c
+// ubc_check has been verified against ubc_check_verify using the 'ubc_check_test' program in the tools section
+*/
+
+#ifndef SHA1DC_NO_STANDARD_INCLUDES
+#include <stdint.h>
+#endif
+#ifdef SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C
+#include SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C
+#endif
+#include "ubc_check.h"
+
+static const uint32_t DV_I_43_0_bit = (uint32_t)(1) << 0;
+static const uint32_t DV_I_44_0_bit = (uint32_t)(1) << 1;
+static const uint32_t DV_I_45_0_bit = (uint32_t)(1) << 2;
+static const uint32_t DV_I_46_0_bit = (uint32_t)(1) << 3;
+static const uint32_t DV_I_46_2_bit = (uint32_t)(1) << 4;
+static const uint32_t DV_I_47_0_bit = (uint32_t)(1) << 5;
+static const uint32_t DV_I_47_2_bit = (uint32_t)(1) << 6;
+static const uint32_t DV_I_48_0_bit = (uint32_t)(1) << 7;
+static const uint32_t DV_I_48_2_bit = (uint32_t)(1) << 8;
+static const uint32_t DV_I_49_0_bit = (uint32_t)(1) << 9;
+static const uint32_t DV_I_49_2_bit = (uint32_t)(1) << 10;
+static const uint32_t DV_I_50_0_bit = (uint32_t)(1) << 11;
+static const uint32_t DV_I_50_2_bit = (uint32_t)(1) << 12;
+static const uint32_t DV_I_51_0_bit = (uint32_t)(1) << 13;
+static const uint32_t DV_I_51_2_bit = (uint32_t)(1) << 14;
+static const uint32_t DV_I_52_0_bit = (uint32_t)(1) << 15;
+static const uint32_t DV_II_45_0_bit = (uint32_t)(1) << 16;
+static const uint32_t DV_II_46_0_bit = (uint32_t)(1) << 17;
+static const uint32_t DV_II_46_2_bit = (uint32_t)(1) << 18;
+static const uint32_t DV_II_47_0_bit = (uint32_t)(1) << 19;
+static const uint32_t DV_II_48_0_bit = (uint32_t)(1) << 20;
+static const uint32_t DV_II_49_0_bit = (uint32_t)(1) << 21;
+static const uint32_t DV_II_49_2_bit = (uint32_t)(1) << 22;
+static const uint32_t DV_II_50_0_bit = (uint32_t)(1) << 23;
+static const uint32_t DV_II_50_2_bit = (uint32_t)(1) << 24;
+static const uint32_t DV_II_51_0_bit = (uint32_t)(1) << 25;
+static const uint32_t DV_II_51_2_bit = (uint32_t)(1) << 26;
+static const uint32_t DV_II_52_0_bit = (uint32_t)(1) << 27;
+static const uint32_t DV_II_53_0_bit = (uint32_t)(1) << 28;
+static const uint32_t DV_II_54_0_bit = (uint32_t)(1) << 29;
+static const uint32_t DV_II_55_0_bit = (uint32_t)(1) << 30;
+static const uint32_t DV_II_56_0_bit = (uint32_t)(1) << 31;
+
+dv_info_t sha1_dvs[] =
+{
+ {1,43,0,58,0,0, { 0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c,0x00000803,0x80000161,0x80000599 } }
+, {1,44,0,58,0,1, { 0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c,0x00000803,0x80000161 } }
+, {1,45,0,58,0,2, { 0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c,0x00000803 } }
+, {1,46,0,58,0,3, { 0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c } }
+, {1,46,2,58,0,4, { 0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590,0x00001020,0x0000039a,0x00000132 } }
+, {1,47,0,58,0,5, { 0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6 } }
+, {1,47,2,58,0,6, { 0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590,0x00001020,0x0000039a } }
+, {1,48,0,58,0,7, { 0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408 } }
+, {1,48,2,58,0,8, { 0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590,0x00001020 } }
+, {1,49,0,58,0,9, { 0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164 } }
+, {1,49,2,58,0,10, { 0x60000000,0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590 } }
+, {1,50,0,65,0,11, { 0x0800000c,0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018 } }
+, {1,50,2,65,0,12, { 0x20000030,0x60000000,0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060 } }
+, {1,51,0,65,0,13, { 0xe8000000,0x0800000c,0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202 } }
+, {1,51,2,65,0,14, { 0xa0000003,0x20000030,0x60000000,0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a } }
+, {1,52,0,65,0,15, { 0x04000010,0xe8000000,0x0800000c,0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012 } }
+, {2,45,0,58,0,16, { 0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a,0x000002e4,0x80000054,0x00000967 } }
+, {2,46,0,58,0,17, { 0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a,0x000002e4,0x80000054 } }
+, {2,46,2,58,0,18, { 0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e,0x0000046c,0x000005b6,0x0000106a,0x00000b90,0x00000152 } }
+, {2,47,0,58,0,19, { 0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a,0x000002e4 } }
+, {2,48,0,58,0,20, { 0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a } }
+, {2,49,0,58,0,21, { 0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d } }
+, {2,49,2,58,0,22, { 0xf0000010,0xf000006a,0x80000040,0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e,0x0000046c,0x000005b6 } }
+, {2,50,0,65,0,23, { 0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b } }
+, {2,50,2,65,0,24, { 0xd0000072,0xf0000010,0xf000006a,0x80000040,0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e,0x0000046c } }
+, {2,51,0,65,0,25, { 0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b } }
+, {2,51,2,65,0,26, { 0x00000043,0xd0000072,0xf0000010,0xf000006a,0x80000040,0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e } }
+, {2,52,0,65,0,27, { 0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014 } }
+, {2,53,0,65,0,28, { 0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089 } }
+, {2,54,0,65,0,29, { 0x0400001c,0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107 } }
+, {2,55,0,65,0,30, { 0x00000010,0x0400001c,0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b } }
+, {2,56,0,65,0,31, { 0x2600001a,0x00000010,0x0400001c,0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046 } }
+, {0,0,0,0,0,0, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}
+};
+void ubc_check(const uint32_t W[80], uint32_t dvmask[1])
+{
+ uint32_t mask = ~((uint32_t)(0));
+ mask &= (((((W[44]^W[45])>>29)&1)-1) | ~(DV_I_48_0_bit|DV_I_51_0_bit|DV_I_52_0_bit|DV_II_45_0_bit|DV_II_46_0_bit|DV_II_50_0_bit|DV_II_51_0_bit));
+ mask &= (((((W[49]^W[50])>>29)&1)-1) | ~(DV_I_46_0_bit|DV_II_45_0_bit|DV_II_50_0_bit|DV_II_51_0_bit|DV_II_55_0_bit|DV_II_56_0_bit));
+ mask &= (((((W[48]^W[49])>>29)&1)-1) | ~(DV_I_45_0_bit|DV_I_52_0_bit|DV_II_49_0_bit|DV_II_50_0_bit|DV_II_54_0_bit|DV_II_55_0_bit));
+ mask &= ((((W[47]^(W[50]>>25))&(1<<4))-(1<<4)) | ~(DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_51_0_bit|DV_II_56_0_bit));
+ mask &= (((((W[47]^W[48])>>29)&1)-1) | ~(DV_I_44_0_bit|DV_I_51_0_bit|DV_II_48_0_bit|DV_II_49_0_bit|DV_II_53_0_bit|DV_II_54_0_bit));
+ mask &= (((((W[46]>>4)^(W[49]>>29))&1)-1) | ~(DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit|DV_II_50_0_bit|DV_II_55_0_bit));
+ mask &= (((((W[46]^W[47])>>29)&1)-1) | ~(DV_I_43_0_bit|DV_I_50_0_bit|DV_II_47_0_bit|DV_II_48_0_bit|DV_II_52_0_bit|DV_II_53_0_bit));
+ mask &= (((((W[45]>>4)^(W[48]>>29))&1)-1) | ~(DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit|DV_II_49_0_bit|DV_II_54_0_bit));
+ mask &= (((((W[45]^W[46])>>29)&1)-1) | ~(DV_I_49_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_47_0_bit|DV_II_51_0_bit|DV_II_52_0_bit));
+ mask &= (((((W[44]>>4)^(W[47]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit|DV_II_48_0_bit|DV_II_53_0_bit));
+ mask &= (((((W[43]>>4)^(W[46]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit|DV_II_47_0_bit|DV_II_52_0_bit));
+ mask &= (((((W[43]^W[44])>>29)&1)-1) | ~(DV_I_47_0_bit|DV_I_50_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_49_0_bit|DV_II_50_0_bit));
+ mask &= (((((W[42]>>4)^(W[45]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_51_0_bit));
+ mask &= (((((W[41]>>4)^(W[44]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_50_0_bit));
+ mask &= (((((W[40]^W[41])>>29)&1)-1) | ~(DV_I_44_0_bit|DV_I_47_0_bit|DV_I_48_0_bit|DV_II_46_0_bit|DV_II_47_0_bit|DV_II_56_0_bit));
+ mask &= (((((W[54]^W[55])>>29)&1)-1) | ~(DV_I_51_0_bit|DV_II_47_0_bit|DV_II_50_0_bit|DV_II_55_0_bit|DV_II_56_0_bit));
+ mask &= (((((W[53]^W[54])>>29)&1)-1) | ~(DV_I_50_0_bit|DV_II_46_0_bit|DV_II_49_0_bit|DV_II_54_0_bit|DV_II_55_0_bit));
+ mask &= (((((W[52]^W[53])>>29)&1)-1) | ~(DV_I_49_0_bit|DV_II_45_0_bit|DV_II_48_0_bit|DV_II_53_0_bit|DV_II_54_0_bit));
+ mask &= ((((W[50]^(W[53]>>25))&(1<<4))-(1<<4)) | ~(DV_I_50_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_48_0_bit|DV_II_54_0_bit));
+ mask &= (((((W[50]^W[51])>>29)&1)-1) | ~(DV_I_47_0_bit|DV_II_46_0_bit|DV_II_51_0_bit|DV_II_52_0_bit|DV_II_56_0_bit));
+ mask &= ((((W[49]^(W[52]>>25))&(1<<4))-(1<<4)) | ~(DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_47_0_bit|DV_II_53_0_bit));
+ mask &= ((((W[48]^(W[51]>>25))&(1<<4))-(1<<4)) | ~(DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_52_0_bit));
+ mask &= (((((W[42]^W[43])>>29)&1)-1) | ~(DV_I_46_0_bit|DV_I_49_0_bit|DV_I_50_0_bit|DV_II_48_0_bit|DV_II_49_0_bit));
+ mask &= (((((W[41]^W[42])>>29)&1)-1) | ~(DV_I_45_0_bit|DV_I_48_0_bit|DV_I_49_0_bit|DV_II_47_0_bit|DV_II_48_0_bit));
+ mask &= (((((W[40]>>4)^(W[43]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_50_0_bit|DV_II_49_0_bit|DV_II_56_0_bit));
+ mask &= (((((W[39]>>4)^(W[42]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_49_0_bit|DV_II_48_0_bit|DV_II_55_0_bit));
+ if (mask & (DV_I_44_0_bit|DV_I_48_0_bit|DV_II_47_0_bit|DV_II_54_0_bit|DV_II_56_0_bit))
+ mask &= (((((W[38]>>4)^(W[41]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_48_0_bit|DV_II_47_0_bit|DV_II_54_0_bit|DV_II_56_0_bit));
+ mask &= (((((W[37]>>4)^(W[40]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_47_0_bit|DV_II_46_0_bit|DV_II_53_0_bit|DV_II_55_0_bit));
+ if (mask & (DV_I_52_0_bit|DV_II_48_0_bit|DV_II_51_0_bit|DV_II_56_0_bit))
+ mask &= (((((W[55]^W[56])>>29)&1)-1) | ~(DV_I_52_0_bit|DV_II_48_0_bit|DV_II_51_0_bit|DV_II_56_0_bit));
+ if (mask & (DV_I_52_0_bit|DV_II_48_0_bit|DV_II_50_0_bit|DV_II_56_0_bit))
+ mask &= ((((W[52]^(W[55]>>25))&(1<<4))-(1<<4)) | ~(DV_I_52_0_bit|DV_II_48_0_bit|DV_II_50_0_bit|DV_II_56_0_bit));
+ if (mask & (DV_I_51_0_bit|DV_II_47_0_bit|DV_II_49_0_bit|DV_II_55_0_bit))
+ mask &= ((((W[51]^(W[54]>>25))&(1<<4))-(1<<4)) | ~(DV_I_51_0_bit|DV_II_47_0_bit|DV_II_49_0_bit|DV_II_55_0_bit));
+ if (mask & (DV_I_48_0_bit|DV_II_47_0_bit|DV_II_52_0_bit|DV_II_53_0_bit))
+ mask &= (((((W[51]^W[52])>>29)&1)-1) | ~(DV_I_48_0_bit|DV_II_47_0_bit|DV_II_52_0_bit|DV_II_53_0_bit));
+ if (mask & (DV_I_46_0_bit|DV_I_49_0_bit|DV_II_45_0_bit|DV_II_48_0_bit))
+ mask &= (((((W[36]>>4)^(W[40]>>29))&1)-1) | ~(DV_I_46_0_bit|DV_I_49_0_bit|DV_II_45_0_bit|DV_II_48_0_bit));
+ if (mask & (DV_I_52_0_bit|DV_II_48_0_bit|DV_II_49_0_bit))
+ mask &= ((0-(((W[53]^W[56])>>29)&1)) | ~(DV_I_52_0_bit|DV_II_48_0_bit|DV_II_49_0_bit));
+ if (mask & (DV_I_50_0_bit|DV_II_46_0_bit|DV_II_47_0_bit))
+ mask &= ((0-(((W[51]^W[54])>>29)&1)) | ~(DV_I_50_0_bit|DV_II_46_0_bit|DV_II_47_0_bit));
+ if (mask & (DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit))
+ mask &= ((0-(((W[50]^W[52])>>29)&1)) | ~(DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit));
+ if (mask & (DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit))
+ mask &= ((0-(((W[49]^W[51])>>29)&1)) | ~(DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit));
+ if (mask & (DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit))
+ mask &= ((0-(((W[48]^W[50])>>29)&1)) | ~(DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit));
+ if (mask & (DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit))
+ mask &= ((0-(((W[47]^W[49])>>29)&1)) | ~(DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit));
+ if (mask & (DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit))
+ mask &= ((0-(((W[46]^W[48])>>29)&1)) | ~(DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit));
+ mask &= ((((W[45]^W[47])&(1<<6))-(1<<6)) | ~(DV_I_47_2_bit|DV_I_49_2_bit|DV_I_51_2_bit));
+ if (mask & (DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit))
+ mask &= ((0-(((W[45]^W[47])>>29)&1)) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit));
+ mask &= (((((W[44]^W[46])>>6)&1)-1) | ~(DV_I_46_2_bit|DV_I_48_2_bit|DV_I_50_2_bit));
+ if (mask & (DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit))
+ mask &= ((0-(((W[44]^W[46])>>29)&1)) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit));
+ mask &= ((0-((W[41]^(W[42]>>5))&(1<<1))) | ~(DV_I_48_2_bit|DV_II_46_2_bit|DV_II_51_2_bit));
+ mask &= ((0-((W[40]^(W[41]>>5))&(1<<1))) | ~(DV_I_47_2_bit|DV_I_51_2_bit|DV_II_50_2_bit));
+ if (mask & (DV_I_44_0_bit|DV_I_46_0_bit|DV_II_56_0_bit))
+ mask &= ((0-(((W[40]^W[42])>>4)&1)) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_II_56_0_bit));
+ mask &= ((0-((W[39]^(W[40]>>5))&(1<<1))) | ~(DV_I_46_2_bit|DV_I_50_2_bit|DV_II_49_2_bit));
+ if (mask & (DV_I_43_0_bit|DV_I_45_0_bit|DV_II_55_0_bit))
+ mask &= ((0-(((W[39]^W[41])>>4)&1)) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_II_55_0_bit));
+ if (mask & (DV_I_44_0_bit|DV_II_54_0_bit|DV_II_56_0_bit))
+ mask &= ((0-(((W[38]^W[40])>>4)&1)) | ~(DV_I_44_0_bit|DV_II_54_0_bit|DV_II_56_0_bit));
+ if (mask & (DV_I_43_0_bit|DV_II_53_0_bit|DV_II_55_0_bit))
+ mask &= ((0-(((W[37]^W[39])>>4)&1)) | ~(DV_I_43_0_bit|DV_II_53_0_bit|DV_II_55_0_bit));
+ mask &= ((0-((W[36]^(W[37]>>5))&(1<<1))) | ~(DV_I_47_2_bit|DV_I_50_2_bit|DV_II_46_2_bit));
+ if (mask & (DV_I_45_0_bit|DV_I_48_0_bit|DV_II_47_0_bit))
+ mask &= (((((W[35]>>4)^(W[39]>>29))&1)-1) | ~(DV_I_45_0_bit|DV_I_48_0_bit|DV_II_47_0_bit));
+ if (mask & (DV_I_48_0_bit|DV_II_48_0_bit))
+ mask &= ((0-((W[63]^(W[64]>>5))&(1<<0))) | ~(DV_I_48_0_bit|DV_II_48_0_bit));
+ if (mask & (DV_I_45_0_bit|DV_II_45_0_bit))
+ mask &= ((0-((W[63]^(W[64]>>5))&(1<<1))) | ~(DV_I_45_0_bit|DV_II_45_0_bit));
+ if (mask & (DV_I_47_0_bit|DV_II_47_0_bit))
+ mask &= ((0-((W[62]^(W[63]>>5))&(1<<0))) | ~(DV_I_47_0_bit|DV_II_47_0_bit));
+ if (mask & (DV_I_46_0_bit|DV_II_46_0_bit))
+ mask &= ((0-((W[61]^(W[62]>>5))&(1<<0))) | ~(DV_I_46_0_bit|DV_II_46_0_bit));
+ mask &= ((0-((W[61]^(W[62]>>5))&(1<<2))) | ~(DV_I_46_2_bit|DV_II_46_2_bit));
+ if (mask & (DV_I_45_0_bit|DV_II_45_0_bit))
+ mask &= ((0-((W[60]^(W[61]>>5))&(1<<0))) | ~(DV_I_45_0_bit|DV_II_45_0_bit));
+ if (mask & (DV_II_51_0_bit|DV_II_54_0_bit))
+ mask &= (((((W[58]^W[59])>>29)&1)-1) | ~(DV_II_51_0_bit|DV_II_54_0_bit));
+ if (mask & (DV_II_50_0_bit|DV_II_53_0_bit))
+ mask &= (((((W[57]^W[58])>>29)&1)-1) | ~(DV_II_50_0_bit|DV_II_53_0_bit));
+ if (mask & (DV_II_52_0_bit|DV_II_54_0_bit))
+ mask &= ((((W[56]^(W[59]>>25))&(1<<4))-(1<<4)) | ~(DV_II_52_0_bit|DV_II_54_0_bit));
+ if (mask & (DV_II_51_0_bit|DV_II_52_0_bit))
+ mask &= ((0-(((W[56]^W[59])>>29)&1)) | ~(DV_II_51_0_bit|DV_II_52_0_bit));
+ if (mask & (DV_II_49_0_bit|DV_II_52_0_bit))
+ mask &= (((((W[56]^W[57])>>29)&1)-1) | ~(DV_II_49_0_bit|DV_II_52_0_bit));
+ if (mask & (DV_II_51_0_bit|DV_II_53_0_bit))
+ mask &= ((((W[55]^(W[58]>>25))&(1<<4))-(1<<4)) | ~(DV_II_51_0_bit|DV_II_53_0_bit));
+ if (mask & (DV_II_50_0_bit|DV_II_52_0_bit))
+ mask &= ((((W[54]^(W[57]>>25))&(1<<4))-(1<<4)) | ~(DV_II_50_0_bit|DV_II_52_0_bit));
+ if (mask & (DV_II_49_0_bit|DV_II_51_0_bit))
+ mask &= ((((W[53]^(W[56]>>25))&(1<<4))-(1<<4)) | ~(DV_II_49_0_bit|DV_II_51_0_bit));
+ mask &= ((((W[51]^(W[50]>>5))&(1<<1))-(1<<1)) | ~(DV_I_50_2_bit|DV_II_46_2_bit));
+ mask &= ((((W[48]^W[50])&(1<<6))-(1<<6)) | ~(DV_I_50_2_bit|DV_II_46_2_bit));
+ if (mask & (DV_I_51_0_bit|DV_I_52_0_bit))
+ mask &= ((0-(((W[48]^W[55])>>29)&1)) | ~(DV_I_51_0_bit|DV_I_52_0_bit));
+ mask &= ((((W[47]^W[49])&(1<<6))-(1<<6)) | ~(DV_I_49_2_bit|DV_I_51_2_bit));
+ mask &= ((((W[48]^(W[47]>>5))&(1<<1))-(1<<1)) | ~(DV_I_47_2_bit|DV_II_51_2_bit));
+ mask &= ((((W[46]^W[48])&(1<<6))-(1<<6)) | ~(DV_I_48_2_bit|DV_I_50_2_bit));
+ mask &= ((((W[47]^(W[46]>>5))&(1<<1))-(1<<1)) | ~(DV_I_46_2_bit|DV_II_50_2_bit));
+ mask &= ((0-((W[44]^(W[45]>>5))&(1<<1))) | ~(DV_I_51_2_bit|DV_II_49_2_bit));
+ mask &= ((((W[43]^W[45])&(1<<6))-(1<<6)) | ~(DV_I_47_2_bit|DV_I_49_2_bit));
+ mask &= (((((W[42]^W[44])>>6)&1)-1) | ~(DV_I_46_2_bit|DV_I_48_2_bit));
+ mask &= ((((W[43]^(W[42]>>5))&(1<<1))-(1<<1)) | ~(DV_II_46_2_bit|DV_II_51_2_bit));
+ mask &= ((((W[42]^(W[41]>>5))&(1<<1))-(1<<1)) | ~(DV_I_51_2_bit|DV_II_50_2_bit));
+ mask &= ((((W[41]^(W[40]>>5))&(1<<1))-(1<<1)) | ~(DV_I_50_2_bit|DV_II_49_2_bit));
+ if (mask & (DV_I_52_0_bit|DV_II_51_0_bit))
+ mask &= ((((W[39]^(W[43]>>25))&(1<<4))-(1<<4)) | ~(DV_I_52_0_bit|DV_II_51_0_bit));
+ if (mask & (DV_I_51_0_bit|DV_II_50_0_bit))
+ mask &= ((((W[38]^(W[42]>>25))&(1<<4))-(1<<4)) | ~(DV_I_51_0_bit|DV_II_50_0_bit));
+ if (mask & (DV_I_48_2_bit|DV_I_51_2_bit))
+ mask &= ((0-((W[37]^(W[38]>>5))&(1<<1))) | ~(DV_I_48_2_bit|DV_I_51_2_bit));
+ if (mask & (DV_I_50_0_bit|DV_II_49_0_bit))
+ mask &= ((((W[37]^(W[41]>>25))&(1<<4))-(1<<4)) | ~(DV_I_50_0_bit|DV_II_49_0_bit));
+ if (mask & (DV_II_52_0_bit|DV_II_54_0_bit))
+ mask &= ((0-((W[36]^W[38])&(1<<4))) | ~(DV_II_52_0_bit|DV_II_54_0_bit));
+ mask &= ((0-((W[35]^(W[36]>>5))&(1<<1))) | ~(DV_I_46_2_bit|DV_I_49_2_bit));
+ if (mask & (DV_I_51_0_bit|DV_II_47_0_bit))
+ mask &= ((((W[35]^(W[39]>>25))&(1<<3))-(1<<3)) | ~(DV_I_51_0_bit|DV_II_47_0_bit));
+if (mask) {
+
+ if (mask & DV_I_43_0_bit)
+ if (
+ !((W[61]^(W[62]>>5)) & (1<<1))
+ || !(!((W[59]^(W[63]>>25)) & (1<<5)))
+ || !((W[58]^(W[63]>>30)) & (1<<0))
+ ) mask &= ~DV_I_43_0_bit;
+ if (mask & DV_I_44_0_bit)
+ if (
+ !((W[62]^(W[63]>>5)) & (1<<1))
+ || !(!((W[60]^(W[64]>>25)) & (1<<5)))
+ || !((W[59]^(W[64]>>30)) & (1<<0))
+ ) mask &= ~DV_I_44_0_bit;
+ if (mask & DV_I_46_2_bit)
+ mask &= ((~((W[40]^W[42])>>2)) | ~DV_I_46_2_bit);
+ if (mask & DV_I_47_2_bit)
+ if (
+ !((W[62]^(W[63]>>5)) & (1<<2))
+ || !(!((W[41]^W[43]) & (1<<6)))
+ ) mask &= ~DV_I_47_2_bit;
+ if (mask & DV_I_48_2_bit)
+ if (
+ !((W[63]^(W[64]>>5)) & (1<<2))
+ || !(!((W[48]^(W[49]<<5)) & (1<<6)))
+ ) mask &= ~DV_I_48_2_bit;
+ if (mask & DV_I_49_2_bit)
+ if (
+ !(!((W[49]^(W[50]<<5)) & (1<<6)))
+ || !((W[42]^W[50]) & (1<<1))
+ || !(!((W[39]^(W[40]<<5)) & (1<<6)))
+ || !((W[38]^W[40]) & (1<<1))
+ ) mask &= ~DV_I_49_2_bit;
+ if (mask & DV_I_50_0_bit)
+ mask &= ((((W[36]^W[37])<<7)) | ~DV_I_50_0_bit);
+ if (mask & DV_I_50_2_bit)
+ mask &= ((((W[43]^W[51])<<11)) | ~DV_I_50_2_bit);
+ if (mask & DV_I_51_0_bit)
+ mask &= ((((W[37]^W[38])<<9)) | ~DV_I_51_0_bit);
+ if (mask & DV_I_51_2_bit)
+ if (
+ !(!((W[51]^(W[52]<<5)) & (1<<6)))
+ || !(!((W[49]^W[51]) & (1<<6)))
+ || !(!((W[37]^(W[37]>>5)) & (1<<1)))
+ || !(!((W[35]^(W[39]>>25)) & (1<<5)))
+ ) mask &= ~DV_I_51_2_bit;
+ if (mask & DV_I_52_0_bit)
+ mask &= ((((W[38]^W[39])<<11)) | ~DV_I_52_0_bit);
+ if (mask & DV_II_46_2_bit)
+ mask &= ((((W[47]^W[51])<<17)) | ~DV_II_46_2_bit);
+ if (mask & DV_II_48_0_bit)
+ if (
+ !(!((W[36]^(W[40]>>25)) & (1<<3)))
+ || !((W[35]^(W[40]<<2)) & (1<<30))
+ ) mask &= ~DV_II_48_0_bit;
+ if (mask & DV_II_49_0_bit)
+ if (
+ !(!((W[37]^(W[41]>>25)) & (1<<3)))
+ || !((W[36]^(W[41]<<2)) & (1<<30))
+ ) mask &= ~DV_II_49_0_bit;
+ if (mask & DV_II_49_2_bit)
+ if (
+ !(!((W[53]^(W[54]<<5)) & (1<<6)))
+ || !(!((W[51]^W[53]) & (1<<6)))
+ || !((W[50]^W[54]) & (1<<1))
+ || !(!((W[45]^(W[46]<<5)) & (1<<6)))
+ || !(!((W[37]^(W[41]>>25)) & (1<<5)))
+ || !((W[36]^(W[41]>>30)) & (1<<0))
+ ) mask &= ~DV_II_49_2_bit;
+ if (mask & DV_II_50_0_bit)
+ if (
+ !((W[55]^W[58]) & (1<<29))
+ || !(!((W[38]^(W[42]>>25)) & (1<<3)))
+ || !((W[37]^(W[42]<<2)) & (1<<30))
+ ) mask &= ~DV_II_50_0_bit;
+ if (mask & DV_II_50_2_bit)
+ if (
+ !(!((W[54]^(W[55]<<5)) & (1<<6)))
+ || !(!((W[52]^W[54]) & (1<<6)))
+ || !((W[51]^W[55]) & (1<<1))
+ || !((W[45]^W[47]) & (1<<1))
+ || !(!((W[38]^(W[42]>>25)) & (1<<5)))
+ || !((W[37]^(W[42]>>30)) & (1<<0))
+ ) mask &= ~DV_II_50_2_bit;
+ if (mask & DV_II_51_0_bit)
+ if (
+ !(!((W[39]^(W[43]>>25)) & (1<<3)))
+ || !((W[38]^(W[43]<<2)) & (1<<30))
+ ) mask &= ~DV_II_51_0_bit;
+ if (mask & DV_II_51_2_bit)
+ if (
+ !(!((W[55]^(W[56]<<5)) & (1<<6)))
+ || !(!((W[53]^W[55]) & (1<<6)))
+ || !((W[52]^W[56]) & (1<<1))
+ || !((W[46]^W[48]) & (1<<1))
+ || !(!((W[39]^(W[43]>>25)) & (1<<5)))
+ || !((W[38]^(W[43]>>30)) & (1<<0))
+ ) mask &= ~DV_II_51_2_bit;
+ if (mask & DV_II_52_0_bit)
+ if (
+ !(!((W[59]^W[60]) & (1<<29)))
+ || !(!((W[40]^(W[44]>>25)) & (1<<3)))
+ || !(!((W[40]^(W[44]>>25)) & (1<<4)))
+ || !((W[39]^(W[44]<<2)) & (1<<30))
+ ) mask &= ~DV_II_52_0_bit;
+ if (mask & DV_II_53_0_bit)
+ if (
+ !((W[58]^W[61]) & (1<<29))
+ || !(!((W[57]^(W[61]>>25)) & (1<<4)))
+ || !(!((W[41]^(W[45]>>25)) & (1<<3)))
+ || !(!((W[41]^(W[45]>>25)) & (1<<4)))
+ ) mask &= ~DV_II_53_0_bit;
+ if (mask & DV_II_54_0_bit)
+ if (
+ !(!((W[58]^(W[62]>>25)) & (1<<4)))
+ || !(!((W[42]^(W[46]>>25)) & (1<<3)))
+ || !(!((W[42]^(W[46]>>25)) & (1<<4)))
+ ) mask &= ~DV_II_54_0_bit;
+ if (mask & DV_II_55_0_bit)
+ if (
+ !(!((W[59]^(W[63]>>25)) & (1<<4)))
+ || !(!((W[57]^(W[59]>>25)) & (1<<4)))
+ || !(!((W[43]^(W[47]>>25)) & (1<<3)))
+ || !(!((W[43]^(W[47]>>25)) & (1<<4)))
+ ) mask &= ~DV_II_55_0_bit;
+ if (mask & DV_II_56_0_bit)
+ if (
+ !(!((W[60]^(W[64]>>25)) & (1<<4)))
+ || !(!((W[44]^(W[48]>>25)) & (1<<3)))
+ || !(!((W[44]^(W[48]>>25)) & (1<<4)))
+ ) mask &= ~DV_II_56_0_bit;
+}
+
+ dvmask[0]=mask;
+}
+
+#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_C
+#include SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_C
+#endif
diff --git a/src/util/hash/sha1dc/ubc_check.h b/src/util/hash/sha1dc/ubc_check.h
new file mode 100644
index 0000000..d7e17dc
--- /dev/null
+++ b/src/util/hash/sha1dc/ubc_check.h
@@ -0,0 +1,52 @@
+/***
+* Copyright 2017 Marc Stevens <marc@marc-stevens.nl>, Dan Shumow <danshu@microsoft.com>
+* Distributed under the MIT Software License.
+* See accompanying file LICENSE.txt or copy at
+* https://opensource.org/licenses/MIT
+***/
+
+/*
+// this file was generated by the 'parse_bitrel' program in the tools section
+// using the data files from directory 'tools/data/3565'
+//
+// sha1_dvs contains a list of SHA-1 Disturbance Vectors (DV) to check
+// dvType, dvK and dvB define the DV: I(K,B) or II(K,B) (see the paper)
+// dm[80] is the expanded message block XOR-difference defined by the DV
+// testt is the step to do the recompression from for collision detection
+// maski and maskb define the bit to check for each DV in the dvmask returned by ubc_check
+//
+// ubc_check takes as input an expanded message block and verifies the unavoidable bitconditions for all listed DVs
+// it returns a dvmask where each bit belonging to a DV is set if all unavoidable bitconditions for that DV have been met
+// thus one needs to do the recompression check for each DV that has its bit set
+*/
+
+#ifndef SHA1DC_UBC_CHECK_H
+#define SHA1DC_UBC_CHECK_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#ifndef SHA1DC_NO_STANDARD_INCLUDES
+#include <stdint.h>
+#endif
+
+#define DVMASKSIZE 1
+typedef struct { int dvType; int dvK; int dvB; int testt; int maski; int maskb; uint32_t dm[80]; } dv_info_t;
+extern dv_info_t sha1_dvs[];
+void ubc_check(const uint32_t W[80], uint32_t dvmask[DVMASKSIZE]);
+
+#define DOSTORESTATE58
+#define DOSTORESTATE65
+
+#define CHECK_DVMASK(_DVMASK) (0 != _DVMASK[0])
+
+#if defined(__cplusplus)
+}
+#endif
+
+#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_H
+#include SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_H
+#endif
+
+#endif
diff --git a/src/util/hash/win32.c b/src/util/hash/win32.c
new file mode 100644
index 0000000..f80c0d5
--- /dev/null
+++ b/src/util/hash/win32.c
@@ -0,0 +1,549 @@
+/*
+ * 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 "win32.h"
+
+#include "runtime.h"
+
+#include <wincrypt.h>
+#include <strsafe.h>
+
+#define GIT_HASH_CNG_DLL_NAME "bcrypt.dll"
+
+/* BCRYPT_SHA1_ALGORITHM */
+#define GIT_HASH_CNG_SHA1_TYPE L"SHA1"
+#define GIT_HASH_CNG_SHA256_TYPE L"SHA256"
+
+/* BCRYPT_OBJECT_LENGTH */
+#define GIT_HASH_CNG_HASH_OBJECT_LEN L"ObjectLength"
+
+/* BCRYPT_HASH_REUSEABLE_FLAGS */
+#define GIT_HASH_CNG_HASH_REUSABLE 0x00000020
+
+/* Definitions */
+
+/* CryptoAPI is available for hashing on Windows XP and newer. */
+struct cryptoapi_provider {
+ HCRYPTPROV handle;
+};
+
+/*
+ * CNG (bcrypt.dll) is significantly more performant than CryptoAPI and is
+ * preferred, however it is only available on Windows 2008 and newer and
+ * must therefore be dynamically loaded, and we must inline constants that
+ * would not exist when building in pre-Windows 2008 environments.
+ */
+
+/* Function declarations for CNG */
+typedef NTSTATUS (WINAPI *cng_open_algorithm_provider_fn)(
+ HANDLE /* BCRYPT_ALG_HANDLE */ *phAlgorithm,
+ LPCWSTR pszAlgId,
+ LPCWSTR pszImplementation,
+ DWORD dwFlags);
+
+typedef NTSTATUS (WINAPI *cng_get_property_fn)(
+ HANDLE /* BCRYPT_HANDLE */ hObject,
+ LPCWSTR pszProperty,
+ PUCHAR pbOutput,
+ ULONG cbOutput,
+ ULONG *pcbResult,
+ ULONG dwFlags);
+
+typedef NTSTATUS (WINAPI *cng_create_hash_fn)(
+ HANDLE /* BCRYPT_ALG_HANDLE */ hAlgorithm,
+ HANDLE /* BCRYPT_HASH_HANDLE */ *phHash,
+ PUCHAR pbHashObject, ULONG cbHashObject,
+ PUCHAR pbSecret,
+ ULONG cbSecret,
+ ULONG dwFlags);
+
+typedef NTSTATUS (WINAPI *cng_finish_hash_fn)(
+ HANDLE /* BCRYPT_HASH_HANDLE */ hHash,
+ PUCHAR pbOutput,
+ ULONG cbOutput,
+ ULONG dwFlags);
+
+typedef NTSTATUS (WINAPI *cng_hash_data_fn)(
+ HANDLE /* BCRYPT_HASH_HANDLE */ hHash,
+ PUCHAR pbInput,
+ ULONG cbInput,
+ ULONG dwFlags);
+
+typedef NTSTATUS (WINAPI *cng_destroy_hash_fn)(
+ HANDLE /* BCRYPT_HASH_HANDLE */ hHash);
+
+typedef NTSTATUS (WINAPI *cng_close_algorithm_provider_fn)(
+ HANDLE /* BCRYPT_ALG_HANDLE */ hAlgorithm,
+ ULONG dwFlags);
+
+struct cng_provider {
+ /* DLL for CNG */
+ HINSTANCE dll;
+
+ /* Function pointers for CNG */
+ cng_open_algorithm_provider_fn open_algorithm_provider;
+ cng_get_property_fn get_property;
+ cng_create_hash_fn create_hash;
+ cng_finish_hash_fn finish_hash;
+ cng_hash_data_fn hash_data;
+ cng_destroy_hash_fn destroy_hash;
+ cng_close_algorithm_provider_fn close_algorithm_provider;
+
+ HANDLE /* BCRYPT_ALG_HANDLE */ sha1_handle;
+ DWORD sha1_object_size;
+
+ HANDLE /* BCRYPT_ALG_HANDLE */ sha256_handle;
+ DWORD sha256_object_size;
+};
+
+typedef struct {
+ git_hash_win32_provider_t type;
+
+ union {
+ struct cryptoapi_provider cryptoapi;
+ struct cng_provider cng;
+ } provider;
+} hash_win32_provider;
+
+/* Hash provider definition */
+
+static hash_win32_provider hash_provider = {0};
+
+/* Hash initialization */
+
+/* Initialize CNG, if available */
+GIT_INLINE(int) cng_provider_init(void)
+{
+ char dll_path[MAX_PATH];
+ DWORD dll_path_len, size_len;
+
+ /* Only use CNG on Windows 2008 / Vista SP1 or better (Windows 6.0 SP1) */
+ if (!git_has_win32_version(6, 0, 1)) {
+ git_error_set(GIT_ERROR_SHA, "CryptoNG is not supported on this platform");
+ return -1;
+ }
+
+ /* Load bcrypt.dll explicitly from the system directory */
+ if ((dll_path_len = GetSystemDirectory(dll_path, MAX_PATH)) == 0 ||
+ dll_path_len > MAX_PATH ||
+ StringCchCat(dll_path, MAX_PATH, "\\") < 0 ||
+ StringCchCat(dll_path, MAX_PATH, GIT_HASH_CNG_DLL_NAME) < 0 ||
+ (hash_provider.provider.cng.dll = LoadLibrary(dll_path)) == NULL) {
+ git_error_set(GIT_ERROR_SHA, "CryptoNG library could not be loaded");
+ return -1;
+ }
+
+ /* Load the function addresses */
+ if ((hash_provider.provider.cng.open_algorithm_provider = (cng_open_algorithm_provider_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptOpenAlgorithmProvider"))) == NULL ||
+ (hash_provider.provider.cng.get_property = (cng_get_property_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptGetProperty"))) == NULL ||
+ (hash_provider.provider.cng.create_hash = (cng_create_hash_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptCreateHash"))) == NULL ||
+ (hash_provider.provider.cng.finish_hash = (cng_finish_hash_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptFinishHash"))) == NULL ||
+ (hash_provider.provider.cng.hash_data = (cng_hash_data_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptHashData"))) == NULL ||
+ (hash_provider.provider.cng.destroy_hash = (cng_destroy_hash_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptDestroyHash"))) == NULL ||
+ (hash_provider.provider.cng.close_algorithm_provider = (cng_close_algorithm_provider_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptCloseAlgorithmProvider"))) == NULL) {
+ FreeLibrary(hash_provider.provider.cng.dll);
+
+ git_error_set(GIT_ERROR_OS, "CryptoNG functions could not be loaded");
+ return -1;
+ }
+
+ /* Load the SHA1 algorithm */
+ if (hash_provider.provider.cng.open_algorithm_provider(&hash_provider.provider.cng.sha1_handle, GIT_HASH_CNG_SHA1_TYPE, NULL, GIT_HASH_CNG_HASH_REUSABLE) < 0 ||
+ hash_provider.provider.cng.get_property(hash_provider.provider.cng.sha1_handle, GIT_HASH_CNG_HASH_OBJECT_LEN, (PBYTE)&hash_provider.provider.cng.sha1_object_size, sizeof(DWORD), &size_len, 0) < 0) {
+ git_error_set(GIT_ERROR_OS, "algorithm provider could not be initialized");
+ goto on_error;
+ }
+
+ /* Load the SHA256 algorithm */
+ if (hash_provider.provider.cng.open_algorithm_provider(&hash_provider.provider.cng.sha256_handle, GIT_HASH_CNG_SHA256_TYPE, NULL, GIT_HASH_CNG_HASH_REUSABLE) < 0 ||
+ hash_provider.provider.cng.get_property(hash_provider.provider.cng.sha256_handle, GIT_HASH_CNG_HASH_OBJECT_LEN, (PBYTE)&hash_provider.provider.cng.sha256_object_size, sizeof(DWORD), &size_len, 0) < 0) {
+ git_error_set(GIT_ERROR_OS, "algorithm provider could not be initialized");
+ goto on_error;
+ }
+
+ hash_provider.type = GIT_HASH_WIN32_CNG;
+ return 0;
+
+on_error:
+ if (hash_provider.provider.cng.sha1_handle)
+ hash_provider.provider.cng.close_algorithm_provider(hash_provider.provider.cng.sha1_handle, 0);
+
+ if (hash_provider.provider.cng.sha256_handle)
+ hash_provider.provider.cng.close_algorithm_provider(hash_provider.provider.cng.sha256_handle, 0);
+
+ if (hash_provider.provider.cng.dll)
+ FreeLibrary(hash_provider.provider.cng.dll);
+
+ return -1;
+}
+
+GIT_INLINE(void) cng_provider_shutdown(void)
+{
+ if (hash_provider.type == GIT_HASH_WIN32_INVALID)
+ return;
+
+ hash_provider.provider.cng.close_algorithm_provider(hash_provider.provider.cng.sha1_handle, 0);
+ hash_provider.provider.cng.close_algorithm_provider(hash_provider.provider.cng.sha256_handle, 0);
+ FreeLibrary(hash_provider.provider.cng.dll);
+
+ hash_provider.type = GIT_HASH_WIN32_INVALID;
+}
+
+/* Initialize CryptoAPI */
+GIT_INLINE(int) cryptoapi_provider_init(void)
+{
+ if (!CryptAcquireContext(&hash_provider.provider.cryptoapi.handle, NULL, 0, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) {
+ git_error_set(GIT_ERROR_OS, "legacy hash context could not be started");
+ return -1;
+ }
+
+ hash_provider.type = GIT_HASH_WIN32_CRYPTOAPI;
+ return 0;
+}
+
+GIT_INLINE(void) cryptoapi_provider_shutdown(void)
+{
+ if (hash_provider.type == GIT_HASH_WIN32_INVALID)
+ return;
+
+ CryptReleaseContext(hash_provider.provider.cryptoapi.handle, 0);
+
+ hash_provider.type = GIT_HASH_WIN32_INVALID;
+}
+
+static void hash_provider_shutdown(void)
+{
+ if (hash_provider.type == GIT_HASH_WIN32_CNG)
+ cng_provider_shutdown();
+ else if (hash_provider.type == GIT_HASH_WIN32_CRYPTOAPI)
+ cryptoapi_provider_shutdown();
+}
+
+static int hash_provider_init(void)
+{
+ int error = 0;
+
+ if (hash_provider.type != GIT_HASH_WIN32_INVALID)
+ return 0;
+
+ if ((error = cng_provider_init()) < 0)
+ error = cryptoapi_provider_init();
+
+ if (!error)
+ error = git_runtime_shutdown_register(hash_provider_shutdown);
+
+ return error;
+}
+
+git_hash_win32_provider_t git_hash_win32_provider(void)
+{
+ return hash_provider.type;
+}
+
+int git_hash_win32_set_provider(git_hash_win32_provider_t provider)
+{
+ if (provider == hash_provider.type)
+ return 0;
+
+ hash_provider_shutdown();
+
+ if (provider == GIT_HASH_WIN32_CNG)
+ return cng_provider_init();
+ else if (provider == GIT_HASH_WIN32_CRYPTOAPI)
+ return cryptoapi_provider_init();
+
+ git_error_set(GIT_ERROR_SHA, "unsupported win32 provider");
+ return -1;
+}
+
+/* CryptoAPI: available in Windows XP and newer */
+
+GIT_INLINE(int) hash_cryptoapi_init(git_hash_win32_ctx *ctx)
+{
+ if (ctx->ctx.cryptoapi.valid)
+ CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);
+
+ if (!CryptCreateHash(hash_provider.provider.cryptoapi.handle, ctx->algorithm, 0, 0, &ctx->ctx.cryptoapi.hash_handle)) {
+ ctx->ctx.cryptoapi.valid = 0;
+ git_error_set(GIT_ERROR_OS, "legacy hash implementation could not be created");
+ return -1;
+ }
+
+ ctx->ctx.cryptoapi.valid = 1;
+ return 0;
+}
+
+GIT_INLINE(int) hash_cryptoapi_update(git_hash_win32_ctx *ctx, const void *_data, size_t len)
+{
+ const BYTE *data = (BYTE *)_data;
+
+ GIT_ASSERT(ctx->ctx.cryptoapi.valid);
+
+ while (len > 0) {
+ DWORD chunk = (len > MAXDWORD) ? MAXDWORD : (DWORD)len;
+
+ if (!CryptHashData(ctx->ctx.cryptoapi.hash_handle, data, chunk, 0)) {
+ git_error_set(GIT_ERROR_OS, "legacy hash data could not be updated");
+ return -1;
+ }
+
+ data += chunk;
+ len -= chunk;
+ }
+
+ return 0;
+}
+
+GIT_INLINE(int) hash_cryptoapi_final(unsigned char *out, git_hash_win32_ctx *ctx)
+{
+ DWORD len = ctx->algorithm == CALG_SHA_256 ? GIT_HASH_SHA256_SIZE : GIT_HASH_SHA1_SIZE;
+ int error = 0;
+
+ GIT_ASSERT(ctx->ctx.cryptoapi.valid);
+
+ if (!CryptGetHashParam(ctx->ctx.cryptoapi.hash_handle, HP_HASHVAL, out, &len, 0)) {
+ git_error_set(GIT_ERROR_OS, "legacy hash data could not be finished");
+ error = -1;
+ }
+
+ CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);
+ ctx->ctx.cryptoapi.valid = 0;
+
+ return error;
+}
+
+GIT_INLINE(void) hash_ctx_cryptoapi_cleanup(git_hash_win32_ctx *ctx)
+{
+ if (ctx->ctx.cryptoapi.valid)
+ CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);
+}
+
+GIT_INLINE(int) hash_sha1_cryptoapi_ctx_init_init(git_hash_win32_ctx *ctx)
+{
+ ctx->algorithm = CALG_SHA1;
+ return hash_cryptoapi_init(ctx);
+}
+
+GIT_INLINE(int) hash_sha256_cryptoapi_ctx_init(git_hash_win32_ctx *ctx)
+{
+ ctx->algorithm = CALG_SHA_256;
+ return hash_cryptoapi_init(ctx);
+}
+
+/* CNG: Available in Windows Server 2008 and newer */
+
+GIT_INLINE(int) hash_sha1_cng_ctx_init(git_hash_win32_ctx *ctx)
+{
+ if ((ctx->ctx.cng.hash_object = git__malloc(hash_provider.provider.cng.sha1_object_size)) == NULL)
+ return -1;
+
+ if (hash_provider.provider.cng.create_hash(hash_provider.provider.cng.sha1_handle, &ctx->ctx.cng.hash_handle, ctx->ctx.cng.hash_object, hash_provider.provider.cng.sha1_object_size, NULL, 0, 0) < 0) {
+ git__free(ctx->ctx.cng.hash_object);
+
+ git_error_set(GIT_ERROR_OS, "sha1 implementation could not be created");
+ return -1;
+ }
+
+ ctx->algorithm = CALG_SHA1;
+ return 0;
+}
+
+GIT_INLINE(int) hash_sha256_cng_ctx_init(git_hash_win32_ctx *ctx)
+{
+ if ((ctx->ctx.cng.hash_object = git__malloc(hash_provider.provider.cng.sha256_object_size)) == NULL)
+ return -1;
+
+ if (hash_provider.provider.cng.create_hash(hash_provider.provider.cng.sha256_handle, &ctx->ctx.cng.hash_handle, ctx->ctx.cng.hash_object, hash_provider.provider.cng.sha256_object_size, NULL, 0, 0) < 0) {
+ git__free(ctx->ctx.cng.hash_object);
+
+ git_error_set(GIT_ERROR_OS, "sha256 implementation could not be created");
+ return -1;
+ }
+
+ ctx->algorithm = CALG_SHA_256;
+ return 0;
+}
+
+GIT_INLINE(int) hash_cng_init(git_hash_win32_ctx *ctx)
+{
+ BYTE hash[GIT_HASH_SHA256_SIZE];
+ ULONG size = ctx->algorithm == CALG_SHA_256 ? GIT_HASH_SHA256_SIZE : GIT_HASH_SHA1_SIZE;
+
+ if (!ctx->ctx.cng.updated)
+ return 0;
+
+ /* CNG needs to be finished to restart */
+ if (hash_provider.provider.cng.finish_hash(ctx->ctx.cng.hash_handle, hash, size, 0) < 0) {
+ git_error_set(GIT_ERROR_OS, "hash implementation could not be finished");
+ return -1;
+ }
+
+ ctx->ctx.cng.updated = 0;
+
+ return 0;
+}
+
+GIT_INLINE(int) hash_cng_update(git_hash_win32_ctx *ctx, const void *_data, size_t len)
+{
+ PBYTE data = (PBYTE)_data;
+
+ while (len > 0) {
+ ULONG chunk = (len > ULONG_MAX) ? ULONG_MAX : (ULONG)len;
+
+ if (hash_provider.provider.cng.hash_data(ctx->ctx.cng.hash_handle, data, chunk, 0) < 0) {
+ git_error_set(GIT_ERROR_OS, "hash could not be updated");
+ return -1;
+ }
+
+ data += chunk;
+ len -= chunk;
+ }
+
+ return 0;
+}
+
+GIT_INLINE(int) hash_cng_final(unsigned char *out, git_hash_win32_ctx *ctx)
+{
+ ULONG size = ctx->algorithm == CALG_SHA_256 ? GIT_HASH_SHA256_SIZE : GIT_HASH_SHA1_SIZE;
+
+ if (hash_provider.provider.cng.finish_hash(ctx->ctx.cng.hash_handle, out, size, 0) < 0) {
+ git_error_set(GIT_ERROR_OS, "hash could not be finished");
+ return -1;
+ }
+
+ ctx->ctx.cng.updated = 0;
+
+ return 0;
+}
+
+GIT_INLINE(void) hash_ctx_cng_cleanup(git_hash_win32_ctx *ctx)
+{
+ hash_provider.provider.cng.destroy_hash(ctx->ctx.cng.hash_handle);
+ git__free(ctx->ctx.cng.hash_object);
+}
+
+/* Indirection between CryptoAPI and CNG */
+
+GIT_INLINE(int) hash_sha1_win32_ctx_init(git_hash_win32_ctx *ctx)
+{
+ GIT_ASSERT_ARG(hash_provider.type);
+
+ memset(ctx, 0x0, sizeof(git_hash_win32_ctx));
+ return (hash_provider.type == GIT_HASH_WIN32_CNG) ? hash_sha1_cng_ctx_init(ctx) : hash_sha1_cryptoapi_ctx_init_init(ctx);
+}
+
+GIT_INLINE(int) hash_sha256_win32_ctx_init(git_hash_win32_ctx *ctx)
+{
+ GIT_ASSERT_ARG(hash_provider.type);
+
+ memset(ctx, 0x0, sizeof(git_hash_win32_ctx));
+ return (hash_provider.type == GIT_HASH_WIN32_CNG) ? hash_sha256_cng_ctx_init(ctx) : hash_sha256_cryptoapi_ctx_init(ctx);
+}
+
+GIT_INLINE(int) hash_win32_init(git_hash_win32_ctx *ctx)
+{
+ return (hash_provider.type == GIT_HASH_WIN32_CNG) ? hash_cng_init(ctx) : hash_cryptoapi_init(ctx);
+}
+
+GIT_INLINE(int) hash_win32_update(git_hash_win32_ctx *ctx, const void *data, size_t len)
+{
+ return (hash_provider.type == GIT_HASH_WIN32_CNG) ? hash_cng_update(ctx, data, len) : hash_cryptoapi_update(ctx, data, len);
+}
+
+GIT_INLINE(int) hash_win32_final(unsigned char *out, git_hash_win32_ctx *ctx)
+{
+ GIT_ASSERT_ARG(ctx);
+ return (hash_provider.type == GIT_HASH_WIN32_CNG) ? hash_cng_final(out, ctx) : hash_cryptoapi_final(out, ctx);
+}
+
+GIT_INLINE(void) hash_win32_cleanup(git_hash_win32_ctx *ctx)
+{
+ if (hash_provider.type == GIT_HASH_WIN32_CNG)
+ hash_ctx_cng_cleanup(ctx);
+ else if(hash_provider.type == GIT_HASH_WIN32_CRYPTOAPI)
+ hash_ctx_cryptoapi_cleanup(ctx);
+}
+
+#ifdef GIT_SHA1_WIN32
+
+int git_hash_sha1_global_init(void)
+{
+ return hash_provider_init();
+}
+
+int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx)
+{
+ GIT_ASSERT_ARG(ctx);
+ return hash_sha1_win32_ctx_init(&ctx->win32);
+}
+
+int git_hash_sha1_init(git_hash_sha1_ctx *ctx)
+{
+ GIT_ASSERT_ARG(ctx);
+ return hash_win32_init(&ctx->win32);
+}
+
+int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len)
+{
+ GIT_ASSERT_ARG(ctx);
+ return hash_win32_update(&ctx->win32, data, len);
+}
+
+int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx)
+{
+ GIT_ASSERT_ARG(ctx);
+ return hash_win32_final(out, &ctx->win32);
+}
+
+void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx)
+{
+ if (!ctx)
+ return;
+ hash_win32_cleanup(&ctx->win32);
+}
+
+#endif
+
+#ifdef GIT_SHA256_WIN32
+
+int git_hash_sha256_global_init(void)
+{
+ return hash_provider_init();
+}
+
+int git_hash_sha256_ctx_init(git_hash_sha256_ctx *ctx)
+{
+ GIT_ASSERT_ARG(ctx);
+ return hash_sha256_win32_ctx_init(&ctx->win32);
+}
+
+int git_hash_sha256_init(git_hash_sha256_ctx *ctx)
+{
+ GIT_ASSERT_ARG(ctx);
+ return hash_win32_init(&ctx->win32);
+}
+
+int git_hash_sha256_update(git_hash_sha256_ctx *ctx, const void *data, size_t len)
+{
+ GIT_ASSERT_ARG(ctx);
+ return hash_win32_update(&ctx->win32, data, len);
+}
+
+int git_hash_sha256_final(unsigned char *out, git_hash_sha256_ctx *ctx)
+{
+ GIT_ASSERT_ARG(ctx);
+ return hash_win32_final(out, &ctx->win32);
+}
+
+void git_hash_sha256_ctx_cleanup(git_hash_sha256_ctx *ctx)
+{
+ if (!ctx)
+ return;
+ hash_win32_cleanup(&ctx->win32);
+}
+
+#endif
diff --git a/src/util/hash/win32.h b/src/util/hash/win32.h
new file mode 100644
index 0000000..a9fb87a
--- /dev/null
+++ b/src/util/hash/win32.h
@@ -0,0 +1,60 @@
+/*
+ * 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_hash_win32_h__
+#define INCLUDE_hash_win32_h__
+
+#include "hash/sha.h"
+
+#include <wincrypt.h>
+
+typedef enum {
+ GIT_HASH_WIN32_INVALID = 0,
+ GIT_HASH_WIN32_CRYPTOAPI,
+ GIT_HASH_WIN32_CNG
+} git_hash_win32_provider_t;
+
+struct git_hash_win32_cryptoapi_ctx {
+ bool valid;
+ HCRYPTHASH hash_handle;
+};
+
+struct git_hash_win32_cng_ctx {
+ bool updated;
+ HANDLE /* BCRYPT_HASH_HANDLE */ hash_handle;
+ PBYTE hash_object;
+};
+
+typedef struct {
+ ALG_ID algorithm;
+
+ union {
+ struct git_hash_win32_cryptoapi_ctx cryptoapi;
+ struct git_hash_win32_cng_ctx cng;
+ } ctx;
+} git_hash_win32_ctx;
+
+/*
+ * Gets/sets the current hash provider (cng or cryptoapi). This is only
+ * for testing purposes.
+ */
+git_hash_win32_provider_t git_hash_win32_provider(void);
+int git_hash_win32_set_provider(git_hash_win32_provider_t provider);
+
+#ifdef GIT_SHA1_WIN32
+struct git_hash_sha1_ctx {
+ git_hash_win32_ctx win32;
+};
+#endif
+
+#ifdef GIT_SHA256_WIN32
+struct git_hash_sha256_ctx {
+ git_hash_win32_ctx win32;
+};
+#endif
+
+#endif
diff --git a/src/util/integer.h b/src/util/integer.h
new file mode 100644
index 0000000..6327717
--- /dev/null
+++ b/src/util/integer.h
@@ -0,0 +1,218 @@
+/*
+ * 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_integer_h__
+#define INCLUDE_integer_h__
+
+/** @return true if p fits into the range of a size_t */
+GIT_INLINE(int) git__is_sizet(int64_t p)
+{
+ size_t r = (size_t)p;
+ return p == (int64_t)r;
+}
+
+/** @return true if p fits into the range of an ssize_t */
+GIT_INLINE(int) git__is_ssizet(size_t p)
+{
+ ssize_t r = (ssize_t)p;
+ return p == (size_t)r;
+}
+
+/** @return true if p fits into the range of a uint16_t */
+GIT_INLINE(int) git__is_uint16(size_t p)
+{
+ uint16_t r = (uint16_t)p;
+ return p == (size_t)r;
+}
+
+/** @return true if p fits into the range of a uint32_t */
+GIT_INLINE(int) git__is_uint32(size_t p)
+{
+ uint32_t r = (uint32_t)p;
+ return p == (size_t)r;
+}
+
+/** @return true if p fits into the range of an unsigned long */
+GIT_INLINE(int) git__is_ulong(int64_t p)
+{
+ unsigned long r = (unsigned long)p;
+ return p == (int64_t)r;
+}
+
+/** @return true if p fits into the range of an int */
+GIT_INLINE(int) git__is_int(int64_t p)
+{
+ int r = (int)p;
+ return p == (int64_t)r;
+}
+
+/* Use clang/gcc compiler intrinsics whenever possible */
+#if (__has_builtin(__builtin_add_overflow) || \
+ (defined(__GNUC__) && (__GNUC__ >= 5)))
+
+# if (SIZE_MAX == UINT_MAX)
+# define git__add_sizet_overflow(out, one, two) \
+ __builtin_uadd_overflow(one, two, out)
+# define git__multiply_sizet_overflow(out, one, two) \
+ __builtin_umul_overflow(one, two, out)
+# elif (SIZE_MAX == ULONG_MAX)
+# define git__add_sizet_overflow(out, one, two) \
+ __builtin_uaddl_overflow(one, two, out)
+# define git__multiply_sizet_overflow(out, one, two) \
+ __builtin_umull_overflow(one, two, out)
+# elif (SIZE_MAX == ULLONG_MAX)
+# define git__add_sizet_overflow(out, one, two) \
+ __builtin_uaddll_overflow(one, two, out)
+# define git__multiply_sizet_overflow(out, one, two) \
+ __builtin_umulll_overflow(one, two, out)
+# else
+# error compiler has add with overflow intrinsics but SIZE_MAX is unknown
+# endif
+
+# define git__add_int_overflow(out, one, two) \
+ __builtin_sadd_overflow(one, two, out)
+# define git__sub_int_overflow(out, one, two) \
+ __builtin_ssub_overflow(one, two, out)
+
+# define git__add_int64_overflow(out, one, two) \
+ __builtin_add_overflow(one, two, out)
+
+/* clang on 32-bit systems produces an undefined reference to `__mulodi4`. */
+# if !defined(__clang__) || !defined(GIT_ARCH_32)
+# define git__multiply_int64_overflow(out, one, two) \
+ __builtin_mul_overflow(one, two, out)
+# endif
+
+/* Use Microsoft's safe integer handling functions where available */
+#elif defined(_MSC_VER)
+
+# define ENABLE_INTSAFE_SIGNED_FUNCTIONS
+# include <intsafe.h>
+
+# define git__add_sizet_overflow(out, one, two) \
+ (SizeTAdd(one, two, out) != S_OK)
+# define git__multiply_sizet_overflow(out, one, two) \
+ (SizeTMult(one, two, out) != S_OK)
+
+#define git__add_int_overflow(out, one, two) \
+ (IntAdd(one, two, out) != S_OK)
+#define git__sub_int_overflow(out, one, two) \
+ (IntSub(one, two, out) != S_OK)
+
+#define git__add_int64_overflow(out, one, two) \
+ (LongLongAdd(one, two, out) != S_OK)
+#define git__multiply_int64_overflow(out, one, two) \
+ (LongLongMult(one, two, out) != S_OK)
+
+#else
+
+/**
+ * Sets `one + two` into `out`, unless the arithmetic would overflow.
+ * @return false if the result fits in a `size_t`, true on overflow.
+ */
+GIT_INLINE(bool) git__add_sizet_overflow(size_t *out, size_t one, size_t two)
+{
+ if (SIZE_MAX - one < two)
+ return true;
+ *out = one + two;
+ return false;
+}
+
+/**
+ * Sets `one * two` into `out`, unless the arithmetic would overflow.
+ * @return false if the result fits in a `size_t`, true on overflow.
+ */
+GIT_INLINE(bool) git__multiply_sizet_overflow(size_t *out, size_t one, size_t two)
+{
+ if (one && SIZE_MAX / one < two)
+ return true;
+ *out = one * two;
+ return false;
+}
+
+GIT_INLINE(bool) git__add_int_overflow(int *out, int one, int two)
+{
+ if ((two > 0 && one > (INT_MAX - two)) ||
+ (two < 0 && one < (INT_MIN - two)))
+ return true;
+ *out = one + two;
+ return false;
+}
+
+GIT_INLINE(bool) git__sub_int_overflow(int *out, int one, int two)
+{
+ if ((two > 0 && one < (INT_MIN + two)) ||
+ (two < 0 && one > (INT_MAX + two)))
+ return true;
+ *out = one - two;
+ return false;
+}
+
+GIT_INLINE(bool) git__add_int64_overflow(int64_t *out, int64_t one, int64_t two)
+{
+ if ((two > 0 && one > (INT64_MAX - two)) ||
+ (two < 0 && one < (INT64_MIN - two)))
+ return true;
+ *out = one + two;
+ return false;
+}
+
+#endif
+
+/* If we could not provide an intrinsic implementation for this, provide a (slow) fallback. */
+#if !defined(git__multiply_int64_overflow)
+GIT_INLINE(bool) git__multiply_int64_overflow(int64_t *out, int64_t one, int64_t two)
+{
+ /*
+ * Detects whether `INT64_MAX < (one * two) || INT64_MIN > (one * two)`,
+ * without incurring in undefined behavior. That is done by performing the
+ * comparison with a division instead of a multiplication, which translates
+ * to `INT64_MAX / one < two || INT64_MIN / one > two`. Some caveats:
+ *
+ * - The comparison sign is inverted when both sides of the inequality are
+ * multiplied/divided by a negative number, so if `one < 0` the comparison
+ * needs to be flipped.
+ * - `INT64_MAX / -1` itself overflows (or traps), so that case should be
+ * avoided.
+ * - Since the overflow flag is defined as the discrepance between the result
+ * of performing the multiplication in a signed integer at twice the width
+ * of the operands, and the truncated+sign-extended version of that same
+ * result, there are four cases where the result is the opposite of what
+ * would be expected:
+ * * `INT64_MIN * -1` / `-1 * INT64_MIN`
+ * * `INT64_MIN * 1 / `1 * INT64_MIN`
+ */
+ if (one && two) {
+ if (one > 0 && two > 0) {
+ if (INT64_MAX / one < two)
+ return true;
+ } else if (one < 0 && two < 0) {
+ if ((one == -1 && two == INT64_MIN) ||
+ (two == -1 && one == INT64_MIN)) {
+ *out = INT64_MIN;
+ return false;
+ }
+ if (INT64_MAX / one > two)
+ return true;
+ } else if (one > 0 && two < 0) {
+ if ((one == 1 && two == INT64_MIN) ||
+ (INT64_MIN / one > two))
+ return true;
+ } else if (one == -1) {
+ if (INT64_MIN / two > one)
+ return true;
+ } else {
+ if ((one == INT64_MIN && two == 1) ||
+ (INT64_MIN / one < two))
+ return true;
+ }
+ }
+ *out = one * two;
+ return false;
+}
+#endif
+
+#endif
diff --git a/src/util/khash.h b/src/util/khash.h
new file mode 100644
index 0000000..c9b7f13
--- /dev/null
+++ b/src/util/khash.h
@@ -0,0 +1,615 @@
+/* The MIT License
+
+ Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk>
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+
+/*
+ An example:
+
+#include "khash.h"
+KHASH_MAP_INIT_INT(32, char)
+int main() {
+ int ret, is_missing;
+ khiter_t k;
+ khash_t(32) *h = kh_init(32);
+ k = kh_put(32, h, 5, &ret);
+ kh_value(h, k) = 10;
+ k = kh_get(32, h, 10);
+ is_missing = (k == kh_end(h));
+ k = kh_get(32, h, 5);
+ kh_del(32, h, k);
+ for (k = kh_begin(h); k != kh_end(h); ++k)
+ if (kh_exist(h, k)) kh_value(h, k) = 1;
+ kh_destroy(32, h);
+ return 0;
+}
+*/
+
+/*
+ 2013-05-02 (0.2.8):
+
+ * Use quadratic probing. When the capacity is power of 2, stepping function
+ i*(i+1)/2 guarantees to traverse each bucket. It is better than double
+ hashing on cache performance and is more robust than linear probing.
+
+ In theory, double hashing should be more robust than quadratic probing.
+ However, my implementation is probably not for large hash tables, because
+ the second hash function is closely tied to the first hash function,
+ which reduce the effectiveness of double hashing.
+
+ Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php
+
+ 2011-12-29 (0.2.7):
+
+ * Minor code clean up; no actual effect.
+
+ 2011-09-16 (0.2.6):
+
+ * The capacity is a power of 2. This seems to dramatically improve the
+ speed for simple keys. Thank Zilong Tan for the suggestion. Reference:
+
+ - http://code.google.com/p/ulib/
+ - http://nothings.org/computer/judy/
+
+ * Allow to optionally use linear probing which usually has better
+ performance for random input. Double hashing is still the default as it
+ is more robust to certain non-random input.
+
+ * Added Wang's integer hash function (not used by default). This hash
+ function is more robust to certain non-random input.
+
+ 2011-02-14 (0.2.5):
+
+ * Allow to declare global functions.
+
+ 2009-09-26 (0.2.4):
+
+ * Improve portability
+
+ 2008-09-19 (0.2.3):
+
+ * Corrected the example
+ * Improved interfaces
+
+ 2008-09-11 (0.2.2):
+
+ * Improved speed a little in kh_put()
+
+ 2008-09-10 (0.2.1):
+
+ * Added kh_clear()
+ * Fixed a compiling error
+
+ 2008-09-02 (0.2.0):
+
+ * Changed to token concatenation which increases flexibility.
+
+ 2008-08-31 (0.1.2):
+
+ * Fixed a bug in kh_get(), which has not been tested previously.
+
+ 2008-08-31 (0.1.1):
+
+ * Added destructor
+*/
+
+
+#ifndef __AC_KHASH_H
+#define __AC_KHASH_H
+
+/*!
+ @header
+
+ Generic hash table library.
+ */
+
+#define AC_VERSION_KHASH_H "0.2.8"
+
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+/* compiler specific configuration */
+
+typedef uint32_t khint32_t;
+typedef uint64_t khint64_t;
+
+#ifndef kh_inline
+#ifdef _MSC_VER
+#define kh_inline __inline
+#elif defined(__GNUC__)
+#define kh_inline __inline__
+#else
+#define kh_inline
+#endif
+#endif /* kh_inline */
+
+typedef khint32_t khint_t;
+typedef khint_t khiter_t;
+
+#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2)
+#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1)
+#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3)
+#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1)))
+#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1)))
+#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1)))
+#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1))
+
+#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4)
+
+#ifndef kroundup32
+#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x))
+#endif
+
+#ifndef kcalloc
+#define kcalloc(N,Z) calloc(N,Z)
+#endif
+#ifndef kmalloc
+#define kmalloc(Z) malloc(Z)
+#endif
+#ifndef krealloc
+#define krealloc(P,Z) realloc(P,Z)
+#endif
+#ifndef kreallocarray
+#define kreallocarray(P,N,Z) ((SIZE_MAX - N < Z) ? NULL : krealloc(P, (N*Z)))
+#endif
+#ifndef kfree
+#define kfree(P) free(P)
+#endif
+
+static const double __ac_HASH_UPPER = 0.77;
+
+#define __KHASH_TYPE(name, khkey_t, khval_t) \
+ typedef struct kh_##name##_s { \
+ khint_t n_buckets, size, n_occupied, upper_bound; \
+ khint32_t *flags; \
+ khkey_t *keys; \
+ khval_t *vals; \
+ } kh_##name##_t;
+
+#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \
+ extern kh_##name##_t *kh_init_##name(void); \
+ extern void kh_destroy_##name(kh_##name##_t *h); \
+ extern void kh_clear_##name(kh_##name##_t *h); \
+ extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \
+ extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \
+ extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \
+ extern void kh_del_##name(kh_##name##_t *h, khint_t x);
+
+#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
+ SCOPE kh_##name##_t *kh_init_##name(void) { \
+ return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \
+ } \
+ SCOPE void kh_destroy_##name(kh_##name##_t *h) \
+ { \
+ if (h) { \
+ kfree((void *)h->keys); kfree(h->flags); \
+ kfree((void *)h->vals); \
+ kfree(h); \
+ } \
+ } \
+ SCOPE void kh_clear_##name(kh_##name##_t *h) \
+ { \
+ if (h && h->flags) { \
+ memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \
+ h->size = h->n_occupied = 0; \
+ } \
+ } \
+ SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \
+ { \
+ if (h->n_buckets) { \
+ khint_t k, i, last, mask, step = 0; \
+ mask = h->n_buckets - 1; \
+ k = __hash_func(key); i = k & mask; \
+ last = i; \
+ while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
+ i = (i + (++step)) & mask; \
+ if (i == last) return h->n_buckets; \
+ } \
+ return __ac_iseither(h->flags, i)? h->n_buckets : i; \
+ } else return 0; \
+ } \
+ SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \
+ { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \
+ khint32_t *new_flags = 0; \
+ khint_t j = 1; \
+ { \
+ kroundup32(new_n_buckets); \
+ if (new_n_buckets < 4) new_n_buckets = 4; \
+ if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \
+ else { /* hash table size to be changed (shrink or expand); rehash */ \
+ new_flags = (khint32_t*)kreallocarray(NULL, __ac_fsize(new_n_buckets), sizeof(khint32_t)); \
+ if (!new_flags) return -1; \
+ memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
+ if (h->n_buckets < new_n_buckets) { /* expand */ \
+ khkey_t *new_keys = (khkey_t*)kreallocarray((void *)h->keys, new_n_buckets, sizeof(khkey_t)); \
+ if (!new_keys) { kfree(new_flags); return -1; } \
+ h->keys = new_keys; \
+ if (kh_is_map) { \
+ khval_t *new_vals = (khval_t*)kreallocarray((void *)h->vals, new_n_buckets, sizeof(khval_t)); \
+ if (!new_vals) { kfree(new_flags); return -1; } \
+ h->vals = new_vals; \
+ } \
+ } /* otherwise shrink */ \
+ } \
+ } \
+ if (j) { /* rehashing is needed */ \
+ for (j = 0; j != h->n_buckets; ++j) { \
+ if (__ac_iseither(h->flags, j) == 0) { \
+ khkey_t key = h->keys[j]; \
+ khval_t val; \
+ khint_t new_mask; \
+ new_mask = new_n_buckets - 1; \
+ if (kh_is_map) val = h->vals[j]; \
+ __ac_set_isdel_true(h->flags, j); \
+ while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \
+ khint_t k, i, step = 0; \
+ k = __hash_func(key); \
+ i = k & new_mask; \
+ while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \
+ __ac_set_isempty_false(new_flags, i); \
+ if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \
+ { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \
+ if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \
+ __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \
+ } else { /* write the element and jump out of the loop */ \
+ h->keys[i] = key; \
+ if (kh_is_map) h->vals[i] = val; \
+ break; \
+ } \
+ } \
+ } \
+ } \
+ if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \
+ h->keys = (khkey_t*)kreallocarray((void *)h->keys, new_n_buckets, sizeof(khkey_t)); \
+ if (kh_is_map) h->vals = (khval_t*)kreallocarray((void *)h->vals, new_n_buckets, sizeof(khval_t)); \
+ } \
+ kfree(h->flags); /* free the working space */ \
+ h->flags = new_flags; \
+ h->n_buckets = new_n_buckets; \
+ h->n_occupied = h->size; \
+ h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \
+ } \
+ return 0; \
+ } \
+ SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \
+ { \
+ khint_t x; \
+ if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \
+ if (h->n_buckets > (h->size<<1)) { \
+ if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \
+ *ret = -1; return h->n_buckets; \
+ } \
+ } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \
+ *ret = -1; return h->n_buckets; \
+ } \
+ } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \
+ { \
+ khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \
+ x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \
+ if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \
+ else { \
+ last = i; \
+ while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
+ if (__ac_isdel(h->flags, i)) site = i; \
+ i = (i + (++step)) & mask; \
+ if (i == last) { x = site; break; } \
+ } \
+ if (x == h->n_buckets) { \
+ if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \
+ else x = i; \
+ } \
+ } \
+ } \
+ if (__ac_isempty(h->flags, x)) { /* not present at all */ \
+ h->keys[x] = key; \
+ __ac_set_isboth_false(h->flags, x); \
+ ++h->size; ++h->n_occupied; \
+ *ret = 1; \
+ } else if (__ac_isdel(h->flags, x)) { /* deleted */ \
+ h->keys[x] = key; \
+ __ac_set_isboth_false(h->flags, x); \
+ ++h->size; \
+ *ret = 2; \
+ } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \
+ return x; \
+ } \
+ SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \
+ { \
+ if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \
+ __ac_set_isdel_true(h->flags, x); \
+ --h->size; \
+ } \
+ }
+
+#define KHASH_DECLARE(name, khkey_t, khval_t) \
+ __KHASH_TYPE(name, khkey_t, khval_t) \
+ __KHASH_PROTOTYPES(name, khkey_t, khval_t)
+
+#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
+ __KHASH_TYPE(name, khkey_t, khval_t) \
+ __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
+
+#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
+ KHASH_INIT2(name, static kh_inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
+
+/* --- BEGIN OF HASH FUNCTIONS --- */
+
+/*! @function
+ @abstract Integer hash function
+ @param key The integer [khint32_t]
+ @return The hash value [khint_t]
+ */
+#define kh_int_hash_func(key) (khint32_t)(key)
+/*! @function
+ @abstract Integer comparison function
+ */
+#define kh_int_hash_equal(a, b) ((a) == (b))
+/*! @function
+ @abstract 64-bit integer hash function
+ @param key The integer [khint64_t]
+ @return The hash value [khint_t]
+ */
+#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11)
+/*! @function
+ @abstract 64-bit integer comparison function
+ */
+#define kh_int64_hash_equal(a, b) ((a) == (b))
+/*! @function
+ @abstract const char* hash function
+ @param s Pointer to a null terminated string
+ @return The hash value
+ */
+static kh_inline khint_t __ac_X31_hash_string(const char *s)
+{
+ khint_t h = (khint_t)*s;
+ if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s;
+ return h;
+}
+/*! @function
+ @abstract Another interface to const char* hash function
+ @param key Pointer to a null terminated string [const char*]
+ @return The hash value [khint_t]
+ */
+#define kh_str_hash_func(key) __ac_X31_hash_string(key)
+/*! @function
+ @abstract Const char* comparison function
+ */
+#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0)
+
+static kh_inline khint_t __ac_Wang_hash(khint_t key)
+{
+ key += ~(key << 15);
+ key ^= (key >> 10);
+ key += (key << 3);
+ key ^= (key >> 6);
+ key += ~(key << 11);
+ key ^= (key >> 16);
+ return key;
+}
+#define kh_int_hash_func2(k) __ac_Wang_hash((khint_t)key)
+
+/* --- END OF HASH FUNCTIONS --- */
+
+/* Other convenient macros... */
+
+/*!
+ @abstract Type of the hash table.
+ @param name Name of the hash table [symbol]
+ */
+#define khash_t(name) kh_##name##_t
+
+/*! @function
+ @abstract Initiate a hash table.
+ @param name Name of the hash table [symbol]
+ @return Pointer to the hash table [khash_t(name)*]
+ */
+#define kh_init(name) kh_init_##name()
+
+/*! @function
+ @abstract Destroy a hash table.
+ @param name Name of the hash table [symbol]
+ @param h Pointer to the hash table [khash_t(name)*]
+ */
+#define kh_destroy(name, h) kh_destroy_##name(h)
+
+/*! @function
+ @abstract Reset a hash table without deallocating memory.
+ @param name Name of the hash table [symbol]
+ @param h Pointer to the hash table [khash_t(name)*]
+ */
+#define kh_clear(name, h) kh_clear_##name(h)
+
+/*! @function
+ @abstract Resize a hash table.
+ @param name Name of the hash table [symbol]
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param s New size [khint_t]
+ */
+#define kh_resize(name, h, s) kh_resize_##name(h, s)
+
+/*! @function
+ @abstract Insert a key to the hash table.
+ @param name Name of the hash table [symbol]
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param k Key [type of keys]
+ @param r Extra return code: -1 if the operation failed;
+ 0 if the key is present in the hash table;
+ 1 if the bucket is empty (never used); 2 if the element in
+ the bucket has been deleted [int*]
+ @return Iterator to the inserted element [khint_t]
+ */
+#define kh_put(name, h, k, r) kh_put_##name(h, k, r)
+
+/*! @function
+ @abstract Retrieve a key from the hash table.
+ @param name Name of the hash table [symbol]
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param k Key [type of keys]
+ @return Iterator to the found element, or kh_end(h) if the element is absent [khint_t]
+ */
+#define kh_get(name, h, k) kh_get_##name(h, k)
+
+/*! @function
+ @abstract Remove a key from the hash table.
+ @param name Name of the hash table [symbol]
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param k Iterator to the element to be deleted [khint_t]
+ */
+#define kh_del(name, h, k) kh_del_##name(h, k)
+
+/*! @function
+ @abstract Test whether a bucket contains data.
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param x Iterator to the bucket [khint_t]
+ @return 1 if containing data; 0 otherwise [int]
+ */
+#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x)))
+
+/*! @function
+ @abstract Get key given an iterator
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param x Iterator to the bucket [khint_t]
+ @return Key [type of keys]
+ */
+#define kh_key(h, x) ((h)->keys[x])
+
+/*! @function
+ @abstract Get value given an iterator
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param x Iterator to the bucket [khint_t]
+ @return Value [type of values]
+ @discussion For hash sets, calling this results in segfault.
+ */
+#define kh_val(h, x) ((h)->vals[x])
+
+/*! @function
+ @abstract Alias of kh_val()
+ */
+#define kh_value(h, x) ((h)->vals[x])
+
+/*! @function
+ @abstract Get the start iterator
+ @param h Pointer to the hash table [khash_t(name)*]
+ @return The start iterator [khint_t]
+ */
+#define kh_begin(h) (khint_t)(0)
+
+/*! @function
+ @abstract Get the end iterator
+ @param h Pointer to the hash table [khash_t(name)*]
+ @return The end iterator [khint_t]
+ */
+#define kh_end(h) ((h)->n_buckets)
+
+/*! @function
+ @abstract Get the number of elements in the hash table
+ @param h Pointer to the hash table [khash_t(name)*]
+ @return Number of elements in the hash table [khint_t]
+ */
+#define kh_size(h) ((h)->size)
+
+/*! @function
+ @abstract Get the number of buckets in the hash table
+ @param h Pointer to the hash table [khash_t(name)*]
+ @return Number of buckets in the hash table [khint_t]
+ */
+#define kh_n_buckets(h) ((h)->n_buckets)
+
+/*! @function
+ @abstract Iterate over the entries in the hash table
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param kvar Variable to which key will be assigned
+ @param vvar Variable to which value will be assigned
+ @param code Block of code to execute
+ */
+#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \
+ for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \
+ if (!kh_exist(h,__i)) continue; \
+ (kvar) = kh_key(h,__i); \
+ (vvar) = kh_val(h,__i); \
+ code; \
+ } }
+
+/*! @function
+ @abstract Iterate over the values in the hash table
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param vvar Variable to which value will be assigned
+ @param code Block of code to execute
+ */
+#define kh_foreach_value(h, vvar, code) { khint_t __i; \
+ for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \
+ if (!kh_exist(h,__i)) continue; \
+ (vvar) = kh_val(h,__i); \
+ code; \
+ } }
+
+/* More convenient interfaces */
+
+/*! @function
+ @abstract Instantiate a hash set containing integer keys
+ @param name Name of the hash table [symbol]
+ */
+#define KHASH_SET_INIT_INT(name) \
+ KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal)
+
+/*! @function
+ @abstract Instantiate a hash map containing integer keys
+ @param name Name of the hash table [symbol]
+ @param khval_t Type of values [type]
+ */
+#define KHASH_MAP_INIT_INT(name, khval_t) \
+ KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal)
+
+/*! @function
+ @abstract Instantiate a hash map containing 64-bit integer keys
+ @param name Name of the hash table [symbol]
+ */
+#define KHASH_SET_INIT_INT64(name) \
+ KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal)
+
+/*! @function
+ @abstract Instantiate a hash map containing 64-bit integer keys
+ @param name Name of the hash table [symbol]
+ @param khval_t Type of values [type]
+ */
+#define KHASH_MAP_INIT_INT64(name, khval_t) \
+ KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal)
+
+typedef const char *kh_cstr_t;
+/*! @function
+ @abstract Instantiate a hash map containing const char* keys
+ @param name Name of the hash table [symbol]
+ */
+#define KHASH_SET_INIT_STR(name) \
+ KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal)
+
+/*! @function
+ @abstract Instantiate a hash map containing const char* keys
+ @param name Name of the hash table [symbol]
+ @param khval_t Type of values [type]
+ */
+#define KHASH_MAP_INIT_STR(name, khval_t) \
+ KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal)
+
+#endif /* __AC_KHASH_H */
diff --git a/src/util/map.h b/src/util/map.h
new file mode 100644
index 0000000..c101e46
--- /dev/null
+++ b/src/util/map.h
@@ -0,0 +1,46 @@
+/*
+ * 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_map_h__
+#define INCLUDE_map_h__
+
+#include "git2_util.h"
+
+
+/* p_mmap() prot values */
+#define GIT_PROT_NONE 0x0
+#define GIT_PROT_READ 0x1
+#define GIT_PROT_WRITE 0x2
+#define GIT_PROT_EXEC 0x4
+
+/* git__mmmap() flags values */
+#define GIT_MAP_FILE 0
+#define GIT_MAP_SHARED 1
+#define GIT_MAP_PRIVATE 2
+#define GIT_MAP_TYPE 0xf
+#define GIT_MAP_FIXED 0x10
+
+#ifdef __amigaos4__
+#define MAP_FAILED 0
+#endif
+
+typedef struct { /* memory mapped buffer */
+ void *data; /* data bytes */
+ size_t len; /* data length */
+#ifdef GIT_WIN32
+ HANDLE fmh; /* file mapping handle */
+#endif
+} git_map;
+
+#define GIT_MMAP_VALIDATE(out, len, prot, flags) do { \
+ GIT_ASSERT(out != NULL && len > 0); \
+ GIT_ASSERT((prot & GIT_PROT_WRITE) || (prot & GIT_PROT_READ)); \
+ GIT_ASSERT((flags & GIT_MAP_FIXED) == 0); } while (0)
+
+extern int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, off64_t offset);
+extern int p_munmap(git_map *map);
+
+#endif
diff --git a/src/util/net.c b/src/util/net.c
new file mode 100644
index 0000000..afd52ce
--- /dev/null
+++ b/src/util/net.c
@@ -0,0 +1,1154 @@
+/*
+ * 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 "net.h"
+
+#include <ctype.h>
+
+#include "posix.h"
+#include "str.h"
+#include "http_parser.h"
+#include "runtime.h"
+
+#define DEFAULT_PORT_HTTP "80"
+#define DEFAULT_PORT_HTTPS "443"
+#define DEFAULT_PORT_GIT "9418"
+#define DEFAULT_PORT_SSH "22"
+
+#define GIT_NET_URL_PARSER_INIT { 0 }
+
+typedef struct {
+ int hierarchical : 1;
+
+ const char *scheme;
+ const char *user;
+ const char *password;
+ const char *host;
+ const char *port;
+ const char *path;
+ const char *query;
+ const char *fragment;
+
+ size_t scheme_len;
+ size_t user_len;
+ size_t password_len;
+ size_t host_len;
+ size_t port_len;
+ size_t path_len;
+ size_t query_len;
+ size_t fragment_len;
+} git_net_url_parser;
+
+bool git_net_hostname_matches_cert(
+ const char *hostname,
+ const char *pattern)
+{
+ for (;;) {
+ char c = git__tolower(*pattern++);
+
+ if (c == '\0')
+ return *hostname ? false : true;
+
+ if (c == '*') {
+ c = *pattern;
+
+ /* '*' at the end matches everything left */
+ if (c == '\0')
+ return true;
+
+ /*
+ * We've found a pattern, so move towards the
+ * next matching char. The '.' is handled
+ * specially because wildcards aren't allowed
+ * to cross subdomains.
+ */
+ while(*hostname) {
+ char h = git__tolower(*hostname);
+
+ if (h == c)
+ return git_net_hostname_matches_cert(hostname++, pattern);
+ else if (h == '.')
+ return git_net_hostname_matches_cert(hostname, pattern);
+
+ hostname++;
+ }
+
+ return false;
+ }
+
+ if (c != git__tolower(*hostname++))
+ return false;
+ }
+
+ return false;
+}
+
+#define is_valid_scheme_char(c) \
+ (((c) >= 'a' && (c) <= 'z') || \
+ ((c) >= 'A' && (c) <= 'Z') || \
+ ((c) >= '0' && (c) <= '9') || \
+ (c) == '+' || (c) == '-' || (c) == '.')
+
+bool git_net_str_is_url(const char *str)
+{
+ const char *c;
+
+ for (c = str; *c; c++) {
+ if (*c == ':' && *(c+1) == '/' && *(c+2) == '/')
+ return true;
+
+ if (!is_valid_scheme_char(*c))
+ break;
+ }
+
+ return false;
+}
+
+static const char *default_port_for_scheme(const char *scheme)
+{
+ if (strcmp(scheme, "http") == 0)
+ return DEFAULT_PORT_HTTP;
+ else if (strcmp(scheme, "https") == 0)
+ return DEFAULT_PORT_HTTPS;
+ else if (strcmp(scheme, "git") == 0)
+ return DEFAULT_PORT_GIT;
+ else if (strcmp(scheme, "ssh") == 0 ||
+ strcmp(scheme, "ssh+git") == 0 ||
+ strcmp(scheme, "git+ssh") == 0)
+ return DEFAULT_PORT_SSH;
+
+ return NULL;
+}
+
+static bool is_ssh_scheme(const char *scheme, size_t scheme_len)
+{
+ if (!scheme_len)
+ return false;
+
+ return strncasecmp(scheme, "ssh", scheme_len) == 0 ||
+ strncasecmp(scheme, "ssh+git", scheme_len) == 0 ||
+ strncasecmp(scheme, "git+ssh", scheme_len) == 0;
+}
+
+int git_net_url_dup(git_net_url *out, git_net_url *in)
+{
+ if (in->scheme) {
+ out->scheme = git__strdup(in->scheme);
+ GIT_ERROR_CHECK_ALLOC(out->scheme);
+ }
+
+ if (in->host) {
+ out->host = git__strdup(in->host);
+ GIT_ERROR_CHECK_ALLOC(out->host);
+ }
+
+ if (in->port) {
+ out->port = git__strdup(in->port);
+ GIT_ERROR_CHECK_ALLOC(out->port);
+ }
+
+ if (in->path) {
+ out->path = git__strdup(in->path);
+ GIT_ERROR_CHECK_ALLOC(out->path);
+ }
+
+ if (in->query) {
+ out->query = git__strdup(in->query);
+ GIT_ERROR_CHECK_ALLOC(out->query);
+ }
+
+ if (in->username) {
+ out->username = git__strdup(in->username);
+ GIT_ERROR_CHECK_ALLOC(out->username);
+ }
+
+ if (in->password) {
+ out->password = git__strdup(in->password);
+ GIT_ERROR_CHECK_ALLOC(out->password);
+ }
+
+ return 0;
+}
+
+static int url_invalid(const char *message)
+{
+ git_error_set(GIT_ERROR_NET, "invalid url: %s", message);
+ return GIT_EINVALIDSPEC;
+}
+
+static int url_parse_authority(
+ git_net_url_parser *parser,
+ const char *authority,
+ size_t len)
+{
+ const char *c, *hostport_end, *host_end = NULL,
+ *userpass_end, *user_end = NULL;
+
+ enum {
+ HOSTPORT, HOST, IPV6, HOST_END, USERPASS, USER
+ } state = HOSTPORT;
+
+ if (len == 0)
+ return 0;
+
+ /*
+ * walk the authority backwards so that we can parse google code's
+ * ssh urls that are not rfc compliant and allow @ in the username
+ */
+ for (hostport_end = authority + len, c = hostport_end - 1;
+ c >= authority && !user_end;
+ c--) {
+ switch (state) {
+ case HOSTPORT:
+ if (*c == ':') {
+ parser->port = c + 1;
+ parser->port_len = hostport_end - parser->port;
+ host_end = c;
+ state = HOST;
+ break;
+ }
+
+ /*
+ * if we've only seen digits then we don't know
+ * if we're parsing just a host or a host and port.
+ * if we see a non-digit, then we're in a host,
+ * otherwise, fall through to possibly match the
+ * "@" (user/host separator).
+ */
+
+ if (*c < '0' || *c > '9') {
+ host_end = hostport_end;
+ state = HOST;
+ }
+
+ /* fall through */
+
+ case HOST:
+ if (*c == ']' && host_end == c + 1) {
+ host_end = c;
+ state = IPV6;
+ }
+
+ else if (*c == '@') {
+ parser->host = c + 1;
+ parser->host_len = host_end ?
+ host_end - parser->host :
+ hostport_end - parser->host;
+ userpass_end = c;
+ state = USERPASS;
+ }
+
+ else if (*c == '[' || *c == ']' || *c == ':') {
+ return url_invalid("malformed hostname");
+ }
+
+ break;
+
+ case IPV6:
+ if (*c == '[') {
+ parser->host = c + 1;
+ parser->host_len = host_end - parser->host;
+ state = HOST_END;
+ }
+
+ else if ((*c < '0' || *c > '9') &&
+ (*c < 'a' || *c > 'f') &&
+ (*c < 'A' || *c > 'F') &&
+ (*c != ':')) {
+ return url_invalid("malformed hostname");
+ }
+
+ break;
+
+ case HOST_END:
+ if (*c == '@') {
+ userpass_end = c;
+ state = USERPASS;
+ break;
+ }
+
+ return url_invalid("malformed hostname");
+
+ case USERPASS:
+ if (*c == '@' &&
+ !is_ssh_scheme(parser->scheme, parser->scheme_len))
+ return url_invalid("malformed hostname");
+
+ if (*c == ':') {
+ parser->password = c + 1;
+ parser->password_len = userpass_end - parser->password;
+ user_end = c;
+ state = USER;
+ break;
+ }
+
+ break;
+
+ default:
+ GIT_ASSERT(!"unhandled state");
+ }
+ }
+
+ switch (state) {
+ case HOSTPORT:
+ parser->host = authority;
+ parser->host_len = (hostport_end - parser->host);
+ break;
+ case HOST:
+ parser->host = authority;
+ parser->host_len = (host_end - parser->host);
+ break;
+ case IPV6:
+ return url_invalid("malformed hostname");
+ case HOST_END:
+ break;
+ case USERPASS:
+ parser->user = authority;
+ parser->user_len = (userpass_end - parser->user);
+ break;
+ case USER:
+ parser->user = authority;
+ parser->user_len = (user_end - parser->user);
+ break;
+ default:
+ GIT_ASSERT(!"unhandled state");
+ }
+
+ return 0;
+}
+
+static int url_parse_path(
+ git_net_url_parser *parser,
+ const char *path,
+ size_t len)
+{
+ const char *c, *end;
+
+ enum { PATH, QUERY, FRAGMENT } state = PATH;
+
+ parser->path = path;
+ end = path + len;
+
+ for (c = path; c < end; c++) {
+ switch (state) {
+ case PATH:
+ switch (*c) {
+ case '?':
+ parser->path_len = (c - parser->path);
+ parser->query = c + 1;
+ state = QUERY;
+ break;
+ case '#':
+ parser->path_len = (c - parser->path);
+ parser->fragment = c + 1;
+ state = FRAGMENT;
+ break;
+ }
+ break;
+
+ case QUERY:
+ if (*c == '#') {
+ parser->query_len = (c - parser->query);
+ parser->fragment = c + 1;
+ state = FRAGMENT;
+ }
+ break;
+
+ case FRAGMENT:
+ break;
+
+ default:
+ GIT_ASSERT(!"unhandled state");
+ }
+ }
+
+ switch (state) {
+ case PATH:
+ parser->path_len = (c - parser->path);
+ break;
+ case QUERY:
+ parser->query_len = (c - parser->query);
+ break;
+ case FRAGMENT:
+ parser->fragment_len = (c - parser->fragment);
+ break;
+ }
+
+ return 0;
+}
+
+static int url_parse_finalize(git_net_url *url, git_net_url_parser *parser)
+{
+ git_str scheme = GIT_STR_INIT, user = GIT_STR_INIT,
+ password = GIT_STR_INIT, host = GIT_STR_INIT,
+ port = GIT_STR_INIT, path = GIT_STR_INIT,
+ query = GIT_STR_INIT, fragment = GIT_STR_INIT;
+ const char *default_port;
+ int error = 0;
+
+ if (parser->scheme_len) {
+ if ((error = git_str_put(&scheme, parser->scheme, parser->scheme_len)) < 0)
+ goto done;
+
+ git__strntolower(scheme.ptr, scheme.size);
+ }
+
+ if (parser->user_len &&
+ (error = git_str_decode_percent(&user, parser->user, parser->user_len)) < 0)
+ goto done;
+
+ if (parser->password_len &&
+ (error = git_str_decode_percent(&password, parser->password, parser->password_len)) < 0)
+ goto done;
+
+ if (parser->host_len &&
+ (error = git_str_decode_percent(&host, parser->host, parser->host_len)) < 0)
+ goto done;
+
+ if (parser->port_len)
+ error = git_str_put(&port, parser->port, parser->port_len);
+ else if (parser->scheme_len && (default_port = default_port_for_scheme(scheme.ptr)) != NULL)
+ error = git_str_puts(&port, default_port);
+
+ if (error < 0)
+ goto done;
+
+ if (parser->path_len)
+ error = git_str_put(&path, parser->path, parser->path_len);
+ else if (parser->hierarchical)
+ error = git_str_puts(&path, "/");
+
+ if (error < 0)
+ goto done;
+
+ if (parser->query_len &&
+ (error = git_str_decode_percent(&query, parser->query, parser->query_len)) < 0)
+ goto done;
+
+ if (parser->fragment_len &&
+ (error = git_str_decode_percent(&fragment, parser->fragment, parser->fragment_len)) < 0)
+ goto done;
+
+ url->scheme = git_str_detach(&scheme);
+ url->host = git_str_detach(&host);
+ url->port = git_str_detach(&port);
+ url->path = git_str_detach(&path);
+ url->query = git_str_detach(&query);
+ url->fragment = git_str_detach(&fragment);
+ url->username = git_str_detach(&user);
+ url->password = git_str_detach(&password);
+
+ error = 0;
+
+done:
+ git_str_dispose(&scheme);
+ git_str_dispose(&user);
+ git_str_dispose(&password);
+ git_str_dispose(&host);
+ git_str_dispose(&port);
+ git_str_dispose(&path);
+ git_str_dispose(&query);
+ git_str_dispose(&fragment);
+
+ return error;
+}
+
+int git_net_url_parse(git_net_url *url, const char *given)
+{
+ git_net_url_parser parser = GIT_NET_URL_PARSER_INIT;
+ const char *c, *authority, *path;
+ size_t authority_len = 0, path_len = 0;
+ int error = 0;
+
+ enum {
+ SCHEME_START, SCHEME,
+ AUTHORITY_START, AUTHORITY,
+ PATH_START, PATH
+ } state = SCHEME_START;
+
+ memset(url, 0, sizeof(git_net_url));
+
+ for (c = given; *c; c++) {
+ switch (state) {
+ case SCHEME_START:
+ parser.scheme = c;
+ state = SCHEME;
+
+ /* fall through */
+
+ case SCHEME:
+ if (*c == ':') {
+ parser.scheme_len = (c - parser.scheme);
+
+ if (parser.scheme_len &&
+ *(c+1) == '/' && *(c+2) == '/') {
+ c += 2;
+ parser.hierarchical = 1;
+ state = AUTHORITY_START;
+ } else {
+ state = PATH_START;
+ }
+ } else if (!is_valid_scheme_char(*c)) {
+ /*
+ * an illegal scheme character means that we
+ * were just given a relative path
+ */
+ path = given;
+ state = PATH;
+ break;
+ }
+ break;
+
+ case AUTHORITY_START:
+ authority = c;
+ state = AUTHORITY;
+
+ /* fall through */
+ case AUTHORITY:
+ if (*c != '/')
+ break;
+
+ authority_len = (c - authority);
+
+ /* fall through */
+ case PATH_START:
+ path = c;
+ state = PATH;
+ break;
+
+ case PATH:
+ break;
+
+ default:
+ GIT_ASSERT(!"unhandled state");
+ }
+ }
+
+ switch (state) {
+ case SCHEME:
+ /*
+ * if we never saw a ':' then we were given a relative
+ * path, not a bare scheme
+ */
+ path = given;
+ path_len = (c - path);
+ break;
+ case AUTHORITY_START:
+ break;
+ case AUTHORITY:
+ authority_len = (c - authority);
+ break;
+ case PATH_START:
+ break;
+ case PATH:
+ path_len = (c - path);
+ break;
+ default:
+ GIT_ASSERT(!"unhandled state");
+ }
+
+ if (authority_len &&
+ (error = url_parse_authority(&parser, authority, authority_len)) < 0)
+ goto done;
+
+ if (path_len &&
+ (error = url_parse_path(&parser, path, path_len)) < 0)
+ goto done;
+
+ error = url_parse_finalize(url, &parser);
+
+done:
+ return error;
+}
+
+int git_net_url_parse_http(
+ git_net_url *url,
+ const char *given)
+{
+ git_net_url_parser parser = GIT_NET_URL_PARSER_INIT;
+ const char *c, *authority, *path = NULL;
+ size_t authority_len = 0, path_len = 0;
+ int error;
+
+ /* Hopefully this is a proper URL with a scheme. */
+ if (git_net_str_is_url(given))
+ return git_net_url_parse(url, given);
+
+ memset(url, 0, sizeof(git_net_url));
+
+ /* Without a scheme, we are in the host (authority) section. */
+ for (c = authority = given; *c; c++) {
+ if (!path && *c == '/') {
+ authority_len = (c - authority);
+ path = c;
+ }
+ }
+
+ if (path)
+ path_len = (c - path);
+ else
+ authority_len = (c - authority);
+
+ parser.scheme = "http";
+ parser.scheme_len = 4;
+ parser.hierarchical = 1;
+
+ if (authority_len &&
+ (error = url_parse_authority(&parser, authority, authority_len)) < 0)
+ return error;
+
+ if (path_len &&
+ (error = url_parse_path(&parser, path, path_len)) < 0)
+ return error;
+
+ return url_parse_finalize(url, &parser);
+}
+
+static int scp_invalid(const char *message)
+{
+ git_error_set(GIT_ERROR_NET, "invalid scp-style path: %s", message);
+ return GIT_EINVALIDSPEC;
+}
+
+static bool is_ipv6(const char *str)
+{
+ const char *c;
+ size_t colons = 0;
+
+ if (*str++ != '[')
+ return false;
+
+ for (c = str; *c; c++) {
+ if (*c == ':')
+ colons++;
+
+ if (*c == ']')
+ return (colons > 1);
+
+ if (*c != ':' &&
+ (*c < '0' || *c > '9') &&
+ (*c < 'a' || *c > 'f') &&
+ (*c < 'A' || *c > 'F'))
+ return false;
+ }
+
+ return false;
+}
+
+static bool has_at(const char *str)
+{
+ const char *c;
+
+ for (c = str; *c; c++) {
+ if (*c == '@')
+ return true;
+
+ if (*c == ':')
+ break;
+ }
+
+ return false;
+}
+
+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;
+ size_t user_len = 0, host_len = 0, port_len = 0;
+ unsigned short bracket = 0;
+
+ enum {
+ NONE,
+ USER,
+ HOST_START, HOST, HOST_END,
+ IPV6, IPV6_END,
+ PORT_START, PORT, PORT_END,
+ PATH_START
+ } state = NONE;
+
+ memset(url, 0, sizeof(git_net_url));
+
+ for (c = given; *c && !path; c++) {
+ switch (state) {
+ case NONE:
+ switch (*c) {
+ case '@':
+ return scp_invalid("unexpected '@'");
+ case ':':
+ return scp_invalid("unexpected ':'");
+ case '[':
+ if (is_ipv6(c)) {
+ state = IPV6;
+ host = c;
+ } else if (bracket++ > 1) {
+ return scp_invalid("unexpected '['");
+ }
+ break;
+ default:
+ if (has_at(c)) {
+ state = USER;
+ user = c;
+ } else {
+ state = HOST;
+ host = c;
+ }
+ break;
+ }
+ break;
+
+ case USER:
+ if (*c == '@') {
+ user_len = (c - user);
+ state = HOST_START;
+ }
+ break;
+
+ case HOST_START:
+ state = (*c == '[') ? IPV6 : HOST;
+ host = c;
+ break;
+
+ case HOST:
+ if (*c == ':') {
+ host_len = (c - host);
+ state = bracket ? PORT_START : PATH_START;
+ } else if (*c == ']') {
+ if (bracket-- == 0)
+ return scp_invalid("unexpected ']'");
+
+ host_len = (c - host);
+ state = HOST_END;
+ }
+ break;
+
+ case HOST_END:
+ if (*c != ':')
+ return scp_invalid("unexpected character after hostname");
+ state = PATH_START;
+ break;
+
+ case IPV6:
+ if (*c == ']')
+ state = IPV6_END;
+ break;
+
+ case IPV6_END:
+ if (*c != ':')
+ return scp_invalid("unexpected character after ipv6 address");
+
+ host_len = (c - host);
+ state = bracket ? PORT_START : PATH_START;
+ break;
+
+ case PORT_START:
+ port = c;
+ state = PORT;
+ break;
+
+ case PORT:
+ if (*c == ']') {
+ if (bracket-- == 0)
+ return scp_invalid("unexpected ']'");
+
+ port_len = c - port;
+ state = PORT_END;
+ }
+ break;
+
+ case PORT_END:
+ if (*c != ':')
+ return scp_invalid("unexpected character after ipv6 address");
+
+ state = PATH_START;
+ break;
+
+ case PATH_START:
+ path = c;
+ break;
+
+ default:
+ GIT_ASSERT(!"unhandled state");
+ }
+ }
+
+ if (!path)
+ return scp_invalid("path is required");
+
+ GIT_ERROR_CHECK_ALLOC(url->scheme = git__strdup("ssh"));
+
+ if (user_len)
+ GIT_ERROR_CHECK_ALLOC(url->username = git__strndup(user, user_len));
+
+ GIT_ASSERT(host_len);
+ GIT_ERROR_CHECK_ALLOC(url->host = git__strndup(host, host_len));
+
+ if (port_len)
+ GIT_ERROR_CHECK_ALLOC(url->port = git__strndup(port, port_len));
+ else
+ GIT_ERROR_CHECK_ALLOC(url->port = git__strdup(default_port));
+
+ GIT_ASSERT(path);
+ GIT_ERROR_CHECK_ALLOC(url->path = git__strdup(path));
+
+ return 0;
+}
+
+int git_net_url_parse_standard_or_scp(git_net_url *url, const char *given)
+{
+ return git_net_str_is_url(given) ?
+ git_net_url_parse(url, given) :
+ git_net_url_parse_scp(url, given);
+}
+
+int git_net_url_joinpath(
+ git_net_url *out,
+ git_net_url *one,
+ const char *two)
+{
+ git_str path = GIT_STR_INIT;
+ const char *query;
+ size_t one_len, two_len;
+
+ git_net_url_dispose(out);
+
+ if ((query = strchr(two, '?')) != NULL) {
+ two_len = query - two;
+
+ if (*(++query) != '\0') {
+ out->query = git__strdup(query);
+ GIT_ERROR_CHECK_ALLOC(out->query);
+ }
+ } else {
+ two_len = strlen(two);
+ }
+
+ /* Strip all trailing `/`s from the first path */
+ one_len = one->path ? strlen(one->path) : 0;
+ while (one_len && one->path[one_len - 1] == '/')
+ one_len--;
+
+ /* Strip all leading `/`s from the second path */
+ while (*two == '/') {
+ two++;
+ two_len--;
+ }
+
+ git_str_put(&path, one->path, one_len);
+ git_str_putc(&path, '/');
+ git_str_put(&path, two, two_len);
+
+ if (git_str_oom(&path))
+ return -1;
+
+ out->path = git_str_detach(&path);
+
+ if (one->scheme) {
+ out->scheme = git__strdup(one->scheme);
+ GIT_ERROR_CHECK_ALLOC(out->scheme);
+ }
+
+ if (one->host) {
+ out->host = git__strdup(one->host);
+ GIT_ERROR_CHECK_ALLOC(out->host);
+ }
+
+ if (one->port) {
+ out->port = git__strdup(one->port);
+ GIT_ERROR_CHECK_ALLOC(out->port);
+ }
+
+ if (one->username) {
+ out->username = git__strdup(one->username);
+ GIT_ERROR_CHECK_ALLOC(out->username);
+ }
+
+ if (one->password) {
+ out->password = git__strdup(one->password);
+ GIT_ERROR_CHECK_ALLOC(out->password);
+ }
+
+ return 0;
+}
+
+/*
+ * Some servers strip the query parameters from the Location header
+ * when sending a redirect. Others leave it in place.
+ * Check for both, starting with the stripped case first,
+ * since it appears to be more common.
+ */
+static void remove_service_suffix(
+ git_net_url *url,
+ const char *service_suffix)
+{
+ const char *service_query = strchr(service_suffix, '?');
+ size_t full_suffix_len = strlen(service_suffix);
+ size_t suffix_len = service_query ?
+ (size_t)(service_query - service_suffix) : full_suffix_len;
+ size_t path_len = strlen(url->path);
+ ssize_t truncate = -1;
+
+ /*
+ * Check for a redirect without query parameters,
+ * like "/newloc/info/refs"'
+ */
+ if (suffix_len && path_len >= suffix_len) {
+ size_t suffix_offset = path_len - suffix_len;
+
+ if (git__strncmp(url->path + suffix_offset, service_suffix, suffix_len) == 0 &&
+ (!service_query || git__strcmp(url->query, service_query + 1) == 0)) {
+ truncate = suffix_offset;
+ }
+ }
+
+ /*
+ * If we haven't already found where to truncate to remove the
+ * suffix, check for a redirect with query parameters, like
+ * "/newloc/info/refs?service=git-upload-pack"
+ */
+ if (truncate < 0 && git__suffixcmp(url->path, service_suffix) == 0)
+ truncate = path_len - full_suffix_len;
+
+ /* Ensure we leave a minimum of '/' as the path */
+ if (truncate == 0)
+ truncate++;
+
+ if (truncate > 0) {
+ url->path[truncate] = '\0';
+
+ git__free(url->query);
+ url->query = NULL;
+ }
+}
+
+int git_net_url_apply_redirect(
+ git_net_url *url,
+ const char *redirect_location,
+ bool allow_offsite,
+ const char *service_suffix)
+{
+ git_net_url tmp = GIT_NET_URL_INIT;
+ int error = 0;
+
+ GIT_ASSERT(url);
+ GIT_ASSERT(redirect_location);
+
+ if (redirect_location[0] == '/') {
+ git__free(url->path);
+
+ if ((url->path = git__strdup(redirect_location)) == NULL) {
+ error = -1;
+ goto done;
+ }
+ } else {
+ git_net_url *original = url;
+
+ if ((error = git_net_url_parse(&tmp, redirect_location)) < 0)
+ goto done;
+
+ /* Validate that this is a legal redirection */
+
+ if (original->scheme &&
+ strcmp(original->scheme, tmp.scheme) != 0 &&
+ strcmp(tmp.scheme, "https") != 0) {
+ git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'",
+ original->scheme, tmp.scheme);
+
+ error = -1;
+ goto done;
+ }
+
+ if (original->host &&
+ !allow_offsite &&
+ git__strcasecmp(original->host, tmp.host) != 0) {
+ git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'",
+ original->host, tmp.host);
+
+ error = -1;
+ goto done;
+ }
+
+ git_net_url_swap(url, &tmp);
+ }
+
+ /* Remove the service suffix if it was given to us */
+ if (service_suffix)
+ remove_service_suffix(url, service_suffix);
+
+done:
+ git_net_url_dispose(&tmp);
+ return error;
+}
+
+bool git_net_url_valid(git_net_url *url)
+{
+ return (url->host && url->port && url->path);
+}
+
+bool git_net_url_is_default_port(git_net_url *url)
+{
+ const char *default_port;
+
+ if (url->scheme && (default_port = default_port_for_scheme(url->scheme)) != NULL)
+ return (strcmp(url->port, default_port) == 0);
+ else
+ return false;
+}
+
+bool git_net_url_is_ipv6(git_net_url *url)
+{
+ return (strchr(url->host, ':') != NULL);
+}
+
+void git_net_url_swap(git_net_url *a, git_net_url *b)
+{
+ git_net_url tmp = GIT_NET_URL_INIT;
+
+ memcpy(&tmp, a, sizeof(git_net_url));
+ memcpy(a, b, sizeof(git_net_url));
+ memcpy(b, &tmp, sizeof(git_net_url));
+}
+
+int git_net_url_fmt(git_str *buf, git_net_url *url)
+{
+ GIT_ASSERT_ARG(url);
+ GIT_ASSERT_ARG(url->scheme);
+ GIT_ASSERT_ARG(url->host);
+
+ git_str_puts(buf, url->scheme);
+ git_str_puts(buf, "://");
+
+ if (url->username) {
+ git_str_puts(buf, url->username);
+
+ if (url->password) {
+ git_str_puts(buf, ":");
+ git_str_puts(buf, url->password);
+ }
+
+ git_str_putc(buf, '@');
+ }
+
+ git_str_puts(buf, url->host);
+
+ if (url->port && !git_net_url_is_default_port(url)) {
+ git_str_putc(buf, ':');
+ git_str_puts(buf, url->port);
+ }
+
+ git_str_puts(buf, url->path ? url->path : "/");
+
+ if (url->query) {
+ git_str_putc(buf, '?');
+ git_str_puts(buf, url->query);
+ }
+
+ return git_str_oom(buf) ? -1 : 0;
+}
+
+int git_net_url_fmt_path(git_str *buf, git_net_url *url)
+{
+ git_str_puts(buf, url->path ? url->path : "/");
+
+ if (url->query) {
+ git_str_putc(buf, '?');
+ git_str_puts(buf, url->query);
+ }
+
+ return git_str_oom(buf) ? -1 : 0;
+}
+
+static bool matches_pattern(
+ git_net_url *url,
+ const char *pattern,
+ size_t pattern_len)
+{
+ const char *domain, *port = NULL, *colon;
+ size_t host_len, domain_len, port_len = 0, wildcard = 0;
+
+ GIT_UNUSED(url);
+ GIT_UNUSED(pattern);
+
+ if (!pattern_len)
+ return false;
+ else if (pattern_len == 1 && pattern[0] == '*')
+ return true;
+ else if (pattern_len > 1 && pattern[0] == '*' && pattern[1] == '.')
+ wildcard = 2;
+ else if (pattern[0] == '.')
+ wildcard = 1;
+
+ domain = pattern + wildcard;
+ domain_len = pattern_len - wildcard;
+
+ if ((colon = memchr(domain, ':', domain_len)) != NULL) {
+ domain_len = colon - domain;
+ port = colon + 1;
+ port_len = pattern_len - wildcard - domain_len - 1;
+ }
+
+ /* A pattern's port *must* match if it's specified */
+ if (port_len && git__strlcmp(url->port, port, port_len) != 0)
+ return false;
+
+ /* No wildcard? Host must match exactly. */
+ if (!wildcard)
+ return !git__strlcmp(url->host, domain, domain_len);
+
+ /* Wildcard: ensure there's (at least) a suffix match */
+ if ((host_len = strlen(url->host)) < domain_len ||
+ memcmp(url->host + (host_len - domain_len), domain, domain_len))
+ return false;
+
+ /* The pattern is *.domain and the host is simply domain */
+ if (host_len == domain_len)
+ return true;
+
+ /* The pattern is *.domain and the host is foo.domain */
+ return (url->host[host_len - domain_len - 1] == '.');
+}
+
+bool git_net_url_matches_pattern(git_net_url *url, const char *pattern)
+{
+ return matches_pattern(url, pattern, strlen(pattern));
+}
+
+bool git_net_url_matches_pattern_list(
+ git_net_url *url,
+ const char *pattern_list)
+{
+ const char *pattern, *pattern_end, *sep;
+
+ for (pattern = pattern_list;
+ pattern && *pattern;
+ pattern = sep ? sep + 1 : NULL) {
+ sep = strchr(pattern, ',');
+ pattern_end = sep ? sep : strchr(pattern, '\0');
+
+ if (matches_pattern(url, pattern, (pattern_end - pattern)))
+ return true;
+ }
+
+ return false;
+}
+
+void git_net_url_dispose(git_net_url *url)
+{
+ if (url->username)
+ git__memzero(url->username, strlen(url->username));
+
+ if (url->password)
+ git__memzero(url->password, strlen(url->password));
+
+ git__free(url->scheme); url->scheme = NULL;
+ git__free(url->host); url->host = NULL;
+ git__free(url->port); url->port = NULL;
+ git__free(url->path); url->path = NULL;
+ git__free(url->query); url->query = NULL;
+ git__free(url->fragment); url->fragment = NULL;
+ git__free(url->username); url->username = NULL;
+ git__free(url->password); url->password = NULL;
+}
diff --git a/src/util/net.h b/src/util/net.h
new file mode 100644
index 0000000..8024956
--- /dev/null
+++ b/src/util/net.h
@@ -0,0 +1,110 @@
+/*
+ * 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_net_h__
+#define INCLUDE_net_h__
+
+#include "git2_util.h"
+
+/*
+ * Hostname handling
+ */
+
+/*
+ * See if a given hostname matches a certificate name pattern, according
+ * to RFC2818 rules (which specifies HTTP over TLS). Mainly, an asterisk
+ * matches anything, but is limited to a single url component.
+ */
+extern bool git_net_hostname_matches_cert(
+ const char *hostname,
+ const char *pattern);
+
+/*
+ * URL handling
+ */
+
+typedef struct git_net_url {
+ char *scheme;
+ char *host;
+ char *port;
+ char *path;
+ char *query;
+ char *fragment;
+ char *username;
+ char *password;
+} git_net_url;
+
+#define GIT_NET_URL_INIT { NULL }
+
+/** Is a given string a url? */
+extern bool git_net_str_is_url(const char *str);
+
+/** Duplicate a URL */
+extern int git_net_url_dup(git_net_url *out, git_net_url *in);
+
+/** Parses a string containing a URL into a structure. */
+extern int git_net_url_parse(git_net_url *url, const char *str);
+
+/** Parses a string containing an SCP style path into a URL structure. */
+extern int git_net_url_parse_scp(git_net_url *url, const char *str);
+
+/**
+ * Parses a string containing a standard URL or an SCP style path into
+ * a URL structure.
+ */
+extern int git_net_url_parse_standard_or_scp(git_net_url *url, const char *str);
+
+/**
+ * Parses a string containing an HTTP endpoint that may not be a
+ * well-formed URL. For example, "localhost" or "localhost:port".
+ */
+extern int git_net_url_parse_http(
+ git_net_url *url,
+ const char *str);
+
+/** Appends a path and/or query string to the given URL */
+extern int git_net_url_joinpath(
+ git_net_url *out,
+ git_net_url *in,
+ const char *path);
+
+/** Ensures that a URL is minimally valid (contains a host, port and path) */
+extern bool git_net_url_valid(git_net_url *url);
+
+/** Returns true if the URL is on the default port. */
+extern bool git_net_url_is_default_port(git_net_url *url);
+
+/** Returns true if the host portion of the URL is an ipv6 address. */
+extern bool git_net_url_is_ipv6(git_net_url *url);
+
+/* Applies a redirect to the URL with a git-aware service suffix. */
+extern int git_net_url_apply_redirect(
+ git_net_url *url,
+ const char *redirect_location,
+ bool allow_offsite,
+ const char *service_suffix);
+
+/** Swaps the contents of one URL for another. */
+extern void git_net_url_swap(git_net_url *a, git_net_url *b);
+
+/** Places the URL into the given buffer. */
+extern int git_net_url_fmt(git_str *out, git_net_url *url);
+
+/** Place the path and query string into the given buffer. */
+extern int git_net_url_fmt_path(git_str *buf, git_net_url *url);
+
+/** Determines if the url matches given pattern or pattern list */
+extern bool git_net_url_matches_pattern(
+ git_net_url *url,
+ const char *pattern);
+extern bool git_net_url_matches_pattern_list(
+ git_net_url *url,
+ const char *pattern_list);
+
+/** Disposes the contents of the structure. */
+extern void git_net_url_dispose(git_net_url *url);
+
+#endif
diff --git a/src/util/pool.c b/src/util/pool.c
new file mode 100644
index 0000000..16ffa39
--- /dev/null
+++ b/src/util/pool.c
@@ -0,0 +1,260 @@
+/*
+ * 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 "pool.h"
+
+#include "posix.h"
+#ifndef GIT_WIN32
+#include <unistd.h>
+#endif
+
+struct git_pool_page {
+ git_pool_page *next;
+ size_t size;
+ size_t avail;
+ GIT_ALIGN(char data[GIT_FLEX_ARRAY], 8);
+};
+
+static void *pool_alloc_page(git_pool *pool, size_t size);
+
+#ifndef GIT_DEBUG_POOL
+
+static size_t system_page_size = 0;
+
+int git_pool_global_init(void)
+{
+ if (git__page_size(&system_page_size) < 0)
+ system_page_size = 4096;
+ /* allow space for malloc overhead */
+ system_page_size -= (2 * sizeof(void *)) + sizeof(git_pool_page);
+ return 0;
+}
+
+int git_pool_init(git_pool *pool, size_t item_size)
+{
+ GIT_ASSERT_ARG(pool);
+ GIT_ASSERT_ARG(item_size >= 1);
+
+ memset(pool, 0, sizeof(git_pool));
+ pool->item_size = item_size;
+ pool->page_size = system_page_size;
+
+ return 0;
+}
+
+void git_pool_clear(git_pool *pool)
+{
+ git_pool_page *scan, *next;
+
+ for (scan = pool->pages; scan != NULL; scan = next) {
+ next = scan->next;
+ git__free(scan);
+ }
+
+ pool->pages = NULL;
+}
+
+static void *pool_alloc_page(git_pool *pool, size_t size)
+{
+ git_pool_page *page;
+ const size_t new_page_size = (size <= pool->page_size) ? pool->page_size : size;
+ size_t alloc_size;
+
+ if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, new_page_size, sizeof(git_pool_page)) ||
+ !(page = git__malloc(alloc_size)))
+ return NULL;
+
+ page->size = new_page_size;
+ page->avail = new_page_size - size;
+ page->next = pool->pages;
+
+ pool->pages = page;
+
+ return page->data;
+}
+
+static void *pool_alloc(git_pool *pool, size_t size)
+{
+ git_pool_page *page = pool->pages;
+ void *ptr = NULL;
+
+ if (!page || page->avail < size)
+ return pool_alloc_page(pool, size);
+
+ ptr = &page->data[page->size - page->avail];
+ page->avail -= size;
+
+ return ptr;
+}
+
+uint32_t git_pool__open_pages(git_pool *pool)
+{
+ uint32_t ct = 0;
+ git_pool_page *scan;
+ for (scan = pool->pages; scan != NULL; scan = scan->next) ct++;
+ return ct;
+}
+
+bool git_pool__ptr_in_pool(git_pool *pool, void *ptr)
+{
+ git_pool_page *scan;
+ for (scan = pool->pages; scan != NULL; scan = scan->next)
+ if ((void *)scan->data <= ptr &&
+ (void *)(((char *)scan->data) + scan->size) > ptr)
+ return true;
+ return false;
+}
+
+#else
+
+int git_pool_global_init(void)
+{
+ return 0;
+}
+
+static int git_pool__ptr_cmp(const void * a, const void * b)
+{
+ if(a > b) {
+ return 1;
+ }
+ if(a < b) {
+ return -1;
+ }
+ else {
+ return 0;
+ }
+}
+
+int git_pool_init(git_pool *pool, size_t item_size)
+{
+ GIT_ASSERT_ARG(pool);
+ GIT_ASSERT_ARG(item_size >= 1);
+
+ memset(pool, 0, sizeof(git_pool));
+ pool->item_size = item_size;
+ pool->page_size = git_pool__system_page_size();
+ git_vector_init(&pool->allocations, 100, git_pool__ptr_cmp);
+
+ return 0;
+}
+
+void git_pool_clear(git_pool *pool)
+{
+ git_vector_free_deep(&pool->allocations);
+}
+
+static void *pool_alloc(git_pool *pool, size_t size) {
+ void *ptr = NULL;
+ if((ptr = git__malloc(size)) == NULL) {
+ return NULL;
+ }
+ git_vector_insert_sorted(&pool->allocations, ptr, NULL);
+ return ptr;
+}
+
+bool git_pool__ptr_in_pool(git_pool *pool, void *ptr)
+{
+ size_t pos;
+ return git_vector_bsearch(&pos, &pool->allocations, ptr) != GIT_ENOTFOUND;
+}
+#endif
+
+void git_pool_swap(git_pool *a, git_pool *b)
+{
+ git_pool temp;
+
+ if (a == b)
+ return;
+
+ memcpy(&temp, a, sizeof(temp));
+ memcpy(a, b, sizeof(temp));
+ memcpy(b, &temp, sizeof(temp));
+}
+
+static size_t alloc_size(git_pool *pool, size_t count)
+{
+ const size_t align = sizeof(void *) - 1;
+
+ if (pool->item_size > 1) {
+ const size_t item_size = (pool->item_size + align) & ~align;
+ return item_size * count;
+ }
+
+ return (count + align) & ~align;
+}
+
+void *git_pool_malloc(git_pool *pool, size_t items)
+{
+ return pool_alloc(pool, alloc_size(pool, items));
+}
+
+void *git_pool_mallocz(git_pool *pool, size_t items)
+{
+ const size_t size = alloc_size(pool, items);
+ void *ptr = pool_alloc(pool, size);
+ if (ptr)
+ memset(ptr, 0x0, size);
+ return ptr;
+}
+
+char *git_pool_strndup(git_pool *pool, const char *str, size_t n)
+{
+ char *ptr = NULL;
+
+ GIT_ASSERT_ARG_WITH_RETVAL(pool, NULL);
+ GIT_ASSERT_ARG_WITH_RETVAL(str, NULL);
+ GIT_ASSERT_ARG_WITH_RETVAL(pool->item_size == sizeof(char), NULL);
+
+ if (n == SIZE_MAX)
+ return NULL;
+
+ if ((ptr = git_pool_malloc(pool, (n + 1))) != NULL) {
+ memcpy(ptr, str, n);
+ ptr[n] = '\0';
+ }
+
+ return ptr;
+}
+
+char *git_pool_strdup(git_pool *pool, const char *str)
+{
+ GIT_ASSERT_ARG_WITH_RETVAL(pool, NULL);
+ GIT_ASSERT_ARG_WITH_RETVAL(str, NULL);
+ GIT_ASSERT_ARG_WITH_RETVAL(pool->item_size == sizeof(char), NULL);
+
+ return git_pool_strndup(pool, str, strlen(str));
+}
+
+char *git_pool_strdup_safe(git_pool *pool, const char *str)
+{
+ return str ? git_pool_strdup(pool, str) : NULL;
+}
+
+char *git_pool_strcat(git_pool *pool, const char *a, const char *b)
+{
+ void *ptr;
+ size_t len_a, len_b, total;
+
+ GIT_ASSERT_ARG_WITH_RETVAL(pool, NULL);
+ GIT_ASSERT_ARG_WITH_RETVAL(pool->item_size == sizeof(char), NULL);
+
+ len_a = a ? strlen(a) : 0;
+ len_b = b ? strlen(b) : 0;
+
+ if (GIT_ADD_SIZET_OVERFLOW(&total, len_a, len_b) ||
+ GIT_ADD_SIZET_OVERFLOW(&total, total, 1))
+ return NULL;
+
+ if ((ptr = git_pool_malloc(pool, total)) != NULL) {
+ if (len_a)
+ memcpy(ptr, a, len_a);
+ if (len_b)
+ memcpy(((char *)ptr) + len_a, b, len_b);
+ *(((char *)ptr) + len_a + len_b) = '\0';
+ }
+ return ptr;
+}
diff --git a/src/util/pool.h b/src/util/pool.h
new file mode 100644
index 0000000..0238431
--- /dev/null
+++ b/src/util/pool.h
@@ -0,0 +1,146 @@
+/*
+ * 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_pool_h__
+#define INCLUDE_pool_h__
+
+#include "git2_util.h"
+
+#include "vector.h"
+
+typedef struct git_pool_page git_pool_page;
+
+#ifndef GIT_DEBUG_POOL
+/**
+ * Chunked allocator.
+ *
+ * A `git_pool` can be used when you want to cheaply allocate
+ * multiple items of the same type and are willing to free them
+ * all together with a single call. The two most common cases
+ * are a set of fixed size items (such as lots of OIDs) or a
+ * bunch of strings.
+ *
+ * Internally, a `git_pool` allocates pages of memory and then
+ * deals out blocks from the trailing unused portion of each page.
+ * The pages guarantee that the number of actual allocations done
+ * will be much smaller than the number of items needed.
+ *
+ * For examples of how to set up a `git_pool` see `git_pool_init`.
+ */
+typedef struct {
+ git_pool_page *pages; /* allocated pages */
+ size_t item_size; /* size of single alloc unit in bytes */
+ size_t page_size; /* size of page in bytes */
+} git_pool;
+
+#define GIT_POOL_INIT { NULL, 0, 0 }
+
+#else
+
+/**
+ * Debug chunked allocator.
+ *
+ * Acts just like `git_pool` but instead of actually pooling allocations it
+ * passes them through to `git__malloc`. This makes it possible to easily debug
+ * systems that use `git_pool` using valgrind.
+ *
+ * In order to track allocations during the lifetime of the pool we use a
+ * `git_vector`. When the pool is deallocated everything in the vector is
+ * freed.
+ *
+ * `API is exactly the same as the standard `git_pool` with one exception.
+ * Since we aren't allocating pages to hand out in chunks we can't easily
+ * implement `git_pool__open_pages`.
+ */
+typedef struct {
+ git_vector allocations;
+ size_t item_size;
+ size_t page_size;
+} git_pool;
+
+#define GIT_POOL_INIT { GIT_VECTOR_INIT, 0, 0 }
+
+#endif
+
+/**
+ * Initialize a pool.
+ *
+ * To allocation strings, use like this:
+ *
+ * git_pool_init(&string_pool, 1);
+ * my_string = git_pool_strdup(&string_pool, your_string);
+ *
+ * To allocate items of fixed size, use like this:
+ *
+ * git_pool_init(&pool, sizeof(item));
+ * my_item = git_pool_malloc(&pool, 1);
+ *
+ * Of course, you can use this in other ways, but those are the
+ * two most common patterns.
+ */
+extern int git_pool_init(git_pool *pool, size_t item_size);
+
+/**
+ * Free all items in pool
+ */
+extern void git_pool_clear(git_pool *pool);
+
+/**
+ * Swap two pools with one another
+ */
+extern void git_pool_swap(git_pool *a, git_pool *b);
+
+/**
+ * Allocate space for one or more items from a pool.
+ */
+extern void *git_pool_malloc(git_pool *pool, size_t items);
+extern void *git_pool_mallocz(git_pool *pool, size_t items);
+
+/**
+ * Allocate space and duplicate string data into it.
+ *
+ * This is allowed only for pools with item_size == sizeof(char)
+ */
+extern char *git_pool_strndup(git_pool *pool, const char *str, size_t n);
+
+/**
+ * Allocate space and duplicate a string into it.
+ *
+ * This is allowed only for pools with item_size == sizeof(char)
+ */
+extern char *git_pool_strdup(git_pool *pool, const char *str);
+
+/**
+ * Allocate space and duplicate a string into it, NULL is no error.
+ *
+ * This is allowed only for pools with item_size == sizeof(char)
+ */
+extern char *git_pool_strdup_safe(git_pool *pool, const char *str);
+
+/**
+ * Allocate space for the concatenation of two strings.
+ *
+ * This is allowed only for pools with item_size == sizeof(char)
+ */
+extern char *git_pool_strcat(git_pool *pool, const char *a, const char *b);
+
+/*
+ * Misc utilities
+ */
+#ifndef GIT_DEBUG_POOL
+extern uint32_t git_pool__open_pages(git_pool *pool);
+#endif
+extern bool git_pool__ptr_in_pool(git_pool *pool, void *ptr);
+
+/**
+ * This function is being called by our global setup routines to
+ * initialize the system pool size.
+ *
+ * @return 0 on success, <0 on failure
+ */
+extern int git_pool_global_init(void);
+
+#endif
diff --git a/src/util/posix.c b/src/util/posix.c
new file mode 100644
index 0000000..cfc0e07
--- /dev/null
+++ b/src/util/posix.c
@@ -0,0 +1,357 @@
+/*
+ * 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 "posix.h"
+
+#include "fs_path.h"
+#include <stdio.h>
+#include <ctype.h>
+
+size_t p_fsync__cnt = 0;
+
+#ifndef GIT_WIN32
+
+#ifdef NO_ADDRINFO
+
+int p_getaddrinfo(
+ const char *host,
+ const char *port,
+ struct addrinfo *hints,
+ struct addrinfo **info)
+{
+ struct addrinfo *ainfo, *ai;
+ int p = 0;
+
+ GIT_UNUSED(hints);
+
+ if ((ainfo = git__malloc(sizeof(struct addrinfo))) == NULL)
+ return -1;
+
+ if ((ainfo->ai_hostent = gethostbyname(host)) == NULL) {
+ git__free(ainfo);
+ return -2;
+ }
+
+ ainfo->ai_servent = getservbyname(port, 0);
+
+ if (ainfo->ai_servent)
+ ainfo->ai_port = ainfo->ai_servent->s_port;
+ else
+ ainfo->ai_port = htons(atol(port));
+
+ memcpy(&ainfo->ai_addr_in.sin_addr,
+ ainfo->ai_hostent->h_addr_list[0],
+ ainfo->ai_hostent->h_length);
+
+ ainfo->ai_protocol = 0;
+ ainfo->ai_socktype = hints->ai_socktype;
+ ainfo->ai_family = ainfo->ai_hostent->h_addrtype;
+ ainfo->ai_addr_in.sin_family = ainfo->ai_family;
+ ainfo->ai_addr_in.sin_port = ainfo->ai_port;
+ ainfo->ai_addr = (struct addrinfo *)&ainfo->ai_addr_in;
+ ainfo->ai_addrlen = sizeof(struct sockaddr_in);
+
+ *info = ainfo;
+
+ if (ainfo->ai_hostent->h_addr_list[1] == NULL) {
+ ainfo->ai_next = NULL;
+ return 0;
+ }
+
+ ai = ainfo;
+
+ for (p = 1; ainfo->ai_hostent->h_addr_list[p] != NULL; p++) {
+ if (!(ai->ai_next = git__malloc(sizeof(struct addrinfo)))) {
+ p_freeaddrinfo(ainfo);
+ return -1;
+ }
+ memcpy(ai->ai_next, ainfo, sizeof(struct addrinfo));
+ memcpy(&ai->ai_next->ai_addr_in.sin_addr,
+ ainfo->ai_hostent->h_addr_list[p],
+ ainfo->ai_hostent->h_length);
+ ai->ai_next->ai_addr = (struct addrinfo *)&ai->ai_next->ai_addr_in;
+ ai = ai->ai_next;
+ }
+
+ ai->ai_next = NULL;
+ return 0;
+}
+
+void p_freeaddrinfo(struct addrinfo *info)
+{
+ struct addrinfo *p, *next;
+
+ p = info;
+
+ while(p != NULL) {
+ next = p->ai_next;
+ git__free(p);
+ p = next;
+ }
+}
+
+const char *p_gai_strerror(int ret)
+{
+ switch(ret) {
+ case -1: return "Out of memory"; break;
+ case -2: return "Address lookup failed"; break;
+ default: return "Unknown error"; break;
+ }
+}
+
+#endif /* NO_ADDRINFO */
+
+int p_open(const char *path, volatile int flags, ...)
+{
+ mode_t mode = 0;
+
+ #ifdef GIT_DEBUG_STRICT_OPEN
+ if (strstr(path, "//") != NULL) {
+ errno = EACCES;
+ return -1;
+ }
+ #endif
+
+ if (flags & O_CREAT) {
+ va_list arg_list;
+
+ va_start(arg_list, flags);
+ mode = (mode_t)va_arg(arg_list, int);
+ va_end(arg_list);
+ }
+
+ return open(path, flags | O_BINARY | O_CLOEXEC, mode);
+}
+
+int p_creat(const char *path, mode_t mode)
+{
+ return open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_CLOEXEC, mode);
+}
+
+int p_getcwd(char *buffer_out, size_t size)
+{
+ char *cwd_buffer;
+
+ GIT_ASSERT_ARG(buffer_out);
+ GIT_ASSERT_ARG(size > 0);
+
+ cwd_buffer = getcwd(buffer_out, size);
+
+ if (cwd_buffer == NULL)
+ return -1;
+
+ git_fs_path_mkposix(buffer_out);
+ git_fs_path_string_to_dir(buffer_out, size); /* append trailing slash */
+
+ return 0;
+}
+
+int p_rename(const char *from, const char *to)
+{
+ if (!link(from, to)) {
+ p_unlink(from);
+ return 0;
+ }
+
+ if (!rename(from, to))
+ return 0;
+
+ return -1;
+}
+
+#endif /* GIT_WIN32 */
+
+ssize_t p_read(git_file fd, void *buf, size_t cnt)
+{
+ char *b = buf;
+
+ if (!git__is_ssizet(cnt)) {
+#ifdef GIT_WIN32
+ SetLastError(ERROR_INVALID_PARAMETER);
+#endif
+ errno = EINVAL;
+ return -1;
+ }
+
+ while (cnt) {
+ ssize_t r;
+#ifdef GIT_WIN32
+ r = read(fd, b, cnt > INT_MAX ? INT_MAX : (unsigned int)cnt);
+#else
+ r = read(fd, b, cnt);
+#endif
+ if (r < 0) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ return -1;
+ }
+ if (!r)
+ break;
+ cnt -= r;
+ b += r;
+ }
+ return (b - (char *)buf);
+}
+
+int p_write(git_file fd, const void *buf, size_t cnt)
+{
+ const char *b = buf;
+
+ while (cnt) {
+ ssize_t r;
+#ifdef GIT_WIN32
+ GIT_ASSERT((size_t)((unsigned int)cnt) == cnt);
+ r = write(fd, b, (unsigned int)cnt);
+#else
+ r = write(fd, b, cnt);
+#endif
+ if (r < 0) {
+ if (errno == EINTR || GIT_ISBLOCKED(errno))
+ continue;
+ return -1;
+ }
+ if (!r) {
+ errno = EPIPE;
+ return -1;
+ }
+ cnt -= r;
+ b += r;
+ }
+ return 0;
+}
+
+#ifdef NO_MMAP
+
+#include "map.h"
+
+int git__page_size(size_t *page_size)
+{
+ /* dummy; here we don't need any alignment anyway */
+ *page_size = 4096;
+ return 0;
+}
+
+int git__mmap_alignment(size_t *alignment)
+{
+ /* dummy; here we don't need any alignment anyway */
+ *alignment = 4096;
+ return 0;
+}
+
+
+int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, off64_t offset)
+{
+ const char *ptr;
+ size_t remaining_len;
+
+ GIT_MMAP_VALIDATE(out, len, prot, flags);
+
+ /* writes cannot be emulated without handling pagefaults since write happens by
+ * writing to mapped memory */
+ if (prot & GIT_PROT_WRITE) {
+ git_error_set(GIT_ERROR_OS, "trying to map %s-writeable",
+ ((flags & GIT_MAP_TYPE) == GIT_MAP_SHARED) ? "shared": "private");
+ return -1;
+ }
+
+ if (!git__is_ssizet(len)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ out->len = 0;
+ out->data = git__malloc(len);
+ GIT_ERROR_CHECK_ALLOC(out->data);
+
+ remaining_len = len;
+ ptr = (const char *)out->data;
+ while (remaining_len > 0) {
+ ssize_t nb;
+ HANDLE_EINTR(nb, p_pread(fd, (void *)ptr, remaining_len, offset));
+ if (nb <= 0) {
+ git_error_set(GIT_ERROR_OS, "mmap emulation failed");
+ git__free(out->data);
+ out->data = NULL;
+ return -1;
+ }
+
+ ptr += nb;
+ offset += nb;
+ remaining_len -= nb;
+ }
+
+ out->len = len;
+ return 0;
+}
+
+int p_munmap(git_map *map)
+{
+ GIT_ASSERT_ARG(map);
+ git__free(map->data);
+
+ /* Initializing will help debug use-after-free */
+ map->len = 0;
+ map->data = NULL;
+
+ return 0;
+}
+
+#endif
+
+#if defined(GIT_IO_POLL) || defined(GIT_IO_WSAPOLL)
+
+/* Handled by posix.h; this test simplifies the final else */
+
+#elif defined(GIT_IO_SELECT)
+
+int p_poll(struct pollfd *fds, unsigned int nfds, int timeout_ms)
+{
+ fd_set read_fds, write_fds, except_fds;
+ struct timeval timeout = { 0, 0 };
+ unsigned int i;
+ int max_fd = -1, ret;
+
+ FD_ZERO(&read_fds);
+ FD_ZERO(&write_fds);
+ FD_ZERO(&except_fds);
+
+ for (i = 0; i < nfds; i++) {
+ if ((fds[i].events & POLLIN))
+ FD_SET(fds[i].fd, &read_fds);
+
+ if ((fds[i].events & POLLOUT))
+ FD_SET(fds[i].fd, &write_fds);
+
+ if ((fds[i].events & POLLPRI))
+ FD_SET(fds[i].fd, &except_fds);
+
+ max_fd = MAX(max_fd, fds[i].fd);
+ }
+
+ if (timeout_ms > 0) {
+ timeout.tv_sec = timeout_ms / 1000;
+ timeout.tv_usec = (timeout_ms % 1000) * 1000;
+ }
+
+ if ((ret = select(max_fd + 1, &read_fds, &write_fds, &except_fds,
+ timeout_ms < 0 ? NULL : &timeout)) < 0)
+ goto done;
+
+ for (i = 0; i < nfds; i++) {
+ fds[i].revents = 0 |
+ FD_ISSET(fds[i].fd, &read_fds) ? POLLIN : 0 |
+ FD_ISSET(fds[i].fd, &write_fds) ? POLLOUT : 0 |
+ FD_ISSET(fds[i].fd, &except_fds) ? POLLPRI : 0;
+ }
+
+done:
+ return ret;
+}
+
+#else
+# error no poll compatible implementation
+#endif
diff --git a/src/util/posix.h b/src/util/posix.h
new file mode 100644
index 0000000..7470745
--- /dev/null
+++ b/src/util/posix.h
@@ -0,0 +1,220 @@
+/*
+ * 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_posix_h__
+#define INCLUDE_posix_h__
+
+#include "git2_util.h"
+
+#include <stdlib.h>
+#include <fcntl.h>
+#include <time.h>
+
+/* stat: file mode type testing macros */
+#ifndef S_IFGITLINK
+#define S_IFGITLINK 0160000
+#define S_ISGITLINK(m) (((m) & S_IFMT) == S_IFGITLINK)
+#endif
+
+#ifndef S_IFLNK
+#define S_IFLNK 0120000
+#undef _S_IFLNK
+#define _S_IFLNK S_IFLNK
+#endif
+
+#ifndef S_IWUSR
+#define S_IWUSR 00200
+#endif
+
+#ifndef S_IXUSR
+#define S_IXUSR 00100
+#endif
+
+#ifndef S_ISLNK
+#define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK)
+#endif
+
+#ifndef S_ISDIR
+#define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR)
+#endif
+
+#ifndef S_ISREG
+#define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG)
+#endif
+
+#ifndef S_ISFIFO
+#define S_ISFIFO(m) (((m) & _S_IFMT) == _S_IFIFO)
+#endif
+
+/* if S_ISGID is not defined, then don't try to set it */
+#ifndef S_ISGID
+#define S_ISGID 0
+#endif
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+#ifndef O_CLOEXEC
+#define O_CLOEXEC 0
+#endif
+#ifndef SOCK_CLOEXEC
+#define SOCK_CLOEXEC 0
+#endif
+
+/* access() mode parameter #defines */
+#ifndef F_OK
+#define F_OK 0 /* existence check */
+#endif
+#ifndef W_OK
+#define W_OK 2 /* write mode check */
+#endif
+#ifndef R_OK
+#define R_OK 4 /* read mode check */
+#endif
+
+/* Determine whether an errno value indicates that a read or write failed
+ * because the descriptor is blocked.
+ */
+#if defined(EWOULDBLOCK)
+#define GIT_ISBLOCKED(e) ((e) == EAGAIN || (e) == EWOULDBLOCK)
+#else
+#define GIT_ISBLOCKED(e) ((e) == EAGAIN)
+#endif
+
+/* define some standard errnos that the runtime may be missing. for example,
+ * mingw lacks EAFNOSUPPORT. */
+#ifndef EAFNOSUPPORT
+#define EAFNOSUPPORT (INT_MAX-1)
+#endif
+
+/* Compiler independent macro to handle signal interrpted system calls */
+#define HANDLE_EINTR(result, x) do { \
+ result = (x); \
+ } while (result == -1 && errno == EINTR);
+
+
+/* Provide a 64-bit size for offsets. */
+
+#if defined(_MSC_VER)
+typedef __int64 off64_t;
+#elif defined(__HAIKU__)
+typedef __haiku_std_int64 off64_t;
+#elif defined(__APPLE__)
+typedef __int64_t off64_t;
+#elif defined(_AIX)
+typedef long long off64_t;
+#else
+typedef int64_t off64_t;
+#endif
+
+typedef int git_file;
+
+/**
+ * Standard POSIX Methods
+ *
+ * All the methods starting with the `p_` prefix are
+ * direct ports of the standard POSIX methods.
+ *
+ * Some of the methods are slightly wrapped to provide
+ * saner defaults. Some of these methods are emulated
+ * in Windows platforms.
+ *
+ * Use your manpages to check the docs on these.
+ */
+
+extern ssize_t p_read(git_file fd, void *buf, size_t cnt);
+extern int p_write(git_file fd, const void *buf, size_t cnt);
+
+extern ssize_t p_pread(int fd, void *data, size_t size, off64_t offset);
+extern ssize_t p_pwrite(int fd, const void *data, size_t size, off64_t offset);
+
+#define p_close(fd) close(fd)
+#define p_umask(m) umask(m)
+
+extern int p_open(const char *path, int flags, ...);
+extern int p_creat(const char *path, mode_t mode);
+extern int p_getcwd(char *buffer_out, size_t size);
+extern int p_rename(const char *from, const char *to);
+
+extern int git__page_size(size_t *page_size);
+extern int git__mmap_alignment(size_t *page_size);
+
+/* The number of times `p_fsync` has been called. Note that this is for
+ * test code only; it it not necessarily thread-safe and should not be
+ * relied upon in production.
+ */
+extern size_t p_fsync__cnt;
+
+/**
+ * Platform-dependent methods
+ */
+#ifdef GIT_WIN32
+# include "win32/posix.h"
+#else
+# include "unix/posix.h"
+#endif
+
+#include "strnlen.h"
+
+#ifdef NO_READDIR_R
+GIT_INLINE(int) p_readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result)
+{
+ GIT_UNUSED(entry);
+ *result = readdir(dirp);
+ return 0;
+}
+#else /* NO_READDIR_R */
+# define p_readdir_r(d,e,r) readdir_r(d,e,r)
+#endif
+
+#ifdef NO_ADDRINFO
+# include <netdb.h>
+struct addrinfo {
+ struct hostent *ai_hostent;
+ struct servent *ai_servent;
+ struct sockaddr_in ai_addr_in;
+ struct sockaddr *ai_addr;
+ size_t ai_addrlen;
+ int ai_family;
+ int ai_socktype;
+ int ai_protocol;
+ long ai_port;
+ struct addrinfo *ai_next;
+};
+
+extern int p_getaddrinfo(const char *host, const char *port,
+ struct addrinfo *hints, struct addrinfo **info);
+extern void p_freeaddrinfo(struct addrinfo *info);
+extern const char *p_gai_strerror(int ret);
+#else
+# define p_getaddrinfo(a, b, c, d) getaddrinfo(a, b, c, d)
+# define p_freeaddrinfo(a) freeaddrinfo(a)
+# define p_gai_strerror(c) gai_strerror(c)
+#endif /* NO_ADDRINFO */
+
+#ifdef GIT_IO_POLL
+# include <poll.h>
+# define p_poll poll
+#elif GIT_IO_WSAPOLL
+# include <winsock2.h>
+# define p_poll WSAPoll
+#else
+# define POLLIN 0x01
+# define POLLPRI 0x02
+# define POLLOUT 0x04
+# define POLLERR 0x08
+# define POLLHUP 0x10
+
+struct pollfd {
+ int fd;
+ short events;
+ short revents;
+};
+
+extern int p_poll(struct pollfd *fds, unsigned int nfds, int timeout);
+#endif
+
+#endif
diff --git a/src/util/pqueue.c b/src/util/pqueue.c
new file mode 100644
index 0000000..3820e99
--- /dev/null
+++ b/src/util/pqueue.c
@@ -0,0 +1,125 @@
+/*
+ * 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 "pqueue.h"
+
+#include "util.h"
+
+#define PQUEUE_LCHILD_OF(I) (((I)<<1)+1)
+#define PQUEUE_RCHILD_OF(I) (((I)<<1)+2)
+#define PQUEUE_PARENT_OF(I) (((I)-1)>>1)
+
+int git_pqueue_init(
+ git_pqueue *pq,
+ uint32_t flags,
+ size_t init_size,
+ git_vector_cmp cmp)
+{
+ int error = git_vector_init(pq, init_size, cmp);
+
+ if (!error) {
+ /* mix in our flags */
+ pq->flags |= flags;
+
+ /* if fixed size heap, pretend vector is exactly init_size elements */
+ if ((flags & GIT_PQUEUE_FIXED_SIZE) && init_size > 0)
+ pq->_alloc_size = init_size;
+ }
+
+ return error;
+}
+
+static void pqueue_up(git_pqueue *pq, size_t el)
+{
+ size_t parent_el = PQUEUE_PARENT_OF(el);
+ void *kid = git_vector_get(pq, el);
+
+ while (el > 0) {
+ void *parent = pq->contents[parent_el];
+
+ if (pq->_cmp(parent, kid) <= 0)
+ break;
+
+ pq->contents[el] = parent;
+
+ el = parent_el;
+ parent_el = PQUEUE_PARENT_OF(el);
+ }
+
+ pq->contents[el] = kid;
+}
+
+static void pqueue_down(git_pqueue *pq, size_t el)
+{
+ void *parent = git_vector_get(pq, el), *kid, *rkid;
+
+ while (1) {
+ size_t kid_el = PQUEUE_LCHILD_OF(el);
+
+ if ((kid = git_vector_get(pq, kid_el)) == NULL)
+ break;
+
+ if ((rkid = git_vector_get(pq, kid_el + 1)) != NULL &&
+ pq->_cmp(kid, rkid) > 0) {
+ kid = rkid;
+ kid_el += 1;
+ }
+
+ if (pq->_cmp(parent, kid) <= 0)
+ break;
+
+ pq->contents[el] = kid;
+ el = kid_el;
+ }
+
+ pq->contents[el] = parent;
+}
+
+int git_pqueue_insert(git_pqueue *pq, void *item)
+{
+ int error = 0;
+
+ /* if heap is full, pop the top element if new one should replace it */
+ if ((pq->flags & GIT_PQUEUE_FIXED_SIZE) != 0 &&
+ pq->length >= pq->_alloc_size)
+ {
+ /* skip this item if below min item in heap or if
+ * we do not have a comparison function */
+ if (!pq->_cmp || pq->_cmp(item, git_vector_get(pq, 0)) <= 0)
+ return 0;
+ /* otherwise remove the min item before inserting new */
+ (void)git_pqueue_pop(pq);
+ }
+
+ if (!(error = git_vector_insert(pq, item)) && pq->_cmp)
+ pqueue_up(pq, pq->length - 1);
+
+ return error;
+}
+
+void *git_pqueue_pop(git_pqueue *pq)
+{
+ void *rval;
+
+ if (!pq->_cmp) {
+ rval = git_vector_last(pq);
+ } else {
+ rval = git_pqueue_get(pq, 0);
+ }
+
+ if (git_pqueue_size(pq) > 1 && pq->_cmp) {
+ /* move last item to top of heap, shrink, and push item down */
+ pq->contents[0] = git_vector_last(pq);
+ git_vector_pop(pq);
+ pqueue_down(pq, 0);
+ } else {
+ /* all we need to do is shrink the heap in this case */
+ git_vector_pop(pq);
+ }
+
+ return rval;
+}
diff --git a/src/util/pqueue.h b/src/util/pqueue.h
new file mode 100644
index 0000000..97232b4
--- /dev/null
+++ b/src/util/pqueue.h
@@ -0,0 +1,59 @@
+/*
+ * 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_pqueue_h__
+#define INCLUDE_pqueue_h__
+
+#include "git2_util.h"
+
+#include "vector.h"
+
+typedef git_vector git_pqueue;
+
+enum {
+ /* flag meaning: don't grow heap, keep highest values only */
+ GIT_PQUEUE_FIXED_SIZE = (GIT_VECTOR_FLAG_MAX << 1)
+};
+
+/**
+ * Initialize priority queue
+ *
+ * @param pq The priority queue struct to initialize
+ * @param flags Flags (see above) to control queue behavior
+ * @param init_size The initial queue size
+ * @param cmp The entry priority comparison function
+ * @return 0 on success, <0 on error
+ */
+extern int git_pqueue_init(
+ git_pqueue *pq,
+ uint32_t flags,
+ size_t init_size,
+ git_vector_cmp cmp);
+
+#define git_pqueue_free git_vector_free
+#define git_pqueue_clear git_vector_clear
+#define git_pqueue_size git_vector_length
+#define git_pqueue_get git_vector_get
+#define git_pqueue_reverse git_vector_reverse
+
+/**
+ * Insert a new item into the queue
+ *
+ * @param pq The priority queue
+ * @param item Pointer to the item data
+ * @return 0 on success, <0 on failure
+ */
+extern int git_pqueue_insert(git_pqueue *pq, void *item);
+
+/**
+ * Remove the top item in the priority queue
+ *
+ * @param pq The priority queue
+ * @return item from heap on success, NULL if queue is empty
+ */
+extern void *git_pqueue_pop(git_pqueue *pq);
+
+#endif
diff --git a/src/util/rand.c b/src/util/rand.c
new file mode 100644
index 0000000..2ed0605
--- /dev/null
+++ b/src/util/rand.c
@@ -0,0 +1,236 @@
+/* Written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org)
+
+To the extent possible under law, the author has dedicated all copyright
+and related and neighboring rights to this software to the public domain
+worldwide. This software is distributed without any warranty.
+
+See <http://creativecommons.org/publicdomain/zero/1.0/>. */
+
+#include "git2_util.h"
+#include "rand.h"
+#include "runtime.h"
+
+#if defined(GIT_RAND_GETENTROPY)
+# include <sys/random.h>
+#endif
+
+#if defined(GIT_WIN32)
+# include <wincrypt.h>
+#endif
+
+static uint64_t state[4];
+static git_mutex state_lock;
+
+typedef union {
+ double f;
+ uint64_t d;
+} bits;
+
+#if defined(GIT_WIN32)
+GIT_INLINE(int) getseed(uint64_t *seed)
+{
+ HCRYPTPROV provider;
+ SYSTEMTIME systemtime;
+ FILETIME filetime, idletime, kerneltime, usertime;
+
+ if (CryptAcquireContext(&provider, 0, 0, PROV_RSA_FULL,
+ CRYPT_VERIFYCONTEXT|CRYPT_SILENT)) {
+ BOOL success = CryptGenRandom(provider, sizeof(uint64_t), (void *)seed);
+ CryptReleaseContext(provider, 0);
+
+ if (success)
+ return 0;
+ }
+
+ GetSystemTime(&systemtime);
+ if (!SystemTimeToFileTime(&systemtime, &filetime)) {
+ git_error_set(GIT_ERROR_OS, "could not get time for random seed");
+ return -1;
+ }
+
+ /* Fall-through: generate a seed from the time and system state */
+ *seed = 0;
+ *seed |= ((uint64_t)filetime.dwLowDateTime << 32);
+ *seed |= ((uint64_t)filetime.dwHighDateTime);
+
+ GetSystemTimes(&idletime, &kerneltime, &usertime);
+
+ *seed ^= ((uint64_t)idletime.dwLowDateTime << 32);
+ *seed ^= ((uint64_t)kerneltime.dwLowDateTime);
+ *seed ^= ((uint64_t)usertime.dwLowDateTime << 32);
+
+ *seed ^= ((uint64_t)idletime.dwHighDateTime);
+ *seed ^= ((uint64_t)kerneltime.dwHighDateTime << 12);
+ *seed ^= ((uint64_t)usertime.dwHighDateTime << 24);
+
+ *seed ^= ((uint64_t)GetCurrentProcessId() << 32);
+ *seed ^= ((uint64_t)GetCurrentThreadId() << 48);
+
+ *seed ^= git_time_monotonic();
+
+ /* Mix in the addresses of some functions and variables */
+ *seed ^= (((uint64_t)((uintptr_t)seed) << 32));
+ *seed ^= (((uint64_t)((uintptr_t)&errno)));
+
+ return 0;
+}
+
+#else
+
+GIT_INLINE(int) getseed(uint64_t *seed)
+{
+ struct timeval tv;
+ double loadavg[3];
+ int fd;
+
+# if defined(GIT_RAND_GETLOADAVG)
+ bits convert;
+# endif
+
+# if defined(GIT_RAND_GETENTROPY)
+ GIT_UNUSED((fd = 0));
+
+ if (getentropy(seed, sizeof(uint64_t)) == 0)
+ return 0;
+# else
+ /*
+ * Try to read from /dev/urandom; most modern systems will have
+ * this, but we may be chrooted, etc, so it's not a fatal error
+ */
+ if ((fd = open("/dev/urandom", O_RDONLY)) >= 0) {
+ ssize_t ret = read(fd, seed, sizeof(uint64_t));
+ close(fd);
+
+ if (ret == (ssize_t)sizeof(uint64_t))
+ return 0;
+ }
+# endif
+
+ /* Fall-through: generate a seed from the time and system state */
+ if (gettimeofday(&tv, NULL) < 0) {
+ git_error_set(GIT_ERROR_OS, "could get time for random seed");
+ return -1;
+ }
+
+ *seed = 0;
+ *seed |= ((uint64_t)tv.tv_usec << 40);
+ *seed |= ((uint64_t)tv.tv_sec);
+
+ *seed ^= ((uint64_t)getpid() << 48);
+ *seed ^= ((uint64_t)getppid() << 32);
+ *seed ^= ((uint64_t)getpgid(0) << 28);
+ *seed ^= ((uint64_t)getsid(0) << 16);
+ *seed ^= ((uint64_t)getuid() << 8);
+ *seed ^= ((uint64_t)getgid());
+
+# if defined(GIT_RAND_GETLOADAVG)
+ getloadavg(loadavg, 3);
+
+ 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();
+
+ /* Mix in the addresses of some variables */
+ *seed ^= ((uint64_t)((size_t)((void *)seed)) << 32);
+ *seed ^= ((uint64_t)((size_t)((void *)&errno)));
+
+ return 0;
+}
+#endif
+
+static void git_rand_global_shutdown(void)
+{
+ git_mutex_free(&state_lock);
+}
+
+int git_rand_global_init(void)
+{
+ uint64_t seed = 0;
+
+ if (git_mutex_init(&state_lock) < 0 || getseed(&seed) < 0)
+ return -1;
+
+ if (!seed) {
+ git_error_set(GIT_ERROR_INTERNAL, "failed to generate random seed");
+ return -1;
+ }
+
+ git_rand_seed(seed);
+ git_runtime_shutdown_register(git_rand_global_shutdown);
+
+ return 0;
+}
+
+/*
+ * This is splitmix64. xoroshiro256** uses 256 bit seed; this is used
+ * to generate 256 bits of seed from the given 64, per the author's
+ * recommendation.
+ */
+GIT_INLINE(uint64_t) splitmix64(uint64_t *in)
+{
+ uint64_t z;
+
+ *in += 0x9e3779b97f4a7c15;
+
+ z = *in;
+ z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
+ z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
+ return z ^ (z >> 31);
+}
+
+void git_rand_seed(uint64_t seed)
+{
+ uint64_t mixer;
+
+ mixer = seed;
+
+ git_mutex_lock(&state_lock);
+ state[0] = splitmix64(&mixer);
+ state[1] = splitmix64(&mixer);
+ state[2] = splitmix64(&mixer);
+ state[3] = splitmix64(&mixer);
+ git_mutex_unlock(&state_lock);
+}
+
+/* This is xoshiro256** 1.0, one of our all-purpose, rock-solid
+ generators. It has excellent (sub-ns) speed, a state (256 bits) that is
+ large enough for any parallel application, and it passes all tests we
+ are aware of.
+
+ For generating just floating-point numbers, xoshiro256+ is even faster.
+
+ The state must be seeded so that it is not everywhere zero. If you have
+ a 64-bit seed, we suggest to seed a splitmix64 generator and use its
+ output to fill s. */
+
+GIT_INLINE(uint64_t) rotl(const uint64_t x, int k) {
+ return (x << k) | (x >> (64 - k));
+}
+
+uint64_t git_rand_next(void) {
+ uint64_t t, result;
+
+ git_mutex_lock(&state_lock);
+
+ result = rotl(state[1] * 5, 7) * 9;
+
+ t = state[1] << 17;
+
+ state[2] ^= state[0];
+ state[3] ^= state[1];
+ state[1] ^= state[2];
+ state[0] ^= state[3];
+
+ state[2] ^= t;
+
+ state[3] = rotl(state[3], 45);
+
+ git_mutex_unlock(&state_lock);
+
+ return result;
+}
diff --git a/src/util/rand.h b/src/util/rand.h
new file mode 100644
index 0000000..fa0619a
--- /dev/null
+++ b/src/util/rand.h
@@ -0,0 +1,37 @@
+/*
+ * 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_rand_h__
+#define INCLUDE_rand_h__
+
+#include "git2_util.h"
+
+/**
+ * Initialize the random number generation subsystem. This will
+ * seed the random number generator with the system's entropy pool,
+ * if available, and will fall back to the current time and
+ * system information if not.
+ */
+int git_rand_global_init(void);
+
+/**
+ * Seed the pseudo-random number generator. This is not needed to be
+ * called; the PRNG is seeded by `git_rand_global_init`, but it may
+ * be useful for testing. When the same seed is specified, the same
+ * sequence of random numbers from `git_rand_next` is emitted.
+ *
+ * @param seed the seed to use
+ */
+void git_rand_seed(uint64_t seed);
+
+/**
+ * Get the next pseudo-random number in the sequence.
+ *
+ * @return a 64-bit pseudo-random number
+ */
+uint64_t git_rand_next(void);
+
+#endif
diff --git a/src/util/regexp.c b/src/util/regexp.c
new file mode 100644
index 0000000..0870088
--- /dev/null
+++ b/src/util/regexp.c
@@ -0,0 +1,221 @@
+/*
+ * 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 "regexp.h"
+
+#if defined(GIT_REGEX_BUILTIN) || defined(GIT_REGEX_PCRE)
+
+int git_regexp_compile(git_regexp *r, const char *pattern, int flags)
+{
+ int erroffset, cflags = 0;
+ const char *error = NULL;
+
+ if (flags & GIT_REGEXP_ICASE)
+ cflags |= PCRE_CASELESS;
+
+ if ((*r = pcre_compile(pattern, cflags, &error, &erroffset, NULL)) == NULL) {
+ git_error_set_str(GIT_ERROR_REGEX, error);
+ return GIT_EINVALIDSPEC;
+ }
+
+ return 0;
+}
+
+void git_regexp_dispose(git_regexp *r)
+{
+ pcre_free(*r);
+ *r = NULL;
+}
+
+int git_regexp_match(const git_regexp *r, const char *string)
+{
+ int error;
+ if ((error = pcre_exec(*r, NULL, string, (int) strlen(string), 0, 0, NULL, 0)) < 0)
+ return (error == PCRE_ERROR_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC;
+ return 0;
+}
+
+int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches, git_regmatch *matches)
+{
+ int static_ovec[9] = {0}, *ovec;
+ int error;
+ size_t i;
+
+ /* The ovec array always needs to be a multiple of three */
+ if (nmatches <= ARRAY_SIZE(static_ovec) / 3)
+ ovec = static_ovec;
+ else
+ ovec = git__calloc(nmatches * 3, sizeof(*ovec));
+ GIT_ERROR_CHECK_ALLOC(ovec);
+
+ if ((error = pcre_exec(*r, NULL, string, (int) strlen(string), 0, 0, ovec, (int) nmatches * 3)) < 0)
+ goto out;
+
+ if (error == 0)
+ error = (int) nmatches;
+
+ for (i = 0; i < (unsigned int) error; i++) {
+ matches[i].start = (ovec[i * 2] < 0) ? -1 : ovec[i * 2];
+ matches[i].end = (ovec[i * 2 + 1] < 0) ? -1 : ovec[i * 2 + 1];
+ }
+ for (i = (unsigned int) error; i < nmatches; i++)
+ matches[i].start = matches[i].end = -1;
+
+out:
+ if (nmatches > ARRAY_SIZE(static_ovec) / 3)
+ git__free(ovec);
+ if (error < 0)
+ return (error == PCRE_ERROR_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC;
+ return 0;
+}
+
+#elif defined(GIT_REGEX_PCRE2)
+
+int git_regexp_compile(git_regexp *r, const char *pattern, int flags)
+{
+ unsigned char errmsg[1024];
+ PCRE2_SIZE erroff;
+ int error, cflags = 0;
+
+ if (flags & GIT_REGEXP_ICASE)
+ cflags |= PCRE2_CASELESS;
+
+ if ((*r = pcre2_compile((const unsigned char *) pattern, PCRE2_ZERO_TERMINATED,
+ cflags, &error, &erroff, NULL)) == NULL) {
+ pcre2_get_error_message(error, errmsg, sizeof(errmsg));
+ git_error_set_str(GIT_ERROR_REGEX, (char *) errmsg);
+ return GIT_EINVALIDSPEC;
+ }
+
+ return 0;
+}
+
+void git_regexp_dispose(git_regexp *r)
+{
+ pcre2_code_free(*r);
+ *r = NULL;
+}
+
+int git_regexp_match(const git_regexp *r, const char *string)
+{
+ pcre2_match_data *data;
+ int error;
+
+ data = pcre2_match_data_create(1, NULL);
+ GIT_ERROR_CHECK_ALLOC(data);
+
+ error = pcre2_match(*r, (const unsigned char *) string, strlen(string), 0, 0, data, NULL);
+ pcre2_match_data_free(data);
+ if (error < 0)
+ return (error == PCRE2_ERROR_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC;
+
+ return 0;
+}
+
+int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches, git_regmatch *matches)
+{
+ pcre2_match_data *data = NULL;
+ PCRE2_SIZE *ovec;
+ int error;
+ size_t i;
+
+ if ((data = pcre2_match_data_create(nmatches, NULL)) == NULL) {
+ git_error_set_oom();
+ goto out;
+ }
+
+ if ((error = pcre2_match(*r, (const unsigned char *) string, strlen(string),
+ 0, 0, data, NULL)) < 0)
+ goto out;
+
+ if (error == 0 || (unsigned int) error > nmatches)
+ error = nmatches;
+ ovec = pcre2_get_ovector_pointer(data);
+
+ for (i = 0; i < (unsigned int) error; i++) {
+ matches[i].start = (ovec[i * 2] == PCRE2_UNSET) ? -1 : (ssize_t) ovec[i * 2];
+ matches[i].end = (ovec[i * 2 + 1] == PCRE2_UNSET) ? -1 : (ssize_t) ovec[i * 2 + 1];
+ }
+ for (i = (unsigned int) error; i < nmatches; i++)
+ matches[i].start = matches[i].end = -1;
+
+out:
+ pcre2_match_data_free(data);
+ if (error < 0)
+ return (error == PCRE2_ERROR_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC;
+ return 0;
+}
+
+#elif defined(GIT_REGEX_REGCOMP) || defined(GIT_REGEX_REGCOMP_L)
+
+#if defined(GIT_REGEX_REGCOMP_L)
+# include <xlocale.h>
+#endif
+
+int git_regexp_compile(git_regexp *r, const char *pattern, int flags)
+{
+ int cflags = REG_EXTENDED, error;
+ char errmsg[1024];
+
+ if (flags & GIT_REGEXP_ICASE)
+ cflags |= REG_ICASE;
+
+# if defined(GIT_REGEX_REGCOMP)
+ if ((error = regcomp(r, pattern, cflags)) != 0)
+# else
+ if ((error = regcomp_l(r, pattern, cflags, (locale_t) 0)) != 0)
+# endif
+ {
+ regerror(error, r, errmsg, sizeof(errmsg));
+ git_error_set_str(GIT_ERROR_REGEX, errmsg);
+ return GIT_EINVALIDSPEC;
+ }
+
+ return 0;
+}
+
+void git_regexp_dispose(git_regexp *r)
+{
+ regfree(r);
+}
+
+int git_regexp_match(const git_regexp *r, const char *string)
+{
+ int error;
+ if ((error = regexec(r, string, 0, NULL, 0)) != 0)
+ return (error == REG_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC;
+ return 0;
+}
+
+int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches, git_regmatch *matches)
+{
+ regmatch_t static_m[3], *m;
+ int error;
+ size_t i;
+
+ if (nmatches <= ARRAY_SIZE(static_m))
+ m = static_m;
+ else
+ m = git__calloc(nmatches, sizeof(*m));
+
+ if ((error = regexec(r, string, nmatches, m, 0)) != 0)
+ goto out;
+
+ for (i = 0; i < nmatches; i++) {
+ matches[i].start = (m[i].rm_so < 0) ? -1 : m[i].rm_so;
+ matches[i].end = (m[i].rm_eo < 0) ? -1 : m[i].rm_eo;
+ }
+
+out:
+ if (nmatches > ARRAY_SIZE(static_m))
+ git__free(m);
+ if (error)
+ return (error == REG_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC;
+ return 0;
+}
+
+#endif
diff --git a/src/util/regexp.h b/src/util/regexp.h
new file mode 100644
index 0000000..d0862b1
--- /dev/null
+++ b/src/util/regexp.h
@@ -0,0 +1,97 @@
+/*
+ * 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_regexp_h__
+#define INCLUDE_regexp_h__
+
+#include "git2_util.h"
+
+#if defined(GIT_REGEX_BUILTIN) || defined(GIT_REGEX_PCRE)
+# include "pcre.h"
+typedef pcre *git_regexp;
+# define GIT_REGEX_INIT NULL
+#elif defined(GIT_REGEX_PCRE2)
+# define PCRE2_CODE_UNIT_WIDTH 8
+# include <pcre2.h>
+typedef pcre2_code *git_regexp;
+# define GIT_REGEX_INIT NULL
+#elif defined(GIT_REGEX_REGCOMP) || defined(GIT_REGEX_REGCOMP_L)
+# include <regex.h>
+typedef regex_t git_regexp;
+# define GIT_REGEX_INIT { 0 }
+#else
+# error "No regex backend"
+#endif
+
+/** Options supported by @git_regexp_compile. */
+typedef enum {
+ /** Enable case-insensitive matching */
+ GIT_REGEXP_ICASE = (1 << 0)
+} git_regexp_flags_t;
+
+/** Structure containing information about regular expression matching groups */
+typedef struct {
+ /** Start of the given match. -1 if the group didn't match anything */
+ ssize_t start;
+ /** End of the given match. -1 if the group didn't match anything */
+ ssize_t end;
+} git_regmatch;
+
+/**
+ * Compile a regular expression. The compiled expression needs to
+ * be cleaned up afterwards with `git_regexp_dispose`.
+ *
+ * @param r Pointer to the storage where to initialize the regular expression.
+ * @param pattern The pattern that shall be compiled.
+ * @param flags Flags to alter how the pattern shall be handled.
+ * 0 for defaults, otherwise see @git_regexp_flags_t.
+ * @return 0 on success, otherwise a negative return value.
+ */
+int git_regexp_compile(git_regexp *r, const char *pattern, int flags);
+
+/**
+ * Free memory associated with the regular expression
+ *
+ * @param r The regular expression structure to dispose.
+ */
+void git_regexp_dispose(git_regexp *r);
+
+/**
+ * Test whether a given string matches a compiled regular
+ * expression.
+ *
+ * @param r Compiled regular expression.
+ * @param string String to match against the regular expression.
+ * @return 0 if the string matches, a negative error code
+ * otherwise. GIT_ENOTFOUND if no match was found,
+ * GIT_EINVALIDSPEC if the regular expression matching
+ * was invalid.
+ */
+int git_regexp_match(const git_regexp *r, const char *string);
+
+/**
+ * Search for matches inside of a given string.
+ *
+ * Given a regular expression with capturing groups, this
+ * function will populate provided @git_regmatch structures with
+ * offsets for each of the given matches. Non-matching groups
+ * will have start and end values of the respective @git_regmatch
+ * structure set to -1.
+ *
+ * @param r Compiled regular expression.
+ * @param string String to match against the regular expression.
+ * @param nmatches Number of @git_regmatch structures provided by
+ * the user.
+ * @param matches Pointer to an array of @git_regmatch structures.
+ * @return 0 if the string matches, a negative error code
+ * otherwise. GIT_ENOTFOUND if no match was found,
+ * GIT_EINVALIDSPEC if the regular expression matching
+ * was invalid.
+ */
+int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches, git_regmatch *matches);
+
+#endif
diff --git a/src/util/runtime.c b/src/util/runtime.c
new file mode 100644
index 0000000..a7711ff
--- /dev/null
+++ b/src/util/runtime.c
@@ -0,0 +1,162 @@
+/*
+ * 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 "runtime.h"
+
+static git_runtime_shutdown_fn shutdown_callback[32];
+static git_atomic32 shutdown_callback_count;
+
+static git_atomic32 init_count;
+
+static int init_common(git_runtime_init_fn init_fns[], size_t cnt)
+{
+ size_t i;
+ int ret;
+
+ /* Initialize subsystems that have global state */
+ for (i = 0; i < cnt; i++) {
+ if ((ret = init_fns[i]()) != 0)
+ break;
+ }
+
+ GIT_MEMORY_BARRIER;
+
+ return ret;
+}
+
+static void shutdown_common(void)
+{
+ git_runtime_shutdown_fn cb;
+ int pos;
+
+ for (pos = git_atomic32_get(&shutdown_callback_count);
+ pos > 0;
+ pos = git_atomic32_dec(&shutdown_callback_count)) {
+ cb = git_atomic_swap(shutdown_callback[pos - 1], NULL);
+
+ if (cb != NULL)
+ cb();
+ }
+}
+
+int git_runtime_shutdown_register(git_runtime_shutdown_fn callback)
+{
+ int count = git_atomic32_inc(&shutdown_callback_count);
+
+ if (count > (int)ARRAY_SIZE(shutdown_callback) || count == 0) {
+ git_error_set(GIT_ERROR_INVALID,
+ "too many shutdown callbacks registered");
+ git_atomic32_dec(&shutdown_callback_count);
+ return -1;
+ }
+
+ shutdown_callback[count - 1] = callback;
+
+ return 0;
+}
+
+#if defined(GIT_THREADS) && defined(GIT_WIN32)
+
+/*
+ * On Win32, we use a spinlock to provide locking semantics. This is
+ * lighter-weight than a proper critical section.
+ */
+static volatile LONG init_spinlock = 0;
+
+GIT_INLINE(int) init_lock(void)
+{
+ while (InterlockedCompareExchange(&init_spinlock, 1, 0)) { Sleep(0); }
+ return 0;
+}
+
+GIT_INLINE(int) init_unlock(void)
+{
+ InterlockedExchange(&init_spinlock, 0);
+ return 0;
+}
+
+#elif defined(GIT_THREADS) && defined(_POSIX_THREADS)
+
+/*
+ * On POSIX, we need to use a proper mutex for locking. We might prefer
+ * a spinlock here, too, but there's no static initializer for a
+ * pthread_spinlock_t.
+ */
+static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+GIT_INLINE(int) init_lock(void)
+{
+ return pthread_mutex_lock(&init_mutex) == 0 ? 0 : -1;
+}
+
+GIT_INLINE(int) init_unlock(void)
+{
+ return pthread_mutex_unlock(&init_mutex) == 0 ? 0 : -1;
+}
+
+#elif defined(GIT_THREADS)
+# error unknown threading model
+#else
+
+# define init_lock() git__noop()
+# define init_unlock() git__noop()
+
+#endif
+
+int git_runtime_init(git_runtime_init_fn init_fns[], size_t cnt)
+{
+ int ret;
+
+ if (init_lock() < 0)
+ return -1;
+
+ /* Only do work on a 0 -> 1 transition of the refcount */
+ if ((ret = git_atomic32_inc(&init_count)) == 1) {
+ if (init_common(init_fns, cnt) < 0)
+ ret = -1;
+ }
+
+ if (init_unlock() < 0)
+ return -1;
+
+ return ret;
+}
+
+int git_runtime_init_count(void)
+{
+ int ret;
+
+ if (init_lock() < 0)
+ return -1;
+
+ ret = git_atomic32_get(&init_count);
+
+ if (init_unlock() < 0)
+ return -1;
+
+ return ret;
+}
+
+int git_runtime_shutdown(void)
+{
+ int ret;
+
+ /* Enter the lock */
+ if (init_lock() < 0)
+ return -1;
+
+ /* Only do work on a 1 -> 0 transition of the refcount */
+ if ((ret = git_atomic32_dec(&init_count)) == 0)
+ shutdown_common();
+
+ /* Exit the lock */
+ if (init_unlock() < 0)
+ return -1;
+
+ return ret;
+}
diff --git a/src/util/runtime.h b/src/util/runtime.h
new file mode 100644
index 0000000..6cbfd60
--- /dev/null
+++ b/src/util/runtime.h
@@ -0,0 +1,62 @@
+/*
+ * 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"
+
+typedef int (*git_runtime_init_fn)(void);
+typedef void (*git_runtime_shutdown_fn)(void);
+
+/**
+ * Start up a new runtime. If this is the first time that this
+ * function is called within the context of the current library
+ * or executable, then the given `init_fns` will be invoked. If
+ * it is not the first time, they will be ignored.
+ *
+ * The given initialization functions _may_ register shutdown
+ * handlers using `git_runtime_shutdown_register` to be notified
+ * when the runtime is shutdown.
+ *
+ * @param init_fns The list of initialization functions to call
+ * @param cnt The number of init_fns
+ * @return The number of initializations performed (including this one) or an error
+ */
+int git_runtime_init(git_runtime_init_fn init_fns[], size_t cnt);
+
+/*
+ * Returns the number of initializations active (the number of calls to
+ * `git_runtime_init` minus the number of calls sto `git_runtime_shutdown`).
+ * If 0, the runtime is not currently initialized.
+ *
+ * @return The number of initializations performed or an error
+ */
+int git_runtime_init_count(void);
+
+/**
+ * Shut down the runtime. If this is the last shutdown call,
+ * such that there are no remaining `init` calls, then any
+ * shutdown hooks that have been registered will be invoked.
+ *
+ * The number of outstanding initializations will be returned.
+ * If this number is 0, then the runtime is shutdown.
+ *
+ * @return The number of outstanding initializations (after this one) or an error
+ */
+int git_runtime_shutdown(void);
+
+/**
+ * Register a shutdown handler for this runtime. This should be done
+ * by a function invoked by `git_runtime_init` to ensure that the
+ * appropriate locks are taken.
+ *
+ * @param callback The shutdown handler callback
+ * @return 0 or an error code
+ */
+int git_runtime_shutdown_register(git_runtime_shutdown_fn callback);
+
+#endif
diff --git a/src/util/sortedcache.c b/src/util/sortedcache.c
new file mode 100644
index 0000000..7ff900e
--- /dev/null
+++ b/src/util/sortedcache.c
@@ -0,0 +1,380 @@
+/*
+ * 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 "sortedcache.h"
+
+int git_sortedcache_new(
+ git_sortedcache **out,
+ size_t item_path_offset,
+ git_sortedcache_free_item_fn free_item,
+ void *free_item_payload,
+ git_vector_cmp item_cmp,
+ const char *path)
+{
+ git_sortedcache *sc;
+ size_t pathlen, alloclen;
+
+ pathlen = path ? strlen(path) : 0;
+
+ GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_sortedcache), pathlen);
+ GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
+ sc = git__calloc(1, alloclen);
+ GIT_ERROR_CHECK_ALLOC(sc);
+
+ if (git_pool_init(&sc->pool, 1) < 0 ||
+ git_vector_init(&sc->items, 4, item_cmp) < 0 ||
+ git_strmap_new(&sc->map) < 0)
+ goto fail;
+
+ if (git_rwlock_init(&sc->lock)) {
+ git_error_set(GIT_ERROR_OS, "failed to initialize lock");
+ goto fail;
+ }
+
+ sc->item_path_offset = item_path_offset;
+ sc->free_item = free_item;
+ sc->free_item_payload = free_item_payload;
+ GIT_REFCOUNT_INC(sc);
+ if (pathlen)
+ memcpy(sc->path, path, pathlen);
+
+ *out = sc;
+ return 0;
+
+fail:
+ git_strmap_free(sc->map);
+ git_vector_free(&sc->items);
+ git_pool_clear(&sc->pool);
+ git__free(sc);
+ return -1;
+}
+
+void git_sortedcache_incref(git_sortedcache *sc)
+{
+ GIT_REFCOUNT_INC(sc);
+}
+
+const char *git_sortedcache_path(git_sortedcache *sc)
+{
+ return sc->path;
+}
+
+static void sortedcache_clear(git_sortedcache *sc)
+{
+ git_strmap_clear(sc->map);
+
+ if (sc->free_item) {
+ size_t i;
+ void *item;
+
+ git_vector_foreach(&sc->items, i, item) {
+ sc->free_item(sc->free_item_payload, item);
+ }
+ }
+
+ git_vector_clear(&sc->items);
+
+ git_pool_clear(&sc->pool);
+}
+
+static void sortedcache_free(git_sortedcache *sc)
+{
+ /* acquire write lock to make sure everyone else is done */
+ if (git_sortedcache_wlock(sc) < 0)
+ return;
+
+ sortedcache_clear(sc);
+ git_vector_free(&sc->items);
+ git_strmap_free(sc->map);
+
+ git_sortedcache_wunlock(sc);
+
+ git_rwlock_free(&sc->lock);
+ git__free(sc);
+}
+
+void git_sortedcache_free(git_sortedcache *sc)
+{
+ if (!sc)
+ return;
+ GIT_REFCOUNT_DEC(sc, sortedcache_free);
+}
+
+static int sortedcache_copy_item(void *payload, void *tgt_item, void *src_item)
+{
+ git_sortedcache *sc = payload;
+ /* path will already have been copied by upsert */
+ memcpy(tgt_item, src_item, sc->item_path_offset);
+ return 0;
+}
+
+/* copy a sorted cache */
+int git_sortedcache_copy(
+ git_sortedcache **out,
+ git_sortedcache *src,
+ bool lock,
+ int (*copy_item)(void *payload, void *tgt_item, void *src_item),
+ void *payload)
+{
+ int error = 0;
+ git_sortedcache *tgt;
+ size_t i;
+ void *src_item, *tgt_item;
+
+ /* just use memcpy if no special copy fn is passed in */
+ if (!copy_item) {
+ copy_item = sortedcache_copy_item;
+ payload = src;
+ }
+
+ if ((error = git_sortedcache_new(
+ &tgt, src->item_path_offset,
+ src->free_item, src->free_item_payload,
+ src->items._cmp, src->path)) < 0)
+ return error;
+
+ if (lock && git_sortedcache_rlock(src) < 0) {
+ git_sortedcache_free(tgt);
+ return -1;
+ }
+
+ git_vector_foreach(&src->items, i, src_item) {
+ char *path = ((char *)src_item) + src->item_path_offset;
+
+ if ((error = git_sortedcache_upsert(&tgt_item, tgt, path)) < 0 ||
+ (error = copy_item(payload, tgt_item, src_item)) < 0)
+ break;
+ }
+
+ if (lock)
+ git_sortedcache_runlock(src);
+ if (error)
+ git_sortedcache_free(tgt);
+
+ *out = !error ? tgt : NULL;
+
+ return error;
+}
+
+/* lock sortedcache while making modifications */
+int git_sortedcache_wlock(git_sortedcache *sc)
+{
+ GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */
+
+ if (git_rwlock_wrlock(&sc->lock) < 0) {
+ git_error_set(GIT_ERROR_OS, "unable to acquire write lock on cache");
+ return -1;
+ }
+ return 0;
+}
+
+/* unlock sorted cache when done with modifications */
+void git_sortedcache_wunlock(git_sortedcache *sc)
+{
+ git_vector_sort(&sc->items);
+ git_rwlock_wrunlock(&sc->lock);
+}
+
+/* lock sortedcache for read */
+int git_sortedcache_rlock(git_sortedcache *sc)
+{
+ GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */
+
+ if (git_rwlock_rdlock(&sc->lock) < 0) {
+ git_error_set(GIT_ERROR_OS, "unable to acquire read lock on cache");
+ return -1;
+ }
+ return 0;
+}
+
+/* unlock sorted cache when done reading */
+void git_sortedcache_runlock(git_sortedcache *sc)
+{
+ GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */
+ git_rwlock_rdunlock(&sc->lock);
+}
+
+/* if the file has changed, lock cache and load file contents into buf;
+ * returns <0 on error, >0 if file has not changed
+ */
+int git_sortedcache_lockandload(git_sortedcache *sc, git_str *buf)
+{
+ int error, fd;
+ struct stat st;
+
+ if ((error = git_sortedcache_wlock(sc)) < 0)
+ return error;
+
+ if ((error = git_futils_filestamp_check(&sc->stamp, sc->path)) <= 0)
+ goto unlock;
+
+ if ((fd = git_futils_open_ro(sc->path)) < 0) {
+ error = fd;
+ goto unlock;
+ }
+
+ if (p_fstat(fd, &st) < 0) {
+ git_error_set(GIT_ERROR_OS, "failed to stat file");
+ error = -1;
+ (void)p_close(fd);
+ goto unlock;
+ }
+
+ if (!git__is_sizet(st.st_size)) {
+ git_error_set(GIT_ERROR_INVALID, "unable to load file larger than size_t");
+ error = -1;
+ (void)p_close(fd);
+ goto unlock;
+ }
+
+ if (buf)
+ error = git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size);
+
+ (void)p_close(fd);
+
+ if (error < 0)
+ goto unlock;
+
+ return 1; /* return 1 -> file needs reload and was successfully loaded */
+
+unlock:
+ git_sortedcache_wunlock(sc);
+ return error;
+}
+
+void git_sortedcache_updated(git_sortedcache *sc)
+{
+ /* update filestamp to latest value */
+ git_futils_filestamp_check(&sc->stamp, sc->path);
+}
+
+/* release all items in sorted cache */
+int git_sortedcache_clear(git_sortedcache *sc, bool wlock)
+{
+ if (wlock && git_sortedcache_wlock(sc) < 0)
+ return -1;
+
+ sortedcache_clear(sc);
+
+ if (wlock)
+ git_sortedcache_wunlock(sc);
+
+ return 0;
+}
+
+/* find and/or insert item, returning pointer to item data */
+int git_sortedcache_upsert(void **out, git_sortedcache *sc, const char *key)
+{
+ size_t keylen, itemlen;
+ int error = 0;
+ char *item_key;
+ void *item;
+
+ if ((item = git_strmap_get(sc->map, key)) != NULL)
+ goto done;
+
+ keylen = strlen(key);
+ itemlen = sc->item_path_offset + keylen + 1;
+ itemlen = (itemlen + 7) & ~7;
+
+ if ((item = git_pool_mallocz(&sc->pool, itemlen)) == NULL) {
+ /* don't use GIT_ERROR_CHECK_ALLOC b/c of lock */
+ error = -1;
+ goto done;
+ }
+
+ /* one strange thing is that even if the vector or hash table insert
+ * fail, there is no way to free the pool item so we just abandon it
+ */
+
+ item_key = ((char *)item) + sc->item_path_offset;
+ memcpy(item_key, key, keylen);
+
+ if ((error = git_strmap_set(sc->map, item_key, item)) < 0)
+ goto done;
+
+ if ((error = git_vector_insert(&sc->items, item)) < 0)
+ git_strmap_delete(sc->map, item_key);
+
+done:
+ if (out)
+ *out = !error ? item : NULL;
+ return error;
+}
+
+/* lookup item by key */
+void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key)
+{
+ return git_strmap_get(sc->map, key);
+}
+
+/* find out how many items are in the cache */
+size_t git_sortedcache_entrycount(const git_sortedcache *sc)
+{
+ return git_vector_length(&sc->items);
+}
+
+/* lookup item by index */
+void *git_sortedcache_entry(git_sortedcache *sc, size_t pos)
+{
+ /* make sure the items are sorted so this gets the correct item */
+ if (!git_vector_is_sorted(&sc->items))
+ git_vector_sort(&sc->items);
+
+ return git_vector_get(&sc->items, pos);
+}
+
+/* helper struct so bsearch callback can know offset + key value for cmp */
+struct sortedcache_magic_key {
+ size_t offset;
+ const char *key;
+};
+
+static int sortedcache_magic_cmp(const void *key, const void *value)
+{
+ const struct sortedcache_magic_key *magic = key;
+ const char *value_key = ((const char *)value) + magic->offset;
+ return strcmp(magic->key, value_key);
+}
+
+/* lookup index of item by key */
+int git_sortedcache_lookup_index(
+ size_t *out, git_sortedcache *sc, const char *key)
+{
+ struct sortedcache_magic_key magic;
+
+ magic.offset = sc->item_path_offset;
+ magic.key = key;
+
+ return git_vector_bsearch2(out, &sc->items, sortedcache_magic_cmp, &magic);
+}
+
+/* remove entry from cache */
+int git_sortedcache_remove(git_sortedcache *sc, size_t pos)
+{
+ char *item;
+
+ /*
+ * Because of pool allocation, this can't actually remove the item,
+ * but we can remove it from the items vector and the hash table.
+ */
+
+ if ((item = git_vector_get(&sc->items, pos)) == NULL) {
+ git_error_set(GIT_ERROR_INVALID, "removing item out of range");
+ return GIT_ENOTFOUND;
+ }
+
+ (void)git_vector_remove(&sc->items, pos);
+
+ git_strmap_delete(sc->map, item + sc->item_path_offset);
+
+ if (sc->free_item)
+ sc->free_item(sc->free_item_payload, item);
+
+ return 0;
+}
+
diff --git a/src/util/sortedcache.h b/src/util/sortedcache.h
new file mode 100644
index 0000000..3eee465
--- /dev/null
+++ b/src/util/sortedcache.h
@@ -0,0 +1,182 @@
+/*
+ * 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_sorted_cache_h__
+#define INCLUDE_sorted_cache_h__
+
+#include "git2_util.h"
+
+#include "util.h"
+#include "futils.h"
+#include "vector.h"
+#include "thread.h"
+#include "pool.h"
+#include "strmap.h"
+
+#include <stddef.h>
+
+/*
+ * The purpose of this data structure is to cache the parsed contents of a
+ * file (a.k.a. the backing file) where each item in the file can be
+ * identified by a key string and you want to both look them up by name
+ * and traverse them in sorted order. Each item is assumed to itself end
+ * in a GIT_FLEX_ARRAY.
+ */
+
+typedef void (*git_sortedcache_free_item_fn)(void *payload, void *item);
+
+typedef struct {
+ git_refcount rc;
+ git_rwlock lock;
+ size_t item_path_offset;
+ git_sortedcache_free_item_fn free_item;
+ void *free_item_payload;
+ git_pool pool;
+ git_vector items;
+ git_strmap *map;
+ git_futils_filestamp stamp;
+ char path[GIT_FLEX_ARRAY];
+} git_sortedcache;
+
+/* Create a new sortedcache
+ *
+ * Even though every sortedcache stores items with a GIT_FLEX_ARRAY at
+ * the end containing their key string, you have to provide the item_cmp
+ * sorting function because the sorting function doesn't get a payload
+ * and therefore can't know the offset to the item key string. :-(
+ *
+ * @param out The allocated git_sortedcache
+ * @param item_path_offset Offset to the GIT_FLEX_ARRAY item key in the
+ * struct - use offsetof(struct mine, key-field) to get this
+ * @param free_item Optional callback to free each item
+ * @param free_item_payload Optional payload passed to free_item callback
+ * @param item_cmp Compare the keys of two items
+ * @param path The path to the backing store file for this cache; this
+ * may be NULL. The cache makes it easy to load this and check
+ * if it has been modified since the last load and/or write.
+ */
+GIT_WARN_UNUSED_RESULT int git_sortedcache_new(
+ git_sortedcache **out,
+ size_t item_path_offset, /* use offsetof(struct, path-field) macro */
+ git_sortedcache_free_item_fn free_item,
+ void *free_item_payload,
+ git_vector_cmp item_cmp,
+ const char *path);
+
+/* Copy a sorted cache
+ *
+ * - `copy_item` can be NULL to just use memcpy
+ * - if `lock`, grabs read lock on `src` during copy and releases after
+ */
+GIT_WARN_UNUSED_RESULT int git_sortedcache_copy(
+ git_sortedcache **out,
+ git_sortedcache *src,
+ bool lock,
+ int (*copy_item)(void *payload, void *tgt_item, void *src_item),
+ void *payload);
+
+/* Free sorted cache (first calling `free_item` callbacks)
+ *
+ * Don't call on a locked collection - it may acquire a write lock
+ */
+void git_sortedcache_free(git_sortedcache *sc);
+
+/* Increment reference count - balance with call to free */
+void git_sortedcache_incref(git_sortedcache *sc);
+
+/* Get the pathname associated with this cache at creation time */
+const char *git_sortedcache_path(git_sortedcache *sc);
+
+/*
+ * CACHE WRITE FUNCTIONS
+ *
+ * The following functions require you to have a writer lock to make the
+ * modification. Some of the functions take a `wlock` parameter and
+ * will optionally lock and unlock for you if that is passed as true.
+ *
+ */
+
+/* Lock sortedcache for write */
+GIT_WARN_UNUSED_RESULT int git_sortedcache_wlock(git_sortedcache *sc);
+
+/* Unlock sorted cache when done with write */
+void git_sortedcache_wunlock(git_sortedcache *sc);
+
+/* Lock cache and load backing file into a buffer.
+ *
+ * This grabs a write lock on the cache then looks at the modification
+ * time and size of the file on disk.
+ *
+ * If the file appears to have changed, this loads the file contents into
+ * the buffer and returns a positive value leaving the cache locked - the
+ * caller should parse the file content, update the cache as needed, then
+ * release the lock. NOTE: In this case, the caller MUST unlock the cache.
+ *
+ * If the file appears to be unchanged, then this automatically releases
+ * the lock on the cache, clears the buffer, and returns 0.
+ *
+ * @return 0 if up-to-date, 1 if out-of-date, <0 on error
+ */
+GIT_WARN_UNUSED_RESULT int git_sortedcache_lockandload(
+ git_sortedcache *sc, git_str *buf);
+
+/* Refresh file timestamp after write completes
+ * You should already be holding the write lock when you call this.
+ */
+void git_sortedcache_updated(git_sortedcache *sc);
+
+/* Release all items in sorted cache
+ *
+ * If `wlock` is true, grabs write lock and releases when done, otherwise
+ * you should already be holding a write lock when you call this.
+ */
+GIT_WARN_UNUSED_RESULT int git_sortedcache_clear(
+ git_sortedcache *sc, bool wlock);
+
+/* Find and/or insert item, returning pointer to item data.
+ * You should already be holding the write lock when you call this.
+ */
+GIT_WARN_UNUSED_RESULT int git_sortedcache_upsert(
+ void **out, git_sortedcache *sc, const char *key);
+
+/* Removes entry at pos from cache
+ * You should already be holding the write lock when you call this.
+ */
+int git_sortedcache_remove(git_sortedcache *sc, size_t pos);
+
+/*
+ * CACHE READ FUNCTIONS
+ *
+ * The following functions access items in the cache. To prevent the
+ * results from being invalidated before they can be used, you should be
+ * holding either a read lock or a write lock when using these functions.
+ *
+ */
+
+/* Lock sortedcache for read */
+GIT_WARN_UNUSED_RESULT int git_sortedcache_rlock(git_sortedcache *sc);
+
+/* Unlock sorted cache when done with read */
+void git_sortedcache_runlock(git_sortedcache *sc);
+
+/* Lookup item by key - returns NULL if not found */
+void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key);
+
+/* Get how many items are in the cache
+ *
+ * You can call this function without holding a lock, but be aware
+ * that it may change before you use it.
+ */
+size_t git_sortedcache_entrycount(const git_sortedcache *sc);
+
+/* Lookup item by index - returns NULL if out of range */
+void *git_sortedcache_entry(git_sortedcache *sc, size_t pos);
+
+/* Lookup index of item by key - returns GIT_ENOTFOUND if not found */
+int git_sortedcache_lookup_index(
+ size_t *out, git_sortedcache *sc, const char *key);
+
+#endif
diff --git a/src/util/staticstr.h b/src/util/staticstr.h
new file mode 100644
index 0000000..b7d0790
--- /dev/null
+++ b/src/util/staticstr.h
@@ -0,0 +1,66 @@
+/*
+ * 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_stackstr_h__
+#define INCLUDE_stackstr_h__
+
+#include "git2_util.h"
+
+typedef struct {
+ /* Length of / number of bytes used by `data`. */
+ size_t len;
+
+ /* Size of the allocated `data` buffer. */
+ size_t size;
+
+ /* The actual string buffer data. */
+ char data[GIT_FLEX_ARRAY];
+} git_staticstr;
+
+#define git_staticstr_with_size(__size) \
+ struct { \
+ size_t len; \
+ size_t size; \
+ char data[__size]; \
+ }
+
+#define git_staticstr_init(__str, __size) \
+ do { \
+ (__str)->len = 0; \
+ (__str)->size = __size; \
+ (__str)->data[0] = '\0'; \
+ } while(0)
+
+#define git_staticstr_offset(__str) \
+ ((__str)->data + (__str)->len)
+
+#define git_staticstr_remain(__str) \
+ ((__str)->len > (__str)->size ? 0 : ((__str)->size - (__str)->len))
+
+#define git_staticstr_increase(__str, __len) \
+ do { ((__str)->len += __len); } while(0)
+
+#define git_staticstr_consume_bytes(__str, __len) \
+ do { git_staticstr_consume(__str, (__str)->data + __len); } while(0)
+
+#define git_staticstr_consume(__str, __end) \
+ do { \
+ if (__end > (__str)->data && \
+ __end <= (__str)->data + (__str)->len) { \
+ size_t __consumed = __end - (__str)->data; \
+ memmove((__str)->data, __end, (__str)->len - __consumed); \
+ (__str)->len -= __consumed; \
+ (__str)->data[(__str)->len] = '\0'; \
+ } \
+ } while(0)
+
+#define git_staticstr_clear(__str) \
+ do { \
+ (__str)->len = 0; \
+ (__str)->data[0] = 0; \
+ } while(0)
+
+#endif
diff --git a/src/util/str.c b/src/util/str.c
new file mode 100644
index 0000000..0d405bf
--- /dev/null
+++ b/src/util/str.c
@@ -0,0 +1,1372 @@
+/*
+ * 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 "str.h"
+#include "posix.h"
+#include <ctype.h>
+
+/* Used as default value for git_str->ptr so that people can always
+ * assume ptr is non-NULL and zero terminated even for new git_strs.
+ */
+char git_str__initstr[1];
+
+char git_str__oom[1];
+
+#define ENSURE_SIZE(b, d) \
+ if ((b)->ptr == git_str__oom || \
+ ((d) > (b)->asize && git_str_grow((b), (d)) < 0))\
+ return -1;
+
+
+int git_str_init(git_str *buf, size_t initial_size)
+{
+ buf->asize = 0;
+ buf->size = 0;
+ buf->ptr = git_str__initstr;
+
+ ENSURE_SIZE(buf, initial_size);
+
+ return 0;
+}
+
+int git_str_try_grow(
+ git_str *buf, size_t target_size, bool mark_oom)
+{
+ char *new_ptr;
+ size_t new_size;
+
+ if (buf->ptr == git_str__oom)
+ return -1;
+
+ if (buf->asize == 0 && buf->size != 0) {
+ git_error_set(GIT_ERROR_INVALID, "cannot grow a borrowed buffer");
+ return GIT_EINVALID;
+ }
+
+ if (!target_size)
+ target_size = buf->size;
+
+ if (target_size <= buf->asize)
+ return 0;
+
+ if (buf->asize == 0) {
+ new_size = target_size;
+ new_ptr = NULL;
+ } else {
+ new_size = buf->asize;
+ /*
+ * Grow the allocated buffer by 1.5 to allow
+ * re-use of memory holes resulting from the
+ * realloc. If this is still too small, then just
+ * use the target size.
+ */
+ if ((new_size = (new_size << 1) - (new_size >> 1)) < target_size)
+ new_size = target_size;
+ new_ptr = buf->ptr;
+ }
+
+ /* round allocation up to multiple of 8 */
+ new_size = (new_size + 7) & ~7;
+
+ if (new_size < buf->size) {
+ if (mark_oom) {
+ if (buf->ptr && buf->ptr != git_str__initstr)
+ git__free(buf->ptr);
+ buf->ptr = git_str__oom;
+ }
+
+ git_error_set_oom();
+ return -1;
+ }
+
+ new_ptr = git__realloc(new_ptr, new_size);
+
+ if (!new_ptr) {
+ if (mark_oom) {
+ if (buf->ptr && (buf->ptr != git_str__initstr))
+ git__free(buf->ptr);
+ buf->ptr = git_str__oom;
+ }
+ return -1;
+ }
+
+ buf->asize = new_size;
+ buf->ptr = new_ptr;
+
+ /* truncate the existing buffer size if necessary */
+ if (buf->size >= buf->asize)
+ buf->size = buf->asize - 1;
+ buf->ptr[buf->size] = '\0';
+
+ return 0;
+}
+
+int git_str_grow(git_str *buffer, size_t target_size)
+{
+ return git_str_try_grow(buffer, target_size, true);
+}
+
+int git_str_grow_by(git_str *buffer, size_t additional_size)
+{
+ size_t newsize;
+
+ if (GIT_ADD_SIZET_OVERFLOW(&newsize, buffer->size, additional_size)) {
+ buffer->ptr = git_str__oom;
+ return -1;
+ }
+
+ return git_str_try_grow(buffer, newsize, true);
+}
+
+void git_str_dispose(git_str *buf)
+{
+ if (!buf) return;
+
+ if (buf->asize > 0 && buf->ptr != NULL && buf->ptr != git_str__oom)
+ git__free(buf->ptr);
+
+ git_str_init(buf, 0);
+}
+
+void git_str_clear(git_str *buf)
+{
+ buf->size = 0;
+
+ if (!buf->ptr) {
+ buf->ptr = git_str__initstr;
+ buf->asize = 0;
+ }
+
+ if (buf->asize > 0)
+ buf->ptr[0] = '\0';
+}
+
+int git_str_set(git_str *buf, const void *data, size_t len)
+{
+ size_t alloclen;
+
+ if (len == 0 || data == NULL) {
+ git_str_clear(buf);
+ } else {
+ if (data != buf->ptr) {
+ GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1);
+ ENSURE_SIZE(buf, alloclen);
+ memmove(buf->ptr, data, len);
+ }
+
+ buf->size = len;
+ if (buf->asize > buf->size)
+ buf->ptr[buf->size] = '\0';
+
+ }
+ return 0;
+}
+
+int git_str_sets(git_str *buf, const char *string)
+{
+ return git_str_set(buf, string, string ? strlen(string) : 0);
+}
+
+int git_str_putc(git_str *buf, char c)
+{
+ size_t new_size;
+ GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, 2);
+ ENSURE_SIZE(buf, new_size);
+ buf->ptr[buf->size++] = c;
+ buf->ptr[buf->size] = '\0';
+ return 0;
+}
+
+int git_str_putcn(git_str *buf, char c, size_t len)
+{
+ size_t new_size;
+ GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len);
+ GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
+ ENSURE_SIZE(buf, new_size);
+ memset(buf->ptr + buf->size, c, len);
+ buf->size += len;
+ buf->ptr[buf->size] = '\0';
+ return 0;
+}
+
+int git_str_put(git_str *buf, const char *data, size_t len)
+{
+ if (len) {
+ size_t new_size;
+
+ GIT_ASSERT_ARG(data);
+
+ GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len);
+ GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
+ ENSURE_SIZE(buf, new_size);
+ memmove(buf->ptr + buf->size, data, len);
+ buf->size += len;
+ buf->ptr[buf->size] = '\0';
+ }
+ return 0;
+}
+
+int git_str_puts(git_str *buf, const char *string)
+{
+ GIT_ASSERT_ARG(string);
+
+ return git_str_put(buf, string, strlen(string));
+}
+
+static char hex_encode[] = "0123456789abcdef";
+
+int git_str_encode_hexstr(git_str *str, const char *data, size_t len)
+{
+ size_t new_size, i;
+ char *s;
+
+ GIT_ERROR_CHECK_ALLOC_MULTIPLY(&new_size, len, 2);
+ GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
+
+ if (git_str_grow_by(str, new_size) < 0)
+ return -1;
+
+ s = str->ptr + str->size;
+
+ for (i = 0; i < len; i++) {
+ *s++ = hex_encode[(data[i] & 0xf0) >> 4];
+ *s++ = hex_encode[(data[i] & 0x0f)];
+ }
+
+ str->size += (len * 2);
+ str->ptr[str->size] = '\0';
+
+ return 0;
+}
+
+static const char base64_encode[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+int git_str_encode_base64(git_str *buf, const char *data, size_t len)
+{
+ size_t extra = len % 3;
+ uint8_t *write, a, b, c;
+ const uint8_t *read = (const uint8_t *)data;
+ size_t blocks = (len / 3) + !!extra, alloclen;
+
+ GIT_ERROR_CHECK_ALLOC_ADD(&blocks, blocks, 1);
+ GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloclen, blocks, 4);
+ GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, buf->size);
+
+ ENSURE_SIZE(buf, alloclen);
+ write = (uint8_t *)&buf->ptr[buf->size];
+
+ /* convert each run of 3 bytes into 4 output bytes */
+ for (len -= extra; len > 0; len -= 3) {
+ a = *read++;
+ b = *read++;
+ c = *read++;
+
+ *write++ = base64_encode[a >> 2];
+ *write++ = base64_encode[(a & 0x03) << 4 | b >> 4];
+ *write++ = base64_encode[(b & 0x0f) << 2 | c >> 6];
+ *write++ = base64_encode[c & 0x3f];
+ }
+
+ if (extra > 0) {
+ a = *read++;
+ b = (extra > 1) ? *read++ : 0;
+
+ *write++ = base64_encode[a >> 2];
+ *write++ = base64_encode[(a & 0x03) << 4 | b >> 4];
+ *write++ = (extra > 1) ? base64_encode[(b & 0x0f) << 2] : '=';
+ *write++ = '=';
+ }
+
+ buf->size = ((char *)write) - buf->ptr;
+ buf->ptr[buf->size] = '\0';
+
+ return 0;
+}
+
+/* The inverse of base64_encode */
+static const int8_t base64_decode[] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
+};
+
+int git_str_decode_base64(git_str *buf, const char *base64, size_t len)
+{
+ size_t i;
+ int8_t a, b, c, d;
+ size_t orig_size = buf->size, new_size;
+
+ if (len % 4) {
+ git_error_set(GIT_ERROR_INVALID, "invalid base64 input");
+ return -1;
+ }
+
+ GIT_ASSERT_ARG(len % 4 == 0);
+ GIT_ERROR_CHECK_ALLOC_ADD(&new_size, (len / 4 * 3), buf->size);
+ GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
+ ENSURE_SIZE(buf, new_size);
+
+ for (i = 0; i < len; i += 4) {
+ if ((a = base64_decode[(unsigned char)base64[i]]) < 0 ||
+ (b = base64_decode[(unsigned char)base64[i+1]]) < 0 ||
+ (c = base64_decode[(unsigned char)base64[i+2]]) < 0 ||
+ (d = base64_decode[(unsigned char)base64[i+3]]) < 0) {
+ buf->size = orig_size;
+ buf->ptr[buf->size] = '\0';
+
+ git_error_set(GIT_ERROR_INVALID, "invalid base64 input");
+ return -1;
+ }
+
+ buf->ptr[buf->size++] = ((a << 2) | (b & 0x30) >> 4);
+ buf->ptr[buf->size++] = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
+ buf->ptr[buf->size++] = (c & 0x03) << 6 | (d & 0x3f);
+ }
+
+ buf->ptr[buf->size] = '\0';
+ return 0;
+}
+
+static const char base85_encode[] =
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~";
+
+int git_str_encode_base85(git_str *buf, const char *data, size_t len)
+{
+ size_t blocks = (len / 4) + !!(len % 4), alloclen;
+
+ GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloclen, blocks, 5);
+ GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, buf->size);
+ GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
+
+ ENSURE_SIZE(buf, alloclen);
+
+ while (len) {
+ uint32_t acc = 0;
+ char b85[5];
+ int i;
+
+ for (i = 24; i >= 0; i -= 8) {
+ uint8_t ch = *data++;
+ acc |= (uint32_t)ch << i;
+
+ if (--len == 0)
+ break;
+ }
+
+ for (i = 4; i >= 0; i--) {
+ int val = acc % 85;
+ acc /= 85;
+
+ b85[i] = base85_encode[val];
+ }
+
+ for (i = 0; i < 5; i++)
+ buf->ptr[buf->size++] = b85[i];
+ }
+
+ buf->ptr[buf->size] = '\0';
+
+ return 0;
+}
+
+/* The inverse of base85_encode */
+static const int8_t base85_decode[] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 63, -1, 64, 65, 66, 67, -1, 68, 69, 70, 71, -1, 72, -1, -1,
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1, 73, 74, 75, 76, 77,
+ 78, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, -1, -1, -1, 79, 80,
+ 81, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 82, 83, 84, 85, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
+};
+
+int git_str_decode_base85(
+ git_str *buf,
+ const char *base85,
+ size_t base85_len,
+ size_t output_len)
+{
+ size_t orig_size = buf->size, new_size;
+
+ if (base85_len % 5 ||
+ output_len > base85_len * 4 / 5) {
+ git_error_set(GIT_ERROR_INVALID, "invalid base85 input");
+ return -1;
+ }
+
+ GIT_ERROR_CHECK_ALLOC_ADD(&new_size, output_len, buf->size);
+ GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
+ ENSURE_SIZE(buf, new_size);
+
+ while (output_len) {
+ unsigned acc = 0;
+ int de, cnt = 4;
+ unsigned char ch;
+ do {
+ ch = *base85++;
+ de = base85_decode[ch];
+ if (--de < 0)
+ goto on_error;
+
+ acc = acc * 85 + de;
+ } while (--cnt);
+ ch = *base85++;
+ de = base85_decode[ch];
+ if (--de < 0)
+ goto on_error;
+
+ /* Detect overflow. */
+ if (0xffffffff / 85 < acc ||
+ 0xffffffff - de < (acc *= 85))
+ goto on_error;
+
+ acc += de;
+
+ cnt = (output_len < 4) ? (int)output_len : 4;
+ output_len -= cnt;
+ do {
+ acc = (acc << 8) | (acc >> 24);
+ buf->ptr[buf->size++] = acc;
+ } while (--cnt);
+ }
+
+ buf->ptr[buf->size] = 0;
+
+ return 0;
+
+on_error:
+ buf->size = orig_size;
+ buf->ptr[buf->size] = '\0';
+
+ git_error_set(GIT_ERROR_INVALID, "invalid base85 input");
+ return -1;
+}
+
+#define HEX_DECODE(c) ((c | 32) % 39 - 9)
+
+int git_str_decode_percent(
+ git_str *buf,
+ const char *str,
+ size_t str_len)
+{
+ size_t str_pos, new_size;
+
+ GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, str_len);
+ GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
+ ENSURE_SIZE(buf, new_size);
+
+ 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])) {
+ buf->ptr[buf->size] = (HEX_DECODE(str[str_pos + 1]) << 4) +
+ HEX_DECODE(str[str_pos + 2]);
+ str_pos += 2;
+ } else {
+ buf->ptr[buf->size] = str[str_pos];
+ }
+ }
+
+ buf->ptr[buf->size] = '\0';
+ return 0;
+}
+
+int git_str_vprintf(git_str *buf, const char *format, va_list ap)
+{
+ size_t expected_size, new_size;
+ int len;
+
+ GIT_ERROR_CHECK_ALLOC_MULTIPLY(&expected_size, strlen(format), 2);
+ GIT_ERROR_CHECK_ALLOC_ADD(&expected_size, expected_size, buf->size);
+ ENSURE_SIZE(buf, expected_size);
+
+ while (1) {
+ va_list args;
+ va_copy(args, ap);
+
+ len = p_vsnprintf(
+ buf->ptr + buf->size,
+ buf->asize - buf->size,
+ format, args
+ );
+
+ va_end(args);
+
+ if (len < 0) {
+ git__free(buf->ptr);
+ buf->ptr = git_str__oom;
+ return -1;
+ }
+
+ if ((size_t)len + 1 <= buf->asize - buf->size) {
+ buf->size += len;
+ break;
+ }
+
+ GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len);
+ GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
+ ENSURE_SIZE(buf, new_size);
+ }
+
+ return 0;
+}
+
+int git_str_printf(git_str *buf, const char *format, ...)
+{
+ int r;
+ va_list ap;
+
+ va_start(ap, format);
+ r = git_str_vprintf(buf, format, ap);
+ va_end(ap);
+
+ return r;
+}
+
+int git_str_copy_cstr(char *data, size_t datasize, const git_str *buf)
+{
+ size_t copylen;
+
+ GIT_ASSERT_ARG(data);
+ GIT_ASSERT_ARG(datasize);
+ GIT_ASSERT_ARG(buf);
+
+ data[0] = '\0';
+
+ if (buf->size == 0 || buf->asize <= 0)
+ return 0;
+
+ copylen = buf->size;
+ if (copylen > datasize - 1)
+ copylen = datasize - 1;
+ memmove(data, buf->ptr, copylen);
+ data[copylen] = '\0';
+
+ return 0;
+}
+
+void git_str_consume_bytes(git_str *buf, size_t len)
+{
+ git_str_consume(buf, buf->ptr + len);
+}
+
+void git_str_consume(git_str *buf, const char *end)
+{
+ if (end > buf->ptr && end <= buf->ptr + buf->size) {
+ size_t consumed = end - buf->ptr;
+ memmove(buf->ptr, end, buf->size - consumed);
+ buf->size -= consumed;
+ buf->ptr[buf->size] = '\0';
+ }
+}
+
+void git_str_truncate(git_str *buf, size_t len)
+{
+ if (len >= buf->size)
+ return;
+
+ buf->size = len;
+ if (buf->size < buf->asize)
+ buf->ptr[buf->size] = '\0';
+}
+
+void git_str_shorten(git_str *buf, size_t amount)
+{
+ if (buf->size > amount)
+ git_str_truncate(buf, buf->size - amount);
+ else
+ git_str_clear(buf);
+}
+
+void git_str_truncate_at_char(git_str *buf, char separator)
+{
+ ssize_t idx = git_str_find(buf, separator);
+ if (idx >= 0)
+ git_str_truncate(buf, (size_t)idx);
+}
+
+void git_str_rtruncate_at_char(git_str *buf, char separator)
+{
+ ssize_t idx = git_str_rfind_next(buf, separator);
+ git_str_truncate(buf, idx < 0 ? 0 : (size_t)idx);
+}
+
+void git_str_swap(git_str *str_a, git_str *str_b)
+{
+ git_str t = *str_a;
+ *str_a = *str_b;
+ *str_b = t;
+}
+
+char *git_str_detach(git_str *buf)
+{
+ char *data = buf->ptr;
+
+ if (buf->asize == 0 || buf->ptr == git_str__oom)
+ return NULL;
+
+ git_str_init(buf, 0);
+
+ return data;
+}
+
+int git_str_attach(git_str *buf, char *ptr, size_t asize)
+{
+ git_str_dispose(buf);
+
+ if (ptr) {
+ buf->ptr = ptr;
+ buf->size = strlen(ptr);
+ if (asize)
+ buf->asize = (asize < buf->size) ? buf->size + 1 : asize;
+ else /* pass 0 to fall back on strlen + 1 */
+ buf->asize = buf->size + 1;
+ }
+
+ ENSURE_SIZE(buf, asize);
+ return 0;
+}
+
+void git_str_attach_notowned(git_str *buf, const char *ptr, size_t size)
+{
+ if (git_str_is_allocated(buf))
+ git_str_dispose(buf);
+
+ if (!size) {
+ git_str_init(buf, 0);
+ } else {
+ buf->ptr = (char *)ptr;
+ buf->asize = 0;
+ buf->size = size;
+ }
+}
+
+int git_str_join_n(git_str *buf, char separator, int nbuf, ...)
+{
+ va_list ap;
+ int i;
+ size_t total_size = 0, original_size = buf->size;
+ char *out, *original = buf->ptr;
+
+ if (buf->size > 0 && buf->ptr[buf->size - 1] != separator)
+ ++total_size; /* space for initial separator */
+
+ /* Make two passes to avoid multiple reallocation */
+
+ va_start(ap, nbuf);
+ for (i = 0; i < nbuf; ++i) {
+ const char *segment;
+ size_t segment_len;
+
+ segment = va_arg(ap, const char *);
+ if (!segment)
+ continue;
+
+ segment_len = strlen(segment);
+
+ GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, segment_len);
+
+ if (segment_len == 0 || segment[segment_len - 1] != separator)
+ GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, 1);
+ }
+ va_end(ap);
+
+ /* expand buffer if needed */
+ if (total_size == 0)
+ return 0;
+
+ GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, 1);
+ if (git_str_grow_by(buf, total_size) < 0)
+ return -1;
+
+ out = buf->ptr + buf->size;
+
+ /* append separator to existing buf if needed */
+ if (buf->size > 0 && out[-1] != separator)
+ *out++ = separator;
+
+ va_start(ap, nbuf);
+ for (i = 0; i < nbuf; ++i) {
+ const char *segment;
+ size_t segment_len;
+
+ segment = va_arg(ap, const char *);
+ if (!segment)
+ continue;
+
+ /* deal with join that references buffer's original content */
+ if (segment >= original && segment < original + original_size) {
+ size_t offset = (segment - original);
+ segment = buf->ptr + offset;
+ segment_len = original_size - offset;
+ } else {
+ segment_len = strlen(segment);
+ }
+
+ /* skip leading separators */
+ if (out > buf->ptr && out[-1] == separator)
+ while (segment_len > 0 && *segment == separator) {
+ segment++;
+ segment_len--;
+ }
+
+ /* copy over next buffer */
+ if (segment_len > 0) {
+ memmove(out, segment, segment_len);
+ out += segment_len;
+ }
+
+ /* append trailing separator (except for last item) */
+ if (i < nbuf - 1 && out > buf->ptr && out[-1] != separator)
+ *out++ = separator;
+ }
+ va_end(ap);
+
+ /* set size based on num characters actually written */
+ buf->size = out - buf->ptr;
+ buf->ptr[buf->size] = '\0';
+
+ return 0;
+}
+
+int git_str_join(
+ git_str *buf,
+ char separator,
+ const char *str_a,
+ const char *str_b)
+{
+ size_t strlen_a = str_a ? strlen(str_a) : 0;
+ size_t strlen_b = strlen(str_b);
+ size_t alloc_len;
+ int need_sep = 0;
+ ssize_t offset_a = -1;
+
+ /* not safe to have str_b point internally to the buffer */
+ if (buf->size)
+ GIT_ASSERT_ARG(str_b < buf->ptr || str_b >= buf->ptr + buf->size);
+
+ /* figure out if we need to insert a separator */
+ if (separator && strlen_a) {
+ while (*str_b == separator) { str_b++; strlen_b--; }
+ if (str_a[strlen_a - 1] != separator)
+ need_sep = 1;
+ }
+
+ /* str_a could be part of the buffer */
+ if (buf->size && str_a >= buf->ptr && str_a < buf->ptr + buf->size)
+ offset_a = str_a - buf->ptr;
+
+ GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, strlen_a, strlen_b);
+ GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, need_sep);
+ GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1);
+ ENSURE_SIZE(buf, alloc_len);
+
+ /* fix up internal pointers */
+ if (offset_a >= 0)
+ str_a = buf->ptr + offset_a;
+
+ /* do the actual copying */
+ if (offset_a != 0 && str_a)
+ memmove(buf->ptr, str_a, strlen_a);
+ if (need_sep)
+ buf->ptr[strlen_a] = separator;
+ memcpy(buf->ptr + strlen_a + need_sep, str_b, strlen_b);
+
+ buf->size = strlen_a + strlen_b + need_sep;
+ buf->ptr[buf->size] = '\0';
+
+ return 0;
+}
+
+int git_str_join3(
+ git_str *buf,
+ char separator,
+ const char *str_a,
+ const char *str_b,
+ const char *str_c)
+{
+ size_t len_a = strlen(str_a),
+ len_b = strlen(str_b),
+ len_c = strlen(str_c),
+ len_total;
+ int sep_a = 0, sep_b = 0;
+ char *tgt;
+
+ /* for this function, disallow pointers into the existing buffer */
+ GIT_ASSERT(str_a < buf->ptr || str_a >= buf->ptr + buf->size);
+ GIT_ASSERT(str_b < buf->ptr || str_b >= buf->ptr + buf->size);
+ GIT_ASSERT(str_c < buf->ptr || str_c >= buf->ptr + buf->size);
+
+ if (separator) {
+ if (len_a > 0) {
+ while (*str_b == separator) { str_b++; len_b--; }
+ sep_a = (str_a[len_a - 1] != separator);
+ }
+ if (len_a > 0 || len_b > 0)
+ while (*str_c == separator) { str_c++; len_c--; }
+ if (len_b > 0)
+ sep_b = (str_b[len_b - 1] != separator);
+ }
+
+ GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_a, sep_a);
+ GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, len_b);
+ GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, sep_b);
+ GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, len_c);
+ GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, 1);
+ ENSURE_SIZE(buf, len_total);
+
+ tgt = buf->ptr;
+
+ if (len_a) {
+ memcpy(tgt, str_a, len_a);
+ tgt += len_a;
+ }
+ if (sep_a)
+ *tgt++ = separator;
+ if (len_b) {
+ memcpy(tgt, str_b, len_b);
+ tgt += len_b;
+ }
+ if (sep_b)
+ *tgt++ = separator;
+ if (len_c)
+ memcpy(tgt, str_c, len_c);
+
+ buf->size = len_a + sep_a + len_b + sep_b + len_c;
+ buf->ptr[buf->size] = '\0';
+
+ return 0;
+}
+
+void git_str_rtrim(git_str *buf)
+{
+ while (buf->size > 0) {
+ if (!git__isspace(buf->ptr[buf->size - 1]))
+ break;
+
+ buf->size--;
+ }
+
+ if (buf->asize > buf->size)
+ buf->ptr[buf->size] = '\0';
+}
+
+int git_str_cmp(const git_str *a, const git_str *b)
+{
+ int result = memcmp(a->ptr, b->ptr, min(a->size, b->size));
+ return (result != 0) ? result :
+ (a->size < b->size) ? -1 : (a->size > b->size) ? 1 : 0;
+}
+
+int git_str_splice(
+ git_str *buf,
+ size_t where,
+ size_t nb_to_remove,
+ const char *data,
+ size_t nb_to_insert)
+{
+ char *splice_loc;
+ size_t new_size, alloc_size;
+
+ GIT_ASSERT(buf);
+ GIT_ASSERT(where <= buf->size);
+ GIT_ASSERT(nb_to_remove <= buf->size - where);
+
+ splice_loc = buf->ptr + where;
+
+ /* Ported from git.git
+ * https://github.com/git/git/blob/16eed7c/strbuf.c#L159-176
+ */
+ GIT_ERROR_CHECK_ALLOC_ADD(&new_size, (buf->size - nb_to_remove), nb_to_insert);
+ GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, new_size, 1);
+ ENSURE_SIZE(buf, alloc_size);
+
+ memmove(splice_loc + nb_to_insert,
+ splice_loc + nb_to_remove,
+ buf->size - where - nb_to_remove);
+
+ memcpy(splice_loc, data, nb_to_insert);
+
+ buf->size = new_size;
+ buf->ptr[buf->size] = '\0';
+ return 0;
+}
+
+/* Quote per http://marc.info/?l=git&m=112927316408690&w=2 */
+int git_str_quote(git_str *buf)
+{
+ const char whitespace[] = { 'a', 'b', 't', 'n', 'v', 'f', 'r' };
+ git_str quoted = GIT_STR_INIT;
+ size_t i = 0;
+ bool quote = false;
+ int error = 0;
+
+ /* walk to the first char that needs quoting */
+ if (buf->size && buf->ptr[0] == '!')
+ quote = true;
+
+ for (i = 0; !quote && i < buf->size; i++) {
+ if (buf->ptr[i] == '"' || buf->ptr[i] == '\\' ||
+ buf->ptr[i] < ' ' || buf->ptr[i] > '~') {
+ quote = true;
+ break;
+ }
+ }
+
+ if (!quote)
+ goto done;
+
+ git_str_putc(&quoted, '"');
+ git_str_put(&quoted, buf->ptr, i);
+
+ for (; i < buf->size; i++) {
+ /* whitespace - use the map above, which is ordered by ascii value */
+ if (buf->ptr[i] >= '\a' && buf->ptr[i] <= '\r') {
+ git_str_putc(&quoted, '\\');
+ git_str_putc(&quoted, whitespace[buf->ptr[i] - '\a']);
+ }
+
+ /* double quote and backslash must be escaped */
+ else if (buf->ptr[i] == '"' || buf->ptr[i] == '\\') {
+ git_str_putc(&quoted, '\\');
+ git_str_putc(&quoted, buf->ptr[i]);
+ }
+
+ /* escape anything unprintable as octal */
+ else if (buf->ptr[i] != ' ' &&
+ (buf->ptr[i] < '!' || buf->ptr[i] > '~')) {
+ git_str_printf(&quoted, "\\%03o", (unsigned char)buf->ptr[i]);
+ }
+
+ /* yay, printable! */
+ else {
+ git_str_putc(&quoted, buf->ptr[i]);
+ }
+ }
+
+ git_str_putc(&quoted, '"');
+
+ if (git_str_oom(&quoted)) {
+ error = -1;
+ goto done;
+ }
+
+ git_str_swap(&quoted, buf);
+
+done:
+ git_str_dispose(&quoted);
+ return error;
+}
+
+/* Unquote per http://marc.info/?l=git&m=112927316408690&w=2 */
+int git_str_unquote(git_str *buf)
+{
+ size_t i, j;
+ char ch;
+
+ git_str_rtrim(buf);
+
+ if (buf->size < 2 || buf->ptr[0] != '"' || buf->ptr[buf->size-1] != '"')
+ goto invalid;
+
+ for (i = 0, j = 1; j < buf->size-1; i++, j++) {
+ ch = buf->ptr[j];
+
+ if (ch == '\\') {
+ if (j == buf->size-2)
+ goto invalid;
+
+ ch = buf->ptr[++j];
+
+ switch (ch) {
+ /* \" or \\ simply copy the char in */
+ case '"': case '\\':
+ break;
+
+ /* add the appropriate escaped char */
+ case 'a': ch = '\a'; break;
+ case 'b': ch = '\b'; break;
+ case 'f': ch = '\f'; break;
+ case 'n': ch = '\n'; break;
+ case 'r': ch = '\r'; break;
+ case 't': ch = '\t'; break;
+ case 'v': ch = '\v'; break;
+
+ /* \xyz digits convert to the char*/
+ case '0': case '1': case '2': case '3':
+ if (j == buf->size-3) {
+ git_error_set(GIT_ERROR_INVALID,
+ "truncated quoted character \\%c", ch);
+ return -1;
+ }
+
+ if (buf->ptr[j+1] < '0' || buf->ptr[j+1] > '7' ||
+ buf->ptr[j+2] < '0' || buf->ptr[j+2] > '7') {
+ git_error_set(GIT_ERROR_INVALID,
+ "truncated quoted character \\%c%c%c",
+ buf->ptr[j], buf->ptr[j+1], buf->ptr[j+2]);
+ return -1;
+ }
+
+ ch = ((buf->ptr[j] - '0') << 6) |
+ ((buf->ptr[j+1] - '0') << 3) |
+ (buf->ptr[j+2] - '0');
+ j += 2;
+ break;
+
+ default:
+ git_error_set(GIT_ERROR_INVALID, "invalid quoted character \\%c", ch);
+ return -1;
+ }
+ }
+
+ buf->ptr[i] = ch;
+ }
+
+ buf->ptr[i] = '\0';
+ buf->size = i;
+
+ return 0;
+
+invalid:
+ git_error_set(GIT_ERROR_INVALID, "invalid quoted line");
+ return -1;
+}
+
+int git_str_puts_escaped(
+ git_str *buf,
+ const char *string,
+ const char *esc_chars,
+ const char *esc_with)
+{
+ const char *scan;
+ size_t total = 0, esc_len = strlen(esc_with), count, alloclen;
+
+ if (!string)
+ return 0;
+
+ for (scan = string; *scan; ) {
+ /* count run of non-escaped characters */
+ count = strcspn(scan, esc_chars);
+ total += count;
+ scan += count;
+ /* count run of escaped characters */
+ count = strspn(scan, esc_chars);
+ total += count * (esc_len + 1);
+ scan += count;
+ }
+
+ GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, total, 1);
+ if (git_str_grow_by(buf, alloclen) < 0)
+ return -1;
+
+ for (scan = string; *scan; ) {
+ count = strcspn(scan, esc_chars);
+
+ memmove(buf->ptr + buf->size, scan, count);
+ scan += count;
+ buf->size += count;
+
+ for (count = strspn(scan, esc_chars); count > 0; --count) {
+ /* copy escape sequence */
+ memmove(buf->ptr + buf->size, esc_with, esc_len);
+ buf->size += esc_len;
+ /* copy character to be escaped */
+ buf->ptr[buf->size] = *scan;
+ buf->size++;
+ scan++;
+ }
+ }
+
+ buf->ptr[buf->size] = '\0';
+
+ return 0;
+}
+
+void git_str_unescape(git_str *buf)
+{
+ buf->size = git__unescape(buf->ptr);
+}
+
+int git_str_crlf_to_lf(git_str *tgt, const git_str *src)
+{
+ const char *scan = src->ptr;
+ const char *scan_end = src->ptr + src->size;
+ const char *next = memchr(scan, '\r', src->size);
+ size_t new_size;
+ char *out;
+
+ GIT_ASSERT(tgt != src);
+
+ if (!next)
+ return git_str_set(tgt, src->ptr, src->size);
+
+ /* reduce reallocs while in the loop */
+ GIT_ERROR_CHECK_ALLOC_ADD(&new_size, src->size, 1);
+ if (git_str_grow(tgt, new_size) < 0)
+ return -1;
+
+ out = tgt->ptr;
+ tgt->size = 0;
+
+ /* Find the next \r and copy whole chunk up to there to tgt */
+ for (; next; scan = next + 1, next = memchr(scan, '\r', scan_end - scan)) {
+ if (next > scan) {
+ size_t copylen = (size_t)(next - scan);
+ memcpy(out, scan, copylen);
+ out += copylen;
+ }
+
+ /* Do not drop \r unless it is followed by \n */
+ if (next + 1 == scan_end || next[1] != '\n')
+ *out++ = '\r';
+ }
+
+ /* Copy remaining input into dest */
+ if (scan < scan_end) {
+ size_t remaining = (size_t)(scan_end - scan);
+ memcpy(out, scan, remaining);
+ out += remaining;
+ }
+
+ tgt->size = (size_t)(out - tgt->ptr);
+ tgt->ptr[tgt->size] = '\0';
+
+ return 0;
+}
+
+int git_str_lf_to_crlf(git_str *tgt, const git_str *src)
+{
+ const char *start = src->ptr;
+ const char *end = start + src->size;
+ const char *scan = start;
+ const char *next = memchr(scan, '\n', src->size);
+ size_t alloclen;
+
+ GIT_ASSERT(tgt != src);
+
+ if (!next)
+ return git_str_set(tgt, src->ptr, src->size);
+
+ /* attempt to reduce reallocs while in the loop */
+ GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, src->size, src->size >> 4);
+ GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
+ if (git_str_grow(tgt, alloclen) < 0)
+ return -1;
+ tgt->size = 0;
+
+ for (; next; scan = next + 1, next = memchr(scan, '\n', end - scan)) {
+ size_t copylen = next - scan;
+
+ /* if we find mixed line endings, carry on */
+ if (copylen && next[-1] == '\r')
+ copylen--;
+
+ GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, copylen, 3);
+ if (git_str_grow_by(tgt, alloclen) < 0)
+ return -1;
+
+ if (copylen) {
+ memcpy(tgt->ptr + tgt->size, scan, copylen);
+ tgt->size += copylen;
+ }
+
+ tgt->ptr[tgt->size++] = '\r';
+ tgt->ptr[tgt->size++] = '\n';
+ }
+
+ tgt->ptr[tgt->size] = '\0';
+ return git_str_put(tgt, scan, end - scan);
+}
+
+int git_str_common_prefix(git_str *buf, char *const *const strings, size_t count)
+{
+ size_t i;
+ const char *str, *pfx;
+
+ git_str_clear(buf);
+
+ if (!strings || !count)
+ return 0;
+
+ /* initialize common prefix to first string */
+ if (git_str_sets(buf, strings[0]) < 0)
+ return -1;
+
+ /* go through the rest of the strings, truncating to shared prefix */
+ for (i = 1; i < count; ++i) {
+
+ for (str = strings[i], pfx = buf->ptr;
+ *str && *str == *pfx;
+ str++, pfx++)
+ /* scanning */;
+
+ git_str_truncate(buf, pfx - buf->ptr);
+
+ if (!buf->size)
+ break;
+ }
+
+ return 0;
+}
+
+int git_str_is_binary(const git_str *buf)
+{
+ const char *scan = buf->ptr, *end = buf->ptr + buf->size;
+ git_str_bom_t bom;
+ int printable = 0, nonprintable = 0;
+
+ scan += git_str_detect_bom(&bom, buf);
+
+ if (bom > GIT_STR_BOM_UTF8)
+ return 1;
+
+ while (scan < end) {
+ unsigned char c = *scan++;
+
+ /* Printable characters are those above SPACE (0x1F) excluding DEL,
+ * and including BS, ESC and FF.
+ */
+ if ((c > 0x1F && c != 127) || c == '\b' || c == '\033' || c == '\014')
+ printable++;
+ else if (c == '\0')
+ return true;
+ else if (!git__isspace(c))
+ nonprintable++;
+ }
+
+ return ((printable >> 7) < nonprintable);
+}
+
+int git_str_contains_nul(const git_str *buf)
+{
+ return (memchr(buf->ptr, '\0', buf->size) != NULL);
+}
+
+int git_str_detect_bom(git_str_bom_t *bom, const git_str *buf)
+{
+ const char *ptr;
+ size_t len;
+
+ *bom = GIT_STR_BOM_NONE;
+ /* need at least 2 bytes to look for any BOM */
+ if (buf->size < 2)
+ return 0;
+
+ ptr = buf->ptr;
+ len = buf->size;
+
+ switch (*ptr++) {
+ case 0:
+ if (len >= 4 && ptr[0] == 0 && ptr[1] == '\xFE' && ptr[2] == '\xFF') {
+ *bom = GIT_STR_BOM_UTF32_BE;
+ return 4;
+ }
+ break;
+ case '\xEF':
+ if (len >= 3 && ptr[0] == '\xBB' && ptr[1] == '\xBF') {
+ *bom = GIT_STR_BOM_UTF8;
+ return 3;
+ }
+ break;
+ case '\xFE':
+ if (*ptr == '\xFF') {
+ *bom = GIT_STR_BOM_UTF16_BE;
+ return 2;
+ }
+ break;
+ case '\xFF':
+ if (*ptr != '\xFE')
+ break;
+ if (len >= 4 && ptr[1] == 0 && ptr[2] == 0) {
+ *bom = GIT_STR_BOM_UTF32_LE;
+ return 4;
+ } else {
+ *bom = GIT_STR_BOM_UTF16_LE;
+ return 2;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+bool git_str_gather_text_stats(
+ git_str_text_stats *stats, const git_str *buf, bool skip_bom)
+{
+ const char *scan = buf->ptr, *end = buf->ptr + buf->size;
+ int skip;
+
+ memset(stats, 0, sizeof(*stats));
+
+ /* BOM detection */
+ skip = git_str_detect_bom(&stats->bom, buf);
+ if (skip_bom)
+ scan += skip;
+
+ /* Ignore EOF character */
+ if (buf->size > 0 && end[-1] == '\032')
+ end--;
+
+ /* Counting loop */
+ while (scan < end) {
+ unsigned char c = *scan++;
+
+ if (c > 0x1F && c != 0x7F)
+ stats->printable++;
+ else switch (c) {
+ case '\0':
+ stats->nul++;
+ stats->nonprintable++;
+ break;
+ case '\n':
+ stats->lf++;
+ break;
+ case '\r':
+ stats->cr++;
+ if (scan < end && *scan == '\n')
+ stats->crlf++;
+ break;
+ case '\t': case '\f': case '\v': case '\b': case 0x1b: /*ESC*/
+ stats->printable++;
+ break;
+ default:
+ stats->nonprintable++;
+ break;
+ }
+ }
+
+ /* Treat files with a bare CR as binary */
+ return (stats->cr != stats->crlf || stats->nul > 0 ||
+ ((stats->printable >> 7) < stats->nonprintable));
+}
diff --git a/src/util/str.h b/src/util/str.h
new file mode 100644
index 0000000..588e6fc
--- /dev/null
+++ b/src/util/str.h
@@ -0,0 +1,357 @@
+/*
+ * 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_str_h__
+#define INCLUDE_str_h__
+
+#include "git2_util.h"
+
+struct git_str {
+ char *ptr;
+ size_t asize;
+ size_t size;
+};
+
+typedef enum {
+ GIT_STR_BOM_NONE = 0,
+ GIT_STR_BOM_UTF8 = 1,
+ GIT_STR_BOM_UTF16_LE = 2,
+ GIT_STR_BOM_UTF16_BE = 3,
+ GIT_STR_BOM_UTF32_LE = 4,
+ GIT_STR_BOM_UTF32_BE = 5
+} git_str_bom_t;
+
+typedef struct {
+ git_str_bom_t bom; /* BOM found at head of text */
+ unsigned int nul, cr, lf, crlf; /* NUL, CR, LF and CRLF counts */
+ unsigned int printable, nonprintable; /* These are just approximations! */
+} git_str_text_stats;
+
+extern char git_str__initstr[];
+extern char git_str__oom[];
+
+/* Use to initialize string buffer structure when git_str is on stack */
+#define GIT_STR_INIT { git_str__initstr, 0, 0 }
+
+/**
+ * Static initializer for git_str from static string buffer
+ */
+#define GIT_STR_INIT_CONST(str, len) { (char *)(str), 0, (size_t)(len) }
+
+GIT_INLINE(bool) git_str_is_allocated(const git_str *str)
+{
+ return (str->ptr != NULL && str->asize > 0);
+}
+
+/**
+ * Initialize a git_str structure.
+ *
+ * For the cases where GIT_STR_INIT cannot be used to do static
+ * initialization.
+ */
+extern int git_str_init(git_str *str, size_t initial_size);
+
+extern void git_str_dispose(git_str *str);
+
+/**
+ * Resize the string buffer allocation to make more space.
+ *
+ * This will attempt to grow the string buffer to accommodate the target
+ * size. The bstring buffer's `ptr` will be replaced with a newly
+ * allocated block of data. Be careful so that memory allocated by the
+ * caller is not lost. As a special variant, if you pass `target_size` as
+ * 0 and the memory is not allocated by libgit2, this will allocate a new
+ * buffer of size `size` and copy the external data into it.
+ *
+ * Currently, this will never shrink a buffer, only expand it.
+ *
+ * If the allocation fails, this will return an error and the buffer will be
+ * marked as invalid for future operations, invaliding the contents.
+ *
+ * @param str The buffer to be resized; may or may not be allocated yet
+ * @param target_size The desired available size
+ * @return 0 on success, -1 on allocation failure
+ */
+int git_str_grow(git_str *str, size_t target_size);
+
+/**
+ * Resize the buffer allocation to make more space.
+ *
+ * This will attempt to grow the string buffer to accommodate the
+ * additional size. It is similar to `git_str_grow`, but performs the
+ * new size calculation, checking for overflow.
+ *
+ * Like `git_str_grow`, if this is a user-supplied string buffer,
+ * this will allocate a new string uffer.
+ */
+extern int git_str_grow_by(git_str *str, size_t additional_size);
+
+/**
+ * Attempt to grow the buffer to hold at least `target_size` bytes.
+ *
+ * If the allocation fails, this will return an error. If `mark_oom` is
+ * true, this will mark the string buffer as invalid for future
+ * operations; if false, existing string buffer content will be preserved,
+ * but calling code must handle that string buffer was not expanded. If
+ * `preserve_external` is true, then any existing data pointed to be
+ * `ptr` even if `asize` is zero will be copied into the newly allocated
+ * string buffer.
+ */
+extern int git_str_try_grow(
+ git_str *str, size_t target_size, bool mark_oom);
+
+extern void git_str_swap(git_str *str_a, git_str *str_b);
+extern char *git_str_detach(git_str *str);
+extern int git_str_attach(git_str *str, char *ptr, size_t asize);
+
+/* Populates a `git_str` where the contents are not "owned" by the string
+ * buffer, and calls to `git_str_dispose` will not free the given str.
+ */
+extern void git_str_attach_notowned(
+ git_str *str, const char *ptr, size_t size);
+
+/**
+ * Test if there have been any reallocation failures with this git_str.
+ *
+ * Any function that writes to a git_str can fail due to memory allocation
+ * issues. If one fails, the git_str will be marked with an OOM error and
+ * further calls to modify the string buffer will fail. Check
+ * git_str_oom() at the end of your sequence and it will be true if you
+ * ran out of memory at any point with that string buffer.
+ *
+ * @return false if no error, true if allocation error
+ */
+GIT_INLINE(bool) git_str_oom(const git_str *str)
+{
+ return (str->ptr == git_str__oom);
+}
+
+/*
+ * Functions below that return int value error codes will return 0 on
+ * success or -1 on failure (which generally means an allocation failed).
+ * Using a git_str where the allocation has failed with result in -1 from
+ * all further calls using that string buffer. As a result, you can
+ * ignore the return code of these functions and call them in a series
+ * then just call git_str_oom at the end.
+ */
+
+int git_str_set(git_str *str, const void *data, size_t datalen);
+
+int git_str_sets(git_str *str, const char *string);
+int git_str_putc(git_str *str, char c);
+int git_str_putcn(git_str *str, char c, size_t len);
+int git_str_put(git_str *str, const char *data, size_t len);
+int git_str_puts(git_str *str, const char *string);
+int git_str_printf(git_str *str, const char *format, ...) GIT_FORMAT_PRINTF(2, 3);
+int git_str_vprintf(git_str *str, const char *format, va_list ap);
+void git_str_clear(git_str *str);
+void git_str_consume_bytes(git_str *str, size_t len);
+void git_str_consume(git_str *str, const char *end);
+void git_str_truncate(git_str *str, size_t len);
+void git_str_shorten(git_str *str, size_t amount);
+void git_str_truncate_at_char(git_str *path, char separator);
+void git_str_rtruncate_at_char(git_str *path, char separator);
+
+/** General join with separator */
+int git_str_join_n(git_str *str, char separator, int len, ...);
+/** Fast join of two strings - first may legally point into `str` data */
+int git_str_join(git_str *str, char separator, const char *str_a, const char *str_b);
+/** Fast join of three strings - cannot reference `str` data */
+int git_str_join3(git_str *str, char separator, const char *str_a, const char *str_b, const char *str_c);
+
+/**
+ * Join two strings as paths, inserting a slash between as needed.
+ * @return 0 on success, -1 on failure
+ */
+GIT_INLINE(int) git_str_joinpath(git_str *str, const char *a, const char *b)
+{
+ return git_str_join(str, '/', a, b);
+}
+
+GIT_INLINE(const char *) git_str_cstr(const git_str *str)
+{
+ return str->ptr;
+}
+
+GIT_INLINE(size_t) git_str_len(const git_str *str)
+{
+ return str->size;
+}
+
+int git_str_copy_cstr(char *data, size_t datasize, const git_str *str);
+
+#define git_str_PUTS(str, cstr) git_str_put(str, cstr, sizeof(cstr) - 1)
+
+GIT_INLINE(ssize_t) git_str_rfind_next(const git_str *str, char ch)
+{
+ ssize_t idx = (ssize_t)str->size - 1;
+ while (idx >= 0 && str->ptr[idx] == ch) idx--;
+ while (idx >= 0 && str->ptr[idx] != ch) idx--;
+ return idx;
+}
+
+GIT_INLINE(ssize_t) git_str_rfind(const git_str *str, char ch)
+{
+ ssize_t idx = (ssize_t)str->size - 1;
+ while (idx >= 0 && str->ptr[idx] != ch) idx--;
+ return idx;
+}
+
+GIT_INLINE(ssize_t) git_str_find(const git_str *str, char ch)
+{
+ void *found = memchr(str->ptr, ch, str->size);
+ return found ? (ssize_t)((const char *)found - str->ptr) : -1;
+}
+
+/* Remove whitespace from the end of the string buffer */
+void git_str_rtrim(git_str *str);
+
+int git_str_cmp(const git_str *a, const git_str *b);
+
+/* Quote and unquote a string buffer as specified in
+ * http://marc.info/?l=git&m=112927316408690&w=2
+ */
+int git_str_quote(git_str *str);
+int git_str_unquote(git_str *str);
+
+/* Write data as a hex string */
+int git_str_encode_hexstr(git_str *str, const char *data, size_t len);
+
+/* Write data as base64 encoded in string buffer */
+int git_str_encode_base64(git_str *str, const char *data, size_t len);
+/* Decode the given bas64 and write the result to the string buffer */
+int git_str_decode_base64(git_str *str, const char *base64, size_t len);
+
+/* Write data as "base85" encoded in string buffer */
+int git_str_encode_base85(git_str *str, const char *data, size_t len);
+/* Decode the given "base85" and write the result to the string buffer */
+int git_str_decode_base85(git_str *str, const char *base64, size_t len, size_t output_len);
+
+/*
+ * Decode the given percent-encoded string and write the result to the
+ * string buffer.
+ */
+int git_str_decode_percent(git_str *str, const char *encoded, size_t len);
+
+/*
+ * Insert, remove or replace a portion of the string buffer.
+ *
+ * @param str The string buffer to work with
+ *
+ * @param where The location in the string buffer where the transformation
+ * should be applied.
+ *
+ * @param nb_to_remove The number of chars to be removed. 0 to not
+ * remove any character in the string buffer.
+ *
+ * @param data A pointer to the data which should be inserted.
+ *
+ * @param nb_to_insert The number of chars to be inserted. 0 to not
+ * insert any character from the string buffer.
+ *
+ * @return 0 or an error code.
+ */
+int git_str_splice(
+ git_str *str,
+ size_t where,
+ size_t nb_to_remove,
+ const char *data,
+ size_t nb_to_insert);
+
+/**
+ * Append string to string buffer, prefixing each character from
+ * `esc_chars` with `esc_with` string.
+ *
+ * @param str String buffer to append data to
+ * @param string String to escape and append
+ * @param esc_chars Characters to be escaped
+ * @param esc_with String to insert in from of each found character
+ * @return 0 on success, <0 on failure (probably allocation problem)
+ */
+extern int git_str_puts_escaped(
+ git_str *str,
+ const char *string,
+ const char *esc_chars,
+ const char *esc_with);
+
+/**
+ * Append string escaping characters that are regex special
+ */
+GIT_INLINE(int) git_str_puts_escape_regex(git_str *str, const char *string)
+{
+ return git_str_puts_escaped(str, string, "^.[]$()|*+?{}\\", "\\");
+}
+
+/**
+ * Unescape all characters in a string buffer in place
+ *
+ * I.e. remove backslashes
+ */
+extern void git_str_unescape(git_str *str);
+
+/**
+ * Replace all \r\n with \n.
+ *
+ * @return 0 on success, -1 on memory error
+ */
+extern int git_str_crlf_to_lf(git_str *tgt, const git_str *src);
+
+/**
+ * Replace all \n with \r\n. Does not modify existing \r\n.
+ *
+ * @return 0 on success, -1 on memory error
+ */
+extern int git_str_lf_to_crlf(git_str *tgt, const git_str *src);
+
+/**
+ * Fill string buffer with the common prefix of a array of strings
+ *
+ * String buffer will be set to empty if there is no common prefix
+ */
+extern int git_str_common_prefix(git_str *buf, char *const *const strings, size_t count);
+
+/**
+ * Check if a string buffer begins with a UTF BOM
+ *
+ * @param bom Set to the type of BOM detected or GIT_BOM_NONE
+ * @param str String buffer in which to check the first bytes for a BOM
+ * @return Number of bytes of BOM data (or 0 if no BOM found)
+ */
+extern int git_str_detect_bom(git_str_bom_t *bom, const git_str *str);
+
+/**
+ * Gather stats for a piece of text
+ *
+ * Fill the `stats` structure with counts of unreadable characters, carriage
+ * returns, etc, so it can be used in heuristics. This automatically skips
+ * a trailing EOF (\032 character). Also it will look for a BOM at the
+ * start of the text and can be told to skip that as well.
+ *
+ * @param stats Structure to be filled in
+ * @param str Text to process
+ * @param skip_bom Exclude leading BOM from stats if true
+ * @return Does the string buffer heuristically look like binary data
+ */
+extern bool git_str_gather_text_stats(
+ git_str_text_stats *stats, const git_str *str, bool skip_bom);
+
+/**
+* Check quickly if string buffer looks like it contains binary data
+*
+* @param str string buffer to check
+* @return 1 if string buffer looks like non-text data
+*/
+int git_str_is_binary(const git_str *str);
+
+/**
+* Check quickly if buffer contains a NUL byte
+*
+* @param str string buffer to check
+* @return 1 if string buffer contains a NUL byte
+*/
+int git_str_contains_nul(const git_str *str);
+
+#endif
diff --git a/src/util/strmap.c b/src/util/strmap.c
new file mode 100644
index 0000000..c6e5b6d
--- /dev/null
+++ b/src/util/strmap.c
@@ -0,0 +1,100 @@
+/*
+ * 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 "strmap.h"
+
+#define kmalloc git__malloc
+#define kcalloc git__calloc
+#define krealloc git__realloc
+#define kreallocarray git__reallocarray
+#define kfree git__free
+#include "khash.h"
+
+__KHASH_TYPE(str, const char *, void *)
+
+__KHASH_IMPL(str, static kh_inline, const char *, void *, 1, kh_str_hash_func, kh_str_hash_equal)
+
+int git_strmap_new(git_strmap **out)
+{
+ *out = kh_init(str);
+ GIT_ERROR_CHECK_ALLOC(*out);
+
+ return 0;
+}
+
+void git_strmap_free(git_strmap *map)
+{
+ kh_destroy(str, map);
+}
+
+void git_strmap_clear(git_strmap *map)
+{
+ kh_clear(str, map);
+}
+
+size_t git_strmap_size(git_strmap *map)
+{
+ return kh_size(map);
+}
+
+void *git_strmap_get(git_strmap *map, const char *key)
+{
+ size_t idx = kh_get(str, map, key);
+ if (idx == kh_end(map) || !kh_exist(map, idx))
+ return NULL;
+ return kh_val(map, idx);
+}
+
+int git_strmap_set(git_strmap *map, const char *key, void *value)
+{
+ size_t idx;
+ int rval;
+
+ idx = kh_put(str, map, key, &rval);
+ if (rval < 0)
+ return -1;
+
+ if (rval == 0)
+ kh_key(map, idx) = key;
+
+ kh_val(map, idx) = value;
+
+ return 0;
+}
+
+int git_strmap_delete(git_strmap *map, const char *key)
+{
+ khiter_t idx = kh_get(str, map, key);
+ if (idx == kh_end(map))
+ return GIT_ENOTFOUND;
+ kh_del(str, map, idx);
+ return 0;
+}
+
+int git_strmap_exists(git_strmap *map, const char *key)
+{
+ return kh_get(str, map, key) != kh_end(map);
+}
+
+int git_strmap_iterate(void **value, git_strmap *map, size_t *iter, const char **key)
+{
+ size_t i = *iter;
+
+ while (i < map->n_buckets && !kh_exist(map, i))
+ i++;
+
+ if (i >= map->n_buckets)
+ return GIT_ITEROVER;
+
+ if (key)
+ *key = kh_key(map, i);
+ if (value)
+ *value = kh_val(map, i);
+ *iter = ++i;
+
+ return 0;
+}
diff --git a/src/util/strmap.h b/src/util/strmap.h
new file mode 100644
index 0000000..b64d3dc
--- /dev/null
+++ b/src/util/strmap.h
@@ -0,0 +1,131 @@
+/*
+ * 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_strmap_h__
+#define INCLUDE_strmap_h__
+
+#include "git2_util.h"
+
+/** A map with C strings as key. */
+typedef struct kh_str_s git_strmap;
+
+/**
+ * Allocate a new string map.
+ *
+ * @param out Pointer to the map that shall be allocated.
+ * @return 0 on success, an error code if allocation has failed.
+ */
+int git_strmap_new(git_strmap **out);
+
+/**
+ * Free memory associated with the map.
+ *
+ * Note that this function will _not_ free keys or values added
+ * to this map.
+ *
+ * @param map Pointer to the map that is to be free'd. May be
+ * `NULL`.
+ */
+void git_strmap_free(git_strmap *map);
+
+/**
+ * Clear all entries from the map.
+ *
+ * This function will remove all entries from the associated map.
+ * Memory associated with it will not be released, though.
+ *
+ * @param map Pointer to the map that shall be cleared. May be
+ * `NULL`.
+ */
+void git_strmap_clear(git_strmap *map);
+
+/**
+ * Return the number of elements in the map.
+ *
+ * @parameter map map containing the elements
+ * @return number of elements in the map
+ */
+size_t git_strmap_size(git_strmap *map);
+
+/**
+ * Return value associated with the given key.
+ *
+ * @param map map to search key in
+ * @param key key to search for
+ * @return value associated with the given key or NULL if the key was not found
+ */
+void *git_strmap_get(git_strmap *map, const char *key);
+
+/**
+ * Set the entry for key to value.
+ *
+ * If the map has no corresponding entry for the given key, a new
+ * entry will be created with the given value. If an entry exists
+ * already, its value will be updated to match the given value.
+ *
+ * @param map map to create new entry in
+ * @param key key to set
+ * @param value value to associate the key with; may be NULL
+ * @return zero if the key was successfully set, a negative error
+ * code otherwise
+ */
+int git_strmap_set(git_strmap *map, const char *key, void *value);
+
+/**
+ * Delete an entry from the map.
+ *
+ * Delete the given key and its value from the map. If no such
+ * key exists, this will do nothing.
+ *
+ * @param map map to delete key in
+ * @param key key to delete
+ * @return `0` if the key has been deleted, GIT_ENOTFOUND if no
+ * such key was found, a negative code in case of an
+ * error
+ */
+int git_strmap_delete(git_strmap *map, const char *key);
+
+/**
+ * Check whether a key exists in the given map.
+ *
+ * @param map map to query for the key
+ * @param key key to search for
+ * @return 0 if the key has not been found, 1 otherwise
+ */
+int git_strmap_exists(git_strmap *map, const char *key);
+
+/**
+ * Iterate over entries of the map.
+ *
+ * This functions allows to iterate over all key-value entries of
+ * the map. The current position is stored in the `iter` variable
+ * and should be initialized to `0` before the first call to this
+ * function.
+ *
+ * @param map map to iterate over
+ * @param value pointer to the variable where to store the current
+ * value. May be NULL.
+ * @param iter iterator storing the current position. Initialize
+ * with zero previous to the first call.
+ * @param key pointer to the variable where to store the current
+ * key. May be NULL.
+ * @return `0` if the next entry was correctly retrieved.
+ * GIT_ITEROVER if no entries are left. A negative error
+ * code otherwise.
+ */
+int git_strmap_iterate(void **value, git_strmap *map, size_t *iter, const char **key);
+
+#define git_strmap_foreach(h, kvar, vvar, code) { size_t __i = 0; \
+ while (git_strmap_iterate((void **) &(vvar), h, &__i, &(kvar)) == 0) { \
+ code; \
+ } }
+
+#define git_strmap_foreach_value(h, vvar, code) { size_t __i = 0; \
+ while (git_strmap_iterate((void **) &(vvar), h, &__i, NULL) == 0) { \
+ code; \
+ } }
+
+#endif
diff --git a/src/util/strnlen.h b/src/util/strnlen.h
new file mode 100644
index 0000000..eecfe3c
--- /dev/null
+++ b/src/util/strnlen.h
@@ -0,0 +1,24 @@
+/*
+ * 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_strlen_h__
+#define INCLUDE_strlen_h__
+
+#if defined(__MINGW32__) || defined(__sun) || defined(__APPLE__) || defined(__MidnightBSD__) ||\
+ (defined(_MSC_VER) && _MSC_VER < 1500)
+# define NO_STRNLEN
+#endif
+
+#ifdef NO_STRNLEN
+GIT_INLINE(size_t) p_strnlen(const char *s, size_t maxlen) {
+ const char *end = memchr(s, 0, maxlen);
+ return end ? (size_t)(end - s) : maxlen;
+}
+#else
+# define p_strnlen strnlen
+#endif
+
+#endif
diff --git a/src/util/thread.c b/src/util/thread.c
new file mode 100644
index 0000000..bc7364f
--- /dev/null
+++ b/src/util/thread.c
@@ -0,0 +1,140 @@
+/*
+ * 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"
+
+#if !defined(GIT_THREADS)
+
+#define TLSDATA_MAX 16
+
+typedef struct {
+ void *value;
+ void (GIT_SYSTEM_CALL *destroy_fn)(void *);
+} tlsdata_value;
+
+static tlsdata_value tlsdata_values[TLSDATA_MAX];
+static int tlsdata_cnt = 0;
+
+int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *))
+{
+ if (tlsdata_cnt >= TLSDATA_MAX)
+ return -1;
+
+ tlsdata_values[tlsdata_cnt].value = NULL;
+ tlsdata_values[tlsdata_cnt].destroy_fn = destroy_fn;
+
+ *key = tlsdata_cnt;
+ tlsdata_cnt++;
+
+ return 0;
+}
+
+int git_tlsdata_set(git_tlsdata_key key, void *value)
+{
+ if (key < 0 || key > tlsdata_cnt)
+ return -1;
+
+ tlsdata_values[key].value = value;
+ return 0;
+}
+
+void *git_tlsdata_get(git_tlsdata_key key)
+{
+ if (key < 0 || key > tlsdata_cnt)
+ return NULL;
+
+ return tlsdata_values[key].value;
+}
+
+int git_tlsdata_dispose(git_tlsdata_key key)
+{
+ void *value;
+ void (*destroy_fn)(void *) = NULL;
+
+ if (key < 0 || key > tlsdata_cnt)
+ return -1;
+
+ value = tlsdata_values[key].value;
+ destroy_fn = tlsdata_values[key].destroy_fn;
+
+ tlsdata_values[key].value = NULL;
+ tlsdata_values[key].destroy_fn = NULL;
+
+ if (value && destroy_fn)
+ destroy_fn(value);
+
+ return 0;
+}
+
+#elif defined(GIT_WIN32)
+
+int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *))
+{
+ DWORD fls_index = FlsAlloc(destroy_fn);
+
+ if (fls_index == FLS_OUT_OF_INDEXES)
+ return -1;
+
+ *key = fls_index;
+ return 0;
+}
+
+int git_tlsdata_set(git_tlsdata_key key, void *value)
+{
+ if (!FlsSetValue(key, value))
+ return -1;
+
+ return 0;
+}
+
+void *git_tlsdata_get(git_tlsdata_key key)
+{
+ return FlsGetValue(key);
+}
+
+int git_tlsdata_dispose(git_tlsdata_key key)
+{
+ if (!FlsFree(key))
+ return -1;
+
+ return 0;
+}
+
+#elif defined(_POSIX_THREADS)
+
+int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *))
+{
+ if (pthread_key_create(key, destroy_fn) != 0)
+ return -1;
+
+ return 0;
+}
+
+int git_tlsdata_set(git_tlsdata_key key, void *value)
+{
+ if (pthread_setspecific(key, value) != 0)
+ return -1;
+
+ return 0;
+}
+
+void *git_tlsdata_get(git_tlsdata_key key)
+{
+ return pthread_getspecific(key);
+}
+
+int git_tlsdata_dispose(git_tlsdata_key key)
+{
+ if (pthread_key_delete(key) != 0)
+ return -1;
+
+ return 0;
+}
+
+#else
+# error unknown threading model
+#endif
diff --git a/src/util/thread.h b/src/util/thread.h
new file mode 100644
index 0000000..c32554b
--- /dev/null
+++ b/src/util/thread.h
@@ -0,0 +1,480 @@
+/*
+ * 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_thread_h__
+#define INCLUDE_thread_h__
+
+#if defined(GIT_THREADS)
+
+#if defined(__clang__)
+
+# if (__clang_major__ < 3 || (__clang_major__ == 3 && __clang_minor__ < 1))
+# error Atomic primitives do not exist on this version of clang; configure libgit2 with -DUSE_THREADS=OFF
+# else
+# define GIT_BUILTIN_ATOMIC
+# endif
+
+#elif defined(__GNUC__)
+
+# if (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 1))
+# error Atomic primitives do not exist on this version of gcc; configure libgit2 with -DUSE_THREADS=OFF
+# elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7))
+# define GIT_BUILTIN_ATOMIC
+# else
+# define GIT_BUILTIN_SYNC
+# endif
+
+#endif
+
+#endif /* GIT_THREADS */
+
+/* Common operations even if threading has been disabled */
+typedef struct {
+#if defined(GIT_WIN32)
+ volatile long val;
+#else
+ volatile int val;
+#endif
+} git_atomic32;
+
+#ifdef GIT_ARCH_64
+
+typedef struct {
+#if defined(GIT_WIN32)
+ volatile __int64 val;
+#else
+ volatile int64_t val;
+#endif
+} git_atomic64;
+
+typedef git_atomic64 git_atomic_ssize;
+
+#define git_atomic_ssize_set git_atomic64_set
+#define git_atomic_ssize_add git_atomic64_add
+#define git_atomic_ssize_get git_atomic64_get
+
+#else
+
+typedef git_atomic32 git_atomic_ssize;
+
+#define git_atomic_ssize_set git_atomic32_set
+#define git_atomic_ssize_add git_atomic32_add
+#define git_atomic_ssize_get git_atomic32_get
+
+#endif
+
+#ifdef GIT_THREADS
+
+#ifdef GIT_WIN32
+# include "win32/thread.h"
+#else
+# include "unix/pthread.h"
+#endif
+
+/*
+ * Atomically sets the contents of *a to be val.
+ */
+GIT_INLINE(void) git_atomic32_set(git_atomic32 *a, int val)
+{
+#if defined(GIT_WIN32)
+ InterlockedExchange(&a->val, (LONG)val);
+#elif defined(GIT_BUILTIN_ATOMIC)
+ __atomic_store_n(&a->val, val, __ATOMIC_SEQ_CST);
+#elif defined(GIT_BUILTIN_SYNC)
+ __sync_lock_test_and_set(&a->val, val);
+#else
+# error "Unsupported architecture for atomic operations"
+#endif
+}
+
+/*
+ * Atomically increments the contents of *a by 1, and stores the result back into *a.
+ * @return the result of the operation.
+ */
+GIT_INLINE(int) git_atomic32_inc(git_atomic32 *a)
+{
+#if defined(GIT_WIN32)
+ return InterlockedIncrement(&a->val);
+#elif defined(GIT_BUILTIN_ATOMIC)
+ return __atomic_add_fetch(&a->val, 1, __ATOMIC_SEQ_CST);
+#elif defined(GIT_BUILTIN_SYNC)
+ return __sync_add_and_fetch(&a->val, 1);
+#else
+# error "Unsupported architecture for atomic operations"
+#endif
+}
+
+/*
+ * Atomically adds the contents of *a and addend, and stores the result back into *a.
+ * @return the result of the operation.
+ */
+GIT_INLINE(int) git_atomic32_add(git_atomic32 *a, int32_t addend)
+{
+#if defined(GIT_WIN32)
+ return InterlockedAdd(&a->val, addend);
+#elif defined(GIT_BUILTIN_ATOMIC)
+ return __atomic_add_fetch(&a->val, addend, __ATOMIC_SEQ_CST);
+#elif defined(GIT_BUILTIN_SYNC)
+ return __sync_add_and_fetch(&a->val, addend);
+#else
+# error "Unsupported architecture for atomic operations"
+#endif
+}
+
+/*
+ * Atomically decrements the contents of *a by 1, and stores the result back into *a.
+ * @return the result of the operation.
+ */
+GIT_INLINE(int) git_atomic32_dec(git_atomic32 *a)
+{
+#if defined(GIT_WIN32)
+ return InterlockedDecrement(&a->val);
+#elif defined(GIT_BUILTIN_ATOMIC)
+ return __atomic_sub_fetch(&a->val, 1, __ATOMIC_SEQ_CST);
+#elif defined(GIT_BUILTIN_SYNC)
+ return __sync_sub_and_fetch(&a->val, 1);
+#else
+# error "Unsupported architecture for atomic operations"
+#endif
+}
+
+/*
+ * Atomically gets the contents of *a.
+ * @return the contents of *a.
+ */
+GIT_INLINE(int) git_atomic32_get(git_atomic32 *a)
+{
+#if defined(GIT_WIN32)
+ return (int)InterlockedCompareExchange(&a->val, 0, 0);
+#elif defined(GIT_BUILTIN_ATOMIC)
+ return __atomic_load_n(&a->val, __ATOMIC_SEQ_CST);
+#elif defined(GIT_BUILTIN_SYNC)
+ return __sync_val_compare_and_swap(&a->val, 0, 0);
+#else
+# error "Unsupported architecture for atomic operations"
+#endif
+}
+
+GIT_INLINE(void *) git_atomic__compare_and_swap(
+ void * volatile *ptr, void *oldval, void *newval)
+{
+#if defined(GIT_WIN32)
+ return InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval);
+#elif defined(GIT_BUILTIN_ATOMIC)
+ void *foundval = oldval;
+ __atomic_compare_exchange(ptr, &foundval, &newval, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
+ return foundval;
+#elif defined(GIT_BUILTIN_SYNC)
+ return __sync_val_compare_and_swap(ptr, oldval, newval);
+#else
+# error "Unsupported architecture for atomic operations"
+#endif
+}
+
+GIT_INLINE(volatile void *) git_atomic__swap(
+ void * volatile *ptr, void *newval)
+{
+#if defined(GIT_WIN32)
+ return InterlockedExchangePointer(ptr, newval);
+#elif defined(GIT_BUILTIN_ATOMIC)
+ void * foundval = NULL;
+ __atomic_exchange(ptr, &newval, &foundval, __ATOMIC_SEQ_CST);
+ return foundval;
+#elif defined(GIT_BUILTIN_SYNC)
+ return (volatile void *)__sync_lock_test_and_set(ptr, newval);
+#else
+# error "Unsupported architecture for atomic operations"
+#endif
+}
+
+GIT_INLINE(volatile void *) git_atomic__load(void * volatile *ptr)
+{
+#if defined(GIT_WIN32)
+ void *newval = NULL, *oldval = NULL;
+ return (volatile void *)InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval);
+#elif defined(GIT_BUILTIN_ATOMIC)
+ return (volatile void *)__atomic_load_n(ptr, __ATOMIC_SEQ_CST);
+#elif defined(GIT_BUILTIN_SYNC)
+ return (volatile void *)__sync_val_compare_and_swap(ptr, 0, 0);
+#else
+# error "Unsupported architecture for atomic operations"
+#endif
+}
+
+#ifdef GIT_ARCH_64
+
+/*
+ * Atomically adds the contents of *a and addend, and stores the result back into *a.
+ * @return the result of the operation.
+ */
+GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend)
+{
+#if defined(GIT_WIN32)
+ return InterlockedAdd64(&a->val, addend);
+#elif defined(GIT_BUILTIN_ATOMIC)
+ return __atomic_add_fetch(&a->val, addend, __ATOMIC_SEQ_CST);
+#elif defined(GIT_BUILTIN_SYNC)
+ return __sync_add_and_fetch(&a->val, addend);
+#else
+# error "Unsupported architecture for atomic operations"
+#endif
+}
+
+/*
+ * Atomically sets the contents of *a to be val.
+ */
+GIT_INLINE(void) git_atomic64_set(git_atomic64 *a, int64_t val)
+{
+#if defined(GIT_WIN32)
+ InterlockedExchange64(&a->val, val);
+#elif defined(GIT_BUILTIN_ATOMIC)
+ __atomic_store_n(&a->val, val, __ATOMIC_SEQ_CST);
+#elif defined(GIT_BUILTIN_SYNC)
+ __sync_lock_test_and_set(&a->val, val);
+#else
+# error "Unsupported architecture for atomic operations"
+#endif
+}
+
+/*
+ * Atomically gets the contents of *a.
+ * @return the contents of *a.
+ */
+GIT_INLINE(int64_t) git_atomic64_get(git_atomic64 *a)
+{
+#if defined(GIT_WIN32)
+ return (int64_t)InterlockedCompareExchange64(&a->val, 0, 0);
+#elif defined(GIT_BUILTIN_ATOMIC)
+ return __atomic_load_n(&a->val, __ATOMIC_SEQ_CST);
+#elif defined(GIT_BUILTIN_SYNC)
+ return __sync_val_compare_and_swap(&a->val, 0, 0);
+#else
+# error "Unsupported architecture for atomic operations"
+#endif
+}
+
+#endif
+
+#else
+
+#define git_threads_global_init git__noop
+
+#define git_thread unsigned int
+#define git_thread_create(t, s, a) git__noop(t, s, a)
+#define git_thread_join(i, s) git__noop_args(i, s)
+
+/* Pthreads Mutex */
+#define git_mutex unsigned int
+#define git_mutex_init(a) git__noop_args(a)
+#define git_mutex_init(a) git__noop_args(a)
+#define git_mutex_lock(a) git__noop_args(a)
+#define git_mutex_unlock(a) git__noop_args(a)
+#define git_mutex_free(a) git__noop_args(a)
+
+/* Pthreads condition vars */
+#define git_cond unsigned int
+#define git_cond_init(c) git__noop_args(c)
+#define git_cond_free(c) git__noop_args(c)
+#define git_cond_wait(c, l) git__noop_args(c, l)
+#define git_cond_signal(c) git__noop_args(c)
+#define git_cond_broadcast(c) git__noop_args(c)
+
+/* Pthreads rwlock */
+#define git_rwlock unsigned int
+#define git_rwlock_init(a) git__noop_args(a)
+#define git_rwlock_rdlock(a) git__noop_args(a)
+#define git_rwlock_rdunlock(a) git__noop_args(a)
+#define git_rwlock_wrlock(a) git__noop_args(a)
+#define git_rwlock_wrunlock(a) git__noop_args(a)
+#define git_rwlock_free(a) git__noop_args(a)
+
+#define GIT_RWLOCK_STATIC_INIT 0
+
+
+GIT_INLINE(void) git_atomic32_set(git_atomic32 *a, int val)
+{
+ a->val = val;
+}
+
+GIT_INLINE(int) git_atomic32_inc(git_atomic32 *a)
+{
+ return ++a->val;
+}
+
+GIT_INLINE(int) git_atomic32_add(git_atomic32 *a, int32_t addend)
+{
+ a->val += addend;
+ return a->val;
+}
+
+GIT_INLINE(int) git_atomic32_dec(git_atomic32 *a)
+{
+ return --a->val;
+}
+
+GIT_INLINE(int) git_atomic32_get(git_atomic32 *a)
+{
+ return (int)a->val;
+}
+
+GIT_INLINE(void *) git_atomic__compare_and_swap(
+ void * volatile *ptr, void *oldval, void *newval)
+{
+ void *foundval = *ptr;
+ if (foundval == oldval)
+ *ptr = newval;
+ return foundval;
+}
+
+GIT_INLINE(volatile void *) git_atomic__swap(
+ void * volatile *ptr, void *newval)
+{
+ volatile void *old = *ptr;
+ *ptr = newval;
+ return old;
+}
+
+GIT_INLINE(volatile void *) git_atomic__load(void * volatile *ptr)
+{
+ return *ptr;
+}
+
+#ifdef GIT_ARCH_64
+
+GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend)
+{
+ a->val += addend;
+ return a->val;
+}
+
+GIT_INLINE(void) git_atomic64_set(git_atomic64 *a, int64_t val)
+{
+ a->val = val;
+}
+
+GIT_INLINE(int64_t) git_atomic64_get(git_atomic64 *a)
+{
+ return (int64_t)a->val;
+}
+
+#endif
+
+#endif
+
+/*
+ * Atomically replace the contents of *ptr (if they are equal to oldval) with
+ * newval. ptr must point to a pointer or a value that is the same size as a
+ * pointer. This is semantically compatible with:
+ *
+ * #define git_atomic_compare_and_swap(ptr, oldval, newval) \
+ * ({ \
+ * void *foundval = *ptr; \
+ * if (foundval == oldval) \
+ * *ptr = newval; \
+ * foundval; \
+ * })
+ *
+ * @return the original contents of *ptr.
+ */
+#define git_atomic_compare_and_swap(ptr, oldval, newval) \
+ git_atomic__compare_and_swap((void * volatile *)ptr, oldval, newval)
+
+/*
+ * Atomically replace the contents of v with newval. v must be the same size as
+ * a pointer. This is semantically compatible with:
+ *
+ * #define git_atomic_swap(v, newval) \
+ * ({ \
+ * volatile void *old = v; \
+ * v = newval; \
+ * old; \
+ * })
+ *
+ * @return the original contents of v.
+ */
+#define git_atomic_swap(v, newval) \
+ (void *)git_atomic__swap((void * volatile *)&(v), newval)
+
+/*
+ * Atomically reads the contents of v. v must be the same size as a pointer.
+ * This is semantically compatible with:
+ *
+ * #define git_atomic_load(v) v
+ *
+ * @return the contents of v.
+ */
+#define git_atomic_load(v) \
+ (void *)git_atomic__load((void * volatile *)&(v))
+
+#if defined(GIT_THREADS)
+
+# if defined(GIT_WIN32)
+# define GIT_MEMORY_BARRIER MemoryBarrier()
+# elif defined(GIT_BUILTIN_ATOMIC)
+# define GIT_MEMORY_BARRIER __atomic_thread_fence(__ATOMIC_SEQ_CST)
+# elif defined(GIT_BUILTIN_SYNC)
+# define GIT_MEMORY_BARRIER __sync_synchronize()
+# endif
+
+#else
+
+# define GIT_MEMORY_BARRIER /* noop */
+
+#endif
+
+/* Thread-local data */
+
+#if !defined(GIT_THREADS)
+# define git_tlsdata_key int
+#elif defined(GIT_WIN32)
+# define git_tlsdata_key DWORD
+#elif defined(_POSIX_THREADS)
+# define git_tlsdata_key pthread_key_t
+#else
+# error unknown threading model
+#endif
+
+/**
+ * Create a thread-local data key. The destroy function will be
+ * called upon thread exit. On some platforms, it may be called
+ * when all threads have deleted their keys.
+ *
+ * Note that the tlsdata functions do not set an error message on
+ * failure; this is because the error handling in libgit2 is itself
+ * handled by thread-local data storage.
+ *
+ * @param key the tlsdata key
+ * @param destroy_fn function pointer called upon thread exit
+ * @return 0 on success, non-zero on failure
+ */
+int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *));
+
+/**
+ * Set a the thread-local value for the given key.
+ *
+ * @param key the tlsdata key to store data on
+ * @param value the pointer to store
+ * @return 0 on success, non-zero on failure
+ */
+int git_tlsdata_set(git_tlsdata_key key, void *value);
+
+/**
+ * Get the thread-local value for the given key.
+ *
+ * @param key the tlsdata key to retrieve the value of
+ * @return the pointer stored with git_tlsdata_set
+ */
+void *git_tlsdata_get(git_tlsdata_key key);
+
+/**
+ * Delete the given thread-local key.
+ *
+ * @param key the tlsdata key to dispose
+ * @return 0 on success, non-zero on failure
+ */
+int git_tlsdata_dispose(git_tlsdata_key key);
+
+#endif
diff --git a/src/util/tsort.c b/src/util/tsort.c
new file mode 100644
index 0000000..2ef03d0
--- /dev/null
+++ b/src/util/tsort.c
@@ -0,0 +1,382 @@
+/*
+ * 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"
+
+/**
+ * An array-of-pointers implementation of Python's Timsort
+ * Based on code by Christopher Swenson under the MIT license
+ *
+ * Copyright (c) 2010 Christopher Swenson
+ * Copyright (c) 2011 Vicent Marti
+ */
+
+#ifndef MAX
+# define MAX(x,y) (((x) > (y) ? (x) : (y)))
+#endif
+
+#ifndef MIN
+# define MIN(x,y) (((x) < (y) ? (x) : (y)))
+#endif
+
+static int binsearch(
+ void **dst, const void *x, size_t size, git__sort_r_cmp cmp, void *payload)
+{
+ int l, c, r;
+ void *lx, *cx;
+
+ l = 0;
+ r = (int)size - 1;
+ c = r >> 1;
+ lx = dst[l];
+
+ /* check for beginning conditions */
+ if (cmp(x, lx, payload) < 0)
+ return 0;
+
+ else if (cmp(x, lx, payload) == 0) {
+ int i = 1;
+ while (cmp(x, dst[i], payload) == 0)
+ i++;
+ return i;
+ }
+
+ /* guaranteed not to be >= rx */
+ cx = dst[c];
+ while (1) {
+ const int val = cmp(x, cx, payload);
+ if (val < 0) {
+ if (c - l <= 1) return c;
+ r = c;
+ } else if (val > 0) {
+ if (r - c <= 1) return c + 1;
+ l = c;
+ lx = cx;
+ } else {
+ do {
+ cx = dst[++c];
+ } while (cmp(x, cx, payload) == 0);
+ return c;
+ }
+ c = l + ((r - l) >> 1);
+ cx = dst[c];
+ }
+}
+
+/* Binary insertion sort, but knowing that the first "start" entries are sorted. Used in timsort. */
+static void bisort(
+ void **dst, size_t start, size_t size, git__sort_r_cmp cmp, void *payload)
+{
+ size_t i;
+ void *x;
+ int location;
+
+ for (i = start; i < size; i++) {
+ int j;
+ /* If this entry is already correct, just move along */
+ if (cmp(dst[i - 1], dst[i], payload) <= 0)
+ continue;
+
+ /* Else we need to find the right place, shift everything over, and squeeze in */
+ x = dst[i];
+ location = binsearch(dst, x, i, cmp, payload);
+ for (j = (int)i - 1; j >= location; j--) {
+ dst[j + 1] = dst[j];
+ }
+ dst[location] = x;
+ }
+}
+
+
+/* timsort implementation, based on timsort.txt */
+struct tsort_run {
+ ssize_t start;
+ ssize_t length;
+};
+
+struct tsort_store {
+ size_t alloc;
+ git__sort_r_cmp cmp;
+ void *payload;
+ void **storage;
+};
+
+static void reverse_elements(void **dst, ssize_t start, ssize_t end)
+{
+ while (start < end) {
+ void *tmp = dst[start];
+ dst[start] = dst[end];
+ dst[end] = tmp;
+
+ start++;
+ end--;
+ }
+}
+
+static ssize_t count_run(
+ void **dst, ssize_t start, ssize_t size, struct tsort_store *store)
+{
+ ssize_t curr = start + 2;
+
+ if (size - start == 1)
+ return 1;
+
+ if (start >= size - 2) {
+ if (store->cmp(dst[size - 2], dst[size - 1], store->payload) > 0) {
+ void *tmp = dst[size - 1];
+ dst[size - 1] = dst[size - 2];
+ dst[size - 2] = tmp;
+ }
+
+ return 2;
+ }
+
+ if (store->cmp(dst[start], dst[start + 1], store->payload) <= 0) {
+ while (curr < size - 1 &&
+ store->cmp(dst[curr - 1], dst[curr], store->payload) <= 0)
+ curr++;
+
+ return curr - start;
+ } else {
+ while (curr < size - 1 &&
+ store->cmp(dst[curr - 1], dst[curr], store->payload) > 0)
+ curr++;
+
+ /* reverse in-place */
+ reverse_elements(dst, start, curr - 1);
+ return curr - start;
+ }
+}
+
+static size_t compute_minrun(size_t n)
+{
+ int r = 0;
+ while (n >= 64) {
+ r |= n & 1;
+ n >>= 1;
+ }
+ return n + r;
+}
+
+static int check_invariant(struct tsort_run *stack, ssize_t stack_curr)
+{
+ if (stack_curr < 2)
+ return 1;
+
+ else if (stack_curr == 2) {
+ const ssize_t A = stack[stack_curr - 2].length;
+ const ssize_t B = stack[stack_curr - 1].length;
+ return (A > B);
+ } else {
+ const ssize_t A = stack[stack_curr - 3].length;
+ const ssize_t B = stack[stack_curr - 2].length;
+ const ssize_t C = stack[stack_curr - 1].length;
+ return !((A <= B + C) || (B <= C));
+ }
+}
+
+static int resize(struct tsort_store *store, size_t new_size)
+{
+ if (store->alloc < new_size) {
+ void **tempstore;
+
+ tempstore = git__reallocarray(store->storage, new_size, sizeof(void *));
+
+ /**
+ * Do not propagate on OOM; this will abort the sort and
+ * leave the array unsorted, but no error code will be
+ * raised
+ */
+ if (tempstore == NULL)
+ return -1;
+
+ store->storage = tempstore;
+ store->alloc = new_size;
+ }
+
+ return 0;
+}
+
+static void merge(void **dst, const struct tsort_run *stack, ssize_t stack_curr, struct tsort_store *store)
+{
+ const ssize_t A = stack[stack_curr - 2].length;
+ const ssize_t B = stack[stack_curr - 1].length;
+ const ssize_t curr = stack[stack_curr - 2].start;
+
+ void **storage;
+ ssize_t i, j, k;
+
+ if (resize(store, MIN(A, B)) < 0)
+ return;
+
+ storage = store->storage;
+
+ /* left merge */
+ if (A < B) {
+ memcpy(storage, &dst[curr], A * sizeof(void *));
+ i = 0;
+ j = curr + A;
+
+ for (k = curr; k < curr + A + B; k++) {
+ if ((i < A) && (j < curr + A + B)) {
+ if (store->cmp(storage[i], dst[j], store->payload) <= 0)
+ dst[k] = storage[i++];
+ else
+ dst[k] = dst[j++];
+ } else if (i < A) {
+ dst[k] = storage[i++];
+ } else
+ dst[k] = dst[j++];
+ }
+ } else {
+ memcpy(storage, &dst[curr + A], B * sizeof(void *));
+ i = B - 1;
+ j = curr + A - 1;
+
+ for (k = curr + A + B - 1; k >= curr; k--) {
+ if ((i >= 0) && (j >= curr)) {
+ if (store->cmp(dst[j], storage[i], store->payload) > 0)
+ dst[k] = dst[j--];
+ else
+ dst[k] = storage[i--];
+ } else if (i >= 0)
+ dst[k] = storage[i--];
+ else
+ dst[k] = dst[j--];
+ }
+ }
+}
+
+static ssize_t collapse(void **dst, struct tsort_run *stack, ssize_t stack_curr, struct tsort_store *store, ssize_t size)
+{
+ ssize_t A, B, C;
+
+ while (1) {
+ /* if the stack only has one thing on it, we are done with the collapse */
+ if (stack_curr <= 1)
+ break;
+
+ /* if this is the last merge, just do it */
+ if ((stack_curr == 2) && (stack[0].length + stack[1].length == size)) {
+ merge(dst, stack, stack_curr, store);
+ stack[0].length += stack[1].length;
+ stack_curr--;
+ break;
+ }
+
+ /* check if the invariant is off for a stack of 2 elements */
+ else if ((stack_curr == 2) && (stack[0].length <= stack[1].length)) {
+ merge(dst, stack, stack_curr, store);
+ stack[0].length += stack[1].length;
+ stack_curr--;
+ break;
+ }
+ else if (stack_curr == 2)
+ break;
+
+ A = stack[stack_curr - 3].length;
+ B = stack[stack_curr - 2].length;
+ C = stack[stack_curr - 1].length;
+
+ /* check first invariant */
+ if (A <= B + C) {
+ if (A < C) {
+ merge(dst, stack, stack_curr - 1, store);
+ stack[stack_curr - 3].length += stack[stack_curr - 2].length;
+ stack[stack_curr - 2] = stack[stack_curr - 1];
+ stack_curr--;
+ } else {
+ merge(dst, stack, stack_curr, store);
+ stack[stack_curr - 2].length += stack[stack_curr - 1].length;
+ stack_curr--;
+ }
+ } else if (B <= C) {
+ merge(dst, stack, stack_curr, store);
+ stack[stack_curr - 2].length += stack[stack_curr - 1].length;
+ stack_curr--;
+ } else
+ break;
+ }
+
+ return stack_curr;
+}
+
+#define PUSH_NEXT() do {\
+ len = count_run(dst, curr, size, store);\
+ run = minrun;\
+ if (run > (ssize_t)size - curr) run = size - curr;\
+ if (run > len) {\
+ bisort(&dst[curr], len, run, cmp, payload);\
+ len = run;\
+ }\
+ run_stack[stack_curr].start = curr;\
+ run_stack[stack_curr++].length = len;\
+ curr += len;\
+ if (curr == (ssize_t)size) {\
+ /* finish up */ \
+ while (stack_curr > 1) { \
+ merge(dst, run_stack, stack_curr, store); \
+ run_stack[stack_curr - 2].length += run_stack[stack_curr - 1].length; \
+ stack_curr--; \
+ } \
+ if (store->storage != NULL) {\
+ git__free(store->storage);\
+ store->storage = NULL;\
+ }\
+ return;\
+ }\
+}\
+while (0)
+
+void git__tsort_r(
+ void **dst, size_t size, git__sort_r_cmp cmp, void *payload)
+{
+ struct tsort_store _store, *store = &_store;
+ struct tsort_run run_stack[128];
+
+ ssize_t stack_curr = 0;
+ ssize_t len, run;
+ ssize_t curr = 0;
+ ssize_t minrun;
+
+ if (size < 64) {
+ bisort(dst, 1, size, cmp, payload);
+ return;
+ }
+
+ /* compute the minimum run length */
+ minrun = (ssize_t)compute_minrun(size);
+
+ /* temporary storage for merges */
+ store->alloc = 0;
+ store->storage = NULL;
+ store->cmp = cmp;
+ store->payload = payload;
+
+ PUSH_NEXT();
+ PUSH_NEXT();
+ PUSH_NEXT();
+
+ while (1) {
+ if (!check_invariant(run_stack, stack_curr)) {
+ stack_curr = collapse(dst, run_stack, stack_curr, store, size);
+ continue;
+ }
+
+ PUSH_NEXT();
+ }
+}
+
+static int tsort_r_cmp(const void *a, const void *b, void *payload)
+{
+ return ((git__tsort_cmp)payload)(a, b);
+}
+
+void git__tsort(void **dst, size_t size, git__tsort_cmp cmp)
+{
+ git__tsort_r(dst, size, tsort_r_cmp, cmp);
+}
diff --git a/src/util/unix/map.c b/src/util/unix/map.c
new file mode 100644
index 0000000..9330776
--- /dev/null
+++ b/src/util/unix/map.c
@@ -0,0 +1,76 @@
+/*
+ * 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"
+
+#if !defined(GIT_WIN32) && !defined(NO_MMAP)
+
+#include "map.h"
+#include <sys/mman.h>
+#include <unistd.h>
+#include <errno.h>
+
+int git__page_size(size_t *page_size)
+{
+ long sc_page_size = sysconf(_SC_PAGE_SIZE);
+ if (sc_page_size < 0) {
+ git_error_set(GIT_ERROR_OS, "can't determine system page size");
+ return -1;
+ }
+ *page_size = (size_t) sc_page_size;
+ return 0;
+}
+
+int git__mmap_alignment(size_t *alignment)
+{
+ return git__page_size(alignment);
+}
+
+int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, off64_t offset)
+{
+ int mprot = PROT_READ;
+ int mflag = 0;
+
+ GIT_MMAP_VALIDATE(out, len, prot, flags);
+
+ out->data = NULL;
+ out->len = 0;
+
+ if (prot & GIT_PROT_WRITE)
+ mprot |= PROT_WRITE;
+
+ if ((flags & GIT_MAP_TYPE) == GIT_MAP_SHARED)
+ mflag = MAP_SHARED;
+ else if ((flags & GIT_MAP_TYPE) == GIT_MAP_PRIVATE)
+ mflag = MAP_PRIVATE;
+ else
+ mflag = MAP_SHARED;
+
+ out->data = mmap(NULL, len, mprot, mflag, fd, offset);
+
+ if (!out->data || out->data == MAP_FAILED) {
+ git_error_set(GIT_ERROR_OS, "failed to mmap. Could not write data");
+ return -1;
+ }
+
+ out->len = len;
+
+ return 0;
+}
+
+int p_munmap(git_map *map)
+{
+ GIT_ASSERT_ARG(map);
+ munmap(map->data, map->len);
+ map->data = NULL;
+ map->len = 0;
+
+ return 0;
+}
+
+#endif
+
diff --git a/src/util/unix/posix.h b/src/util/unix/posix.h
new file mode 100644
index 0000000..778477e
--- /dev/null
+++ b/src/util/unix/posix.h
@@ -0,0 +1,104 @@
+/*
+ * 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_unix_posix_h__
+#define INCLUDE_unix_posix_h__
+
+#include "git2_util.h"
+
+#include <stdio.h>
+#include <dirent.h>
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+
+typedef int GIT_SOCKET;
+#define INVALID_SOCKET -1
+
+#define p_lseek(f,n,w) lseek(f, n, w)
+#define p_fstat(f,b) fstat(f, b)
+#define p_lstat(p,b) lstat(p,b)
+#define p_stat(p,b) stat(p, b)
+
+#if defined(GIT_USE_STAT_MTIMESPEC)
+# define st_atime_nsec st_atimespec.tv_nsec
+# define st_mtime_nsec st_mtimespec.tv_nsec
+# define st_ctime_nsec st_ctimespec.tv_nsec
+#elif defined(GIT_USE_STAT_MTIM)
+# define st_atime_nsec st_atim.tv_nsec
+# define st_mtime_nsec st_mtim.tv_nsec
+# define st_ctime_nsec st_ctim.tv_nsec
+#elif !defined(GIT_USE_STAT_MTIME_NSEC) && defined(GIT_USE_NSEC)
+# error GIT_USE_NSEC defined but unknown struct stat nanosecond type
+#endif
+
+#define p_utimes(f, t) utimes(f, t)
+
+#define p_readlink(a, b, c) readlink(a, b, c)
+#define p_symlink(o,n) symlink(o, n)
+#define p_link(o,n) link(o, n)
+#define p_unlink(p) unlink(p)
+#define p_mkdir(p,m) mkdir(p, m)
+extern char *p_realpath(const char *, char *);
+
+GIT_INLINE(int) p_fsync(int fd)
+{
+ p_fsync__cnt++;
+ return fsync(fd);
+}
+
+#define p_recv(s,b,l,f) recv(s,b,l,f)
+#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)
+#define p_rmdir(p) rmdir(p)
+#define p_access(p,m) access(p,m)
+#define p_ftruncate(fd, sz) ftruncate(fd, sz)
+
+/*
+ * Pre-Android 5 did not implement a virtual filesystem atop FAT
+ * partitions for Unix permissions, which causes chmod to fail. However,
+ * Unix permissions have no effect on Android anyway as file permissions
+ * are not actually managed this way, so treating it as a no-op across
+ * all Android is safe.
+ */
+#ifdef __ANDROID__
+# define p_chmod(p,m) 0
+#else
+# define p_chmod(p,m) chmod(p, m)
+#endif
+
+/* see win32/posix.h for explanation about why this exists */
+#define p_lstat_posixly(p,b) lstat(p,b)
+
+#define p_localtime_r(c, r) localtime_r(c, r)
+#define p_gmtime_r(c, r) gmtime_r(c, r)
+
+#define p_timeval timeval
+
+#ifdef GIT_USE_FUTIMENS
+GIT_INLINE(int) p_futimes(int f, const struct p_timeval t[2])
+{
+ struct timespec s[2];
+ s[0].tv_sec = t[0].tv_sec;
+ s[0].tv_nsec = t[0].tv_usec * 1000;
+ s[1].tv_sec = t[1].tv_sec;
+ s[1].tv_nsec = t[1].tv_usec * 1000;
+ return futimens(f, s);
+}
+#else
+# define p_futimes futimes
+#endif
+
+#define p_pread(f, d, s, o) pread(f, d, s, o)
+#define p_pwrite(f, d, s, o) pwrite(f, d, s, o)
+
+#endif
diff --git a/src/util/unix/pthread.h b/src/util/unix/pthread.h
new file mode 100644
index 0000000..55f4ae2
--- /dev/null
+++ b/src/util/unix/pthread.h
@@ -0,0 +1,57 @@
+/*
+ * 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_unix_pthread_h__
+#define INCLUDE_unix_pthread_h__
+
+typedef struct {
+ pthread_t thread;
+} git_thread;
+
+GIT_INLINE(int) git_threads_global_init(void) { return 0; }
+
+#define git_thread_create(git_thread_ptr, start_routine, arg) \
+ pthread_create(&(git_thread_ptr)->thread, NULL, start_routine, arg)
+#define git_thread_join(git_thread_ptr, status) \
+ pthread_join((git_thread_ptr)->thread, status)
+#define git_thread_currentid() ((size_t)(pthread_self()))
+#define git_thread_exit(retval) pthread_exit(retval)
+
+/* Git Mutex */
+#define git_mutex pthread_mutex_t
+#define git_mutex_init(a) pthread_mutex_init(a, NULL)
+#define git_mutex_lock(a) pthread_mutex_lock(a)
+#define git_mutex_unlock(a) pthread_mutex_unlock(a)
+#define git_mutex_free(a) pthread_mutex_destroy(a)
+
+/* Git condition vars */
+#define git_cond pthread_cond_t
+#define git_cond_init(c) pthread_cond_init(c, NULL)
+#define git_cond_free(c) pthread_cond_destroy(c)
+#define git_cond_wait(c, l) pthread_cond_wait(c, l)
+#define git_cond_signal(c) pthread_cond_signal(c)
+#define git_cond_broadcast(c) pthread_cond_broadcast(c)
+
+/* Pthread (-ish) rwlock
+ *
+ * This differs from normal pthreads rwlocks in two ways:
+ * 1. Separate APIs for releasing read locks and write locks (as
+ * opposed to the pure POSIX API which only has one unlock fn)
+ * 2. You should not use recursive read locks (i.e. grabbing a read
+ * lock in a thread that already holds a read lock) because the
+ * Windows implementation doesn't support it
+ */
+#define git_rwlock pthread_rwlock_t
+#define git_rwlock_init(a) pthread_rwlock_init(a, NULL)
+#define git_rwlock_rdlock(a) pthread_rwlock_rdlock(a)
+#define git_rwlock_rdunlock(a) pthread_rwlock_unlock(a)
+#define git_rwlock_wrlock(a) pthread_rwlock_wrlock(a)
+#define git_rwlock_wrunlock(a) pthread_rwlock_unlock(a)
+#define git_rwlock_free(a) pthread_rwlock_destroy(a)
+#define GIT_RWLOCK_STATIC_INIT PTHREAD_RWLOCK_INITIALIZER
+
+#endif
diff --git a/src/util/unix/realpath.c b/src/util/unix/realpath.c
new file mode 100644
index 0000000..9e31a63
--- /dev/null
+++ b/src/util/unix/realpath.c
@@ -0,0 +1,32 @@
+/*
+ * 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"
+
+#ifndef GIT_WIN32
+
+#include <limits.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+char *p_realpath(const char *pathname, char *resolved)
+{
+ char *ret;
+ if ((ret = realpath(pathname, resolved)) == NULL)
+ return NULL;
+
+#ifdef __OpenBSD__
+ /* The OpenBSD realpath function behaves differently,
+ * figure out if the file exists */
+ if (access(ret, F_OK) < 0)
+ ret = NULL;
+#endif
+ return ret;
+}
+
+#endif
diff --git a/src/util/utf8.c b/src/util/utf8.c
new file mode 100644
index 0000000..c566fdf
--- /dev/null
+++ b/src/util/utf8.c
@@ -0,0 +1,150 @@
+/*
+ * 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 "utf8.h"
+
+#include "git2_util.h"
+
+/*
+ * git_utf8_iterate is taken from the utf8proc project,
+ * http://www.public-software-group.org/utf8proc
+ *
+ * Copyright (c) 2009 Public Software Group e. V., Berlin, Germany
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the ""Software""),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+static const uint8_t utf8proc_utf8class[256] = {
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static int utf8_charlen(const uint8_t *str, size_t str_len)
+{
+ uint8_t length;
+ size_t i;
+
+ length = utf8proc_utf8class[str[0]];
+ if (!length)
+ return -1;
+
+ if (str_len > 0 && length > str_len)
+ return -1;
+
+ for (i = 1; i < length; i++) {
+ if ((str[i] & 0xC0) != 0x80)
+ return -1;
+ }
+
+ return (int)length;
+}
+
+int git_utf8_iterate(uint32_t *out, const char *_str, size_t str_len)
+{
+ const uint8_t *str = (const uint8_t *)_str;
+ uint32_t uc = 0;
+ int length;
+
+ *out = 0;
+
+ if ((length = utf8_charlen(str, str_len)) < 0)
+ return -1;
+
+ switch (length) {
+ case 1:
+ uc = str[0];
+ break;
+ case 2:
+ uc = ((str[0] & 0x1F) << 6) + (str[1] & 0x3F);
+ if (uc < 0x80) uc = -1;
+ break;
+ case 3:
+ uc = ((str[0] & 0x0F) << 12) + ((str[1] & 0x3F) << 6)
+ + (str[2] & 0x3F);
+ if (uc < 0x800 || (uc >= 0xD800 && uc < 0xE000) ||
+ (uc >= 0xFDD0 && uc < 0xFDF0)) uc = -1;
+ break;
+ case 4:
+ uc = ((str[0] & 0x07) << 18) + ((str[1] & 0x3F) << 12)
+ + ((str[2] & 0x3F) << 6) + (str[3] & 0x3F);
+ if (uc < 0x10000 || uc >= 0x110000) uc = -1;
+ break;
+ default:
+ return -1;
+ }
+
+ if ((uc & 0xFFFF) >= 0xFFFE)
+ return -1;
+
+ *out = uc;
+ return length;
+}
+
+size_t git_utf8_char_length(const char *_str, size_t str_len)
+{
+ const uint8_t *str = (const uint8_t *)_str;
+ size_t offset = 0, count = 0;
+
+ while (offset < str_len) {
+ int length = utf8_charlen(str + offset, str_len - offset);
+
+ if (length < 0)
+ length = 1;
+
+ offset += length;
+ count++;
+ }
+
+ return count;
+}
+
+size_t git_utf8_valid_buf_length(const char *_str, size_t str_len)
+{
+ const uint8_t *str = (const uint8_t *)_str;
+ size_t offset = 0;
+
+ while (offset < str_len) {
+ int length = utf8_charlen(str + offset, str_len - offset);
+
+ if (length < 0)
+ break;
+
+ offset += length;
+ }
+
+ return offset;
+}
diff --git a/src/util/utf8.h b/src/util/utf8.h
new file mode 100644
index 0000000..753ab07
--- /dev/null
+++ b/src/util/utf8.h
@@ -0,0 +1,52 @@
+/*
+ * 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_utf8_h__
+#define INCLUDE_utf8_h__
+
+#include "git2_util.h"
+
+/*
+ * Iterate through an UTF-8 string, yielding one codepoint at a time.
+ *
+ * @param out pointer where to store the current codepoint
+ * @param str current position in the string
+ * @param str_len size left in the string
+ * @return length in bytes of the read codepoint; -1 if the codepoint was invalid
+ */
+extern int git_utf8_iterate(uint32_t *out, const char *str, size_t str_len);
+
+/**
+ * Returns the number of characters in the given string.
+ *
+ * This function will count invalid codepoints; if any given byte is
+ * not part of a valid UTF-8 codepoint, then it will be counted toward
+ * the length in characters.
+ *
+ * In other words:
+ * 0x24 (U+0024 "$") has length 1
+ * 0xc2 0xa2 (U+00A2 "¢") has length 1
+ * 0x24 0xc2 0xa2 (U+0024 U+00A2 "$¢") has length 2
+ * 0xf0 0x90 0x8d 0x88 (U+10348 "𐍈") has length 1
+ * 0x24 0xc0 0xc1 0x34 (U+0024 <invalid> <invalid> "4) has length 4
+ *
+ * @param str string to scan
+ * @param str_len size of the string
+ * @return length in characters of the string
+ */
+extern size_t git_utf8_char_length(const char *str, size_t str_len);
+
+/**
+ * Iterate through an UTF-8 string and stops after finding any invalid UTF-8
+ * codepoints.
+ *
+ * @param str string to scan
+ * @param str_len size of the string
+ * @return length in bytes of the string that contains valid data
+ */
+extern size_t git_utf8_valid_buf_length(const char *str, size_t str_len);
+
+#endif
diff --git a/src/util/util.c b/src/util/util.c
new file mode 100644
index 0000000..c8e8303
--- /dev/null
+++ b/src/util/util.c
@@ -0,0 +1,824 @@
+/*
+ * 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 "util.h"
+
+#include "git2_util.h"
+
+#ifdef GIT_WIN32
+# include "win32/utf-conv.h"
+# include "win32/w32_buffer.h"
+
+# ifndef WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+# endif
+# include <windows.h>
+
+# ifdef GIT_QSORT_MSC
+# include <search.h>
+# endif
+#endif
+
+#ifdef _MSC_VER
+# include <Shlwapi.h>
+#endif
+
+#if defined(hpux) || defined(__hpux) || defined(_hpux)
+# include <sys/pstat.h>
+#endif
+
+int git__strntol64(int64_t *result, const char *nptr, size_t nptr_len, const char **endptr, int base)
+{
+ const char *p;
+ int64_t n, nn, v;
+ int c, ovfl, neg, ndig;
+
+ p = nptr;
+ neg = 0;
+ n = 0;
+ ndig = 0;
+ ovfl = 0;
+
+ /*
+ * White space
+ */
+ while (nptr_len && git__isspace(*p))
+ p++, nptr_len--;
+
+ if (!nptr_len)
+ goto Return;
+
+ /*
+ * Sign
+ */
+ if (*p == '-' || *p == '+') {
+ if (*p == '-')
+ neg = 1;
+ p++;
+ nptr_len--;
+ }
+
+ if (!nptr_len)
+ goto Return;
+
+ /*
+ * Automatically detect the base if none was given to us.
+ * Right now, we assume that a number starting with '0x'
+ * is hexadecimal and a number starting with '0' is
+ * octal.
+ */
+ if (base == 0) {
+ if (*p != '0')
+ base = 10;
+ else if (nptr_len > 2 && (p[1] == 'x' || p[1] == 'X'))
+ base = 16;
+ else
+ base = 8;
+ }
+
+ if (base < 0 || 36 < base)
+ goto Return;
+
+ /*
+ * Skip prefix of '0x'-prefixed hexadecimal numbers. There is no
+ * need to do the same for '0'-prefixed octal numbers as a
+ * leading '0' does not have any impact. Also, if we skip a
+ * leading '0' in such a string, then we may end up with no
+ * digits left and produce an error later on which isn't one.
+ */
+ if (base == 16 && nptr_len > 2 && p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
+ p += 2;
+ nptr_len -= 2;
+ }
+
+ /*
+ * Non-empty sequence of digits
+ */
+ for (; nptr_len > 0; p++,ndig++,nptr_len--) {
+ c = *p;
+ v = base;
+ if ('0'<=c && c<='9')
+ v = c - '0';
+ else if ('a'<=c && c<='z')
+ v = c - 'a' + 10;
+ else if ('A'<=c && c<='Z')
+ v = c - 'A' + 10;
+ if (v >= base)
+ break;
+ v = neg ? -v : v;
+ if (git__multiply_int64_overflow(&nn, n, base) || git__add_int64_overflow(&n, nn, v)) {
+ ovfl = 1;
+ /* Keep on iterating until the end of this number */
+ continue;
+ }
+ }
+
+Return:
+ if (ndig == 0) {
+ git_error_set(GIT_ERROR_INVALID, "failed to convert string to long: not a number");
+ return -1;
+ }
+
+ if (endptr)
+ *endptr = p;
+
+ if (ovfl) {
+ git_error_set(GIT_ERROR_INVALID, "failed to convert string to long: overflow error");
+ return -1;
+ }
+
+ *result = n;
+ return 0;
+}
+
+int git__strntol32(int32_t *result, const char *nptr, size_t nptr_len, const char **endptr, int base)
+{
+ const char *tmp_endptr;
+ int32_t tmp_int;
+ int64_t tmp_long;
+ int error;
+
+ if ((error = git__strntol64(&tmp_long, nptr, nptr_len, &tmp_endptr, base)) < 0)
+ return error;
+
+ tmp_int = tmp_long & 0xFFFFFFFF;
+ if (tmp_int != tmp_long) {
+ int len = (int)(tmp_endptr - nptr);
+ git_error_set(GIT_ERROR_INVALID, "failed to convert: '%.*s' is too large", len, nptr);
+ return -1;
+ }
+
+ *result = tmp_int;
+ if (endptr)
+ *endptr = tmp_endptr;
+
+ return error;
+}
+
+int git__strcasecmp(const char *a, const char *b)
+{
+ while (*a && *b && git__tolower(*a) == git__tolower(*b))
+ ++a, ++b;
+ return ((unsigned char)git__tolower(*a) - (unsigned char)git__tolower(*b));
+}
+
+int git__strcasesort_cmp(const char *a, const char *b)
+{
+ int cmp = 0;
+
+ while (*a && *b) {
+ if (*a != *b) {
+ if (git__tolower(*a) != git__tolower(*b))
+ break;
+ /* use case in sort order even if not in equivalence */
+ if (!cmp)
+ cmp = (int)(*(const uint8_t *)a) - (int)(*(const uint8_t *)b);
+ }
+
+ ++a, ++b;
+ }
+
+ if (*a || *b)
+ return (unsigned char)git__tolower(*a) - (unsigned char)git__tolower(*b);
+
+ return cmp;
+}
+
+int git__strncasecmp(const char *a, const char *b, size_t sz)
+{
+ int al, bl;
+
+ do {
+ al = (unsigned char)git__tolower(*a);
+ bl = (unsigned char)git__tolower(*b);
+ ++a, ++b;
+ } while (--sz && al && al == bl);
+
+ return al - bl;
+}
+
+void git__strntolower(char *str, size_t len)
+{
+ size_t i;
+
+ for (i = 0; i < len; ++i) {
+ str[i] = (char)git__tolower(str[i]);
+ }
+}
+
+void git__strtolower(char *str)
+{
+ git__strntolower(str, strlen(str));
+}
+
+GIT_INLINE(int) prefixcmp(const char *str, size_t str_n, const char *prefix, bool icase)
+{
+ int s, p;
+
+ while (str_n--) {
+ s = (unsigned char)*str++;
+ p = (unsigned char)*prefix++;
+
+ if (icase) {
+ s = git__tolower(s);
+ p = git__tolower(p);
+ }
+
+ if (!p)
+ return 0;
+
+ if (s != p)
+ return s - p;
+ }
+
+ return (0 - *prefix);
+}
+
+int git__prefixcmp(const char *str, const char *prefix)
+{
+ unsigned char s, p;
+
+ while (1) {
+ p = *prefix++;
+ s = *str++;
+
+ if (!p)
+ return 0;
+
+ if (s != p)
+ return s - p;
+ }
+}
+
+int git__prefixncmp(const char *str, size_t str_n, const char *prefix)
+{
+ return prefixcmp(str, str_n, prefix, false);
+}
+
+int git__prefixcmp_icase(const char *str, const char *prefix)
+{
+ return prefixcmp(str, SIZE_MAX, prefix, true);
+}
+
+int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix)
+{
+ return prefixcmp(str, str_n, prefix, true);
+}
+
+int git__suffixcmp(const char *str, const char *suffix)
+{
+ size_t a = strlen(str);
+ size_t b = strlen(suffix);
+ if (a < b)
+ return -1;
+ return strcmp(str + (a - b), suffix);
+}
+
+char *git__strtok(char **end, const char *sep)
+{
+ char *ptr = *end;
+
+ while (*ptr && strchr(sep, *ptr))
+ ++ptr;
+
+ if (*ptr) {
+ char *start = ptr;
+ *end = start + 1;
+
+ while (**end && !strchr(sep, **end))
+ ++*end;
+
+ if (**end) {
+ **end = '\0';
+ ++*end;
+ }
+
+ return start;
+ }
+
+ return NULL;
+}
+
+/* Similar to strtok, but does not collapse repeated tokens. */
+char *git__strsep(char **end, const char *sep)
+{
+ char *start = *end, *ptr = *end;
+
+ while (*ptr && !strchr(sep, *ptr))
+ ++ptr;
+
+ if (*ptr) {
+ *end = ptr + 1;
+ *ptr = '\0';
+
+ return start;
+ }
+
+ return NULL;
+}
+
+size_t git__linenlen(const char *buffer, size_t buffer_len)
+{
+ char *nl = memchr(buffer, '\n', buffer_len);
+ return nl ? (size_t)(nl - buffer) + 1 : buffer_len;
+}
+
+/*
+ * Adapted Not So Naive algorithm from http://www-igm.univ-mlv.fr/~lecroq/string/
+ */
+const void * git__memmem(const void *haystack, size_t haystacklen,
+ const void *needle, size_t needlelen)
+{
+ const char *h, *n;
+ size_t j, k, l;
+
+ if (needlelen > haystacklen || !haystacklen || !needlelen)
+ return NULL;
+
+ h = (const char *) haystack,
+ n = (const char *) needle;
+
+ if (needlelen == 1)
+ return memchr(haystack, *n, haystacklen);
+
+ if (n[0] == n[1]) {
+ k = 2;
+ l = 1;
+ } else {
+ k = 1;
+ l = 2;
+ }
+
+ j = 0;
+ while (j <= haystacklen - needlelen) {
+ if (n[1] != h[j + 1]) {
+ j += k;
+ } else {
+ if (memcmp(n + 2, h + j + 2, needlelen - 2) == 0 &&
+ n[0] == h[j])
+ return h + j;
+ j += l;
+ }
+ }
+
+ return NULL;
+}
+
+void git__hexdump(const char *buffer, size_t len)
+{
+ static const size_t LINE_WIDTH = 16;
+
+ size_t line_count, last_line, i, j;
+ const char *line;
+
+ line_count = (len / LINE_WIDTH);
+ last_line = (len % LINE_WIDTH);
+
+ for (i = 0; i < line_count; ++i) {
+ printf("%08" PRIxZ " ", (i * LINE_WIDTH));
+
+ line = buffer + (i * LINE_WIDTH);
+ for (j = 0; j < LINE_WIDTH; ++j, ++line) {
+ printf("%02x ", (unsigned char)*line & 0xFF);
+
+ if (j == (LINE_WIDTH / 2))
+ printf(" ");
+ }
+
+ printf(" |");
+
+ line = buffer + (i * LINE_WIDTH);
+ for (j = 0; j < LINE_WIDTH; ++j, ++line)
+ printf("%c", (*line >= 32 && *line <= 126) ? *line : '.');
+
+ printf("|\n");
+ }
+
+ if (last_line > 0) {
+ printf("%08" PRIxZ " ", (line_count * LINE_WIDTH));
+
+ line = buffer + (line_count * LINE_WIDTH);
+ for (j = 0; j < last_line; ++j, ++line) {
+ printf("%02x ", (unsigned char)*line & 0xFF);
+
+ if (j == (LINE_WIDTH / 2))
+ printf(" ");
+ }
+
+ if (j < (LINE_WIDTH / 2))
+ printf(" ");
+ for (j = 0; j < (LINE_WIDTH - last_line); ++j)
+ printf(" ");
+
+ printf(" |");
+
+ line = buffer + (line_count * LINE_WIDTH);
+ for (j = 0; j < last_line; ++j, ++line)
+ printf("%c", (*line >= 32 && *line <= 126) ? *line : '.');
+
+ printf("|\n");
+ }
+
+ printf("\n");
+}
+
+#ifdef GIT_LEGACY_HASH
+uint32_t git__hash(const void *key, int len, unsigned int seed)
+{
+ const uint32_t m = 0x5bd1e995;
+ const int r = 24;
+ uint32_t h = seed ^ len;
+
+ const unsigned char *data = (const unsigned char *)key;
+
+ while(len >= 4) {
+ uint32_t k = *(uint32_t *)data;
+
+ k *= m;
+ k ^= k >> r;
+ k *= m;
+
+ h *= m;
+ h ^= k;
+
+ data += 4;
+ len -= 4;
+ }
+
+ switch(len) {
+ case 3: h ^= data[2] << 16;
+ case 2: h ^= data[1] << 8;
+ case 1: h ^= data[0];
+ h *= m;
+ };
+
+ h ^= h >> 13;
+ h *= m;
+ h ^= h >> 15;
+
+ return h;
+}
+#else
+/*
+ Cross-platform version of Murmurhash3
+ http://code.google.com/p/smhasher/wiki/MurmurHash3
+ by Austin Appleby (aappleby@gmail.com)
+
+ This code is on the public domain.
+*/
+uint32_t git__hash(const void *key, int len, uint32_t seed)
+{
+
+#define MURMUR_BLOCK() {\
+ k1 *= c1; \
+ k1 = git__rotl(k1,11);\
+ k1 *= c2;\
+ h1 ^= k1;\
+ h1 = h1*3 + 0x52dce729;\
+ c1 = c1*5 + 0x7b7d159c;\
+ c2 = c2*5 + 0x6bce6396;\
+}
+
+ const uint8_t *data = (const uint8_t*)key;
+ const int nblocks = len / 4;
+
+ const uint32_t *blocks = (const uint32_t *)(data + nblocks * 4);
+ const uint8_t *tail = (const uint8_t *)(data + nblocks * 4);
+
+ uint32_t h1 = 0x971e137b ^ seed;
+ uint32_t k1;
+
+ uint32_t c1 = 0x95543787;
+ uint32_t c2 = 0x2ad7eb25;
+
+ int i;
+
+ for (i = -nblocks; i; i++) {
+ k1 = blocks[i];
+ MURMUR_BLOCK();
+ }
+
+ k1 = 0;
+
+ switch(len & 3) {
+ case 3: k1 ^= tail[2] << 16;
+ /* fall through */
+ case 2: k1 ^= tail[1] << 8;
+ /* fall through */
+ case 1: k1 ^= tail[0];
+ MURMUR_BLOCK();
+ }
+
+ h1 ^= len;
+ h1 ^= h1 >> 16;
+ h1 *= 0x85ebca6b;
+ h1 ^= h1 >> 13;
+ h1 *= 0xc2b2ae35;
+ h1 ^= h1 >> 16;
+
+ return h1;
+}
+#endif
+
+/**
+ * A modified `bsearch` from the BSD glibc.
+ *
+ * Copyright (c) 1990 Regents of the University of California.
+ * All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. [rescinded 22 July 1999]
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+int git__bsearch(
+ void **array,
+ size_t array_len,
+ const void *key,
+ int (*compare)(const void *, const void *),
+ size_t *position)
+{
+ size_t lim;
+ int cmp = -1;
+ void **part, **base = array;
+
+ for (lim = array_len; lim != 0; lim >>= 1) {
+ part = base + (lim >> 1);
+ cmp = (*compare)(key, *part);
+ if (cmp == 0) {
+ base = part;
+ break;
+ }
+ if (cmp > 0) { /* key > p; take right partition */
+ base = part + 1;
+ lim--;
+ } /* else take left partition */
+ }
+
+ if (position)
+ *position = (base - array);
+
+ return (cmp == 0) ? 0 : GIT_ENOTFOUND;
+}
+
+int git__bsearch_r(
+ void **array,
+ size_t array_len,
+ const void *key,
+ int (*compare_r)(const void *, const void *, void *),
+ void *payload,
+ size_t *position)
+{
+ size_t lim;
+ int cmp = -1;
+ void **part, **base = array;
+
+ for (lim = array_len; lim != 0; lim >>= 1) {
+ part = base + (lim >> 1);
+ cmp = (*compare_r)(key, *part, payload);
+ if (cmp == 0) {
+ base = part;
+ break;
+ }
+ if (cmp > 0) { /* key > p; take right partition */
+ base = part + 1;
+ lim--;
+ } /* else take left partition */
+ }
+
+ if (position)
+ *position = (base - array);
+
+ return (cmp == 0) ? 0 : GIT_ENOTFOUND;
+}
+
+/**
+ * A strcmp wrapper
+ *
+ * We don't want direct pointers to the CRT on Windows, we may
+ * get stdcall conflicts.
+ */
+int git__strcmp_cb(const void *a, const void *b)
+{
+ return 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);
+}
+
+int git__parse_bool(int *out, const char *value)
+{
+ /* A missing value means true */
+ if (value == NULL ||
+ !strcasecmp(value, "true") ||
+ !strcasecmp(value, "yes") ||
+ !strcasecmp(value, "on")) {
+ *out = 1;
+ return 0;
+ }
+ if (!strcasecmp(value, "false") ||
+ !strcasecmp(value, "no") ||
+ !strcasecmp(value, "off") ||
+ value[0] == '\0') {
+ *out = 0;
+ return 0;
+ }
+
+ return -1;
+}
+
+size_t git__unescape(char *str)
+{
+ char *scan, *pos = str;
+
+ if (!str)
+ return 0;
+
+ for (scan = str; *scan; pos++, scan++) {
+ if (*scan == '\\' && *(scan + 1) != '\0')
+ scan++; /* skip '\' but include next char */
+ if (pos != scan)
+ *pos = *scan;
+ }
+
+ if (pos != scan) {
+ *pos = '\0';
+ }
+
+ return (pos - str);
+}
+
+#if defined(GIT_QSORT_MSC) || defined(GIT_QSORT_BSD)
+typedef struct {
+ git__sort_r_cmp cmp;
+ void *payload;
+} git__qsort_r_glue;
+
+static int GIT_LIBGIT2_CALL git__qsort_r_glue_cmp(
+ void *payload, const void *a, const void *b)
+{
+ git__qsort_r_glue *glue = payload;
+ return glue->cmp(a, b, glue->payload);
+}
+#endif
+
+
+#if !defined(GIT_QSORT_BSD) && \
+ !defined(GIT_QSORT_GNU) && \
+ !defined(GIT_QSORT_C11) && \
+ !defined(GIT_QSORT_MSC)
+
+static void swap(uint8_t *a, uint8_t *b, size_t elsize)
+{
+ char tmp[256];
+
+ while (elsize) {
+ size_t n = elsize < sizeof(tmp) ? elsize : sizeof(tmp);
+ memcpy(tmp, a + elsize - n, n);
+ memcpy(a + elsize - n, b + elsize - n, n);
+ memcpy(b + elsize - n, tmp, n);
+ elsize -= n;
+ }
+}
+
+static void insertsort(
+ void *els, size_t nel, size_t elsize,
+ git__sort_r_cmp cmp, void *payload)
+{
+ uint8_t *base = els;
+ uint8_t *end = base + nel * elsize;
+ uint8_t *i, *j;
+
+ for (i = base + elsize; i < end; i += elsize)
+ for (j = i; j > base && cmp(j, j - elsize, payload) < 0; j -= elsize)
+ swap(j, j - elsize, elsize);
+}
+
+#endif
+
+void git__qsort_r(
+ void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload)
+{
+#if defined(GIT_QSORT_GNU)
+ qsort_r(els, nel, elsize, cmp, payload);
+#elif defined(GIT_QSORT_C11)
+ qsort_s(els, nel, elsize, cmp, payload);
+#elif defined(GIT_QSORT_BSD)
+ git__qsort_r_glue glue = { cmp, payload };
+ qsort_r(els, nel, elsize, &glue, git__qsort_r_glue_cmp);
+#elif defined(GIT_QSORT_MSC)
+ git__qsort_r_glue glue = { cmp, payload };
+ qsort_s(els, nel, elsize, git__qsort_r_glue_cmp, &glue);
+#else
+ insertsort(els, nel, elsize, cmp, payload);
+#endif
+}
+
+#ifdef GIT_WIN32
+int git__getenv(git_str *out, const char *name)
+{
+ wchar_t *wide_name = NULL, *wide_value = NULL;
+ DWORD value_len;
+ int error = -1;
+
+ git_str_clear(out);
+
+ if (git_utf8_to_16_alloc(&wide_name, name) < 0)
+ return -1;
+
+ if ((value_len = GetEnvironmentVariableW(wide_name, NULL, 0)) > 0) {
+ wide_value = git__malloc(value_len * sizeof(wchar_t));
+ GIT_ERROR_CHECK_ALLOC(wide_value);
+
+ value_len = GetEnvironmentVariableW(wide_name, wide_value, value_len);
+ }
+
+ if (value_len)
+ error = git_str_put_w(out, wide_value, value_len);
+ else if (GetLastError() == ERROR_SUCCESS || GetLastError() == ERROR_ENVVAR_NOT_FOUND)
+ error = GIT_ENOTFOUND;
+ else
+ git_error_set(GIT_ERROR_OS, "could not read environment variable '%s'", name);
+
+ git__free(wide_name);
+ git__free(wide_value);
+ return error;
+}
+#else
+int git__getenv(git_str *out, const char *name)
+{
+ const char *val = getenv(name);
+
+ git_str_clear(out);
+
+ if (!val)
+ return GIT_ENOTFOUND;
+
+ return git_str_puts(out, val);
+}
+#endif
+
+/*
+ * By doing this in two steps we can at least get
+ * the function to be somewhat coherent, even
+ * with this disgusting nest of #ifdefs.
+ */
+#ifndef _SC_NPROCESSORS_ONLN
+# ifdef _SC_NPROC_ONLN
+# define _SC_NPROCESSORS_ONLN _SC_NPROC_ONLN
+# elif defined _SC_CRAY_NCPU
+# define _SC_NPROCESSORS_ONLN _SC_CRAY_NCPU
+# endif
+#endif
+
+int git__online_cpus(void)
+{
+#ifdef _SC_NPROCESSORS_ONLN
+ long ncpus;
+#endif
+
+#ifdef _WIN32
+ SYSTEM_INFO info;
+ GetSystemInfo(&info);
+
+ if ((int)info.dwNumberOfProcessors > 0)
+ return (int)info.dwNumberOfProcessors;
+#elif defined(hpux) || defined(__hpux) || defined(_hpux)
+ struct pst_dynamic psd;
+
+ if (!pstat_getdynamic(&psd, sizeof(psd), (size_t)1, 0))
+ return (int)psd.psd_proc_cnt;
+#endif
+
+#ifdef _SC_NPROCESSORS_ONLN
+ if ((ncpus = (long)sysconf(_SC_NPROCESSORS_ONLN)) > 0)
+ return (int)ncpus;
+#endif
+
+ return 1;
+}
diff --git a/src/util/util.h b/src/util/util.h
new file mode 100644
index 0000000..7f178b1
--- /dev/null
+++ b/src/util/util.h
@@ -0,0 +1,396 @@
+/*
+ * 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_util_h__
+#define INCLUDE_util_h__
+
+#ifndef GIT_WIN32
+# include <ctype.h>
+#endif
+
+#include "str.h"
+#include "git2_util.h"
+#include "strnlen.h"
+#include "thread.h"
+
+#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
+#define bitsizeof(x) (CHAR_BIT * sizeof(x))
+#define MSB(x, bits) ((x) & (~UINT64_C(0) << (bitsizeof(x) - (bits))))
+#ifndef min
+# define min(a,b) ((a) < (b) ? (a) : (b))
+#endif
+#ifndef max
+# define max(a,b) ((a) > (b) ? (a) : (b))
+#endif
+
+#if defined(__GNUC__)
+# define GIT_CONTAINER_OF(ptr, type, member) \
+ __builtin_choose_expr( \
+ __builtin_offsetof(type, member) == 0 && \
+ __builtin_types_compatible_p(__typeof__(&((type *) 0)->member), __typeof__(ptr)), \
+ ((type *) (ptr)), \
+ (void)0)
+#else
+# define GIT_CONTAINER_OF(ptr, type, member) (type *)(ptr)
+#endif
+
+/**
+ * Return the length of a constant string.
+ * We are aware that `strlen` performs the same task and is usually
+ * optimized away by the compiler, whilst being safer because it returns
+ * valid values when passed a pointer instead of a constant string; however
+ * this macro will transparently work with wide-char and single-char strings.
+ */
+#define CONST_STRLEN(x) ((sizeof(x)/sizeof(x[0])) - 1)
+
+#define STRCMP_CASESELECT(IGNORE_CASE, STR1, STR2) \
+ ((IGNORE_CASE) ? strcasecmp((STR1), (STR2)) : strcmp((STR1), (STR2)))
+
+#define CASESELECT(IGNORE_CASE, ICASE, CASE) \
+ ((IGNORE_CASE) ? (ICASE) : (CASE))
+
+extern int git__prefixcmp(const char *str, const char *prefix);
+extern int git__prefixcmp_icase(const char *str, const char *prefix);
+extern int git__prefixncmp(const char *str, size_t str_n, const char *prefix);
+extern int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix);
+extern int git__suffixcmp(const char *str, const char *suffix);
+
+GIT_INLINE(int) git__signum(int val)
+{
+ return ((val > 0) - (val < 0));
+}
+
+extern int git__strntol32(int32_t *n, const char *buff, size_t buff_len, const char **end_buf, int base);
+extern int git__strntol64(int64_t *n, const char *buff, size_t buff_len, const char **end_buf, int base);
+
+
+extern void git__hexdump(const char *buffer, size_t n);
+extern uint32_t git__hash(const void *key, int len, uint32_t seed);
+
+/* 32-bit cross-platform rotl */
+#ifdef _MSC_VER /* use built-in method in MSVC */
+# define git__rotl(v, s) (uint32_t)_rotl(v, s)
+#else /* use bitops in GCC; with o2 this gets optimized to a rotl instruction */
+# define git__rotl(v, s) (uint32_t)(((uint32_t)(v) << (s)) | ((uint32_t)(v) >> (32 - (s))))
+#endif
+
+extern char *git__strtok(char **end, const char *sep);
+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)
+{
+ while (*s && *s != '\n') s++;
+ while (*s == '\n' || *s == '\r') s++;
+ return s;
+}
+
+GIT_INLINE(const void *) git__memrchr(const void *s, int c, size_t n)
+{
+ const unsigned char *cp;
+
+ if (n != 0) {
+ cp = (unsigned char *)s + n;
+ do {
+ if (*(--cp) == (unsigned char)c)
+ return cp;
+ } while (--n != 0);
+ }
+
+ return NULL;
+}
+
+extern const void * git__memmem(const void *haystack, size_t haystacklen,
+ const void *needle, size_t needlelen);
+
+typedef int (*git__tsort_cmp)(const void *a, const void *b);
+
+extern void git__tsort(void **dst, size_t size, git__tsort_cmp cmp);
+
+typedef int (*git__sort_r_cmp)(const void *a, const void *b, void *payload);
+
+extern void git__tsort_r(
+ void **dst, size_t size, git__sort_r_cmp cmp, void *payload);
+
+extern void git__qsort_r(
+ void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload);
+
+/**
+ * @param position If non-NULL, this will be set to the position where the
+ * element is or would be inserted if not found.
+ * @return 0 if found; GIT_ENOTFOUND if not found
+ */
+extern int git__bsearch(
+ void **array,
+ size_t array_len,
+ const void *key,
+ int (*compare)(const void *key, const void *element),
+ size_t *position);
+
+extern int git__bsearch_r(
+ void **array,
+ size_t array_len,
+ const void *key,
+ int (*compare_r)(const void *key, const void *element, void *payload),
+ void *payload,
+ size_t *position);
+
+#define git__strcmp strcmp
+#define git__strncmp strncmp
+
+extern int git__strcmp_cb(const void *a, const void *b);
+extern int git__strcasecmp_cb(const void *a, const void *b);
+
+extern int git__strcasecmp(const char *a, const char *b);
+extern int git__strncasecmp(const char *a, const char *b, size_t sz);
+
+extern int git__strcasesort_cmp(const char *a, const char *b);
+
+/*
+ * Compare some NUL-terminated `a` to a possibly non-NUL terminated
+ * `b` of length `b_len`; like `strncmp` but ensuring that
+ * `strlen(a) == b_len` as well.
+ */
+GIT_INLINE(int) git__strlcmp(const char *a, const char *b, size_t b_len)
+{
+ int cmp = strncmp(a, b, b_len);
+ return cmp ? cmp : (int)a[b_len];
+}
+
+typedef struct {
+ git_atomic32 refcount;
+ void *owner;
+} git_refcount;
+
+typedef void (*git_refcount_freeptr)(void *r);
+
+#define GIT_REFCOUNT_INC(r) { \
+ git_atomic32_inc(&(r)->rc.refcount); \
+}
+
+#define GIT_REFCOUNT_DEC(_r, do_free) { \
+ git_refcount *r = &(_r)->rc; \
+ int val = git_atomic32_dec(&r->refcount); \
+ if (val <= 0 && r->owner == NULL) { do_free(_r); } \
+}
+
+#define GIT_REFCOUNT_OWN(r, o) { \
+ (void)git_atomic_swap((r)->rc.owner, o); \
+}
+
+#define GIT_REFCOUNT_OWNER(r) git_atomic_load((r)->rc.owner)
+
+#define GIT_REFCOUNT_VAL(r) git_atomic32_get((r)->rc.refcount)
+
+
+static signed char from_hex[] = {
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 00 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 10 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 20 */
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, /* 30 */
+-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 40 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 50 */
+-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 60 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 70 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 80 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 90 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* a0 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* b0 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* c0 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* d0 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* e0 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* f0 */
+};
+
+GIT_INLINE(int) git__fromhex(char h)
+{
+ return from_hex[(unsigned char) h];
+}
+
+GIT_INLINE(int) git__ishex(const char *str)
+{
+ unsigned i;
+ for (i=0; str[i] != '\0'; i++)
+ if (git__fromhex(str[i]) < 0)
+ return 0;
+ return 1;
+}
+
+GIT_INLINE(size_t) git__size_t_bitmask(size_t v)
+{
+ v--;
+ v |= v >> 1;
+ v |= v >> 2;
+ v |= v >> 4;
+ v |= v >> 8;
+ v |= v >> 16;
+
+ return v;
+}
+
+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');
+}
+
+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.
+ *
+ * Valid values for true are: 'true', 'yes', 'on'
+ * Valid values for false are: 'false', 'no', 'off'
+ */
+extern int git__parse_bool(int *out, const char *value);
+
+/*
+ * Unescapes a string in-place.
+ *
+ * Edge cases behavior:
+ * - "jackie\" -> "jacky\"
+ * - "chan\\" -> "chan\"
+ */
+extern size_t git__unescape(char *str);
+
+/*
+ * Safely zero-out memory, making sure that the compiler
+ * doesn't optimize away the operation.
+ */
+GIT_INLINE(void) git__memzero(void *data, size_t size)
+{
+#ifdef _MSC_VER
+ SecureZeroMemory((PVOID)data, size);
+#else
+ volatile uint8_t *scan = (volatile uint8_t *)data;
+
+ while (size--)
+ *scan++ = 0x0;
+#endif
+}
+
+#ifdef GIT_WIN32
+
+GIT_INLINE(uint64_t) git_time_monotonic(void)
+{
+ /* GetTickCount64 returns the number of milliseconds that have
+ * elapsed since the system was started. */
+ return GetTickCount64();
+}
+
+#elif __APPLE__
+
+#include <mach/mach_time.h>
+#include <sys/time.h>
+
+GIT_INLINE(uint64_t) git_time_monotonic(void)
+{
+ static double scaling_factor = 0;
+
+ if (scaling_factor == 0) {
+ mach_timebase_info_data_t info;
+
+ scaling_factor = mach_timebase_info(&info) == KERN_SUCCESS ?
+ ((double)info.numer / (double)info.denom) / 1.0E6 :
+ -1;
+ } else if (scaling_factor < 0) {
+ struct timeval tv;
+
+ /* mach_timebase_info failed; fall back to gettimeofday */
+ gettimeofday(&tv, NULL);
+ return (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
+ }
+
+ return (uint64_t)(mach_absolute_time() * scaling_factor);
+}
+
+#elif defined(__amigaos4__)
+
+#include <proto/timer.h>
+
+GIT_INLINE(uint64_t) git_time_monotonic(void)
+{
+ struct TimeVal tv;
+ ITimer->GetUpTime(&tv);
+ return (tv.Seconds * 1000) + (tv.Microseconds / 1000);
+}
+
+#else
+
+#include <sys/time.h>
+
+GIT_INLINE(uint64_t) git_time_monotonic(void)
+{
+ struct timeval tv;
+
+#ifdef CLOCK_MONOTONIC
+ struct timespec tp;
+ if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0)
+ return (tp.tv_sec * 1000) + (tp.tv_nsec / 1.0E6);
+#endif
+
+ /* Fall back to using gettimeofday */
+ gettimeofday(&tv, NULL);
+ return (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
+}
+
+#endif
+
+extern int git__getenv(git_str *out, const char *name);
+
+extern int git__online_cpus(void);
+
+GIT_INLINE(int) git__noop(void) { return 0; }
+GIT_INLINE(int) git__noop_args(void *a, ...) { GIT_UNUSED(a); return 0; }
+
+#include "alloc.h"
+
+#endif
diff --git a/src/util/varint.c b/src/util/varint.c
new file mode 100644
index 0000000..9ffc1d7
--- /dev/null
+++ b/src/util/varint.c
@@ -0,0 +1,43 @@
+/*
+ * 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 "varint.h"
+
+uintmax_t git_decode_varint(const unsigned char *bufp, size_t *varint_len)
+{
+ const unsigned char *buf = bufp;
+ unsigned char c = *buf++;
+ uintmax_t val = c & 127;
+ while (c & 128) {
+ val += 1;
+ if (!val || MSB(val, 7)) {
+ /* This is not a valid varint_len, so it signals
+ the error */
+ *varint_len = 0;
+ return 0; /* overflow */
+ }
+ c = *buf++;
+ val = (val << 7) + (c & 127);
+ }
+ *varint_len = buf - bufp;
+ return val;
+}
+
+int git_encode_varint(unsigned char *buf, size_t bufsize, uintmax_t value)
+{
+ unsigned char varint[16];
+ unsigned pos = sizeof(varint) - 1;
+ varint[pos] = value & 127;
+ while (value >>= 7)
+ varint[--pos] = 128 | (--value & 127);
+ if (buf) {
+ if (bufsize < (sizeof(varint) - pos))
+ return -1;
+ memcpy(buf, varint + pos, sizeof(varint) - pos);
+ }
+ return sizeof(varint) - pos;
+}
diff --git a/src/util/varint.h b/src/util/varint.h
new file mode 100644
index 0000000..79b8f55
--- /dev/null
+++ b/src/util/varint.h
@@ -0,0 +1,17 @@
+/*
+ * 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_varint_h__
+#define INCLUDE_varint_h__
+
+#include "git2_util.h"
+
+#include <stdint.h>
+
+extern int git_encode_varint(unsigned char *, size_t, uintmax_t);
+extern uintmax_t git_decode_varint(const unsigned char *, size_t *);
+
+#endif
diff --git a/src/util/vector.c b/src/util/vector.c
new file mode 100644
index 0000000..4a4bc8c
--- /dev/null
+++ b/src/util/vector.c
@@ -0,0 +1,431 @@
+/*
+ * 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 "vector.h"
+
+#include "integer.h"
+
+/* In elements, not bytes */
+#define MIN_ALLOCSIZE 8
+
+GIT_INLINE(size_t) compute_new_size(git_vector *v)
+{
+ size_t new_size = v->_alloc_size;
+
+ /* Use a resize factor of 1.5, which is quick to compute using integer
+ * instructions and less than the golden ratio (1.618...) */
+ if (new_size < MIN_ALLOCSIZE)
+ new_size = MIN_ALLOCSIZE;
+ else if (new_size <= (SIZE_MAX / 3) * 2)
+ new_size += new_size / 2;
+ else
+ new_size = SIZE_MAX;
+
+ return new_size;
+}
+
+GIT_INLINE(int) resize_vector(git_vector *v, size_t new_size)
+{
+ void *new_contents;
+
+ if (new_size == 0)
+ return 0;
+
+ new_contents = git__reallocarray(v->contents, new_size, sizeof(void *));
+ GIT_ERROR_CHECK_ALLOC(new_contents);
+
+ v->_alloc_size = new_size;
+ v->contents = new_contents;
+
+ return 0;
+}
+
+int git_vector_size_hint(git_vector *v, size_t size_hint)
+{
+ if (v->_alloc_size >= size_hint)
+ return 0;
+ return resize_vector(v, size_hint);
+}
+
+int git_vector_dup(git_vector *v, const git_vector *src, git_vector_cmp cmp)
+{
+ GIT_ASSERT_ARG(v);
+ GIT_ASSERT_ARG(src);
+
+ v->_alloc_size = 0;
+ v->contents = NULL;
+ v->_cmp = cmp ? cmp : src->_cmp;
+ v->length = src->length;
+ v->flags = src->flags;
+ if (cmp != src->_cmp)
+ git_vector_set_sorted(v, 0);
+
+ if (src->length) {
+ size_t bytes;
+ GIT_ERROR_CHECK_ALLOC_MULTIPLY(&bytes, src->length, sizeof(void *));
+ v->contents = git__malloc(bytes);
+ GIT_ERROR_CHECK_ALLOC(v->contents);
+ v->_alloc_size = src->length;
+ memcpy(v->contents, src->contents, bytes);
+ }
+
+ return 0;
+}
+
+void git_vector_free(git_vector *v)
+{
+ if (!v)
+ return;
+
+ git__free(v->contents);
+ v->contents = NULL;
+
+ v->length = 0;
+ v->_alloc_size = 0;
+}
+
+void git_vector_free_deep(git_vector *v)
+{
+ size_t i;
+
+ if (!v)
+ return;
+
+ for (i = 0; i < v->length; ++i) {
+ git__free(v->contents[i]);
+ v->contents[i] = NULL;
+ }
+
+ git_vector_free(v);
+}
+
+int git_vector_init(git_vector *v, size_t initial_size, git_vector_cmp cmp)
+{
+ GIT_ASSERT_ARG(v);
+
+ v->_alloc_size = 0;
+ v->_cmp = cmp;
+ v->length = 0;
+ v->flags = GIT_VECTOR_SORTED;
+ v->contents = NULL;
+
+ return resize_vector(v, max(initial_size, MIN_ALLOCSIZE));
+}
+
+void **git_vector_detach(size_t *size, size_t *asize, git_vector *v)
+{
+ void **data = v->contents;
+
+ if (size)
+ *size = v->length;
+ if (asize)
+ *asize = v->_alloc_size;
+
+ v->_alloc_size = 0;
+ v->length = 0;
+ v->contents = NULL;
+
+ return data;
+}
+
+int git_vector_insert(git_vector *v, void *element)
+{
+ GIT_ASSERT_ARG(v);
+
+ if (v->length >= v->_alloc_size &&
+ resize_vector(v, compute_new_size(v)) < 0)
+ return -1;
+
+ v->contents[v->length++] = element;
+
+ git_vector_set_sorted(v, v->length <= 1);
+
+ return 0;
+}
+
+int git_vector_insert_sorted(
+ git_vector *v, void *element, int (*on_dup)(void **old, void *new))
+{
+ int result;
+ size_t pos;
+
+ GIT_ASSERT_ARG(v);
+ GIT_ASSERT(v->_cmp);
+
+ if (!git_vector_is_sorted(v))
+ git_vector_sort(v);
+
+ if (v->length >= v->_alloc_size &&
+ resize_vector(v, compute_new_size(v)) < 0)
+ return -1;
+
+ /* If we find the element and have a duplicate handler callback,
+ * invoke it. If it returns non-zero, then cancel insert, otherwise
+ * proceed with normal insert.
+ */
+ if (!git__bsearch(v->contents, v->length, element, v->_cmp, &pos) &&
+ on_dup && (result = on_dup(&v->contents[pos], element)) < 0)
+ return result;
+
+ /* shift elements to the right */
+ if (pos < v->length)
+ memmove(v->contents + pos + 1, v->contents + pos,
+ (v->length - pos) * sizeof(void *));
+
+ v->contents[pos] = element;
+ v->length++;
+
+ return 0;
+}
+
+void git_vector_sort(git_vector *v)
+{
+ if (git_vector_is_sorted(v) || !v->_cmp)
+ return;
+
+ if (v->length > 1)
+ git__tsort(v->contents, v->length, v->_cmp);
+ git_vector_set_sorted(v, 1);
+}
+
+int git_vector_bsearch2(
+ size_t *at_pos,
+ git_vector *v,
+ git_vector_cmp key_lookup,
+ const void *key)
+{
+ GIT_ASSERT_ARG(v);
+ GIT_ASSERT_ARG(key);
+ GIT_ASSERT(key_lookup);
+
+ /* need comparison function to sort the vector */
+ if (!v->_cmp)
+ return -1;
+
+ git_vector_sort(v);
+
+ return git__bsearch(v->contents, v->length, key, key_lookup, at_pos);
+}
+
+int git_vector_search2(
+ size_t *at_pos, const git_vector *v, git_vector_cmp key_lookup, const void *key)
+{
+ size_t i;
+
+ GIT_ASSERT_ARG(v);
+ GIT_ASSERT_ARG(key);
+ GIT_ASSERT(key_lookup);
+
+ for (i = 0; i < v->length; ++i) {
+ if (key_lookup(key, v->contents[i]) == 0) {
+ if (at_pos)
+ *at_pos = i;
+
+ return 0;
+ }
+ }
+
+ return GIT_ENOTFOUND;
+}
+
+static int strict_comparison(const void *a, const void *b)
+{
+ return (a == b) ? 0 : -1;
+}
+
+int git_vector_search(size_t *at_pos, const git_vector *v, const void *entry)
+{
+ return git_vector_search2(at_pos, v, v->_cmp ? v->_cmp : strict_comparison, entry);
+}
+
+int git_vector_remove(git_vector *v, size_t idx)
+{
+ size_t shift_count;
+
+ GIT_ASSERT_ARG(v);
+
+ if (idx >= v->length)
+ return GIT_ENOTFOUND;
+
+ shift_count = v->length - idx - 1;
+
+ if (shift_count)
+ memmove(&v->contents[idx], &v->contents[idx + 1],
+ shift_count * sizeof(void *));
+
+ v->length--;
+ return 0;
+}
+
+void git_vector_pop(git_vector *v)
+{
+ if (v->length > 0)
+ v->length--;
+}
+
+void git_vector_uniq(git_vector *v, void (*git_free_cb)(void *))
+{
+ git_vector_cmp cmp;
+ size_t i, j;
+
+ if (v->length <= 1)
+ return;
+
+ git_vector_sort(v);
+ cmp = v->_cmp ? v->_cmp : strict_comparison;
+
+ for (i = 0, j = 1 ; j < v->length; ++j)
+ if (!cmp(v->contents[i], v->contents[j])) {
+ if (git_free_cb)
+ git_free_cb(v->contents[i]);
+
+ v->contents[i] = v->contents[j];
+ } else
+ v->contents[++i] = v->contents[j];
+
+ v->length -= j - i - 1;
+}
+
+void git_vector_remove_matching(
+ git_vector *v,
+ int (*match)(const git_vector *v, size_t idx, void *payload),
+ void *payload)
+{
+ size_t i, j;
+
+ for (i = 0, j = 0; j < v->length; ++j) {
+ v->contents[i] = v->contents[j];
+
+ if (!match(v, i, payload))
+ i++;
+ }
+
+ v->length = i;
+}
+
+void git_vector_clear(git_vector *v)
+{
+ v->length = 0;
+ git_vector_set_sorted(v, 1);
+}
+
+void git_vector_swap(git_vector *a, git_vector *b)
+{
+ git_vector t;
+
+ if (a != b) {
+ memcpy(&t, a, sizeof(t));
+ memcpy(a, b, sizeof(t));
+ memcpy(b, &t, sizeof(t));
+ }
+}
+
+int git_vector_resize_to(git_vector *v, size_t new_length)
+{
+ if (new_length > v->_alloc_size &&
+ resize_vector(v, new_length) < 0)
+ return -1;
+
+ if (new_length > v->length)
+ memset(&v->contents[v->length], 0,
+ sizeof(void *) * (new_length - v->length));
+
+ v->length = new_length;
+
+ return 0;
+}
+
+int git_vector_insert_null(git_vector *v, size_t idx, size_t insert_len)
+{
+ size_t new_length;
+
+ GIT_ASSERT_ARG(insert_len > 0);
+ GIT_ASSERT_ARG(idx <= v->length);
+
+ GIT_ERROR_CHECK_ALLOC_ADD(&new_length, v->length, insert_len);
+
+ if (new_length > v->_alloc_size && resize_vector(v, new_length) < 0)
+ return -1;
+
+ memmove(&v->contents[idx + insert_len], &v->contents[idx],
+ sizeof(void *) * (v->length - idx));
+ memset(&v->contents[idx], 0, sizeof(void *) * insert_len);
+
+ v->length = new_length;
+ return 0;
+}
+
+int git_vector_remove_range(git_vector *v, size_t idx, size_t remove_len)
+{
+ size_t new_length = v->length - remove_len;
+ size_t end_idx = 0;
+
+ GIT_ASSERT_ARG(remove_len > 0);
+
+ if (git__add_sizet_overflow(&end_idx, idx, remove_len))
+ GIT_ASSERT(0);
+
+ GIT_ASSERT(end_idx <= v->length);
+
+ if (end_idx < v->length)
+ memmove(&v->contents[idx], &v->contents[end_idx],
+ sizeof(void *) * (v->length - end_idx));
+
+ memset(&v->contents[new_length], 0, sizeof(void *) * remove_len);
+
+ v->length = new_length;
+ return 0;
+}
+
+int git_vector_set(void **old, git_vector *v, size_t position, void *value)
+{
+ if (position + 1 > v->length) {
+ if (git_vector_resize_to(v, position + 1) < 0)
+ return -1;
+ }
+
+ if (old != NULL)
+ *old = v->contents[position];
+
+ v->contents[position] = value;
+
+ return 0;
+}
+
+int git_vector_verify_sorted(const git_vector *v)
+{
+ size_t i;
+
+ if (!git_vector_is_sorted(v))
+ return -1;
+
+ for (i = 1; i < v->length; ++i) {
+ if (v->_cmp(v->contents[i - 1], v->contents[i]) > 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+void git_vector_reverse(git_vector *v)
+{
+ size_t a, b;
+
+ if (v->length == 0)
+ return;
+
+ a = 0;
+ b = v->length - 1;
+
+ while (a < b) {
+ void *tmp = v->contents[a];
+ v->contents[a] = v->contents[b];
+ v->contents[b] = tmp;
+ a++;
+ b--;
+ }
+}
diff --git a/src/util/vector.h b/src/util/vector.h
new file mode 100644
index 0000000..e50cdfe
--- /dev/null
+++ b/src/util/vector.h
@@ -0,0 +1,128 @@
+/*
+ * 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_vector_h__
+#define INCLUDE_vector_h__
+
+#include "git2_util.h"
+
+typedef int (*git_vector_cmp)(const void *, const void *);
+
+enum {
+ GIT_VECTOR_SORTED = (1u << 0),
+ GIT_VECTOR_FLAG_MAX = (1u << 1)
+};
+
+typedef struct git_vector {
+ size_t _alloc_size;
+ git_vector_cmp _cmp;
+ void **contents;
+ size_t length;
+ uint32_t flags;
+} git_vector;
+
+#define GIT_VECTOR_INIT {0}
+
+GIT_WARN_UNUSED_RESULT int git_vector_init(
+ git_vector *v, size_t initial_size, git_vector_cmp cmp);
+void git_vector_free(git_vector *v);
+void git_vector_free_deep(git_vector *v); /* free each entry and self */
+void git_vector_clear(git_vector *v);
+GIT_WARN_UNUSED_RESULT int git_vector_dup(
+ git_vector *v, const git_vector *src, git_vector_cmp cmp);
+void git_vector_swap(git_vector *a, git_vector *b);
+int git_vector_size_hint(git_vector *v, size_t size_hint);
+
+void **git_vector_detach(size_t *size, size_t *asize, git_vector *v);
+
+void git_vector_sort(git_vector *v);
+
+/** Linear search for matching entry using internal comparison function */
+int git_vector_search(size_t *at_pos, const git_vector *v, const void *entry);
+
+/** Linear search for matching entry using explicit comparison function */
+int git_vector_search2(size_t *at_pos, const git_vector *v, git_vector_cmp cmp, const void *key);
+
+/**
+ * Binary search for matching entry using explicit comparison function that
+ * returns position where item would go if not found.
+ */
+int git_vector_bsearch2(
+ size_t *at_pos, git_vector *v, git_vector_cmp cmp, const void *key);
+
+/** Binary search for matching entry using internal comparison function */
+GIT_INLINE(int) git_vector_bsearch(size_t *at_pos, git_vector *v, const void *key)
+{
+ return git_vector_bsearch2(at_pos, v, v->_cmp, key);
+}
+
+GIT_INLINE(void *) git_vector_get(const git_vector *v, size_t position)
+{
+ return (position < v->length) ? v->contents[position] : NULL;
+}
+
+#define GIT_VECTOR_GET(V,I) ((I) < (V)->length ? (V)->contents[(I)] : NULL)
+
+GIT_INLINE(size_t) git_vector_length(const git_vector *v)
+{
+ return v->length;
+}
+
+GIT_INLINE(void *) git_vector_last(const git_vector *v)
+{
+ return (v->length > 0) ? git_vector_get(v, v->length - 1) : NULL;
+}
+
+#define git_vector_foreach(v, iter, elem) \
+ for ((iter) = 0; (iter) < (v)->length && ((elem) = (v)->contents[(iter)], 1); (iter)++ )
+
+#define git_vector_rforeach(v, iter, elem) \
+ for ((iter) = (v)->length - 1; (iter) < SIZE_MAX && ((elem) = (v)->contents[(iter)], 1); (iter)-- )
+
+int git_vector_insert(git_vector *v, void *element);
+int git_vector_insert_sorted(git_vector *v, void *element,
+ int (*on_dup)(void **old, void *new));
+int git_vector_remove(git_vector *v, size_t idx);
+void git_vector_pop(git_vector *v);
+void git_vector_uniq(git_vector *v, void (*git_free_cb)(void *));
+
+void git_vector_remove_matching(
+ git_vector *v,
+ int (*match)(const git_vector *v, size_t idx, void *payload),
+ void *payload);
+
+int git_vector_resize_to(git_vector *v, size_t new_length);
+int git_vector_insert_null(git_vector *v, size_t idx, size_t insert_len);
+int git_vector_remove_range(git_vector *v, size_t idx, size_t remove_len);
+
+int git_vector_set(void **old, git_vector *v, size_t position, void *value);
+
+/** Check if vector is sorted */
+#define git_vector_is_sorted(V) (((V)->flags & GIT_VECTOR_SORTED) != 0)
+
+/** Directly set sorted state of vector */
+#define git_vector_set_sorted(V,S) do { \
+ (V)->flags = (S) ? ((V)->flags | GIT_VECTOR_SORTED) : \
+ ((V)->flags & ~GIT_VECTOR_SORTED); } while (0)
+
+/** Set the comparison function used for sorting the vector */
+GIT_INLINE(void) git_vector_set_cmp(git_vector *v, git_vector_cmp cmp)
+{
+ if (cmp != v->_cmp) {
+ v->_cmp = cmp;
+ git_vector_set_sorted(v, 0);
+ }
+}
+
+/* Just use this in tests, not for realz. returns -1 if not sorted */
+int git_vector_verify_sorted(const git_vector *v);
+
+/**
+ * Reverse the vector in-place.
+ */
+void git_vector_reverse(git_vector *v);
+
+#endif
diff --git a/src/util/wildmatch.c b/src/util/wildmatch.c
new file mode 100644
index 0000000..a894e48
--- /dev/null
+++ b/src/util/wildmatch.c
@@ -0,0 +1,320 @@
+/*
+ * 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.
+ *
+ * Do shell-style pattern matching for ?, \, [], and * characters.
+ * It is 8bit clean.
+ *
+ * Written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986.
+ * Rich $alz is now <rsalz@bbn.com>.
+ *
+ * Modified by Wayne Davison to special-case '/' matching, to make '**'
+ * work differently than '*', and to fix the character-class code.
+ *
+ * Imported from git.git.
+ */
+
+#include "wildmatch.h"
+
+#define GIT_SPACE 0x01
+#define GIT_DIGIT 0x02
+#define GIT_ALPHA 0x04
+#define GIT_GLOB_SPECIAL 0x08
+#define GIT_REGEX_SPECIAL 0x10
+#define GIT_PATHSPEC_MAGIC 0x20
+#define GIT_CNTRL 0x40
+#define GIT_PUNCT 0x80
+
+enum {
+ S = GIT_SPACE,
+ A = GIT_ALPHA,
+ D = GIT_DIGIT,
+ G = GIT_GLOB_SPECIAL, /* *, ?, [, \\ */
+ R = GIT_REGEX_SPECIAL, /* $, (, ), +, ., ^, {, | */
+ P = GIT_PATHSPEC_MAGIC, /* other non-alnum, except for ] and } */
+ X = GIT_CNTRL,
+ U = GIT_PUNCT,
+ Z = GIT_CNTRL | GIT_SPACE
+};
+
+static const unsigned char sane_ctype[256] = {
+ X, X, X, X, X, X, X, X, X, Z, Z, X, X, Z, X, X, /* 0.. 15 */
+ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, /* 16.. 31 */
+ S, P, P, P, R, P, P, P, R, R, G, R, P, P, R, P, /* 32.. 47 */
+ D, D, D, D, D, D, D, D, D, D, P, P, P, P, P, G, /* 48.. 63 */
+ P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 64.. 79 */
+ A, A, A, A, A, A, A, A, A, A, A, G, G, U, R, P, /* 80.. 95 */
+ P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 96..111 */
+ A, A, A, A, A, A, A, A, A, A, A, R, R, U, P, X, /* 112..127 */
+ /* Nothing in the 128.. range */
+};
+
+#define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0)
+#define is_glob_special(x) sane_istest(x,GIT_GLOB_SPECIAL)
+
+typedef unsigned char uchar;
+
+/* What character marks an inverted character class? */
+#define NEGATE_CLASS '!'
+#define NEGATE_CLASS2 '^'
+
+#define CC_EQ(class, len, litmatch) ((len) == sizeof (litmatch)-1 \
+ && *(class) == *(litmatch) \
+ && strncmp((char*)class, litmatch, len) == 0)
+
+#if defined STDC_HEADERS || !defined isascii
+# define ISASCII(c) 1
+#else
+# define ISASCII(c) isascii(c)
+#endif
+
+#ifdef isblank
+# define ISBLANK(c) (ISASCII(c) && isblank(c))
+#else
+# define ISBLANK(c) ((c) == ' ' || (c) == '\t')
+#endif
+
+#ifdef isgraph
+# define ISGRAPH(c) (ISASCII(c) && isgraph(c))
+#else
+# define ISGRAPH(c) (ISASCII(c) && isprint(c) && !isspace(c))
+#endif
+
+#define ISPRINT(c) (ISASCII(c) && isprint(c))
+#define ISDIGIT(c) (ISASCII(c) && isdigit(c))
+#define ISALNUM(c) (ISASCII(c) && isalnum(c))
+#define ISALPHA(c) (ISASCII(c) && isalpha(c))
+#define ISCNTRL(c) (ISASCII(c) && iscntrl(c))
+#define ISLOWER(c) (ISASCII(c) && islower(c))
+#define ISPUNCT(c) (ISASCII(c) && ispunct(c))
+#define ISSPACE(c) (ISASCII(c) && isspace(c))
+#define ISUPPER(c) (ISASCII(c) && isupper(c))
+#define ISXDIGIT(c) (ISASCII(c) && isxdigit(c))
+
+/* Match pattern "p" against "text" */
+static int dowild(const uchar *p, const uchar *text, unsigned int flags)
+{
+ uchar p_ch;
+ const uchar *pattern = p;
+
+ for ( ; (p_ch = *p) != '\0'; text++, p++) {
+ int matched, match_slash, negated;
+ uchar t_ch, prev_ch;
+ if ((t_ch = *text) == '\0' && p_ch != '*')
+ return WM_ABORT_ALL;
+ if ((flags & WM_CASEFOLD) && ISUPPER(t_ch))
+ t_ch = tolower(t_ch);
+ if ((flags & WM_CASEFOLD) && ISUPPER(p_ch))
+ p_ch = tolower(p_ch);
+ switch (p_ch) {
+ case '\\':
+ /* Literal match with following character. Note that the test
+ * in "default" handles the p[1] == '\0' failure case. */
+ p_ch = *++p;
+ /* FALLTHROUGH */
+ default:
+ if (t_ch != p_ch)
+ return WM_NOMATCH;
+ continue;
+ case '?':
+ /* Match anything but '/'. */
+ if ((flags & WM_PATHNAME) && t_ch == '/')
+ return WM_NOMATCH;
+ continue;
+ case '*':
+ if (*++p == '*') {
+ const uchar *prev_p = p - 2;
+ while (*++p == '*') {}
+ if (!(flags & WM_PATHNAME))
+ /* without WM_PATHNAME, '*' == '**' */
+ match_slash = 1;
+ else if ((prev_p < pattern || *prev_p == '/') &&
+ (*p == '\0' || *p == '/' ||
+ (p[0] == '\\' && p[1] == '/'))) {
+ /*
+ * Assuming we already match 'foo/' and are at
+ * <star star slash>, just assume it matches
+ * nothing and go ahead match the rest of the
+ * pattern with the remaining string. This
+ * helps make foo/<*><*>/bar (<> because
+ * otherwise it breaks C comment syntax) match
+ * both foo/bar and foo/a/bar.
+ */
+ if (p[0] == '/' &&
+ dowild(p + 1, text, flags) == WM_MATCH)
+ return WM_MATCH;
+ match_slash = 1;
+ } else /* WM_PATHNAME is set */
+ match_slash = 0;
+ } else
+ /* without WM_PATHNAME, '*' == '**' */
+ match_slash = flags & WM_PATHNAME ? 0 : 1;
+ if (*p == '\0') {
+ /* Trailing "**" matches everything. Trailing "*" matches
+ * only if there are no more slash characters. */
+ if (!match_slash) {
+ if (strchr((char*)text, '/') != NULL)
+ return WM_NOMATCH;
+ }
+ return WM_MATCH;
+ } else if (!match_slash && *p == '/') {
+ /*
+ * _one_ asterisk followed by a slash
+ * with WM_PATHNAME matches the next
+ * directory
+ */
+ const char *slash = strchr((char*)text, '/');
+ if (!slash)
+ return WM_NOMATCH;
+ text = (const uchar*)slash;
+ /* the slash is consumed by the top-level for loop */
+ break;
+ }
+ while (1) {
+ if (t_ch == '\0')
+ break;
+ /*
+ * Try to advance faster when an asterisk is
+ * followed by a literal. We know in this case
+ * that the string before the literal
+ * must belong to "*".
+ * If match_slash is false, do not look past
+ * the first slash as it cannot belong to '*'.
+ */
+ if (!is_glob_special(*p)) {
+ p_ch = *p;
+ if ((flags & WM_CASEFOLD) && ISUPPER(p_ch))
+ p_ch = tolower(p_ch);
+ while ((t_ch = *text) != '\0' &&
+ (match_slash || t_ch != '/')) {
+ if ((flags & WM_CASEFOLD) && ISUPPER(t_ch))
+ t_ch = tolower(t_ch);
+ if (t_ch == p_ch)
+ break;
+ text++;
+ }
+ if (t_ch != p_ch)
+ return WM_NOMATCH;
+ }
+ if ((matched = dowild(p, text, flags)) != WM_NOMATCH) {
+ if (!match_slash || matched != WM_ABORT_TO_STARSTAR)
+ return matched;
+ } else if (!match_slash && t_ch == '/')
+ return WM_ABORT_TO_STARSTAR;
+ t_ch = *++text;
+ }
+ return WM_ABORT_ALL;
+ case '[':
+ p_ch = *++p;
+#ifdef NEGATE_CLASS2
+ if (p_ch == NEGATE_CLASS2)
+ p_ch = NEGATE_CLASS;
+#endif
+ /* Assign literal 1/0 because of "matched" comparison. */
+ negated = p_ch == NEGATE_CLASS ? 1 : 0;
+ if (negated) {
+ /* Inverted character class. */
+ p_ch = *++p;
+ }
+ prev_ch = 0;
+ matched = 0;
+ do {
+ if (!p_ch)
+ return WM_ABORT_ALL;
+ if (p_ch == '\\') {
+ p_ch = *++p;
+ if (!p_ch)
+ return WM_ABORT_ALL;
+ if (t_ch == p_ch)
+ matched = 1;
+ } else if (p_ch == '-' && prev_ch && p[1] && p[1] != ']') {
+ p_ch = *++p;
+ if (p_ch == '\\') {
+ p_ch = *++p;
+ if (!p_ch)
+ return WM_ABORT_ALL;
+ }
+ if (t_ch <= p_ch && t_ch >= prev_ch)
+ matched = 1;
+ else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch)) {
+ uchar t_ch_upper = toupper(t_ch);
+ if (t_ch_upper <= p_ch && t_ch_upper >= prev_ch)
+ matched = 1;
+ }
+ p_ch = 0; /* This makes "prev_ch" get set to 0. */
+ } else if (p_ch == '[' && p[1] == ':') {
+ const uchar *s;
+ int i;
+ for (s = p += 2; (p_ch = *p) && p_ch != ']'; p++) {} /*SHARED ITERATOR*/
+ if (!p_ch)
+ return WM_ABORT_ALL;
+ i = (int)(p - s - 1);
+ if (i < 0 || p[-1] != ':') {
+ /* Didn't find ":]", so treat like a normal set. */
+ p = s - 2;
+ p_ch = '[';
+ if (t_ch == p_ch)
+ matched = 1;
+ continue;
+ }
+ if (CC_EQ(s,i, "alnum")) {
+ if (ISALNUM(t_ch))
+ matched = 1;
+ } else if (CC_EQ(s,i, "alpha")) {
+ if (ISALPHA(t_ch))
+ matched = 1;
+ } else if (CC_EQ(s,i, "blank")) {
+ if (ISBLANK(t_ch))
+ matched = 1;
+ } else if (CC_EQ(s,i, "cntrl")) {
+ if (ISCNTRL(t_ch))
+ matched = 1;
+ } else if (CC_EQ(s,i, "digit")) {
+ if (ISDIGIT(t_ch))
+ matched = 1;
+ } else if (CC_EQ(s,i, "graph")) {
+ if (ISGRAPH(t_ch))
+ matched = 1;
+ } else if (CC_EQ(s,i, "lower")) {
+ if (ISLOWER(t_ch))
+ matched = 1;
+ } else if (CC_EQ(s,i, "print")) {
+ if (ISPRINT(t_ch))
+ matched = 1;
+ } else if (CC_EQ(s,i, "punct")) {
+ if (ISPUNCT(t_ch))
+ matched = 1;
+ } else if (CC_EQ(s,i, "space")) {
+ if (ISSPACE(t_ch))
+ matched = 1;
+ } else if (CC_EQ(s,i, "upper")) {
+ if (ISUPPER(t_ch))
+ matched = 1;
+ else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch))
+ matched = 1;
+ } else if (CC_EQ(s,i, "xdigit")) {
+ if (ISXDIGIT(t_ch))
+ matched = 1;
+ } else /* malformed [:class:] string */
+ return WM_ABORT_ALL;
+ p_ch = 0; /* This makes "prev_ch" get set to 0. */
+ } else if (t_ch == p_ch)
+ matched = 1;
+ } while (prev_ch = p_ch, (p_ch = *++p) != ']');
+ if (matched == negated ||
+ ((flags & WM_PATHNAME) && t_ch == '/'))
+ return WM_NOMATCH;
+ continue;
+ }
+ }
+
+ return *text ? WM_NOMATCH : WM_MATCH;
+}
+
+/* Match the "pattern" against the "text" string. */
+int wildmatch(const char *pattern, const char *text, unsigned int flags)
+{
+ return dowild((const uchar*)pattern, (const uchar*)text, flags);
+}
diff --git a/src/util/wildmatch.h b/src/util/wildmatch.h
new file mode 100644
index 0000000..f206405
--- /dev/null
+++ b/src/util/wildmatch.h
@@ -0,0 +1,23 @@
+/*
+ * 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_wildmatch_h__
+#define INCLUDE_wildmatch_h__
+
+#include "git2_util.h"
+
+#define WM_CASEFOLD 1
+#define WM_PATHNAME 2
+
+#define WM_NOMATCH 1
+#define WM_MATCH 0
+#define WM_ABORT_ALL -1
+#define WM_ABORT_TO_STARSTAR -2
+
+int wildmatch(const char *pattern, const char *text, unsigned int flags);
+
+#endif
diff --git a/src/util/win32/dir.c b/src/util/win32/dir.c
new file mode 100644
index 0000000..44052ca
--- /dev/null
+++ b/src/util/win32/dir.c
@@ -0,0 +1,122 @@
+/*
+ * 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 "dir.h"
+
+#define GIT__WIN32_NO_WRAP_DIR
+#include "posix.h"
+
+git__DIR *git__opendir(const char *dir)
+{
+ git_win32_path filter_w;
+ git__DIR *new = NULL;
+ size_t dirlen, alloclen;
+
+ if (!dir || !git_win32__findfirstfile_filter(filter_w, dir))
+ return NULL;
+
+ dirlen = strlen(dir);
+
+ if (GIT_ADD_SIZET_OVERFLOW(&alloclen, sizeof(*new), dirlen) ||
+ GIT_ADD_SIZET_OVERFLOW(&alloclen, alloclen, 1) ||
+ !(new = git__calloc(1, alloclen)))
+ return NULL;
+
+ memcpy(new->dir, dir, dirlen);
+
+ new->h = FindFirstFileW(filter_w, &new->f);
+
+ if (new->h == INVALID_HANDLE_VALUE) {
+ git_error_set(GIT_ERROR_OS, "could not open directory '%s'", dir);
+ git__free(new);
+ return NULL;
+ }
+
+ new->first = 1;
+ return new;
+}
+
+int git__readdir_ext(
+ git__DIR *d,
+ struct git__dirent *entry,
+ struct git__dirent **result,
+ int *is_dir)
+{
+ if (!d || !entry || !result || d->h == INVALID_HANDLE_VALUE)
+ return -1;
+
+ *result = NULL;
+
+ if (d->first)
+ d->first = 0;
+ else if (!FindNextFileW(d->h, &d->f)) {
+ if (GetLastError() == ERROR_NO_MORE_FILES)
+ return 0;
+ git_error_set(GIT_ERROR_OS, "could not read from directory '%s'", d->dir);
+ return -1;
+ }
+
+ /* Convert the path to UTF-8 */
+ if (git_win32_path_to_utf8(entry->d_name, d->f.cFileName) < 0)
+ return -1;
+
+ entry->d_ino = 0;
+
+ *result = entry;
+
+ if (is_dir != NULL)
+ *is_dir = ((d->f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0);
+
+ return 0;
+}
+
+struct git__dirent *git__readdir(git__DIR *d)
+{
+ struct git__dirent *result;
+ if (git__readdir_ext(d, &d->entry, &result, NULL) < 0)
+ return NULL;
+ return result;
+}
+
+void git__rewinddir(git__DIR *d)
+{
+ git_win32_path filter_w;
+
+ if (!d)
+ return;
+
+ if (d->h != INVALID_HANDLE_VALUE) {
+ FindClose(d->h);
+ d->h = INVALID_HANDLE_VALUE;
+ d->first = 0;
+ }
+
+ if (!git_win32__findfirstfile_filter(filter_w, d->dir))
+ return;
+
+ d->h = FindFirstFileW(filter_w, &d->f);
+
+ if (d->h == INVALID_HANDLE_VALUE)
+ git_error_set(GIT_ERROR_OS, "could not open directory '%s'", d->dir);
+ else
+ d->first = 1;
+}
+
+int git__closedir(git__DIR *d)
+{
+ if (!d)
+ return 0;
+
+ if (d->h != INVALID_HANDLE_VALUE) {
+ FindClose(d->h);
+ d->h = INVALID_HANDLE_VALUE;
+ }
+
+ git__free(d);
+ return 0;
+}
+
diff --git a/src/util/win32/dir.h b/src/util/win32/dir.h
new file mode 100644
index 0000000..8101115
--- /dev/null
+++ b/src/util/win32/dir.h
@@ -0,0 +1,44 @@
+/*
+ * 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_win32_dir_h__
+#define INCLUDE_win32_dir_h__
+
+#include "git2_util.h"
+
+#include "w32_util.h"
+
+struct git__dirent {
+ int d_ino;
+ git_win32_utf8_path d_name;
+};
+
+typedef struct {
+ HANDLE h;
+ WIN32_FIND_DATAW f;
+ struct git__dirent entry;
+ int first;
+ char dir[GIT_FLEX_ARRAY];
+} git__DIR;
+
+extern git__DIR *git__opendir(const char *);
+extern struct git__dirent *git__readdir(git__DIR *);
+extern int git__readdir_ext(
+ git__DIR *, struct git__dirent *, struct git__dirent **, int *);
+extern void git__rewinddir(git__DIR *);
+extern int git__closedir(git__DIR *);
+
+# ifndef GIT__WIN32_NO_WRAP_DIR
+# define dirent git__dirent
+# define DIR git__DIR
+# define opendir git__opendir
+# define readdir git__readdir
+# define readdir_r(d,e,r) git__readdir_ext((d),(e),(r),NULL)
+# define rewinddir git__rewinddir
+# define closedir git__closedir
+# endif
+
+#endif
diff --git a/src/util/win32/error.c b/src/util/win32/error.c
new file mode 100644
index 0000000..dfd6fa1
--- /dev/null
+++ b/src/util/win32/error.c
@@ -0,0 +1,53 @@
+/*
+ * 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 "error.h"
+
+#include "utf-conv.h"
+
+#ifdef GIT_WINHTTP
+# include <winhttp.h>
+#endif
+
+char *git_win32_get_error_message(DWORD error_code)
+{
+ LPWSTR lpMsgBuf = NULL;
+ HMODULE hModule = NULL;
+ char *utf8_msg = NULL;
+ DWORD dwFlags =
+ FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS;
+
+ if (!error_code)
+ return NULL;
+
+#ifdef GIT_WINHTTP
+ /* Errors raised by WinHTTP are not in the system resource table */
+ if (error_code >= WINHTTP_ERROR_BASE &&
+ error_code <= WINHTTP_ERROR_LAST)
+ hModule = GetModuleHandleW(L"winhttp");
+#endif
+
+ GIT_UNUSED(hModule);
+
+ if (hModule)
+ dwFlags |= FORMAT_MESSAGE_FROM_HMODULE;
+ else
+ dwFlags |= FORMAT_MESSAGE_FROM_SYSTEM;
+
+ if (FormatMessageW(dwFlags, hModule, error_code,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPWSTR)&lpMsgBuf, 0, NULL)) {
+ /* Convert the message to UTF-8. If this fails, we will
+ * return NULL, which is a condition expected by the caller */
+ if (git_utf8_from_16_alloc(&utf8_msg, lpMsgBuf) < 0)
+ utf8_msg = NULL;
+
+ LocalFree(lpMsgBuf);
+ }
+
+ return utf8_msg;
+}
diff --git a/src/util/win32/error.h b/src/util/win32/error.h
new file mode 100644
index 0000000..fd53b7f
--- /dev/null
+++ b/src/util/win32/error.h
@@ -0,0 +1,15 @@
+/*
+ * 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_win32_error_h__
+#define INCLUDE_win32_error_h__
+
+#include "git2_util.h"
+
+extern char *git_win32_get_error_message(DWORD error_code);
+
+#endif
diff --git a/src/util/win32/map.c b/src/util/win32/map.c
new file mode 100644
index 0000000..52e1363
--- /dev/null
+++ b/src/util/win32/map.c
@@ -0,0 +1,141 @@
+/*
+ * 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 "map.h"
+#include <errno.h>
+
+#ifndef NO_MMAP
+
+static DWORD get_page_size(void)
+{
+ static DWORD page_size;
+ SYSTEM_INFO sys;
+
+ if (!page_size) {
+ GetSystemInfo(&sys);
+ page_size = sys.dwPageSize;
+ }
+
+ return page_size;
+}
+
+static DWORD get_allocation_granularity(void)
+{
+ static DWORD granularity;
+ SYSTEM_INFO sys;
+
+ if (!granularity) {
+ GetSystemInfo(&sys);
+ granularity = sys.dwAllocationGranularity;
+ }
+
+ return granularity;
+}
+
+int git__page_size(size_t *page_size)
+{
+ *page_size = get_page_size();
+ return 0;
+}
+
+int git__mmap_alignment(size_t *page_size)
+{
+ *page_size = get_allocation_granularity();
+ return 0;
+}
+
+int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, off64_t offset)
+{
+ HANDLE fh = (HANDLE)_get_osfhandle(fd);
+ DWORD alignment = get_allocation_granularity();
+ DWORD fmap_prot = 0;
+ DWORD view_prot = 0;
+ DWORD off_low = 0;
+ DWORD off_hi = 0;
+ off64_t page_start;
+ off64_t page_offset;
+
+ GIT_MMAP_VALIDATE(out, len, prot, flags);
+
+ out->data = NULL;
+ out->len = 0;
+ out->fmh = NULL;
+
+ if (fh == INVALID_HANDLE_VALUE) {
+ errno = EBADF;
+ git_error_set(GIT_ERROR_OS, "failed to mmap. Invalid handle value");
+ return -1;
+ }
+
+ if (prot & GIT_PROT_WRITE)
+ fmap_prot |= PAGE_READWRITE;
+ else if (prot & GIT_PROT_READ)
+ fmap_prot |= PAGE_READONLY;
+
+ if (prot & GIT_PROT_WRITE)
+ view_prot |= FILE_MAP_WRITE;
+ if (prot & GIT_PROT_READ)
+ view_prot |= FILE_MAP_READ;
+
+ page_start = (offset / alignment) * alignment;
+ page_offset = offset - page_start;
+
+ if (page_offset != 0) { /* offset must be multiple of the allocation granularity */
+ errno = EINVAL;
+ git_error_set(GIT_ERROR_OS, "failed to mmap. Offset must be multiple of allocation granularity");
+ return -1;
+ }
+
+ out->fmh = CreateFileMapping(fh, NULL, fmap_prot, 0, 0, NULL);
+ if (!out->fmh || out->fmh == INVALID_HANDLE_VALUE) {
+ git_error_set(GIT_ERROR_OS, "failed to mmap. Invalid handle value");
+ out->fmh = NULL;
+ return -1;
+ }
+
+ off_low = (DWORD)(page_start);
+ off_hi = (DWORD)(page_start >> 32);
+ out->data = MapViewOfFile(out->fmh, view_prot, off_hi, off_low, len);
+ if (!out->data) {
+ git_error_set(GIT_ERROR_OS, "failed to mmap. No data written");
+ CloseHandle(out->fmh);
+ out->fmh = NULL;
+ return -1;
+ }
+ out->len = len;
+
+ return 0;
+}
+
+int p_munmap(git_map *map)
+{
+ int error = 0;
+
+ GIT_ASSERT_ARG(map);
+
+ if (map->data) {
+ if (!UnmapViewOfFile(map->data)) {
+ git_error_set(GIT_ERROR_OS, "failed to munmap. Could not unmap view of file");
+ error = -1;
+ }
+ map->data = NULL;
+ }
+
+ if (map->fmh) {
+ if (!CloseHandle(map->fmh)) {
+ git_error_set(GIT_ERROR_OS, "failed to munmap. Could not close handle");
+ error = -1;
+ }
+ map->fmh = NULL;
+ }
+
+ return error;
+}
+
+#endif
diff --git a/src/util/win32/mingw-compat.h b/src/util/win32/mingw-compat.h
new file mode 100644
index 0000000..aa2bef9
--- /dev/null
+++ b/src/util/win32/mingw-compat.h
@@ -0,0 +1,23 @@
+/*
+ * 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_win32_mingw_compat_h__
+#define INCLUDE_win32_mingw_compat_h__
+
+#if defined(__MINGW32__)
+
+#undef stat
+
+#if _WIN32_WINNT < 0x0600 && !defined(__MINGW64_VERSION_MAJOR)
+#undef MemoryBarrier
+void __mingworg_MemoryBarrier(void);
+#define MemoryBarrier __mingworg_MemoryBarrier
+#define VOLUME_NAME_DOS 0x0
+#endif
+
+#endif
+
+#endif
diff --git a/src/util/win32/msvc-compat.h b/src/util/win32/msvc-compat.h
new file mode 100644
index 0000000..03f9f36
--- /dev/null
+++ b/src/util/win32/msvc-compat.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_win32_msvc_compat_h__
+#define INCLUDE_win32_msvc_compat_h__
+
+#if defined(_MSC_VER)
+
+typedef unsigned short mode_t;
+typedef SSIZE_T ssize_t;
+
+#ifdef _WIN64
+# define SSIZE_MAX _I64_MAX
+#else
+# define SSIZE_MAX LONG_MAX
+#endif
+
+#define strcasecmp(s1, s2) _stricmp(s1, s2)
+#define strncasecmp(s1, s2, c) _strnicmp(s1, s2, c)
+
+#endif
+
+/*
+ * Offer GIT_LIBGIT2_CALL for our calling conventions (__cdecl, always).
+ * This is useful for providing callbacks to userspace code.
+ *
+ * Offer GIT_SYSTEM_CALL for the system calling conventions (__stdcall on
+ * Win32). Useful for providing callbacks to system libraries.
+ */
+#define GIT_LIBGIT2_CALL __cdecl
+#define GIT_SYSTEM_CALL NTAPI
+
+#endif
diff --git a/src/util/win32/path_w32.c b/src/util/win32/path_w32.c
new file mode 100644
index 0000000..7a559e4
--- /dev/null
+++ b/src/util/win32/path_w32.c
@@ -0,0 +1,642 @@
+/*
+ * 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 "path_w32.h"
+
+#include "fs_path.h"
+#include "utf-conv.h"
+#include "posix.h"
+#include "reparse.h"
+#include "dir.h"
+
+#define PATH__NT_NAMESPACE L"\\\\?\\"
+#define PATH__NT_NAMESPACE_LEN 4
+
+#define PATH__ABSOLUTE_LEN 3
+
+#define path__is_nt_namespace(p) \
+ (((p)[0] == '\\' && (p)[1] == '\\' && (p)[2] == '?' && (p)[3] == '\\') || \
+ ((p)[0] == '/' && (p)[1] == '/' && (p)[2] == '?' && (p)[3] == '/'))
+
+#define path__is_unc(p) \
+ (((p)[0] == '\\' && (p)[1] == '\\') || ((p)[0] == '/' && (p)[1] == '/'))
+
+#define path__startswith_slash(p) \
+ ((p)[0] == '\\' || (p)[0] == '/')
+
+GIT_INLINE(int) path__cwd(wchar_t *path, int size)
+{
+ int len;
+
+ if ((len = GetCurrentDirectoryW(size, path)) == 0) {
+ errno = GetLastError() == ERROR_ACCESS_DENIED ? EACCES : ENOENT;
+ return -1;
+ } else if (len > size) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ /* The Win32 APIs may return "\\?\" once you've used it first.
+ * But it may not. What a gloriously predictable API!
+ */
+ if (wcsncmp(path, PATH__NT_NAMESPACE, PATH__NT_NAMESPACE_LEN))
+ return len;
+
+ len -= PATH__NT_NAMESPACE_LEN;
+
+ memmove(path, path + PATH__NT_NAMESPACE_LEN, sizeof(wchar_t) * len);
+ return len;
+}
+
+static wchar_t *path__skip_server(wchar_t *path)
+{
+ wchar_t *c;
+
+ for (c = path; *c; c++) {
+ if (git_fs_path_is_dirsep(*c))
+ return c + 1;
+ }
+
+ return c;
+}
+
+static wchar_t *path__skip_prefix(wchar_t *path)
+{
+ if (path__is_nt_namespace(path)) {
+ path += PATH__NT_NAMESPACE_LEN;
+
+ if (wcsncmp(path, L"UNC\\", 4) == 0)
+ path = path__skip_server(path + 4);
+ else if (git_fs_path_is_absolute(path))
+ path += PATH__ABSOLUTE_LEN;
+ } else if (git_fs_path_is_absolute(path)) {
+ path += PATH__ABSOLUTE_LEN;
+ } else if (path__is_unc(path)) {
+ path = path__skip_server(path + 2);
+ }
+
+ return path;
+}
+
+int git_win32_path_canonicalize(git_win32_path path)
+{
+ wchar_t *base, *from, *to, *next;
+ size_t len;
+
+ base = to = path__skip_prefix(path);
+
+ /* Unposixify if the prefix */
+ for (from = path; from < to; from++) {
+ if (*from == L'/')
+ *from = L'\\';
+ }
+
+ while (*from) {
+ for (next = from; *next; ++next) {
+ if (*next == L'/') {
+ *next = L'\\';
+ break;
+ }
+
+ if (*next == L'\\')
+ break;
+ }
+
+ len = next - from;
+
+ if (len == 1 && from[0] == L'.')
+ /* do nothing with singleton dot */;
+
+ else if (len == 2 && from[0] == L'.' && from[1] == L'.') {
+ if (to == base) {
+ /* no more path segments to strip, eat the "../" */
+ if (*next == L'\\')
+ len++;
+
+ base = to;
+ } else {
+ /* back up a path segment */
+ while (to > base && to[-1] == L'\\') to--;
+ while (to > base && to[-1] != L'\\') to--;
+ }
+ } else {
+ if (*next == L'\\' && *from != L'\\')
+ len++;
+
+ if (to != from)
+ memmove(to, from, sizeof(wchar_t) * len);
+
+ to += len;
+ }
+
+ from += len;
+
+ while (*from == L'\\') from++;
+ }
+
+ /* Strip trailing backslashes */
+ while (to > base && to[-1] == L'\\') to--;
+
+ *to = L'\0';
+
+ if ((to - path) > INT_MAX) {
+ SetLastError(ERROR_FILENAME_EXCED_RANGE);
+ return -1;
+ }
+
+ return (int)(to - path);
+}
+
+static int git_win32_path_join(
+ git_win32_path dest,
+ const wchar_t *one,
+ size_t one_len,
+ const wchar_t *two,
+ size_t two_len)
+{
+ size_t backslash = 0;
+
+ if (one_len && two_len && one[one_len - 1] != L'\\')
+ backslash = 1;
+
+ if (one_len + two_len + backslash > MAX_PATH) {
+ git_error_set(GIT_ERROR_INVALID, "path too long");
+ return -1;
+ }
+
+ memmove(dest, one, one_len * sizeof(wchar_t));
+
+ if (backslash)
+ dest[one_len] = L'\\';
+
+ memcpy(dest + one_len + backslash, two, two_len * sizeof(wchar_t));
+ dest[one_len + backslash + two_len] = L'\0';
+
+ return 0;
+}
+
+struct win32_path_iter {
+ wchar_t *env;
+ const wchar_t *current_dir;
+};
+
+static int win32_path_iter_init(struct win32_path_iter *iter)
+{
+ DWORD len = GetEnvironmentVariableW(L"PATH", NULL, 0);
+
+ if (!len && GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
+ iter->env = NULL;
+ iter->current_dir = NULL;
+ return 0;
+ } else if (!len) {
+ git_error_set(GIT_ERROR_OS, "could not load PATH");
+ return -1;
+ }
+
+ iter->env = git__malloc(len * sizeof(wchar_t));
+ GIT_ERROR_CHECK_ALLOC(iter->env);
+
+ len = GetEnvironmentVariableW(L"PATH", iter->env, len);
+
+ if (len == 0) {
+ git_error_set(GIT_ERROR_OS, "could not load PATH");
+ return -1;
+ }
+
+ iter->current_dir = iter->env;
+ return 0;
+}
+
+static int win32_path_iter_next(
+ const wchar_t **out,
+ size_t *out_len,
+ struct win32_path_iter *iter)
+{
+ const wchar_t *start;
+ wchar_t term;
+ size_t len = 0;
+
+ if (!iter->current_dir || !*iter->current_dir)
+ return GIT_ITEROVER;
+
+ term = (*iter->current_dir == L'"') ? *iter->current_dir++ : L';';
+ start = iter->current_dir;
+
+ while (*iter->current_dir && *iter->current_dir != term) {
+ iter->current_dir++;
+ len++;
+ }
+
+ *out = start;
+ *out_len = len;
+
+ if (term == L'"' && *iter->current_dir)
+ iter->current_dir++;
+
+ while (*iter->current_dir == L';')
+ iter->current_dir++;
+
+ return 0;
+}
+
+static void win32_path_iter_dispose(struct win32_path_iter *iter)
+{
+ if (!iter)
+ return;
+
+ git__free(iter->env);
+ iter->env = NULL;
+ iter->current_dir = NULL;
+}
+
+int git_win32_path_find_executable(git_win32_path fullpath, wchar_t *exe)
+{
+ struct win32_path_iter path_iter;
+ const wchar_t *dir;
+ size_t dir_len, exe_len = wcslen(exe);
+ bool found = false;
+
+ if (win32_path_iter_init(&path_iter) < 0)
+ return -1;
+
+ while (win32_path_iter_next(&dir, &dir_len, &path_iter) != GIT_ITEROVER) {
+ if (git_win32_path_join(fullpath, dir, dir_len, exe, exe_len) < 0)
+ continue;
+
+ if (_waccess(fullpath, 0) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ win32_path_iter_dispose(&path_iter);
+
+ if (found)
+ return 0;
+
+ fullpath[0] = L'\0';
+ return GIT_ENOTFOUND;
+}
+
+static int win32_path_cwd(wchar_t *out, size_t len)
+{
+ int cwd_len;
+
+ if (len > INT_MAX) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ if ((cwd_len = path__cwd(out, (int)len)) < 0)
+ return -1;
+
+ /* UNC paths */
+ if (wcsncmp(L"\\\\", out, 2) == 0) {
+ /* Our buffer must be at least 5 characters larger than the
+ * current working directory: we swallow one of the leading
+ * '\'s, but we we add a 'UNC' specifier to the path, plus
+ * a trailing directory separator, plus a NUL.
+ */
+ if (cwd_len > GIT_WIN_PATH_MAX - 4) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ memmove(out+2, out, sizeof(wchar_t) * cwd_len);
+ out[0] = L'U';
+ out[1] = L'N';
+ out[2] = L'C';
+
+ cwd_len += 2;
+ }
+
+ /* Our buffer must be at least 2 characters larger than the current
+ * working directory. (One character for the directory separator,
+ * one for the null.
+ */
+ else if (cwd_len > GIT_WIN_PATH_MAX - 2) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ return cwd_len;
+}
+
+int git_win32_path_from_utf8(git_win32_path out, const char *src)
+{
+ wchar_t *dest = out;
+
+ /* All win32 paths are in NT-prefixed format, beginning with "\\?\". */
+ memcpy(dest, PATH__NT_NAMESPACE, sizeof(wchar_t) * PATH__NT_NAMESPACE_LEN);
+ dest += PATH__NT_NAMESPACE_LEN;
+
+ /* See if this is an absolute path (beginning with a drive letter) */
+ if (git_fs_path_is_absolute(src)) {
+ if (git_utf8_to_16(dest, GIT_WIN_PATH_MAX, src) < 0)
+ goto on_error;
+ }
+ /* File-prefixed NT-style paths beginning with \\?\ */
+ else if (path__is_nt_namespace(src)) {
+ /* Skip the NT prefix, the destination already contains it */
+ if (git_utf8_to_16(dest, GIT_WIN_PATH_MAX, src + PATH__NT_NAMESPACE_LEN) < 0)
+ goto on_error;
+ }
+ /* UNC paths */
+ else if (path__is_unc(src)) {
+ memcpy(dest, L"UNC\\", sizeof(wchar_t) * 4);
+ dest += 4;
+
+ /* Skip the leading "\\" */
+ if (git_utf8_to_16(dest, GIT_WIN_PATH_MAX - 2, src + 2) < 0)
+ goto on_error;
+ }
+ /* Absolute paths omitting the drive letter */
+ else if (path__startswith_slash(src)) {
+ if (path__cwd(dest, GIT_WIN_PATH_MAX) < 0)
+ goto on_error;
+
+ if (!git_fs_path_is_absolute(dest)) {
+ errno = ENOENT;
+ goto on_error;
+ }
+
+ /* Skip the drive letter specification ("C:") */
+ if (git_utf8_to_16(dest + 2, GIT_WIN_PATH_MAX - 2, src) < 0)
+ goto on_error;
+ }
+ /* Relative paths */
+ else {
+ int cwd_len;
+
+ if ((cwd_len = win32_path_cwd(dest, GIT_WIN_PATH_MAX)) < 0)
+ goto on_error;
+
+ dest[cwd_len++] = L'\\';
+
+ if (git_utf8_to_16(dest + cwd_len, GIT_WIN_PATH_MAX - cwd_len, src) < 0)
+ goto on_error;
+ }
+
+ return git_win32_path_canonicalize(out);
+
+on_error:
+ /* set windows error code so we can use its error message */
+ if (errno == ENAMETOOLONG)
+ SetLastError(ERROR_FILENAME_EXCED_RANGE);
+
+ return -1;
+}
+
+int git_win32_path_relative_from_utf8(git_win32_path out, const char *src)
+{
+ wchar_t *dest = out, *p;
+ int len;
+
+ /* Handle absolute paths */
+ if (git_fs_path_is_absolute(src) ||
+ path__is_nt_namespace(src) ||
+ path__is_unc(src) ||
+ path__startswith_slash(src)) {
+ return git_win32_path_from_utf8(out, src);
+ }
+
+ if ((len = git_utf8_to_16(dest, GIT_WIN_PATH_MAX, src)) < 0)
+ return -1;
+
+ for (p = dest; p < (dest + len); p++) {
+ if (*p == L'/')
+ *p = L'\\';
+ }
+
+ return len;
+}
+
+int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src)
+{
+ char *out = dest;
+ int len;
+
+ /* Strip NT namespacing "\\?\" */
+ if (path__is_nt_namespace(src)) {
+ src += 4;
+
+ /* "\\?\UNC\server\share" -> "\\server\share" */
+ if (wcsncmp(src, L"UNC\\", 4) == 0) {
+ src += 4;
+
+ memcpy(dest, "\\\\", 2);
+ out = dest + 2;
+ }
+ }
+
+ if ((len = git_utf8_from_16(out, GIT_WIN_PATH_UTF8, src)) < 0)
+ return len;
+
+ git_fs_path_mkposix(dest);
+
+ return len;
+}
+
+char *git_win32_path_8dot3_name(const char *path)
+{
+ git_win32_path longpath, shortpath;
+ wchar_t *start;
+ char *shortname;
+ int len, namelen = 1;
+
+ if (git_win32_path_from_utf8(longpath, path) < 0)
+ return NULL;
+
+ len = GetShortPathNameW(longpath, shortpath, GIT_WIN_PATH_UTF16);
+
+ while (len && shortpath[len-1] == L'\\')
+ shortpath[--len] = L'\0';
+
+ if (len == 0 || len >= GIT_WIN_PATH_UTF16)
+ return NULL;
+
+ for (start = shortpath + (len - 1);
+ start > shortpath && *(start-1) != '/' && *(start-1) != '\\';
+ start--)
+ namelen++;
+
+ /* We may not have actually been given a short name. But if we have,
+ * it will be in the ASCII byte range, so we don't need to worry about
+ * multi-byte sequences and can allocate naively.
+ */
+ if (namelen > 12 || (shortname = git__malloc(namelen + 1)) == NULL)
+ return NULL;
+
+ if ((len = git_utf8_from_16(shortname, namelen + 1, start)) < 0)
+ return NULL;
+
+ return shortname;
+}
+
+static bool path_is_volume(wchar_t *target, size_t target_len)
+{
+ return (target_len && wcsncmp(target, L"\\??\\Volume{", 11) == 0);
+}
+
+/* On success, returns the length, in characters, of the path stored in dest.
+ * On failure, returns a negative value. */
+int git_win32_path_readlink_w(git_win32_path dest, const git_win32_path path)
+{
+ BYTE buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
+ GIT_REPARSE_DATA_BUFFER *reparse_buf = (GIT_REPARSE_DATA_BUFFER *)buf;
+ HANDLE handle = NULL;
+ DWORD ioctl_ret;
+ wchar_t *target;
+ size_t target_len;
+
+ int error = -1;
+
+ handle = CreateFileW(path, GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
+ FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);
+
+ if (handle == INVALID_HANDLE_VALUE) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0,
+ reparse_buf, sizeof(buf), &ioctl_ret, NULL)) {
+ errno = EINVAL;
+ goto on_error;
+ }
+
+ switch (reparse_buf->ReparseTag) {
+ case IO_REPARSE_TAG_SYMLINK:
+ target = reparse_buf->ReparseBuffer.SymbolicLink.PathBuffer +
+ (reparse_buf->ReparseBuffer.SymbolicLink.SubstituteNameOffset / sizeof(WCHAR));
+ target_len = reparse_buf->ReparseBuffer.SymbolicLink.SubstituteNameLength / sizeof(WCHAR);
+ break;
+ case IO_REPARSE_TAG_MOUNT_POINT:
+ target = reparse_buf->ReparseBuffer.MountPoint.PathBuffer +
+ (reparse_buf->ReparseBuffer.MountPoint.SubstituteNameOffset / sizeof(WCHAR));
+ target_len = reparse_buf->ReparseBuffer.MountPoint.SubstituteNameLength / sizeof(WCHAR);
+ break;
+ default:
+ errno = EINVAL;
+ goto on_error;
+ }
+
+ if (path_is_volume(target, target_len)) {
+ /* This path is a reparse point that represents another volume mounted
+ * at this location, it is not a symbolic link our input was canonical.
+ */
+ errno = EINVAL;
+ error = -1;
+ } else if (target_len) {
+ /* The path may need to have a namespace prefix removed. */
+ target_len = git_win32_path_remove_namespace(target, target_len);
+
+ /* Need one additional character in the target buffer
+ * for the terminating NULL. */
+ if (GIT_WIN_PATH_UTF16 > target_len) {
+ wcscpy(dest, target);
+ error = (int)target_len;
+ }
+ }
+
+on_error:
+ CloseHandle(handle);
+ return error;
+}
+
+/**
+ * Removes any trailing backslashes from a path, except in the case of a drive
+ * letter path (C:\, D:\, etc.). This function cannot fail.
+ *
+ * @param path The path which should be trimmed.
+ * @return The length of the modified string (<= the input length)
+ */
+size_t git_win32_path_trim_end(wchar_t *str, size_t len)
+{
+ while (1) {
+ if (!len || str[len - 1] != L'\\')
+ break;
+
+ /*
+ * Don't trim backslashes from drive letter paths, which
+ * are 3 characters long and of the form C:\, D:\, etc.
+ */
+ if (len == 3 && git_win32__isalpha(str[0]) && str[1] == ':')
+ break;
+
+ len--;
+ }
+
+ str[len] = L'\0';
+
+ return len;
+}
+
+/**
+ * Removes any of the following namespace prefixes from a path,
+ * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail.
+ *
+ * @param path The path which should be converted.
+ * @return The length of the modified string (<= the input length)
+ */
+size_t git_win32_path_remove_namespace(wchar_t *str, size_t len)
+{
+ static const wchar_t dosdevices_namespace[] = L"\\\?\?\\";
+ static const wchar_t nt_namespace[] = L"\\\\?\\";
+ static const wchar_t unc_namespace_remainder[] = L"UNC\\";
+ static const wchar_t unc_prefix[] = L"\\\\";
+
+ const wchar_t *prefix = NULL, *remainder = NULL;
+ size_t prefix_len = 0, remainder_len = 0;
+
+ /* "\??\" -- DOS Devices prefix */
+ if (len >= CONST_STRLEN(dosdevices_namespace) &&
+ !wcsncmp(str, dosdevices_namespace, CONST_STRLEN(dosdevices_namespace))) {
+ remainder = str + CONST_STRLEN(dosdevices_namespace);
+ remainder_len = len - CONST_STRLEN(dosdevices_namespace);
+ }
+ /* "\\?\" -- NT namespace prefix */
+ else if (len >= CONST_STRLEN(nt_namespace) &&
+ !wcsncmp(str, nt_namespace, CONST_STRLEN(nt_namespace))) {
+ remainder = str + CONST_STRLEN(nt_namespace);
+ remainder_len = len - CONST_STRLEN(nt_namespace);
+ }
+
+ /* "\??\UNC\", "\\?\UNC\" -- UNC prefix */
+ if (remainder_len >= CONST_STRLEN(unc_namespace_remainder) &&
+ !wcsncmp(remainder, unc_namespace_remainder, CONST_STRLEN(unc_namespace_remainder))) {
+
+ /*
+ * The proper Win32 path for a UNC share has "\\" at beginning of it
+ * and looks like "\\server\share\<folderStructure>". So remove the
+ * UNC namespace and add a prefix of "\\" in its place.
+ */
+ remainder += CONST_STRLEN(unc_namespace_remainder);
+ remainder_len -= CONST_STRLEN(unc_namespace_remainder);
+
+ prefix = unc_prefix;
+ prefix_len = CONST_STRLEN(unc_prefix);
+ }
+
+ /*
+ * Sanity check that the new string isn't longer than the old one.
+ * (This could only happen due to programmer error introducing a
+ * prefix longer than the namespace it replaces.)
+ */
+ if (remainder && len >= remainder_len + prefix_len) {
+ if (prefix)
+ memmove(str, prefix, prefix_len * sizeof(wchar_t));
+
+ memmove(str + prefix_len, remainder, remainder_len * sizeof(wchar_t));
+
+ len = remainder_len + prefix_len;
+ str[len] = L'\0';
+ }
+
+ return git_win32_path_trim_end(str, len);
+}
diff --git a/src/util/win32/path_w32.h b/src/util/win32/path_w32.h
new file mode 100644
index 0000000..b241d5c
--- /dev/null
+++ b/src/util/win32/path_w32.h
@@ -0,0 +1,91 @@
+/*
+ * 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_win32_path_w32_h__
+#define INCLUDE_win32_path_w32_h__
+
+#include "git2_util.h"
+
+/**
+ * Create a Win32 path (in UCS-2 format) from a UTF-8 string. If the given
+ * path is relative, then it will be turned into an absolute path by having
+ * the current working directory prepended.
+ *
+ * @param dest The buffer to receive the wide string.
+ * @param src The UTF-8 string to convert.
+ * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
+ */
+extern int git_win32_path_from_utf8(git_win32_path dest, const char *src);
+
+/**
+ * Create a Win32 path (in UCS-2 format) from a UTF-8 string. If the given
+ * path is relative, then it will not be turned into an absolute path.
+ *
+ * @param dest The buffer to receive the wide string.
+ * @param src The UTF-8 string to convert.
+ * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
+ */
+extern int git_win32_path_relative_from_utf8(git_win32_path dest, const char *src);
+
+/**
+ * Canonicalize a Win32 UCS-2 path so that it is suitable for delivery to the
+ * Win32 APIs: remove multiple directory separators, squashing to a single one,
+ * strip trailing directory separators, ensure directory separators are all
+ * canonical (always backslashes, never forward slashes) and process any
+ * directory entries of '.' or '..'.
+ *
+ * Note that this is intended to be used on absolute Windows paths, those
+ * that start with `C:\`, `\\server\share`, `\\?\`, etc.
+ *
+ * This processes the buffer in place.
+ *
+ * @param path The buffer to process
+ * @return The new length of the buffer, in wchar_t's (not counting the NULL terminator)
+ */
+extern int git_win32_path_canonicalize(git_win32_path path);
+
+/**
+ * Create an internal format (posix-style) UTF-8 path from a Win32 UCS-2 path.
+ *
+ * @param dest The buffer to receive the UTF-8 string.
+ * @param src The wide string to convert.
+ * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure
+ */
+extern int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src);
+
+/**
+ * Get the short name for the terminal path component in the given path.
+ * For example, given "C:\Foo\Bar\Asdf.txt", this will return the short name
+ * for the file "Asdf.txt".
+ *
+ * @param path The given path in UTF-8
+ * @return The name of the shortname for the given path
+ */
+extern char *git_win32_path_8dot3_name(const char *path);
+
+extern int git_win32_path_readlink_w(git_win32_path dest, const git_win32_path path);
+
+/**
+ * Removes any trailing backslashes from a path, except in the case of a drive
+ * letter path (C:\, D:\, etc.). This function cannot fail.
+ *
+ * @param path The path which should be trimmed.
+ * @return The length of the modified string (<= the input length)
+ */
+size_t git_win32_path_trim_end(wchar_t *str, size_t len);
+
+/**
+ * Removes any of the following namespace prefixes from a path,
+ * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail.
+ *
+ * @param path The path which should be converted.
+ * @return The length of the modified string (<= the input length)
+ */
+size_t git_win32_path_remove_namespace(wchar_t *str, size_t len);
+
+int git_win32_path_find_executable(git_win32_path fullpath, wchar_t* exe);
+
+#endif
diff --git a/src/util/win32/posix.h b/src/util/win32/posix.h
new file mode 100644
index 0000000..03fa2ac
--- /dev/null
+++ b/src/util/win32/posix.h
@@ -0,0 +1,62 @@
+/*
+ * 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_win32_posix_h__
+#define INCLUDE_win32_posix_h__
+
+#include "git2_util.h"
+#include "../posix.h"
+#include "win32-compat.h"
+#include "path_w32.h"
+#include "utf-conv.h"
+#include "dir.h"
+
+extern unsigned long git_win32__createfile_sharemode;
+extern int git_win32__retries;
+
+typedef SOCKET GIT_SOCKET;
+
+#define p_lseek(f,n,w) _lseeki64(f, n, w)
+
+extern int p_fstat(int fd, struct stat *buf);
+extern int p_lstat(const char *file_name, struct stat *buf);
+extern int p_stat(const char *path, struct stat *buf);
+
+extern int p_utimes(const char *filename, const struct p_timeval times[2]);
+extern int p_futimes(int fd, const struct p_timeval times[2]);
+
+extern int p_readlink(const char *path, char *buf, size_t bufsiz);
+extern int p_symlink(const char *old, const char *new);
+extern int p_link(const char *old, const char *new);
+extern int p_unlink(const char *path);
+extern int p_mkdir(const char *path, mode_t mode);
+extern int p_fsync(int fd);
+extern char *p_realpath(const char *orig_path, char *buffer);
+
+extern int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags);
+extern int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags);
+extern int p_inet_pton(int af, const char *src, void* dst);
+
+extern int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr);
+extern int p_snprintf(char *buffer, size_t count, const char *format, ...) GIT_FORMAT_PRINTF(3, 4);
+extern int p_chdir(const char *path);
+extern int p_chmod(const char *path, mode_t mode);
+extern int p_rmdir(const char *path);
+extern int p_access(const char *path, mode_t mode);
+extern int p_ftruncate(int fd, off64_t size);
+
+/* p_lstat is almost but not quite POSIX correct. Specifically, the use of
+ * ENOTDIR is wrong, in that it does not mean precisely that a non-directory
+ * entry was encountered. Making it correct is potentially expensive,
+ * however, so this is a separate version of p_lstat to use when correct
+ * POSIX ENOTDIR semantics is required.
+ */
+extern int p_lstat_posixly(const char *filename, struct stat *buf);
+
+extern struct tm * p_localtime_r(const time_t *timer, struct tm *result);
+extern struct tm * p_gmtime_r(const time_t *timer, struct tm *result);
+
+#endif
diff --git a/src/util/win32/posix_w32.c b/src/util/win32/posix_w32.c
new file mode 100644
index 0000000..3fec469
--- /dev/null
+++ b/src/util/win32/posix_w32.c
@@ -0,0 +1,1047 @@
+/*
+ * 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 "../posix.h"
+#include "../futils.h"
+#include "fs_path.h"
+#include "path_w32.h"
+#include "utf-conv.h"
+#include "reparse.h"
+#include <errno.h>
+#include <io.h>
+#include <fcntl.h>
+#include <ws2tcpip.h>
+
+#ifndef FILE_NAME_NORMALIZED
+# define FILE_NAME_NORMALIZED 0
+#endif
+
+#ifndef IO_REPARSE_TAG_SYMLINK
+#define IO_REPARSE_TAG_SYMLINK (0xA000000CL)
+#endif
+
+#ifndef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
+# define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x02
+#endif
+
+#ifndef SYMBOLIC_LINK_FLAG_DIRECTORY
+# define SYMBOLIC_LINK_FLAG_DIRECTORY 0x01
+#endif
+
+/* Allowable mode bits on Win32. Using mode bits that are not supported on
+ * Win32 (eg S_IRWXU) is generally ignored, but Wine warns loudly about it
+ * so we simply remove them.
+ */
+#define WIN32_MODE_MASK (_S_IREAD | _S_IWRITE)
+
+unsigned long git_win32__createfile_sharemode =
+ FILE_SHARE_READ | FILE_SHARE_WRITE;
+int git_win32__retries = 10;
+
+GIT_INLINE(void) set_errno(void)
+{
+ switch (GetLastError()) {
+ case ERROR_FILE_NOT_FOUND:
+ case ERROR_PATH_NOT_FOUND:
+ case ERROR_INVALID_DRIVE:
+ case ERROR_NO_MORE_FILES:
+ case ERROR_BAD_NETPATH:
+ case ERROR_BAD_NET_NAME:
+ case ERROR_BAD_PATHNAME:
+ case ERROR_FILENAME_EXCED_RANGE:
+ errno = ENOENT;
+ break;
+ case ERROR_BAD_ENVIRONMENT:
+ errno = E2BIG;
+ break;
+ case ERROR_BAD_FORMAT:
+ case ERROR_INVALID_STARTING_CODESEG:
+ case ERROR_INVALID_STACKSEG:
+ case ERROR_INVALID_MODULETYPE:
+ case ERROR_INVALID_EXE_SIGNATURE:
+ case ERROR_EXE_MARKED_INVALID:
+ case ERROR_BAD_EXE_FORMAT:
+ case ERROR_ITERATED_DATA_EXCEEDS_64k:
+ case ERROR_INVALID_MINALLOCSIZE:
+ case ERROR_DYNLINK_FROM_INVALID_RING:
+ case ERROR_IOPL_NOT_ENABLED:
+ case ERROR_INVALID_SEGDPL:
+ case ERROR_AUTODATASEG_EXCEEDS_64k:
+ case ERROR_RING2SEG_MUST_BE_MOVABLE:
+ case ERROR_RELOC_CHAIN_XEEDS_SEGLIM:
+ case ERROR_INFLOOP_IN_RELOC_CHAIN:
+ errno = ENOEXEC;
+ break;
+ case ERROR_INVALID_HANDLE:
+ case ERROR_INVALID_TARGET_HANDLE:
+ case ERROR_DIRECT_ACCESS_HANDLE:
+ errno = EBADF;
+ break;
+ case ERROR_WAIT_NO_CHILDREN:
+ case ERROR_CHILD_NOT_COMPLETE:
+ errno = ECHILD;
+ break;
+ case ERROR_NO_PROC_SLOTS:
+ case ERROR_MAX_THRDS_REACHED:
+ case ERROR_NESTING_NOT_ALLOWED:
+ errno = EAGAIN;
+ break;
+ case ERROR_ARENA_TRASHED:
+ case ERROR_NOT_ENOUGH_MEMORY:
+ case ERROR_INVALID_BLOCK:
+ case ERROR_NOT_ENOUGH_QUOTA:
+ errno = ENOMEM;
+ break;
+ case ERROR_ACCESS_DENIED:
+ case ERROR_CURRENT_DIRECTORY:
+ case ERROR_WRITE_PROTECT:
+ case ERROR_BAD_UNIT:
+ case ERROR_NOT_READY:
+ case ERROR_BAD_COMMAND:
+ case ERROR_CRC:
+ case ERROR_BAD_LENGTH:
+ case ERROR_SEEK:
+ case ERROR_NOT_DOS_DISK:
+ case ERROR_SECTOR_NOT_FOUND:
+ case ERROR_OUT_OF_PAPER:
+ case ERROR_WRITE_FAULT:
+ case ERROR_READ_FAULT:
+ case ERROR_GEN_FAILURE:
+ case ERROR_SHARING_VIOLATION:
+ case ERROR_LOCK_VIOLATION:
+ case ERROR_WRONG_DISK:
+ case ERROR_SHARING_BUFFER_EXCEEDED:
+ case ERROR_NETWORK_ACCESS_DENIED:
+ case ERROR_CANNOT_MAKE:
+ case ERROR_FAIL_I24:
+ case ERROR_DRIVE_LOCKED:
+ case ERROR_SEEK_ON_DEVICE:
+ case ERROR_NOT_LOCKED:
+ case ERROR_LOCK_FAILED:
+ errno = EACCES;
+ break;
+ case ERROR_FILE_EXISTS:
+ case ERROR_ALREADY_EXISTS:
+ errno = EEXIST;
+ break;
+ case ERROR_NOT_SAME_DEVICE:
+ errno = EXDEV;
+ break;
+ case ERROR_INVALID_FUNCTION:
+ case ERROR_INVALID_ACCESS:
+ case ERROR_INVALID_DATA:
+ case ERROR_INVALID_PARAMETER:
+ case ERROR_NEGATIVE_SEEK:
+ errno = EINVAL;
+ break;
+ case ERROR_TOO_MANY_OPEN_FILES:
+ errno = EMFILE;
+ break;
+ case ERROR_DISK_FULL:
+ errno = ENOSPC;
+ break;
+ case ERROR_BROKEN_PIPE:
+ errno = EPIPE;
+ break;
+ case ERROR_DIR_NOT_EMPTY:
+ errno = ENOTEMPTY;
+ break;
+ default:
+ errno = EINVAL;
+ }
+}
+
+GIT_INLINE(bool) last_error_retryable(void)
+{
+ int os_error = GetLastError();
+
+ return (os_error == ERROR_SHARING_VIOLATION ||
+ os_error == ERROR_ACCESS_DENIED);
+}
+
+#define do_with_retries(fn, remediation) \
+ do { \
+ int __retry, __ret; \
+ for (__retry = git_win32__retries; __retry; __retry--) { \
+ if ((__ret = (fn)) != GIT_RETRY) \
+ return __ret; \
+ if (__retry > 1 && (__ret = (remediation)) != 0) { \
+ if (__ret == GIT_RETRY) \
+ continue; \
+ return __ret; \
+ } \
+ Sleep(5); \
+ } \
+ return -1; \
+ } while (0) \
+
+static int ensure_writable(wchar_t *path)
+{
+ DWORD attrs;
+
+ if ((attrs = GetFileAttributesW(path)) == INVALID_FILE_ATTRIBUTES)
+ goto on_error;
+
+ if ((attrs & FILE_ATTRIBUTE_READONLY) == 0)
+ return 0;
+
+ if (!SetFileAttributesW(path, (attrs & ~FILE_ATTRIBUTE_READONLY)))
+ goto on_error;
+
+ return GIT_RETRY;
+
+on_error:
+ set_errno();
+ return -1;
+}
+
+/**
+ * Truncate or extend file.
+ *
+ * We now take a "git_off_t" rather than "long" because
+ * files may be longer than 2Gb.
+ */
+int p_ftruncate(int fd, off64_t size)
+{
+ if (size < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+#if !defined(__MINGW32__) || defined(MINGW_HAS_SECURE_API)
+ return ((_chsize_s(fd, size) == 0) ? 0 : -1);
+#else
+ /* TODO MINGW32 Find a replacement for _chsize() that handles big files. */
+ if (size > INT32_MAX) {
+ errno = EFBIG;
+ return -1;
+ }
+ return _chsize(fd, (long)size);
+#endif
+}
+
+int p_mkdir(const char *path, mode_t mode)
+{
+ git_win32_path buf;
+
+ GIT_UNUSED(mode);
+
+ if (git_win32_path_from_utf8(buf, path) < 0)
+ return -1;
+
+ return _wmkdir(buf);
+}
+
+int p_link(const char *old, const char *new)
+{
+ GIT_UNUSED(old);
+ GIT_UNUSED(new);
+ errno = ENOSYS;
+ return -1;
+}
+
+GIT_INLINE(int) unlink_once(const wchar_t *path)
+{
+ DWORD error;
+
+ if (DeleteFileW(path))
+ return 0;
+
+ if ((error = GetLastError()) == ERROR_ACCESS_DENIED) {
+ WIN32_FILE_ATTRIBUTE_DATA fdata;
+ if (!GetFileAttributesExW(path, GetFileExInfoStandard, &fdata) ||
+ !(fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) ||
+ !(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
+ goto out;
+
+ if (RemoveDirectoryW(path))
+ return 0;
+ }
+
+out:
+ SetLastError(error);
+
+ if (last_error_retryable())
+ return GIT_RETRY;
+
+ set_errno();
+ return -1;
+}
+
+int p_unlink(const char *path)
+{
+ git_win32_path wpath;
+
+ if (git_win32_path_from_utf8(wpath, path) < 0)
+ return -1;
+
+ do_with_retries(unlink_once(wpath), ensure_writable(wpath));
+}
+
+int p_fsync(int fd)
+{
+ HANDLE fh = (HANDLE)_get_osfhandle(fd);
+
+ p_fsync__cnt++;
+
+ if (fh == INVALID_HANDLE_VALUE) {
+ errno = EBADF;
+ return -1;
+ }
+
+ if (!FlushFileBuffers(fh)) {
+ DWORD code = GetLastError();
+
+ if (code == ERROR_INVALID_HANDLE)
+ errno = EINVAL;
+ else
+ errno = EIO;
+
+ return -1;
+ }
+
+ return 0;
+}
+
+#define WIN32_IS_WSEP(CH) ((CH) == L'/' || (CH) == L'\\')
+
+static int lstat_w(
+ wchar_t *path,
+ struct stat *buf,
+ bool posix_enotdir)
+{
+ WIN32_FILE_ATTRIBUTE_DATA fdata;
+
+ if (GetFileAttributesExW(path, GetFileExInfoStandard, &fdata)) {
+ if (!buf)
+ return 0;
+
+ return git_win32__file_attribute_to_stat(buf, &fdata, path);
+ }
+
+ switch (GetLastError()) {
+ case ERROR_ACCESS_DENIED:
+ errno = EACCES;
+ break;
+ default:
+ errno = ENOENT;
+ break;
+ }
+
+ /* To match POSIX behavior, set ENOTDIR when any of the folders in the
+ * file path is a regular file, otherwise set ENOENT.
+ */
+ if (errno == ENOENT && posix_enotdir) {
+ size_t path_len = wcslen(path);
+
+ /* scan up path until we find an existing item */
+ while (1) {
+ DWORD attrs;
+
+ /* remove last directory component */
+ for (path_len--; path_len > 0 && !WIN32_IS_WSEP(path[path_len]); path_len--);
+
+ if (path_len <= 0)
+ break;
+
+ path[path_len] = L'\0';
+ attrs = GetFileAttributesW(path);
+
+ if (attrs != INVALID_FILE_ATTRIBUTES) {
+ if (!(attrs & FILE_ATTRIBUTE_DIRECTORY))
+ errno = ENOTDIR;
+ break;
+ }
+ }
+ }
+
+ return -1;
+}
+
+static int do_lstat(const char *path, struct stat *buf, bool posixly_correct)
+{
+ git_win32_path path_w;
+ int len;
+
+ if ((len = git_win32_path_from_utf8(path_w, path)) < 0)
+ return -1;
+
+ git_win32_path_trim_end(path_w, len);
+
+ return lstat_w(path_w, buf, posixly_correct);
+}
+
+int p_lstat(const char *filename, struct stat *buf)
+{
+ return do_lstat(filename, buf, false);
+}
+
+int p_lstat_posixly(const char *filename, struct stat *buf)
+{
+ return do_lstat(filename, buf, true);
+}
+
+int p_readlink(const char *path, char *buf, size_t bufsiz)
+{
+ git_win32_path path_w, target_w;
+ git_win32_utf8_path target;
+ int len;
+
+ /* readlink(2) does not NULL-terminate the string written
+ * to the target buffer. Furthermore, the target buffer need
+ * not be large enough to hold the entire result. A truncated
+ * result should be written in this case. Since this truncation
+ * could occur in the middle of the encoding of a code point,
+ * we need to buffer the result on the stack. */
+
+ if (git_win32_path_from_utf8(path_w, path) < 0 ||
+ git_win32_path_readlink_w(target_w, path_w) < 0 ||
+ (len = git_win32_path_to_utf8(target, target_w)) < 0)
+ return -1;
+
+ bufsiz = min((size_t)len, bufsiz);
+ memcpy(buf, target, bufsiz);
+
+ return (int)bufsiz;
+}
+
+static bool target_is_dir(const char *target, const char *path)
+{
+ git_str resolved = GIT_STR_INIT;
+ git_win32_path resolved_w;
+ bool isdir = true;
+
+ if (git_fs_path_is_absolute(target))
+ git_win32_path_from_utf8(resolved_w, target);
+ else if (git_fs_path_dirname_r(&resolved, path) < 0 ||
+ git_fs_path_apply_relative(&resolved, target) < 0 ||
+ git_win32_path_from_utf8(resolved_w, resolved.ptr) < 0)
+ goto out;
+
+ isdir = GetFileAttributesW(resolved_w) & FILE_ATTRIBUTE_DIRECTORY;
+
+out:
+ git_str_dispose(&resolved);
+ return isdir;
+}
+
+int p_symlink(const char *target, const char *path)
+{
+ git_win32_path target_w, path_w;
+ DWORD dwFlags;
+
+ /*
+ * Convert both target and path to Windows-style paths. Note that we do
+ * not want to use `git_win32_path_from_utf8` for converting the target,
+ * as that function will automatically pre-pend the current working
+ * directory in case the path is not absolute. As Git will instead use
+ * relative symlinks, this is not something we want.
+ */
+ if (git_win32_path_from_utf8(path_w, path) < 0 ||
+ git_win32_path_relative_from_utf8(target_w, target) < 0)
+ return -1;
+
+ dwFlags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
+ if (target_is_dir(target, path))
+ dwFlags |= SYMBOLIC_LINK_FLAG_DIRECTORY;
+
+ if (!CreateSymbolicLinkW(path_w, target_w, dwFlags))
+ return -1;
+
+ return 0;
+}
+
+struct open_opts {
+ DWORD access;
+ DWORD sharing;
+ SECURITY_ATTRIBUTES security;
+ DWORD creation_disposition;
+ DWORD attributes;
+ int osf_flags;
+};
+
+GIT_INLINE(void) open_opts_from_posix(struct open_opts *opts, int flags, mode_t mode)
+{
+ memset(opts, 0, sizeof(struct open_opts));
+
+ switch (flags & (O_WRONLY | O_RDWR)) {
+ case O_WRONLY:
+ opts->access = GENERIC_WRITE;
+ break;
+ case O_RDWR:
+ opts->access = GENERIC_READ | GENERIC_WRITE;
+ break;
+ default:
+ opts->access = GENERIC_READ;
+ break;
+ }
+
+ opts->sharing = (DWORD)git_win32__createfile_sharemode;
+
+ switch (flags & (O_CREAT | O_TRUNC | O_EXCL)) {
+ case O_CREAT | O_EXCL:
+ case O_CREAT | O_TRUNC | O_EXCL:
+ opts->creation_disposition = CREATE_NEW;
+ break;
+ case O_CREAT | O_TRUNC:
+ opts->creation_disposition = CREATE_ALWAYS;
+ break;
+ case O_TRUNC:
+ opts->creation_disposition = TRUNCATE_EXISTING;
+ break;
+ case O_CREAT:
+ opts->creation_disposition = OPEN_ALWAYS;
+ break;
+ default:
+ opts->creation_disposition = OPEN_EXISTING;
+ break;
+ }
+
+ opts->attributes = ((flags & O_CREAT) && !(mode & S_IWRITE)) ?
+ FILE_ATTRIBUTE_READONLY : FILE_ATTRIBUTE_NORMAL;
+ opts->osf_flags = flags & (O_RDONLY | O_APPEND);
+
+ opts->security.nLength = sizeof(SECURITY_ATTRIBUTES);
+ opts->security.lpSecurityDescriptor = NULL;
+ opts->security.bInheritHandle = 0;
+}
+
+GIT_INLINE(int) open_once(
+ const wchar_t *path,
+ struct open_opts *opts)
+{
+ int fd;
+
+ HANDLE handle = CreateFileW(path, opts->access, opts->sharing,
+ &opts->security, opts->creation_disposition, opts->attributes, 0);
+
+ if (handle == INVALID_HANDLE_VALUE) {
+ if (last_error_retryable())
+ return GIT_RETRY;
+
+ set_errno();
+ return -1;
+ }
+
+ if ((fd = _open_osfhandle((intptr_t)handle, opts->osf_flags)) < 0)
+ CloseHandle(handle);
+
+ return fd;
+}
+
+int p_open(const char *path, int flags, ...)
+{
+ git_win32_path wpath;
+ mode_t mode = 0;
+ struct open_opts opts = {0};
+
+ #ifdef GIT_DEBUG_STRICT_OPEN
+ if (strstr(path, "//") != NULL) {
+ errno = EACCES;
+ return -1;
+ }
+ #endif
+
+ if (git_win32_path_from_utf8(wpath, path) < 0)
+ return -1;
+
+ if (flags & O_CREAT) {
+ va_list arg_list;
+
+ va_start(arg_list, flags);
+ mode = (mode_t)va_arg(arg_list, int);
+ va_end(arg_list);
+ }
+
+ open_opts_from_posix(&opts, flags, mode);
+
+ do_with_retries(
+ open_once(wpath, &opts),
+ 0);
+}
+
+int p_creat(const char *path, mode_t mode)
+{
+ return p_open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);
+}
+
+int p_utimes(const char *path, const struct p_timeval times[2])
+{
+ git_win32_path wpath;
+ int fd, error;
+ DWORD attrs_orig, attrs_new = 0;
+ struct open_opts opts = { 0 };
+
+ if (git_win32_path_from_utf8(wpath, path) < 0)
+ return -1;
+
+ attrs_orig = GetFileAttributesW(wpath);
+
+ if (attrs_orig & FILE_ATTRIBUTE_READONLY) {
+ attrs_new = attrs_orig & ~FILE_ATTRIBUTE_READONLY;
+
+ if (!SetFileAttributesW(wpath, attrs_new)) {
+ git_error_set(GIT_ERROR_OS, "failed to set attributes");
+ return -1;
+ }
+ }
+
+ open_opts_from_posix(&opts, O_RDWR, 0);
+
+ if ((fd = open_once(wpath, &opts)) < 0) {
+ error = -1;
+ goto done;
+ }
+
+ error = p_futimes(fd, times);
+ close(fd);
+
+done:
+ if (attrs_orig != attrs_new) {
+ DWORD os_error = GetLastError();
+ SetFileAttributesW(wpath, attrs_orig);
+ SetLastError(os_error);
+ }
+
+ return error;
+}
+
+int p_futimes(int fd, const struct p_timeval times[2])
+{
+ HANDLE handle;
+ FILETIME atime = { 0 }, mtime = { 0 };
+
+ if (times == NULL) {
+ SYSTEMTIME st;
+
+ GetSystemTime(&st);
+ SystemTimeToFileTime(&st, &atime);
+ SystemTimeToFileTime(&st, &mtime);
+ }
+ else {
+ git_win32__timeval_to_filetime(&atime, times[0]);
+ git_win32__timeval_to_filetime(&mtime, times[1]);
+ }
+
+ if ((handle = (HANDLE)_get_osfhandle(fd)) == INVALID_HANDLE_VALUE)
+ return -1;
+
+ if (SetFileTime(handle, NULL, &atime, &mtime) == 0)
+ return -1;
+
+ return 0;
+}
+
+int p_getcwd(char *buffer_out, size_t size)
+{
+ git_win32_path buf;
+ wchar_t *cwd = _wgetcwd(buf, GIT_WIN_PATH_UTF16);
+
+ if (!cwd)
+ return -1;
+
+ git_win32_path_remove_namespace(cwd, wcslen(cwd));
+
+ /* Convert the working directory back to UTF-8 */
+ if (git_utf8_from_16(buffer_out, size, cwd) < 0) {
+ DWORD code = GetLastError();
+
+ if (code == ERROR_INSUFFICIENT_BUFFER)
+ errno = ERANGE;
+ else
+ errno = EINVAL;
+
+ return -1;
+ }
+
+ git_fs_path_mkposix(buffer_out);
+ return 0;
+}
+
+static int getfinalpath_w(
+ git_win32_path dest,
+ const wchar_t *path)
+{
+ HANDLE hFile;
+ DWORD dwChars;
+
+ /* Use FILE_FLAG_BACKUP_SEMANTICS so we can open a directory. Do not
+ * specify FILE_FLAG_OPEN_REPARSE_POINT; we want to open a handle to the
+ * target of the link. */
+ hFile = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE,
+ NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+
+ if (INVALID_HANDLE_VALUE == hFile)
+ return -1;
+
+ /* Call GetFinalPathNameByHandle */
+ dwChars = GetFinalPathNameByHandleW(hFile, dest, GIT_WIN_PATH_UTF16, FILE_NAME_NORMALIZED);
+ CloseHandle(hFile);
+
+ if (!dwChars || dwChars >= GIT_WIN_PATH_UTF16)
+ return -1;
+
+ /* The path may be delivered to us with a namespace prefix; remove */
+ return (int)git_win32_path_remove_namespace(dest, dwChars);
+}
+
+static int follow_and_lstat_link(git_win32_path path, struct stat *buf)
+{
+ git_win32_path target_w;
+
+ if (getfinalpath_w(target_w, path) < 0)
+ return -1;
+
+ return lstat_w(target_w, buf, false);
+}
+
+int p_fstat(int fd, struct stat *buf)
+{
+ BY_HANDLE_FILE_INFORMATION fhInfo;
+
+ HANDLE fh = (HANDLE)_get_osfhandle(fd);
+
+ if (fh == INVALID_HANDLE_VALUE ||
+ !GetFileInformationByHandle(fh, &fhInfo)) {
+ errno = EBADF;
+ return -1;
+ }
+
+ git_win32__file_information_to_stat(buf, &fhInfo);
+ return 0;
+}
+
+int p_stat(const char *path, struct stat *buf)
+{
+ git_win32_path path_w;
+ int len;
+
+ if ((len = git_win32_path_from_utf8(path_w, path)) < 0 ||
+ lstat_w(path_w, buf, false) < 0)
+ return -1;
+
+ /* The item is a symbolic link or mount point. No need to iterate
+ * to follow multiple links; use GetFinalPathNameFromHandle. */
+ if (S_ISLNK(buf->st_mode))
+ return follow_and_lstat_link(path_w, buf);
+
+ return 0;
+}
+
+int p_chdir(const char *path)
+{
+ git_win32_path buf;
+
+ if (git_win32_path_from_utf8(buf, path) < 0)
+ return -1;
+
+ return _wchdir(buf);
+}
+
+int p_chmod(const char *path, mode_t mode)
+{
+ git_win32_path buf;
+
+ if (git_win32_path_from_utf8(buf, path) < 0)
+ return -1;
+
+ return _wchmod(buf, mode);
+}
+
+int p_rmdir(const char *path)
+{
+ git_win32_path buf;
+ int error;
+
+ if (git_win32_path_from_utf8(buf, path) < 0)
+ return -1;
+
+ error = _wrmdir(buf);
+
+ if (error == -1) {
+ switch (GetLastError()) {
+ /* _wrmdir() is documented to return EACCES if "A program has an open
+ * handle to the directory." This sounds like what everybody else calls
+ * EBUSY. Let's convert appropriate error codes.
+ */
+ case ERROR_SHARING_VIOLATION:
+ errno = EBUSY;
+ break;
+
+ /* This error can be returned when trying to rmdir an extant file. */
+ case ERROR_DIRECTORY:
+ errno = ENOTDIR;
+ break;
+ }
+ }
+
+ return error;
+}
+
+char *p_realpath(const char *orig_path, char *buffer)
+{
+ git_win32_path orig_path_w, buffer_w;
+
+ 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
+ * is used to resolve the path -- which is a concurrency issue because the current
+ * 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;
+ else
+ errno = EINVAL;
+
+ return NULL;
+ }
+
+ /* The path must exist. */
+ if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) {
+ errno = ENOENT;
+ return NULL;
+ }
+
+ if (!buffer && !(buffer = git__malloc(GIT_WIN_PATH_UTF8))) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ /* Convert the path to UTF-8. If the caller provided a buffer, then it
+ * is assumed to be GIT_WIN_PATH_UTF8 characters in size. If it isn't,
+ * then we may overflow. */
+ if (git_win32_path_to_utf8(buffer, buffer_w) < 0)
+ return NULL;
+
+ git_fs_path_mkposix(buffer);
+
+ return buffer;
+}
+
+int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr)
+{
+#if defined(_MSC_VER)
+ int len;
+
+ if (count == 0)
+ return _vscprintf(format, argptr);
+
+ #if _MSC_VER >= 1500
+ len = _vsnprintf_s(buffer, count, _TRUNCATE, format, argptr);
+ #else
+ len = _vsnprintf(buffer, count, format, argptr);
+ #endif
+
+ if (len < 0)
+ return _vscprintf(format, argptr);
+
+ return len;
+#else /* MinGW */
+ return vsnprintf(buffer, count, format, argptr);
+#endif
+}
+
+int p_snprintf(char *buffer, size_t count, const char *format, ...)
+{
+ va_list va;
+ int r;
+
+ va_start(va, format);
+ r = p_vsnprintf(buffer, count, format, va);
+ va_end(va);
+
+ return r;
+}
+
+int p_access(const char *path, mode_t mode)
+{
+ git_win32_path buf;
+
+ if (git_win32_path_from_utf8(buf, path) < 0)
+ return -1;
+
+ return _waccess(buf, mode & WIN32_MODE_MASK);
+}
+
+GIT_INLINE(int) rename_once(const wchar_t *from, const wchar_t *to)
+{
+ if (MoveFileExW(from, to, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED))
+ return 0;
+
+ if (last_error_retryable())
+ return GIT_RETRY;
+
+ set_errno();
+ return -1;
+}
+
+int p_rename(const char *from, const char *to)
+{
+ git_win32_path wfrom, wto;
+
+ if (git_win32_path_from_utf8(wfrom, from) < 0 ||
+ git_win32_path_from_utf8(wto, to) < 0)
+ return -1;
+
+ do_with_retries(rename_once(wfrom, wto), ensure_writable(wto));
+}
+
+int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags)
+{
+ if ((size_t)((int)length) != length)
+ return -1; /* git_error_set will be done by caller */
+
+ return recv(socket, buffer, (int)length, flags);
+}
+
+int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags)
+{
+ if ((size_t)((int)length) != length)
+ return -1; /* git_error_set will be done by caller */
+
+ return send(socket, buffer, (int)length, flags);
+}
+
+/**
+ * Borrowed from http://old.nabble.com/Porting-localtime_r-and-gmtime_r-td15282276.html
+ * On Win32, `gmtime_r` doesn't exist but `gmtime` is threadsafe, so we can use that
+ */
+struct tm *
+p_localtime_r (const time_t *timer, struct tm *result)
+{
+ struct tm *local_result;
+ local_result = localtime (timer);
+
+ if (local_result == NULL || result == NULL)
+ return NULL;
+
+ memcpy (result, local_result, sizeof (struct tm));
+ return result;
+}
+struct tm *
+p_gmtime_r (const time_t *timer, struct tm *result)
+{
+ struct tm *local_result;
+ local_result = gmtime (timer);
+
+ if (local_result == NULL || result == NULL)
+ return NULL;
+
+ memcpy (result, local_result, sizeof (struct tm));
+ return result;
+}
+
+int p_inet_pton(int af, const char *src, void *dst)
+{
+ struct sockaddr_storage sin;
+ void *addr;
+ int sin_len = sizeof(struct sockaddr_storage), addr_len;
+ int error = 0;
+
+ if (af == AF_INET) {
+ addr = &((struct sockaddr_in *)&sin)->sin_addr;
+ addr_len = sizeof(struct in_addr);
+ } else if (af == AF_INET6) {
+ addr = &((struct sockaddr_in6 *)&sin)->sin6_addr;
+ addr_len = sizeof(struct in6_addr);
+ } else {
+ errno = EAFNOSUPPORT;
+ return -1;
+ }
+
+ if ((error = WSAStringToAddressA((LPSTR)src, af, NULL, (LPSOCKADDR)&sin, &sin_len)) == 0) {
+ memcpy(dst, addr, addr_len);
+ return 1;
+ }
+
+ switch(WSAGetLastError()) {
+ case WSAEINVAL:
+ return 0;
+ case WSAEFAULT:
+ errno = ENOSPC;
+ return -1;
+ case WSA_NOT_ENOUGH_MEMORY:
+ errno = ENOMEM;
+ return -1;
+ }
+
+ errno = EINVAL;
+ return -1;
+}
+
+ssize_t p_pread(int fd, void *data, size_t size, off64_t offset)
+{
+ HANDLE fh;
+ DWORD rsize = 0;
+ OVERLAPPED ov = {0};
+ LARGE_INTEGER pos = {0};
+ off64_t final_offset = 0;
+
+ /* Fail if the final offset would have overflowed to match POSIX semantics. */
+ if (!git__is_ssizet(size) || git__add_int64_overflow(&final_offset, offset, (int64_t)size)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /*
+ * Truncate large writes to the maximum allowable size: the caller
+ * needs to always call this in a loop anyways.
+ */
+ if (size > INT32_MAX) {
+ size = INT32_MAX;
+ }
+
+ pos.QuadPart = offset;
+ ov.Offset = pos.LowPart;
+ ov.OffsetHigh = pos.HighPart;
+ fh = (HANDLE)_get_osfhandle(fd);
+
+ if (ReadFile(fh, data, (DWORD)size, &rsize, &ov)) {
+ return (ssize_t)rsize;
+ }
+
+ set_errno();
+ return -1;
+}
+
+ssize_t p_pwrite(int fd, const void *data, size_t size, off64_t offset)
+{
+ HANDLE fh;
+ DWORD wsize = 0;
+ OVERLAPPED ov = {0};
+ LARGE_INTEGER pos = {0};
+ off64_t final_offset = 0;
+
+ /* Fail if the final offset would have overflowed to match POSIX semantics. */
+ if (!git__is_ssizet(size) || git__add_int64_overflow(&final_offset, offset, (int64_t)size)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /*
+ * Truncate large writes to the maximum allowable size: the caller
+ * needs to always call this in a loop anyways.
+ */
+ if (size > INT32_MAX) {
+ size = INT32_MAX;
+ }
+
+ pos.QuadPart = offset;
+ ov.Offset = pos.LowPart;
+ ov.OffsetHigh = pos.HighPart;
+ fh = (HANDLE)_get_osfhandle(fd);
+
+ if (WriteFile(fh, data, (DWORD)size, &wsize, &ov)) {
+ return (ssize_t)wsize;
+ }
+
+ set_errno();
+ return -1;
+}
diff --git a/src/util/win32/precompiled.c b/src/util/win32/precompiled.c
new file mode 100644
index 0000000..5f656a4
--- /dev/null
+++ b/src/util/win32/precompiled.c
@@ -0,0 +1 @@
+#include "precompiled.h"
diff --git a/src/util/win32/precompiled.h b/src/util/win32/precompiled.h
new file mode 100644
index 0000000..1163c3d
--- /dev/null
+++ b/src/util/win32/precompiled.h
@@ -0,0 +1,21 @@
+#include "git2_util.h"
+
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <time.h>
+#include <stdarg.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <io.h>
+#include <direct.h>
+#ifdef GIT_THREADS
+ #include "win32/thread.h"
+#endif
+
+#include "git2.h"
diff --git a/src/util/win32/reparse.h b/src/util/win32/reparse.h
new file mode 100644
index 0000000..2331231
--- /dev/null
+++ b/src/util/win32/reparse.h
@@ -0,0 +1,57 @@
+/*
+* 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_win32_reparse_h__
+#define INCLUDE_win32_reparse_h__
+
+/* This structure is defined on MSDN at
+* http://msdn.microsoft.com/en-us/library/windows/hardware/ff552012(v=vs.85).aspx
+*
+* It was formerly included in the Windows 2000 SDK and remains defined in
+* MinGW, so we must define it with a silly name to avoid conflicting.
+*/
+typedef struct _GIT_REPARSE_DATA_BUFFER {
+ ULONG ReparseTag;
+ USHORT ReparseDataLength;
+ USHORT Reserved;
+ union {
+ struct {
+ USHORT SubstituteNameOffset;
+ USHORT SubstituteNameLength;
+ USHORT PrintNameOffset;
+ USHORT PrintNameLength;
+ ULONG Flags;
+ WCHAR PathBuffer[1];
+ } SymbolicLink;
+ struct {
+ USHORT SubstituteNameOffset;
+ USHORT SubstituteNameLength;
+ USHORT PrintNameOffset;
+ USHORT PrintNameLength;
+ WCHAR PathBuffer[1];
+ } MountPoint;
+ struct {
+ UCHAR DataBuffer[1];
+ } Generic;
+ } ReparseBuffer;
+} GIT_REPARSE_DATA_BUFFER;
+
+#define REPARSE_DATA_HEADER_SIZE 8
+#define REPARSE_DATA_MOUNTPOINT_HEADER_SIZE 8
+#define REPARSE_DATA_UNION_SIZE 12
+
+/* Missing in MinGW */
+#ifndef FSCTL_GET_REPARSE_POINT
+# define FSCTL_GET_REPARSE_POINT 0x000900a8
+#endif
+
+/* Missing in MinGW */
+#ifndef FSCTL_SET_REPARSE_POINT
+# define FSCTL_SET_REPARSE_POINT 0x000900a4
+#endif
+
+#endif
diff --git a/src/util/win32/thread.c b/src/util/win32/thread.c
new file mode 100644
index 0000000..f5cacd3
--- /dev/null
+++ b/src/util/win32/thread.c
@@ -0,0 +1,262 @@
+/*
+ * 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 "thread.h"
+#include "runtime.h"
+
+#define CLEAN_THREAD_EXIT 0x6F012842
+
+typedef void (WINAPI *win32_srwlock_fn)(GIT_SRWLOCK *);
+
+static win32_srwlock_fn win32_srwlock_initialize;
+static win32_srwlock_fn win32_srwlock_acquire_shared;
+static win32_srwlock_fn win32_srwlock_release_shared;
+static win32_srwlock_fn win32_srwlock_acquire_exclusive;
+static win32_srwlock_fn win32_srwlock_release_exclusive;
+
+static DWORD fls_index;
+
+/* The thread procedure stub used to invoke the caller's procedure
+ * and capture the return value for later collection. Windows will
+ * only hold a DWORD, but we need to be able to store an entire
+ * void pointer. This requires the indirection. */
+static DWORD WINAPI git_win32__threadproc(LPVOID lpParameter)
+{
+ git_thread *thread = lpParameter;
+
+ /* Set the current thread for `git_thread_exit` */
+ FlsSetValue(fls_index, thread);
+
+ thread->result = thread->proc(thread->param);
+
+ return CLEAN_THREAD_EXIT;
+}
+
+static void git_threads_global_shutdown(void)
+{
+ FlsFree(fls_index);
+}
+
+int git_threads_global_init(void)
+{
+ HMODULE hModule = GetModuleHandleW(L"kernel32");
+
+ if (hModule) {
+ win32_srwlock_initialize = (win32_srwlock_fn)(void *)
+ GetProcAddress(hModule, "InitializeSRWLock");
+ win32_srwlock_acquire_shared = (win32_srwlock_fn)(void *)
+ GetProcAddress(hModule, "AcquireSRWLockShared");
+ win32_srwlock_release_shared = (win32_srwlock_fn)(void *)
+ GetProcAddress(hModule, "ReleaseSRWLockShared");
+ win32_srwlock_acquire_exclusive = (win32_srwlock_fn)(void *)
+ GetProcAddress(hModule, "AcquireSRWLockExclusive");
+ win32_srwlock_release_exclusive = (win32_srwlock_fn)(void *)
+ GetProcAddress(hModule, "ReleaseSRWLockExclusive");
+ }
+
+ if ((fls_index = FlsAlloc(NULL)) == FLS_OUT_OF_INDEXES)
+ return -1;
+
+ return git_runtime_shutdown_register(git_threads_global_shutdown);
+}
+
+int git_thread_create(
+ git_thread *GIT_RESTRICT thread,
+ void *(*start_routine)(void*),
+ void *GIT_RESTRICT arg)
+{
+ thread->result = NULL;
+ thread->param = arg;
+ thread->proc = start_routine;
+ thread->thread = CreateThread(
+ NULL, 0, git_win32__threadproc, thread, 0, NULL);
+
+ return thread->thread ? 0 : -1;
+}
+
+int git_thread_join(
+ git_thread *thread,
+ void **value_ptr)
+{
+ DWORD exit;
+
+ if (WaitForSingleObject(thread->thread, INFINITE) != WAIT_OBJECT_0)
+ return -1;
+
+ if (!GetExitCodeThread(thread->thread, &exit)) {
+ CloseHandle(thread->thread);
+ return -1;
+ }
+
+ /* Check for the thread having exited uncleanly. If exit was unclean,
+ * then we don't have a return value to give back to the caller. */
+ GIT_ASSERT(exit == CLEAN_THREAD_EXIT);
+
+ if (value_ptr)
+ *value_ptr = thread->result;
+
+ CloseHandle(thread->thread);
+ return 0;
+}
+
+void git_thread_exit(void *value)
+{
+ git_thread *thread = FlsGetValue(fls_index);
+
+ if (thread)
+ thread->result = value;
+
+ ExitThread(CLEAN_THREAD_EXIT);
+}
+
+size_t git_thread_currentid(void)
+{
+ return GetCurrentThreadId();
+}
+
+int git_mutex_init(git_mutex *GIT_RESTRICT mutex)
+{
+ InitializeCriticalSection(mutex);
+ return 0;
+}
+
+int git_mutex_free(git_mutex *mutex)
+{
+ DeleteCriticalSection(mutex);
+ return 0;
+}
+
+int git_mutex_lock(git_mutex *mutex)
+{
+ EnterCriticalSection(mutex);
+ return 0;
+}
+
+int git_mutex_unlock(git_mutex *mutex)
+{
+ LeaveCriticalSection(mutex);
+ return 0;
+}
+
+int git_cond_init(git_cond *cond)
+{
+ /* This is an auto-reset event. */
+ *cond = CreateEventW(NULL, FALSE, FALSE, NULL);
+ GIT_ASSERT(*cond);
+
+ /* If we can't create the event, claim that the reason was out-of-memory.
+ * The actual reason can be fetched with GetLastError(). */
+ return *cond ? 0 : ENOMEM;
+}
+
+int git_cond_free(git_cond *cond)
+{
+ BOOL closed;
+
+ if (!cond)
+ return EINVAL;
+
+ closed = CloseHandle(*cond);
+ GIT_ASSERT(closed);
+ GIT_UNUSED(closed);
+
+ *cond = NULL;
+ return 0;
+}
+
+int git_cond_wait(git_cond *cond, git_mutex *mutex)
+{
+ int error;
+ DWORD wait_result;
+
+ if (!cond || !mutex)
+ return EINVAL;
+
+ /* The caller must be holding the mutex. */
+ error = git_mutex_unlock(mutex);
+
+ if (error)
+ return error;
+
+ wait_result = WaitForSingleObject(*cond, INFINITE);
+ GIT_ASSERT(WAIT_OBJECT_0 == wait_result);
+ GIT_UNUSED(wait_result);
+
+ return git_mutex_lock(mutex);
+}
+
+int git_cond_signal(git_cond *cond)
+{
+ BOOL signaled;
+
+ if (!cond)
+ return EINVAL;
+
+ signaled = SetEvent(*cond);
+ GIT_ASSERT(signaled);
+ GIT_UNUSED(signaled);
+
+ return 0;
+}
+
+int git_rwlock_init(git_rwlock *GIT_RESTRICT lock)
+{
+ if (win32_srwlock_initialize)
+ win32_srwlock_initialize(&lock->native.srwl);
+ else
+ InitializeCriticalSection(&lock->native.csec);
+
+ return 0;
+}
+
+int git_rwlock_rdlock(git_rwlock *lock)
+{
+ if (win32_srwlock_acquire_shared)
+ win32_srwlock_acquire_shared(&lock->native.srwl);
+ else
+ EnterCriticalSection(&lock->native.csec);
+
+ return 0;
+}
+
+int git_rwlock_rdunlock(git_rwlock *lock)
+{
+ if (win32_srwlock_release_shared)
+ win32_srwlock_release_shared(&lock->native.srwl);
+ else
+ LeaveCriticalSection(&lock->native.csec);
+
+ return 0;
+}
+
+int git_rwlock_wrlock(git_rwlock *lock)
+{
+ if (win32_srwlock_acquire_exclusive)
+ win32_srwlock_acquire_exclusive(&lock->native.srwl);
+ else
+ EnterCriticalSection(&lock->native.csec);
+
+ return 0;
+}
+
+int git_rwlock_wrunlock(git_rwlock *lock)
+{
+ if (win32_srwlock_release_exclusive)
+ win32_srwlock_release_exclusive(&lock->native.srwl);
+ else
+ LeaveCriticalSection(&lock->native.csec);
+
+ return 0;
+}
+
+int git_rwlock_free(git_rwlock *lock)
+{
+ if (!win32_srwlock_initialize)
+ DeleteCriticalSection(&lock->native.csec);
+ git__memzero(lock, sizeof(*lock));
+ return 0;
+}
diff --git a/src/util/win32/thread.h b/src/util/win32/thread.h
new file mode 100644
index 0000000..184762e
--- /dev/null
+++ b/src/util/win32/thread.h
@@ -0,0 +1,64 @@
+/*
+ * 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_win32_thread_h__
+#define INCLUDE_win32_thread_h__
+
+#include "git2_util.h"
+
+#if defined (_MSC_VER)
+# define GIT_RESTRICT __restrict
+#else
+# define GIT_RESTRICT __restrict__
+#endif
+
+typedef struct {
+ HANDLE thread;
+ void *(*proc)(void *);
+ void *param;
+ void *result;
+} git_thread;
+
+typedef CRITICAL_SECTION git_mutex;
+typedef HANDLE git_cond;
+
+typedef struct { void *Ptr; } GIT_SRWLOCK;
+
+typedef struct {
+ union {
+ GIT_SRWLOCK srwl;
+ CRITICAL_SECTION csec;
+ } native;
+} git_rwlock;
+
+int git_threads_global_init(void);
+
+int git_thread_create(git_thread *GIT_RESTRICT,
+ void *(*) (void *),
+ void *GIT_RESTRICT);
+int git_thread_join(git_thread *, void **);
+size_t git_thread_currentid(void);
+void git_thread_exit(void *);
+
+int git_mutex_init(git_mutex *GIT_RESTRICT mutex);
+int git_mutex_free(git_mutex *);
+int git_mutex_lock(git_mutex *);
+int git_mutex_unlock(git_mutex *);
+
+int git_cond_init(git_cond *);
+int git_cond_free(git_cond *);
+int git_cond_wait(git_cond *, git_mutex *);
+int git_cond_signal(git_cond *);
+
+int git_rwlock_init(git_rwlock *GIT_RESTRICT lock);
+int git_rwlock_rdlock(git_rwlock *);
+int git_rwlock_rdunlock(git_rwlock *);
+int git_rwlock_wrlock(git_rwlock *);
+int git_rwlock_wrunlock(git_rwlock *);
+int git_rwlock_free(git_rwlock *);
+
+#endif
diff --git a/src/util/win32/utf-conv.c b/src/util/win32/utf-conv.c
new file mode 100644
index 0000000..ad35c0c
--- /dev/null
+++ b/src/util/win32/utf-conv.c
@@ -0,0 +1,144 @@
+/*
+ * 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 "utf-conv.h"
+
+GIT_INLINE(void) git__set_errno(void)
+{
+ if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
+ errno = ENAMETOOLONG;
+ else
+ errno = EINVAL;
+}
+
+int git_utf8_to_16(wchar_t *dest, size_t dest_size, const char *src)
+{
+ /* Length of -1 indicates NULL termination of the input string. */
+ return git_utf8_to_16_with_len(dest, dest_size, src, -1);
+}
+
+int git_utf8_to_16_with_len(
+ wchar_t *dest,
+ size_t _dest_size,
+ const char *src,
+ int src_len)
+{
+ int dest_size = (int)min(_dest_size, INT_MAX);
+ int len;
+
+ /*
+ * Subtract 1 from the result to turn 0 into -1 (an error code) and
+ * to not count the NULL terminator as part of the string's length.
+ * MultiByteToWideChar never returns int's minvalue, so underflow
+ * is not possible.
+ */
+ len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
+ src, src_len, dest, dest_size) - 1;
+
+ if (len < 0)
+ git__set_errno();
+
+ return len;
+}
+
+int git_utf8_from_16(char *dest, size_t dest_size, const wchar_t *src)
+{
+ /* Length of -1 indicates NULL termination of the input string. */
+ return git_utf8_from_16_with_len(dest, dest_size, src, -1);
+}
+
+int git_utf8_from_16_with_len(
+ char *dest,
+ size_t _dest_size,
+ const wchar_t *src,
+ int src_len)
+{
+ int dest_size = (int)min(_dest_size, INT_MAX);
+ int len;
+
+ /*
+ * Subtract 1 from the result to turn 0 into -1 (an error code) and
+ * to not count the NULL terminator as part of the string's length.
+ * WideCharToMultiByte never returns int's minvalue, so underflow
+ * is not possible.
+ */
+ len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
+ src, src_len, dest, dest_size, NULL, NULL) - 1;
+
+ if (len < 0)
+ git__set_errno();
+
+ return len;
+}
+
+int git_utf8_to_16_alloc(wchar_t **dest, const char *src)
+{
+ /* Length of -1 indicates NULL termination of the input string. */
+ return git_utf8_to_16_alloc_with_len(dest, src, -1);
+}
+
+int git_utf8_to_16_alloc_with_len(wchar_t **dest, const char *src, int src_len)
+{
+ int utf16_size;
+
+ *dest = NULL;
+
+ utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
+ src, src_len, NULL, 0);
+
+ if (!utf16_size) {
+ git__set_errno();
+ return -1;
+ }
+
+ *dest = git__mallocarray(utf16_size, sizeof(wchar_t));
+ GIT_ERROR_CHECK_ALLOC(*dest);
+
+ utf16_size = git_utf8_to_16_with_len(*dest, (size_t)utf16_size,
+ src, src_len);
+
+ if (utf16_size < 0) {
+ git__free(*dest);
+ *dest = NULL;
+ }
+
+ return utf16_size;
+}
+
+int git_utf8_from_16_alloc(char **dest, const wchar_t *src)
+{
+ /* Length of -1 indicates NULL termination of the input string. */
+ return git_utf8_from_16_alloc_with_len(dest, src, -1);
+}
+
+int git_utf8_from_16_alloc_with_len(char **dest, const wchar_t *src, int src_len)
+{
+ int utf8_size;
+
+ *dest = NULL;
+
+ utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
+ src, src_len, NULL, 0, NULL, NULL);
+
+ if (!utf8_size) {
+ git__set_errno();
+ return -1;
+ }
+
+ *dest = git__malloc(utf8_size);
+ GIT_ERROR_CHECK_ALLOC(*dest);
+
+ utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
+ src, src_len, *dest, utf8_size, NULL, NULL);
+
+ if (utf8_size < 0) {
+ git__free(*dest);
+ *dest = NULL;
+ }
+
+ return utf8_size;
+}
diff --git a/src/util/win32/utf-conv.h b/src/util/win32/utf-conv.h
new file mode 100644
index 0000000..301f5a6
--- /dev/null
+++ b/src/util/win32/utf-conv.h
@@ -0,0 +1,127 @@
+/*
+ * 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_win32_utf_conv_h__
+#define INCLUDE_win32_utf_conv_h__
+
+#include "git2_util.h"
+
+#include <wchar.h>
+
+#ifndef WC_ERR_INVALID_CHARS
+# define WC_ERR_INVALID_CHARS 0x80
+#endif
+
+/**
+ * Converts a NUL-terminated UTF-8 string to wide characters. This is a
+ * convenience function for `git_utf8_to_16_with_len`.
+ *
+ * @param dest The buffer to receive the wide string.
+ * @param dest_size The size of the buffer, in characters.
+ * @param src The UTF-8 string to convert.
+ * @return The length of the wide string, in characters
+ * (not counting the NULL terminator), or < 0 for failure
+ */
+int git_utf8_to_16(wchar_t *dest, size_t dest_size, const char *src);
+
+/**
+ * Converts a UTF-8 string to wide characters.
+ *
+ * @param dest The buffer to receive the wide string.
+ * @param dest_size The size of the buffer, in characters.
+ * @param src The UTF-8 string to convert.
+ * @param src_len The length of the string to convert.
+ * @return The length of the wide string, in characters
+ * (not counting the NULL terminator), or < 0 for failure
+ */
+int git_utf8_to_16_with_len(
+ wchar_t *dest,
+ size_t dest_size,
+ const char *src,
+ int src_len);
+
+/**
+ * Converts a NUL-terminated wide string to UTF-8. This is a convenience
+ * function for `git_utf8_from_16_with_len`.
+ *
+ * @param dest The buffer to receive the UTF-8 string.
+ * @param dest_size The size of the buffer, in bytes.
+ * @param src The wide string to convert.
+ * @param src_len The length of the string to convert.
+ * @return The length of the UTF-8 string, in bytes
+ * (not counting the NULL terminator), or < 0 for failure
+ */
+int git_utf8_from_16(char *dest, size_t dest_size, const wchar_t *src);
+
+/**
+ * Converts a wide string to UTF-8.
+ *
+ * @param dest The buffer to receive the UTF-8 string.
+ * @param dest_size The size of the buffer, in bytes.
+ * @param src The wide string to convert.
+ * @param src_len The length of the string to convert.
+ * @return The length of the UTF-8 string, in bytes
+ * (not counting the NULL terminator), or < 0 for failure
+ */
+int git_utf8_from_16_with_len(char *dest, size_t dest_size, const wchar_t *src, int src_len);
+
+/**
+ * Converts a UTF-8 string to wide characters. Memory is allocated to hold
+ * the converted string. The caller is responsible for freeing the string
+ * with git__free.
+ *
+ * @param dest Receives a pointer to the wide string.
+ * @param src The UTF-8 string to convert.
+ * @return The length of the wide string, in characters
+ * (not counting the NULL terminator), or < 0 for failure
+ */
+int git_utf8_to_16_alloc(wchar_t **dest, const char *src);
+
+/**
+ * Converts a UTF-8 string to wide characters. Memory is allocated to hold
+ * the converted string. The caller is responsible for freeing the string
+ * with git__free.
+ *
+ * @param dest Receives a pointer to the wide string.
+ * @param src The UTF-8 string to convert.
+ * @param src_len The length of the string.
+ * @return The length of the wide string, in characters
+ * (not counting the NULL terminator), or < 0 for failure
+ */
+int git_utf8_to_16_alloc_with_len(
+ wchar_t **dest,
+ const char *src,
+ int src_len);
+
+/**
+ * Converts a wide string to UTF-8. Memory is allocated to hold the
+ * converted string. The caller is responsible for freeing the string
+ * with git__free.
+ *
+ * @param dest Receives a pointer to the UTF-8 string.
+ * @param src The wide string to convert.
+ * @return The length of the UTF-8 string, in bytes
+ * (not counting the NULL terminator), or < 0 for failure
+ */
+int git_utf8_from_16_alloc(char **dest, const wchar_t *src);
+
+/**
+ * Converts a wide string to UTF-8. Memory is allocated to hold the
+ * converted string. The caller is responsible for freeing the string
+ * with git__free.
+ *
+ * @param dest Receives a pointer to the UTF-8 string.
+ * @param src The wide string to convert.
+ * @param src_len The length of the wide string.
+ * @return The length of the UTF-8 string, in bytes
+ * (not counting the NULL terminator), or < 0 for failure
+ */
+int git_utf8_from_16_alloc_with_len(
+ char **dest,
+ const wchar_t *src,
+ int src_len);
+
+#endif
diff --git a/src/util/win32/version.h b/src/util/win32/version.h
new file mode 100644
index 0000000..7966769
--- /dev/null
+++ b/src/util/win32/version.h
@@ -0,0 +1,37 @@
+/*
+ * 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_win32_version_h__
+#define INCLUDE_win32_version_h__
+
+#include <windows.h>
+
+GIT_INLINE(int) git_has_win32_version(int major, int minor, int service_pack)
+{
+ OSVERSIONINFOEX version_test = {0};
+ DWORD version_test_mask;
+ DWORDLONG version_condition_mask = 0;
+
+ version_test.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+ version_test.dwMajorVersion = major;
+ version_test.dwMinorVersion = minor;
+ version_test.wServicePackMajor = (WORD)service_pack;
+ version_test.wServicePackMinor = 0;
+
+ version_test_mask = (VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR);
+
+ VER_SET_CONDITION(version_condition_mask, VER_MAJORVERSION, VER_GREATER_EQUAL);
+ VER_SET_CONDITION(version_condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL);
+ VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
+ VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL);
+
+ if (!VerifyVersionInfo(&version_test, version_test_mask, version_condition_mask))
+ return 0;
+
+ return 1;
+}
+
+#endif
diff --git a/src/util/win32/w32_buffer.c b/src/util/win32/w32_buffer.c
new file mode 100644
index 0000000..6fee820
--- /dev/null
+++ b/src/util/win32/w32_buffer.c
@@ -0,0 +1,57 @@
+/*
+ * 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 "w32_buffer.h"
+
+#include "utf-conv.h"
+
+GIT_INLINE(int) handle_wc_error(void)
+{
+ if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
+ errno = ENAMETOOLONG;
+ else
+ errno = EINVAL;
+
+ return -1;
+}
+
+int git_str_put_w(git_str *buf, const wchar_t *string_w, size_t len_w)
+{
+ int utf8_len, utf8_write_len;
+ size_t new_size;
+
+ if (!len_w) {
+ return 0;
+ } else if (len_w > INT_MAX) {
+ git_error_set_oom();
+ return -1;
+ }
+
+ GIT_ASSERT(string_w);
+
+ /* Measure the string necessary for conversion */
+ if ((utf8_len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string_w, (int)len_w, NULL, 0, NULL, NULL)) == 0)
+ return 0;
+
+ GIT_ASSERT(utf8_len > 0);
+
+ GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, (size_t)utf8_len);
+ GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
+
+ if (git_str_grow(buf, new_size) < 0)
+ return -1;
+
+ if ((utf8_write_len = WideCharToMultiByte(
+ CP_UTF8, WC_ERR_INVALID_CHARS, string_w, (int)len_w, &buf->ptr[buf->size], utf8_len, NULL, NULL)) == 0)
+ return handle_wc_error();
+
+ GIT_ASSERT(utf8_write_len == utf8_len);
+
+ buf->size += utf8_write_len;
+ buf->ptr[buf->size] = '\0';
+ return 0;
+}
diff --git a/src/util/win32/w32_buffer.h b/src/util/win32/w32_buffer.h
new file mode 100644
index 0000000..68ea960
--- /dev/null
+++ b/src/util/win32/w32_buffer.h
@@ -0,0 +1,19 @@
+/*
+ * 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_win32_w32_buffer_h__
+#define INCLUDE_win32_w32_buffer_h__
+
+#include "git2_util.h"
+#include "str.h"
+
+/**
+ * Convert a wide character string to UTF-8 and append the results to the
+ * buffer.
+ */
+int git_str_put_w(git_str *buf, const wchar_t *string_w, size_t len_w);
+
+#endif
diff --git a/src/util/win32/w32_common.h b/src/util/win32/w32_common.h
new file mode 100644
index 0000000..c20b3e8
--- /dev/null
+++ b/src/util/win32/w32_common.h
@@ -0,0 +1,48 @@
+/*
+ * 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_win32_w32_common_h__
+#define INCLUDE_win32_w32_common_h__
+
+#include <git2/common.h>
+
+/*
+ * 4096 is the max allowed Git path. `MAX_PATH` (260) is the typical max allowed
+ * Windows path length, however win32 Unicode APIs generally allow up to 32,767
+ * if prefixed with "\\?\" (i.e. converted to an NT-style name).
+ */
+#define GIT_WIN_PATH_MAX GIT_PATH_MAX
+
+/*
+ * Provides a large enough buffer to support Windows Git paths:
+ * GIT_WIN_PATH_MAX is 4096, corresponding to a maximum path length of 4095
+ * characters plus a NULL terminator. Prefixing with "\\?\" adds 4 characters,
+ * but if the original was a UNC path, then we turn "\\server\share" into
+ * "\\?\UNC\server\share". So we replace the first two characters with
+ * 8 characters, a net gain of 6, so the maximum length is GIT_WIN_PATH_MAX+6.
+ */
+#define GIT_WIN_PATH_UTF16 GIT_WIN_PATH_MAX+6
+
+/* Maximum size of a UTF-8 Win32 Git path. We remove the "\\?\" or "\\?\UNC\"
+ * prefixes for presentation, bringing us back to 4095 (non-NULL)
+ * characters. UTF-8 does have 4-byte sequences, but they are encoded in
+ * UTF-16 using surrogate pairs, which takes up the space of two characters.
+ * Two characters in the range U+0800 -> U+FFFF take up more space in UTF-8
+ * (6 bytes) than one surrogate pair (4 bytes).
+ */
+#define GIT_WIN_PATH_UTF8 ((GIT_WIN_PATH_MAX - 1) * 3 + 1)
+
+/*
+ * The length of a Windows "shortname", for 8.3 compatibility.
+ */
+#define GIT_WIN_PATH_SHORTNAME 13
+
+/* Win32 path types */
+typedef wchar_t git_win32_path[GIT_WIN_PATH_UTF16];
+typedef char git_win32_utf8_path[GIT_WIN_PATH_UTF8];
+
+#endif
diff --git a/src/util/win32/w32_leakcheck.c b/src/util/win32/w32_leakcheck.c
new file mode 100644
index 0000000..0f095de
--- /dev/null
+++ b/src/util/win32/w32_leakcheck.c
@@ -0,0 +1,581 @@
+/*
+ * 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 "w32_leakcheck.h"
+
+#if defined(GIT_WIN32_LEAKCHECK)
+
+#include "Windows.h"
+#include "Dbghelp.h"
+#include "win32/posix.h"
+#include "hash.h"
+#include "runtime.h"
+
+/* Stack frames (for stack tracing, below) */
+
+static bool g_win32_stack_initialized = false;
+static HANDLE g_win32_stack_process = INVALID_HANDLE_VALUE;
+static git_win32_leakcheck_stack_aux_cb_alloc g_aux_cb_alloc = NULL;
+static git_win32_leakcheck_stack_aux_cb_lookup g_aux_cb_lookup = NULL;
+
+int git_win32_leakcheck_stack_set_aux_cb(
+ git_win32_leakcheck_stack_aux_cb_alloc cb_alloc,
+ git_win32_leakcheck_stack_aux_cb_lookup cb_lookup)
+{
+ g_aux_cb_alloc = cb_alloc;
+ g_aux_cb_lookup = cb_lookup;
+
+ return 0;
+}
+
+/**
+ * Load symbol table data. This should be done in the primary
+ * thread at startup (under a lock if there are other threads
+ * active).
+ */
+void git_win32_leakcheck_stack_init(void)
+{
+ if (!g_win32_stack_initialized) {
+ g_win32_stack_process = GetCurrentProcess();
+ SymSetOptions(SYMOPT_LOAD_LINES);
+ SymInitialize(g_win32_stack_process, NULL, TRUE);
+ g_win32_stack_initialized = true;
+ }
+}
+
+/**
+ * Cleanup symbol table data. This should be done in the
+ * primary thead at shutdown (under a lock if there are other
+ * threads active).
+ */
+void git_win32_leakcheck_stack_cleanup(void)
+{
+ if (g_win32_stack_initialized) {
+ SymCleanup(g_win32_stack_process);
+ g_win32_stack_process = INVALID_HANDLE_VALUE;
+ g_win32_stack_initialized = false;
+ }
+}
+
+int git_win32_leakcheck_stack_capture(git_win32_leakcheck_stack_raw_data *pdata, int skip)
+{
+ if (!g_win32_stack_initialized) {
+ git_error_set(GIT_ERROR_INVALID, "git_win32_stack not initialized.");
+ return GIT_ERROR;
+ }
+
+ memset(pdata, 0, sizeof(*pdata));
+ pdata->nr_frames = RtlCaptureStackBackTrace(
+ skip+1, GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES, pdata->frames, NULL);
+
+ /* If an "aux" data provider was registered, ask it to capture
+ * whatever data it needs and give us an "aux_id" to it so that
+ * we can refer to it later when reporting.
+ */
+ if (g_aux_cb_alloc)
+ (g_aux_cb_alloc)(&pdata->aux_id);
+
+ return 0;
+}
+
+int git_win32_leakcheck_stack_compare(
+ git_win32_leakcheck_stack_raw_data *d1,
+ git_win32_leakcheck_stack_raw_data *d2)
+{
+ return memcmp(d1, d2, sizeof(*d1));
+}
+
+int git_win32_leakcheck_stack_format(
+ char *pbuf, size_t buf_len,
+ const git_win32_leakcheck_stack_raw_data *pdata,
+ const char *prefix, const char *suffix)
+{
+#define MY_MAX_FILENAME 255
+
+ /* SYMBOL_INFO has char FileName[1] at the end. The docs say to
+ * to malloc it with extra space for your desired max filename.
+ */
+ struct {
+ SYMBOL_INFO symbol;
+ char extra[MY_MAX_FILENAME + 1];
+ } s;
+
+ IMAGEHLP_LINE64 line;
+ size_t buf_used = 0;
+ unsigned int k;
+ char detail[MY_MAX_FILENAME * 2]; /* filename plus space for function name and formatting */
+ size_t detail_len;
+
+ if (!g_win32_stack_initialized) {
+ git_error_set(GIT_ERROR_INVALID, "git_win32_stack not initialized.");
+ return GIT_ERROR;
+ }
+
+ if (!prefix)
+ prefix = "\t";
+ if (!suffix)
+ suffix = "\n";
+
+ memset(pbuf, 0, buf_len);
+
+ memset(&s, 0, sizeof(s));
+ s.symbol.MaxNameLen = MY_MAX_FILENAME;
+ s.symbol.SizeOfStruct = sizeof(SYMBOL_INFO);
+
+ memset(&line, 0, sizeof(line));
+ line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
+
+ for (k=0; k < pdata->nr_frames; k++) {
+ DWORD64 frame_k = (DWORD64)pdata->frames[k];
+ DWORD dwUnused;
+
+ if (SymFromAddr(g_win32_stack_process, frame_k, 0, &s.symbol) &&
+ SymGetLineFromAddr64(g_win32_stack_process, frame_k, &dwUnused, &line)) {
+ const char *pslash;
+ const char *pfile;
+
+ pslash = strrchr(line.FileName, '\\');
+ pfile = ((pslash) ? (pslash+1) : line.FileName);
+ p_snprintf(detail, sizeof(detail), "%s%s:%d> %s%s",
+ prefix, pfile, line.LineNumber, s.symbol.Name, suffix);
+ } else {
+ /* This happens when we cross into another module.
+ * For example, in CLAR tests, this is typically
+ * the CRT startup code. Just print an unknown
+ * frame and continue.
+ */
+ p_snprintf(detail, sizeof(detail), "%s??%s", prefix, suffix);
+ }
+ detail_len = strlen(detail);
+
+ if (buf_len < (buf_used + detail_len + 1)) {
+ /* we don't have room for this frame in the buffer, so just stop. */
+ break;
+ }
+
+ memcpy(&pbuf[buf_used], detail, detail_len);
+ buf_used += detail_len;
+ }
+
+ /* "aux_id" 0 is reserved to mean no aux data. This is needed to handle
+ * allocs that occur before the aux callbacks were registered.
+ */
+ if (pdata->aux_id > 0) {
+ p_snprintf(detail, sizeof(detail), "%saux_id: %d%s",
+ prefix, pdata->aux_id, suffix);
+ detail_len = strlen(detail);
+ if ((buf_used + detail_len + 1) < buf_len) {
+ memcpy(&pbuf[buf_used], detail, detail_len);
+ buf_used += detail_len;
+ }
+
+ /* If an "aux" data provider is still registered, ask it to append its detailed
+ * data to the end of ours using the "aux_id" it gave us when this de-duped
+ * item was created.
+ */
+ if (g_aux_cb_lookup)
+ (g_aux_cb_lookup)(pdata->aux_id, &pbuf[buf_used], (buf_len - buf_used - 1));
+ }
+
+ return GIT_OK;
+}
+
+int git_win32_leakcheck_stack(
+ char * pbuf, size_t buf_len,
+ int skip,
+ const char *prefix, const char *suffix)
+{
+ git_win32_leakcheck_stack_raw_data data;
+ int error;
+
+ if ((error = git_win32_leakcheck_stack_capture(&data, skip)) < 0)
+ return error;
+ if ((error = git_win32_leakcheck_stack_format(pbuf, buf_len, &data, prefix, suffix)) < 0)
+ return error;
+ return 0;
+}
+
+/* Stack tracing */
+
+#define STACKTRACE_UID_LEN (15)
+
+/**
+ * The stacktrace of an allocation can be distilled
+ * to a unique id based upon the stackframe pointers
+ * and ignoring any size arguments. We will use these
+ * UIDs as the (char const*) __FILE__ argument we
+ * give to the CRT malloc routines.
+ */
+typedef struct {
+ char uid[STACKTRACE_UID_LEN + 1];
+} git_win32_leakcheck_stacktrace_uid;
+
+/**
+ * All mallocs with the same stacktrace will be de-duped
+ * and aggregated into this row.
+ */
+typedef struct {
+ git_win32_leakcheck_stacktrace_uid uid; /* must be first */
+ git_win32_leakcheck_stack_raw_data raw_data;
+ unsigned int count_allocs; /* times this alloc signature seen since init */
+ unsigned int count_allocs_at_last_checkpoint; /* times since last mark */
+ unsigned int transient_count_leaks; /* sum of leaks */
+} git_win32_leakcheck_stacktrace_row;
+
+static CRITICAL_SECTION g_crtdbg_stacktrace_cs;
+
+/**
+ * CRTDBG memory leak tracking takes a "char const * const file_name"
+ * and stores the pointer in the heap data (instead of allocing a copy
+ * for itself). Normally, this is not a problem, since we usually pass
+ * in __FILE__. But I'm going to lie to it and pass in the address of
+ * the UID in place of the file_name. Also, I do not want to alloc the
+ * stacktrace data (because we are called from inside our alloc routines).
+ * Therefore, I'm creating a very large static pool array to store row
+ * data. This also eliminates the temptation to realloc it (and move the
+ * UID pointers).
+ *
+ * And to efficiently look for duplicates we need an index on the rows
+ * so we can bsearch it. Again, without mallocing.
+ *
+ * If we observe more than MY_ROW_LIMIT unique malloc signatures, we
+ * fall through and use the traditional __FILE__ processing and don't
+ * try to de-dup them. If your testing hits this limit, just increase
+ * it and try again.
+ */
+
+#define MY_ROW_LIMIT (2 * 1024 * 1024)
+static git_win32_leakcheck_stacktrace_row g_cs_rows[MY_ROW_LIMIT];
+static git_win32_leakcheck_stacktrace_row *g_cs_index[MY_ROW_LIMIT];
+
+static unsigned int g_cs_end = MY_ROW_LIMIT;
+static unsigned int g_cs_ins = 0; /* insertion point == unique allocs seen */
+static unsigned int g_count_total_allocs = 0; /* number of allocs seen */
+static unsigned int g_transient_count_total_leaks = 0; /* number of total leaks */
+static unsigned int g_transient_count_dedup_leaks = 0; /* number of unique leaks */
+static bool g_limit_reached = false; /* had allocs after we filled row table */
+
+static unsigned int g_checkpoint_id = 0; /* to better label leak checkpoints */
+static bool g_transient_leaks_since_mark = false; /* payload for hook */
+
+/**
+ * Compare function for bsearch on g_cs_index table.
+ */
+static int row_cmp(const void *v1, const void *v2)
+{
+ git_win32_leakcheck_stack_raw_data *d1 = (git_win32_leakcheck_stack_raw_data*)v1;
+ git_win32_leakcheck_stacktrace_row *r2 = (git_win32_leakcheck_stacktrace_row *)v2;
+
+ return (git_win32_leakcheck_stack_compare(d1, &r2->raw_data));
+}
+
+/**
+ * Unique insert the new data into the row and index tables.
+ * We have to sort by the stackframe data itself, not the uid.
+ */
+static git_win32_leakcheck_stacktrace_row * insert_unique(
+ const git_win32_leakcheck_stack_raw_data *pdata)
+{
+ size_t pos;
+ if (git__bsearch(g_cs_index, g_cs_ins, pdata, row_cmp, &pos) < 0) {
+ /* Append new unique item to row table. */
+ memcpy(&g_cs_rows[g_cs_ins].raw_data, pdata, sizeof(*pdata));
+ sprintf(g_cs_rows[g_cs_ins].uid.uid, "##%08lx", g_cs_ins);
+
+ /* Insert pointer to it into the proper place in the index table. */
+ if (pos < g_cs_ins)
+ memmove(&g_cs_index[pos+1], &g_cs_index[pos], (g_cs_ins - pos)*sizeof(g_cs_index[0]));
+ g_cs_index[pos] = &g_cs_rows[g_cs_ins];
+
+ g_cs_ins++;
+ }
+
+ g_cs_index[pos]->count_allocs++;
+
+ return g_cs_index[pos];
+}
+
+/**
+ * Hook function to receive leak data from the CRT. (This includes
+ * both "<file_name>:(<line_number>)" data, but also each of the
+ * various headers and fields.
+ *
+ * Scan this for the special "##<pos>" UID forms that we substituted
+ * for the "<file_name>". Map <pos> back to the row data and
+ * increment its leak count.
+ *
+ * See https://msdn.microsoft.com/en-us/library/74kabxyx.aspx
+ *
+ * We suppress the actual crtdbg output.
+ */
+static int __cdecl report_hook(int nRptType, char *szMsg, int *retVal)
+{
+ static int hook_result = TRUE; /* FALSE to get stock dump; TRUE to suppress. */
+ unsigned int pos;
+
+ *retVal = 0; /* do not invoke debugger */
+
+ if ((szMsg[0] != '#') || (szMsg[1] != '#'))
+ return hook_result;
+
+ if (sscanf(&szMsg[2], "%08lx", &pos) < 1)
+ return hook_result;
+ if (pos >= g_cs_ins)
+ return hook_result;
+
+ if (g_transient_leaks_since_mark) {
+ if (g_cs_rows[pos].count_allocs == g_cs_rows[pos].count_allocs_at_last_checkpoint)
+ return hook_result;
+ }
+
+ g_cs_rows[pos].transient_count_leaks++;
+
+ if (g_cs_rows[pos].transient_count_leaks == 1)
+ g_transient_count_dedup_leaks++;
+
+ g_transient_count_total_leaks++;
+
+ return hook_result;
+}
+
+/**
+ * Write leak data to all of the various places we need.
+ * We force the caller to sprintf() the message first
+ * because we want to avoid fprintf() because it allocs.
+ */
+static void my_output(const char *buf)
+{
+ fwrite(buf, strlen(buf), 1, stderr);
+ OutputDebugString(buf);
+}
+
+/**
+ * For each row with leaks, dump a stacktrace for it.
+ */
+static void dump_summary(const char *label)
+{
+ unsigned int k;
+ char buf[10 * 1024];
+
+ if (g_transient_count_total_leaks == 0)
+ return;
+
+ fflush(stdout);
+ fflush(stderr);
+ my_output("\n");
+
+ if (g_limit_reached) {
+ sprintf(buf,
+ "LEAK SUMMARY: de-dup row table[%d] filled. Increase MY_ROW_LIMIT.\n",
+ MY_ROW_LIMIT);
+ my_output(buf);
+ }
+
+ if (!label)
+ label = "";
+
+ if (g_transient_leaks_since_mark) {
+ sprintf(buf, "LEAK CHECKPOINT %d: leaks %d unique %d: %s\n",
+ g_checkpoint_id, g_transient_count_total_leaks, g_transient_count_dedup_leaks, label);
+ my_output(buf);
+ } else {
+ sprintf(buf, "LEAK SUMMARY: TOTAL leaks %d de-duped %d: %s\n",
+ g_transient_count_total_leaks, g_transient_count_dedup_leaks, label);
+ my_output(buf);
+ }
+ my_output("\n");
+
+ for (k = 0; k < g_cs_ins; k++) {
+ if (g_cs_rows[k].transient_count_leaks > 0) {
+ sprintf(buf, "LEAK: %s leaked %d of %d times:\n",
+ g_cs_rows[k].uid.uid,
+ g_cs_rows[k].transient_count_leaks,
+ g_cs_rows[k].count_allocs);
+ my_output(buf);
+
+ if (git_win32_leakcheck_stack_format(
+ buf, sizeof(buf), &g_cs_rows[k].raw_data,
+ NULL, NULL) >= 0) {
+ my_output(buf);
+ }
+
+ my_output("\n");
+ }
+ }
+
+ fflush(stderr);
+}
+
+/**
+ * Initialize our memory leak tracking and de-dup data structures.
+ * This should ONLY be called by git_libgit2_init().
+ */
+void git_win32_leakcheck_stacktrace_init(void)
+{
+ InitializeCriticalSection(&g_crtdbg_stacktrace_cs);
+
+ EnterCriticalSection(&g_crtdbg_stacktrace_cs);
+
+ _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
+
+ _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE);
+ _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE);
+ _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE);
+
+ _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
+ _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
+ _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
+
+ LeaveCriticalSection(&g_crtdbg_stacktrace_cs);
+}
+
+int git_win32_leakcheck_stacktrace_dump(
+ git_win32_leakcheck_stacktrace_options opt,
+ const char *label)
+{
+ _CRT_REPORT_HOOK old;
+ unsigned int k;
+ int r = 0;
+
+#define IS_BIT_SET(o,b) (((o) & (b)) != 0)
+
+ bool b_set_mark = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_SET_MARK);
+ bool b_leaks_since_mark = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK);
+ bool b_leaks_total = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL);
+ bool b_quiet = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET);
+
+ if (b_leaks_since_mark && b_leaks_total) {
+ git_error_set(GIT_ERROR_INVALID, "cannot combine LEAKS_SINCE_MARK and LEAKS_TOTAL.");
+ return GIT_ERROR;
+ }
+ if (!b_set_mark && !b_leaks_since_mark && !b_leaks_total) {
+ git_error_set(GIT_ERROR_INVALID, "nothing to do.");
+ return GIT_ERROR;
+ }
+
+ EnterCriticalSection(&g_crtdbg_stacktrace_cs);
+
+ if (b_leaks_since_mark || b_leaks_total) {
+ /* All variables with "transient" in the name are per-dump counters
+ * and reset before each dump. This lets us handle checkpoints.
+ */
+ g_transient_count_total_leaks = 0;
+ g_transient_count_dedup_leaks = 0;
+ for (k = 0; k < g_cs_ins; k++) {
+ g_cs_rows[k].transient_count_leaks = 0;
+ }
+ }
+
+ g_transient_leaks_since_mark = b_leaks_since_mark;
+
+ old = _CrtSetReportHook(report_hook);
+ _CrtDumpMemoryLeaks();
+ _CrtSetReportHook(old);
+
+ if (b_leaks_since_mark || b_leaks_total) {
+ r = g_transient_count_dedup_leaks;
+
+ if (!b_quiet)
+ dump_summary(label);
+ }
+
+ if (b_set_mark) {
+ for (k = 0; k < g_cs_ins; k++) {
+ g_cs_rows[k].count_allocs_at_last_checkpoint = g_cs_rows[k].count_allocs;
+ }
+
+ g_checkpoint_id++;
+ }
+
+ LeaveCriticalSection(&g_crtdbg_stacktrace_cs);
+
+ return r;
+}
+
+/**
+ * Shutdown our memory leak tracking and dump summary data.
+ * This should ONLY be called by git_libgit2_shutdown().
+ *
+ * We explicitly call _CrtDumpMemoryLeaks() during here so
+ * that we can compute summary data for the leaks. We print
+ * the stacktrace of each unique leak.
+ *
+ * This cleanup does not happen if the app calls exit()
+ * without calling the libgit2 shutdown code.
+ *
+ * This info we print here is independent of any automatic
+ * reporting during exit() caused by _CRTDBG_LEAK_CHECK_DF.
+ * Set it in your app if you also want traditional reporting.
+ */
+void git_win32_leakcheck_stacktrace_cleanup(void)
+{
+ /* At shutdown/cleanup, dump cumulative leak info
+ * with everything since startup. This might generate
+ * extra noise if the caller has been doing checkpoint
+ * dumps, but it might also eliminate some false
+ * positives for resources previously reported during
+ * checkpoints.
+ */
+ git_win32_leakcheck_stacktrace_dump(
+ GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL,
+ "CLEANUP");
+
+ DeleteCriticalSection(&g_crtdbg_stacktrace_cs);
+}
+
+const char *git_win32_leakcheck_stacktrace(int skip, const char *file)
+{
+ git_win32_leakcheck_stack_raw_data new_data;
+ git_win32_leakcheck_stacktrace_row *row;
+ const char * result = file;
+
+ if (git_win32_leakcheck_stack_capture(&new_data, skip+1) < 0)
+ return result;
+
+ EnterCriticalSection(&g_crtdbg_stacktrace_cs);
+
+ if (g_cs_ins < g_cs_end) {
+ row = insert_unique(&new_data);
+ result = row->uid.uid;
+ } else {
+ g_limit_reached = true;
+ }
+
+ g_count_total_allocs++;
+
+ LeaveCriticalSection(&g_crtdbg_stacktrace_cs);
+
+ return result;
+}
+
+static void git_win32_leakcheck_global_shutdown(void)
+{
+ git_win32_leakcheck_stacktrace_cleanup();
+ git_win32_leakcheck_stack_cleanup();
+}
+
+bool git_win32_leakcheck_has_leaks(void)
+{
+ return (g_transient_count_total_leaks > 0);
+}
+
+int git_win32_leakcheck_global_init(void)
+{
+ git_win32_leakcheck_stacktrace_init();
+ git_win32_leakcheck_stack_init();
+
+ return git_runtime_shutdown_register(git_win32_leakcheck_global_shutdown);
+}
+
+#else
+
+int git_win32_leakcheck_global_init(void)
+{
+ return 0;
+}
+
+#endif
diff --git a/src/util/win32/w32_leakcheck.h b/src/util/win32/w32_leakcheck.h
new file mode 100644
index 0000000..82d8638
--- /dev/null
+++ b/src/util/win32/w32_leakcheck.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_win32_leakcheck_h__
+#define INCLUDE_win32_leakcheck_h__
+
+#include "git2_util.h"
+
+/* Initialize the win32 leak checking system. */
+int git_win32_leakcheck_global_init(void);
+
+#if defined(GIT_WIN32_LEAKCHECK)
+
+#include <stdlib.h>
+#include <crtdbg.h>
+
+#include "git2/errors.h"
+#include "strnlen.h"
+
+bool git_win32_leakcheck_has_leaks(void);
+
+/* Stack frames (for stack tracing, below) */
+
+/**
+ * This type defines a callback to be used to augment a C stacktrace
+ * with "aux" data. This can be used, for example, to allow LibGit2Sharp
+ * (or other interpreted consumer libraries) to give us C# stacktrace
+ * data for the PInvoke.
+ *
+ * This callback will be called during crtdbg-instrumented allocs.
+ *
+ * @param aux_id [out] A returned "aux_id" representing a unique
+ * (de-duped at the C# layer) stacktrace. "aux_id" 0 is reserved
+ * to mean no aux stacktrace data.
+ */
+typedef void (*git_win32_leakcheck_stack_aux_cb_alloc)(unsigned int *aux_id);
+
+/**
+ * This type defines a callback to be used to augment the output of
+ * a stacktrace. This will be used to request the C# layer format
+ * the C# stacktrace associated with "aux_id" into the provided
+ * buffer.
+ *
+ * This callback will be called during leak reporting.
+ *
+ * @param aux_id The "aux_id" key associated with a stacktrace.
+ * @param aux_msg A buffer where a formatted message should be written.
+ * @param aux_msg_len The size of the buffer.
+ */
+typedef void (*git_win32_leakcheck_stack_aux_cb_lookup)(unsigned int aux_id, char *aux_msg, size_t aux_msg_len);
+
+/**
+ * Register an "aux" data provider to augment our C stacktrace data.
+ *
+ * This can be used, for example, to allow LibGit2Sharp (or other
+ * interpreted consumer libraries) to give us the C# stacktrace of
+ * the PInvoke.
+ *
+ * If you choose to use this feature, it should be registered during
+ * initialization and not changed for the duration of the process.
+ */
+int git_win32_leakcheck_stack_set_aux_cb(
+ git_win32_leakcheck_stack_aux_cb_alloc cb_alloc,
+ git_win32_leakcheck_stack_aux_cb_lookup cb_lookup);
+
+/**
+ * Maximum number of stackframes to record for a
+ * single stacktrace.
+ */
+#define GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES 30
+
+/**
+ * Wrapper containing the raw unprocessed stackframe
+ * data for a single stacktrace and any "aux_id".
+ *
+ * I put the aux_id first so leaks will be sorted by it.
+ * So, for example, if a specific callstack in C# leaks
+ * a repo handle, all of the pointers within the associated
+ * repo pointer will be grouped together.
+ */
+typedef struct {
+ unsigned int aux_id;
+ unsigned int nr_frames;
+ void *frames[GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES];
+} git_win32_leakcheck_stack_raw_data;
+
+/**
+ * Capture raw stack trace data for the current process/thread.
+ *
+ * @param skip Number of initial frames to skip. Pass 0 to
+ * begin with the caller of this routine. Pass 1 to begin
+ * with its caller. And so on.
+ */
+int git_win32_leakcheck_stack_capture(git_win32_leakcheck_stack_raw_data *pdata, int skip);
+
+/**
+ * Compare 2 raw stacktraces with the usual -1,0,+1 result.
+ * This includes any "aux_id" values in the comparison, so that
+ * our de-dup is also "aux" context relative.
+ */
+int git_win32_leakcheck_stack_compare(
+ git_win32_leakcheck_stack_raw_data *d1,
+ git_win32_leakcheck_stack_raw_data *d2);
+
+/**
+ * Format raw stacktrace data into buffer WITHOUT using any mallocs.
+ *
+ * @param prefix String written before each frame; defaults to "\t".
+ * @param suffix String written after each frame; defaults to "\n".
+ */
+int git_win32_leakcheck_stack_format(
+ char *pbuf, size_t buf_len,
+ const git_win32_leakcheck_stack_raw_data *pdata,
+ const char *prefix, const char *suffix);
+
+/**
+ * Convenience routine to capture and format stacktrace into
+ * a buffer WITHOUT using any mallocs. This is primarily a
+ * wrapper for testing.
+ *
+ * @param skip Number of initial frames to skip. Pass 0 to
+ * begin with the caller of this routine. Pass 1 to begin
+ * with its caller. And so on.
+ * @param prefix String written before each frame; defaults to "\t".
+ * @param suffix String written after each frame; defaults to "\n".
+ */
+int git_win32_leakcheck_stack(
+ char * pbuf, size_t buf_len,
+ int skip,
+ const char *prefix, const char *suffix);
+
+/* Stack tracing */
+
+/* MSVC CRTDBG memory leak reporting.
+ *
+ * We DO NOT use the "_CRTDBG_MAP_ALLOC" macro described in the MSVC
+ * documentation because all allocs/frees in libgit2 already go through
+ * the "git__" routines defined in this file. Simply using the normal
+ * reporting mechanism causes all leaks to be attributed to a routine
+ * here in util.h (ie, the actual call to calloc()) rather than the
+ * caller of git__calloc().
+ *
+ * Therefore, we declare a set of "git__crtdbg__" routines to replace
+ * the corresponding "git__" routines and re-define the "git__" symbols
+ * as macros. This allows us to get and report the file:line info of
+ * the real caller.
+ *
+ * We DO NOT replace the "git__free" routine because it needs to remain
+ * a function pointer because it is used as a function argument when
+ * setting up various structure "destructors".
+ *
+ * We also DO NOT use the "_CRTDBG_MAP_ALLOC" macro because it causes
+ * "free" to be remapped to "_free_dbg" and this causes problems for
+ * structures which define a field named "free".
+ *
+ * Finally, CRTDBG must be explicitly enabled and configured at program
+ * startup. See tests/main.c for an example.
+ */
+
+/**
+ * Checkpoint options.
+ */
+typedef enum git_win32_leakcheck_stacktrace_options {
+ /**
+ * Set checkpoint marker.
+ */
+ GIT_WIN32_LEAKCHECK_STACKTRACE_SET_MARK = (1 << 0),
+
+ /**
+ * Dump leaks since last checkpoint marker.
+ * May not be combined with _LEAKS_TOTAL.
+ *
+ * Note that this may generate false positives for global TLS
+ * error state and other global caches that aren't cleaned up
+ * until the thread/process terminates. So when using this
+ * around a region of interest, also check the final (at exit)
+ * dump before digging into leaks reported here.
+ */
+ GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK = (1 << 1),
+
+ /**
+ * Dump leaks since init. May not be combined
+ * with _LEAKS_SINCE_MARK.
+ */
+ GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL = (1 << 2),
+
+ /**
+ * Suppress printing during dumps.
+ * Just return leak count.
+ */
+ GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET = (1 << 3),
+
+} git_win32_leakcheck_stacktrace_options;
+
+/**
+ * Checkpoint memory state and/or dump unique stack traces of
+ * current memory leaks.
+ *
+ * @return number of unique leaks (relative to requested starting
+ * point) or error.
+ */
+int git_win32_leakcheck_stacktrace_dump(
+ git_win32_leakcheck_stacktrace_options opt,
+ const char *label);
+
+/**
+ * Construct stacktrace and append it to the global buffer.
+ * Return pointer to start of this string. On any error or
+ * lack of buffer space, just return the given file buffer
+ * so it will behave as usual.
+ *
+ * This should ONLY be called by our internal memory allocations
+ * routines.
+ */
+const char *git_win32_leakcheck_stacktrace(int skip, const char *file);
+
+#endif
+#endif
diff --git a/src/util/win32/w32_util.c b/src/util/win32/w32_util.c
new file mode 100644
index 0000000..f5b006a
--- /dev/null
+++ b/src/util/win32/w32_util.c
@@ -0,0 +1,126 @@
+/*
+ * 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 "w32_util.h"
+
+/**
+ * Creates a FindFirstFile(Ex) filter string from a UTF-8 path.
+ * The filter string enumerates all items in the directory.
+ *
+ * @param dest The buffer to receive the filter string.
+ * @param src The UTF-8 path of the directory to enumerate.
+ * @return True if the filter string was created successfully; false otherwise
+ */
+bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src)
+{
+ static const wchar_t suffix[] = L"\\*";
+ int len = git_win32_path_from_utf8(dest, src);
+
+ /* Ensure the path was converted */
+ if (len < 0)
+ return false;
+
+ /* Ensure that the path does not end with a trailing slash,
+ * because we're about to add one. Don't rely our trim_end
+ * helper, because we want to remove the backslash even for
+ * drive letter paths, in this case. */
+ if (len > 0 &&
+ (dest[len - 1] == L'/' || dest[len - 1] == L'\\')) {
+ dest[len - 1] = L'\0';
+ len--;
+ }
+
+ /* Ensure we have enough room to add the suffix */
+ if ((size_t)len >= GIT_WIN_PATH_UTF16 - CONST_STRLEN(suffix))
+ return false;
+
+ wcscat(dest, suffix);
+ return true;
+}
+
+/**
+ * Ensures the given path (file or folder) has the +H (hidden) attribute set.
+ *
+ * @param path The path which should receive the +H bit.
+ * @return 0 on success; -1 on failure
+ */
+int git_win32__set_hidden(const char *path, bool hidden)
+{
+ git_win32_path buf;
+ DWORD attrs, newattrs;
+
+ if (git_win32_path_from_utf8(buf, path) < 0)
+ return -1;
+
+ attrs = GetFileAttributesW(buf);
+
+ /* Ensure the path exists */
+ if (attrs == INVALID_FILE_ATTRIBUTES)
+ return -1;
+
+ if (hidden)
+ newattrs = attrs | FILE_ATTRIBUTE_HIDDEN;
+ else
+ newattrs = attrs & ~FILE_ATTRIBUTE_HIDDEN;
+
+ if (attrs != newattrs && !SetFileAttributesW(buf, newattrs)) {
+ git_error_set(GIT_ERROR_OS, "failed to %s hidden bit for '%s'",
+ hidden ? "set" : "unset", path);
+ return -1;
+ }
+
+ return 0;
+}
+
+int git_win32__hidden(bool *out, const char *path)
+{
+ git_win32_path buf;
+ DWORD attrs;
+
+ if (git_win32_path_from_utf8(buf, path) < 0)
+ return -1;
+
+ attrs = GetFileAttributesW(buf);
+
+ /* Ensure the path exists */
+ if (attrs == INVALID_FILE_ATTRIBUTES)
+ return -1;
+
+ *out = (attrs & FILE_ATTRIBUTE_HIDDEN) ? true : false;
+ return 0;
+}
+
+int git_win32__file_attribute_to_stat(
+ struct stat *st,
+ const WIN32_FILE_ATTRIBUTE_DATA *attrdata,
+ const wchar_t *path)
+{
+ git_win32__stat_init(st,
+ attrdata->dwFileAttributes,
+ attrdata->nFileSizeHigh,
+ attrdata->nFileSizeLow,
+ attrdata->ftCreationTime,
+ attrdata->ftLastAccessTime,
+ attrdata->ftLastWriteTime);
+
+ if (attrdata->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && path) {
+ git_win32_path target;
+
+ if (git_win32_path_readlink_w(target, path) >= 0) {
+ st->st_mode = (st->st_mode & ~S_IFMT) | S_IFLNK;
+
+ /* st_size gets the UTF-8 length of the target name, in bytes,
+ * not counting the NULL terminator */
+ if ((st->st_size = git_utf8_from_16(NULL, 0, target)) < 0) {
+ git_error_set(GIT_ERROR_OS, "could not convert reparse point name for '%ls'", path);
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+}
diff --git a/src/util/win32/w32_util.h b/src/util/win32/w32_util.h
new file mode 100644
index 0000000..5196637
--- /dev/null
+++ b/src/util/win32/w32_util.h
@@ -0,0 +1,144 @@
+/*
+ * 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_win32_w32_util_h__
+#define INCLUDE_win32_w32_util_h__
+
+#include "git2_util.h"
+
+#include "utf-conv.h"
+#include "posix.h"
+#include "path_w32.h"
+
+/*
+
+#include "common.h"
+#include "path.h"
+#include "path_w32.h"
+#include "utf-conv.h"
+#include "posix.h"
+#include "reparse.h"
+#include "dir.h"
+*/
+
+
+GIT_INLINE(bool) git_win32__isalpha(wchar_t c)
+{
+ return ((c >= L'A' && c <= L'Z') || (c >= L'a' && c <= L'z'));
+}
+
+/**
+ * Creates a FindFirstFile(Ex) filter string from a UTF-8 path.
+ * The filter string enumerates all items in the directory.
+ *
+ * @param dest The buffer to receive the filter string.
+ * @param src The UTF-8 path of the directory to enumerate.
+ * @return True if the filter string was created successfully; false otherwise
+ */
+bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src);
+
+/**
+ * Ensures the given path (file or folder) has the +H (hidden) attribute set
+ * or unset.
+ *
+ * @param path The path that should receive the +H bit.
+ * @param hidden true to set +H, false to unset it
+ * @return 0 on success; -1 on failure
+ */
+extern int git_win32__set_hidden(const char *path, bool hidden);
+
+/**
+ * Determines if the given file or folder has the hidden attribute set.
+ * @param hidden pointer to store hidden value
+ * @param path The path that should be queried for hiddenness.
+ * @return 0 on success or an error code.
+ */
+extern int git_win32__hidden(bool *hidden, const char *path);
+
+extern int git_win32__file_attribute_to_stat(
+ struct stat *st,
+ const WIN32_FILE_ATTRIBUTE_DATA *attrdata,
+ const wchar_t *path);
+
+/**
+ * Converts a FILETIME structure to a struct timespec.
+ *
+ * @param FILETIME A pointer to a FILETIME
+ * @param ts A pointer to the timespec structure to fill in
+ */
+GIT_INLINE(void) git_win32__filetime_to_timespec(
+ const FILETIME *ft,
+ struct timespec *ts)
+{
+ int64_t winTime = ((int64_t)ft->dwHighDateTime << 32) + ft->dwLowDateTime;
+ winTime -= INT64_C(116444736000000000); /* Windows to Unix Epoch conversion */
+ ts->tv_sec = (time_t)(winTime / 10000000);
+#ifdef GIT_USE_NSEC
+ ts->tv_nsec = (winTime % 10000000) * 100;
+#else
+ ts->tv_nsec = 0;
+#endif
+}
+
+GIT_INLINE(void) git_win32__timeval_to_filetime(
+ FILETIME *ft, const struct p_timeval tv)
+{
+ int64_t ticks = (tv.tv_sec * INT64_C(10000000)) +
+ (tv.tv_usec * INT64_C(10)) + INT64_C(116444736000000000);
+
+ ft->dwHighDateTime = ((ticks >> 32) & INT64_C(0xffffffff));
+ ft->dwLowDateTime = (ticks & INT64_C(0xffffffff));
+}
+
+GIT_INLINE(void) git_win32__stat_init(
+ struct stat *st,
+ DWORD dwFileAttributes,
+ DWORD nFileSizeHigh,
+ DWORD nFileSizeLow,
+ FILETIME ftCreationTime,
+ FILETIME ftLastAccessTime,
+ FILETIME ftLastWriteTime)
+{
+ mode_t mode = S_IREAD;
+
+ memset(st, 0, sizeof(struct stat));
+
+ if (dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ mode |= S_IFDIR;
+ else
+ mode |= S_IFREG;
+
+ if ((dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0)
+ mode |= S_IWRITE;
+
+ st->st_ino = 0;
+ st->st_gid = 0;
+ st->st_uid = 0;
+ st->st_nlink = 1;
+ st->st_mode = mode;
+ st->st_size = ((int64_t)nFileSizeHigh << 32) + nFileSizeLow;
+ st->st_dev = _getdrive() - 1;
+ st->st_rdev = st->st_dev;
+ git_win32__filetime_to_timespec(&ftLastAccessTime, &(st->st_atim));
+ git_win32__filetime_to_timespec(&ftLastWriteTime, &(st->st_mtim));
+ git_win32__filetime_to_timespec(&ftCreationTime, &(st->st_ctim));
+}
+
+GIT_INLINE(void) git_win32__file_information_to_stat(
+ struct stat *st,
+ const BY_HANDLE_FILE_INFORMATION *fileinfo)
+{
+ git_win32__stat_init(st,
+ fileinfo->dwFileAttributes,
+ fileinfo->nFileSizeHigh,
+ fileinfo->nFileSizeLow,
+ fileinfo->ftCreationTime,
+ fileinfo->ftLastAccessTime,
+ fileinfo->ftLastWriteTime);
+}
+
+#endif
diff --git a/src/util/win32/win32-compat.h b/src/util/win32/win32-compat.h
new file mode 100644
index 0000000..dee40a4
--- /dev/null
+++ b/src/util/win32/win32-compat.h
@@ -0,0 +1,52 @@
+/*
+ * 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_win32_win32_compat_h__
+#define INCLUDE_win32_win32_compat_h__
+
+#include <stdint.h>
+#include <time.h>
+#include <wchar.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+typedef long suseconds_t;
+
+struct p_timeval {
+ time_t tv_sec;
+ suseconds_t tv_usec;
+};
+
+struct p_timespec {
+ time_t tv_sec;
+ long tv_nsec;
+};
+
+#define timespec p_timespec
+
+struct p_stat {
+ _dev_t st_dev;
+ _ino_t st_ino;
+ mode_t st_mode;
+ short st_nlink;
+ short st_uid;
+ short st_gid;
+ _dev_t st_rdev;
+ __int64 st_size;
+ struct timespec st_atim;
+ struct timespec st_mtim;
+ struct timespec st_ctim;
+#define st_atime st_atim.tv_sec
+#define st_mtime st_mtim.tv_sec
+#define st_ctime st_ctim.tv_sec
+#define st_atime_nsec st_atim.tv_nsec
+#define st_mtime_nsec st_mtim.tv_nsec
+#define st_ctime_nsec st_ctim.tv_nsec
+};
+
+#define stat p_stat
+
+#endif
diff --git a/src/util/zstream.c b/src/util/zstream.c
new file mode 100644
index 0000000..cb8b125
--- /dev/null
+++ b/src/util/zstream.c
@@ -0,0 +1,210 @@
+/*
+ * 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 "zstream.h"
+
+#include <zlib.h>
+
+#include "str.h"
+
+#define ZSTREAM_BUFFER_SIZE (1024 * 1024)
+#define ZSTREAM_BUFFER_MIN_EXTRA 8
+
+GIT_INLINE(int) zstream_seterr(git_zstream *zs)
+{
+ switch (zs->zerr) {
+ case Z_OK:
+ case Z_STREAM_END:
+ case Z_BUF_ERROR: /* not fatal; we retry with a larger buffer */
+ return 0;
+ case Z_MEM_ERROR:
+ git_error_set_oom();
+ break;
+ default:
+ if (zs->z.msg)
+ git_error_set_str(GIT_ERROR_ZLIB, zs->z.msg);
+ else
+ git_error_set(GIT_ERROR_ZLIB, "unknown compression error");
+ }
+
+ return -1;
+}
+
+int git_zstream_init(git_zstream *zstream, git_zstream_t type)
+{
+ zstream->type = type;
+
+ if (zstream->type == GIT_ZSTREAM_INFLATE)
+ zstream->zerr = inflateInit(&zstream->z);
+ else
+ zstream->zerr = deflateInit(&zstream->z, Z_DEFAULT_COMPRESSION);
+ return zstream_seterr(zstream);
+}
+
+void git_zstream_free(git_zstream *zstream)
+{
+ if (zstream->type == GIT_ZSTREAM_INFLATE)
+ inflateEnd(&zstream->z);
+ else
+ deflateEnd(&zstream->z);
+}
+
+void git_zstream_reset(git_zstream *zstream)
+{
+ if (zstream->type == GIT_ZSTREAM_INFLATE)
+ inflateReset(&zstream->z);
+ else
+ deflateReset(&zstream->z);
+ zstream->in = NULL;
+ zstream->in_len = 0;
+ zstream->zerr = Z_STREAM_END;
+}
+
+int git_zstream_set_input(git_zstream *zstream, const void *in, size_t in_len)
+{
+ zstream->in = in;
+ zstream->in_len = in_len;
+ zstream->zerr = Z_OK;
+ return 0;
+}
+
+bool git_zstream_done(git_zstream *zstream)
+{
+ return (!zstream->in_len && zstream->zerr == Z_STREAM_END);
+}
+
+bool git_zstream_eos(git_zstream *zstream)
+{
+ return zstream->zerr == Z_STREAM_END;
+}
+
+size_t git_zstream_suggest_output_len(git_zstream *zstream)
+{
+ if (zstream->in_len > ZSTREAM_BUFFER_SIZE)
+ return ZSTREAM_BUFFER_SIZE;
+ else if (zstream->in_len > ZSTREAM_BUFFER_MIN_EXTRA)
+ return zstream->in_len;
+ else
+ return ZSTREAM_BUFFER_MIN_EXTRA;
+}
+
+int git_zstream_get_output_chunk(
+ void *out, size_t *out_len, git_zstream *zstream)
+{
+ size_t in_queued, in_used, out_queued;
+
+ /* set up input data */
+ zstream->z.next_in = (Bytef *)zstream->in;
+
+ /* feed as much data to zlib as it can consume, at most UINT_MAX */
+ if (zstream->in_len > UINT_MAX) {
+ zstream->z.avail_in = UINT_MAX;
+ zstream->flush = Z_NO_FLUSH;
+ } else {
+ zstream->z.avail_in = (uInt)zstream->in_len;
+ zstream->flush = Z_FINISH;
+ }
+ in_queued = (size_t)zstream->z.avail_in;
+
+ /* set up output data */
+ zstream->z.next_out = out;
+ zstream->z.avail_out = (uInt)*out_len;
+
+ if ((size_t)zstream->z.avail_out != *out_len)
+ zstream->z.avail_out = UINT_MAX;
+ out_queued = (size_t)zstream->z.avail_out;
+
+ /* compress next chunk */
+ if (zstream->type == GIT_ZSTREAM_INFLATE)
+ zstream->zerr = inflate(&zstream->z, zstream->flush);
+ else
+ zstream->zerr = deflate(&zstream->z, zstream->flush);
+
+ if (zstream_seterr(zstream))
+ return -1;
+
+ in_used = (in_queued - zstream->z.avail_in);
+ zstream->in_len -= in_used;
+ zstream->in += in_used;
+
+ *out_len = (out_queued - zstream->z.avail_out);
+
+ return 0;
+}
+
+int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream)
+{
+ size_t out_remain = *out_len;
+
+ if (zstream->in_len && zstream->zerr == Z_STREAM_END) {
+ git_error_set(GIT_ERROR_ZLIB, "zlib input had trailing garbage");
+ return -1;
+ }
+
+ while (out_remain > 0 && zstream->zerr != Z_STREAM_END) {
+ size_t out_written = out_remain;
+
+ if (git_zstream_get_output_chunk(out, &out_written, zstream) < 0)
+ return -1;
+
+ out_remain -= out_written;
+ out = ((char *)out) + out_written;
+ }
+
+ /* either we finished the input or we did not flush the data */
+ GIT_ASSERT(zstream->in_len > 0 || zstream->flush == Z_FINISH);
+
+ /* set out_size to number of bytes actually written to output */
+ *out_len = *out_len - out_remain;
+
+ return 0;
+}
+
+static int zstream_buf(git_str *out, const void *in, size_t in_len, git_zstream_t type)
+{
+ git_zstream zs = GIT_ZSTREAM_INIT;
+ int error = 0;
+
+ if ((error = git_zstream_init(&zs, type)) < 0)
+ return error;
+
+ if ((error = git_zstream_set_input(&zs, in, in_len)) < 0)
+ goto done;
+
+ while (!git_zstream_done(&zs)) {
+ size_t step = git_zstream_suggest_output_len(&zs), written;
+
+ if ((error = git_str_grow_by(out, step)) < 0)
+ goto done;
+
+ written = out->asize - out->size;
+
+ if ((error = git_zstream_get_output(
+ out->ptr + out->size, &written, &zs)) < 0)
+ goto done;
+
+ out->size += written;
+ }
+
+ /* NULL terminate for consistency if possible */
+ if (out->size < out->asize)
+ out->ptr[out->size] = '\0';
+
+done:
+ git_zstream_free(&zs);
+ return error;
+}
+
+int git_zstream_deflatebuf(git_str *out, const void *in, size_t in_len)
+{
+ return zstream_buf(out, in, in_len, GIT_ZSTREAM_DEFLATE);
+}
+
+int git_zstream_inflatebuf(git_str *out, const void *in, size_t in_len)
+{
+ return zstream_buf(out, in, in_len, GIT_ZSTREAM_INFLATE);
+}
diff --git a/src/util/zstream.h b/src/util/zstream.h
new file mode 100644
index 0000000..d78b112
--- /dev/null
+++ b/src/util/zstream.h
@@ -0,0 +1,54 @@
+/*
+ * 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_zstream_h__
+#define INCLUDE_zstream_h__
+
+#include "git2_util.h"
+
+#include <zlib.h>
+
+#include "str.h"
+
+typedef enum {
+ GIT_ZSTREAM_INFLATE,
+ GIT_ZSTREAM_DEFLATE
+} git_zstream_t;
+
+typedef struct {
+ z_stream z;
+ git_zstream_t type;
+ const char *in;
+ size_t in_len;
+ int flush;
+ int zerr;
+} git_zstream;
+
+#define GIT_ZSTREAM_INIT {{0}}
+
+int git_zstream_init(git_zstream *zstream, git_zstream_t type);
+void git_zstream_free(git_zstream *zstream);
+
+int git_zstream_set_input(git_zstream *zstream, const void *in, size_t in_len);
+
+size_t git_zstream_suggest_output_len(git_zstream *zstream);
+
+/* get as much output as is available in the input buffer */
+int git_zstream_get_output_chunk(
+ void *out, size_t *out_len, git_zstream *zstream);
+
+/* get all the output from the entire input buffer */
+int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream);
+
+bool git_zstream_done(git_zstream *zstream);
+bool git_zstream_eos(git_zstream *zstream);
+
+void git_zstream_reset(git_zstream *zstream);
+
+int git_zstream_deflatebuf(git_str *out, const void *in, size_t in_len);
+int git_zstream_inflatebuf(git_str *out, const void *in, size_t in_len);
+
+#endif