Makefile: stop pretending to support rpmbuild
[gitweb.git] / compat / mingw.c
index 9d435e2cf4487de66f97c8a829975584bee52c9d..54c82ecf201dde2c317efe6862173c1b40f2552d 100644 (file)
@@ -4,6 +4,9 @@
 #include <wchar.h>
 #include "../strbuf.h"
 #include "../run-command.h"
+#include "../cache.h"
+
+#define HCAST(type, handle) ((type)(intptr_t)handle)
 
 static const int delay[] = { 0, 1, 10, 20, 40 };
 
@@ -311,7 +314,7 @@ int mingw_open (const char *filename, int oflags, ...)
                return -1;
        fd = _wopen(wfilename, oflags, mode);
 
-       if (fd < 0 && (oflags & O_CREAT) && errno == EACCES) {
+       if (fd < 0 && (oflags & O_ACCMODE) != O_RDONLY && errno == EACCES) {
                DWORD attrs = GetFileAttributesW(wfilename);
                if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY))
                        errno = EISDIR;
@@ -393,6 +396,23 @@ int mingw_fflush(FILE *stream)
        return ret;
 }
 
+#undef write
+ssize_t mingw_write(int fd, const void *buf, size_t len)
+{
+       ssize_t result = write(fd, buf, len);
+
+       if (result < 0 && errno == EINVAL && buf) {
+               /* check if fd is a pipe */
+               HANDLE h = (HANDLE) _get_osfhandle(fd);
+               if (GetFileType(h) == FILE_TYPE_PIPE)
+                       errno = EPIPE;
+               else
+                       errno = EINVAL;
+       }
+
+       return result;
+}
+
 int mingw_access(const char *filename, int mode)
 {
        wchar_t wfilename[MAX_PATH];
@@ -434,6 +454,39 @@ static inline time_t filetime_to_time_t(const FILETIME *ft)
        return (time_t)(filetime_to_hnsec(ft) / 10000000);
 }
 
+/**
+ * Verifies that safe_create_leading_directories() would succeed.
+ */
+static int has_valid_directory_prefix(wchar_t *wfilename)
+{
+       int n = wcslen(wfilename);
+
+       while (n > 0) {
+               wchar_t c = wfilename[--n];
+               DWORD attributes;
+
+               if (!is_dir_sep(c))
+                       continue;
+
+               wfilename[n] = L'\0';
+               attributes = GetFileAttributesW(wfilename);
+               wfilename[n] = c;
+               if (attributes == FILE_ATTRIBUTE_DIRECTORY ||
+                               attributes == FILE_ATTRIBUTE_DEVICE)
+                       return 1;
+               if (attributes == INVALID_FILE_ATTRIBUTES)
+                       switch (GetLastError()) {
+                       case ERROR_PATH_NOT_FOUND:
+                               continue;
+                       case ERROR_FILE_NOT_FOUND:
+                               /* This implies parent directory exists. */
+                               return 1;
+                       }
+               return 0;
+       }
+       return 1;
+}
+
 /* We keep the do_lstat code in a separate function to avoid recursion.
  * When a path ends with a slash, the stat will fail with ENOENT. In
  * this case, we strip the trailing slashes and stat again.
@@ -494,6 +547,12 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf)
        case ERROR_NOT_ENOUGH_MEMORY:
                errno = ENOMEM;
                break;
+       case ERROR_PATH_NOT_FOUND:
+               if (!has_valid_directory_prefix(wfilename)) {
+                       errno = ENOTDIR;
+                       break;
+               }
+               /* fallthru */
        default:
                errno = ENOENT;
                break;
@@ -673,14 +732,14 @@ int pipe(int filedes[2])
                errno = err_win_to_posix(GetLastError());
                return -1;
        }
-       filedes[0] = _open_osfhandle((int)h[0], O_NOINHERIT);
+       filedes[0] = _open_osfhandle(HCAST(int, h[0]), O_NOINHERIT);
        if (filedes[0] < 0) {
                CloseHandle(h[0]);
                CloseHandle(h[1]);
                return -1;
        }
-       filedes[1] = _open_osfhandle((int)h[1], O_NOINHERIT);
-       if (filedes[0] < 0) {
+       filedes[1] = _open_osfhandle(HCAST(int, h[1]), O_NOINHERIT);
+       if (filedes[1] < 0) {
                close(filedes[0]);
                CloseHandle(h[1]);
                return -1;
@@ -751,7 +810,7 @@ static const char *quote_arg(const char *arg)
                return arg;
 
        /* insert \ where necessary */
-       d = q = xmalloc(len+n+3);
+       d = q = xmalloc(st_add3(len, n, 3));
        *d++ = '"';
        while (*arg) {
                if (*arg == '"')
@@ -834,7 +893,7 @@ static char **get_path_split(void)
        if (!n)
                return NULL;
 
-       path = xmalloc((n+1)*sizeof(char *));
+       ALLOC_ARRAY(path, n + 1);
        p = envpath;
        i = 0;
        do {
@@ -898,11 +957,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 */
+       ALLOC_ARRAY(tmpenv, size + i);
+       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 {
@@ -913,15 +1005,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)
 {
        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 */
@@ -938,7 +1030,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
@@ -946,7 +1038,6 @@ 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));
@@ -978,36 +1069,17 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env,
                        free(quoted);
        }
 
-       wargs = xmalloc((2 * args.len + 1) * sizeof(wchar_t));
+       ALLOC_ARRAY(wargs, st_add(st_mult(2, args.len), 1));
        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) {
@@ -1039,10 +1111,10 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env,
 
 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)
 {
@@ -1066,14 +1138,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);
        }
@@ -1096,7 +1168,7 @@ static int try_shell_exec(const char *cmd, char *const *argv)
                int argc = 0;
                const char **argv2;
                while (argv[argc]) argc++;
-               argv2 = xmalloc(sizeof(*argv) * (argc+1));
+               ALLOC_ARRAY(argv2, argc + 1);
                argv2[0] = (char *)cmd; /* full path to the script file */
                memcpy(&argv2[1], &argv[1], sizeof(*argv) * argc);
                pid = mingw_spawnv(prog, argv2, 1);
@@ -1170,108 +1242,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;
 }
 
 /*
@@ -1590,7 +1642,12 @@ int mingw_rename(const char *pold, const char *pnew)
        if (gle == ERROR_ACCESS_DENIED &&
            (attrs = GetFileAttributesW(wpnew)) != INVALID_FILE_ATTRIBUTES) {
                if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
-                       errno = EISDIR;
+                       DWORD attrsold = GetFileAttributesW(wpold);
+                       if (attrsold == INVALID_FILE_ATTRIBUTES ||
+                           !(attrsold & FILE_ATTRIBUTE_DIRECTORY))
+                               errno = EISDIR;
+                       else if (!_wrmdir(wpnew))
+                               goto repeat;
                        return -1;
                }
                if ((attrs & FILE_ATTRIBUTE_READONLY) &&
@@ -1835,7 +1892,8 @@ void mingw_open_html(const char *unixpath)
                die("cannot run browser");
 
        printf("Launching default browser to display HTML ...\n");
-       r = (int)ShellExecute(NULL, "open", htmlpath, NULL, "\\", SW_SHOWNORMAL);
+       r = HCAST(int, ShellExecute(NULL, "open", htmlpath,
+                               NULL, "\\", SW_SHOWNORMAL));
        FreeLibrary(shell32);
        /* see the MSDN documentation referring to the result codes here */
        if (r <= 32) {
@@ -1921,28 +1979,31 @@ pid_t waitpid(pid_t pid, int *status, int options)
        return -1;
 }
 
+int mingw_skip_dos_drive_prefix(char **path)
+{
+       int ret = has_dos_drive_prefix(*path);
+       *path += ret;
+       return ret;
+}
+
 int mingw_offset_1st_component(const char *path)
 {
-       int offset = 0;
-       if (has_dos_drive_prefix(path))
-               offset = 2;
+       char *pos = (char *)path;
 
        /* unc paths */
-       else if (is_dir_sep(path[0]) && is_dir_sep(path[1])) {
-
+       if (!skip_dos_drive_prefix(&pos) &&
+                       is_dir_sep(pos[0]) && is_dir_sep(pos[1])) {
                /* skip server name */
-               char *pos = strpbrk(path + 2, "\\/");
+               pos = strpbrk(pos + 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]);
+       return pos + is_dir_sep(*pos) - path;
 }
 
 int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen)
@@ -2030,6 +2091,37 @@ int xwcstoutf(char *utf, const wchar_t *wcs, size_t utflen)
        return -1;
 }
 
+static void setup_windows_environment()
+{
+       char *tmp = getenv("TMPDIR");
+
+       /* on Windows it is TMP and TEMP */
+       if (!tmp) {
+               if (!(tmp = getenv("TMP")))
+                       tmp = getenv("TEMP");
+               if (tmp) {
+                       setenv("TMPDIR", tmp, 1);
+                       tmp = getenv("TMPDIR");
+               }
+       }
+
+       if (tmp) {
+               /*
+                * Convert all dir separators to forward slashes,
+                * to help shell commands called from the Git
+                * executable (by not mistaking the dir separators
+                * for escape characters).
+                */
+               for (; *tmp; tmp++)
+                       if (*tmp == '\\')
+                               *tmp = '/';
+       }
+
+       /* simulate TERM to enable auto-color (see color.c) */
+       if (!getenv("TERM"))
+               setenv("TERM", "cygwin", 1);
+}
+
 /*
  * Disable MSVCRT command line wildcard expansion (__getmainargs called from
  * mingw startup code, see init.c in mingw runtime).
@@ -2049,9 +2141,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;
@@ -2065,20 +2171,37 @@ void mingw_startup()
        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 */
+       setup_windows_environment();
+
        /* initialize critical section for waitpid pinfo_t list */
        InitializeCriticalSection(&pinfo_cs);
 
@@ -2091,3 +2214,16 @@ void mingw_startup()
        /* initialize Unicode console */
        winansi_init();
 }
+
+int uname(struct utsname *buf)
+{
+       unsigned v = (unsigned)GetVersion();
+       memset(buf, 0, sizeof(*buf));
+       xsnprintf(buf->sysname, sizeof(buf->sysname), "Windows");
+       xsnprintf(buf->release, sizeof(buf->release),
+                "%u.%u", v & 0xff, (v >> 8) & 0xff);
+       /* assuming NT variants only.. */
+       xsnprintf(buf->version, sizeof(buf->version),
+                 "%u", (v >> 16) & 0x7fff);
+       return 0;
+}