Merge branch 'js/mingw-shutdown'
[gitweb.git] / compat / mingw.c
index 1cbc9e83d460804e4cafd2c76025ad0d42d81a3e..f6e9ff7762356e099d2fe20fd31359bc0a1f68a2 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] = 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, ...)
 {
@@ -968,7 +1130,7 @@ char **make_augmented_environ(const char *const *vars)
 
 /*
  * 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.
  */
@@ -1256,7 +1418,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.
@@ -1298,6 +1459,11 @@ int mingw_rename(const char *pold, const char *pnew)
                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;
 }