[PATCH v2 1/5] ntdll: add support for IOCTL_COPYCHUNK.

Alex Xu (Hello71) alex_y_xu at yahoo.ca
Wed Aug 4 11:40:09 CDT 2021


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.
4. copy_file_range is supported on FreeBSD.
---
 configure              |   1 +
 configure.ac           |   1 +
 dlls/ntdll/unix/file.c | 121 +++++++++++++++++++++++++++++++++++++++++
 include/config.h.in    |   3 +
 include/winioctl.h     |  34 ++++++++++++
 5 files changed, 160 insertions(+)

diff --git a/configure b/configure
index c159b12ad6d..f49a5b38443 100755
--- a/configure
+++ b/configure
@@ -18039,6 +18039,7 @@ for ac_func in \
         __res_get_state \
         __res_getservers \
 	_spawnvp \
+	copy_file_range \
 	epoll_create \
 	fnmatch \
 	fork \
diff --git a/configure.ac b/configure.ac
index b5d3217f2a0..e2b9a39249a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2177,6 +2177,7 @@ AC_CHECK_FUNCS(\
         __res_get_state \
         __res_getservers \
 	_spawnvp \
+	copy_file_range \
 	epoll_create \
 	fnmatch \
 	fork \
diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c
index 45e444d8576..4e355ef8809 100644
--- a/dlls/ntdll/unix/file.c
+++ b/dlls/ntdll/unix/file.c
@@ -333,6 +333,7 @@ NTSTATUS errno_to_status( int err )
     TRACE( "errno = %d\n", err );
     switch (err)
     {
+    case 0:         return STATUS_SUCCESS;
     case EAGAIN:    return STATUS_SHARING_VIOLATION;
     case EBADF:     return STATUS_INVALID_HANDLE;
     case EBUSY:     return STATUS_DEVICE_BUSY;
@@ -364,6 +365,7 @@ NTSTATUS errno_to_status( int err )
 #endif
     case ENOEXEC:   /* ?? */
     case EEXIST:    /* ?? */
+    case ENOMEM:    return STATUS_NO_MEMORY;
     default:
         FIXME( "Converting errno %d to STATUS_UNSUCCESSFUL\n", err );
         return STATUS_UNSUCCESSFUL;
@@ -5711,6 +5713,121 @@ 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:
+        status = errno_to_status( errno );
+        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.@)
  */
@@ -5726,6 +5843,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 c1a68104fb8..abd17b94fe7 100644
--- a/include/config.h.in
+++ b/include/config.h.in
@@ -46,6 +46,9 @@
 /* Define to 1 if you have the <CommonCrypto/CommonCryptor.h> header file. */
 #undef HAVE_COMMONCRYPTO_COMMONCRYPTOR_H
 
+/* Define to 1 if you have the `copy_file_range' function. */
+#undef HAVE_COPY_FILE_RANGE
+
 /* Define to 1 if you have the <CoreAudio/CoreAudio.h> 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.32.0




More information about the wine-devel mailing list