diff options
Diffstat (limited to 'compat/mingw.c')
-rw-r--r-- | compat/mingw.c | 222 |
1 files changed, 206 insertions, 16 deletions
diff --git a/compat/mingw.c b/compat/mingw.c index bee6054419..6ef0cc4f99 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -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. */ @@ -1219,6 +1398,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 +1435,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 +1476,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; } |