diff options
Diffstat (limited to 'compat')
-rw-r--r-- | compat/bswap.h | 5 | ||||
-rw-r--r-- | compat/cygwin.c | 19 | ||||
-rw-r--r-- | compat/cygwin.h | 2 | ||||
-rw-r--r-- | compat/fileno.c | 7 | ||||
-rw-r--r-- | compat/mingw.c | 664 | ||||
-rw-r--r-- | compat/mingw.h | 99 | ||||
-rw-r--r-- | compat/mmap.c | 2 | ||||
-rw-r--r-- | compat/msvc.h | 8 | ||||
-rw-r--r-- | compat/obstack.c | 17 | ||||
-rw-r--r-- | compat/obstack.h | 18 | ||||
-rw-r--r-- | compat/poll/poll.c | 18 | ||||
-rw-r--r-- | compat/poll/poll.h | 15 | ||||
-rw-r--r-- | compat/precompose_utf8.c | 2 | ||||
-rw-r--r-- | compat/regex/regcomp.c | 8 | ||||
-rw-r--r-- | compat/win32/path-utils.c | 28 | ||||
-rw-r--r-- | compat/win32/path-utils.h | 20 | ||||
-rw-r--r-- | compat/win32/pthread.c | 138 | ||||
-rw-r--r-- | compat/win32/pthread.h | 28 | ||||
-rw-r--r-- | compat/win32/trace2_win32_process_info.c | 147 | ||||
-rw-r--r-- | compat/winansi.c | 12 |
20 files changed, 815 insertions, 442 deletions
diff --git a/compat/bswap.h b/compat/bswap.h index 5078ce5ecc..e4e25735ce 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 @@ -210,3 +213,5 @@ static inline void put_be64(void *ptr, uint64_t value) } #endif + +#endif /* COMPAT_BSWAP_H */ diff --git a/compat/cygwin.c b/compat/cygwin.c deleted file mode 100644 index b9862d606d..0000000000 --- a/compat/cygwin.c +++ /dev/null @@ -1,19 +0,0 @@ -#include "../git-compat-util.h" -#include "../cache.h" - -int cygwin_offset_1st_component(const char *path) -{ - const char *pos = path; - /* unc paths */ - if (is_dir_sep(pos[0]) && is_dir_sep(pos[1])) { - /* skip server name */ - pos = strchr(pos + 2, '/'); - if (!pos) - return 0; /* Error: malformed unc path */ - - do { - pos++; - } while (*pos && pos[0] != '/'); - } - return pos + is_dir_sep(*pos) - path; -} diff --git a/compat/cygwin.h b/compat/cygwin.h deleted file mode 100644 index 8e52de4644..0000000000 --- a/compat/cygwin.h +++ /dev/null @@ -1,2 +0,0 @@ -int cygwin_offset_1st_component(const char *path); -#define offset_1st_component cygwin_offset_1st_component diff --git a/compat/fileno.c b/compat/fileno.c new file mode 100644 index 0000000000..7b105f4cd7 --- /dev/null +++ b/compat/fileno.c @@ -0,0 +1,7 @@ +#define COMPAT_CODE +#include "../git-compat-util.h" + +int git_fileno(FILE *stream) +{ + return fileno(stream); +} diff --git a/compat/mingw.c b/compat/mingw.c index 18caf21969..6b04514cdc 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -5,6 +5,9 @@ #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) @@ -202,6 +205,60 @@ static int ask_yes_no_if_possible(const char *format, ...) } } +/* Windows only */ +enum hide_dotfiles_type { + HIDE_DOTFILES_FALSE = 0, + HIDE_DOTFILES_TRUE, + HIDE_DOTFILES_DOTGITONLY +}; + +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; + } + + 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; @@ -294,7 +351,7 @@ static inline int needs_hiding(const char *path) return 0; /* We cannot use basename(), as it would remove trailing slashes */ - mingw_skip_dos_drive_prefix((char **)&path); + win32_skip_dos_drive_prefix((char **)&path); if (!*path) return 0; @@ -592,9 +649,11 @@ 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; } /** @@ -653,9 +712,9 @@ 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_DATAW findbuf; HANDLE handle = FindFirstFileW(wfilename, &findbuf); @@ -736,6 +795,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); @@ -748,32 +830,31 @@ int mingw_stat(const char *file_name, struct stat *buf) 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) @@ -917,8 +998,29 @@ struct tm *localtime_r(const time_t *timep, struct tm *result) char *mingw_getcwd(char *pointer, int len) { - wchar_t wpointer[MAX_PATH]; - if (!_wgetcwd(wpointer, ARRAY_SIZE(wpointer))) + 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; @@ -927,10 +1029,10 @@ char *mingw_getcwd(char *pointer, int len) } /* - * 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; @@ -985,6 +1087,37 @@ static const char *quote_arg(const char *arg) 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 != '{') + continue; + if (!buf.len) + strbuf_addch(&buf, '"'); + if (p != p2) + strbuf_add(&buf, p2, p - p2); + if (!ws && *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]; @@ -1070,44 +1203,142 @@ static char *path_lookup(const char *cmd, int exe_only) return prog; } -static int do_putenv(char **env, const char *name, int size, int free_old); +static const wchar_t *wcschrnul(const wchar_t *s, wchar_t c) +{ + while (*s && *s != c) + s++; + return s; +} + +/* Compare only keys */ +static int wenvcmp(const void *a, const void *b) +{ + wchar_t *p = *(wchar_t **)a, *q = *(wchar_t **)b; + size_t p_len, q_len; + + /* Find the keys */ + p_len = wcschrnul(p, L'=') - p; + q_len = wcschrnul(q, L'=') - q; -/* used number of elements of environ array, including terminating NULL */ -static int environ_size = 0; -/* allocated size of environ array, in bytes */ -static int environ_alloc = 0; + /* 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); +} + +/* We need a stable sort to convert the environment between UTF-16 <-> UTF-8 */ +#ifndef INTERNAL_QSORT +#include "qsort.c" +#endif /* - * Create environment block suitable for CreateProcess. Merges current - * process environment and the supplied environment changes. + * 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 wchar_t *make_environment_block(char **deltaenv) { - wchar_t *wenvblk = NULL; - char **tmpenv; - int i = 0, size = environ_size, wenvsz = 0, wenvpos = 0; + wchar_t *wenv = GetEnvironmentStringsW(), *wdeltaenv, *result, *p; + size_t wlen, s, delta_size, size; - while (deltaenv && deltaenv[i]) - i++; + wchar_t **array = NULL; + size_t alloc = 0, nr = 0, i; - /* copy the environment, leaving space for changes */ - ALLOC_ARRAY(tmpenv, size + i); - memcpy(tmpenv, environ, size * sizeof(char*)); + size = 1; /* for extra NUL at the end */ - /* merge supplied environment changes into the temporary environment */ - for (i = 0; deltaenv && deltaenv[i]; i++) - size = do_putenv(tmpenv, deltaenv[i], size, 0); + /* 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; + } + + ALLOC_ARRAY(result, size); + memcpy(result, wenv, size * sizeof(*wenv)); + FreeEnvironmentStringsW(wenv); + return result; + } + + /* + * If there is a deltaenv, let's accumulate all keys into `array`, + * sort them using the stable git_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); - /* create environment block from temporary environment */ - for (i = 0; tmpenv[i]; i++) { - size = 2 * strlen(tmpenv[i]) + 2; /* +2 for final \0 */ - ALLOC_GROW(wenvblk, (wenvpos + size) * sizeof(wchar_t), wenvsz); - wenvpos += xutftowcs(&wenvblk[wenvpos], tmpenv[i], size) + 1; + /* 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_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; + memcpy(p, array[i], size * sizeof(*p)); + p += size; + } + *p = L'\0'; + + free(array); + free(wdeltaenv); + FreeEnvironmentStringsW(wenv); + return result; +} + +static void do_unset_environment_variables(void) +{ + 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; } - /* add final \0 terminator */ - wenvblk[wenvpos] = 0; - free(tmpenv); - return wenvblk; } struct pinfo_t { @@ -1118,6 +1349,47 @@ struct pinfo_t { static struct pinfo_t *pinfo = NULL; CRITICAL_SECTION pinfo_cs; +/* 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 && !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; + } + 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) @@ -1128,9 +1400,14 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen 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(*argv) ? quote_arg_msys2 : quote_arg_msvc; + + do_unset_environment_variables(); /* Determine whether or not we are associated to a console */ - HANDLE cons = CreateFile("CONOUT$", GENERIC_WRITE, + cons = CreateFile("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (cons == INVALID_HANDLE_VALUE) { @@ -1274,19 +1551,23 @@ static int try_shell_exec(const char *cmd, char *const *argv) return 0; prog = path_lookup(interpr, 1); if (prog) { + int exec_id; int argc = 0; const char **argv2; while (argv[argc]) argc++; ALLOC_ARRAY(argv2, argc + 1); argv2[0] = (char *)cmd; /* full path to the script file */ memcpy(&argv2[1], &argv[1], sizeof(*argv) * 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); @@ -1299,12 +1580,17 @@ int mingw_execv(const char *cmd, char *const *argv) /* check if git_command is a shell script */ if (!try_shell_exec(cmd, argv)) { int pid, status; + int exec_id; + exec_id = trace2_exec(cmd, (const char **)argv); pid = mingw_spawnv(cmd, (const char **)argv, 0); - if (pid < 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; @@ -1349,87 +1635,83 @@ int mingw_kill(pid_t pid, int sig) } /* - * Compare environment entries by key (i.e. stopping at '=' or '\0'). + * UTF-8 versions of getenv(), putenv() and unsetenv(). + * Internally, they use the CRT's stock UNICODE routines + * to avoid data loss. */ -static int compareenv(const void *v1, const void *v2) +char *mingw_getenv(const char *name) { - const char *e1 = *(const char**)v1; - const char *e2 = *(const char**)v2; +#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]; - for (;;) { - int c1 = *e1++; - int c2 = *e2++; - c1 = (c1 == '=') ? 0 : tolower(c1); - c2 = (c2 == '=') ? 0 : tolower(c2); - if (c1 > c2) - return 1; - if (c1 < c2) - return -1; - if (c1 == 0) - return 0; - } -} + if (!name || !*name) + return NULL; -static int bsearchenv(char **env, const char *name, size_t size) -{ - unsigned low = 0, high = size; - while (low < high) { - unsigned mid = low + ((high - low) >> 1); - int cmp = compareenv(&env[mid], &name); - if (cmp < 0) - low = mid + 1; - else if (cmp > 0) - high = mid; - else - return mid; + 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); + 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 ~low; /* not found, return 1's complement of insert position */ + free(w_key); + + 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); + + /* + * 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; } -/* - * If name contains '=', then sets the variable, otherwise it unsets it - * Size includes the terminating NULL. Env must have room for size + 1 entries - * (in case of insert). Returns the new size. Optionally frees removed entries. - */ -static int do_putenv(char **env, const char *name, int size, int free_old) +int mingw_putenv(const char *namevalue) { - int i = bsearchenv(env, name, size - 1); + int size; + wchar_t *wide, *equal; + BOOL result; - /* optionally free removed / replaced entry */ - if (i >= 0 && free_old) - free(env[i]); + if (!namevalue || !*namevalue) + return 0; - if (strchr(name, '=')) { - /* if new value ('key=value') is specified, insert or replace entry */ - if (i < 0) { - i = ~i; - memmove(&env[i + 1], &env[i], (size - i) * sizeof(char*)); - size++; - } - env[i] = (char*) name; - } else if (i >= 0) { - /* otherwise ('key') remove existing entry */ - size--; - memmove(&env[i], &env[i + 1], (size - i) * sizeof(char*)); + 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); } - return size; -} + free(wide); -char *mingw_getenv(const char *name) -{ - char *value; - int pos = bsearchenv(environ, name, environ_size - 1); - if (pos < 0) - return NULL; - value = strchr(environ[pos], '='); - return value ? &value[1] : NULL; -} + if (!result) + errno = err_win_to_posix(GetLastError()); -int mingw_putenv(const char *namevalue) -{ - ALLOC_GROW(environ, (environ_size + 1) * sizeof(char*), environ_alloc); - environ_size = do_putenv(environ, namevalue, environ_size, 1); - return 0; + return result ? 0 : -1; } /* @@ -1577,7 +1859,8 @@ static void ensure_socket_initialization(void) WSAGetLastError()); for (name = libraries; *name; name++) { - ipv6_dll = LoadLibrary(*name); + ipv6_dll = LoadLibraryExA(*name, NULL, + LOAD_LIBRARY_SEARCH_SYSTEM32); if (!ipv6_dll) continue; @@ -1798,18 +2081,63 @@ 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; + DWORD len; + + if (initialized) + return p; - DWORD len = sizeof(user_name); - if (!GetUserName(user_name, &len)) + len = sizeof(user_name); + if (!GetUserName(user_name, &len)) { + 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; @@ -1856,7 +2184,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) @@ -1967,24 +2295,12 @@ int mingw_raise(int sig) int link(const char *oldpath, const char *newpath) { - typedef BOOL (WINAPI *T)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES); - static T create_hard_link = NULL; 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) { - create_hard_link = (T) GetProcAddress( - GetModuleHandle("kernel32.dll"), "CreateHardLinkW"); - if (!create_hard_link) - create_hard_link = (T)-1; - } - if (create_hard_link == (T)-1) { - errno = ENOSYS; - return -1; - } - if (!create_hard_link(wnewpath, woldpath, NULL)) { + if (!CreateHardLinkW(wnewpath, woldpath, NULL)) { errno = err_win_to_posix(GetLastError()); return -1; } @@ -2043,33 +2359,6 @@ pid_t waitpid(pid_t pid, int *status, int options) return -1; } -int mingw_skip_dos_drive_prefix(char **path) -{ - int ret = has_dos_drive_prefix(*path); - *path += ret; - return ret; -} - -int mingw_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; -} - int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen) { int upos = 0, wpos = 0; @@ -2291,17 +2580,6 @@ void mingw_startup(void) maxlen = wcslen(wargv[0]); for (i = 1; i < argc; i++) maxlen = max(maxlen, wcslen(wargv[i])); - for (i = 0; wenv[i]; i++) - maxlen = max(maxlen, wcslen(wenv[i])); - - /* - * nedmalloc can't free CRT memory, allocate resizable environment - * list. Note that xmalloc / xmemdupz etc. call getenv, so we cannot - * use it while initializing the environment itself. - */ - environ_size = i + 1; - environ_alloc = alloc_nr(environ_size * sizeof(char*)); - environ = malloc_startup(environ_alloc); /* allocate buffer (wchar_t encodes to max 3 UTF-8 bytes) */ maxlen = 3 * maxlen + 1; @@ -2310,17 +2588,13 @@ void mingw_startup(void) /* convert command line arguments and environment to UTF-8 */ for (i = 0; i < argc; i++) __argv[i] = wcstoutfdup_startup(buffer, wargv[i], maxlen); - for (i = 0; wenv[i]; i++) - environ[i] = wcstoutfdup_startup(buffer, wenv[i], maxlen); - environ[i] = NULL; free(buffer); - /* sort environment for O(log n) getenv / putenv */ - qsort(environ, i, sizeof(char*), compareenv); - /* fix Windows specific environment settings */ setup_windows_environment(); + unset_environment_variables = xstrdup("PERL5LIB"); + /* initialize critical section for waitpid pinfo_t list */ InitializeCriticalSection(&pinfo_cs); diff --git a/compat/mingw.h b/compat/mingw.h index 571019d0bd..4d73f8aa9d 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -11,6 +11,9 @@ typedef _sigset_t sigset_t; #undef _POSIX_THREAD_SAFE_FUNCTIONS #endif +extern 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 */ @@ -144,8 +147,7 @@ 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; } @@ -257,11 +259,35 @@ char *mingw_mktemp(char *template); 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 *namevalue); -#define putenv mingw_putenv -#define unsetenv mingw_putenv +int mingw_putenv(const char *name); int mingw_gethostname(char *host, int namelen); #define gethostname mingw_gethostname @@ -327,18 +353,41 @@ static inline int getrlimit(int resource, struct rlimit *rlp) } /* - * Use mingw specific stat()/lstat()/fstat() implementations 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 +struct timespec { + time_t tv_sec; + long tv_nsec; +}; #endif -/* use struct stat with 64 bit st_size */ +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 _stati64 +#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); @@ -351,13 +400,6 @@ int mingw_fstat(int fd, struct stat *buf); #endif #define lstat mingw_lstat -#ifndef _stati64 -# define _stati64(x,y) mingw_stat(x,y) -#elif defined (_USE_32BIT_TIME_T) -# define _stat32i64(x,y) mingw_stat(x,y) -#else -# define _stat64(x,y) mingw_stat(x,y) -#endif int mingw_utime(const char *file_name, const struct utimbuf *times); #define utime mingw_utime @@ -390,6 +432,9 @@ int mingw_raise(int sig); 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); @@ -397,33 +442,15 @@ HANDLE winansi_get_osfhandle(int fd); * git specific compatibility */ -#define has_dos_drive_prefix(path) \ - (isalpha(*(path)) && (path)[1] == ':' ? 2 : 0) -int mingw_skip_dos_drive_prefix(char **path); -#define skip_dos_drive_prefix mingw_skip_dos_drive_prefix -static inline int mingw_is_dir_sep(int c) -{ - return c == '/' || c == '\\'; -} -#define is_dir_sep mingw_is_dir_sep -static inline char *mingw_find_last_dir_sep(const char *path) -{ - char *ret = NULL; - for (; *path; ++path) - if (is_dir_sep(*path)) - ret = (char *)path; - return ret; -} static inline void convert_slashes(char *path) { for (; *path; path++) if (*path == '\\') *path = '/'; } -#define find_last_dir_sep mingw_find_last_dir_sep -int mingw_offset_1st_component(const char *path); -#define offset_1st_component mingw_offset_1st_component #define PATH_SEP ';' +extern 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" diff --git a/compat/mmap.c b/compat/mmap.c index 7f662fef7b..14d31010df 100644 --- a/compat/mmap.c +++ b/compat/mmap.c @@ -4,7 +4,7 @@ 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); diff --git a/compat/msvc.h b/compat/msvc.h index 580bb55bf4..29a8ce8204 100644 --- a/compat/msvc.h +++ b/compat/msvc.h @@ -10,18 +10,12 @@ #define inline __inline #define __inline__ __inline #define __attribute__(x) +#define strcasecmp _stricmp #define strncasecmp _strnicmp #define ftruncate _chsize #define strtoull _strtoui64 #define strtoll _strtoi64 -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); -} - #undef ERROR #include "compat/mingw.h" diff --git a/compat/obstack.c b/compat/obstack.c index 4d1d95beeb..27cd5c1ea1 100644 --- a/compat/obstack.c +++ b/compat/obstack.c @@ -112,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) @@ -159,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; @@ -206,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 6bc24b7644..ced94d0118 100644 --- a/compat/obstack.h +++ b/compat/obstack.h @@ -160,11 +160,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 @@ -235,10 +239,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)) diff --git a/compat/poll/poll.c b/compat/poll/poll.c index 7ed3fbbea1..4459408c7d 100644 --- a/compat/poll/poll.c +++ b/compat/poll/poll.c @@ -18,6 +18,9 @@ You should have received a copy of the GNU General Public License along 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__ # pragma GCC diagnostic ignored "-Wtype-limits" @@ -29,9 +32,6 @@ #include <sys/types.h> -/* Specification. */ -#include <poll.h> - #include <errno.h> #include <limits.h> #include <assert.h> @@ -55,6 +55,9 @@ # include <unistd.h> #endif +/* Specification. */ +#include "poll.h" + #ifdef HAVE_SYS_IOCTL_H # include <sys/ioctl.h> #endif @@ -449,7 +452,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, start = 0, elapsed, orig_timeout = 0; + DWORD ret, wait_timeout, nhandles, orig_timeout = 0; + ULONGLONG start = 0; fd_set rfds, wfds, xfds; BOOL poll_again; MSG msg; @@ -465,7 +469,7 @@ poll (struct pollfd *pfd, nfds_t nfd, int timeout) if (timeout != INFTIM) { orig_timeout = timeout; - start = GetTickCount(); + start = GetTickCount64(); } if (!hEvent) @@ -614,8 +618,8 @@ restart: if (!rc && orig_timeout && timeout != INFTIM) { - elapsed = GetTickCount() - start; - timeout = elapsed >= orig_timeout ? 0 : orig_timeout - elapsed; + ULONGLONG elapsed = GetTickCount64() - start; + timeout = elapsed >= orig_timeout ? 0 : (int)(orig_timeout - elapsed); } if (!rc && timeout) diff --git a/compat/poll/poll.h b/compat/poll/poll.h index cd1995292a..1e1597360f 100644 --- a/compat/poll/poll.h +++ b/compat/poll/poll.h @@ -21,6 +21,21 @@ #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 de61c15d34..136250fbf6 100644 --- a/compat/precompose_utf8.c +++ b/compat/precompose_utf8.c @@ -79,7 +79,7 @@ void precompose_argv(int argc, const char **argv) size_t namelen; oldarg = argv[i]; if (has_non_ascii(oldarg, (size_t)-1, &namelen)) { - newarg = reencode_string_iconv(oldarg, namelen, ic_precompose, NULL); + newarg = reencode_string_iconv(oldarg, namelen, ic_precompose, 0, NULL); if (newarg) argv[i] = newarg; } diff --git a/compat/regex/regcomp.c b/compat/regex/regcomp.c index 51cd60baa3..c0d838834a 100644 --- a/compat/regex/regcomp.c +++ b/compat/regex/regcomp.c @@ -17,6 +17,14 @@ 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); static void re_compile_fastmap_iter (regex_t *bufp, diff --git a/compat/win32/path-utils.c b/compat/win32/path-utils.c new file mode 100644 index 0000000000..d9d3641de8 --- /dev/null +++ b/compat/win32/path-utils.c @@ -0,0 +1,28 @@ +#include "../../git-compat-util.h" + +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..0f70d43920 --- /dev/null +++ b/compat/win32/path-utils.h @@ -0,0 +1,20 @@ +#define has_dos_drive_prefix(path) \ + (isalpha(*(path)) && (path)[1] == ':' ? 2 : 0) +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 +int win32_offset_1st_component(const char *path); +#define offset_1st_component win32_offset_1st_component diff --git a/compat/win32/pthread.c b/compat/win32/pthread.c index e18f5c6e2e..2e7eead42c 100644 --- a/compat/win32/pthread.c +++ b/compat/win32/pthread.c @@ -56,141 +56,3 @@ pthread_t pthread_self(void) 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 1c164088fb..c6cb8dd219 100644 --- a/compat/win32/pthread.h +++ b/compat/win32/pthread.h @@ -32,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 diff --git a/compat/win32/trace2_win32_process_info.c b/compat/win32/trace2_win32_process_info.c new file mode 100644 index 0000000000..52bd62034b --- /dev/null +++ b/compat/win32/trace2_win32_process_info.c @@ -0,0 +1,147 @@ +#include "../../cache.h" +#include "../../json-writer.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); +} + +void trace2_collect_process_info(void) +{ + if (!trace2_is_enabled()) + return; + + get_is_being_debugged(); + get_ancestry(); +} diff --git a/compat/winansi.c b/compat/winansi.c index a11a0f16d2..f4f08237f9 100644 --- a/compat/winansi.c +++ b/compat/winansi.c @@ -474,6 +474,18 @@ static void die_lasterr(const char *fmt, ...) va_end(params); } +#undef dup2 +int winansi_dup2(int oldfd, int newfd) +{ + int ret = dup2(oldfd, newfd); + + if (!ret && newfd >= 0 && newfd <= 2) + fd_is_interactive[newfd] = oldfd < 0 || oldfd > 2 ? + 0 : fd_is_interactive[oldfd]; + + return ret; +} + static HANDLE duplicate_handle(HANDLE hnd) { HANDLE hresult, hproc = GetCurrentProcess(); |