Code

Merge branch 'ss/blame-textconv-fake-working-tree'
[git.git] / compat / mingw.c
index bee605441910caeddd1e94eb7795f615a3d1348a..efdc703257c9589018e59dc5bc93e9980e0f30b6 100644 (file)
@@ -2,6 +2,9 @@
 #include "win32.h"
 #include <conio.h>
 #include "../strbuf.h"
+#include "../run-command.h"
+
+static const int delay[] = { 0, 1, 10, 20, 40 };
 
 int err_win_to_posix(DWORD winerr)
 {
@@ -116,6 +119,165 @@ int err_win_to_posix(DWORD winerr)
        return error;
 }
 
+static inline int is_file_in_use_error(DWORD errcode)
+{
+       switch (errcode) {
+       case ERROR_SHARING_VIOLATION:
+       case ERROR_ACCESS_DENIED:
+               return 1;
+       }
+
+       return 0;
+}
+
+static int read_yes_no_answer(void)
+{
+       char answer[1024];
+
+       if (fgets(answer, sizeof(answer), stdin)) {
+               size_t answer_len = strlen(answer);
+               int got_full_line = 0, c;
+
+               /* remove the newline */
+               if (answer_len >= 2 && answer[answer_len-2] == '\r') {
+                       answer[answer_len-2] = '\0';
+                       got_full_line = 1;
+               } else if (answer_len >= 1 && answer[answer_len-1] == '\n') {
+                       answer[answer_len-1] = '\0';
+                       got_full_line = 1;
+               }
+               /* flush the buffer in case we did not get the full line */
+               if (!got_full_line)
+                       while ((c = getchar()) != EOF && c != '\n')
+                               ;
+       } else
+               /* we could not read, return the
+                * default answer which is no */
+               return 0;
+
+       if (tolower(answer[0]) == 'y' && !answer[1])
+               return 1;
+       if (!strncasecmp(answer, "yes", sizeof(answer)))
+               return 1;
+       if (tolower(answer[0]) == 'n' && !answer[1])
+               return 0;
+       if (!strncasecmp(answer, "no", sizeof(answer)))
+               return 0;
+
+       /* did not find an answer we understand */
+       return -1;
+}
+
+static int ask_yes_no_if_possible(const char *format, ...)
+{
+       char question[4096];
+       const char *retry_hook[] = { NULL, NULL, NULL };
+       va_list args;
+
+       va_start(args, format);
+       vsnprintf(question, sizeof(question), format, args);
+       va_end(args);
+
+       if ((retry_hook[0] = mingw_getenv("GIT_ASK_YESNO"))) {
+               retry_hook[1] = question;
+               return !run_command_v_opt(retry_hook, 0);
+       }
+
+       if (!isatty(_fileno(stdin)) || !isatty(_fileno(stderr)))
+               return 0;
+
+       while (1) {
+               int answer;
+               fprintf(stderr, "%s (y/n) ", question);
+
+               if ((answer = read_yes_no_answer()) >= 0)
+                       return answer;
+
+               fprintf(stderr, "Sorry, I did not understand your answer. "
+                               "Please type 'y' or 'n'\n");
+       }
+}
+
+#undef unlink
+int mingw_unlink(const char *pathname)
+{
+       int ret, tries = 0;
+
+       /* read-only files cannot be removed */
+       chmod(pathname, 0666);
+       while ((ret = unlink(pathname)) == -1 && tries < ARRAY_SIZE(delay)) {
+               if (!is_file_in_use_error(GetLastError()))
+                       break;
+               /*
+                * We assume that some other process had the source or
+                * destination file open at the wrong moment and retry.
+                * In order to give the other process a higher chance to
+                * complete its operation, we give up our time slice now.
+                * If we have to retry again, we do sleep a bit.
+                */
+               Sleep(delay[tries]);
+               tries++;
+       }
+       while (ret == -1 && is_file_in_use_error(GetLastError()) &&
+              ask_yes_no_if_possible("Unlink of file '%s' failed. "
+                       "Should I try again?", pathname))
+              ret = unlink(pathname);
+       return ret;
+}
+
+static int is_dir_empty(const char *path)
+{
+       struct strbuf buf = STRBUF_INIT;
+       WIN32_FIND_DATAA findbuf;
+       HANDLE handle;
+
+       strbuf_addf(&buf, "%s\\*", path);
+       handle = FindFirstFileA(buf.buf, &findbuf);
+       if (handle == INVALID_HANDLE_VALUE) {
+               strbuf_release(&buf);
+               return GetLastError() == ERROR_NO_MORE_FILES;
+       }
+
+       while (!strcmp(findbuf.cFileName, ".") ||
+                       !strcmp(findbuf.cFileName, ".."))
+               if (!FindNextFile(handle, &findbuf)) {
+                       strbuf_release(&buf);
+                       return GetLastError() == ERROR_NO_MORE_FILES;
+               }
+       FindClose(handle);
+       strbuf_release(&buf);
+       return 0;
+}
+
+#undef rmdir
+int mingw_rmdir(const char *pathname)
+{
+       int ret, tries = 0;
+
+       while ((ret = rmdir(pathname)) == -1 && tries < ARRAY_SIZE(delay)) {
+               if (!is_file_in_use_error(GetLastError()))
+                       break;
+               if (!is_dir_empty(pathname)) {
+                       errno = ENOTEMPTY;
+                       break;
+               }
+               /*
+                * We assume that some other process had the source or
+                * destination file open at the wrong moment and retry.
+                * In order to give the other process a higher chance to
+                * complete its operation, we give up our time slice now.
+                * If we have to retry again, we do sleep a bit.
+                */
+               Sleep(delay[tries]);
+               tries++;
+       }
+       while (ret == -1 && is_file_in_use_error(GetLastError()) &&
+              ask_yes_no_if_possible("Deletion of directory '%s' failed. "
+                       "Should I try again?", pathname))
+              ret = rmdir(pathname);
+       return ret;
+}
+
 #undef open
 int mingw_open (const char *filename, int oflags, ...)
 {
@@ -437,19 +599,6 @@ char *mingw_getcwd(char *pointer, int len)
        return ret;
 }
 
-#undef getenv
-char *mingw_getenv(const char *name)
-{
-       char *result = getenv(name);
-       if (!result && !strcmp(name, "TMPDIR")) {
-               /* on Windows it is TMP and TEMP */
-               result = getenv("TMP");
-               if (!result)
-                       result = getenv("TEMP");
-       }
-       return result;
-}
-
 /*
  * See http://msdn2.microsoft.com/en-us/library/17w5ykft(vs.71).aspx
  * (Parsing C++ Command-Line Arguments)
@@ -549,7 +698,7 @@ static const char *parse_interpreter(const char *cmd)
  */
 static char **get_path_split(void)
 {
-       char *p, **path, *envpath = getenv("PATH");
+       char *p, **path, *envpath = mingw_getenv("PATH");
        int i, n = 0;
 
        if (!envpath || !*envpath)
@@ -966,9 +1115,39 @@ char **make_augmented_environ(const char *const *vars)
        return env;
 }
 
+#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)
+{
+       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 *mingw_getenv(const char *name)
+{
+       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;
+}
+
 /*
  * Note, this isn't a complete replacement for getaddrinfo. It assumes
- * that service contains a numerical port, or that it it is null. It
+ * that service contains a numerical port, or that it is null. It
  * does a simple search using gethostbyname, and returns one IPv4 host
  * if one was found.
  */
@@ -1004,7 +1183,7 @@ static int WSAAPI getaddrinfo_stub(const char *node, const char *service,
        }
        ai->ai_addrlen = sizeof(struct sockaddr_in);
        if (hints && (hints->ai_flags & AI_CANONNAME))
-               ai->ai_canonname = h ? strdup(h->h_name) : NULL;
+               ai->ai_canonname = h ? xstrdup(h->h_name) : NULL;
        else
                ai->ai_canonname = NULL;
 
@@ -1142,6 +1321,13 @@ static void ensure_socket_initialization(void)
        initialized = 1;
 }
 
+#undef gethostname
+int mingw_gethostname(char *name, int namelen)
+{
+    ensure_socket_initialization();
+    return gethostname(name, namelen);
+}
+
 #undef gethostbyname
 struct hostent *mingw_gethostbyname(const char *host)
 {
@@ -1219,6 +1405,13 @@ int mingw_setsockopt(int sockfd, int lvl, int optname, void *optval, int optlen)
        return setsockopt(s, lvl, optname, (const char*)optval, optlen);
 }
 
+#undef shutdown
+int mingw_shutdown(int sockfd, int how)
+{
+       SOCKET s = (SOCKET)_get_osfhandle(sockfd);
+       return shutdown(s, how);
+}
+
 #undef listen
 int mingw_listen(int sockfd, int backlog)
 {
@@ -1249,7 +1442,6 @@ int mingw_rename(const char *pold, const char *pnew)
 {
        DWORD attrs, gle;
        int tries = 0;
-       static const int delay[] = { 0, 1, 10, 20, 40 };
 
        /*
         * Try native rename() first to get errno right.
@@ -1291,6 +1483,11 @@ repeat:
                tries++;
                goto repeat;
        }
+       if (gle == ERROR_ACCESS_DENIED &&
+              ask_yes_no_if_possible("Rename from '%s' to '%s' failed. "
+                      "Should I try again?", pold, pnew))
+               goto repeat;
+
        errno = EACCES;
        return -1;
 }