From 8481eb2772345933b4bb94623f8eb86c85831d07 Mon Sep 17 00:00:00 2001 From: 11soda11 <115734183+Sodastream11@users.noreply.github.com> Date: Thu, 1 Jan 2026 20:14:51 +0100 Subject: [PATCH] win,fs: treat appexeclinks as regular files Currently, fs__readlink_handle resolves AppExecLink Reparse points. This however, is unsupported and userspace programs are supposed to treat AppExecLinks as regular files, which is why this same kind of handling logic was removed from powershell and rejected by the dotnet runtime team. Remove this logic and add a CreateFileW wrapper (used by fs_stat) which opens unsupported reparse points (e.g. AppExecLinks) like regular files. --- src/win/fs.c | 73 +++++++++++++++++++----------------------------- src/win/winapi.h | 7 ----- test/test-fs.c | 14 ++++++++-- test/test-list.h | 4 +-- 4 files changed, 42 insertions(+), 56 deletions(-) diff --git a/src/win/fs.c b/src/win/fs.c index 4092de0ab..dfca7185f 100644 --- a/src/win/fs.c +++ b/src/win/fs.c @@ -191,8 +191,6 @@ static int fs__readlink_handle(HANDLE handle, WCHAR* w_target; DWORD w_target_len; DWORD bytes; - size_t i; - size_t len; if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, @@ -303,38 +301,6 @@ static int fs__readlink_handle(HANDLE handle, w_target += 4; w_target_len -= 4; - } else if (reparse_data->ReparseTag == IO_REPARSE_TAG_APPEXECLINK) { - /* String #3 in the list has the target filename. */ - if (reparse_data->AppExecLinkReparseBuffer.StringCount < 3) { - SetLastError(ERROR_SYMLINK_NOT_SUPPORTED); - return -1; - } - w_target = reparse_data->AppExecLinkReparseBuffer.StringList; - /* The StringList buffer contains a list of strings separated by "\0", */ - /* with "\0\0" terminating the list. Move to the 3rd string in the list: */ - for (i = 0; i < 2; ++i) { - len = wcslen(w_target); - if (len == 0) { - SetLastError(ERROR_SYMLINK_NOT_SUPPORTED); - return -1; - } - w_target += len + 1; - } - w_target_len = wcslen(w_target); - if (w_target_len == 0) { - SetLastError(ERROR_SYMLINK_NOT_SUPPORTED); - return -1; - } - /* Make sure it is an absolute path. */ - if (!(w_target_len >= 3 && - ((w_target[0] >= L'a' && w_target[0] <= L'z') || - (w_target[0] >= L'A' && w_target[0] <= L'Z')) && - w_target[1] == L':' && - w_target[2] == L'\\')) { - SetLastError(ERROR_SYMLINK_NOT_SUPPORTED); - return -1; - } - } else { /* Reparse tag does not indicate a symlink. */ SetLastError(ERROR_SYMLINK_NOT_SUPPORTED); @@ -345,6 +311,33 @@ static int fs__readlink_handle(HANDLE handle, return uv_utf16_to_wtf8(w_target, w_target_len, target_ptr, target_len_ptr); } +/* CreateFileW wrapper. Treats reparse points which windows can't resolve as regular files */ +static HANDLE fs__create_file(WCHAR* path, DWORD desired_access, int do_lstat) { + if (!do_lstat) { + HANDLE handle; + handle = CreateFileW(path, + desired_access, + FILE_SHARE_READ | FILE_SHARE_WRITE | + FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + + if (handle != INVALID_HANDLE_VALUE + || GetLastError() != ERROR_CANT_ACCESS_FILE) + return handle; + } + + return CreateFileW(path, + desired_access, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, + NULL); +} + static int fs__capture_path(uv_fs_t* req, const char* path, @@ -2212,17 +2205,7 @@ static DWORD fs__stat_impl_from_path(WCHAR* path, } /* If the new API does not exist, use the old API. */ - flags = FILE_FLAG_BACKUP_SEMANTICS; - if (do_lstat) - flags |= FILE_FLAG_OPEN_REPARSE_POINT; - - handle = CreateFileW(path, - FILE_READ_ATTRIBUTES, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - NULL, - OPEN_EXISTING, - flags, - NULL); + handle = fs__create_file(path, FILE_READ_ATTRIBUTES, do_lstat); if (handle == INVALID_HANDLE_VALUE) { ret = GetLastError(); diff --git a/src/win/winapi.h b/src/win/winapi.h index 2f25dc55f..9ee0b5e65 100644 --- a/src/win/winapi.h +++ b/src/win/winapi.h @@ -4177,10 +4177,6 @@ typedef struct _REPARSE_DATA_BUFFER { struct { UCHAR DataBuffer[1]; } GenericReparseBuffer; - struct { - ULONG StringCount; - WCHAR StringList[1]; - } AppExecLinkReparseBuffer; }; } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; @@ -4589,9 +4585,6 @@ typedef struct _SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION { #ifndef IO_REPARSE_TAG_LX_SYMLINK # define IO_REPARSE_TAG_LX_SYMLINK (0xA000001DL) #endif -#ifndef IO_REPARSE_TAG_APPEXECLINK -# define IO_REPARSE_TAG_APPEXECLINK (0x8000001BL) -#endif typedef VOID (NTAPI *PIO_APC_ROUTINE) (PVOID ApcContext, diff --git a/test/test-fs.c b/test/test-fs.c index 2a20baf67..ae7d41c84 100644 --- a/test/test-fs.c +++ b/test/test-fs.c @@ -2790,7 +2790,7 @@ TEST_FS_IMPL(fs_readlink_lx_symlink) { return 0; } -TEST_FS_IMPL(fs_lstat_windows_store_apps) { +TEST_FS_IMPL(fs_stat_windows_store_apps) { uv_loop_t* loop; char localappdata[MAX_PATH]; char windowsapps_path[MAX_PATH]; @@ -2798,6 +2798,7 @@ TEST_FS_IMPL(fs_lstat_windows_store_apps) { size_t len; int r; uv_fs_t req; + uv_fs_t lstat_req; uv_fs_t stat_req; uv_dirent_t dirent; @@ -2835,7 +2836,16 @@ TEST_FS_IMPL(fs_lstat_windows_store_apps) { dirent.name) < 0) { continue; } - ASSERT_OK(uv_fs_lstat(loop, &stat_req, file_path, NULL)); + ASSERT_OK(uv_fs_lstat(loop, &lstat_req, file_path, NULL)); + ASSERT_OK(uv_fs_stat(loop, &stat_req, file_path, NULL)); + + /* Appexeclinks should be treated as regular files, + * so stat should return the same info as lstat + * see https://github.com/libuv/libuv/pull/4936#issuecomment-3703811492 + * */ + ASSERT_OK(strcmp(stat_req.path, lstat_req.path)); + ASSERT_OK(memcmp(&stat_req.statbuf, &lstat_req.statbuf, + sizeof(stat_req.statbuf))); } MAKE_VALGRIND_HAPPY(loop); return 0; diff --git a/test/test-list.h b/test/test-list.h index 527ea013c..a134765e2 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -389,7 +389,7 @@ TEST_FS_DECLARE (fs_symlink_dir) TEST_FS_DECLARE (fs_symlink_junction) TEST_FS_DECLARE (fs_non_symlink_reparse_point) TEST_FS_DECLARE (fs_readlink_lx_symlink) -TEST_FS_DECLARE (fs_lstat_windows_store_apps) +TEST_FS_DECLARE (fs_stat_windows_store_apps) TEST_FS_DECLARE (fs_open_flags) #endif #if defined(_WIN32) && !defined(USING_UV_SHARED) @@ -1122,7 +1122,7 @@ TASK_LIST_START TEST_FS_ENTRY (fs_symlink_junction) TEST_FS_ENTRY (fs_non_symlink_reparse_point) TEST_FS_ENTRY (fs_readlink_lx_symlink) - TEST_FS_ENTRY (fs_lstat_windows_store_apps) + TEST_FS_ENTRY (fs_stat_windows_store_apps) TEST_FS_ENTRY (fs_open_flags) #endif #if defined(_WIN32) && !defined(USING_UV_SHARED)