[PATCH v3 0/2] MR9732: ntdll: Support FSCTL_DUPLICATE_EXTENTS_TO_FILE
Implement FSCTL_DUPLICATE_EXTENTS_TO_FILE handling in ntdll on Linux by wiring the IOCTL to FICLONERANGE/FICLONE where available and returning STATUS_INVALID_DEVICE_REQUEST otherwise. Adds the necessary constants/types when missing. -- v3: ntdll: Advertise FILE_SUPPORTS_BLOCK_REFCOUNTING when reflink is supported. https://gitlab.winehq.org/wine/wine/-/merge_requests/9732
From: strudelll <strudelll@etersoft.ru> --- dlls/ntdll/unix/file.c | 59 ++++++++++++++++++++++++++++++++++++++++++ include/winioctl.h | 10 +++++++ 2 files changed, 69 insertions(+) diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c index fdc0c32fc4f..b6641f31eb9 100644 --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -52,6 +52,9 @@ #include <sys/socket.h> #include <sys/time.h> #include <sys/ioctl.h> +#ifdef __linux__ +# include <linux/fs.h> +#endif #ifdef HAVE_SYS_ATTR_H #include <sys/attr.h> #endif @@ -6643,6 +6646,57 @@ static void ignore_server_ioctl_struct_holes( ULONG code, const void *in_buffer, #endif } +static NTSTATUS duplicate_extents_to_file( HANDLE dst_handle, const void *in_buf, ULONG in_len ) +{ + const DUPLICATE_EXTENTS_DATA *data = in_buf; + NTSTATUS status; + int dst_fd = -1, src_fd = -1; + int needs_close_dst = 0, needs_close_src = 0; + + if (!data || in_len < sizeof(*data)) return STATUS_BUFFER_TOO_SMALL; + + status = server_get_unix_fd( dst_handle, FILE_WRITE_DATA, &dst_fd, &needs_close_dst, NULL, NULL ); + if (status) return status; + status = server_get_unix_fd( data->FileHandle, FILE_READ_DATA, &src_fd, &needs_close_src, NULL, NULL ); + if (status) goto done; + +#ifdef FICLONERANGE + struct file_clone_range r = + { + .src_fd = src_fd, + .src_offset = data->SourceFileOffset.QuadPart, + .src_length = data->ByteCount.QuadPart, + .dest_offset = data->TargetFileOffset.QuadPart, + }; + + if (!ioctl( dst_fd, FICLONERANGE, &r )) { status = STATUS_SUCCESS; goto done; } + if (!(errno == EOPNOTSUPP || errno == ENOTTY || errno == EXDEV || errno == EINVAL)) + { + status = errno_to_status( errno ); + goto done; + } +#endif + +#ifdef FICLONE + if (!data->SourceFileOffset.QuadPart && !data->TargetFileOffset.QuadPart) + { + if (!ioctl( dst_fd, FICLONE, src_fd )) { status = STATUS_SUCCESS; goto done; } + if (!(errno == EOPNOTSUPP || errno == ENOTTY || errno == EXDEV)) + { + status = errno_to_status( errno ); + goto done; + } + } +#endif + + status = STATUS_INVALID_DEVICE_REQUEST; + +done: + if (needs_close_src && src_fd != -1) close( src_fd ); + if (needs_close_dst && dst_fd != -1) close( dst_fd ); + return status; +} + /****************************************************************************** * NtFsControlFile (NTDLL.@) @@ -6733,6 +6787,11 @@ NTSTATUS WINAPI NtFsControlFile( HANDLE handle, HANDLE event, PIO_APC_ROUTINE ap break; } + case FSCTL_DUPLICATE_EXTENTS_TO_FILE: + status = duplicate_extents_to_file( handle, in_buffer, in_size ); + break; + + case FSCTL_SET_SPARSE: TRACE("FSCTL_SET_SPARSE: Ignoring request\n"); status = STATUS_SUCCESS; diff --git a/include/winioctl.h b/include/winioctl.h index 08fb307a321..3bb26f4dc42 100644 --- a/include/winioctl.h +++ b/include/winioctl.h @@ -315,6 +315,16 @@ #define FSCTL_DELETE_CORRUPTED_REFS_CONTAINER CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 253, METHOD_BUFFERED, FILE_ANY_ACCESS) #define FSCTL_SCRUB_UNDISCOVERABLE_ID CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 254, METHOD_BUFFERED, FILE_ANY_ACCESS) +#ifndef _DUPLICATE_EXTENTS_DATA_DEFINED +typedef struct _DUPLICATE_EXTENTS_DATA { + HANDLE FileHandle; + LARGE_INTEGER SourceFileOffset; + LARGE_INTEGER TargetFileOffset; + LARGE_INTEGER ByteCount; +} DUPLICATE_EXTENTS_DATA, *PDUPLICATE_EXTENTS_DATA; +#define _DUPLICATE_EXTENTS_DATA_DEFINED +#endif + #define FSCTL_PIPE_ASSIGN_EVENT CTL_CODE(FILE_DEVICE_NAMED_PIPE, 0, METHOD_BUFFERED, FILE_ANY_ACCESS) #define FSCTL_PIPE_DISCONNECT CTL_CODE(FILE_DEVICE_NAMED_PIPE, 1, METHOD_BUFFERED, FILE_ANY_ACCESS) #define FSCTL_PIPE_LISTEN CTL_CODE(FILE_DEVICE_NAMED_PIPE, 2, METHOD_BUFFERED, FILE_ANY_ACCESS) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9732
From: strudelll <strudelll@etersoft.ru> Detect reflink-capable filesystems (btrfs or successful FICLONERANGE) and set FILE_SUPPORTS_BLOCK_REFCOUNTING in FileFsAttributeInformation. Add a conformance test that uses FSCTL_DUPLICATE_EXTENTS_TO_FILE and expects the flag when reflink works, skipping otherwise. --- dlls/kernel32/tests/volume.c | 86 ++++++++++++++++++++++++++++++++++++ dlls/ntdll/unix/file.c | 34 +++++++++++++- 2 files changed, 118 insertions(+), 2 deletions(-) diff --git a/dlls/kernel32/tests/volume.c b/dlls/kernel32/tests/volume.c index 975aa7bb69d..5573d5e1cb0 100644 --- a/dlls/kernel32/tests/volume.c +++ b/dlls/kernel32/tests/volume.c @@ -2014,6 +2014,91 @@ static void test_GetVolumeInformationByHandle(void) CloseHandle( file ); } +static void test_block_refcounting_flag(void) +{ + WCHAR tmp_path[MAX_PATH], src_path[MAX_PATH], dst_path[MAX_PATH]; + HANDLE src = INVALID_HANDLE_VALUE, dst = INVALID_HANDLE_VALUE; + DWORD sectors_per_cluster = 0, bytes_per_sector = 0, cluster_size, flags; + DWORD written, bytes_returned; + DUPLICATE_EXTENTS_DATA dup = {0}; + char *buffer = NULL; + BOOL ret; + + if (!GetTempPathW( ARRAY_SIZE(tmp_path), tmp_path ) || !tmp_path[0]) + { + skip("GetTempPathW failed, skipping block refcounting test.\n"); + return; + } + + if (!GetTempFileNameW( tmp_path, L"cln", 0, src_path )) + { + skip("GetTempFileNameW failed, skipping block refcounting test.\n"); + return; + } + + if (!GetTempFileNameW( tmp_path, L"cln", 0, dst_path )) + { + DeleteFileW( src_path ); + skip("GetTempFileNameW failed, skipping block refcounting test.\n"); + return; + } + + src = CreateFileW( src_path, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL ); + ok(src != INVALID_HANDLE_VALUE, "failed to create source file, err %lu\n", GetLastError()); + if (src == INVALID_HANDLE_VALUE) + goto cleanup; + + dst = CreateFileW( dst_path, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL ); + ok(dst != INVALID_HANDLE_VALUE, "failed to create dest file, err %lu\n", GetLastError()); + if (dst == INVALID_HANDLE_VALUE) + goto cleanup; + + ret = GetDiskFreeSpaceW( tmp_path, §ors_per_cluster, &bytes_per_sector, NULL, NULL ); + cluster_size = ret ? sectors_per_cluster * bytes_per_sector : 0; + if (!cluster_size) cluster_size = 4096; + + buffer = HeapAlloc( GetProcessHeap(), 0, cluster_size ); + ok(!!buffer, "failed to allocate %lu bytes\n", cluster_size); + if (!buffer) + goto cleanup; + + memset( buffer, 0x5a, cluster_size ); + ret = WriteFile( src, buffer, cluster_size, &written, NULL ); + ok(ret && written == cluster_size, "WriteFile failed, ret %d written %lu err %lu\n", ret, written, GetLastError()); + if (!ret) + goto cleanup; + + dup.FileHandle = src; + dup.SourceFileOffset.QuadPart = 0; + dup.TargetFileOffset.QuadPart = 0; + dup.ByteCount.QuadPart = cluster_size; + + ret = DeviceIoControl( dst, FSCTL_DUPLICATE_EXTENTS_TO_FILE, &dup, sizeof(dup), + NULL, 0, &bytes_returned, NULL ); + if (!ret) + { + skip("duplicate extents not supported on %s, err %lu\n", debugstr_w(tmp_path), GetLastError()); + goto cleanup; + } + + ret = GetVolumeInformationW( tmp_path, NULL, 0, NULL, NULL, &flags, NULL, 0 ); + ok(ret, "GetVolumeInformationW failed for %s, err %lu\n", debugstr_w(tmp_path), GetLastError()); + if (ret) + ok(flags & FILE_SUPPORTS_BLOCK_REFCOUNTING, + "expected FILE_SUPPORTS_BLOCK_REFCOUNTING for %s, got %#lx\n", debugstr_w(tmp_path), flags); + +cleanup: + if (src != INVALID_HANDLE_VALUE) CloseHandle( src ); + if (dst != INVALID_HANDLE_VALUE) CloseHandle( dst ); + DeleteFileW( src_path ); + DeleteFileW( dst_path ); + HeapFree( GetProcessHeap(), 0, buffer ); +} + static void test_mountmgr_query_points(void) { char input_buffer[64]; @@ -2217,6 +2302,7 @@ START_TEST(volume) test_cdrom_ioctl(); test_mounted_folder(); test_GetVolumeInformationByHandle(); + test_block_refcounting_flag(); test_mountmgr_query_points(); test_GetDiskSpaceInformationA(); } diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c index b6641f31eb9..ad4dadc08fc 100644 --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -156,6 +156,10 @@ typedef struct /* Case-insensitivity attribute */ #define EXT4_CASEFOLD_FL 0x40000000 +#ifndef BTRFS_SUPER_MAGIC +#define BTRFS_SUPER_MAGIC 0x9123683e +#endif + #ifndef O_DIRECTORY # define O_DIRECTORY 0200000 /* must be directory */ #endif @@ -7465,8 +7469,10 @@ NTSTATUS WINAPI NtQueryVolumeInformationFile( HANDLE handle, IO_STATUS_BLOCK *io static const WCHAR udfW[] = {'U','D','F'}; FILE_FS_ATTRIBUTE_INFORMATION *info = buffer; + struct statfs stfs; struct mountmgr_unix_drive drive; enum mountmgr_fs_type fs_type = MOUNTMGR_FS_TYPE_NTFS; + BOOL supports_block_refcounting = FALSE; if (length < sizeof(FILE_FS_ATTRIBUTE_INFORMATION)) { @@ -7474,11 +7480,32 @@ NTSTATUS WINAPI NtQueryVolumeInformationFile( HANDLE handle, IO_STATUS_BLOCK *io break; } +#if defined(linux) && defined(HAVE_FSTATFS) + if (!fstatfs( fd, &stfs ) && stfs.f_type == BTRFS_SUPER_MAGIC) + supports_block_refcounting = TRUE; +#endif + +#ifdef FICLONERANGE + if (!supports_block_refcounting) + { + struct file_clone_range range = + { + .src_fd = fd, + .src_offset = 0, + .src_length = 0, + .dest_offset = 0, + }; + + if (!ioctl( fd, FICLONERANGE, &range )) + supports_block_refcounting = TRUE; + else if (errno != EOPNOTSUPP && errno != ENOTTY) + supports_block_refcounting = TRUE; + } +#endif + if (!get_mountmgr_fs_info( handle, fd, &drive, sizeof(drive) )) fs_type = drive.fs_type; else { - struct statfs stfs; - if (!fstatfs( fd, &stfs )) { #if defined(linux) && defined(HAVE_FSTATFS) @@ -7541,6 +7568,9 @@ NTSTATUS WINAPI NtQueryVolumeInformationFile( HANDLE handle, IO_STATUS_BLOCK *io break; } + if (supports_block_refcounting) + info->FileSystemAttributes |= FILE_SUPPORTS_BLOCK_REFCOUNTING; + io->Information = offsetof( FILE_FS_ATTRIBUTE_INFORMATION, FileSystemName ) + info->FileSystemNameLength; status = STATUS_SUCCESS; break; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9732
participants (2)
-
nikita pushkarev (@strudelll) -
strudelll