diff --git a/dummy_headers/afunix.h b/dummy_headers/afunix.h new file mode 100644 index 0000000..fbee6f5 --- /dev/null +++ b/dummy_headers/afunix.h @@ -0,0 +1,6 @@ +#define UNIX_PATH_MAX 108 +struct sockaddr_un +{ + ADDRESS_FAMILY sun_family; /* AF_UNIX */ + char sun_path[UNIX_PATH_MAX]; /* pathname */ +} SOCKADDR_UN, *PSOCKADDR_UN;; diff --git a/socketpair.c b/socketpair.c index ededc9f..8b3e441 100644 --- a/socketpair.c +++ b/socketpair.c @@ -49,11 +49,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include +#include #ifdef WIN32 # include /* socklen_t, et al (MSVC20xx) */ # include # include +# include #else # include # include @@ -73,12 +75,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. int dumb_socketpair(SOCKET socks[2], int make_overlapped) { union { + struct sockaddr_un unaddr; struct sockaddr_in inaddr; struct sockaddr addr; } a; SOCKET listener; - int e; - socklen_t addrlen = sizeof(a.inaddr); + int e, ii; + int domain = AF_UNIX; + socklen_t addrlen = sizeof(a.unaddr); DWORD flags = (make_overlapped ? WSA_FLAG_OVERLAPPED : 0); int reuse = 1; @@ -88,52 +92,120 @@ int dumb_socketpair(SOCKET socks[2], int make_overlapped) } socks[0] = socks[1] = -1; - listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (listener == -1) - return SOCKET_ERROR; - - memset(&a, 0, sizeof(a)); - a.inaddr.sin_family = AF_INET; - a.inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - a.inaddr.sin_port = 0; - - for (;;) { - if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, - (char*) &reuse, (socklen_t) sizeof(reuse)) == -1) - break; - if (bind(listener, &a.addr, sizeof(a.inaddr)) == SOCKET_ERROR) - break; + /* AF_UNIX/SOCK_STREAM became available in Windows 10 + * ( https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows ) + * + * We will attempt to use AF_UNIX, but fallback to using AF_INET if + * setting up AF_UNIX socket fails in any other way, which it surely will + * on earlier versions of Windows. + */ + for (ii = 0; ii < 2; ii++) { + listener = socket(domain, SOCK_STREAM, domain == AF_INET ? IPPROTO_TCP : 0); + if (listener == INVALID_SOCKET) + goto fallback; memset(&a, 0, sizeof(a)); - if (getsockname(listener, &a.addr, &addrlen) == SOCKET_ERROR) - break; - // win32 getsockname may only set the port number, p=0.0005. - // ( http://msdn.microsoft.com/library/ms738543.aspx ): - a.inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - a.inaddr.sin_family = AF_INET; + if (domain == AF_UNIX) { + /* Abstract sockets (filesystem-independent) don't work, contrary to + * the claims of the aforementioned blog post: + * ( https://github.com/microsoft/WSL/issues/4240#issuecomment-549663217 ) + * + * So we must use a named path, and that comes with all the attendant + * problems of permissions and collisions. Trying various temporary + * directories and putting high-res time and PID in the filename, that + * seems like a less-bad option. + */ + LARGE_INTEGER ticks; + DWORD n; + int bind_try = 0; + + for (;;) { + switch (bind_try++) { + case 0: + /* "The returned string ends with a backslash" */ + n = GetTempPath(UNIX_PATH_MAX, a.unaddr.sun_path); + break; + case 1: + /* Heckuva job with API consistency, Microsoft! Reversed argument order, and + * "This path does not end with a backslash unless the Windows directory is the root directory.." + */ + n = GetWindowsDirectory(a.unaddr.sun_path, UNIX_PATH_MAX); + n += snprintf(a.unaddr.sun_path + n, UNIX_PATH_MAX - n, "\\Temp\\"); + break; + case 2: + n = snprintf(a.unaddr.sun_path, UNIX_PATH_MAX, "C:\\Temp\\"); + break; + case 3: + n = 0; /* Current directory */ + break; + case 4: + goto fallback; + } + + /* GetTempFileName could be used here. + * ( https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettempfilenamea ) + * However it only adds 16 bits of time-based random bits, + * fails if there isn't room for a 14-character filename, and + * seems to offers no other apparent advantages. So we will + * use high-res timer ticks and PID for filename. + */ + QueryPerformanceCounter(&ticks); + snprintf(a.unaddr.sun_path + n, UNIX_PATH_MAX - n, + "%"PRIx64"-%"PRId32".$$$", ticks.QuadPart, GetCurrentProcessId()); + a.unaddr.sun_family = AF_UNIX; + + if (bind(listener, &a.addr, addrlen) != SOCKET_ERROR) + break; + } + } else { + a.inaddr.sin_family = AF_INET; + a.inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + a.inaddr.sin_port = 0; + + if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, + (char *) &reuse, (socklen_t) sizeof(reuse)) == -1) + goto fallback;; + + if (bind(listener, &a.addr, addrlen) == SOCKET_ERROR) + goto fallback; + + memset(&a, 0, sizeof(a)); + if (getsockname(listener, &a.addr, &addrlen) == SOCKET_ERROR) + goto fallback; + + // win32 getsockname may only set the port number, p=0.0005. + // ( https://docs.microsoft.com/windows/win32/api/winsock/nf-winsock-getsockname ): + a.inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + a.inaddr.sin_family = AF_INET; + } if (listen(listener, 1) == SOCKET_ERROR) - break; + goto fallback; - socks[0] = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, flags); - if (socks[0] == -1) - break; - if (connect(socks[0], &a.addr, sizeof(a.inaddr)) == SOCKET_ERROR) - break; + socks[0] = WSASocket(domain, SOCK_STREAM, 0, NULL, 0, flags); + if (socks[0] == INVALID_SOCKET) + goto fallback; + if (connect(socks[0], &a.addr, addrlen) == SOCKET_ERROR) + goto fallback; socks[1] = accept(listener, NULL, NULL); - if (socks[1] == -1) - break; + if (socks[1] == INVALID_SOCKET) + goto fallback; closesocket(listener); return 0; + + fallback: + domain = AF_INET; + addrlen = sizeof(a.inaddr); + + e = WSAGetLastError(); + closesocket(listener); + closesocket(socks[0]); + closesocket(socks[1]); + WSASetLastError(e); } - e = WSAGetLastError(); - closesocket(listener); - closesocket(socks[0]); - closesocket(socks[1]); - WSASetLastError(e); socks[0] = socks[1] = -1; return SOCKET_ERROR; }