summaryrefslogtreecommitdiff
path: root/compat
diff options
context:
space:
mode:
Diffstat (limited to 'compat')
-rw-r--r--compat/mingw.c281
-rw-r--r--compat/mingw.h13
-rw-r--r--compat/nedmalloc/malloc.c.h6
-rw-r--r--compat/obstack.h8
-rw-r--r--compat/regex/regcomp.c2
-rw-r--r--compat/regex/regex.h7
-rw-r--r--compat/regex/regex_internal.c2
-rw-r--r--compat/regex/regexec.c4
-rw-r--r--compat/terminal.c249
-rw-r--r--compat/terminal.h3
-rw-r--r--compat/vcbuild/find_vs_env.bat2
-rw-r--r--compat/winansi.c12
12 files changed, 531 insertions, 58 deletions
diff --git a/compat/mingw.c b/compat/mingw.c
index bd24d913f9..402c1ad91c 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -114,6 +114,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 +213,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 +233,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;
}
@@ -393,7 +404,7 @@ int mingw_mkdir(const char *path, int mode)
int ret;
wchar_t wpath[MAX_PATH];
- if (!is_valid_win32_path(path)) {
+ if (!is_valid_win32_path(path, 0)) {
errno = EINVAL;
return -1;
}
@@ -479,21 +490,21 @@ int mingw_open (const char *filename, int oflags, ...)
mode = va_arg(args, int);
va_end(args);
- if (!is_valid_win32_path(filename)) {
+ if (!is_valid_win32_path(filename, !create)) {
errno = create ? EINVAL : ENOENT;
return -1;
}
- if (filename && !strcmp(filename, "/dev/null"))
- filename = "nul";
-
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) {
@@ -550,16 +561,18 @@ FILE *mingw_fopen (const char *filename, const char *otype)
int hide = needs_hiding(filename);
FILE *file;
wchar_t wfilename[MAX_PATH], wotype[4];
- if (!is_valid_win32_path(filename)) {
+ if (filename && !strcmp(filename, "/dev/null"))
+ wcscpy(wfilename, L"nul");
+ else if (!is_valid_win32_path(filename, 1)) {
int create = otype && strchr(otype, 'w');
errno = create ? EINVAL : ENOENT;
return NULL;
- }
- if (filename && !strcmp(filename, "/dev/null"))
- filename = "nul";
- if (xutftowcs_path(wfilename, filename) < 0 ||
- xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
+ } 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;
@@ -577,16 +590,18 @@ 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 (!is_valid_win32_path(filename)) {
+ if (filename && !strcmp(filename, "/dev/null"))
+ wcscpy(wfilename, L"nul");
+ else if (!is_valid_win32_path(filename, 1)) {
int create = otype && strchr(otype, 'w');
errno = create ? EINVAL : ENOENT;
return NULL;
- }
- if (filename && !strcmp(filename, "/dev/null"))
- filename = "nul";
- if (xutftowcs_path(wfilename, filename) < 0 ||
- xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
+ } 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;
@@ -1007,16 +1022,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)
@@ -1436,8 +1451,13 @@ 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;
@@ -1447,6 +1467,19 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
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();
/* Determine whether or not we are associated to a console */
@@ -1474,11 +1507,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';
@@ -1511,16 +1556,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);
/*
@@ -1605,7 +1732,7 @@ static int try_shell_exec(const char *cmd, char *const *argv)
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);
+ COPY_ARRAY(&argv2[1], &argv[1], argc);
exec_id = trace2_exec(prog, argv2);
pid = mingw_spawnv(prog, argv2, 1);
if (pid >= 0) {
@@ -2406,14 +2533,16 @@ static void setup_windows_environment(void)
}
}
-int is_valid_win32_path(const char *path)
+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++);
@@ -2428,7 +2557,83 @@ int is_valid_win32_path(const char *path)
return 1;
i = periods = preceding_space_or_period = 0;
- continue;
+
+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 */
diff --git a/compat/mingw.h b/compat/mingw.h
index 04ca731a6b..714bc1d591 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -461,10 +461,17 @@ char *mingw_query_user_email(void);
*
* - 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);
-#define is_valid_path(path) is_valid_win32_path(path)
+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.
@@ -572,7 +579,7 @@ int xwcstoutf(char *utf, const wchar_t *wcs, size_t utflen);
/*
* A critical section used in the implementation of the spawn
- * functions (mingw_spawnv[p]e()) and waitpid(). Intialised in
+ * functions (mingw_spawnv[p]e()) and waitpid(). Initialised in
* the replacement main() macro below.
*/
extern CRITICAL_SECTION pinfo_cs;
diff --git a/compat/nedmalloc/malloc.c.h b/compat/nedmalloc/malloc.c.h
index 9134349590..814845d4b3 100644
--- a/compat/nedmalloc/malloc.c.h
+++ b/compat/nedmalloc/malloc.c.h
@@ -1564,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;
@@ -1655,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 */
@@ -2485,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
diff --git a/compat/obstack.h b/compat/obstack.h
index ae36ed6a66..f90a46d9b9 100644
--- a/compat/obstack.h
+++ b/compat/obstack.h
@@ -79,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
@@ -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/regex/regcomp.c b/compat/regex/regcomp.c
index c0d838834a..d1bc09e49b 100644
--- a/compat/regex/regcomp.c
+++ b/compat/regex/regcomp.c
@@ -3462,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)
diff --git a/compat/regex/regex.h b/compat/regex/regex.h
index 4d81358a83..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
@@ -322,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 59bf151336..ec51cf3446 100644
--- a/compat/regex/regex_internal.c
+++ b/compat/regex/regex_internal.c
@@ -1616,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/regexec.c b/compat/regex/regexec.c
index 1b5d89fd5e..49358ae475 100644
--- a/compat/regex/regexec.c
+++ b/compat/regex/regexec.c
@@ -2420,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
@@ -3347,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
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/find_vs_env.bat b/compat/vcbuild/find_vs_env.bat
index 40194dd230..b35d264c0e 100644
--- a/compat/vcbuild/find_vs_env.bat
+++ b/compat/vcbuild/find_vs_env.bat
@@ -18,7 +18,7 @@ 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" commmand prompt setup script, installs
+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
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;
}