Merge branch 'js/mingw-utf8-env'
authorJunio C Hamano <gitster@pobox.com>
Tue, 13 Nov 2018 13:37:21 +0000 (22:37 +0900)
committerJunio C Hamano <gitster@pobox.com>
Tue, 13 Nov 2018 13:37:21 +0000 (22:37 +0900)
Windows fix.

* js/mingw-utf8-env:
mingw: reencode environment variables on the fly (UTF-16 <-> UTF-8)
t7800: fix quoting

1  2 
compat/mingw.c
compat/mingw.h
t/t7800-difftool.sh
diff --combined compat/mingw.c
index e8e538a2e048a17ff7e07c411cd7b51021c90a4a,c1de85f11893bf5ac71b313aa4b2ad21370f5de5..d2f4fabb44b898ea6049b8b3fecadc26ddbb6253
@@@ -5,8 -5,6 +5,8 @@@
  #include "../strbuf.h"
  #include "../run-command.h"
  #include "../cache.h"
 +#include "win32/lazyload.h"
 +#include "../config.h"
  
  #define HCAST(type, handle) ((type)(intptr_t)handle)
  
@@@ -204,60 -202,6 +204,60 @@@ static int ask_yes_no_if_possible(cons
        }
  }
  
 +/* Windows only */
 +enum hide_dotfiles_type {
 +      HIDE_DOTFILES_FALSE = 0,
 +      HIDE_DOTFILES_TRUE,
 +      HIDE_DOTFILES_DOTGITONLY
 +};
 +
 +static enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;
 +static char *unset_environment_variables;
 +
 +int mingw_core_config(const char *var, const char *value, void *cb)
 +{
 +      if (!strcmp(var, "core.hidedotfiles")) {
 +              if (value && !strcasecmp(value, "dotgitonly"))
 +                      hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;
 +              else
 +                      hide_dotfiles = git_config_bool(var, value);
 +              return 0;
 +      }
 +
 +      if (!strcmp(var, "core.unsetenvvars")) {
 +              free(unset_environment_variables);
 +              unset_environment_variables = xstrdup(value);
 +              return 0;
 +      }
 +
 +      return 0;
 +}
 +
 +/* Normalizes NT paths as returned by some low-level APIs. */
 +static wchar_t *normalize_ntpath(wchar_t *wbuf)
 +{
 +      int i;
 +      /* fix absolute path prefixes */
 +      if (wbuf[0] == '\\') {
 +              /* strip NT namespace prefixes */
 +              if (!wcsncmp(wbuf, L"\\??\\", 4) ||
 +                  !wcsncmp(wbuf, L"\\\\?\\", 4))
 +                      wbuf += 4;
 +              else if (!wcsnicmp(wbuf, L"\\DosDevices\\", 12))
 +                      wbuf += 12;
 +              /* replace remaining '...UNC\' with '\\' */
 +              if (!wcsnicmp(wbuf, L"UNC\\", 4)) {
 +                      wbuf += 2;
 +                      *wbuf = '\\';
 +              }
 +      }
 +      /* convert backslashes to slashes */
 +      for (i = 0; wbuf[i]; i++)
 +              if (wbuf[i] == '\\')
 +                      wbuf[i] = '/';
 +      return wbuf;
 +}
 +
  int mingw_unlink(const char *pathname)
  {
        int ret, tries = 0;
@@@ -397,19 -341,6 +397,19 @@@ int mingw_mkdir(const char *path, int m
        return ret;
  }
  
 +/*
 + * Calling CreateFile() using FILE_APPEND_DATA and without FILE_WRITE_DATA
 + * is documented in [1] as opening a writable file handle in append mode.
 + * (It is believed that) this is atomic since it is maintained by the
 + * kernel unlike the O_APPEND flag which is racily maintained by the CRT.
 + *
 + * [1] https://docs.microsoft.com/en-us/windows/desktop/fileio/file-access-rights-constants
 + *
 + * This trick does not appear to work for named pipes.  Instead it creates
 + * a named pipe client handle that cannot be written to.  Callers should
 + * just use the regular _wopen() for them.  (And since client handle gets
 + * bound to a unique server handle, it isn't really an issue.)
 + */
  static int mingw_open_append(wchar_t const *wfilename, int oflags, ...)
  {
        HANDLE handle;
                        NULL, create, FILE_ATTRIBUTE_NORMAL, NULL);
        if (handle == INVALID_HANDLE_VALUE)
                return errno = err_win_to_posix(GetLastError()), -1;
 +
        /*
         * No O_APPEND here, because the CRT uses it only to reset the
 -       * file pointer to EOF on write(); but that is not necessary
 -       * for a file created with FILE_APPEND_DATA.
 +       * file pointer to EOF before each write(); but that is not
 +       * necessary (and may lead to races) for a file created with
 +       * FILE_APPEND_DATA.
         */
        fd = _open_osfhandle((intptr_t)handle, O_BINARY);
        if (fd < 0)
        return fd;
  }
  
 +/*
 + * Does the pathname map to the local named pipe filesystem?
 + * That is, does it have a "//./pipe/" prefix?
 + */
 +static int is_local_named_pipe_path(const char *filename)
 +{
 +      return (is_dir_sep(filename[0]) &&
 +              is_dir_sep(filename[1]) &&
 +              filename[2] == '.'  &&
 +              is_dir_sep(filename[3]) &&
 +              !strncasecmp(filename+4, "pipe", 4) &&
 +              is_dir_sep(filename[8]) &&
 +              filename[9]);
 +}
 +
  int mingw_open (const char *filename, int oflags, ...)
  {
        typedef int (*open_fn_t)(wchar_t const *wfilename, int oflags, ...);
        if (filename && !strcmp(filename, "/dev/null"))
                filename = "nul";
  
 -      if (oflags & O_APPEND)
 +      if ((oflags & O_APPEND) && !is_local_named_pipe_path(filename))
                open_fn = mingw_open_append;
        else
                open_fn = _wopen;
@@@ -648,11 -562,9 +648,11 @@@ static inline long long filetime_to_hns
        return winTime - 116444736000000000LL;
  }
  
 -static inline time_t filetime_to_time_t(const FILETIME *ft)
 +static inline void filetime_to_timespec(const FILETIME *ft, struct timespec *ts)
  {
 -      return (time_t)(filetime_to_hnsec(ft) / 10000000);
 +      long long hnsec = filetime_to_hnsec(ft);
 +      ts->tv_sec = (time_t)(hnsec / 10000000);
 +      ts->tv_nsec = (hnsec % 10000000) * 100;
  }
  
  /**
@@@ -711,9 -623,9 +711,9 @@@ static int do_lstat(int follow, const c
                buf->st_size = fdata.nFileSizeLow |
                        (((off_t)fdata.nFileSizeHigh)<<32);
                buf->st_dev = buf->st_rdev = 0; /* not used by Git */
 -              buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
 -              buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
 -              buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime));
 +              filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim));
 +              filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));
 +              filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim));
                if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
                        WIN32_FIND_DATAW findbuf;
                        HANDLE handle = FindFirstFileW(wfilename, &findbuf);
@@@ -794,29 -706,6 +794,29 @@@ static int do_stat_internal(int follow
        return do_lstat(follow, alt_name, buf);
  }
  
 +static int get_file_info_by_handle(HANDLE hnd, struct stat *buf)
 +{
 +      BY_HANDLE_FILE_INFORMATION fdata;
 +
 +      if (!GetFileInformationByHandle(hnd, &fdata)) {
 +              errno = err_win_to_posix(GetLastError());
 +              return -1;
 +      }
 +
 +      buf->st_ino = 0;
 +      buf->st_gid = 0;
 +      buf->st_uid = 0;
 +      buf->st_nlink = 1;
 +      buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
 +      buf->st_size = fdata.nFileSizeLow |
 +              (((off_t)fdata.nFileSizeHigh)<<32);
 +      buf->st_dev = buf->st_rdev = 0; /* not used by Git */
 +      filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim));
 +      filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));
 +      filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim));
 +      return 0;
 +}
 +
  int mingw_lstat(const char *file_name, struct stat *buf)
  {
        return do_stat_internal(0, file_name, buf);
@@@ -829,31 -718,32 +829,31 @@@ int mingw_stat(const char *file_name, s
  int mingw_fstat(int fd, struct stat *buf)
  {
        HANDLE fh = (HANDLE)_get_osfhandle(fd);
 -      BY_HANDLE_FILE_INFORMATION fdata;
 +      DWORD avail, type = GetFileType(fh) & ~FILE_TYPE_REMOTE;
  
 -      if (fh == INVALID_HANDLE_VALUE) {
 -              errno = EBADF;
 -              return -1;
 -      }
 -      /* direct non-file handles to MS's fstat() */
 -      if (GetFileType(fh) != FILE_TYPE_DISK)
 -              return _fstati64(fd, buf);
 +      switch (type) {
 +      case FILE_TYPE_DISK:
 +              return get_file_info_by_handle(fh, buf);
  
 -      if (GetFileInformationByHandle(fh, &fdata)) {
 -              buf->st_ino = 0;
 -              buf->st_gid = 0;
 -              buf->st_uid = 0;
 +      case FILE_TYPE_CHAR:
 +      case FILE_TYPE_PIPE:
 +              /* initialize stat fields */
 +              memset(buf, 0, sizeof(*buf));
                buf->st_nlink = 1;
 -              buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
 -              buf->st_size = fdata.nFileSizeLow |
 -                      (((off_t)fdata.nFileSizeHigh)<<32);
 -              buf->st_dev = buf->st_rdev = 0; /* not used by Git */
 -              buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
 -              buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
 -              buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime));
 +
 +              if (type == FILE_TYPE_CHAR) {
 +                      buf->st_mode = _S_IFCHR;
 +              } else {
 +                      buf->st_mode = _S_IFIFO;
 +                      if (PeekNamedPipe(fh, NULL, 0, NULL, &avail, NULL))
 +                              buf->st_size = avail;
 +              }
                return 0;
 +
 +      default:
 +              errno = EBADF;
 +              return -1;
        }
 -      errno = EBADF;
 -      return -1;
  }
  
  static inline void time_t_to_filetime(time_t t, FILETIME *ft)
@@@ -997,29 -887,8 +997,29 @@@ struct tm *localtime_r(const time_t *ti
  
  char *mingw_getcwd(char *pointer, int len)
  {
 -      wchar_t wpointer[MAX_PATH];
 -      if (!_wgetcwd(wpointer, ARRAY_SIZE(wpointer)))
 +      wchar_t cwd[MAX_PATH], wpointer[MAX_PATH];
 +      DWORD ret = GetCurrentDirectoryW(ARRAY_SIZE(cwd), cwd);
 +
 +      if (!ret || ret >= ARRAY_SIZE(cwd)) {
 +              errno = ret ? ENAMETOOLONG : err_win_to_posix(GetLastError());
 +              return NULL;
 +      }
 +      ret = GetLongPathNameW(cwd, wpointer, ARRAY_SIZE(wpointer));
 +      if (!ret && GetLastError() == ERROR_ACCESS_DENIED) {
 +              HANDLE hnd = CreateFileW(cwd, 0,
 +                      FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
 +                      OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
 +              if (hnd == INVALID_HANDLE_VALUE)
 +                      return NULL;
 +              ret = GetFinalPathNameByHandleW(hnd, wpointer, ARRAY_SIZE(wpointer), 0);
 +              CloseHandle(hnd);
 +              if (!ret || ret >= ARRAY_SIZE(wpointer))
 +                      return NULL;
 +              if (xwcstoutf(pointer, normalize_ntpath(wpointer), len) < 0)
 +                      return NULL;
 +              return pointer;
 +      }
 +      if (!ret || ret >= ARRAY_SIZE(wpointer))
                return NULL;
        if (xwcstoutf(pointer, wpointer, len) < 0)
                return NULL;
@@@ -1171,67 -1040,123 +1171,144 @@@ static char *path_lookup(const char *cm
        return prog;
  }
  
- static int do_putenv(char **env, const char *name, int size, int free_old);
+ static const wchar_t *wcschrnul(const wchar_t *s, wchar_t c)
+ {
+       while (*s && *s != c)
+               s++;
+       return s;
+ }
+ /* Compare only keys */
+ static int wenvcmp(const void *a, const void *b)
+ {
+       wchar_t *p = *(wchar_t **)a, *q = *(wchar_t **)b;
+       size_t p_len, q_len;
+       /* Find the keys */
+       p_len = wcschrnul(p, L'=') - p;
+       q_len = wcschrnul(q, L'=') - q;
  
- /* used number of elements of environ array, including terminating NULL */
- static int environ_size = 0;
- /* allocated size of environ array, in bytes */
- static int environ_alloc = 0;
+       /* If the length differs, include the shorter key's NUL */
+       if (p_len < q_len)
+               p_len++;
+       else if (p_len > q_len)
+               p_len = q_len + 1;
+       return _wcsnicmp(p, q, p_len);
+ }
+ /* We need a stable sort to convert the environment between UTF-16 <-> UTF-8 */
+ #ifndef INTERNAL_QSORT
+ #include "qsort.c"
+ #endif
  
  /*
-  * Create environment block suitable for CreateProcess. Merges current
-  * process environment and the supplied environment changes.
+  * Build an environment block combining the inherited environment
+  * merged with the given list of settings.
+  *
+  * Values of the form "KEY=VALUE" in deltaenv override inherited values.
+  * Values of the form "KEY" in deltaenv delete inherited values.
+  *
+  * Multiple entries in deltaenv for the same key are explicitly allowed.
+  *
+  * We return a contiguous block of UNICODE strings with a final trailing
+  * zero word.
   */
  static wchar_t *make_environment_block(char **deltaenv)
  {
-       wchar_t *wenvblk = NULL;
-       char **tmpenv;
-       int i = 0, size = environ_size, wenvsz = 0, wenvpos = 0;
+       wchar_t *wenv = GetEnvironmentStringsW(), *wdeltaenv, *result, *p;
+       size_t wlen, s, delta_size, size;
  
-       while (deltaenv && deltaenv[i])
-               i++;
+       wchar_t **array = NULL;
+       size_t alloc = 0, nr = 0, i;
  
-       /* copy the environment, leaving space for changes */
-       ALLOC_ARRAY(tmpenv, size + i);
-       memcpy(tmpenv, environ, size * sizeof(char*));
+       size = 1; /* for extra NUL at the end */
+       /* If there is no deltaenv to apply, simply return a copy. */
+       if (!deltaenv || !*deltaenv) {
+               for (p = wenv; p && *p; ) {
+                       size_t s = wcslen(p) + 1;
+                       size += s;
+                       p += s;
+               }
  
-       /* merge supplied environment changes into the temporary environment */
-       for (i = 0; deltaenv && deltaenv[i]; i++)
-               size = do_putenv(tmpenv, deltaenv[i], size, 0);
+               ALLOC_ARRAY(result, size);
+               memcpy(result, wenv, size * sizeof(*wenv));
+               FreeEnvironmentStringsW(wenv);
+               return result;
+       }
  
-       /* 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;
+       /*
+        * If there is a deltaenv, let's accumulate all keys into `array`,
+        * sort them using the stable git_qsort() and then copy, skipping
+        * duplicate keys
+        */
+       for (p = wenv; p && *p; ) {
+               ALLOC_GROW(array, nr + 1, alloc);
+               s = wcslen(p) + 1;
+               array[nr++] = p;
+               p += s;
+               size += s;
        }
-       /* add final \0 terminator */
-       wenvblk[wenvpos] = 0;
-       free(tmpenv);
-       return wenvblk;
+       /* (over-)assess size needed for wchar version of deltaenv */
+       for (delta_size = 0, i = 0; deltaenv[i]; i++)
+               delta_size += strlen(deltaenv[i]) * 2 + 1;
+       ALLOC_ARRAY(wdeltaenv, delta_size);
+       /* convert the deltaenv, appending to array */
+       for (i = 0, p = wdeltaenv; deltaenv[i]; i++) {
+               ALLOC_GROW(array, nr + 1, alloc);
+               wlen = xutftowcs(p, deltaenv[i], wdeltaenv + delta_size - p);
+               array[nr++] = p;
+               p += wlen + 1;
+       }
+       git_qsort(array, nr, sizeof(*array), wenvcmp);
+       ALLOC_ARRAY(result, size + delta_size);
+       for (p = result, i = 0; i < nr; i++) {
+               /* Skip any duplicate keys; last one wins */
+               while (i + 1 < nr && !wenvcmp(array + i, array + i + 1))
+                      i++;
+               /* Skip "to delete" entry */
+               if (!wcschr(array[i], L'='))
+                       continue;
+               size = wcslen(array[i]) + 1;
+               memcpy(p, array[i], size * sizeof(*p));
+               p += size;
+       }
+       *p = L'\0';
+       free(array);
+       free(wdeltaenv);
+       FreeEnvironmentStringsW(wenv);
+       return result;
  }
  
 +static void do_unset_environment_variables(void)
 +{
 +      static int done;
 +      char *p = unset_environment_variables;
 +
 +      if (done || !p)
 +              return;
 +      done = 1;
 +
 +      for (;;) {
 +              char *comma = strchr(p, ',');
 +
 +              if (comma)
 +                      *comma = '\0';
 +              unsetenv(p);
 +              if (!comma)
 +                      break;
 +              p = comma + 1;
 +      }
 +}
 +
  struct pinfo_t {
        struct pinfo_t *next;
        pid_t pid;
@@@ -1250,12 -1175,9 +1327,12 @@@ static pid_t mingw_spawnve_fd(const cha
        wchar_t wcmd[MAX_PATH], wdir[MAX_PATH], *wargs, *wenvblk = NULL;
        unsigned flags = CREATE_UNICODE_ENVIRONMENT;
        BOOL ret;
 +      HANDLE cons;
 +
 +      do_unset_environment_variables();
  
        /* Determine whether or not we are associated to a console */
 -      HANDLE cons = CreateFile("CONOUT$", GENERIC_WRITE,
 +      cons = CreateFile("CONOUT$", GENERIC_WRITE,
                        FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
                        FILE_ATTRIBUTE_NORMAL, NULL);
        if (cons == INVALID_HANDLE_VALUE) {
@@@ -1474,87 -1396,83 +1551,83 @@@ int mingw_kill(pid_t pid, int sig
  }
  
  /*
-  * Compare environment entries by key (i.e. stopping at '=' or '\0').
+  * UTF-8 versions of getenv(), putenv() and unsetenv().
+  * Internally, they use the CRT's stock UNICODE routines
+  * to avoid data loss.
   */
static int compareenv(const void *v1, const void *v2)
char *mingw_getenv(const char *name)
  {
-       const char *e1 = *(const char**)v1;
-       const char *e2 = *(const char**)v2;
+ #define GETENV_MAX_RETAIN 30
+       static char *values[GETENV_MAX_RETAIN];
+       static int value_counter;
+       int len_key, len_value;
+       wchar_t *w_key;
+       char *value;
+       wchar_t w_value[32768];
  
-       for (;;) {
-               int c1 = *e1++;
-               int c2 = *e2++;
-               c1 = (c1 == '=') ? 0 : tolower(c1);
-               c2 = (c2 == '=') ? 0 : tolower(c2);
-               if (c1 > c2)
-                       return 1;
-               if (c1 < c2)
-                       return -1;
-               if (c1 == 0)
-                       return 0;
-       }
- }
+       if (!name || !*name)
+               return NULL;
  
- static int bsearchenv(char **env, const char *name, size_t size)
- {
-       unsigned low = 0, high = size;
-       while (low < high) {
-               unsigned mid = low + ((high - low) >> 1);
-               int cmp = compareenv(&env[mid], &name);
-               if (cmp < 0)
-                       low = mid + 1;
-               else if (cmp > 0)
-                       high = mid;
-               else
-                       return mid;
+       len_key = strlen(name) + 1;
+       /* We cannot use xcalloc() here because that uses getenv() itself */
+       w_key = calloc(len_key, sizeof(wchar_t));
+       if (!w_key)
+               die("Out of memory, (tried to allocate %u wchar_t's)", len_key);
+       xutftowcs(w_key, name, len_key);
+       len_value = GetEnvironmentVariableW(w_key, w_value, ARRAY_SIZE(w_value));
+       if (!len_value && GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
+               free(w_key);
+               return NULL;
        }
-       return ~low; /* not found, return 1's complement of insert position */
+       free(w_key);
+       len_value = len_value * 3 + 1;
+       /* We cannot use xcalloc() here because that uses getenv() itself */
+       value = calloc(len_value, sizeof(char));
+       if (!value)
+               die("Out of memory, (tried to allocate %u bytes)", len_value);
+       xwcstoutf(value, w_value, len_value);
+       /*
+        * We return `value` which is an allocated value and the caller is NOT
+        * expecting to have to free it, so we keep a round-robin array,
+        * invalidating the buffer after GETENV_MAX_RETAIN getenv() calls.
+        */
+       free(values[value_counter]);
+       values[value_counter++] = value;
+       if (value_counter >= ARRAY_SIZE(values))
+               value_counter = 0;
+       return value;
  }
  
- /*
-  * If name contains '=', then sets the variable, otherwise it unsets it
-  * Size includes the terminating NULL. Env must have room for size + 1 entries
-  * (in case of insert). Returns the new size. Optionally frees removed entries.
-  */
- static int do_putenv(char **env, const char *name, int size, int free_old)
+ int mingw_putenv(const char *namevalue)
  {
-       int i = bsearchenv(env, name, size - 1);
+       int size;
+       wchar_t *wide, *equal;
+       BOOL result;
  
-       /* optionally free removed / replaced entry */
-       if (i >= 0 && free_old)
-               free(env[i]);
+       if (!namevalue || !*namevalue)
+               return 0;
  
-       if (strchr(name, '=')) {
-               /* if new value ('key=value') is specified, insert or replace entry */
-               if (i < 0) {
-                       i = ~i;
-                       memmove(&env[i + 1], &env[i], (size - i) * sizeof(char*));
-                       size++;
-               }
-               env[i] = (char*) name;
-       } else if (i >= 0) {
-               /* otherwise ('key') remove existing entry */
-               size--;
-               memmove(&env[i], &env[i + 1], (size - i) * sizeof(char*));
+       size = strlen(namevalue) * 2 + 1;
+       wide = calloc(size, sizeof(wchar_t));
+       if (!wide)
+               die("Out of memory, (tried to allocate %u wchar_t's)", size);
+       xutftowcs(wide, namevalue, size);
+       equal = wcschr(wide, L'=');
+       if (!equal)
+               result = SetEnvironmentVariableW(wide, NULL);
+       else {
+               *equal = L'\0';
+               result = SetEnvironmentVariableW(wide, equal + 1);
        }
-       return size;
- }
+       free(wide);
  
- char *mingw_getenv(const char *name)
- {
-       char *value;
-       int pos = bsearchenv(environ, name, environ_size - 1);
-       if (pos < 0)
-               return NULL;
-       value = strchr(environ[pos], '=');
-       return value ? &value[1] : NULL;
- }
+       if (!result)
+               errno = err_win_to_posix(GetLastError());
  
- int mingw_putenv(const char *namevalue)
- {
-       ALLOC_GROW(environ, (environ_size + 1) * sizeof(char*), environ_alloc);
-       environ_size = do_putenv(environ, namevalue, environ_size, 1);
-       return 0;
+       return result ? 0 : -1;
  }
  
  /*
@@@ -1702,8 -1620,7 +1775,8 @@@ static void ensure_socket_initializatio
                        WSAGetLastError());
  
        for (name = libraries; *name; name++) {
 -              ipv6_dll = LoadLibrary(*name);
 +              ipv6_dll = LoadLibraryExA(*name, NULL,
 +                                        LOAD_LIBRARY_SEARCH_SYSTEM32);
                if (!ipv6_dll)
                        continue;
  
@@@ -1924,63 -1841,18 +1997,63 @@@ int mingw_getpagesize(void
        return si.dwAllocationGranularity;
  }
  
 +/* See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724435.aspx */
 +enum EXTENDED_NAME_FORMAT {
 +      NameDisplay = 3,
 +      NameUserPrincipal = 8
 +};
 +
 +static char *get_extended_user_info(enum EXTENDED_NAME_FORMAT type)
 +{
 +      DECLARE_PROC_ADDR(secur32.dll, BOOL, GetUserNameExW,
 +              enum EXTENDED_NAME_FORMAT, LPCWSTR, PULONG);
 +      static wchar_t wbuffer[1024];
 +      DWORD len;
 +
 +      if (!INIT_PROC_ADDR(GetUserNameExW))
 +              return NULL;
 +
 +      len = ARRAY_SIZE(wbuffer);
 +      if (GetUserNameExW(type, wbuffer, &len)) {
 +              char *converted = xmalloc((len *= 3));
 +              if (xwcstoutf(converted, wbuffer, len) >= 0)
 +                      return converted;
 +              free(converted);
 +      }
 +
 +      return NULL;
 +}
 +
 +char *mingw_query_user_email(void)
 +{
 +      return get_extended_user_info(NameUserPrincipal);
 +}
 +
  struct passwd *getpwuid(int uid)
  {
 +      static unsigned initialized;
        static char user_name[100];
 -      static struct passwd p;
 +      static struct passwd *p;
 +      DWORD len;
 +
 +      if (initialized)
 +              return p;
  
 -      DWORD len = sizeof(user_name);
 -      if (!GetUserName(user_name, &len))
 +      len = sizeof(user_name);
 +      if (!GetUserName(user_name, &len)) {
 +              initialized = 1;
                return NULL;
 -      p.pw_name = user_name;
 -      p.pw_gecos = "unknown";
 -      p.pw_dir = NULL;
 -      return &p;
 +      }
 +
 +      p = xmalloc(sizeof(*p));
 +      p->pw_name = user_name;
 +      p->pw_gecos = get_extended_user_info(NameDisplay);
 +      if (!p->pw_gecos)
 +              p->pw_gecos = "unknown";
 +      p->pw_dir = NULL;
 +
 +      initialized = 1;
 +      return p;
  }
  
  static HANDLE timer_event;
@@@ -2462,17 -2334,6 +2535,6 @@@ void mingw_startup(void
        maxlen = wcslen(wargv[0]);
        for (i = 1; i < argc; i++)
                maxlen = max(maxlen, wcslen(wargv[i]));
-       for (i = 0; wenv[i]; i++)
-               maxlen = max(maxlen, wcslen(wenv[i]));
-       /*
-        * nedmalloc can't free CRT memory, allocate resizable environment
-        * list. Note that xmalloc / xmemdupz etc. call getenv, so we cannot
-        * use it while initializing the environment itself.
-        */
-       environ_size = i + 1;
-       environ_alloc = alloc_nr(environ_size * sizeof(char*));
-       environ = malloc_startup(environ_alloc);
  
        /* allocate buffer (wchar_t encodes to max 3 UTF-8 bytes) */
        maxlen = 3 * maxlen + 1;
        /* convert command line arguments and environment to UTF-8 */
        for (i = 0; i < argc; i++)
                __argv[i] = wcstoutfdup_startup(buffer, wargv[i], maxlen);
-       for (i = 0; wenv[i]; i++)
-               environ[i] = wcstoutfdup_startup(buffer, wenv[i], maxlen);
-       environ[i] = NULL;
        free(buffer);
  
-       /* sort environment for O(log n) getenv / putenv */
-       qsort(environ, i, sizeof(char*), compareenv);
        /* fix Windows specific environment settings */
        setup_windows_environment();
  
 +      unset_environment_variables = xstrdup("PERL5LIB");
 +
        /* initialize critical section for waitpid pinfo_t list */
        InitializeCriticalSection(&pinfo_cs);
  
diff --combined compat/mingw.h
index 2f1f8e3e05367041580c150d82514ea09bcf0d7f,be6f3a0e26f495cb82f45c655a71f43e39a9fb9f..8c24ddaa3efc20e4454ebc87c51fa30316f64a22
@@@ -11,9 -11,6 +11,9 @@@ typedef _sigset_t sigset_t
  #undef _POSIX_THREAD_SAFE_FUNCTIONS
  #endif
  
 +extern int mingw_core_config(const char *var, const char *value, void *cb);
 +#define platform_core_config mingw_core_config
 +
  /*
   * things that are not available in header files
   */
@@@ -260,11 -257,35 +260,35 @@@ char *mingw_mktemp(char *template)
  char *mingw_getcwd(char *pointer, int len);
  #define getcwd mingw_getcwd
  
+ #ifdef NO_UNSETENV
+ #error "NO_UNSETENV is incompatible with the Windows-specific startup code!"
+ #endif
+ /*
+  * We bind *env() routines (even the mingw_ ones) to private mingw_ versions.
+  * These talk to the CRT using UNICODE/wchar_t, but maintain the original
+  * narrow-char API.
+  *
+  * Note that the MSCRT maintains both ANSI (getenv()) and UNICODE (_wgetenv())
+  * routines and stores both versions of each environment variable in parallel
+  * (and secretly updates both when you set one or the other), but it uses CP_ACP
+  * to do the conversion rather than CP_UTF8.
+  *
+  * Since everything in the git code base is UTF8, we define the mingw_ routines
+  * to access the CRT using the UNICODE routines and manually convert them to
+  * UTF8.  This also avoids round-trip problems.
+  *
+  * This also helps with our linkage, since "_wenviron" is publicly exported
+  * from the CRT.  But to access "_environ" we would have to statically link
+  * to the CRT (/MT).
+  *
+  * We require NO_SETENV (and let gitsetenv() call our mingw_putenv).
+  */
+ #define getenv       mingw_getenv
+ #define putenv       mingw_putenv
+ #define unsetenv     mingw_putenv
  char *mingw_getenv(const char *name);
- #define getenv mingw_getenv
- int mingw_putenv(const char *namevalue);
- #define putenv mingw_putenv
- #define unsetenv mingw_putenv
+ int   mingw_putenv(const char *name);
  
  int mingw_gethostname(char *host, int namelen);
  #define gethostname mingw_gethostname
@@@ -330,41 -351,18 +354,41 @@@ static inline int getrlimit(int resourc
  }
  
  /*
 - * Use mingw specific stat()/lstat()/fstat() implementations on Windows.
 + * Use mingw specific stat()/lstat()/fstat() implementations on Windows,
 + * including our own struct stat with 64 bit st_size and nanosecond-precision
 + * file times.
   */
  #ifndef __MINGW64_VERSION_MAJOR
  #define off_t off64_t
  #define lseek _lseeki64
 +struct timespec {
 +      time_t tv_sec;
 +      long tv_nsec;
 +};
  #endif
  
 -/* use struct stat with 64 bit st_size */
 +struct mingw_stat {
 +    _dev_t st_dev;
 +    _ino_t st_ino;
 +    _mode_t st_mode;
 +    short st_nlink;
 +    short st_uid;
 +    short st_gid;
 +    _dev_t st_rdev;
 +    off64_t st_size;
 +    struct timespec st_atim;
 +    struct timespec st_mtim;
 +    struct timespec st_ctim;
 +};
 +
 +#define st_atime st_atim.tv_sec
 +#define st_mtime st_mtim.tv_sec
 +#define st_ctime st_ctim.tv_sec
 +
  #ifdef stat
  #undef stat
  #endif
 -#define stat _stati64
 +#define stat mingw_stat
  int mingw_lstat(const char *file_name, struct stat *buf);
  int mingw_stat(const char *file_name, struct stat *buf);
  int mingw_fstat(int fd, struct stat *buf);
  #endif
  #define lstat mingw_lstat
  
 -#ifndef _stati64
 -# define _stati64(x,y) mingw_stat(x,y)
 -#elif defined (_USE_32BIT_TIME_T)
 -# define _stat32i64(x,y) mingw_stat(x,y)
 -#else
 -# define _stat64(x,y) mingw_stat(x,y)
 -#endif
  
  int mingw_utime(const char *file_name, const struct utimbuf *times);
  #define utime mingw_utime
@@@ -409,9 -414,6 +433,9 @@@ int mingw_raise(int sig)
  int winansi_isatty(int fd);
  #define isatty winansi_isatty
  
 +int winansi_dup2(int oldfd, int newfd);
 +#define dup2 winansi_dup2
 +
  void winansi_init(void);
  HANDLE winansi_get_osfhandle(int fd);
  
@@@ -446,8 -448,6 +470,8 @@@ static inline void convert_slashes(cha
  int mingw_offset_1st_component(const char *path);
  #define offset_1st_component mingw_offset_1st_component
  #define PATH_SEP ';'
 +extern char *mingw_query_user_email(void);
 +#define query_user_email mingw_query_user_email
  #if !defined(__MINGW64_VERSION_MAJOR) && (!defined(_MSC_VER) || _MSC_VER < 1800)
  #define PRIuMAX "I64u"
  #define PRId64 "I64d"
diff --combined t/t7800-difftool.sh
index 562bd215a5315757dcc7c10e311106f2c7b0ff72,f29aa8f344f1a6ff8a953b20efb7b6b0486d4d81..22b9199d599284e9fcf5d7451b98db7ee99fffbe
@@@ -332,7 -332,7 +332,7 @@@ test_expect_success 'difftool --extcmd 
  test_expect_success 'difftool --extcmd cat arg2' '
        echo branch >expect &&
        git difftool --no-prompt \
-               --extcmd sh\ -c\ \"cat\ \$2\" branch >actual &&
+               --extcmd sh\ -c\ \"cat\ \\\"\$2\\\"\" branch >actual &&
        test_cmp expect actual
  '
  
@@@ -557,7 -557,7 +557,7 @@@ test_expect_success SYMLINKS 'difftool 
        EOF
        git difftool --dir-diff --symlink \
                --extcmd "./.git/CHECK_SYMLINKS" branch HEAD &&
 -      test_cmp actual expect
 +      test_cmp expect actual
  '
  
  write_script modify-right-file <<\EOF