diff options
Diffstat (limited to 'compat')
-rw-r--r-- | compat/mingw.c | 688 | ||||
-rw-r--r-- | compat/mingw.h | 156 | ||||
-rw-r--r-- | compat/poll/poll.c | 2 | ||||
-rw-r--r-- | compat/win32/alloca.h (renamed from compat/vcbuild/include/alloca.h) | 0 | ||||
-rw-r--r-- | compat/win32/dirent.c | 116 | ||||
-rw-r--r-- | compat/win32/dirent.h | 8 | ||||
-rw-r--r-- | compat/winansi.c | 450 |
7 files changed, 1018 insertions, 402 deletions
diff --git a/compat/mingw.c b/compat/mingw.c index 363a957a1d..c5c37e53ce 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -1,8 +1,10 @@ #include "../git-compat-util.h" #include "win32.h" #include <conio.h> +#include <wchar.h> #include "../strbuf.h" #include "../run-command.h" +#include "../cache.h" static const int delay[] = { 0, 1, 10, 20, 40 }; @@ -198,14 +200,16 @@ static int ask_yes_no_if_possible(const char *format, ...) } } -#undef unlink int mingw_unlink(const char *pathname) { int ret, tries = 0; + wchar_t wpathname[MAX_PATH]; + if (xutftowcs_path(wpathname, pathname) < 0) + return -1; /* read-only files cannot be removed */ - chmod(pathname, 0666); - while ((ret = unlink(pathname)) == -1 && tries < ARRAY_SIZE(delay)) { + _wchmod(wpathname, 0666); + while ((ret = _wunlink(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) { if (!is_file_in_use_error(GetLastError())) break; /* @@ -221,45 +225,45 @@ int mingw_unlink(const char *pathname) while (ret == -1 && is_file_in_use_error(GetLastError()) && ask_yes_no_if_possible("Unlink of file '%s' failed. " "Should I try again?", pathname)) - ret = unlink(pathname); + ret = _wunlink(wpathname); return ret; } -static int is_dir_empty(const char *path) +static int is_dir_empty(const wchar_t *wpath) { - struct strbuf buf = STRBUF_INIT; - WIN32_FIND_DATAA findbuf; + WIN32_FIND_DATAW findbuf; HANDLE handle; - - strbuf_addf(&buf, "%s\\*", path); - handle = FindFirstFileA(buf.buf, &findbuf); - if (handle == INVALID_HANDLE_VALUE) { - strbuf_release(&buf); + wchar_t wbuf[MAX_PATH + 2]; + wcscpy(wbuf, wpath); + wcscat(wbuf, L"\\*"); + handle = FindFirstFileW(wbuf, &findbuf); + if (handle == INVALID_HANDLE_VALUE) return GetLastError() == ERROR_NO_MORE_FILES; - } - while (!strcmp(findbuf.cFileName, ".") || - !strcmp(findbuf.cFileName, "..")) - if (!FindNextFile(handle, &findbuf)) { - strbuf_release(&buf); - return GetLastError() == ERROR_NO_MORE_FILES; + while (!wcscmp(findbuf.cFileName, L".") || + !wcscmp(findbuf.cFileName, L"..")) + if (!FindNextFileW(handle, &findbuf)) { + DWORD err = GetLastError(); + FindClose(handle); + return err == ERROR_NO_MORE_FILES; } FindClose(handle); - strbuf_release(&buf); return 0; } -#undef rmdir int mingw_rmdir(const char *pathname) { int ret, tries = 0; + wchar_t wpathname[MAX_PATH]; + if (xutftowcs_path(wpathname, pathname) < 0) + return -1; - while ((ret = rmdir(pathname)) == -1 && tries < ARRAY_SIZE(delay)) { + while ((ret = _wrmdir(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) { if (!is_file_in_use_error(GetLastError())) errno = err_win_to_posix(GetLastError()); if (errno != EACCES) break; - if (!is_dir_empty(pathname)) { + if (!is_dir_empty(wpathname)) { errno = ENOTEMPTY; break; } @@ -276,16 +280,26 @@ int mingw_rmdir(const char *pathname) while (ret == -1 && errno == EACCES && is_file_in_use_error(GetLastError()) && ask_yes_no_if_possible("Deletion of directory '%s' failed. " "Should I try again?", pathname)) - ret = rmdir(pathname); + ret = _wrmdir(wpathname); + return ret; +} + +int mingw_mkdir(const char *path, int mode) +{ + int ret; + wchar_t wpath[MAX_PATH]; + if (xutftowcs_path(wpath, path) < 0) + return -1; + ret = _wmkdir(wpath); return ret; } -#undef open int mingw_open (const char *filename, int oflags, ...) { va_list args; unsigned mode; int fd; + wchar_t wfilename[MAX_PATH]; va_start(args, oflags); mode = va_arg(args, int); @@ -294,10 +308,12 @@ int mingw_open (const char *filename, int oflags, ...) if (filename && !strcmp(filename, "/dev/null")) filename = "nul"; - fd = open(filename, oflags, mode); + if (xutftowcs_path(wfilename, filename) < 0) + return -1; + fd = _wopen(wfilename, oflags, mode); if (fd < 0 && (oflags & O_CREAT) && errno == EACCES) { - DWORD attrs = GetFileAttributes(filename); + DWORD attrs = GetFileAttributesW(wfilename); if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY)) errno = EISDIR; } @@ -332,17 +348,28 @@ int mingw_fgetc(FILE *stream) #undef fopen 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"; - return fopen(filename, otype); + if (xutftowcs_path(wfilename, filename) < 0 || + xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0) + return NULL; + file = _wfopen(wfilename, wotype); + return file; } -#undef freopen 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"; - return freopen(filename, otype, stream); + if (xutftowcs_path(wfilename, filename) < 0 || + xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0) + return NULL; + file = _wfreopen(wfilename, wotype, stream); + return file; } #undef fflush @@ -367,6 +394,31 @@ int mingw_fflush(FILE *stream) return ret; } +int mingw_access(const char *filename, int mode) +{ + wchar_t wfilename[MAX_PATH]; + if (xutftowcs_path(wfilename, filename) < 0) + return -1; + /* X_OK is not supported by the MSVCRT version */ + return _waccess(wfilename, mode & ~X_OK); +} + +int mingw_chdir(const char *dirname) +{ + wchar_t wdirname[MAX_PATH]; + if (xutftowcs_path(wdirname, dirname) < 0) + return -1; + return _wchdir(wdirname); +} + +int mingw_chmod(const char *filename, int mode) +{ + wchar_t wfilename[MAX_PATH]; + if (xutftowcs_path(wfilename, filename) < 0) + return -1; + return _wchmod(wfilename, mode); +} + /* * The unit of FILETIME is 100-nanoseconds since January 1, 1601, UTC. * Returns the 100-nanoseconds ("hekto nanoseconds") since the epoch. @@ -392,10 +444,12 @@ static inline time_t filetime_to_time_t(const FILETIME *ft) */ static int do_lstat(int follow, const char *file_name, struct stat *buf) { - int err; WIN32_FILE_ATTRIBUTE_DATA fdata; + wchar_t wfilename[MAX_PATH]; + if (xutftowcs_path(wfilename, file_name) < 0) + return -1; - if (!(err = get_file_attr(file_name, &fdata))) { + if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) { buf->st_ino = 0; buf->st_gid = 0; buf->st_uid = 0; @@ -408,8 +462,8 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf) buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime)); buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime)); if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { - WIN32_FIND_DATAA findbuf; - HANDLE handle = FindFirstFileA(file_name, &findbuf); + WIN32_FIND_DATAW findbuf; + HANDLE handle = FindFirstFileW(wfilename, &findbuf); if (handle != INVALID_HANDLE_VALUE) { if ((findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && (findbuf.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) { @@ -428,7 +482,23 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf) } return 0; } - errno = err; + switch (GetLastError()) { + case ERROR_ACCESS_DENIED: + case ERROR_SHARING_VIOLATION: + case ERROR_LOCK_VIOLATION: + case ERROR_SHARING_BUFFER_EXCEEDED: + errno = EACCES; + break; + case ERROR_BUFFER_OVERFLOW: + errno = ENAMETOOLONG; + break; + case ERROR_NOT_ENOUGH_MEMORY: + errno = ENOMEM; + break; + default: + errno = ENOENT; + break; + } return -1; } @@ -441,7 +511,7 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf) static int do_stat_internal(int follow, const char *file_name, struct stat *buf) { int namelen; - static char alt_name[PATH_MAX]; + char alt_name[PATH_MAX]; if (!do_lstat(follow, file_name, buf)) return 0; @@ -516,16 +586,20 @@ int mingw_utime (const char *file_name, const struct utimbuf *times) { FILETIME mft, aft; int fh, rc; + DWORD attrs; + wchar_t wfilename[MAX_PATH]; + if (xutftowcs_path(wfilename, file_name) < 0) + return -1; /* must have write permission */ - DWORD attrs = GetFileAttributes(file_name); + attrs = GetFileAttributesW(wfilename); if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_READONLY)) { /* ignore errors here; open() will report them */ - SetFileAttributes(file_name, attrs & ~FILE_ATTRIBUTE_READONLY); + SetFileAttributesW(wfilename, attrs & ~FILE_ATTRIBUTE_READONLY); } - if ((fh = open(file_name, O_RDWR | O_BINARY)) < 0) { + if ((fh = _wopen(wfilename, O_RDWR | O_BINARY)) < 0) { rc = -1; goto revert_attrs; } @@ -548,7 +622,7 @@ revert_attrs: if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_READONLY)) { /* ignore errors again */ - SetFileAttributes(file_name, attrs); + SetFileAttributesW(wfilename, attrs); } return rc; } @@ -559,6 +633,18 @@ unsigned int sleep (unsigned int seconds) return 0; } +char *mingw_mktemp(char *template) +{ + wchar_t wtemplate[MAX_PATH]; + if (xutftowcs_path(wtemplate, template) < 0) + return NULL; + if (!_wmktemp(wtemplate)) + return NULL; + if (xwcstoutf(template, wtemplate, strlen(template) + 1) < 0) + return NULL; + return template; +} + int mkstemp(char *template) { char *filename = mktemp(template); @@ -617,17 +703,18 @@ struct tm *localtime_r(const time_t *timep, struct tm *result) return result; } -#undef getcwd char *mingw_getcwd(char *pointer, int len) { int i; - char *ret = getcwd(pointer, len); - if (!ret) - return ret; + wchar_t wpointer[MAX_PATH]; + if (!_wgetcwd(wpointer, ARRAY_SIZE(wpointer))) + return NULL; + if (xwcstoutf(pointer, wpointer, len) < 0) + return NULL; for (i = 0; pointer[i]; i++) if (pointer[i] == '\\') pointer[i] = '/'; - return ret; + return pointer; } /* @@ -812,11 +899,44 @@ static char *path_lookup(const char *cmd, char **path, int exe_only) return prog; } -static int env_compare(const void *a, const void *b) +static int do_putenv(char **env, const char *name, int size, int free_old); + +/* 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; + +/* + * Create environment block suitable for CreateProcess. Merges current + * process environment and the supplied environment changes. + */ +static wchar_t *make_environment_block(char **deltaenv) { - char *const *ea = a; - char *const *eb = b; - return strcasecmp(*ea, *eb); + wchar_t *wenvblk = NULL; + char **tmpenv; + int i = 0, size = environ_size, wenvsz = 0, wenvpos = 0; + + while (deltaenv && deltaenv[i]) + i++; + + /* copy the environment, leaving space for changes */ + tmpenv = xmalloc((size + i) * sizeof(char*)); + memcpy(tmpenv, environ, size * sizeof(char*)); + + /* merge supplied environment changes into the temporary environment */ + for (i = 0; deltaenv && deltaenv[i]; i++) + size = do_putenv(tmpenv, deltaenv[i], size, 0); + + /* 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; + } + /* add final \0 terminator */ + wenvblk[wenvpos] = 0; + free(tmpenv); + return wenvblk; } struct pinfo_t { @@ -827,14 +947,15 @@ struct pinfo_t { static struct pinfo_t *pinfo = NULL; CRITICAL_SECTION pinfo_cs; -static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env, +static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaenv, const char *dir, int prepend_cmd, int fhin, int fhout, int fherr) { - STARTUPINFO si; + STARTUPINFOW si; PROCESS_INFORMATION pi; - struct strbuf envblk, args; - unsigned flags; + struct strbuf args; + wchar_t wcmd[MAX_PATH], wdir[MAX_PATH], *wargs, *wenvblk = NULL; + unsigned flags = CREATE_UNICODE_ENVIRONMENT; BOOL ret; /* Determine whether or not we are associated to a console */ @@ -851,7 +972,7 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env, * instead of CREATE_NO_WINDOW to make ssh * recognize that it has no console. */ - flags = DETACHED_PROCESS; + flags |= DETACHED_PROCESS; } else { /* There is already a console. If we specified * DETACHED_PROCESS here, too, Windows would @@ -859,15 +980,19 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env, * The same is true for CREATE_NO_WINDOW. * Go figure! */ - flags = 0; CloseHandle(cons); } memset(&si, 0, sizeof(si)); si.cb = sizeof(si); si.dwFlags = STARTF_USESTDHANDLES; - si.hStdInput = (HANDLE) _get_osfhandle(fhin); - si.hStdOutput = (HANDLE) _get_osfhandle(fhout); - si.hStdError = (HANDLE) _get_osfhandle(fherr); + si.hStdInput = winansi_get_osfhandle(fhin); + si.hStdOutput = winansi_get_osfhandle(fhout); + si.hStdError = winansi_get_osfhandle(fherr); + + if (xutftowcs_path(wcmd, cmd) < 0) + return -1; + if (dir && xutftowcs_path(wdir, dir) < 0) + return -1; /* concatenate argv, quoting args as we go */ strbuf_init(&args, 0); @@ -886,33 +1011,18 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env, free(quoted); } - if (env) { - int count = 0; - char **e, **sorted_env; - - for (e = env; *e; e++) - count++; - - /* environment must be sorted */ - sorted_env = xmalloc(sizeof(*sorted_env) * (count + 1)); - memcpy(sorted_env, env, sizeof(*sorted_env) * (count + 1)); - qsort(sorted_env, count, sizeof(*sorted_env), env_compare); + wargs = xmalloc((2 * args.len + 1) * sizeof(wchar_t)); + xutftowcs(wargs, args.buf, 2 * args.len + 1); + strbuf_release(&args); - strbuf_init(&envblk, 0); - for (e = sorted_env; *e; e++) { - strbuf_addstr(&envblk, *e); - strbuf_addch(&envblk, '\0'); - } - free(sorted_env); - } + wenvblk = make_environment_block(deltaenv); memset(&pi, 0, sizeof(pi)); - ret = CreateProcess(cmd, args.buf, NULL, NULL, TRUE, flags, - env ? envblk.buf : NULL, dir, &si, &pi); + ret = CreateProcessW(wcmd, wargs, NULL, NULL, TRUE, flags, + wenvblk, dir ? wdir : NULL, &si, &pi); - if (env) - strbuf_release(&envblk); - strbuf_release(&args); + free(wenvblk); + free(wargs); if (!ret) { errno = ENOENT; @@ -941,13 +1051,12 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env, return (pid_t)pi.dwProcessId; } -static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env, - int prepend_cmd) +static pid_t mingw_spawnv(const char *cmd, const char **argv, int prepend_cmd) { - return mingw_spawnve_fd(cmd, argv, env, NULL, prepend_cmd, 0, 1, 2); + return mingw_spawnve_fd(cmd, argv, NULL, NULL, prepend_cmd, 0, 1, 2); } -pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env, +pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **deltaenv, const char *dir, int fhin, int fhout, int fherr) { @@ -971,14 +1080,14 @@ pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env, pid = -1; } else { - pid = mingw_spawnve_fd(iprog, argv, env, dir, 1, + pid = mingw_spawnve_fd(iprog, argv, deltaenv, dir, 1, fhin, fhout, fherr); free(iprog); } argv[0] = argv0; } else - pid = mingw_spawnve_fd(prog, argv, env, dir, 0, + pid = mingw_spawnve_fd(prog, argv, deltaenv, dir, 0, fhin, fhout, fherr); free(prog); } @@ -986,7 +1095,7 @@ pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env, return pid; } -static int try_shell_exec(const char *cmd, char *const *argv, char **env) +static int try_shell_exec(const char *cmd, char *const *argv) { const char *interpr = parse_interpreter(cmd); char **path; @@ -1004,7 +1113,7 @@ static int try_shell_exec(const char *cmd, char *const *argv, char **env) argv2 = xmalloc(sizeof(*argv) * (argc+1)); argv2[0] = (char *)cmd; /* full path to the script file */ memcpy(&argv2[1], &argv[1], sizeof(*argv) * argc); - pid = mingw_spawnve(prog, argv2, env, 1); + pid = mingw_spawnv(prog, argv2, 1); if (pid >= 0) { int status; if (waitpid(pid, &status, 0) < 0) @@ -1019,19 +1128,20 @@ static int try_shell_exec(const char *cmd, char *const *argv, char **env) return pid; } -static void mingw_execve(const char *cmd, char *const *argv, char *const *env) +int mingw_execv(const char *cmd, char *const *argv) { /* check if git_command is a shell script */ - if (!try_shell_exec(cmd, argv, (char **)env)) { + if (!try_shell_exec(cmd, argv)) { int pid, status; - pid = mingw_spawnve(cmd, (const char **)argv, (char **)env, 0); + pid = mingw_spawnv(cmd, (const char **)argv, 0); if (pid < 0) - return; + return -1; if (waitpid(pid, &status, 0) < 0) status = 255; exit(status); } + return -1; } int mingw_execvp(const char *cmd, char *const *argv) @@ -1040,7 +1150,7 @@ int mingw_execvp(const char *cmd, char *const *argv) char *prog = path_lookup(cmd, path, 0); if (prog) { - mingw_execve(prog, argv, environ); + mingw_execv(prog, argv); free(prog); } else errno = ENOENT; @@ -1049,12 +1159,6 @@ int mingw_execvp(const char *cmd, char *const *argv) return -1; } -int mingw_execv(const char *cmd, char *const *argv) -{ - mingw_execve(cmd, argv, environ); - return -1; -} - int mingw_kill(pid_t pid, int sig) { if (pid > 0 && sig == SIGTERM) { @@ -1080,108 +1184,88 @@ int mingw_kill(pid_t pid, int sig) return -1; } -static char **copy_environ(void) -{ - char **env; - int i = 0; - while (environ[i]) - i++; - env = xmalloc((i+1)*sizeof(*env)); - for (i = 0; environ[i]; i++) - env[i] = xstrdup(environ[i]); - env[i] = NULL; - return env; -} - -void free_environ(char **env) -{ - int i; - for (i = 0; env[i]; i++) - free(env[i]); - free(env); +/* + * Compare environment entries by key (i.e. stopping at '=' or '\0'). + */ +static int compareenv(const void *v1, const void *v2) +{ + const char *e1 = *(const char**)v1; + const char *e2 = *(const char**)v2; + + 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; + } } -static int lookup_env(char **env, const char *name, size_t nmln) +static int bsearchenv(char **env, const char *name, size_t size) { - int i; - - for (i = 0; env[i]; i++) { - if (0 == strncmp(env[i], name, nmln) - && '=' == env[i][nmln]) - /* matches */ - return i; + 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; } - return -1; + return ~low; /* not found, return 1's complement of insert position */ } /* * 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 char **env_setenv(char **env, const char *name) +static int do_putenv(char **env, const char *name, int size, int free_old) { - char *eq = strchrnul(name, '='); - int i = lookup_env(env, name, eq-name); + int i = bsearchenv(env, name, size - 1); - if (i < 0) { - if (*eq) { - for (i = 0; env[i]; i++) - ; - env = xrealloc(env, (i+2)*sizeof(*env)); - env[i] = xstrdup(name); - env[i+1] = NULL; - } - } - else { + /* optionally free removed / replaced entry */ + if (i >= 0 && free_old) free(env[i]); - if (*eq) - env[i] = xstrdup(name); - else - for (; env[i]; i++) - env[i] = env[i+1]; - } - return env; -} -/* - * Copies global environ and adjusts variables as specified by vars. - */ -char **make_augmented_environ(const char *const *vars) -{ - char **env = copy_environ(); - - while (*vars) - env = env_setenv(env, *vars++); - return env; + 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*)); + } + return size; } -#undef getenv - -/* - * The system's getenv looks up the name in a case-insensitive manner. - * This version tries a case-sensitive lookup and falls back to - * case-insensitive if nothing was found. This is necessary because, - * as a prominent example, CMD sets 'Path', but not 'PATH'. - * Warning: not thread-safe. - */ -static char *getenv_cs(const char *name) +char *mingw_getenv(const char *name) { - size_t len = strlen(name); - int i = lookup_env(environ, name, len); - if (i >= 0) - return environ[i] + len + 1; /* skip past name and '=' */ - return getenv(name); + char *value; + int pos = bsearchenv(environ, name, environ_size - 1); + if (pos < 0) + return NULL; + value = strchr(environ[pos], '='); + return value ? &value[1] : NULL; } -char *mingw_getenv(const char *name) +int mingw_putenv(const char *namevalue) { - char *result = getenv_cs(name); - if (!result && !strcmp(name, "TMPDIR")) { - /* on Windows it is TMP and TEMP */ - result = getenv_cs("TMP"); - if (!result) - result = getenv_cs("TEMP"); - } - return result; + ALLOC_GROW(environ, (environ_size + 1) * sizeof(char*), environ_alloc); + environ_size = do_putenv(environ, namevalue, environ_size, 1); + return 0; } /* @@ -1480,33 +1564,36 @@ int mingw_rename(const char *pold, const char *pnew) { DWORD attrs, gle; int tries = 0; + wchar_t wpold[MAX_PATH], wpnew[MAX_PATH]; + if (xutftowcs_path(wpold, pold) < 0 || xutftowcs_path(wpnew, pnew) < 0) + return -1; /* * Try native rename() first to get errno right. * It is based on MoveFile(), which cannot overwrite existing files. */ - if (!rename(pold, pnew)) + if (!_wrename(wpold, wpnew)) return 0; if (errno != EEXIST) return -1; repeat: - if (MoveFileEx(pold, pnew, MOVEFILE_REPLACE_EXISTING)) + if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING)) return 0; /* TODO: translate more errors */ gle = GetLastError(); if (gle == ERROR_ACCESS_DENIED && - (attrs = GetFileAttributes(pnew)) != INVALID_FILE_ATTRIBUTES) { + (attrs = GetFileAttributesW(wpnew)) != INVALID_FILE_ATTRIBUTES) { if (attrs & FILE_ATTRIBUTE_DIRECTORY) { errno = EISDIR; return -1; } if ((attrs & FILE_ATTRIBUTE_READONLY) && - SetFileAttributes(pnew, attrs & ~FILE_ATTRIBUTE_READONLY)) { - if (MoveFileEx(pold, pnew, MOVEFILE_REPLACE_EXISTING)) + SetFileAttributesW(wpnew, attrs & ~FILE_ATTRIBUTE_READONLY)) { + if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING)) return 0; gle = GetLastError(); /* revert file attributes on failure */ - SetFileAttributes(pnew, attrs); + SetFileAttributesW(wpnew, attrs); } } if (tries < ARRAY_SIZE(delay) && gle == ERROR_ACCESS_DENIED) { @@ -1752,11 +1839,16 @@ void mingw_open_html(const char *unixpath) int link(const char *oldpath, const char *newpath) { - typedef BOOL (WINAPI *T)(const char*, const char*, LPSECURITY_ATTRIBUTES); + 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"), "CreateHardLinkA"); + GetModuleHandle("kernel32.dll"), "CreateHardLinkW"); if (!create_hard_link) create_hard_link = (T)-1; } @@ -1764,7 +1856,7 @@ int link(const char *oldpath, const char *newpath) errno = ENOSYS; return -1; } - if (!create_hard_link(newpath, oldpath, NULL)) { + if (!create_hard_link(wnewpath, woldpath, NULL)) { errno = err_win_to_posix(GetLastError()); return -1; } @@ -1822,3 +1914,217 @@ pid_t waitpid(pid_t pid, int *status, int options) errno = EINVAL; return -1; } + +int mingw_offset_1st_component(const char *path) +{ + int offset = 0; + if (has_dos_drive_prefix(path)) + offset = 2; + + /* unc paths */ + else if (is_dir_sep(path[0]) && is_dir_sep(path[1])) { + + /* skip server name */ + char *pos = strpbrk(path + 2, "\\/"); + if (!pos) + return 0; /* Error: malformed unc path */ + + do { + pos++; + } while (*pos && !is_dir_sep(*pos)); + + offset = pos - path; + } + + return offset + is_dir_sep(path[offset]); +} + +int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen) +{ + int upos = 0, wpos = 0; + const unsigned char *utf = (const unsigned char*) utfs; + if (!utf || !wcs || wcslen < 1) { + errno = EINVAL; + return -1; + } + /* reserve space for \0 */ + wcslen--; + if (utflen < 0) + utflen = INT_MAX; + + while (upos < utflen) { + int c = utf[upos++] & 0xff; + if (utflen == INT_MAX && c == 0) + break; + + if (wpos >= wcslen) { + wcs[wpos] = 0; + errno = ERANGE; + return -1; + } + + if (c < 0x80) { + /* ASCII */ + wcs[wpos++] = c; + } else if (c >= 0xc2 && c < 0xe0 && upos < utflen && + (utf[upos] & 0xc0) == 0x80) { + /* 2-byte utf-8 */ + c = ((c & 0x1f) << 6); + c |= (utf[upos++] & 0x3f); + wcs[wpos++] = c; + } else if (c >= 0xe0 && c < 0xf0 && upos + 1 < utflen && + !(c == 0xe0 && utf[upos] < 0xa0) && /* over-long encoding */ + (utf[upos] & 0xc0) == 0x80 && + (utf[upos + 1] & 0xc0) == 0x80) { + /* 3-byte utf-8 */ + c = ((c & 0x0f) << 12); + c |= ((utf[upos++] & 0x3f) << 6); + c |= (utf[upos++] & 0x3f); + wcs[wpos++] = c; + } else if (c >= 0xf0 && c < 0xf5 && upos + 2 < utflen && + wpos + 1 < wcslen && + !(c == 0xf0 && utf[upos] < 0x90) && /* over-long encoding */ + !(c == 0xf4 && utf[upos] >= 0x90) && /* > \u10ffff */ + (utf[upos] & 0xc0) == 0x80 && + (utf[upos + 1] & 0xc0) == 0x80 && + (utf[upos + 2] & 0xc0) == 0x80) { + /* 4-byte utf-8: convert to \ud8xx \udcxx surrogate pair */ + c = ((c & 0x07) << 18); + c |= ((utf[upos++] & 0x3f) << 12); + c |= ((utf[upos++] & 0x3f) << 6); + c |= (utf[upos++] & 0x3f); + c -= 0x10000; + wcs[wpos++] = 0xd800 | (c >> 10); + wcs[wpos++] = 0xdc00 | (c & 0x3ff); + } else if (c >= 0xa0) { + /* invalid utf-8 byte, printable unicode char: convert 1:1 */ + wcs[wpos++] = c; + } else { + /* invalid utf-8 byte, non-printable unicode: convert to hex */ + static const char *hex = "0123456789abcdef"; + wcs[wpos++] = hex[c >> 4]; + if (wpos < wcslen) + wcs[wpos++] = hex[c & 0x0f]; + } + } + wcs[wpos] = 0; + return wpos; +} + +int xwcstoutf(char *utf, const wchar_t *wcs, size_t utflen) +{ + if (!wcs || !utf || utflen < 1) { + errno = EINVAL; + return -1; + } + utflen = WideCharToMultiByte(CP_UTF8, 0, wcs, -1, utf, utflen, NULL, NULL); + if (utflen) + return utflen - 1; + errno = ERANGE; + return -1; +} + +/* + * Disable MSVCRT command line wildcard expansion (__getmainargs called from + * mingw startup code, see init.c in mingw runtime). + */ +int _CRT_glob = 0; + +typedef struct { + int newmode; +} _startupinfo; + +extern int __wgetmainargs(int *argc, wchar_t ***argv, wchar_t ***env, int glob, + _startupinfo *si); + +static NORETURN void die_startup() +{ + fputs("fatal: not enough memory for initialization", stderr); + exit(128); +} + +static void *malloc_startup(size_t size) +{ + void *result = malloc(size); + if (!result) + die_startup(); + return result; +} + +static char *wcstoutfdup_startup(char *buffer, const wchar_t *wcs, size_t len) +{ + len = xwcstoutf(buffer, wcs, len) + 1; + return memcpy(malloc_startup(len), buffer, len); +} + +void mingw_startup() +{ + int i, maxlen, argc; + char *buffer; + wchar_t **wenv, **wargv; + _startupinfo si; + + /* get wide char arguments and environment */ + si.newmode = 0; + if (__wgetmainargs(&argc, &wargv, &wenv, _CRT_glob, &si) < 0) + die_startup(); + + /* determine size of argv and environ conversion buffer */ + maxlen = wcslen(_wpgmptr); + 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; + buffer = malloc_startup(maxlen); + + /* convert command line arguments and environment to UTF-8 */ + __argv[0] = wcstoutfdup_startup(buffer, _wpgmptr, maxlen); + for (i = 1; 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 */ + + /* on Windows it is TMP and TEMP */ + if (!mingw_getenv("TMPDIR")) { + const char *tmp = mingw_getenv("TMP"); + if (!tmp) + tmp = mingw_getenv("TEMP"); + if (tmp) + setenv("TMPDIR", tmp, 1); + } + + /* simulate TERM to enable auto-color (see color.c) */ + if (!getenv("TERM")) + setenv("TERM", "cygwin", 1); + + /* initialize critical section for waitpid pinfo_t list */ + InitializeCriticalSection(&pinfo_cs); + + /* set up default file mode and file modes for stdin/out/err */ + _fmode = _O_BINARY; + _setmode(_fileno(stdin), _O_BINARY); + _setmode(_fileno(stdout), _O_BINARY); + _setmode(_fileno(stderr), _O_BINARY); + + /* initialize Unicode console */ + winansi_init(); +} diff --git a/compat/mingw.h b/compat/mingw.h index e033e720c9..df0e3203ab 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -35,6 +35,9 @@ typedef int socklen_t; #ifndef EWOULDBLOCK #define EWOULDBLOCK EAGAIN #endif +#ifndef ELOOP +#define ELOOP EMLINK +#endif #define SHUT_WR SD_SEND #define SIGHUP 1 @@ -118,10 +121,7 @@ static inline int fcntl(int fd, int cmd, ...) * simple adaptors */ -static inline int mingw_mkdir(const char *path, int mode) -{ - return mkdir(path); -} +int mingw_mkdir(const char *path, int mode); #define mkdir mingw_mkdir #define WNOHANG 1 @@ -192,11 +192,27 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream); int mingw_fflush(FILE *stream); #define fflush mingw_fflush +int mingw_access(const char *filename, int mode); +#undef access +#define access mingw_access + +int mingw_chdir(const char *dirname); +#define chdir mingw_chdir + +int mingw_chmod(const char *filename, int mode); +#define chmod mingw_chmod + +char *mingw_mktemp(char *template); +#define mktemp mingw_mktemp + char *mingw_getcwd(char *pointer, int len); #define getcwd mingw_getcwd 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_gethostname(char *host, int namelen); #define gethostname mingw_gethostname @@ -317,12 +333,8 @@ int mingw_raise(int sig); * ANSI emulation wrappers */ -int winansi_fputs(const char *str, FILE *stream); -int winansi_printf(const char *format, ...) __attribute__((format (printf, 1, 2))); -int winansi_fprintf(FILE *stream, const char *format, ...) __attribute__((format (printf, 2, 3))); -#define fputs winansi_fputs -#define printf(...) winansi_printf(__VA_ARGS__) -#define fprintf(...) winansi_fprintf(__VA_ARGS__) +void winansi_init(void); +HANDLE winansi_get_osfhandle(int fd); /* * git specific compatibility @@ -339,6 +351,8 @@ static inline char *mingw_find_last_dir_sep(const char *path) return ret; } #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 ';' #define PRIuMAX "I64u" #define PRId64 "I64d" @@ -346,12 +360,112 @@ static inline char *mingw_find_last_dir_sep(const char *path) void mingw_open_html(const char *path); #define open_html mingw_open_html -/* - * helpers +void mingw_mark_as_git_dir(const char *dir); +#define mark_as_git_dir mingw_mark_as_git_dir + +/** + * Converts UTF-8 encoded string to UTF-16LE. + * + * To support repositories with legacy-encoded file names, invalid UTF-8 bytes + * 0xa0 - 0xff are converted to corresponding printable Unicode chars \u00a0 - + * \u00ff, and invalid UTF-8 bytes 0x80 - 0x9f (which would make non-printable + * Unicode) are converted to hex-code. + * + * Lead-bytes not followed by an appropriate number of trail-bytes, over-long + * encodings and 4-byte encodings > \u10ffff are detected as invalid UTF-8. + * + * Maximum space requirement for the target buffer is two wide chars per UTF-8 + * char (((strlen(utf) * 2) + 1) [* sizeof(wchar_t)]). + * + * The maximum space is needed only if the entire input string consists of + * invalid UTF-8 bytes in range 0x80-0x9f, as per the following table: + * + * | | UTF-8 | UTF-16 | + * Code point | UTF-8 sequence | bytes | words | ratio + * --------------+-------------------+-------+--------+------- + * 000000-00007f | 0-7f | 1 | 1 | 1 + * 000080-0007ff | c2-df + 80-bf | 2 | 1 | 0.5 + * 000800-00ffff | e0-ef + 2 * 80-bf | 3 | 1 | 0.33 + * 010000-10ffff | f0-f4 + 3 * 80-bf | 4 | 2 (a) | 0.5 + * invalid | 80-9f | 1 | 2 (b) | 2 + * invalid | a0-ff | 1 | 1 | 1 + * + * (a) encoded as UTF-16 surrogate pair + * (b) encoded as two hex digits + * + * Note that, while the UTF-8 encoding scheme can be extended to 5-byte, 6-byte + * or even indefinite-byte sequences, the largest valid code point \u10ffff + * encodes as only 4 UTF-8 bytes. + * + * Parameters: + * wcs: wide char target buffer + * utf: string to convert + * wcslen: size of target buffer (in wchar_t's) + * utflen: size of string to convert, or -1 if 0-terminated + * + * Returns: + * length of converted string (_wcslen(wcs)), or -1 on failure + * + * Errors: + * EINVAL: one of the input parameters is invalid (e.g. NULL) + * ERANGE: the output buffer is too small */ +int xutftowcsn(wchar_t *wcs, const char *utf, size_t wcslen, int utflen); -char **make_augmented_environ(const char *const *vars); -void free_environ(char **env); +/** + * Simplified variant of xutftowcsn, assumes input string is \0-terminated. + */ +static inline int xutftowcs(wchar_t *wcs, const char *utf, size_t wcslen) +{ + return xutftowcsn(wcs, utf, wcslen, -1); +} + +/** + * Simplified file system specific variant of xutftowcsn, assumes output + * buffer size is MAX_PATH wide chars and input string is \0-terminated, + * fails with ENAMETOOLONG if input string is too long. + */ +static inline int xutftowcs_path(wchar_t *wcs, const char *utf) +{ + int result = xutftowcsn(wcs, utf, MAX_PATH, -1); + if (result < 0 && errno == ERANGE) + errno = ENAMETOOLONG; + return result; +} + +/** + * Converts UTF-16LE encoded string to UTF-8. + * + * Maximum space requirement for the target buffer is three UTF-8 chars per + * wide char ((_wcslen(wcs) * 3) + 1). + * + * The maximum space is needed only if the entire input string consists of + * UTF-16 words in range 0x0800-0xd7ff or 0xe000-0xffff (i.e. \u0800-\uffff + * modulo surrogate pairs), as per the following table: + * + * | | UTF-16 | UTF-8 | + * Code point | UTF-16 sequence | words | bytes | ratio + * --------------+-----------------------+--------+-------+------- + * 000000-00007f | 0000-007f | 1 | 1 | 1 + * 000080-0007ff | 0080-07ff | 1 | 2 | 2 + * 000800-00ffff | 0800-d7ff / e000-ffff | 1 | 3 | 3 + * 010000-10ffff | d800-dbff + dc00-dfff | 2 | 4 | 2 + * + * Note that invalid code points > 10ffff cannot be represented in UTF-16. + * + * Parameters: + * utf: target buffer + * wcs: wide string to convert + * utflen: size of target buffer + * + * Returns: + * length of converted string, or -1 on failure + * + * Errors: + * EINVAL: one of the input parameters is invalid (e.g. NULL) + * ERANGE: the output buffer is too small + */ +int xwcstoutf(char *utf, const wchar_t *wcs, size_t utflen); /* * A critical section used in the implementation of the spawn @@ -361,22 +475,16 @@ void free_environ(char **env); extern CRITICAL_SECTION pinfo_cs; /* - * A replacement of main() that ensures that argv[0] has a path - * and that default fmode and std(in|out|err) are in binary mode + * A replacement of main() that adds win32 specific initialization. */ +void mingw_startup(); #define main(c,v) dummy_decl_mingw_main(); \ static int mingw_main(c,v); \ int main(int argc, char **argv) \ { \ - extern CRITICAL_SECTION pinfo_cs; \ - _fmode = _O_BINARY; \ - _setmode(_fileno(stdin), _O_BINARY); \ - _setmode(_fileno(stdout), _O_BINARY); \ - _setmode(_fileno(stderr), _O_BINARY); \ - argv[0] = xstrdup(_pgmptr); \ - InitializeCriticalSection(&pinfo_cs); \ - return mingw_main(argc, argv); \ + mingw_startup(); \ + return mingw_main(__argc, (void *)__argv); \ } \ static int mingw_main(c,v) diff --git a/compat/poll/poll.c b/compat/poll/poll.c index 31163f2ae7..a9b41d89f4 100644 --- a/compat/poll/poll.c +++ b/compat/poll/poll.c @@ -605,7 +605,7 @@ restart: if (!rc && timeout == INFTIM) { - SwitchToThread(); + SleepEx (1, TRUE); goto restart; } diff --git a/compat/vcbuild/include/alloca.h b/compat/win32/alloca.h index c0d7985b7e..c0d7985b7e 100644 --- a/compat/vcbuild/include/alloca.h +++ b/compat/win32/alloca.h diff --git a/compat/win32/dirent.c b/compat/win32/dirent.c index 7a0debe51b..52420ec7d4 100644 --- a/compat/win32/dirent.c +++ b/compat/win32/dirent.c @@ -1,96 +1,81 @@ -#include "../git-compat-util.h" -#include "dirent.h" +#include "../../git-compat-util.h" struct DIR { struct dirent dd_dir; /* includes d_type */ HANDLE dd_handle; /* FindFirstFile handle */ int dd_stat; /* 0-based index */ - char dd_name[1]; /* extend struct */ }; +static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAW *fdata) +{ + /* convert UTF-16 name to UTF-8 */ + xwcstoutf(ent->d_name, fdata->cFileName, sizeof(ent->d_name)); + + /* Set file type, based on WIN32_FIND_DATA */ + if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + ent->d_type = DT_DIR; + else + ent->d_type = DT_REG; +} + DIR *opendir(const char *name) { - DWORD attrs = GetFileAttributesA(name); + wchar_t pattern[MAX_PATH + 2]; /* + 2 for '/' '*' */ + WIN32_FIND_DATAW fdata; + HANDLE h; int len; - DIR *p; + DIR *dir; - /* check for valid path */ - if (attrs == INVALID_FILE_ATTRIBUTES) { - errno = ENOENT; + /* convert name to UTF-16 and check length < MAX_PATH */ + if ((len = xutftowcs_path(pattern, name)) < 0) return NULL; - } - /* check if it's a directory */ - if (!(attrs & FILE_ATTRIBUTE_DIRECTORY)) { - errno = ENOTDIR; + /* append optional '/' and wildcard '*' */ + if (len && !is_dir_sep(pattern[len - 1])) + pattern[len++] = '/'; + pattern[len++] = '*'; + pattern[len] = 0; + + /* open find handle */ + h = FindFirstFileW(pattern, &fdata); + if (h == INVALID_HANDLE_VALUE) { + DWORD err = GetLastError(); + errno = (err == ERROR_DIRECTORY) ? ENOTDIR : err_win_to_posix(err); return NULL; } - /* check that the pattern won't be too long for FindFirstFileA */ - len = strlen(name); - if (is_dir_sep(name[len - 1])) - len--; - if (len + 2 >= MAX_PATH) { - errno = ENAMETOOLONG; - return NULL; - } - - p = malloc(sizeof(DIR) + len + 2); - if (!p) - return NULL; - - memset(p, 0, sizeof(DIR) + len + 2); - strcpy(p->dd_name, name); - p->dd_name[len] = '/'; - p->dd_name[len+1] = '*'; - - p->dd_handle = INVALID_HANDLE_VALUE; - return p; + /* initialize DIR structure and copy first dir entry */ + dir = xmalloc(sizeof(DIR)); + dir->dd_handle = h; + dir->dd_stat = 0; + finddata2dirent(&dir->dd_dir, &fdata); + return dir; } struct dirent *readdir(DIR *dir) { - WIN32_FIND_DATAA buf; - HANDLE handle; - - if (!dir || !dir->dd_handle) { + if (!dir) { errno = EBADF; /* No set_errno for mingw */ return NULL; } - if (dir->dd_handle == INVALID_HANDLE_VALUE && dir->dd_stat == 0) { - DWORD lasterr; - handle = FindFirstFileA(dir->dd_name, &buf); - lasterr = GetLastError(); - dir->dd_handle = handle; - if (handle == INVALID_HANDLE_VALUE && (lasterr != ERROR_NO_MORE_FILES)) { - errno = err_win_to_posix(lasterr); + /* if first entry, dirent has already been set up by opendir */ + if (dir->dd_stat) { + /* get next entry and convert from WIN32_FIND_DATA to dirent */ + WIN32_FIND_DATAW fdata; + if (FindNextFileW(dir->dd_handle, &fdata)) { + finddata2dirent(&dir->dd_dir, &fdata); + } else { + DWORD lasterr = GetLastError(); + /* POSIX says you shouldn't set errno when readdir can't + find any more files; so, if another error we leave it set. */ + if (lasterr != ERROR_NO_MORE_FILES) + errno = err_win_to_posix(lasterr); return NULL; } - } else if (dir->dd_handle == INVALID_HANDLE_VALUE) { - return NULL; - } else if (!FindNextFileA(dir->dd_handle, &buf)) { - DWORD lasterr = GetLastError(); - FindClose(dir->dd_handle); - dir->dd_handle = INVALID_HANDLE_VALUE; - /* POSIX says you shouldn't set errno when readdir can't - find any more files; so, if another error we leave it set. */ - if (lasterr != ERROR_NO_MORE_FILES) - errno = err_win_to_posix(lasterr); - return NULL; } - /* We get here if `buf' contains valid data. */ - strcpy(dir->dd_dir.d_name, buf.cFileName); ++dir->dd_stat; - - /* Set file type, based on WIN32_FIND_DATA */ - dir->dd_dir.d_type = 0; - if (buf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - dir->dd_dir.d_type |= DT_DIR; - else - dir->dd_dir.d_type |= DT_REG; - return &dir->dd_dir; } @@ -101,8 +86,7 @@ int closedir(DIR *dir) return -1; } - if (dir->dd_handle != INVALID_HANDLE_VALUE) - FindClose(dir->dd_handle); + FindClose(dir->dd_handle); free(dir); return 0; } diff --git a/compat/win32/dirent.h b/compat/win32/dirent.h index 927a25ca76..058207e4bf 100644 --- a/compat/win32/dirent.h +++ b/compat/win32/dirent.h @@ -9,12 +9,8 @@ typedef struct DIR DIR; #define DT_LNK 3 struct dirent { - long d_ino; /* Always zero. */ - char d_name[FILENAME_MAX]; /* File name. */ - union { - unsigned short d_reclen; /* Always zero. */ - unsigned char d_type; /* Reimplementation adds this */ - }; + unsigned char d_type; /* file type to prevent lstat after readdir */ + char d_name[MAX_PATH * 3]; /* file name (* 3 for UTF-8 conversion) */ }; DIR *opendir(const char *dirname); diff --git a/compat/winansi.c b/compat/winansi.c index dedce2104e..efc5bb3a4b 100644 --- a/compat/winansi.c +++ b/compat/winansi.c @@ -2,15 +2,10 @@ * Copyright 2008 Peter Harris <git@peter.is-a-geek.org> */ +#undef NOGDI #include "../git-compat-util.h" - -/* - Functions to be wrapped: -*/ -#undef printf -#undef fprintf -#undef fputs -/* TODO: write */ +#include <wingdi.h> +#include <winreg.h> /* ANSI codes used by git: m, K @@ -23,29 +18,114 @@ static HANDLE console; static WORD plain_attr; static WORD attr; static int negative; +static int non_ascii_used = 0; +static HANDLE hthread, hread, hwrite; +static HANDLE hconsole1, hconsole2; + +#ifdef __MINGW32__ +typedef struct _CONSOLE_FONT_INFOEX { + ULONG cbSize; + DWORD nFont; + COORD dwFontSize; + UINT FontFamily; + UINT FontWeight; + WCHAR FaceName[LF_FACESIZE]; +} CONSOLE_FONT_INFOEX, *PCONSOLE_FONT_INFOEX; +#endif + +typedef BOOL (WINAPI *PGETCURRENTCONSOLEFONTEX)(HANDLE, BOOL, + PCONSOLE_FONT_INFOEX); + +static void warn_if_raster_font(void) +{ + DWORD fontFamily = 0; + PGETCURRENTCONSOLEFONTEX pGetCurrentConsoleFontEx; + + /* don't bother if output was ascii only */ + if (!non_ascii_used) + return; + + /* GetCurrentConsoleFontEx is available since Vista */ + pGetCurrentConsoleFontEx = (PGETCURRENTCONSOLEFONTEX) GetProcAddress( + GetModuleHandle("kernel32.dll"), + "GetCurrentConsoleFontEx"); + if (pGetCurrentConsoleFontEx) { + CONSOLE_FONT_INFOEX cfi; + cfi.cbSize = sizeof(cfi); + if (pGetCurrentConsoleFontEx(console, 0, &cfi)) + fontFamily = cfi.FontFamily; + } else { + /* pre-Vista: check default console font in registry */ + HKEY hkey; + if (ERROR_SUCCESS == RegOpenKeyExA(HKEY_CURRENT_USER, "Console", + 0, KEY_READ, &hkey)) { + DWORD size = sizeof(fontFamily); + RegQueryValueExA(hkey, "FontFamily", NULL, NULL, + (LPVOID) &fontFamily, &size); + RegCloseKey(hkey); + } + } + + if (!(fontFamily & TMPF_TRUETYPE)) { + const wchar_t *msg = L"\nWarning: Your console font probably " + L"doesn\'t support Unicode. If you experience strange " + L"characters in the output, consider switching to a " + L"TrueType font such as Consolas!\n"; + DWORD dummy; + WriteConsoleW(console, msg, wcslen(msg), &dummy, NULL); + } +} -static void init(void) +static int is_console(int fd) { CONSOLE_SCREEN_BUFFER_INFO sbi; + HANDLE hcon; static int initialized = 0; - if (initialized) - return; - console = GetStdHandle(STD_OUTPUT_HANDLE); - if (console == INVALID_HANDLE_VALUE) - console = NULL; + /* get OS handle of the file descriptor */ + hcon = (HANDLE) _get_osfhandle(fd); + if (hcon == INVALID_HANDLE_VALUE) + return 0; - if (!console) - return; + /* check if its a device (i.e. console, printer, serial port) */ + if (GetFileType(hcon) != FILE_TYPE_CHAR) + return 0; - GetConsoleScreenBufferInfo(console, &sbi); - attr = plain_attr = sbi.wAttributes; - negative = 0; + /* check if its a handle to a console output screen buffer */ + if (!GetConsoleScreenBufferInfo(hcon, &sbi)) + return 0; + + /* initialize attributes */ + if (!initialized) { + console = hcon; + attr = plain_attr = sbi.wAttributes; + negative = 0; + initialized = 1; + } - initialized = 1; + return 1; } +#define BUFFER_SIZE 4096 +#define MAX_PARAMS 16 + +static void write_console(unsigned char *str, size_t len) +{ + /* only called from console_thread, so a static buffer will do */ + static wchar_t wbuf[2 * BUFFER_SIZE + 1]; + DWORD dummy; + + /* convert utf-8 to utf-16 */ + int wlen = xutftowcsn(wbuf, (char*) str, ARRAY_SIZE(wbuf), len); + + /* write directly to console */ + WriteConsoleW(console, wbuf, wlen, &dummy, NULL); + + /* remember if non-ascii characters are printed */ + if (wlen != len) + non_ascii_used = 1; +} #define FOREGROUND_ALL (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE) #define BACKGROUND_ALL (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE) @@ -90,18 +170,13 @@ static void erase_in_line(void) &dummy); } - -static const char *set_attr(const char *str) +static void set_attr(char func, const int *params, int paramlen) { - const char *func; - size_t len = strspn(str, "0123456789;"); - func = str + len; - - switch (*func) { + int i; + switch (func) { case 'm': - do { - long val = strtol(str, (char **)&str, 10); - switch (val) { + for (i = 0; i < paramlen; i++) { + switch (params[i]) { case 0: /* reset */ attr = plain_attr; negative = 0; @@ -224,9 +299,7 @@ static const char *set_attr(const char *str) /* Unsupported code */ break; } - str++; - } while (*(str-1) == ';'); - + } set_console_attr(); break; case 'K': @@ -236,122 +309,271 @@ static const char *set_attr(const char *str) /* Unsupported code */ break; } - - return func + 1; } -static int ansi_emulate(const char *str, FILE *stream) +enum { + TEXT = 0, ESCAPE = 033, BRACKET = '[' +}; + +static DWORD WINAPI console_thread(LPVOID unused) { - int rv = 0; - const char *pos = str; - - while (*pos) { - pos = strstr(str, "\033["); - if (pos) { - size_t len = pos - str; - - if (len) { - size_t out_len = fwrite(str, 1, len, stream); - rv += out_len; - if (out_len < len) - return rv; + unsigned char buffer[BUFFER_SIZE]; + DWORD bytes; + int start, end = 0, c, parampos = 0, state = TEXT; + int params[MAX_PARAMS]; + + while (1) { + /* read next chunk of bytes from the pipe */ + if (!ReadFile(hread, buffer + end, BUFFER_SIZE - end, &bytes, + NULL)) { + /* exit if pipe has been closed or disconnected */ + if (GetLastError() == ERROR_PIPE_NOT_CONNECTED || + GetLastError() == ERROR_BROKEN_PIPE) + break; + /* ignore other errors */ + continue; + } + + /* scan the bytes and handle ANSI control codes */ + bytes += end; + start = end = 0; + while (end < bytes) { + c = buffer[end++]; + switch (state) { + case TEXT: + if (c == ESCAPE) { + /* print text seen so far */ + if (end - 1 > start) + write_console(buffer + start, + end - 1 - start); + + /* then start parsing escape sequence */ + start = end - 1; + memset(params, 0, sizeof(params)); + parampos = 0; + state = ESCAPE; + } + break; + + case ESCAPE: + /* continue if "\033[", otherwise bail out */ + state = (c == BRACKET) ? BRACKET : TEXT; + break; + + case BRACKET: + /* parse [0-9;]* into array of parameters */ + if (c >= '0' && c <= '9') { + params[parampos] *= 10; + params[parampos] += c - '0'; + } else if (c == ';') { + /* + * next parameter, bail out if out of + * bounds + */ + parampos++; + if (parampos >= MAX_PARAMS) + state = TEXT; + } else { + /* + * end of escape sequence, change + * console attributes + */ + set_attr(c, params, parampos + 1); + start = end; + state = TEXT; + } + break; } + } - str = pos + 2; - rv += 2; + /* print remaining text unless parsing an escape sequence */ + if (state == TEXT && end > start) { + /* check for incomplete UTF-8 sequences and fix end */ + if (buffer[end - 1] >= 0x80) { + if (buffer[end -1] >= 0xc0) + end--; + else if (end - 1 > start && + buffer[end - 2] >= 0xe0) + end -= 2; + else if (end - 2 > start && + buffer[end - 3] >= 0xf0) + end -= 3; + } - fflush(stream); + /* print remaining complete UTF-8 sequences */ + if (end > start) + write_console(buffer + start, end - start); - pos = set_attr(str); - rv += pos - str; - str = pos; + /* move remaining bytes to the front */ + if (end < bytes) + memmove(buffer, buffer + end, bytes - end); + end = bytes - end; } else { - rv += strlen(str); - fputs(str, stream); - return rv; + /* all data has been consumed, mark buffer empty */ + end = 0; } } - return rv; + + /* check if the console font supports unicode */ + warn_if_raster_font(); + + CloseHandle(hread); + return 0; } -int winansi_fputs(const char *str, FILE *stream) +static void winansi_exit(void) { - int rv; - - if (!isatty(fileno(stream))) - return fputs(str, stream); + /* flush all streams */ + _flushall(); - init(); + /* signal console thread to exit */ + FlushFileBuffers(hwrite); + DisconnectNamedPipe(hwrite); - if (!console) - return fputs(str, stream); + /* wait for console thread to copy remaining data */ + WaitForSingleObject(hthread, INFINITE); - rv = ansi_emulate(str, stream); + /* cleanup handles... */ + CloseHandle(hwrite); + CloseHandle(hthread); +} - if (rv >= 0) - return 0; - else - return EOF; +static void die_lasterr(const char *fmt, ...) +{ + va_list params; + va_start(params, fmt); + errno = err_win_to_posix(GetLastError()); + die_errno(fmt, params); + va_end(params); } -static int winansi_vfprintf(FILE *stream, const char *format, va_list list) +static HANDLE duplicate_handle(HANDLE hnd) { - int len, rv; - char small_buf[256]; - char *buf = small_buf; - va_list cp; + HANDLE hresult, hproc = GetCurrentProcess(); + if (!DuplicateHandle(hproc, hnd, hproc, &hresult, 0, TRUE, + DUPLICATE_SAME_ACCESS)) + die_lasterr("DuplicateHandle(%li) failed", (long) hnd); + return hresult; +} - if (!isatty(fileno(stream))) - goto abort; - init(); +/* + * Make MSVCRT's internal file descriptor control structure accessible + * so that we can tweak OS handles and flags directly (we need MSVCRT + * to treat our pipe handle as if it were a console). + * + * We assume that the ioinfo structure (exposed by MSVCRT.dll via + * __pioinfo) starts with the OS handle and the flags. The exact size + * varies between MSVCRT versions, so we try different sizes until + * toggling the FDEV bit of _pioinfo(1)->osflags is reflected in + * isatty(1). + */ +typedef struct { + HANDLE osfhnd; + char osflags; +} ioinfo; - if (!console) - goto abort; +extern __declspec(dllimport) ioinfo *__pioinfo[]; - va_copy(cp, list); - len = vsnprintf(small_buf, sizeof(small_buf), format, cp); - va_end(cp); +static size_t sizeof_ioinfo = 0; - if (len > sizeof(small_buf) - 1) { - buf = malloc(len + 1); - if (!buf) - goto abort; +#define IOINFO_L2E 5 +#define IOINFO_ARRAY_ELTS (1 << IOINFO_L2E) - len = vsnprintf(buf, len + 1, format, list); - } +#define FDEV 0x40 - rv = ansi_emulate(buf, stream); +static inline ioinfo* _pioinfo(int fd) +{ + return (ioinfo*)((char*)__pioinfo[fd >> IOINFO_L2E] + + (fd & (IOINFO_ARRAY_ELTS - 1)) * sizeof_ioinfo); +} - if (buf != small_buf) - free(buf); - return rv; +static int init_sizeof_ioinfo() +{ + int istty, wastty; + /* don't init twice */ + if (sizeof_ioinfo) + return sizeof_ioinfo >= 256; + + sizeof_ioinfo = sizeof(ioinfo); + wastty = isatty(1); + while (sizeof_ioinfo < 256) { + /* toggle FDEV flag, check isatty, then toggle back */ + _pioinfo(1)->osflags ^= FDEV; + istty = isatty(1); + _pioinfo(1)->osflags ^= FDEV; + /* return if we found the correct size */ + if (istty != wastty) + return 0; + sizeof_ioinfo += sizeof(void*); + } + error("Tweaking file descriptors doesn't work with this MSVCRT.dll"); + return 1; +} -abort: - rv = vfprintf(stream, format, list); - return rv; +static HANDLE swap_osfhnd(int fd, HANDLE new_handle) +{ + ioinfo *pioinfo; + HANDLE old_handle; + + /* init ioinfo size if we haven't done so */ + if (init_sizeof_ioinfo()) + return INVALID_HANDLE_VALUE; + + /* get ioinfo pointer and change the handles */ + pioinfo = _pioinfo(fd); + old_handle = pioinfo->osfhnd; + pioinfo->osfhnd = new_handle; + return old_handle; } -int winansi_fprintf(FILE *stream, const char *format, ...) +void winansi_init(void) { - va_list list; - int rv; + int con1, con2; + char name[32]; - va_start(list, format); - rv = winansi_vfprintf(stream, format, list); - va_end(list); + /* check if either stdout or stderr is a console output screen buffer */ + con1 = is_console(1); + con2 = is_console(2); + if (!con1 && !con2) + return; - return rv; + /* create a named pipe to communicate with the console thread */ + sprintf(name, "\\\\.\\pipe\\winansi%lu", GetCurrentProcessId()); + hwrite = CreateNamedPipe(name, PIPE_ACCESS_OUTBOUND, + PIPE_TYPE_BYTE | PIPE_WAIT, 1, BUFFER_SIZE, 0, 0, NULL); + if (hwrite == INVALID_HANDLE_VALUE) + die_lasterr("CreateNamedPipe failed"); + + hread = CreateFile(name, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); + if (hread == INVALID_HANDLE_VALUE) + die_lasterr("CreateFile for named pipe failed"); + + /* start console spool thread on the pipe's read end */ + hthread = CreateThread(NULL, 0, console_thread, NULL, 0, NULL); + if (hthread == INVALID_HANDLE_VALUE) + die_lasterr("CreateThread(console_thread) failed"); + + /* schedule cleanup routine */ + if (atexit(winansi_exit)) + die_errno("atexit(winansi_exit) failed"); + + /* redirect stdout / stderr to the pipe */ + if (con1) + hconsole1 = swap_osfhnd(1, duplicate_handle(hwrite)); + if (con2) + hconsole2 = swap_osfhnd(2, duplicate_handle(hwrite)); } -int winansi_printf(const char *format, ...) +/* + * Returns the real console handle if stdout / stderr is a pipe redirecting + * to the console. Allows spawn / exec to pass the console to the next process. + */ +HANDLE winansi_get_osfhandle(int fd) { - va_list list; - int rv; - - va_start(list, format); - rv = winansi_vfprintf(stdout, format, list); - va_end(list); - - return rv; + HANDLE hnd = (HANDLE) _get_osfhandle(fd); + if ((fd == 1 || fd == 2) && isatty(fd) + && GetFileType(hnd) == FILE_TYPE_PIPE) + return (fd == 1) ? hconsole1 : hconsole2; + return hnd; } |