From: "Alex Xu (Hello71)" Subject: [PATCH v3 1/5] ntdll: add support for IOCTL_COPYCHUNK. Message-Id: <20220127215056.243525-2-alex_y_xu@yahoo.ca> Date: Thu, 27 Jan 2022 16:50:52 -0500 In-Reply-To: <20220127215056.243525-1-alex_y_xu@yahoo.ca> References: <20220127215056.243525-1-alex_y_xu@yahoo.ca> This API really sucks; it literally just passes through the raw SMB request. However, there are some benefits over FSCTL_DUPLICATE_EXTENTS_TO_FILE: 1. it's easier to use from setupapi than FSCTL_DUPLICATE_EXTENTS_TO_FILE which can't plausibly be emulated. 2. on Windows, IOCTL_COPYCHUNK is already called (indirectly) from CopyFile on Windows. see e.g. https://www.ghisler.ch/board/viewtopic.php?t=43945. 3. copy_file_range allows kernel acceleration if reflink isn't possible. 4. copy_file_range is supported on FreeBSD. --- configure | 6 ++ configure.ac | 1 + dlls/ntdll/unix/file.c | 121 +++++++++++++++++++++++++++++++++++++++++ include/config.h.in | 3 + include/winioctl.h | 34 ++++++++++++ 5 files changed, 165 insertions(+) diff --git a/configure b/configure index 7760e43b8e9..78e502c28d6 100755 --- a/configure +++ b/configure @@ -19717,6 +19717,12 @@ esac ac_save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $BUILTINFLAG" +ac_fn_c_check_func "$LINENO" "copy_file_range" "ac_cv_func_copy_file_range" +if test "x$ac_cv_func_copy_file_range" = xyes +then : + printf "%s\n" "#define HAVE_COPY_FILE_RANGE 1" >>confdefs.h + +fi ac_fn_c_check_func "$LINENO" "epoll_create" "ac_cv_func_epoll_create" if test "x$ac_cv_func_epoll_create" = xyes then : diff --git a/configure.ac b/configure.ac index 181047ef0d8..de2f810b4ac 100644 --- a/configure.ac +++ b/configure.ac @@ -1957,6 +1957,7 @@ dnl **** Check for functions **** ac_save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $BUILTINFLAG" AC_CHECK_FUNCS(\ + copy_file_range \ epoll_create \ fstatfs \ futimens \ diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c index a29b5cbb980..cedb57e8f2f 100644 --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -347,6 +347,7 @@ NTSTATUS errno_to_status( int err ) #ifdef ETIME /* Missing on FreeBSD */ case ETIME: return STATUS_IO_TIMEOUT; #endif + case ENOMEM: return STATUS_NO_MEMORY; case ENOEXEC: /* ?? */ case EEXIST: /* ?? */ default: @@ -5694,6 +5695,122 @@ NTSTATUS WINAPI NtWriteFileGather( HANDLE file, HANDLE event, PIO_APC_ROUTINE ap } +static NTSTATUS netfs_DeviceIoControl( HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc, void *apc_context, + IO_STATUS_BLOCK *io, ULONG code, void *in_buffer, ULONG in_size, + void *out_buffer, ULONG out_size ) +{ + /* wine extension: support IOCTL_COPYCHUNK even on local files (no way to tell with + * copy_file_range anyways) */ + NTSTATUS status; + + switch (code) + { + case IOCTL_PREPARE_COPYCHUNK: + { + /* wine extension: out_buffer only needs to be big enough to hold a HANDLE */ + if (out_size < sizeof(HANDLE)) + { + io->Information = 0; + status = STATUS_BUFFER_TOO_SMALL; + break; + } + io->Information = sizeof(HANDLE); + *(HANDLE *)out_buffer = handle; + status = STATUS_SUCCESS; + break; + } + + /* wine extension: support IOCTL_COPYCHUNK with chunk sizes greater than 1 MB */ + case IOCTL_COPYCHUNK: + { + static const SIZE_T buffer_size = 65536; + SRV_COPYCHUNK_COPY *cc = in_buffer; + SRV_COPYCHUNK_RESPONSE *ccr = out_buffer; + int src_fd, dst_fd, src_needs_close, dst_needs_close; + void *buffer = NULL; + BOOL fallback = FALSE; + if (in_size < sizeof(*cc)) + { + status = STATUS_INVALID_PARAMETER; + break; + } + if (out_size < sizeof(*ccr)) + { + status = STATUS_BUFFER_TOO_SMALL; + break; + } + status = server_get_unix_fd( handle, FILE_WRITE_DATA, &dst_fd, &dst_needs_close, NULL, NULL ); + if (status) break; + status = server_get_unix_fd( (HANDLE)(ULONG_PTR)cc->SourceFile.ResumeKey, FILE_READ_DATA, &src_fd, &src_needs_close, NULL, NULL ); + if (status) + { + if (dst_needs_close) close( dst_fd ); + break; + } + io->Information = sizeof(*ccr); + ccr->TotalBytesWritten = 0; + for (ccr->ChunksWritten = 0; ccr->ChunksWritten < cc->ChunkCount; ccr->ChunksWritten++) + { + off_t off_in = cc->Chunk[ccr->ChunksWritten].SourceOffset.QuadPart; + off_t off_out = cc->Chunk[ccr->ChunksWritten].DestinationOffset.QuadPart; + size_t len = cc->Chunk[ccr->ChunksWritten].Length; +#ifdef HAVE_COPY_FILE_RANGE + if (!fallback) + { + ssize_t res = copy_file_range( src_fd, &off_in, dst_fd, &off_out, len, 0 ); + if (res == -1) + { + if (errno == EXDEV || errno == EINVAL || errno == ENOSYS) fallback = TRUE; + else goto copychunk_out; + } + else + { + ccr->ChunkBytesWritten = ccr->TotalBytesWritten = res; + } + } + if (fallback) +#endif + { + if (!buffer) buffer = malloc( buffer_size ); + if (!buffer) goto copychunk_out; + ccr->ChunkBytesWritten = 0; + char *p = buffer; + ssize_t count = pread( src_fd, buffer, min( buffer_size, len ), off_in ); + if (count == -1) goto copychunk_out; + off_in += count; + while (count) + { + ssize_t res = pwrite( dst_fd, p, count, off_out ); + if (res == -1) goto copychunk_out; + p += res; + count -= res; + off_out += res; + ccr->ChunkBytesWritten += res; + ccr->TotalBytesWritten += res; + } + } + if (ccr->ChunkBytesWritten != cc->Chunk[ccr->ChunksWritten].Length) break; + ccr->ChunkBytesWritten = 0; + } + errno = 0; + +copychunk_out: + if (errno) status = errno_to_status( errno ); + else status = STATUS_SUCCESS; + if (buffer) free( buffer ); + if (src_needs_close) close( src_fd ); + if (dst_needs_close) close( dst_fd ); + break; + } + default: + status = STATUS_NOT_SUPPORTED; + } + + io->u.Status = status; + return status; +} + + /****************************************************************************** * NtDeviceIoControlFile (NTDLL.@) */ @@ -5709,6 +5826,10 @@ NTSTATUS WINAPI NtDeviceIoControlFile( HANDLE handle, HANDLE event, PIO_APC_ROUT switch (device) { + case FILE_DEVICE_NETWORK_FILE_SYSTEM: + status = netfs_DeviceIoControl( handle, event, apc, apc_context, io, code, + in_buffer, in_size, out_buffer, out_size ); + break; case FILE_DEVICE_BEEP: case FILE_DEVICE_NETWORK: status = sock_ioctl( handle, event, apc, apc_context, io, code, in_buffer, in_size, out_buffer, out_size ); diff --git a/include/config.h.in b/include/config.h.in index 9717dc17236..9150285b39e 100644 --- a/include/config.h.in +++ b/include/config.h.in @@ -37,6 +37,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_CL_CL_H +/* Define to 1 if you have the `copy_file_range' function. */ +#undef HAVE_COPY_FILE_RANGE + /* Define to 1 if you have the header file. */ #undef HAVE_COREAUDIO_COREAUDIO_H diff --git a/include/winioctl.h b/include/winioctl.h index ae04c37a462..fbc0b80e359 100644 --- a/include/winioctl.h +++ b/include/winioctl.h @@ -331,6 +331,9 @@ #define FSCTL_PIPE_INTERNAL_TRANSCEIVE CTL_CODE(FILE_DEVICE_NAMED_PIPE, 2047, METHOD_NEITHER, FILE_READ_DATA | FILE_WRITE_DATA) #define FSCTL_PIPE_INTERNAL_READ_OVFLOW CTL_CODE(FILE_DEVICE_NAMED_PIPE, 2048, METHOD_BUFFERED, FILE_READ_DATA) +#define IOCTL_PREPARE_COPYCHUNK CTL_CODE(FILE_DEVICE_NETWORK_FILE_SYSTEM, 261, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_COPYCHUNK CTL_CODE(FILE_DEVICE_NETWORK_FILE_SYSTEM, 262, METHOD_BUFFERED, FILE_READ_ACCESS) + #define IOCTL_STORAGE_BASE FILE_DEVICE_MASS_STORAGE #define IOCTL_STORAGE_CHECK_VERIFY CTL_CODE(IOCTL_STORAGE_BASE, 0x0200, METHOD_BUFFERED, FILE_READ_ACCESS) #define IOCTL_STORAGE_CHECK_VERIFY2 CTL_CODE(IOCTL_STORAGE_BASE, 0x0200, METHOD_BUFFERED, FILE_ANY_ACCESS) @@ -591,6 +594,37 @@ typedef struct RETRIEVAL_POINTERS_BUFFER { } Extents[1]; } RETRIEVAL_POINTERS_BUFFER, *PRETRIEVAL_POINTERS_BUFFER; +typedef struct _SRV_RESUME_KEY { + UINT64 ResumeKey; + UINT64 Timestamp; + UINT64 Pid; +} SRV_RESUME_KEY, *PSRV_RESUME_KEY; + +typedef struct _SRV_REQUEST_RESUME_KEY { + SRV_RESUME_KEY Key; + ULONG ContextLength; + BYTE Context[1]; +} SRV_REQUEST_RESUME_KEY, *PSRV_REQUEST_RESUME_KEY; + +typedef struct _SRV_COPYCHUNK { + LARGE_INTEGER SourceOffset; + LARGE_INTEGER DestinationOffset; + ULONG Length; +} SRV_COPYCHUNK, *PSRV_COPYCHUNK; + +typedef struct _SRV_COPYCHUNK_COPY { + SRV_RESUME_KEY SourceFile; + ULONG ChunkCount; + ULONG Reserved; + SRV_COPYCHUNK Chunk[1]; // Array +} SRV_COPYCHUNK_COPY, *PSRV_COPYCHUNK_COPY; + +typedef struct _SRV_COPYCHUNK_RESPONSE { + ULONG ChunksWritten; + ULONG ChunkBytesWritten; + ULONG TotalBytesWritten; +} SRV_COPYCHUNK_RESPONSE, *PSRV_COPYCHUNK_RESPONSE; + /* End: _WIN32_WINNT >= 0x0400 */ /* -- 2.35.0