win: readlink support for IO_REPARSE_TAG_LX_SYMLINK (#4994)

This adds support for "Linux"-style Windows symbolic links, reparse tag
0xA000001D (IO_REPARSE_TAG_LX_SYMLINK).
This commit is contained in:
Cody Tapscott 2026-01-16 04:38:18 -05:00 committed by GitHub
parent 3908e54252
commit 588ea9b913
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 135 additions and 0 deletions

View File

@ -247,6 +247,32 @@ static int fs__readlink_handle(HANDLE handle,
}
}
} else if (reparse_data->ReparseTag == IO_REPARSE_TAG_LX_SYMLINK) {
/* Real (Linux) symlink */
char* buffer;
char* target;
size_t target_len;
target_len = (reparse_data->ReparseDataLength -
sizeof(ULONG)); /* Version field */
buffer = (char*) reparse_data->LinuxSymbolicLinkReparseBuffer.PathBuffer;
if (target_len_ptr != NULL) {
*target_len_ptr = target_len;
}
if (target_ptr != NULL) {
assert(*target_ptr == NULL);
target = uv__malloc(target_len + 1);
if (target == NULL) {
return UV_ENOMEM;
}
memcpy(target, buffer, target_len);
target[target_len] = '\0';
*target_ptr = target;
}
return 0;
} else if (reparse_data->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) {
/* Junction. */
w_target = reparse_data->MountPointReparseBuffer.PathBuffer +

View File

@ -4163,6 +4163,10 @@ typedef struct _REPARSE_DATA_BUFFER {
ULONG Flags;
WCHAR PathBuffer[1];
} SymbolicLinkReparseBuffer;
struct {
ULONG Version;
UCHAR PathBuffer[1];
} LinuxSymbolicLinkReparseBuffer;
struct {
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
@ -4582,6 +4586,9 @@ typedef struct _SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION {
#ifndef IO_REPARSE_TAG_SYMLINK
# define IO_REPARSE_TAG_SYMLINK (0xA000000CL)
#endif
#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

View File

@ -23,6 +23,7 @@
#include "task.h"
#include <errno.h>
#include <stddef.h> /* offsetof */
#include <string.h> /* memset */
#include <fcntl.h>
#include <sys/stat.h>
@ -37,6 +38,13 @@
# ifndef ERROR_SYMLINK_NOT_SUPPORTED
# define ERROR_SYMLINK_NOT_SUPPORTED 1464
# endif
# ifndef REPARSE_DATA_BUFFER_HEADER_SIZE
# define REPARSE_DATA_BUFFER_HEADER_SIZE \
offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer)
# endif
# ifndef IO_REPARSE_TAG_LX_SYMLINK
# define IO_REPARSE_TAG_LX_SYMLINK (0xA000001DL)
# endif
# ifndef S_IFIFO
# define S_IFIFO _S_IFIFO
# endif
@ -77,6 +85,24 @@ typedef struct {
double mtime;
} utime_check_t;
#ifdef _WIN32
# ifndef REPARSE_DATA_BUFFER
typedef struct _REPARSE_DATA_BUFFER {
ULONG ReparseTag;
USHORT ReparseDataLength;
USHORT Reserved;
union {
struct {
ULONG Version;
UCHAR PathBuffer[1];
} LinuxSymbolicLinkReparseBuffer;
struct {
UCHAR DataBuffer[1];
} GenericReparseBuffer;
} DUMMYUNIONNAME;
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
# endif
#endif
static int dummy_cb_count;
static int close_cb_count;
@ -2694,6 +2720,80 @@ TEST_FS_IMPL(fs_non_symlink_reparse_point) {
return 0;
}
TEST_FS_IMPL(fs_readlink_lx_symlink) {
uv_fs_t req;
int r;
HANDLE file_handle;
REPARSE_DATA_BUFFER* reparse_buffer;
DWORD bytes_returned;
const char* target_path = "target_file";
size_t target_len = strlen(target_path);
size_t buffer_size;
/* set-up */
unlink("test_dir/lx_symlink");
rmdir("test_dir");
loop = uv_default_loop();
uv_fs_mkdir(NULL, &req, "test_dir", 0777, NULL);
uv_fs_req_cleanup(&req);
file_handle = CreateFile("test_dir/lx_symlink",
GENERIC_WRITE | FILE_WRITE_ATTRIBUTES,
0,
NULL,
CREATE_ALWAYS,
FILE_FLAG_OPEN_REPARSE_POINT |
FILE_FLAG_BACKUP_SEMANTICS,
NULL);
ASSERT_PTR_NE(file_handle, INVALID_HANDLE_VALUE);
/* Allocate buffer for reparse data */
buffer_size = REPARSE_DATA_BUFFER_HEADER_SIZE +
sizeof(ULONG) + /* Version field */
target_len;
reparse_buffer = malloc(buffer_size);
ASSERT_NOT_NULL(reparse_buffer);
/* Set up Linux symlink reparse buffer */
memset(reparse_buffer, 0, buffer_size);
reparse_buffer->ReparseTag = IO_REPARSE_TAG_LX_SYMLINK;
reparse_buffer->ReparseDataLength = sizeof(ULONG) + target_len;
reparse_buffer->Reserved = 0;
reparse_buffer->LinuxSymbolicLinkReparseBuffer.Version = 2;
memcpy(reparse_buffer->LinuxSymbolicLinkReparseBuffer.PathBuffer,
target_path,
target_len);
r = DeviceIoControl(file_handle,
FSCTL_SET_REPARSE_POINT,
reparse_buffer,
buffer_size,
NULL,
0,
&bytes_returned,
NULL);
ASSERT(r);
CloseHandle(file_handle);
/* Test that readlink works on the Linux symlink */
r = uv_fs_readlink(NULL, &req, "test_dir/lx_symlink", NULL);
ASSERT_OK(r);
ASSERT_NOT_NULL(req.ptr);
ASSERT_OK(strcmp(req.ptr, target_path));
uv_fs_req_cleanup(&req);
/* clean-up */
free(reparse_buffer);
unlink("test_dir/lx_symlink");
rmdir("test_dir");
MAKE_VALGRIND_HAPPY(loop);
return 0;
}
TEST_FS_IMPL(fs_lstat_windows_store_apps) {
uv_loop_t* loop;
char localappdata[MAX_PATH];

View File

@ -385,6 +385,7 @@ TEST_FS_DECLARE (fs_symlink_dir)
#ifdef _WIN32
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_open_flags)
#endif
@ -1113,6 +1114,7 @@ TASK_LIST_START
#ifdef _WIN32
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_open_flags)
#endif