Merge branch 'js/mingw-msdn-url'
authorJunio C Hamano <gitster@pobox.com>
Sun, 18 Nov 2018 09:23:58 +0000 (18:23 +0900)
committerJunio C Hamano <gitster@pobox.com>
Sun, 18 Nov 2018 09:23:58 +0000 (18:23 +0900)
The URL to an MSDN page in a comment has been updated.

* js/mingw-msdn-url:
mingw: replace an obsolete link with the superseding one

1  2 
compat/mingw.c
diff --combined compat/mingw.c
index 14ef81874d182b12a3cb6581639398c63ab5eee3,54758a3a5071be2fc089db934738c7896bb493f3..34b3880b29d57eee6d6ae0afbb786d7980e7fa3e
@@@ -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;
  }
  
  /*
-  * See http://msdn2.microsoft.com/en-us/library/17w5ykft(vs.71).aspx
-  * (Parsing C++ Command-Line Arguments)
+  * See "Parsing C++ Command-Line Arguments" at Microsoft's Docs:
+  * https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments
   */
  static const char *quote_arg(const char *arg)
  {
@@@ -1171,142 -1040,44 +1171,142 @@@ 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;
 +
 +      wchar_t **array = NULL;
 +      size_t alloc = 0, nr = 0, i;
 +
 +      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;
 +              }
 +
 +              ALLOC_ARRAY(result, size);
 +              memcpy(result, wenv, size * sizeof(*wenv));
 +              FreeEnvironmentStringsW(wenv);
 +              return result;
 +      }
 +
 +      /*
 +       * If there is a deltaenv, let's accumulate all keys into `array`,
 +       * sort them using the stable git_qsort() and then copy, skipping
 +       * duplicate keys
 +       */
 +      for (p = wenv; p && *p; ) {
 +              ALLOC_GROW(array, nr + 1, alloc);
 +              s = wcslen(p) + 1;
 +              array[nr++] = p;
 +              p += s;
 +              size += s;
 +      }
 +
 +      /* (over-)assess size needed for wchar version of deltaenv */
 +      for (delta_size = 0, i = 0; deltaenv[i]; i++)
 +              delta_size += strlen(deltaenv[i]) * 2 + 1;
 +      ALLOC_ARRAY(wdeltaenv, delta_size);
 +
 +      /* 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';
  
 -      while (deltaenv && deltaenv[i])
 -              i++;
 +      free(array);
 +      free(wdeltaenv);
 +      FreeEnvironmentStringsW(wenv);
 +      return result;
 +}
  
 -      /* copy the environment, leaving space for changes */
 -      ALLOC_ARRAY(tmpenv, size + i);
 -      memcpy(tmpenv, environ, size * sizeof(char*));
 +static void do_unset_environment_variables(void)
 +{
 +      static int done;
 +      char *p = unset_environment_variables;
 +
 +      if (done || !p)
 +              return;
 +      done = 1;
  
 -      /* merge supplied environment changes into the temporary environment */
 -      for (i = 0; deltaenv && deltaenv[i]; i++)
 -              size = do_putenv(tmpenv, deltaenv[i], size, 0);
 +      for (;;) {
 +              char *comma = strchr(p, ',');
  
 -      /* 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 (comma)
 +                      *comma = '\0';
 +              unsetenv(p);
 +              if (!comma)
 +                      break;
 +              p = comma + 1;
        }
 -      /* add final \0 terminator */
 -      wenvblk[wenvpos] = 0;
 -      free(tmpenv);
 -      return wenvblk;
  }
  
  struct pinfo_t {
@@@ -1327,12 -1098,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) {
@@@ -1551,83 -1319,87 +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;
  }
  
  /*
@@@ -1775,8 -1547,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;
  
@@@ -1997,63 -1768,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;
@@@ -2211,12 -1937,24 +2211,12 @@@ int mingw_raise(int sig
  
  int link(const char *oldpath, const char *newpath)
  {
 -      typedef BOOL (WINAPI *T)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES);
 -      static T create_hard_link = NULL;
        wchar_t woldpath[MAX_PATH], wnewpath[MAX_PATH];
        if (xutftowcs_path(woldpath, oldpath) < 0 ||
                xutftowcs_path(wnewpath, newpath) < 0)
                return -1;
  
 -      if (!create_hard_link) {
 -              create_hard_link = (T) GetProcAddress(
 -                      GetModuleHandle("kernel32.dll"), "CreateHardLinkW");
 -              if (!create_hard_link)
 -                      create_hard_link = (T)-1;
 -      }
 -      if (create_hard_link == (T)-1) {
 -              errno = ENOSYS;
 -              return -1;
 -      }
 -      if (!create_hard_link(wnewpath, woldpath, NULL)) {
 +      if (!CreateHardLinkW(wnewpath, woldpath, NULL)) {
                errno = err_win_to_posix(GetLastError());
                return -1;
        }
@@@ -2523,6 -2261,17 +2523,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);