Merge branch 'sk/mingw-uni-fix-more'
authorJunio C Hamano <gitster@pobox.com>
Wed, 30 Jul 2014 21:21:09 +0000 (14:21 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 30 Jul 2014 21:21:09 +0000 (14:21 -0700)
Most of these are battle-tested in msysgit and are needed to
complete what has been merged to 'master' already.

* sk/mingw-uni-fix-more:
Win32: enable color output in Windows cmd.exe
Win32: patch Windows environment on startup
Win32: keep the environment sorted
Win32: use low-level memory allocation during initialization
Win32: reduce environment array reallocations
Win32: don't copy the environment twice when spawning child processes
Win32: factor out environment block creation
Win32: unify environment function names
Win32: unify environment case-sensitivity
Win32: fix environment memory leaks
Win32: Unicode environment (incoming)
Win32: Unicode environment (outgoing)
Revert "Windows: teach getenv to do a case-sensitive search"
tests: do not pass iso8859-1 encoded parameter

1  2 
compat/mingw.c
compat/mingw.h
config.mak.uname
run-command.c
diff --combined compat/mingw.c
index 9d435e2cf4487de66f97c8a829975584bee52c9d,df0fa03194aa696b91132094dd32abb54fdf7cd4..c5c37e53ce0fc534b1739a75c4abde0c67027d14
@@@ -4,6 -4,7 +4,7 @@@
  #include <wchar.h>
  #include "../strbuf.h"
  #include "../run-command.h"
+ #include "../cache.h"
  
  static const int delay[] = { 0, 1, 10, 20, 40 };
  
@@@ -898,11 -899,44 +899,44 @@@ static char *path_lookup(const char *cm
        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 {
  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)
  {
        STARTUPINFOW si;
        PROCESS_INFORMATION pi;
-       struct strbuf envblk, args;
-       wchar_t wcmd[MAX_PATH], wdir[MAX_PATH], *wargs;
-       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 */
                 * 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
                 * The same is true for CREATE_NO_WINDOW.
                 * Go figure!
                 */
-               flags = 0;
                CloseHandle(cons);
        }
        memset(&si, 0, sizeof(si));
        xutftowcs(wargs, args.buf, 2 * args.len + 1);
        strbuf_release(&args);
  
-       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);
-               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 = CreateProcessW(wcmd, wargs, NULL, NULL, TRUE, flags,
-               env ? envblk.buf : NULL, dir ? wdir : NULL, &si, &pi);
+               wenvblk, dir ? wdir : NULL, &si, &pi);
  
-       if (env)
-               strbuf_release(&envblk);
+       free(wenvblk);
        free(wargs);
  
        if (!ret) {
  
  static pid_t mingw_spawnv(const char *cmd, const char **argv, int prepend_cmd)
  {
-       return mingw_spawnve_fd(cmd, argv, environ, 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)
  {
                                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);
        }
@@@ -1170,108 -1184,88 +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;
  }
  
  /*
@@@ -1316,7 -1310,8 +1310,7 @@@ static int WSAAPI getaddrinfo_stub(cons
        else
                ai->ai_canonname = NULL;
  
 -      sin = xmalloc(ai->ai_addrlen);
 -      memset(sin, 0, ai->ai_addrlen);
 +      sin = xcalloc(1, ai->ai_addrlen);
        sin->sin_family = AF_INET;
        /* Note: getaddrinfo is supposed to allow service to be a string,
         * which should be looked up using getservbyname. This is
@@@ -2049,9 -2044,23 +2043,23 @@@ static NORETURN void die_startup(
        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, len, maxlen, argc;
+       int i, maxlen, argc;
        char *buffer;
        wchar_t **wenv, **wargv;
        _startupinfo si;
        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 = xmalloc(maxlen);
+       buffer = malloc_startup(maxlen);
  
        /* convert command line arguments and environment to UTF-8 */
-       len = xwcstoutf(buffer, _wpgmptr, maxlen);
-       __argv[0] = xmemdupz(buffer, len);
-       for (i = 1; i < argc; i++) {
-               len = xwcstoutf(buffer, wargv[i], maxlen);
-               __argv[i] = xmemdupz(buffer, len);
-       }
+       __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);
  
diff --combined compat/mingw.h
index 510530c69054e12dcf0d705a61041457ba62843d,3851857c2d8a1197e97b6a0baeb5601163de0733..df0e3203abed06374a789be191ff130e3ba728b6
@@@ -35,9 -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
@@@ -210,6 -207,9 +210,9 @@@ char *mingw_getcwd(char *pointer, int l
  
  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
@@@ -357,12 -357,8 +360,8 @@@ int mingw_offset_1st_component(const ch
  void mingw_open_html(const char *path);
  #define open_html mingw_open_html
  
- /*
-  * helpers
-  */
- char **make_augmented_environ(const char *const *vars);
- void free_environ(char **env);
+ 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.
diff --combined config.mak.uname
index 00cf4c6b832c19143bd7c8f8015f793f8142c548,462988e8ab44f964e093707c9838ee84a7b86e11..15ee15e98c2cb232e1e42315dc44f7efd87b4f1c
@@@ -34,7 -34,6 +34,7 @@@ ifeq ($(uname_S),Linux
        HAVE_PATHS_H = YesPlease
        LIBC_CONTAINS_LIBINTL = YesPlease
        HAVE_DEV_TTY = YesPlease
 +      HAVE_CLOCK_GETTIME = YesPlease
  endif
  ifeq ($(uname_S),GNU/kFreeBSD)
        HAVE_ALLOCA_H = YesPlease
@@@ -327,7 -326,6 +327,6 @@@ ifeq ($(uname_S),Windows
        NO_IPV6 = YesPlease
        NO_UNIX_SOCKETS = YesPlease
        NO_SETENV = YesPlease
-       NO_UNSETENV = YesPlease
        NO_STRCASESTR = YesPlease
        NO_STRLCPY = YesPlease
        NO_MEMMEM = YesPlease
@@@ -480,7 -478,6 +479,6 @@@ ifneq (,$(findstring MINGW,$(uname_S))
        NO_SYMLINK_HEAD = YesPlease
        NO_UNIX_SOCKETS = YesPlease
        NO_SETENV = YesPlease
-       NO_UNSETENV = YesPlease
        NO_STRCASESTR = YesPlease
        NO_STRLCPY = YesPlease
        NO_MEMMEM = YesPlease
diff --combined run-command.c
index 576dfeaa3f5e7bfe6485ea1dace1113c62f254f8,82dd0853a195adc000cd88c9b5874969f3836df0..35a3ebf07b1792ac7b0ecef30781223591413093
@@@ -454,7 -454,6 +454,6 @@@ fail_pipe
  {
        int fhin = 0, fhout = 1, fherr = 2;
        const char **sargv = cmd->argv;
-       char **env = environ;
  
        if (cmd->no_stdin)
                fhin = open("/dev/null", O_RDWR);
        else if (cmd->out > 1)
                fhout = dup(cmd->out);
  
-       if (cmd->env)
-               env = make_augmented_environ(cmd->env);
        if (cmd->git_cmd)
                cmd->argv = prepare_git_cmd(cmd->argv);
        else if (cmd->use_shell)
                cmd->argv = prepare_shell_cmd(cmd->argv);
  
-       cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, env, cmd->dir,
-                                 fhin, fhout, fherr);
+       cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, (char**) cmd->env,
+                       cmd->dir, fhin, fhout, fherr);
        failed_errno = errno;
        if (cmd->pid < 0 && (!cmd->silent_exec_failure || errno != ENOENT))
                error("cannot spawn %s: %s", cmd->argv[0], strerror(errno));
        if (cmd->clean_on_exit && cmd->pid >= 0)
                mark_child_for_cleanup(cmd->pid);
  
-       if (cmd->env)
-               free_environ(env);
        if (cmd->git_cmd)
                free(cmd->argv);
  
@@@ -770,21 -764,28 +764,21 @@@ char *find_hook(const char *name
  int run_hook_ve(const char *const *env, const char *name, va_list args)
  {
        struct child_process hook;
 -      struct argv_array argv = ARGV_ARRAY_INIT;
        const char *p;
 -      int ret;
  
        p = find_hook(name);
        if (!p)
                return 0;
  
 -      argv_array_push(&argv, p);
 -
 -      while ((p = va_arg(args, const char *)))
 -              argv_array_push(&argv, p);
 -
        memset(&hook, 0, sizeof(hook));
 -      hook.argv = argv.argv;
 +      argv_array_push(&hook.args, p);
 +      while ((p = va_arg(args, const char *)))
 +              argv_array_push(&hook.args, p);
        hook.env = env;
        hook.no_stdin = 1;
        hook.stdout_to_stderr = 1;
  
 -      ret = run_command(&hook);
 -      argv_array_clear(&argv);
 -      return ret;
 +      return run_command(&hook);
  }
  
  int run_hook_le(const char *const *env, const char *name, ...)