[PATCH v50 0/9] MR7650: Add support for AF_UNIX sockets (followup)
Follow-up of !2786, which appears to have been abandoned. Depends on !8182, !8575, !8578, !8863, !9831, !9835 # Execute minimal test case 1. checkout wine into \~/src/wine 2. in dlls/ws2_32/tests/sock.c wrap all calls to test functions after `Init()` until `test_afunix()` with #if 0 ... #endif 3. build into \~/src/wine-build 4. run ``` (cd ~/src/wine-build; \ WINEPREFIX=~/src/wine/.wine \ ../wine/tools/runtest -q -P wine -T . -M ws2_32.dll \ -p dlls/ws2_32/tests/i386-windows/ws2_32_test.exe sock) ``` or without the wrapper and some imported wine debug channels enabled ``` (cd ~/src/wine-build; WINEPREFIX=~/src/wine/.wine \ WINEDEBUG=+file,+winsock \ WINEDLLOVERRIDES=';ws2_32.dll=b' \ WINETEST_PLATFORM=wine \ ./wine dlls/ws2_32/tests/i386-windows/ws2_32_test.exe sock) ``` # How to debug wineserver Since `wineserver` runs in the background, simple calls to printf() will show nothing when the test case is executed. A workaround is to open a file and call fprintf() to write debug messages to this file, which can then be inspected. The wineserver man page mentions that there is limited support to control wineserver debugging by using `WINEDEBUG=+server` ``` -d[n], --debug[=n] Set the debug level to n. 0 means no debugging information, 1 is the normal level, and 2 is for extra verbose debugging. If n is not specified, the default is 1. The debug output will be sent to stderr. wine(1) will automatically enable normal level debugging when starting wineserver if the +server option is set in the WINEDEBUG variable. ``` However, higher levels probably cannot be set in this way. # Problems with synchronized log output - The Windows test binary prints test failures to stdout (line-buffered or fully buffered). - Wine/Wineserver debug prints to stderr (unbuffered). When merged into a single stream (stderr+stdout), stdout lines get fragmented and interleaved between Wine/Wineserver trace messages., which looks like this: ``` 012c:trace:file:NtWriteFile (0xd,(nil),(nil),(nil),0x6afd40,0x4190bc,0x00000012,(nil),(nil)) [?25lsock.c[?25h012c:trace:file:NtWriteFile = SUCCESS (18) 0124:trace:file:NtDeviceIoControlFile (0x10,(nil),(nil),(nil),0x74ddd0,0x00504000,(nil),0x00000000,0x74ddcc,0x00000004)xts={} ) ``` Unfortunately, synchronized logs are important for analyzing processes. Combining synchronized strace and wine trace messages can be generated with ``` #!/bin/bash -x p=$PWD cd ~/src/wine-build WINEPREFIX=~/src/wine/.wine \ WINEDEBUG=+file,+winsock \ WINEDLLOVERRIDES=';ws2_32.dll=b' \ WINETEST_PLATFORM=wine \ stdbuf -o0 -eL strace -tt -f -o strace.log \ ./wine dlls/ws2_32/tests/i386-windows/ws2_32_test.exe sock 2>&1 | ts 'WINE %H:%M:%.S' > wine.log cat wine.log strace.log | grep -v 'write(\|read(\|rt_sigprocmask' | sort -k 2 > $p/trace.log ``` # How to debug a wine test case At https://gitlab.winehq.org/wine/wine/-/blob/master/tools/runtest#L151 there is mentioned a environment variable name `WINETEST_WRAPPER`, with which it may be possible to run gdb to debug test cases ``` (cd ~/src/wine-build; make -j10 ;\ WINETEST_WRAPPER="gdb --args" \ WINEPREFIX=~/src/wine/.wine \ ../wine/tools/runtest -P wine -T . -M ws2_32.dll \ -p dlls/ws2_32/tests/i386-windows/ws2_32_test.exe sock) Reading symbols from ./wine... (gdb) set follow-fork-mode child (gdb) r current directory: 'Z:\home\ralf.habacker\src\wine-build' sock.c:14624: Test failed: test_afunix.sock: wrong attr ffffffff sock.c:14658: Test failed: test_afunix.sock: wrong family 0 sock.c:14693: Test failed: test_afunix.sock: wrong family 0 ... ``` or without the wrapper and some important debug channels enabled ``` (cd ~/src/wine-build; \ make -j10; \ WINEDEBUG=+file,+winsock \ WINEDLLOVERRIDES=';ws2_32.dll=b' \ WINETEST_PLATFORM=wine \ gdb --args ./wine dlls/ws2_32/tests/i386-windows/ws2_32_test.exe sock) ``` With this you can debug the wine application, but unfortunally not the windows api. # How to debug a wine test case (windows api) 1. After building wine install the binaries into a temporary location ``` make -C ~/src/wine-build install DESTDIR=~/src/wine-install ``` 2. run ``` (cd ../wine-build; \ WINEPREFIX=~/src/wine/.wine \ WINEDLLOVERRIDES=';ws2_32.dll=b' \ WINETEST_PLATFORM=wine \ ./wine programs/winedbg/i386-windows/winedbg.exe --gdb dlls/ws2_32/tests/i386-windows/ws2_32_test.exe sock) ``` Now let's look at the loaded shared libraries ``` Wine-gdb> info sharedlibrary From To Syms Read Shared Object Library 0xf7b39000 0xf7bff6e0 Yes /home/user/src/wine-build/dlls/ntdll/ntdll.so 0x00401000 0x00478e2c Yes /home/user/src/wine-build/dlls/ws2_32/tests/i386-windows/ws2_32_test.exe 0x7bcc1000 0x7bd69280 Yes /home/user/src/wine/.wine/drive_c/windows/system32/ntdll.dll 0x7bb31000 0x7bb91594 Yes /home/user/src/wine/.wine/drive_c/windows/system32/kernel32.dll 0x7b5b1000 0x7b841f38 Yes /home/user/src/wine/.wine/drive_c/windows/system32/kernelbase.dll 0x77fd1000 0x77ff89d8 Yes /home/user/src/wine/.wine/drive_c/windows/system32/iphlpapi.dll 0x7b4b1000 0x7b4ebcb8 Yes /home/user/src/wine/.wine/drive_c/windows/system32/advapi32.dll 0x7b1e1000 0x7b280014 Yes /home/user/src/wine/.wine/drive_c/windows/system32/msvcrt.dll 0x7b151000 0x7b16de40 Yes /home/user/src/wine/.wine/drive_c/windows/system32/sechost.dll 0x7adf1000 0x7aec2690 Yes /home/user/src/wine/.wine/drive_c/windows/system32/ucrtbase.dll 0x77f71000 0x77f85a80 Yes /home/user/src/wine/.wine/drive_c/windows/system32/dnsapi.dll 0x77f31000 0x77f3b240 Yes /home/user/src/wine/.wine/drive_c/windows/system32/nsi.dll 0x7ad41000 0x7ad6653c Yes /home/user/src/wine/.wine/drive_c/windows/system32/ws2_32.dll 0x7a111000 0x7a2cc55c Yes /home/user/src/wine/.wine/drive_c/windows/system32/user32.dll 0x7a631000 0x7a6ae67c Yes /home/user/src/wine/.wine/drive_c/windows/system32/gdi32.dll 0x7a0b1000 0x7a0e48e8 Yes /home/user/src/wine/.wine/drive_c/windows/system32/win32u.dll 0x78121000 0x7813de90 Yes /home/user/src/wine/.wine/drive_c/windows/system32/imm32.dll ``` It is visible, that the unix variant of the shared libraries (ntdll.so) are read from the build dir and also the test case. The Windows part (\*.dll) is loaded from the wine prefix, so that these must be copied from the build directory initially and whenever changes are made. With this is possible to set breakpoints ``` Wine-gdb> b GetFileAttributesW@4 Breakpoint 1 at 0x7b5d3725: file ../wine/dlls/kernelbase/file.c, line 1665. Wine-gdb> c Continuing. Breakpoint 1, GetFileAttributesW@4 ( name=0x40fcfa <test_afunix+1434> L"\xec83\x8304\xfff8\x940f\x89c0\x247c\xf08\xc0b6\x44c7\x424\x1bbf\103\x489\xe824\x52e6\001\x44c7", <incomplete sequence \x824>) at ../wine/dlls/kernelbase/file.c:1665 1665 TRACE( "%s\n", debugstr_w(name) ); ``` # Followup By adding `WINESYSTEMDLLPATH=/home/user/src/wine-install/usr/local/lib/wine/i386-windows/` to the command line, some shared libraries are loaded directly from the build directory, so they do not need to be copied. ``` From To Syms Read Shared Object Library 0xf7eb2000 0xf7f786e0 Yes /home/user/src/wine-build/dlls/ntdll/ntdll.so 0x00401000 0x00478e2c Yes /home/user/src/wine-build/dlls/ws2_32/tests/i386-windows/ws2_32_test.exe 0x7bcc1000 0x7bd69280 Yes /home/user/src/wine/.wine/drive_c/windows/system32/ntdll.dll 0x7bb31000 0x7bb91594 Yes /home/user/src/wine/.wine/drive_c/windows/system32/kernel32.dll 0x7b5b1000 0x7b841f38 Yes /home/user/src/wine/.wine/drive_c/windows/system32/kernelbase.dll 0x77fd1000 0x77ff89d8 Yes /home/user/src/wine/.wine/drive_c/windows/system32/iphlpapi.dll 0x7b4b1000 0x7b4ebcb8 Yes /home/user/src/wine-install/usr/local/lib/wine/i386-windows/advapi32.dll 0x7b1e1000 0x7b280014 Yes /home/user/src/wine-install/usr/local/lib/wine/i386-windows/msvcrt.dll 0x7b151000 0x7b16de40 Yes /home/user/src/wine-install/usr/local/lib/wine/i386-windows/sechost.dll 0x7adf1000 0x7aec2690 Yes /home/user/src/wine-install/usr/local/lib/wine/i386-windows/ucrtbase.dll 0x77f71000 0x77f85a80 Yes /home/user/src/wine/.wine/drive_c/windows/system32/dnsapi.dll 0x77f31000 0x77f3b240 Yes /home/user/src/wine/.wine/drive_c/windows/system32/nsi.dll 0x7ad41000 0x7ad6653c Yes /home/user/src/wine-install/usr/local/lib/wine/i386-windows/ws2_32.dll 0x7a111000 0x7a2cc55c Yes /home/user/src/wine-install/usr/local/lib/wine/i386-windows/user32.dll 0x7a631000 0x7a6ae67c Yes /home/user/src/wine-install/usr/local/lib/wine/i386-windows/gdi32.dll 0x79e31000 0x79e648e8 Yes /home/user/src/wine-install/usr/local/lib/wine/i386-windows/win32u.dll 0x78121000 0x7813de90 Yes /home/user/src/wine/.wine/drive_c/windows/system32/imm32.dll ``` Why this does not apply to all system libraries is currently unclear. It would help if at least ntdll.dll could be loaded from the build directory as it gets several changes here. -- v50: server: open bound AF_UNIX sockets as reparse points server: give AF_UNIX sockets their family before bind ntdll: report bound AF_UNIX sockets as reparse points server: harden the AF_UNIX socket path handling ws2_32: Add note in bind() for AF_UNIX sockets server: Fix getsockname() and accept() on AF_UNIX sockets. server: Introduce error when attempting to create a SOCK_DGRAM AF_UNIX socket. ws2_32: Add support for AF_UNIX sockets. server: Allow for deletion of socket files. https://gitlab.winehq.org/wine/wine/-/merge_requests/7650
From: Ally Sommers <dropbear.sh@gmail.com> Deleting the socket file is a common pattern with AF_UNIX sockets, and is analogous to unbinding. Ugliness removed by Ralf Habacker. --- server/fd.c | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/server/fd.c b/server/fd.c index 68745a47ee3..24c93aa3aab 100644 --- a/server/fd.c +++ b/server/fd.c @@ -1941,20 +1941,42 @@ struct fd *open_fd( struct fd *root, const char *name, struct unicode_str nt_nam { /* check for trailing slash on file path */ if ((errno == ENOENT || (errno == ENOTDIR && !(options & FILE_DIRECTORY_FILE))) && name[strlen(name) - 1] == '/') + { set_error( STATUS_OBJECT_NAME_INVALID ); + goto error; + } + + if (stat( name, &st )) + { + file_set_error(); + goto error; + } + + /* POSIX requires that open(2) throws EOPNOTSUPP when `path` is a Unix + * socket. BSD throws EOPNOTSUPP in this case and the additional case of + * O_SHLOCK or O_EXLOCK being passed when `path` resides on a filesystem + * without lock support. Contrary to POSIX, Linux returns ENXIO in this + * case, so we also check that error code here. + */ + if ((errno == EOPNOTSUPP || errno == ENXIO) && S_ISSOCK(st.st_mode) && (options & FILE_DELETE_ON_CLOSE)) + ; /* no error, go to regular deletion code path */ else + { file_set_error(); - goto error; + goto error; + } } } fd->nt_name = dup_nt_name( root, nt_name, &fd->nt_namelen ); fd->unix_name = NULL; - fstat( fd->unix_fd, &st ); + /* st was set from the file name if the file could not be opened */ + if (fd->unix_fd != -1) + fstat( fd->unix_fd, &st ); *mode = st.st_mode; /* only bother with an inode for normal files and directories */ - if (S_ISREG(st.st_mode) || S_ISDIR(st.st_mode)) + if (S_ISREG(st.st_mode) || S_ISDIR(st.st_mode) || S_ISSOCK(st.st_mode)) { unsigned int err; struct inode *inode = get_inode( st.st_dev, st.st_ino, fd->unix_fd ); @@ -2016,7 +2038,10 @@ struct fd *open_fd( struct fd *root, const char *name, struct unicode_str nt_nam set_error( STATUS_OBJECT_NAME_COLLISION ); goto error; } - ftruncate( fd->unix_fd, 0 ); + if (fd->unix_fd != -1) + ftruncate( fd->unix_fd, 0 ); + else + truncate( fd->unix_name, 0 ); } } else /* special file */ -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/7650
From: Ally Sommers <dropbear.sh@gmail.com> This commit additionally modifies wineserver's sock_ioctl to handle the provided pathname by changing directories and then returning after the native call. This is NOT threadsafe, but wineserver is not multithreaded. --- dlls/ntdll/unix/socket.c | 4 + dlls/ws2_32/socket.c | 83 +++++++++++++++++-- dlls/ws2_32/ws2_32_private.h | 13 +++ server/sock.c | 152 ++++++++++++++++++++++++++++++++--- 4 files changed, 238 insertions(+), 14 deletions(-) diff --git a/dlls/ntdll/unix/socket.c b/dlls/ntdll/unix/socket.c index ab301f13949..29c3644f21b 100644 --- a/dlls/ntdll/unix/socket.c +++ b/dlls/ntdll/unix/socket.c @@ -67,6 +67,8 @@ # define HAS_IRDA #endif +#include <sys/un.h> + #include "ntstatus.h" #include "windef.h" #include "winioctl.h" @@ -77,6 +79,7 @@ #include "ws2tcpip.h" #include "wsipx.h" #include "af_irda.h" +#include "afunix.h" #include "wine/afd.h" #include "unix_private.h" @@ -105,6 +108,7 @@ union unix_sockaddr #ifdef HAS_IRDA struct sockaddr_irda irda; #endif + struct sockaddr_un un; }; struct async_recv_ioctl diff --git a/dlls/ws2_32/socket.c b/dlls/ws2_32/socket.c index b2601c6e4af..7a5720583d3 100644 --- a/dlls/ws2_32/socket.c +++ b/dlls/ws2_32/socket.c @@ -182,6 +182,19 @@ static const WSAPROTOCOL_INFOW supported_protocols[] = .iProtocol = BTHPROTO_RFCOMM, .szProtocol = L"MSAFD RfComm [Bluetooth]", }, + { + .dwServiceFlags1 = XP1_GUARANTEED_DELIVERY | XP1_GUARANTEED_ORDER | XP1_IFS_HANDLES, + .dwProviderFlags = PFL_MATCHES_PROTOCOL_ZERO, + .ProviderId = {0xa00943d9, 0x9c2e, 0x4633, {0x9b, 0x59, 0x00, 0x57, 0xa3, 0x16, 0x09, 0x94}}, + .dwCatalogEntryId = 1007, + .ProtocolChain.ChainLen = 1, + .iVersion = 2, + .iAddressFamily = AF_UNIX, + .iMaxSockAddr = sizeof(struct sockaddr_un), + .iMinSockAddr = offsetof(struct sockaddr_un, sun_path), + .iSocketType = SOCK_STREAM, + .szProtocol = L"AF_UNIX", + }, }; DECLARE_CRITICAL_SECTION(cs_socket_list); @@ -353,6 +366,11 @@ const char *debugstr_sockaddr( const struct sockaddr *a ) bth_addr.rgBytes[1], bth_addr.rgBytes[0], wine_dbgstr_guid( &addr->serviceClassId ), addr->port ); } + case AF_UNIX: + { + return wine_dbg_sprintf("{ family AF_UNIX, path %s }", + ((const SOCKADDR_UN *)a)->sun_path); + } default: return wine_dbg_sprintf("{ family %d }", a->sa_family); } @@ -1251,6 +1269,9 @@ int WINAPI bind( SOCKET s, const struct sockaddr *addr, int len ) IO_STATUS_BLOCK io; HANDLE sync_event; NTSTATUS status; + const int bind_len = len; + char *unix_path = NULL; + int unix_varargs_size = 0; TRACE( "socket %#Ix, addr %s, len %d\n", s, debugstr_sockaddr(addr), len ); @@ -1300,6 +1321,15 @@ int WINAPI bind( SOCKET s, const struct sockaddr *addr, int len ) return -1; } break; + + case AF_UNIX: + if (len < offsetof(struct sockaddr_un, sun_path)) + { + SetLastError( WSAEFAULT ); + return -1; + } + break; + default: FIXME( "unknown protocol %u\n", addr->sa_family ); SetLastError( WSAEAFNOSUPPORT ); @@ -1308,7 +1338,29 @@ int WINAPI bind( SOCKET s, const struct sockaddr *addr, int len ) if (!(sync_event = get_sync_event())) return -1; - params = malloc( sizeof(int) + len ); + if (addr->sa_family == AF_UNIX && *addr->sa_data) + { + struct sockaddr_un sun = { 0 }; + WCHAR *sun_pathW; + memcpy(&sun, addr, len); + if (strlen( sun.sun_path )) + { + sun_pathW = strdupAtoW( sun.sun_path ); + unix_path = wine_get_unix_file_name( sun_pathW ); + free( sun_pathW ); + if (!unix_path) + return SOCKET_ERROR; + } + else + { + unix_path = malloc(1); + *unix_path = '\0'; + } + len = sizeof(sun); + unix_varargs_size = strlen( unix_path ); + } + + params = malloc( sizeof(int) + len + unix_varargs_size ); ret_addr = malloc( len ); if (!params || !ret_addr) { @@ -1318,10 +1370,14 @@ int WINAPI bind( SOCKET s, const struct sockaddr *addr, int len ) return -1; } params->unknown = 0; - memcpy( ¶ms->addr, addr, len ); + if (addr->sa_family == AF_UNIX) + memset( ¶ms->addr, 0, len ); + memcpy( ¶ms->addr, addr, bind_len ); + if (unix_path) + memcpy( (char *)¶ms->addr + len, unix_path, unix_varargs_size ); status = NtDeviceIoControlFile( (HANDLE)s, sync_event, NULL, NULL, &io, IOCTL_AFD_BIND, - params, sizeof(int) + len, ret_addr, len ); + params, sizeof(int) + len + unix_varargs_size, ret_addr, len ); if (status == STATUS_PENDING) { if (WaitForSingleObject( sync_event, INFINITE ) == WAIT_FAILED) @@ -1334,6 +1390,7 @@ int WINAPI bind( SOCKET s, const struct sockaddr *addr, int len ) free( params ); free( ret_addr ); + free( unix_path ); SetLastError( NtStatusToWSAError( status ) ); return status ? -1 : 0; @@ -1374,11 +1431,24 @@ int WINAPI connect( SOCKET s, const struct sockaddr *addr, int len ) HANDLE sync_event; NTSTATUS status; + char *unix_path = NULL; + int unix_varargs_size = 0; + TRACE( "socket %#Ix, addr %s, len %d\n", s, debugstr_sockaddr(addr), len ); if (!(sync_event = get_sync_event())) return -1; - if (!(params = malloc( sizeof(*params) + len ))) + if (addr->sa_family == AF_UNIX && *addr->sa_data) + { + WCHAR *sun_pathW = strdupAtoW(addr->sa_data); + unix_path = wine_get_unix_file_name(sun_pathW); + free(sun_pathW); + if (!unix_path) + return SOCKET_ERROR; + unix_varargs_size = strlen(unix_path); + } + + if (!(params = malloc( sizeof(*params) + len + unix_varargs_size ))) { SetLastError( ERROR_NOT_ENOUGH_MEMORY ); return -1; @@ -1386,10 +1456,13 @@ int WINAPI connect( SOCKET s, const struct sockaddr *addr, int len ) params->addr_len = len; params->synchronous = TRUE; memcpy( params + 1, addr, len ); + if (unix_path) + memcpy( (char *)(params + 1) + len, unix_path, unix_varargs_size ); status = NtDeviceIoControlFile( (HANDLE)s, sync_event, NULL, NULL, &io, IOCTL_AFD_WINE_CONNECT, - params, sizeof(*params) + len, NULL, 0 ); + params, sizeof(*params) + len + unix_varargs_size, NULL, 0 ); free( params ); + free( unix_path ); if (status == STATUS_PENDING) { if (wait_event_alertable( sync_event ) == WAIT_FAILED) return -1; diff --git a/dlls/ws2_32/ws2_32_private.h b/dlls/ws2_32/ws2_32_private.h index a65db62bd5f..2c8c6aed5a7 100644 --- a/dlls/ws2_32/ws2_32_private.h +++ b/dlls/ws2_32/ws2_32_private.h @@ -46,6 +46,7 @@ #include "mstcpip.h" #include "af_irda.h" #include "winnt.h" +#include "afunix.h" #define USE_WC_PREFIX /* For CMSG_DATA */ #include "iphlpapi.h" #include "ip2string.h" @@ -73,6 +74,18 @@ static inline char *strdupWtoA( const WCHAR *str ) return ret; } +static inline WCHAR *strdupAtoW( const char *str ) +{ + WCHAR *ret = NULL; + if (str) + { + DWORD len = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0); + if ((ret = malloc( len * sizeof(WCHAR) ))) + MultiByteToWideChar(CP_ACP, 0, str, -1, ret, len); + } + return ret; +} + static const char magic_loopback_addr[] = {127, 12, 34, 56}; const char *debugstr_sockaddr( const struct sockaddr *addr ); diff --git a/server/sock.c b/server/sock.c index 0716b3f71f4..528ec612665 100644 --- a/server/sock.c +++ b/server/sock.c @@ -95,6 +95,8 @@ # endif #endif +#include <sys/un.h> + #include "ntstatus.h" #include "windef.h" #include "winternl.h" @@ -105,6 +107,7 @@ #include "tcpmib.h" #include "wsipx.h" #include "af_irda.h" +#include "afunix.h" #include "bthsdpdef.h" #include "bluetoothapis.h" #include "bthdef.h" @@ -146,6 +149,7 @@ union win_sockaddr struct WS_sockaddr_in6 in6; struct WS_sockaddr_ipx ipx; SOCKADDR_IRDA irda; + struct WS_sockaddr_un un; }; union unix_sockaddr @@ -162,6 +166,7 @@ union unix_sockaddr #ifdef HAS_BLUETOOTH struct sockaddr_rc rfcomm; #endif + struct sockaddr_un un; }; static struct list poll_list = LIST_INIT( poll_list ); @@ -724,6 +729,9 @@ static socklen_t get_unix_sockaddr_any( union unix_sockaddr *uaddr, int ws_famil uaddr->irda.sir_family = AF_IRDA; return sizeof(uaddr->irda); #endif + case WS_AF_UNIX: + uaddr->un.sun_family = AF_UNIX; + return sizeof(uaddr->un); default: return 0; } @@ -1827,6 +1835,7 @@ static int get_unix_family( int family ) #ifdef AF_BLUETOOTH case WS_AF_BTH: return AF_BLUETOOTH; #endif + case WS_AF_UNIX: return AF_UNIX; case WS_AF_UNSPEC: return AF_UNSPEC; default: return -1; } @@ -2646,8 +2655,13 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) if (listen( unix_fd, params->backlog ) < 0) { - set_error( sock_get_ntstatus( errno ) ); - return; + /* Due to the way we handle the Windows AF_UNIX bind edge case, we also need to + * ignore listen's error. */ + if (!(errno == EINVAL && sock->family == WS_AF_UNIX && !*sock->addr.un.sun_path)) + { + set_error( sock_get_ntstatus( errno ) ); + return; + } } sock->state = SOCK_LISTENING; @@ -2717,7 +2731,55 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) break; } - unix_len = sockaddr_to_unix( addr, params->addr_len, &unix_addr ); + if (sock->family == WS_AF_UNIX) + { + if (*addr->sa_data) + { + int unix_path_len = get_req_data_size() - sizeof(*params) - params->addr_len; + char *unix_path; + char *base_name; + + if (!(unix_path = mem_alloc( unix_path_len + 1 ))) + return; + + memcpy( unix_path, (char *)(params + 1) + params->addr_len, unix_path_len ); + unix_path[unix_path_len] = '\0'; + + base_name = strrchr(unix_path, '/'); + if (base_name) + { + if (base_name != unix_path) + (++base_name)[-1] = '\0'; + } + else + base_name = unix_path; + + if (chdir( unix_path ) == -1) + { + set_error( sock_get_ntstatus( errno ) ); + free( unix_path ); + return; + } + + send_len -= unix_path_len; + unix_len = sizeof(unix_addr.un); + memset( &unix_addr.un, 0, sizeof(unix_addr.un) ); + unix_addr.un.sun_family = AF_UNIX; + memcpy( unix_addr.un.sun_path, base_name, strlen( base_name ) ); + free( unix_path ); + } + else + { + /* Contrary to documentation, Windows does not currently support abstract Unix + * sockets. connect() throws WSAEINVAL if sun_family is AF_UNIX and sun_path + * begins with '\0', even though bind() will succeed. */ + set_win32_error( WSAEINVAL ); + return; + } + } + else + unix_len = sockaddr_to_unix( addr, params->addr_len, &unix_addr ); + if (!unix_len) { set_error( STATUS_INVALID_ADDRESS ); @@ -2769,6 +2831,9 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) return; } + if (sock->family == WS_AF_UNIX && *addr->sa_data) + fchdir(server_dir_fd); + /* a connected or connecting socket can no longer be accepted into */ allow_fd_caching( sock->fd ); @@ -3018,6 +3083,7 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) data_size_t in_size; socklen_t unix_len; int v6only = 1; + int unix_path_len = 0; /* the ioctl is METHOD_NEITHER, so ntdll gives us the output buffer as * input */ @@ -3027,8 +3093,10 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) return; } in_size = get_req_data_size() - get_reply_max_size(); + if (params->addr.sa_family == WS_AF_UNIX) + unix_path_len = in_size - sizeof(params->unknown) - sizeof(struct WS_sockaddr_un); if (in_size < offsetof(struct afd_bind_params, addr.sa_data) - || get_reply_max_size() < in_size - sizeof(int)) + || get_reply_max_size() < in_size - sizeof(int) - unix_path_len) { set_error( STATUS_INVALID_PARAMETER ); return; @@ -3040,7 +3108,47 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) return; } - unix_len = sockaddr_to_unix( ¶ms->addr, in_size - sizeof(int), &unix_addr ); + if (sock->family == WS_AF_UNIX) + { + if (*params->addr.sa_data) + { + char *unix_path; + char *base_name; + + if (!(unix_path = mem_alloc( unix_path_len + 1 ))) + return; + + memcpy( unix_path, (char *)(¶ms->addr) + sizeof(struct WS_sockaddr_un), unix_path_len ); + unix_path[unix_path_len] = '\0'; + + base_name = strrchr(unix_path, '/'); + if (base_name) + { + if (base_name != unix_path) + (++base_name)[-1] = '\0'; + } + else + base_name = unix_path; + + if (chdir( unix_path ) == -1) + { + free( unix_path ); + set_error( sock_get_ntstatus( errno ) ); + return; + } + + memset( &unix_addr.un, 0, sizeof(unix_addr.un) ); + memcpy( unix_addr.un.sun_path, base_name, strlen( base_name ) ); + free( unix_path ); + } + else + memset(unix_addr.un.sun_path, 0, sizeof(unix_addr.un.sun_path)); + unix_addr.un.sun_family = AF_UNIX; + unix_len = sizeof(unix_addr.un); + } + else + unix_len = sockaddr_to_unix( ¶ms->addr, in_size - sizeof(int), &unix_addr ); + if (!unix_len) { set_error( STATUS_INVALID_ADDRESS ); @@ -3118,8 +3226,16 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) if (errno == EADDRINUSE && sock->reuseaddr) errno = EACCES; - set_error( sock_get_ntstatus( errno ) ); - return; + /* Windows' AF_UNIX implementation has an edge case allowing for a socket to bind to + * an empty path. Linux doesn't, so it throws EINVAL. We check for this situation + * here and avoid early-exiting if it's the case. */ + if (!(errno == EINVAL && sock->family == WS_AF_UNIX && !*params->addr.sa_data)) + { + set_error( sock_get_ntstatus( errno ) ); + if (sock->family == WS_AF_UNIX && *params->addr.sa_data) + fchdir(server_dir_fd); + return; + } } sock->bound = 1; @@ -3131,13 +3247,23 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) * actual unix address */ if (bind_addr.addr.sa_family == AF_INET) bind_addr.in.sin_addr = unix_addr.in.sin_addr; - sock->addr_len = sockaddr_from_unix( &bind_addr, &sock->addr.addr, sizeof(sock->addr) ); + if (bind_addr.addr.sa_family == AF_UNIX) + { + sock->addr.un.sun_family = WS_AF_UNIX; + memcpy(sock->addr.un.sun_path, params->addr.sa_data, sizeof(sock->addr.un.sun_path)); + sock->addr_len = sizeof(sock->addr.un); + } + else + sock->addr_len = sockaddr_from_unix( &bind_addr, &sock->addr.addr, sizeof(sock->addr) ); } update_addr_usage( sock, &bind_addr, v6only ); if (get_reply_max_size() >= sock->addr_len) set_reply_data( &sock->addr, sock->addr_len ); + + if (sock->family == WS_AF_UNIX && *params->addr.sa_data) + fchdir(server_dir_fd); return; } @@ -3154,7 +3280,15 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) return; } - set_reply_data( &sock->addr, sock->addr_len ); + if (sock->family == WS_AF_UNIX) + { + if (*sock->addr.un.sun_path) + set_reply_data( &sock->addr, sizeof(sock->addr.un.sun_family) + strlen(sock->addr.un.sun_path) + 1 ); + else + set_reply_data( &sock->addr, sizeof(sock->addr.un) ); + } + else + set_reply_data( &sock->addr, sock->addr_len ); return; case IOCTL_AFD_WINE_GETPEERNAME: -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/7650
From: Ally Sommers <dropbear.sh@gmail.com> --- server/sock.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/sock.c b/server/sock.c index 528ec612665..1137764f289 100644 --- a/server/sock.c +++ b/server/sock.c @@ -1936,6 +1936,12 @@ static int init_socket( struct sock *sock, int family, int type, int protocol ) return -1; } + if (unix_family == AF_UNIX && unix_type == SOCK_DGRAM) + { + set_win32_error(WSAEAFNOSUPPORT); + return -1; + } + sockfd = socket( unix_family, unix_type, unix_protocol ); #ifdef linux if (sockfd == -1 && errno == EPERM && unix_type == SOCK_RAW -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/7650
From: Ally Sommers <dropbear.sh@gmail.com> --- server/sock.c | 72 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 12 deletions(-) diff --git a/server/sock.c b/server/sock.c index 1137764f289..42cc6f82a34 100644 --- a/server/sock.c +++ b/server/sock.c @@ -2135,11 +2135,32 @@ static struct sock *accept_socket( struct sock *sock ) unix_len = sizeof(unix_addr); if (!getsockname( acceptfd, &unix_addr.addr, &unix_len )) { - acceptsock->addr_len = sockaddr_from_unix( &unix_addr, &acceptsock->addr.addr, sizeof(acceptsock->addr) ); + if (sock->family == WS_AF_UNIX) + { + acceptsock->addr_len = sock->addr_len; + acceptsock->addr.un = sock->addr.un; + } + else + { + acceptsock->addr_len = sockaddr_from_unix( &unix_addr, + &acceptsock->addr.addr, + sizeof(acceptsock->addr) ); + } + if (!getpeername( acceptfd, &unix_addr.addr, &unix_len )) - acceptsock->peer_addr_len = sockaddr_from_unix( &unix_addr, - &acceptsock->peer_addr.addr, - sizeof(acceptsock->peer_addr) ); + { + if (sock->family == WS_AF_UNIX) + { + acceptsock->peer_addr_len = sizeof( sock->peer_addr.un ); + acceptsock->peer_addr.un = sock->peer_addr.un; + } + else + { + acceptsock->peer_addr_len = sockaddr_from_unix( &unix_addr, + &acceptsock->peer_addr.addr, + sizeof(acceptsock->peer_addr) ); + } + } } } @@ -2199,11 +2220,31 @@ static int accept_into_socket( struct sock *sock, struct sock *acceptsock ) unix_len = sizeof(unix_addr); if (!getsockname( get_unix_fd( newfd ), &unix_addr.addr, &unix_len )) { - acceptsock->addr_len = sockaddr_from_unix( &unix_addr, &acceptsock->addr.addr, sizeof(acceptsock->addr) ); + if (sock->family == WS_AF_UNIX) + { + acceptsock->addr_len = sock->addr_len; + acceptsock->addr.un = sock->addr.un; + } + else + { + acceptsock->addr_len = sockaddr_from_unix( &unix_addr, + &acceptsock->addr.addr, + sizeof(acceptsock->addr) ); + } if (!getpeername( get_unix_fd( newfd ), &unix_addr.addr, &unix_len )) - acceptsock->peer_addr_len = sockaddr_from_unix( &unix_addr, - &acceptsock->peer_addr.addr, - sizeof(acceptsock->peer_addr) ); + { + if (sock->family == WS_AF_UNIX) + { + acceptsock->peer_addr_len = sizeof( sock->peer_addr.un ); + acceptsock->peer_addr.un = sock->peer_addr.un; + } + else + { + acceptsock->peer_addr_len = sockaddr_from_unix( &unix_addr, + &acceptsock->peer_addr.addr, + sizeof(acceptsock->peer_addr) ); + } + } } clear_error(); @@ -2844,10 +2885,17 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) allow_fd_caching( sock->fd ); unix_len = sizeof(unix_addr); - getsockname( unix_fd, &unix_addr.addr, &unix_len ); - sock->addr_len = sockaddr_from_unix( &unix_addr, &sock->addr.addr, sizeof(sock->addr) ); - sock->peer_addr_len = sockaddr_from_unix( &peer_addr, &sock->peer_addr.addr, sizeof(sock->peer_addr)); - + if (sock->family == WS_AF_UNIX) + { + sock->peer_addr.un = *(struct WS_sockaddr_un *)addr; + sock->peer_addr_len = sizeof(struct WS_sockaddr_un); + } + else + { + getsockname( unix_fd, &unix_addr.addr, &unix_len ); + sock->addr_len = sockaddr_from_unix( &unix_addr, &sock->addr.addr, sizeof(sock->addr) ); + sock->peer_addr_len = sockaddr_from_unix( &peer_addr, &sock->peer_addr.addr, sizeof(sock->peer_addr)); + } sock->bound = 1; if (!ret) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/7650
From: Ralf Habacker <ralf.habacker@freenet.de> --- dlls/ws2_32/socket.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dlls/ws2_32/socket.c b/dlls/ws2_32/socket.c index 7a5720583d3..818ad66e30a 100644 --- a/dlls/ws2_32/socket.c +++ b/dlls/ws2_32/socket.c @@ -1340,6 +1340,8 @@ int WINAPI bind( SOCKET s, const struct sockaddr *addr, int len ) if (addr->sa_family == AF_UNIX && *addr->sa_data) { + /* The corresponding unix path is appended to a buffer with + * the structure sockaddr_un and can have a length of <= PATH_MAX */ struct sockaddr_un sun = { 0 }; WCHAR *sun_pathW; memcpy(&sun, addr, len); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/7650
From: Giang Nguyen <nen24t@gmail.com> Two defensive fixes on top of the AF_UNIX patchset: 1. sun_path is 108 bytes, but base_name was copied with strlen() and no bound check into a stack union. Not reachable through the Windows API, since SOCKADDR_UN.sun_path already caps the input, but the server should not trust request data for a fixed-size buffer. Now rejected with WSAEINVAL. 2. The connect() failure path returned without fchdir(server_dir_fd), leaving the server's working directory in the socket directory. Not observable in practice because open_fd() restores it on the next file operation, but the state is better cleaned up where it is created. Neither is a triggerable bug -- both were verified as not reachable, and ws2_32:sock test_afunix is unchanged at 32 failing assertions. Signed-off-by: Giang Nguyen <nen24t@gmail.com> --- server/sock.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/server/sock.c b/server/sock.c index 42cc6f82a34..c53f55533bc 100644 --- a/server/sock.c +++ b/server/sock.c @@ -2801,6 +2801,13 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) else base_name = unix_path; + if (strlen( base_name ) >= sizeof(unix_addr.un.sun_path)) + { + free( unix_path ); + set_win32_error( WSAEINVAL ); + return; + } + if (chdir( unix_path ) == -1) { set_error( sock_get_ntstatus( errno ) ); @@ -2872,15 +2879,15 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) } } + if (sock->family == WS_AF_UNIX && *addr->sa_data) + fchdir(server_dir_fd); + if (ret < 0 && errno != EINPROGRESS) { set_error( sock_get_ntstatus( errno ) ); return; } - if (sock->family == WS_AF_UNIX && *addr->sa_data) - fchdir(server_dir_fd); - /* a connected or connecting socket can no longer be accepted into */ allow_fd_caching( sock->fd ); @@ -3184,6 +3191,13 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) else base_name = unix_path; + if (strlen( base_name ) >= sizeof(unix_addr.un.sun_path)) + { + free( unix_path ); + set_win32_error( WSAEINVAL ); + return; + } + if (chdir( unix_path ) == -1) { free( unix_path ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/7650
From: Giang Nguyen <nen24t@gmail.com> GetFileAttributes() on a bound AF_UNIX socket file returns ERROR_INVALID_PARAMETER. The cause is in ntdll rather than in the server: NtQueryAttributesFile and NtQueryFullAttributesFile, and the FileBasic and FileStat paths of NtQueryInformationFile, reject anything that is neither S_ISREG nor S_ISDIR with STATUS_INVALID_INFO_CLASS, and a socket file is neither. get_file_info() now flags S_ISSOCK with FILE_ATTRIBUTE_REPARSE_POINT and IO_REPARSE_TAG_AF_UNIX, and the four guards accept S_ISSOCK. GetFileAttributes() then returns 0x420, FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_ARCHIVE, which is what sock.c:14624 and sock.c:14703 expect. The change only admits cases that were rejected outright before, so it cannot regress a path that already worked. ws2_32:sock test_afunix: 32 failing assertions down to 24. Signed-off-by: Giang Nguyen <nen24t@gmail.com> --- dlls/ntdll/unix/file.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c index 7b15004bd9b..78eaea08258 100644 --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -1795,6 +1795,12 @@ static int get_file_info( const char *path, struct stat *st, ULONG *attr, ULONG if (reparse_tag) *reparse_tag = IO_REPARSE_TAG_LX_SYMLINK; } } + /* a bound AF_UNIX socket is a reparse point on Windows */ + else if (S_ISSOCK( st->st_mode )) + { + *attr |= FILE_ATTRIBUTE_REPARSE_POINT; + if (reparse_tag) *reparse_tag = IO_REPARSE_TAG_AF_UNIX; + } else if (S_ISDIR( st->st_mode ) && (parent_path = malloc( len + 4 ))) { struct stat parent_st; @@ -4899,7 +4905,7 @@ NTSTATUS WINAPI NtQueryFullAttributesFile( const OBJECT_ATTRIBUTES *attr, if (get_file_info( unix_name, &st, &attributes, NULL ) == -1) status = errno_to_status( errno ); - else if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) + else if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode) && !S_ISSOCK(st.st_mode)) status = STATUS_INVALID_INFO_CLASS; else fill_file_info( &st, attributes, info, FileNetworkOpenInformation ); @@ -4928,7 +4934,7 @@ NTSTATUS WINAPI NtQueryAttributesFile( const OBJECT_ATTRIBUTES *attr, FILE_BASIC if (get_file_info( unix_name, &st, &attributes, NULL ) == -1) status = errno_to_status( errno ); - else if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) + else if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode) && !S_ISSOCK(st.st_mode)) status = STATUS_INVALID_INFO_CLASS; else status = fill_file_info( &st, attributes, info, FileBasicInformation ); @@ -5058,7 +5064,7 @@ NTSTATUS WINAPI NtQueryInformationFile( HANDLE handle, IO_STATUS_BLOCK *io, case FileBasicInformation: if (fd_get_file_info( handle, fd, options, &st, &attr, NULL ) == -1) status = errno_to_status( errno ); - else if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) + else if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode) && !S_ISSOCK(st.st_mode)) status = STATUS_INVALID_INFO_CLASS; else fill_file_info( &st, attr, ptr, class ); @@ -5165,7 +5171,7 @@ NTSTATUS WINAPI NtQueryInformationFile( HANDLE handle, IO_STATUS_BLOCK *io, case FileStatInformation: if (fd_get_file_info( handle, fd, options, &st, &attr, &reparse_tag ) == -1) status = errno_to_status( errno ); - else if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) + else if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode) && !S_ISSOCK(st.st_mode)) status = STATUS_INVALID_INFO_CLASS; else { -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/7650
From: Giang Nguyen <nen24t@gmail.com> Windows reports AF_UNIX with an empty path for a socket that was never bound, and for the peer of an accepted connection whose client never bound. Both structures were left zeroed, so getsockname() and getpeername() returned family 0. init_socket() now presets sun_family and the full length for WS_AF_UNIX, which covers both cases: the unbound client socket, and the accept path that copies the listener's never populated peer address. Only the WS_AF_UNIX branch is touched; every other family keeps the zeroed structure it had. ws2_32:sock test_afunix: 24 failing assertions down to 16, clearing sock.c:14658 and sock.c:14693. Signed-off-by: Giang Nguyen <nen24t@gmail.com> --- server/sock.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/server/sock.c b/server/sock.c index c53f55533bc..e42612a0ecc 100644 --- a/server/sock.c +++ b/server/sock.c @@ -2035,6 +2035,18 @@ static int init_socket( struct sock *sock, int family, int type, int protocol ) sock->type = type; sock->family = family; + /* Windows reports AF_UNIX with an empty path for an unbound socket, so the + * family has to be present before bind() ever runs. */ + if (family == WS_AF_UNIX) + { + memset( &sock->addr.un, 0, sizeof(sock->addr.un) ); + sock->addr.un.sun_family = WS_AF_UNIX; + sock->addr_len = sizeof(sock->addr.un); + memset( &sock->peer_addr.un, 0, sizeof(sock->peer_addr.un) ); + sock->peer_addr.un.sun_family = WS_AF_UNIX; + sock->peer_addr_len = sizeof(sock->peer_addr.un); + } + if (is_tcp_socket( sock )) { value = 1; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/7650
From: Giang Nguyen <nen24t@gmail.com> Windows exposes a bound AF_UNIX socket file as a reparse point: opening it without FILE_OPEN_REPARSE_POINT fails with STATUS_IO_REPARSE_TAG_NOT_HANDLED, opening it with the flag yields a handle that carries only metadata, and FSCTL_GET_REPARSE_POINT on that handle reports IO_REPARSE_TAG_AF_UNIX with an empty reparse buffer. open() on a socket file fails with ENXIO on Linux, and the socket branch in open_fd() only caught that for FILE_DELETE_ON_CLOSE, so every other open ended in STATUS_NO_SUCH_DEVICE. get_reparse_point() recognises a reparse point by the '?' name suffix plus the WINEREPARSE extended attribute, neither of which a real socket inode has. open_fd() now maps the plain open to STATUS_IO_REPARSE_TAG_NOT_HANDLED and serves FILE_OPEN_REPARSE_POINT from O_PATH, which is exactly a descriptor carrying metadata and nothing else. get_reparse_point() answers for a socket before the suffix check, from the inode type rather than the extended attribute. The FILE_DELETE_ON_CLOSE path keeps its own branch. O_PATH is Linux-specific and has no configure check. Guarded by #ifdef, a platform without it keeps the previous behaviour for the reparse open, while the plain open still reports the correct error. ws2_32:sock test_afunix: 16 failing assertions down to 0, clearing sock.c:14712, 14716, 14719 and 14720. ntdll:file and kernel32:file stay at 0 failures. Signed-off-by: Giang Nguyen <nen24t@gmail.com> --- server/fd.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/server/fd.c b/server/fd.c index 24c93aa3aab..d68fa489519 100644 --- a/server/fd.c +++ b/server/fd.c @@ -1958,8 +1958,32 @@ struct fd *open_fd( struct fd *root, const char *name, struct unicode_str nt_nam * without lock support. Contrary to POSIX, Linux returns ENXIO in this * case, so we also check that error code here. */ - if ((errno == EOPNOTSUPP || errno == ENXIO) && S_ISSOCK(st.st_mode) && (options & FILE_DELETE_ON_CLOSE)) - ; /* no error, go to regular deletion code path */ + if ((errno == EOPNOTSUPP || errno == ENXIO) && S_ISSOCK(st.st_mode)) + { + /* Windows exposes a bound AF_UNIX socket as a reparse point: opening it + * without FILE_OPEN_REPARSE_POINT fails with STATUS_IO_REPARSE_TAG_NOT_HANDLED, + * and with the flag it yields a handle that only carries metadata, which is + * what O_PATH gives us here. + */ + if (options & FILE_DELETE_ON_CLOSE) + ; /* no error, go to regular deletion code path */ + else if (!(options & FILE_OPEN_REPARSE_POINT)) + { + set_error( STATUS_IO_REPARSE_TAG_NOT_HANDLED ); + goto error; + } + else + { +#ifdef O_PATH + fd->unix_fd = open( name, O_PATH ); +#endif + if (fd->unix_fd == -1) + { + file_set_error(); + goto error; + } + } + } else { file_set_error(); @@ -2490,6 +2514,7 @@ static void get_reparse_point( struct fd *fd, struct async *async ) /* we can't just allocate get_reply_max_size() here; * Linux won't return any data if the size is too small */ char buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; + struct stat st; int ret; if (!fd->unix_name) @@ -2504,6 +2529,26 @@ static void get_reparse_point( struct fd *fd, struct async *async ) return; } + /* A bound AF_UNIX socket carries its reparse tag in the inode type, not in the + * name suffix and extended attribute the other reparse points use. Windows + * reports IO_REPARSE_TAG_AF_UNIX with no reparse data at all. */ + if (fd->unix_fd != -1 && !fstat( fd->unix_fd, &st ) && S_ISSOCK( st.st_mode )) + { + REPARSE_DATA_BUFFER *data = (REPARSE_DATA_BUFFER *)buffer; + unsigned int size = sizeof(data->ReparseTag) + sizeof(data->ReparseDataLength) + + sizeof(data->Reserved); + + if (get_reply_max_size() < size) + { + set_error( STATUS_BUFFER_TOO_SMALL ); + return; + } + memset( data, 0, size ); + data->ReparseTag = IO_REPARSE_TAG_AF_UNIX; + set_reply_data( data, size ); + return; + } + if (fd->unix_name[strlen( fd->unix_name ) - 1] != '?') { set_error( STATUS_NOT_A_REPARSE_POINT ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/7650
Switched to x86_64 builds and updated references to i386 -- https://gitlab.winehq.org/wine/wine/-/merge_requests/7650#note_149382
With [0001-afunix-add-test-for-WSAioctl-SIO_AF_UNIX_GETPEERPID.patch](/uploads/3d08bc4e55d088d82d0ac0d7ee4062ce/0001-afunix-add-test-for-WSAioctl-SIO_AF_UNIX_GETPEERPID.patch) applied and running the ws2_32 tests I get ``` 0024:fixme:winsock:WSAIoctl unimplemented ioctl _WSAIOR(IOC_VENDOR, 256) 0024:fixme:winsock:server_ioctl_sock Unsupported ioctl 0x120348 (device=0x12 access=0 func=0xd2 method=0) sock.c:15051: Test failed: failed to get WSAioctl, error 10045 0020:sock: 841 tests executed (0 marked as todo, 0 as flaky, 1 failure), 0 skipped. ``` -- https://gitlab.winehq.org/wine/wine/-/merge_requests/7650#note_149384
On Thu Aug 20 12:31:21 2026 +0000, Ralf Habacker wrote:
Switched to x86_64 builds and updated references to i386 @giang17: I found a feature that hasn't been implemented yet and added a test case for it. Do you have any idea what else might be missing?
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/7650#note_149386
On Thu Aug 20 12:25:10 2026 +0000, Ralf Habacker wrote:
With [0001-afunix-add-test-for-WSAioctl-SIO_AF_UNIX_GETPEERPID.patch](/uploads/3d08bc4e55d088d82d0ac0d7ee4062ce/0001-afunix-add-test-for-WSAioctl-SIO_AF_UNIX_GETPEERPID.patch) applied and running the ws2_32 tests I get ``` 0024:fixme:winsock:WSAIoctl unimplemented ioctl _WSAIOR(IOC_VENDOR, 256) 0024:fixme:winsock:server_ioctl_sock Unsupported ioctl 0x120348 (device=0x12 access=0 func=0xd2 method=0) sock.c:15051: Test failed: failed to get WSAioctl, error 10045 0020:sock: 841 tests executed (0 marked as todo, 0 as flaky, 1 failure), 0 skipped. ``` @giang17: I found a feature that hasn't been implemented yet and added a test case for it. Do you have any idea what else might be missing?
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/7650#note_149387
Do you have any idea what else might be missing?
A systematic sweep over the AF_UNIX surface, run against a build carrying the commits from this MR, found most of it in good shape. SO_TYPE, SO_ERROR, SO_RCVBUF/SO_SNDBUF, SO_REUSEADDR, SO_LINGER, SO_PROTOCOL_INFOW, FIONBIO, FIONREAD, WSASend/WSARecv, shutdown, select/WSAPoll, WSADuplicateSocketW, getsockname/getpeername, AcceptEx, DisconnectEx, WSASendMsg, WSAEnumProtocols, bind() returning WSAEADDRINUSE on a taken path, socket(AF_UNIX, SOCK_DGRAM) being rejected, and DeleteFile on a bound socket all behave as expected. Three things came up. Patches for all three are below; they apply to current master. ### 1. SIO_AF_UNIX_GETPEERPID is missing from include/afunix.h Not only from the implementation. The Windows SDK (and mingw-w64) define it in that header as `_WSAIOR(IOC_VENDOR, 256)`, so code including afunix.h and using the ioctl does not compile against Wine's headers at all. ### 2. ConnectEx() does not work on AF_UNIX It fails with WSAEINVAL (10022), and wineserver logs: ``` sock_get_ntstatus() can't map error: No such file or directory ``` connect() appends the translated Unix path after the sockaddr in the IOCTL_AFD_WINE_CONNECT request; WS2_ConnectEx() does not, it copies only the sockaddr and the send buffer. The server derives `unix_path_len` from everything following the address, so it ends up with an empty path, and connect(2) returns ENOENT - for which sock_get_ntstatus() has no case, so it falls through to STATUS_UNSUCCESSFUL. There is a design question behind this. In the server the send data and the Unix path occupy the same vararg slot: `send_len` is computed identically to `unix_path_len` and then reduced by it. Appending the path in WS2_ConnectEx() fixes the common `send_len == 0` case, but ConnectEx *with* connect data on an AF_UNIX address stays ambiguous unless `afd_connect_params` gains an explicit length field. The patch below takes the conservative route and rejects that combination with WSAEOPNOTSUPP; adding a length field would be the more complete fix, should connect data on AF_UNIX be worth supporting. Independently of AF_UNIX, sock_get_ntstatus() having no ENOENT case means any path related failure ends up as that perror line plus STATUS_UNSUCCESSFUL. There is no patch for it here, because the matching Winsock error would need to be checked against Windows first - STATUS_OBJECT_NAME_NOT_FOUND currently maps to WSAENETDOWN in ws2_32, which does not look right for a missing socket file. ### 3. The reparse tag is only visible through part of the paths that expose it For a bound socket: ``` GetFileAttributes 0x420, REPARSE_POINT set ok GetFileAttributesEx 0x420, REPARSE_POINT set ok FSCTL_GET_REPARSE_POINT tag 0x80000023 ok GetFileInformationByHandleEx/AttributeTagInfo attr 0x20, tag 0 missing FindFirstFile (dwReserved0) tag 0 missing ``` The handle based case is the interesting one: on the very same handle FSCTL_GET_REPARSE_POINT returns IO_REPARSE_TAG_AF_UNIX correctly, while FileAttributeTagInfo reports neither the tag nor even FILE_ATTRIBUTE_REPARSE_POINT. NtQueryInformationFile(FileAttributeTagInformation) goes through fd_get_file_info(), which - unlike get_file_info() - has no S_ISSOCK case, so what a caller sees depends on whether it asks by path or by handle. FindFirstFile is a separate and not AF_UNIX specific matter: the EaSize field of the directory info structures is hardcoded to 0 with a FIXME in dlls/ntdll/unix/file.c, so directory enumeration reports no reparse tag for symlinks and mount points either. Left alone here. ### Patches **include: add SIO_AF_UNIX_GETPEERPID to afunix.h** ```diff --- a/include/afunix.h +++ b/include/afunix.h @@ -33,4 +33,6 @@ typedef struct WS(sockaddr_un) char sun_path[UNIX_PATH_MAX]; } SOCKADDR_UN, *PSOCKADDR_UN; +#define SIO_AF_UNIX_GETPEERPID _WSAIOR(WS(IOC_VENDOR), 256) + #endif /* _WS2AFUNIX_ */ ``` **ntdll: report AF_UNIX sockets as reparse points for handle based queries** ```diff --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -1684,6 +1684,12 @@ static int fd_get_file_info( HANDLE handle, int fd, unsigned int options, if (ret == -1) return ret; *attr |= get_file_attributes( st ); if (reparse_tag) *reparse_tag = 0; + /* a bound AF_UNIX socket is a reparse point on Windows */ + if (S_ISSOCK( st->st_mode )) + { + *attr |= FILE_ATTRIBUTE_REPARSE_POINT; + if (reparse_tag) *reparse_tag = IO_REPARSE_TAG_AF_UNIX; + } /* consider mount points to be reparse points (IO_REPARSE_TAG_MOUNT_POINT) */ if (options & FILE_OPEN_REPARSE_POINT) { ``` **ws2_32: pass the Unix path to the server in ConnectEx() on AF_UNIX sockets** ```diff --- a/dlls/ws2_32/socket.c +++ b/dlls/ws2_32/socket.c @@ -1398,6 +1398,8 @@ static BOOL WINAPI WS2_ConnectEx( SOCKET s, const struct sockaddr *name, int nam { struct afd_connect_params *params; void *cvalue = NULL; + char *unix_path = NULL; + int unix_varargs_size = 0; NTSTATUS status; TRACE( "socket %#Ix, ptr %p %s, length %d, send_buffer %p, send_len %lu, overlapped %p\n", @@ -1409,24 +1411,50 @@ static BOOL WINAPI WS2_ConnectEx( SOCKET s, const struct sockaddr *name, int nam return FALSE; } + if (name->sa_family == AF_UNIX && *name->sa_data) + { + WCHAR *sun_pathW; + + /* The server expects the translated Unix path appended to the address, + * which leaves no room for connect data on AF_UNIX sockets. */ + if (send_len) + { + SetLastError( WSAEOPNOTSUPP ); + return FALSE; + } + + sun_pathW = strdupAtoW( name->sa_data ); + unix_path = wine_get_unix_file_name( sun_pathW ); + free( sun_pathW ); + if (!unix_path) + return FALSE; + unix_varargs_size = strlen( unix_path ); + } + if (!((ULONG_PTR)overlapped->hEvent & 1)) cvalue = overlapped; overlapped->Internal = STATUS_PENDING; overlapped->InternalHigh = 0; - if (!(params = malloc( sizeof(*params) + namelen + send_len ))) + if (!(params = malloc( sizeof(*params) + namelen + send_len + unix_varargs_size ))) { + free( unix_path ); SetLastError( ERROR_NOT_ENOUGH_MEMORY ); return SOCKET_ERROR; } params->addr_len = namelen; params->synchronous = FALSE; memcpy( params + 1, name, namelen ); - memcpy( (char *)(params + 1) + namelen, send_buffer, send_len ); + if (unix_path) + memcpy( (char *)(params + 1) + namelen, unix_path, unix_varargs_size ); + else + memcpy( (char *)(params + 1) + namelen, send_buffer, send_len ); status = NtDeviceIoControlFile( SOCKET2HANDLE(s), overlapped->hEvent, NULL, cvalue, (IO_STATUS_BLOCK *)overlapped, IOCTL_AFD_WINE_CONNECT, - params, sizeof(*params) + namelen + send_len, NULL, 0 ); + params, sizeof(*params) + namelen + send_len + unix_varargs_size, + NULL, 0 ); free( params ); + free( unix_path ); if (ret_len) *ret_len = overlapped->InternalHigh; SetLastError( NtStatusToWSAError( status ) ); TRACE( "status %#lx.\n", status ); ``` -- https://gitlab.winehq.org/wine/wine/-/merge_requests/7650#note_149411
The three patches from the previous comment are now on a branch, in case fetching is easier than copying them out of the comment: https://gitlab.winehq.org/giang17/wine/-/tree/afunix-followup That is this MR's series with the three commits on top (11125e58, 3614e867, 43a9f548). Two remarks on the SIO_AF_UNIX_GETPEERPID test patch: **The WS_ variant references a macro that is not defined there.** In the USE_WS_PREFIX branch of winsock2.h (lines 338-383) the added line reads #define WS_SIO_AF_UNIX_GETPEERPID _WSAIOR(IOC_VENDOR, 256) but IOC_VENDOR is only defined in the #else branch further down; inside the USE_WS_PREFIX branch only WS_IOC_VENDOR exists. The neighbouring lines all use WS_IOC_WS2, so this looks like a copy from the non-prefix half. It only surfaces when something builds with USE_WS_PREFIX and uses the macro, which the test does not. **The two definitions collide.** The test patch defines SIO_AF_UNIX_GETPEERPID in winsock2.h, the branch above defines it in afunix.h, and dlls/ws2_32/tests/sock.c includes both headers (lines 26 and 34). The definitions are not token identical (`WS(IOC_VENDOR)` versus `IOC_VENDOR`), so the preprocessor warns: warning: "SIO_AF_UNIX_GETPEERPID" redefined Only one of the two should remain. The Windows SDK and mingw-w64 declare the ioctl in afunix.h, which argues for that side; Wine keeping all SIO_* macros in winsock2.h argues for the other. If winsock2.h is preferred, the afunix.h commit on the branch should simply be dropped. Worth deciding once rather than twice, given bug 60201: the consumers listed there (userland socketpair() emulation in OCaml and QEMU, plus dbus and glib) are all ports of Windows source, and that kind of code includes afunix.h for SOCKADDR_UN. Whichever header ends up carrying the macro, an implementation for 60201 can then build on it without a second header change. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/7650#note_149435
On Thu Aug 20 21:52:25 2026 +0000, Giang Nguyen wrote:
The three patches from the previous comment are now on a branch, in case fetching is easier than copying them out of the comment: https://gitlab.winehq.org/giang17/wine/-/tree/afunix-followup That is this MR's series with the three commits on top (11125e58, 3614e867, 43a9f548). Two remarks on the SIO_AF_UNIX_GETPEERPID test patch: **The WS_ variant references a macro that is not defined there.** In the USE_WS_PREFIX branch of winsock2.h (lines 338-383) the added line reads #define WS_SIO_AF_UNIX_GETPEERPID _WSAIOR(IOC_VENDOR, 256) but IOC_VENDOR is only defined in the #else branch further down; inside the USE_WS_PREFIX branch only WS_IOC_VENDOR exists. The neighbouring lines all use WS_IOC_WS2, so this looks like a copy from the non-prefix half. It only surfaces when something builds with USE_WS_PREFIX and uses the macro, which the test does not. **The two definitions collide.** The test patch defines SIO_AF_UNIX_GETPEERPID in winsock2.h, the branch above defines it in afunix.h, and dlls/ws2_32/tests/sock.c includes both headers (lines 26 and 34). The definitions are not token identical (`WS(IOC_VENDOR)` versus `IOC_VENDOR`), so the preprocessor warns: warning: "SIO_AF_UNIX_GETPEERPID" redefined Only one of the two should remain. The Windows SDK and mingw-w64 declare the ioctl in afunix.h, which argues for that side; Wine keeping all SIO_* macros in winsock2.h argues for the other. If winsock2.h is preferred, the afunix.h commit on the branch should simply be dropped. Worth deciding once rather than twice, given bug 60201: the consumers listed there (userland socketpair() emulation in OCaml and QEMU, plus dbus and glib) are all ports of Windows source, and that kind of code includes afunix.h for SOCKADDR_UN. Whichever header ends up carrying the macro, an implementation for 60201 can then build on it without a second header change. Thanks for pointing this out. I updated the test file added to https://gitlab.winehq.org/wine/wine/-/merge_requests/7650#note_149384, but needed
diff --git a/include/afunix.h b/include/afunix.h
index 0a55a99a381..5f6a6d815a9 100644
--- a/include/afunix.h
+++ b/include/afunix.h
@@ -33,6 +33,10 @@ typedef struct WS(sockaddr_un)
char sun_path[UNIX_PATH_MAX];
} SOCKADDR_UN, *PSOCKADDR_UN;
+#ifdef USE_WS_PREFIX
#define SIO_AF_UNIX_GETPEERPID _WSAIOR(WS(IOC_VENDOR), 256)
+#else
+#define SIO_AF_UNIX_GETPEERPID _WSAIOR(IOC_VENDOR, 256)
+#endif
#endif /* _WS2AFUNIX_ */
to let the test be able to be compiled. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/7650#note_149474
The fix is right, and the reason behind it is worth recording: the problem is not the macro itself but the include order. afunix.h defines WS() at the top but, unlike winsock2.h (line 1286) and mswsock.h (line 258), it never undefines it again. In dlls/ws2_32/tests/sock.c afunix.h is included at line 34 and mswsock.h at line 36, so by the time SIO_AF_UNIX_GETPEERPID is used, WS is already gone - macros expand at the point of use, not where they are defined. What is left is a call to an undeclared function, so it does not stop at a warning: warning: implicit declaration of function 'WS' undefined reference to `WS' So splitting on USE_WS_PREFIX without WS() in the macro body is not a matter of taste; it is the form that survives any include order. Worth keeping in mind should someone reach for WS() there again. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/7650#note_149609
The patch set ending with commit 30718c3ce0c1b24815d3821f125367b358879369 was added to the `Wine Staging` project with commit https://gitlab.winehq.org/wine/wine-staging/-/commit/13571d4c508b85b52f2e3d1... and is now included in version 11.16. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/7650#note_149614
participants (5)
-
Ally Sommers -
Giang Nguyen -
Giang Nguyen (@giang17) -
Ralf Habacker -
Ralf Habacker (@rhabacker)