From ea8a7287e2b96b9c24e5e89fe863e5bfa60bfdda Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 17 Nov 2022 14:50:25 -0800 Subject: Add support for `sockaddr_un` on Windows. (#6513) * Windows: Fix warning about undefined if_indextoname() * Windows: Fix UNIXSocket on MINGW and make .pair more reliable * Windows: Use nonblock=true for read tests with scheduler * Windows: Move socket detection from File.socket? to File.stat Add S_IFSOCK to Windows and interpret reparse points accordingly. Enable tests that work now. * Windows: Use wide-char functions to UNIXSocket This fixes behaviour with non-ASCII characters. It also fixes deletion of temporary UNIXSocket.pair files. * Windows: Add UNIXSocket tests for specifics of Windows impl. * Windows: fix VC build due to missing _snwprintf Avoid usage of _snwprintf, since it fails linking ruby.dll like so: linking shared-library x64-vcruntime140-ruby320.dll x64-vcruntime140-ruby320.def : error LNK2001: unresolved external symbol snwprintf x64-vcruntime140-ruby320.def : error LNK2001: unresolved external symbol vsnwprintf_l whereas linking miniruby.exe succeeds. This patch uses snprintf on the UTF-8 string instead. Also remove branch GetWindowsDirectoryW, since it doesn't work. * Windows: Fix dangling symlink test failures Co-authored-by: Lars Kanis --- win32/Makefile.sub | 3 ++ win32/file.h | 4 ++ win32/win32.c | 154 ++++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 142 insertions(+), 19 deletions(-) (limited to 'win32') diff --git a/win32/Makefile.sub b/win32/Makefile.sub index e84f978bb7..4b8904c536 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -629,6 +629,9 @@ $(CONFIG_H): $(MKFILES) $(srcdir)/win32/Makefile.sub $(win_srcdir)/Makefile.sub #define HAVE_STDDEF_H 1 #define HAVE_STRING_H 1 #define HAVE_MEMORY_H 1 +!if $(MSC_VER) >= 1920 +#define HAVE_AFUNIX_H 1 +!endif !if $(MSC_VER) >= 1400 #define HAVE_LONG_LONG 1 !else diff --git a/win32/file.h b/win32/file.h index ef701487dd..4f1f36a75c 100644 --- a/win32/file.h +++ b/win32/file.h @@ -1,6 +1,10 @@ #ifndef RUBY_WIN32_FILE_H #define RUBY_WIN32_FILE_H +#ifndef IO_REPARSE_TAG_AF_UNIX +# define IO_REPARSE_TAG_AF_UNIX 0x80000023 +#endif + enum { MINIMUM_REPARSE_BUFFER_PATH_LEN = 100 }; diff --git a/win32/win32.c b/win32/win32.c index 3ddfe9bfdf..fee31677f0 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -49,6 +49,9 @@ #ifdef __MINGW32__ #include #endif +#ifdef HAVE_AFUNIX_H +# include +#endif #include "ruby/win32.h" #include "ruby/vm.h" #include "win32/dir.h" @@ -4018,15 +4021,93 @@ rb_w32_getservbyport(int port, const char *proto) return r; } +#ifdef HAVE_AFUNIX_H + +/* License: Ruby's */ +static size_t +socketpair_unix_path(struct sockaddr_un *sock_un) +{ + SOCKET listener; + WCHAR wpath[sizeof(sock_un->sun_path)/sizeof(*sock_un->sun_path)] = L""; + + /* AF_UNIX/SOCK_STREAM became available in Windows 10 + * See https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows + */ + listener = socket(AF_UNIX, SOCK_STREAM, 0); + if (listener == INVALID_SOCKET) + return 0; + + memset(sock_un, 0, sizeof(*sock_un)); + sock_un->sun_family = 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. + */ + for (int try = 0; ; try++) { + LARGE_INTEGER ticks; + size_t path_len = 0; + const size_t maxpath = sizeof(sock_un->sun_path)/sizeof(*sock_un->sun_path); + + switch (try) { + case 0: + /* user temp dir from TMP or TEMP env var, it ends with a backslash */ + path_len = GetTempPathW(maxpath, wpath); + break; + case 1: + wcsncpy(wpath, L"C:/Temp/", maxpath); + path_len = lstrlenW(wpath); + break; + case 2: + /* Current directory */ + path_len = 0; + break; + case 3: + closesocket(listener); + return 0; + } + + /* Windows UNIXSocket implementation expects UTF-8 instead of UTF16 */ + path_len = WideCharToMultiByte(CP_UTF8, 0, wpath, path_len, sock_un->sun_path, maxpath, NULL, NULL); + QueryPerformanceCounter(&ticks); + path_len += snprintf(sock_un->sun_path + path_len, + maxpath - path_len, + "%lld-%ld.($)", + ticks.QuadPart, + GetCurrentProcessId()); + + /* Convert to UTF16 for DeleteFileW */ + MultiByteToWideChar(CP_UTF8, 0, sock_un->sun_path, -1, wpath, sizeof(wpath)/sizeof(*wpath)); + + if (bind(listener, (struct sockaddr *)sock_un, sizeof(*sock_un)) != SOCKET_ERROR) + break; + } + closesocket(listener); + DeleteFileW(wpath); + return sizeof(*sock_un); +} +#endif + /* License: Ruby's */ static int socketpair_internal(int af, int type, int protocol, SOCKET *sv) { SOCKET svr = INVALID_SOCKET, r = INVALID_SOCKET, w = INVALID_SOCKET; struct sockaddr_in sock_in4; + #ifdef INET6 struct sockaddr_in6 sock_in6; #endif + +#ifdef HAVE_AFUNIX_H + struct sockaddr_un sock_un = {0, {0}}; + WCHAR wpath[sizeof(sock_un.sun_path)/sizeof(*sock_un.sun_path)] = L""; +#endif + struct sockaddr *addr; int ret = -1; int len; @@ -4050,6 +4131,15 @@ socketpair_internal(int af, int type, int protocol, SOCKET *sv) addr = (struct sockaddr *)&sock_in6; len = sizeof(sock_in6); break; +#endif +#ifdef HAVE_AFUNIX_H + case AF_UNIX: + addr = (struct sockaddr *)&sock_un; + len = socketpair_unix_path(&sock_un); + MultiByteToWideChar(CP_UTF8, 0, sock_un.sun_path, -1, wpath, sizeof(wpath)/sizeof(*wpath)); + if (len) + break; + /* fall through */ #endif default: errno = EAFNOSUPPORT; @@ -4101,6 +4191,10 @@ socketpair_internal(int af, int type, int protocol, SOCKET *sv) } if (svr != INVALID_SOCKET) closesocket(svr); +#ifdef HAVE_AFUNIX_H + if (sock_un.sun_family == AF_UNIX) + DeleteFileW(wpath); +#endif } return ret; @@ -5632,10 +5726,8 @@ fileattr_to_unixmode(DWORD attr, const WCHAR *path, unsigned mode) /* format is already set */ } else if (attr & FILE_ATTRIBUTE_REPARSE_POINT) { - if (rb_w32_reparse_symlink_p(path)) - mode |= S_IFLNK | S_IEXEC; - else - mode |= S_IFDIR | S_IEXEC; + /* Only used by stat_by_find in the case the file can not be opened. + * In this case we can't get more details. */ } else if (attr & FILE_ATTRIBUTE_DIRECTORY) { mode |= S_IFDIR | S_IEXEC; @@ -5710,14 +5802,6 @@ stat_by_find(const WCHAR *path, struct stati128 *st) { HANDLE h; WIN32_FIND_DATAW wfd; - /* GetFileAttributesEx failed; check why. */ - int e = GetLastError(); - - if ((e == ERROR_FILE_NOT_FOUND) || (e == ERROR_INVALID_NAME) - || (e == ERROR_PATH_NOT_FOUND || (e == ERROR_BAD_NETPATH))) { - errno = map_errno(e); - return -1; - } /* Fall back to FindFirstFile for ERROR_SHARING_VIOLATION */ h = FindFirstFileW(path, &wfd); @@ -5753,9 +5837,24 @@ winnt_stat(const WCHAR *path, struct stati128 *st, BOOL lstat) DWORD flags = lstat ? FILE_FLAG_OPEN_REPARSE_POINT : 0; HANDLE f; WCHAR finalname[PATH_MAX]; + int open_error; memset(st, 0, sizeof(*st)); f = open_special(path, 0, flags); + open_error = GetLastError(); + if (f == INVALID_HANDLE_VALUE && !lstat) { + /* Support stat (not only lstat) of UNIXSocket */ + FILE_ATTRIBUTE_TAG_INFO attr_info; + DWORD e; + + f = open_special(path, 0, FILE_FLAG_OPEN_REPARSE_POINT); + e = GetFileInformationByHandleEx( f, FileAttributeTagInfo, + &attr_info, sizeof(attr_info)); + if (!e || attr_info.ReparseTag != IO_REPARSE_TAG_AF_UNIX) { + CloseHandle(f); + f = INVALID_HANDLE_VALUE; + } + } if (f != INVALID_HANDLE_VALUE) { DWORD attr = stati128_handle(f, st); const DWORD len = get_final_path(f, finalname, numberof(finalname), 0); @@ -5767,15 +5866,26 @@ winnt_stat(const WCHAR *path, struct stati128 *st, BOOL lstat) case FILE_TYPE_PIPE: mode = S_IFIFO; break; + default: + if (attr & FILE_ATTRIBUTE_REPARSE_POINT) { + FILE_ATTRIBUTE_TAG_INFO attr_info; + DWORD e; + + e = GetFileInformationByHandleEx( f, FileAttributeTagInfo, + &attr_info, sizeof(attr_info)); + if (e && attr_info.ReparseTag == IO_REPARSE_TAG_AF_UNIX) { + st->st_size = 0; + mode |= S_IFSOCK; + } else if (rb_w32_reparse_symlink_p(path)) { + /* TODO: size in which encoding? */ + st->st_size = 0; + mode |= S_IFLNK | S_IEXEC; + } else { + mode |= S_IFDIR | S_IEXEC; + } + } } CloseHandle(f); - if (attr & FILE_ATTRIBUTE_REPARSE_POINT) { - /* TODO: size in which encoding? */ - if (rb_w32_reparse_symlink_p(path)) - st->st_size = 0; - else - attr &= ~FILE_ATTRIBUTE_REPARSE_POINT; - } if (attr & FILE_ATTRIBUTE_DIRECTORY) { if (check_valid_dir(path)) return -1; } @@ -5788,6 +5898,12 @@ winnt_stat(const WCHAR *path, struct stati128 *st, BOOL lstat) } } else { + if ((open_error == ERROR_FILE_NOT_FOUND) || (open_error == ERROR_INVALID_NAME) + || (open_error == ERROR_PATH_NOT_FOUND || (open_error == ERROR_BAD_NETPATH))) { + errno = map_errno(open_error); + return -1; + } + if (stat_by_find(path, st)) return -1; } -- cgit v1.2.3