Merge branch 'sk/mingw-unicode-spawn-args'
[gitweb.git] / compat / mingw.c
index e9892f8ee48db4739321d37726cff63dec8c0f13..3baaa4dfaedd1e451f810a6bead28b2f7e9aa95d 100644 (file)
@@ -441,7 +441,7 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf)
 static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
 {
        int namelen;
-       static char alt_name[PATH_MAX];
+       char alt_name[PATH_MAX];
 
        if (!do_lstat(follow, file_name, buf))
                return 0;
@@ -831,9 +831,10 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env,
                              const char *dir,
                              int prepend_cmd, int fhin, int fhout, int fherr)
 {
-       STARTUPINFO si;
+       STARTUPINFOW si;
        PROCESS_INFORMATION pi;
        struct strbuf envblk, args;
+       wchar_t wcmd[MAX_PATH], wdir[MAX_PATH], *wargs;
        unsigned flags;
        BOOL ret;
 
@@ -865,9 +866,14 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env,
        memset(&si, 0, sizeof(si));
        si.cb = sizeof(si);
        si.dwFlags = STARTF_USESTDHANDLES;
-       si.hStdInput = (HANDLE) _get_osfhandle(fhin);
-       si.hStdOutput = (HANDLE) _get_osfhandle(fhout);
-       si.hStdError = (HANDLE) _get_osfhandle(fherr);
+       si.hStdInput = winansi_get_osfhandle(fhin);
+       si.hStdOutput = winansi_get_osfhandle(fhout);
+       si.hStdError = winansi_get_osfhandle(fherr);
+
+       if (xutftowcs_path(wcmd, cmd) < 0)
+               return -1;
+       if (dir && xutftowcs_path(wdir, dir) < 0)
+               return -1;
 
        /* concatenate argv, quoting args as we go */
        strbuf_init(&args, 0);
@@ -886,6 +892,10 @@ 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));
+       xutftowcs(wargs, args.buf, 2 * args.len + 1);
+       strbuf_release(&args);
+
        if (env) {
                int count = 0;
                char **e, **sorted_env;
@@ -907,12 +917,12 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env,
        }
 
        memset(&pi, 0, sizeof(pi));
-       ret = CreateProcess(cmd, args.buf, NULL, NULL, TRUE, flags,
-               env ? envblk.buf : NULL, dir, &si, &pi);
+       ret = CreateProcessW(wcmd, wargs, NULL, NULL, TRUE, flags,
+               env ? envblk.buf : NULL, dir ? wdir : NULL, &si, &pi);
 
        if (env)
                strbuf_release(&envblk);
-       strbuf_release(&args);
+       free(wargs);
 
        if (!ret) {
                errno = ENOENT;
@@ -941,10 +951,9 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env,
        return (pid_t)pi.dwProcessId;
 }
 
-static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env,
-                          int prepend_cmd)
+static pid_t mingw_spawnv(const char *cmd, const char **argv, int prepend_cmd)
 {
-       return mingw_spawnve_fd(cmd, argv, env, NULL, prepend_cmd, 0, 1, 2);
+       return mingw_spawnve_fd(cmd, argv, environ, NULL, prepend_cmd, 0, 1, 2);
 }
 
 pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env,
@@ -986,7 +995,7 @@ pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env,
        return pid;
 }
 
-static int try_shell_exec(const char *cmd, char *const *argv, char **env)
+static int try_shell_exec(const char *cmd, char *const *argv)
 {
        const char *interpr = parse_interpreter(cmd);
        char **path;
@@ -1004,7 +1013,7 @@ static int try_shell_exec(const char *cmd, char *const *argv, char **env)
                argv2 = xmalloc(sizeof(*argv) * (argc+1));
                argv2[0] = (char *)cmd; /* full path to the script file */
                memcpy(&argv2[1], &argv[1], sizeof(*argv) * argc);
-               pid = mingw_spawnve(prog, argv2, env, 1);
+               pid = mingw_spawnv(prog, argv2, 1);
                if (pid >= 0) {
                        int status;
                        if (waitpid(pid, &status, 0) < 0)
@@ -1019,19 +1028,20 @@ static int try_shell_exec(const char *cmd, char *const *argv, char **env)
        return pid;
 }
 
-static void mingw_execve(const char *cmd, char *const *argv, char *const *env)
+int mingw_execv(const char *cmd, char *const *argv)
 {
        /* check if git_command is a shell script */
-       if (!try_shell_exec(cmd, argv, (char **)env)) {
+       if (!try_shell_exec(cmd, argv)) {
                int pid, status;
 
-               pid = mingw_spawnve(cmd, (const char **)argv, (char **)env, 0);
+               pid = mingw_spawnv(cmd, (const char **)argv, 0);
                if (pid < 0)
-                       return;
+                       return -1;
                if (waitpid(pid, &status, 0) < 0)
                        status = 255;
                exit(status);
        }
+       return -1;
 }
 
 int mingw_execvp(const char *cmd, char *const *argv)
@@ -1040,7 +1050,7 @@ int mingw_execvp(const char *cmd, char *const *argv)
        char *prog = path_lookup(cmd, path, 0);
 
        if (prog) {
-               mingw_execve(prog, argv, environ);
+               mingw_execv(prog, argv);
                free(prog);
        } else
                errno = ENOENT;
@@ -1049,12 +1059,6 @@ int mingw_execvp(const char *cmd, char *const *argv)
        return -1;
 }
 
-int mingw_execv(const char *cmd, char *const *argv)
-{
-       mingw_execve(cmd, argv, environ);
-       return -1;
-}
-
 int mingw_kill(pid_t pid, int sig)
 {
        if (pid > 0 && sig == SIGTERM) {
@@ -1823,3 +1827,174 @@ pid_t waitpid(pid_t pid, int *status, int options)
        errno = EINVAL;
        return -1;
 }
+
+int mingw_offset_1st_component(const char *path)
+{
+       int offset = 0;
+       if (has_dos_drive_prefix(path))
+               offset = 2;
+
+       /* unc paths */
+       else if (is_dir_sep(path[0]) && is_dir_sep(path[1])) {
+
+               /* skip server name */
+               char *pos = strpbrk(path + 2, "\\/");
+               if (!pos)
+                       return 0; /* Error: malformed unc path */
+
+               do {
+                       pos++;
+               } while (*pos && !is_dir_sep(*pos));
+
+               offset = pos - path;
+       }
+
+       return offset + is_dir_sep(path[offset]);
+}
+
+int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen)
+{
+       int upos = 0, wpos = 0;
+       const unsigned char *utf = (const unsigned char*) utfs;
+       if (!utf || !wcs || wcslen < 1) {
+               errno = EINVAL;
+               return -1;
+       }
+       /* reserve space for \0 */
+       wcslen--;
+       if (utflen < 0)
+               utflen = INT_MAX;
+
+       while (upos < utflen) {
+               int c = utf[upos++] & 0xff;
+               if (utflen == INT_MAX && c == 0)
+                       break;
+
+               if (wpos >= wcslen) {
+                       wcs[wpos] = 0;
+                       errno = ERANGE;
+                       return -1;
+               }
+
+               if (c < 0x80) {
+                       /* ASCII */
+                       wcs[wpos++] = c;
+               } else if (c >= 0xc2 && c < 0xe0 && upos < utflen &&
+                               (utf[upos] & 0xc0) == 0x80) {
+                       /* 2-byte utf-8 */
+                       c = ((c & 0x1f) << 6);
+                       c |= (utf[upos++] & 0x3f);
+                       wcs[wpos++] = c;
+               } else if (c >= 0xe0 && c < 0xf0 && upos + 1 < utflen &&
+                               !(c == 0xe0 && utf[upos] < 0xa0) && /* over-long encoding */
+                               (utf[upos] & 0xc0) == 0x80 &&
+                               (utf[upos + 1] & 0xc0) == 0x80) {
+                       /* 3-byte utf-8 */
+                       c = ((c & 0x0f) << 12);
+                       c |= ((utf[upos++] & 0x3f) << 6);
+                       c |= (utf[upos++] & 0x3f);
+                       wcs[wpos++] = c;
+               } else if (c >= 0xf0 && c < 0xf5 && upos + 2 < utflen &&
+                               wpos + 1 < wcslen &&
+                               !(c == 0xf0 && utf[upos] < 0x90) && /* over-long encoding */
+                               !(c == 0xf4 && utf[upos] >= 0x90) && /* > \u10ffff */
+                               (utf[upos] & 0xc0) == 0x80 &&
+                               (utf[upos + 1] & 0xc0) == 0x80 &&
+                               (utf[upos + 2] & 0xc0) == 0x80) {
+                       /* 4-byte utf-8: convert to \ud8xx \udcxx surrogate pair */
+                       c = ((c & 0x07) << 18);
+                       c |= ((utf[upos++] & 0x3f) << 12);
+                       c |= ((utf[upos++] & 0x3f) << 6);
+                       c |= (utf[upos++] & 0x3f);
+                       c -= 0x10000;
+                       wcs[wpos++] = 0xd800 | (c >> 10);
+                       wcs[wpos++] = 0xdc00 | (c & 0x3ff);
+               } else if (c >= 0xa0) {
+                       /* invalid utf-8 byte, printable unicode char: convert 1:1 */
+                       wcs[wpos++] = c;
+               } else {
+                       /* invalid utf-8 byte, non-printable unicode: convert to hex */
+                       static const char *hex = "0123456789abcdef";
+                       wcs[wpos++] = hex[c >> 4];
+                       if (wpos < wcslen)
+                               wcs[wpos++] = hex[c & 0x0f];
+               }
+       }
+       wcs[wpos] = 0;
+       return wpos;
+}
+
+int xwcstoutf(char *utf, const wchar_t *wcs, size_t utflen)
+{
+       if (!wcs || !utf || utflen < 1) {
+               errno = EINVAL;
+               return -1;
+       }
+       utflen = WideCharToMultiByte(CP_UTF8, 0, wcs, -1, utf, utflen, NULL, NULL);
+       if (utflen)
+               return utflen - 1;
+       errno = ERANGE;
+       return -1;
+}
+
+/*
+ * Disable MSVCRT command line wildcard expansion (__getmainargs called from
+ * mingw startup code, see init.c in mingw runtime).
+ */
+int _CRT_glob = 0;
+
+typedef struct {
+       int newmode;
+} _startupinfo;
+
+extern int __wgetmainargs(int *argc, wchar_t ***argv, wchar_t ***env, int glob,
+               _startupinfo *si);
+
+static NORETURN void die_startup()
+{
+       fputs("fatal: not enough memory for initialization", stderr);
+       exit(128);
+}
+
+void mingw_startup()
+{
+       int i, len, maxlen, argc;
+       char *buffer;
+       wchar_t **wenv, **wargv;
+       _startupinfo si;
+
+       /* get wide char arguments and environment */
+       si.newmode = 0;
+       if (__wgetmainargs(&argc, &wargv, &wenv, _CRT_glob, &si) < 0)
+               die_startup();
+
+       /* determine size of argv and environ conversion buffer */
+       maxlen = wcslen(_wpgmptr);
+       for (i = 1; i < argc; i++)
+               maxlen = max(maxlen, wcslen(wargv[i]));
+
+       /* allocate buffer (wchar_t encodes to max 3 UTF-8 bytes) */
+       maxlen = 3 * maxlen + 1;
+       buffer = xmalloc(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);
+       }
+       free(buffer);
+
+       /* initialize critical section for waitpid pinfo_t list */
+       InitializeCriticalSection(&pinfo_cs);
+
+       /* set up default file mode and file modes for stdin/out/err */
+       _fmode = _O_BINARY;
+       _setmode(_fileno(stdin), _O_BINARY);
+       _setmode(_fileno(stdout), _O_BINARY);
+       _setmode(_fileno(stderr), _O_BINARY);
+
+       /* initialize Unicode console */
+       winansi_init();
+}