Merge branch 'hv/mingw-fs-funnies'

* hv/mingw-fs-funnies:
  mingw_rmdir: set errno=ENOTEMPTY when appropriate
  mingw: add fallback for rmdir in case directory is in use
  mingw: make failures to unlink or move raise a question
  mingw: work around irregular failures of unlink on windows
  mingw: move unlink wrapper to mingw.c
This commit is contained in:
Junio C Hamano 2011-02-27 21:17:37 -08:00
commit 70ae2beadd
2 changed files with 173 additions and 9 deletions

View 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, ...)
{
@ -1249,7 +1411,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 +1452,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;
}

View File

@ -119,14 +119,6 @@ static inline int mingw_mkdir(const char *path, int mode)
}
#define mkdir mingw_mkdir
static inline int mingw_unlink(const char *pathname)
{
/* read-only files cannot be removed */
chmod(pathname, 0666);
return unlink(pathname);
}
#define unlink mingw_unlink
#define WNOHANG 1
pid_t waitpid(pid_t pid, int *status, unsigned options);
@ -174,6 +166,12 @@ int link(const char *oldpath, const char *newpath);
* replacements of existing functions
*/
int mingw_unlink(const char *pathname);
#define unlink mingw_unlink
int mingw_rmdir(const char *path);
#define rmdir mingw_rmdir
int mingw_open (const char *filename, int oflags, ...);
#define open mingw_open