summaryrefslogtreecommitdiff
path: root/compat
diff options
context:
space:
mode:
Diffstat (limited to 'compat')
-rw-r--r--compat/access.c31
-rw-r--r--compat/apple-common-crypto.h96
-rw-r--r--compat/basename.c66
-rw-r--r--compat/bswap.h147
-rw-r--r--compat/compiler.h41
-rw-r--r--compat/cygwin.c149
-rw-r--r--compat/cygwin.h9
-rw-r--r--compat/fileno.c7
-rw-r--r--compat/fnmatch/fnmatch.c492
-rw-r--r--compat/fnmatch/fnmatch.h84
-rw-r--r--compat/fopen.c4
-rw-r--r--compat/hstrerror.c2
-rw-r--r--compat/inet_ntop.c6
-rw-r--r--compat/linux/procinfo.c176
-rw-r--r--compat/mingw.c2243
-rw-r--r--compat/mingw.h388
-rw-r--r--compat/mmap.c13
-rw-r--r--compat/msvc.h33
-rw-r--r--compat/nedmalloc/Readme.txt2
-rw-r--r--compat/nedmalloc/malloc.c.h37
-rw-r--r--compat/nedmalloc/nedmalloc.c30
-rw-r--r--compat/obstack.c22
-rw-r--r--compat/obstack.h35
-rw-r--r--compat/open.c25
-rw-r--r--compat/poll/poll.c72
-rw-r--r--compat/poll/poll.h18
-rw-r--r--compat/precompose_utf8.c83
-rw-r--r--compat/precompose_utf8.h8
-rw-r--r--compat/qsort_s.c (renamed from compat/qsort.c)27
-rw-r--r--compat/regex/regcomp.c41
-rw-r--r--compat/regex/regex.c9
-rw-r--r--compat/regex/regex.h12
-rw-r--r--compat/regex/regex_internal.c17
-rw-r--r--compat/regex/regex_internal.h6
-rw-r--r--compat/regex/regexec.c25
-rw-r--r--compat/setenv.c2
-rw-r--r--compat/sha1-chunked.c19
-rw-r--r--compat/sha1-chunked.h2
-rw-r--r--compat/simple-ipc/ipc-shared.c32
-rw-r--r--compat/simple-ipc/ipc-unix-socket.c1007
-rw-r--r--compat/simple-ipc/ipc-win32.c900
-rw-r--r--compat/snprintf.c2
-rw-r--r--compat/stat.c48
-rw-r--r--compat/strdup.c11
-rw-r--r--compat/strtok_r.c61
-rw-r--r--compat/stub/procinfo.c11
-rw-r--r--compat/terminal.c324
-rw-r--r--compat/terminal.h6
-rw-r--r--compat/unsetenv.c2
-rw-r--r--compat/vcbuild/.gitignore3
-rw-r--r--compat/vcbuild/README66
-rw-r--r--compat/vcbuild/find_vs_env.bat168
-rw-r--r--compat/vcbuild/include/sys/poll.h1
-rw-r--r--compat/vcbuild/include/unistd.h7
-rwxr-xr-x[-rw-r--r--]compat/vcbuild/scripts/clink.pl93
-rwxr-xr-x[-rw-r--r--]compat/vcbuild/scripts/lib.pl0
-rw-r--r--compat/vcbuild/vcpkg_copy_dlls.bat39
-rw-r--r--compat/vcbuild/vcpkg_install.bat80
-rw-r--r--compat/win32.h2
-rw-r--r--compat/win32/alloca.h (renamed from compat/vcbuild/include/alloca.h)0
-rw-r--r--compat/win32/dirent.c116
-rw-r--r--compat/win32/dirent.h8
-rw-r--r--compat/win32/git.manifest25
-rw-r--r--compat/win32/lazyload.h61
-rw-r--r--compat/win32/path-utils.c52
-rw-r--r--compat/win32/path-utils.h37
-rw-r--r--compat/win32/pthread.c140
-rw-r--r--compat/win32/pthread.h52
-rw-r--r--compat/win32/syslog.c12
-rw-r--r--compat/win32/trace2_win32_process_info.c191
-rw-r--r--compat/win32mmap.c31
-rw-r--r--compat/winansi.c548
72 files changed, 6537 insertions, 2078 deletions
diff --git a/compat/access.c b/compat/access.c
new file mode 100644
index 0000000000..19fda3e877
--- /dev/null
+++ b/compat/access.c
@@ -0,0 +1,31 @@
+#define COMPAT_CODE_ACCESS
+#include "../git-compat-util.h"
+
+/* Do the same thing access(2) does, but use the effective uid,
+ * and don't make the mistake of telling root that any file is
+ * executable. This version uses stat(2).
+ */
+int git_access(const char *path, int mode)
+{
+ struct stat st;
+
+ /* do not interfere a normal user */
+ if (geteuid())
+ return access(path, mode);
+
+ if (stat(path, &st) < 0)
+ return -1;
+
+ /* Root can read or write any file. */
+ if (!(mode & X_OK))
+ return 0;
+
+ /* Root can execute any file that has any one of the execute
+ * bits set.
+ */
+ if (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
+ return 0;
+
+ errno = EACCES;
+ return -1;
+}
diff --git a/compat/apple-common-crypto.h b/compat/apple-common-crypto.h
new file mode 100644
index 0000000000..11727f3e1e
--- /dev/null
+++ b/compat/apple-common-crypto.h
@@ -0,0 +1,96 @@
+/* suppress inclusion of conflicting openssl functions */
+#define OPENSSL_NO_MD5
+#define HEADER_HMAC_H
+#define HEADER_SHA_H
+#include <CommonCrypto/CommonHMAC.h>
+#define EVP_md5(...) kCCHmacAlgMD5
+/* CCHmac doesn't take md_len and the return type is void */
+#define HMAC git_CC_HMAC
+static inline unsigned char *git_CC_HMAC(CCHmacAlgorithm alg,
+ const void *key, int key_len,
+ const unsigned char *data, size_t data_len,
+ unsigned char *md, unsigned int *md_len)
+{
+ CCHmac(alg, key, key_len, data, data_len, md);
+ return md;
+}
+
+#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
+#define APPLE_LION_OR_NEWER
+#include <Security/Security.h>
+/* Apple's TYPE_BOOL conflicts with config.c */
+#undef TYPE_BOOL
+#endif
+
+#ifndef SHA1_MAX_BLOCK_SIZE
+#error Using Apple Common Crypto library requires setting SHA1_MAX_BLOCK_SIZE
+#endif
+
+#ifdef APPLE_LION_OR_NEWER
+#define git_CC_error_check(pattern, err) \
+ do { \
+ if (err) { \
+ die(pattern, (long)CFErrorGetCode(err)); \
+ } \
+ } while(0)
+
+#define EVP_EncodeBlock git_CC_EVP_EncodeBlock
+static inline int git_CC_EVP_EncodeBlock(unsigned char *out,
+ const unsigned char *in, int inlen)
+{
+ CFErrorRef err;
+ SecTransformRef encoder;
+ CFDataRef input, output;
+ CFIndex length;
+
+ encoder = SecEncodeTransformCreate(kSecBase64Encoding, &err);
+ git_CC_error_check("SecEncodeTransformCreate failed: %ld", err);
+
+ input = CFDataCreate(kCFAllocatorDefault, in, inlen);
+ SecTransformSetAttribute(encoder, kSecTransformInputAttributeName,
+ input, &err);
+ git_CC_error_check("SecTransformSetAttribute failed: %ld", err);
+
+ output = SecTransformExecute(encoder, &err);
+ git_CC_error_check("SecTransformExecute failed: %ld", err);
+
+ length = CFDataGetLength(output);
+ CFDataGetBytes(output, CFRangeMake(0, length), out);
+
+ CFRelease(output);
+ CFRelease(input);
+ CFRelease(encoder);
+
+ return (int)strlen((const char *)out);
+}
+
+#define EVP_DecodeBlock git_CC_EVP_DecodeBlock
+static int inline git_CC_EVP_DecodeBlock(unsigned char *out,
+ const unsigned char *in, int inlen)
+{
+ CFErrorRef err;
+ SecTransformRef decoder;
+ CFDataRef input, output;
+ CFIndex length;
+
+ decoder = SecDecodeTransformCreate(kSecBase64Encoding, &err);
+ git_CC_error_check("SecEncodeTransformCreate failed: %ld", err);
+
+ input = CFDataCreate(kCFAllocatorDefault, in, inlen);
+ SecTransformSetAttribute(decoder, kSecTransformInputAttributeName,
+ input, &err);
+ git_CC_error_check("SecTransformSetAttribute failed: %ld", err);
+
+ output = SecTransformExecute(decoder, &err);
+ git_CC_error_check("SecTransformExecute failed: %ld", err);
+
+ length = CFDataGetLength(output);
+ CFDataGetBytes(output, CFRangeMake(0, length), out);
+
+ CFRelease(output);
+ CFRelease(input);
+ CFRelease(decoder);
+
+ return (int)strlen((const char *)out);
+}
+#endif /* APPLE_LION_OR_NEWER */
diff --git a/compat/basename.c b/compat/basename.c
index d8f8a3c6dc..96bd9533b4 100644
--- a/compat/basename.c
+++ b/compat/basename.c
@@ -1,15 +1,71 @@
#include "../git-compat-util.h"
+#include "../strbuf.h"
/* Adapted from libiberty's basename.c. */
char *gitbasename (char *path)
{
const char *base;
- /* Skip over the disk name in MSDOS pathnames. */
- if (has_dos_drive_prefix(path))
- path += 2;
+
+ if (path)
+ skip_dos_drive_prefix(&path);
+
+ if (!path || !*path)
+ return ".";
+
for (base = path; *path; path++) {
- if (is_dir_sep(*path))
- base = path + 1;
+ if (!is_dir_sep(*path))
+ continue;
+ do {
+ path++;
+ } while (is_dir_sep(*path));
+ if (*path)
+ base = path;
+ else
+ while (--path != base && is_dir_sep(*path))
+ *path = '\0';
}
return (char *)base;
}
+
+char *gitdirname(char *path)
+{
+ static struct strbuf buf = STRBUF_INIT;
+ char *p = path, *slash = NULL, c;
+ int dos_drive_prefix;
+
+ if (!p)
+ return ".";
+
+ if ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p)
+ goto dot;
+
+ /*
+ * POSIX.1-2001 says dirname("/") should return "/", and dirname("//")
+ * should return "//", but dirname("///") should return "/" again.
+ */
+ if (is_dir_sep(*p)) {
+ if (!p[1] || (is_dir_sep(p[1]) && !p[2]))
+ return path;
+ slash = ++p;
+ }
+ while ((c = *(p++)))
+ if (is_dir_sep(c)) {
+ char *tentative = p - 1;
+
+ /* POSIX.1-2001 says to ignore trailing slashes */
+ while (is_dir_sep(*p))
+ p++;
+ if (*p)
+ slash = tentative;
+ }
+
+ if (slash) {
+ *slash = '\0';
+ return path;
+ }
+
+dot:
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "%.*s.", dos_drive_prefix, path);
+ return buf.buf;
+}
diff --git a/compat/bswap.h b/compat/bswap.h
index 5061214f73..512f6f4b99 100644
--- a/compat/bswap.h
+++ b/compat/bswap.h
@@ -1,3 +1,6 @@
+#ifndef COMPAT_BSWAP_H
+#define COMPAT_BSWAP_H
+
/*
* Let's make sure we always have a sane definition for ntohl()/htonl().
* Some libraries define those as a function call, just to perform byte
@@ -17,7 +20,20 @@ static inline uint32_t default_swab32(uint32_t val)
((val & 0x000000ff) << 24));
}
+static inline uint64_t default_bswap64(uint64_t val)
+{
+ return (((val & (uint64_t)0x00000000000000ffULL) << 56) |
+ ((val & (uint64_t)0x000000000000ff00ULL) << 40) |
+ ((val & (uint64_t)0x0000000000ff0000ULL) << 24) |
+ ((val & (uint64_t)0x00000000ff000000ULL) << 8) |
+ ((val & (uint64_t)0x000000ff00000000ULL) >> 8) |
+ ((val & (uint64_t)0x0000ff0000000000ULL) >> 24) |
+ ((val & (uint64_t)0x00ff000000000000ULL) >> 40) |
+ ((val & (uint64_t)0xff00000000000000ULL) >> 56));
+}
+
#undef bswap32
+#undef bswap64
#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
@@ -32,15 +48,42 @@ static inline uint32_t git_bswap32(uint32_t x)
return result;
}
-#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
+#define bswap64 git_bswap64
+#if defined(__x86_64__)
+static inline uint64_t git_bswap64(uint64_t x)
+{
+ uint64_t result;
+ if (__builtin_constant_p(x))
+ result = default_bswap64(x);
+ else
+ __asm__("bswap %q0" : "=r" (result) : "0" (x));
+ return result;
+}
+#else
+static inline uint64_t git_bswap64(uint64_t x)
+{
+ union { uint64_t i64; uint32_t i32[2]; } tmp, result;
+ if (__builtin_constant_p(x))
+ result.i64 = default_bswap64(x);
+ else {
+ tmp.i64 = x;
+ result.i32[0] = git_bswap32(tmp.i32[1]);
+ result.i32[1] = git_bswap32(tmp.i32[0]);
+ }
+ return result.i64;
+}
+#endif
+
+#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64) || defined(_M_ARM64))
#include <stdlib.h>
#define bswap32(x) _byteswap_ulong(x)
+#define bswap64(x) _byteswap_uint64(x)
#endif
-#ifdef bswap32
+#if defined(bswap32)
#undef ntohl
#undef htonl
@@ -48,3 +91,103 @@ static inline uint32_t git_bswap32(uint32_t x)
#define htonl(x) bswap32(x)
#endif
+
+#if defined(bswap64)
+
+#undef ntohll
+#undef htonll
+#define ntohll(x) bswap64(x)
+#define htonll(x) bswap64(x)
+
+#else
+
+#undef ntohll
+#undef htonll
+
+#if defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && defined(__BIG_ENDIAN)
+
+# define GIT_BYTE_ORDER __BYTE_ORDER
+# define GIT_LITTLE_ENDIAN __LITTLE_ENDIAN
+# define GIT_BIG_ENDIAN __BIG_ENDIAN
+
+#elif defined(BYTE_ORDER) && defined(LITTLE_ENDIAN) && defined(BIG_ENDIAN)
+
+# define GIT_BYTE_ORDER BYTE_ORDER
+# define GIT_LITTLE_ENDIAN LITTLE_ENDIAN
+# define GIT_BIG_ENDIAN BIG_ENDIAN
+
+#else
+
+# define GIT_BIG_ENDIAN 4321
+# define GIT_LITTLE_ENDIAN 1234
+
+# if defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN)
+# define GIT_BYTE_ORDER GIT_BIG_ENDIAN
+# elif defined(_LITTLE_ENDIAN) && !defined(_BIG_ENDIAN)
+# define GIT_BYTE_ORDER GIT_LITTLE_ENDIAN
+# elif defined(__THW_BIG_ENDIAN__) && !defined(__THW_LITTLE_ENDIAN__)
+# define GIT_BYTE_ORDER GIT_BIG_ENDIAN
+# elif defined(__THW_LITTLE_ENDIAN__) && !defined(__THW_BIG_ENDIAN__)
+# define GIT_BYTE_ORDER GIT_LITTLE_ENDIAN
+# else
+# error "Cannot determine endianness"
+# endif
+
+#endif
+
+#if GIT_BYTE_ORDER == GIT_BIG_ENDIAN
+# define ntohll(n) (n)
+# define htonll(n) (n)
+#else
+# define ntohll(n) default_bswap64(n)
+# define htonll(n) default_bswap64(n)
+#endif
+
+#endif
+
+static inline uint16_t get_be16(const void *ptr)
+{
+ const unsigned char *p = ptr;
+ return (uint16_t)p[0] << 8 |
+ (uint16_t)p[1] << 0;
+}
+
+static inline uint32_t get_be32(const void *ptr)
+{
+ const unsigned char *p = ptr;
+ return (uint32_t)p[0] << 24 |
+ (uint32_t)p[1] << 16 |
+ (uint32_t)p[2] << 8 |
+ (uint32_t)p[3] << 0;
+}
+
+static inline uint64_t get_be64(const void *ptr)
+{
+ const unsigned char *p = ptr;
+ return (uint64_t)get_be32(&p[0]) << 32 |
+ (uint64_t)get_be32(&p[4]) << 0;
+}
+
+static inline void put_be32(void *ptr, uint32_t value)
+{
+ unsigned char *p = ptr;
+ p[0] = value >> 24;
+ p[1] = value >> 16;
+ p[2] = value >> 8;
+ p[3] = value >> 0;
+}
+
+static inline void put_be64(void *ptr, uint64_t value)
+{
+ unsigned char *p = ptr;
+ p[0] = value >> 56;
+ p[1] = value >> 48;
+ p[2] = value >> 40;
+ p[3] = value >> 32;
+ p[4] = value >> 24;
+ p[5] = value >> 16;
+ p[6] = value >> 8;
+ p[7] = value >> 0;
+}
+
+#endif /* COMPAT_BSWAP_H */
diff --git a/compat/compiler.h b/compat/compiler.h
new file mode 100644
index 0000000000..10dbb65937
--- /dev/null
+++ b/compat/compiler.h
@@ -0,0 +1,41 @@
+#ifndef COMPILER_H
+#define COMPILER_H
+
+#include "git-compat-util.h"
+#include "strbuf.h"
+
+#ifdef __GLIBC__
+#include <gnu/libc-version.h>
+#endif
+
+static inline void get_compiler_info(struct strbuf *info)
+{
+ int len = info->len;
+#ifdef __clang__
+ strbuf_addf(info, "clang: %s\n", __clang_version__);
+#elif defined(__GNUC__)
+ strbuf_addf(info, "gnuc: %d.%d\n", __GNUC__, __GNUC_MINOR__);
+#endif
+
+#ifdef _MSC_VER
+ strbuf_addf(info, "MSVC version: %02d.%02d.%05d\n",
+ _MSC_VER / 100, _MSC_VER % 100, _MSC_FULL_VER % 100000);
+#endif
+
+ if (len == info->len)
+ strbuf_addstr(info, _("no compiler information available\n"));
+}
+
+static inline void get_libc_info(struct strbuf *info)
+{
+ int len = info->len;
+
+#ifdef __GLIBC__
+ strbuf_addf(info, "glibc: %s\n", gnu_get_libc_version());
+#endif
+
+ if (len == info->len)
+ strbuf_addstr(info, _("no libc information available\n"));
+}
+
+#endif /* COMPILER_H */
diff --git a/compat/cygwin.c b/compat/cygwin.c
deleted file mode 100644
index 5428858875..0000000000
--- a/compat/cygwin.c
+++ /dev/null
@@ -1,149 +0,0 @@
-#define WIN32_LEAN_AND_MEAN
-#ifdef CYGWIN_V15_WIN32API
-#include "../git-compat-util.h"
-#include "win32.h"
-#else
-#include <sys/stat.h>
-#include <sys/errno.h>
-#include "win32.h"
-#include "../git-compat-util.h"
-#endif
-#include "../cache.h" /* to read configuration */
-
-static inline void filetime_to_timespec(const FILETIME *ft, struct timespec *ts)
-{
- long long winTime = ((long long)ft->dwHighDateTime << 32) +
- ft->dwLowDateTime;
- winTime -= 116444736000000000LL; /* Windows to Unix Epoch conversion */
- /* convert 100-nsecond interval to seconds and nanoseconds */
- ts->tv_sec = (time_t)(winTime/10000000);
- ts->tv_nsec = (long)(winTime - ts->tv_sec*10000000LL) * 100;
-}
-
-#define size_to_blocks(s) (((s)+511)/512)
-
-/* do_stat is a common implementation for cygwin_lstat and cygwin_stat.
- *
- * To simplify its logic, in the case of cygwin symlinks, this implementation
- * falls back to the cygwin version of stat/lstat, which is provided as the
- * last argument.
- */
-static int do_stat(const char *file_name, struct stat *buf, stat_fn_t cygstat)
-{
- WIN32_FILE_ATTRIBUTE_DATA fdata;
-
- if (file_name[0] == '/')
- return cygstat (file_name, buf);
-
- if (!(errno = get_file_attr(file_name, &fdata))) {
- /*
- * If the system attribute is set and it is not a directory then
- * it could be a symbol link created in the nowinsymlinks mode.
- * Normally, Cygwin works in the winsymlinks mode, so this situation
- * is very unlikely. For the sake of simplicity of our code, let's
- * Cygwin to handle it.
- */
- if ((fdata.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) &&
- !(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
- return cygstat(file_name, buf);
-
- /* fill out the stat structure */
- buf->st_dev = buf->st_rdev = 0; /* not used by Git */
- buf->st_ino = 0;
- buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
- buf->st_nlink = 1;
- buf->st_uid = buf->st_gid = 0;
-#ifdef __CYGWIN_USE_BIG_TYPES__
- buf->st_size = ((_off64_t)fdata.nFileSizeHigh << 32) +
- fdata.nFileSizeLow;
-#else
- buf->st_size = (off_t)fdata.nFileSizeLow;
-#endif
- buf->st_blocks = size_to_blocks(buf->st_size);
- filetime_to_timespec(&fdata.ftLastAccessTime, &buf->st_atim);
- filetime_to_timespec(&fdata.ftLastWriteTime, &buf->st_mtim);
- filetime_to_timespec(&fdata.ftCreationTime, &buf->st_ctim);
- return 0;
- } else if (errno == ENOENT) {
- /*
- * In the winsymlinks mode (which is the default), Cygwin
- * emulates symbol links using Windows shortcut files. These
- * files are formed by adding .lnk extension. So, if we have
- * not found the specified file name, it could be that it is
- * a symbol link. Let's Cygwin to deal with that.
- */
- return cygstat(file_name, buf);
- }
- return -1;
-}
-
-/* We provide our own lstat/stat functions, since the provided Cygwin versions
- * of these functions are too slow. These stat functions are tailored for Git's
- * usage, and therefore they are not meant to be complete and correct emulation
- * of lstat/stat functionality.
- */
-static int cygwin_lstat(const char *path, struct stat *buf)
-{
- return do_stat(path, buf, lstat);
-}
-
-static int cygwin_stat(const char *path, struct stat *buf)
-{
- return do_stat(path, buf, stat);
-}
-
-
-/*
- * At start up, we are trying to determine whether Win32 API or cygwin stat
- * functions should be used. The choice is determined by core.ignorecygwinfstricks.
- * Reading this option is not always possible immediately as git_dir may
- * not be set yet. So until it is set, use cygwin lstat/stat functions.
- * However, if core.filemode is set, we must use the Cygwin posix
- * stat/lstat as the Windows stat functions do not determine posix filemode.
- *
- * Note that git_cygwin_config() does NOT call git_default_config() and this
- * is deliberate. Many commands read from config to establish initial
- * values in variables and later tweak them from elsewhere (e.g. command line).
- * init_stat() is called lazily on demand, typically much late in the program,
- * and calling git_default_config() from here would break such variables.
- */
-static int native_stat = 1;
-static int core_filemode = 1; /* matches trust_executable_bit default */
-
-static int git_cygwin_config(const char *var, const char *value, void *cb)
-{
- if (!strcmp(var, "core.ignorecygwinfstricks"))
- native_stat = git_config_bool(var, value);
- else if (!strcmp(var, "core.filemode"))
- core_filemode = git_config_bool(var, value);
- return 0;
-}
-
-static int init_stat(void)
-{
- if (have_git_dir() && git_config(git_cygwin_config,NULL)) {
- if (!core_filemode && native_stat) {
- cygwin_stat_fn = cygwin_stat;
- cygwin_lstat_fn = cygwin_lstat;
- } else {
- cygwin_stat_fn = stat;
- cygwin_lstat_fn = lstat;
- }
- return 0;
- }
- return 1;
-}
-
-static int cygwin_stat_stub(const char *file_name, struct stat *buf)
-{
- return (init_stat() ? stat : *cygwin_stat_fn)(file_name, buf);
-}
-
-static int cygwin_lstat_stub(const char *file_name, struct stat *buf)
-{
- return (init_stat() ? lstat : *cygwin_lstat_fn)(file_name, buf);
-}
-
-stat_fn_t cygwin_stat_fn = cygwin_stat_stub;
-stat_fn_t cygwin_lstat_fn = cygwin_lstat_stub;
-
diff --git a/compat/cygwin.h b/compat/cygwin.h
deleted file mode 100644
index a3229f5b4f..0000000000
--- a/compat/cygwin.h
+++ /dev/null
@@ -1,9 +0,0 @@
-#include <sys/types.h>
-#include <sys/stat.h>
-
-typedef int (*stat_fn_t)(const char*, struct stat*);
-extern stat_fn_t cygwin_stat_fn;
-extern stat_fn_t cygwin_lstat_fn;
-
-#define stat(path, buf) (*cygwin_stat_fn)(path, buf)
-#define lstat(path, buf) (*cygwin_lstat_fn)(path, buf)
diff --git a/compat/fileno.c b/compat/fileno.c
new file mode 100644
index 0000000000..8e80ef335d
--- /dev/null
+++ b/compat/fileno.c
@@ -0,0 +1,7 @@
+#define COMPAT_CODE_FILENO
+#include "../git-compat-util.h"
+
+int git_fileno(FILE *stream)
+{
+ return fileno(stream);
+}
diff --git a/compat/fnmatch/fnmatch.c b/compat/fnmatch/fnmatch.c
deleted file mode 100644
index b8b7dc2543..0000000000
--- a/compat/fnmatch/fnmatch.c
+++ /dev/null
@@ -1,492 +0,0 @@
-/* Copyright (C) 1991, 92, 93, 96, 97, 98, 99 Free Software Foundation, Inc.
- This file is part of the GNU C Library.
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public License as
- published by the Free Software Foundation; either version 2 of the
- License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Library General Public License for more details.
-
- You should have received a copy of the GNU Library General Public
- License along with this library; see the file COPYING.LIB. If not,
- write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- Boston, MA 02111-1307, USA. */
-
-#if HAVE_CONFIG_H
-# include <config.h>
-#endif
-
-/* Enable GNU extensions in fnmatch.h. */
-#ifndef _GNU_SOURCE
-# define _GNU_SOURCE 1
-#endif
-
-#include <errno.h>
-#include <fnmatch.h>
-#include <ctype.h>
-
-#if HAVE_STRING_H || defined _LIBC
-# include <string.h>
-#else
-# include <strings.h>
-#endif
-
-#if defined STDC_HEADERS || defined _LIBC
-# include <stdlib.h>
-#endif
-
-/* For platforms which support the ISO C amendment 1 functionality we
- support user defined character classes. */
-#if defined _LIBC || (defined HAVE_WCTYPE_H && defined HAVE_WCHAR_H)
-/* Solaris 2.5 has a bug: <wchar.h> must be included before <wctype.h>. */
-# include <wchar.h>
-# include <wctype.h>
-#endif
-
-/* Comment out all this code if we are using the GNU C Library, and are not
- actually compiling the library itself. This code is part of the GNU C
- Library, but also included in many other GNU distributions. Compiling
- and linking in this code is a waste when using the GNU C library
- (especially if it is a shared library). Rather than having every GNU
- program understand `configure --with-gnu-libc' and omit the object files,
- it is simpler to just do this in the source for each such file. */
-
-#if defined _LIBC || !defined __GNU_LIBRARY__
-
-
-# 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))
-
-# define STREQ(s1, s2) ((strcmp (s1, s2) == 0))
-
-# if defined _LIBC || (defined HAVE_WCTYPE_H && defined HAVE_WCHAR_H)
-/* The GNU C library provides support for user-defined character classes
- and the functions from ISO C amendment 1. */
-# ifdef CHARCLASS_NAME_MAX
-# define CHAR_CLASS_MAX_LENGTH CHARCLASS_NAME_MAX
-# else
-/* This shouldn't happen but some implementation might still have this
- problem. Use a reasonable default value. */
-# define CHAR_CLASS_MAX_LENGTH 256
-# endif
-
-# ifdef _LIBC
-# define IS_CHAR_CLASS(string) __wctype (string)
-# else
-# define IS_CHAR_CLASS(string) wctype (string)
-# endif
-# else
-# define CHAR_CLASS_MAX_LENGTH 6 /* Namely, `xdigit'. */
-
-# define IS_CHAR_CLASS(string) \
- (STREQ (string, "alpha") || STREQ (string, "upper") \
- || STREQ (string, "lower") || STREQ (string, "digit") \
- || STREQ (string, "alnum") || STREQ (string, "xdigit") \
- || STREQ (string, "space") || STREQ (string, "print") \
- || STREQ (string, "punct") || STREQ (string, "graph") \
- || STREQ (string, "cntrl") || STREQ (string, "blank"))
-# endif
-
-/* Avoid depending on library functions or files
- whose names are inconsistent. */
-
-# if !defined _LIBC && !defined getenv
-extern char *getenv ();
-# endif
-
-# ifndef errno
-extern int errno;
-# endif
-
-# ifndef NULL
-# define NULL 0
-# endif
-
-/* This function doesn't exist on most systems. */
-
-# if !defined HAVE___STRCHRNUL && !defined _LIBC
-static char *
-__strchrnul (const char *s, int c)
-
-
-{
- char *result = strchr (s, c);
- if (result == NULL)
- result = strchr (s, '\0');
- return result;
-}
-# endif
-
-# ifndef internal_function
-/* Inside GNU libc we mark some function in a special way. In other
- environments simply ignore the marking. */
-# define internal_function
-# endif
-
-/* Match STRING against the filename pattern PATTERN, returning zero if
- it matches, nonzero if not. */
-static int internal_fnmatch __P ((const char *pattern, const char *string,
- int no_leading_period, int flags))
- internal_function;
-static int
-internal_function
-internal_fnmatch (const char *pattern, const char *string, int no_leading_period, int flags)
-
-
-
-
-{
- register const char *p = pattern, *n = string;
- register unsigned char c;
-
-/* Note that this evaluates C many times. */
-# ifdef _LIBC
-# define FOLD(c) ((flags & FNM_CASEFOLD) ? tolower (c) : (c))
-# else
-# define FOLD(c) ((flags & FNM_CASEFOLD) && ISUPPER (c) ? tolower (c) : (c))
-# endif
-
- while ((c = *p++) != '\0')
- {
- c = FOLD (c);
-
- switch (c)
- {
- case '?':
- if (*n == '\0')
- return FNM_NOMATCH;
- else if (*n == '/' && (flags & FNM_FILE_NAME))
- return FNM_NOMATCH;
- else if (*n == '.' && no_leading_period
- && (n == string
- || (n[-1] == '/' && (flags & FNM_FILE_NAME))))
- return FNM_NOMATCH;
- break;
-
- case '\\':
- if (!(flags & FNM_NOESCAPE))
- {
- c = *p++;
- if (c == '\0')
- /* Trailing \ loses. */
- return FNM_NOMATCH;
- c = FOLD (c);
- }
- if (FOLD ((unsigned char) *n) != c)
- return FNM_NOMATCH;
- break;
-
- case '*':
- if (*n == '.' && no_leading_period
- && (n == string
- || (n[-1] == '/' && (flags & FNM_FILE_NAME))))
- return FNM_NOMATCH;
-
- for (c = *p++; c == '?' || c == '*'; c = *p++)
- {
- if (*n == '/' && (flags & FNM_FILE_NAME))
- /* A slash does not match a wildcard under FNM_FILE_NAME. */
- return FNM_NOMATCH;
- else if (c == '?')
- {
- /* A ? needs to match one character. */
- if (*n == '\0')
- /* There isn't another character; no match. */
- return FNM_NOMATCH;
- else
- /* One character of the string is consumed in matching
- this ? wildcard, so *??? won't match if there are
- less than three characters. */
- ++n;
- }
- }
-
- if (c == '\0')
- /* The wildcard(s) is/are the last element of the pattern.
- If the name is a file name and contains another slash
- this does mean it cannot match. */
- return ((flags & FNM_FILE_NAME) && strchr (n, '/') != NULL
- ? FNM_NOMATCH : 0);
- else
- {
- const char *endp;
-
- endp = __strchrnul (n, (flags & FNM_FILE_NAME) ? '/' : '\0');
-
- if (c == '[')
- {
- int flags2 = ((flags & FNM_FILE_NAME)
- ? flags : (flags & ~FNM_PERIOD));
-
- for (--p; n < endp; ++n)
- if (internal_fnmatch (p, n,
- (no_leading_period
- && (n == string
- || (n[-1] == '/'
- && (flags
- & FNM_FILE_NAME)))),
- flags2)
- == 0)
- return 0;
- }
- else if (c == '/' && (flags & FNM_FILE_NAME))
- {
- while (*n != '\0' && *n != '/')
- ++n;
- if (*n == '/'
- && (internal_fnmatch (p, n + 1, flags & FNM_PERIOD,
- flags) == 0))
- return 0;
- }
- else
- {
- int flags2 = ((flags & FNM_FILE_NAME)
- ? flags : (flags & ~FNM_PERIOD));
-
- if (c == '\\' && !(flags & FNM_NOESCAPE))
- c = *p;
- c = FOLD (c);
- for (--p; n < endp; ++n)
- if (FOLD ((unsigned char) *n) == c
- && (internal_fnmatch (p, n,
- (no_leading_period
- && (n == string
- || (n[-1] == '/'
- && (flags
- & FNM_FILE_NAME)))),
- flags2) == 0))
- return 0;
- }
- }
-
- /* If we come here no match is possible with the wildcard. */
- return FNM_NOMATCH;
-
- case '[':
- {
- /* Nonzero if the sense of the character class is inverted. */
- static int posixly_correct;
- register int not;
- char cold;
-
- if (posixly_correct == 0)
- posixly_correct = getenv ("POSIXLY_CORRECT") != NULL ? 1 : -1;
-
- if (*n == '\0')
- return FNM_NOMATCH;
-
- if (*n == '.' && no_leading_period && (n == string
- || (n[-1] == '/'
- && (flags
- & FNM_FILE_NAME))))
- return FNM_NOMATCH;
-
- if (*n == '/' && (flags & FNM_FILE_NAME))
- /* `/' cannot be matched. */
- return FNM_NOMATCH;
-
- not = (*p == '!' || (posixly_correct < 0 && *p == '^'));
- if (not)
- ++p;
-
- c = *p++;
- for (;;)
- {
- unsigned char fn = FOLD ((unsigned char) *n);
-
- if (!(flags & FNM_NOESCAPE) && c == '\\')
- {
- if (*p == '\0')
- return FNM_NOMATCH;
- c = FOLD ((unsigned char) *p);
- ++p;
-
- if (c == fn)
- goto matched;
- }
- else if (c == '[' && *p == ':')
- {
- /* Leave room for the null. */
- char str[CHAR_CLASS_MAX_LENGTH + 1];
- size_t c1 = 0;
-# if defined _LIBC || (defined HAVE_WCTYPE_H && defined HAVE_WCHAR_H)
- wctype_t wt;
-# endif
- const char *startp = p;
-
- for (;;)
- {
- if (c1 > CHAR_CLASS_MAX_LENGTH)
- /* The name is too long and therefore the pattern
- is ill-formed. */
- return FNM_NOMATCH;
-
- c = *++p;
- if (c == ':' && p[1] == ']')
- {
- p += 2;
- break;
- }
- if (c < 'a' || c >= 'z')
- {
- /* This cannot possibly be a character class name.
- Match it as a normal range. */
- p = startp;
- c = '[';
- goto normal_bracket;
- }
- str[c1++] = c;
- }
- str[c1] = '\0';
-
-# if defined _LIBC || (defined HAVE_WCTYPE_H && defined HAVE_WCHAR_H)
- wt = IS_CHAR_CLASS (str);
- if (wt == 0)
- /* Invalid character class name. */
- return FNM_NOMATCH;
-
- if (__iswctype (__btowc ((unsigned char) *n), wt))
- goto matched;
-# else
- if ((STREQ (str, "alnum") && ISALNUM ((unsigned char) *n))
- || (STREQ (str, "alpha") && ISALPHA ((unsigned char) *n))
- || (STREQ (str, "blank") && ISBLANK ((unsigned char) *n))
- || (STREQ (str, "cntrl") && ISCNTRL ((unsigned char) *n))
- || (STREQ (str, "digit") && ISDIGIT ((unsigned char) *n))
- || (STREQ (str, "graph") && ISGRAPH ((unsigned char) *n))
- || (STREQ (str, "lower") && ISLOWER ((unsigned char) *n))
- || (STREQ (str, "print") && ISPRINT ((unsigned char) *n))
- || (STREQ (str, "punct") && ISPUNCT ((unsigned char) *n))
- || (STREQ (str, "space") && ISSPACE ((unsigned char) *n))
- || (STREQ (str, "upper") && ISUPPER ((unsigned char) *n))
- || (STREQ (str, "xdigit") && ISXDIGIT ((unsigned char) *n)))
- goto matched;
-# endif
- }
- else if (c == '\0')
- /* [ (unterminated) loses. */
- return FNM_NOMATCH;
- else
- {
- normal_bracket:
- if (FOLD (c) == fn)
- goto matched;
-
- cold = c;
- c = *p++;
-
- if (c == '-' && *p != ']')
- {
- /* It is a range. */
- unsigned char cend = *p++;
- if (!(flags & FNM_NOESCAPE) && cend == '\\')
- cend = *p++;
- if (cend == '\0')
- return FNM_NOMATCH;
-
- if (cold <= fn && fn <= FOLD (cend))
- goto matched;
-
- c = *p++;
- }
- }
-
- if (c == ']')
- break;
- }
-
- if (!not)
- return FNM_NOMATCH;
- break;
-
- matched:
- /* Skip the rest of the [...] that already matched. */
- while (c != ']')
- {
- if (c == '\0')
- /* [... (unterminated) loses. */
- return FNM_NOMATCH;
-
- c = *p++;
- if (!(flags & FNM_NOESCAPE) && c == '\\')
- {
- if (*p == '\0')
- return FNM_NOMATCH;
- /* XXX 1003.2d11 is unclear if this is right. */
- ++p;
- }
- else if (c == '[' && *p == ':')
- {
- do
- if (*++p == '\0')
- return FNM_NOMATCH;
- while (*p != ':' || p[1] == ']');
- p += 2;
- c = *p;
- }
- }
- if (not)
- return FNM_NOMATCH;
- }
- break;
-
- default:
- if (c != FOLD ((unsigned char) *n))
- return FNM_NOMATCH;
- }
-
- ++n;
- }
-
- if (*n == '\0')
- return 0;
-
- if ((flags & FNM_LEADING_DIR) && *n == '/')
- /* The FNM_LEADING_DIR flag says that "foo*" matches "foobar/frobozz". */
- return 0;
-
- return FNM_NOMATCH;
-
-# undef FOLD
-}
-
-
-int
-fnmatch (const char *pattern, const char *string, int flags)
-
-
-
-{
- return internal_fnmatch (pattern, string, flags & FNM_PERIOD, flags);
-}
-
-#endif /* _LIBC or not __GNU_LIBRARY__. */
diff --git a/compat/fnmatch/fnmatch.h b/compat/fnmatch/fnmatch.h
deleted file mode 100644
index cc3ec37940..0000000000
--- a/compat/fnmatch/fnmatch.h
+++ /dev/null
@@ -1,84 +0,0 @@
-/* Copyright (C) 1991, 92, 93, 96, 97, 98, 99 Free Software Foundation, Inc.
- This file is part of the GNU C Library.
-
- The GNU C Library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public License as
- published by the Free Software Foundation; either version 2 of the
- License, or (at your option) any later version.
-
- The GNU C Library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Library General Public License for more details.
-
- You should have received a copy of the GNU Library General Public
- License along with the GNU C Library; see the file COPYING.LIB. If not,
- write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- Boston, MA 02111-1307, USA. */
-
-#ifndef _FNMATCH_H
-#define _FNMATCH_H 1
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#if defined __cplusplus || (defined __STDC__ && __STDC__) || defined WINDOWS32
-# if !defined __GLIBC__ || !defined __P
-# undef __P
-# define __P(protos) protos
-# endif
-#else /* Not C++ or ANSI C. */
-# undef __P
-# define __P(protos) ()
-/* We can get away without defining `const' here only because in this file
- it is used only inside the prototype for `fnmatch', which is elided in
- non-ANSI C where `const' is problematical. */
-#endif /* C++ or ANSI C. */
-
-#ifndef const
-# if (defined __STDC__ && __STDC__) || defined __cplusplus
-# define __const const
-# else
-# define __const
-# endif
-#endif
-
-/* We #undef these before defining them because some losing systems
- (HP-UX A.08.07 for example) define these in <unistd.h>. */
-#undef FNM_PATHNAME
-#undef FNM_NOESCAPE
-#undef FNM_PERIOD
-
-/* Bits set in the FLAGS argument to `fnmatch'. */
-#define FNM_PATHNAME (1 << 0) /* No wildcard can ever match `/'. */
-#define FNM_NOESCAPE (1 << 1) /* Backslashes don't quote special chars. */
-#define FNM_PERIOD (1 << 2) /* Leading `.' is matched only explicitly. */
-
-#if !defined _POSIX_C_SOURCE || _POSIX_C_SOURCE < 2 || defined _GNU_SOURCE
-# define FNM_FILE_NAME FNM_PATHNAME /* Preferred GNU name. */
-# define FNM_LEADING_DIR (1 << 3) /* Ignore `/...' after a match. */
-# define FNM_CASEFOLD (1 << 4) /* Compare without regard to case. */
-#endif
-
-/* Value returned by `fnmatch' if STRING does not match PATTERN. */
-#define FNM_NOMATCH 1
-
-/* This value is returned if the implementation does not support
- `fnmatch'. Since this is not the case here it will never be
- returned but the conformance test suites still require the symbol
- to be defined. */
-#ifdef _XOPEN_SOURCE
-# define FNM_NOSYS (-1)
-#endif
-
-/* Match NAME against the filename pattern PATTERN,
- returning zero if it matches, FNM_NOMATCH if not. */
-extern int fnmatch __P ((__const char *__pattern, __const char *__name,
- int __flags));
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* fnmatch.h */
diff --git a/compat/fopen.c b/compat/fopen.c
index b5ca142fed..107b3e8182 100644
--- a/compat/fopen.c
+++ b/compat/fopen.c
@@ -1,14 +1,14 @@
/*
* The order of the following two lines is important.
*
- * FREAD_READS_DIRECTORIES is undefined before including git-compat-util.h
+ * SUPPRESS_FOPEN_REDEFINITION is defined before including git-compat-util.h
* to avoid the redefinition of fopen within git-compat-util.h. This is
* necessary since fopen is a macro on some platforms which may be set
* based on compiler options. For example, on AIX fopen is set to fopen64
* when _LARGE_FILES is defined. The previous technique of merely undefining
* fopen after including git-compat-util.h is inadequate in this case.
*/
-#undef FREAD_READS_DIRECTORIES
+#define SUPPRESS_FOPEN_REDEFINITION
#include "../git-compat-util.h"
FILE *git_fopen(const char *path, const char *mode)
diff --git a/compat/hstrerror.c b/compat/hstrerror.c
index 069c555da4..b85a2fa956 100644
--- a/compat/hstrerror.c
+++ b/compat/hstrerror.c
@@ -16,6 +16,6 @@ const char *githstrerror(int err)
case TRY_AGAIN:
return "Non-authoritative \"host not found\", or SERVERFAIL";
}
- sprintf(buffer, "Name resolution error %d", err);
+ snprintf(buffer, sizeof(buffer), "Name resolution error %d", err);
return buffer;
}
diff --git a/compat/inet_ntop.c b/compat/inet_ntop.c
index 90b7cc45f3..68307262be 100644
--- a/compat/inet_ntop.c
+++ b/compat/inet_ntop.c
@@ -53,11 +53,11 @@ inet_ntop4(const u_char *src, char *dst, size_t size)
nprinted = snprintf(tmp, sizeof(tmp), fmt, src[0], src[1], src[2], src[3]);
if (nprinted < 0)
return (NULL); /* we assume "errno" was set by "snprintf()" */
- if ((size_t)nprinted > size) {
+ if ((size_t)nprinted >= size) {
errno = ENOSPC;
return (NULL);
}
- strcpy(dst, tmp);
+ strlcpy(dst, tmp, size);
return (dst);
}
@@ -154,7 +154,7 @@ inet_ntop6(const u_char *src, char *dst, size_t size)
errno = ENOSPC;
return (NULL);
}
- strcpy(dst, tmp);
+ strlcpy(dst, tmp, size);
return (dst);
}
#endif
diff --git a/compat/linux/procinfo.c b/compat/linux/procinfo.c
new file mode 100644
index 0000000000..bc2f9382a1
--- /dev/null
+++ b/compat/linux/procinfo.c
@@ -0,0 +1,176 @@
+#include "cache.h"
+
+#include "strbuf.h"
+#include "strvec.h"
+#include "trace2.h"
+
+/*
+ * We need more complex parsing in stat_parent_pid() and
+ * parse_proc_stat() below than a dumb fscanf(). That's because while
+ * the statcomm field is surrounded by parentheses, the process itself
+ * is free to insert any arbitrary byte sequence its its name. That
+ * can include newlines, spaces, closing parentheses etc.
+ *
+ * See do_task_stat() in fs/proc/array.c in linux.git, this is in
+ * contrast with the escaped version of the name found in
+ * /proc/%d/status.
+ *
+ * So instead of using fscanf() we'll read N bytes from it, look for
+ * the first "(", and then the last ")", anything in-between is our
+ * process name.
+ *
+ * How much N do we need? On Linux /proc/sys/kernel/pid_max is 2^15 by
+ * default, but it can be raised set to values of up to 2^22. So
+ * that's 7 digits for a PID. We have 2 PIDs in the first four fields
+ * we're interested in, so 2 * 7 = 14.
+ *
+ * We then have 3 spaces between those four values, and we'd like to
+ * get to the space between the 4th and the 5th (the "pgrp" field) to
+ * make sure we read the entire "ppid" field. So that brings us up to
+ * 14 + 3 + 1 = 18. Add the two parentheses around the "comm" value
+ * and it's 20. The "state" value itself is then one character (now at
+ * 21).
+ *
+ * Finally the maximum length of the "comm" name itself is 15
+ * characters, e.g. a setting of "123456789abcdefg" will be truncated
+ * to "123456789abcdef". See PR_SET_NAME in prctl(2). So all in all
+ * we'd need to read 21 + 15 = 36 bytes.
+ *
+ * Let's just read 2^6 (64) instead for good measure. If PID_MAX ever
+ * grows past 2^22 we'll be future-proof. We'll then anchor at the
+ * last ")" we find to locate the parent PID.
+ */
+#define STAT_PARENT_PID_READ_N 64
+
+static int parse_proc_stat(struct strbuf *sb, struct strbuf *name,
+ int *statppid)
+{
+ const char *comm_lhs = strchr(sb->buf, '(');
+ const char *comm_rhs = strrchr(sb->buf, ')');
+ const char *ppid_lhs, *ppid_rhs;
+ char *p;
+ pid_t ppid;
+
+ if (!comm_lhs || !comm_rhs)
+ goto bad_kernel;
+
+ /*
+ * We're at the ")", that's followed by " X ", where X is a
+ * single "state" character. So advance by 4 bytes.
+ */
+ ppid_lhs = comm_rhs + 4;
+
+ /*
+ * Read until the space between the "ppid" and "pgrp" fields
+ * to make sure we're anchored after the untruncated "ppid"
+ * field..
+ */
+ ppid_rhs = strchr(ppid_lhs, ' ');
+ if (!ppid_rhs)
+ goto bad_kernel;
+
+ ppid = strtol(ppid_lhs, &p, 10);
+ if (ppid_rhs == p) {
+ const char *comm = comm_lhs + 1;
+ size_t commlen = comm_rhs - comm;
+
+ strbuf_add(name, comm, commlen);
+ *statppid = ppid;
+
+ return 0;
+ }
+
+bad_kernel:
+ /*
+ * We were able to read our STAT_PARENT_PID_READ_N bytes from
+ * /proc/%d/stat, but the content is bad. Broken kernel?
+ * Should not happen, but handle it gracefully.
+ */
+ return -1;
+}
+
+static int stat_parent_pid(pid_t pid, struct strbuf *name, int *statppid)
+{
+ struct strbuf procfs_path = STRBUF_INIT;
+ struct strbuf sb = STRBUF_INIT;
+ FILE *fp;
+ int ret = -1;
+
+ /* try to use procfs if it's present. */
+ strbuf_addf(&procfs_path, "/proc/%d/stat", pid);
+ fp = fopen(procfs_path.buf, "r");
+ if (!fp)
+ goto cleanup;
+
+ /*
+ * We could be more strict here and assert that we read at
+ * least STAT_PARENT_PID_READ_N. My reading of procfs(5) is
+ * that on any modern kernel (at least since 2.6.0 released in
+ * 2003) even if all the mandatory numeric fields were zero'd
+ * out we'd get at least 100 bytes, but let's just check that
+ * we got anything at all and trust the parse_proc_stat()
+ * function to handle its "Bad Kernel?" error checking.
+ */
+ if (!strbuf_fread(&sb, STAT_PARENT_PID_READ_N, fp))
+ goto cleanup;
+ if (parse_proc_stat(&sb, name, statppid) < 0)
+ goto cleanup;
+
+ ret = 0;
+cleanup:
+ if (fp)
+ fclose(fp);
+ strbuf_release(&procfs_path);
+ strbuf_release(&sb);
+
+ return ret;
+}
+
+static void push_ancestry_name(struct strvec *names, pid_t pid)
+{
+ struct strbuf name = STRBUF_INIT;
+ int ppid;
+
+ if (stat_parent_pid(pid, &name, &ppid) < 0)
+ goto cleanup;
+
+ strvec_push(names, name.buf);
+
+ /*
+ * Both errors and reaching the end of the process chain are
+ * reported as fields of 0 by proc(5)
+ */
+ if (ppid)
+ push_ancestry_name(names, ppid);
+cleanup:
+ strbuf_release(&name);
+
+ return;
+}
+
+void trace2_collect_process_info(enum trace2_process_info_reason reason)
+{
+ struct strvec names = STRVEC_INIT;
+
+ if (!trace2_is_enabled())
+ return;
+
+ switch (reason) {
+ case TRACE2_PROCESS_INFO_EXIT:
+ /*
+ * The Windows version of this calls its
+ * get_peak_memory_info() here. We may want to insert
+ * similar process-end statistics here in the future.
+ */
+ break;
+ case TRACE2_PROCESS_INFO_STARTUP:
+ push_ancestry_name(&names, getppid());
+
+ if (names.nr)
+ trace2_cmd_ancestry(names.v);
+ strvec_clear(&names);
+ break;
+ }
+
+ return;
+}
diff --git a/compat/mingw.c b/compat/mingw.c
index b673625580..9e0cd1e097 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -1,11 +1,31 @@
#include "../git-compat-util.h"
#include "win32.h"
#include <conio.h>
+#include <wchar.h>
#include "../strbuf.h"
#include "../run-command.h"
+#include "../cache.h"
+#include "win32/lazyload.h"
+#include "../config.h"
+#include "dir.h"
+
+#define HCAST(type, handle) ((type)(intptr_t)handle)
static const int delay[] = { 0, 1, 10, 20, 40 };
+void open_in_gdb(void)
+{
+ static struct child_process cp = CHILD_PROCESS_INIT;
+ extern char *_pgmptr;
+
+ strvec_pushl(&cp.args, "mintty", "gdb", NULL);
+ strvec_pushf(&cp.args, "--pid=%d", getpid());
+ cp.clean_on_exit = 1;
+ if (start_command(&cp) < 0)
+ die_errno("Could not start gdb");
+ sleep(1);
+}
+
int err_win_to_posix(DWORD winerr)
{
int error = ENOSYS;
@@ -107,6 +127,7 @@ int err_win_to_posix(DWORD winerr)
case ERROR_SHARING_BUFFER_EXCEEDED: error = ENFILE; break;
case ERROR_SHARING_VIOLATION: error = EACCES; break;
case ERROR_STACK_OVERFLOW: error = ENOMEM; break;
+ case ERROR_SUCCESS: BUG("err_win_to_posix() called without an error!");
case ERROR_SWAPERROR: error = ENOENT; break;
case ERROR_TOO_MANY_MODULES: error = EMFILE; break;
case ERROR_TOO_MANY_OPEN_FILES: error = EMFILE; break;
@@ -198,14 +219,83 @@ static int ask_yes_no_if_possible(const char *format, ...)
}
}
-#undef unlink
+/* Windows only */
+enum hide_dotfiles_type {
+ HIDE_DOTFILES_FALSE = 0,
+ HIDE_DOTFILES_TRUE,
+ HIDE_DOTFILES_DOTGITONLY
+};
+
+static int core_restrict_inherited_handles = -1;
+static enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;
+static char *unset_environment_variables;
+
+int mingw_core_config(const char *var, const char *value, void *cb)
+{
+ if (!strcmp(var, "core.hidedotfiles")) {
+ if (value && !strcasecmp(value, "dotgitonly"))
+ hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;
+ else
+ hide_dotfiles = git_config_bool(var, value);
+ return 0;
+ }
+
+ if (!strcmp(var, "core.unsetenvvars")) {
+ free(unset_environment_variables);
+ unset_environment_variables = xstrdup(value);
+ return 0;
+ }
+
+ if (!strcmp(var, "core.restrictinheritedhandles")) {
+ if (value && !strcasecmp(value, "auto"))
+ core_restrict_inherited_handles = -1;
+ else
+ core_restrict_inherited_handles =
+ git_config_bool(var, value);
+ return 0;
+ }
+
+ return 0;
+}
+
+/* Normalizes NT paths as returned by some low-level APIs. */
+static wchar_t *normalize_ntpath(wchar_t *wbuf)
+{
+ int i;
+ /* fix absolute path prefixes */
+ if (wbuf[0] == '\\') {
+ /* strip NT namespace prefixes */
+ if (!wcsncmp(wbuf, L"\\??\\", 4) ||
+ !wcsncmp(wbuf, L"\\\\?\\", 4))
+ wbuf += 4;
+ else if (!wcsnicmp(wbuf, L"\\DosDevices\\", 12))
+ wbuf += 12;
+ /* replace remaining '...UNC\' with '\\' */
+ if (!wcsnicmp(wbuf, L"UNC\\", 4)) {
+ wbuf += 2;
+ *wbuf = '\\';
+ }
+ }
+ /* convert backslashes to slashes */
+ for (i = 0; wbuf[i]; i++)
+ if (wbuf[i] == '\\')
+ wbuf[i] = '/';
+ return wbuf;
+}
+
int mingw_unlink(const char *pathname)
{
int ret, tries = 0;
+ wchar_t wpathname[MAX_PATH];
+ if (xutftowcs_path(wpathname, pathname) < 0)
+ return -1;
+
+ if (DeleteFileW(wpathname))
+ return 0;
/* read-only files cannot be removed */
- chmod(pathname, 0666);
- while ((ret = unlink(pathname)) == -1 && tries < ARRAY_SIZE(delay)) {
+ _wchmod(wpathname, 0666);
+ while ((ret = _wunlink(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
if (!is_file_in_use_error(GetLastError()))
break;
/*
@@ -221,45 +311,66 @@ int mingw_unlink(const char *pathname)
while (ret == -1 && is_file_in_use_error(GetLastError()) &&
ask_yes_no_if_possible("Unlink of file '%s' failed. "
"Should I try again?", pathname))
- ret = unlink(pathname);
+ ret = _wunlink(wpathname);
return ret;
}
-static int is_dir_empty(const char *path)
+static int is_dir_empty(const wchar_t *wpath)
{
- struct strbuf buf = STRBUF_INIT;
- WIN32_FIND_DATAA findbuf;
+ WIN32_FIND_DATAW findbuf;
HANDLE handle;
-
- strbuf_addf(&buf, "%s\\*", path);
- handle = FindFirstFileA(buf.buf, &findbuf);
- if (handle == INVALID_HANDLE_VALUE) {
- strbuf_release(&buf);
+ wchar_t wbuf[MAX_PATH + 2];
+ wcscpy(wbuf, wpath);
+ wcscat(wbuf, L"\\*");
+ handle = FindFirstFileW(wbuf, &findbuf);
+ if (handle == INVALID_HANDLE_VALUE)
return GetLastError() == ERROR_NO_MORE_FILES;
- }
- while (!strcmp(findbuf.cFileName, ".") ||
- !strcmp(findbuf.cFileName, ".."))
- if (!FindNextFile(handle, &findbuf)) {
- strbuf_release(&buf);
- return GetLastError() == ERROR_NO_MORE_FILES;
+ while (!wcscmp(findbuf.cFileName, L".") ||
+ !wcscmp(findbuf.cFileName, L".."))
+ if (!FindNextFileW(handle, &findbuf)) {
+ DWORD err = GetLastError();
+ FindClose(handle);
+ return err == ERROR_NO_MORE_FILES;
}
FindClose(handle);
- strbuf_release(&buf);
return 0;
}
-#undef rmdir
int mingw_rmdir(const char *pathname)
{
int ret, tries = 0;
+ wchar_t wpathname[MAX_PATH];
+ struct stat st;
- while ((ret = rmdir(pathname)) == -1 && tries < ARRAY_SIZE(delay)) {
+ /*
+ * Contrary to Linux' `rmdir()`, Windows' _wrmdir() and _rmdir()
+ * (and `RemoveDirectoryW()`) will attempt to remove the target of a
+ * symbolic link (if it points to a directory).
+ *
+ * This behavior breaks the assumption of e.g. `remove_path()` which
+ * upon successful deletion of a file will attempt to remove its parent
+ * directories recursively until failure (which usually happens when
+ * the directory is not empty).
+ *
+ * Therefore, before calling `_wrmdir()`, we first check if the path is
+ * a symbolic link. If it is, we exit and return the same error as
+ * Linux' `rmdir()` would, i.e. `ENOTDIR`.
+ */
+ if (!mingw_lstat(pathname, &st) && S_ISLNK(st.st_mode)) {
+ errno = ENOTDIR;
+ return -1;
+ }
+
+ if (xutftowcs_path(wpathname, pathname) < 0)
+ return -1;
+
+ while ((ret = _wrmdir(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
if (!is_file_in_use_error(GetLastError()))
errno = err_win_to_posix(GetLastError());
if (errno != EACCES)
break;
- if (!is_dir_empty(pathname)) {
+ if (!is_dir_empty(wpathname)) {
errno = ENOTEMPTY;
break;
}
@@ -276,51 +387,201 @@ int mingw_rmdir(const char *pathname)
while (ret == -1 && errno == EACCES && is_file_in_use_error(GetLastError()) &&
ask_yes_no_if_possible("Deletion of directory '%s' failed. "
"Should I try again?", pathname))
- ret = rmdir(pathname);
+ ret = _wrmdir(wpathname);
+ if (!ret)
+ invalidate_lstat_cache();
return ret;
}
-#undef open
+static inline int needs_hiding(const char *path)
+{
+ const char *basename;
+
+ if (hide_dotfiles == HIDE_DOTFILES_FALSE)
+ return 0;
+
+ /* We cannot use basename(), as it would remove trailing slashes */
+ win32_skip_dos_drive_prefix((char **)&path);
+ if (!*path)
+ return 0;
+
+ for (basename = path; *path; path++)
+ if (is_dir_sep(*path)) {
+ do {
+ path++;
+ } while (is_dir_sep(*path));
+ /* ignore trailing slashes */
+ if (*path)
+ basename = path;
+ else
+ break;
+ }
+
+ if (hide_dotfiles == HIDE_DOTFILES_TRUE)
+ return *basename == '.';
+
+ assert(hide_dotfiles == HIDE_DOTFILES_DOTGITONLY);
+ return !strncasecmp(".git", basename, 4) &&
+ (!basename[4] || is_dir_sep(basename[4]));
+}
+
+static int set_hidden_flag(const wchar_t *path, int set)
+{
+ DWORD original = GetFileAttributesW(path), modified;
+ if (set)
+ modified = original | FILE_ATTRIBUTE_HIDDEN;
+ else
+ modified = original & ~FILE_ATTRIBUTE_HIDDEN;
+ if (original == modified || SetFileAttributesW(path, modified))
+ return 0;
+ errno = err_win_to_posix(GetLastError());
+ return -1;
+}
+
+int mingw_mkdir(const char *path, int mode)
+{
+ int ret;
+ wchar_t wpath[MAX_PATH];
+
+ if (!is_valid_win32_path(path, 0)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (xutftowcs_path(wpath, path) < 0)
+ return -1;
+ ret = _wmkdir(wpath);
+ if (!ret && needs_hiding(path))
+ return set_hidden_flag(wpath, 1);
+ return ret;
+}
+
+/*
+ * Calling CreateFile() using FILE_APPEND_DATA and without FILE_WRITE_DATA
+ * is documented in [1] as opening a writable file handle in append mode.
+ * (It is believed that) this is atomic since it is maintained by the
+ * kernel unlike the O_APPEND flag which is racily maintained by the CRT.
+ *
+ * [1] https://docs.microsoft.com/en-us/windows/desktop/fileio/file-access-rights-constants
+ *
+ * This trick does not appear to work for named pipes. Instead it creates
+ * a named pipe client handle that cannot be written to. Callers should
+ * just use the regular _wopen() for them. (And since client handle gets
+ * bound to a unique server handle, it isn't really an issue.)
+ */
+static int mingw_open_append(wchar_t const *wfilename, int oflags, ...)
+{
+ HANDLE handle;
+ int fd;
+ DWORD create = (oflags & O_CREAT) ? OPEN_ALWAYS : OPEN_EXISTING;
+
+ /* only these flags are supported */
+ if ((oflags & ~O_CREAT) != (O_WRONLY | O_APPEND))
+ return errno = ENOSYS, -1;
+
+ /*
+ * FILE_SHARE_WRITE is required to permit child processes
+ * to append to the file.
+ */
+ handle = CreateFileW(wfilename, FILE_APPEND_DATA,
+ FILE_SHARE_WRITE | FILE_SHARE_READ,
+ NULL, create, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (handle == INVALID_HANDLE_VALUE) {
+ DWORD err = GetLastError();
+
+ /*
+ * Some network storage solutions (e.g. Isilon) might return
+ * ERROR_INVALID_PARAMETER instead of expected error
+ * ERROR_PATH_NOT_FOUND, which results in an unknown error. If
+ * so, let's turn the error to ERROR_PATH_NOT_FOUND instead.
+ */
+ if (err == ERROR_INVALID_PARAMETER)
+ err = ERROR_PATH_NOT_FOUND;
+
+ errno = err_win_to_posix(err);
+ return -1;
+ }
+
+ /*
+ * No O_APPEND here, because the CRT uses it only to reset the
+ * file pointer to EOF before each write(); but that is not
+ * necessary (and may lead to races) for a file created with
+ * FILE_APPEND_DATA.
+ */
+ fd = _open_osfhandle((intptr_t)handle, O_BINARY);
+ if (fd < 0)
+ CloseHandle(handle);
+ return fd;
+}
+
+/*
+ * Does the pathname map to the local named pipe filesystem?
+ * That is, does it have a "//./pipe/" prefix?
+ */
+static int is_local_named_pipe_path(const char *filename)
+{
+ return (is_dir_sep(filename[0]) &&
+ is_dir_sep(filename[1]) &&
+ filename[2] == '.' &&
+ is_dir_sep(filename[3]) &&
+ !strncasecmp(filename+4, "pipe", 4) &&
+ is_dir_sep(filename[8]) &&
+ filename[9]);
+}
+
int mingw_open (const char *filename, int oflags, ...)
{
+ typedef int (*open_fn_t)(wchar_t const *wfilename, int oflags, ...);
va_list args;
unsigned mode;
- int fd;
+ int fd, create = (oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL);
+ wchar_t wfilename[MAX_PATH];
+ open_fn_t open_fn;
va_start(args, oflags);
mode = va_arg(args, int);
va_end(args);
+ if (!is_valid_win32_path(filename, !create)) {
+ errno = create ? EINVAL : ENOENT;
+ return -1;
+ }
+
+ if ((oflags & O_APPEND) && !is_local_named_pipe_path(filename))
+ open_fn = mingw_open_append;
+ else
+ open_fn = _wopen;
+
if (filename && !strcmp(filename, "/dev/null"))
- filename = "nul";
+ wcscpy(wfilename, L"nul");
+ else if (xutftowcs_path(wfilename, filename) < 0)
+ return -1;
- fd = open(filename, oflags, mode);
+ fd = open_fn(wfilename, oflags, mode);
- if (fd < 0 && (oflags & O_CREAT) && errno == EACCES) {
- DWORD attrs = GetFileAttributes(filename);
+ if (fd < 0 && (oflags & O_ACCMODE) != O_RDONLY && errno == EACCES) {
+ DWORD attrs = GetFileAttributesW(wfilename);
if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY))
errno = EISDIR;
}
+ if ((oflags & O_CREAT) && needs_hiding(filename)) {
+ /*
+ * Internally, _wopen() uses the CreateFile() API which errors
+ * out with an ERROR_ACCESS_DENIED if CREATE_ALWAYS was
+ * specified and an already existing file's attributes do not
+ * match *exactly*. As there is no mode or flag we can set that
+ * would correspond to FILE_ATTRIBUTE_HIDDEN, let's just try
+ * again *without* the O_CREAT flag (that corresponds to the
+ * CREATE_ALWAYS flag of CreateFile()).
+ */
+ if (fd < 0 && errno == EACCES)
+ fd = open_fn(wfilename, oflags & ~O_CREAT, mode);
+ if (fd >= 0 && set_hidden_flag(wfilename, 1))
+ warning("could not mark '%s' as hidden.", filename);
+ }
return fd;
}
-#undef write
-ssize_t mingw_write(int fd, const void *buf, size_t count)
-{
- /*
- * While write() calls to a file on a local disk are translated
- * into WriteFile() calls with a maximum size of 64KB on Windows
- * XP and 256KB on Vista, no such cap is placed on writes to
- * files over the network on Windows XP. Unfortunately, there
- * seems to be a limit of 32MB-28KB on X64 and 64MB-32KB on x86;
- * bigger writes fail on Windows XP.
- * So we cap to a nice 31MB here to avoid write failures over
- * the net without changing the number of WriteFile() calls in
- * the local case.
- */
- return write(fd, buf, min(count, 31 * 1024 * 1024));
-}
-
static BOOL WINAPI ctrl_ignore(DWORD type)
{
return TRUE;
@@ -349,17 +610,58 @@ int mingw_fgetc(FILE *stream)
#undef fopen
FILE *mingw_fopen (const char *filename, const char *otype)
{
+ int hide = needs_hiding(filename);
+ FILE *file;
+ wchar_t wfilename[MAX_PATH], wotype[4];
if (filename && !strcmp(filename, "/dev/null"))
- filename = "nul";
- return fopen(filename, otype);
+ wcscpy(wfilename, L"nul");
+ else if (!is_valid_win32_path(filename, 1)) {
+ int create = otype && strchr(otype, 'w');
+ errno = create ? EINVAL : ENOENT;
+ return NULL;
+ } else if (xutftowcs_path(wfilename, filename) < 0)
+ return NULL;
+
+ if (xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
+ return NULL;
+
+ if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
+ error("could not unhide %s", filename);
+ return NULL;
+ }
+ file = _wfopen(wfilename, wotype);
+ if (!file && GetLastError() == ERROR_INVALID_NAME)
+ errno = ENOENT;
+ if (file && hide && set_hidden_flag(wfilename, 1))
+ warning("could not mark '%s' as hidden.", filename);
+ return file;
}
-#undef freopen
FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
{
+ int hide = needs_hiding(filename);
+ FILE *file;
+ wchar_t wfilename[MAX_PATH], wotype[4];
if (filename && !strcmp(filename, "/dev/null"))
- filename = "nul";
- return freopen(filename, otype, stream);
+ wcscpy(wfilename, L"nul");
+ else if (!is_valid_win32_path(filename, 1)) {
+ int create = otype && strchr(otype, 'w');
+ errno = create ? EINVAL : ENOENT;
+ return NULL;
+ } else if (xutftowcs_path(wfilename, filename) < 0)
+ return NULL;
+
+ if (xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
+ return NULL;
+
+ if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
+ error("could not unhide %s", filename);
+ return NULL;
+ }
+ file = _wfreopen(wfilename, wotype, stream);
+ if (file && hide && set_hidden_flag(wfilename, 1))
+ warning("could not mark '%s' as hidden.", filename);
+ return file;
}
#undef fflush
@@ -384,6 +686,50 @@ int mingw_fflush(FILE *stream)
return ret;
}
+#undef write
+ssize_t mingw_write(int fd, const void *buf, size_t len)
+{
+ ssize_t result = write(fd, buf, len);
+
+ if (result < 0 && errno == EINVAL && buf) {
+ /* check if fd is a pipe */
+ HANDLE h = (HANDLE) _get_osfhandle(fd);
+ if (GetFileType(h) == FILE_TYPE_PIPE)
+ errno = EPIPE;
+ else
+ errno = EINVAL;
+ }
+
+ return result;
+}
+
+int mingw_access(const char *filename, int mode)
+{
+ wchar_t wfilename[MAX_PATH];
+ if (!strcmp("nul", filename) || !strcmp("/dev/null", filename))
+ return 0;
+ if (xutftowcs_path(wfilename, filename) < 0)
+ return -1;
+ /* X_OK is not supported by the MSVCRT version */
+ return _waccess(wfilename, mode & ~X_OK);
+}
+
+int mingw_chdir(const char *dirname)
+{
+ wchar_t wdirname[MAX_PATH];
+ if (xutftowcs_path(wdirname, dirname) < 0)
+ return -1;
+ return _wchdir(wdirname);
+}
+
+int mingw_chmod(const char *filename, int mode)
+{
+ wchar_t wfilename[MAX_PATH];
+ if (xutftowcs_path(wfilename, filename) < 0)
+ return -1;
+ return _wchmod(wfilename, mode);
+}
+
/*
* The unit of FILETIME is 100-nanoseconds since January 1, 1601, UTC.
* Returns the 100-nanoseconds ("hekto nanoseconds") since the epoch.
@@ -395,9 +741,44 @@ static inline long long filetime_to_hnsec(const FILETIME *ft)
return winTime - 116444736000000000LL;
}
-static inline time_t filetime_to_time_t(const FILETIME *ft)
+static inline void filetime_to_timespec(const FILETIME *ft, struct timespec *ts)
{
- return (time_t)(filetime_to_hnsec(ft) / 10000000);
+ long long hnsec = filetime_to_hnsec(ft);
+ ts->tv_sec = (time_t)(hnsec / 10000000);
+ ts->tv_nsec = (hnsec % 10000000) * 100;
+}
+
+/**
+ * Verifies that safe_create_leading_directories() would succeed.
+ */
+static int has_valid_directory_prefix(wchar_t *wfilename)
+{
+ int n = wcslen(wfilename);
+
+ while (n > 0) {
+ wchar_t c = wfilename[--n];
+ DWORD attributes;
+
+ if (!is_dir_sep(c))
+ continue;
+
+ wfilename[n] = L'\0';
+ attributes = GetFileAttributesW(wfilename);
+ wfilename[n] = c;
+ if (attributes == FILE_ATTRIBUTE_DIRECTORY ||
+ attributes == FILE_ATTRIBUTE_DEVICE)
+ return 1;
+ if (attributes == INVALID_FILE_ATTRIBUTES)
+ switch (GetLastError()) {
+ case ERROR_PATH_NOT_FOUND:
+ continue;
+ case ERROR_FILE_NOT_FOUND:
+ /* This implies parent directory exists. */
+ return 1;
+ }
+ return 0;
+ }
+ return 1;
}
/* We keep the do_lstat code in a separate function to avoid recursion.
@@ -409,10 +790,12 @@ static inline time_t filetime_to_time_t(const FILETIME *ft)
*/
static int do_lstat(int follow, const char *file_name, struct stat *buf)
{
- int err;
WIN32_FILE_ATTRIBUTE_DATA fdata;
+ wchar_t wfilename[MAX_PATH];
+ if (xutftowcs_path(wfilename, file_name) < 0)
+ return -1;
- if (!(err = get_file_attr(file_name, &fdata))) {
+ if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
buf->st_ino = 0;
buf->st_gid = 0;
buf->st_uid = 0;
@@ -421,12 +804,12 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf)
buf->st_size = fdata.nFileSizeLow |
(((off_t)fdata.nFileSizeHigh)<<32);
buf->st_dev = buf->st_rdev = 0; /* not used by Git */
- buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
- buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
- buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime));
+ filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim));
+ filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));
+ filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim));
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
- WIN32_FIND_DATAA findbuf;
- HANDLE handle = FindFirstFileA(file_name, &findbuf);
+ WIN32_FIND_DATAW findbuf;
+ HANDLE handle = FindFirstFileW(wfilename, &findbuf);
if (handle != INVALID_HANDLE_VALUE) {
if ((findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
(findbuf.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) {
@@ -445,7 +828,29 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf)
}
return 0;
}
- errno = err;
+ switch (GetLastError()) {
+ case ERROR_ACCESS_DENIED:
+ case ERROR_SHARING_VIOLATION:
+ case ERROR_LOCK_VIOLATION:
+ case ERROR_SHARING_BUFFER_EXCEEDED:
+ errno = EACCES;
+ break;
+ case ERROR_BUFFER_OVERFLOW:
+ errno = ENAMETOOLONG;
+ break;
+ case ERROR_NOT_ENOUGH_MEMORY:
+ errno = ENOMEM;
+ break;
+ case ERROR_PATH_NOT_FOUND:
+ if (!has_valid_directory_prefix(wfilename)) {
+ errno = ENOTDIR;
+ break;
+ }
+ /* fallthru */
+ default:
+ errno = ENOENT;
+ break;
+ }
return -1;
}
@@ -458,7 +863,7 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf)
static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
{
int namelen;
- static char alt_name[PATH_MAX];
+ char alt_name[PATH_MAX];
if (!do_lstat(follow, file_name, buf))
return 0;
@@ -482,6 +887,29 @@ static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
return do_lstat(follow, alt_name, buf);
}
+static int get_file_info_by_handle(HANDLE hnd, struct stat *buf)
+{
+ BY_HANDLE_FILE_INFORMATION fdata;
+
+ if (!GetFileInformationByHandle(hnd, &fdata)) {
+ errno = err_win_to_posix(GetLastError());
+ return -1;
+ }
+
+ buf->st_ino = 0;
+ buf->st_gid = 0;
+ buf->st_uid = 0;
+ buf->st_nlink = 1;
+ buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
+ buf->st_size = fdata.nFileSizeLow |
+ (((off_t)fdata.nFileSizeHigh)<<32);
+ buf->st_dev = buf->st_rdev = 0; /* not used by Git */
+ filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim));
+ filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));
+ filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim));
+ return 0;
+}
+
int mingw_lstat(const char *file_name, struct stat *buf)
{
return do_stat_internal(0, file_name, buf);
@@ -491,36 +919,34 @@ int mingw_stat(const char *file_name, struct stat *buf)
return do_stat_internal(1, file_name, buf);
}
-#undef fstat
int mingw_fstat(int fd, struct stat *buf)
{
HANDLE fh = (HANDLE)_get_osfhandle(fd);
- BY_HANDLE_FILE_INFORMATION fdata;
+ DWORD avail, type = GetFileType(fh) & ~FILE_TYPE_REMOTE;
- if (fh == INVALID_HANDLE_VALUE) {
- errno = EBADF;
- return -1;
- }
- /* direct non-file handles to MS's fstat() */
- if (GetFileType(fh) != FILE_TYPE_DISK)
- return _fstati64(fd, buf);
+ switch (type) {
+ case FILE_TYPE_DISK:
+ return get_file_info_by_handle(fh, buf);
- if (GetFileInformationByHandle(fh, &fdata)) {
- buf->st_ino = 0;
- buf->st_gid = 0;
- buf->st_uid = 0;
+ case FILE_TYPE_CHAR:
+ case FILE_TYPE_PIPE:
+ /* initialize stat fields */
+ memset(buf, 0, sizeof(*buf));
buf->st_nlink = 1;
- buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
- buf->st_size = fdata.nFileSizeLow |
- (((off_t)fdata.nFileSizeHigh)<<32);
- buf->st_dev = buf->st_rdev = 0; /* not used by Git */
- buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
- buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
- buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime));
+
+ if (type == FILE_TYPE_CHAR) {
+ buf->st_mode = _S_IFCHR;
+ } else {
+ buf->st_mode = _S_IFIFO;
+ if (PeekNamedPipe(fh, NULL, 0, NULL, &avail, NULL))
+ buf->st_size = avail;
+ }
return 0;
+
+ default:
+ errno = EBADF;
+ return -1;
}
- errno = EBADF;
- return -1;
}
static inline void time_t_to_filetime(time_t t, FILETIME *ft)
@@ -534,16 +960,20 @@ int mingw_utime (const char *file_name, const struct utimbuf *times)
{
FILETIME mft, aft;
int fh, rc;
+ DWORD attrs;
+ wchar_t wfilename[MAX_PATH];
+ if (xutftowcs_path(wfilename, file_name) < 0)
+ return -1;
/* must have write permission */
- DWORD attrs = GetFileAttributes(file_name);
+ attrs = GetFileAttributesW(wfilename);
if (attrs != INVALID_FILE_ATTRIBUTES &&
(attrs & FILE_ATTRIBUTE_READONLY)) {
/* ignore errors here; open() will report them */
- SetFileAttributes(file_name, attrs & ~FILE_ATTRIBUTE_READONLY);
+ SetFileAttributesW(wfilename, attrs & ~FILE_ATTRIBUTE_READONLY);
}
- if ((fh = open(file_name, O_RDWR | O_BINARY)) < 0) {
+ if ((fh = _wopen(wfilename, O_RDWR | O_BINARY)) < 0) {
rc = -1;
goto revert_attrs;
}
@@ -566,17 +996,49 @@ revert_attrs:
if (attrs != INVALID_FILE_ATTRIBUTES &&
(attrs & FILE_ATTRIBUTE_READONLY)) {
/* ignore errors again */
- SetFileAttributes(file_name, attrs);
+ SetFileAttributesW(wfilename, attrs);
}
return rc;
}
+#undef strftime
+size_t mingw_strftime(char *s, size_t max,
+ const char *format, const struct tm *tm)
+{
+ /* a pointer to the original strftime in case we can't find the UCRT version */
+ static size_t (*fallback)(char *, size_t, const char *, const struct tm *) = strftime;
+ size_t ret;
+ DECLARE_PROC_ADDR(ucrtbase.dll, size_t, strftime, char *, size_t,
+ const char *, const struct tm *);
+
+ if (INIT_PROC_ADDR(strftime))
+ ret = strftime(s, max, format, tm);
+ else
+ ret = fallback(s, max, format, tm);
+
+ if (!ret && errno == EINVAL)
+ die("invalid strftime format: '%s'", format);
+ return ret;
+}
+
unsigned int sleep (unsigned int seconds)
{
Sleep(seconds*1000);
return 0;
}
+char *mingw_mktemp(char *template)
+{
+ wchar_t wtemplate[MAX_PATH];
+ if (xutftowcs_path(wtemplate, template) < 0)
+ return NULL;
+ if (!_wmktemp(wtemplate))
+ return NULL;
+ if (xwcstoutf(template, wtemplate, strlen(template) + 1) < 0)
+ return NULL;
+ return template;
+}
+
int mkstemp(char *template)
{
char *filename = mktemp(template);
@@ -606,14 +1068,14 @@ int pipe(int filedes[2])
errno = err_win_to_posix(GetLastError());
return -1;
}
- filedes[0] = _open_osfhandle((int)h[0], O_NOINHERIT);
+ filedes[0] = _open_osfhandle(HCAST(int, h[0]), O_NOINHERIT);
if (filedes[0] < 0) {
CloseHandle(h[0]);
CloseHandle(h[1]);
return -1;
}
- filedes[1] = _open_osfhandle((int)h[1], O_NOINHERIT);
- if (filedes[0] < 0) {
+ filedes[1] = _open_osfhandle(HCAST(int, h[1]), O_NOINHERIT);
+ if (filedes[1] < 0) {
close(filedes[0]);
CloseHandle(h[1]);
return -1;
@@ -623,36 +1085,55 @@ int pipe(int filedes[2])
struct tm *gmtime_r(const time_t *timep, struct tm *result)
{
- /* gmtime() in MSVCRT.DLL is thread-safe, but not reentrant */
- memcpy(result, gmtime(timep), sizeof(struct tm));
- return result;
+ if (gmtime_s(result, timep) == 0)
+ return result;
+ return NULL;
}
struct tm *localtime_r(const time_t *timep, struct tm *result)
{
- /* localtime() in MSVCRT.DLL is thread-safe, but not reentrant */
- memcpy(result, localtime(timep), sizeof(struct tm));
- return result;
+ if (localtime_s(result, timep) == 0)
+ return result;
+ return NULL;
}
-#undef getcwd
char *mingw_getcwd(char *pointer, int len)
{
- int i;
- char *ret = getcwd(pointer, len);
- if (!ret)
- return ret;
- for (i = 0; pointer[i]; i++)
- if (pointer[i] == '\\')
- pointer[i] = '/';
- return ret;
+ wchar_t cwd[MAX_PATH], wpointer[MAX_PATH];
+ DWORD ret = GetCurrentDirectoryW(ARRAY_SIZE(cwd), cwd);
+
+ if (!ret || ret >= ARRAY_SIZE(cwd)) {
+ errno = ret ? ENAMETOOLONG : err_win_to_posix(GetLastError());
+ return NULL;
+ }
+ ret = GetLongPathNameW(cwd, wpointer, ARRAY_SIZE(wpointer));
+ if (!ret && GetLastError() == ERROR_ACCESS_DENIED) {
+ HANDLE hnd = CreateFileW(cwd, 0,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
+ OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+ if (hnd == INVALID_HANDLE_VALUE)
+ return NULL;
+ ret = GetFinalPathNameByHandleW(hnd, wpointer, ARRAY_SIZE(wpointer), 0);
+ CloseHandle(hnd);
+ if (!ret || ret >= ARRAY_SIZE(wpointer))
+ return NULL;
+ if (xwcstoutf(pointer, normalize_ntpath(wpointer), len) < 0)
+ return NULL;
+ return pointer;
+ }
+ if (!ret || ret >= ARRAY_SIZE(wpointer))
+ return NULL;
+ if (xwcstoutf(pointer, wpointer, len) < 0)
+ return NULL;
+ convert_slashes(pointer);
+ return pointer;
}
/*
- * See http://msdn2.microsoft.com/en-us/library/17w5ykft(vs.71).aspx
- * (Parsing C++ Command-Line Arguments)
+ * See "Parsing C++ Command-Line Arguments" at Microsoft's Docs:
+ * https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments
*/
-static const char *quote_arg(const char *arg)
+static const char *quote_arg_msvc(const char *arg)
{
/* count chars to quote */
int len = 0, n = 0;
@@ -672,7 +1153,7 @@ static const char *quote_arg(const char *arg)
p++;
len++;
}
- if (*p == '"')
+ if (*p == '"' || !*p)
n += count*2 + 1;
continue;
}
@@ -683,7 +1164,7 @@ static const char *quote_arg(const char *arg)
return arg;
/* insert \ where necessary */
- d = q = xmalloc(len+n+3);
+ d = q = xmalloc(st_add3(len, n, 3));
*d++ = '"';
while (*arg) {
if (*arg == '"')
@@ -694,19 +1175,54 @@ static const char *quote_arg(const char *arg)
count++;
*d++ = *arg++;
}
- if (*arg == '"') {
+ if (*arg == '"' || !*arg) {
while (count-- > 0)
*d++ = '\\';
+ /* don't escape the surrounding end quote */
+ if (!*arg)
+ break;
*d++ = '\\';
}
}
*d++ = *arg++;
}
*d++ = '"';
- *d++ = 0;
+ *d++ = '\0';
return q;
}
+#include "quote.h"
+
+static const char *quote_arg_msys2(const char *arg)
+{
+ struct strbuf buf = STRBUF_INIT;
+ const char *p2 = arg, *p;
+
+ for (p = arg; *p; p++) {
+ int ws = isspace(*p);
+ if (!ws && *p != '\\' && *p != '"' && *p != '{' && *p != '\'' &&
+ *p != '?' && *p != '*' && *p != '~')
+ continue;
+ if (!buf.len)
+ strbuf_addch(&buf, '"');
+ if (p != p2)
+ strbuf_add(&buf, p2, p - p2);
+ if (*p == '\\' || *p == '"')
+ strbuf_addch(&buf, '\\');
+ p2 = p;
+ }
+
+ if (p == arg)
+ strbuf_addch(&buf, '"');
+ else if (!buf.len)
+ return arg;
+ else
+ strbuf_add(&buf, p2, p - p2);
+
+ strbuf_addch(&buf, '"');
+ return strbuf_detach(&buf, 0);
+}
+
static const char *parse_interpreter(const char *cmd)
{
static char buf[100];
@@ -743,120 +1259,295 @@ static const char *parse_interpreter(const char *cmd)
}
/*
- * Splits the PATH into parts.
+ * exe_only means that we only want to detect .exe files, but not scripts
+ * (which do not have an extension)
*/
-static char **get_path_split(void)
+static char *lookup_prog(const char *dir, int dirlen, const char *cmd,
+ int isexe, int exe_only)
{
- char *p, **path, *envpath = mingw_getenv("PATH");
- int i, n = 0;
+ char path[MAX_PATH];
+ wchar_t wpath[MAX_PATH];
+ snprintf(path, sizeof(path), "%.*s\\%s.exe", dirlen, dir, cmd);
- if (!envpath || !*envpath)
+ if (xutftowcs_path(wpath, path) < 0)
return NULL;
- envpath = xstrdup(envpath);
- p = envpath;
- while (p) {
- char *dir = p;
- p = strchr(p, ';');
- if (p) *p++ = '\0';
- if (*dir) { /* not earlier, catches series of ; */
- ++n;
+ if (!isexe && _waccess(wpath, F_OK) == 0)
+ return xstrdup(path);
+ wpath[wcslen(wpath)-4] = '\0';
+ if ((!exe_only || isexe) && _waccess(wpath, F_OK) == 0) {
+ if (!(GetFileAttributesW(wpath) & FILE_ATTRIBUTE_DIRECTORY)) {
+ path[strlen(path)-4] = '\0';
+ return xstrdup(path);
}
}
- if (!n)
+ return NULL;
+}
+
+/*
+ * Determines the absolute path of cmd using the split path in path.
+ * If cmd contains a slash or backslash, no lookup is performed.
+ */
+static char *path_lookup(const char *cmd, int exe_only)
+{
+ const char *path;
+ char *prog = NULL;
+ int len = strlen(cmd);
+ int isexe = len >= 4 && !strcasecmp(cmd+len-4, ".exe");
+
+ if (strpbrk(cmd, "/\\"))
+ return xstrdup(cmd);
+
+ path = mingw_getenv("PATH");
+ if (!path)
return NULL;
- path = xmalloc((n+1)*sizeof(char *));
- p = envpath;
- i = 0;
- do {
- if (*p)
- path[i++] = xstrdup(p);
- p = p+strlen(p)+1;
- } while (i < n);
- path[i] = NULL;
+ while (!prog) {
+ const char *sep = strchrnul(path, ';');
+ int dirlen = sep - path;
+ if (dirlen)
+ prog = lookup_prog(path, dirlen, cmd, isexe, exe_only);
+ if (!*sep)
+ break;
+ path = sep + 1;
+ }
- free(envpath);
+ return prog;
+}
- return path;
+static const wchar_t *wcschrnul(const wchar_t *s, wchar_t c)
+{
+ while (*s && *s != c)
+ s++;
+ return s;
}
-static void free_path_split(char **path)
+/* Compare only keys */
+static int wenvcmp(const void *a, const void *b)
{
- char **p = path;
+ wchar_t *p = *(wchar_t **)a, *q = *(wchar_t **)b;
+ size_t p_len, q_len;
- if (!path)
- return;
+ /* Find the keys */
+ p_len = wcschrnul(p, L'=') - p;
+ q_len = wcschrnul(q, L'=') - q;
- while (*p)
- free(*p++);
- free(path);
+ /* If the length differs, include the shorter key's NUL */
+ if (p_len < q_len)
+ p_len++;
+ else if (p_len > q_len)
+ p_len = q_len + 1;
+
+ return _wcsnicmp(p, q, p_len);
}
/*
- * exe_only means that we only want to detect .exe files, but not scripts
- * (which do not have an extension)
+ * Build an environment block combining the inherited environment
+ * merged with the given list of settings.
+ *
+ * Values of the form "KEY=VALUE" in deltaenv override inherited values.
+ * Values of the form "KEY" in deltaenv delete inherited values.
+ *
+ * Multiple entries in deltaenv for the same key are explicitly allowed.
+ *
+ * We return a contiguous block of UNICODE strings with a final trailing
+ * zero word.
*/
-static char *lookup_prog(const char *dir, const char *cmd, int isexe, int exe_only)
+static wchar_t *make_environment_block(char **deltaenv)
{
- char path[MAX_PATH];
- snprintf(path, sizeof(path), "%s/%s.exe", dir, cmd);
+ wchar_t *wenv = GetEnvironmentStringsW(), *wdeltaenv, *result, *p;
+ size_t wlen, s, delta_size, size;
- if (!isexe && access(path, F_OK) == 0)
- return xstrdup(path);
- path[strlen(path)-4] = '\0';
- if ((!exe_only || isexe) && access(path, F_OK) == 0)
- if (!(GetFileAttributes(path) & FILE_ATTRIBUTE_DIRECTORY))
- return xstrdup(path);
- return NULL;
-}
+ wchar_t **array = NULL;
+ size_t alloc = 0, nr = 0, i;
-/*
- * Determines the absolute path of cmd using the split path in path.
- * If cmd contains a slash or backslash, no lookup is performed.
- */
-static char *path_lookup(const char *cmd, char **path, int exe_only)
-{
- char *prog = NULL;
- int len = strlen(cmd);
- int isexe = len >= 4 && !strcasecmp(cmd+len-4, ".exe");
+ size = 1; /* for extra NUL at the end */
- if (strchr(cmd, '/') || strchr(cmd, '\\'))
- prog = xstrdup(cmd);
+ /* If there is no deltaenv to apply, simply return a copy. */
+ if (!deltaenv || !*deltaenv) {
+ for (p = wenv; p && *p; ) {
+ size_t s = wcslen(p) + 1;
+ size += s;
+ p += s;
+ }
- while (!prog && *path)
- prog = lookup_prog(*path++, cmd, isexe, exe_only);
+ ALLOC_ARRAY(result, size);
+ COPY_ARRAY(result, wenv, size);
+ FreeEnvironmentStringsW(wenv);
+ return result;
+ }
- return prog;
+ /*
+ * If there is a deltaenv, let's accumulate all keys into `array`,
+ * sort them using the stable git_stable_qsort() and then copy,
+ * skipping duplicate keys
+ */
+ for (p = wenv; p && *p; ) {
+ ALLOC_GROW(array, nr + 1, alloc);
+ s = wcslen(p) + 1;
+ array[nr++] = p;
+ p += s;
+ size += s;
+ }
+
+ /* (over-)assess size needed for wchar version of deltaenv */
+ for (delta_size = 0, i = 0; deltaenv[i]; i++)
+ delta_size += strlen(deltaenv[i]) * 2 + 1;
+ ALLOC_ARRAY(wdeltaenv, delta_size);
+
+ /* convert the deltaenv, appending to array */
+ for (i = 0, p = wdeltaenv; deltaenv[i]; i++) {
+ ALLOC_GROW(array, nr + 1, alloc);
+ wlen = xutftowcs(p, deltaenv[i], wdeltaenv + delta_size - p);
+ array[nr++] = p;
+ p += wlen + 1;
+ }
+
+ git_stable_qsort(array, nr, sizeof(*array), wenvcmp);
+ ALLOC_ARRAY(result, size + delta_size);
+
+ for (p = result, i = 0; i < nr; i++) {
+ /* Skip any duplicate keys; last one wins */
+ while (i + 1 < nr && !wenvcmp(array + i, array + i + 1))
+ i++;
+
+ /* Skip "to delete" entry */
+ if (!wcschr(array[i], L'='))
+ continue;
+
+ size = wcslen(array[i]) + 1;
+ COPY_ARRAY(p, array[i], size);
+ p += size;
+ }
+ *p = L'\0';
+
+ free(array);
+ free(wdeltaenv);
+ FreeEnvironmentStringsW(wenv);
+ return result;
}
-static int env_compare(const void *a, const void *b)
+static void do_unset_environment_variables(void)
{
- char *const *ea = a;
- char *const *eb = b;
- return strcasecmp(*ea, *eb);
+ static int done;
+ char *p = unset_environment_variables;
+
+ if (done || !p)
+ return;
+ done = 1;
+
+ for (;;) {
+ char *comma = strchr(p, ',');
+
+ if (comma)
+ *comma = '\0';
+ unsetenv(p);
+ if (!comma)
+ break;
+ p = comma + 1;
+ }
}
struct pinfo_t {
struct pinfo_t *next;
pid_t pid;
HANDLE proc;
-} pinfo_t;
-struct pinfo_t *pinfo = NULL;
+};
+static struct pinfo_t *pinfo = NULL;
CRITICAL_SECTION pinfo_cs;
-static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env,
+/* Used to match and chomp off path components */
+static inline int match_last_path_component(const char *path, size_t *len,
+ const char *component)
+{
+ size_t component_len = strlen(component);
+ if (*len < component_len + 1 ||
+ !is_dir_sep(path[*len - component_len - 1]) ||
+ fspathncmp(path + *len - component_len, component, component_len))
+ return 0;
+ *len -= component_len + 1;
+ /* chomp off repeated dir separators */
+ while (*len > 0 && is_dir_sep(path[*len - 1]))
+ (*len)--;
+ return 1;
+}
+
+static int is_msys2_sh(const char *cmd)
+{
+ if (!cmd)
+ return 0;
+
+ if (!strcmp(cmd, "sh")) {
+ static int ret = -1;
+ char *p;
+
+ if (ret >= 0)
+ return ret;
+
+ p = path_lookup(cmd, 0);
+ if (!p)
+ ret = 0;
+ else {
+ size_t len = strlen(p);
+
+ ret = match_last_path_component(p, &len, "sh.exe") &&
+ match_last_path_component(p, &len, "bin") &&
+ match_last_path_component(p, &len, "usr");
+ free(p);
+ }
+ return ret;
+ }
+
+ if (ends_with(cmd, "\\sh.exe")) {
+ static char *sh;
+
+ if (!sh)
+ sh = path_lookup("sh", 0);
+
+ return !fspathcmp(cmd, sh);
+ }
+
+ return 0;
+}
+
+static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaenv,
const char *dir,
int prepend_cmd, int fhin, int fhout, int fherr)
{
- STARTUPINFO si;
+ static int restrict_handle_inheritance = -1;
+ STARTUPINFOEXW si;
PROCESS_INFORMATION pi;
- struct strbuf envblk, args;
- unsigned flags;
+ LPPROC_THREAD_ATTRIBUTE_LIST attr_list = NULL;
+ HANDLE stdhandles[3];
+ DWORD stdhandles_count = 0;
+ SIZE_T size;
+ struct strbuf args;
+ wchar_t wcmd[MAX_PATH], wdir[MAX_PATH], *wargs, *wenvblk = NULL;
+ unsigned flags = CREATE_UNICODE_ENVIRONMENT;
BOOL ret;
+ HANDLE cons;
+ const char *(*quote_arg)(const char *arg) =
+ is_msys2_sh(cmd ? cmd : *argv) ?
+ quote_arg_msys2 : quote_arg_msvc;
+ const char *strace_env;
+
+ /* Make sure to override previous errors, if any */
+ errno = 0;
+
+ if (restrict_handle_inheritance < 0)
+ restrict_handle_inheritance = core_restrict_inherited_handles;
+ /*
+ * The following code to restrict which handles are inherited seems
+ * to work properly only on Windows 7 and later, so let's disable it
+ * on Windows Vista and 2008.
+ */
+ if (restrict_handle_inheritance < 0)
+ restrict_handle_inheritance = GetVersion() >> 16 >= 7601;
+
+ do_unset_environment_variables();
/* Determine whether or not we are associated to a console */
- HANDLE cons = CreateFile("CONOUT$", GENERIC_WRITE,
+ cons = CreateFileW(L"CONOUT$", GENERIC_WRITE,
FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
if (cons == INVALID_HANDLE_VALUE) {
@@ -869,7 +1560,7 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env,
* instead of CREATE_NO_WINDOW to make ssh
* recognize that it has no console.
*/
- flags = DETACHED_PROCESS;
+ flags |= DETACHED_PROCESS;
} else {
/* There is already a console. If we specified
* DETACHED_PROCESS here, too, Windows would
@@ -877,15 +1568,33 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env,
* The same is true for CREATE_NO_WINDOW.
* Go figure!
*/
- flags = 0;
CloseHandle(cons);
}
memset(&si, 0, sizeof(si));
- si.cb = sizeof(si);
- si.dwFlags = STARTF_USESTDHANDLES;
- si.hStdInput = (HANDLE) _get_osfhandle(fhin);
- si.hStdOutput = (HANDLE) _get_osfhandle(fhout);
- si.hStdError = (HANDLE) _get_osfhandle(fherr);
+ si.StartupInfo.cb = sizeof(si);
+ si.StartupInfo.hStdInput = winansi_get_osfhandle(fhin);
+ si.StartupInfo.hStdOutput = winansi_get_osfhandle(fhout);
+ si.StartupInfo.hStdError = winansi_get_osfhandle(fherr);
+
+ /* The list of handles cannot contain duplicates */
+ if (si.StartupInfo.hStdInput != INVALID_HANDLE_VALUE)
+ stdhandles[stdhandles_count++] = si.StartupInfo.hStdInput;
+ if (si.StartupInfo.hStdOutput != INVALID_HANDLE_VALUE &&
+ si.StartupInfo.hStdOutput != si.StartupInfo.hStdInput)
+ stdhandles[stdhandles_count++] = si.StartupInfo.hStdOutput;
+ if (si.StartupInfo.hStdError != INVALID_HANDLE_VALUE &&
+ si.StartupInfo.hStdError != si.StartupInfo.hStdInput &&
+ si.StartupInfo.hStdError != si.StartupInfo.hStdOutput)
+ stdhandles[stdhandles_count++] = si.StartupInfo.hStdError;
+ if (stdhandles_count)
+ si.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
+
+ if (*argv && !strcmp(cmd, *argv))
+ wcmd[0] = L'\0';
+ else if (xutftowcs_path(wcmd, cmd) < 0)
+ return -1;
+ if (dir && xutftowcs_path(wdir, dir) < 0)
+ return -1;
/* concatenate argv, quoting args as we go */
strbuf_init(&args, 0);
@@ -904,38 +1613,130 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env,
free(quoted);
}
- if (env) {
- int count = 0;
- char **e, **sorted_env;
+ strace_env = getenv("GIT_STRACE_COMMANDS");
+ if (strace_env) {
+ char *p = path_lookup("strace.exe", 1);
+ if (!p)
+ return error("strace not found!");
+ if (xutftowcs_path(wcmd, p) < 0) {
+ free(p);
+ return -1;
+ }
+ free(p);
+ if (!strcmp("1", strace_env) ||
+ !strcasecmp("yes", strace_env) ||
+ !strcasecmp("true", strace_env))
+ strbuf_insert(&args, 0, "strace ", 7);
+ else {
+ const char *quoted = quote_arg(strace_env);
+ struct strbuf buf = STRBUF_INIT;
+ strbuf_addf(&buf, "strace -o %s ", quoted);
+ if (quoted != strace_env)
+ free((char *)quoted);
+ strbuf_insert(&args, 0, buf.buf, buf.len);
+ strbuf_release(&buf);
+ }
+ }
- for (e = env; *e; e++)
- count++;
+ ALLOC_ARRAY(wargs, st_add(st_mult(2, args.len), 1));
+ xutftowcs(wargs, args.buf, 2 * args.len + 1);
+ strbuf_release(&args);
- /* environment must be sorted */
- sorted_env = xmalloc(sizeof(*sorted_env) * (count + 1));
- memcpy(sorted_env, env, sizeof(*sorted_env) * (count + 1));
- qsort(sorted_env, count, sizeof(*sorted_env), env_compare);
+ wenvblk = make_environment_block(deltaenv);
- strbuf_init(&envblk, 0);
- for (e = sorted_env; *e; e++) {
- strbuf_addstr(&envblk, *e);
- strbuf_addch(&envblk, '\0');
- }
- free(sorted_env);
+ memset(&pi, 0, sizeof(pi));
+ if (restrict_handle_inheritance && stdhandles_count &&
+ (InitializeProcThreadAttributeList(NULL, 1, 0, &size) ||
+ GetLastError() == ERROR_INSUFFICIENT_BUFFER) &&
+ (attr_list = (LPPROC_THREAD_ATTRIBUTE_LIST)
+ (HeapAlloc(GetProcessHeap(), 0, size))) &&
+ InitializeProcThreadAttributeList(attr_list, 1, 0, &size) &&
+ UpdateProcThreadAttribute(attr_list, 0,
+ PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
+ stdhandles,
+ stdhandles_count * sizeof(HANDLE),
+ NULL, NULL)) {
+ si.lpAttributeList = attr_list;
+ flags |= EXTENDED_STARTUPINFO_PRESENT;
}
- memset(&pi, 0, sizeof(pi));
- ret = CreateProcess(cmd, args.buf, NULL, NULL, TRUE, flags,
- env ? envblk.buf : NULL, dir, &si, &pi);
+ ret = CreateProcessW(*wcmd ? wcmd : NULL, wargs, NULL, NULL,
+ stdhandles_count ? TRUE : FALSE,
+ flags, wenvblk, dir ? wdir : NULL,
+ &si.StartupInfo, &pi);
- if (env)
- strbuf_release(&envblk);
- strbuf_release(&args);
+ /*
+ * On Windows 2008 R2, it seems that specifying certain types of handles
+ * (such as FILE_TYPE_CHAR or FILE_TYPE_PIPE) will always produce an
+ * error. Rather than playing finicky and fragile games, let's just try
+ * to detect this situation and simply try again without restricting any
+ * handle inheritance. This is still better than failing to create
+ * processes.
+ */
+ if (!ret && restrict_handle_inheritance && stdhandles_count) {
+ DWORD err = GetLastError();
+ struct strbuf buf = STRBUF_INIT;
+
+ if (err != ERROR_NO_SYSTEM_RESOURCES &&
+ /*
+ * On Windows 7 and earlier, handles on pipes and character
+ * devices are inherited automatically, and cannot be
+ * specified in the thread handle list. Rather than trying
+ * to catch each and every corner case (and running the
+ * chance of *still* forgetting a few), let's just fall
+ * back to creating the process without trying to limit the
+ * handle inheritance.
+ */
+ !(err == ERROR_INVALID_PARAMETER &&
+ GetVersion() >> 16 < 9200) &&
+ !getenv("SUPPRESS_HANDLE_INHERITANCE_WARNING")) {
+ DWORD fl = 0;
+ int i;
+
+ setenv("SUPPRESS_HANDLE_INHERITANCE_WARNING", "1", 1);
+
+ for (i = 0; i < stdhandles_count; i++) {
+ HANDLE h = stdhandles[i];
+ strbuf_addf(&buf, "handle #%d: %p (type %lx, "
+ "handle info (%d) %lx\n", i, h,
+ GetFileType(h),
+ GetHandleInformation(h, &fl),
+ fl);
+ }
+ strbuf_addstr(&buf, "\nThis is a bug; please report it "
+ "at\nhttps://github.com/git-for-windows/"
+ "git/issues/new\n\n"
+ "To suppress this warning, please set "
+ "the environment variable\n\n"
+ "\tSUPPRESS_HANDLE_INHERITANCE_WARNING=1"
+ "\n");
+ }
+ restrict_handle_inheritance = 0;
+ flags &= ~EXTENDED_STARTUPINFO_PRESENT;
+ ret = CreateProcessW(*wcmd ? wcmd : NULL, wargs, NULL, NULL,
+ TRUE, flags, wenvblk, dir ? wdir : NULL,
+ &si.StartupInfo, &pi);
+ if (!ret)
+ errno = err_win_to_posix(GetLastError());
+ if (ret && buf.len) {
+ warning("failed to restrict file handles (%ld)\n\n%s",
+ err, buf.buf);
+ }
+ strbuf_release(&buf);
+ } else if (!ret)
+ errno = err_win_to_posix(GetLastError());
- if (!ret) {
- errno = ENOENT;
+ if (si.lpAttributeList)
+ DeleteProcThreadAttributeList(si.lpAttributeList);
+ if (attr_list)
+ HeapFree(GetProcessHeap(), 0, attr_list);
+
+ free(wenvblk);
+ free(wargs);
+
+ if (!ret)
return -1;
- }
+
CloseHandle(pi.hThread);
/*
@@ -959,19 +1760,17 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env,
return (pid_t)pi.dwProcessId;
}
-static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env,
- int prepend_cmd)
+static pid_t mingw_spawnv(const char *cmd, const char **argv, int prepend_cmd)
{
- return mingw_spawnve_fd(cmd, argv, env, NULL, prepend_cmd, 0, 1, 2);
+ return mingw_spawnve_fd(cmd, argv, NULL, NULL, prepend_cmd, 0, 1, 2);
}
-pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env,
+pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **deltaenv,
const char *dir,
int fhin, int fhout, int fherr)
{
pid_t pid;
- char **path = get_path_split();
- char *prog = path_lookup(cmd, path, 0);
+ char *prog = path_lookup(cmd, 0);
if (!prog) {
errno = ENOENT;
@@ -982,94 +1781,95 @@ pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env,
if (interpr) {
const char *argv0 = argv[0];
- char *iprog = path_lookup(interpr, path, 1);
+ char *iprog = path_lookup(interpr, 1);
argv[0] = prog;
if (!iprog) {
errno = ENOENT;
pid = -1;
}
else {
- pid = mingw_spawnve_fd(iprog, argv, env, dir, 1,
+ pid = mingw_spawnve_fd(iprog, argv, deltaenv, dir, 1,
fhin, fhout, fherr);
free(iprog);
}
argv[0] = argv0;
}
else
- pid = mingw_spawnve_fd(prog, argv, env, dir, 0,
+ pid = mingw_spawnve_fd(prog, argv, deltaenv, dir, 0,
fhin, fhout, fherr);
free(prog);
}
- free_path_split(path);
return pid;
}
-static int try_shell_exec(const char *cmd, char *const *argv, char **env)
+static int try_shell_exec(const char *cmd, char *const *argv)
{
const char *interpr = parse_interpreter(cmd);
- char **path;
char *prog;
int pid = 0;
if (!interpr)
return 0;
- path = get_path_split();
- prog = path_lookup(interpr, path, 1);
+ prog = path_lookup(interpr, 1);
if (prog) {
+ int exec_id;
int argc = 0;
- const char **argv2;
+#ifndef _MSC_VER
+ const
+#endif
+ char **argv2;
while (argv[argc]) argc++;
- argv2 = xmalloc(sizeof(*argv) * (argc+1));
+ ALLOC_ARRAY(argv2, argc + 1);
argv2[0] = (char *)cmd; /* full path to the script file */
- memcpy(&argv2[1], &argv[1], sizeof(*argv) * argc);
- pid = mingw_spawnve(prog, argv2, env, 1);
+ COPY_ARRAY(&argv2[1], &argv[1], argc);
+ exec_id = trace2_exec(prog, argv2);
+ pid = mingw_spawnv(prog, argv2, 1);
if (pid >= 0) {
int status;
if (waitpid(pid, &status, 0) < 0)
status = 255;
+ trace2_exec_result(exec_id, status);
exit(status);
}
+ trace2_exec_result(exec_id, -1);
pid = 1; /* indicate that we tried but failed */
free(prog);
free(argv2);
}
- free_path_split(path);
return pid;
}
-static void mingw_execve(const char *cmd, char *const *argv, char *const *env)
+int mingw_execv(const char *cmd, char *const *argv)
{
/* check if git_command is a shell script */
- if (!try_shell_exec(cmd, argv, (char **)env)) {
+ if (!try_shell_exec(cmd, argv)) {
int pid, status;
+ int exec_id;
- pid = mingw_spawnve(cmd, (const char **)argv, (char **)env, 0);
- if (pid < 0)
- return;
+ exec_id = trace2_exec(cmd, (const char **)argv);
+ pid = mingw_spawnv(cmd, (const char **)argv, 0);
+ if (pid < 0) {
+ trace2_exec_result(exec_id, -1);
+ return -1;
+ }
if (waitpid(pid, &status, 0) < 0)
status = 255;
+ trace2_exec_result(exec_id, status);
exit(status);
}
+ return -1;
}
int mingw_execvp(const char *cmd, char *const *argv)
{
- char **path = get_path_split();
- char *prog = path_lookup(cmd, path, 0);
+ char *prog = path_lookup(cmd, 0);
if (prog) {
- mingw_execve(prog, argv, environ);
+ mingw_execv(prog, argv);
free(prog);
} else
errno = ENOENT;
- free_path_split(path);
- return -1;
-}
-
-int mingw_execv(const char *cmd, char *const *argv)
-{
- mingw_execve(cmd, argv, environ);
return -1;
}
@@ -1086,253 +1886,104 @@ int mingw_kill(pid_t pid, int sig)
errno = err_win_to_posix(GetLastError());
CloseHandle(h);
return -1;
+ } else if (pid > 0 && sig == 0) {
+ HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
+ if (h) {
+ CloseHandle(h);
+ return 0;
+ }
}
errno = EINVAL;
return -1;
}
-static char **copy_environ(void)
-{
- char **env;
- int i = 0;
- while (environ[i])
- i++;
- env = xmalloc((i+1)*sizeof(*env));
- for (i = 0; environ[i]; i++)
- env[i] = xstrdup(environ[i]);
- env[i] = NULL;
- return env;
-}
-
-void free_environ(char **env)
-{
- int i;
- for (i = 0; env[i]; i++)
- free(env[i]);
- free(env);
-}
-
-static int lookup_env(char **env, const char *name, size_t nmln)
-{
- int i;
-
- for (i = 0; env[i]; i++) {
- if (0 == strncmp(env[i], name, nmln)
- && '=' == env[i][nmln])
- /* matches */
- return i;
- }
- return -1;
-}
-
-/*
- * If name contains '=', then sets the variable, otherwise it unsets it
- */
-static char **env_setenv(char **env, const char *name)
-{
- char *eq = strchrnul(name, '=');
- int i = lookup_env(env, name, eq-name);
-
- if (i < 0) {
- if (*eq) {
- for (i = 0; env[i]; i++)
- ;
- env = xrealloc(env, (i+2)*sizeof(*env));
- env[i] = xstrdup(name);
- env[i+1] = NULL;
- }
- }
- else {
- free(env[i]);
- if (*eq)
- env[i] = xstrdup(name);
- else
- for (; env[i]; i++)
- env[i] = env[i+1];
- }
- return env;
-}
-
/*
- * Copies global environ and adjusts variables as specified by vars.
+ * UTF-8 versions of getenv(), putenv() and unsetenv().
+ * Internally, they use the CRT's stock UNICODE routines
+ * to avoid data loss.
*/
-char **make_augmented_environ(const char *const *vars)
+char *mingw_getenv(const char *name)
{
- char **env = copy_environ();
+#define GETENV_MAX_RETAIN 64
+ static char *values[GETENV_MAX_RETAIN];
+ static int value_counter;
+ int len_key, len_value;
+ wchar_t *w_key;
+ char *value;
+ wchar_t w_value[32768];
- while (*vars)
- env = env_setenv(env, *vars++);
- return env;
-}
-
-#undef getenv
-
-/*
- * The system's getenv looks up the name in a case-insensitive manner.
- * This version tries a case-sensitive lookup and falls back to
- * case-insensitive if nothing was found. This is necessary because,
- * as a prominent example, CMD sets 'Path', but not 'PATH'.
- * Warning: not thread-safe.
- */
-static char *getenv_cs(const char *name)
-{
- size_t len = strlen(name);
- int i = lookup_env(environ, name, len);
- if (i >= 0)
- return environ[i] + len + 1; /* skip past name and '=' */
- return getenv(name);
-}
+ if (!name || !*name)
+ return NULL;
-char *mingw_getenv(const char *name)
-{
- char *result = getenv_cs(name);
- if (!result && !strcmp(name, "TMPDIR")) {
- /* on Windows it is TMP and TEMP */
- result = getenv_cs("TMP");
- if (!result)
- result = getenv_cs("TEMP");
+ len_key = strlen(name) + 1;
+ /* We cannot use xcalloc() here because that uses getenv() itself */
+ w_key = calloc(len_key, sizeof(wchar_t));
+ if (!w_key)
+ die("Out of memory, (tried to allocate %u wchar_t's)", len_key);
+ xutftowcs(w_key, name, len_key);
+ /* GetEnvironmentVariableW() only sets the last error upon failure */
+ SetLastError(ERROR_SUCCESS);
+ len_value = GetEnvironmentVariableW(w_key, w_value, ARRAY_SIZE(w_value));
+ if (!len_value && GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
+ free(w_key);
+ return NULL;
}
- return result;
-}
+ free(w_key);
-/*
- * Note, this isn't a complete replacement for getaddrinfo. It assumes
- * that service contains a numerical port, or that it is null. It
- * does a simple search using gethostbyname, and returns one IPv4 host
- * if one was found.
- */
-static int WSAAPI getaddrinfo_stub(const char *node, const char *service,
- const struct addrinfo *hints,
- struct addrinfo **res)
-{
- struct hostent *h = NULL;
- struct addrinfo *ai;
- struct sockaddr_in *sin;
-
- if (node) {
- h = gethostbyname(node);
- if (!h)
- return WSAGetLastError();
- }
-
- ai = xmalloc(sizeof(struct addrinfo));
- *res = ai;
- ai->ai_flags = 0;
- ai->ai_family = AF_INET;
- ai->ai_socktype = hints ? hints->ai_socktype : 0;
- switch (ai->ai_socktype) {
- case SOCK_STREAM:
- ai->ai_protocol = IPPROTO_TCP;
- break;
- case SOCK_DGRAM:
- ai->ai_protocol = IPPROTO_UDP;
- break;
- default:
- ai->ai_protocol = 0;
- break;
- }
- ai->ai_addrlen = sizeof(struct sockaddr_in);
- if (hints && (hints->ai_flags & AI_CANONNAME))
- ai->ai_canonname = h ? xstrdup(h->h_name) : NULL;
- else
- ai->ai_canonname = NULL;
-
- sin = xmalloc(ai->ai_addrlen);
- memset(sin, 0, ai->ai_addrlen);
- sin->sin_family = AF_INET;
- /* Note: getaddrinfo is supposed to allow service to be a string,
- * which should be looked up using getservbyname. This is
- * currently not implemented */
- if (service)
- sin->sin_port = htons(atoi(service));
- if (h)
- sin->sin_addr = *(struct in_addr *)h->h_addr;
- else if (hints && (hints->ai_flags & AI_PASSIVE))
- sin->sin_addr.s_addr = INADDR_ANY;
- else
- sin->sin_addr.s_addr = INADDR_LOOPBACK;
- ai->ai_addr = (struct sockaddr *)sin;
- ai->ai_next = 0;
- return 0;
-}
+ len_value = len_value * 3 + 1;
+ /* We cannot use xcalloc() here because that uses getenv() itself */
+ value = calloc(len_value, sizeof(char));
+ if (!value)
+ die("Out of memory, (tried to allocate %u bytes)", len_value);
+ xwcstoutf(value, w_value, len_value);
-static void WSAAPI freeaddrinfo_stub(struct addrinfo *res)
-{
- free(res->ai_canonname);
- free(res->ai_addr);
- free(res);
+ /*
+ * We return `value` which is an allocated value and the caller is NOT
+ * expecting to have to free it, so we keep a round-robin array,
+ * invalidating the buffer after GETENV_MAX_RETAIN getenv() calls.
+ */
+ free(values[value_counter]);
+ values[value_counter++] = value;
+ if (value_counter >= ARRAY_SIZE(values))
+ value_counter = 0;
+
+ return value;
}
-static int WSAAPI getnameinfo_stub(const struct sockaddr *sa, socklen_t salen,
- char *host, DWORD hostlen,
- char *serv, DWORD servlen, int flags)
+int mingw_putenv(const char *namevalue)
{
- const struct sockaddr_in *sin = (const struct sockaddr_in *)sa;
- if (sa->sa_family != AF_INET)
- return EAI_FAMILY;
- if (!host && !serv)
- return EAI_NONAME;
-
- if (host && hostlen > 0) {
- struct hostent *ent = NULL;
- if (!(flags & NI_NUMERICHOST))
- ent = gethostbyaddr((const char *)&sin->sin_addr,
- sizeof(sin->sin_addr), AF_INET);
+ int size;
+ wchar_t *wide, *equal;
+ BOOL result;
- if (ent)
- snprintf(host, hostlen, "%s", ent->h_name);
- else if (flags & NI_NAMEREQD)
- return EAI_NONAME;
- else
- snprintf(host, hostlen, "%s", inet_ntoa(sin->sin_addr));
- }
-
- if (serv && servlen > 0) {
- struct servent *ent = NULL;
- if (!(flags & NI_NUMERICSERV))
- ent = getservbyport(sin->sin_port,
- flags & NI_DGRAM ? "udp" : "tcp");
+ if (!namevalue || !*namevalue)
+ return 0;
- if (ent)
- snprintf(serv, servlen, "%s", ent->s_name);
- else
- snprintf(serv, servlen, "%d", ntohs(sin->sin_port));
+ size = strlen(namevalue) * 2 + 1;
+ wide = calloc(size, sizeof(wchar_t));
+ if (!wide)
+ die("Out of memory, (tried to allocate %u wchar_t's)", size);
+ xutftowcs(wide, namevalue, size);
+ equal = wcschr(wide, L'=');
+ if (!equal)
+ result = SetEnvironmentVariableW(wide, NULL);
+ else {
+ *equal = L'\0';
+ result = SetEnvironmentVariableW(wide, equal + 1);
}
+ free(wide);
- return 0;
-}
-
-static HMODULE ipv6_dll = NULL;
-static void (WSAAPI *ipv6_freeaddrinfo)(struct addrinfo *res);
-static int (WSAAPI *ipv6_getaddrinfo)(const char *node, const char *service,
- const struct addrinfo *hints,
- struct addrinfo **res);
-static int (WSAAPI *ipv6_getnameinfo)(const struct sockaddr *sa, socklen_t salen,
- char *host, DWORD hostlen,
- char *serv, DWORD servlen, int flags);
-/*
- * gai_strerror is an inline function in the ws2tcpip.h header, so we
- * don't need to try to load that one dynamically.
- */
+ if (!result)
+ errno = err_win_to_posix(GetLastError());
-static void socket_cleanup(void)
-{
- WSACleanup();
- if (ipv6_dll)
- FreeLibrary(ipv6_dll);
- ipv6_dll = NULL;
- ipv6_freeaddrinfo = freeaddrinfo_stub;
- ipv6_getaddrinfo = getaddrinfo_stub;
- ipv6_getnameinfo = getnameinfo_stub;
+ return result ? 0 : -1;
}
static void ensure_socket_initialization(void)
{
WSADATA wsa;
static int initialized = 0;
- const char *libraries[] = { "ws2_32.dll", "wship6.dll", NULL };
- const char **name;
if (initialized)
return;
@@ -1341,34 +1992,7 @@ static void ensure_socket_initialization(void)
die("unable to initialize winsock subsystem, error %d",
WSAGetLastError());
- for (name = libraries; *name; name++) {
- ipv6_dll = LoadLibrary(*name);
- if (!ipv6_dll)
- continue;
-
- ipv6_freeaddrinfo = (void (WSAAPI *)(struct addrinfo *))
- GetProcAddress(ipv6_dll, "freeaddrinfo");
- ipv6_getaddrinfo = (int (WSAAPI *)(const char *, const char *,
- const struct addrinfo *,
- struct addrinfo **))
- GetProcAddress(ipv6_dll, "getaddrinfo");
- ipv6_getnameinfo = (int (WSAAPI *)(const struct sockaddr *,
- socklen_t, char *, DWORD,
- char *, DWORD, int))
- GetProcAddress(ipv6_dll, "getnameinfo");
- if (!ipv6_freeaddrinfo || !ipv6_getaddrinfo || !ipv6_getnameinfo) {
- FreeLibrary(ipv6_dll);
- ipv6_dll = NULL;
- } else
- break;
- }
- if (!ipv6_freeaddrinfo || !ipv6_getaddrinfo || !ipv6_getnameinfo) {
- ipv6_freeaddrinfo = freeaddrinfo_stub;
- ipv6_getaddrinfo = getaddrinfo_stub;
- ipv6_getnameinfo = getnameinfo_stub;
- }
-
- atexit(socket_cleanup);
+ atexit((void(*)(void)) WSACleanup);
initialized = 1;
}
@@ -1386,24 +2010,12 @@ struct hostent *mingw_gethostbyname(const char *host)
return gethostbyname(host);
}
-void mingw_freeaddrinfo(struct addrinfo *res)
-{
- ipv6_freeaddrinfo(res);
-}
-
+#undef getaddrinfo
int mingw_getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints, struct addrinfo **res)
{
ensure_socket_initialization();
- return ipv6_getaddrinfo(node, service, hints, res);
-}
-
-int mingw_getnameinfo(const struct sockaddr *sa, socklen_t salen,
- char *host, DWORD hostlen, char *serv, DWORD servlen,
- int flags)
-{
- ensure_socket_initialization();
- return ipv6_getnameinfo(sa, salen, host, hostlen, serv, servlen, flags);
+ return getaddrinfo(node, service, hints, res);
}
int mingw_socket(int domain, int type, int protocol)
@@ -1493,33 +2105,41 @@ int mingw_rename(const char *pold, const char *pnew)
{
DWORD attrs, gle;
int tries = 0;
+ wchar_t wpold[MAX_PATH], wpnew[MAX_PATH];
+ if (xutftowcs_path(wpold, pold) < 0 || xutftowcs_path(wpnew, pnew) < 0)
+ return -1;
/*
* Try native rename() first to get errno right.
* It is based on MoveFile(), which cannot overwrite existing files.
*/
- if (!rename(pold, pnew))
+ if (!_wrename(wpold, wpnew))
return 0;
if (errno != EEXIST)
return -1;
repeat:
- if (MoveFileEx(pold, pnew, MOVEFILE_REPLACE_EXISTING))
+ if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING))
return 0;
/* TODO: translate more errors */
gle = GetLastError();
if (gle == ERROR_ACCESS_DENIED &&
- (attrs = GetFileAttributes(pnew)) != INVALID_FILE_ATTRIBUTES) {
+ (attrs = GetFileAttributesW(wpnew)) != INVALID_FILE_ATTRIBUTES) {
if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
- errno = EISDIR;
+ DWORD attrsold = GetFileAttributesW(wpold);
+ if (attrsold == INVALID_FILE_ATTRIBUTES ||
+ !(attrsold & FILE_ATTRIBUTE_DIRECTORY))
+ errno = EISDIR;
+ else if (!_wrmdir(wpnew))
+ goto repeat;
return -1;
}
if ((attrs & FILE_ATTRIBUTE_READONLY) &&
- SetFileAttributes(pnew, attrs & ~FILE_ATTRIBUTE_READONLY)) {
- if (MoveFileEx(pold, pnew, MOVEFILE_REPLACE_EXISTING))
+ SetFileAttributesW(wpnew, attrs & ~FILE_ATTRIBUTE_READONLY)) {
+ if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING))
return 0;
gle = GetLastError();
/* revert file attributes on failure */
- SetFileAttributes(pnew, attrs);
+ SetFileAttributesW(wpnew, attrs);
}
}
if (tries < ARRAY_SIZE(delay) && gle == ERROR_ACCESS_DENIED) {
@@ -1555,18 +2175,69 @@ int mingw_getpagesize(void)
return si.dwAllocationGranularity;
}
+/* See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724435.aspx */
+enum EXTENDED_NAME_FORMAT {
+ NameDisplay = 3,
+ NameUserPrincipal = 8
+};
+
+static char *get_extended_user_info(enum EXTENDED_NAME_FORMAT type)
+{
+ DECLARE_PROC_ADDR(secur32.dll, BOOL, GetUserNameExW,
+ enum EXTENDED_NAME_FORMAT, LPCWSTR, PULONG);
+ static wchar_t wbuffer[1024];
+ DWORD len;
+
+ if (!INIT_PROC_ADDR(GetUserNameExW))
+ return NULL;
+
+ len = ARRAY_SIZE(wbuffer);
+ if (GetUserNameExW(type, wbuffer, &len)) {
+ char *converted = xmalloc((len *= 3));
+ if (xwcstoutf(converted, wbuffer, len) >= 0)
+ return converted;
+ free(converted);
+ }
+
+ return NULL;
+}
+
+char *mingw_query_user_email(void)
+{
+ return get_extended_user_info(NameUserPrincipal);
+}
+
struct passwd *getpwuid(int uid)
{
+ static unsigned initialized;
static char user_name[100];
- static struct passwd p;
+ static struct passwd *p;
+ wchar_t buf[100];
+ DWORD len;
+
+ if (initialized)
+ return p;
+
+ len = ARRAY_SIZE(buf);
+ if (!GetUserNameW(buf, &len)) {
+ initialized = 1;
+ return NULL;
+ }
- DWORD len = sizeof(user_name);
- if (!GetUserName(user_name, &len))
+ if (xwcstoutf(user_name, buf, sizeof(user_name)) < 0) {
+ initialized = 1;
return NULL;
- p.pw_name = user_name;
- p.pw_gecos = "unknown";
- p.pw_dir = NULL;
- return &p;
+ }
+
+ p = xmalloc(sizeof(*p));
+ p->pw_name = user_name;
+ p->pw_gecos = get_extended_user_info(NameDisplay);
+ if (!p->pw_gecos)
+ p->pw_gecos = "unknown";
+ p->pw_dir = NULL;
+
+ initialized = 1;
+ return p;
}
static HANDLE timer_event;
@@ -1613,7 +2284,7 @@ static void stop_timer_thread(void)
if (timer_event)
SetEvent(timer_event); /* tell thread to terminate */
if (timer_thread) {
- int rc = WaitForSingleObject(timer_thread, 1000);
+ int rc = WaitForSingleObject(timer_thread, 10000);
if (rc == WAIT_TIMEOUT)
error("timer thread did not terminate timely");
else if (rc != WAIT_OBJECT_0)
@@ -1677,14 +2348,16 @@ int sigaction(int sig, struct sigaction *in, struct sigaction *out)
#undef signal
sig_handler_t mingw_signal(int sig, sig_handler_t handler)
{
- sig_handler_t old = timer_fn;
+ sig_handler_t old;
switch (sig) {
case SIGALRM:
+ old = timer_fn;
timer_fn = handler;
break;
case SIGINT:
+ old = sigint_fn;
sigint_fn = handler;
break;
@@ -1715,67 +2388,44 @@ int mingw_raise(int sig)
sigint_fn(SIGINT);
return 0;
- default:
+#if defined(_MSC_VER)
+ case SIGILL:
+ case SIGFPE:
+ case SIGSEGV:
+ case SIGTERM:
+ case SIGBREAK:
+ case SIGABRT:
+ case SIGABRT_COMPAT:
+ /*
+ * The <signal.h> header in the MS C Runtime defines 8 signals
+ * as being supported on the platform. Anything else causes an
+ * "Invalid signal or error" (which in DEBUG builds causes the
+ * Abort/Retry/Ignore dialog). We by-pass the CRT for things we
+ * already know will fail.
+ */
return raise(sig);
- }
-}
-
-
-static const char *make_backslash_path(const char *path)
-{
- static char buf[PATH_MAX + 1];
- char *c;
-
- if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX)
- die("Too long path: %.*s", 60, path);
+ default:
+ errno = EINVAL;
+ return -1;
- for (c = buf; *c; c++) {
- if (*c == '/')
- *c = '\\';
- }
- return buf;
-}
+#else
-void mingw_open_html(const char *unixpath)
-{
- const char *htmlpath = make_backslash_path(unixpath);
- typedef HINSTANCE (WINAPI *T)(HWND, const char *,
- const char *, const char *, const char *, INT);
- T ShellExecute;
- HMODULE shell32;
- int r;
+ default:
+ return raise(sig);
- shell32 = LoadLibrary("shell32.dll");
- if (!shell32)
- die("cannot load shell32.dll");
- ShellExecute = (T)GetProcAddress(shell32, "ShellExecuteA");
- if (!ShellExecute)
- die("cannot run browser");
+#endif
- printf("Launching default browser to display HTML ...\n");
- r = (int)ShellExecute(NULL, "open", htmlpath, NULL, "\\", SW_SHOWNORMAL);
- FreeLibrary(shell32);
- /* see the MSDN documentation referring to the result codes here */
- if (r <= 32) {
- die("failed to launch browser for %.*s", MAX_PATH, unixpath);
}
}
int link(const char *oldpath, const char *newpath)
{
- typedef BOOL (WINAPI *T)(const char*, const char*, LPSECURITY_ATTRIBUTES);
- static T create_hard_link = NULL;
- if (!create_hard_link) {
- create_hard_link = (T) GetProcAddress(
- GetModuleHandle("kernel32.dll"), "CreateHardLinkA");
- if (!create_hard_link)
- create_hard_link = (T)-1;
- }
- if (create_hard_link == (T)-1) {
- errno = ENOSYS;
+ wchar_t woldpath[MAX_PATH], wnewpath[MAX_PATH];
+ if (xutftowcs_path(woldpath, oldpath) < 0 ||
+ xutftowcs_path(wnewpath, newpath) < 0)
return -1;
- }
- if (!create_hard_link(newpath, oldpath, NULL)) {
+
+ if (!CreateHardLinkW(wnewpath, woldpath, NULL)) {
errno = err_win_to_posix(GetLastError());
return -1;
}
@@ -1833,3 +2483,446 @@ pid_t waitpid(pid_t pid, int *status, int options)
errno = EINVAL;
return -1;
}
+
+int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen)
+{
+ int upos = 0, wpos = 0;
+ const unsigned char *utf = (const unsigned char*) utfs;
+ if (!utf || !wcs || wcslen < 1) {
+ errno = EINVAL;
+ return -1;
+ }
+ /* reserve space for \0 */
+ wcslen--;
+ if (utflen < 0)
+ utflen = INT_MAX;
+
+ while (upos < utflen) {
+ int c = utf[upos++] & 0xff;
+ if (utflen == INT_MAX && c == 0)
+ break;
+
+ if (wpos >= wcslen) {
+ wcs[wpos] = 0;
+ errno = ERANGE;
+ return -1;
+ }
+
+ if (c < 0x80) {
+ /* ASCII */
+ wcs[wpos++] = c;
+ } else if (c >= 0xc2 && c < 0xe0 && upos < utflen &&
+ (utf[upos] & 0xc0) == 0x80) {
+ /* 2-byte utf-8 */
+ c = ((c & 0x1f) << 6);
+ c |= (utf[upos++] & 0x3f);
+ wcs[wpos++] = c;
+ } else if (c >= 0xe0 && c < 0xf0 && upos + 1 < utflen &&
+ !(c == 0xe0 && utf[upos] < 0xa0) && /* over-long encoding */
+ (utf[upos] & 0xc0) == 0x80 &&
+ (utf[upos + 1] & 0xc0) == 0x80) {
+ /* 3-byte utf-8 */
+ c = ((c & 0x0f) << 12);
+ c |= ((utf[upos++] & 0x3f) << 6);
+ c |= (utf[upos++] & 0x3f);
+ wcs[wpos++] = c;
+ } else if (c >= 0xf0 && c < 0xf5 && upos + 2 < utflen &&
+ wpos + 1 < wcslen &&
+ !(c == 0xf0 && utf[upos] < 0x90) && /* over-long encoding */
+ !(c == 0xf4 && utf[upos] >= 0x90) && /* > \u10ffff */
+ (utf[upos] & 0xc0) == 0x80 &&
+ (utf[upos + 1] & 0xc0) == 0x80 &&
+ (utf[upos + 2] & 0xc0) == 0x80) {
+ /* 4-byte utf-8: convert to \ud8xx \udcxx surrogate pair */
+ c = ((c & 0x07) << 18);
+ c |= ((utf[upos++] & 0x3f) << 12);
+ c |= ((utf[upos++] & 0x3f) << 6);
+ c |= (utf[upos++] & 0x3f);
+ c -= 0x10000;
+ wcs[wpos++] = 0xd800 | (c >> 10);
+ wcs[wpos++] = 0xdc00 | (c & 0x3ff);
+ } else if (c >= 0xa0) {
+ /* invalid utf-8 byte, printable unicode char: convert 1:1 */
+ wcs[wpos++] = c;
+ } else {
+ /* invalid utf-8 byte, non-printable unicode: convert to hex */
+ static const char *hex = "0123456789abcdef";
+ wcs[wpos++] = hex[c >> 4];
+ if (wpos < wcslen)
+ wcs[wpos++] = hex[c & 0x0f];
+ }
+ }
+ wcs[wpos] = 0;
+ return wpos;
+}
+
+int xwcstoutf(char *utf, const wchar_t *wcs, size_t utflen)
+{
+ if (!wcs || !utf || utflen < 1) {
+ errno = EINVAL;
+ return -1;
+ }
+ utflen = WideCharToMultiByte(CP_UTF8, 0, wcs, -1, utf, utflen, NULL, NULL);
+ if (utflen)
+ return utflen - 1;
+ errno = ERANGE;
+ return -1;
+}
+
+static void setup_windows_environment(void)
+{
+ char *tmp = getenv("TMPDIR");
+
+ /* on Windows it is TMP and TEMP */
+ if (!tmp) {
+ if (!(tmp = getenv("TMP")))
+ tmp = getenv("TEMP");
+ if (tmp) {
+ setenv("TMPDIR", tmp, 1);
+ tmp = getenv("TMPDIR");
+ }
+ }
+
+ if (tmp) {
+ /*
+ * Convert all dir separators to forward slashes,
+ * to help shell commands called from the Git
+ * executable (by not mistaking the dir separators
+ * for escape characters).
+ */
+ convert_slashes(tmp);
+ }
+
+ /* simulate TERM to enable auto-color (see color.c) */
+ if (!getenv("TERM"))
+ setenv("TERM", "cygwin", 1);
+
+ /* calculate HOME if not set */
+ if (!getenv("HOME")) {
+ /*
+ * try $HOMEDRIVE$HOMEPATH - the home share may be a network
+ * location, thus also check if the path exists (i.e. is not
+ * disconnected)
+ */
+ if ((tmp = getenv("HOMEDRIVE"))) {
+ struct strbuf buf = STRBUF_INIT;
+ strbuf_addstr(&buf, tmp);
+ if ((tmp = getenv("HOMEPATH"))) {
+ strbuf_addstr(&buf, tmp);
+ if (is_directory(buf.buf))
+ setenv("HOME", buf.buf, 1);
+ else
+ tmp = NULL; /* use $USERPROFILE */
+ }
+ strbuf_release(&buf);
+ }
+ /* use $USERPROFILE if the home share is not available */
+ if (!tmp && (tmp = getenv("USERPROFILE")))
+ setenv("HOME", tmp, 1);
+ }
+}
+
+int is_valid_win32_path(const char *path, int allow_literal_nul)
+{
+ const char *p = path;
+ int preceding_space_or_period = 0, i = 0, periods = 0;
+
+ if (!protect_ntfs)
+ return 1;
+
+ skip_dos_drive_prefix((char **)&path);
+ goto segment_start;
+
+ for (;;) {
+ char c = *(path++);
+ switch (c) {
+ case '\0':
+ case '/': case '\\':
+ /* cannot end in ` ` or `.`, except for `.` and `..` */
+ if (preceding_space_or_period &&
+ (i != periods || periods > 2))
+ return 0;
+ if (!c)
+ return 1;
+
+ i = periods = preceding_space_or_period = 0;
+
+segment_start:
+ switch (*path) {
+ case 'a': case 'A': /* AUX */
+ if (((c = path[++i]) != 'u' && c != 'U') ||
+ ((c = path[++i]) != 'x' && c != 'X')) {
+not_a_reserved_name:
+ path += i;
+ continue;
+ }
+ break;
+ case 'c': case 'C':
+ /* COM1 ... COM9, CON, CONIN$, CONOUT$ */
+ if ((c = path[++i]) != 'o' && c != 'O')
+ goto not_a_reserved_name;
+ c = path[++i];
+ if (c == 'm' || c == 'M') { /* COM1 ... COM9 */
+ c = path[++i];
+ if (c < '1' || c > '9')
+ goto not_a_reserved_name;
+ } else if (c == 'n' || c == 'N') { /* CON */
+ c = path[i + 1];
+ if ((c == 'i' || c == 'I') &&
+ ((c = path[i + 2]) == 'n' ||
+ c == 'N') &&
+ path[i + 3] == '$')
+ i += 3; /* CONIN$ */
+ else if ((c == 'o' || c == 'O') &&
+ ((c = path[i + 2]) == 'u' ||
+ c == 'U') &&
+ ((c = path[i + 3]) == 't' ||
+ c == 'T') &&
+ path[i + 4] == '$')
+ i += 4; /* CONOUT$ */
+ } else
+ goto not_a_reserved_name;
+ break;
+ case 'l': case 'L': /* LPT<N> */
+ if (((c = path[++i]) != 'p' && c != 'P') ||
+ ((c = path[++i]) != 't' && c != 'T') ||
+ !isdigit(path[++i]))
+ goto not_a_reserved_name;
+ break;
+ case 'n': case 'N': /* NUL */
+ if (((c = path[++i]) != 'u' && c != 'U') ||
+ ((c = path[++i]) != 'l' && c != 'L') ||
+ (allow_literal_nul &&
+ !path[i + 1] && p == path))
+ goto not_a_reserved_name;
+ break;
+ case 'p': case 'P': /* PRN */
+ if (((c = path[++i]) != 'r' && c != 'R') ||
+ ((c = path[++i]) != 'n' && c != 'N'))
+ goto not_a_reserved_name;
+ break;
+ default:
+ continue;
+ }
+
+ /*
+ * So far, this looks like a reserved name. Let's see
+ * whether it actually is one: trailing spaces, a file
+ * extension, or an NTFS Alternate Data Stream do not
+ * matter, the name is still reserved if any of those
+ * follow immediately after the actual name.
+ */
+ i++;
+ if (path[i] == ' ') {
+ preceding_space_or_period = 1;
+ while (path[++i] == ' ')
+ ; /* skip all spaces */
+ }
+
+ c = path[i];
+ if (c && c != '.' && c != ':' && c != '/' && c != '\\')
+ goto not_a_reserved_name;
+
+ /* contains reserved name */
+ return 0;
+ case '.':
+ periods++;
+ /* fallthru */
+ case ' ':
+ preceding_space_or_period = 1;
+ i++;
+ continue;
+ case ':': /* DOS drive prefix was already skipped */
+ case '<': case '>': case '"': case '|': case '?': case '*':
+ /* illegal character */
+ return 0;
+ default:
+ if (c > '\0' && c < '\x20')
+ /* illegal character */
+ return 0;
+ }
+ preceding_space_or_period = 0;
+ i++;
+ }
+}
+
+#if !defined(_MSC_VER)
+/*
+ * Disable MSVCRT command line wildcard expansion (__getmainargs called from
+ * mingw startup code, see init.c in mingw runtime).
+ */
+int _CRT_glob = 0;
+#endif
+
+static NORETURN void die_startup(void)
+{
+ fputs("fatal: not enough memory for initialization", stderr);
+ exit(128);
+}
+
+static void *malloc_startup(size_t size)
+{
+ void *result = malloc(size);
+ if (!result)
+ die_startup();
+ return result;
+}
+
+static char *wcstoutfdup_startup(char *buffer, const wchar_t *wcs, size_t len)
+{
+ len = xwcstoutf(buffer, wcs, len) + 1;
+ return memcpy(malloc_startup(len), buffer, len);
+}
+
+static void maybe_redirect_std_handle(const wchar_t *key, DWORD std_id, int fd,
+ DWORD desired_access, DWORD flags)
+{
+ DWORD create_flag = fd ? OPEN_ALWAYS : OPEN_EXISTING;
+ wchar_t buf[MAX_PATH];
+ DWORD max = ARRAY_SIZE(buf);
+ HANDLE handle;
+ DWORD ret = GetEnvironmentVariableW(key, buf, max);
+
+ if (!ret || ret >= max)
+ return;
+
+ /* make sure this does not leak into child processes */
+ SetEnvironmentVariableW(key, NULL);
+ if (!wcscmp(buf, L"off")) {
+ close(fd);
+ handle = GetStdHandle(std_id);
+ if (handle != INVALID_HANDLE_VALUE)
+ CloseHandle(handle);
+ return;
+ }
+ if (std_id == STD_ERROR_HANDLE && !wcscmp(buf, L"2>&1")) {
+ handle = GetStdHandle(STD_OUTPUT_HANDLE);
+ if (handle == INVALID_HANDLE_VALUE) {
+ close(fd);
+ handle = GetStdHandle(std_id);
+ if (handle != INVALID_HANDLE_VALUE)
+ CloseHandle(handle);
+ } else {
+ int new_fd = _open_osfhandle((intptr_t)handle, O_BINARY);
+ SetStdHandle(std_id, handle);
+ dup2(new_fd, fd);
+ /* do *not* close the new_fd: that would close stdout */
+ }
+ return;
+ }
+ handle = CreateFileW(buf, desired_access, 0, NULL, create_flag,
+ flags, NULL);
+ if (handle != INVALID_HANDLE_VALUE) {
+ int new_fd = _open_osfhandle((intptr_t)handle, O_BINARY);
+ SetStdHandle(std_id, handle);
+ dup2(new_fd, fd);
+ close(new_fd);
+ }
+}
+
+static void maybe_redirect_std_handles(void)
+{
+ maybe_redirect_std_handle(L"GIT_REDIRECT_STDIN", STD_INPUT_HANDLE, 0,
+ GENERIC_READ, FILE_ATTRIBUTE_NORMAL);
+ maybe_redirect_std_handle(L"GIT_REDIRECT_STDOUT", STD_OUTPUT_HANDLE, 1,
+ GENERIC_WRITE, FILE_ATTRIBUTE_NORMAL);
+ maybe_redirect_std_handle(L"GIT_REDIRECT_STDERR", STD_ERROR_HANDLE, 2,
+ GENERIC_WRITE, FILE_FLAG_NO_BUFFERING);
+}
+
+#ifdef _MSC_VER
+#ifdef _DEBUG
+#include <crtdbg.h>
+#endif
+#endif
+
+/*
+ * We implement wmain() and compile with -municode, which would
+ * normally ignore main(), but we call the latter from the former
+ * so that we can handle non-ASCII command-line parameters
+ * appropriately.
+ *
+ * To be more compatible with the core git code, we convert
+ * argv into UTF8 and pass them directly to main().
+ */
+int wmain(int argc, const wchar_t **wargv)
+{
+ int i, maxlen, exit_status;
+ char *buffer, **save;
+ const char **argv;
+
+ trace2_initialize_clock();
+
+#ifdef _MSC_VER
+#ifdef _DEBUG
+ _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG);
+#endif
+
+#ifdef USE_MSVC_CRTDBG
+ _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
+#endif
+#endif
+
+ maybe_redirect_std_handles();
+
+ /* determine size of argv and environ conversion buffer */
+ maxlen = wcslen(wargv[0]);
+ for (i = 1; i < argc; i++)
+ maxlen = max(maxlen, wcslen(wargv[i]));
+
+ /* allocate buffer (wchar_t encodes to max 3 UTF-8 bytes) */
+ maxlen = 3 * maxlen + 1;
+ buffer = malloc_startup(maxlen);
+
+ /*
+ * Create a UTF-8 version of w_argv. Also create a "save" copy
+ * to remember all the string pointers because parse_options()
+ * will remove claimed items from the argv that we pass down.
+ */
+ ALLOC_ARRAY(argv, argc + 1);
+ ALLOC_ARRAY(save, argc + 1);
+ for (i = 0; i < argc; i++)
+ argv[i] = save[i] = wcstoutfdup_startup(buffer, wargv[i], maxlen);
+ argv[i] = save[i] = NULL;
+ free(buffer);
+
+ /* fix Windows specific environment settings */
+ setup_windows_environment();
+
+ unset_environment_variables = xstrdup("PERL5LIB");
+
+ /* initialize critical section for waitpid pinfo_t list */
+ InitializeCriticalSection(&pinfo_cs);
+
+ /* set up default file mode and file modes for stdin/out/err */
+ _fmode = _O_BINARY;
+ _setmode(_fileno(stdin), _O_BINARY);
+ _setmode(_fileno(stdout), _O_BINARY);
+ _setmode(_fileno(stderr), _O_BINARY);
+
+ /* initialize Unicode console */
+ winansi_init();
+
+ /* invoke the real main() using our utf8 version of argv. */
+ exit_status = main(argc, argv);
+
+ for (i = 0; i < argc; i++)
+ free(save[i]);
+ free(save);
+ free(argv);
+
+ return exit_status;
+}
+
+int uname(struct utsname *buf)
+{
+ unsigned v = (unsigned)GetVersion();
+ memset(buf, 0, sizeof(*buf));
+ xsnprintf(buf->sysname, sizeof(buf->sysname), "Windows");
+ xsnprintf(buf->release, sizeof(buf->release),
+ "%u.%u", v & 0xff, (v >> 8) & 0xff);
+ /* assuming NT variants only.. */
+ xsnprintf(buf->version, sizeof(buf->version),
+ "%u", (v >> 16) & 0x7fff);
+ return 0;
+}
diff --git a/compat/mingw.h b/compat/mingw.h
index 685cd2c3d4..c9a52ad64a 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -1,27 +1,46 @@
+#ifdef __MINGW64_VERSION_MAJOR
+#include <stdint.h>
+#include <wchar.h>
+typedef _sigset_t sigset_t;
+#endif
#include <winsock2.h>
#include <ws2tcpip.h>
+/* MinGW-w64 reports to have flockfile, but it does not actually have it. */
+#ifdef __MINGW64_VERSION_MAJOR
+#undef _POSIX_THREAD_SAFE_FUNCTIONS
+#endif
+
+int mingw_core_config(const char *var, const char *value, void *cb);
+#define platform_core_config mingw_core_config
+
/*
* things that are not available in header files
*/
-typedef int pid_t;
typedef int uid_t;
typedef int socklen_t;
+#ifndef __MINGW64_VERSION_MAJOR
+typedef int pid_t;
#define hstrerror strerror
+#endif
#define S_IFLNK 0120000 /* Symbolic link */
#define S_ISLNK(x) (((x) & S_IFMT) == S_IFLNK)
#define S_ISSOCK(x) 0
+#ifndef S_IRWXG
#define S_IRGRP 0
#define S_IWGRP 0
#define S_IXGRP 0
#define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
+#endif
+#ifndef S_IRWXO
#define S_IROTH 0
#define S_IWOTH 0
#define S_IXOTH 0
#define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH)
+#endif
#define S_ISUID 0004000
#define S_ISGID 0002000
@@ -32,7 +51,12 @@ typedef int socklen_t;
#define WEXITSTATUS(x) ((x) & 0xff)
#define WTERMSIG(x) SIGTERM
+#ifndef EWOULDBLOCK
#define EWOULDBLOCK EAGAIN
+#endif
+#ifndef ELOOP
+#define ELOOP EMLINK
+#endif
#define SHUT_WR SD_SEND
#define SIGHUP 1
@@ -46,8 +70,19 @@ typedef int socklen_t;
#define F_SETFD 2
#define FD_CLOEXEC 0x1
+#if !defined O_CLOEXEC && defined O_NOINHERIT
+#define O_CLOEXEC O_NOINHERIT
+#endif
+
+#ifndef EAFNOSUPPORT
#define EAFNOSUPPORT WSAEAFNOSUPPORT
+#endif
+#ifndef ECONNABORTED
#define ECONNABORTED WSAECONNABORTED
+#endif
+#ifndef ENOTSOCK
+#define ENOTSOCK WSAENOTSOCK
+#endif
struct passwd {
char *pw_name;
@@ -60,7 +95,6 @@ struct sigaction {
sig_handler_t sa_handler;
unsigned sa_flags;
};
-#define sigemptyset(x) (void)0
#define SA_RESTART 0
struct itimerval {
@@ -68,6 +102,14 @@ struct itimerval {
};
#define ITIMER_REAL 0
+struct utsname {
+ char sysname[16];
+ char nodename[1];
+ char release[16];
+ char version[16];
+ char machine[1];
+};
+
/*
* sanitize preprocessor namespace polluted by Windows headers defining
* macros which collide with git local versions
@@ -84,14 +126,14 @@ static inline int symlink(const char *oldpath, const char *newpath)
{ errno = ENOSYS; return -1; }
static inline int fchmod(int fildes, mode_t mode)
{ errno = ENOSYS; return -1; }
+#ifndef __MINGW64_VERSION_MAJOR
static inline pid_t fork(void)
{ errno = ENOSYS; return -1; }
+#endif
static inline unsigned int alarm(unsigned int seconds)
{ return 0; }
static inline int fsync(int fd)
{ return _commit(fd); }
-static inline pid_t getppid(void)
-{ return 1; }
static inline void sync(void)
{}
static inline uid_t getuid(void)
@@ -105,17 +147,26 @@ static inline int fcntl(int fd, int cmd, ...)
errno = EINVAL;
return -1;
}
-/* bash cannot reliably detect negative return codes as failure */
-#define exit(code) exit((code) & 0xff)
+
+#define sigemptyset(x) (void)0
+static inline int sigaddset(sigset_t *set, int signum)
+{ return 0; }
+#define SIG_BLOCK 0
+#define SIG_UNBLOCK 0
+static inline int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
+{ return 0; }
+static inline pid_t getppid(void)
+{ return 1; }
+static inline pid_t getpgid(pid_t pid)
+{ return pid == 0 ? getpid() : pid; }
+static inline pid_t tcgetpgrp(int fd)
+{ return getpid(); }
/*
* simple adaptors
*/
-static inline int mingw_mkdir(const char *path, int mode)
-{
- return mkdir(path);
-}
+int mingw_mkdir(const char *path, int mode);
#define mkdir mingw_mkdir
#define WNOHANG 1
@@ -153,13 +204,16 @@ int pipe(int filedes[2]);
unsigned int sleep (unsigned int seconds);
int mkstemp(char *template);
int gettimeofday(struct timeval *tv, void *tz);
+#ifndef __MINGW64_VERSION_MAJOR
struct tm *gmtime_r(const time_t *timep, struct tm *result);
struct tm *localtime_r(const time_t *timep, struct tm *result);
+#endif
int getpagesize(void); /* defined in MinGW's libgcc.a */
struct passwd *getpwuid(uid_t uid);
int setitimer(int type, struct itimerval *in, struct itimerval *out);
int sigaction(int sig, struct sigaction *in, struct sigaction *out);
int link(const char *oldpath, const char *newpath);
+int uname(struct utsname *buf);
/*
* replacements of existing functions
@@ -173,9 +227,7 @@ int mingw_rmdir(const char *path);
int mingw_open (const char *filename, int oflags, ...);
#define open mingw_open
-
-ssize_t mingw_write(int fd, const void *buf, size_t count);
-#define write mingw_write
+#undef OPEN_RETURNS_EINTR
int mingw_fgetc(FILE *stream);
#define fgetc mingw_fgetc
@@ -189,11 +241,54 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream);
int mingw_fflush(FILE *stream);
#define fflush mingw_fflush
+ssize_t mingw_write(int fd, const void *buf, size_t len);
+#define write mingw_write
+
+int mingw_access(const char *filename, int mode);
+#undef access
+#define access mingw_access
+
+int mingw_chdir(const char *dirname);
+#define chdir mingw_chdir
+
+int mingw_chmod(const char *filename, int mode);
+#define chmod mingw_chmod
+
+char *mingw_mktemp(char *template);
+#define mktemp mingw_mktemp
+
char *mingw_getcwd(char *pointer, int len);
#define getcwd mingw_getcwd
+#ifdef NO_UNSETENV
+#error "NO_UNSETENV is incompatible with the Windows-specific startup code!"
+#endif
+
+/*
+ * We bind *env() routines (even the mingw_ ones) to private mingw_ versions.
+ * These talk to the CRT using UNICODE/wchar_t, but maintain the original
+ * narrow-char API.
+ *
+ * Note that the MSCRT maintains both ANSI (getenv()) and UNICODE (_wgetenv())
+ * routines and stores both versions of each environment variable in parallel
+ * (and secretly updates both when you set one or the other), but it uses CP_ACP
+ * to do the conversion rather than CP_UTF8.
+ *
+ * Since everything in the git code base is UTF8, we define the mingw_ routines
+ * to access the CRT using the UNICODE routines and manually convert them to
+ * UTF8. This also avoids round-trip problems.
+ *
+ * This also helps with our linkage, since "_wenviron" is publicly exported
+ * from the CRT. But to access "_environ" we would have to statically link
+ * to the CRT (/MT).
+ *
+ * We require NO_SETENV (and let gitsetenv() call our mingw_putenv).
+ */
+#define getenv mingw_getenv
+#define putenv mingw_putenv
+#define unsetenv mingw_putenv
char *mingw_getenv(const char *name);
-#define getenv mingw_getenv
+int mingw_putenv(const char *name);
int mingw_gethostname(char *host, int namelen);
#define gethostname mingw_gethostname
@@ -201,18 +296,10 @@ int mingw_gethostname(char *host, int namelen);
struct hostent *mingw_gethostbyname(const char *host);
#define gethostbyname mingw_gethostbyname
-void mingw_freeaddrinfo(struct addrinfo *res);
-#define freeaddrinfo mingw_freeaddrinfo
-
int mingw_getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints, struct addrinfo **res);
#define getaddrinfo mingw_getaddrinfo
-int mingw_getnameinfo(const struct sockaddr *sa, socklen_t salen,
- char *host, DWORD hostlen, char *serv, DWORD servlen,
- int flags);
-#define getnameinfo mingw_getnameinfo
-
int mingw_socket(int domain, int type, int protocol);
#define socket mingw_socket
@@ -258,23 +345,62 @@ static inline int getrlimit(int resource, struct rlimit *rlp)
return 0;
}
-/* Use mingw_lstat() instead of lstat()/stat() and
- * mingw_fstat() instead of fstat() on Windows.
+/*
+ * Use mingw specific stat()/lstat()/fstat() implementations on Windows,
+ * including our own struct stat with 64 bit st_size and nanosecond-precision
+ * file times.
*/
+#ifndef __MINGW64_VERSION_MAJOR
#define off_t off64_t
#define lseek _lseeki64
-#ifndef ALREADY_DECLARED_STAT_FUNCS
-#define stat _stati64
+#ifndef _MSC_VER
+struct timespec {
+ time_t tv_sec;
+ long tv_nsec;
+};
+#endif
+#endif
+
+struct mingw_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;
+ off64_t 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
+
+#ifdef stat
+#undef stat
+#endif
+#define stat mingw_stat
int mingw_lstat(const char *file_name, struct stat *buf);
int mingw_stat(const char *file_name, struct stat *buf);
int mingw_fstat(int fd, struct stat *buf);
+#ifdef fstat
+#undef fstat
+#endif
#define fstat mingw_fstat
-#define lstat mingw_lstat
-#define _stati64(x,y) mingw_stat(x,y)
+#ifdef lstat
+#undef lstat
#endif
+#define lstat mingw_lstat
+
int mingw_utime(const char *file_name, const struct utimbuf *times);
#define utime mingw_utime
+size_t mingw_strftime(char *s, size_t max,
+ const char *format, const struct tm *tm);
+#define strftime mingw_strftime
pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env,
const char *dir,
@@ -298,62 +424,192 @@ int mingw_raise(int sig);
* ANSI emulation wrappers
*/
-int winansi_fputs(const char *str, FILE *stream);
-int winansi_printf(const char *format, ...) __attribute__((format (printf, 1, 2)));
-int winansi_fprintf(FILE *stream, const char *format, ...) __attribute__((format (printf, 2, 3)));
-#define fputs winansi_fputs
-#define printf(...) winansi_printf(__VA_ARGS__)
-#define fprintf(...) winansi_fprintf(__VA_ARGS__)
+int winansi_isatty(int fd);
+#define isatty winansi_isatty
+
+int winansi_dup2(int oldfd, int newfd);
+#define dup2 winansi_dup2
+
+void winansi_init(void);
+HANDLE winansi_get_osfhandle(int fd);
/*
* git specific compatibility
*/
-#define has_dos_drive_prefix(path) (isalpha(*(path)) && (path)[1] == ':')
-#define is_dir_sep(c) ((c) == '/' || (c) == '\\')
-static inline char *mingw_find_last_dir_sep(const char *path)
+static inline void convert_slashes(char *path)
{
- char *ret = NULL;
- for (; *path; ++path)
- if (is_dir_sep(*path))
- ret = (char *)path;
- return ret;
+ for (; *path; path++)
+ if (*path == '\\')
+ *path = '/';
}
-#define find_last_dir_sep mingw_find_last_dir_sep
#define PATH_SEP ';'
+char *mingw_query_user_email(void);
+#define query_user_email mingw_query_user_email
+#if !defined(__MINGW64_VERSION_MAJOR) && (!defined(_MSC_VER) || _MSC_VER < 1800)
#define PRIuMAX "I64u"
+#define PRId64 "I64d"
+#else
+#include <inttypes.h>
+#endif
-void mingw_open_html(const char *path);
-#define open_html mingw_open_html
+/**
+ * Verifies that the given path is a valid one on Windows.
+ *
+ * In particular, path segments are disallowed which
+ *
+ * - end in a period or a space (except the special directories `.` and `..`).
+ *
+ * - contain any of the reserved characters, e.g. `:`, `;`, `*`, etc
+ *
+ * - correspond to reserved names (such as `AUX`, `PRN`, etc)
+ *
+ * The `allow_literal_nul` parameter controls whether the path `NUL` should
+ * be considered valid (this makes sense e.g. before opening files, as it is
+ * perfectly legitimate to open `NUL` on Windows, just as it is to open
+ * `/dev/null` on Unix/Linux).
+ *
+ * Returns 1 upon success, otherwise 0.
+ */
+int is_valid_win32_path(const char *path, int allow_literal_nul);
+#define is_valid_path(path) is_valid_win32_path(path, 0)
+
+/**
+ * Converts UTF-8 encoded string to UTF-16LE.
+ *
+ * To support repositories with legacy-encoded file names, invalid UTF-8 bytes
+ * 0xa0 - 0xff are converted to corresponding printable Unicode chars \u00a0 -
+ * \u00ff, and invalid UTF-8 bytes 0x80 - 0x9f (which would make non-printable
+ * Unicode) are converted to hex-code.
+ *
+ * Lead-bytes not followed by an appropriate number of trail-bytes, over-long
+ * encodings and 4-byte encodings > \u10ffff are detected as invalid UTF-8.
+ *
+ * Maximum space requirement for the target buffer is two wide chars per UTF-8
+ * char (((strlen(utf) * 2) + 1) [* sizeof(wchar_t)]).
+ *
+ * The maximum space is needed only if the entire input string consists of
+ * invalid UTF-8 bytes in range 0x80-0x9f, as per the following table:
+ *
+ * | | UTF-8 | UTF-16 |
+ * Code point | UTF-8 sequence | bytes | words | ratio
+ * --------------+-------------------+-------+--------+-------
+ * 000000-00007f | 0-7f | 1 | 1 | 1
+ * 000080-0007ff | c2-df + 80-bf | 2 | 1 | 0.5
+ * 000800-00ffff | e0-ef + 2 * 80-bf | 3 | 1 | 0.33
+ * 010000-10ffff | f0-f4 + 3 * 80-bf | 4 | 2 (a) | 0.5
+ * invalid | 80-9f | 1 | 2 (b) | 2
+ * invalid | a0-ff | 1 | 1 | 1
+ *
+ * (a) encoded as UTF-16 surrogate pair
+ * (b) encoded as two hex digits
+ *
+ * Note that, while the UTF-8 encoding scheme can be extended to 5-byte, 6-byte
+ * or even indefinite-byte sequences, the largest valid code point \u10ffff
+ * encodes as only 4 UTF-8 bytes.
+ *
+ * Parameters:
+ * wcs: wide char target buffer
+ * utf: string to convert
+ * wcslen: size of target buffer (in wchar_t's)
+ * utflen: size of string to convert, or -1 if 0-terminated
+ *
+ * Returns:
+ * length of converted string (_wcslen(wcs)), or -1 on failure
+ *
+ * Errors:
+ * EINVAL: one of the input parameters is invalid (e.g. NULL)
+ * ERANGE: the output buffer is too small
+ */
+int xutftowcsn(wchar_t *wcs, const char *utf, size_t wcslen, int utflen);
-/*
- * helpers
+/**
+ * Simplified variant of xutftowcsn, assumes input string is \0-terminated.
*/
+static inline int xutftowcs(wchar_t *wcs, const char *utf, size_t wcslen)
+{
+ return xutftowcsn(wcs, utf, wcslen, -1);
+}
-char **make_augmented_environ(const char *const *vars);
-void free_environ(char **env);
+/**
+ * Simplified file system specific variant of xutftowcsn, assumes output
+ * buffer size is MAX_PATH wide chars and input string is \0-terminated,
+ * fails with ENAMETOOLONG if input string is too long.
+ */
+static inline int xutftowcs_path(wchar_t *wcs, const char *utf)
+{
+ int result = xutftowcsn(wcs, utf, MAX_PATH, -1);
+ if (result < 0 && errno == ERANGE)
+ errno = ENAMETOOLONG;
+ return result;
+}
+
+/**
+ * Converts UTF-16LE encoded string to UTF-8.
+ *
+ * Maximum space requirement for the target buffer is three UTF-8 chars per
+ * wide char ((_wcslen(wcs) * 3) + 1).
+ *
+ * The maximum space is needed only if the entire input string consists of
+ * UTF-16 words in range 0x0800-0xd7ff or 0xe000-0xffff (i.e. \u0800-\uffff
+ * modulo surrogate pairs), as per the following table:
+ *
+ * | | UTF-16 | UTF-8 |
+ * Code point | UTF-16 sequence | words | bytes | ratio
+ * --------------+-----------------------+--------+-------+-------
+ * 000000-00007f | 0000-007f | 1 | 1 | 1
+ * 000080-0007ff | 0080-07ff | 1 | 2 | 2
+ * 000800-00ffff | 0800-d7ff / e000-ffff | 1 | 3 | 3
+ * 010000-10ffff | d800-dbff + dc00-dfff | 2 | 4 | 2
+ *
+ * Note that invalid code points > 10ffff cannot be represented in UTF-16.
+ *
+ * Parameters:
+ * utf: target buffer
+ * wcs: wide string to convert
+ * utflen: size of target buffer
+ *
+ * Returns:
+ * length of converted string, or -1 on failure
+ *
+ * Errors:
+ * EINVAL: one of the input parameters is invalid (e.g. NULL)
+ * ERANGE: the output buffer is too small
+ */
+int xwcstoutf(char *utf, const wchar_t *wcs, size_t utflen);
/*
- * A replacement of main() that ensures that argv[0] has a path
- * and that default fmode and std(in|out|err) are in binary mode
+ * A critical section used in the implementation of the spawn
+ * functions (mingw_spawnv[p]e()) and waitpid(). Initialised in
+ * the replacement main() macro below.
*/
+extern CRITICAL_SECTION pinfo_cs;
-#define main(c,v) dummy_decl_mingw_main(); \
-static int mingw_main(); \
-int main(int argc, const char **argv) \
-{ \
- extern CRITICAL_SECTION pinfo_cs; \
- _fmode = _O_BINARY; \
- _setmode(_fileno(stdin), _O_BINARY); \
- _setmode(_fileno(stdout), _O_BINARY); \
- _setmode(_fileno(stderr), _O_BINARY); \
- argv[0] = xstrdup(_pgmptr); \
- InitializeCriticalSection(&pinfo_cs); \
- return mingw_main(argc, argv); \
-} \
-static int mingw_main(c,v)
+/*
+ * Git, like most portable C applications, implements a main() function. On
+ * Windows, this main() function would receive parameters encoded in the
+ * current locale, but Git for Windows would prefer UTF-8 encoded parameters.
+ *
+ * To make that happen, we still declare main() here, and then declare and
+ * implement wmain() (which is the Unicode variant of main()) and compile with
+ * -municode. This wmain() function reencodes the parameters from UTF-16 to
+ * UTF-8 format, sets up a couple of other things as required on Windows, and
+ * then hands off to the main() function.
+ */
+int wmain(int argc, const wchar_t **w_argv);
+int main(int argc, const char **argv);
+
+/*
+ * For debugging: if a problem occurs, say, in a Git process that is spawned
+ * from another Git process which in turn is spawned from yet another Git
+ * process, it can be quite daunting to figure out what is going on.
+ *
+ * Call this function to open a new MinTTY (this assumes you are in Git for
+ * Windows' SDK) with a GDB that attaches to the current process right away.
+ */
+void open_in_gdb(void);
/*
* Used by Pthread API implementation for Windows
*/
-extern int err_win_to_posix(DWORD winerr);
+int err_win_to_posix(DWORD winerr);
diff --git a/compat/mmap.c b/compat/mmap.c
index c9d46d1742..8d6c02d4bc 100644
--- a/compat/mmap.c
+++ b/compat/mmap.c
@@ -4,17 +4,22 @@ void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t of
{
size_t n = 0;
- if (start != NULL || !(flags & MAP_PRIVATE))
+ if (start != NULL || flags != MAP_PRIVATE || prot != PROT_READ)
die("Invalid usage of mmap when built with NO_MMAP");
- start = xmalloc(length);
+ if (length == 0) {
+ errno = EINVAL;
+ return MAP_FAILED;
+ }
+
+ start = malloc(length);
if (start == NULL) {
errno = ENOMEM;
return MAP_FAILED;
}
while (n < length) {
- ssize_t count = pread(fd, (char *)start + n, length - n, offset + n);
+ ssize_t count = xpread(fd, (char *)start + n, length - n, offset + n);
if (count == 0) {
memset((char *)start+n, 0, length-n);
@@ -22,8 +27,6 @@ void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t of
}
if (count < 0) {
- if (errno == EAGAIN || errno == EINTR)
- continue;
free(start);
errno = EACCES;
return MAP_FAILED;
diff --git a/compat/msvc.h b/compat/msvc.h
index aa4b56315a..1d7a8c6145 100644
--- a/compat/msvc.h
+++ b/compat/msvc.h
@@ -6,37 +6,28 @@
#include <malloc.h>
#include <io.h>
+#pragma warning(disable: 4018) /* signed/unsigned comparison */
+#pragma warning(disable: 4244) /* type conversion, possible loss of data */
+#pragma warning(disable: 4090) /* 'function' : different 'const' qualifiers (ALLOC_GROW etc.)*/
+
/* porting function */
#define inline __inline
#define __inline__ __inline
#define __attribute__(x)
+#define strcasecmp _stricmp
#define strncasecmp _strnicmp
#define ftruncate _chsize
-
-static __inline int strcasecmp (const char *s1, const char *s2)
-{
- int size1 = strlen(s1);
- int sisz2 = strlen(s2);
- return _strnicmp(s1, s2, sisz2 > size1 ? sisz2 : size1);
-}
+#define strtoull _strtoui64
+#define strtoll _strtoi64
#undef ERROR
-/* Use mingw_lstat() instead of lstat()/stat() and mingw_fstat() instead
- * of fstat(). We add the declaration of these functions here, suppressing
- * the corresponding declarations in mingw.h, so that we can use the
- * appropriate structure type (and function) names from the msvc headers.
- */
-#define stat _stat64
-int mingw_lstat(const char *file_name, struct stat *buf);
-int mingw_fstat(int fd, struct stat *buf);
-#define fstat mingw_fstat
-#define lstat mingw_lstat
-#define _stat64(x,y) mingw_lstat(x,y)
-#define ALREADY_DECLARED_STAT_FUNCS
+#define ftello _ftelli64
-#include "compat/mingw.h"
+typedef int sigset_t;
+/* open for reading, writing, or both (not in fcntl.h) */
+#define O_ACCMODE (_O_RDONLY | _O_WRONLY | _O_RDWR)
-#undef ALREADY_DECLARED_STAT_FUNCS
+#include "compat/mingw.h"
#endif
diff --git a/compat/nedmalloc/Readme.txt b/compat/nedmalloc/Readme.txt
index e46d8f112c..07cbf50c0f 100644
--- a/compat/nedmalloc/Readme.txt
+++ b/compat/nedmalloc/Readme.txt
@@ -97,7 +97,7 @@ Chew for reporting this.
v1.04alpha_svn915 7th October 2006:
* Fixed failure to unlock thread cache list if allocating a new list failed.
-Thanks to Dmitry Chichkov for reporting this. Futher thanks to Aleksey Sanin.
+Thanks to Dmitry Chichkov for reporting this. Further thanks to Aleksey Sanin.
* Fixed realloc(0, <size>) segfaulting. Thanks to Dmitry Chichkov for
reporting this.
* Made config defines #ifndef so they can be overridden by the build system.
diff --git a/compat/nedmalloc/malloc.c.h b/compat/nedmalloc/malloc.c.h
index ff7c2c4fd8..814845d4b3 100644
--- a/compat/nedmalloc/malloc.c.h
+++ b/compat/nedmalloc/malloc.c.h
@@ -484,6 +484,10 @@ MAX_RELEASE_CHECK_RATE default: 4095 unless not HAVE_MMAP
#define DLMALLOC_VERSION 20804
#endif /* DLMALLOC_VERSION */
+#if defined(linux)
+#define _GNU_SOURCE 1
+#endif
+
#ifndef WIN32
#ifdef _WIN32
#define WIN32 1
@@ -495,7 +499,9 @@ MAX_RELEASE_CHECK_RATE default: 4095 unless not HAVE_MMAP
#endif /* WIN32 */
#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
+#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x403
+#endif
#include <windows.h>
#define HAVE_MMAP 1
#define HAVE_MORECORE 0
@@ -714,6 +720,9 @@ struct mallinfo {
inlining are defined as macros, so these aren't used for them.
*/
+#ifdef __MINGW64_VERSION_MAJOR
+#undef FORCEINLINE
+#endif
#ifndef FORCEINLINE
#if defined(__GNUC__)
#define FORCEINLINE __inline __attribute__ ((always_inline))
@@ -1376,6 +1385,7 @@ LONG __cdecl _InterlockedExchange(LONG volatile *Target, LONG Value);
/*** Atomic operations ***/
#if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) > 40100
+ #undef _ReadWriteBarrier
#define _ReadWriteBarrier() __sync_synchronize()
#else
static __inline__ __attribute__((always_inline)) long __sync_lock_test_and_set(volatile long * const Target, const long Value)
@@ -1554,7 +1564,7 @@ static FORCEINLINE void* win32direct_mmap(size_t size) {
return (ptr != 0)? ptr: MFAIL;
}
-/* This function supports releasing coalesed segments */
+/* This function supports releasing coalesced segments */
static FORCEINLINE int win32munmap(void* ptr, size_t size) {
MEMORY_BASIC_INFORMATION minfo;
char* cptr = (char*)ptr;
@@ -1645,7 +1655,7 @@ static FORCEINLINE int win32munmap(void* ptr, size_t size) {
#define CALL_MREMAP(addr, osz, nsz, mv) MFAIL
#endif /* HAVE_MMAP && HAVE_MREMAP */
-/* mstate bit set if continguous morecore disabled or failed */
+/* mstate bit set if contiguous morecore disabled or failed */
#define USE_NONCONTIGUOUS_BIT (4U)
/* segment bit set in create_mspace_with_base */
@@ -1745,10 +1755,10 @@ static FORCEINLINE void pthread_release_lock (MLOCK_T *sl) {
assert(sl->l != 0);
assert(sl->threadid == CURRENT_THREAD);
if (--sl->c == 0) {
- sl->threadid = 0;
volatile unsigned int* lp = &sl->l;
int prev = 0;
int ret;
+ sl->threadid = 0;
__asm__ __volatile__ ("lock; xchgl %0, %1"
: "=r" (ret)
: "m" (*(lp)), "0"(prev)
@@ -1792,9 +1802,10 @@ struct win32_mlock_t
volatile long threadid;
};
+static inline int return_0(int i) { return 0; }
#define MLOCK_T struct win32_mlock_t
#define CURRENT_THREAD win32_getcurrentthreadid()
-#define INITIAL_LOCK(sl) (memset(sl, 0, sizeof(MLOCK_T)), 0)
+#define INITIAL_LOCK(sl) (memset(sl, 0, sizeof(MLOCK_T)), return_0(0))
#define ACQUIRE_LOCK(sl) win32_acquire_lock(sl)
#define RELEASE_LOCK(sl) win32_release_lock(sl)
#define TRY_LOCK(sl) win32_try_lock(sl)
@@ -1802,7 +1813,7 @@ struct win32_mlock_t
static MLOCK_T malloc_global_mutex = { 0, 0, 0};
-static FORCEINLINE long win32_getcurrentthreadid() {
+static FORCEINLINE long win32_getcurrentthreadid(void) {
#ifdef _MSC_VER
#if defined(_M_IX86)
long *threadstruct=(long *)__readfsdword(0x18);
@@ -2474,7 +2485,7 @@ typedef struct malloc_segment* msegmentptr;
Trim support
Fields holding the amount of unused topmost memory that should trigger
- timming, and a counter to force periodic scanning to release unused
+ timing, and a counter to force periodic scanning to release unused
non-topmost segments.
Locking
@@ -3055,7 +3066,7 @@ static int init_mparams(void) {
#if !ONLY_MSPACES
/* Set up lock for main malloc area */
gm->mflags = mparams.default_mflags;
- INITIAL_LOCK(&gm->mutex);
+ (void)INITIAL_LOCK(&gm->mutex);
#endif
#if (FOOTERS && !INSECURE)
@@ -3598,8 +3609,8 @@ static void internal_malloc_stats(mstate m) {
and choose its bk node as its replacement.
2. If x was the last node of its size, but not a leaf node, it must
be replaced with a leaf node (not merely one with an open left or
- right), to make sure that lefts and rights of descendents
- correspond properly to bit masks. We use the rightmost descendent
+ right), to make sure that lefts and rights of descendants
+ correspond properly to bit masks. We use the rightmost descendant
of x. We could use any other leaf, but this is easy to locate and
tends to counteract removal of leftmosts elsewhere, and so keeps
paths shorter than minimally guaranteed. This doesn't loop much
@@ -4778,7 +4789,7 @@ void* dlmalloc(size_t bytes) {
void dlfree(void* mem) {
/*
- Consolidate freed chunks with preceeding or succeeding bordering
+ Consolidate freed chunks with preceding or succeeding bordering
free chunks, if they exist, and then place in a bin. Intermixed
with special cases for top, dv, mmapped chunks, and usage errors.
*/
@@ -5006,7 +5017,7 @@ static mstate init_user_mstate(char* tbase, size_t tsize) {
mchunkptr msp = align_as_chunk(tbase);
mstate m = (mstate)(chunk2mem(msp));
memset(m, 0, msize);
- INITIAL_LOCK(&m->mutex);
+ (void)INITIAL_LOCK(&m->mutex);
msp->head = (msize|PINUSE_BIT|CINUSE_BIT);
m->seg.base = m->least_addr = tbase;
m->seg.size = m->footprint = m->max_footprint = tsize;
@@ -5680,10 +5691,10 @@ History:
Wolfram Gloger (Gloger@lrz.uni-muenchen.de).
* Use last_remainder in more cases.
* Pack bins using idea from colin@nyx10.cs.du.edu
- * Use ordered bins instead of best-fit threshhold
+ * Use ordered bins instead of best-fit threshold
* Eliminate block-local decls to simplify tracing and debugging.
* Support another case of realloc via move into top
- * Fix error occuring when initial sbrk_base not word-aligned.
+ * Fix error occurring when initial sbrk_base not word-aligned.
* Rely on page size for units instead of SBRK_UNIT to
avoid surprises about sbrk alignment conventions.
* Add mallinfo, mallopt. Thanks to Raymond Nijssen
diff --git a/compat/nedmalloc/nedmalloc.c b/compat/nedmalloc/nedmalloc.c
index 91c4e7f27b..edb438a777 100644
--- a/compat/nedmalloc/nedmalloc.c
+++ b/compat/nedmalloc/nedmalloc.c
@@ -159,8 +159,8 @@ struct mallinfo nedmallinfo(void) THROWSPEC { return nedpmallinfo(0); }
#endif
int nedmallopt(int parno, int value) THROWSPEC { return nedpmallopt(0, parno, value); }
int nedmalloc_trim(size_t pad) THROWSPEC { return nedpmalloc_trim(0, pad); }
-void nedmalloc_stats() THROWSPEC { nedpmalloc_stats(0); }
-size_t nedmalloc_footprint() THROWSPEC { return nedpmalloc_footprint(0); }
+void nedmalloc_stats(void) THROWSPEC { nedpmalloc_stats(0); }
+size_t nedmalloc_footprint(void) THROWSPEC { return nedpmalloc_footprint(0); }
void **nedindependent_calloc(size_t elemsno, size_t elemsize, void **chunks) THROWSPEC { return nedpindependent_calloc(0, elemsno, elemsize, chunks); }
void **nedindependent_comalloc(size_t elems, size_t *sizes, void **chunks) THROWSPEC { return nedpindependent_comalloc(0, elems, sizes, chunks); }
@@ -510,7 +510,7 @@ static void threadcache_free(nedpool *p, threadcache *tc, int mymspace, void *me
assert(idx<=THREADCACHEMAXBINS);
if(tck==*binsptr)
{
- fprintf(stderr, "Attempt to free already freed memory block %p - aborting!\n", tck);
+ fprintf(stderr, "Attempt to free already freed memory block %p - aborting!\n", (void *)tck);
abort();
}
#ifdef FULLSANITYCHECKS
@@ -938,32 +938,16 @@ void **nedpindependent_comalloc(nedpool *p, size_t elems, size_t *sizes, void **
void **ret;
threadcache *tc;
int mymspace;
- size_t i, *adjustedsizes=(size_t *) alloca(elems*sizeof(size_t));
- if(!adjustedsizes) return 0;
- for(i=0; i<elems; i++)
- adjustedsizes[i]=sizes[i]<sizeof(threadcacheblk) ? sizeof(threadcacheblk) : sizes[i];
+ size_t i, *adjustedsizes=(size_t *) alloca(elems*sizeof(size_t));
+ if(!adjustedsizes) return 0;
+ for(i=0; i<elems; i++)
+ adjustedsizes[i]=sizes[i]<sizeof(threadcacheblk) ? sizeof(threadcacheblk) : sizes[i];
GetThreadCache(&p, &tc, &mymspace, 0);
GETMSPACE(m, p, tc, mymspace, 0,
ret=mspace_independent_comalloc(m, elems, adjustedsizes, chunks));
return ret;
}
-#ifdef OVERRIDE_STRDUP
-/*
- * This implementation is purely there to override the libc version, to
- * avoid a crash due to allocation and free on different 'heaps'.
- */
-char *strdup(const char *s1)
-{
- char *s2 = 0;
- if (s1) {
- s2 = malloc(strlen(s1) + 1);
- strcpy(s2, s1);
- }
- return s2;
-}
-#endif
-
#if defined(__cplusplus)
}
#endif
diff --git a/compat/obstack.c b/compat/obstack.c
index e276ccd7b3..27cd5c1ea1 100644
--- a/compat/obstack.c
+++ b/compat/obstack.c
@@ -14,9 +14,8 @@
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
- License along with the GNU C Library; if not, write to the Free
- Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- Boston, MA 02110-1301, USA. */
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
#include "git-compat-util.h"
#include <gettext.h>
@@ -113,15 +112,15 @@ compat_symbol (libc, _obstack_compat, _obstack, GLIBC_2_0);
# define CALL_CHUNKFUN(h, size) \
(((h) -> use_extra_arg) \
- ? (*(h)->chunkfun) ((h)->extra_arg, (size)) \
- : (*(struct _obstack_chunk *(*) (long)) (h)->chunkfun) ((size)))
+ ? (*(h)->chunkfun.extra) ((h)->extra_arg, (size)) \
+ : (*(h)->chunkfun.plain) ((size)))
# define CALL_FREEFUN(h, old_chunk) \
do { \
if ((h) -> use_extra_arg) \
- (*(h)->freefun) ((h)->extra_arg, (old_chunk)); \
+ (*(h)->freefun.extra) ((h)->extra_arg, (old_chunk)); \
else \
- (*(void (*) (void *)) (h)->freefun) ((old_chunk)); \
+ (*(h)->freefun.plain) ((old_chunk)); \
} while (0)
@@ -160,8 +159,8 @@ _obstack_begin (struct obstack *h,
size = 4096 - extra;
}
- h->chunkfun = (struct _obstack_chunk * (*)(void *, long)) chunkfun;
- h->freefun = (void (*) (void *, struct _obstack_chunk *)) freefun;
+ h->chunkfun.plain = chunkfun;
+ h->freefun.plain = freefun;
h->chunk_size = size;
h->alignment_mask = alignment - 1;
h->use_extra_arg = 0;
@@ -207,8 +206,9 @@ _obstack_begin_1 (struct obstack *h, int size, int alignment,
size = 4096 - extra;
}
- h->chunkfun = (struct _obstack_chunk * (*)(void *,long)) chunkfun;
- h->freefun = (void (*) (void *, struct _obstack_chunk *)) freefun;
+ h->chunkfun.extra = (struct _obstack_chunk * (*)(void *,long)) chunkfun;
+ h->freefun.extra = (void (*) (void *, struct _obstack_chunk *)) freefun;
+
h->chunk_size = size;
h->alignment_mask = alignment - 1;
h->extra_arg = arg;
diff --git a/compat/obstack.h b/compat/obstack.h
index d178bd6716..f90a46d9b9 100644
--- a/compat/obstack.h
+++ b/compat/obstack.h
@@ -14,9 +14,8 @@
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
- License along with the GNU C Library; if not, write to the Free
- Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- Boston, MA 02110-1301, USA. */
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
/* Summary:
@@ -80,7 +79,7 @@ change its address during its lifetime.
When the chars burst over a chunk boundary, we allocate a larger
chunk, and then copy the partly formed object from the end of the old
chunk to the beginning of the new larger chunk. We then carry on
-accreting characters to the end of the object as we normally would.
+accrediting characters to the end of the object as we normally would.
A special macro is provided to add a single char at a time to a
growing object. This allows the use of register variables, which
@@ -128,7 +127,7 @@ extern "C" {
#define __BPTR_ALIGN(B, P, A) ((B) + (((P) - (B) + (A)) & ~(A)))
-/* Similiar to _BPTR_ALIGN (B, P, A), except optimize the common case
+/* Similar to _BPTR_ALIGN (B, P, A), except optimize the common case
where pointers can be converted to integers, aligned as integers,
and converted back again. If PTR_INT_TYPE is narrower than a
pointer (e.g., the AS/400), play it safe and compute the alignment
@@ -136,8 +135,10 @@ extern "C" {
alignment relative to 0. */
#define __PTR_ALIGN(B, P, A) \
- __BPTR_ALIGN (sizeof (PTR_INT_TYPE) < sizeof (void *) ? (B) : (char *) 0, \
- P, A)
+ (sizeof (PTR_INT_TYPE) < sizeof(void *) ? \
+ __BPTR_ALIGN((B), (P), (A)) : \
+ (void *)__BPTR_ALIGN((PTR_INT_TYPE)(void *)0, (PTR_INT_TYPE)(P), (A)) \
+ )
#include <string.h>
@@ -161,11 +162,15 @@ struct obstack /* control current object in current chunk */
void *tempptr;
} temp; /* Temporary for some macros. */
int alignment_mask; /* Mask of alignment for each object. */
- /* These prototypes vary based on `use_extra_arg', and we use
- casts to the prototypeless function type in all assignments,
- but having prototypes here quiets -Wstrict-prototypes. */
- struct _obstack_chunk *(*chunkfun) (void *, long);
- void (*freefun) (void *, struct _obstack_chunk *);
+ /* These prototypes vary based on `use_extra_arg'. */
+ union {
+ void *(*plain) (long);
+ struct _obstack_chunk *(*extra) (void *, long);
+ } chunkfun;
+ union {
+ void (*plain) (void *);
+ void (*extra) (void *, struct _obstack_chunk *);
+ } freefun;
void *extra_arg; /* first arg for chunk alloc/dealloc funcs */
unsigned use_extra_arg:1; /* chunk alloc/dealloc funcs take extra arg */
unsigned maybe_empty_object:1;/* There is a possibility that the current
@@ -236,10 +241,10 @@ extern void (*obstack_alloc_failed_handler) (void);
(void (*) (void *, void *)) (freefun), (arg))
#define obstack_chunkfun(h, newchunkfun) \
- ((h) -> chunkfun = (struct _obstack_chunk *(*)(void *, long)) (newchunkfun))
+ ((h)->chunkfun.extra = (struct _obstack_chunk *(*)(void *, long)) (newchunkfun))
#define obstack_freefun(h, newfreefun) \
- ((h) -> freefun = (void (*)(void *, struct _obstack_chunk *)) (newfreefun))
+ ((h)->freefun.extra = (void (*)(void *, struct _obstack_chunk *)) (newfreefun))
#define obstack_1grow_fast(h,achar) (*((h)->next_free)++ = (achar))
@@ -493,7 +498,7 @@ __extension__ \
( (h)->temp.tempint = (char *) (obj) - (char *) (h)->chunk, \
((((h)->temp.tempint > 0 \
&& (h)->temp.tempint < (h)->chunk_limit - (char *) (h)->chunk)) \
- ? (int) ((h)->next_free = (h)->object_base \
+ ? (ptrdiff_t) ((h)->next_free = (h)->object_base \
= (h)->temp.tempint + (char *) (h)->chunk) \
: (((obstack_free) ((h), (h)->temp.tempint + (char *) (h)->chunk), 0), 0)))
diff --git a/compat/open.c b/compat/open.c
new file mode 100644
index 0000000000..eb3754a23b
--- /dev/null
+++ b/compat/open.c
@@ -0,0 +1,25 @@
+#include "git-compat-util.h"
+
+#undef open
+int git_open_with_retry(const char *path, int flags, ...)
+{
+ mode_t mode = 0;
+ int ret;
+
+ /*
+ * Also O_TMPFILE would take a mode, but it isn't defined everywhere.
+ * And anyway, we don't use it in our code base.
+ */
+ if (flags & O_CREAT) {
+ va_list ap;
+ va_start(ap, flags);
+ mode = va_arg(ap, int);
+ va_end(ap);
+ }
+
+ do {
+ ret = open(path, flags, mode);
+ } while (ret < 0 && errno == EINTR);
+
+ return ret;
+}
diff --git a/compat/poll/poll.c b/compat/poll/poll.c
index 7d226ecb29..afa6d24584 100644
--- a/compat/poll/poll.c
+++ b/compat/poll/poll.c
@@ -16,8 +16,10 @@
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
- with this program; if not, write to the Free Software Foundation,
- Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+ with this program; if not, see <http://www.gnu.org/licenses/>. */
+
+/* To bump the minimum Windows version to Windows Vista */
+#include "git-compat-util.h"
/* Tell gcc not to warn about the (nfd < 0) tests, below. */
#if (__GNUC__ == 4 && 3 <= __GNUC_MINOR__) || 4 < __GNUC__
@@ -30,16 +32,13 @@
#include <sys/types.h>
-/* Specification. */
-#include <poll.h>
-
#include <errno.h>
#include <limits.h>
#include <assert.h>
#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
# define WIN32_NATIVE
-# if defined (_MSC_VER)
+# if defined (_MSC_VER) && !defined(_WIN32_WINNT)
# define _WIN32_WINNT 0x0502
# endif
# include <winsock2.h>
@@ -56,6 +55,9 @@
# include <unistd.h>
#endif
+/* Specification. */
+#include "poll.h"
+
#ifdef HAVE_SYS_IOCTL_H
# include <sys/ioctl.h>
#endif
@@ -76,7 +78,7 @@
#ifdef WIN32_NATIVE
-#define IsConsoleHandle(h) (((long) (h) & 3) == 3)
+#define IsConsoleHandle(h) (((long) (intptr_t) (h) & 3) == 3)
static BOOL
IsSocketHandle (HANDLE h)
@@ -137,22 +139,10 @@ win32_compute_revents (HANDLE h, int *p_sought)
INPUT_RECORD *irbuffer;
DWORD avail, nbuffer;
BOOL bRet;
- IO_STATUS_BLOCK iosb;
- FILE_PIPE_LOCAL_INFORMATION fpli;
- static PNtQueryInformationFile NtQueryInformationFile;
- static BOOL once_only;
switch (GetFileType (h))
{
case FILE_TYPE_PIPE:
- if (!once_only)
- {
- NtQueryInformationFile = (PNtQueryInformationFile)
- GetProcAddress (GetModuleHandle ("ntdll.dll"),
- "NtQueryInformationFile");
- once_only = TRUE;
- }
-
happened = 0;
if (PeekNamedPipe (h, NULL, 0, NULL, &avail, NULL) != 0)
{
@@ -164,22 +154,9 @@ win32_compute_revents (HANDLE h, int *p_sought)
else
{
- /* It was the write-end of the pipe. Check if it is writable.
- If NtQueryInformationFile fails, optimistically assume the pipe is
- writable. This could happen on Win9x, where NtQueryInformationFile
- is not available, or if we inherit a pipe that doesn't permit
- FILE_READ_ATTRIBUTES access on the write end (I think this should
- not happen since WinXP SP2; WINE seems fine too). Otherwise,
- ensure that enough space is available for atomic writes. */
- memset (&iosb, 0, sizeof (iosb));
- memset (&fpli, 0, sizeof (fpli));
-
- if (!NtQueryInformationFile
- || NtQueryInformationFile (h, &iosb, &fpli, sizeof (fpli),
- FilePipeLocalInformation)
- || fpli.WriteQuotaAvailable >= PIPE_BUF
- || (fpli.OutboundQuota < PIPE_BUF &&
- fpli.WriteQuotaAvailable == fpli.OutboundQuota))
+ /* It was the write-end of the pipe. Unfortunately there is no
+ reliable way of knowing if it can be written without blocking.
+ Just say that it's all good. */
happened |= *p_sought & (POLLOUT | POLLWRNORM | POLLWRBAND);
}
return happened;
@@ -438,6 +415,10 @@ poll (struct pollfd *pfd, nfds_t nfd, int timeout)
pfd[i].revents = happened;
rc++;
}
+ else
+ {
+ pfd[i].revents = 0;
+ }
}
return rc;
@@ -446,7 +427,8 @@ poll (struct pollfd *pfd, nfds_t nfd, int timeout)
static HANDLE hEvent;
WSANETWORKEVENTS ev;
HANDLE h, handle_array[FD_SETSIZE + 2];
- DWORD ret, wait_timeout, nhandles;
+ DWORD ret, wait_timeout, nhandles, orig_timeout = 0;
+ ULONGLONG start = 0;
fd_set rfds, wfds, xfds;
BOOL poll_again;
MSG msg;
@@ -459,6 +441,12 @@ poll (struct pollfd *pfd, nfds_t nfd, int timeout)
return -1;
}
+ if (timeout != INFTIM)
+ {
+ orig_timeout = timeout;
+ start = GetTickCount64();
+ }
+
if (!hEvent)
hEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
@@ -576,7 +564,7 @@ restart:
{
/* It's a socket. */
WSAEnumNetworkEvents ((SOCKET) h, NULL, &ev);
- WSAEventSelect ((SOCKET) h, 0, 0);
+ WSAEventSelect ((SOCKET) h, NULL, 0);
/* If we're lucky, WSAEnumNetworkEvents already provided a way
to distinguish FD_READ and FD_ACCEPT; this saves a recv later. */
@@ -603,9 +591,15 @@ restart:
rc++;
}
- if (!rc && timeout == INFTIM)
+ if (!rc && orig_timeout && timeout != INFTIM)
+ {
+ ULONGLONG elapsed = GetTickCount64() - start;
+ timeout = elapsed >= orig_timeout ? 0 : (int)(orig_timeout - elapsed);
+ }
+
+ if (!rc && timeout)
{
- SwitchToThread();
+ SleepEx (1, TRUE);
goto restart;
}
diff --git a/compat/poll/poll.h b/compat/poll/poll.h
index b7aa59d973..1e1597360f 100644
--- a/compat/poll/poll.h
+++ b/compat/poll/poll.h
@@ -16,12 +16,26 @@
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
- with this program; if not, write to the Free Software Foundation,
- Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+ with this program; if not, see <http://www.gnu.org/licenses/>. */
#ifndef _GL_POLL_H
#define _GL_POLL_H
+#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x600
+/* Vista has its own, socket-only poll() */
+#undef POLLIN
+#undef POLLPRI
+#undef POLLOUT
+#undef POLLERR
+#undef POLLHUP
+#undef POLLNVAL
+#undef POLLRDNORM
+#undef POLLRDBAND
+#undef POLLWRNORM
+#undef POLLWRBAND
+#define pollfd compat_pollfd
+#endif
+
/* fake a poll(2) environment */
#define POLLIN 0x0001 /* any readable data available */
#define POLLPRI 0x0002 /* OOB/Urgent readable data */
diff --git a/compat/precompose_utf8.c b/compat/precompose_utf8.c
index 8cf59558e1..cce1d57a46 100644
--- a/compat/precompose_utf8.c
+++ b/compat/precompose_utf8.c
@@ -6,6 +6,7 @@
#define PRECOMPOSE_UNICODE_C
#include "cache.h"
+#include "config.h"
#include "utf8.h"
#include "precompose_utf8.h"
@@ -36,55 +37,68 @@ static size_t has_non_ascii(const char *s, size_t maxlen, size_t *strlen_c)
}
-void probe_utf8_pathname_composition(char *path, int len)
+void probe_utf8_pathname_composition(void)
{
+ struct strbuf path = STRBUF_INIT;
static const char *auml_nfc = "\xc3\xa4";
static const char *auml_nfd = "\x61\xcc\x88";
int output_fd;
if (precomposed_unicode != -1)
return; /* We found it defined in the global config, respect it */
- strcpy(path + len, auml_nfc);
- output_fd = open(path, O_CREAT|O_EXCL|O_RDWR, 0600);
+ git_path_buf(&path, "%s", auml_nfc);
+ output_fd = open(path.buf, O_CREAT|O_EXCL|O_RDWR, 0600);
if (output_fd >= 0) {
close(output_fd);
- strcpy(path + len, auml_nfd);
- /* Indicate to the user, that we can configure it to true */
- if (!access(path, R_OK))
- git_config_set("core.precomposeunicode", "false");
- /* To be backward compatible, set precomposed_unicode to 0 */
- precomposed_unicode = 0;
- strcpy(path + len, auml_nfc);
- if (unlink(path))
- die_errno(_("failed to unlink '%s'"), path);
+ git_path_buf(&path, "%s", auml_nfd);
+ precomposed_unicode = access(path.buf, R_OK) ? 0 : 1;
+ git_config_set("core.precomposeunicode",
+ precomposed_unicode ? "true" : "false");
+ git_path_buf(&path, "%s", auml_nfc);
+ if (unlink(path.buf))
+ die_errno(_("failed to unlink '%s'"), path.buf);
}
+ strbuf_release(&path);
}
-
-void precompose_argv(int argc, const char **argv)
+const char *precompose_string_if_needed(const char *in)
{
- int i = 0;
- const char *oldarg;
- char *newarg;
- iconv_t ic_precompose;
+ size_t inlen;
+ size_t outlen;
+ if (!in)
+ return NULL;
+ if (has_non_ascii(in, (size_t)-1, &inlen)) {
+ iconv_t ic_prec;
+ char *out;
+ if (precomposed_unicode < 0)
+ git_config_get_bool("core.precomposeunicode", &precomposed_unicode);
+ if (precomposed_unicode != 1)
+ return in;
+ ic_prec = iconv_open(repo_encoding, path_encoding);
+ if (ic_prec == (iconv_t) -1)
+ return in;
+
+ out = reencode_string_iconv(in, inlen, ic_prec, 0, &outlen);
+ if (out) {
+ if (outlen == inlen && !memcmp(in, out, outlen))
+ free(out); /* no need to return indentical */
+ else
+ in = out;
+ }
+ iconv_close(ic_prec);
- if (precomposed_unicode != 1)
- return;
+ }
+ return in;
+}
- ic_precompose = iconv_open(repo_encoding, path_encoding);
- if (ic_precompose == (iconv_t) -1)
- return;
+const char *precompose_argv_prefix(int argc, const char **argv, const char *prefix)
+{
+ int i = 0;
while (i < argc) {
- size_t namelen;
- oldarg = argv[i];
- if (has_non_ascii(oldarg, (size_t)-1, &namelen)) {
- newarg = reencode_string_iconv(oldarg, namelen, ic_precompose);
- if (newarg)
- argv[i] = newarg;
- }
+ argv[i] = precompose_string_if_needed(argv[i]);
i++;
}
- iconv_close(ic_precompose);
+ return precompose_string_if_needed(prefix);
}
@@ -134,7 +148,7 @@ struct dirent_prec_psx *precompose_utf8_readdir(PREC_DIR *prec_dir)
if (prec_dir->ic_precompose == (iconv_t)-1) {
die("iconv_open(%s,%s) failed, but needed:\n"
" precomposed unicode is not supported.\n"
- " If you wnat to use decomposed unicode, run\n"
+ " If you want to use decomposed unicode, run\n"
" \"git config core.precomposeunicode false\"\n",
repo_encoding, path_encoding);
} else {
@@ -142,13 +156,12 @@ struct dirent_prec_psx *precompose_utf8_readdir(PREC_DIR *prec_dir)
size_t inleft = namelenz;
char *outpos = &prec_dir->dirent_nfc->d_name[0];
size_t outsz = prec_dir->dirent_nfc->max_name_len;
- size_t cnt;
errno = 0;
- cnt = iconv(prec_dir->ic_precompose, &cp, &inleft, &outpos, &outsz);
+ iconv(prec_dir->ic_precompose, &cp, &inleft, &outpos, &outsz);
if (errno || inleft) {
/*
* iconv() failed and errno could be E2BIG, EILSEQ, EINVAL, EBADF
- * MacOS X avoids illegal byte sequemces.
+ * MacOS X avoids illegal byte sequences.
* If they occur on a mounted drive (e.g. NFS) it is not worth to
* die() for that, but rather let the user see the original name
*/
diff --git a/compat/precompose_utf8.h b/compat/precompose_utf8.h
index 3b73585fc5..fea06cf28a 100644
--- a/compat/precompose_utf8.h
+++ b/compat/precompose_utf8.h
@@ -1,4 +1,6 @@
#ifndef PRECOMPOSE_UNICODE_H
+#define PRECOMPOSE_UNICODE_H
+
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
@@ -26,8 +28,9 @@ typedef struct {
struct dirent_prec_psx *dirent_nfc;
} PREC_DIR;
-void precompose_argv(int argc, const char **argv);
-void probe_utf8_pathname_composition(char *, int);
+const char *precompose_argv_prefix(int argc, const char **argv, const char *prefix);
+const char *precompose_string_if_needed(const char *in);
+void probe_utf8_pathname_composition(void);
PREC_DIR *precompose_utf8_opendir(const char *dirname);
struct dirent_prec_psx *precompose_utf8_readdir(PREC_DIR *dirp);
@@ -41,5 +44,4 @@ int precompose_utf8_closedir(PREC_DIR *dirp);
#define DIR PREC_DIR
#endif /* PRECOMPOSE_UNICODE_C */
-#define PRECOMPOSE_UNICODE_H
#endif /* PRECOMPOSE_UNICODE_H */
diff --git a/compat/qsort.c b/compat/qsort_s.c
index 9574d537bd..52d1f0a73d 100644
--- a/compat/qsort.c
+++ b/compat/qsort_s.c
@@ -3,11 +3,12 @@
/*
* A merge sort implementation, simplified from the qsort implementation
* by Mike Haertel, which is a part of the GNU C Library.
+ * Added context pointer, safety checks and return value.
*/
static void msort_with_tmp(void *b, size_t n, size_t s,
- int (*cmp)(const void *, const void *),
- char *t)
+ int (*cmp)(const void *, const void *, void *),
+ char *t, void *ctx)
{
char *tmp;
char *b1, *b2;
@@ -21,13 +22,13 @@ static void msort_with_tmp(void *b, size_t n, size_t s,
b1 = b;
b2 = (char *)b + (n1 * s);
- msort_with_tmp(b1, n1, s, cmp, t);
- msort_with_tmp(b2, n2, s, cmp, t);
+ msort_with_tmp(b1, n1, s, cmp, t, ctx);
+ msort_with_tmp(b2, n2, s, cmp, t, ctx);
tmp = t;
while (n1 > 0 && n2 > 0) {
- if (cmp(b1, b2) <= 0) {
+ if (cmp(b1, b2, ctx) <= 0) {
memcpy(tmp, b1, s);
tmp += s;
b1 += s;
@@ -44,19 +45,25 @@ static void msort_with_tmp(void *b, size_t n, size_t s,
memcpy(b, t, (n - n2) * s);
}
-void git_qsort(void *b, size_t n, size_t s,
- int (*cmp)(const void *, const void *))
+int git_qsort_s(void *b, size_t n, size_t s,
+ int (*cmp)(const void *, const void *, void *), void *ctx)
{
- const size_t size = n * s;
+ const size_t size = st_mult(n, s);
char buf[1024];
+ if (!n)
+ return 0;
+ if (!b || !cmp)
+ return -1;
+
if (size < sizeof(buf)) {
/* The temporary array fits on the small on-stack buffer. */
- msort_with_tmp(b, n, s, cmp, buf);
+ msort_with_tmp(b, n, s, cmp, buf, ctx);
} else {
/* It's somewhat large, so malloc it. */
char *tmp = xmalloc(size);
- msort_with_tmp(b, n, s, cmp, tmp);
+ msort_with_tmp(b, n, s, cmp, tmp, ctx);
free(tmp);
}
+ return 0;
}
diff --git a/compat/regex/regcomp.c b/compat/regex/regcomp.c
index 8c96ed942c..d1bc09e49b 100644
--- a/compat/regex/regcomp.c
+++ b/compat/regex/regcomp.c
@@ -14,9 +14,16 @@
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
- License along with the GNU C Library; if not, write to the Free
- Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- 02110-1301 USA. */
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#if defined __TANDEM
+ /* This is currently duplicated from git-compat-utils.h */
+# ifdef NO_INTPTR_T
+ typedef long intptr_t;
+ typedef unsigned long uintptr_t;
+# endif
+#endif
static reg_errcode_t re_compile_internal (regex_t *preg, const char * pattern,
size_t length, reg_syntax_t syntax);
@@ -339,7 +346,7 @@ re_compile_fastmap_iter (regex_t *bufp, const re_dfastate_t *init_state,
p = buf;
*p++ = dfa->nodes[node].opr.c;
while (++node < dfa->nodes_len
- && dfa->nodes[node].type == CHARACTER
+ && dfa->nodes[node].type == CHARACTER
&& dfa->nodes[node].mb_partial)
*p++ = dfa->nodes[node].opr.c;
memset (&state, '\0', sizeof (state));
@@ -2095,7 +2102,7 @@ peek_token_bracket (re_token_t *token, re_string_t *input, reg_syntax_t syntax)
/* Entry point of the parser.
Parse the regular expression REGEXP and return the structure tree.
- If an error is occured, ERR is set by error code, and return NULL.
+ If an error has occurred, ERR is set by error code, and return NULL.
This function build the following tree, from regular expression <reg_exp>:
CAT
/ \
@@ -2577,7 +2584,7 @@ parse_dup_op (bin_tree_t *elem, re_string_t *regexp, re_dfa_t *dfa,
old_tree = NULL;
if (elem->token.type == SUBEXP)
- postorder (elem, mark_opt_subexp, (void *) (long) elem->token.opr.idx);
+ postorder (elem, mark_opt_subexp, (void *) (intptr_t) elem->token.opr.idx);
tree = create_tree (dfa, elem, NULL, (end == -1 ? OP_DUP_ASTERISK : OP_ALT));
if (BE (tree == NULL, 0))
@@ -2617,7 +2624,7 @@ parse_dup_op (bin_tree_t *elem, re_string_t *regexp, re_dfa_t *dfa,
Build the range expression which starts from START_ELEM, and ends
at END_ELEM. The result are written to MBCSET and SBCSET.
RANGE_ALLOC is the allocated size of mbcset->range_starts, and
- mbcset->range_ends, is a pointer argument sinse we may
+ mbcset->range_ends, is a pointer argument since we may
update it. */
static reg_errcode_t
@@ -2788,7 +2795,7 @@ parse_bracket_exp (re_string_t *regexp, re_dfa_t *dfa, re_token_t *token,
const int32_t *symb_table;
const unsigned char *extra;
- /* Local function for parse_bracket_exp used in _LIBC environement.
+ /* Local function for parse_bracket_exp used in _LIBC environment.
Seek the collating symbol entry correspondings to NAME.
Return the index of the symbol in the SYMB_TABLE. */
@@ -2892,11 +2899,11 @@ parse_bracket_exp (re_string_t *regexp, re_dfa_t *dfa, re_token_t *token,
return UINT_MAX;
}
- /* Local function for parse_bracket_exp used in _LIBC environement.
+ /* Local function for parse_bracket_exp used in _LIBC environment.
Build the range expression which starts from START_ELEM, and ends
at END_ELEM. The result are written to MBCSET and SBCSET.
RANGE_ALLOC is the allocated size of mbcset->range_starts, and
- mbcset->range_ends, is a pointer argument sinse we may
+ mbcset->range_ends, is a pointer argument since we may
update it. */
auto inline reg_errcode_t
@@ -2976,11 +2983,11 @@ parse_bracket_exp (re_string_t *regexp, re_dfa_t *dfa, re_token_t *token,
return REG_NOERROR;
}
- /* Local function for parse_bracket_exp used in _LIBC environement.
+ /* Local function for parse_bracket_exp used in _LIBC environment.
Build the collating element which is represented by NAME.
The result are written to MBCSET and SBCSET.
COLL_SYM_ALLOC is the allocated size of mbcset->coll_sym, is a
- pointer argument sinse we may update it. */
+ pointer argument since we may update it. */
auto inline reg_errcode_t
__attribute ((always_inline))
@@ -3419,7 +3426,7 @@ parse_bracket_symbol (bracket_elem_t *elem, re_string_t *regexp,
Build the equivalence class which is represented by NAME.
The result are written to MBCSET and SBCSET.
EQUIV_CLASS_ALLOC is the allocated size of mbcset->equiv_classes,
- is a pointer argument sinse we may update it. */
+ is a pointer argument since we may update it. */
static reg_errcode_t
#ifdef RE_ENABLE_I18N
@@ -3455,7 +3462,7 @@ build_equiv_class (bitset_t sbcset, const unsigned char *name)
/* This isn't a valid character. */
return REG_ECOLLATE;
- /* Build single byte matcing table for this equivalence class. */
+ /* Build single byte matching table for this equivalence class. */
char_buf[1] = (unsigned char) '\0';
len = weights[idx1 & 0xffffff];
for (ch = 0; ch < SBC_MAX; ++ch)
@@ -3515,7 +3522,7 @@ build_equiv_class (bitset_t sbcset, const unsigned char *name)
Build the character class which is represented by NAME.
The result are written to MBCSET and SBCSET.
CHAR_CLASS_ALLOC is the allocated size of mbcset->char_classes,
- is a pointer argument sinse we may update it. */
+ is a pointer argument since we may update it. */
static reg_errcode_t
#ifdef RE_ENABLE_I18N
@@ -3715,7 +3722,7 @@ build_charclass_op (re_dfa_t *dfa, RE_TRANSLATE_TYPE trans,
/* This is intended for the expressions like "a{1,3}".
Fetch a number from `input', and return the number.
Return -1, if the number field is empty like "{,1}".
- Return -2, If an error is occured. */
+ Return -2, if an error has occurred. */
static int
fetch_number (re_string_t *input, re_token_t *token, reg_syntax_t syntax)
@@ -3806,7 +3813,7 @@ create_token_tree (re_dfa_t *dfa, bin_tree_t *left, bin_tree_t *right,
static reg_errcode_t
mark_opt_subexp (void *extra, bin_tree_t *node)
{
- int idx = (int) (long) extra;
+ int idx = (int) (intptr_t) extra;
if (node->token.type == SUBEXP && node->token.opr.idx == idx)
node->token.opt_subexp = 1;
diff --git a/compat/regex/regex.c b/compat/regex/regex.c
index 3dd8dfa01f..e6f4a5d177 100644
--- a/compat/regex/regex.c
+++ b/compat/regex/regex.c
@@ -14,15 +14,14 @@
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
- License along with the GNU C Library; if not, write to the Free
- Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- 02110-1301 USA. */
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
-/* Make sure noone compiles this code with a C++ compiler. */
+/* Make sure no one compiles this code with a C++ compiler. */
#ifdef __cplusplus
# error "This is C code, use a C compiler"
#endif
@@ -60,6 +59,8 @@
GNU regex allows. Include it before <regex.h>, which correctly
#undefs RE_DUP_MAX and sets it to the right value. */
#include <limits.h>
+#include <stdint.h>
+#include <stdlib.h>
#ifdef GAWK
#undef alloca
diff --git a/compat/regex/regex.h b/compat/regex/regex.h
index 61c9683872..2d3412860d 100644
--- a/compat/regex/regex.h
+++ b/compat/regex/regex.h
@@ -18,9 +18,8 @@
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
- License along with the GNU C Library; if not, write to the Free
- Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- 02110-1301 USA. */
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
#ifndef _REGEX_H
#define _REGEX_H 1
@@ -42,6 +41,11 @@
extern "C" {
#endif
+#define regcomp git_regcomp
+#define regexec git_regexec
+#define regerror git_regerror
+#define regfree git_regfree
+
/* The following two types have to be signed and unsigned integer type
wide enough to hold a value of a pointer. For most ANSI compilers
ptrdiff_t and size_t should be likely OK. Still size of these two
@@ -323,7 +327,7 @@ typedef enum
/* POSIX regcomp return error codes. (In the order listed in the
standard.) */
REG_BADPAT, /* Invalid pattern. */
- REG_ECOLLATE, /* Inalid collating element. */
+ REG_ECOLLATE, /* Invalid collating element. */
REG_ECTYPE, /* Invalid character class name. */
REG_EESCAPE, /* Trailing backslash. */
REG_ESUBREG, /* Invalid back reference. */
diff --git a/compat/regex/regex_internal.c b/compat/regex/regex_internal.c
index 193854cf5b..ec51cf3446 100644
--- a/compat/regex/regex_internal.c
+++ b/compat/regex/regex_internal.c
@@ -14,9 +14,8 @@
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
- License along with the GNU C Library; if not, write to the Free
- Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- 02110-1301 USA. */
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
static void re_string_construct_common (const char *str, int len,
re_string_t *pstr,
@@ -613,7 +612,7 @@ re_string_reconstruct (re_string_t *pstr, int idx, int eflags)
int low = 0, high = pstr->valid_len, mid;
do
{
- mid = (high + low) / 2;
+ mid = low + (high - low) / 2;
if (pstr->offsets[mid] > offset)
high = mid;
else if (pstr->offsets[mid] < offset)
@@ -1284,7 +1283,7 @@ re_node_set_merge (re_node_set *dest, const re_node_set *src)
/* Insert the new element ELEM to the re_node_set* SET.
SET should not already have ELEM.
- return -1 if an error is occured, return 1 otherwise. */
+ return -1 if an error has occurred, return 1 otherwise. */
static int
internal_function
@@ -1341,7 +1340,7 @@ re_node_set_insert (re_node_set *set, int elem)
/* Insert the new element ELEM to the re_node_set* SET.
SET should not already have any element greater than or equal to ELEM.
- Return -1 if an error is occured, return 1 otherwise. */
+ Return -1 if an error has occurred, return 1 otherwise. */
static int
internal_function
@@ -1394,7 +1393,7 @@ re_node_set_contains (const re_node_set *set, int elem)
right = set->nelem - 1;
while (idx < right)
{
- mid = (idx + right) / 2;
+ mid = idx + (right - idx) / 2;
if (set->elems[mid] < elem)
idx = mid + 1;
else
@@ -1416,7 +1415,7 @@ re_node_set_remove_at (re_node_set *set, int idx)
/* Add the token TOKEN to dfa->nodes, and return the index of the token.
- Or return -1, if an error will be occured. */
+ Or return -1, if an error has occurred. */
static int
internal_function
@@ -1617,7 +1616,7 @@ free_state (re_dfastate_t *state)
re_free (state);
}
-/* Create the new state which is independ of contexts.
+/* Create the new state which is independent of contexts.
Return the new state if succeeded, otherwise return NULL. */
static re_dfastate_t *
diff --git a/compat/regex/regex_internal.h b/compat/regex/regex_internal.h
index 4184d7f5a6..0bad8b841e 100644
--- a/compat/regex/regex_internal.h
+++ b/compat/regex/regex_internal.h
@@ -14,9 +14,8 @@
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
- License along with the GNU C Library; if not, write to the Free
- Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
- 02111-1307 USA. */
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
#ifndef _REGEX_INTERNAL_H
#define _REGEX_INTERNAL_H 1
@@ -24,7 +23,6 @@
#include <assert.h>
#include <ctype.h>
#include <stdio.h>
-#include <stdlib.h>
#include <string.h>
#if defined HAVE_LANGINFO_H || defined HAVE_LANGINFO_CODESET || defined _LIBC
diff --git a/compat/regex/regexec.c b/compat/regex/regexec.c
index 0194965c5d..49358ae475 100644
--- a/compat/regex/regexec.c
+++ b/compat/regex/regexec.c
@@ -14,9 +14,8 @@
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
- License along with the GNU C Library; if not, write to the Free
- Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- 02110-1301 USA. */
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
static reg_errcode_t match_ctx_init (re_match_context_t *cache, int eflags,
int n) internal_function;
@@ -455,7 +454,7 @@ re_search_stub (struct re_pattern_buffer *bufp,
rval = 0;
- /* I hope we needn't fill ther regs with -1's when no match was found. */
+ /* I hope we needn't fill their regs with -1's when no match was found. */
if (result != REG_NOERROR)
rval = -1;
else if (regs != NULL)
@@ -1071,7 +1070,7 @@ acquire_init_state_context (reg_errcode_t *err, const re_match_context_t *mctx,
FL_LONGEST_MATCH means we want the POSIX longest matching.
If P_MATCH_FIRST is not NULL, and the match fails, it is set to the
next place where we may want to try matching.
- Note that the matcher assume that the maching starts from the current
+ Note that the matcher assume that the matching starts from the current
index of the buffer. */
static int
@@ -2239,7 +2238,7 @@ sift_states_iter_mb (const re_match_context_t *mctx, re_sift_context_t *sctx,
dfa->nexts[node_idx]))
/* The node can't accept the `multi byte', or the
destination was already thrown away, then the node
- could't accept the current input `multi byte'. */
+ couldn't accept the current input `multi byte'. */
naccepted = 0;
/* Otherwise, it is sure that the node could accept
`naccepted' bytes input. */
@@ -2313,7 +2312,7 @@ transit_state (reg_errcode_t *err, re_match_context_t *mctx,
}
/* Update the state_log if we need */
-re_dfastate_t *
+static re_dfastate_t *
internal_function
merge_state_with_log (reg_errcode_t *err, re_match_context_t *mctx,
re_dfastate_t *next_state)
@@ -2326,7 +2325,7 @@ merge_state_with_log (reg_errcode_t *err, re_match_context_t *mctx,
mctx->state_log[cur_idx] = next_state;
mctx->state_log_top = cur_idx;
}
- else if (mctx->state_log[cur_idx] == 0)
+ else if (mctx->state_log[cur_idx] == NULL)
{
mctx->state_log[cur_idx] = next_state;
}
@@ -2392,7 +2391,7 @@ merge_state_with_log (reg_errcode_t *err, re_match_context_t *mctx,
/* Skip bytes in the input that correspond to part of a
multi-byte match, then look in the log for a state
from which to restart matching. */
-re_dfastate_t *
+static re_dfastate_t *
internal_function
find_recover_state (reg_errcode_t *err, re_match_context_t *mctx)
{
@@ -2421,7 +2420,7 @@ find_recover_state (reg_errcode_t *err, re_match_context_t *mctx)
/* From the node set CUR_NODES, pick up the nodes whose types are
OP_OPEN_SUBEXP and which have corresponding back references in the regular
expression. And register them to use them later for evaluating the
- correspoding back references. */
+ corresponding back references. */
static reg_errcode_t
internal_function
@@ -3348,7 +3347,7 @@ build_trtable (const re_dfa_t *dfa, re_dfastate_t *state)
dests_node = dests_alloc->dests_node;
dests_ch = dests_alloc->dests_ch;
- /* Initialize transiton table. */
+ /* Initialize transition table. */
state->word_trtable = state->trtable = NULL;
/* At first, group all nodes belonging to `state' into several
@@ -4102,7 +4101,7 @@ extend_buffers (re_match_context_t *mctx)
if (BE (INT_MAX / 2 / sizeof (re_dfastate_t *) <= pstr->bufs_len, 0))
return REG_ESPACE;
- /* Double the lengthes of the buffers. */
+ /* Double the lengths of the buffers. */
ret = re_string_realloc_buffers (pstr, pstr->bufs_len * 2);
if (BE (ret != REG_NOERROR, 0))
return ret;
@@ -4284,7 +4283,7 @@ search_cur_bkref_entry (const re_match_context_t *mctx, int str_idx)
last = right = mctx->nbkref_ents;
for (left = 0; left < right;)
{
- mid = (left + right) / 2;
+ mid = left + (right - left) / 2;
if (mctx->bkref_ents[mid].str_idx < str_idx)
left = mid + 1;
else
diff --git a/compat/setenv.c b/compat/setenv.c
index fc1439a643..7849f258d2 100644
--- a/compat/setenv.c
+++ b/compat/setenv.c
@@ -18,7 +18,7 @@ int gitsetenv(const char *name, const char *value, int replace)
namelen = strlen(name);
valuelen = strlen(value);
- envstr = malloc((namelen + valuelen + 2));
+ envstr = malloc(st_add3(namelen, valuelen, 2));
if (!envstr) {
errno = ENOMEM;
return -1;
diff --git a/compat/sha1-chunked.c b/compat/sha1-chunked.c
new file mode 100644
index 0000000000..6adfcfd540
--- /dev/null
+++ b/compat/sha1-chunked.c
@@ -0,0 +1,19 @@
+#include "cache.h"
+
+int git_SHA1_Update_Chunked(platform_SHA_CTX *c, const void *data, size_t len)
+{
+ size_t nr;
+ size_t total = 0;
+ const char *cdata = (const char*)data;
+
+ while (len) {
+ nr = len;
+ if (nr > SHA1_MAX_BLOCK_SIZE)
+ nr = SHA1_MAX_BLOCK_SIZE;
+ platform_SHA1_Update(c, cdata, nr);
+ total += nr;
+ cdata += nr;
+ len -= nr;
+ }
+ return total;
+}
diff --git a/compat/sha1-chunked.h b/compat/sha1-chunked.h
new file mode 100644
index 0000000000..7b2df28eec
--- /dev/null
+++ b/compat/sha1-chunked.h
@@ -0,0 +1,2 @@
+
+int git_SHA1_Update_Chunked(platform_SHA_CTX *c, const void *data, size_t len);
diff --git a/compat/simple-ipc/ipc-shared.c b/compat/simple-ipc/ipc-shared.c
new file mode 100644
index 0000000000..1b9d359ab6
--- /dev/null
+++ b/compat/simple-ipc/ipc-shared.c
@@ -0,0 +1,32 @@
+#include "cache.h"
+#include "simple-ipc.h"
+#include "strbuf.h"
+#include "pkt-line.h"
+#include "thread-utils.h"
+
+#ifndef SUPPORTS_SIMPLE_IPC
+/*
+ * This source file should only be compiled when Simple IPC is supported.
+ * See the top-level Makefile.
+ */
+#error SUPPORTS_SIMPLE_IPC not defined
+#endif
+
+int ipc_server_run(const char *path, const struct ipc_server_opts *opts,
+ ipc_server_application_cb *application_cb,
+ void *application_data)
+{
+ struct ipc_server_data *server_data = NULL;
+ int ret;
+
+ ret = ipc_server_run_async(&server_data, path, opts,
+ application_cb, application_data);
+ if (ret)
+ return ret;
+
+ ret = ipc_server_await(server_data);
+
+ ipc_server_free(server_data);
+
+ return ret;
+}
diff --git a/compat/simple-ipc/ipc-unix-socket.c b/compat/simple-ipc/ipc-unix-socket.c
new file mode 100644
index 0000000000..4e28857a0a
--- /dev/null
+++ b/compat/simple-ipc/ipc-unix-socket.c
@@ -0,0 +1,1007 @@
+#include "cache.h"
+#include "simple-ipc.h"
+#include "strbuf.h"
+#include "pkt-line.h"
+#include "thread-utils.h"
+#include "unix-socket.h"
+#include "unix-stream-server.h"
+
+#ifndef SUPPORTS_SIMPLE_IPC
+/*
+ * This source file should only be compiled when Simple IPC is supported.
+ * See the top-level Makefile.
+ */
+#error SUPPORTS_SIMPLE_IPC not defined
+#endif
+
+enum ipc_active_state ipc_get_active_state(const char *path)
+{
+ enum ipc_active_state state = IPC_STATE__OTHER_ERROR;
+ struct ipc_client_connect_options options
+ = IPC_CLIENT_CONNECT_OPTIONS_INIT;
+ struct stat st;
+ struct ipc_client_connection *connection_test = NULL;
+
+ options.wait_if_busy = 0;
+ options.wait_if_not_found = 0;
+
+ if (lstat(path, &st) == -1) {
+ switch (errno) {
+ case ENOENT:
+ case ENOTDIR:
+ return IPC_STATE__NOT_LISTENING;
+ default:
+ return IPC_STATE__INVALID_PATH;
+ }
+ }
+
+ /* also complain if a plain file is in the way */
+ if ((st.st_mode & S_IFMT) != S_IFSOCK)
+ return IPC_STATE__INVALID_PATH;
+
+ /*
+ * Just because the filesystem has a S_IFSOCK type inode
+ * at `path`, doesn't mean it that there is a server listening.
+ * Ping it to be sure.
+ */
+ state = ipc_client_try_connect(path, &options, &connection_test);
+ ipc_client_close_connection(connection_test);
+
+ return state;
+}
+
+/*
+ * Retry frequency when trying to connect to a server.
+ *
+ * This value should be short enough that we don't seriously delay our
+ * caller, but not fast enough that our spinning puts pressure on the
+ * system.
+ */
+#define WAIT_STEP_MS (50)
+
+/*
+ * Try to connect to the server. If the server is just starting up or
+ * is very busy, we may not get a connection the first time.
+ */
+static enum ipc_active_state connect_to_server(
+ const char *path,
+ int timeout_ms,
+ const struct ipc_client_connect_options *options,
+ int *pfd)
+{
+ int k;
+
+ *pfd = -1;
+
+ for (k = 0; k < timeout_ms; k += WAIT_STEP_MS) {
+ int fd = unix_stream_connect(path, options->uds_disallow_chdir);
+
+ if (fd != -1) {
+ *pfd = fd;
+ return IPC_STATE__LISTENING;
+ }
+
+ if (errno == ENOENT) {
+ if (!options->wait_if_not_found)
+ return IPC_STATE__PATH_NOT_FOUND;
+
+ goto sleep_and_try_again;
+ }
+
+ if (errno == ETIMEDOUT) {
+ if (!options->wait_if_busy)
+ return IPC_STATE__NOT_LISTENING;
+
+ goto sleep_and_try_again;
+ }
+
+ if (errno == ECONNREFUSED) {
+ if (!options->wait_if_busy)
+ return IPC_STATE__NOT_LISTENING;
+
+ goto sleep_and_try_again;
+ }
+
+ return IPC_STATE__OTHER_ERROR;
+
+ sleep_and_try_again:
+ sleep_millisec(WAIT_STEP_MS);
+ }
+
+ return IPC_STATE__NOT_LISTENING;
+}
+
+/*
+ * The total amount of time that we are willing to wait when trying to
+ * connect to a server.
+ *
+ * When the server is first started, it might take a little while for
+ * it to become ready to service requests. Likewise, the server may
+ * be very (temporarily) busy and not respond to our connections.
+ *
+ * We should gracefully and silently handle those conditions and try
+ * again for a reasonable time period.
+ *
+ * The value chosen here should be long enough for the server
+ * to reliably heal from the above conditions.
+ */
+#define MY_CONNECTION_TIMEOUT_MS (1000)
+
+enum ipc_active_state ipc_client_try_connect(
+ const char *path,
+ const struct ipc_client_connect_options *options,
+ struct ipc_client_connection **p_connection)
+{
+ enum ipc_active_state state = IPC_STATE__OTHER_ERROR;
+ int fd = -1;
+
+ *p_connection = NULL;
+
+ trace2_region_enter("ipc-client", "try-connect", NULL);
+ trace2_data_string("ipc-client", NULL, "try-connect/path", path);
+
+ state = connect_to_server(path, MY_CONNECTION_TIMEOUT_MS,
+ options, &fd);
+
+ trace2_data_intmax("ipc-client", NULL, "try-connect/state",
+ (intmax_t)state);
+ trace2_region_leave("ipc-client", "try-connect", NULL);
+
+ if (state == IPC_STATE__LISTENING) {
+ (*p_connection) = xcalloc(1, sizeof(struct ipc_client_connection));
+ (*p_connection)->fd = fd;
+ }
+
+ return state;
+}
+
+void ipc_client_close_connection(struct ipc_client_connection *connection)
+{
+ if (!connection)
+ return;
+
+ if (connection->fd != -1)
+ close(connection->fd);
+
+ free(connection);
+}
+
+int ipc_client_send_command_to_connection(
+ struct ipc_client_connection *connection,
+ const char *message, size_t message_len,
+ struct strbuf *answer)
+{
+ int ret = 0;
+
+ strbuf_setlen(answer, 0);
+
+ trace2_region_enter("ipc-client", "send-command", NULL);
+
+ if (write_packetized_from_buf_no_flush(message, message_len,
+ connection->fd) < 0 ||
+ packet_flush_gently(connection->fd) < 0) {
+ ret = error(_("could not send IPC command"));
+ goto done;
+ }
+
+ if (read_packetized_to_strbuf(
+ connection->fd, answer,
+ PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR) < 0) {
+ ret = error(_("could not read IPC response"));
+ goto done;
+ }
+
+done:
+ trace2_region_leave("ipc-client", "send-command", NULL);
+ return ret;
+}
+
+int ipc_client_send_command(const char *path,
+ const struct ipc_client_connect_options *options,
+ const char *message, size_t message_len,
+ struct strbuf *answer)
+{
+ int ret = -1;
+ enum ipc_active_state state;
+ struct ipc_client_connection *connection = NULL;
+
+ state = ipc_client_try_connect(path, options, &connection);
+
+ if (state != IPC_STATE__LISTENING)
+ return ret;
+
+ ret = ipc_client_send_command_to_connection(connection,
+ message, message_len,
+ answer);
+
+ ipc_client_close_connection(connection);
+
+ return ret;
+}
+
+static int set_socket_blocking_flag(int fd, int make_nonblocking)
+{
+ int flags;
+
+ flags = fcntl(fd, F_GETFL, NULL);
+
+ if (flags < 0)
+ return -1;
+
+ if (make_nonblocking)
+ flags |= O_NONBLOCK;
+ else
+ flags &= ~O_NONBLOCK;
+
+ return fcntl(fd, F_SETFL, flags);
+}
+
+/*
+ * Magic numbers used to annotate callback instance data.
+ * These are used to help guard against accidentally passing the
+ * wrong instance data across multiple levels of callbacks (which
+ * is easy to do if there are `void*` arguments).
+ */
+enum magic {
+ MAGIC_SERVER_REPLY_DATA,
+ MAGIC_WORKER_THREAD_DATA,
+ MAGIC_ACCEPT_THREAD_DATA,
+ MAGIC_SERVER_DATA,
+};
+
+struct ipc_server_reply_data {
+ enum magic magic;
+ int fd;
+ struct ipc_worker_thread_data *worker_thread_data;
+};
+
+struct ipc_worker_thread_data {
+ enum magic magic;
+ struct ipc_worker_thread_data *next_thread;
+ struct ipc_server_data *server_data;
+ pthread_t pthread_id;
+};
+
+struct ipc_accept_thread_data {
+ enum magic magic;
+ struct ipc_server_data *server_data;
+
+ struct unix_ss_socket *server_socket;
+
+ int fd_send_shutdown;
+ int fd_wait_shutdown;
+ pthread_t pthread_id;
+};
+
+/*
+ * With unix-sockets, the conceptual "ipc-server" is implemented as a single
+ * controller "accept-thread" thread and a pool of "worker-thread" threads.
+ * The former does the usual `accept()` loop and dispatches connections
+ * to an idle worker thread. The worker threads wait in an idle loop for
+ * a new connection, communicate with the client and relay data to/from
+ * the `application_cb` and then wait for another connection from the
+ * server thread. This avoids the overhead of constantly creating and
+ * destroying threads.
+ */
+struct ipc_server_data {
+ enum magic magic;
+ ipc_server_application_cb *application_cb;
+ void *application_data;
+ struct strbuf buf_path;
+
+ struct ipc_accept_thread_data *accept_thread;
+ struct ipc_worker_thread_data *worker_thread_list;
+
+ pthread_mutex_t work_available_mutex;
+ pthread_cond_t work_available_cond;
+
+ /*
+ * Accepted but not yet processed client connections are kept
+ * in a circular buffer FIFO. The queue is empty when the
+ * positions are equal.
+ */
+ int *fifo_fds;
+ int queue_size;
+ int back_pos;
+ int front_pos;
+
+ int shutdown_requested;
+ int is_stopped;
+};
+
+/*
+ * Remove and return the oldest queued connection.
+ *
+ * Returns -1 if empty.
+ */
+static int fifo_dequeue(struct ipc_server_data *server_data)
+{
+ /* ASSERT holding mutex */
+
+ int fd;
+
+ if (server_data->back_pos == server_data->front_pos)
+ return -1;
+
+ fd = server_data->fifo_fds[server_data->front_pos];
+ server_data->fifo_fds[server_data->front_pos] = -1;
+
+ server_data->front_pos++;
+ if (server_data->front_pos == server_data->queue_size)
+ server_data->front_pos = 0;
+
+ return fd;
+}
+
+/*
+ * Push a new fd onto the back of the queue.
+ *
+ * Drop it and return -1 if queue is already full.
+ */
+static int fifo_enqueue(struct ipc_server_data *server_data, int fd)
+{
+ /* ASSERT holding mutex */
+
+ int next_back_pos;
+
+ next_back_pos = server_data->back_pos + 1;
+ if (next_back_pos == server_data->queue_size)
+ next_back_pos = 0;
+
+ if (next_back_pos == server_data->front_pos) {
+ /* Queue is full. Just drop it. */
+ close(fd);
+ return -1;
+ }
+
+ server_data->fifo_fds[server_data->back_pos] = fd;
+ server_data->back_pos = next_back_pos;
+
+ return fd;
+}
+
+/*
+ * Wait for a connection to be queued to the FIFO and return it.
+ *
+ * Returns -1 if someone has already requested a shutdown.
+ */
+static int worker_thread__wait_for_connection(
+ struct ipc_worker_thread_data *worker_thread_data)
+{
+ /* ASSERT NOT holding mutex */
+
+ struct ipc_server_data *server_data = worker_thread_data->server_data;
+ int fd = -1;
+
+ pthread_mutex_lock(&server_data->work_available_mutex);
+ for (;;) {
+ if (server_data->shutdown_requested)
+ break;
+
+ fd = fifo_dequeue(server_data);
+ if (fd >= 0)
+ break;
+
+ pthread_cond_wait(&server_data->work_available_cond,
+ &server_data->work_available_mutex);
+ }
+ pthread_mutex_unlock(&server_data->work_available_mutex);
+
+ return fd;
+}
+
+/*
+ * Forward declare our reply callback function so that any compiler
+ * errors are reported when we actually define the function (in addition
+ * to any errors reported when we try to pass this callback function as
+ * a parameter in a function call). The former are easier to understand.
+ */
+static ipc_server_reply_cb do_io_reply_callback;
+
+/*
+ * Relay application's response message to the client process.
+ * (We do not flush at this point because we allow the caller
+ * to chunk data to the client thru us.)
+ */
+static int do_io_reply_callback(struct ipc_server_reply_data *reply_data,
+ const char *response, size_t response_len)
+{
+ if (reply_data->magic != MAGIC_SERVER_REPLY_DATA)
+ BUG("reply_cb called with wrong instance data");
+
+ return write_packetized_from_buf_no_flush(response, response_len,
+ reply_data->fd);
+}
+
+/* A randomly chosen value. */
+#define MY_WAIT_POLL_TIMEOUT_MS (10)
+
+/*
+ * If the client hangs up without sending any data on the wire, just
+ * quietly close the socket and ignore this client.
+ *
+ * This worker thread is committed to reading the IPC request data
+ * from the client at the other end of this fd. Wait here for the
+ * client to actually put something on the wire -- because if the
+ * client just does a ping (connect and hangup without sending any
+ * data), our use of the pkt-line read routines will spew an error
+ * message.
+ *
+ * Return -1 if the client hung up.
+ * Return 0 if data (possibly incomplete) is ready.
+ */
+static int worker_thread__wait_for_io_start(
+ struct ipc_worker_thread_data *worker_thread_data,
+ int fd)
+{
+ struct ipc_server_data *server_data = worker_thread_data->server_data;
+ struct pollfd pollfd[1];
+ int result;
+
+ for (;;) {
+ pollfd[0].fd = fd;
+ pollfd[0].events = POLLIN;
+
+ result = poll(pollfd, 1, MY_WAIT_POLL_TIMEOUT_MS);
+ if (result < 0) {
+ if (errno == EINTR)
+ continue;
+ goto cleanup;
+ }
+
+ if (result == 0) {
+ /* a timeout */
+
+ int in_shutdown;
+
+ pthread_mutex_lock(&server_data->work_available_mutex);
+ in_shutdown = server_data->shutdown_requested;
+ pthread_mutex_unlock(&server_data->work_available_mutex);
+
+ /*
+ * If a shutdown is already in progress and this
+ * client has not started talking yet, just drop it.
+ */
+ if (in_shutdown)
+ goto cleanup;
+ continue;
+ }
+
+ if (pollfd[0].revents & POLLHUP)
+ goto cleanup;
+
+ if (pollfd[0].revents & POLLIN)
+ return 0;
+
+ goto cleanup;
+ }
+
+cleanup:
+ close(fd);
+ return -1;
+}
+
+/*
+ * Receive the request/command from the client and pass it to the
+ * registered request-callback. The request-callback will compose
+ * a response and call our reply-callback to send it to the client.
+ */
+static int worker_thread__do_io(
+ struct ipc_worker_thread_data *worker_thread_data,
+ int fd)
+{
+ /* ASSERT NOT holding lock */
+
+ struct strbuf buf = STRBUF_INIT;
+ struct ipc_server_reply_data reply_data;
+ int ret = 0;
+
+ reply_data.magic = MAGIC_SERVER_REPLY_DATA;
+ reply_data.worker_thread_data = worker_thread_data;
+
+ reply_data.fd = fd;
+
+ ret = read_packetized_to_strbuf(
+ reply_data.fd, &buf,
+ PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR);
+ if (ret >= 0) {
+ ret = worker_thread_data->server_data->application_cb(
+ worker_thread_data->server_data->application_data,
+ buf.buf, buf.len, do_io_reply_callback, &reply_data);
+
+ packet_flush_gently(reply_data.fd);
+ }
+ else {
+ /*
+ * The client probably disconnected/shutdown before it
+ * could send a well-formed message. Ignore it.
+ */
+ }
+
+ strbuf_release(&buf);
+ close(reply_data.fd);
+
+ return ret;
+}
+
+/*
+ * Block SIGPIPE on the current thread (so that we get EPIPE from
+ * write() rather than an actual signal).
+ *
+ * Note that using sigchain_push() and _pop() to control SIGPIPE
+ * around our IO calls is not thread safe:
+ * [] It uses a global stack of handler frames.
+ * [] It uses ALLOC_GROW() to resize it.
+ * [] Finally, according to the `signal(2)` man-page:
+ * "The effects of `signal()` in a multithreaded process are unspecified."
+ */
+static void thread_block_sigpipe(sigset_t *old_set)
+{
+ sigset_t new_set;
+
+ sigemptyset(&new_set);
+ sigaddset(&new_set, SIGPIPE);
+
+ sigemptyset(old_set);
+ pthread_sigmask(SIG_BLOCK, &new_set, old_set);
+}
+
+/*
+ * Thread proc for an IPC worker thread. It handles a series of
+ * connections from clients. It pulls the next fd from the queue
+ * processes it, and then waits for the next client.
+ *
+ * Block SIGPIPE in this worker thread for the life of the thread.
+ * This avoids stray (and sometimes delayed) SIGPIPE signals caused
+ * by client errors and/or when we are under extremely heavy IO load.
+ *
+ * This means that the application callback will have SIGPIPE blocked.
+ * The callback should not change it.
+ */
+static void *worker_thread_proc(void *_worker_thread_data)
+{
+ struct ipc_worker_thread_data *worker_thread_data = _worker_thread_data;
+ struct ipc_server_data *server_data = worker_thread_data->server_data;
+ sigset_t old_set;
+ int fd, io;
+ int ret;
+
+ trace2_thread_start("ipc-worker");
+
+ thread_block_sigpipe(&old_set);
+
+ for (;;) {
+ fd = worker_thread__wait_for_connection(worker_thread_data);
+ if (fd == -1)
+ break; /* in shutdown */
+
+ io = worker_thread__wait_for_io_start(worker_thread_data, fd);
+ if (io == -1)
+ continue; /* client hung up without sending anything */
+
+ ret = worker_thread__do_io(worker_thread_data, fd);
+
+ if (ret == SIMPLE_IPC_QUIT) {
+ trace2_data_string("ipc-worker", NULL, "queue_stop_async",
+ "application_quit");
+ /*
+ * The application layer is telling the ipc-server
+ * layer to shutdown.
+ *
+ * We DO NOT have a response to send to the client.
+ *
+ * Queue an async stop (to stop the other threads) and
+ * allow this worker thread to exit now (no sense waiting
+ * for the thread-pool shutdown signal).
+ *
+ * Other non-idle worker threads are allowed to finish
+ * responding to their current clients.
+ */
+ ipc_server_stop_async(server_data);
+ break;
+ }
+ }
+
+ trace2_thread_exit();
+ return NULL;
+}
+
+/* A randomly chosen value. */
+#define MY_ACCEPT_POLL_TIMEOUT_MS (60 * 1000)
+
+/*
+ * Accept a new client connection on our socket. This uses non-blocking
+ * IO so that we can also wait for shutdown requests on our socket-pair
+ * without actually spinning on a fast timeout.
+ */
+static int accept_thread__wait_for_connection(
+ struct ipc_accept_thread_data *accept_thread_data)
+{
+ struct pollfd pollfd[2];
+ int result;
+
+ for (;;) {
+ pollfd[0].fd = accept_thread_data->fd_wait_shutdown;
+ pollfd[0].events = POLLIN;
+
+ pollfd[1].fd = accept_thread_data->server_socket->fd_socket;
+ pollfd[1].events = POLLIN;
+
+ result = poll(pollfd, 2, MY_ACCEPT_POLL_TIMEOUT_MS);
+ if (result < 0) {
+ if (errno == EINTR)
+ continue;
+ return result;
+ }
+
+ if (result == 0) {
+ /* a timeout */
+
+ /*
+ * If someone deletes or force-creates a new unix
+ * domain socket at our path, all future clients
+ * will be routed elsewhere and we silently starve.
+ * If that happens, just queue a shutdown.
+ */
+ if (unix_ss_was_stolen(
+ accept_thread_data->server_socket)) {
+ trace2_data_string("ipc-accept", NULL,
+ "queue_stop_async",
+ "socket_stolen");
+ ipc_server_stop_async(
+ accept_thread_data->server_data);
+ }
+ continue;
+ }
+
+ if (pollfd[0].revents & POLLIN) {
+ /* shutdown message queued to socketpair */
+ return -1;
+ }
+
+ if (pollfd[1].revents & POLLIN) {
+ /* a connection is available on server_socket */
+
+ int client_fd =
+ accept(accept_thread_data->server_socket->fd_socket,
+ NULL, NULL);
+ if (client_fd >= 0)
+ return client_fd;
+
+ /*
+ * An error here is unlikely -- it probably
+ * indicates that the connecting process has
+ * already dropped the connection.
+ */
+ continue;
+ }
+
+ BUG("unandled poll result errno=%d r[0]=%d r[1]=%d",
+ errno, pollfd[0].revents, pollfd[1].revents);
+ }
+}
+
+/*
+ * Thread proc for the IPC server "accept thread". This waits for
+ * an incoming socket connection, appends it to the queue of available
+ * connections, and notifies a worker thread to process it.
+ *
+ * Block SIGPIPE in this thread for the life of the thread. This
+ * avoids any stray SIGPIPE signals when closing pipe fds under
+ * extremely heavy loads (such as when the fifo queue is full and we
+ * drop incomming connections).
+ */
+static void *accept_thread_proc(void *_accept_thread_data)
+{
+ struct ipc_accept_thread_data *accept_thread_data = _accept_thread_data;
+ struct ipc_server_data *server_data = accept_thread_data->server_data;
+ sigset_t old_set;
+
+ trace2_thread_start("ipc-accept");
+
+ thread_block_sigpipe(&old_set);
+
+ for (;;) {
+ int client_fd = accept_thread__wait_for_connection(
+ accept_thread_data);
+
+ pthread_mutex_lock(&server_data->work_available_mutex);
+ if (server_data->shutdown_requested) {
+ pthread_mutex_unlock(&server_data->work_available_mutex);
+ if (client_fd >= 0)
+ close(client_fd);
+ break;
+ }
+
+ if (client_fd < 0) {
+ /* ignore transient accept() errors */
+ }
+ else {
+ fifo_enqueue(server_data, client_fd);
+ pthread_cond_broadcast(&server_data->work_available_cond);
+ }
+ pthread_mutex_unlock(&server_data->work_available_mutex);
+ }
+
+ trace2_thread_exit();
+ return NULL;
+}
+
+/*
+ * We can't predict the connection arrival rate relative to the worker
+ * processing rate, therefore we allow the "accept-thread" to queue up
+ * a generous number of connections, since we'd rather have the client
+ * not unnecessarily timeout if we can avoid it. (The assumption is
+ * that this will be used for FSMonitor and a few second wait on a
+ * connection is better than having the client timeout and do the full
+ * computation itself.)
+ *
+ * The FIFO queue size is set to a multiple of the worker pool size.
+ * This value chosen at random.
+ */
+#define FIFO_SCALE (100)
+
+/*
+ * The backlog value for `listen(2)`. This doesn't need to huge,
+ * rather just large enough for our "accept-thread" to wake up and
+ * queue incoming connections onto the FIFO without the kernel
+ * dropping any.
+ *
+ * This value chosen at random.
+ */
+#define LISTEN_BACKLOG (50)
+
+static int create_listener_socket(
+ const char *path,
+ const struct ipc_server_opts *ipc_opts,
+ struct unix_ss_socket **new_server_socket)
+{
+ struct unix_ss_socket *server_socket = NULL;
+ struct unix_stream_listen_opts uslg_opts = UNIX_STREAM_LISTEN_OPTS_INIT;
+ int ret;
+
+ uslg_opts.listen_backlog_size = LISTEN_BACKLOG;
+ uslg_opts.disallow_chdir = ipc_opts->uds_disallow_chdir;
+
+ ret = unix_ss_create(path, &uslg_opts, -1, &server_socket);
+ if (ret)
+ return ret;
+
+ if (set_socket_blocking_flag(server_socket->fd_socket, 1)) {
+ int saved_errno = errno;
+ unix_ss_free(server_socket);
+ errno = saved_errno;
+ return -1;
+ }
+
+ *new_server_socket = server_socket;
+
+ trace2_data_string("ipc-server", NULL, "listen-with-lock", path);
+ return 0;
+}
+
+static int setup_listener_socket(
+ const char *path,
+ const struct ipc_server_opts *ipc_opts,
+ struct unix_ss_socket **new_server_socket)
+{
+ int ret, saved_errno;
+
+ trace2_region_enter("ipc-server", "create-listener_socket", NULL);
+
+ ret = create_listener_socket(path, ipc_opts, new_server_socket);
+
+ saved_errno = errno;
+ trace2_region_leave("ipc-server", "create-listener_socket", NULL);
+ errno = saved_errno;
+
+ return ret;
+}
+
+/*
+ * Start IPC server in a pool of background threads.
+ */
+int ipc_server_run_async(struct ipc_server_data **returned_server_data,
+ const char *path, const struct ipc_server_opts *opts,
+ ipc_server_application_cb *application_cb,
+ void *application_data)
+{
+ struct unix_ss_socket *server_socket = NULL;
+ struct ipc_server_data *server_data;
+ int sv[2];
+ int k;
+ int ret;
+ int nr_threads = opts->nr_threads;
+
+ *returned_server_data = NULL;
+
+ /*
+ * Create a socketpair and set sv[1] to non-blocking. This
+ * will used to send a shutdown message to the accept-thread
+ * and allows the accept-thread to wait on EITHER a client
+ * connection or a shutdown request without spinning.
+ */
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0)
+ return -1;
+
+ if (set_socket_blocking_flag(sv[1], 1)) {
+ int saved_errno = errno;
+ close(sv[0]);
+ close(sv[1]);
+ errno = saved_errno;
+ return -1;
+ }
+
+ ret = setup_listener_socket(path, opts, &server_socket);
+ if (ret) {
+ int saved_errno = errno;
+ close(sv[0]);
+ close(sv[1]);
+ errno = saved_errno;
+ return ret;
+ }
+
+ server_data = xcalloc(1, sizeof(*server_data));
+ server_data->magic = MAGIC_SERVER_DATA;
+ server_data->application_cb = application_cb;
+ server_data->application_data = application_data;
+ strbuf_init(&server_data->buf_path, 0);
+ strbuf_addstr(&server_data->buf_path, path);
+
+ if (nr_threads < 1)
+ nr_threads = 1;
+
+ pthread_mutex_init(&server_data->work_available_mutex, NULL);
+ pthread_cond_init(&server_data->work_available_cond, NULL);
+
+ server_data->queue_size = nr_threads * FIFO_SCALE;
+ CALLOC_ARRAY(server_data->fifo_fds, server_data->queue_size);
+
+ server_data->accept_thread =
+ xcalloc(1, sizeof(*server_data->accept_thread));
+ server_data->accept_thread->magic = MAGIC_ACCEPT_THREAD_DATA;
+ server_data->accept_thread->server_data = server_data;
+ server_data->accept_thread->server_socket = server_socket;
+ server_data->accept_thread->fd_send_shutdown = sv[0];
+ server_data->accept_thread->fd_wait_shutdown = sv[1];
+
+ if (pthread_create(&server_data->accept_thread->pthread_id, NULL,
+ accept_thread_proc, server_data->accept_thread))
+ die_errno(_("could not start accept_thread '%s'"), path);
+
+ for (k = 0; k < nr_threads; k++) {
+ struct ipc_worker_thread_data *wtd;
+
+ wtd = xcalloc(1, sizeof(*wtd));
+ wtd->magic = MAGIC_WORKER_THREAD_DATA;
+ wtd->server_data = server_data;
+
+ if (pthread_create(&wtd->pthread_id, NULL, worker_thread_proc,
+ wtd)) {
+ if (k == 0)
+ die(_("could not start worker[0] for '%s'"),
+ path);
+ /*
+ * Limp along with the thread pool that we have.
+ */
+ break;
+ }
+
+ wtd->next_thread = server_data->worker_thread_list;
+ server_data->worker_thread_list = wtd;
+ }
+
+ *returned_server_data = server_data;
+ return 0;
+}
+
+/*
+ * Gently tell the IPC server treads to shutdown.
+ * Can be run on any thread.
+ */
+int ipc_server_stop_async(struct ipc_server_data *server_data)
+{
+ /* ASSERT NOT holding mutex */
+
+ int fd;
+
+ if (!server_data)
+ return 0;
+
+ trace2_region_enter("ipc-server", "server-stop-async", NULL);
+
+ pthread_mutex_lock(&server_data->work_available_mutex);
+
+ server_data->shutdown_requested = 1;
+
+ /*
+ * Write a byte to the shutdown socket pair to wake up the
+ * accept-thread.
+ */
+ if (write(server_data->accept_thread->fd_send_shutdown, "Q", 1) < 0)
+ error_errno("could not write to fd_send_shutdown");
+
+ /*
+ * Drain the queue of existing connections.
+ */
+ while ((fd = fifo_dequeue(server_data)) != -1)
+ close(fd);
+
+ /*
+ * Gently tell worker threads to stop processing new connections
+ * and exit. (This does not abort in-process conversations.)
+ */
+ pthread_cond_broadcast(&server_data->work_available_cond);
+
+ pthread_mutex_unlock(&server_data->work_available_mutex);
+
+ trace2_region_leave("ipc-server", "server-stop-async", NULL);
+
+ return 0;
+}
+
+/*
+ * Wait for all IPC server threads to stop.
+ */
+int ipc_server_await(struct ipc_server_data *server_data)
+{
+ pthread_join(server_data->accept_thread->pthread_id, NULL);
+
+ if (!server_data->shutdown_requested)
+ BUG("ipc-server: accept-thread stopped for '%s'",
+ server_data->buf_path.buf);
+
+ while (server_data->worker_thread_list) {
+ struct ipc_worker_thread_data *wtd =
+ server_data->worker_thread_list;
+
+ pthread_join(wtd->pthread_id, NULL);
+
+ server_data->worker_thread_list = wtd->next_thread;
+ free(wtd);
+ }
+
+ server_data->is_stopped = 1;
+
+ return 0;
+}
+
+void ipc_server_free(struct ipc_server_data *server_data)
+{
+ struct ipc_accept_thread_data * accept_thread_data;
+
+ if (!server_data)
+ return;
+
+ if (!server_data->is_stopped)
+ BUG("cannot free ipc-server while running for '%s'",
+ server_data->buf_path.buf);
+
+ accept_thread_data = server_data->accept_thread;
+ if (accept_thread_data) {
+ unix_ss_free(accept_thread_data->server_socket);
+
+ if (accept_thread_data->fd_send_shutdown != -1)
+ close(accept_thread_data->fd_send_shutdown);
+ if (accept_thread_data->fd_wait_shutdown != -1)
+ close(accept_thread_data->fd_wait_shutdown);
+
+ free(server_data->accept_thread);
+ }
+
+ while (server_data->worker_thread_list) {
+ struct ipc_worker_thread_data *wtd =
+ server_data->worker_thread_list;
+
+ server_data->worker_thread_list = wtd->next_thread;
+ free(wtd);
+ }
+
+ pthread_cond_destroy(&server_data->work_available_cond);
+ pthread_mutex_destroy(&server_data->work_available_mutex);
+
+ strbuf_release(&server_data->buf_path);
+
+ free(server_data->fifo_fds);
+ free(server_data);
+}
diff --git a/compat/simple-ipc/ipc-win32.c b/compat/simple-ipc/ipc-win32.c
new file mode 100644
index 0000000000..20ea7b65e0
--- /dev/null
+++ b/compat/simple-ipc/ipc-win32.c
@@ -0,0 +1,900 @@
+#include "cache.h"
+#include "simple-ipc.h"
+#include "strbuf.h"
+#include "pkt-line.h"
+#include "thread-utils.h"
+#include "accctrl.h"
+#include "aclapi.h"
+
+#ifndef SUPPORTS_SIMPLE_IPC
+/*
+ * This source file should only be compiled when Simple IPC is supported.
+ * See the top-level Makefile.
+ */
+#error SUPPORTS_SIMPLE_IPC not defined
+#endif
+
+static int initialize_pipe_name(const char *path, wchar_t *wpath, size_t alloc)
+{
+ int off = 0;
+ struct strbuf realpath = STRBUF_INIT;
+
+ if (!strbuf_realpath(&realpath, path, 0))
+ return -1;
+
+ off = swprintf(wpath, alloc, L"\\\\.\\pipe\\");
+ if (xutftowcs(wpath + off, realpath.buf, alloc - off) < 0)
+ return -1;
+
+ /* Handle drive prefix */
+ if (wpath[off] && wpath[off + 1] == L':') {
+ wpath[off + 1] = L'_';
+ off += 2;
+ }
+
+ for (; wpath[off]; off++)
+ if (wpath[off] == L'/')
+ wpath[off] = L'\\';
+
+ strbuf_release(&realpath);
+ return 0;
+}
+
+static enum ipc_active_state get_active_state(wchar_t *pipe_path)
+{
+ if (WaitNamedPipeW(pipe_path, NMPWAIT_USE_DEFAULT_WAIT))
+ return IPC_STATE__LISTENING;
+
+ if (GetLastError() == ERROR_SEM_TIMEOUT)
+ return IPC_STATE__NOT_LISTENING;
+
+ if (GetLastError() == ERROR_FILE_NOT_FOUND)
+ return IPC_STATE__PATH_NOT_FOUND;
+
+ trace2_data_intmax("ipc-debug", NULL, "getstate/waitpipe/gle",
+ (intmax_t)GetLastError());
+
+ return IPC_STATE__OTHER_ERROR;
+}
+
+enum ipc_active_state ipc_get_active_state(const char *path)
+{
+ wchar_t pipe_path[MAX_PATH];
+
+ if (initialize_pipe_name(path, pipe_path, ARRAY_SIZE(pipe_path)) < 0)
+ return IPC_STATE__INVALID_PATH;
+
+ return get_active_state(pipe_path);
+}
+
+#define WAIT_STEP_MS (50)
+
+static enum ipc_active_state connect_to_server(
+ const wchar_t *wpath,
+ DWORD timeout_ms,
+ const struct ipc_client_connect_options *options,
+ int *pfd)
+{
+ DWORD t_start_ms, t_waited_ms;
+ DWORD step_ms;
+ HANDLE hPipe = INVALID_HANDLE_VALUE;
+ DWORD mode = PIPE_READMODE_BYTE;
+ DWORD gle;
+
+ *pfd = -1;
+
+ for (;;) {
+ hPipe = CreateFileW(wpath, GENERIC_READ | GENERIC_WRITE,
+ 0, NULL, OPEN_EXISTING, 0, NULL);
+ if (hPipe != INVALID_HANDLE_VALUE)
+ break;
+
+ gle = GetLastError();
+
+ switch (gle) {
+ case ERROR_FILE_NOT_FOUND:
+ if (!options->wait_if_not_found)
+ return IPC_STATE__PATH_NOT_FOUND;
+ if (!timeout_ms)
+ return IPC_STATE__PATH_NOT_FOUND;
+
+ step_ms = (timeout_ms < WAIT_STEP_MS) ?
+ timeout_ms : WAIT_STEP_MS;
+ sleep_millisec(step_ms);
+
+ timeout_ms -= step_ms;
+ break; /* try again */
+
+ case ERROR_PIPE_BUSY:
+ if (!options->wait_if_busy)
+ return IPC_STATE__NOT_LISTENING;
+ if (!timeout_ms)
+ return IPC_STATE__NOT_LISTENING;
+
+ t_start_ms = (DWORD)(getnanotime() / 1000000);
+
+ if (!WaitNamedPipeW(wpath, timeout_ms)) {
+ DWORD gleWait = GetLastError();
+
+ if (gleWait == ERROR_SEM_TIMEOUT)
+ return IPC_STATE__NOT_LISTENING;
+
+ trace2_data_intmax("ipc-debug", NULL,
+ "connect/waitpipe/gle",
+ (intmax_t)gleWait);
+
+ return IPC_STATE__OTHER_ERROR;
+ }
+
+ /*
+ * A pipe server instance became available.
+ * Race other client processes to connect to
+ * it.
+ *
+ * But first decrement our overall timeout so
+ * that we don't starve if we keep losing the
+ * race. But also guard against special
+ * NPMWAIT_ values (0 and -1).
+ */
+ t_waited_ms = (DWORD)(getnanotime() / 1000000) - t_start_ms;
+ if (t_waited_ms < timeout_ms)
+ timeout_ms -= t_waited_ms;
+ else
+ timeout_ms = 1;
+ break; /* try again */
+
+ default:
+ trace2_data_intmax("ipc-debug", NULL,
+ "connect/createfile/gle",
+ (intmax_t)gle);
+
+ return IPC_STATE__OTHER_ERROR;
+ }
+ }
+
+ if (!SetNamedPipeHandleState(hPipe, &mode, NULL, NULL)) {
+ gle = GetLastError();
+ trace2_data_intmax("ipc-debug", NULL,
+ "connect/setpipestate/gle",
+ (intmax_t)gle);
+
+ CloseHandle(hPipe);
+ return IPC_STATE__OTHER_ERROR;
+ }
+
+ *pfd = _open_osfhandle((intptr_t)hPipe, O_RDWR|O_BINARY);
+ if (*pfd < 0) {
+ gle = GetLastError();
+ trace2_data_intmax("ipc-debug", NULL,
+ "connect/openosfhandle/gle",
+ (intmax_t)gle);
+
+ CloseHandle(hPipe);
+ return IPC_STATE__OTHER_ERROR;
+ }
+
+ /* fd now owns hPipe */
+
+ return IPC_STATE__LISTENING;
+}
+
+/*
+ * The default connection timeout for Windows clients.
+ *
+ * This is not currently part of the ipc_ API (nor the config settings)
+ * because of differences between Windows and other platforms.
+ *
+ * This value was chosen at random.
+ */
+#define WINDOWS_CONNECTION_TIMEOUT_MS (30000)
+
+enum ipc_active_state ipc_client_try_connect(
+ const char *path,
+ const struct ipc_client_connect_options *options,
+ struct ipc_client_connection **p_connection)
+{
+ wchar_t wpath[MAX_PATH];
+ enum ipc_active_state state = IPC_STATE__OTHER_ERROR;
+ int fd = -1;
+
+ *p_connection = NULL;
+
+ trace2_region_enter("ipc-client", "try-connect", NULL);
+ trace2_data_string("ipc-client", NULL, "try-connect/path", path);
+
+ if (initialize_pipe_name(path, wpath, ARRAY_SIZE(wpath)) < 0)
+ state = IPC_STATE__INVALID_PATH;
+ else
+ state = connect_to_server(wpath, WINDOWS_CONNECTION_TIMEOUT_MS,
+ options, &fd);
+
+ trace2_data_intmax("ipc-client", NULL, "try-connect/state",
+ (intmax_t)state);
+ trace2_region_leave("ipc-client", "try-connect", NULL);
+
+ if (state == IPC_STATE__LISTENING) {
+ (*p_connection) = xcalloc(1, sizeof(struct ipc_client_connection));
+ (*p_connection)->fd = fd;
+ }
+
+ return state;
+}
+
+void ipc_client_close_connection(struct ipc_client_connection *connection)
+{
+ if (!connection)
+ return;
+
+ if (connection->fd != -1)
+ close(connection->fd);
+
+ free(connection);
+}
+
+int ipc_client_send_command_to_connection(
+ struct ipc_client_connection *connection,
+ const char *message, size_t message_len,
+ struct strbuf *answer)
+{
+ int ret = 0;
+
+ strbuf_setlen(answer, 0);
+
+ trace2_region_enter("ipc-client", "send-command", NULL);
+
+ if (write_packetized_from_buf_no_flush(message, message_len,
+ connection->fd) < 0 ||
+ packet_flush_gently(connection->fd) < 0) {
+ ret = error(_("could not send IPC command"));
+ goto done;
+ }
+
+ FlushFileBuffers((HANDLE)_get_osfhandle(connection->fd));
+
+ if (read_packetized_to_strbuf(
+ connection->fd, answer,
+ PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR) < 0) {
+ ret = error(_("could not read IPC response"));
+ goto done;
+ }
+
+done:
+ trace2_region_leave("ipc-client", "send-command", NULL);
+ return ret;
+}
+
+int ipc_client_send_command(const char *path,
+ const struct ipc_client_connect_options *options,
+ const char *message, size_t message_len,
+ struct strbuf *response)
+{
+ int ret = -1;
+ enum ipc_active_state state;
+ struct ipc_client_connection *connection = NULL;
+
+ state = ipc_client_try_connect(path, options, &connection);
+
+ if (state != IPC_STATE__LISTENING)
+ return ret;
+
+ ret = ipc_client_send_command_to_connection(connection,
+ message, message_len,
+ response);
+
+ ipc_client_close_connection(connection);
+
+ return ret;
+}
+
+/*
+ * Duplicate the given pipe handle and wrap it in a file descriptor so
+ * that we can use pkt-line on it.
+ */
+static int dup_fd_from_pipe(const HANDLE pipe)
+{
+ HANDLE process = GetCurrentProcess();
+ HANDLE handle;
+ int fd;
+
+ if (!DuplicateHandle(process, pipe, process, &handle, 0, FALSE,
+ DUPLICATE_SAME_ACCESS)) {
+ errno = err_win_to_posix(GetLastError());
+ return -1;
+ }
+
+ fd = _open_osfhandle((intptr_t)handle, O_RDWR|O_BINARY);
+ if (fd < 0) {
+ errno = err_win_to_posix(GetLastError());
+ CloseHandle(handle);
+ return -1;
+ }
+
+ /*
+ * `handle` is now owned by `fd` and will be automatically closed
+ * when the descriptor is closed.
+ */
+
+ return fd;
+}
+
+/*
+ * Magic numbers used to annotate callback instance data.
+ * These are used to help guard against accidentally passing the
+ * wrong instance data across multiple levels of callbacks (which
+ * is easy to do if there are `void*` arguments).
+ */
+enum magic {
+ MAGIC_SERVER_REPLY_DATA,
+ MAGIC_SERVER_THREAD_DATA,
+ MAGIC_SERVER_DATA,
+};
+
+struct ipc_server_reply_data {
+ enum magic magic;
+ int fd;
+ struct ipc_server_thread_data *server_thread_data;
+};
+
+struct ipc_server_thread_data {
+ enum magic magic;
+ struct ipc_server_thread_data *next_thread;
+ struct ipc_server_data *server_data;
+ pthread_t pthread_id;
+ HANDLE hPipe;
+};
+
+/*
+ * On Windows, the conceptual "ipc-server" is implemented as a pool of
+ * n idential/peer "server-thread" threads. That is, there is no
+ * hierarchy of threads; and therefore no controller thread managing
+ * the pool. Each thread has an independent handle to the named pipe,
+ * receives incoming connections, processes the client, and re-uses
+ * the pipe for the next client connection.
+ *
+ * Therefore, the "ipc-server" only needs to maintain a list of the
+ * spawned threads for eventual "join" purposes.
+ *
+ * A single "stop-event" is visible to all of the server threads to
+ * tell them to shutdown (when idle).
+ */
+struct ipc_server_data {
+ enum magic magic;
+ ipc_server_application_cb *application_cb;
+ void *application_data;
+ struct strbuf buf_path;
+ wchar_t wpath[MAX_PATH];
+
+ HANDLE hEventStopRequested;
+ struct ipc_server_thread_data *thread_list;
+ int is_stopped;
+};
+
+enum connect_result {
+ CR_CONNECTED = 0,
+ CR_CONNECT_PENDING,
+ CR_CONNECT_ERROR,
+ CR_WAIT_ERROR,
+ CR_SHUTDOWN,
+};
+
+static enum connect_result queue_overlapped_connect(
+ struct ipc_server_thread_data *server_thread_data,
+ OVERLAPPED *lpo)
+{
+ if (ConnectNamedPipe(server_thread_data->hPipe, lpo))
+ goto failed;
+
+ switch (GetLastError()) {
+ case ERROR_IO_PENDING:
+ return CR_CONNECT_PENDING;
+
+ case ERROR_PIPE_CONNECTED:
+ SetEvent(lpo->hEvent);
+ return CR_CONNECTED;
+
+ default:
+ break;
+ }
+
+failed:
+ error(_("ConnectNamedPipe failed for '%s' (%lu)"),
+ server_thread_data->server_data->buf_path.buf,
+ GetLastError());
+ return CR_CONNECT_ERROR;
+}
+
+/*
+ * Use Windows Overlapped IO to wait for a connection or for our event
+ * to be signalled.
+ */
+static enum connect_result wait_for_connection(
+ struct ipc_server_thread_data *server_thread_data,
+ OVERLAPPED *lpo)
+{
+ enum connect_result r;
+ HANDLE waitHandles[2];
+ DWORD dwWaitResult;
+
+ r = queue_overlapped_connect(server_thread_data, lpo);
+ if (r != CR_CONNECT_PENDING)
+ return r;
+
+ waitHandles[0] = server_thread_data->server_data->hEventStopRequested;
+ waitHandles[1] = lpo->hEvent;
+
+ dwWaitResult = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE);
+ switch (dwWaitResult) {
+ case WAIT_OBJECT_0 + 0:
+ return CR_SHUTDOWN;
+
+ case WAIT_OBJECT_0 + 1:
+ ResetEvent(lpo->hEvent);
+ return CR_CONNECTED;
+
+ default:
+ return CR_WAIT_ERROR;
+ }
+}
+
+/*
+ * Forward declare our reply callback function so that any compiler
+ * errors are reported when we actually define the function (in addition
+ * to any errors reported when we try to pass this callback function as
+ * a parameter in a function call). The former are easier to understand.
+ */
+static ipc_server_reply_cb do_io_reply_callback;
+
+/*
+ * Relay application's response message to the client process.
+ * (We do not flush at this point because we allow the caller
+ * to chunk data to the client thru us.)
+ */
+static int do_io_reply_callback(struct ipc_server_reply_data *reply_data,
+ const char *response, size_t response_len)
+{
+ if (reply_data->magic != MAGIC_SERVER_REPLY_DATA)
+ BUG("reply_cb called with wrong instance data");
+
+ return write_packetized_from_buf_no_flush(response, response_len,
+ reply_data->fd);
+}
+
+/*
+ * Receive the request/command from the client and pass it to the
+ * registered request-callback. The request-callback will compose
+ * a response and call our reply-callback to send it to the client.
+ *
+ * Simple-IPC only contains one round trip, so we flush and close
+ * here after the response.
+ */
+static int do_io(struct ipc_server_thread_data *server_thread_data)
+{
+ struct strbuf buf = STRBUF_INIT;
+ struct ipc_server_reply_data reply_data;
+ int ret = 0;
+
+ reply_data.magic = MAGIC_SERVER_REPLY_DATA;
+ reply_data.server_thread_data = server_thread_data;
+
+ reply_data.fd = dup_fd_from_pipe(server_thread_data->hPipe);
+ if (reply_data.fd < 0)
+ return error(_("could not create fd from pipe for '%s'"),
+ server_thread_data->server_data->buf_path.buf);
+
+ ret = read_packetized_to_strbuf(
+ reply_data.fd, &buf,
+ PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR);
+ if (ret >= 0) {
+ ret = server_thread_data->server_data->application_cb(
+ server_thread_data->server_data->application_data,
+ buf.buf, buf.len, do_io_reply_callback, &reply_data);
+
+ packet_flush_gently(reply_data.fd);
+
+ FlushFileBuffers((HANDLE)_get_osfhandle((reply_data.fd)));
+ }
+ else {
+ /*
+ * The client probably disconnected/shutdown before it
+ * could send a well-formed message. Ignore it.
+ */
+ }
+
+ strbuf_release(&buf);
+ close(reply_data.fd);
+
+ return ret;
+}
+
+/*
+ * Handle IPC request and response with this connected client. And reset
+ * the pipe to prepare for the next client.
+ */
+static int use_connection(struct ipc_server_thread_data *server_thread_data)
+{
+ int ret;
+
+ ret = do_io(server_thread_data);
+
+ FlushFileBuffers(server_thread_data->hPipe);
+ DisconnectNamedPipe(server_thread_data->hPipe);
+
+ return ret;
+}
+
+/*
+ * Thread proc for an IPC server worker thread. It handles a series of
+ * connections from clients. It cleans and reuses the hPipe between each
+ * client.
+ */
+static void *server_thread_proc(void *_server_thread_data)
+{
+ struct ipc_server_thread_data *server_thread_data = _server_thread_data;
+ HANDLE hEventConnected = INVALID_HANDLE_VALUE;
+ OVERLAPPED oConnect;
+ enum connect_result cr;
+ int ret;
+
+ assert(server_thread_data->hPipe != INVALID_HANDLE_VALUE);
+
+ trace2_thread_start("ipc-server");
+ trace2_data_string("ipc-server", NULL, "pipe",
+ server_thread_data->server_data->buf_path.buf);
+
+ hEventConnected = CreateEventW(NULL, TRUE, FALSE, NULL);
+
+ memset(&oConnect, 0, sizeof(oConnect));
+ oConnect.hEvent = hEventConnected;
+
+ for (;;) {
+ cr = wait_for_connection(server_thread_data, &oConnect);
+
+ switch (cr) {
+ case CR_SHUTDOWN:
+ goto finished;
+
+ case CR_CONNECTED:
+ ret = use_connection(server_thread_data);
+ if (ret == SIMPLE_IPC_QUIT) {
+ ipc_server_stop_async(
+ server_thread_data->server_data);
+ goto finished;
+ }
+ if (ret > 0) {
+ /*
+ * Ignore (transient) IO errors with this
+ * client and reset for the next client.
+ */
+ }
+ break;
+
+ case CR_CONNECT_PENDING:
+ /* By construction, this should not happen. */
+ BUG("ipc-server[%s]: unexpeced CR_CONNECT_PENDING",
+ server_thread_data->server_data->buf_path.buf);
+
+ case CR_CONNECT_ERROR:
+ case CR_WAIT_ERROR:
+ /*
+ * Ignore these theoretical errors.
+ */
+ DisconnectNamedPipe(server_thread_data->hPipe);
+ break;
+
+ default:
+ BUG("unandled case after wait_for_connection");
+ }
+ }
+
+finished:
+ CloseHandle(server_thread_data->hPipe);
+ CloseHandle(hEventConnected);
+
+ trace2_thread_exit();
+ return NULL;
+}
+
+/*
+ * We need to build a Windows "SECURITY_ATTRIBUTES" object and use it
+ * to apply an ACL when we create the initial instance of the Named
+ * Pipe. The construction is somewhat involved and consists of
+ * several sequential steps and intermediate objects.
+ *
+ * We use this structure to hold these intermediate pointers so that
+ * we can free them as a group. (It is unclear from the docs whether
+ * some of these intermediate pointers can be freed before we are
+ * finished using the "lpSA" member.)
+ */
+struct my_sa_data
+{
+ PSID pEveryoneSID;
+ PACL pACL;
+ PSECURITY_DESCRIPTOR pSD;
+ LPSECURITY_ATTRIBUTES lpSA;
+};
+
+static void init_sa(struct my_sa_data *d)
+{
+ memset(d, 0, sizeof(*d));
+}
+
+static void release_sa(struct my_sa_data *d)
+{
+ if (d->pEveryoneSID)
+ FreeSid(d->pEveryoneSID);
+ if (d->pACL)
+ LocalFree(d->pACL);
+ if (d->pSD)
+ LocalFree(d->pSD);
+ if (d->lpSA)
+ LocalFree(d->lpSA);
+
+ memset(d, 0, sizeof(*d));
+}
+
+/*
+ * Create SECURITY_ATTRIBUTES to apply to the initial named pipe. The
+ * creator of the first server instance gets to set the ACLs on it.
+ *
+ * We allow the well-known group `EVERYONE` to have read+write access
+ * to the named pipe so that clients can send queries to the daemon
+ * and receive the response.
+ *
+ * Normally, this is not necessary since the daemon is usually
+ * automatically started by a foreground command like `git status`,
+ * but in those cases where an elevated Git command started the daemon
+ * (such that the daemon itself runs with elevation), we need to add
+ * the ACL so that non-elevated commands can write to it.
+ *
+ * The following document was helpful:
+ * https://docs.microsoft.com/en-us/windows/win32/secauthz/creating-a-security-descriptor-for-a-new-object-in-c--
+ *
+ * Returns d->lpSA set to a SA or NULL.
+ */
+static LPSECURITY_ATTRIBUTES get_sa(struct my_sa_data *d)
+{
+ SID_IDENTIFIER_AUTHORITY sid_auth_world = SECURITY_WORLD_SID_AUTHORITY;
+#define NR_EA (1)
+ EXPLICIT_ACCESS ea[NR_EA];
+ DWORD dwResult;
+
+ if (!AllocateAndInitializeSid(&sid_auth_world, 1,
+ SECURITY_WORLD_RID, 0,0,0,0,0,0,0,
+ &d->pEveryoneSID)) {
+ DWORD gle = GetLastError();
+ trace2_data_intmax("ipc-debug", NULL, "alloc-world-sid/gle",
+ (intmax_t)gle);
+ goto fail;
+ }
+
+ memset(ea, 0, NR_EA * sizeof(EXPLICIT_ACCESS));
+
+ ea[0].grfAccessPermissions = GENERIC_READ | GENERIC_WRITE;
+ ea[0].grfAccessMode = SET_ACCESS;
+ ea[0].grfInheritance = NO_INHERITANCE;
+ ea[0].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
+ ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+ ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
+ ea[0].Trustee.ptstrName = (LPTSTR)d->pEveryoneSID;
+
+ dwResult = SetEntriesInAcl(NR_EA, ea, NULL, &d->pACL);
+ if (dwResult != ERROR_SUCCESS) {
+ DWORD gle = GetLastError();
+ trace2_data_intmax("ipc-debug", NULL, "set-acl-entry/gle",
+ (intmax_t)gle);
+ trace2_data_intmax("ipc-debug", NULL, "set-acl-entry/dw",
+ (intmax_t)dwResult);
+ goto fail;
+ }
+
+ d->pSD = (PSECURITY_DESCRIPTOR)LocalAlloc(
+ LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
+ if (!InitializeSecurityDescriptor(d->pSD, SECURITY_DESCRIPTOR_REVISION)) {
+ DWORD gle = GetLastError();
+ trace2_data_intmax("ipc-debug", NULL, "init-sd/gle", (intmax_t)gle);
+ goto fail;
+ }
+
+ if (!SetSecurityDescriptorDacl(d->pSD, TRUE, d->pACL, FALSE)) {
+ DWORD gle = GetLastError();
+ trace2_data_intmax("ipc-debug", NULL, "set-sd-dacl/gle", (intmax_t)gle);
+ goto fail;
+ }
+
+ d->lpSA = (LPSECURITY_ATTRIBUTES)LocalAlloc(LPTR, sizeof(SECURITY_ATTRIBUTES));
+ d->lpSA->nLength = sizeof(SECURITY_ATTRIBUTES);
+ d->lpSA->lpSecurityDescriptor = d->pSD;
+ d->lpSA->bInheritHandle = FALSE;
+
+ return d->lpSA;
+
+fail:
+ release_sa(d);
+ return NULL;
+}
+
+static HANDLE create_new_pipe(wchar_t *wpath, int is_first)
+{
+ HANDLE hPipe;
+ DWORD dwOpenMode, dwPipeMode;
+ struct my_sa_data my_sa_data;
+
+ init_sa(&my_sa_data);
+
+ dwOpenMode = PIPE_ACCESS_INBOUND | PIPE_ACCESS_OUTBOUND |
+ FILE_FLAG_OVERLAPPED;
+
+ dwPipeMode = PIPE_TYPE_MESSAGE | PIPE_READMODE_BYTE | PIPE_WAIT |
+ PIPE_REJECT_REMOTE_CLIENTS;
+
+ if (is_first) {
+ dwOpenMode |= FILE_FLAG_FIRST_PIPE_INSTANCE;
+
+ /*
+ * On Windows, the first server pipe instance gets to
+ * set the ACL / Security Attributes on the named
+ * pipe; subsequent instances inherit and cannot
+ * change them.
+ */
+ get_sa(&my_sa_data);
+ }
+
+ hPipe = CreateNamedPipeW(wpath, dwOpenMode, dwPipeMode,
+ PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0,
+ my_sa_data.lpSA);
+
+ release_sa(&my_sa_data);
+
+ return hPipe;
+}
+
+int ipc_server_run_async(struct ipc_server_data **returned_server_data,
+ const char *path, const struct ipc_server_opts *opts,
+ ipc_server_application_cb *application_cb,
+ void *application_data)
+{
+ struct ipc_server_data *server_data;
+ wchar_t wpath[MAX_PATH];
+ HANDLE hPipeFirst = INVALID_HANDLE_VALUE;
+ int k;
+ int ret = 0;
+ int nr_threads = opts->nr_threads;
+
+ *returned_server_data = NULL;
+
+ ret = initialize_pipe_name(path, wpath, ARRAY_SIZE(wpath));
+ if (ret < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ hPipeFirst = create_new_pipe(wpath, 1);
+ if (hPipeFirst == INVALID_HANDLE_VALUE) {
+ errno = EADDRINUSE;
+ return -2;
+ }
+
+ server_data = xcalloc(1, sizeof(*server_data));
+ server_data->magic = MAGIC_SERVER_DATA;
+ server_data->application_cb = application_cb;
+ server_data->application_data = application_data;
+ server_data->hEventStopRequested = CreateEvent(NULL, TRUE, FALSE, NULL);
+ strbuf_init(&server_data->buf_path, 0);
+ strbuf_addstr(&server_data->buf_path, path);
+ wcscpy(server_data->wpath, wpath);
+
+ if (nr_threads < 1)
+ nr_threads = 1;
+
+ for (k = 0; k < nr_threads; k++) {
+ struct ipc_server_thread_data *std;
+
+ std = xcalloc(1, sizeof(*std));
+ std->magic = MAGIC_SERVER_THREAD_DATA;
+ std->server_data = server_data;
+ std->hPipe = INVALID_HANDLE_VALUE;
+
+ std->hPipe = (k == 0)
+ ? hPipeFirst
+ : create_new_pipe(server_data->wpath, 0);
+
+ if (std->hPipe == INVALID_HANDLE_VALUE) {
+ /*
+ * If we've reached a pipe instance limit for
+ * this path, just use fewer threads.
+ */
+ free(std);
+ break;
+ }
+
+ if (pthread_create(&std->pthread_id, NULL,
+ server_thread_proc, std)) {
+ /*
+ * Likewise, if we're out of threads, just use
+ * fewer threads than requested.
+ *
+ * However, we just give up if we can't even get
+ * one thread. This should not happen.
+ */
+ if (k == 0)
+ die(_("could not start thread[0] for '%s'"),
+ path);
+
+ CloseHandle(std->hPipe);
+ free(std);
+ break;
+ }
+
+ std->next_thread = server_data->thread_list;
+ server_data->thread_list = std;
+ }
+
+ *returned_server_data = server_data;
+ return 0;
+}
+
+int ipc_server_stop_async(struct ipc_server_data *server_data)
+{
+ if (!server_data)
+ return 0;
+
+ /*
+ * Gently tell all of the ipc_server threads to shutdown.
+ * This will be seen the next time they are idle (and waiting
+ * for a connection).
+ *
+ * We DO NOT attempt to force them to drop an active connection.
+ */
+ SetEvent(server_data->hEventStopRequested);
+ return 0;
+}
+
+int ipc_server_await(struct ipc_server_data *server_data)
+{
+ DWORD dwWaitResult;
+
+ if (!server_data)
+ return 0;
+
+ dwWaitResult = WaitForSingleObject(server_data->hEventStopRequested, INFINITE);
+ if (dwWaitResult != WAIT_OBJECT_0)
+ return error(_("wait for hEvent failed for '%s'"),
+ server_data->buf_path.buf);
+
+ while (server_data->thread_list) {
+ struct ipc_server_thread_data *std = server_data->thread_list;
+
+ pthread_join(std->pthread_id, NULL);
+
+ server_data->thread_list = std->next_thread;
+ free(std);
+ }
+
+ server_data->is_stopped = 1;
+
+ return 0;
+}
+
+void ipc_server_free(struct ipc_server_data *server_data)
+{
+ if (!server_data)
+ return;
+
+ if (!server_data->is_stopped)
+ BUG("cannot free ipc-server while running for '%s'",
+ server_data->buf_path.buf);
+
+ strbuf_release(&server_data->buf_path);
+
+ if (server_data->hEventStopRequested != INVALID_HANDLE_VALUE)
+ CloseHandle(server_data->hEventStopRequested);
+
+ while (server_data->thread_list) {
+ struct ipc_server_thread_data *std = server_data->thread_list;
+
+ server_data->thread_list = std->next_thread;
+ free(std);
+ }
+
+ free(server_data);
+}
diff --git a/compat/snprintf.c b/compat/snprintf.c
index 42ea1ac110..0b11688537 100644
--- a/compat/snprintf.c
+++ b/compat/snprintf.c
@@ -9,7 +9,7 @@
* always have room for a trailing NUL byte.
*/
#ifndef SNPRINTF_SIZE_CORR
-#if defined(WIN32) && (!defined(__GNUC__) || __GNUC__ < 4)
+#if defined(WIN32) && (!defined(__GNUC__) || __GNUC__ < 4) && (!defined(_MSC_VER) || _MSC_VER < 1900)
#define SNPRINTF_SIZE_CORR 1
#else
#define SNPRINTF_SIZE_CORR 0
diff --git a/compat/stat.c b/compat/stat.c
new file mode 100644
index 0000000000..a2d3931cb7
--- /dev/null
+++ b/compat/stat.c
@@ -0,0 +1,48 @@
+#define _POSIX_C_SOURCE 200112L
+#include <sys/stat.h> /* *stat, S_IS* */
+#include <sys/types.h> /* mode_t */
+
+static inline mode_t mode_native_to_git(mode_t native_mode)
+{
+ mode_t perm_bits = native_mode & 07777;
+ if (S_ISREG(native_mode))
+ return 0100000 | perm_bits;
+ if (S_ISDIR(native_mode))
+ return 0040000 | perm_bits;
+ if (S_ISLNK(native_mode))
+ return 0120000 | perm_bits;
+ if (S_ISBLK(native_mode))
+ return 0060000 | perm_bits;
+ if (S_ISCHR(native_mode))
+ return 0020000 | perm_bits;
+ if (S_ISFIFO(native_mode))
+ return 0010000 | perm_bits;
+ if (S_ISSOCK(native_mode))
+ return 0140000 | perm_bits;
+ /* Non-standard type bits were given. */
+ return perm_bits;
+}
+
+int git_stat(const char *path, struct stat *buf)
+{
+ int rc = stat(path, buf);
+ if (rc == 0)
+ buf->st_mode = mode_native_to_git(buf->st_mode);
+ return rc;
+}
+
+int git_fstat(int fd, struct stat *buf)
+{
+ int rc = fstat(fd, buf);
+ if (rc == 0)
+ buf->st_mode = mode_native_to_git(buf->st_mode);
+ return rc;
+}
+
+int git_lstat(const char *path, struct stat *buf)
+{
+ int rc = lstat(path, buf);
+ if (rc == 0)
+ buf->st_mode = mode_native_to_git(buf->st_mode);
+ return rc;
+}
diff --git a/compat/strdup.c b/compat/strdup.c
new file mode 100644
index 0000000000..f3fb978eb3
--- /dev/null
+++ b/compat/strdup.c
@@ -0,0 +1,11 @@
+#include "../git-compat-util.h"
+
+char *gitstrdup(const char *s1)
+{
+ size_t len = strlen(s1) + 1;
+ char *s2 = malloc(len);
+
+ if (s2)
+ memcpy(s2, s1, len);
+ return s2;
+}
diff --git a/compat/strtok_r.c b/compat/strtok_r.c
deleted file mode 100644
index 7b5d568a96..0000000000
--- a/compat/strtok_r.c
+++ /dev/null
@@ -1,61 +0,0 @@
-/* Reentrant string tokenizer. Generic version.
- Copyright (C) 1991,1996-1999,2001,2004 Free Software Foundation, Inc.
- This file is part of the GNU C Library.
-
- The GNU C Library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- The GNU C Library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with the GNU C Library; if not, write to the Free
- Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
- 02111-1307 USA. */
-
-#include "../git-compat-util.h"
-
-/* Parse S into tokens separated by characters in DELIM.
- If S is NULL, the saved pointer in SAVE_PTR is used as
- the next starting point. For example:
- char s[] = "-abc-=-def";
- char *sp;
- x = strtok_r(s, "-", &sp); // x = "abc", sp = "=-def"
- x = strtok_r(NULL, "-=", &sp); // x = "def", sp = NULL
- x = strtok_r(NULL, "=", &sp); // x = NULL
- // s = "abc\0-def\0"
-*/
-char *
-gitstrtok_r (char *s, const char *delim, char **save_ptr)
-{
- char *token;
-
- if (s == NULL)
- s = *save_ptr;
-
- /* Scan leading delimiters. */
- s += strspn (s, delim);
- if (*s == '\0')
- {
- *save_ptr = s;
- return NULL;
- }
-
- /* Find the end of the token. */
- token = s;
- s = strpbrk (token, delim);
- if (s == NULL)
- /* This token finishes the string. */
- *save_ptr = token + strlen (token);
- else
- {
- /* Terminate the token and make *SAVE_PTR point past it. */
- *s = '\0';
- *save_ptr = s + 1;
- }
- return token;
-}
diff --git a/compat/stub/procinfo.c b/compat/stub/procinfo.c
new file mode 100644
index 0000000000..12c0a23c9e
--- /dev/null
+++ b/compat/stub/procinfo.c
@@ -0,0 +1,11 @@
+#include "git-compat-util.h"
+
+#include "trace2.h"
+
+/*
+ * Stub. See sample implementations in compat/linux/procinfo.c and
+ * compat/win32/trace2_win32_process_info.c.
+ */
+void trace2_collect_process_info(enum trace2_process_info_reason reason)
+{
+}
diff --git a/compat/terminal.c b/compat/terminal.c
index 9b5e3d1bb8..5b903e7c7e 100644
--- a/compat/terminal.c
+++ b/compat/terminal.c
@@ -2,10 +2,11 @@
#include "compat/terminal.h"
#include "sigchain.h"
#include "strbuf.h"
+#include "run-command.h"
+#include "string-list.h"
+#include "hashmap.h"
-#if defined(HAVE_DEV_TTY) || defined(WIN32)
-
-static void restore_term(void);
+#if defined(HAVE_DEV_TTY) || defined(GIT_WINDOWS_NATIVE)
static void restore_term_on_signal(int sig)
{
@@ -22,7 +23,7 @@ static void restore_term_on_signal(int sig)
static int term_fd = -1;
static struct termios old_term;
-static void restore_term(void)
+void restore_term(void)
{
if (term_fd < 0)
return;
@@ -32,18 +33,25 @@ static void restore_term(void)
term_fd = -1;
}
-static int disable_echo(void)
+int save_term(int full_duplex)
+{
+ if (term_fd < 0)
+ term_fd = open("/dev/tty", O_RDWR);
+
+ return (term_fd < 0) ? -1 : tcgetattr(term_fd, &old_term);
+}
+
+static int disable_bits(tcflag_t bits)
{
struct termios t;
- term_fd = open("/dev/tty", O_RDWR);
- if (tcgetattr(term_fd, &t) < 0)
+ if (save_term(0) < 0)
goto error;
- old_term = t;
+ t = old_term;
sigchain_push_common(restore_term_on_signal);
- t.c_lflag &= ~ECHO;
+ t.c_lflag &= ~bits;
if (!tcsetattr(term_fd, TCSAFLUSH, &t))
return 0;
@@ -53,36 +61,124 @@ error:
return -1;
}
-#elif defined(WIN32)
+static int disable_echo(void)
+{
+ return disable_bits(ECHO);
+}
+
+static int enable_non_canonical(void)
+{
+ return disable_bits(ICANON | ECHO);
+}
+
+#elif defined(GIT_WINDOWS_NATIVE)
#define INPUT_PATH "CONIN$"
#define OUTPUT_PATH "CONOUT$"
#define FORCE_TEXT "t"
+static int use_stty = 1;
+static struct string_list stty_restore = STRING_LIST_INIT_DUP;
static HANDLE hconin = INVALID_HANDLE_VALUE;
-static DWORD cmode;
+static HANDLE hconout = INVALID_HANDLE_VALUE;
+static DWORD cmode_in, cmode_out;
-static void restore_term(void)
+void restore_term(void)
{
+ if (use_stty) {
+ int i;
+ struct child_process cp = CHILD_PROCESS_INIT;
+
+ if (stty_restore.nr == 0)
+ return;
+
+ strvec_push(&cp.args, "stty");
+ for (i = 0; i < stty_restore.nr; i++)
+ strvec_push(&cp.args, stty_restore.items[i].string);
+ run_command(&cp);
+ string_list_clear(&stty_restore, 0);
+ return;
+ }
+
if (hconin == INVALID_HANDLE_VALUE)
return;
- SetConsoleMode(hconin, cmode);
+ SetConsoleMode(hconin, cmode_in);
CloseHandle(hconin);
- hconin = INVALID_HANDLE_VALUE;
+ if (cmode_out) {
+ assert(hconout != INVALID_HANDLE_VALUE);
+ SetConsoleMode(hconout, cmode_out);
+ CloseHandle(hconout);
+ }
+
+ hconin = hconout = INVALID_HANDLE_VALUE;
}
-static int disable_echo(void)
+int save_term(int full_duplex)
{
- hconin = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE,
+ hconin = CreateFileA("CONIN$", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
if (hconin == INVALID_HANDLE_VALUE)
return -1;
- GetConsoleMode(hconin, &cmode);
+ if (full_duplex) {
+ hconout = CreateFileA("CONOUT$", GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ if (hconout == INVALID_HANDLE_VALUE)
+ goto error;
+
+ GetConsoleMode(hconout, &cmode_out);
+ }
+
+ GetConsoleMode(hconin, &cmode_in);
+ use_stty = 0;
+ return 0;
+error:
+ CloseHandle(hconin);
+ hconin = INVALID_HANDLE_VALUE;
+ return -1;
+}
+
+static int disable_bits(DWORD bits)
+{
+ if (use_stty) {
+ struct child_process cp = CHILD_PROCESS_INIT;
+
+ strvec_push(&cp.args, "stty");
+
+ if (bits & ENABLE_LINE_INPUT) {
+ string_list_append(&stty_restore, "icanon");
+ strvec_push(&cp.args, "-icanon");
+ }
+
+ if (bits & ENABLE_ECHO_INPUT) {
+ string_list_append(&stty_restore, "echo");
+ strvec_push(&cp.args, "-echo");
+ }
+
+ if (bits & ENABLE_PROCESSED_INPUT) {
+ string_list_append(&stty_restore, "-ignbrk");
+ string_list_append(&stty_restore, "intr");
+ string_list_append(&stty_restore, "^c");
+ strvec_push(&cp.args, "ignbrk");
+ strvec_push(&cp.args, "intr");
+ strvec_push(&cp.args, "");
+ }
+
+ if (run_command(&cp) == 0)
+ return 0;
+
+ /* `stty` could not be executed; access the Console directly */
+ use_stty = 0;
+ }
+
+ if (save_term(0) < 0)
+ return -1;
+
sigchain_push_common(restore_term_on_signal);
- if (!SetConsoleMode(hconin, cmode & (~ENABLE_ECHO_INPUT))) {
+ if (!SetConsoleMode(hconin, cmode_in & ~bits)) {
CloseHandle(hconin);
hconin = INVALID_HANDLE_VALUE;
return -1;
@@ -91,6 +187,47 @@ static int disable_echo(void)
return 0;
}
+static int disable_echo(void)
+{
+ return disable_bits(ENABLE_ECHO_INPUT);
+}
+
+static int enable_non_canonical(void)
+{
+ return disable_bits(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
+}
+
+/*
+ * Override `getchar()`, as the default implementation does not use
+ * `ReadFile()`.
+ *
+ * This poses a problem when we want to see whether the standard
+ * input has more characters, as the default of Git for Windows is to start the
+ * Bash in a MinTTY, which uses a named pipe to emulate a pty, in which case
+ * our `poll()` emulation calls `PeekNamedPipe()`, which seems to require
+ * `ReadFile()` to be called first to work properly (it only reports 0
+ * available bytes, otherwise).
+ *
+ * So let's just override `getchar()` with a version backed by `ReadFile()` and
+ * go our merry ways from here.
+ */
+static int mingw_getchar(void)
+{
+ DWORD read = 0;
+ unsigned char ch;
+
+ if (!ReadFile(GetStdHandle(STD_INPUT_HANDLE), &ch, 1, &read, NULL))
+ return EOF;
+
+ if (!read) {
+ error("Unexpected 0 read");
+ return EOF;
+ }
+
+ return ch;
+}
+#define getchar mingw_getchar
+
#endif
#ifndef FORCE_TEXT
@@ -122,7 +259,7 @@ char *git_terminal_prompt(const char *prompt, int echo)
fputs(prompt, output_fh);
fflush(output_fh);
- r = strbuf_getline(&buf, input_fh, '\n');
+ r = strbuf_getline_lf(&buf, input_fh);
if (!echo) {
putc('\n', output_fh);
fflush(output_fh);
@@ -137,11 +274,160 @@ char *git_terminal_prompt(const char *prompt, int echo)
return buf.buf;
}
+/*
+ * The `is_known_escape_sequence()` function returns 1 if the passed string
+ * corresponds to an Escape sequence that the terminal capabilities contains.
+ *
+ * To avoid depending on ncurses or other platform-specific libraries, we rely
+ * on the presence of the `infocmp` executable to do the job for us (failing
+ * silently if the program is not available or refused to run).
+ */
+struct escape_sequence_entry {
+ struct hashmap_entry entry;
+ char sequence[FLEX_ARRAY];
+};
+
+static int sequence_entry_cmp(const void *hashmap_cmp_fn_data,
+ const struct escape_sequence_entry *e1,
+ const struct escape_sequence_entry *e2,
+ const void *keydata)
+{
+ return strcmp(e1->sequence, keydata ? keydata : e2->sequence);
+}
+
+static int is_known_escape_sequence(const char *sequence)
+{
+ static struct hashmap sequences;
+ static int initialized;
+
+ if (!initialized) {
+ struct child_process cp = CHILD_PROCESS_INIT;
+ struct strbuf buf = STRBUF_INIT;
+ char *p, *eol;
+
+ hashmap_init(&sequences, (hashmap_cmp_fn)sequence_entry_cmp,
+ NULL, 0);
+
+ strvec_pushl(&cp.args, "infocmp", "-L", "-1", NULL);
+ if (pipe_command(&cp, NULL, 0, &buf, 0, NULL, 0))
+ strbuf_setlen(&buf, 0);
+
+ for (eol = p = buf.buf; *p; p = eol + 1) {
+ p = strchr(p, '=');
+ if (!p)
+ break;
+ p++;
+ eol = strchrnul(p, '\n');
+
+ if (starts_with(p, "\\E")) {
+ char *comma = memchr(p, ',', eol - p);
+ struct escape_sequence_entry *e;
+
+ p[0] = '^';
+ p[1] = '[';
+ FLEX_ALLOC_MEM(e, sequence, p, comma - p);
+ hashmap_entry_init(&e->entry,
+ strhash(e->sequence));
+ hashmap_add(&sequences, &e->entry);
+ }
+ if (!*eol)
+ break;
+ }
+ initialized = 1;
+ }
+
+ return !!hashmap_get_from_hash(&sequences, strhash(sequence), sequence);
+}
+
+int read_key_without_echo(struct strbuf *buf)
+{
+ static int warning_displayed;
+ int ch;
+
+ if (warning_displayed || enable_non_canonical() < 0) {
+ if (!warning_displayed) {
+ warning("reading single keystrokes not supported on "
+ "this platform; reading line instead");
+ warning_displayed = 1;
+ }
+
+ return strbuf_getline(buf, stdin);
+ }
+
+ strbuf_reset(buf);
+ ch = getchar();
+ if (ch == EOF) {
+ restore_term();
+ return EOF;
+ }
+ strbuf_addch(buf, ch);
+
+ if (ch == '\033' /* ESC */) {
+ /*
+ * We are most likely looking at an Escape sequence. Let's try
+ * to read more bytes, waiting at most half a second, assuming
+ * that the sequence is complete if we did not receive any byte
+ * within that time.
+ *
+ * Start by replacing the Escape byte with ^[ */
+ strbuf_splice(buf, buf->len - 1, 1, "^[", 2);
+
+ /*
+ * Query the terminal capabilities once about all the Escape
+ * sequences it knows about, so that we can avoid waiting for
+ * half a second when we know that the sequence is complete.
+ */
+ while (!is_known_escape_sequence(buf->buf)) {
+ struct pollfd pfd = { .fd = 0, .events = POLLIN };
+
+ if (poll(&pfd, 1, 500) < 1)
+ break;
+
+ ch = getchar();
+ if (ch == EOF)
+ return 0;
+ strbuf_addch(buf, ch);
+ }
+ }
+
+ restore_term();
+ return 0;
+}
+
#else
+int save_term(int full_duplex)
+{
+ /* full_duplex == 1, but no support available */
+ return -full_duplex;
+}
+
+void restore_term(void)
+{
+}
+
char *git_terminal_prompt(const char *prompt, int echo)
{
return getpass(prompt);
}
+int read_key_without_echo(struct strbuf *buf)
+{
+ static int warning_displayed;
+ const char *res;
+
+ if (!warning_displayed) {
+ warning("reading single keystrokes not supported on this "
+ "platform; reading line instead");
+ warning_displayed = 1;
+ }
+
+ res = getpass("");
+ strbuf_reset(buf);
+ if (!res)
+ return EOF;
+ strbuf_addstr(buf, res);
+ return 0;
+}
+
#endif
diff --git a/compat/terminal.h b/compat/terminal.h
index 97db7cd69d..e1770c575b 100644
--- a/compat/terminal.h
+++ b/compat/terminal.h
@@ -1,6 +1,12 @@
#ifndef COMPAT_TERMINAL_H
#define COMPAT_TERMINAL_H
+int save_term(int full_duplex);
+void restore_term(void);
+
char *git_terminal_prompt(const char *prompt, int echo);
+/* Read a single keystroke, without echoing it to the terminal */
+int read_key_without_echo(struct strbuf *buf);
+
#endif /* COMPAT_TERMINAL_H */
diff --git a/compat/unsetenv.c b/compat/unsetenv.c
index eb29f5e084..bf5fd7063b 100644
--- a/compat/unsetenv.c
+++ b/compat/unsetenv.c
@@ -2,7 +2,9 @@
void gitunsetenv (const char *name)
{
+#if !defined(__MINGW32__)
extern char **environ;
+#endif
int src, dst;
size_t nmln;
diff --git a/compat/vcbuild/.gitignore b/compat/vcbuild/.gitignore
new file mode 100644
index 0000000000..8f8b794ef3
--- /dev/null
+++ b/compat/vcbuild/.gitignore
@@ -0,0 +1,3 @@
+/vcpkg/
+/MSVC-DEFS-GEN
+/VCPKG-DEFS
diff --git a/compat/vcbuild/README b/compat/vcbuild/README
index df8a6574c9..29ec1d0f10 100644
--- a/compat/vcbuild/README
+++ b/compat/vcbuild/README
@@ -1,3 +1,65 @@
+The Steps to Build Git with VS2015 or VS2017 from the command line.
+
+1. Install the "vcpkg" open source package manager and build essential
+ third-party libraries. The steps for this have been captured in a
+ set of convenience scripts. These can be run from a stock Command
+ Prompt or from an SDK bash window:
+
+ $ cd <repo_root>
+ $ ./compat/vcbuild/vcpkg_install.bat
+
+ The vcpkg tools and all of the third-party sources will be installed
+ in this folder:
+ <repo_root>/compat/vcbuild/vcpkg/
+
+ A file will be created with a set of Makefile macros pointing to a
+ unified "include", "lib", and "bin" directory (release and debug) for
+ all of the required packages. This file will be included by the main
+ Makefile:
+ <repo_root>/compat/vcbuild/MSVC-DEFS-GEN
+
+2. OPTIONALLY copy the third-party *.dll and *.pdb files into the repo
+ root to make it easier to run and debug git.exe without having to
+ manipulate your PATH. This is especially true for debug sessions in
+ Visual Studio.
+
+ Use ONE of the following forms which should match how you want to
+ compile git.exe.
+
+ $ ./compat/vcbuild/vcpkg_copy_dlls.bat debug
+ $ ./compat/vcbuild/vcpkg_copy_dlls.bat release
+
+3. Build git using MSVC from an SDK bash window using one of the
+ following commands:
+
+ $ make MSVC=1
+ $ make MSVC=1 DEBUG=1
+
+================================================================
+
+Alternatively, run `make vcxproj` and then load the generated `git.sln` in
+Visual Studio. The initial build will install the vcpkg system and build the
+dependencies automatically. This will take a while.
+
+Instead of generating the `git.sln` file yourself (which requires a full Git
+for Windows SDK), you may want to consider fetching the `vs/master` branch of
+https://github.com/git-for-windows/git instead (which is updated automatically
+via CI running `make vcxproj`). The `vs/master` branch does not require a Git
+for Windows to build, but you can run the test scripts in a regular Git Bash.
+
+Note that `make vcxproj` will automatically add and commit the generated `.sln`
+and `.vcxproj` files to the repo. This is necessary to allow building a
+fully-testable Git in Visual Studio, where a regular Git Bash can be used to
+run the test scripts (as opposed to a full Git for Windows SDK): a number of
+build targets, such as Git commands implemented as Unix shell scripts (where
+`@@SHELL_PATH@@` and other placeholders are interpolated) require a full-blown
+Git for Windows SDK (which is about 10x the size of a regular Git for Windows
+installation).
+
+If your plan is to open a Pull Request with Git for Windows, it is a good idea
+to drop this commit before submitting.
+
+================================================================
The Steps of Build Git with VS2008
1. You need the build environment, which contains the Git dependencies
@@ -30,8 +92,8 @@ The Steps of Build Git with VS2008
the git operations.
3. Inside Git's directory run the command:
- make common-cmds.h
- to generate the common-cmds.h file needed to compile git.
+ make generated-hdrs
+ to generate the header file needed to compile git.
4. Then either build Git with the GNU Make Makefile in the Git projects
root
diff --git a/compat/vcbuild/find_vs_env.bat b/compat/vcbuild/find_vs_env.bat
new file mode 100644
index 0000000000..b35d264c0e
--- /dev/null
+++ b/compat/vcbuild/find_vs_env.bat
@@ -0,0 +1,168 @@
+@ECHO OFF
+REM ================================================================
+REM You can use either GCC (the default) or MSVC to build git
+REM using the GIT-SDK command line tools.
+REM $ make
+REM $ make MSVC=1
+REM
+REM GIT-SDK BASH windows inherit environment variables with all of
+REM the bin/lib/include paths for GCC. It DOES NOT inherit values
+REM for the corresponding MSVC tools.
+REM
+REM During normal (non-git) Windows development, you launch one
+REM of the provided "developer command prompts" to set environment
+REM variables for the MSVC tools.
+REM
+REM Therefore, to allow MSVC command line builds of git from BASH
+REM and MAKE, we must blend these two different worlds. This script
+REM attempts to do that.
+REM ================================================================
+REM This BAT file starts in a plain (non-developer) command prompt,
+REM searches for the "best" command prompt setup script, installs
+REM it into the current CMD process, and exports the various MSVC
+REM environment variables for use by MAKE.
+REM
+REM The output of this script should be written to a make "include
+REM file" and referenced by the top-level Makefile.
+REM
+REM See "config.mak.uname" (look for compat/vcbuild/MSVC-DEFS-GEN).
+REM ================================================================
+REM The provided command prompts are custom to each VS release and
+REM filled with lots of internal knowledge (such as Registry settings);
+REM even their names vary by release, so it is not appropriate for us
+REM to look inside them. Rather, just run them in a subordinate
+REM process and extract the settings we need.
+REM ================================================================
+REM
+REM Current (VS2017 and beyond)
+REM -------------------
+REM Visual Studio 2017 introduced a new installation layout and
+REM support for side-by-side installation of multiple versions of
+REM VS2017. Furthermore, these can all coexist with installations
+REM of previous versions of VS (which have a completely different
+REM layout on disk).
+REM
+REM VS2017 Update 2 introduced a "vswhere.exe" command:
+REM https://github.com/Microsoft/vswhere
+REM https://blogs.msdn.microsoft.com/heaths/2017/02/25/vswhere-available/
+REM https://blogs.msdn.microsoft.com/vcblog/2017/03/06/finding-the-visual-c-compiler-tools-in-visual-studio-2017/
+REM
+REM VS2015
+REM ------
+REM Visual Studio 2015 uses the traditional VcVarsAll.
+REM
+REM Earlier Versions
+REM ----------------
+REM Currently unsupported.
+REM
+REM ================================================================
+REM Note: Throughout this script we use "dir <path> && <cmd>" rather
+REM than "if exist <path>" because of script problems with pathnames
+REM containing spaces.
+REM ================================================================
+
+REM Sanitize PATH to prevent git-sdk paths from confusing "wmic.exe"
+REM (called internally in some of the system BAT files).
+SET PATH=%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;
+
+REM ================================================================
+
+:current
+ SET vs_where=C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe
+ dir "%vs_where%" >nul 2>nul && GOTO have_vs_where
+ GOTO not_2017
+
+:have_vs_where
+ REM Try to use VsWhere to get the location of VsDevCmd.
+
+ REM Keep VsDevCmd from cd'ing away.
+ SET VSCMD_START_DIR=.
+
+ REM Get the root of the VS product installation.
+ FOR /F "usebackq tokens=*" %%i IN (`"%vs_where%" -latest -requires Microsoft.VisualStudio.Workload.NativeDesktop -property installationPath`) DO @SET vs_ip=%%i
+
+ SET vs_devcmd=%vs_ip%\Common7\Tools\VsDevCmd.bat
+ dir "%vs_devcmd%" >nul 2>nul && GOTO have_vs_devcmd
+ GOTO not_2017
+
+:have_vs_devcmd
+ REM Use VsDevCmd to setup the environment of this process.
+ REM Setup CL for building 64-bit apps using 64-bit tools.
+ @call "%vs_devcmd%" -no_logo -arch=x64 -host_arch=x64
+
+ SET tgt=%VSCMD_ARG_TGT_ARCH%
+
+ SET mn=%VCToolsInstallDir%
+ SET msvc_includes=-I"%mn%INCLUDE"
+ SET msvc_libs=-L"%mn%lib\%tgt%"
+ SET msvc_bin_dir=%mn%bin\Host%VSCMD_ARG_HOST_ARCH%\%tgt%
+
+ SET sdk_dir=%WindowsSdkDir%
+ SET sdk_ver=%WindowsSDKVersion%
+ SET si=%sdk_dir%Include\%sdk_ver%
+ SET sdk_includes=-I"%si%ucrt" -I"%si%um" -I"%si%shared"
+ SET sl=%sdk_dir%lib\%sdk_ver%
+ SET sdk_libs=-L"%sl%ucrt\%tgt%" -L"%sl%um\%tgt%"
+
+ SET vs_ver=%VisualStudioVersion%
+
+ GOTO print_vars
+
+REM ================================================================
+
+:not_2017
+ REM See if VS2015 is installed.
+
+ SET vs_2015_bat=C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat
+ dir "%vs_2015_bat%" >nul 2>nul && GOTO have_vs_2015
+ GOTO not_2015
+
+:have_vs_2015
+ REM Use VcVarsAll like the "x64 Native" command prompt.
+ REM Setup CL for building 64-bit apps using 64-bit tools.
+ @call "%vs_2015_bat%" amd64
+
+ REM Note that in VS2015 they use "x64" in some contexts and "amd64" in others.
+ SET mn=C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\
+ SET msvc_includes=-I"%mn%INCLUDE"
+ SET msvc_libs=-L"%mn%lib\amd64"
+ SET msvc_bin_dir=%mn%bin\amd64
+
+ SET sdk_dir=%WindowsSdkDir%
+ SET sdk_ver=%WindowsSDKVersion%
+ SET si=%sdk_dir%Include\%sdk_ver%
+ SET sdk_includes=-I"%si%ucrt" -I"%si%um" -I"%si%shared" -I"%si%winrt"
+ SET sl=%sdk_dir%lib\%sdk_ver%
+ SET sdk_libs=-L"%sl%ucrt\x64" -L"%sl%um\x64"
+
+ SET vs_ver=%VisualStudioVersion%
+
+ GOTO print_vars
+
+REM ================================================================
+
+:not_2015
+ echo "ERROR: unsupported VS version (older than VS2015)" >&2
+ EXIT /B 1
+
+REM ================================================================
+
+:print_vars
+ REM Dump the essential vars to stdout to allow the main
+ REM Makefile to include it. See config.mak.uname.
+ REM Include DOS-style and BASH-style path for bin dir.
+
+ echo msvc_bin_dir=%msvc_bin_dir%
+ SET X1=%msvc_bin_dir:C:=/C%
+ SET X2=%X1:\=/%
+ echo msvc_bin_dir_msys=%X2%
+
+ echo msvc_includes=%msvc_includes%
+ echo msvc_libs=%msvc_libs%
+
+ echo sdk_includes=%sdk_includes%
+ echo sdk_libs=%sdk_libs%
+
+ echo vs_ver=%vs_ver%
+
+ EXIT /B 0
diff --git a/compat/vcbuild/include/sys/poll.h b/compat/vcbuild/include/sys/poll.h
deleted file mode 100644
index 0d8552a2c6..0000000000
--- a/compat/vcbuild/include/sys/poll.h
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/unistd.h b/compat/vcbuild/include/unistd.h
index b14fcf94da..3a959d124c 100644
--- a/compat/vcbuild/include/unistd.h
+++ b/compat/vcbuild/include/unistd.h
@@ -45,10 +45,17 @@ typedef unsigned long long uintmax_t;
typedef int64_t off64_t;
+#if !defined(_MSC_VER) || _MSC_VER < 1600
#define INTMAX_MIN _I64_MIN
#define INTMAX_MAX _I64_MAX
#define UINTMAX_MAX _UI64_MAX
+#define UINT32_MAX 0xffffffff /* 4294967295U */
+#else
+#include <stdint.h>
+#endif
+
+#define STDIN_FILENO 0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
diff --git a/compat/vcbuild/scripts/clink.pl b/compat/vcbuild/scripts/clink.pl
index 4374771df2..3bd824154b 100644..100755
--- a/compat/vcbuild/scripts/clink.pl
+++ b/compat/vcbuild/scripts/clink.pl
@@ -12,41 +12,122 @@
use strict;
my @args = ();
my @cflags = ();
+my @lflags = ();
my $is_linking = 0;
+my $is_debug = 0;
while (@ARGV) {
my $arg = shift @ARGV;
- if ("$arg" =~ /^-[DIMGO]/) {
+ if ("$arg" eq "-DDEBUG") {
+ # Some vcpkg-based libraries have different names for release
+ # and debug versions. This hack assumes that -DDEBUG comes
+ # before any "-l*" flags.
+ $is_debug = 1;
+ }
+ if ("$arg" =~ /^-I\/mingw(32|64)/) {
+ # eat
+ } elsif ("$arg" =~ /^-[DIMGOZ]/) {
push(@cflags, $arg);
} elsif ("$arg" eq "-o") {
my $file_out = shift @ARGV;
if ("$file_out" =~ /exe$/) {
$is_linking = 1;
+ # Create foo.exe and foo.pdb
push(@args, "-OUT:$file_out");
} else {
+ # Create foo.o and foo.o.pdb
push(@args, "-Fo$file_out");
+ push(@args, "-Fd$file_out.pdb");
}
} elsif ("$arg" eq "-lz") {
+ if ($is_debug) {
+ push(@args, "zlibd.lib");
+ } else{
push(@args, "zlib.lib");
+ }
} elsif ("$arg" eq "-liconv") {
push(@args, "iconv.lib");
} elsif ("$arg" eq "-lcrypto") {
- push(@args, "libeay32.lib");
+ push(@args, "libcrypto.lib");
} elsif ("$arg" eq "-lssl") {
- push(@args, "ssleay32.lib");
+ push(@args, "libssl.lib");
+ } elsif ("$arg" eq "-lcurl") {
+ my $lib = "";
+ # Newer vcpkg definitions call this libcurl_imp.lib; Do we
+ # need to use that instead?
+ foreach my $flag (@lflags) {
+ if ($flag =~ /^-LIBPATH:(.*)/) {
+ foreach my $l ("libcurl_imp.lib", "libcurl.lib") {
+ if (-f "$1/$l") {
+ $lib = $l;
+ last;
+ }
+ }
+ }
+ }
+ push(@args, $lib);
+ } elsif ("$arg" eq "-lexpat") {
+ push(@args, "libexpat.lib");
} elsif ("$arg" =~ /^-L/ && "$arg" ne "-LTCG") {
$arg =~ s/^-L/-LIBPATH:/;
- push(@args, $arg);
- } elsif ("$arg" =~ /^-R/) {
+ push(@lflags, $arg);
+ } elsif ("$arg" =~ /^-[Rl]/) {
# eat
+ } elsif ("$arg" eq "-Werror") {
+ push(@cflags, "-WX");
+ } elsif ("$arg" eq "-Wall") {
+ # cl.exe understands -Wall, but it is really overzealous
+ push(@cflags, "-W4");
+ # disable the "signed/unsigned mismatch" warnings; our source code violates that
+ push(@cflags, "-wd4018");
+ push(@cflags, "-wd4245");
+ push(@cflags, "-wd4389");
+ # disable the "unreferenced formal parameter" warning; our source code violates that
+ push(@cflags, "-wd4100");
+ # disable the "conditional expression is constant" warning; our source code violates that
+ push(@cflags, "-wd4127");
+ # disable the "const object should be initialized" warning; these warnings affect only objects that are `static`
+ push(@cflags, "-wd4132");
+ # disable the "function/data pointer conversion in expression" warning; our source code violates that
+ push(@cflags, "-wd4152");
+ # disable the "non-constant aggregate initializer" warning; our source code violates that
+ push(@cflags, "-wd4204");
+ # disable the "cannot be initialized using address of automatic variable" warning; our source code violates that
+ push(@cflags, "-wd4221");
+ # disable the "possible loss of data" warnings; our source code violates that
+ push(@cflags, "-wd4244");
+ push(@cflags, "-wd4267");
+ # disable the "array is too small to include a terminating null character" warning; we ab-use strings to initialize OIDs
+ push(@cflags, "-wd4295");
+ # disable the "'<<': result of 32-bit shift implicitly converted to 64 bits" warning; our source code violates that
+ push(@cflags, "-wd4334");
+ # disable the "declaration hides previous local declaration" warning; our source code violates that
+ push(@cflags, "-wd4456");
+ # disable the "declaration hides function parameter" warning; our source code violates that
+ push(@cflags, "-wd4457");
+ # disable the "declaration hides global declaration" warning; our source code violates that
+ push(@cflags, "-wd4459");
+ # disable the "potentially uninitialized local variable '<name>' used" warning; our source code violates that
+ push(@cflags, "-wd4701");
+ # disable the "unreachable code" warning; our source code violates that
+ push(@cflags, "-wd4702");
+ # disable the "potentially uninitialized local pointer variable used" warning; our source code violates that
+ push(@cflags, "-wd4703");
+ # disable the "assignment within conditional expression" warning; our source code violates that
+ push(@cflags, "-wd4706");
+ # disable the "'inet_ntoa': Use inet_ntop() or InetNtop() instead" warning; our source code violates that
+ push(@cflags, "-wd4996");
+ } elsif ("$arg" =~ /^-W[a-z]/) {
+ # let's ignore those
} else {
push(@args, $arg);
}
}
if ($is_linking) {
+ push(@args, @lflags);
unshift(@args, "link.exe");
} else {
unshift(@args, "cl.exe");
push(@args, @cflags);
}
-#printf("**** @args\n");
+printf(STDERR "**** @args\n\n\n") if (!defined($ENV{'QUIET_GEN'}));
exit (system(@args) != 0);
diff --git a/compat/vcbuild/scripts/lib.pl b/compat/vcbuild/scripts/lib.pl
index d8054e469f..d8054e469f 100644..100755
--- a/compat/vcbuild/scripts/lib.pl
+++ b/compat/vcbuild/scripts/lib.pl
diff --git a/compat/vcbuild/vcpkg_copy_dlls.bat b/compat/vcbuild/vcpkg_copy_dlls.bat
new file mode 100644
index 0000000000..13661c14f8
--- /dev/null
+++ b/compat/vcbuild/vcpkg_copy_dlls.bat
@@ -0,0 +1,39 @@
+@ECHO OFF
+REM ================================================================
+REM This script is an optional step. It copies the *.dll and *.pdb
+REM files (created by vcpkg_install.bat) into the top-level directory
+REM of the repo so that you can type "./git.exe" and find them without
+REM having to fixup your PATH.
+REM
+REM NOTE: Because the names of some DLL files change between DEBUG and
+REM NOTE: RELEASE builds when built using "vcpkg.exe", you will need
+REM NOTE: to copy up the corresponding version.
+REM ================================================================
+
+ SETLOCAL EnableDelayedExpansion
+
+ @FOR /F "delims=" %%D IN ("%~dp0") DO @SET cwd=%%~fD
+ cd %cwd%
+
+ SET arch=x64-windows
+ SET inst=%cwd%vcpkg\installed\%arch%
+
+ IF [%1]==[release] (
+ echo Copying RELEASE mode DLLs to repo root...
+ ) ELSE IF [%1]==[debug] (
+ SET inst=%inst%\debug
+ echo Copying DEBUG mode DLLs to repo root...
+ ) ELSE (
+ echo ERROR: Invalid argument.
+ echo Usage: %~0 release
+ echo Usage: %~0 debug
+ EXIT /B 1
+ )
+
+ xcopy /e/s/v/y %inst%\bin\*.dll ..\..\
+ xcopy /e/s/v/y %inst%\bin\*.pdb ..\..\
+
+ xcopy /e/s/v/y %inst%\bin\*.dll ..\..\t\helper\
+ xcopy /e/s/v/y %inst%\bin\*.pdb ..\..\t\helper\
+
+ EXIT /B 0
diff --git a/compat/vcbuild/vcpkg_install.bat b/compat/vcbuild/vcpkg_install.bat
new file mode 100644
index 0000000000..ebd0bad242
--- /dev/null
+++ b/compat/vcbuild/vcpkg_install.bat
@@ -0,0 +1,80 @@
+@ECHO OFF
+REM ================================================================
+REM This script installs the "vcpkg" source package manager and uses
+REM it to build the third-party libraries that git requires when it
+REM is built using MSVC.
+REM
+REM [1] Install VCPKG.
+REM [a] Create <root>/compat/vcbuild/vcpkg/
+REM [b] Download "vcpkg".
+REM [c] Compile using the currently installed version of VS.
+REM [d] Create <root>/compat/vcbuild/vcpkg/vcpkg.exe
+REM
+REM [2] Install third-party libraries.
+REM [a] Download each (which may also install CMAKE).
+REM [b] Compile in RELEASE mode and install in:
+REM vcpkg/installed/<arch>/{bin,lib}
+REM [c] Compile in DEBUG mode and install in:
+REM vcpkg/installed/<arch>/debug/{bin,lib}
+REM [d] Install headers in:
+REM vcpkg/installed/<arch>/include
+REM
+REM [3] Create a set of MAKE definitions for the top-level
+REM Makefile to allow "make MSVC=1" to find the above
+REM third-party libraries.
+REM [a] Write vcpkg/VCPGK-DEFS
+REM
+REM https://blogs.msdn.microsoft.com/vcblog/2016/09/19/vcpkg-a-tool-to-acquire-and-build-c-open-source-libraries-on-windows/
+REM https://github.com/Microsoft/vcpkg
+REM https://vcpkg.readthedocs.io/en/latest/
+REM ================================================================
+
+ SETLOCAL EnableDelayedExpansion
+
+ @FOR /F "delims=" %%D IN ("%~dp0") DO @SET cwd=%%~fD
+ cd %cwd%
+
+ dir vcpkg\vcpkg.exe >nul 2>nul && GOTO :install_libraries
+
+ echo Fetching vcpkg in %cwd%vcpkg
+ git.exe clone https://github.com/Microsoft/vcpkg vcpkg
+ IF ERRORLEVEL 1 ( EXIT /B 1 )
+
+ cd vcpkg
+ echo Building vcpkg
+ powershell -exec bypass scripts\bootstrap.ps1
+ IF ERRORLEVEL 1 ( EXIT /B 1 )
+
+ echo Successfully installed %cwd%vcpkg\vcpkg.exe
+
+:install_libraries
+ SET arch=x64-windows
+
+ echo Installing third-party libraries...
+ FOR %%i IN (zlib expat libiconv openssl libssh2 curl) DO (
+ cd %cwd%vcpkg
+ IF NOT EXIST "packages\%%i_%arch%" CALL :sub__install_one %%i
+ IF ERRORLEVEL 1 ( EXIT /B 1 )
+ )
+
+:install_defines
+ cd %cwd%
+ SET inst=%cwd%vcpkg\installed\%arch%
+
+ echo vcpkg_inc=-I"%inst%\include">VCPKG-DEFS
+ echo vcpkg_rel_lib=-L"%inst%\lib">>VCPKG-DEFS
+ echo vcpkg_rel_bin="%inst%\bin">>VCPKG-DEFS
+ echo vcpkg_dbg_lib=-L"%inst%\debug\lib">>VCPKG-DEFS
+ echo vcpkg_dbg_bin="%inst%\debug\bin">>VCPKG-DEFS
+
+ EXIT /B 0
+
+
+:sub__install_one
+ echo Installing package %1...
+
+ .\vcpkg.exe install %1:%arch%
+ IF ERRORLEVEL 1 ( EXIT /B 1 )
+
+ echo Finished %1
+ goto :EOF
diff --git a/compat/win32.h b/compat/win32.h
index 8ce91048de..a97e880757 100644
--- a/compat/win32.h
+++ b/compat/win32.h
@@ -2,7 +2,7 @@
#define WIN32_H
/* common Win32 functions for MinGW and Cygwin */
-#ifndef WIN32 /* Not defined by Cygwin */
+#ifndef GIT_WINDOWS_NATIVE /* Not defined for Cygwin */
#include <windows.h>
#endif
diff --git a/compat/vcbuild/include/alloca.h b/compat/win32/alloca.h
index c0d7985b7e..c0d7985b7e 100644
--- a/compat/vcbuild/include/alloca.h
+++ b/compat/win32/alloca.h
diff --git a/compat/win32/dirent.c b/compat/win32/dirent.c
index 7a0debe51b..52420ec7d4 100644
--- a/compat/win32/dirent.c
+++ b/compat/win32/dirent.c
@@ -1,96 +1,81 @@
-#include "../git-compat-util.h"
-#include "dirent.h"
+#include "../../git-compat-util.h"
struct DIR {
struct dirent dd_dir; /* includes d_type */
HANDLE dd_handle; /* FindFirstFile handle */
int dd_stat; /* 0-based index */
- char dd_name[1]; /* extend struct */
};
+static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAW *fdata)
+{
+ /* convert UTF-16 name to UTF-8 */
+ xwcstoutf(ent->d_name, fdata->cFileName, sizeof(ent->d_name));
+
+ /* Set file type, based on WIN32_FIND_DATA */
+ if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ ent->d_type = DT_DIR;
+ else
+ ent->d_type = DT_REG;
+}
+
DIR *opendir(const char *name)
{
- DWORD attrs = GetFileAttributesA(name);
+ wchar_t pattern[MAX_PATH + 2]; /* + 2 for '/' '*' */
+ WIN32_FIND_DATAW fdata;
+ HANDLE h;
int len;
- DIR *p;
+ DIR *dir;
- /* check for valid path */
- if (attrs == INVALID_FILE_ATTRIBUTES) {
- errno = ENOENT;
+ /* convert name to UTF-16 and check length < MAX_PATH */
+ if ((len = xutftowcs_path(pattern, name)) < 0)
return NULL;
- }
- /* check if it's a directory */
- if (!(attrs & FILE_ATTRIBUTE_DIRECTORY)) {
- errno = ENOTDIR;
+ /* append optional '/' and wildcard '*' */
+ if (len && !is_dir_sep(pattern[len - 1]))
+ pattern[len++] = '/';
+ pattern[len++] = '*';
+ pattern[len] = 0;
+
+ /* open find handle */
+ h = FindFirstFileW(pattern, &fdata);
+ if (h == INVALID_HANDLE_VALUE) {
+ DWORD err = GetLastError();
+ errno = (err == ERROR_DIRECTORY) ? ENOTDIR : err_win_to_posix(err);
return NULL;
}
- /* check that the pattern won't be too long for FindFirstFileA */
- len = strlen(name);
- if (is_dir_sep(name[len - 1]))
- len--;
- if (len + 2 >= MAX_PATH) {
- errno = ENAMETOOLONG;
- return NULL;
- }
-
- p = malloc(sizeof(DIR) + len + 2);
- if (!p)
- return NULL;
-
- memset(p, 0, sizeof(DIR) + len + 2);
- strcpy(p->dd_name, name);
- p->dd_name[len] = '/';
- p->dd_name[len+1] = '*';
-
- p->dd_handle = INVALID_HANDLE_VALUE;
- return p;
+ /* initialize DIR structure and copy first dir entry */
+ dir = xmalloc(sizeof(DIR));
+ dir->dd_handle = h;
+ dir->dd_stat = 0;
+ finddata2dirent(&dir->dd_dir, &fdata);
+ return dir;
}
struct dirent *readdir(DIR *dir)
{
- WIN32_FIND_DATAA buf;
- HANDLE handle;
-
- if (!dir || !dir->dd_handle) {
+ if (!dir) {
errno = EBADF; /* No set_errno for mingw */
return NULL;
}
- if (dir->dd_handle == INVALID_HANDLE_VALUE && dir->dd_stat == 0) {
- DWORD lasterr;
- handle = FindFirstFileA(dir->dd_name, &buf);
- lasterr = GetLastError();
- dir->dd_handle = handle;
- if (handle == INVALID_HANDLE_VALUE && (lasterr != ERROR_NO_MORE_FILES)) {
- errno = err_win_to_posix(lasterr);
+ /* if first entry, dirent has already been set up by opendir */
+ if (dir->dd_stat) {
+ /* get next entry and convert from WIN32_FIND_DATA to dirent */
+ WIN32_FIND_DATAW fdata;
+ if (FindNextFileW(dir->dd_handle, &fdata)) {
+ finddata2dirent(&dir->dd_dir, &fdata);
+ } else {
+ DWORD lasterr = GetLastError();
+ /* POSIX says you shouldn't set errno when readdir can't
+ find any more files; so, if another error we leave it set. */
+ if (lasterr != ERROR_NO_MORE_FILES)
+ errno = err_win_to_posix(lasterr);
return NULL;
}
- } else if (dir->dd_handle == INVALID_HANDLE_VALUE) {
- return NULL;
- } else if (!FindNextFileA(dir->dd_handle, &buf)) {
- DWORD lasterr = GetLastError();
- FindClose(dir->dd_handle);
- dir->dd_handle = INVALID_HANDLE_VALUE;
- /* POSIX says you shouldn't set errno when readdir can't
- find any more files; so, if another error we leave it set. */
- if (lasterr != ERROR_NO_MORE_FILES)
- errno = err_win_to_posix(lasterr);
- return NULL;
}
- /* We get here if `buf' contains valid data. */
- strcpy(dir->dd_dir.d_name, buf.cFileName);
++dir->dd_stat;
-
- /* Set file type, based on WIN32_FIND_DATA */
- dir->dd_dir.d_type = 0;
- if (buf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
- dir->dd_dir.d_type |= DT_DIR;
- else
- dir->dd_dir.d_type |= DT_REG;
-
return &dir->dd_dir;
}
@@ -101,8 +86,7 @@ int closedir(DIR *dir)
return -1;
}
- if (dir->dd_handle != INVALID_HANDLE_VALUE)
- FindClose(dir->dd_handle);
+ FindClose(dir->dd_handle);
free(dir);
return 0;
}
diff --git a/compat/win32/dirent.h b/compat/win32/dirent.h
index 927a25ca76..058207e4bf 100644
--- a/compat/win32/dirent.h
+++ b/compat/win32/dirent.h
@@ -9,12 +9,8 @@ typedef struct DIR DIR;
#define DT_LNK 3
struct dirent {
- long d_ino; /* Always zero. */
- char d_name[FILENAME_MAX]; /* File name. */
- union {
- unsigned short d_reclen; /* Always zero. */
- unsigned char d_type; /* Reimplementation adds this */
- };
+ unsigned char d_type; /* file type to prevent lstat after readdir */
+ char d_name[MAX_PATH * 3]; /* file name (* 3 for UTF-8 conversion) */
};
DIR *opendir(const char *dirname);
diff --git a/compat/win32/git.manifest b/compat/win32/git.manifest
new file mode 100644
index 0000000000..771e3cce43
--- /dev/null
+++ b/compat/win32/git.manifest
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <assemblyIdentity type="win32" name="Git" version="0.0.0.1" />
+ <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+ <security>
+ <requestedPrivileges>
+ <requestedExecutionLevel level="asInvoker" uiAccess="false" />
+ </requestedPrivileges>
+ </security>
+ </trustInfo>
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <!-- Windows Vista -->
+ <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
+ <!-- Windows 7 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+ <!-- Windows 8 -->
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+ <!-- Windows 8.1 -->
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+ <!-- Windows 10 -->
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+ </application>
+ </compatibility>
+</assembly>
diff --git a/compat/win32/lazyload.h b/compat/win32/lazyload.h
new file mode 100644
index 0000000000..2b3637135f
--- /dev/null
+++ b/compat/win32/lazyload.h
@@ -0,0 +1,61 @@
+#ifndef LAZYLOAD_H
+#define LAZYLOAD_H
+
+/*
+ * A pair of macros to simplify loading of DLL functions. Example:
+ *
+ * DECLARE_PROC_ADDR(kernel32.dll, BOOL, CreateHardLinkW,
+ * LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES);
+ *
+ * if (!INIT_PROC_ADDR(CreateHardLinkW))
+ * return error("Could not find CreateHardLinkW() function";
+ *
+ * if (!CreateHardLinkW(source, target, NULL))
+ * return error("could not create hardlink from %S to %S",
+ * source, target);
+ */
+
+typedef void (*FARVOIDPROC)(void);
+
+struct proc_addr {
+ const char *const dll;
+ const char *const function;
+ FARVOIDPROC pfunction;
+ unsigned initialized : 1;
+};
+
+/* Declares a function to be loaded dynamically from a DLL. */
+#define DECLARE_PROC_ADDR(dll, rettype, function, ...) \
+ static struct proc_addr proc_addr_##function = \
+ { #dll, #function, NULL, 0 }; \
+ typedef rettype (WINAPI *proc_type_##function)(__VA_ARGS__); \
+ static proc_type_##function function
+
+/*
+ * Loads a function from a DLL (once-only).
+ * Returns non-NULL function pointer on success.
+ * Returns NULL + errno == ENOSYS on failure.
+ * This function is not thread-safe.
+ */
+#define INIT_PROC_ADDR(function) \
+ (function = (proc_type_##function)get_proc_addr(&proc_addr_##function))
+
+static inline FARVOIDPROC get_proc_addr(struct proc_addr *proc)
+{
+ /* only do this once */
+ if (!proc->initialized) {
+ HANDLE hnd;
+ proc->initialized = 1;
+ hnd = LoadLibraryExA(proc->dll, NULL,
+ LOAD_LIBRARY_SEARCH_SYSTEM32);
+ if (hnd)
+ proc->pfunction = (FARVOIDPROC)GetProcAddress(hnd,
+ proc->function);
+ }
+ /* set ENOSYS if DLL or function was not found */
+ if (!proc->pfunction)
+ errno = ENOSYS;
+ return proc->pfunction;
+}
+
+#endif
diff --git a/compat/win32/path-utils.c b/compat/win32/path-utils.c
new file mode 100644
index 0000000000..ebf2f12eb6
--- /dev/null
+++ b/compat/win32/path-utils.c
@@ -0,0 +1,52 @@
+#include "../../git-compat-util.h"
+
+int win32_has_dos_drive_prefix(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;
+}
+
+int win32_skip_dos_drive_prefix(char **path)
+{
+ int ret = has_dos_drive_prefix(*path);
+ *path += ret;
+ return ret;
+}
+
+int win32_offset_1st_component(const char *path)
+{
+ char *pos = (char *)path;
+
+ /* unc paths */
+ if (!skip_dos_drive_prefix(&pos) &&
+ is_dir_sep(pos[0]) && is_dir_sep(pos[1])) {
+ /* skip server name */
+ pos = strpbrk(pos + 2, "\\/");
+ if (!pos)
+ return 0; /* Error: malformed unc path */
+
+ do {
+ pos++;
+ } while (*pos && !is_dir_sep(*pos));
+ }
+
+ return pos + is_dir_sep(*pos) - path;
+}
diff --git a/compat/win32/path-utils.h b/compat/win32/path-utils.h
new file mode 100644
index 0000000000..bba2b64408
--- /dev/null
+++ b/compat/win32/path-utils.h
@@ -0,0 +1,37 @@
+#ifndef WIN32_PATH_UTILS_H
+#define WIN32_PATH_UTILS_H
+
+int win32_has_dos_drive_prefix(const char *path);
+#define has_dos_drive_prefix win32_has_dos_drive_prefix
+
+int win32_skip_dos_drive_prefix(char **path);
+#define skip_dos_drive_prefix win32_skip_dos_drive_prefix
+static inline int win32_is_dir_sep(int c)
+{
+ return c == '/' || c == '\\';
+}
+#define is_dir_sep win32_is_dir_sep
+static inline char *win32_find_last_dir_sep(const char *path)
+{
+ char *ret = NULL;
+ for (; *path; ++path)
+ if (is_dir_sep(*path))
+ ret = (char *)path;
+ return ret;
+}
+#define find_last_dir_sep win32_find_last_dir_sep
+static inline int win32_has_dir_sep(const char *path)
+{
+ /*
+ * See how long the non-separator part of the given path is, and
+ * if and only if it covers the whole path (i.e. path[len] is NUL),
+ * there is no separator in the path---otherwise there is a separator.
+ */
+ size_t len = strcspn(path, "/\\");
+ return !!path[len];
+}
+#define has_dir_sep(path) win32_has_dir_sep(path)
+int win32_offset_1st_component(const char *path);
+#define offset_1st_component win32_offset_1st_component
+
+#endif
diff --git a/compat/win32/pthread.c b/compat/win32/pthread.c
index 010e875ec4..2e7eead42c 100644
--- a/compat/win32/pthread.c
+++ b/compat/win32/pthread.c
@@ -52,145 +52,7 @@ int win32_pthread_join(pthread_t *thread, void **value_ptr)
pthread_t pthread_self(void)
{
- pthread_t t = { 0 };
+ pthread_t t = { NULL };
t.tid = GetCurrentThreadId();
return t;
}
-
-int pthread_cond_init(pthread_cond_t *cond, const void *unused)
-{
- cond->waiters = 0;
- cond->was_broadcast = 0;
- InitializeCriticalSection(&cond->waiters_lock);
-
- cond->sema = CreateSemaphore(NULL, 0, LONG_MAX, NULL);
- if (!cond->sema)
- die("CreateSemaphore() failed");
-
- cond->continue_broadcast = CreateEvent(NULL, /* security */
- FALSE, /* auto-reset */
- FALSE, /* not signaled */
- NULL); /* name */
- if (!cond->continue_broadcast)
- die("CreateEvent() failed");
-
- return 0;
-}
-
-int pthread_cond_destroy(pthread_cond_t *cond)
-{
- CloseHandle(cond->sema);
- CloseHandle(cond->continue_broadcast);
- DeleteCriticalSection(&cond->waiters_lock);
- return 0;
-}
-
-int pthread_cond_wait(pthread_cond_t *cond, CRITICAL_SECTION *mutex)
-{
- int last_waiter;
-
- EnterCriticalSection(&cond->waiters_lock);
- cond->waiters++;
- LeaveCriticalSection(&cond->waiters_lock);
-
- /*
- * Unlock external mutex and wait for signal.
- * NOTE: we've held mutex locked long enough to increment
- * waiters count above, so there's no problem with
- * leaving mutex unlocked before we wait on semaphore.
- */
- LeaveCriticalSection(mutex);
-
- /* let's wait - ignore return value */
- WaitForSingleObject(cond->sema, INFINITE);
-
- /*
- * Decrease waiters count. If we are the last waiter, then we must
- * notify the broadcasting thread that it can continue.
- * But if we continued due to cond_signal, we do not have to do that
- * because the signaling thread knows that only one waiter continued.
- */
- EnterCriticalSection(&cond->waiters_lock);
- cond->waiters--;
- last_waiter = cond->was_broadcast && cond->waiters == 0;
- LeaveCriticalSection(&cond->waiters_lock);
-
- if (last_waiter) {
- /*
- * cond_broadcast was issued while mutex was held. This means
- * that all other waiters have continued, but are contending
- * for the mutex at the end of this function because the
- * broadcasting thread did not leave cond_broadcast, yet.
- * (This is so that it can be sure that each waiter has
- * consumed exactly one slice of the semaphor.)
- * The last waiter must tell the broadcasting thread that it
- * can go on.
- */
- SetEvent(cond->continue_broadcast);
- /*
- * Now we go on to contend with all other waiters for
- * the mutex. Auf in den Kampf!
- */
- }
- /* lock external mutex again */
- EnterCriticalSection(mutex);
-
- return 0;
-}
-
-/*
- * IMPORTANT: This implementation requires that pthread_cond_signal
- * is called while the mutex is held that is used in the corresponding
- * pthread_cond_wait calls!
- */
-int pthread_cond_signal(pthread_cond_t *cond)
-{
- int have_waiters;
-
- EnterCriticalSection(&cond->waiters_lock);
- have_waiters = cond->waiters > 0;
- LeaveCriticalSection(&cond->waiters_lock);
-
- /*
- * Signal only when there are waiters
- */
- if (have_waiters)
- return ReleaseSemaphore(cond->sema, 1, NULL) ?
- 0 : err_win_to_posix(GetLastError());
- else
- return 0;
-}
-
-/*
- * DOUBLY IMPORTANT: This implementation requires that pthread_cond_broadcast
- * is called while the mutex is held that is used in the corresponding
- * pthread_cond_wait calls!
- */
-int pthread_cond_broadcast(pthread_cond_t *cond)
-{
- EnterCriticalSection(&cond->waiters_lock);
-
- if ((cond->was_broadcast = cond->waiters > 0)) {
- /* wake up all waiters */
- ReleaseSemaphore(cond->sema, cond->waiters, NULL);
- LeaveCriticalSection(&cond->waiters_lock);
- /*
- * At this point all waiters continue. Each one takes its
- * slice of the semaphor. Now it's our turn to wait: Since
- * the external mutex is held, no thread can leave cond_wait,
- * yet. For this reason, we can be sure that no thread gets
- * a chance to eat *more* than one slice. OTOH, it means
- * that the last waiter must send us a wake-up.
- */
- WaitForSingleObject(cond->continue_broadcast, INFINITE);
- /*
- * Since the external mutex is held, no thread can enter
- * cond_wait, and, hence, it is safe to reset this flag
- * without cond->waiters_lock held.
- */
- cond->was_broadcast = 0;
- } else {
- LeaveCriticalSection(&cond->waiters_lock);
- }
- return 0;
-}
diff --git a/compat/win32/pthread.h b/compat/win32/pthread.h
index 8ad187344f..737983d00b 100644
--- a/compat/win32/pthread.h
+++ b/compat/win32/pthread.h
@@ -18,7 +18,10 @@
*/
#define pthread_mutex_t CRITICAL_SECTION
-#define pthread_mutex_init(a,b) (InitializeCriticalSection((a)), 0)
+static inline int return_0(int i) {
+ return 0;
+}
+#define pthread_mutex_init(a,b) return_0((InitializeCriticalSection((a)), 0))
#define pthread_mutex_destroy(a) DeleteCriticalSection((a))
#define pthread_mutex_lock EnterCriticalSection
#define pthread_mutex_unlock LeaveCriticalSection
@@ -29,27 +32,13 @@ typedef int pthread_mutexattr_t;
#define pthread_mutexattr_settype(a, t) 0
#define PTHREAD_MUTEX_RECURSIVE 0
-/*
- * Implement simple condition variable for Windows threads, based on ACE
- * implementation.
- *
- * See original implementation: http://bit.ly/1vkDjo
- * ACE homepage: http://www.cse.wustl.edu/~schmidt/ACE.html
- * See also: http://www.cse.wustl.edu/~schmidt/win32-cv-1.html
- */
-typedef struct {
- LONG waiters;
- int was_broadcast;
- CRITICAL_SECTION waiters_lock;
- HANDLE sema;
- HANDLE continue_broadcast;
-} pthread_cond_t;
-
-extern int pthread_cond_init(pthread_cond_t *cond, const void *unused);
-extern int pthread_cond_destroy(pthread_cond_t *cond);
-extern int pthread_cond_wait(pthread_cond_t *cond, CRITICAL_SECTION *mutex);
-extern int pthread_cond_signal(pthread_cond_t *cond);
-extern int pthread_cond_broadcast(pthread_cond_t *cond);
+#define pthread_cond_t CONDITION_VARIABLE
+
+#define pthread_cond_init(a,b) InitializeConditionVariable((a))
+#define pthread_cond_destroy(a) do {} while (0)
+#define pthread_cond_wait(a,b) return_0(SleepConditionVariableCS((a), (b), INFINITE))
+#define pthread_cond_signal WakeConditionVariable
+#define pthread_cond_broadcast WakeAllConditionVariable
/*
* Simple thread creation implementation using pthread API
@@ -61,8 +50,8 @@ typedef struct {
DWORD tid;
} pthread_t;
-extern int pthread_create(pthread_t *thread, const void *unused,
- void *(*start_routine)(void*), void *arg);
+int pthread_create(pthread_t *thread, const void *unused,
+ void *(*start_routine)(void*), void *arg);
/*
* To avoid the need of copying a struct, we use small macro wrapper to pass
@@ -70,14 +59,14 @@ extern int pthread_create(pthread_t *thread, const void *unused,
*/
#define pthread_join(a, b) win32_pthread_join(&(a), (b))
-extern int win32_pthread_join(pthread_t *thread, void **value_ptr);
+int win32_pthread_join(pthread_t *thread, void **value_ptr);
#define pthread_equal(t1, t2) ((t1).tid == (t2).tid)
-extern pthread_t pthread_self(void);
+pthread_t pthread_self(void);
-static inline int pthread_exit(void *ret)
+static inline void NORETURN pthread_exit(void *ret)
{
- ExitThread((DWORD)ret);
+ ExitThread((DWORD)(intptr_t)ret);
}
typedef DWORD pthread_key_t;
@@ -101,4 +90,11 @@ static inline void *pthread_getspecific(pthread_key_t key)
return TlsGetValue(key);
}
+#ifndef __MINGW64_VERSION_MAJOR
+static inline int pthread_sigmask(int how, const sigset_t *set, sigset_t *oset)
+{
+ return 0;
+}
+#endif
+
#endif /* PTHREAD_H */
diff --git a/compat/win32/syslog.c b/compat/win32/syslog.c
index d015e436d5..161978d720 100644
--- a/compat/win32/syslog.c
+++ b/compat/win32/syslog.c
@@ -28,13 +28,13 @@ void syslog(int priority, const char *fmt, ...)
va_end(ap);
if (str_len < 0) {
- warning("vsnprintf failed: '%s'", strerror(errno));
+ warning_errno("vsnprintf failed");
return;
}
- str = malloc(str_len + 1);
+ str = malloc(st_add(str_len, 1));
if (!str) {
- warning("malloc failed: '%s'", strerror(errno));
+ warning_errno("malloc failed");
return;
}
@@ -43,9 +43,11 @@ void syslog(int priority, const char *fmt, ...)
va_end(ap);
while ((pos = strstr(str, "%1")) != NULL) {
- str = realloc(str, ++str_len + 1);
+ char *oldstr = str;
+ str = realloc(str, st_add(++str_len, 1));
if (!str) {
- warning("realloc failed: '%s'", strerror(errno));
+ free(oldstr);
+ warning_errno("realloc failed");
return;
}
memmove(pos + 2, pos + 1, strlen(pos));
diff --git a/compat/win32/trace2_win32_process_info.c b/compat/win32/trace2_win32_process_info.c
new file mode 100644
index 0000000000..8ccbd1c2c6
--- /dev/null
+++ b/compat/win32/trace2_win32_process_info.c
@@ -0,0 +1,191 @@
+#include "../../cache.h"
+#include "../../json-writer.h"
+#include "lazyload.h"
+#include <Psapi.h>
+#include <tlHelp32.h>
+
+/*
+ * An arbitrarily chosen value to limit the size of the ancestor
+ * array built in git_processes().
+ */
+#define NR_PIDS_LIMIT 10
+
+/*
+ * Find the process data for the given PID in the given snapshot
+ * and update the PROCESSENTRY32 data.
+ */
+static int find_pid(DWORD pid, HANDLE hSnapshot, PROCESSENTRY32 *pe32)
+{
+ pe32->dwSize = sizeof(PROCESSENTRY32);
+
+ if (Process32First(hSnapshot, pe32)) {
+ do {
+ if (pe32->th32ProcessID == pid)
+ return 1;
+ } while (Process32Next(hSnapshot, pe32));
+ }
+ return 0;
+}
+
+/*
+ * Accumulate JSON array of our parent processes:
+ * [
+ * exe-name-parent,
+ * exe-name-grand-parent,
+ * ...
+ * ]
+ *
+ * Note: we only report the filename of the process executable; the
+ * only way to get its full pathname is to use OpenProcess()
+ * and GetModuleFileNameEx() or QueryfullProcessImageName()
+ * and that seems rather expensive (on top of the cost of
+ * getting the snapshot).
+ *
+ * Note: we compute the set of parent processes by walking the PPID
+ * link in each visited PROCESSENTRY32 record. This search
+ * stops when an ancestor process is not found in the snapshot
+ * (because it exited before the current or intermediate parent
+ * process exited).
+ *
+ * This search may compute an incorrect result if the PPID link
+ * refers to the PID of an exited parent and that PID has been
+ * recycled and given to a new unrelated process.
+ *
+ * Worse, it is possible for a child or descendant of the
+ * current process to be given the recycled PID and cause a
+ * PPID-cycle. This would cause an infinite loop building our
+ * parent process array.
+ *
+ * Note: for completeness, the "System Idle" process has PID=0 and
+ * PPID=0 and could cause another PPID-cycle. We don't expect
+ * Git to be a descendant of the idle process, but because of
+ * PID recycling, it might be possible to get a PPID link value
+ * of 0. This too would cause an infinite loop.
+ *
+ * Therefore, we keep an array of the visited PPIDs to guard against
+ * cycles.
+ *
+ * We use a fixed-size array rather than ALLOC_GROW to keep things
+ * simple and avoid the alloc/realloc overhead. It is OK if we
+ * truncate the search and return a partial answer.
+ */
+static void get_processes(struct json_writer *jw, HANDLE hSnapshot)
+{
+ PROCESSENTRY32 pe32;
+ DWORD pid;
+ DWORD pid_list[NR_PIDS_LIMIT];
+ int k, nr_pids = 0;
+
+ pid = GetCurrentProcessId();
+ while (find_pid(pid, hSnapshot, &pe32)) {
+ /* Only report parents. Omit self from the JSON output. */
+ if (nr_pids)
+ jw_array_string(jw, pe32.szExeFile);
+
+ /* Check for cycle in snapshot. (Yes, it happened.) */
+ for (k = 0; k < nr_pids; k++)
+ if (pid == pid_list[k]) {
+ jw_array_string(jw, "(cycle)");
+ return;
+ }
+
+ if (nr_pids == NR_PIDS_LIMIT) {
+ jw_array_string(jw, "(truncated)");
+ return;
+ }
+
+ pid_list[nr_pids++] = pid;
+
+ pid = pe32.th32ParentProcessID;
+ }
+}
+
+/*
+ * Emit JSON data for the current and parent processes. Individual
+ * trace2 targets can decide how to actually print it.
+ */
+static void get_ancestry(void)
+{
+ HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+
+ if (hSnapshot != INVALID_HANDLE_VALUE) {
+ struct json_writer jw = JSON_WRITER_INIT;
+
+ jw_array_begin(&jw, 0);
+ get_processes(&jw, hSnapshot);
+ jw_end(&jw);
+
+ trace2_data_json("process", the_repository, "windows/ancestry",
+ &jw);
+
+ jw_release(&jw);
+ CloseHandle(hSnapshot);
+ }
+}
+
+/*
+ * Is a debugger attached to the current process?
+ *
+ * This will catch debug runs (where the debugger started the process).
+ * This is the normal case. Since this code is called during our startup,
+ * it will not report instances where a debugger is attached dynamically
+ * to a running git process, but that is relatively rare.
+ */
+static void get_is_being_debugged(void)
+{
+ if (IsDebuggerPresent())
+ trace2_data_intmax("process", the_repository,
+ "windows/debugger_present", 1);
+}
+
+/*
+ * Emit JSON data with the peak memory usage of the current process.
+ */
+static void get_peak_memory_info(void)
+{
+ DECLARE_PROC_ADDR(psapi.dll, BOOL, GetProcessMemoryInfo, HANDLE,
+ PPROCESS_MEMORY_COUNTERS, DWORD);
+
+ if (INIT_PROC_ADDR(GetProcessMemoryInfo)) {
+ PROCESS_MEMORY_COUNTERS pmc;
+
+ if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc,
+ sizeof(pmc))) {
+ struct json_writer jw = JSON_WRITER_INIT;
+
+ jw_object_begin(&jw, 0);
+
+#define KV(kv) #kv, (intmax_t)pmc.kv
+
+ jw_object_intmax(&jw, KV(PageFaultCount));
+ jw_object_intmax(&jw, KV(PeakWorkingSetSize));
+ jw_object_intmax(&jw, KV(PeakPagefileUsage));
+
+ jw_end(&jw);
+
+ trace2_data_json("process", the_repository,
+ "windows/memory", &jw);
+ jw_release(&jw);
+ }
+ }
+}
+
+void trace2_collect_process_info(enum trace2_process_info_reason reason)
+{
+ if (!trace2_is_enabled())
+ return;
+
+ switch (reason) {
+ case TRACE2_PROCESS_INFO_STARTUP:
+ get_is_being_debugged();
+ get_ancestry();
+ return;
+
+ case TRACE2_PROCESS_INFO_EXIT:
+ get_peak_memory_info();
+ return;
+
+ default:
+ BUG("trace2_collect_process_info: unknown reason '%d'", reason);
+ }
+}
diff --git a/compat/win32mmap.c b/compat/win32mmap.c
index 61d2ef8e46..519d51f2b6 100644
--- a/compat/win32mmap.c
+++ b/compat/win32mmap.c
@@ -2,37 +2,42 @@
void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset)
{
- HANDLE hmap;
+ HANDLE osfhandle, hmap;
void *temp;
- off_t len;
- struct stat st;
+ LARGE_INTEGER len;
uint64_t o = offset;
uint32_t l = o & 0xFFFFFFFF;
uint32_t h = (o >> 32) & 0xFFFFFFFF;
- if (!fstat(fd, &st))
- len = st.st_size;
- else
+ osfhandle = (HANDLE)_get_osfhandle(fd);
+ if (!GetFileSizeEx(osfhandle, &len))
die("mmap: could not determine filesize");
- if ((length + offset) > len)
- length = xsize_t(len - offset);
+ if ((length + offset) > len.QuadPart)
+ length = xsize_t(len.QuadPart - offset);
if (!(flags & MAP_PRIVATE))
die("Invalid usage of mmap when built with USE_WIN32_MMAP");
- hmap = CreateFileMapping((HANDLE)_get_osfhandle(fd), 0, PAGE_WRITECOPY,
- 0, 0, 0);
+ hmap = CreateFileMapping(osfhandle, NULL,
+ prot == PROT_READ ? PAGE_READONLY : PAGE_WRITECOPY, 0, 0, NULL);
- if (!hmap)
+ if (!hmap) {
+ errno = EINVAL;
return MAP_FAILED;
+ }
- temp = MapViewOfFileEx(hmap, FILE_MAP_COPY, h, l, length, start);
+ temp = MapViewOfFileEx(hmap, prot == PROT_READ ?
+ FILE_MAP_READ : FILE_MAP_COPY, h, l, length, start);
if (!CloseHandle(hmap))
warning("unable to close file mapping handle");
- return temp ? temp : MAP_FAILED;
+ if (temp)
+ return temp;
+
+ errno = GetLastError() == ERROR_COMMITMENT_LIMIT ? EFBIG : EINVAL;
+ return MAP_FAILED;
}
int git_munmap(void *start, size_t length)
diff --git a/compat/winansi.c b/compat/winansi.c
index dedce2104e..c27b20a79d 100644
--- a/compat/winansi.c
+++ b/compat/winansi.c
@@ -2,15 +2,17 @@
* Copyright 2008 Peter Harris <git@peter.is-a-geek.org>
*/
+#undef NOGDI
#include "../git-compat-util.h"
+#include <wingdi.h>
+#include <winreg.h>
+#include "win32.h"
+#include "win32/lazyload.h"
-/*
- Functions to be wrapped:
-*/
-#undef printf
-#undef fprintf
-#undef fputs
-/* TODO: write */
+static int fd_is_interactive[3] = { 0, 0, 0 };
+#define FD_CONSOLE 0x1
+#define FD_SWAPPED 0x2
+#define FD_MSYS 0x4
/*
ANSI codes used by git: m, K
@@ -23,29 +25,130 @@ static HANDLE console;
static WORD plain_attr;
static WORD attr;
static int negative;
+static int non_ascii_used = 0;
+static HANDLE hthread, hread, hwrite;
+static HANDLE hconsole1, hconsole2;
+
+#ifdef __MINGW32__
+#if !defined(__MINGW64_VERSION_MAJOR) || __MINGW64_VERSION_MAJOR < 5
+typedef struct _CONSOLE_FONT_INFOEX {
+ ULONG cbSize;
+ DWORD nFont;
+ COORD dwFontSize;
+ UINT FontFamily;
+ UINT FontWeight;
+ WCHAR FaceName[LF_FACESIZE];
+} CONSOLE_FONT_INFOEX, *PCONSOLE_FONT_INFOEX;
+#endif
+#endif
+
+static void warn_if_raster_font(void)
+{
+ DWORD fontFamily = 0;
+ DECLARE_PROC_ADDR(kernel32.dll, BOOL, GetCurrentConsoleFontEx,
+ HANDLE, BOOL, PCONSOLE_FONT_INFOEX);
+
+ /* don't bother if output was ascii only */
+ if (!non_ascii_used)
+ return;
+
+ /* GetCurrentConsoleFontEx is available since Vista */
+ if (INIT_PROC_ADDR(GetCurrentConsoleFontEx)) {
+ CONSOLE_FONT_INFOEX cfi;
+ cfi.cbSize = sizeof(cfi);
+ if (GetCurrentConsoleFontEx(console, 0, &cfi))
+ fontFamily = cfi.FontFamily;
+ } else {
+ /* pre-Vista: check default console font in registry */
+ HKEY hkey;
+ if (ERROR_SUCCESS == RegOpenKeyExA(HKEY_CURRENT_USER, "Console",
+ 0, KEY_READ, &hkey)) {
+ DWORD size = sizeof(fontFamily);
+ RegQueryValueExA(hkey, "FontFamily", NULL, NULL,
+ (LPVOID) &fontFamily, &size);
+ RegCloseKey(hkey);
+ }
+ }
+
+ if (!(fontFamily & TMPF_TRUETYPE)) {
+ const wchar_t *msg = L"\nWarning: Your console font probably "
+ L"doesn\'t support Unicode. If you experience strange "
+ L"characters in the output, consider switching to a "
+ L"TrueType font such as Consolas!\n";
+ DWORD dummy;
+ WriteConsoleW(console, msg, wcslen(msg), &dummy, NULL);
+ }
+}
-static void init(void)
+static int is_console(int fd)
{
CONSOLE_SCREEN_BUFFER_INFO sbi;
+ DWORD mode;
+ HANDLE hcon;
static int initialized = 0;
- if (initialized)
- return;
- console = GetStdHandle(STD_OUTPUT_HANDLE);
- if (console == INVALID_HANDLE_VALUE)
- console = NULL;
+ /* get OS handle of the file descriptor */
+ hcon = (HANDLE) _get_osfhandle(fd);
+ if (hcon == INVALID_HANDLE_VALUE)
+ return 0;
- if (!console)
- return;
+ /* check if its a device (i.e. console, printer, serial port) */
+ if (GetFileType(hcon) != FILE_TYPE_CHAR)
+ return 0;
- GetConsoleScreenBufferInfo(console, &sbi);
- attr = plain_attr = sbi.wAttributes;
- negative = 0;
+ /* check if its a handle to a console output screen buffer */
+ if (!fd) {
+ if (!GetConsoleMode(hcon, &mode))
+ return 0;
+ /*
+ * This code path is only reached if there is no console
+ * attached to stdout/stderr, i.e. we will not need to output
+ * any text to any console, therefore we might just as well
+ * use black as foreground color.
+ */
+ sbi.wAttributes = 0;
+ } else if (!GetConsoleScreenBufferInfo(hcon, &sbi))
+ return 0;
- initialized = 1;
+ if (fd >= 0 && fd <= 2)
+ fd_is_interactive[fd] |= FD_CONSOLE;
+
+ /* initialize attributes */
+ if (!initialized) {
+ console = hcon;
+ attr = plain_attr = sbi.wAttributes;
+ negative = 0;
+ initialized = 1;
+ }
+
+ return 1;
}
+#define BUFFER_SIZE 4096
+#define MAX_PARAMS 16
+
+static void write_console(unsigned char *str, size_t len)
+{
+ /* only called from console_thread, so a static buffer will do */
+ static wchar_t wbuf[2 * BUFFER_SIZE + 1];
+ DWORD dummy;
+
+ /* convert utf-8 to utf-16 */
+ int wlen = xutftowcsn(wbuf, (char*) str, ARRAY_SIZE(wbuf), len);
+ if (wlen < 0) {
+ wchar_t *err = L"[invalid]";
+ WriteConsoleW(console, err, wcslen(err), &dummy, NULL);
+ return;
+ }
+
+ /* write directly to console */
+ WriteConsoleW(console, wbuf, wlen, &dummy, NULL);
+
+ /* remember if non-ascii characters are printed */
+ if (wlen != len)
+ non_ascii_used = 1;
+}
#define FOREGROUND_ALL (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE)
#define BACKGROUND_ALL (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE)
@@ -90,18 +193,13 @@ static void erase_in_line(void)
&dummy);
}
-
-static const char *set_attr(const char *str)
+static void set_attr(char func, const int *params, int paramlen)
{
- const char *func;
- size_t len = strspn(str, "0123456789;");
- func = str + len;
-
- switch (*func) {
+ int i;
+ switch (func) {
case 'm':
- do {
- long val = strtol(str, (char **)&str, 10);
- switch (val) {
+ for (i = 0; i < paramlen; i++) {
+ switch (params[i]) {
case 0: /* reset */
attr = plain_attr;
negative = 0;
@@ -224,9 +322,7 @@ static const char *set_attr(const char *str)
/* Unsupported code */
break;
}
- str++;
- } while (*(str-1) == ';');
-
+ }
set_console_attr();
break;
case 'K':
@@ -236,122 +332,350 @@ static const char *set_attr(const char *str)
/* Unsupported code */
break;
}
-
- return func + 1;
}
-static int ansi_emulate(const char *str, FILE *stream)
+enum {
+ TEXT = 0, ESCAPE = 033, BRACKET = '['
+};
+
+static DWORD WINAPI console_thread(LPVOID unused)
{
- int rv = 0;
- const char *pos = str;
-
- while (*pos) {
- pos = strstr(str, "\033[");
- if (pos) {
- size_t len = pos - str;
-
- if (len) {
- size_t out_len = fwrite(str, 1, len, stream);
- rv += out_len;
- if (out_len < len)
- return rv;
+ unsigned char buffer[BUFFER_SIZE];
+ DWORD bytes;
+ int start, end = 0, c, parampos = 0, state = TEXT;
+ int params[MAX_PARAMS];
+
+ while (1) {
+ /* read next chunk of bytes from the pipe */
+ if (!ReadFile(hread, buffer + end, BUFFER_SIZE - end, &bytes,
+ NULL)) {
+ /* exit if pipe has been closed or disconnected */
+ if (GetLastError() == ERROR_PIPE_NOT_CONNECTED ||
+ GetLastError() == ERROR_BROKEN_PIPE)
+ break;
+ /* ignore other errors */
+ continue;
+ }
+
+ /* scan the bytes and handle ANSI control codes */
+ bytes += end;
+ start = end = 0;
+ while (end < bytes) {
+ c = buffer[end++];
+ switch (state) {
+ case TEXT:
+ if (c == ESCAPE) {
+ /* print text seen so far */
+ if (end - 1 > start)
+ write_console(buffer + start,
+ end - 1 - start);
+
+ /* then start parsing escape sequence */
+ start = end - 1;
+ memset(params, 0, sizeof(params));
+ parampos = 0;
+ state = ESCAPE;
+ }
+ break;
+
+ case ESCAPE:
+ /* continue if "\033[", otherwise bail out */
+ state = (c == BRACKET) ? BRACKET : TEXT;
+ break;
+
+ case BRACKET:
+ /* parse [0-9;]* into array of parameters */
+ if (c >= '0' && c <= '9') {
+ params[parampos] *= 10;
+ params[parampos] += c - '0';
+ } else if (c == ';') {
+ /*
+ * next parameter, bail out if out of
+ * bounds
+ */
+ parampos++;
+ if (parampos >= MAX_PARAMS)
+ state = TEXT;
+ } else {
+ /*
+ * end of escape sequence, change
+ * console attributes
+ */
+ set_attr(c, params, parampos + 1);
+ start = end;
+ state = TEXT;
+ }
+ break;
}
+ }
- str = pos + 2;
- rv += 2;
+ /* print remaining text unless parsing an escape sequence */
+ if (state == TEXT && end > start) {
+ /* check for incomplete UTF-8 sequences and fix end */
+ if (buffer[end - 1] >= 0x80) {
+ if (buffer[end -1] >= 0xc0)
+ end--;
+ else if (end - 1 > start &&
+ buffer[end - 2] >= 0xe0)
+ end -= 2;
+ else if (end - 2 > start &&
+ buffer[end - 3] >= 0xf0)
+ end -= 3;
+ }
- fflush(stream);
+ /* print remaining complete UTF-8 sequences */
+ if (end > start)
+ write_console(buffer + start, end - start);
- pos = set_attr(str);
- rv += pos - str;
- str = pos;
+ /* move remaining bytes to the front */
+ if (end < bytes)
+ memmove(buffer, buffer + end, bytes - end);
+ end = bytes - end;
} else {
- rv += strlen(str);
- fputs(str, stream);
- return rv;
+ /* all data has been consumed, mark buffer empty */
+ end = 0;
}
}
- return rv;
+
+ /* check if the console font supports unicode */
+ warn_if_raster_font();
+
+ CloseHandle(hread);
+ return 0;
}
-int winansi_fputs(const char *str, FILE *stream)
+static void winansi_exit(void)
{
- int rv;
+ /* flush all streams */
+ _flushall();
- if (!isatty(fileno(stream)))
- return fputs(str, stream);
+ /* signal console thread to exit */
+ FlushFileBuffers(hwrite);
+ DisconnectNamedPipe(hwrite);
- init();
+ /* wait for console thread to copy remaining data */
+ WaitForSingleObject(hthread, INFINITE);
- if (!console)
- return fputs(str, stream);
-
- rv = ansi_emulate(str, stream);
+ /* cleanup handles... */
+ CloseHandle(hwrite);
+ CloseHandle(hthread);
+}
- if (rv >= 0)
- return 0;
- else
- return EOF;
+static void die_lasterr(const char *fmt, ...)
+{
+ va_list params;
+ va_start(params, fmt);
+ errno = err_win_to_posix(GetLastError());
+ die_errno(fmt, params);
+ va_end(params);
}
-static int winansi_vfprintf(FILE *stream, const char *format, va_list list)
+#undef dup2
+int winansi_dup2(int oldfd, int newfd)
{
- int len, rv;
- char small_buf[256];
- char *buf = small_buf;
- va_list cp;
+ int ret = dup2(oldfd, newfd);
- if (!isatty(fileno(stream)))
- goto abort;
+ if (!ret && newfd >= 0 && newfd <= 2)
+ fd_is_interactive[newfd] = oldfd < 0 || oldfd > 2 ?
+ 0 : fd_is_interactive[oldfd];
- init();
+ return ret;
+}
- if (!console)
- goto abort;
+static HANDLE duplicate_handle(HANDLE hnd)
+{
+ HANDLE hresult, hproc = GetCurrentProcess();
+ if (!DuplicateHandle(hproc, hnd, hproc, &hresult, 0, TRUE,
+ DUPLICATE_SAME_ACCESS))
+ die_lasterr("DuplicateHandle(%li) failed",
+ (long) (intptr_t) hnd);
+ return hresult;
+}
- va_copy(cp, list);
- len = vsnprintf(small_buf, sizeof(small_buf), format, cp);
- va_end(cp);
+static HANDLE swap_osfhnd(int fd, HANDLE new_handle)
+{
+ /*
+ * Create a copy of the original handle associated with fd
+ * because the original will get closed when we dup2().
+ */
+ HANDLE handle = (HANDLE)_get_osfhandle(fd);
+ HANDLE duplicate = duplicate_handle(handle);
+
+ /* Create a temp fd associated with the already open "new_handle". */
+ int new_fd = _open_osfhandle((intptr_t)new_handle, O_BINARY);
+
+ assert((fd == 1) || (fd == 2));
+
+ /*
+ * Use stock dup2() to re-bind fd to the new handle. Note that
+ * this will implicitly close(1) and close both fd=1 and the
+ * originally associated handle. It will open a new fd=1 and
+ * call DuplicateHandle() on the handle associated with new_fd.
+ * It is because of this implicit close() that we created the
+ * copy of the original.
+ *
+ * Note that we need to update the cached console handle to the
+ * duplicated one because the dup2() call will implicitly close
+ * the original one.
+ *
+ * Note that dup2() when given target := {0,1,2} will also
+ * call SetStdHandle(), so we don't need to worry about that.
+ */
+ if (console == handle)
+ console = duplicate;
+ dup2(new_fd, fd);
+
+ /* Close the temp fd. This explicitly closes "new_handle"
+ * (because it has been associated with it).
+ */
+ close(new_fd);
+
+ if (fd == 2)
+ setvbuf(stderr, NULL, _IONBF, BUFSIZ);
+ fd_is_interactive[fd] |= FD_SWAPPED;
+
+ return duplicate;
+}
- if (len > sizeof(small_buf) - 1) {
- buf = malloc(len + 1);
- if (!buf)
- goto abort;
+#ifdef DETECT_MSYS_TTY
- len = vsnprintf(buf, len + 1, format, list);
- }
+#include <winternl.h>
- rv = ansi_emulate(buf, stream);
+#if defined(_MSC_VER)
- if (buf != small_buf)
- free(buf);
- return rv;
+typedef struct _OBJECT_NAME_INFORMATION
+{
+ UNICODE_STRING Name;
+ WCHAR NameBuffer[FLEX_ARRAY];
+} OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION;
-abort:
- rv = vfprintf(stream, format, list);
- return rv;
-}
+#define ObjectNameInformation 1
+
+#else
+#include <ntstatus.h>
+#endif
-int winansi_fprintf(FILE *stream, const char *format, ...)
+static void detect_msys_tty(int fd)
{
- va_list list;
- int rv;
+ ULONG result;
+ BYTE buffer[1024];
+ POBJECT_NAME_INFORMATION nameinfo = (POBJECT_NAME_INFORMATION) buffer;
+ PWSTR name;
+
+ /* check if fd is a pipe */
+ HANDLE h = (HANDLE) _get_osfhandle(fd);
+ if (GetFileType(h) != FILE_TYPE_PIPE)
+ return;
- va_start(list, format);
- rv = winansi_vfprintf(stream, format, list);
- va_end(list);
+ /* get pipe name */
+ if (!NT_SUCCESS(NtQueryObject(h, ObjectNameInformation,
+ buffer, sizeof(buffer) - 2, &result)))
+ return;
+ name = nameinfo->Name.Buffer;
+ name[nameinfo->Name.Length / sizeof(*name)] = 0;
+
+ /*
+ * Check if this could be a MSYS2 pty pipe ('msys-XXXX-ptyN-XX')
+ * or a cygwin pty pipe ('cygwin-XXXX-ptyN-XX')
+ */
+ if ((!wcsstr(name, L"msys-") && !wcsstr(name, L"cygwin-")) ||
+ !wcsstr(name, L"-pty"))
+ return;
- return rv;
+ if (fd == 2)
+ setvbuf(stderr, NULL, _IONBF, BUFSIZ);
+ fd_is_interactive[fd] |= FD_MSYS;
}
-int winansi_printf(const char *format, ...)
+#endif
+
+/*
+ * Wrapper for isatty(). Most calls in the main git code
+ * call isatty(1 or 2) to see if the instance is interactive
+ * and should: be colored, show progress, paginate output.
+ * We lie and give results for what the descriptor WAS at
+ * startup (and ignore any pipe redirection we internally
+ * do).
+ */
+#undef isatty
+int winansi_isatty(int fd)
{
- va_list list;
- int rv;
+ if (fd >= 0 && fd <= 2)
+ return fd_is_interactive[fd] != 0;
+ return isatty(fd);
+}
- va_start(list, format);
- rv = winansi_vfprintf(stdout, format, list);
- va_end(list);
+void winansi_init(void)
+{
+ int con1, con2;
+ wchar_t name[32];
+
+ /* check if either stdout or stderr is a console output screen buffer */
+ con1 = is_console(1);
+ con2 = is_console(2);
+
+ /* Also compute console bit for fd 0 even though we don't need the result here. */
+ is_console(0);
+
+ if (!con1 && !con2) {
+#ifdef DETECT_MSYS_TTY
+ /* check if stdin / stdout / stderr are MSYS2 pty pipes */
+ detect_msys_tty(0);
+ detect_msys_tty(1);
+ detect_msys_tty(2);
+#endif
+ return;
+ }
+
+ /* create a named pipe to communicate with the console thread */
+ if (swprintf(name, ARRAY_SIZE(name) - 1, L"\\\\.\\pipe\\winansi%lu",
+ GetCurrentProcessId()) < 0)
+ die("Could not initialize winansi pipe name");
+ hwrite = CreateNamedPipeW(name, PIPE_ACCESS_OUTBOUND,
+ PIPE_TYPE_BYTE | PIPE_WAIT, 1, BUFFER_SIZE, 0, 0, NULL);
+ if (hwrite == INVALID_HANDLE_VALUE)
+ die_lasterr("CreateNamedPipe failed");
+
+ hread = CreateFileW(name, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
+ if (hread == INVALID_HANDLE_VALUE)
+ die_lasterr("CreateFile for named pipe failed");
+
+ /* start console spool thread on the pipe's read end */
+ hthread = CreateThread(NULL, 0, console_thread, NULL, 0, NULL);
+ if (hthread == INVALID_HANDLE_VALUE)
+ die_lasterr("CreateThread(console_thread) failed");
+
+ /* schedule cleanup routine */
+ if (atexit(winansi_exit))
+ die_errno("atexit(winansi_exit) failed");
+
+ /* redirect stdout / stderr to the pipe */
+ if (con1)
+ hconsole1 = swap_osfhnd(1, duplicate_handle(hwrite));
+ if (con2)
+ hconsole2 = swap_osfhnd(2, duplicate_handle(hwrite));
+}
- return rv;
+/*
+ * Returns the real console handle if stdout / stderr is a pipe redirecting
+ * to the console. Allows spawn / exec to pass the console to the next process.
+ */
+HANDLE winansi_get_osfhandle(int fd)
+{
+ HANDLE ret;
+
+ if (fd == 1 && (fd_is_interactive[1] & FD_SWAPPED))
+ return hconsole1;
+ if (fd == 2 && (fd_is_interactive[2] & FD_SWAPPED))
+ return hconsole2;
+
+ ret = (HANDLE)_get_osfhandle(fd);
+
+ /*
+ * There are obviously circumstances under which _get_osfhandle()
+ * returns (HANDLE)-2. This is not documented anywhere, but that is so
+ * clearly an invalid handle value that we can just work around this
+ * and return the correct value for invalid handles.
+ */
+ return ret == (HANDLE)-2 ? INVALID_HANDLE_VALUE : ret;
}