diff options
Diffstat (limited to 'compat')
-rw-r--r-- | compat/mingw.c | 373 | ||||
-rw-r--r-- | compat/mingw.h | 31 | ||||
-rw-r--r-- | compat/obstack.h | 6 | ||||
-rw-r--r-- | compat/poll/poll.c | 31 | ||||
-rw-r--r-- | compat/regex/regex.h | 5 | ||||
-rw-r--r-- | compat/terminal.c | 249 | ||||
-rw-r--r-- | compat/terminal.h | 3 | ||||
-rwxr-xr-x | compat/vcbuild/scripts/clink.pl | 4 | ||||
-rw-r--r-- | compat/win32/path-utils.c | 24 | ||||
-rw-r--r-- | compat/win32/path-utils.h | 5 | ||||
-rw-r--r-- | compat/winansi.c | 12 |
11 files changed, 668 insertions, 75 deletions
diff --git a/compat/mingw.c b/compat/mingw.c index 2f4654c968..d14065d60e 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -13,6 +13,19 @@ 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; + + argv_array_pushl(&cp.args, "mintty", "gdb", NULL); + argv_array_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; @@ -114,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; @@ -212,6 +226,7 @@ enum hide_dotfiles_type { 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; @@ -231,6 +246,15 @@ int mingw_core_config(const char *var, const char *value, void *cb) 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; } @@ -392,6 +416,12 @@ 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); @@ -465,7 +495,7 @@ 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; @@ -473,16 +503,21 @@ int mingw_open (const char *filename, int oflags, ...) mode = va_arg(args, int); va_end(args); - if (filename && !strcmp(filename, "/dev/null")) - filename = "nul"; + 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 (xutftowcs_path(wfilename, filename) < 0) + if (filename && !strcmp(filename, "/dev/null")) + wcscpy(wfilename, L"nul"); + else if (xutftowcs_path(wfilename, filename) < 0) return -1; + fd = open_fn(wfilename, oflags, mode); if (fd < 0 && (oflags & O_ACCMODE) != O_RDONLY && errno == EACCES) { @@ -540,10 +575,17 @@ FILE *mingw_fopen (const char *filename, const char *otype) FILE *file; wchar_t wfilename[MAX_PATH], wotype[4]; if (filename && !strcmp(filename, "/dev/null")) - filename = "nul"; - if (xutftowcs_path(wfilename, filename) < 0 || - xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0) + 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; @@ -562,10 +604,17 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream) FILE *file; wchar_t wfilename[MAX_PATH], wotype[4]; if (filename && !strcmp(filename, "/dev/null")) - filename = "nul"; - if (xutftowcs_path(wfilename, filename) < 0 || - xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0) + 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; @@ -986,16 +1035,16 @@ 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; } char *mingw_getcwd(char *pointer, int len) @@ -1054,7 +1103,7 @@ static const char *quote_arg_msvc(const char *arg) p++; len++; } - if (*p == '"') + if (*p == '"' || !*p) n += count*2 + 1; continue; } @@ -1076,16 +1125,19 @@ static const char *quote_arg_msvc(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; } @@ -1098,13 +1150,14 @@ static const char *quote_arg_msys2(const char *arg) for (p = arg; *p; p++) { int ws = isspace(*p); - if (!ws && *p != '\\' && *p != '"' && *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 (!ws && *p != '{') + if (*p == '\\' || *p == '"') strbuf_addch(&buf, '\\'); p2 = p; } @@ -1114,7 +1167,7 @@ static const char *quote_arg_msys2(const char *arg) else if (!buf.len) return arg; else - strbuf_add(&buf, p2, p - p2), + strbuf_add(&buf, p2, p - p2); strbuf_addch(&buf, '"'); return strbuf_detach(&buf, 0); @@ -1192,7 +1245,7 @@ static char *path_lookup(const char *cmd, int exe_only) int len = strlen(cmd); int isexe = len >= 4 && !strcasecmp(cmd+len-4, ".exe"); - if (strchr(cmd, '/') || strchr(cmd, '\\')) + if (strpbrk(cmd, "/\\")) return xstrdup(cmd); path = mingw_getenv("PATH"); @@ -1371,7 +1424,10 @@ static inline int match_last_path_component(const char *path, size_t *len, static int is_msys2_sh(const char *cmd) { - if (cmd && !strcmp(cmd, "sh")) { + if (!cmd) + return 0; + + if (!strcmp(cmd, "sh")) { static int ret = -1; char *p; @@ -1391,6 +1447,16 @@ static int is_msys2_sh(const char *cmd) } return ret; } + + if (ends_with(cmd, "\\sh.exe")) { + static char *sh; + + if (!sh) + sh = path_lookup("sh", 0); + + return !fspathcmp(cmd, sh); + } + return 0; } @@ -1398,15 +1464,34 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen const char *dir, int prepend_cmd, int fhin, int fhout, int fherr) { - STARTUPINFOW si; + static int restrict_handle_inheritance = -1; + STARTUPINFOEXW si; PROCESS_INFORMATION pi; + 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(*argv) ? quote_arg_msys2 : quote_arg_msvc; + is_msys2_sh(cmd ? cmd : *argv) ? + quote_arg_msys2 : quote_arg_msvc; + + /* 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(); @@ -1435,11 +1520,23 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen CloseHandle(cons); } memset(&si, 0, sizeof(si)); - si.cb = sizeof(si); - si.dwFlags = STARTF_USESTDHANDLES; - si.hStdInput = winansi_get_osfhandle(fhin); - si.hStdOutput = winansi_get_osfhandle(fhout); - si.hStdError = winansi_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'; @@ -1472,16 +1569,98 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen wenvblk = make_environment_block(deltaenv); memset(&pi, 0, sizeof(pi)); - ret = CreateProcessW(*wcmd ? wcmd : NULL, wargs, NULL, NULL, TRUE, - flags, wenvblk, dir ? wdir : NULL, &si, &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; + } + + ret = CreateProcessW(*wcmd ? wcmd : NULL, wargs, NULL, NULL, + stdhandles_count ? TRUE : FALSE, + flags, wenvblk, dir ? wdir : NULL, + &si.StartupInfo, &pi); + + /* + * 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 (si.lpAttributeList) + DeleteProcThreadAttributeList(si.lpAttributeList); + if (attr_list) + HeapFree(GetProcessHeap(), 0, attr_list); free(wenvblk); free(wargs); - if (!ret) { - errno = ENOENT; + if (!ret) return -1; - } + CloseHandle(pi.hThread); /* @@ -2367,6 +2546,128 @@ static void setup_windows_environment(void) } } +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': /* COM<N>, CON, CONIN$, CONOUT$ */ + if ((c = path[++i]) != 'o' && c != 'O') + goto not_a_reserved_name; + c = path[++i]; + if (c == 'm' || c == 'M') { /* COM<N> */ + if (!isdigit(path[++i])) + 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 diff --git a/compat/mingw.h b/compat/mingw.h index 1a46334399..e6fe810ba9 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -453,6 +453,27 @@ char *mingw_query_user_email(void); #endif /** + * 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 @@ -578,6 +599,16 @@ 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. + */ +extern void open_in_gdb(void); + +/* * Used by Pthread API implementation for Windows */ int err_win_to_posix(DWORD winerr); diff --git a/compat/obstack.h b/compat/obstack.h index 01e7c81840..f90a46d9b9 100644 --- a/compat/obstack.h +++ b/compat/obstack.h @@ -135,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> diff --git a/compat/poll/poll.c b/compat/poll/poll.c index 0e95dd493c..afa6d24584 100644 --- a/compat/poll/poll.c +++ b/compat/poll/poll.c @@ -139,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)(void (*)(void)) - GetProcAddress (GetModuleHandleW (L"ntdll.dll"), - "NtQueryInformationFile"); - once_only = TRUE; - } - happened = 0; if (PeekNamedPipe (h, NULL, 0, NULL, &avail, NULL) != 0) { @@ -166,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; diff --git a/compat/regex/regex.h b/compat/regex/regex.h index 08a2609663..2d3412860d 100644 --- a/compat/regex/regex.h +++ b/compat/regex/regex.h @@ -41,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 diff --git a/compat/terminal.c b/compat/terminal.c index fa13ee672d..35bca03d14 100644 --- a/compat/terminal.c +++ b/compat/terminal.c @@ -2,6 +2,9 @@ #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(GIT_WINDOWS_NATIVE) @@ -32,7 +35,7 @@ static void restore_term(void) term_fd = -1; } -static int disable_echo(void) +static int disable_bits(tcflag_t bits) { struct termios t; @@ -43,7 +46,7 @@ static int disable_echo(void) old_term = t; sigchain_push_common(restore_term_on_signal); - t.c_lflag &= ~ECHO; + t.c_lflag &= ~bits; if (!tcsetattr(term_fd, TCSAFLUSH, &t)) return 0; @@ -53,17 +56,44 @@ error: return -1; } +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 void restore_term(void) { + if (use_stty) { + int i; + struct child_process cp = CHILD_PROCESS_INIT; + + if (stty_restore.nr == 0) + return; + + argv_array_push(&cp.args, "stty"); + for (i = 0; i < stty_restore.nr; i++) + argv_array_push(&cp.args, stty_restore.items[i].string); + run_command(&cp); + string_list_clear(&stty_restore, 0); + return; + } + if (hconin == INVALID_HANDLE_VALUE) return; @@ -72,8 +102,39 @@ static void restore_term(void) hconin = INVALID_HANDLE_VALUE; } -static int disable_echo(void) +static int disable_bits(DWORD bits) { + if (use_stty) { + struct child_process cp = CHILD_PROCESS_INIT; + + argv_array_push(&cp.args, "stty"); + + if (bits & ENABLE_LINE_INPUT) { + string_list_append(&stty_restore, "icanon"); + argv_array_push(&cp.args, "-icanon"); + } + + if (bits & ENABLE_ECHO_INPUT) { + string_list_append(&stty_restore, "echo"); + argv_array_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"); + argv_array_push(&cp.args, "ignbrk"); + argv_array_push(&cp.args, "intr"); + argv_array_push(&cp.args, ""); + } + + if (run_command(&cp) == 0) + return 0; + + /* `stty` could not be executed; access the Console directly */ + use_stty = 0; + } + hconin = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); @@ -82,7 +143,7 @@ static int disable_echo(void) GetConsoleMode(hconin, &cmode); sigchain_push_common(restore_term_on_signal); - if (!SetConsoleMode(hconin, cmode & (~ENABLE_ECHO_INPUT))) { + if (!SetConsoleMode(hconin, cmode & ~bits)) { CloseHandle(hconin); hconin = INVALID_HANDLE_VALUE; return -1; @@ -91,6 +152,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 @@ -137,6 +239,126 @@ 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); + + argv_array_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 char *git_terminal_prompt(const char *prompt, int echo) @@ -144,4 +366,23 @@ 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..a9d52b8464 100644 --- a/compat/terminal.h +++ b/compat/terminal.h @@ -3,4 +3,7 @@ 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/vcbuild/scripts/clink.pl b/compat/vcbuild/scripts/clink.pl index ec95a3b2d0..d9f71b7cbb 100755 --- a/compat/vcbuild/scripts/clink.pl +++ b/compat/vcbuild/scripts/clink.pl @@ -45,9 +45,9 @@ while (@ARGV) { } elsif ("$arg" eq "-liconv") { push(@args, "libiconv.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 diff --git a/compat/win32/path-utils.c b/compat/win32/path-utils.c index d9d3641de8..ebf2f12eb6 100644 --- a/compat/win32/path-utils.c +++ b/compat/win32/path-utils.c @@ -1,5 +1,29 @@ #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); diff --git a/compat/win32/path-utils.h b/compat/win32/path-utils.h index 8ed062a6b7..f2e70872cd 100644 --- a/compat/win32/path-utils.h +++ b/compat/win32/path-utils.h @@ -1,8 +1,9 @@ #ifndef WIN32_PATH_UTILS_H #define WIN32_PATH_UTILS_H -#define has_dos_drive_prefix(path) \ - (isalpha(*(path)) && (path)[1] == ':' ? 2 : 0) +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) diff --git a/compat/winansi.c b/compat/winansi.c index 54fd701cbf..c27b20a79d 100644 --- a/compat/winansi.c +++ b/compat/winansi.c @@ -662,10 +662,20 @@ void winansi_init(void) */ 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; - return (HANDLE)_get_osfhandle(fd); + 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; } |