verify_dotfile: mention case-insensitivity in comment
[gitweb.git] / compat / mingw.c
index f74da235f598d8b0346fac8b48c4155f55ae5b81..fe0e3ccd24887a830b99dabb9c66db668ba4f566 100644 (file)
@@ -6,6 +6,8 @@
 #include "../run-command.h"
 #include "../cache.h"
 
+#define HCAST(type, handle) ((type)(intptr_t)handle)
+
 static const int delay[] = { 0, 1, 10, 20, 40 };
 
 int err_win_to_posix(DWORD winerr)
@@ -284,6 +286,49 @@ int mingw_rmdir(const char *pathname)
        return ret;
 }
 
+static inline int needs_hiding(const char *path)
+{
+       const char *basename;
+
+       if (hide_dotfiles == HIDE_DOTFILES_FALSE)
+               return 0;
+
+       /* We cannot use basename(), as it would remove trailing slashes */
+       mingw_skip_dos_drive_prefix((char **)&path);
+       if (!*path)
+               return 0;
+
+       for (basename = path; *path; path++)
+               if (is_dir_sep(*path)) {
+                       do {
+                               path++;
+                       } while (is_dir_sep(*path));
+                       /* ignore trailing slashes */
+                       if (*path)
+                               basename = path;
+               }
+
+       if (hide_dotfiles == HIDE_DOTFILES_TRUE)
+               return *basename == '.';
+
+       assert(hide_dotfiles == HIDE_DOTFILES_DOTGITONLY);
+       return !strncasecmp(".git", basename, 4) &&
+               (!basename[4] || is_dir_sep(basename[4]));
+}
+
+static int set_hidden_flag(const wchar_t *path, int set)
+{
+       DWORD original = GetFileAttributesW(path), modified;
+       if (set)
+               modified = original | FILE_ATTRIBUTE_HIDDEN;
+       else
+               modified = original & ~FILE_ATTRIBUTE_HIDDEN;
+       if (original == modified || SetFileAttributesW(path, modified))
+               return 0;
+       errno = err_win_to_posix(GetLastError());
+       return -1;
+}
+
 int mingw_mkdir(const char *path, int mode)
 {
        int ret;
@@ -291,6 +336,8 @@ int mingw_mkdir(const char *path, int mode)
        if (xutftowcs_path(wpath, path) < 0)
                return -1;
        ret = _wmkdir(wpath);
+       if (!ret && needs_hiding(path))
+               return set_hidden_flag(wpath, 1);
        return ret;
 }
 
@@ -317,6 +364,21 @@ int mingw_open (const char *filename, int oflags, ...)
                if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY))
                        errno = EISDIR;
        }
+       if ((oflags & O_CREAT) && needs_hiding(filename)) {
+               /*
+                * Internally, _wopen() uses the CreateFile() API which errors
+                * out with an ERROR_ACCESS_DENIED if CREATE_ALWAYS was
+                * specified and an already existing file's attributes do not
+                * match *exactly*. As there is no mode or flag we can set that
+                * would correspond to FILE_ATTRIBUTE_HIDDEN, let's just try
+                * again *without* the O_CREAT flag (that corresponds to the
+                * CREATE_ALWAYS flag of CreateFile()).
+                */
+               if (fd < 0 && errno == EACCES)
+                       fd = _wopen(wfilename, oflags & ~O_CREAT, mode);
+               if (fd >= 0 && set_hidden_flag(wfilename, 1))
+                       warning("could not mark '%s' as hidden.", filename);
+       }
        return fd;
 }
 
@@ -348,6 +410,7 @@ int mingw_fgetc(FILE *stream)
 #undef fopen
 FILE *mingw_fopen (const char *filename, const char *otype)
 {
+       int hide = needs_hiding(filename);
        FILE *file;
        wchar_t wfilename[MAX_PATH], wotype[4];
        if (filename && !strcmp(filename, "/dev/null"))
@@ -355,12 +418,19 @@ FILE *mingw_fopen (const char *filename, const char *otype)
        if (xutftowcs_path(wfilename, filename) < 0 ||
                xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
                return NULL;
+       if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
+               error("could not unhide %s", filename);
+               return NULL;
+       }
        file = _wfopen(wfilename, wotype);
+       if (file && hide && set_hidden_flag(wfilename, 1))
+               warning("could not mark '%s' as hidden.", filename);
        return file;
 }
 
 FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
 {
+       int hide = needs_hiding(filename);
        FILE *file;
        wchar_t wfilename[MAX_PATH], wotype[4];
        if (filename && !strcmp(filename, "/dev/null"))
@@ -368,7 +438,13 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
        if (xutftowcs_path(wfilename, filename) < 0 ||
                xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
                return NULL;
+       if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
+               error("could not unhide %s", filename);
+               return NULL;
+       }
        file = _wfreopen(wfilename, wotype, stream);
+       if (file && hide && set_hidden_flag(wfilename, 1))
+               warning("could not mark '%s' as hidden.", filename);
        return file;
 }
 
@@ -394,6 +470,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];
@@ -435,6 +528,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.
@@ -495,6 +621,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;
@@ -674,13 +806,13 @@ 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);
+       filedes[1] = _open_osfhandle(HCAST(int, h[1]), O_NOINHERIT);
        if (filedes[1] < 0) {
                close(filedes[0]);
                CloseHandle(h[1]);
@@ -705,15 +837,12 @@ struct tm *localtime_r(const time_t *timep, struct tm *result)
 
 char *mingw_getcwd(char *pointer, int len)
 {
-       int i;
        wchar_t wpointer[MAX_PATH];
        if (!_wgetcwd(wpointer, ARRAY_SIZE(wpointer)))
                return NULL;
        if (xwcstoutf(pointer, wpointer, len) < 0)
                return NULL;
-       for (i = 0; pointer[i]; i++)
-               if (pointer[i] == '\\')
-                       pointer[i] = '/';
+       convert_slashes(pointer);
        return pointer;
 }
 
@@ -752,7 +881,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 == '"')
@@ -832,10 +961,12 @@ static char **get_path_split(void)
                        ++n;
                }
        }
-       if (!n)
+       if (!n) {
+               free(envpath);
                return NULL;
+       }
 
-       path = xmalloc((n+1)*sizeof(char *));
+       ALLOC_ARRAY(path, n + 1);
        p = envpath;
        i = 0;
        do {
@@ -920,7 +1051,7 @@ static wchar_t *make_environment_block(char **deltaenv)
                i++;
 
        /* copy the environment, leaving space for changes */
-       tmpenv = xmalloc((size + i) * sizeof(char*));
+       ALLOC_ARRAY(tmpenv, size + i);
        memcpy(tmpenv, environ, size * sizeof(char*));
 
        /* merge supplied environment changes into the temporary environment */
@@ -1011,7 +1142,7 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
                        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);
 
@@ -1110,7 +1241,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);
@@ -1584,7 +1715,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) &&
@@ -1796,47 +1932,6 @@ int mingw_raise(int sig)
        }
 }
 
-
-static const char *make_backslash_path(const char *path)
-{
-       static char buf[PATH_MAX + 1];
-       char *c;
-
-       if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX)
-               die("Too long path: %.*s", 60, path);
-
-       for (c = buf; *c; c++) {
-               if (*c == '/')
-                       *c = '\\';
-       }
-       return buf;
-}
-
-void mingw_open_html(const char *unixpath)
-{
-       const char *htmlpath = make_backslash_path(unixpath);
-       typedef HINSTANCE (WINAPI *T)(HWND, const char *,
-                       const char *, const char *, const char *, INT);
-       T ShellExecute;
-       HMODULE shell32;
-       int r;
-
-       shell32 = LoadLibrary("shell32.dll");
-       if (!shell32)
-               die("cannot load shell32.dll");
-       ShellExecute = (T)GetProcAddress(shell32, "ShellExecuteA");
-       if (!ShellExecute)
-               die("cannot run browser");
-
-       printf("Launching default browser to display HTML ...\n");
-       r = (int)ShellExecute(NULL, "open", htmlpath, NULL, "\\", SW_SHOWNORMAL);
-       FreeLibrary(shell32);
-       /* see the MSDN documentation referring to the result codes here */
-       if (r <= 32) {
-               die("failed to launch browser for %.*s", MAX_PATH, unixpath);
-       }
-}
-
 int link(const char *oldpath, const char *newpath)
 {
        typedef BOOL (WINAPI *T)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES);
@@ -1915,28 +2010,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)
@@ -2024,6 +2122,35 @@ int xwcstoutf(char *utf, const wchar_t *wcs, size_t utflen)
        return -1;
 }
 
+static void setup_windows_environment(void)
+{
+       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).
+                */
+               convert_slashes(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).
@@ -2037,7 +2164,7 @@ typedef struct {
 extern int __wgetmainargs(int *argc, wchar_t ***argv, wchar_t ***env, int glob,
                _startupinfo *si);
 
-static NORETURN void die_startup()
+static NORETURN void die_startup(void)
 {
        fputs("fatal: not enough memory for initialization", stderr);
        exit(128);
@@ -2057,7 +2184,7 @@ static char *wcstoutfdup_startup(char *buffer, const wchar_t *wcs, size_t len)
        return memcpy(malloc_startup(len), buffer, len);
 }
 
-void mingw_startup()
+void mingw_startup(void)
 {
        int i, maxlen, argc;
        char *buffer;
@@ -2102,19 +2229,7 @@ void mingw_startup()
        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);
+       setup_windows_environment();
 
        /* initialize critical section for waitpid pinfo_t list */
        InitializeCriticalSection(&pinfo_cs);
@@ -2131,11 +2246,13 @@ void mingw_startup()
 
 int uname(struct utsname *buf)
 {
-       DWORD v = GetVersion();
+       unsigned v = (unsigned)GetVersion();
        memset(buf, 0, sizeof(*buf));
-       strcpy(buf->sysname, "Windows");
-       sprintf(buf->release, "%u.%u", v & 0xff, (v >> 8) & 0xff);
+       xsnprintf(buf->sysname, sizeof(buf->sysname), "Windows");
+       xsnprintf(buf->release, sizeof(buf->release),
+                "%u.%u", v & 0xff, (v >> 8) & 0xff);
        /* assuming NT variants only.. */
-       sprintf(buf->version, "%u", (v >> 16) & 0x7fff);
+       xsnprintf(buf->version, sizeof(buf->version),
+                 "%u", (v >> 16) & 0x7fff);
        return 0;
 }