Enable support for IPv6 on MinGW
[gitweb.git] / compat / mingw.c
index cdeda1d9859fd545950e0c39ee7de3c9dc09bb07..0d73f15fa894cb0ded20f0cdd1f7fec3da1643f1 100644 (file)
@@ -1,8 +1,9 @@
 #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)
 {
@@ -122,13 +123,17 @@ 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))
@@ -525,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';
@@ -579,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);
@@ -823,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;
@@ -860,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);
@@ -885,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;
@@ -982,6 +1176,18 @@ int mingw_rename(const char *pold, const char *pnew)
        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];
@@ -1011,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)
@@ -1107,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;
 }
@@ -1138,7 +1344,7 @@ void mingw_open_html(const char *unixpath)
 
 int link(const char *oldpath, const char *newpath)
 {
-       typedef BOOL WINAPI (*T)(const char*, const char*, LPSECURITY_ATTRIBUTES);
+       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(
@@ -1156,3 +1362,78 @@ int link(const char *oldpath, const char *newpath)
        }
        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