From 8d6a099de9a56b34e97c7d712c8e7b89e8b7b642 Mon Sep 17 00:00:00 2001 From: Jonathan Giroux Date: Thu, 1 Sep 2022 20:08:59 +0200 Subject: [PATCH] upgrade to Dokan 2.0.5 --- Cargo.lock | 6 +- appveyor.yml | 8 +- dokan-sys/Cargo.toml | 2 +- dokan-sys/src/dokany | 2 +- dokan-sys/src/lib.rs | 85 +++++++++++++------ dokan/Cargo.toml | 4 +- dokan/examples/memfs/main.rs | 27 ++++-- dokan/src/lib.rs | 160 ++++++++++++++++++++++++----------- dokan/src/tests.rs | 124 +++++++++++++++++---------- 9 files changed, 279 insertions(+), 139 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0bee3b4..7f07200 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "aho-corasick" version = "0.7.18" @@ -64,7 +66,7 @@ dependencies = [ [[package]] name = "dokan" -version = "0.2.0+dokan150" +version = "0.3.0+dokan205" dependencies = [ "bitflags", "clap", @@ -78,7 +80,7 @@ dependencies = [ [[package]] name = "dokan-sys" -version = "0.2.0+dokan150" +version = "0.3.0+dokan205" dependencies = [ "cc", "libc", diff --git a/appveyor.yml b/appveyor.yml index a175933..cbd8401 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -49,11 +49,11 @@ install: - ps: $Env:PATH = "$Env:PATH;C:\Users\appveyor\.cargo\bin" - ps: | if ($Env:USE_INSTALLED_LIB -eq $true) { - $Env:DokanLibrary1_LibraryPath_x64 = "C:\Program Files\Dokan\Dokan Library-1.5.0\lib\" - $Env:DokanLibrary1_LibraryPath_x86 = "C:\Program Files\Dokan\Dokan Library-1.5.0\x86\lib\" + $Env:DokanLibrary1_LibraryPath_x64 = "C:\Program Files\Dokan\DokanLibrary-2.0.5\lib\" + $Env:DokanLibrary1_LibraryPath_x86 = "C:\Program Files\Dokan\DokanLibrary-2.0.5\x86\lib\" } else { - rm C:\Windows\System32\dokan1.dll - rm C:\Windows\SysWOW64\dokan1.dll + rm C:\Windows\System32\dokan2.dll + rm C:\Windows\SysWOW64\dokan2.dll } before_build: diff --git a/dokan-sys/Cargo.toml b/dokan-sys/Cargo.toml index a289403..c7fedfe 100644 --- a/dokan-sys/Cargo.toml +++ b/dokan-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dokan-sys" -version = "0.2.0+dokan150" +version = "0.3.0+dokan205" authors = ["DDoSolitary "] description = "Raw FFI bindings for Dokan (user mode file system library for Windows)" homepage = "https://dokan-dev.github.io" diff --git a/dokan-sys/src/dokany b/dokan-sys/src/dokany index 6f8a347..1dc492a 160000 --- a/dokan-sys/src/dokany +++ b/dokan-sys/src/dokany @@ -1 +1 @@ -Subproject commit 6f8a3472dfbb36bd2340b3b59aa4a72e7d8b8795 +Subproject commit 1dc492a3219525cd13ef21fe9bd77788676193b8 diff --git a/dokan-sys/src/lib.rs b/dokan-sys/src/lib.rs index f971103..c6a532d 100644 --- a/dokan-sys/src/lib.rs +++ b/dokan-sys/src/lib.rs @@ -17,7 +17,7 @@ use libc::c_int; use winapi::shared::basetsd::ULONG64; use winapi::shared::minwindef::{BOOL, DWORD, FILETIME, LPCVOID, LPDWORD, LPVOID, MAX_PATH}; use winapi::shared::ntdef::{ - BOOLEAN, HANDLE, LONGLONG, LPCWSTR, LPWSTR, NTSTATUS, PULONG, PULONGLONG, PVOID64, UCHAR, + BOOLEAN, HANDLE, LONGLONG, LPCWSTR, LPWSTR, NTSTATUS, PULONG, PULONGLONG, PVOID, SCHAR, UCHAR, ULONG, UNICODE_STRING, USHORT, WCHAR, }; use winapi::um::fileapi::LPBY_HANDLE_FILE_INFORMATION; @@ -30,26 +30,28 @@ pub mod win32; include!(concat!(env!("OUT_DIR"), "/version.rs")); -pub const DOKAN_OPTION_DEBUG: ULONG = 1; -pub const DOKAN_OPTION_STDERR: ULONG = 2; -pub const DOKAN_OPTION_ALT_STREAM: ULONG = 4; -pub const DOKAN_OPTION_WRITE_PROTECT: ULONG = 8; -pub const DOKAN_OPTION_NETWORK: ULONG = 16; -pub const DOKAN_OPTION_REMOVABLE: ULONG = 32; -pub const DOKAN_OPTION_MOUNT_MANAGER: ULONG = 64; -pub const DOKAN_OPTION_CURRENT_SESSION: ULONG = 128; -pub const DOKAN_OPTION_FILELOCK_USER_MODE: ULONG = 256; -pub const DOKAN_OPTION_ENABLE_NOTIFICATION_API: ULONG = 512; -pub const DOKAN_OPTION_ENABLE_FCB_GARBAGE_COLLECTION: ULONG = 2048; -pub const DOKAN_OPTION_CASE_SENSITIVE: ULONG = 4096; -pub const DOKAN_OPTION_ENABLE_UNMOUNT_NETWORK_DRIVE: ULONG = 8192; -pub const DOKAN_OPTION_DISPATCH_DRIVER_LOGS: ULONG = 16384; +pub const DOKAN_OPTION_DEBUG: ULONG = 1 << 0; +pub const DOKAN_OPTION_STDERR: ULONG = 1 << 1; +pub const DOKAN_OPTION_ALT_STREAM: ULONG = 1 << 2; +pub const DOKAN_OPTION_WRITE_PROTECT: ULONG = 1 << 3; +pub const DOKAN_OPTION_NETWORK: ULONG = 1 << 4; +pub const DOKAN_OPTION_REMOVABLE: ULONG = 1 << 5; +pub const DOKAN_OPTION_MOUNT_MANAGER: ULONG = 1 << 6; +pub const DOKAN_OPTION_CURRENT_SESSION: ULONG = 1 << 7; +pub const DOKAN_OPTION_FILELOCK_USER_MODE: ULONG = 1 << 8; +pub const DOKAN_OPTION_CASE_SENSITIVE: ULONG = 1 << 9; +pub const DOKAN_OPTION_ENABLE_UNMOUNT_NETWORK_DRIVE: ULONG = 1 << 10; +pub const DOKAN_OPTION_DISPATCH_DRIVER_LOGS: ULONG = 1 << 11; +pub const DOKAN_OPTION_ALLOW_IPC_BATCHING: ULONG = 1 << 12; + +pub type DOKAN_HANDLE = *mut libc::c_void; +pub type PDOKAN_HANDLE = *mut DOKAN_HANDLE; #[repr(C)] #[derive(Debug)] pub struct DOKAN_OPTIONS { pub Version: USHORT, - pub ThreadCount: USHORT, + pub SingleThread: BOOLEAN, pub Options: ULONG, pub GlobalContext: ULONG64, pub MountPoint: LPCWSTR, @@ -57,6 +59,8 @@ pub struct DOKAN_OPTIONS { pub Timeout: ULONG, pub AllocationUnitSize: ULONG, pub SectorSize: ULONG, + pub VolumeSecurityDescriptorLength: ULONG, + pub VolumeSecurityDescriptor: [SCHAR; 1024 * 16], } pub type PDOKAN_OPTIONS = *mut DOKAN_OPTIONS; @@ -67,6 +71,7 @@ pub struct DOKAN_FILE_INFO { pub Context: ULONG64, pub DokanContext: ULONG64, pub DokanOptions: PDOKAN_OPTIONS, + pub ProcessingContext: PVOID, pub ProcessId: ULONG, pub IsDirectory: UCHAR, pub DeleteOnClose: UCHAR, @@ -79,8 +84,7 @@ pub struct DOKAN_FILE_INFO { pub type PDOKAN_FILE_INFO = *mut DOKAN_FILE_INFO; pub type PFillFindData = unsafe extern "stdcall" fn(PWIN32_FIND_DATAW, PDOKAN_FILE_INFO) -> c_int; -pub type PFillFindStreamData = - unsafe extern "stdcall" fn(PWIN32_FIND_STREAM_DATA, PDOKAN_FILE_INFO) -> c_int; +pub type PFillFindStreamData = unsafe extern "stdcall" fn(PWIN32_FIND_STREAM_DATA, PVOID) -> BOOL; #[repr(C)] pub struct DOKAN_ACCESS_STATE { @@ -246,7 +250,9 @@ pub struct DOKAN_OPERATIONS { DokanFileInfo: PDOKAN_FILE_INFO, ) -> NTSTATUS, >, - pub Mounted: Option NTSTATUS>, + pub Mounted: Option< + extern "stdcall" fn(MountPoint: LPCWSTR, DokanFileInfo: PDOKAN_FILE_INFO) -> NTSTATUS, + >, pub Unmounted: Option NTSTATUS>, pub GetFileSecurity: Option< extern "stdcall" fn( @@ -271,6 +277,7 @@ pub struct DOKAN_OPERATIONS { extern "stdcall" fn( FileName: LPCWSTR, FillFindStreamData: PFillFindStreamData, + FindStreamContext: PVOID, DokanFileInfo: PDOKAN_FILE_INFO, ) -> NTSTATUS, >, @@ -288,19 +295,32 @@ pub const DOKAN_MOUNT_POINT_ERROR: c_int = -6; pub const DOKAN_VERSION_ERROR: c_int = -7; #[repr(C)] -pub struct DOKAN_CONTROL { +pub struct DOKAN_MOUNT_POINT_INFO { pub Type: ULONG, pub MountPoint: [WCHAR; MAX_PATH], pub UNCName: [WCHAR; 64], pub DeviceName: [WCHAR; 64], - pub VolumeDeviceObject: PVOID64, pub SessionId: ULONG, + pub MountOptions: ULONG, } -pub type PDOKAN_CONTROL = *mut DOKAN_CONTROL; +pub type PDOKAN_MOUNT_POINT_INFO = *mut DOKAN_MOUNT_POINT_INFO; extern "stdcall" { + pub fn DokanInit(); + pub fn DokanShutdown(); pub fn DokanMain(DokanOptions: PDOKAN_OPTIONS, DokanOperations: PDOKAN_OPERATIONS) -> c_int; + pub fn DokanCreateFileSystem( + DokanOptions: PDOKAN_OPTIONS, + DokanOperations: PDOKAN_OPERATIONS, + DokanInstance: *mut DOKAN_HANDLE, + ) -> c_int; + pub fn DokanIsFileSystemRunning(DokanInstance: DOKAN_HANDLE) -> BOOL; + pub fn DokanWaitForFileSystemClosed( + DokanInstance: DOKAN_HANDLE, + dwMilliseconds: DWORD, + ) -> DWORD; + pub fn DokanCloseHandle(DokanInstance: DOKAN_HANDLE); pub fn DokanUnmount(DriveLetter: WCHAR) -> BOOL; pub fn DokanRemoveMountPoint(MountPoint: LPCWSTR) -> BOOL; pub fn DokanIsNameInExpression(Expression: LPCWSTR, Name: LPCWSTR, IgnoreCase: BOOL) -> BOOL; @@ -308,8 +328,8 @@ extern "stdcall" { pub fn DokanDriverVersion() -> ULONG; pub fn DokanResetTimeout(Timeout: ULONG, DokanFileInfo: PDOKAN_FILE_INFO) -> BOOL; pub fn DokanOpenRequestorToken(DokanFileInfo: PDOKAN_FILE_INFO) -> HANDLE; - pub fn DokanGetMountPointList(uncOnly: BOOL, nbRead: PULONG) -> PDOKAN_CONTROL; - pub fn DokanReleaseMountPointList(list: PDOKAN_CONTROL); + pub fn DokanGetMountPointList(uncOnly: BOOL, nbRead: PULONG) -> PDOKAN_MOUNT_POINT_INFO; + pub fn DokanReleaseMountPointList(list: PDOKAN_MOUNT_POINT_INFO); pub fn DokanMapKernelToUserCreateFileFlags( DesiredAccess: ACCESS_MASK, FileAttributes: ULONG, @@ -319,11 +339,20 @@ extern "stdcall" { outFileAttributesAndFlags: *mut DWORD, outCreationDisposition: *mut DWORD, ); - pub fn DokanNotifyCreate(FilePath: LPCWSTR, IsDirectory: BOOL) -> BOOL; - pub fn DokanNotifyDelete(FilePath: LPCWSTR, IsDirectory: BOOL) -> BOOL; - pub fn DokanNotifyUpdate(FilePath: LPCWSTR) -> BOOL; - pub fn DokanNotifyXAttrUpdate(FilePath: LPCWSTR) -> BOOL; + pub fn DokanNotifyCreate( + DokanInstance: DOKAN_HANDLE, + FilePath: LPCWSTR, + IsDirectory: BOOL, + ) -> BOOL; + pub fn DokanNotifyDelete( + DokanInstance: DOKAN_HANDLE, + FilePath: LPCWSTR, + IsDirectory: BOOL, + ) -> BOOL; + pub fn DokanNotifyUpdate(DokanInstance: DOKAN_HANDLE, FilePath: LPCWSTR) -> BOOL; + pub fn DokanNotifyXAttrUpdate(DokanInstance: DOKAN_HANDLE, FilePath: LPCWSTR) -> BOOL; pub fn DokanNotifyRename( + DokanInstance: DOKAN_HANDLE, OldPath: LPCWSTR, NewPath: LPCWSTR, IsDirectory: BOOL, diff --git a/dokan/Cargo.toml b/dokan/Cargo.toml index 0e284dc..4b098b8 100644 --- a/dokan/Cargo.toml +++ b/dokan/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dokan" -version = "0.2.0+dokan150" +version = "0.3.0+dokan205" authors = ["DDoSolitary "] description = "Rust-friendly wrapper for Dokan (user mode file system library for Windows)" homepage = "https://dokan-dev.github.io" @@ -16,7 +16,7 @@ edition = "2018" appveyor = { repository = "Liryna/dokan-rust" } [dependencies] -dokan-sys = { version = "= 0.2.0", path = "../dokan-sys" } +dokan-sys = { version = "= 0.3.0", path = "../dokan-sys" } bitflags = "1.2.1" widestring = "0.4.3" winapi = { version = "0.3.9", features = ["std", "errhandlingapi", "handleapi", "heapapi", "ioapiset", "minwinbase", "minwindef", "ntdef", "ntstatus", "processthreadsapi", "sddl", "securitybaseapi", "synchapi", "winbase", "winerror", "winnt"] } diff --git a/dokan/examples/memfs/main.rs b/dokan/examples/memfs/main.rs index 9ea9d95..f64f3ed 100644 --- a/dokan/examples/memfs/main.rs +++ b/dokan/examples/memfs/main.rs @@ -1206,6 +1206,18 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { }) } + fn mounted( + &'b self, + _mount_point: &U16CStr, + _info: &OperationInfo<'a, 'b, Self>, + ) -> Result<(), OperationError> { + Ok(()) + } + + fn unmounted(&'b self, _info: &OperationInfo<'a, 'b, Self>) -> Result<(), OperationError> { + Ok(()) + } + fn get_file_security( &'b self, _file_name: &U16CStr, @@ -1284,12 +1296,9 @@ fn main() -> Result<(), Box> { .help("Mount point path."), ) .arg( - Arg::with_name("thread_count") + Arg::with_name("single_thread") .short("t") - .long("threads") - .takes_value(true) - .value_name("THREAD_COUNT") - .default_value("0") + .long("single-thread") .help("Thread count. Use \"0\" to let Dokan choose it automatically."), ) .arg( @@ -1313,10 +1322,16 @@ fn main() -> Result<(), Box> { if matches.is_present("removable") { flags |= MountFlags::REMOVABLE; } + + init(); + Drive::new() .mount_point(&mount_point) - .thread_count(matches.value_of("thread_count").unwrap().parse()?) + .single_thread(matches.is_present("single_thread")) .flags(flags) .mount(&MemFsHandler::new())?; + + shutdown(); + Ok(()) } diff --git a/dokan/src/lib.rs b/dokan/src/lib.rs index 89bdc2b..5f88a8d 100644 --- a/dokan/src/lib.rs +++ b/dokan/src/lib.rs @@ -35,11 +35,11 @@ use std::{mem, panic, ptr, slice}; use dokan_sys::{win32::*, *}; use widestring::{U16CStr, U16CString}; -use winapi::ctypes::c_int; +use winapi::shared::basetsd::ULONG64; use winapi::shared::minwindef::{ BOOL, DWORD, FALSE, FILETIME, LPCVOID, LPDWORD, LPVOID, MAX_PATH, PULONG, TRUE, ULONG, }; -use winapi::shared::ntdef::{HANDLE, LONGLONG, LPCWSTR, LPWSTR, NTSTATUS, PULONGLONG}; +use winapi::shared::ntdef::{HANDLE, LONGLONG, LPCWSTR, LPWSTR, NTSTATUS, PULONGLONG, PVOID}; use winapi::shared::ntstatus::{ STATUS_BUFFER_OVERFLOW, STATUS_INTERNAL_ERROR, STATUS_NOT_IMPLEMENTED, STATUS_OBJECT_NAME_COLLISION, STATUS_SUCCESS, @@ -47,6 +47,7 @@ use winapi::shared::ntstatus::{ use winapi::um::fileapi::{BY_HANDLE_FILE_INFORMATION, LPBY_HANDLE_FILE_INFORMATION}; use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE}; use winapi::um::minwinbase::WIN32_FIND_DATAW; +use winapi::um::winbase::INFINITE; use winapi::um::winnt::{ACCESS_MASK, PSECURITY_DESCRIPTOR, PSECURITY_INFORMATION}; pub use dokan_sys::{DOKAN_IO_SECURITY_CONTEXT, PDOKAN_IO_SECURITY_CONTEXT}; @@ -60,6 +61,21 @@ pub use dokan_sys::DOKAN_NP_NAME as NP_NAME; /// The version of Dokan that this wrapper is targeting. pub use dokan_sys::DOKAN_VERSION as WRAPPER_VERSION; +/// Initialize all required Dokan internal resources. +/// +/// This needs to be called only once before trying to use other functions for the first time. +/// Otherwise they will fail and raise an exception. +pub fn init() { + unsafe { DokanInit() } +} + +/// Release all allocated resources by \ref DokanInit when they are no longer needed. +/// +/// This should be called when the application no longer expects to create a new FileSystem and after all devices are unmount. +pub fn shutdown() { + unsafe { DokanShutdown() } +} + /// Gets version of the loaded Dokan library. /// /// The returned value is the version number without dots. For example, it returns `131` if Dokan @@ -180,7 +196,7 @@ pub struct MountPointInfo { } struct MountPointListWrapper { - list_ptr: PDOKAN_CONTROL, + list_ptr: PDOKAN_MOUNT_POINT_INFO, } impl Drop for MountPointListWrapper { @@ -245,32 +261,40 @@ pub fn get_mount_point_list(unc_only: bool) -> Option> { /// /// Returns `true` on success. #[must_use] -pub fn notify_create(path: impl AsRef, is_dir: bool) -> bool { - unsafe { DokanNotifyCreate(path.as_ref().as_ptr(), is_dir.into()) == TRUE } +pub fn notify_create( + dokan_instance: DOKAN_HANDLE, + path: impl AsRef, + is_dir: bool, +) -> bool { + unsafe { DokanNotifyCreate(dokan_instance, path.as_ref().as_ptr(), is_dir.into()) == TRUE } } /// Notifies Dokan that a file or directory has been deleted. /// /// Returns `true` on success. #[must_use] -pub fn notify_delete(path: impl AsRef, is_dir: bool) -> bool { - unsafe { DokanNotifyDelete(path.as_ref().as_ptr(), is_dir.into()) == TRUE } +pub fn notify_delete( + dokan_instance: DOKAN_HANDLE, + path: impl AsRef, + is_dir: bool, +) -> bool { + unsafe { DokanNotifyDelete(dokan_instance, path.as_ref().as_ptr(), is_dir.into()) == TRUE } } /// Notifies Dokan that attributes of a file or directory has been changed. /// /// Returns `true` on success. #[must_use] -pub fn notify_update(path: impl AsRef) -> bool { - unsafe { DokanNotifyUpdate(path.as_ref().as_ptr()) == TRUE } +pub fn notify_update(dokan_instance: DOKAN_HANDLE, path: impl AsRef) -> bool { + unsafe { DokanNotifyUpdate(dokan_instance, path.as_ref().as_ptr()) == TRUE } } /// Notifies Dokan that extended attributes of a file or directory has been changed. /// /// Returns `true` on success. #[must_use] -pub fn notify_xattr_update(path: impl AsRef) -> bool { - unsafe { DokanNotifyXAttrUpdate(path.as_ref().as_ptr()) == TRUE } +pub fn notify_xattr_update(dokan_instance: DOKAN_HANDLE, path: impl AsRef) -> bool { + unsafe { DokanNotifyXAttrUpdate(dokan_instance, path.as_ref().as_ptr()) == TRUE } } /// Notifies Dokan that a file or directory has been renamed. @@ -280,6 +304,7 @@ pub fn notify_xattr_update(path: impl AsRef) -> bool { /// Returns `true` on success. #[must_use] pub fn notify_rename( + dokan_instance: DOKAN_HANDLE, old_path: impl AsRef, new_path: impl AsRef, is_dir: bool, @@ -287,6 +312,7 @@ pub fn notify_rename( ) -> bool { unsafe { DokanNotifyRename( + dokan_instance, old_path.as_ref().as_ptr(), new_path.as_ref().as_ptr(), is_dir.into(), @@ -379,15 +405,6 @@ bitflags! { /// they will always fail and return `false`. /// /// [`notify_create`]: fn.notify_create.html - const ENABLE_NOTIFICATION_API = DOKAN_OPTION_ENABLE_NOTIFICATION_API; - - /// Enable garbage collection of file control blocks (FCB). - /// - /// It prevents filter drivers (like anti-virus software) from exponentially slowing down certain operations due - /// to repeatedly rebuilding state that they attach to the FCB header. - const ENABLE_FCB_GARBAGE_COLLECTION = DOKAN_OPTION_ENABLE_FCB_GARBAGE_COLLECTION; - - /// Enable case-sensitive file names. const CASE_SENSITIVE = DOKAN_OPTION_CASE_SENSITIVE; /// Allow unmounting network drives from Windows Explorer. @@ -395,6 +412,11 @@ bitflags! { /// Forward the kernel driver global and volume logs to the userland. const DISPATCH_DRIVER_LOGS = DOKAN_OPTION_DISPATCH_DRIVER_LOGS; + + /// Pull batches of events from the driver instead of a single one and execute them parallelly. + /// This option should only be used on computers with low cpu count + /// and userland filesystem taking time to process requests (like remote storage). + const ALLOW_IPC_BATCHING = DOKAN_OPTION_ALLOW_IPC_BATCHING; } } @@ -521,8 +543,8 @@ impl<'a, 'b: 'a, 'c: 'b, T: FileSystemHandler<'b, 'c> + 'c> OperationInfo<'b, 'c } /// Gets the number of threads used to handle file system operations. - pub fn thread_count(&self) -> u16 { - self.mount_options().ThreadCount + pub fn single_thread(&self) -> bool { + self.mount_options().SingleThread != 0 } /// Gets flags that controls behavior of the mounted volume. @@ -1328,7 +1350,11 @@ pub trait FileSystemHandler<'a, 'b: 'a>: Sync + Sized + 'b { } /// Called when Dokan has successfully mounted the volume. - fn mounted(&'b self, _info: &OperationInfo<'a, 'b, Self>) -> Result<(), OperationError> { + fn mounted( + &'b self, + _mount_point: &U16CStr, + _info: &OperationInfo<'a, 'b, Self>, + ) -> Result<(), OperationError> { Err(OperationError::NtStatus(STATUS_NOT_IMPLEMENTED)) } @@ -1394,13 +1420,14 @@ pub trait FileSystemHandler<'a, 'b: 'a>: Sync + Sized + 'b { } } -fn fill_data_wrapper>( - fill_data: unsafe extern "stdcall" fn(*mut T, PDOKAN_FILE_INFO) -> c_int, - dokan_file_info: PDOKAN_FILE_INFO, +fn fill_data_wrapper, TArg: Copy, TResult: PartialEq>( + fill_data: unsafe extern "stdcall" fn(*mut T, TArg) -> TResult, + dokan_file_info: TArg, + success_value: TResult, ) -> impl FnMut(&U) -> Result<(), FillDataError> { move |data| { let mut ffi_data = data.to_raw_struct().ok_or(FillDataError::NameTooLong)?; - if unsafe { fill_data(&mut ffi_data, dokan_file_info) == 0 } { + if unsafe { fill_data(&mut ffi_data, dokan_file_info) == success_value } { Ok(()) } else { Err(FillDataError::BufferFull) @@ -1569,7 +1596,7 @@ extern "stdcall" fn find_files<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( ) -> NTSTATUS { panic::catch_unwind(|| unsafe { let file_name = U16CStr::from_ptr_str(file_name); - let fill_wrapper = fill_data_wrapper::<_, FindData>(fill_find_data, dokan_file_info); + let fill_wrapper = fill_data_wrapper(fill_find_data, dokan_file_info, 0); let info = OperationInfo::<'a, 'b, T>::new(dokan_file_info); info.handler() .find_files(file_name, fill_wrapper, &info, info.context()) @@ -1587,7 +1614,7 @@ extern "stdcall" fn find_files_with_pattern<'a, 'b: 'a, T: FileSystemHandler<'a, panic::catch_unwind(|| unsafe { let file_name = U16CStr::from_ptr_str(file_name); let search_pattern = U16CStr::from_ptr_str(search_pattern); - let fill_wrapper = fill_data_wrapper(fill_find_data, dokan_file_info); + let fill_wrapper = fill_data_wrapper(fill_find_data, dokan_file_info, 0); let info = OperationInfo::<'a, 'b, T>::new(dokan_file_info); info.handler() .find_files_with_pattern( @@ -1854,28 +1881,26 @@ extern "stdcall" fn get_volume_information<'a, 'b: 'a, T: FileSystemHandler<'a, .unwrap_or(STATUS_INTERNAL_ERROR) } -// Same rationale as lock_unlock_file. -fn mounted_unmounted<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( +extern "stdcall" fn mounted<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( + mount_point: LPCWSTR, dokan_file_info: PDOKAN_FILE_INFO, - func: fn(&'b T, &OperationInfo<'a, 'b, T>) -> Result<(), OperationError>, ) -> NTSTATUS { - panic::catch_unwind(|| { + panic::catch_unwind(|| unsafe { + let mount_point = U16CStr::from_ptr_str(mount_point); let info = OperationInfo::<'a, 'b, T>::new(dokan_file_info); - func(info.handler(), &info).ntstatus() + info.handler().mounted(mount_point, &info).ntstatus() }) .unwrap_or(STATUS_INTERNAL_ERROR) } -extern "stdcall" fn mounted<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( - dokan_file_info: PDOKAN_FILE_INFO, -) -> NTSTATUS { - mounted_unmounted(dokan_file_info, T::mounted) -} - extern "stdcall" fn unmounted<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( dokan_file_info: PDOKAN_FILE_INFO, ) -> NTSTATUS { - mounted_unmounted(dokan_file_info, T::unmounted) + panic::catch_unwind(|| { + let info = OperationInfo::<'a, 'b, T>::new(dokan_file_info); + info.handler().unmounted(&info).ntstatus() + }) + .unwrap_or(STATUS_INTERNAL_ERROR) } extern "stdcall" fn get_file_security<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( @@ -1938,11 +1963,12 @@ extern "stdcall" fn set_file_security<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + extern "stdcall" fn find_streams<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( file_name: LPCWSTR, fill_find_stream_data: PFillFindStreamData, + find_stream_context: PVOID, dokan_file_info: PDOKAN_FILE_INFO, ) -> NTSTATUS { panic::catch_unwind(|| unsafe { let file_name = U16CStr::from_ptr_str(file_name); - let fill_wrapper = fill_data_wrapper(fill_find_stream_data, dokan_file_info); + let fill_wrapper = fill_data_wrapper(fill_find_stream_data, find_stream_context, 1); let info = OperationInfo::<'a, 'b, T>::new(dokan_file_info); info.handler() .find_streams(file_name, fill_wrapper, &info, info.context()) @@ -2012,7 +2038,7 @@ impl<'a> Drive<'a> { Drive { options: DOKAN_OPTIONS { Version: WRAPPER_VERSION as u16, - ThreadCount: 0, + SingleThread: 0, Options: 0, GlobalContext: 0, MountPoint: ptr::null(), @@ -2020,14 +2046,16 @@ impl<'a> Drive<'a> { Timeout: 0, AllocationUnitSize: 0, SectorSize: 0, + VolumeSecurityDescriptorLength: 0, + VolumeSecurityDescriptor: [0; 1024 * 16], }, phantom: PhantomData, } } /// Sets the number of threads used to handle file system operations. - pub fn thread_count(&mut self, value: u16) -> &mut Self { - self.options.ThreadCount = value; + pub fn single_thread(&mut self, value: bool) -> &mut Self { + self.options.SingleThread = value.into(); self } @@ -2084,7 +2112,7 @@ impl<'a> Drive<'a> { pub fn mount<'b, 'c: 'b, T: FileSystemHandler<'b, 'c> + 'c>( &mut self, handler: &'c T, - ) -> Result<(), MountError> { + ) -> Result { let mut operations = DOKAN_OPERATIONS { ZwCreateFile: Some(create_file::<'b, 'c, T>), Cleanup: Some(cleanup::<'b, 'c, T>), @@ -2112,12 +2140,44 @@ impl<'a> Drive<'a> { SetFileSecurity: Some(set_file_security::<'b, 'c, T>), FindStreams: Some(find_streams::<'b, 'c, T>), }; + self.options.GlobalContext = handler as *const _ as u64; - let result = unsafe { DokanMain(&mut self.options, &mut operations) }; - self.options.GlobalContext = 0; + + let mut instance: DOKAN_HANDLE = unsafe { mem::MaybeUninit::uninit().assume_init_mut() }; + let result = + unsafe { DokanCreateFileSystem(&mut self.options, &mut operations, &mut instance) }; + match result { - DOKAN_SUCCESS => Ok(()), - _ => unsafe { Err(mem::transmute(result)) }, + DOKAN_SUCCESS => Ok(MountHandle { + instance, + global_context: &mut self.options.GlobalContext, + }), + _ => { + self.options.GlobalContext = 0; + unsafe { Err(mem::transmute(result)) } + } + } + } +} + +#[derive(Debug, PartialEq)] +pub struct MountHandle { + instance: DOKAN_HANDLE, + global_context: *mut ULONG64, +} + +impl MountHandle { + pub fn instance(&self) -> DOKAN_HANDLE { + self.instance + } +} + +impl Drop for MountHandle { + fn drop(&mut self) { + unsafe { + DokanWaitForFileSystemClosed(self.instance, INFINITE); + DokanCloseHandle(self.instance); + *self.global_context = 0; } } } diff --git a/dokan/src/tests.rs b/dokan/src/tests.rs index 943d9f9..60bba29 100644 --- a/dokan/src/tests.rs +++ b/dokan/src/tests.rs @@ -9,6 +9,7 @@ use std::thread; use parking_lot::Mutex; use regex::Regex; +use winapi::ctypes::c_int; use winapi::shared::minwindef::{FALSE, HLOCAL}; use winapi::shared::ntdef::{HANDLE, NULL}; use winapi::shared::ntstatus::{STATUS_ACCESS_DENIED, STATUS_NOT_IMPLEMENTED}; @@ -151,7 +152,7 @@ struct OperationInfoDump { synchronous_io: bool, no_cache: bool, write_to_eof: bool, - thread_count: u16, + single_thread: bool, mount_flags: MountFlags, mount_point: Option, unc_name: Option, @@ -187,6 +188,10 @@ enum HandlerSignal { OperationInfo(OperationInfoDump), } +struct DokanInstance(DOKAN_HANDLE); + +unsafe impl Send for DokanInstance {} + #[derive(Debug)] struct TestHandler { tx: SyncSender, @@ -358,7 +363,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { synchronous_io: info.synchronous_io(), no_cache: info.no_cache(), write_to_eof: info.write_to_eof(), - thread_count: info.thread_count(), + single_thread: info.single_thread(), mount_flags: info.mount_flags(), mount_point: info.mount_point().map(|s| s.to_owned()), unc_name: info.unc_name().map(|s| s.to_owned()), @@ -773,7 +778,11 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { }) } - fn mounted(&'b self, _info: &OperationInfo<'a, 'b, Self>) -> Result<(), OperationError> { + fn mounted( + &'b self, + _mount_point: &U16CStr, + _info: &OperationInfo<'a, 'b, Self>, + ) -> Result<(), OperationError> { self.tx.send(HandlerSignal::Mounted).unwrap(); Ok(()) } @@ -868,10 +877,15 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { #[test] fn test_mount_error() { let (tx, _rx) = mpsc::sync_channel(1024); + + init(); + let result = Drive::new() .mount_point(&convert_str("0")) .mount(&TestHandler { tx }); assert_eq!(result, Err(MountError::MountError)); + + shutdown(); } lazy_static::lazy_static! { @@ -879,21 +893,23 @@ lazy_static::lazy_static! { } #[allow(unused_must_use)] -fn with_test_drive(f: impl FnOnce(&Receiver)) { +fn with_test_drive(f: impl FnOnce(&Receiver, DOKAN_HANDLE)) { let _guard = TEST_DRIVE_LOCK.lock(); // In case previous tests failed and didn't unmount the drive. unmount(convert_str("Z:\\")); + init(); + + let (tx_instance, rx_instance) = mpsc::sync_channel(1); let (tx, rx) = mpsc::sync_channel(1024); let handle = thread::spawn(move || { Drive::new() - .thread_count(4) + .single_thread(false) .flags( MountFlags::CURRENT_SESSION | MountFlags::FILELOCK_USER_MODE - | MountFlags::ALT_STREAM - | MountFlags::ENABLE_NOTIFICATION_API, + | MountFlags::ALT_STREAM, ) .mount_point(&convert_str("Z:\\")) // Min value specified by DOKAN_IRP_PENDING_TIMEOUT. @@ -901,17 +917,23 @@ fn with_test_drive(f: impl FnOnce(&Receiver)) { .allocation_unit_size(1024) .sector_size(1024) .mount(&TestHandler { tx }) + .map(|handle| { + tx_instance.send(DokanInstance(handle.instance())).unwrap(); + }) }); + let instance = rx_instance.recv().unwrap().0; assert_eq!(rx.recv().unwrap(), HandlerSignal::Mounted); - f(&rx); + f(&rx, instance); assert!(unmount(convert_str("Z:\\"))); assert_eq!(rx.recv().unwrap(), HandlerSignal::Unmounted); handle.join().unwrap().unwrap(); + + shutdown(); } #[test] fn test_get_mount_point_list() { - with_test_drive(|_rx| unsafe { + with_test_drive(|_rx, _instance| unsafe { let list = get_mount_point_list(false).unwrap(); assert_eq!(list.len(), 1); let info = &list[0]; @@ -931,7 +953,7 @@ fn test_get_mount_point_list() { #[test] fn test_panic() { - with_test_drive(|_rx| unsafe { + with_test_drive(|_rx, _instance| unsafe { let path = convert_str("Z:\\test_panic"); assert_eq!( CreateFileW( @@ -951,7 +973,7 @@ fn test_panic() { #[test] fn test_get_volume_information() { - with_test_drive(|_rx| unsafe { + with_test_drive(|_rx, _instance| unsafe { let path = convert_str("Z:\\"); let mut volume_name = [0; MAX_PATH + 1]; let mut fs_name = [0; MAX_PATH + 1]; @@ -993,7 +1015,7 @@ fn test_get_volume_information() { #[test] fn test_get_disk_free_space() { - with_test_drive(|_rx| unsafe { + with_test_drive(|_rx, _instance| unsafe { let path = convert_str("Z:\\"); let mut free_bytes_available = 0u64; let mut total_number_of_bytes = 0u64; @@ -1032,7 +1054,7 @@ fn open_file(path: impl AsRef) -> HANDLE { #[test] fn test_create_file() { - with_test_drive(|rx| unsafe { + with_test_drive(|rx, _instance| unsafe { let hf = open_file("Z:\\test_create_file"); assert_eq!(CloseHandle(hf), TRUE); assert_eq!( @@ -1050,7 +1072,7 @@ fn test_create_file() { #[test] fn test_close_file() { - with_test_drive(|rx| unsafe { + with_test_drive(|rx, _instance| unsafe { let hf = open_file("Z:\\test_close_file"); assert_eq!(CloseHandle(hf), TRUE); assert_eq!(rx.recv().unwrap(), HandlerSignal::Cleanup); @@ -1061,7 +1083,7 @@ fn test_close_file() { #[test] fn test_file_io() { - with_test_drive(|rx| unsafe { + with_test_drive(|rx, _instance| unsafe { let hf = open_file("Z:\\test_file_io"); let mut buf = [0u8; 255]; let mut len = 0; @@ -1104,7 +1126,7 @@ fn test_file_io() { #[test] fn test_get_file_information() { - with_test_drive(|_rx| unsafe { + with_test_drive(|_rx, _instance| unsafe { let hf = open_file("Z:\\test_get_file_information"); let mut info = mem::zeroed(); assert_eq!(GetFileInformationByHandle(hf, &mut info), TRUE); @@ -1189,7 +1211,7 @@ fn check_dir_content(pattern: &str, file_name: &str) { #[test] fn test_find_files() { - with_test_drive(|rx| { + with_test_drive(|rx, _instance| { check_dir_content("Z:\\test_find_files\\*", "test_inner_file"); check_dir_content( "Z:\\test_find_files_with_pattern\\*", @@ -1204,7 +1226,7 @@ fn test_find_files() { #[test] fn test_set_file_attributes() { - with_test_drive(|rx| unsafe { + with_test_drive(|rx, _instance| unsafe { let path = convert_str("Z:\\test_set_file_attributes"); assert_eq!( SetFileAttributesW(path.as_ptr(), FILE_ATTRIBUTE_READONLY), @@ -1219,7 +1241,7 @@ fn test_set_file_attributes() { #[test] fn test_set_file_time() { - with_test_drive(|rx| unsafe { + with_test_drive(|rx, _instance| unsafe { let hf = open_file("Z:\\test_set_file_time"); let ctime = UNIX_EPOCH; let atime = UNIX_EPOCH + Duration::from_secs(1); @@ -1267,7 +1289,7 @@ fn test_set_file_time() { #[test] fn test_delete_file() { - with_test_drive(|rx| unsafe { + with_test_drive(|rx, _instance| unsafe { let path = convert_str("Z:\\test_delete_file"); assert_eq!(DeleteFileW(path.as_ptr()), TRUE); assert_eq!(rx.recv().unwrap(), HandlerSignal::DeleteFile(true)); @@ -1276,7 +1298,7 @@ fn test_delete_file() { #[test] fn test_delete_directory() { - with_test_drive(|rx| unsafe { + with_test_drive(|rx, _instance| unsafe { let path = convert_str("Z:\\test_delete_directory"); assert_eq!(RemoveDirectoryW(path.as_ptr()), TRUE); assert_eq!(rx.recv().unwrap(), HandlerSignal::DeleteDirectory(true)); @@ -1285,7 +1307,7 @@ fn test_delete_directory() { #[test] fn test_move_file() { - with_test_drive(|rx| unsafe { + with_test_drive(|rx, _instance| unsafe { let path = convert_str("Z:\\test_move_file"); let new_path = convert_str("Z:\\test_move_file_new"); assert_eq!( @@ -1301,7 +1323,7 @@ fn test_move_file() { #[test] fn test_set_end_of_file() { - with_test_drive(|rx| unsafe { + with_test_drive(|rx, _instance| unsafe { let hf = open_file("Z:\\test_set_end_of_file"); assert_eq!(SetFileValidData(hf, i64::MAX), TRUE); assert_eq!(rx.recv().unwrap(), HandlerSignal::SetEndOfFile(i64::MAX)); @@ -1311,7 +1333,7 @@ fn test_set_end_of_file() { #[test] fn test_set_allocation_size() { - with_test_drive(|rx| unsafe { + with_test_drive(|rx, _instance| unsafe { let hf = open_file("Z:\\test_set_allocation_size"); let dist_low = 42; let mut dist_high = 42; @@ -1328,7 +1350,7 @@ fn test_set_allocation_size() { #[test] fn test_lock_unlock_file() { - with_test_drive(|rx| unsafe { + with_test_drive(|rx, _instance| unsafe { let hf = open_file("Z:\\test_lock_unlock_file"); assert_eq!(LockFile(hf, 0, 0, 1, 0), TRUE); assert_eq!(rx.recv().unwrap(), HandlerSignal::LockFile(0, 1)); @@ -1340,7 +1362,7 @@ fn test_lock_unlock_file() { #[test] fn test_get_file_security() { - with_test_drive(|rx| unsafe { + with_test_drive(|rx, _instance| unsafe { let expected_desc = create_test_descriptor(); let path = convert_str("Z:\\test_get_file_security"); let mut desc_len = 0; @@ -1381,7 +1403,7 @@ fn test_get_file_security() { #[test] fn test_get_file_security_overflow() { - with_test_drive(|_rx| unsafe { + with_test_drive(|_rx, _instance| unsafe { let path = convert_str("Z:\\test_get_file_security_overflow"); let mut ret_len = 0; assert_eq!( @@ -1401,7 +1423,7 @@ fn test_get_file_security_overflow() { #[test] fn test_set_file_security() { - with_test_drive(|rx| unsafe { + with_test_drive(|rx, _instance| unsafe { let path = convert_str("Z:\\test_set_file_security"); let mut desc = create_test_descriptor(); let desc_ptr = desc.as_mut_ptr() as PSECURITY_DESCRIPTOR; @@ -1424,7 +1446,7 @@ fn test_set_file_security() { #[test] fn test_find_streams() { - with_test_drive(|_rx| unsafe { + with_test_drive(|_rx, _instance| unsafe { let path = convert_str("Z:\\test_find_streams"); let mut data = mem::zeroed::(); let hf = FindFirstStreamW( @@ -1448,7 +1470,7 @@ fn test_find_streams() { #[test] #[ignore] fn test_reset_timeout() { - with_test_drive(|_rx| unsafe { + with_test_drive(|_rx, _instance| unsafe { let path = convert_str("Z:\\test_reset_timeout"); let hf = CreateFileW( path.as_ptr(), @@ -1466,7 +1488,7 @@ fn test_reset_timeout() { #[test] fn test_open_requester_token() { - with_test_drive(|rx| unsafe { + with_test_drive(|rx, _instance| unsafe { let expected_info_buffer = get_current_user_info(); let hf = open_file("Z:\\test_open_requester_token"); assert_eq!(CloseHandle(hf), TRUE); @@ -1483,7 +1505,7 @@ fn test_open_requester_token() { #[test] fn test_operation_info() { - with_test_drive(|rx| unsafe { + with_test_drive(|rx, _instance| unsafe { let hf = open_file("Z:\\test_operation_info"); assert_eq!(CloseHandle(hf), TRUE); assert_eq!( @@ -1496,11 +1518,10 @@ fn test_operation_info() { synchronous_io: false, no_cache: false, write_to_eof: false, - thread_count: 4, + single_thread: false, mount_flags: MountFlags::CURRENT_SESSION | MountFlags::FILELOCK_USER_MODE - | MountFlags::ALT_STREAM - | MountFlags::ENABLE_NOTIFICATION_API, + | MountFlags::ALT_STREAM, mount_point: Some(convert_str("Z:\\")), unc_name: None, timeout: Duration::from_secs(15), @@ -1513,7 +1534,7 @@ fn test_operation_info() { #[test] fn test_output_ptr_null() { - with_test_drive(|_rx| unsafe { + with_test_drive(|_rx, _instance| unsafe { let path = convert_str("Z:\\"); assert_eq!( GetDiskFreeSpaceExW( @@ -1564,12 +1585,12 @@ extern "stdcall" fn failing_fill_data_stub(_data: *mut (), _info: PDOKAN_FILE_IN #[test] fn test_fill_data_error() { - let mut wrapper = fill_data_wrapper(fill_data_stub, ptr::null_mut()); + let mut wrapper = fill_data_wrapper(fill_data_stub, ptr::null_mut(), 0); assert_eq!( wrapper(&ToRawStructStub { should_fail: true }), Err(FillDataError::NameTooLong) ); - let mut wrapper = fill_data_wrapper(failing_fill_data_stub, ptr::null_mut()); + let mut wrapper = fill_data_wrapper(failing_fill_data_stub, ptr::null_mut(), 0); assert_eq!( wrapper(&ToRawStructStub { should_fail: false }), Err(FillDataError::BufferFull) @@ -1676,7 +1697,7 @@ impl Iterator for DirectoryChangeIterator { #[test] fn test_notify() { - with_test_drive(|_rx| { + with_test_drive(|_rx, instance| { let (tx, rx) = mpsc::channel(); let handle = thread::spawn(move || { let mut iter = DirectoryChangeIterator::new(convert_str("Z:\\")); @@ -1687,24 +1708,36 @@ fn test_notify() { } }); assert_eq!(rx.recv().unwrap(), None); - assert!(notify_create(convert_str("Z:\\test_notify_create"), false)); + assert!(notify_create( + instance, + convert_str("Z:\\test_notify_create"), + false + )); assert_eq!( rx.recv().unwrap(), Some((FILE_ACTION_ADDED, convert_str("test_notify_create"))) ); - assert!(notify_delete(convert_str("Z:\\test_notify_delete"), false)); + assert!(notify_delete( + instance, + convert_str("Z:\\test_notify_delete"), + false + )); assert_eq!( rx.recv().unwrap(), Some((FILE_ACTION_REMOVED, convert_str("test_notify_delete"))) ); - assert!(notify_update(convert_str("Z:\\test_notify_update"))); + assert!(notify_update( + instance, + convert_str("Z:\\test_notify_update") + )); assert_eq!( rx.recv().unwrap(), Some((FILE_ACTION_MODIFIED, convert_str("test_notify_update"))) ); - assert!(notify_xattr_update(convert_str( - "Z:\\test_notify_xattr_update" - ))); + assert!(notify_xattr_update( + instance, + convert_str("Z:\\test_notify_xattr_update") + )); assert_eq!( rx.recv().unwrap(), Some(( @@ -1713,6 +1746,7 @@ fn test_notify() { )) ); assert!(notify_rename( + instance, convert_str("Z:\\test_notify_rename_old"), convert_str("Z:\\test_notify_rename_new"), false,