summaryrefslogtreecommitdiff
path: root/compat/mingw.c
diff options
context:
space:
mode:
Diffstat (limited to 'compat/mingw.c')
-rw-r--r--compat/mingw.c476
1 files changed, 455 insertions, 21 deletions
diff --git a/compat/mingw.c b/compat/mingw.c
index 3dbe6a77ff..0d73f15fa8 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -1,21 +1,139 @@
#include "../git-compat-util.h"
#include "win32.h"
+#include <conio.h>
#include "../strbuf.h"
-unsigned int _CRT_fmode = _O_BINARY;
+#include <shellapi.h>
+
+static int err_win_to_posix(DWORD winerr)
+{
+ int error = ENOSYS;
+ switch(winerr) {
+ case ERROR_ACCESS_DENIED: error = EACCES; break;
+ case ERROR_ACCOUNT_DISABLED: error = EACCES; break;
+ case ERROR_ACCOUNT_RESTRICTION: error = EACCES; break;
+ case ERROR_ALREADY_ASSIGNED: error = EBUSY; break;
+ case ERROR_ALREADY_EXISTS: error = EEXIST; break;
+ case ERROR_ARITHMETIC_OVERFLOW: error = ERANGE; break;
+ case ERROR_BAD_COMMAND: error = EIO; break;
+ case ERROR_BAD_DEVICE: error = ENODEV; break;
+ case ERROR_BAD_DRIVER_LEVEL: error = ENXIO; break;
+ case ERROR_BAD_EXE_FORMAT: error = ENOEXEC; break;
+ case ERROR_BAD_FORMAT: error = ENOEXEC; break;
+ case ERROR_BAD_LENGTH: error = EINVAL; break;
+ case ERROR_BAD_PATHNAME: error = ENOENT; break;
+ case ERROR_BAD_PIPE: error = EPIPE; break;
+ case ERROR_BAD_UNIT: error = ENODEV; break;
+ case ERROR_BAD_USERNAME: error = EINVAL; break;
+ case ERROR_BROKEN_PIPE: error = EPIPE; break;
+ case ERROR_BUFFER_OVERFLOW: error = ENAMETOOLONG; break;
+ case ERROR_BUSY: error = EBUSY; break;
+ case ERROR_BUSY_DRIVE: error = EBUSY; break;
+ case ERROR_CALL_NOT_IMPLEMENTED: error = ENOSYS; break;
+ case ERROR_CANNOT_MAKE: error = EACCES; break;
+ case ERROR_CANTOPEN: error = EIO; break;
+ case ERROR_CANTREAD: error = EIO; break;
+ case ERROR_CANTWRITE: error = EIO; break;
+ case ERROR_CRC: error = EIO; break;
+ case ERROR_CURRENT_DIRECTORY: error = EACCES; break;
+ case ERROR_DEVICE_IN_USE: error = EBUSY; break;
+ case ERROR_DEV_NOT_EXIST: error = ENODEV; break;
+ case ERROR_DIRECTORY: error = EINVAL; break;
+ case ERROR_DIR_NOT_EMPTY: error = ENOTEMPTY; break;
+ case ERROR_DISK_CHANGE: error = EIO; break;
+ case ERROR_DISK_FULL: error = ENOSPC; break;
+ case ERROR_DRIVE_LOCKED: error = EBUSY; break;
+ case ERROR_ENVVAR_NOT_FOUND: error = EINVAL; break;
+ case ERROR_EXE_MARKED_INVALID: error = ENOEXEC; break;
+ case ERROR_FILENAME_EXCED_RANGE: error = ENAMETOOLONG; break;
+ case ERROR_FILE_EXISTS: error = EEXIST; break;
+ case ERROR_FILE_INVALID: error = ENODEV; break;
+ case ERROR_FILE_NOT_FOUND: error = ENOENT; break;
+ case ERROR_GEN_FAILURE: error = EIO; break;
+ case ERROR_HANDLE_DISK_FULL: error = ENOSPC; break;
+ case ERROR_INSUFFICIENT_BUFFER: error = ENOMEM; break;
+ case ERROR_INVALID_ACCESS: error = EACCES; break;
+ case ERROR_INVALID_ADDRESS: error = EFAULT; break;
+ case ERROR_INVALID_BLOCK: error = EFAULT; break;
+ case ERROR_INVALID_DATA: error = EINVAL; break;
+ case ERROR_INVALID_DRIVE: error = ENODEV; break;
+ case ERROR_INVALID_EXE_SIGNATURE: error = ENOEXEC; break;
+ case ERROR_INVALID_FLAGS: error = EINVAL; break;
+ case ERROR_INVALID_FUNCTION: error = ENOSYS; break;
+ case ERROR_INVALID_HANDLE: error = EBADF; break;
+ case ERROR_INVALID_LOGON_HOURS: error = EACCES; break;
+ case ERROR_INVALID_NAME: error = EINVAL; break;
+ case ERROR_INVALID_OWNER: error = EINVAL; break;
+ case ERROR_INVALID_PARAMETER: error = EINVAL; break;
+ case ERROR_INVALID_PASSWORD: error = EPERM; break;
+ case ERROR_INVALID_PRIMARY_GROUP: error = EINVAL; break;
+ case ERROR_INVALID_SIGNAL_NUMBER: error = EINVAL; break;
+ case ERROR_INVALID_TARGET_HANDLE: error = EIO; break;
+ case ERROR_INVALID_WORKSTATION: error = EACCES; break;
+ case ERROR_IO_DEVICE: error = EIO; break;
+ case ERROR_IO_INCOMPLETE: error = EINTR; break;
+ case ERROR_LOCKED: error = EBUSY; break;
+ case ERROR_LOCK_VIOLATION: error = EACCES; break;
+ case ERROR_LOGON_FAILURE: error = EACCES; break;
+ case ERROR_MAPPED_ALIGNMENT: error = EINVAL; break;
+ case ERROR_META_EXPANSION_TOO_LONG: error = E2BIG; break;
+ case ERROR_MORE_DATA: error = EPIPE; break;
+ case ERROR_NEGATIVE_SEEK: error = ESPIPE; break;
+ case ERROR_NOACCESS: error = EFAULT; break;
+ case ERROR_NONE_MAPPED: error = EINVAL; break;
+ case ERROR_NOT_ENOUGH_MEMORY: error = ENOMEM; break;
+ case ERROR_NOT_READY: error = EAGAIN; break;
+ case ERROR_NOT_SAME_DEVICE: error = EXDEV; break;
+ case ERROR_NO_DATA: error = EPIPE; break;
+ case ERROR_NO_MORE_SEARCH_HANDLES: error = EIO; break;
+ case ERROR_NO_PROC_SLOTS: error = EAGAIN; break;
+ case ERROR_NO_SUCH_PRIVILEGE: error = EACCES; break;
+ case ERROR_OPEN_FAILED: error = EIO; break;
+ case ERROR_OPEN_FILES: error = EBUSY; break;
+ case ERROR_OPERATION_ABORTED: error = EINTR; break;
+ case ERROR_OUTOFMEMORY: error = ENOMEM; break;
+ case ERROR_PASSWORD_EXPIRED: error = EACCES; break;
+ case ERROR_PATH_BUSY: error = EBUSY; break;
+ case ERROR_PATH_NOT_FOUND: error = ENOENT; break;
+ case ERROR_PIPE_BUSY: error = EBUSY; break;
+ case ERROR_PIPE_CONNECTED: error = EPIPE; break;
+ case ERROR_PIPE_LISTENING: error = EPIPE; break;
+ case ERROR_PIPE_NOT_CONNECTED: error = EPIPE; break;
+ case ERROR_PRIVILEGE_NOT_HELD: error = EACCES; break;
+ case ERROR_READ_FAULT: error = EIO; break;
+ case ERROR_SEEK: error = EIO; break;
+ case ERROR_SEEK_ON_DEVICE: error = ESPIPE; break;
+ case ERROR_SHARING_BUFFER_EXCEEDED: error = ENFILE; break;
+ case ERROR_SHARING_VIOLATION: error = EACCES; break;
+ case ERROR_STACK_OVERFLOW: error = ENOMEM; break;
+ case ERROR_SWAPERROR: error = ENOENT; break;
+ case ERROR_TOO_MANY_MODULES: error = EMFILE; break;
+ case ERROR_TOO_MANY_OPEN_FILES: error = EMFILE; break;
+ case ERROR_UNRECOGNIZED_MEDIA: error = ENXIO; break;
+ case ERROR_UNRECOGNIZED_VOLUME: error = ENODEV; break;
+ case ERROR_WAIT_NO_CHILDREN: error = ECHILD; break;
+ case ERROR_WRITE_FAULT: error = EIO; break;
+ case ERROR_WRITE_PROTECT: error = EROFS; break;
+ }
+ return error;
+}
#undef open
int mingw_open (const char *filename, int oflags, ...)
{
va_list args;
unsigned mode;
+ int fd;
+
va_start(args, oflags);
mode = va_arg(args, int);
va_end(args);
if (!strcmp(filename, "/dev/null"))
filename = "nul";
- int fd = open(filename, oflags, mode);
+
+ fd = open(filename, oflags, mode);
+
if (fd < 0 && (oflags & O_CREAT) && errno == EACCES) {
DWORD attrs = GetFileAttributes(filename);
if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY))
@@ -46,7 +164,8 @@ static int do_lstat(const char *file_name, struct stat *buf)
buf->st_uid = 0;
buf->st_nlink = 1;
buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
- buf->st_size = fdata.nFileSizeLow; /* Can't use nFileSizeHigh, since it's not a stat64 */
+ buf->st_size = fdata.nFileSizeLow |
+ (((off_t)fdata.nFileSizeHigh)<<32);
buf->st_dev = buf->st_rdev = 0; /* not used by Git */
buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
@@ -101,7 +220,7 @@ int mingw_fstat(int fd, struct stat *buf)
}
/* direct non-file handles to MS's fstat() */
if (GetFileType(fh) != FILE_TYPE_DISK)
- return fstat(fd, buf);
+ return _fstati64(fd, buf);
if (GetFileInformationByHandle(fh, &fdata)) {
buf->st_ino = 0;
@@ -109,7 +228,8 @@ int mingw_fstat(int fd, struct stat *buf)
buf->st_uid = 0;
buf->st_nlink = 1;
buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
- buf->st_size = fdata.nFileSizeLow; /* Can't use nFileSizeHigh, since it's not a stat64 */
+ buf->st_size = fdata.nFileSizeLow |
+ (((off_t)fdata.nFileSizeHigh)<<32);
buf->st_dev = buf->st_rdev = 0; /* not used by Git */
buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
@@ -281,7 +401,7 @@ repeat:
* 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
- * relinguishing here.
+ * relinquishing here.
*/
Sleep(0);
goto repeat;
@@ -342,7 +462,7 @@ static const char *quote_arg(const char *arg)
const char *p = arg;
if (!*p) force_quotes = 1;
while (*p) {
- if (isspace(*p) || *p == '*' || *p == '?' || *p == '{')
+ if (isspace(*p) || *p == '*' || *p == '?' || *p == '{' || *p == '\'')
force_quotes = 1;
else if (*p == '"')
n++;
@@ -410,8 +530,8 @@ static const char *parse_interpreter(const char *cmd)
if (buf[0] != '#' || buf[1] != '!')
return NULL;
buf[n] = '\0';
- p = strchr(buf, '\n');
- if (!p)
+ p = buf + strcspn(buf, "\r\n");
+ if (!*p)
return NULL;
*p = '\0';
@@ -447,7 +567,7 @@ static char **get_path_split(void)
if (!n)
return NULL;
- path = xmalloc((n+1)*sizeof(char*));
+ path = xmalloc((n+1)*sizeof(char *));
p = envpath;
i = 0;
do {
@@ -464,10 +584,11 @@ static char **get_path_split(void)
static void free_path_split(char **path)
{
+ char **p = path;
+
if (!path)
return;
- char **p = path;
while (*p)
free(*p++);
free(path);
@@ -708,7 +829,7 @@ void mingw_execvp(const char *cmd, char *const *argv)
free_path_split(path);
}
-char **copy_environ()
+static char **copy_environ(void)
{
char **env;
int i = 0;
@@ -745,7 +866,7 @@ static int lookup_env(char **env, const char *name, size_t nmln)
/*
* If name contains '=', then sets the variable, otherwise it unsets it
*/
-char **env_setenv(char **env, const char *name)
+static char **env_setenv(char **env, const char *name)
{
char *eq = strchrnul(name, '=');
int i = lookup_env(env, name, eq-name);
@@ -770,19 +891,207 @@ char **env_setenv(char **env, const char *name)
return env;
}
-/* this is the first function to call into WS_32; initialize it */
-#undef gethostbyname
-struct hostent *mingw_gethostbyname(const char *host)
+/*
+ * Copies global environ and adjusts variables as specified by vars.
+ */
+char **make_augmented_environ(const char *const *vars)
+{
+ char **env = copy_environ();
+
+ while (*vars)
+ env = env_setenv(env, *vars++);
+ return env;
+}
+
+/*
+ * Note, this isn't a complete replacement for getaddrinfo. It assumes
+ * that service contains a numerical port, or that it 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 = gethostbyname(node);
+ struct addrinfo *ai;
+ struct sockaddr_in *sin;
+
+ if (!h)
+ return WSAGetLastError();
+
+ ai = xmalloc(sizeof(struct addrinfo));
+ *res = ai;
+ ai->ai_flags = 0;
+ ai->ai_family = AF_INET;
+ ai->ai_socktype = hints->ai_socktype;
+ switch (hints->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);
+ ai->ai_canonname = strdup(h->h_name);
+
+ sin = xmalloc(ai->ai_addrlen);
+ memset(sin, 0, ai->ai_addrlen);
+ sin->sin_family = AF_INET;
+ if (service)
+ sin->sin_port = htons(atoi(service));
+ sin->sin_addr = *(struct in_addr *)h->h_addr;
+ 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 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;
@@ -819,7 +1128,9 @@ int mingw_connect(int sockfd, struct sockaddr *sa, size_t sz)
#undef rename
int mingw_rename(const char *pold, const char *pnew)
{
- DWORD attrs;
+ DWORD attrs, gle;
+ int tries = 0;
+ static const int delay[] = { 0, 1, 10, 20, 40 };
/*
* Try native rename() first to get errno right.
@@ -829,10 +1140,12 @@ int mingw_rename(const char *pold, const char *pnew)
return 0;
if (errno != EEXIST)
return -1;
+repeat:
if (MoveFileEx(pold, pnew, MOVEFILE_REPLACE_EXISTING))
return 0;
/* TODO: translate more errors */
- if (GetLastError() == ERROR_ACCESS_DENIED &&
+ gle = GetLastError();
+ if (gle == ERROR_ACCESS_DENIED &&
(attrs = GetFileAttributes(pnew)) != INVALID_FILE_ATTRIBUTES) {
if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
errno = EISDIR;
@@ -842,14 +1155,39 @@ int mingw_rename(const char *pold, const char *pnew)
SetFileAttributes(pnew, attrs & ~FILE_ATTRIBUTE_READONLY)) {
if (MoveFileEx(pold, pnew, MOVEFILE_REPLACE_EXISTING))
return 0;
+ gle = GetLastError();
/* revert file attributes on failure */
SetFileAttributes(pnew, attrs);
}
}
+ if (tries < ARRAY_SIZE(delay) && gle == ERROR_ACCESS_DENIED) {
+ /*
+ * 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++;
+ 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];
@@ -879,7 +1217,7 @@ static sig_handler_t timer_fn = SIG_DFL;
* length to call the signal handler.
*/
-static __stdcall unsigned ticktack(void *dummy)
+static unsigned __stdcall ticktack(void *dummy)
{
while (WaitForSingleObject(timer_event, timer_interval) == WAIT_TIMEOUT) {
if (timer_fn == SIG_DFL)
@@ -975,9 +1313,9 @@ int sigaction(int sig, struct sigaction *in, struct sigaction *out)
#undef signal
sig_handler_t mingw_signal(int sig, sig_handler_t handler)
{
+ sig_handler_t old = timer_fn;
if (sig != SIGALRM)
return signal(sig, handler);
- sig_handler_t old = timer_fn;
timer_fn = handler;
return old;
}
@@ -1003,3 +1341,99 @@ void mingw_open_html(const char *unixpath)
printf("Launching default browser to display HTML ...\n");
ShellExecute(NULL, "open", htmlpath, NULL, "\\", 0);
}
+
+int link(const char *oldpath, const char *newpath)
+{
+ typedef BOOL (WINAPI *T)(const char*, const char*, LPSECURITY_ATTRIBUTES);
+ static T create_hard_link = NULL;
+ if (!create_hard_link) {
+ create_hard_link = (T) GetProcAddress(
+ GetModuleHandle("kernel32.dll"), "CreateHardLinkA");
+ if (!create_hard_link)
+ create_hard_link = (T)-1;
+ }
+ if (create_hard_link == (T)-1) {
+ errno = ENOSYS;
+ return -1;
+ }
+ if (!create_hard_link(newpath, oldpath, NULL)) {
+ errno = err_win_to_posix(GetLastError());
+ return -1;
+ }
+ return 0;
+}
+
+char *getpass(const char *prompt)
+{
+ struct strbuf buf = STRBUF_INIT;
+
+ fputs(prompt, stderr);
+ for (;;) {
+ char c = _getch();
+ if (c == '\r' || c == '\n')
+ break;
+ strbuf_addch(&buf, c);
+ }
+ 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) */
+};
+
+struct dirent *mingw_readdir(DIR *dir)
+{
+ WIN32_FIND_DATAA buf;
+ HANDLE handle;
+ struct mingw_DIR *mdir = (struct mingw_DIR*)dir;
+
+ if (!dir->dd_handle) {
+ errno = EBADF; /* No set_errno for mingw */
+ return NULL;
+ }
+
+ 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;
+ }
+ } 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;
+
+ /* 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;
+
+ return (struct dirent*)&dir->dd_dir;
+}
+#endif // !NO_MINGW_REPLACE_READDIR