diff options
Diffstat (limited to 'compat/mingw.c')
-rw-r--r-- | compat/mingw.c | 1034 |
1 files changed, 809 insertions, 225 deletions
diff --git a/compat/mingw.c b/compat/mingw.c index 6b5b5b2c70..b673625580 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -2,10 +2,11 @@ #include "win32.h" #include <conio.h> #include "../strbuf.h" +#include "../run-command.h" -#include <shellapi.h> +static const int delay[] = { 0, 1, 10, 20, 40 }; -static int err_win_to_posix(DWORD winerr) +int err_win_to_posix(DWORD winerr) { int error = ENOSYS; switch(winerr) { @@ -118,6 +119,167 @@ static 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())) + errno = err_win_to_posix(GetLastError()); + if (errno != EACCES) + 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 && errno == EACCES && 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, ...) { @@ -129,7 +291,7 @@ int mingw_open (const char *filename, int oflags, ...) mode = va_arg(args, int); va_end(args); - if (!strcmp(filename, "/dev/null")) + if (filename && !strcmp(filename, "/dev/null")) filename = "nul"; fd = open(filename, oflags, mode); @@ -142,23 +304,115 @@ int mingw_open (const char *filename, int oflags, ...) return fd; } -static inline time_t filetime_to_time_t(const FILETIME *ft) +#undef write +ssize_t mingw_write(int fd, const void *buf, size_t count) +{ + /* + * While write() calls to a file on a local disk are translated + * into WriteFile() calls with a maximum size of 64KB on Windows + * XP and 256KB on Vista, no such cap is placed on writes to + * files over the network on Windows XP. Unfortunately, there + * seems to be a limit of 32MB-28KB on X64 and 64MB-32KB on x86; + * bigger writes fail on Windows XP. + * So we cap to a nice 31MB here to avoid write failures over + * the net without changing the number of WriteFile() calls in + * the local case. + */ + return write(fd, buf, min(count, 31 * 1024 * 1024)); +} + +static BOOL WINAPI ctrl_ignore(DWORD type) +{ + return TRUE; +} + +#undef fgetc +int mingw_fgetc(FILE *stream) +{ + int ch; + if (!isatty(_fileno(stream))) + return fgetc(stream); + + SetConsoleCtrlHandler(ctrl_ignore, TRUE); + while (1) { + ch = fgetc(stream); + if (ch != EOF || GetLastError() != ERROR_OPERATION_ABORTED) + break; + + /* Ctrl+C was pressed, simulate SIGINT and retry */ + mingw_raise(SIGINT); + } + SetConsoleCtrlHandler(ctrl_ignore, FALSE); + return ch; +} + +#undef fopen +FILE *mingw_fopen (const char *filename, const char *otype) +{ + if (filename && !strcmp(filename, "/dev/null")) + filename = "nul"; + return fopen(filename, otype); +} + +#undef freopen +FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream) +{ + if (filename && !strcmp(filename, "/dev/null")) + filename = "nul"; + return freopen(filename, otype, stream); +} + +#undef fflush +int mingw_fflush(FILE *stream) +{ + int ret = fflush(stream); + + /* + * write() is used behind the scenes of stdio output functions. + * Since git code does not check for errors after each stdio write + * operation, it can happen that write() is called by a later + * stdio function even if an earlier write() call failed. In the + * case of a pipe whose readable end was closed, only the first + * call to write() reports EPIPE on Windows. Subsequent write() + * calls report EINVAL. It is impossible to notice whether this + * fflush invocation triggered such a case, therefore, we have to + * catch all EINVAL errors whole-sale. + */ + if (ret && errno == EINVAL) + errno = EPIPE; + + return ret; +} + +/* + * The unit of FILETIME is 100-nanoseconds since January 1, 1601, UTC. + * Returns the 100-nanoseconds ("hekto nanoseconds") since the epoch. + */ +static inline long long filetime_to_hnsec(const FILETIME *ft) { long long winTime = ((long long)ft->dwHighDateTime << 32) + ft->dwLowDateTime; - winTime -= 116444736000000000LL; /* Windows to Unix Epoch conversion */ - winTime /= 10000000; /* Nano to seconds resolution */ - return (time_t)winTime; + /* Windows to Unix Epoch conversion */ + return winTime - 116444736000000000LL; +} + +static inline time_t filetime_to_time_t(const FILETIME *ft) +{ + return (time_t)(filetime_to_hnsec(ft) / 10000000); } /* 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. + * + * If follow is true then act like stat() and report on the link + * target. Otherwise report on the link itself. */ -static int do_lstat(const char *file_name, struct stat *buf) +static int do_lstat(int follow, const char *file_name, struct stat *buf) { + int err; WIN32_FILE_ATTRIBUTE_DATA fdata; - if (!(errno = get_file_attr(file_name, &fdata))) { + if (!(err = get_file_attr(file_name, &fdata))) { buf->st_ino = 0; buf->st_gid = 0; buf->st_uid = 0; @@ -170,8 +424,28 @@ static int do_lstat(const char *file_name, struct stat *buf) buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime)); buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime)); buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime)); + if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + WIN32_FIND_DATAA findbuf; + HANDLE handle = FindFirstFileA(file_name, &findbuf); + if (handle != INVALID_HANDLE_VALUE) { + if ((findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && + (findbuf.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) { + if (follow) { + char buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; + buf->st_size = readlink(file_name, buffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE); + } else { + buf->st_mode = S_IFLNK; + } + buf->st_mode |= S_IREAD; + if (!(findbuf.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) + buf->st_mode |= S_IWRITE; + } + FindClose(handle); + } + } return 0; } + errno = err; return -1; } @@ -181,12 +455,12 @@ static int do_lstat(const char *file_name, struct stat *buf) * complete. Note that Git stat()s are redirected to mingw_lstat() * too, since Windows doesn't really handle symlinks that well. */ -int mingw_lstat(const char *file_name, struct stat *buf) +static int do_stat_internal(int follow, const char *file_name, struct stat *buf) { int namelen; static char alt_name[PATH_MAX]; - if (!do_lstat(file_name, buf)) + if (!do_lstat(follow, file_name, buf)) return 0; /* if file_name ended in a '/', Windows returned ENOENT; @@ -205,7 +479,16 @@ int mingw_lstat(const char *file_name, struct stat *buf) memcpy(alt_name, file_name, namelen); alt_name[namelen] = 0; - return do_lstat(alt_name, buf); + return do_lstat(follow, alt_name, buf); +} + +int mingw_lstat(const char *file_name, struct stat *buf) +{ + return do_stat_internal(0, file_name, buf); +} +int mingw_stat(const char *file_name, struct stat *buf) +{ + return do_stat_internal(1, file_name, buf); } #undef fstat @@ -253,17 +536,38 @@ int mingw_utime (const char *file_name, const struct utimbuf *times) int fh, rc; /* must have write permission */ - if ((fh = open(file_name, O_RDWR | O_BINARY)) < 0) - return -1; + DWORD attrs = GetFileAttributes(file_name); + if (attrs != INVALID_FILE_ATTRIBUTES && + (attrs & FILE_ATTRIBUTE_READONLY)) { + /* ignore errors here; open() will report them */ + SetFileAttributes(file_name, attrs & ~FILE_ATTRIBUTE_READONLY); + } + + if ((fh = open(file_name, O_RDWR | O_BINARY)) < 0) { + rc = -1; + goto revert_attrs; + } - time_t_to_filetime(times->modtime, &mft); - time_t_to_filetime(times->actime, &aft); + if (times) { + time_t_to_filetime(times->modtime, &mft); + time_t_to_filetime(times->actime, &aft); + } else { + GetSystemTimeAsFileTime(&mft); + aft = mft; + } if (!SetFileTime((HANDLE)_get_osfhandle(fh), NULL, &aft, &mft)) { errno = EINVAL; rc = -1; } else rc = 0; close(fh); + +revert_attrs: + if (attrs != INVALID_FILE_ATTRIBUTES && + (attrs & FILE_ATTRIBUTE_READONLY)) { + /* ignore errors again */ + SetFileAttributes(file_name, attrs); + } return rc; } @@ -283,129 +587,37 @@ int mkstemp(char *template) int gettimeofday(struct timeval *tv, void *tz) { - SYSTEMTIME st; - struct tm tm; - GetSystemTime(&st); - tm.tm_year = st.wYear-1900; - tm.tm_mon = st.wMonth-1; - tm.tm_mday = st.wDay; - tm.tm_hour = st.wHour; - tm.tm_min = st.wMinute; - tm.tm_sec = st.wSecond; - tv->tv_sec = tm_to_time_t(&tm); - if (tv->tv_sec < 0) - return -1; - tv->tv_usec = st.wMilliseconds*1000; + FILETIME ft; + long long hnsec; + + GetSystemTimeAsFileTime(&ft); + hnsec = filetime_to_hnsec(&ft); + tv->tv_sec = hnsec / 10000000; + tv->tv_usec = (hnsec % 10000000) / 10; return 0; } int pipe(int filedes[2]) { - int fd; - HANDLE h[2], parent; + HANDLE h[2]; - if (_pipe(filedes, 8192, 0) < 0) - return -1; - - parent = GetCurrentProcess(); - - if (!DuplicateHandle (parent, (HANDLE)_get_osfhandle(filedes[0]), - parent, &h[0], 0, FALSE, DUPLICATE_SAME_ACCESS)) { - close(filedes[0]); - close(filedes[1]); - return -1; - } - if (!DuplicateHandle (parent, (HANDLE)_get_osfhandle(filedes[1]), - parent, &h[1], 0, FALSE, DUPLICATE_SAME_ACCESS)) { - close(filedes[0]); - close(filedes[1]); - CloseHandle(h[0]); + /* this creates non-inheritable handles */ + if (!CreatePipe(&h[0], &h[1], NULL, 8192)) { + errno = err_win_to_posix(GetLastError()); return -1; } - fd = _open_osfhandle((int)h[0], O_NOINHERIT); - if (fd < 0) { - close(filedes[0]); - close(filedes[1]); + filedes[0] = _open_osfhandle((int)h[0], O_NOINHERIT); + if (filedes[0] < 0) { CloseHandle(h[0]); CloseHandle(h[1]); return -1; } - close(filedes[0]); - filedes[0] = fd; - fd = _open_osfhandle((int)h[1], O_NOINHERIT); - if (fd < 0) { + filedes[1] = _open_osfhandle((int)h[1], O_NOINHERIT); + if (filedes[0] < 0) { close(filedes[0]); - close(filedes[1]); CloseHandle(h[1]); return -1; } - close(filedes[1]); - filedes[1] = fd; - return 0; -} - -int poll(struct pollfd *ufds, unsigned int nfds, int timeout) -{ - int i, pending; - - if (timeout >= 0) { - if (nfds == 0) { - Sleep(timeout); - return 0; - } - return errno = EINVAL, error("poll timeout not supported"); - } - - /* When there is only one fd to wait for, then we pretend that - * input is available and let the actual wait happen when the - * caller invokes read(). - */ - if (nfds == 1) { - if (!(ufds[0].events & POLLIN)) - return errno = EINVAL, error("POLLIN not set"); - ufds[0].revents = POLLIN; - return 0; - } - -repeat: - pending = 0; - for (i = 0; i < nfds; i++) { - DWORD avail = 0; - HANDLE h = (HANDLE) _get_osfhandle(ufds[i].fd); - if (h == INVALID_HANDLE_VALUE) - return -1; /* errno was set */ - - if (!(ufds[i].events & POLLIN)) - return errno = EINVAL, error("POLLIN not set"); - - /* this emulation works only for pipes */ - if (!PeekNamedPipe(h, NULL, 0, NULL, &avail, NULL)) { - int err = GetLastError(); - if (err == ERROR_BROKEN_PIPE) { - ufds[i].revents = POLLHUP; - pending++; - } else { - errno = EINVAL; - return error("PeekNamedPipe failed," - " GetLastError: %u", err); - } - } else if (avail) { - ufds[i].revents = POLLIN; - pending++; - } else - ufds[i].revents = 0; - } - if (!pending) { - /* The only times that we spin here is when the process - * that is connected through the pipes is waiting for - * its own input data to become available. But since - * the process (pack-objects) is itself CPU intensive, - * it will happily pick up the time slice that we are - * relinquishing here. - */ - Sleep(0); - goto repeat; - } return 0; } @@ -436,19 +648,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) @@ -548,7 +747,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) @@ -613,7 +812,7 @@ static char *lookup_prog(const char *dir, const char *cmd, int isexe, int exe_on } /* - * Determines the absolute path of cmd using the the split path in path. + * Determines the absolute path of cmd using the split path in path. * If cmd contains a slash or backslash, no lookup is performed. */ static char *path_lookup(const char *cmd, char **path, int exe_only) @@ -638,8 +837,17 @@ static int env_compare(const void *a, const void *b) return strcasecmp(*ea, *eb); } -static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env, - int prepend_cmd) +struct pinfo_t { + struct pinfo_t *next; + pid_t pid; + HANDLE proc; +} pinfo_t; +struct pinfo_t *pinfo = NULL; +CRITICAL_SECTION pinfo_cs; + +static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env, + const char *dir, + int prepend_cmd, int fhin, int fhout, int fherr) { STARTUPINFO si; PROCESS_INFORMATION pi; @@ -675,9 +883,9 @@ static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env, memset(&si, 0, sizeof(si)); si.cb = sizeof(si); si.dwFlags = STARTF_USESTDHANDLES; - si.hStdInput = (HANDLE) _get_osfhandle(0); - si.hStdOutput = (HANDLE) _get_osfhandle(1); - si.hStdError = (HANDLE) _get_osfhandle(2); + si.hStdInput = (HANDLE) _get_osfhandle(fhin); + si.hStdOutput = (HANDLE) _get_osfhandle(fhout); + si.hStdError = (HANDLE) _get_osfhandle(fherr); /* concatenate argv, quoting args as we go */ strbuf_init(&args, 0); @@ -718,7 +926,7 @@ static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env, memset(&pi, 0, sizeof(pi)); ret = CreateProcess(cmd, args.buf, NULL, NULL, TRUE, flags, - env ? envblk.buf : NULL, NULL, &si, &pi); + env ? envblk.buf : NULL, dir, &si, &pi); if (env) strbuf_release(&envblk); @@ -729,10 +937,37 @@ static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env, return -1; } CloseHandle(pi.hThread); - return (pid_t)pi.hProcess; + + /* + * The process ID is the human-readable identifier of the process + * that we want to present in log and error messages. The handle + * is not useful for this purpose. But we cannot close it, either, + * because it is not possible to turn a process ID into a process + * handle after the process terminated. + * Keep the handle in a list for waitpid. + */ + EnterCriticalSection(&pinfo_cs); + { + struct pinfo_t *info = xmalloc(sizeof(struct pinfo_t)); + info->pid = pi.dwProcessId; + info->proc = pi.hProcess; + info->next = pinfo; + pinfo = info; + } + LeaveCriticalSection(&pinfo_cs); + + return (pid_t)pi.dwProcessId; +} + +static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env, + int prepend_cmd) +{ + return mingw_spawnve_fd(cmd, argv, env, NULL, prepend_cmd, 0, 1, 2); } -pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env) +pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env, + const char *dir, + int fhin, int fhout, int fherr) { pid_t pid; char **path = get_path_split(); @@ -754,13 +989,15 @@ pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env) pid = -1; } else { - pid = mingw_spawnve(iprog, argv, env, 1); + pid = mingw_spawnve_fd(iprog, argv, env, dir, 1, + fhin, fhout, fherr); free(iprog); } argv[0] = argv0; } else - pid = mingw_spawnve(prog, argv, env, 0); + pid = mingw_spawnve_fd(prog, argv, env, dir, 0, + fhin, fhout, fherr); free(prog); } free_path_split(path); @@ -815,7 +1052,7 @@ static void mingw_execve(const char *cmd, char *const *argv, char *const *env) } } -void mingw_execvp(const char *cmd, char *const *argv) +int mingw_execvp(const char *cmd, char *const *argv) { char **path = get_path_split(); char *prog = path_lookup(cmd, path, 0); @@ -827,6 +1064,32 @@ void mingw_execvp(const char *cmd, char *const *argv) errno = ENOENT; free_path_split(path); + return -1; +} + +int mingw_execv(const char *cmd, char *const *argv) +{ + mingw_execve(cmd, argv, environ); + return -1; +} + +int mingw_kill(pid_t pid, int sig) +{ + if (pid > 0 && sig == SIGTERM) { + HANDLE h = OpenProcess(PROCESS_TERMINATE, FALSE, pid); + + if (TerminateProcess(h, -1)) { + CloseHandle(h); + return 0; + } + + errno = err_win_to_posix(GetLastError()); + CloseHandle(h); + return -1; + } + + errno = EINVAL; + return -1; } static char **copy_environ(void) @@ -903,23 +1166,253 @@ char **make_augmented_environ(const char *const *vars) return env; } -/* this is the first function to call into WS_32; initialize it */ -#undef gethostbyname -struct hostent *mingw_gethostbyname(const char *host) +#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 is null. It + * does a simple search using gethostbyname, and returns one IPv4 host + * if one was found. + */ +static int WSAAPI getaddrinfo_stub(const char *node, const char *service, + const struct addrinfo *hints, + struct addrinfo **res) +{ + struct hostent *h = NULL; + struct addrinfo *ai; + struct sockaddr_in *sin; + + if (node) { + h = gethostbyname(node); + if (!h) + return WSAGetLastError(); + } + + ai = xmalloc(sizeof(struct addrinfo)); + *res = ai; + ai->ai_flags = 0; + ai->ai_family = AF_INET; + ai->ai_socktype = hints ? hints->ai_socktype : 0; + switch (ai->ai_socktype) { + case SOCK_STREAM: + ai->ai_protocol = IPPROTO_TCP; + break; + case SOCK_DGRAM: + ai->ai_protocol = IPPROTO_UDP; + break; + default: + ai->ai_protocol = 0; + break; + } + ai->ai_addrlen = sizeof(struct sockaddr_in); + if (hints && (hints->ai_flags & AI_CANONNAME)) + ai->ai_canonname = h ? xstrdup(h->h_name) : NULL; + else + ai->ai_canonname = NULL; + + sin = xmalloc(ai->ai_addrlen); + memset(sin, 0, ai->ai_addrlen); + sin->sin_family = AF_INET; + /* Note: getaddrinfo is supposed to allow service to be a string, + * which should be looked up using getservbyname. This is + * currently not implemented */ + if (service) + sin->sin_port = htons(atoi(service)); + if (h) + sin->sin_addr = *(struct in_addr *)h->h_addr; + else if (hints && (hints->ai_flags & AI_PASSIVE)) + sin->sin_addr.s_addr = INADDR_ANY; + else + sin->sin_addr.s_addr = INADDR_LOOPBACK; + ai->ai_addr = (struct sockaddr *)sin; + ai->ai_next = 0; + return 0; +} + +static void WSAAPI freeaddrinfo_stub(struct addrinfo *res) +{ + free(res->ai_canonname); + free(res->ai_addr); + free(res); +} + +static int WSAAPI getnameinfo_stub(const struct sockaddr *sa, socklen_t salen, + char *host, DWORD hostlen, + char *serv, DWORD servlen, int flags) +{ + const struct sockaddr_in *sin = (const struct sockaddr_in *)sa; + if (sa->sa_family != AF_INET) + return EAI_FAMILY; + if (!host && !serv) + return EAI_NONAME; + + if (host && hostlen > 0) { + struct hostent *ent = NULL; + if (!(flags & NI_NUMERICHOST)) + ent = gethostbyaddr((const char *)&sin->sin_addr, + sizeof(sin->sin_addr), AF_INET); + + if (ent) + snprintf(host, hostlen, "%s", ent->h_name); + else if (flags & NI_NAMEREQD) + return EAI_NONAME; + else + snprintf(host, hostlen, "%s", inet_ntoa(sin->sin_addr)); + } + + if (serv && servlen > 0) { + struct servent *ent = NULL; + if (!(flags & NI_NUMERICSERV)) + ent = getservbyport(sin->sin_port, + flags & NI_DGRAM ? "udp" : "tcp"); + + if (ent) + snprintf(serv, servlen, "%s", ent->s_name); + else + snprintf(serv, servlen, "%d", ntohs(sin->sin_port)); + } + + return 0; +} + +static HMODULE ipv6_dll = NULL; +static void (WSAAPI *ipv6_freeaddrinfo)(struct addrinfo *res); +static int (WSAAPI *ipv6_getaddrinfo)(const char *node, const char *service, + const struct addrinfo *hints, + struct addrinfo **res); +static int (WSAAPI *ipv6_getnameinfo)(const struct sockaddr *sa, socklen_t salen, + char *host, DWORD hostlen, + char *serv, DWORD servlen, int flags); +/* + * gai_strerror is an inline function in the ws2tcpip.h header, so we + * don't need to try to load that one dynamically. + */ + +static void socket_cleanup(void) +{ + WSACleanup(); + if (ipv6_dll) + FreeLibrary(ipv6_dll); + ipv6_dll = NULL; + ipv6_freeaddrinfo = freeaddrinfo_stub; + ipv6_getaddrinfo = getaddrinfo_stub; + ipv6_getnameinfo = getnameinfo_stub; +} + +static void ensure_socket_initialization(void) { WSADATA wsa; + static int initialized = 0; + const char *libraries[] = { "ws2_32.dll", "wship6.dll", NULL }; + const char **name; + + if (initialized) + return; if (WSAStartup(MAKEWORD(2,2), &wsa)) die("unable to initialize winsock subsystem, error %d", WSAGetLastError()); - atexit((void(*)(void)) WSACleanup); + + for (name = libraries; *name; name++) { + ipv6_dll = LoadLibrary(*name); + if (!ipv6_dll) + continue; + + ipv6_freeaddrinfo = (void (WSAAPI *)(struct addrinfo *)) + GetProcAddress(ipv6_dll, "freeaddrinfo"); + ipv6_getaddrinfo = (int (WSAAPI *)(const char *, const char *, + const struct addrinfo *, + struct addrinfo **)) + GetProcAddress(ipv6_dll, "getaddrinfo"); + ipv6_getnameinfo = (int (WSAAPI *)(const struct sockaddr *, + socklen_t, char *, DWORD, + char *, DWORD, int)) + GetProcAddress(ipv6_dll, "getnameinfo"); + if (!ipv6_freeaddrinfo || !ipv6_getaddrinfo || !ipv6_getnameinfo) { + FreeLibrary(ipv6_dll); + ipv6_dll = NULL; + } else + break; + } + if (!ipv6_freeaddrinfo || !ipv6_getaddrinfo || !ipv6_getnameinfo) { + ipv6_freeaddrinfo = freeaddrinfo_stub; + ipv6_getaddrinfo = getaddrinfo_stub; + ipv6_getnameinfo = getnameinfo_stub; + } + + atexit(socket_cleanup); + 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) +{ + ensure_socket_initialization(); return gethostbyname(host); } +void mingw_freeaddrinfo(struct addrinfo *res) +{ + ipv6_freeaddrinfo(res); +} + +int mingw_getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res) +{ + ensure_socket_initialization(); + return ipv6_getaddrinfo(node, service, hints, res); +} + +int mingw_getnameinfo(const struct sockaddr *sa, socklen_t salen, + char *host, DWORD hostlen, char *serv, DWORD servlen, + int flags) +{ + ensure_socket_initialization(); + return ipv6_getnameinfo(sa, salen, host, hostlen, serv, servlen, flags); +} + int mingw_socket(int domain, int type, int protocol) { int sockfd; - SOCKET s = WSASocket(domain, type, protocol, NULL, 0, 0); + SOCKET s; + + ensure_socket_initialization(); + s = WSASocket(domain, type, protocol, NULL, 0, 0); if (s == INVALID_SOCKET) { /* * WSAGetLastError() values are regular BSD error codes @@ -949,12 +1442,57 @@ int mingw_connect(int sockfd, struct sockaddr *sa, size_t sz) return connect(s, sa, sz); } +#undef bind +int mingw_bind(int sockfd, struct sockaddr *sa, size_t sz) +{ + SOCKET s = (SOCKET)_get_osfhandle(sockfd); + return bind(s, sa, sz); +} + +#undef setsockopt +int mingw_setsockopt(int sockfd, int lvl, int optname, void *optval, int optlen) +{ + SOCKET s = (SOCKET)_get_osfhandle(sockfd); + 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) +{ + SOCKET s = (SOCKET)_get_osfhandle(sockfd); + return listen(s, backlog); +} + +#undef accept +int mingw_accept(int sockfd1, struct sockaddr *sa, socklen_t *sz) +{ + int sockfd2; + + SOCKET s1 = (SOCKET)_get_osfhandle(sockfd1); + SOCKET s2 = accept(s1, sa, sz); + + /* convert into a file descriptor */ + if ((sockfd2 = _open_osfhandle(s2, O_RDWR|O_BINARY)) < 0) { + int err = errno; + closesocket(s2); + return error("unable to make a socket file descriptor: %s", + strerror(err)); + } + return sockfd2; +} + #undef rename 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. @@ -996,10 +1534,27 @@ 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; } +/* + * Note that this doesn't return the actual pagesize, but + * the allocation granularity. If future Windows specific git code + * needs the real getpagesize function, we need to find another solution. + */ +int mingw_getpagesize(void) +{ + SYSTEM_INFO si; + GetSystemInfo(&si); + return si.dwAllocationGranularity; +} + struct passwd *getpwuid(int uid) { static char user_name[100]; @@ -1018,7 +1573,7 @@ static HANDLE timer_event; static HANDLE timer_thread; static int timer_interval; static int one_shot; -static sig_handler_t timer_fn = SIG_DFL; +static sig_handler_t timer_fn = SIG_DFL, sigint_fn = SIG_DFL; /* The timer works like this: * The thread, ticktack(), is a trivial routine that most of the time @@ -1032,10 +1587,7 @@ static sig_handler_t timer_fn = SIG_DFL; static unsigned __stdcall ticktack(void *dummy) { while (WaitForSingleObject(timer_event, timer_interval) == WAIT_TIMEOUT) { - if (timer_fn == SIG_DFL) - die("Alarm"); - if (timer_fn != SIG_IGN) - timer_fn(SIGALRM); + mingw_raise(SIGALRM); if (one_shot) break; } @@ -1126,12 +1678,49 @@ int sigaction(int sig, struct sigaction *in, struct sigaction *out) sig_handler_t mingw_signal(int sig, sig_handler_t handler) { sig_handler_t old = timer_fn; - if (sig != SIGALRM) + + switch (sig) { + case SIGALRM: + timer_fn = handler; + break; + + case SIGINT: + sigint_fn = handler; + break; + + default: return signal(sig, handler); - timer_fn = handler; + } + return old; } +#undef raise +int mingw_raise(int sig) +{ + switch (sig) { + case SIGALRM: + if (timer_fn == SIG_DFL) { + if (isatty(STDERR_FILENO)) + fputs("Alarm clock\n", stderr); + exit(128 + SIGALRM); + } else if (timer_fn != SIG_IGN) + timer_fn(SIGALRM); + return 0; + + case SIGINT: + if (sigint_fn == SIG_DFL) + exit(128 + SIGINT); + else if (sigint_fn != SIG_IGN) + sigint_fn(SIGINT); + return 0; + + default: + return raise(sig); + } +} + + static const char *make_backslash_path(const char *path) { static char buf[PATH_MAX + 1]; @@ -1150,8 +1739,26 @@ static const char *make_backslash_path(const char *path) 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"); - ShellExecute(NULL, "open", htmlpath, NULL, "\\", 0); + 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) @@ -1175,77 +1782,54 @@ int link(const char *oldpath, const char *newpath) return 0; } -char *getpass(const char *prompt) +pid_t waitpid(pid_t pid, int *status, int options) { - struct strbuf buf = STRBUF_INIT; + HANDLE h = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, + FALSE, pid); + if (!h) { + errno = ECHILD; + return -1; + } - fputs(prompt, stderr); - for (;;) { - char c = _getch(); - if (c == '\r' || c == '\n') - break; - strbuf_addch(&buf, c); + if (pid > 0 && options & WNOHANG) { + if (WAIT_OBJECT_0 != WaitForSingleObject(h, 0)) { + CloseHandle(h); + return 0; + } + options &= ~WNOHANG; } - fputs("\n", stderr); - return strbuf_detach(&buf, NULL); -} -#ifndef NO_MINGW_REPLACE_READDIR -/* MinGW readdir implementation to avoid extra lstats for Git */ -struct mingw_DIR -{ - struct _finddata_t dd_dta; /* disk transfer area for this dir */ - struct mingw_dirent dd_dir; /* Our own implementation, including d_type */ - long dd_handle; /* _findnext handle */ - int dd_stat; /* 0 = next entry to read is first entry, -1 = off the end, positive = 0 based index of next entry */ - char dd_name[1]; /* given path for dir with search pattern (struct is extended) */ -}; + if (options == 0) { + struct pinfo_t **ppinfo; + if (WaitForSingleObject(h, INFINITE) != WAIT_OBJECT_0) { + CloseHandle(h); + return 0; + } -struct dirent *mingw_readdir(DIR *dir) -{ - WIN32_FIND_DATAA buf; - HANDLE handle; - struct mingw_DIR *mdir = (struct mingw_DIR*)dir; + if (status) + GetExitCodeProcess(h, (LPDWORD)status); - if (!dir->dd_handle) { - errno = EBADF; /* No set_errno for mingw */ - return NULL; - } + EnterCriticalSection(&pinfo_cs); - if (dir->dd_handle == (long)INVALID_HANDLE_VALUE && dir->dd_stat == 0) - { - DWORD lasterr; - handle = FindFirstFileA(dir->dd_name, &buf); - lasterr = GetLastError(); - dir->dd_handle = (long)handle; - if (handle == INVALID_HANDLE_VALUE && (lasterr != ERROR_NO_MORE_FILES)) { - errno = err_win_to_posix(lasterr); - return NULL; + ppinfo = &pinfo; + while (*ppinfo) { + struct pinfo_t *info = *ppinfo; + if (info->pid == pid) { + CloseHandle(info->proc); + *ppinfo = info->next; + free(info); + break; + } + ppinfo = &info->next; } - } else if (dir->dd_handle == (long)INVALID_HANDLE_VALUE) { - return NULL; - } else if (!FindNextFileA((HANDLE)dir->dd_handle, &buf)) { - DWORD lasterr = GetLastError(); - FindClose((HANDLE)dir->dd_handle); - dir->dd_handle = (long)INVALID_HANDLE_VALUE; - /* POSIX says you shouldn't set errno when readdir can't - find any more files; so, if another error we leave it set. */ - if (lasterr != ERROR_NO_MORE_FILES) - errno = err_win_to_posix(lasterr); - return NULL; - } - /* We get here if `buf' contains valid data. */ - strcpy(dir->dd_dir.d_name, buf.cFileName); - ++dir->dd_stat; + LeaveCriticalSection(&pinfo_cs); - /* Set file type, based on WIN32_FIND_DATA */ - mdir->dd_dir.d_type = 0; - if (buf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - mdir->dd_dir.d_type |= DT_DIR; - else - mdir->dd_dir.d_type |= DT_REG; + CloseHandle(h); + return pid; + } + CloseHandle(h); - return (struct dirent*)&dir->dd_dir; + errno = EINVAL; + return -1; } -#endif // !NO_MINGW_REPLACE_READDIR |