diff --git a/Changelog.asciidoc b/Changelog.asciidoc
index bd3cc663..d84ece56 100644
--- a/Changelog.asciidoc
+++ b/Changelog.asciidoc
@@ -1,6 +1,13 @@
= Changelog
+v1.4B1 (2018.2 B1)::
+
+Changes since v1.3:
+
+* New `Control` file system operation allows sending custom control codes to the file system using the Windows `DeviceIoControl` API.
+
+
v1.3 (2018.1)::
Changes since v1.2POST1:
diff --git a/build/VStudio/testing/winfsp-tests.vcxproj b/build/VStudio/testing/winfsp-tests.vcxproj
index 74c4edc5..2e906314 100644
--- a/build/VStudio/testing/winfsp-tests.vcxproj
+++ b/build/VStudio/testing/winfsp-tests.vcxproj
@@ -182,6 +182,7 @@
+
diff --git a/build/VStudio/testing/winfsp-tests.vcxproj.filters b/build/VStudio/testing/winfsp-tests.vcxproj.filters
index 0d3b22f1..217f99ff 100644
--- a/build/VStudio/testing/winfsp-tests.vcxproj.filters
+++ b/build/VStudio/testing/winfsp-tests.vcxproj.filters
@@ -88,6 +88,9 @@
Source
+
+ Source
+
diff --git a/inc/fuse/fuse.h b/inc/fuse/fuse.h
index 1b01eadf..dd0c2c03 100644
--- a/inc/fuse/fuse.h
+++ b/inc/fuse/fuse.h
@@ -87,7 +87,7 @@ struct fuse_operations
/* _ */ unsigned int flag_nopath:1;
/* _ */ unsigned int flag_utime_omit_ok:1;
/* _ */ unsigned int flag_reserved:29;
- /* _ */ int (*ioctl)(const char *path, int cmd, void *arg, struct fuse_file_info *fi,
+ /* S */ int (*ioctl)(const char *path, int cmd, void *arg, struct fuse_file_info *fi,
unsigned int flags, void *data);
/* _ */ int (*poll)(const char *path, struct fuse_file_info *fi,
struct fuse_pollhandle *ph, unsigned *reventsp);
diff --git a/inc/fuse/winfsp_fuse.h b/inc/fuse/winfsp_fuse.h
index 7f1b3ecf..38a8e244 100644
--- a/inc/fuse/winfsp_fuse.h
+++ b/inc/fuse/winfsp_fuse.h
@@ -53,6 +53,17 @@ extern "C" {
#endif
#endif
+#define FSP_FUSE_DEVICE_TYPE (0x8000 | 'W' | 'F' * 0x100) /* DeviceIoControl -> ioctl */
+#define FSP_FUSE_CTLCODE_FROM_IOCTL(cmd)\
+ (FSP_FUSE_DEVICE_TYPE << 16) | (((c) & 0x0fff) << 2)
+#define FSP_FUSE_IOCTL(cmd, isiz, osiz) \
+ ( \
+ (((osiz) != 0) << 31) | \
+ (((isiz) != 0) << 30) | \
+ (((isiz) | (osiz)) << 16) | \
+ (cmd) \
+ )
+
/*
* FUSE uses a number of types (notably: struct stat) that are OS specific.
* Furthermore there are sometimes multiple definitions of the same type even
diff --git a/inc/winfsp/fsctl.h b/inc/winfsp/fsctl.h
index b2f52646..570e3e94 100644
--- a/inc/winfsp/fsctl.h
+++ b/inc/winfsp/fsctl.h
@@ -82,6 +82,8 @@ FSP_FSCTL_STATIC_ASSERT(FSP_FSCTL_VOLUME_NAME_SIZEMAX <= 260 * sizeof(WCHAR),
#define FSP_FSCTL_TRANSACT_REQ_TOKEN_HANDLE(T) ((HANDLE)((T) & 0xffffffff))
#define FSP_FSCTL_TRANSACT_REQ_TOKEN_PID(T) ((UINT32)(((T) >> 32) & 0xffffffff))
+#define FSP_FSCTL_DEVICECONTROL_SIZEMAX (4 * 1024) /* must be < FSP_FSCTL_TRANSACT_{REQ,RSP}_SIZEMAX */
+
/* marshalling */
#pragma warning(push)
#pragma warning(disable:4200) /* zero-sized array in struct/union */
@@ -154,11 +156,13 @@ enum
UINT32 AlwaysUseDoubleBuffering:1;\
UINT32 PassQueryDirectoryFileName:1; /* pass FileName during QueryDirectory (GetDirInfoByName) */\
UINT32 FlushAndPurgeOnCleanup:1; /* keeps file off "standby" list */\
- UINT32 KmReservedFlags:1;\
+ UINT32 DeviceControl:1; /* support user-mode ioctl handling */\
/* user-mode flags */\
UINT32 UmFileContextIsUserContext2:1; /* user mode: FileContext parameter is UserContext2 */\
UINT32 UmFileContextIsFullContext:1; /* user mode: FileContext parameter is FullContext */\
- UINT32 UmReservedFlags:14;\
+ UINT32 UmReservedFlags:6;\
+ /* additional kernel-mode flags */\
+ UINT32 KmReservedFlags:8;\
WCHAR Prefix[FSP_FSCTL_VOLUME_PREFIX_SIZE / sizeof(WCHAR)]; /* UNC prefix (\Server\Share) */\
WCHAR FileSystemName[FSP_FSCTL_VOLUME_FSNAME_SIZE / sizeof(WCHAR)];
#define FSP_FSCTL_VOLUME_PARAMS_V1_FIELD_DEFN\
@@ -381,6 +385,14 @@ typedef struct
UINT16 TargetOnFileSystem; /* the target of the symbolic link is on this file system */
} FileSystemControl;
struct
+ {
+ UINT64 UserContext;
+ UINT64 UserContext2;
+ UINT32 IoControlCode;
+ FSP_FSCTL_TRANSACT_BUF Buffer;
+ UINT32 OutputLength;
+ } DeviceControl;
+ struct
{
UINT64 UserContext;
UINT64 UserContext2;
@@ -466,6 +478,10 @@ typedef struct
FSP_FSCTL_TRANSACT_BUF Buffer;
} FileSystemControl;
struct
+ {
+ FSP_FSCTL_TRANSACT_BUF Buffer;
+ } DeviceControl;
+ struct
{
FSP_FSCTL_TRANSACT_BUF SecurityDescriptor;
} QuerySecurity;
diff --git a/inc/winfsp/winfsp.h b/inc/winfsp/winfsp.h
index 6892acc9..c4fcc1f8 100644
--- a/inc/winfsp/winfsp.h
+++ b/inc/winfsp/winfsp.h
@@ -822,12 +822,41 @@ typedef struct _FSP_FILE_SYSTEM_INTERFACE
NTSTATUS (*GetDirInfoByName)(FSP_FILE_SYSTEM *FileSystem,
PVOID FileContext, PWSTR FileName,
FSP_FSCTL_DIR_INFO *DirInfo);
+ /**
+ * Process control code.
+ *
+ * This function is called when a program uses the DeviceIoControl API.
+ *
+ * @param FileSystem
+ * The file system on which this request is posted.
+ * @param FileContext
+ * The file context of the file or directory to be controled.
+ * @param ControlCode
+ * The control code for the operation. This code must have a DeviceType with bit
+ * 0x8000 set and must have a TransferType of METHOD_BUFFERED.
+ * @param InputBuffer
+ * Pointer to a buffer that contains the input data.
+ * @param InputBufferLength
+ * Input data length.
+ * @param OutputBuffer
+ * Pointer to a buffer that will receive the output data.
+ * @param OutputBufferLength
+ * Output data length.
+ * @param PBytesTransferred [out]
+ * Pointer to a memory location that will receive the actual number of bytes transferred.
+ * @return
+ * STATUS_SUCCESS or error code.
+ */
+ NTSTATUS (*Control)(FSP_FILE_SYSTEM *FileSystem,
+ PVOID FileContext, UINT32 ControlCode,
+ PVOID InputBuffer, ULONG InputBufferLength,
+ PVOID OutputBuffer, ULONG OutputBufferLength, PULONG PBytesTransferred);
/*
* This ensures that this interface will always contain 64 function pointers.
* Please update when changing the interface as it is important for future compatibility.
*/
- NTSTATUS (*Reserved[39])();
+ NTSTATUS (*Reserved[38])();
} FSP_FILE_SYSTEM_INTERFACE;
FSP_FSCTL_STATIC_ASSERT(sizeof(FSP_FILE_SYSTEM_INTERFACE) == 64 * sizeof(NTSTATUS (*)()),
"FSP_FILE_SYSTEM_INTERFACE must have 64 entries.");
@@ -1141,6 +1170,8 @@ FSP_API NTSTATUS FspFileSystemOpQueryDirectory(FSP_FILE_SYSTEM *FileSystem,
FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response);
FSP_API NTSTATUS FspFileSystemOpFileSystemControl(FSP_FILE_SYSTEM *FileSystem,
FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response);
+FSP_API NTSTATUS FspFileSystemOpDeviceControl(FSP_FILE_SYSTEM *FileSystem,
+ FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response);
FSP_API NTSTATUS FspFileSystemOpQuerySecurity(FSP_FILE_SYSTEM *FileSystem,
FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response);
FSP_API NTSTATUS FspFileSystemOpSetSecurity(FSP_FILE_SYSTEM *FileSystem,
diff --git a/src/dll/fs.c b/src/dll/fs.c
index d102a32a..c2facf2e 100644
--- a/src/dll/fs.c
+++ b/src/dll/fs.c
@@ -162,6 +162,7 @@ FSP_API NTSTATUS FspFileSystemCreate(PWSTR DevicePath,
FileSystem->Operations[FspFsctlTransactSetVolumeInformationKind] = FspFileSystemOpSetVolumeInformation;
FileSystem->Operations[FspFsctlTransactQueryDirectoryKind] = FspFileSystemOpQueryDirectory;
FileSystem->Operations[FspFsctlTransactFileSystemControlKind] = FspFileSystemOpFileSystemControl;
+ FileSystem->Operations[FspFsctlTransactDeviceControlKind] = FspFileSystemOpDeviceControl;
FileSystem->Operations[FspFsctlTransactQuerySecurityKind] = FspFileSystemOpQuerySecurity;
FileSystem->Operations[FspFsctlTransactSetSecurityKind] = FspFileSystemOpSetSecurity;
FileSystem->Operations[FspFsctlTransactQueryStreamInformationKind] = FspFileSystemOpQueryStreamInformation;
diff --git a/src/dll/fsop.c b/src/dll/fsop.c
index 37a4e21a..d792ff1e 100644
--- a/src/dll/fsop.c
+++ b/src/dll/fsop.c
@@ -1247,6 +1247,30 @@ FSP_API NTSTATUS FspFileSystemOpFileSystemControl(FSP_FILE_SYSTEM *FileSystem,
return Result;
}
+FSP_API NTSTATUS FspFileSystemOpDeviceControl(FSP_FILE_SYSTEM *FileSystem,
+ FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response)
+{
+ NTSTATUS Result;
+ ULONG BytesTransferred;
+
+ if (0 == FileSystem->Interface->Control)
+ return STATUS_INVALID_DEVICE_REQUEST;
+
+ Result = FileSystem->Interface->Control(FileSystem,
+ (PVOID)ValOfFileContext(Request->Req.DeviceControl),
+ Request->Req.DeviceControl.IoControlCode,
+ Request->Buffer, Request->Req.DeviceControl.Buffer.Size,
+ Response->Buffer, Request->Req.DeviceControl.OutputLength/* FSD guarantees correct size! */,
+ &BytesTransferred);
+ if (!NT_SUCCESS(Result))
+ return STATUS_BUFFER_OVERFLOW != Result ? Result : STATUS_BUFFER_TOO_SMALL;
+
+ Response->Size = (UINT16)(sizeof *Response + BytesTransferred);
+ Response->Rsp.DeviceControl.Buffer.Offset = 0;
+ Response->Rsp.DeviceControl.Buffer.Size = (UINT16)BytesTransferred;
+ return STATUS_SUCCESS;
+}
+
FSP_API NTSTATUS FspFileSystemOpQuerySecurity(FSP_FILE_SYSTEM *FileSystem,
FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response)
{
diff --git a/src/dll/fuse/fuse.c b/src/dll/fuse/fuse.c
index d742642d..d9dbe953 100644
--- a/src/dll/fuse/fuse.c
+++ b/src/dll/fuse/fuse.c
@@ -649,6 +649,7 @@ FSP_FUSE_API struct fuse *fsp_fuse_new(struct fsp_fuse_env *env,
opt_data.VolumeParams.ReadOnlyVolume = FALSE;
opt_data.VolumeParams.PostCleanupWhenModifiedOnly = TRUE;
opt_data.VolumeParams.PassQueryDirectoryFileName = TRUE;
+ opt_data.VolumeParams.DeviceControl = TRUE;
opt_data.VolumeParams.UmFileContextIsUserContext2 = TRUE;
if (L'\0' == opt_data.VolumeParams.FileSystemName[0])
memcpy(opt_data.VolumeParams.FileSystemName, L"FUSE", 5 * sizeof(WCHAR));
diff --git a/src/dll/fuse/fuse_intf.c b/src/dll/fuse/fuse_intf.c
index f764a449..3363a771 100644
--- a/src/dll/fuse/fuse_intf.c
+++ b/src/dll/fuse/fuse_intf.c
@@ -2125,6 +2125,47 @@ static NTSTATUS fsp_fuse_intf_DeleteReparsePoint(FSP_FILE_SYSTEM *FileSystem,
return STATUS_ACCESS_DENIED;
}
+static NTSTATUS fsp_fuse_intf_Control(FSP_FILE_SYSTEM *FileSystem,
+ PVOID FileNode, UINT32 ControlCode,
+ PVOID InputBuffer, ULONG InputBufferLength,
+ PVOID OutputBuffer, ULONG OutputBufferLength, PULONG PBytesTransferred)
+{
+ struct fuse *f = FileSystem->UserContext;
+ struct fsp_fuse_file_desc *filedesc = FileNode;
+ struct fuse_file_info fi;
+ int cmd;
+ int err;
+
+ if (0 == f->ops.ioctl)
+ return STATUS_INVALID_DEVICE_REQUEST;
+
+ if (FSP_FUSE_DEVICE_TYPE != DEVICE_TYPE_FROM_CTL_CODE(ControlCode))
+ return STATUS_INVALID_DEVICE_REQUEST;
+
+ if (0 != InputBufferLength && 0 != OutputBufferLength &&
+ InputBufferLength != OutputBufferLength)
+ return STATUS_INVALID_DEVICE_REQUEST;
+
+ memset(&fi, 0, sizeof fi);
+ fi.flags = filedesc->OpenFlags;
+ fi.fh = filedesc->FileHandle;
+
+ /* construct a Linux compatible ioctl code */
+ cmd = FSP_FUSE_IOCTL((ControlCode >> 2) & 0xfff, InputBufferLength, OutputBufferLength);
+
+ if (0 == OutputBufferLength)
+ err = f->ops.ioctl(filedesc->PosixPath, cmd, 0, &fi, 0, InputBuffer);
+ else
+ {
+ if (0 != InputBufferLength)
+ // OutputBuffer points to Response->Buffer which is FSP_FSCTL_TRANSACT_RSP_BUFFER_SIZEMAX long
+ memcpy(OutputBuffer, InputBuffer, InputBufferLength);
+ err = f->ops.ioctl(filedesc->PosixPath, cmd, 0, &fi, 0, OutputBuffer);
+ }
+
+ return fsp_fuse_ntstatus_from_errno(f->env, err);
+}
+
FSP_FILE_SYSTEM_INTERFACE fsp_fuse_intf =
{
fsp_fuse_intf_GetVolumeInfo,
@@ -2152,6 +2193,7 @@ FSP_FILE_SYSTEM_INTERFACE fsp_fuse_intf =
fsp_fuse_intf_DeleteReparsePoint,
0,
fsp_fuse_intf_GetDirInfoByName,
+ fsp_fuse_intf_Control,
};
/*
diff --git a/src/dotnet/FileSystemBase.cs b/src/dotnet/FileSystemBase.cs
index e66da75a..e208abd9 100644
--- a/src/dotnet/FileSystemBase.cs
+++ b/src/dotnet/FileSystemBase.cs
@@ -966,6 +966,49 @@ namespace Fsp
FileInfo = default(FileInfo);
return STATUS_INVALID_DEVICE_REQUEST;
}
+ ///
+ /// Processes a control code.
+ ///
+ ///
+ /// This function is called when a program uses the DeviceIoControl API.
+ ///
+ ///
+ /// The file node of the file or directory to be controled.
+ ///
+ ///
+ /// The file descriptor of the file or directory to be controled.
+ ///
+ ///
+ /// The control code for the operation. This code must have a DeviceType with bit
+ /// 0x8000 set and must have a TransferType of METHOD_BUFFERED.
+ ///
+ ///
+ /// Pointer to a buffer that contains the input data.
+ ///
+ ///
+ /// Input data length.
+ ///
+ ///
+ /// Pointer to a buffer that will receive the output data.
+ ///
+ ///
+ /// Output data length.
+ ///
+ ///
+ /// Receives the actual number of bytes transferred.
+ ///
+ /// STATUS_SUCCESS or error code.
+ public virtual Int32 Control(
+ Object FileNode,
+ Object FileDesc,
+ UInt32 ControlCode,
+ IntPtr InputBuffer, UInt32 InputBufferLength,
+ IntPtr OutputBuffer, UInt32 OutputBufferLength,
+ out UInt32 BytesTransferred)
+ {
+ BytesTransferred = default(UInt32);
+ return STATUS_INVALID_DEVICE_REQUEST;
+ }
/* helpers */
///
diff --git a/src/dotnet/FileSystemHost.cs b/src/dotnet/FileSystemHost.cs
index d98ad582..55a14c2c 100644
--- a/src/dotnet/FileSystemHost.cs
+++ b/src/dotnet/FileSystemHost.cs
@@ -1020,6 +1020,35 @@ namespace Fsp
return ExceptionHandler(FileSystem, ex);
}
}
+ private static Int32 Control(
+ IntPtr FileSystemPtr,
+ ref FullContext FullContext,
+ UInt32 ControlCode,
+ IntPtr InputBuffer, UInt32 InputBufferLength,
+ IntPtr OutputBuffer, UInt32 OutputBufferLength,
+ out UInt32 PBytesTransferred)
+ {
+ FileSystemBase FileSystem = (FileSystemBase)Api.GetUserContext(FileSystemPtr);
+ try
+ {
+ Object FileNode, FileDesc;
+ Api.GetFullContext(ref FullContext, out FileNode, out FileDesc);
+ return FileSystem.Control(
+ FileNode,
+ FileDesc,
+ ControlCode,
+ InputBuffer,
+ InputBufferLength,
+ OutputBuffer,
+ OutputBufferLength,
+ out PBytesTransferred);
+ }
+ catch (Exception ex)
+ {
+ PBytesTransferred = default(UInt32);
+ return ExceptionHandler(FileSystem, ex);
+ }
+ }
static FileSystemHost()
{
@@ -1048,6 +1077,7 @@ namespace Fsp
_FileSystemInterface.DeleteReparsePoint = DeleteReparsePoint;
_FileSystemInterface.GetStreamInfo = GetStreamInfo;
_FileSystemInterface.GetDirInfoByName = GetDirInfoByName;
+ _FileSystemInterface.Control = Control;
_FileSystemInterfacePtr = Marshal.AllocHGlobal(FileSystemInterface.Size);
Marshal.StructureToPtr(_FileSystemInterface, _FileSystemInterfacePtr, false);
diff --git a/src/dotnet/Interop.cs b/src/dotnet/Interop.cs
index c9a7c95b..c81e9b67 100644
--- a/src/dotnet/Interop.cs
+++ b/src/dotnet/Interop.cs
@@ -457,6 +457,14 @@ namespace Fsp.Interop
ref FullContext FullContext,
[MarshalAs(UnmanagedType.LPWStr)] String FileName,
out DirInfo DirInfo);
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate Int32 Control(
+ IntPtr FileSystem,
+ ref FullContext FullContext,
+ UInt32 ControlCode,
+ IntPtr InputBuffer, UInt32 InputBufferLength,
+ IntPtr OutputBuffer, UInt32 OutputBufferLength,
+ out UInt32 PBytesTransferred);
}
internal static int Size = IntPtr.Size * 64;
@@ -486,7 +494,8 @@ namespace Fsp.Interop
internal Proto.DeleteReparsePoint DeleteReparsePoint;
internal Proto.GetStreamInfo GetStreamInfo;
internal Proto.GetDirInfoByName GetDirInfoByName;
- /* NTSTATUS (*Reserved[39])(); */
+ internal Proto.Control Control;
+ /* NTSTATUS (*Reserved[38])(); */
}
[SuppressUnmanagedCodeSecurity]
diff --git a/src/sys/devctl.c b/src/sys/devctl.c
index a076ec63..5fc0e32a 100644
--- a/src/sys/devctl.c
+++ b/src/sys/devctl.c
@@ -20,20 +20,81 @@
static NTSTATUS FspFsvolDeviceControl(
PDEVICE_OBJECT DeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp);
FSP_IOCMPL_DISPATCH FspFsvolDeviceControlComplete;
+static FSP_IOP_REQUEST_FINI FspFsvolDeviceControlRequestFini;
FSP_DRIVER_DISPATCH FspDeviceControl;
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, FspFsvolDeviceControl)
#pragma alloc_text(PAGE, FspFsvolDeviceControlComplete)
+#pragma alloc_text(PAGE, FspFsvolDeviceControlRequestFini)
#pragma alloc_text(PAGE, FspDeviceControl)
#endif
+enum
+{
+ RequestFileNode = 0,
+};
+
static NTSTATUS FspFsvolDeviceControl(
- PDEVICE_OBJECT DeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp)
+ PDEVICE_OBJECT FsvolDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp)
{
PAGED_CODE();
- return STATUS_INVALID_DEVICE_REQUEST;
+ FSP_FSVOL_DEVICE_EXTENSION *FsvolDeviceExtension = FspFsvolDeviceExtension(FsvolDeviceObject);
+ PFILE_OBJECT FileObject = IrpSp->FileObject;
+ ULONG IoControlCode = IrpSp->Parameters.DeviceIoControl.IoControlCode;
+
+ /* do we support DeviceControl? */
+ if (!FsvolDeviceExtension->VolumeParams.DeviceControl)
+ return STATUS_INVALID_DEVICE_REQUEST;
+
+ /* do not forward IRP's originating in the kernel! */
+ if (KernelMode == Irp->RequestorMode)
+ return STATUS_INVALID_DEVICE_REQUEST;
+
+ /* only allow custom devices and METHOD_BUFFERED */
+ if (0 == (DEVICE_TYPE_FROM_CTL_CODE(IoControlCode) & 0x8000) ||
+ METHOD_BUFFERED != METHOD_FROM_CTL_CODE(IoControlCode))
+ return STATUS_INVALID_DEVICE_REQUEST;
+
+ /* is this a valid FileObject? */
+ if (!FspFileNodeIsValid(FileObject->FsContext))
+ return STATUS_INVALID_PARAMETER;
+
+ NTSTATUS Result;
+ FSP_FILE_NODE *FileNode = FileObject->FsContext;
+ FSP_FILE_DESC *FileDesc = FileObject->FsContext2;
+ PVOID InputBuffer = Irp->AssociatedIrp.SystemBuffer;
+ ULONG InputBufferLength = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
+ ULONG OutputBufferLength = IrpSp->Parameters.DeviceIoControl.OutputBufferLength;
+ FSP_FSCTL_TRANSACT_REQ *Request;
+
+ ASSERT(FileNode == FileDesc->FileNode);
+
+ if (FSP_FSCTL_DEVICECONTROL_SIZEMAX < InputBufferLength ||
+ FSP_FSCTL_DEVICECONTROL_SIZEMAX < OutputBufferLength)
+ return STATUS_INVALID_BUFFER_SIZE;
+
+ Result = FspIopCreateRequestEx(Irp, 0, InputBufferLength,
+ FspFsvolDeviceControlRequestFini, &Request);
+ if (!NT_SUCCESS(Result))
+ return Result;
+
+ FspFileNodeAcquireShared(FileNode, Full);
+
+ Request->Kind = FspFsctlTransactDeviceControlKind;
+ Request->Req.DeviceControl.UserContext = FileNode->UserContext;
+ Request->Req.DeviceControl.UserContext2 = FileDesc->UserContext2;
+ Request->Req.DeviceControl.IoControlCode = IoControlCode;
+ Request->Req.DeviceControl.Buffer.Offset = 0;
+ Request->Req.DeviceControl.Buffer.Size = (UINT16)InputBufferLength;
+ Request->Req.DeviceControl.OutputLength = OutputBufferLength;
+ RtlCopyMemory(Request->Buffer, InputBuffer, InputBufferLength);
+
+ FspFileNodeSetOwner(FileNode, Full, Request);
+ FspIopRequestContext(Request, RequestFileNode) = FileNode;
+
+ return FSP_STATUS_IOQ_POST;
}
NTSTATUS FspFsvolDeviceControlComplete(
@@ -41,12 +102,46 @@ NTSTATUS FspFsvolDeviceControlComplete(
{
FSP_ENTER_IOC(PAGED_CODE());
+ if (!NT_SUCCESS(Response->IoStatus.Status))
+ {
+ Irp->IoStatus.Information = 0;
+ Result = Response->IoStatus.Status;
+ FSP_RETURN();
+ }
+
+ PVOID OutputBuffer = Irp->AssociatedIrp.SystemBuffer;
+ ULONG OutputBufferLength = IrpSp->Parameters.DeviceIoControl.OutputBufferLength;
+
+ if (Response->Buffer + Response->Rsp.DeviceControl.Buffer.Offset +
+ Response->Rsp.DeviceControl.Buffer.Size > (PUINT8)Response + Response->Size)
+ FSP_RETURN(Result = STATUS_INTERNAL_ERROR);
+
+ if (OutputBufferLength >= Response->Rsp.DeviceControl.Buffer.Size)
+ OutputBufferLength = Response->Rsp.DeviceControl.Buffer.Size;
+ else
+ Result = STATUS_BUFFER_OVERFLOW;
+
+ RtlCopyMemory(OutputBuffer, Response->Buffer + Response->Rsp.DeviceControl.Buffer.Offset,
+ OutputBufferLength);
+
+ Irp->IoStatus.Information = OutputBufferLength;
+
FSP_LEAVE_IOC(
"%s, FileObject=%p",
IoctlCodeSym(IrpSp->Parameters.DeviceIoControl.IoControlCode),
IrpSp->FileObject);
}
+static VOID FspFsvolDeviceControlRequestFini(FSP_FSCTL_TRANSACT_REQ *Request, PVOID Context[4])
+{
+ PAGED_CODE();
+
+ FSP_FILE_NODE *FileNode = Context[RequestFileNode];
+
+ if (0 != FileNode)
+ FspFileNodeReleaseOwner(FileNode, Full, Request);
+}
+
NTSTATUS FspDeviceControl(
PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
diff --git a/tst/memfs/memfs.cpp b/tst/memfs/memfs.cpp
index e5006d12..e1ba473e 100644
--- a/tst/memfs/memfs.cpp
+++ b/tst/memfs/memfs.cpp
@@ -55,6 +55,11 @@ FSP_FSCTL_STATIC_ASSERT(MEMFS_MAX_PATH > MAX_PATH,
*/
#define MEMFS_SLOWIO
+/*
+ * Define the MEMFS_CONTROL macro to include DeviceControl support.
+ */
+#define MEMFS_CONTROL
+
/*
* Define the DEBUG_BUFFER_CHECK macro on Windows 8 or above. This includes
* a check for the Write buffer to ensure that it is read-only.
@@ -1895,6 +1900,38 @@ static NTSTATUS GetStreamInfo(FSP_FILE_SYSTEM *FileSystem,
}
#endif
+#if defined(MEMFS_CONTROL)
+static NTSTATUS Control(FSP_FILE_SYSTEM *FileSystem,
+ PVOID FileContext, UINT32 ControlCode,
+ PVOID InputBuffer, ULONG InputBufferLength,
+ PVOID OutputBuffer, ULONG OutputBufferLength, PULONG PBytesTransferred)
+{
+ /* MEMFS also supports encryption! See below :) */
+ if (CTL_CODE(0x8000 + 'M', 'R', METHOD_BUFFERED, FILE_ANY_ACCESS) == ControlCode)
+ {
+ if (OutputBufferLength != InputBufferLength)
+ return STATUS_INVALID_PARAMETER;
+
+ for (PUINT8 P = (PUINT8)InputBuffer, Q = (PUINT8)OutputBuffer, EndP = P + InputBufferLength;
+ EndP > P; P++, Q++)
+ {
+ if (('A' <= *P && *P <= 'M') || ('a' <= *P && *P <= 'm'))
+ *Q = *P + 13;
+ else
+ if (('N' <= *P && *P <= 'Z') || ('n' <= *P && *P <= 'z'))
+ *Q = *P - 13;
+ else
+ *Q = *P;
+ }
+
+ *PBytesTransferred = InputBufferLength;
+ return STATUS_SUCCESS;
+ }
+
+ return STATUS_INVALID_DEVICE_REQUEST;
+}
+#endif
+
static FSP_FILE_SYSTEM_INTERFACE MemfsInterface =
{
GetVolumeInfo,
@@ -1937,6 +1974,11 @@ static FSP_FILE_SYSTEM_INTERFACE MemfsInterface =
#else
0,
#endif
+#if defined(MEMFS_CONTROL)
+ Control,
+#else
+ 0,
+#endif
};
/*
@@ -2028,6 +2070,9 @@ NTSTATUS MemfsCreateFunnel(
VolumeParams.PassQueryDirectoryFileName = 1;
#endif
VolumeParams.FlushAndPurgeOnCleanup = FlushAndPurgeOnCleanup;
+#if defined(MEMFS_CONTROL)
+ VolumeParams.DeviceControl = 1;
+#endif
if (0 != VolumePrefix)
wcscpy_s(VolumeParams.Prefix, sizeof VolumeParams.Prefix / sizeof(WCHAR), VolumePrefix);
wcscpy_s(VolumeParams.FileSystemName, sizeof VolumeParams.FileSystemName / sizeof(WCHAR),
diff --git a/tst/winfsp-tests/devctl-test.c b/tst/winfsp-tests/devctl-test.c
new file mode 100644
index 00000000..457d40ea
--- /dev/null
+++ b/tst/winfsp-tests/devctl-test.c
@@ -0,0 +1,74 @@
+/**
+ * @file devctl-test.c
+ *
+ * @copyright 2015-2018 Bill Zissimopoulos
+ */
+/*
+ * This file is part of WinFsp.
+ *
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License version 3 as published by the Free Software
+ * Foundation.
+ *
+ * Licensees holding a valid commercial license may use this file in
+ * accordance with the commercial license agreement provided with the
+ * software.
+ */
+
+#include
+#include
+#include
+#include "memfs.h"
+
+#include "winfsp-tests.h"
+
+static void devctl_dotest(ULONG Flags, PWSTR Prefix, PWSTR Drive)
+{
+ void *memfs = memfs_start(Flags);
+
+ WCHAR FilePath[1024];
+ HANDLE Handle;
+ BOOL Success;
+ CHAR Buffer[26];
+ DWORD BytesTransferred;
+
+ StringCbPrintfW(FilePath, sizeof FilePath, L"%s%s\\",
+ Prefix ? L"" : L"\\\\?\\GLOBALROOT", Prefix ? Prefix : memfs_volumename(memfs));
+
+ Handle = CreateFileW(FilePath,
+ GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS, 0);
+ ASSERT(INVALID_HANDLE_VALUE != Handle);
+
+ Success = DeviceIoControl(Handle,
+ CTL_CODE(0x8000 + 'M', 'R', METHOD_BUFFERED, FILE_ANY_ACCESS),
+ "ABCDEFghijklmNOPQRStuvwxyz", 26,
+ Buffer, sizeof Buffer,
+ &BytesTransferred,
+ 0);
+ ASSERT(Success);
+
+ ASSERT(26 == BytesTransferred);
+ ASSERT(0 == memcmp("NOPQRStuvwxyzABCDEFghijklm", Buffer, BytesTransferred));
+
+ Success = CloseHandle(Handle);
+ ASSERT(Success);
+
+ memfs_stop(memfs);
+}
+
+static void devctl_test(void)
+{
+ if (WinFspDiskTests)
+ devctl_dotest(MemfsDisk, 0, 0);
+ if (WinFspNetTests)
+ devctl_dotest(MemfsNet, L"\\\\memfs\\share", L"\\\\memfs\\share");
+}
+
+void devctl_tests(void)
+{
+ if (OptExternal)
+ return;
+
+ TEST(devctl_test);
+}
diff --git a/tst/winfsp-tests/winfsp-tests.c b/tst/winfsp-tests/winfsp-tests.c
index e28b9587..8d4a4437 100644
--- a/tst/winfsp-tests/winfsp-tests.c
+++ b/tst/winfsp-tests/winfsp-tests.c
@@ -199,6 +199,7 @@ int main(int argc, char *argv[])
TESTSUITE(lock_tests);
TESTSUITE(dirctl_tests);
TESTSUITE(exec_tests);
+ TESTSUITE(devctl_tests);
TESTSUITE(reparse_tests);
TESTSUITE(stream_tests);
TESTSUITE(oplock_tests);