diff --git a/Cargo.lock b/Cargo.lock index 7f07200..9915b79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,18 @@ version = 3 [[package]] name = "aho-corasick" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" dependencies = [ "memchr", ] [[package]] name = "ansi_term" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" dependencies = [ "winapi", ] @@ -31,17 +31,23 @@ dependencies = [ "winapi", ] +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "bitflags" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "cc" -version = "1.0.68" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" [[package]] name = "cfg-if" @@ -51,9 +57,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "2.33.3" +version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "ansi_term", "atty", @@ -64,12 +70,23 @@ dependencies = [ "vec_map", ] +[[package]] +name = "ctrlc" +version = "3.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d91974fbbe88ec1df0c24a4f00f99583667a7e2e6272b2b92d294d81e462173" +dependencies = [ + "nix", + "winapi", +] + [[package]] name = "dokan" -version = "0.3.0+dokan205" +version = "0.3.1+dokan206" dependencies = [ "bitflags", "clap", + "ctrlc", "dokan-sys", "lazy_static", "parking_lot", @@ -80,7 +97,7 @@ dependencies = [ [[package]] name = "dokan-sys" -version = "0.3.0+dokan205" +version = "0.3.1+dokan206" dependencies = [ "cc", "libc", @@ -89,18 +106,18 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "instant" -version = "0.1.9" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", ] @@ -113,30 +130,43 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.95" +version = "0.2.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36" +checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966" [[package]] name = "lock_api" -version = "0.4.4" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ + "autocfg", "scopeguard", ] [[package]] name = "memchr" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "nix" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if", + "libc", +] [[package]] name = "parking_lot" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", @@ -145,9 +175,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" dependencies = [ "cfg-if", "instant", @@ -159,18 +189,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.8" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.5.4" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" dependencies = [ "aho-corasick", "memchr", @@ -179,9 +209,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.25" +version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "scopeguard" @@ -191,9 +221,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "smallvec" -version = "1.6.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" [[package]] name = "strsim" @@ -212,9 +242,9 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "vec_map" diff --git a/README.md b/README.md index a7f6b6e..7a4f82d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This project allows you to easily use [Dokan](https://github.com/dokan-dev/dokan - [![crates.io](https://img.shields.io/crates/v/dokan-sys)](https://crates.io/crates/dokan-sys) `dokan-sys` provides raw bindings to the functions and structures provided by Dokan. -- [![crates.io](https://img.shields.io/crates/v/dokan)](https://crates.io/crates/dokan) `dokan` is built on top of dokan-sys and provides high-level, Rust-friendly wrappers for Dokan. +- [![crates.io](https://img.shields.io/crates/v/dokan)](https://crates.io/crates/dokan) `dokan` is built on top of `dokan-sys` and provides high-level, Rust-friendly wrappers for Dokan. Generally, it is recommended to use the `dokan` crate, which has the unsafe raw bindings wrapped and is easier to use. However, if you want to access the low-level interface provided by Dokan, `dokan-sys` can save you from writing the function and structure definitions yourself. @@ -14,11 +14,11 @@ Generally, it is recommended to use the `dokan` crate, which has the unsafe raw `dokan-sys`, which is also a dependency of `dokan`, requires the import library of the native Dokan library in order to link against it. -If the `DokanLibrary1_LibraryPath_{ARCH}` environment variable exists (`{ARCH}` can be `x86` or `x64` depending on the architecture of your target platform), `dokan-sys` will look for the import library in the directory specified by the aforementioned environment variable. These environment variables are automatically set by Dokan's installer since v1.0.0. +If the `DokanLibrary2_LibraryPath_{ARCH}` environment variable exists (`{ARCH}` can be `x86` or `x64` depending on the architecture of your target platform), `dokan-sys` will look for the import library in the directory specified by the aforementioned environment variable. These environment variables are automatically set by Dokan's installer since v1.0.0. Otherwise, `dokan-sys` will build the import library from bundled Dokan source code. The DLL file will be built as well and you can use the `DOKAN_DLL_OUTPUT_PATH` environment variable to have the build script copy it to the specified directory. -Note that the versions of the `dokan-sys` crate, the linked import library and the Dokan library loaded at runtime should be identical, or you may run into troubles. So please take care when using the `DokanLibrary1_LibraryPath_*` environment variables and [deploying your application](https://github.com/dokan-dev/dokany/wiki/How-to-package-your-application-with-Dokan#dokan-application-considerations). +Note that the versions of the `dokan-sys` crate, the linked import library and the Dokan library loaded at runtime should be identical, or you may run into troubles. So please take care when using the `DokanLibrary2_LibraryPath_*` environment variables and [deploying your application](https://github.com/dokan-dev/dokany/wiki/How-to-package-your-application-with-Dokan#dokan-application-considerations). # Usage diff --git a/appveyor.yml b/appveyor.yml index 0814a29..6b46bec 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,9 +1,9 @@ -os: Visual Studio 2019 branches: only: - master environment: + DOKAN_CONSOLE_DEBUG_LOG: 1 RUST_BACKTRACE: 1 AccessTokenDokanDoc: secure: Z5/daTZ6OJaDbslmhxSxxB6leKaqHTnTbISy153Y4cdJm/oTAbDHaofb9TqvKXV1 @@ -20,45 +20,66 @@ environment: TOOLCHAIN: msvc USE_INSTALLED_LIB: "true" - ARCH: x86_64 + BITS: "64" TOOLCHAIN: gnu - MSYSTEM: MINGW64 - ARCH: x86_64 + BITS: "64" TOOLCHAIN: gnu - MSYSTEM: MINGW64 USE_INSTALLED_LIB: "true" - ARCH: i686 + BITS: "32" TOOLCHAIN: gnu - MSYSTEM: MINGW32 - ARCH: i686 + BITS: "32" TOOLCHAIN: gnu - MSYSTEM: MINGW32 USE_INSTALLED_LIB: "true" -install: - - ps: | - Invoke-WebRequest https://github.com/dokan-dev/dokany/releases/download/v2.0.5.1000/DokanSetup.exe -OutFile "$Env:TEMP\DokanSetup.exe" - Start-Process "$Env:TEMP\DokanSetup.exe" -ArgumentList "/quiet /norestart" -Wait +os: Visual Studio 2022 +platform: x64 + +init: - ps: | - if ($Env:TOOLCHAIN -eq "gnu") { - cmd /c 'C:\msys64\usr\bin\bash.exe -l -c "pacman -Syu --noconfirm --noprogressbar" 2>&1 && taskkill /f /fi "MODULES eq msys-2.0.dll"' - cmd /c 'C:\msys64\usr\bin\bash.exe -l -c "pacman -Syu --needed --noconfirm --noprogressbar $MINGW_PACKAGE_PREFIX-toolchain" 2>&1 && taskkill /f /fi "MODULES eq msys-2.0.dll"' - $Env:PATH = "C:\msys64\$Env:MSYSTEM\bin;$Env:PATH" + function CheckLastExitCode($ExpectedExitCode = 0) { + if ($LastExitCode -ne $ExpectedExitCode) { + throw "execution failed with code '$LastExitCode' (expected '$ExpectedExitCode')" + } } - - ps: Invoke-WebRequest https://win.rustup.rs/x86_64 -OutFile "$Env:TEMP\rustup-init.exe" - - cmd: "\"%TEMP%\\rustup-init.exe\" -y --default-host %ARCH%-pc-windows-%TOOLCHAIN%" - - ps: $Env:PATH = "$Env:PATH;C:\Users\appveyor\.cargo\bin" - - ps: | + + function ExecuteExe { + Param( + [Parameter(Mandatory=$true)] + [String] + $Exe, + [Array] + [Parameter(ValueFromRemainingArguments=$true)] + $Arguments + ) + & $Exe @Arguments + CheckLastExitCode + } + +install: + - ps: &download-dokany | + Invoke-WebRequest https://github.com/dokan-dev/dokany/releases/download/v2.0.6.1000/DokanSetup.exe -OutFile "$Env:TEMP\DokanSetup.exe" + Start-Process "$Env:TEMP\DokanSetup.exe" -ArgumentList '/quiet /norestart' -Wait + if ($Env:USE_INSTALLED_LIB -eq $true) { - $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\" + $Env:DokanLibrary2_LibraryPath_x64 = 'C:\Program Files\Dokan\Dokan Library-2.0.6\lib\' + $Env:DokanLibrary2_LibraryPath_x86 = 'C:\Program Files\Dokan\Dokan Library-2.0.6\x86\lib\' } else { - rm C:\Windows\System32\dokan2.dll - rm C:\Windows\SysWOW64\dokan2.dll + Remove-Item 'C:\Windows\System32\dokan2.dll' + Remove-Item 'C:\Windows\SysWOW64\dokan2.dll' } + - ps: | + Invoke-WebRequest https://win.rustup.rs/x86_64 -OutFile "$Env:TEMP\rustup-init.exe" + & "$Env:TEMP\rustup-init.exe" -y --default-host "$Env:ARCH-pc-windows-$Env:TOOLCHAIN" + CheckLastExitCode + $Env:PATH = "$Env:PATH;C:\Users\appveyor\.cargo\bin" + before_build: - - ps: $version = (git describe --tags) - - ps: Update-AppveyorBuild -Version $version + - ps: $Version = (git describe --tags) + - ps: Update-AppveyorBuild -Version $Version - cmd: git submodule update --init - ps: | if ($Env:USE_INSTALLED_LIB -ne $true) { @@ -79,10 +100,9 @@ deploy_script: if ($Env:APPVEYOR_REPO_TAG -ne $true -or $Env:UPLOAD_DOC -ne $true) { return; } - git config --global user.email appveyor@appveyor.org - git config --global user.name appveyor - cmd /c "git clone https://lirynastark:$($Env:AccessTokenDokanDoc)@github.com/dokan-dev/dokan-rust-doc.git doc 2>&1" - if ($LASTEXITCODE -ne 0) { $host.SetShouldExit($LASTEXITCODE) } + ExecuteExe -- git config --global user.email appveyor@appveyor.org + ExecuteExe -- git config --global user.name appveyor + ExecuteExe -- git clone https://lirynastark:$($Env:AccessTokenDokanDoc)@github.com/dokan-dev/dokan-rust-doc.git doc 2>&1 if (Test-Path doc\html) { Remove-Item -Recurse -Force doc\html\* } else { @@ -92,11 +112,36 @@ deploy_script: cd doc if ($(git status --porcelain)) { Write-Host "Updating documentation..." -ForegroundColor Green - cmd /c "git add -A 2>&1" - cmd /c "git commit -m `"Automatically update documentation for $version`" 2>&1" - cmd /c "git push 2>&1" - if ($LASTEXITCODE -ne 0) { $host.SetShouldExit($LASTEXITCODE) } + ExecuteExe -- git add -A 2>&1 + ExecuteExe -- git commit -m "Automatically update documentation for $version" 2>&1 + ExecuteExe -- git push 2>&1 Write-Host -ForegroundColor Green "Documentation updated!" } else { Write-Host -ForegroundColor Green "No documentation changes detected." } + +for: + - matrix: + only: + - TOOLCHAIN: gnu + + install: + - ps: *download-dokany + - ps: | + function ExecuteBash($SCRIPT) { + & "C:\msys64\mingw$Env:BITS.exe" -c $SCRIPT + } + + ExecuteBash @" + pacman -Syu --noconfirm --noprogressbar + pacman -Syu --needed --noconfirm --noprogressbar + pacman -S mingw-w64-$ARCH-rust + "@ + + build_script: + - ps: ExecuteBash 'cargo build --release --workspace --all-targets' + - ps: ExecuteBash 'cargo doc --release --workspace' + + test_script: + - ps: ExecuteBash 'cargo test --release --workspace' + - ps: ExecuteBash 'cargo test --release --workspace -- --ignored' diff --git a/dokan-sys/CHANGELOG.md b/dokan-sys/CHANGELOG.md new file mode 100644 index 0000000..c3b3e5b --- /dev/null +++ b/dokan-sys/CHANGELOG.md @@ -0,0 +1,22 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.3.1] - 2022-10-04 + +### Added + +- Various `FILE_*` constants. +- `VOLUME_SECURITY_DESCRIPTOR_MAX_SIZE` + +### Changed + +- Upgrade to **Dokan 2.0.6**. + +[unreleased]: https://github.com/dokan-dev/dokan-rust/compare/dokan-sys@v0.3.1...HEAD +[0.3.1]: https://github.com/dokan-dev/dokan-rust/releases/tag/dokan-sys@v0.3.1 diff --git a/dokan-sys/Cargo.toml b/dokan-sys/Cargo.toml index c7fedfe..3a533b7 100644 --- a/dokan-sys/Cargo.toml +++ b/dokan-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dokan-sys" -version = "0.3.0+dokan205" +version = "0.3.1+dokan206" authors = ["DDoSolitary "] description = "Raw FFI bindings for Dokan (user mode file system library for Windows)" homepage = "https://dokan-dev.github.io" @@ -10,7 +10,7 @@ keywords = ["ffi", "dokan", "bindings", "driver", "filesystem"] categories = ["external-ffi-bindings"] readme = "README.md" license = "MIT" -edition = "2018" +edition = "2021" links = "dokan" build = "build.rs" diff --git a/dokan-sys/build.rs b/dokan-sys/build.rs index ef8ed00..4fc85d9 100644 --- a/dokan-sys/build.rs +++ b/dokan-sys/build.rs @@ -1,8 +1,9 @@ extern crate cc; -use std::env; -use std::fs; -use std::process::{Command, Stdio}; +use std::{ + env, fs, + process::{Command, Stdio}, +}; use cc::{Build, Tool}; diff --git a/dokan-sys/src/dokany b/dokan-sys/src/dokany index 1dc492a..5464da7 160000 --- a/dokan-sys/src/dokany +++ b/dokan-sys/src/dokany @@ -1 +1 @@ -Subproject commit 1dc492a3219525cd13ef21fe9bd77788676193b8 +Subproject commit 5464da7187cf1a3fb05977df28c1da98087051c5 diff --git a/dokan-sys/src/lib.rs b/dokan-sys/src/lib.rs index c6a532d..fa0bd26 100644 --- a/dokan-sys/src/lib.rs +++ b/dokan-sys/src/lib.rs @@ -7,24 +7,29 @@ //! //! For more information, refer to corresponding items in [Dokan's documentation]. //! +//! Consider using the high-level [`dokan`] crate. +//! //! [Dokan]: https://github.com/dokan-dev/dokany //! [Dokan's documentation]: https://dokan-dev.github.io/dokany-doc/html/ - -extern crate libc; -extern crate winapi; +//! [`dokan`]: https://crates.io/crates/dokan 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, PVOID, SCHAR, UCHAR, - ULONG, UNICODE_STRING, USHORT, WCHAR, -}; -use winapi::um::fileapi::LPBY_HANDLE_FILE_INFORMATION; -use winapi::um::minwinbase::PWIN32_FIND_DATAW; -use winapi::um::winnt::{ACCESS_MASK, PSECURITY_DESCRIPTOR, PSECURITY_INFORMATION}; - use win32::PWIN32_FIND_STREAM_DATA; +use winapi::{ + shared::{ + basetsd::ULONG64, + minwindef::{BOOL, DWORD, FILETIME, LPCVOID, LPDWORD, LPVOID, MAX_PATH}, + ntdef::{ + BOOLEAN, HANDLE, LONGLONG, LPCWSTR, LPWSTR, NTSTATUS, PULONG, PULONGLONG, PVOID, SCHAR, + UCHAR, ULONG, UNICODE_STRING, USHORT, WCHAR, + }, + }, + um::{ + fileapi::LPBY_HANDLE_FILE_INFORMATION, + minwinbase::PWIN32_FIND_DATAW, + winnt::{ACCESS_MASK, PSECURITY_DESCRIPTOR, PSECURITY_INFORMATION}, + }, +}; pub mod win32; @@ -47,6 +52,8 @@ 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; +pub const VOLUME_SECURITY_DESCRIPTOR_MAX_SIZE: usize = 1024 * 16; + #[repr(C)] #[derive(Debug)] pub struct DOKAN_OPTIONS { @@ -60,7 +67,7 @@ pub struct DOKAN_OPTIONS { pub AllocationUnitSize: ULONG, pub SectorSize: ULONG, pub VolumeSecurityDescriptorLength: ULONG, - pub VolumeSecurityDescriptor: [SCHAR; 1024 * 16], + pub VolumeSecurityDescriptor: [SCHAR; VOLUME_SECURITY_DESCRIPTOR_MAX_SIZE], } pub type PDOKAN_OPTIONS = *mut DOKAN_OPTIONS; diff --git a/dokan-sys/src/win32.rs b/dokan-sys/src/win32.rs index 3bc5e6a..8172942 100644 --- a/dokan-sys/src/win32.rs +++ b/dokan-sys/src/win32.rs @@ -1,5 +1,7 @@ -use winapi::shared::minwindef::MAX_PATH; -use winapi::shared::ntdef::{LARGE_INTEGER, WCHAR}; +use winapi::shared::{ + minwindef::MAX_PATH, + ntdef::{LARGE_INTEGER, WCHAR}, +}; #[repr(C)] pub struct WIN32_FIND_STREAM_DATA { @@ -8,3 +10,56 @@ pub struct WIN32_FIND_STREAM_DATA { } pub type PWIN32_FIND_STREAM_DATA = *mut WIN32_FIND_STREAM_DATA; + +// from ntifs.h +pub const FILE_DEVICE_DISK_FILE_SYSTEM: u32 = 0x00000008; +pub const FILE_DEVICE_NETWORK_FILE_SYSTEM: u32 = 0x00000014; + +// from wdm.h +pub const FILE_SUPERSEDE: u32 = 0x00000000; +pub const FILE_OPEN: u32 = 0x00000001; +pub const FILE_CREATE: u32 = 0x00000002; +pub const FILE_OPEN_IF: u32 = 0x00000003; +pub const FILE_OVERWRITE: u32 = 0x00000004; +pub const FILE_OVERWRITE_IF: u32 = 0x00000005; +pub const FILE_MAXIMUM_DISPOSITION: u32 = 0x00000005; + +pub const FILE_DIRECTORY_FILE: u32 = 0x00000001; +pub const FILE_WRITE_THROUGH: u32 = 0x00000002; +pub const FILE_SEQUENTIAL_ONLY: u32 = 0x00000004; +pub const FILE_NO_INTERMEDIATE_BUFFERING: u32 = 0x00000008; + +pub const FILE_SYNCHRONOUS_IO_ALERT: u32 = 0x00000010; +pub const FILE_SYNCHRONOUS_IO_NONALERT: u32 = 0x00000020; +pub const FILE_NON_DIRECTORY_FILE: u32 = 0x00000040; +pub const FILE_CREATE_TREE_CONNECTION: u32 = 0x00000080; + +pub const FILE_COMPLETE_IF_OPLOCKED: u32 = 0x00000100; +pub const FILE_NO_EA_KNOWLEDGE: u32 = 0x00000200; +pub const FILE_OPEN_REMOTE_INSTANCE: u32 = 0x00000400; +pub const FILE_RANDOM_ACCESS: u32 = 0x00000800; + +pub const FILE_DELETE_ON_CLOSE: u32 = 0x00001000; +pub const FILE_OPEN_BY_FILE_ID: u32 = 0x00002000; +pub const FILE_OPEN_FOR_BACKUP_INTENT: u32 = 0x00004000; +pub const FILE_NO_COMPRESSION: u32 = 0x00008000; + +pub const FILE_OPEN_REQUIRING_OPLOCK: u32 = 0x00010000; +pub const FILE_DISALLOW_EXCLUSIVE: u32 = 0x00020000; +pub const FILE_SESSION_AWARE: u32 = 0x00040000; +pub const FILE_RESERVE_OPFILTER: u32 = 0x00100000; +pub const FILE_OPEN_REPARSE_POINT: u32 = 0x00200000; +pub const FILE_OPEN_NO_RECALL: u32 = 0x00400000; +pub const FILE_OPEN_FOR_FREE_SPACE_QUERY: u32 = 0x00800000; + +pub const FILE_VALID_OPTION_FLAGS: u32 = 0x00ffffff; + +pub const FILE_SUPERSEDED: u32 = 0x00000000; +pub const FILE_OPENED: u32 = 0x00000001; +pub const FILE_CREATED: u32 = 0x00000002; +pub const FILE_OVERWRITTEN: u32 = 0x00000003; +pub const FILE_EXISTS: u32 = 0x00000004; +pub const FILE_DOES_NOT_EXIST: u32 = 0x00000005; + +pub const FILE_WRITE_TO_END_OF_FILE: u32 = 0xffffffff; +pub const FILE_USE_FILE_POINTER_POSITION: u32 = 0xfffffffe; diff --git a/dokan/CHANGELOG.md b/dokan/CHANGELOG.md new file mode 100644 index 0000000..5206469 --- /dev/null +++ b/dokan/CHANGELOG.md @@ -0,0 +1,34 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.3.1] - 2022-10-04 + +### Added + +- `FileSystemHandle` to send a `DOKAN_HANDLE` across threads. +- `map_win32_error_to_ntstatus` +- `win32_ensure` +- In `memfs` example: add status messages and show how another thread can unmount the file system. + +### Changed + +- Upgrade to **Dokan 2.0.6** through `dokan-sys`. +- Split the code into multiple files. +- Replace `Drive` builder by `FileSystemMounter` and `MountOptions`. +- Operations errors are simply `NTSTATUS`. + To return errors from `GetLastError`, use `win32_ensure` or `map_win32_error_to_ntstatus`. +- Access mount point list through an iterator. + +### Fixed + +- Access to dangling pointer caused panic when the file system handle was used. + It's the reason for `FileSystemMounter`, which keeps needed variables onto the stack. + +[unreleased]: https://github.com/dokan-dev/dokan-rust/compare/dokan@v0.3.1...HEAD +[0.3.1]: https://github.com/dokan-dev/dokan-rust/releases/tag/dokan@v0.3.1 diff --git a/dokan/Cargo.toml b/dokan/Cargo.toml index 4b098b8..59400c9 100644 --- a/dokan/Cargo.toml +++ b/dokan/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dokan" -version = "0.3.0+dokan205" +version = "0.3.1+dokan206" authors = ["DDoSolitary "] description = "Rust-friendly wrapper for Dokan (user mode file system library for Windows)" homepage = "https://dokan-dev.github.io" @@ -10,19 +10,20 @@ keywords = ["dokan", "bindings", "driver", "filesystem"] categories = ["external-ffi-bindings"] readme = "README.md" license = "MIT" -edition = "2018" +edition = "2021" [badges] appveyor = { repository = "Liryna/dokan-rust" } [dependencies] -dokan-sys = { version = "= 0.3.0", path = "../dokan-sys" } bitflags = "1.2.1" +dokan-sys = { version = "0.3.1", path = "../dokan-sys" } 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"] } [dev-dependencies] clap = "2.33.3" +ctrlc = "3.2.3" lazy_static = "1.4.0" parking_lot = "0.11.1" regex = "1.4.3" diff --git a/dokan/examples/memfs/err_utils.rs b/dokan/examples/memfs/err_utils.rs deleted file mode 100644 index 8257dbe..0000000 --- a/dokan/examples/memfs/err_utils.rs +++ /dev/null @@ -1,19 +0,0 @@ -use dokan::OperationError; -use winapi::shared::ntdef; -use winapi::um::errhandlingapi; - -pub fn nt_err(stat: ntdef::NTSTATUS) -> OperationError { - OperationError::NtStatus(stat) -} - -pub fn nt_res(stat: ntdef::NTSTATUS) -> Result { - Err(nt_err(stat)) -} - -fn win32_last_err() -> OperationError { - unsafe { OperationError::Win32(errhandlingapi::GetLastError()) } -} - -pub fn win32_last_res() -> Result { - Err(win32_last_err()) -} diff --git a/dokan/examples/memfs/main.rs b/dokan/examples/memfs/main.rs index f64f3ed..7b3b3ad 100644 --- a/dokan/examples/memfs/main.rs +++ b/dokan/examples/memfs/main.rs @@ -1,29 +1,36 @@ -extern crate clap; -extern crate dokan; -extern crate widestring; -extern crate winapi; - -use std::borrow::Borrow; -use std::collections::HashMap; -use std::hash::{Hash, Hasher}; -use std::os::windows::io::AsRawHandle; -use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; -use std::sync::{Arc, Mutex, RwLock, Weak}; -use std::time::SystemTime; +mod path; +mod security; + +use std::{ + borrow::Borrow, + collections::HashMap, + hash::{Hash, Hasher}, + os::windows::io::AsRawHandle, + sync::{ + atomic::{AtomicBool, AtomicU64, Ordering}, + Arc, Mutex, RwLock, Weak, + }, + time::SystemTime, +}; use clap::{App, Arg}; -use dokan::*; +use dokan::{ + init, shutdown, unmount, CreateFileInfo, DiskSpaceInfo, FileInfo, FileSystemHandler, + FileSystemMounter, FileTimeOperation, FillDataError, FillDataResult, FindData, FindStreamData, + MountFlags, MountOptions, OperationInfo, OperationResult, VolumeInfo, IO_SECURITY_CONTEXT, +}; +use dokan_sys::win32::{ + FILE_CREATE, FILE_DELETE_ON_CLOSE, FILE_DIRECTORY_FILE, FILE_MAXIMUM_DISPOSITION, + FILE_NON_DIRECTORY_FILE, FILE_OPEN, FILE_OPEN_IF, FILE_OVERWRITE, FILE_OVERWRITE_IF, + FILE_SUPERSEDE, +}; use widestring::{U16CStr, U16CString, U16Str, U16String}; -use winapi::shared::{ntdef, ntstatus::*}; -use winapi::um::winnt; - -mod err_utils; -mod path; -mod security; +use winapi::{ + shared::{ntdef, ntstatus::*}, + um::winnt, +}; -use err_utils::*; -use path::FullName; -use security::SecurityDescriptor; +use crate::{path::FullName, security::SecurityDescriptor}; #[derive(Debug)] struct AltStream { @@ -415,9 +422,9 @@ impl MemFsHandler { parent: &Arc, children: &mut HashMap, is_dir: bool, - ) -> Result, OperationError> { + ) -> OperationResult> { if attrs & winnt::FILE_ATTRIBUTE_READONLY > 0 && delete_on_close { - return nt_res(STATUS_CANNOT_DELETE); + return Err(STATUS_CANNOT_DELETE); } let mut stat = Stat::new( self.next_id(), @@ -462,44 +469,32 @@ impl MemFsHandler { } } -fn check_fill_data_error(res: Result<(), FillDataError>) -> Result<(), OperationError> { - match res { - Ok(()) => Ok(()), - Err(FillDataError::BufferFull) => nt_res(STATUS_INTERNAL_ERROR), +fn ignore_name_too_long(err: FillDataError) -> OperationResult<()> { + match err { + // Normal behavior. + FillDataError::BufferFull => Err(STATUS_BUFFER_OVERFLOW), // Silently ignore this error because 1) file names passed to create_file should have been checked // by Windows. 2) We don't want an error on a single file to make the whole directory unreadable. - Err(FillDataError::NameTooLong) => Ok(()), + FillDataError::NameTooLong => Ok(()), } } -const FILE_SUPERSEDE: u32 = 0; -const FILE_OPEN: u32 = 1; -const FILE_CREATE: u32 = 2; -const FILE_OPEN_IF: u32 = 3; -const FILE_OVERWRITE: u32 = 4; -const FILE_OVERWRITE_IF: u32 = 5; -const FILE_MAXIMUM_DISPOSITION: u32 = 5; - -const FILE_DIRECTORY_FILE: u32 = 0x00000001; -const FILE_NON_DIRECTORY_FILE: u32 = 0x00000040; -const FILE_DELETE_ON_CLOSE: u32 = 0x00001000; - -impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { +impl<'c, 'h: 'c> FileSystemHandler<'c, 'h> for MemFsHandler { type Context = EntryHandle; fn create_file( - &'b self, + &'h self, file_name: &U16CStr, - security_context: &DOKAN_IO_SECURITY_CONTEXT, + security_context: &IO_SECURITY_CONTEXT, desired_access: winnt::ACCESS_MASK, file_attributes: u32, _share_access: u32, create_disposition: u32, create_options: u32, - info: &mut OperationInfo<'a, 'b, Self>, - ) -> Result, OperationError> { + info: &mut OperationInfo<'c, 'h, Self>, + ) -> OperationResult> { if create_disposition > FILE_MAXIMUM_DISPOSITION { - return nt_res(STATUS_INVALID_PARAMETER); + return Err(STATUS_INVALID_PARAMETER); } let delete_on_close = create_options & FILE_DELETE_ON_CLOSE > 0; let path_info = path::split_path(&self.root, file_name)?; @@ -516,13 +511,13 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { && (desired_access & winnt::FILE_WRITE_DATA > 0 || desired_access & winnt::FILE_APPEND_DATA > 0) { - return nt_res(STATUS_ACCESS_DENIED); + return Err(STATUS_ACCESS_DENIED); } if stat.delete_pending { - return nt_res(STATUS_DELETE_PENDING); + return Err(STATUS_DELETE_PENDING); } if is_readonly && delete_on_close { - return nt_res(STATUS_CANNOT_DELETE); + return Err(STATUS_CANNOT_DELETE); } std::mem::drop(stat); let ret = if let Some(stream_info) = &name.stream_info { @@ -535,18 +530,18 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { stat.alt_streams.get(stream_name).map(|s| Arc::clone(s)) { if stream.read().unwrap().delete_pending { - return nt_res(STATUS_DELETE_PENDING); + return Err(STATUS_DELETE_PENDING); } match create_disposition { FILE_SUPERSEDE | FILE_OVERWRITE | FILE_OVERWRITE_IF => { if create_disposition != FILE_SUPERSEDE && is_readonly { - return nt_res(STATUS_ACCESS_DENIED); + return Err(STATUS_ACCESS_DENIED); } stat.attrs.value |= winnt::FILE_ATTRIBUTE_ARCHIVE; stat.update_mtime(SystemTime::now()); stream.write().unwrap().data.clear(); } - FILE_CREATE => return nt_res(STATUS_OBJECT_NAME_COLLISION), + FILE_CREATE => return Err(STATUS_OBJECT_NAME_COLLISION), _ => (), } Some((stream, false)) @@ -554,10 +549,10 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { if create_disposition == FILE_OPEN || create_disposition == FILE_OVERWRITE { - return nt_res(STATUS_OBJECT_NAME_NOT_FOUND); + return Err(STATUS_OBJECT_NAME_NOT_FOUND); } if is_readonly { - return nt_res(STATUS_ACCESS_DENIED); + return Err(STATUS_ACCESS_DENIED); } let stream = Arc::new(RwLock::new(AltStream::new())); stat.update_atime(SystemTime::now()); @@ -581,14 +576,14 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { match entry { Entry::File(file) => { if create_options & FILE_DIRECTORY_FILE > 0 { - return nt_res(STATUS_NOT_A_DIRECTORY); + return Err(STATUS_NOT_A_DIRECTORY); } match create_disposition { FILE_SUPERSEDE | FILE_OVERWRITE | FILE_OVERWRITE_IF => { if create_disposition != FILE_SUPERSEDE && is_readonly || is_hidden_system { - return nt_res(STATUS_ACCESS_DENIED); + return Err(STATUS_ACCESS_DENIED); } file.data.write().unwrap().clear(); let mut stat = file.stat.write().unwrap(); @@ -597,12 +592,12 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { ); stat.update_mtime(SystemTime::now()); } - FILE_CREATE => return nt_res(STATUS_OBJECT_NAME_COLLISION), + FILE_CREATE => return Err(STATUS_OBJECT_NAME_COLLISION), _ => (), } Ok(CreateFileInfo { context: EntryHandle::new( - Entry::File(Arc::clone(file)), + Entry::File(Arc::clone(&file)), None, delete_on_close, ), @@ -612,26 +607,26 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { } Entry::Directory(dir) => { if create_options & FILE_NON_DIRECTORY_FILE > 0 { - return nt_res(STATUS_FILE_IS_A_DIRECTORY); + return Err(STATUS_FILE_IS_A_DIRECTORY); } match create_disposition { FILE_OPEN | FILE_OPEN_IF => Ok(CreateFileInfo { context: EntryHandle::new( - Entry::Directory(Arc::clone(dir)), + Entry::Directory(Arc::clone(&dir)), None, delete_on_close, ), is_dir: true, new_file_created: false, }), - FILE_CREATE => nt_res(STATUS_OBJECT_NAME_COLLISION), - _ => nt_res(STATUS_INVALID_PARAMETER), + FILE_CREATE => Err(STATUS_OBJECT_NAME_COLLISION), + _ => Err(STATUS_INVALID_PARAMETER), } } } } else { if parent.stat.read().unwrap().delete_pending { - return nt_res(STATUS_DELETE_PENDING); + return Err(STATUS_DELETE_PENDING); } let token = info.requester_token().unwrap(); if create_options & FILE_DIRECTORY_FILE > 0 { @@ -646,12 +641,12 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { &mut children, true, ), - FILE_OPEN => nt_res(STATUS_OBJECT_NAME_NOT_FOUND), - _ => nt_res(STATUS_INVALID_PARAMETER), + FILE_OPEN => Err(STATUS_OBJECT_NAME_NOT_FOUND), + _ => Err(STATUS_INVALID_PARAMETER), } } else { if create_disposition == FILE_OPEN || create_disposition == FILE_OVERWRITE { - nt_res(STATUS_OBJECT_NAME_NOT_FOUND) + Err(STATUS_OBJECT_NAME_NOT_FOUND) } else { self.create_new( &name, @@ -669,7 +664,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { } else { if create_disposition == FILE_OPEN || create_disposition == FILE_OPEN_IF { if create_options & FILE_NON_DIRECTORY_FILE > 0 { - nt_res(STATUS_FILE_IS_A_DIRECTORY) + Err(STATUS_FILE_IS_A_DIRECTORY) } else { Ok(CreateFileInfo { context: EntryHandle::new( @@ -682,16 +677,16 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { }) } } else { - nt_res(STATUS_INVALID_PARAMETER) + Err(STATUS_INVALID_PARAMETER) } } } fn close_file( - &'b self, + &'h self, _file_name: &U16CStr, - _info: &OperationInfo<'a, 'b, Self>, - context: &'a Self::Context, + _info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, ) { let mut stat = context.entry.stat().write().unwrap(); if let Some(mtime) = context.mtime_delayed.lock().unwrap().clone() { @@ -707,13 +702,13 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { } fn read_file( - &'b self, + &'h self, _file_name: &U16CStr, offset: i64, buffer: &mut [u8], - _info: &OperationInfo<'a, 'b, Self>, - context: &'a Self::Context, - ) -> Result { + _info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult { let mut do_read = |data: &Vec<_>| { let offset = offset as usize; let len = std::cmp::min(buffer.len(), data.len() - offset); @@ -726,18 +721,18 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { } else if let Entry::File(file) = &context.entry { Ok(do_read(&file.data.read().unwrap())) } else { - nt_res(STATUS_INVALID_DEVICE_REQUEST) + Err(STATUS_INVALID_DEVICE_REQUEST) } } fn write_file( - &'b self, + &'h self, _file_name: &U16CStr, offset: i64, buffer: &[u8], - info: &OperationInfo<'a, 'b, Self>, - context: &'a Self::Context, - ) -> Result { + info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult { let do_write = |data: &mut Vec<_>| { let offset = if info.write_to_eof() { data.len() @@ -757,7 +752,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { } else if let Entry::File(file) = &context.entry { Ok(do_write(&mut file.data.write().unwrap())) } else { - nt_res(STATUS_ACCESS_DENIED) + Err(STATUS_ACCESS_DENIED) }; if ret.is_ok() { context.entry.stat().write().unwrap().attrs.value |= winnt::FILE_ATTRIBUTE_ARCHIVE; @@ -773,20 +768,20 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { } fn flush_file_buffers( - &'b self, + &'h self, _file_name: &U16CStr, - _info: &OperationInfo<'a, 'b, Self>, - _context: &'a Self::Context, - ) -> Result<(), OperationError> { + _info: &OperationInfo<'c, 'h, Self>, + _context: &'c Self::Context, + ) -> OperationResult<()> { Ok(()) } fn get_file_information( - &'b self, + &'h self, _file_name: &U16CStr, - _info: &OperationInfo<'a, 'b, Self>, - context: &'a Self::Context, - ) -> Result { + _info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult { let stat = context.entry.stat().read().unwrap(); let alt_stream = context.alt_stream.read().unwrap(); Ok(FileInfo { @@ -808,20 +803,20 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { } fn find_files( - &'b self, + &'h self, _file_name: &U16CStr, - mut fill_find_data: impl FnMut(&FindData) -> Result<(), FillDataError>, - _info: &OperationInfo<'a, 'b, Self>, - context: &'a Self::Context, - ) -> Result<(), OperationError> { + mut fill_find_data: impl FnMut(&FindData) -> FillDataResult, + _info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult<()> { if context.alt_stream.read().unwrap().is_some() { - return nt_res(STATUS_INVALID_DEVICE_REQUEST); + return Err(STATUS_INVALID_DEVICE_REQUEST); } if let Entry::Directory(dir) = &context.entry { let children = dir.children.read().unwrap(); for (k, v) in children.iter() { let stat = v.stat().read().unwrap(); - let res = fill_find_data(&FindData { + fill_find_data(&FindData { attributes: stat.attrs.get_output_attrs(v.is_dir()), creation_time: stat.ctime, last_access_time: stat.atime, @@ -831,22 +826,22 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { Entry::Directory(_) => 0, }, file_name: U16CString::from_ustr(&k.0).unwrap(), - }); - check_fill_data_error(res)?; + }) + .or_else(ignore_name_too_long)?; } Ok(()) } else { - nt_res(STATUS_INVALID_DEVICE_REQUEST) + Err(STATUS_INVALID_DEVICE_REQUEST) } } fn set_file_attributes( - &'b self, + &'h self, _file_name: &U16CStr, file_attributes: u32, - _info: &OperationInfo<'a, 'b, Self>, - context: &'a Self::Context, - ) -> Result<(), OperationError> { + _info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult<()> { let mut stat = context.entry.stat().write().unwrap(); stat.attrs = Attributes::new(file_attributes); context.update_atime(&mut stat, SystemTime::now()); @@ -854,26 +849,27 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { } fn set_file_time( - &'b self, + &'h self, _file_name: &U16CStr, - creation_time: FileTimeInfo, - last_access_time: FileTimeInfo, - last_write_time: FileTimeInfo, - _info: &OperationInfo<'a, 'b, Self>, - context: &'a Self::Context, - ) -> Result<(), OperationError> { + creation_time: FileTimeOperation, + last_access_time: FileTimeOperation, + last_write_time: FileTimeOperation, + _info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult<()> { let mut stat = context.entry.stat().write().unwrap(); - let process_time_info = - |time_info: &FileTimeInfo, time: &mut SystemTime, flag: &AtomicBool| match time_info { - FileTimeInfo::SetTime(new_time) => { - if flag.load(Ordering::Relaxed) { - *time = *new_time - } + let process_time_info = |time_info: &FileTimeOperation, + time: &mut SystemTime, + flag: &AtomicBool| match time_info { + FileTimeOperation::SetTime(new_time) => { + if flag.load(Ordering::Relaxed) { + *time = *new_time } - FileTimeInfo::DisableUpdate => flag.store(false, Ordering::Relaxed), - FileTimeInfo::ResumeUpdate => flag.store(true, Ordering::Relaxed), - FileTimeInfo::DontChange => (), - }; + } + FileTimeOperation::DisableUpdate => flag.store(false, Ordering::Relaxed), + FileTimeOperation::ResumeUpdate => flag.store(true, Ordering::Relaxed), + FileTimeOperation::DontChange => (), + }; process_time_info(&creation_time, &mut stat.ctime, &context.ctime_enabled); process_time_info(&last_write_time, &mut stat.mtime, &context.mtime_enabled); process_time_info(&last_access_time, &mut stat.atime, &context.atime_enabled); @@ -881,13 +877,13 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { } fn delete_file( - &'b self, + &'h self, _file_name: &U16CStr, - info: &OperationInfo<'a, 'b, Self>, - context: &'a Self::Context, - ) -> Result<(), OperationError> { + info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult<()> { if context.entry.stat().read().unwrap().attrs.value & winnt::FILE_ATTRIBUTE_READONLY > 0 { - return nt_res(STATUS_CANNOT_DELETE); + return Err(STATUS_CANNOT_DELETE); } let alt_stream = context.alt_stream.read().unwrap(); if let Some(stream) = alt_stream.as_ref() { @@ -899,13 +895,13 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { } fn delete_directory( - &'b self, + &'h self, _file_name: &U16CStr, - info: &OperationInfo<'a, 'b, Self>, - context: &'a Self::Context, - ) -> Result<(), OperationError> { + info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult<()> { if context.alt_stream.read().unwrap().is_some() { - return nt_res(STATUS_INVALID_DEVICE_REQUEST); + return Err(STATUS_INVALID_DEVICE_REQUEST); } if let Entry::Directory(dir) = &context.entry { // Lock children first to avoid race conditions. @@ -913,32 +909,32 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { let mut stat = dir.stat.write().unwrap(); if stat.parent.upgrade().is_none() { // Root directory can't be deleted. - return nt_res(STATUS_ACCESS_DENIED); + return Err(STATUS_ACCESS_DENIED); } if info.delete_on_close() && !children.is_empty() { - nt_res(STATUS_DIRECTORY_NOT_EMPTY) + Err(STATUS_DIRECTORY_NOT_EMPTY) } else { stat.delete_pending = info.delete_on_close(); Ok(()) } } else { - nt_res(STATUS_INVALID_DEVICE_REQUEST) + Err(STATUS_INVALID_DEVICE_REQUEST) } } fn move_file( - &'b self, + &'h self, file_name: &U16CStr, new_file_name: &U16CStr, replace_if_existing: bool, - _info: &OperationInfo<'a, 'b, Self>, - context: &'a Self::Context, - ) -> Result<(), OperationError> { + _info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult<()> { let src_path = file_name.as_slice(); let offset = src_path .iter() .rposition(|x| *x == '\\' as u16) - .ok_or(nt_err(STATUS_INVALID_PARAMETER))?; + .ok_or(STATUS_INVALID_PARAMETER)?; let src_name = U16Str::from_slice(&src_path[offset + 1..]); let src_parent = context .entry @@ -947,7 +943,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { .unwrap() .parent .upgrade() - .ok_or(nt_err(STATUS_INVALID_DEVICE_REQUEST))?; + .ok_or(STATUS_INVALID_DEVICE_REQUEST)?; if new_file_name.as_slice().first() == Some(&(':' as u16)) { let src_stream_info = FullName::new(src_name)?.stream_info; let dst_stream_info = @@ -972,9 +968,9 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { { Ok(()) } else if !replace_if_existing { - nt_res(STATUS_OBJECT_NAME_COLLISION) + Err(STATUS_OBJECT_NAME_COLLISION) } else if stream.read().unwrap().handle_count > 0 { - nt_res(STATUS_ACCESS_DENIED) + Err(STATUS_ACCESS_DENIED) } else { streams.remove(name_ref).unwrap(); Ok(()) @@ -987,7 +983,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { match (src_is_default, dst_is_default) { (true, true) => { if context.entry.is_dir() { - return nt_res(STATUS_OBJECT_NAME_INVALID); + return Err(STATUS_OBJECT_NAME_INVALID); } } (true, false) => { @@ -1008,7 +1004,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { .is_none()); *context.alt_stream.write().unwrap() = Some(stream); } else { - return nt_res(STATUS_OBJECT_NAME_INVALID); + return Err(STATUS_OBJECT_NAME_INVALID); } } (false, true) => { @@ -1017,10 +1013,10 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { let src_stream = context_stream.as_ref().unwrap(); let mut src_stream_locked = src_stream.write().unwrap(); if src_stream_locked.handle_count > 1 { - return nt_res(STATUS_SHARING_VIOLATION); + return Err(STATUS_SHARING_VIOLATION); } if !replace_if_existing { - return nt_res(STATUS_OBJECT_NAME_COLLISION); + return Err(STATUS_OBJECT_NAME_COLLISION); } src_stream_locked.handle_count -= 1; stat.delete_pending = src_stream_locked.delete_pending; @@ -1032,7 +1028,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { std::mem::drop(src_stream_locked); *context_stream = None; } else { - return nt_res(STATUS_OBJECT_NAME_INVALID); + return Err(STATUS_OBJECT_NAME_INVALID); } } (false, false) => { @@ -1050,12 +1046,12 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { stat.update_atime(SystemTime::now()); } else { if context.alt_stream.read().unwrap().is_some() { - return nt_res(STATUS_OBJECT_NAME_INVALID); + return Err(STATUS_OBJECT_NAME_INVALID); } - let (dst_name, dst_parent) = path::split_path(&self.root, new_file_name)? - .ok_or(nt_err(STATUS_OBJECT_NAME_INVALID))?; + let (dst_name, dst_parent) = + path::split_path(&self.root, new_file_name)?.ok_or(STATUS_OBJECT_NAME_INVALID)?; if dst_name.stream_info.is_some() { - return nt_res(STATUS_OBJECT_NAME_INVALID); + return Err(STATUS_OBJECT_NAME_INVALID); } let now = SystemTime::now(); let src_name_ref = EntryNameRef::new(src_name); @@ -1065,16 +1061,16 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { if &context.entry == entry { Ok(()) } else if !replace_if_existing { - nt_res(STATUS_OBJECT_NAME_COLLISION) + Err(STATUS_OBJECT_NAME_COLLISION) } else if context.entry.is_dir() || entry.is_dir() { - nt_res(STATUS_ACCESS_DENIED) + Err(STATUS_ACCESS_DENIED) } else { let stat = entry.stat().read().unwrap(); let can_replace = stat.handle_count > 0 || stat.attrs.value & winnt::FILE_ATTRIBUTE_READONLY > 0; std::mem::drop(stat); if can_replace { - nt_res(STATUS_ACCESS_DENIED) + Err(STATUS_ACCESS_DENIED) } else { children.remove(dst_name_ref).unwrap(); Ok(()) @@ -1113,12 +1109,12 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { } fn set_end_of_file( - &'b self, + &'h self, _file_name: &U16CStr, offset: i64, - _info: &OperationInfo<'a, 'b, Self>, - context: &'a Self::Context, - ) -> Result<(), OperationError> { + _info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult<()> { let alt_stream = context.alt_stream.read().unwrap(); let ret = if let Some(stream) = alt_stream.as_ref() { stream.write().unwrap().data.resize(offset as usize, 0); @@ -1127,7 +1123,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { file.data.write().unwrap().resize(offset as usize, 0); Ok(()) } else { - nt_res(STATUS_INVALID_DEVICE_REQUEST) + Err(STATUS_INVALID_DEVICE_REQUEST) }; if ret.is_ok() { context.update_mtime( @@ -1139,12 +1135,12 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { } fn set_allocation_size( - &'b self, + &'h self, _file_name: &U16CStr, alloc_size: i64, - _info: &OperationInfo<'a, 'b, Self>, - context: &'a Self::Context, - ) -> Result<(), OperationError> { + _info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult<()> { let set_alloc = |data: &mut Vec<_>| { let alloc_size = alloc_size as usize; let cap = data.capacity(); @@ -1166,7 +1162,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { set_alloc(&mut file.data.write().unwrap()); Ok(()) } else { - nt_res(STATUS_INVALID_DEVICE_REQUEST) + Err(STATUS_INVALID_DEVICE_REQUEST) }; if ret.is_ok() { context.update_mtime( @@ -1178,9 +1174,9 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { } fn get_disk_free_space( - &'b self, - _info: &OperationInfo<'a, 'b, Self>, - ) -> Result { + &'h self, + _info: &OperationInfo<'c, 'h, Self>, + ) -> OperationResult { Ok(DiskSpaceInfo { byte_count: 1024 * 1024 * 1024, free_byte_count: 512 * 1024 * 1024, @@ -1189,9 +1185,9 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { } fn get_volume_information( - &'b self, - _info: &OperationInfo<'a, 'b, Self>, - ) -> Result { + &'h self, + _info: &OperationInfo<'c, 'h, Self>, + ) -> OperationResult { Ok(VolumeInfo { name: U16CString::from_str("dokan-rust memfs").unwrap(), serial_number: 0, @@ -1207,26 +1203,26 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { } fn mounted( - &'b self, + &'h self, _mount_point: &U16CStr, - _info: &OperationInfo<'a, 'b, Self>, - ) -> Result<(), OperationError> { + _info: &OperationInfo<'c, 'h, Self>, + ) -> OperationResult<()> { Ok(()) } - fn unmounted(&'b self, _info: &OperationInfo<'a, 'b, Self>) -> Result<(), OperationError> { + fn unmounted(&'h self, _info: &OperationInfo<'c, 'h, Self>) -> OperationResult<()> { Ok(()) } fn get_file_security( - &'b self, + &'h self, _file_name: &U16CStr, security_information: u32, security_descriptor: winnt::PSECURITY_DESCRIPTOR, buffer_length: u32, - _info: &OperationInfo<'a, 'b, Self>, - context: &'a Self::Context, - ) -> Result { + _info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult { context .entry .stat() @@ -1237,14 +1233,14 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { } fn set_file_security( - &'b self, + &'h self, _file_name: &U16CStr, security_information: u32, security_descriptor: winnt::PSECURITY_DESCRIPTOR, _buffer_length: u32, - _info: &OperationInfo<'a, 'b, Self>, - context: &'a Self::Context, - ) -> Result<(), OperationError> { + _info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult<()> { let mut stat = context.entry.stat().write().unwrap(); let ret = stat .sec_desc @@ -1256,28 +1252,28 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for MemFsHandler { } fn find_streams( - &'b self, + &'h self, _file_name: &U16CStr, - mut fill_find_stream_data: impl FnMut(&FindStreamData) -> Result<(), FillDataError>, - _info: &OperationInfo<'a, 'b, Self>, - context: &'a Self::Context, - ) -> Result<(), OperationError> { + mut fill_find_stream_data: impl FnMut(&FindStreamData) -> FillDataResult, + _info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult<()> { if let Entry::File(file) = &context.entry { - let res = fill_find_stream_data(&FindStreamData { + fill_find_stream_data(&FindStreamData { size: file.data.read().unwrap().len() as i64, name: U16CString::from_str("::$DATA").unwrap(), - }); - check_fill_data_error(res)?; + }) + .or_else(ignore_name_too_long)?; } for (k, v) in context.entry.stat().read().unwrap().alt_streams.iter() { let mut name_buf = vec![':' as u16]; name_buf.extend_from_slice(k.0.as_slice()); name_buf.extend_from_slice(U16String::from_str(":$DATA").as_slice()); - let res = fill_find_stream_data(&FindStreamData { + fill_find_stream_data(&FindStreamData { size: v.read().unwrap().data.len() as i64, name: U16CString::from_ustr(U16Str::from_slice(&name_buf)).unwrap(), - }); - check_fill_data_error(res)?; + }) + .or_else(ignore_name_too_long)?; } Ok(()) } @@ -1299,7 +1295,7 @@ fn main() -> Result<(), Box> { Arg::with_name("single_thread") .short("t") .long("single-thread") - .help("Thread count. Use \"0\" to let Dokan choose it automatically."), + .help("Force a single thread. Otherwise Dokan will allocate the number of threads regarding the workload."), ) .arg( Arg::with_name("dokan_debug") @@ -1314,7 +1310,9 @@ fn main() -> Result<(), Box> { .help("Mount as a removable drive."), ) .get_matches(); + let mount_point = U16CString::from_str(matches.value_of("mount_point").unwrap())?; + let mut flags = MountFlags::ALT_STREAM; if matches.is_present("dokan_debug") { flags |= MountFlags::DEBUG | MountFlags::STDERR; @@ -1323,13 +1321,38 @@ fn main() -> Result<(), Box> { flags |= MountFlags::REMOVABLE; } + let options = MountOptions { + single_thread: matches.is_present("single_thread"), + flags, + ..Default::default() + }; + + let handler = MemFsHandler::new(); + init(); - Drive::new() - .mount_point(&mount_point) - .single_thread(matches.is_present("single_thread")) - .flags(flags) - .mount(&MemFsHandler::new())?; + let mut mounter = FileSystemMounter::new(&handler, &mount_point, &options); + + println!("File system will mount..."); + + let file_system = mounter.mount()?; + + // Another thread can unmount the file system. + let mount_point = mount_point.clone(); + ctrlc::set_handler(move || { + if unmount(&mount_point) { + println!("File system will unmount...") + } else { + eprintln!("Failed to unmount file system."); + } + }) + .expect("failed to set Ctrl-C handler"); + + println!("File system is mounted, press Ctrl-C to unmount."); + + drop(file_system); + + println!("File system is unmounted."); shutdown(); diff --git a/dokan/examples/memfs/path.rs b/dokan/examples/memfs/path.rs index 5816be5..26a6000 100644 --- a/dokan/examples/memfs/path.rs +++ b/dokan/examples/memfs/path.rs @@ -1,11 +1,9 @@ -use std::borrow::Borrow; -use std::sync::Arc; +use std::{borrow::Borrow, sync::Arc}; -use dokan::OperationError; +use dokan::OperationResult; use widestring::{U16CStr, U16Str, U16String}; use winapi::shared::ntstatus::*; -use crate::err_utils::*; use crate::{DirEntry, Entry, EntryName, EntryNameRef}; // Use the same value as NTFS. @@ -25,7 +23,7 @@ pub struct StreamInfo<'a> { } impl StreamInfo<'_> { - pub fn check_default(&self, is_dir: bool) -> Result { + pub fn check_default(&self, is_dir: bool) -> OperationResult { if is_dir { if self.name.is_empty() || EntryNameRef::new(self.name) == EntryName(U16String::from_str("$I30")).borrow() @@ -33,17 +31,17 @@ impl StreamInfo<'_> { if self.type_ == StreamType::IndexAllocation { Ok(true) } else { - nt_res(STATUS_OBJECT_NAME_INVALID) + Err(STATUS_OBJECT_NAME_INVALID) } } else if self.type_ == StreamType::Data { Ok(false) } else { - nt_res(STATUS_OBJECT_NAME_INVALID) + Err(STATUS_OBJECT_NAME_INVALID) } } else if self.type_ == StreamType::Data { Ok(self.name.is_empty()) } else { - nt_res(STATUS_OBJECT_NAME_INVALID) + Err(STATUS_OBJECT_NAME_INVALID) } } } @@ -55,7 +53,7 @@ pub struct FullName<'a> { } impl<'a> FullName<'a> { - pub fn new(name: &'a U16Str) -> Result { + pub fn new(name: &'a U16Str) -> OperationResult { let name_slice = name.as_slice(); if let Some(offset1) = name_slice.iter().position(|x| *x == ':' as u16) { let file_name = U16Str::from_slice(&name_slice[..offset1]); @@ -74,7 +72,7 @@ impl<'a> FullName<'a> { } else if stream_type_str == EntryName(U16String::from_str("$BITMAP")).borrow() { StreamType::Bitmap } else { - return nt_res(STATUS_OBJECT_NAME_INVALID); + return Err(STATUS_OBJECT_NAME_INVALID); }; Ok(Self { file_name, @@ -101,13 +99,10 @@ impl<'a> FullName<'a> { } } -fn find_dir_entry( - cur_entry: &Arc, - path: &[&U16Str], -) -> Result, OperationError> { +fn find_dir_entry(cur_entry: &Arc, path: &[&U16Str]) -> OperationResult> { if let Some(name) = path.get(0) { if name.len() > MAX_COMPONENT_LENGTH as usize { - return nt_res(STATUS_OBJECT_NAME_INVALID); + return Err(STATUS_OBJECT_NAME_INVALID); } match cur_entry .children @@ -116,7 +111,7 @@ fn find_dir_entry( .get(EntryNameRef::new(name)) { Some(Entry::Directory(dir)) => find_dir_entry(dir, &path[1..]), - _ => nt_res(STATUS_OBJECT_PATH_NOT_FOUND), + _ => Err(STATUS_OBJECT_PATH_NOT_FOUND), } } else { Ok(Arc::clone(cur_entry)) @@ -126,7 +121,7 @@ fn find_dir_entry( pub fn split_path<'a>( root: &Arc, path: &'a U16CStr, -) -> Result, Arc)>, OperationError> { +) -> OperationResult, Arc)>> { let path = path .as_slice() .split(|x| *x == '\\' as u16) @@ -138,7 +133,7 @@ pub fn split_path<'a>( } else { let name = *path.iter().last().unwrap(); if name.len() > MAX_COMPONENT_LENGTH as usize { - return nt_res(STATUS_OBJECT_NAME_INVALID); + return Err(STATUS_OBJECT_NAME_INVALID); } Ok(Some(( FullName::new(name)?, diff --git a/dokan/examples/memfs/security.rs b/dokan/examples/memfs/security.rs index d615285..2b77590 100644 --- a/dokan/examples/memfs/security.rs +++ b/dokan/examples/memfs/security.rs @@ -1,11 +1,10 @@ -use std::pin::Pin; -use std::{mem, ptr}; +use std::{mem, pin::Pin, ptr}; -use dokan::OperationError; -use winapi::shared::{minwindef, ntdef, ntstatus::*, winerror}; -use winapi::um::{errhandlingapi, heapapi, securitybaseapi, winnt}; - -use crate::err_utils::*; +use dokan::{map_win32_error_to_ntstatus, win32_ensure, OperationResult}; +use winapi::{ + shared::{minwindef, ntdef, ntstatus::*, winerror}, + um::{errhandlingapi::GetLastError, heapapi, securitybaseapi, winnt}, +}; #[derive(Debug)] struct PrivateObjectSecurity { @@ -35,26 +34,24 @@ unsafe impl Sync for SecurityDescriptor {} unsafe impl Send for SecurityDescriptor {} -fn get_well_known_sid(sid_type: winnt::WELL_KNOWN_SID_TYPE) -> Result, OperationError> { +fn get_well_known_sid(sid_type: winnt::WELL_KNOWN_SID_TYPE) -> OperationResult> { unsafe { let mut sid = vec![0u8; mem::size_of::() + mem::size_of::() * 7].into_boxed_slice(); let mut len = sid.len() as u32; - let ret = securitybaseapi::CreateWellKnownSid( - sid_type, - ptr::null_mut(), - sid.as_mut_ptr() as winnt::PSID, - &mut len, - ); - if ret == minwindef::TRUE { - Ok(sid) - } else { - win32_last_res() - } + win32_ensure( + securitybaseapi::CreateWellKnownSid( + sid_type, + ptr::null_mut(), + sid.as_mut_ptr() as winnt::PSID, + &mut len, + ) == minwindef::TRUE, + )?; + Ok(sid) } } -fn create_default_dacl() -> Result, OperationError> { +fn create_default_dacl() -> OperationResult> { unsafe { let admins_sid = get_well_known_sid(winnt::WinBuiltinAdministratorsSid)?; let system_sid = get_well_known_sid(winnt::WinLocalSystemSid)?; @@ -68,59 +65,57 @@ fn create_default_dacl() -> Result, OperationError> { + auth_sid.len() + users_sid.len(); let mut acl = vec![0u8; acl_len].into_boxed_slice(); - let ret = securitybaseapi::InitializeAcl( - acl.as_mut_ptr() as winnt::PACL, - acl_len as u32, - winnt::ACL_REVISION as u32, - ); - if ret == minwindef::FALSE { - return win32_last_res(); - } + win32_ensure( + securitybaseapi::InitializeAcl( + acl.as_mut_ptr() as winnt::PACL, + acl_len as u32, + winnt::ACL_REVISION as u32, + ) == minwindef::TRUE, + )?; let flags = (winnt::CONTAINER_INHERIT_ACE | winnt::OBJECT_INHERIT_ACE) as u32; - let ret = securitybaseapi::AddAccessAllowedAceEx( - acl.as_mut_ptr() as winnt::PACL, - winnt::ACL_REVISION as u32, - flags, - winnt::FILE_ALL_ACCESS, - admins_sid.as_ptr() as winnt::PSID, - ); - if ret == minwindef::FALSE { - return win32_last_res(); - } - let ret = securitybaseapi::AddAccessAllowedAceEx( - acl.as_mut_ptr() as winnt::PACL, - winnt::ACL_REVISION as u32, - flags, - winnt::FILE_ALL_ACCESS, - system_sid.as_ptr() as winnt::PSID, - ); - if ret == minwindef::FALSE { - return win32_last_res(); - } - let ret = securitybaseapi::AddAccessAllowedAceEx( - acl.as_mut_ptr() as winnt::PACL, - winnt::ACL_REVISION as u32, - flags, - winnt::FILE_GENERIC_READ - | winnt::FILE_GENERIC_WRITE - | winnt::FILE_GENERIC_EXECUTE - | winnt::DELETE, - auth_sid.as_ptr() as winnt::PSID, - ); - if ret == minwindef::FALSE { - return win32_last_res(); - } - let ret = securitybaseapi::AddAccessAllowedAceEx( - acl.as_mut_ptr() as winnt::PACL, - winnt::ACL_REVISION as u32, - flags, - winnt::FILE_GENERIC_READ | winnt::FILE_GENERIC_EXECUTE, - users_sid.as_ptr() as winnt::PSID, - ); - if ret == minwindef::FALSE { - return win32_last_res(); - } + win32_ensure( + securitybaseapi::AddAccessAllowedAceEx( + acl.as_mut_ptr() as winnt::PACL, + winnt::ACL_REVISION as u32, + flags, + winnt::FILE_ALL_ACCESS, + admins_sid.as_ptr() as winnt::PSID, + ) == minwindef::TRUE, + )?; + + win32_ensure( + securitybaseapi::AddAccessAllowedAceEx( + acl.as_mut_ptr() as winnt::PACL, + winnt::ACL_REVISION as u32, + flags, + winnt::FILE_ALL_ACCESS, + system_sid.as_ptr() as winnt::PSID, + ) == minwindef::TRUE, + )?; + + win32_ensure( + securitybaseapi::AddAccessAllowedAceEx( + acl.as_mut_ptr() as winnt::PACL, + winnt::ACL_REVISION as u32, + flags, + winnt::FILE_GENERIC_READ + | winnt::FILE_GENERIC_WRITE + | winnt::FILE_GENERIC_EXECUTE + | winnt::DELETE, + auth_sid.as_ptr() as winnt::PSID, + ) == minwindef::TRUE, + )?; + + win32_ensure( + securitybaseapi::AddAccessAllowedAceEx( + acl.as_mut_ptr() as winnt::PACL, + winnt::ACL_REVISION as u32, + flags, + winnt::FILE_GENERIC_READ | winnt::FILE_GENERIC_EXECUTE, + users_sid.as_ptr() as winnt::PSID, + ) == minwindef::TRUE, + )?; Ok(acl) } @@ -139,43 +134,41 @@ impl SecurityDescriptor { creator_desc: winnt::PSECURITY_DESCRIPTOR, token: ntdef::HANDLE, is_dir: bool, - ) -> Result { + ) -> OperationResult { unsafe { if !creator_desc.is_null() && securitybaseapi::IsValidSecurityDescriptor(creator_desc) == minwindef::FALSE { - return nt_res(STATUS_INVALID_PARAMETER); + return Err(STATUS_INVALID_PARAMETER); } let mut priv_desc = ptr::null_mut(); - let ret = securitybaseapi::CreatePrivateObjectSecurity( - parent_desc.desc_ptr, - creator_desc, - &mut priv_desc, - is_dir as minwindef::BOOL, - token, - &FILE_GENERIC_MAPPING as *const _ as *mut _, - ); - if ret == minwindef::FALSE { - return win32_last_res(); - } + win32_ensure( + securitybaseapi::CreatePrivateObjectSecurity( + parent_desc.desc_ptr, + creator_desc, + &mut priv_desc, + is_dir as minwindef::BOOL, + token, + &FILE_GENERIC_MAPPING as *const _ as *mut _, + ) == minwindef::TRUE, + )?; + let priv_desc = PrivateObjectSecurity::from_raw(priv_desc); let heap = heapapi::GetProcessHeap(); - if heap.is_null() { - return win32_last_res(); - } + win32_ensure(!heap.is_null())?; + let len = securitybaseapi::GetSecurityDescriptorLength(priv_desc.value) as usize; let buf = heapapi::HeapAlloc(heap, 0, len); - if buf.is_null() { - return win32_last_res(); - } + win32_ensure(!buf.is_null())?; + ptr::copy_nonoverlapping(priv_desc.value as *const u8, buf as *mut _, len); Ok(Self { desc_ptr: buf }) } } - pub fn new_default() -> Result { + pub fn new_default() -> OperationResult { let owner_sid = Pin::new(get_well_known_sid(winnt::WinLocalSystemSid)?); let group_sid = Pin::new(get_well_known_sid(winnt::WinLocalSystemSid)?); let dacl = Pin::new(create_default_dacl()?); @@ -183,59 +176,56 @@ impl SecurityDescriptor { unsafe { let mut abs_desc = mem::zeroed::(); let abs_desc_ptr = &mut abs_desc as *mut _ as winnt::PSECURITY_DESCRIPTOR; - let ret = securitybaseapi::InitializeSecurityDescriptor( - abs_desc_ptr, - winnt::SECURITY_DESCRIPTOR_REVISION, - ); - if ret == minwindef::FALSE { - return win32_last_res(); - } - let ret = securitybaseapi::SetSecurityDescriptorOwner( - abs_desc_ptr, - owner_sid.as_ptr() as winnt::PSID, - minwindef::FALSE, - ); - if ret == minwindef::FALSE { - return win32_last_res(); - } - let ret = securitybaseapi::SetSecurityDescriptorGroup( - abs_desc_ptr, - group_sid.as_ptr() as winnt::PSID, - minwindef::FALSE, - ); - if ret == minwindef::FALSE { - return win32_last_res(); - } - let ret = securitybaseapi::SetSecurityDescriptorDacl( - abs_desc_ptr, - minwindef::TRUE, - dacl.as_ptr() as winnt::PACL, - minwindef::FALSE, - ); - if ret == minwindef::FALSE { - return win32_last_res(); - } + win32_ensure( + securitybaseapi::InitializeSecurityDescriptor( + abs_desc_ptr, + winnt::SECURITY_DESCRIPTOR_REVISION, + ) == minwindef::TRUE, + )?; + + win32_ensure( + securitybaseapi::SetSecurityDescriptorOwner( + abs_desc_ptr, + owner_sid.as_ptr() as winnt::PSID, + minwindef::FALSE, + ) == minwindef::TRUE, + )?; + + win32_ensure( + securitybaseapi::SetSecurityDescriptorGroup( + abs_desc_ptr, + group_sid.as_ptr() as winnt::PSID, + minwindef::FALSE, + ) == minwindef::TRUE, + )?; + + win32_ensure( + securitybaseapi::SetSecurityDescriptorDacl( + abs_desc_ptr, + minwindef::TRUE, + dacl.as_ptr() as winnt::PACL, + minwindef::FALSE, + ) == minwindef::TRUE, + )?; let mut len = 0; let ret = securitybaseapi::MakeSelfRelativeSD(abs_desc_ptr, ptr::null_mut(), &mut len); - let err = errhandlingapi::GetLastError(); + let err = GetLastError(); if ret != minwindef::FALSE || err != winerror::ERROR_INSUFFICIENT_BUFFER { - return Err(OperationError::Win32(err)); + return Err(map_win32_error_to_ntstatus(err)); } let heap = heapapi::GetProcessHeap(); - if heap.is_null() { - return win32_last_res(); - } + win32_ensure(!heap.is_null())?; + let buf = heapapi::HeapAlloc(heap, 0, len as usize); - if buf.is_null() { - return win32_last_res(); - } - let ret = securitybaseapi::MakeSelfRelativeSD(abs_desc_ptr, buf, &mut len); - if ret == minwindef::FALSE { - return win32_last_res(); - } + win32_ensure(!buf.is_null())?; + + win32_ensure( + securitybaseapi::MakeSelfRelativeSD(abs_desc_ptr, buf, &mut len) == minwindef::TRUE, + )?; + Ok(Self { desc_ptr: buf }) } } @@ -245,7 +235,7 @@ impl SecurityDescriptor { sec_info: winnt::SECURITY_INFORMATION, sec_desc: winnt::PSECURITY_DESCRIPTOR, sec_desc_len: u32, - ) -> Result { + ) -> OperationResult { unsafe { let len = securitybaseapi::GetSecurityDescriptorLength(self.desc_ptr); if len > sec_desc_len { @@ -253,18 +243,17 @@ impl SecurityDescriptor { } let mut ret_len = 0; - let ret = securitybaseapi::GetPrivateObjectSecurity( - self.desc_ptr, - sec_info, - sec_desc, - sec_desc_len, - &mut ret_len, - ); - if ret == minwindef::TRUE { - Ok(len) - } else { - win32_last_res() - } + win32_ensure( + securitybaseapi::GetPrivateObjectSecurity( + self.desc_ptr, + sec_info, + sec_desc, + sec_desc_len, + &mut ret_len, + ) == minwindef::TRUE, + )?; + + Ok(len) } } @@ -272,25 +261,24 @@ impl SecurityDescriptor { &mut self, sec_info: winnt::SECURITY_INFORMATION, sec_desc: winnt::PSECURITY_DESCRIPTOR, - ) -> Result<(), OperationError> { + ) -> OperationResult<()> { unsafe { if securitybaseapi::IsValidSecurityDescriptor(sec_desc) == minwindef::FALSE { - return nt_res(STATUS_INVALID_PARAMETER); + return Err(STATUS_INVALID_PARAMETER); } - let ret = securitybaseapi::SetPrivateObjectSecurityEx( - sec_info, - sec_desc, - &mut self.desc_ptr, - winnt::SEF_AVOID_PRIVILEGE_CHECK | winnt::SEF_AVOID_OWNER_CHECK, - &FILE_GENERIC_MAPPING as *const _ as *mut _, - ptr::null_mut(), - ); - if ret == minwindef::TRUE { - Ok(()) - } else { - win32_last_res() - } + win32_ensure( + securitybaseapi::SetPrivateObjectSecurityEx( + sec_info, + sec_desc, + &mut self.desc_ptr, + winnt::SEF_AVOID_PRIVILEGE_CHECK | winnt::SEF_AVOID_OWNER_CHECK, + &FILE_GENERIC_MAPPING as *const _ as *mut _, + ptr::null_mut(), + ) == minwindef::TRUE, + )?; + + Ok(()) } } } diff --git a/dokan/src/data.rs b/dokan/src/data.rs new file mode 100644 index 0000000..91cefbc --- /dev/null +++ b/dokan/src/data.rs @@ -0,0 +1,19 @@ +mod create_file_info; +mod disk_space_info; +mod file_info; +mod file_time_operation; +mod fill_data; +mod find_data; +mod mount_point; +mod operation_info; +mod volume_info; + +pub use create_file_info::*; +pub use disk_space_info::*; +pub use file_info::*; +pub use file_time_operation::*; +pub use fill_data::*; +pub use find_data::*; +pub use mount_point::*; +pub use operation_info::*; +pub use volume_info::*; diff --git a/dokan/src/data/create_file_info.rs b/dokan/src/data/create_file_info.rs new file mode 100644 index 0000000..e904517 --- /dev/null +++ b/dokan/src/data/create_file_info.rs @@ -0,0 +1,14 @@ +/// Information about the created or opened file returned by [`FileSystemHandler::create_file`]. +/// +/// [`FileSystemHandler::create_file`]: crate::FileSystemHandler::create_file +#[derive(Debug, Clone)] +pub struct CreateFileInfo { + /// The context to be associated with the new file object. + pub context: T, + + /// Indicates whether the file is a directory. + pub is_dir: bool, + + /// Indicates whether a new file has been created. + pub new_file_created: bool, +} diff --git a/dokan/src/data/disk_space_info.rs b/dokan/src/data/disk_space_info.rs new file mode 100644 index 0000000..d432da8 --- /dev/null +++ b/dokan/src/data/disk_space_info.rs @@ -0,0 +1,14 @@ +/// Information about disk space returned by [`FileSystemHandler::get_disk_free_space`]. +/// +/// [`FileSystemHandler::get_disk_free_space`]: crate::FileSystemHandler::get_disk_free_space +#[derive(Debug, Clone)] +pub struct DiskSpaceInfo { + /// Total number of bytes that are available to the calling user. + pub byte_count: u64, + + /// Total number of free bytes on the disk. + pub free_byte_count: u64, + + /// Total number of free bytes that are available to the calling user. + pub available_byte_count: u64, +} diff --git a/dokan/src/data/file_info.rs b/dokan/src/data/file_info.rs new file mode 100644 index 0000000..78d85b3 --- /dev/null +++ b/dokan/src/data/file_info.rs @@ -0,0 +1,53 @@ +use std::time::SystemTime; + +use winapi::um::fileapi::BY_HANDLE_FILE_INFORMATION; + +use crate::to_file_time::ToFileTime; + +/// Information about a file returned by [`FileSystemHandler::get_file_information`]. +/// +/// [`FileSystemHandler::get_file_information`]: crate::FileSystemHandler::get_file_information +#[derive(Debug, Clone)] +pub struct FileInfo { + /// Attribute flags of the file. + /// + /// It can be combination of one or more [file attribute constants] defined by Windows. + /// + /// [file attribute constants]: https://docs.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants + pub attributes: u32, + + /// The time when the file was created. + pub creation_time: SystemTime, + + /// The time when the file was last accessed. + pub last_access_time: SystemTime, + + /// The time when the file was last written to. + pub last_write_time: SystemTime, + + /// Size of the file. + pub file_size: u64, + + /// Number of hardlinks to the file. + pub number_of_links: u32, + + /// The index that uniquely identifies the file in a volume. + pub file_index: u64, +} + +impl FileInfo { + pub fn to_raw_struct(&self) -> BY_HANDLE_FILE_INFORMATION { + BY_HANDLE_FILE_INFORMATION { + dwFileAttributes: self.attributes, + ftCreationTime: self.creation_time.to_filetime(), + ftLastAccessTime: self.last_access_time.to_filetime(), + ftLastWriteTime: self.last_write_time.to_filetime(), + dwVolumeSerialNumber: 0, + nFileSizeHigh: (self.file_size >> 32) as u32, + nFileSizeLow: self.file_size as u32, + nNumberOfLinks: self.number_of_links, + nFileIndexHigh: (self.file_index >> 32) as u32, + nFileIndexLow: self.file_index as u32, + } + } +} diff --git a/dokan/src/data/file_time_operation.rs b/dokan/src/data/file_time_operation.rs new file mode 100644 index 0000000..b995a8b --- /dev/null +++ b/dokan/src/data/file_time_operation.rs @@ -0,0 +1,42 @@ +use std::{ + mem::transmute_copy, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; + +use winapi::shared::minwindef::FILETIME; + +use crate::to_file_time::FILETIME_OFFSET; + +/// Operation to perform on a file's corresponding time information. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum FileTimeOperation { + /// Set corresponding time information of the file. + SetTime(SystemTime), + /// Don't change corresponding time information of the file. + DontChange, + /// Disable update of corresponding time information caused by further operations on the file handle. + DisableUpdate, + /// Resume update of corresponding time information caused by further operations on the file handle. + ResumeUpdate, +} + +impl From<*const FILETIME> for FileTimeOperation { + fn from(time: *const FILETIME) -> Self { + unsafe { + let time_val = transmute_copy::<_, i64>(&*time); + match time_val { + 0 => FileTimeOperation::DontChange, + -1 => FileTimeOperation::DisableUpdate, + -2 => FileTimeOperation::ResumeUpdate, + _ => { + let time_val = time_val as u64; + FileTimeOperation::SetTime( + UNIX_EPOCH - FILETIME_OFFSET + + Duration::from_micros(time_val / 10) + + Duration::from_nanos(time_val % 10 * 100), + ) + } + } + } + } +} diff --git a/dokan/src/data/fill_data.rs b/dokan/src/data/fill_data.rs new file mode 100644 index 0000000..f39100e --- /dev/null +++ b/dokan/src/data/fill_data.rs @@ -0,0 +1,45 @@ +use std::{ + error::Error, + fmt::{self, Display, Formatter}, +}; + +use winapi::shared::{ + ntdef::NTSTATUS, + ntstatus::{STATUS_BUFFER_OVERFLOW, STATUS_INTERNAL_ERROR}, +}; + +/// Error type for the `fill_data` callbacks. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum FillDataError { + /// File name exceeds the limit of [`MAX_PATH`]. + /// + /// [`MAX_PATH`]: winapi::shared::minwindef::MAX_PATH + NameTooLong, + + /// Buffer is full. + BufferFull, +} + +impl Into for FillDataError { + fn into(self) -> NTSTATUS { + match self { + FillDataError::NameTooLong => STATUS_INTERNAL_ERROR, + FillDataError::BufferFull => STATUS_BUFFER_OVERFLOW, + } + } +} + +impl Error for FillDataError {} + +impl Display for FillDataError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let msg = match self { + FillDataError::NameTooLong => "file name length exceeds the limit of MAX_PATH", + FillDataError::BufferFull => "buffer is full", + }; + write!(f, "{}", msg) + } +} + +/// Returned by `fill_data` callbacks. +pub type FillDataResult = Result<(), FillDataError>; diff --git a/dokan/src/data/find_data.rs b/dokan/src/data/find_data.rs new file mode 100644 index 0000000..c500cf8 --- /dev/null +++ b/dokan/src/data/find_data.rs @@ -0,0 +1,161 @@ +use std::{mem::transmute, time::SystemTime}; + +use dokan_sys::win32::WIN32_FIND_STREAM_DATA; +use widestring::U16CString; +use winapi::{shared::minwindef::MAX_PATH, um::minwinbase::WIN32_FIND_DATAW}; + +use crate::{to_file_time::ToFileTime, FillDataError, FillDataResult}; + +pub(crate) trait ToRawStruct { + fn to_raw_struct(&self) -> Option; +} + +/// Information about a file provided by [`FileSystemHandler::find_files`] or +/// [`FileSystemHandler::find_files_with_pattern`]. +/// +/// [`FileSystemHandler::find_files`]: crate::FileSystemHandler::find_files +/// [`FileSystemHandler::find_files_with_pattern`]: crate::FileSystemHandler::find_files_with_pattern +#[derive(Debug, Clone)] +pub struct FindData { + /// Attribute flags of the file. + /// + /// It can be combination of one or more [file attribute constants] defined by Windows. + /// + /// [file attribute constants]: https://docs.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants + pub attributes: u32, + + /// The time when the file was created. + pub creation_time: SystemTime, + + /// The time when the file was last accessed. + pub last_access_time: SystemTime, + + /// The time when the file was last written to. + pub last_write_time: SystemTime, + + /// Size of the file. + pub file_size: u64, + + /// Name of the file. + pub file_name: U16CString, +} + +impl ToRawStruct for FindData { + fn to_raw_struct(&self) -> Option { + let name_slice = self.file_name.as_slice_with_nul(); + if name_slice.len() <= MAX_PATH { + let mut c_file_name = [0; MAX_PATH]; + c_file_name[..name_slice.len()].copy_from_slice(name_slice); + Some(WIN32_FIND_DATAW { + dwFileAttributes: self.attributes, + ftCreationTime: self.creation_time.to_filetime(), + ftLastAccessTime: self.last_access_time.to_filetime(), + ftLastWriteTime: self.last_write_time.to_filetime(), + nFileSizeHigh: (self.file_size >> 32) as u32, + nFileSizeLow: self.file_size as u32, + dwReserved0: 0, + dwReserved1: 0, + cFileName: c_file_name, + cAlternateFileName: [0; 14], + }) + } else { + None + } + } +} + +/// Information about an alternative stream provided by [`FileSystemHandler::find_streams`]. +/// +/// [`FileSystemHandler::find_streams`]: crate::FileSystemHandler::find_streams +#[derive(Debug, Clone)] +pub struct FindStreamData { + /// Size of the stream. + pub size: i64, + + /// Name of stream. + /// + /// The format of this name should be `:streamname:$streamtype`. See [NTFS Streams] for more + /// information. + /// + /// [NTFS Streams]: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/c54dec26-1551-4d3a-a0ea-4fa40f848eb3 + pub name: U16CString, +} + +const MAX_STREAM_NAME: usize = MAX_PATH + 36; + +impl ToRawStruct for FindStreamData { + fn to_raw_struct(&self) -> Option { + let name_slice = self.name.as_slice_with_nul(); + if name_slice.len() <= MAX_STREAM_NAME { + let mut c_stream_name = [0; MAX_STREAM_NAME]; + c_stream_name[..name_slice.len()].copy_from_slice(name_slice); + Some(WIN32_FIND_STREAM_DATA { + StreamSize: unsafe { transmute(self.size) }, + cStreamName: c_stream_name, + }) + } else { + None + } + } +} + +pub(crate) fn wrap_fill_data, TArg: Copy, TResult: PartialEq>( + fill_data: unsafe extern "stdcall" fn(*mut T, TArg) -> TResult, + fill_data_arg: TArg, + success_value: TResult, +) -> impl FnMut(&U) -> FillDataResult { + move |data| { + let mut ffi_data = data.to_raw_struct().ok_or(FillDataError::NameTooLong)?; + if unsafe { fill_data(&mut ffi_data, fill_data_arg) == success_value } { + Ok(()) + } else { + Err(FillDataError::BufferFull) + } + } +} + +#[cfg(test)] +mod tests { + use std::ptr; + + use dokan_sys::PDOKAN_FILE_INFO; + use winapi::ctypes::c_int; + + use super::*; + + struct ToRawStructStub { + should_fail: bool, + } + + impl ToRawStruct<()> for ToRawStructStub { + fn to_raw_struct(&self) -> Option<()> { + if self.should_fail { + None + } else { + Some(()) + } + } + } + + extern "stdcall" fn fill_data_stub(_data: *mut (), _info: PDOKAN_FILE_INFO) -> c_int { + 0 + } + + extern "stdcall" fn failing_fill_data_stub(_data: *mut (), _info: PDOKAN_FILE_INFO) -> c_int { + 1 + } + + #[test] + fn test_wrap_fill_data() { + let mut wrapper = wrap_fill_data(fill_data_stub, ptr::null_mut(), 0); + assert_eq!( + wrapper(&ToRawStructStub { should_fail: true }), + Err(FillDataError::NameTooLong) + ); + let mut wrapper = wrap_fill_data(failing_fill_data_stub, ptr::null_mut(), 0); + assert_eq!( + wrapper(&ToRawStructStub { should_fail: false }), + Err(FillDataError::BufferFull) + ); + } +} diff --git a/dokan/src/data/mount_point.rs b/dokan/src/data/mount_point.rs new file mode 100644 index 0000000..682e347 --- /dev/null +++ b/dokan/src/data/mount_point.rs @@ -0,0 +1,149 @@ +use std::{iter::Map, mem::transmute, slice}; + +use dokan_sys::{ + win32::{FILE_DEVICE_DISK_FILE_SYSTEM, FILE_DEVICE_NETWORK_FILE_SYSTEM}, + *, +}; +use widestring::U16CStr; +use winapi::shared::minwindef::ULONG; + +/// Mount point device type. +#[repr(u32)] +#[derive(Debug, Clone, PartialEq)] +pub enum DeviceType { + Disk = FILE_DEVICE_DISK_FILE_SYSTEM, + Network = FILE_DEVICE_NETWORK_FILE_SYSTEM, +} + +impl From for DeviceType { + fn from(value: u32) -> Self { + unsafe { transmute(value) } + } +} + +/// Information about a mount point listed by [`list_mount_points`]. +#[derive(Debug, Clone)] +pub struct MountPointInfo<'a> { + /// File system type of the mounted volume. + pub device_type: DeviceType, + + /// Mount point path. + pub mount_point: Option<&'a U16CStr>, + + /// UNC name of the network volume. + pub unc_name: Option<&'a U16CStr>, + + /// Device name of the mounted volume. + pub device_name: &'a U16CStr, + + /// The session in which the volume is mounted. + /// + /// It will be `-1` if the volume is mounted globally. + pub session_id: u32, +} + +impl<'a> From<&'a DOKAN_MOUNT_POINT_INFO> for MountPointInfo<'a> { + fn from(info: &'a DOKAN_MOUNT_POINT_INFO) -> Self { + let mount_point = if info.MountPoint[0] == 0 { + None + } else { + Some(U16CStr::from_slice_with_nul(&info.MountPoint).unwrap()) + }; + + let unc_name = if info.UNCName[0] == 0 { + None + } else { + Some(U16CStr::from_slice_with_nul(&info.UNCName).unwrap()) + }; + + MountPointInfo { + device_type: info.Type.into(), + mount_point, + unc_name, + device_name: U16CStr::from_slice_with_nul(&info.DeviceName).unwrap(), + session_id: info.SessionId, + } + } +} + +/// A list of [`MountPointInfo`] provided by [`list_mount_points`]. +pub struct MountPointList { + list_ptr: PDOKAN_MOUNT_POINT_INFO, + len: usize, +} + +impl MountPointList { + pub fn len(&self) -> usize { + self.len + } +} + +impl<'a> IntoIterator for &'a MountPointList { + type Item = MountPointInfo<'a>; + + type IntoIter = Map< + slice::Iter<'a, DOKAN_MOUNT_POINT_INFO>, + fn(&'a DOKAN_MOUNT_POINT_INFO) -> MountPointInfo, + >; + + fn into_iter(self) -> Self::IntoIter { + unsafe { slice::from_raw_parts(self.list_ptr, self.len) } + .iter() + .map(Into::into) + } +} + +impl Drop for MountPointList { + fn drop(&mut self) { + unsafe { + DokanReleaseMountPointList(self.list_ptr); + } + } +} + +/// Lists of active Dokan mount points. +/// +/// Returns `None` in case of error. +pub fn list_mount_points<'a>(unc_only: bool) -> Option { + unsafe { + let mut len: ULONG = 0; + let list_ptr = DokanGetMountPointList(unc_only.into(), &mut len); + if list_ptr.is_null() { + None + } else { + let len = len as usize; + Some(MountPointList { list_ptr, len }) + } + } +} + +#[test] +fn can_list_mount_points() { + use std::process; + + use regex::Regex; + use winapi::{shared::minwindef::TRUE, um::processthreadsapi::ProcessIdToSessionId}; + + use crate::usage_tests::{convert_str, with_test_drive}; + + with_test_drive(|_| unsafe { + let list = list_mount_points(false).unwrap(); + let list_as_vec: Vec<_> = list.into_iter().collect(); + assert_eq!(list_as_vec.len(), 1); + let info = &list_as_vec[0]; + assert_eq!(info.device_type, DeviceType::Disk); + assert_eq!( + info.mount_point, + Some(convert_str("\\DosDevices\\Z:").as_ref()) + ); + assert_eq!(info.unc_name, None); + assert!( + Regex::new(r"^\\Device\\Volume\{[0-9a-z]{8}-([0-9a-z]{4}-){3}[0-9a-z]{12}\}$") + .unwrap() + .is_match(&info.device_name.to_string_lossy()) + ); + let mut session_id = 0; + assert_eq!(ProcessIdToSessionId(process::id(), &mut session_id), TRUE); + assert_eq!(info.session_id, session_id); + }); +} diff --git a/dokan/src/data/operation_info.rs b/dokan/src/data/operation_info.rs new file mode 100644 index 0000000..896d0de --- /dev/null +++ b/dokan/src/data/operation_info.rs @@ -0,0 +1,165 @@ +use std::{ + marker::PhantomData, + os::windows::prelude::{FromRawHandle, OwnedHandle}, + time::Duration, +}; + +use dokan_sys::{ + DokanOpenRequestorToken, DokanResetTimeout, DOKAN_FILE_INFO, DOKAN_OPTIONS, PDOKAN_FILE_INFO, +}; +use widestring::U16CStr; +use winapi::{shared::minwindef::TRUE, um::handleapi::INVALID_HANDLE_VALUE}; + +use crate::{file_system_handler::FileSystemHandler, MountFlags}; + +/// Information about the current operation. +#[derive(Debug)] +pub struct OperationInfo<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h> { + file_info: PDOKAN_FILE_INFO, + phantom_handler: PhantomData<&'h FSH>, + phantom_context: PhantomData<&'c FSH::Context>, +} + +impl<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h> OperationInfo<'c, 'h, FSH> { + pub fn new(file_info: PDOKAN_FILE_INFO) -> Self { + OperationInfo { + file_info, + phantom_handler: PhantomData, + phantom_context: PhantomData, + } + } + + pub fn file_info(&self) -> &DOKAN_FILE_INFO { + unsafe { &*self.file_info } + } + + pub fn mount_options(&self) -> &DOKAN_OPTIONS { + unsafe { &*self.file_info().DokanOptions } + } + + pub fn handler(&self) -> &'h FSH { + unsafe { &*(self.mount_options().GlobalContext as *const _) } + } + + pub fn context(&self) -> &'c FSH::Context { + unsafe { &*(self.file_info().Context as *const _) } + } + + pub fn drop_context(&mut self) { + unsafe { + let info = &mut *self.file_info; + let ptr = info.Context as *mut FSH::Context; + if !ptr.is_null() { + drop(Box::from_raw(ptr)); + info.Context = 0; + } + } + } + + /// Gets process ID of the calling process. + pub fn pid(&self) -> u32 { + self.file_info().ProcessId + } + + /// Gets whether the target file is a directory. + pub fn is_dir(&self) -> bool { + self.file_info().IsDirectory != 0 + } + + /// Gets whether the file should be deleted when it is closed. + pub fn delete_on_close(&self) -> bool { + self.file_info().DeleteOnClose != 0 + } + + /// Gets whether it is a paging I/O operation. + pub fn paging_io(&self) -> bool { + self.file_info().PagingIo != 0 + } + + /// Gets whether it is a synchronous I/O operation. + pub fn synchronous_io(&self) -> bool { + self.file_info().SynchronousIo != 0 + } + + /// Gets whether it is a non-cached I/O operation. + pub fn no_cache(&self) -> bool { + self.file_info().Nocache != 0 + } + + /// Gets whether the current write operation should write to end of file instead of the + /// position specified by the offset argument. + pub fn write_to_eof(&self) -> bool { + self.file_info().WriteToEndOfFile != 0 + } + + /// Gets the number of threads used to handle file system operations. + pub fn single_thread(&self) -> bool { + self.mount_options().SingleThread != 0 + } + + /// Gets flags that controls behavior of the mounted volume. + pub fn mount_flags(&self) -> MountFlags { + MountFlags::from_bits_truncate(self.mount_options().Options) + } + + /// Gets mount point path. + pub fn mount_point(&self) -> Option<&U16CStr> { + let ptr = self.mount_options().MountPoint; + if ptr.is_null() { + None + } else { + unsafe { Some(U16CStr::from_ptr_str(ptr)) } + } + } + + /// Gets UNC name of the network drive. + pub fn unc_name(&self) -> Option<&U16CStr> { + let ptr = self.mount_options().UNCName; + if ptr.is_null() { + None + } else { + unsafe { Some(U16CStr::from_ptr_str(ptr)) } + } + } + + /// Gets the time that Dokan will wait for an operation to complete. + /// + /// See [`MountOptions::timeout`] for more information. + /// + /// [`MountOptions::timeout`]: crate::MountOptions::timeout + pub fn timeout(&self) -> Duration { + Duration::from_millis(self.mount_options().Timeout.into()) + } + + /// Gets allocation unit size of the volume. + pub fn allocation_unit_size(&self) -> u32 { + self.mount_options().AllocationUnitSize + } + + /// Gets sector size of the volume. + pub fn sector_size(&self) -> u32 { + self.mount_options().SectorSize + } + + /// Temporarily extend the timeout of the current operation. + /// + /// Returns `true` on success. + #[must_use] + pub fn reset_timeout(&self, timeout: Duration) -> bool { + unsafe { DokanResetTimeout(timeout.as_millis() as u32, self.file_info) == TRUE } + } + + /// Gets the access token associated with the calling process. + /// + /// Returns `None` on error. + pub fn requester_token(&self) -> Option { + unsafe { + let value = DokanOpenRequestorToken(self.file_info); + if value == INVALID_HANDLE_VALUE { + None + } else { + Some(OwnedHandle::from_raw_handle(value)) + } + } + } +} diff --git a/dokan/src/data/volume_info.rs b/dokan/src/data/volume_info.rs new file mode 100644 index 0000000..bd2796a --- /dev/null +++ b/dokan/src/data/volume_info.rs @@ -0,0 +1,33 @@ +use widestring::U16CString; + +/// Information about volume returned by [`FileSystemHandler::get_volume_information`]. +/// +/// [`FileSystemHandler::get_volume_information`]: crate::FileSystemHandler::get_volume_information +#[derive(Debug, Clone)] +pub struct VolumeInfo { + /// Name of the volume. + pub name: U16CString, + + /// Serial number of the volume. + pub serial_number: u32, + + /// The maximum length of a path component that is supported. + pub max_component_length: u32, + + /// The flags associated with the file system. + /// + /// It can be combination of one or more [flags] defined by Windows. + /// + /// `FILE_READ_ONLY_VOLUME` is automatically added if + /// [`MountFlags::WRITE_PROTECT`] was specified when mounting the volume. + /// + /// [flags]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getvolumeinformationw#parameters + /// [`MountFlags::WRITE_PROTECT`]: crate::MountFlags::WRITE_PROTECT + pub fs_flags: u32, + + /// Name of the file system. + /// + /// Windows checks feature availability based on file system name, so it is recommended to set + /// it to well-known names like NTFS or FAT. + pub fs_name: U16CString, +} diff --git a/dokan/src/file_system.rs b/dokan/src/file_system.rs new file mode 100644 index 0000000..7042770 --- /dev/null +++ b/dokan/src/file_system.rs @@ -0,0 +1,346 @@ +use std::{ + error::Error, + fmt::{self, Display, Formatter}, + marker::PhantomData, + mem::transmute, + ptr, + time::Duration, +}; + +use bitflags::bitflags; +use dokan_sys::{ + DokanCloseHandle, DokanCreateFileSystem, DokanWaitForFileSystemClosed, + DOKAN_DRIVER_INSTALL_ERROR, DOKAN_DRIVE_LETTER_ERROR, DOKAN_ERROR, DOKAN_HANDLE, + DOKAN_MOUNT_ERROR, DOKAN_MOUNT_POINT_ERROR, DOKAN_OPERATIONS, DOKAN_OPTIONS, + DOKAN_OPTION_ALLOW_IPC_BATCHING, DOKAN_OPTION_ALT_STREAM, DOKAN_OPTION_CASE_SENSITIVE, + DOKAN_OPTION_CURRENT_SESSION, DOKAN_OPTION_DEBUG, DOKAN_OPTION_DISPATCH_DRIVER_LOGS, + DOKAN_OPTION_ENABLE_UNMOUNT_NETWORK_DRIVE, DOKAN_OPTION_FILELOCK_USER_MODE, + DOKAN_OPTION_MOUNT_MANAGER, DOKAN_OPTION_NETWORK, DOKAN_OPTION_REMOVABLE, DOKAN_OPTION_STDERR, + DOKAN_OPTION_WRITE_PROTECT, DOKAN_START_ERROR, DOKAN_SUCCESS, DOKAN_VERSION_ERROR, + VOLUME_SECURITY_DESCRIPTOR_MAX_SIZE, +}; +use widestring::U16CStr; +use winapi::{shared::ntdef::SCHAR, um::winbase::INFINITE}; + +use crate::{file_system_handler::FileSystemHandler, operations, WRAPPER_VERSION}; + +bitflags! { + /// Flags that control behavior of the mounted volume, as part of [`MountOptions`]. + pub struct MountFlags : u32 { + /// Enable debug message output. + const DEBUG = DOKAN_OPTION_DEBUG; + + /// Write debug messages to stderr. + const STDERR = DOKAN_OPTION_STDERR; + + /// Enable support for alternative streams. + /// + /// The driver will fail any attempts to access a path with a colon (`:`). + const ALT_STREAM = DOKAN_OPTION_ALT_STREAM; + + /// Make the mounted volume write-protected (i.e. read-only). + const WRITE_PROTECT = DOKAN_OPTION_WRITE_PROTECT; + + /// Mount as a network drive. + /// + /// Dokan network provider must be installed for this to work. + const NETWORK = DOKAN_OPTION_NETWORK; + + /// Mount as a removable device. + const REMOVABLE = DOKAN_OPTION_REMOVABLE; + + /// Use Mount Manager to mount the volume. + const MOUNT_MANAGER = DOKAN_OPTION_MOUNT_MANAGER; + + /// Mount the volume on current session only. + const CURRENT_SESSION = DOKAN_OPTION_CURRENT_SESSION; + + /// Use [`FileSystemHandler::lock_file`] and [`FileSystemHandler::unlock_file`] to handle + /// file locking. + /// + /// Dokan will take care of file locking if this flags is not present. + const FILELOCK_USER_MODE = DOKAN_OPTION_FILELOCK_USER_MODE; + + /// Case sensitive path. + /// + /// By default all paths are case insensitive. + /// + /// For case sensitive: `\dir\File` & `\diR\file` are different files, + /// but for case insensitive they are the same. + const CASE_SENSITIVE = DOKAN_OPTION_CASE_SENSITIVE; + + /// Allow unmounting network drives from Windows Explorer. + const ENABLE_UNMOUNT_NETWORK_DRIVE = DOKAN_OPTION_ENABLE_UNMOUNT_NETWORK_DRIVE; + + /// 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; + } +} + +/// Options for [`FileSystemMounter::new`]. +pub struct MountOptions<'a> { + /// Only use a single thread to process events. This is highly not recommended as can easily create a bottleneck. + pub single_thread: bool, + + /// Controls behavior of the volume. + pub flags: MountFlags, + + /// UNC Name for the Network Redirector. + /// + /// See [Support for UNC Naming]. + /// + /// [Support for UNC Naming]: https://msdn.microsoft.com/en-us/library/windows/hardware/ff556761(v=vs.85).aspx + pub unc_name: Option<&'a U16CStr>, + + /// Max timeout of each request before Dokan gives up to wait events to complete. + /// Timeout request is a sign that the userland implementation is no longer able to properly manage requests in time. + /// The driver will therefore unmount the device when a timeout trigger in order to keep the system stable. + /// + /// This timeout can be temporarily extended for an operation with + /// [`OperationInfo::reset_timeout`]. + /// + /// If zero, defaults to 15 seconds. + /// + /// [`OperationInfo::reset_timeout`]: crate::OperationInfo::reset_timeout + pub timeout: Duration, + + /// Allocation Unit Size of the volume. This will affect the file size. + pub allocation_unit_size: u32, + + /// Sector Size of the volume. This will affect the file size. + pub sector_size: u32, + + /// Optional Volume Security descriptor. + /// + /// See [`InitializeSecurityDescriptor`]. + /// + /// [`InitializeSecurityDescriptor`]: https://docs.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-initializesecuritydescriptor + pub volume_security_descriptor: Option<[SCHAR; VOLUME_SECURITY_DESCRIPTOR_MAX_SIZE]>, +} + +impl<'a> Default for MountOptions<'a> { + fn default() -> Self { + Self { + single_thread: Default::default(), + flags: MountFlags::empty(), + unc_name: Default::default(), + timeout: Default::default(), + allocation_unit_size: Default::default(), + sector_size: Default::default(), + volume_security_descriptor: Default::default(), + } + } +} + +/// Error type for [`FileSystemMounter::mount`]. +#[repr(i32)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum FileSystemMountError { + /// A general error. + General = DOKAN_ERROR, + + /// Bad drive letter. + DriveLetter = DOKAN_DRIVE_LETTER_ERROR, + + /// Can't install the Dokan driver. + DriverInstall = DOKAN_DRIVER_INSTALL_ERROR, + + /// The driver responds that something is wrong. + Start = DOKAN_START_ERROR, + + /// Can't assign a drive letter or mount point. + /// + /// This probably means that the mount point is already used by another volume. + Mount = DOKAN_MOUNT_ERROR, + + /// The mount point is invalid. + MountPoint = DOKAN_MOUNT_POINT_ERROR, + + /// The Dokan version that this wrapper is targeting is incompatible with the loaded Dokan + /// library. + Version = DOKAN_VERSION_ERROR, +} + +impl From for FileSystemMountError { + fn from(value: i32) -> Self { + unsafe { transmute(value) } + } +} + +impl Error for FileSystemMountError {} + +impl Display for FileSystemMountError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let msg = match self { + FileSystemMountError::General => "general error", + FileSystemMountError::DriveLetter => "bad drive letter", + FileSystemMountError::DriverInstall => "can't install driver", + FileSystemMountError::Start => "the driver responds that something is wrong", + FileSystemMountError::Mount => "can't assign a drive letter or mount point, probably already used by another volume", + FileSystemMountError::MountPoint => "the mount point is invalid", + FileSystemMountError::Version => "requested an incompatible version", + }; + write!(f, "{}", msg) + } +} + +/// A mounter of [`FileSystem`]. +pub struct FileSystemMounter<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h> { + options: DOKAN_OPTIONS, + operations: DOKAN_OPERATIONS, + phantom_handler: PhantomData<&'h FSH>, + phantom_context: PhantomData<&'c FSH::Context>, +} + +impl<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h> FileSystemMounter<'c, 'h, FSH> { + /// Creates a file system. It should be `mut`, as [`mount`](Self::mount) requires it. + /// + /// # Arguments + /// + /// * `handler` - Implements [`FileSystemHandler`]. + /// * `mount_point`- Can be a driver letter like `"M"` or a folder path `"C:\mount\dokan"` on a NTFS partition. + /// * `options` - Customizes behavior. + pub fn new(handler: &'h FSH, mount_point: &'h U16CStr, options: &'h MountOptions) -> Self { + Self { + options: DOKAN_OPTIONS { + Version: WRAPPER_VERSION as u16, + SingleThread: options.single_thread.into(), + Options: options.flags.bits(), + GlobalContext: handler as *const _ as u64, + MountPoint: mount_point.as_ptr(), + UNCName: match options.unc_name { + Some(s) => s.as_ptr(), + None => ptr::null(), + }, + Timeout: options.timeout.as_millis() as u32, + AllocationUnitSize: options.allocation_unit_size, + SectorSize: options.sector_size, + VolumeSecurityDescriptorLength: match options.volume_security_descriptor { + Some(_) => VOLUME_SECURITY_DESCRIPTOR_MAX_SIZE as u32, + None => 0, + }, + VolumeSecurityDescriptor: match options.volume_security_descriptor { + Some(descriptor) => descriptor, + None => [0; VOLUME_SECURITY_DESCRIPTOR_MAX_SIZE], + }, + }, + operations: DOKAN_OPERATIONS { + ZwCreateFile: Some(operations::create_file::<'c, 'h, FSH>), + Cleanup: Some(operations::cleanup::<'c, 'h, FSH>), + CloseFile: Some(operations::close_file::<'c, 'h, FSH>), + ReadFile: Some(operations::read_file::<'c, 'h, FSH>), + WriteFile: Some(operations::write_file::<'c, 'h, FSH>), + FlushFileBuffers: Some(operations::flush_file_buffers::<'c, 'h, FSH>), + GetFileInformation: Some(operations::get_file_information::<'c, 'h, FSH>), + FindFiles: Some(operations::find_files::<'c, 'h, FSH>), + FindFilesWithPattern: Some(operations::find_files_with_pattern::<'c, 'h, FSH>), + SetFileAttributes: Some(operations::set_file_attributes::<'c, 'h, FSH>), + SetFileTime: Some(operations::set_file_time::<'c, 'h, FSH>), + DeleteFile: Some(operations::delete_file::<'c, 'h, FSH>), + DeleteDirectory: Some(operations::delete_directory::<'c, 'h, FSH>), + MoveFile: Some(operations::move_file::<'c, 'h, FSH>), + SetEndOfFile: Some(operations::set_end_of_file::<'c, 'h, FSH>), + SetAllocationSize: Some(operations::set_allocation_size::<'c, 'h, FSH>), + LockFile: Some(operations::lock_file::<'c, 'h, FSH>), + UnlockFile: Some(operations::unlock_file::<'c, 'h, FSH>), + GetDiskFreeSpace: Some(operations::get_disk_free_space::<'c, 'h, FSH>), + GetVolumeInformation: Some(operations::get_volume_information::<'c, 'h, FSH>), + Mounted: Some(operations::mounted::<'c, 'h, FSH>), + Unmounted: Some(operations::unmounted::<'c, 'h, FSH>), + GetFileSecurity: Some(operations::get_file_security::<'c, 'h, FSH>), + SetFileSecurity: Some(operations::set_file_security::<'c, 'h, FSH>), + FindStreams: Some(operations::find_streams::<'c, 'h, FSH>), + }, + phantom_handler: PhantomData, + phantom_context: PhantomData, + } + } + + /// Mounts the file system. If successful, blocks the current thread until the file system gets unmounted. + pub fn mount(&mut self) -> Result, FileSystemMountError> { + let mut instance = ptr::null_mut(); + + let result = unsafe { + DokanCreateFileSystem(&mut self.options, &mut self.operations, &mut instance) + }; + + if result == DOKAN_SUCCESS { + Ok(FileSystem { + instance, + _pin: PhantomData, + }) + } else { + Err(result.into()) + } + } +} + +/// A successfully mounted file system. +/// +/// When dropped, the current thread will block until the file system gets unmounted. +pub struct FileSystem<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h> { + instance: DOKAN_HANDLE, + _pin: PhantomData<&'h FileSystemMounter<'c, 'h, FSH>>, +} + +impl<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h> FileSystem<'c, 'h, FSH> { + pub fn instance(&self) -> FileSystemHandle { + FileSystemHandle(self.instance) + } +} + +impl<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h> PartialEq for FileSystem<'c, 'h, FSH> { + fn eq(&self, other: &Self) -> bool { + self.instance == other.instance + } +} + +impl<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h> Drop for FileSystem<'c, 'h, FSH> { + fn drop(&mut self) { + unsafe { + DokanWaitForFileSystemClosed(self.instance, INFINITE); + DokanCloseHandle(self.instance); + } + } +} + +#[test] +fn can_fail_to_mount() { + use std::sync::mpsc; + + use crate::{ + init, shutdown, + usage_tests::{convert_str, TestHandler}, + }; + + let (tx, _rx) = mpsc::sync_channel(1024); + + init(); + + { + let mount_point = convert_str("0"); + let handler = TestHandler::new(tx); + let options = Default::default(); + let mut file_system = FileSystemMounter::new(&handler, &mount_point, &options); + match file_system.mount() { + Ok(_) => panic!("file system successfully mounted, but it should not"), + Err(err) => assert_eq!(err, FileSystemMountError::Mount), + }; + } + + shutdown(); +} + +/// A handle to a [`FileSystem`] instance, to be passed to `notify_*` functions. +/// +/// Warning: because it is meant to be sent across threads, the handle bypasses its file system's lifetime. +/// Therefore, ensure you do not use it after the file system is unmounted. +#[derive(Clone, Copy)] +pub struct FileSystemHandle(pub(crate) DOKAN_HANDLE); + +unsafe impl Send for FileSystemHandle {} diff --git a/dokan/src/file_system_handler.rs b/dokan/src/file_system_handler.rs new file mode 100644 index 0000000..af7a3d1 --- /dev/null +++ b/dokan/src/file_system_handler.rs @@ -0,0 +1,483 @@ +use dokan_sys::DOKAN_IO_SECURITY_CONTEXT; +use widestring::U16CStr; +use winapi::{ + shared::{ntdef::NTSTATUS, ntstatus::STATUS_NOT_IMPLEMENTED}, + um::winnt::{ACCESS_MASK, PSECURITY_DESCRIPTOR}, +}; + +use crate::data::{ + CreateFileInfo, DiskSpaceInfo, FileInfo, FileTimeOperation, FillDataResult, FindData, + FindStreamData, OperationInfo, VolumeInfo, +}; + +/// Returned by [`FileSystemHandler`]'s methods. +pub type OperationResult = Result; + +/// Handles operations for a mounted file system. +/// +/// Dokan invokes the callback functions in this trait to handle file system operations. These +/// functions have similar semantics to that of corresponding Windows API functions. +/// +/// Implementation of most callback functions can be omitted by returning `Err(`[`STATUS_NOT_IMPLEMENTED`]`)` +/// if the corresponding feature is not supported. To make things flexible, all of the functions are +/// provided with a default implementation which is a no-op and returns `Err(`[`STATUS_NOT_IMPLEMENTED`]`)` +/// (except [`cleanup`] and [`close_file`] which don't have return values). However, omitting the +/// implementation of some important callbacks such as [`create_file`] will make the file system +/// unusable. +/// +/// `Err` type is [`NTSTATUS`]. Use [`map_win32_error_to_ntstatus`] to convert from Win32 errors +/// (e.g. returned by [`GetLastError`]). +/// +/// [`cleanup`]: Self::cleanup +/// [`close_file`]: Self::close_file +/// [`create_file`]: Self::create_file +/// [`map_win32_error_to_ntstatus`]: crate::map_win32_error_to_ntstatus +/// [`GetLastError`]: winapi::um::errhandlingapi::GetLastError +#[allow(unused_variables)] +pub trait FileSystemHandler<'c, 'h: 'c>: Sync + Sized + 'h { + /// Type of the context associated with an open file object. + type Context: Sync + 'c; + + /// Called when a file object is created. + /// + /// The flags p-them to flags accepted by [`CreateFile`] using the + /// [`map_kernel_to_user_create_file_flags`] helper function. + /// + /// [`ZwCreateFile`]: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-zwcreatefile + /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew + /// [`map_kernel_to_user_create_file_flags`]: crate::map_kernel_to_user_create_file_flags + fn create_file( + &'h self, + file_name: &U16CStr, + security_context: &DOKAN_IO_SECURITY_CONTEXT, + desired_access: ACCESS_MASK, + file_attributes: u32, + share_access: u32, + create_disposition: u32, + create_options: u32, + info: &mut OperationInfo<'c, 'h, Self>, + ) -> OperationResult> { + Err(STATUS_NOT_IMPLEMENTED) + } + + /// Called when the last handle for the file object has been closed. + /// + /// If [`info.delete_on_close`] returns `true`, the file should be deleted in this function. As the function doesn't + /// have a return value, you should make sure the file is deletable in [`delete_file`] or [`delete_directory`]. + /// + /// Note that the file object hasn't been released and there might be more I/O operations before + /// [`close_file`] gets called. (This typically happens when the file is memory-mapped.) + /// + /// Normally [`close_file`] will be called shortly after this function. However, the file object + /// may also be reused, and in that case [`create_file`] will be called instead. + /// + /// [`info.delete_on_close`]: OperationInfo::delete_on_close + /// [`delete_file`]: Self::delete_file + /// [`delete_directory`]: Self::delete_directory + /// [`close_file`]: Self::close_file + /// [`create_file`]: Self::create_file + fn cleanup( + &'h self, + file_name: &U16CStr, + info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) { + } + + /// Called when the last handle for the handle object has been closed and released. + /// + /// This is the last function called during the lifetime of the file object. You can safely + /// release any resources allocated for it (such as file handles, buffers, etc.). The associated + /// [`context`] object will also be dropped once this function returns. In case the file object is + /// reused and thus this function isn't called, the [`context`] will be dropped before + /// [`create_file`] gets called. + /// + /// [`context`]: Self::Context + /// [`create_file`]: Self::create_file + fn close_file( + &'h self, + file_name: &U16CStr, + info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) { + } + + /// Reads data from the file. + /// + /// The number of bytes that actually gets read should be returned. + /// + /// See [`ReadFile`] for more information. + /// + /// [`ReadFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile + fn read_file( + &'h self, + file_name: &U16CStr, + offset: i64, + buffer: &mut [u8], + info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult { + Err(STATUS_NOT_IMPLEMENTED) + } + + /// Writes data to the file. + /// + /// The number of bytes that actually gets written should be returned. + /// + /// If [`info.write_to_eof`] returns `true`, data should be written to the end of file and the + /// `offset` parameter should be ignored. + /// + /// See [`WriteFile`] for more information. + /// + /// [`info.write_to_eof`]: OperationInfo::write_to_eof + /// [`WriteFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile + fn write_file( + &'h self, + file_name: &U16CStr, + offset: i64, + buffer: &[u8], + info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult { + Err(STATUS_NOT_IMPLEMENTED) + } + + /// Flushes the buffer of the file and causes all buffered data to be written to the file. + /// + /// See [`FlushFileBuffers`] for more information. + /// + /// [`FlushFileBuffers`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-flushfilebuffers + fn flush_file_buffers( + &'h self, + file_name: &U16CStr, + info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult<()> { + Err(STATUS_NOT_IMPLEMENTED) + } + + /// Gets information about the file. + /// + /// See [`GetFileInformationByHandle`] for more information. + /// + /// [`GetFileInformationByHandle`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileinformationbyhandle + fn get_file_information( + &'h self, + file_name: &U16CStr, + info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult { + Err(STATUS_NOT_IMPLEMENTED) + } + + /// Lists all child items in the directory. + /// + /// `fill_find_data` should be called for every child item in the directory. + /// + /// It will only be called if [`find_files_with_pattern`] returns [`STATUS_NOT_IMPLEMENTED`]. + /// + /// See [`FindFirstFile`] for more information. + /// + /// [`find_files_with_pattern`]: Self::find_files_with_pattern + /// [`FindFirstFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirstfilew + fn find_files( + &'h self, + file_name: &U16CStr, + fill_find_data: impl FnMut(&FindData) -> FillDataResult, + info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult<()> { + Err(STATUS_NOT_IMPLEMENTED) + } + + /// Lists all child items that matches the specified `pattern` in the directory. + /// + /// `fill_find_data` should be called for every matching child item in the directory. + /// + /// [`is_name_in_expression`] can be used to determine if a file name matches the pattern. + /// + /// If this function returns [`STATUS_NOT_IMPLEMENTED`], [`find_files`] will be called instead and + /// pattern matching will be handled directly by Dokan. + /// + /// See [`FindFirstFile`] for more information. + /// + /// [`is_name_in_expression`]: crate::is_name_in_expression + /// [`find_files`]: Self::find_files + /// [`FindFirstFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirstfilew + fn find_files_with_pattern( + &'h self, + file_name: &U16CStr, + pattern: &U16CStr, + fill_find_data: impl FnMut(&FindData) -> FillDataResult, + info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult<()> { + Err(STATUS_NOT_IMPLEMENTED) + } + + /// Sets attributes of the file. + /// + /// `file_attributes` can be combination of one or more [file attribute constants] defined by + /// Windows. + /// + /// See [`SetFileAttributes`] for more information. + /// + /// [file attribute constants]: https://docs.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants + /// [`SetFileAttributes`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfileattributesw + fn set_file_attributes( + &'h self, + file_name: &U16CStr, + file_attributes: u32, + info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult<()> { + Err(STATUS_NOT_IMPLEMENTED) + } + + /// Sets the time when the file was created, last accessed and last written. + /// + /// See [`SetFileTime`] for more information. + /// + /// [`SetFileTime`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfiletime + fn set_file_time( + &'h self, + file_name: &U16CStr, + creation_time: FileTimeOperation, + last_access_time: FileTimeOperation, + last_write_time: FileTimeOperation, + info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult<()> { + Err(STATUS_NOT_IMPLEMENTED) + } + + /// Checks if the file can be deleted. + /// + /// The file should not be deleted in this function. Instead, it should only check if the file + /// can be deleted and return `Ok` if that is possible. + /// + /// It will also be called with [`info.delete_on_close`] returning `false` to notify that the + /// file is no longer requested to be deleted. + /// + /// [`info.delete_on_close`]: OperationInfo::delete_on_close + fn delete_file( + &'h self, + file_name: &U16CStr, + info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult<()> { + Err(STATUS_NOT_IMPLEMENTED) + } + + /// Checks if the directory can be deleted. + /// + /// Similar to [`delete_file`], it should only check if the directory can be deleted and delay + /// the actual deletion to the [`cleanup`] function. + /// + /// It will also be called with [`info.delete_on_close`] returning `false` to notify that the + /// directory is no longer requested to be deleted. + /// + /// [`delete_file`]: Self::delete_file + /// [`cleanup`]: Self::cleanup + /// [`info.delete_on_close`]: OperationInfo::delete_on_close + fn delete_directory( + &'h self, + file_name: &U16CStr, + info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult<()> { + Err(STATUS_NOT_IMPLEMENTED) + } + + /// Moves the file. + /// + /// If the `new_file_name` already exists, the function should only replace the existing file + /// when `replace_if_existing` is `true`, otherwise it should return appropriate error. + /// + /// Note that renaming is a special kind of moving and is also handled by this function. + /// + /// See [`MoveFileEx`] for more information. + /// + /// [`MoveFileEx`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefileexw + fn move_file( + &'h self, + file_name: &U16CStr, + new_file_name: &U16CStr, + replace_if_existing: bool, + info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult<()> { + Err(STATUS_NOT_IMPLEMENTED) + } + + /// Sets end-of-file position of the file. + /// + /// The `offset` value is zero-based, so it actually refers to the offset to the byte + /// immediately following the last valid byte in the file. + /// + /// See [`FILE_END_OF_FILE_INFORMATION`] for more information. + /// + /// [`FILE_END_OF_FILE_INFORMATION`]: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-_file_end_of_file_information + fn set_end_of_file( + &'h self, + file_name: &U16CStr, + offset: i64, + info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult<()> { + Err(STATUS_NOT_IMPLEMENTED) + } + + /// Sets allocation size of the file. + /// + /// The allocation size is the number of bytes allocated in the underlying physical device for + /// the file. + /// + /// See [`FILE_ALLOCATION_INFORMATION`] for more information. + /// + /// [`FILE_ALLOCATION_INFORMATION`]: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_file_allocation_information + fn set_allocation_size( + &'h self, + file_name: &U16CStr, + alloc_size: i64, + info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult<()> { + Err(STATUS_NOT_IMPLEMENTED) + } + + /// Locks the file for exclusive access. + /// + /// It will only be called if [`MountFlags::FILELOCK_USER_MODE`] was specified when mounting the + /// volume, otherwise Dokan will take care of file locking. + /// + /// See [`LockFile`] for more information. + /// + /// [`MountFlags::FILELOCK_USER_MODE`]: crate::MountFlags::FILELOCK_USER_MODE + /// [`LockFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-lockfile + fn lock_file( + &'h self, + file_name: &U16CStr, + offset: i64, + length: i64, + info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult<()> { + Err(STATUS_NOT_IMPLEMENTED) + } + + /// Unlocks the previously locked file. + /// + /// It will only be called if [`MountFlags::FILELOCK_USER_MODE`] was specified when mounting the + /// volume, otherwise Dokan will take care of file locking. + /// + /// See [`UnlockFile`] for more information. + /// + /// [`MountFlags::FILELOCK_USER_MODE`]: crate::MountFlags::FILELOCK_USER_MODE + /// [`UnlockFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-unlockfile + fn unlock_file( + &'h self, + file_name: &U16CStr, + offset: i64, + length: i64, + info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult<()> { + Err(STATUS_NOT_IMPLEMENTED) + } + + /// Gets free space information about the disk. + /// + /// See [`GetDiskFreeSpaceEx`] for more information. + /// + /// [`GetDiskFreeSpaceEx`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getdiskfreespaceexw + fn get_disk_free_space( + &'h self, + info: &OperationInfo<'c, 'h, Self>, + ) -> OperationResult { + Err(STATUS_NOT_IMPLEMENTED) + } + + /// Gets information about the volume and file system. + /// + /// See [`GetVolumeInformation`] for more information. + /// + /// [`GetVolumeInformation`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getvolumeinformationbyhandlew + fn get_volume_information( + &'h self, + info: &OperationInfo<'c, 'h, Self>, + ) -> OperationResult { + Err(STATUS_NOT_IMPLEMENTED) + } + + /// Called when Dokan has successfully mounted the volume. + fn mounted( + &'h self, + mount_point: &U16CStr, + info: &OperationInfo<'c, 'h, Self>, + ) -> OperationResult<()> { + Err(STATUS_NOT_IMPLEMENTED) + } + + /// Called when Dokan is unmounting the volume. + fn unmounted(&'h self, info: &OperationInfo<'c, 'h, Self>) -> OperationResult<()> { + Err(STATUS_NOT_IMPLEMENTED) + } + + /// Gets security information of a file. + /// + /// Size of the security descriptor in bytes should be returned on success. If the buffer is not + /// large enough, the number should still be returned, and [`STATUS_BUFFER_OVERFLOW`] will be + /// automatically passed to Dokan if it is larger than `buffer_length`. + /// + /// See [`GetFileSecurity`] for more information. + /// + /// [`STATUS_BUFFER_OVERFLOW`]: winapi::shared::ntstatus::STATUS_BUFFER_OVERFLOW + /// [`GetFileSecurity`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getfilesecuritya + fn get_file_security( + &'h self, + file_name: &U16CStr, + security_information: u32, + security_descriptor: PSECURITY_DESCRIPTOR, + buffer_length: u32, + info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult { + Err(STATUS_NOT_IMPLEMENTED) + } + + /// Sets security information of a file. + /// + /// See [`SetFileSecurity`] for more information. + /// + /// [`SetFileSecurity`]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setfilesecuritya + fn set_file_security( + &'h self, + file_name: &U16CStr, + security_information: u32, + security_descriptor: PSECURITY_DESCRIPTOR, + buffer_length: u32, + info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult<()> { + Err(STATUS_NOT_IMPLEMENTED) + } + + /// Lists all alternative streams of the file. + /// + /// `fill_find_stream_data` should be called for every stream of the file, including the default + /// data stream `::$DATA`. + /// + /// See [`FindFirstStream`] for more information. + /// + /// [`FindFirstStream`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirststreamw + fn find_streams( + &'h self, + file_name: &U16CStr, + fill_find_stream_data: impl FnMut(&FindStreamData) -> FillDataResult, + info: &OperationInfo<'c, 'h, Self>, + context: &'c Self::Context, + ) -> OperationResult<()> { + Err(STATUS_NOT_IMPLEMENTED) + } +} diff --git a/dokan/src/lib.rs b/dokan/src/lib.rs index 5f88a8d..feca09e 100644 --- a/dokan/src/lib.rs +++ b/dokan/src/lib.rs @@ -5,63 +5,54 @@ //! new file systems on Windows. //! //! This crate is a Rust-friendly wrapper for Dokan, allowing you to create file systems using Rust. +//! It builds upon the low-level [`dokan-sys`] crate. //! //! In general, to create a file system with this library, you need to implement the -//! [`FileSystemHandler`] trait, and pass it to [`Drive::mount`]. +//! [`FileSystemHandler`] trait, create a [`FileSystemMounter`], and [mount](FileSystemMounter::mount) it +//! to create a [`FileSystem`]. When dropped, the latter will block the current thread until it gets unmounted. +//! You have to call [`init`] once before, and [`shutdown`] when you're done. +//! +//! The same explanations with a few lines of code: see [the MemFS example](https://github.com/dokan-dev/dokan-rust/blob/master/dokan/examples/memfs/main.rs#L1330)! //! //! Please note that some of the constants from Win32 API that might be used when interacting with //! this crate are not provided directly here. However, you can easily find them in the -//! [winapi] crate. +//! [`winapi`] crate. //! //! [Dokan]: https://dokan-dev.github.io/ -//! [`FileSystemHandler`]: trait.FileSystemHandler.html -//! [`Drive::mount`]: struct.Drive.html#method.mount -//! [winapi]: https://crates.io/crates/winapi +//! [`dokan-sys`]: https://crates.io/crates/dokan-sys +//! [`winapi`]: https://crates.io/crates/winapi -#[macro_use] -extern crate bitflags; -extern crate dokan_sys; -extern crate widestring; +mod data; +mod file_system; +mod file_system_handler; +mod notify; +mod operations; +mod operations_helpers; +mod to_file_time; #[cfg(test)] -mod tests; - -use std::error::Error; -use std::fmt::{self, Display, Formatter}; -use std::marker::PhantomData; -use std::os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle}; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use std::{mem, panic, ptr, slice}; - -use dokan_sys::{win32::*, *}; -use widestring::{U16CStr, U16CString}; -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, PVOID}; -use winapi::shared::ntstatus::{ - STATUS_BUFFER_OVERFLOW, STATUS_INTERNAL_ERROR, STATUS_NOT_IMPLEMENTED, - STATUS_OBJECT_NAME_COLLISION, STATUS_SUCCESS, +mod usage_tests; + +use dokan_sys::*; +use widestring::U16CStr; +use winapi::{ + shared::{ + minwindef::{DWORD, FALSE, TRUE}, + ntdef::NTSTATUS, + }, + um::{errhandlingapi::GetLastError, winnt::ACCESS_MASK}, }; -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}; +pub use crate::{data::*, file_system::*, file_system_handler::*, notify::*}; -/// Name of Dokan's kernel driver file. -pub use dokan_sys::DOKAN_DRIVER_NAME as DRIVER_NAME; -/// The major version number of Dokan that this wrapper is targeting. -pub use dokan_sys::DOKAN_MAJOR_API_VERSION as MAJOR_API_VERSION; -/// Name of Dokan's network provider. -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; +/// Re-exported from `dokan-sys` for convenience. +pub use dokan_sys::{ + DOKAN_DRIVER_NAME as DRIVER_NAME, DOKAN_IO_SECURITY_CONTEXT as IO_SECURITY_CONTEXT, + DOKAN_MAJOR_API_VERSION as MAJOR_API_VERSION, DOKAN_NP_NAME as NP_NAME, + DOKAN_VERSION as WRAPPER_VERSION, +}; -/// Initialize all required Dokan internal resources. +/// Initializes 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. @@ -69,7 +60,7 @@ pub fn init() { unsafe { DokanInit() } } -/// Release all allocated resources by \ref DokanInit when they are no longer needed. +/// Releases all allocated resources by [`init`] 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() { @@ -80,25 +71,32 @@ pub fn shutdown() { /// /// The returned value is the version number without dots. For example, it returns `131` if Dokan /// v1.3.1 is loaded. -pub fn lib_version() -> u32 { +pub fn get_lib_version() -> u32 { unsafe { DokanVersion() } } /// Gets version of the Dokan driver installed on the current system. /// /// The returned value is the version number without dots. -pub fn driver_version() -> u32 { +pub fn get_driver_version() -> u32 { unsafe { DokanDriverVersion() } } +#[test] +fn test_versions() { + assert_eq!(MAJOR_API_VERSION, (get_lib_version() / 100).to_string()); + assert!(get_driver_version() < 1000); + assert_eq!(DRIVER_NAME, format!("dokan{}.sys", MAJOR_API_VERSION)); + assert_eq!(NP_NAME, format!("Dokan{}", MAJOR_API_VERSION)); +} + /// Checks whether the `name` matches the specified `expression`. /// /// This is a helper function that can be used to implement -/// [`FileSystemHandler::find_files_with_pattern`]. It behaves like the [FsRtlIsNameInExpression] +/// [`FileSystemHandler::find_files_with_pattern`]. It behaves like the [`FsRtlIsNameInExpression`] /// routine provided for file system drivers by Windows. /// -/// [`FileSystemHandler::find_files_with_pattern`]: trait.FileSystemHandler.html#method.find_files_with_pattern -/// [FsRtlIsNameInExpression]: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-_fsrtl_advanced_fcb_header-fsrtlisnameinexpression +/// [`FsRtlIsNameInExpression`]: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-_fsrtl_advanced_fcb_header-fsrtlisnameinexpression pub fn is_name_in_expression( expression: impl AsRef, name: impl AsRef, @@ -113,12 +111,95 @@ pub fn is_name_in_expression( } } -/// The flags returned by [`map_kernel_to_user_create_file_flags`]. -/// -/// These flags are the same as those accepted by [CreateFile]. -/// -/// [`map_kernel_to_user_create_file_flags`]: fn.map_kernel_to_user_create_file_flags.html -/// [CreateFile]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew +#[test] +fn test_is_name_in_expression() { + use usage_tests::convert_str; + + assert_eq!( + is_name_in_expression(convert_str("foo"), convert_str("foo"), true), + true + ); + assert_eq!( + is_name_in_expression(convert_str("*"), convert_str("foo"), true), + true + ); + assert_eq!( + is_name_in_expression(convert_str("?"), convert_str("x"), true), + true + ); + assert_eq!( + is_name_in_expression(convert_str("?"), convert_str("foo"), true), + false + ); + assert_eq!( + is_name_in_expression(convert_str("F*"), convert_str("foo"), true), + true + ); + assert_eq!( + is_name_in_expression(convert_str("F*"), convert_str("foo"), false), + false + ); +} + +/// Converts Win32 error (e.g. returned by [`GetLastError`]) to [`NTSTATUS`]. +pub fn map_win32_error_to_ntstatus(error: DWORD) -> NTSTATUS { + unsafe { DokanNtStatusFromWin32(error) } +} + +#[test] +fn can_map_win32_error_to_ntstatus() { + use winapi::shared::{ntstatus::STATUS_INTERNAL_ERROR, winerror::ERROR_INTERNAL_ERROR}; + + assert_eq!( + map_win32_error_to_ntstatus(ERROR_INTERNAL_ERROR), + STATUS_INTERNAL_ERROR + ); +} + +/// For convenience, returns an `Err(`[`NTSTATUS`]`)` from [`GetLastError`] if the condition is `false`. +/// +/// It builds upon [`map_win32_error_to_ntstatus`]. +/// +/// **Warning**: success of some functions can only be known by checking `GetLastError`. +/// In such cases, **do not use this function!** +/// For instance, `ReadFile` and `WriteFile` in asynchronous mode are successful if they +/// return `FALSE` and `GetLastError` returns `ERROR_IO_PENDING`. +/// +/// # Example +/// +/// ``` +/// # use std::ptr; +/// # +/// # use dokan::win32_ensure; +/// # use widestring::U16CString; +/// # use winapi::{shared::ntdef::NTSTATUS, um::processenv::GetCurrentDirectoryW}; +/// # +/// fn get_current_directory() -> Result { +/// unsafe { +/// let len = GetCurrentDirectoryW(0, ptr::null_mut()); +/// win32_ensure(len != 0)?; +/// +/// let mut buffer = Vec::with_capacity(len as usize); +/// let actual_len = GetCurrentDirectoryW(len, buffer.as_mut_ptr()); +/// win32_ensure(actual_len != 0)?; +/// assert_eq!(actual_len, len); +/// +/// Ok(U16CString::from_vec_with_nul_unchecked(buffer)) +/// } +/// } +/// ``` +pub fn win32_ensure(condition: bool) -> Result<(), NTSTATUS> { + match condition { + true => Ok(()), + false => Err(map_win32_error_to_ntstatus(unsafe { GetLastError() })), + } +} + +/// Flags returned by [`map_kernel_to_user_create_file_flags`]. +/// +/// These flags are the same as those accepted by [`CreateFile`]. +/// +/// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew #[derive(Debug, Clone, Eq, PartialEq)] pub struct UserCreateFileFlags { /// The requested access to the file. @@ -130,14 +211,13 @@ pub struct UserCreateFileFlags { } /// Converts the arguments passed to [`FileSystemHandler::create_file`] to flags accepted by the -/// Win32 [CreateFile] function. +/// Win32 [`CreateFile`] function. /// -/// Dokan forwards the parameters directly from [IRP_MJ_CREATE]. This functions converts them to +/// Dokan forwards the parameters directly from [`IRP_MJ_CREATE`]. This functions converts them to /// corresponding flags in Win32, making it easier to process them. /// -/// [`FileSystemHandler::create_file`]: trait.FileSystemHandler.html#method.create_file -/// [CreateFile]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew -/// [IRP_MJ_CREATE]: https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/irp-mj-create +/// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew +/// [`IRP_MJ_CREATE`]: https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/irp-mj-create pub fn map_kernel_to_user_create_file_flags( desired_access: ACCESS_MASK, file_attributes: u32, @@ -163,169 +243,46 @@ pub fn map_kernel_to_user_create_file_flags( result } -/// Unmount a Dokan volume from the specified mount point. -/// -/// Returns `true` on success. +#[test] +fn test_map_kernel_to_user_create_file_flags() { + use dokan_sys::win32::{FILE_OPEN, FILE_WRITE_THROUGH}; + use winapi::um::{ + fileapi::OPEN_EXISTING, + winbase::FILE_FLAG_WRITE_THROUGH, + winnt::{ + FILE_ALL_ACCESS, FILE_ATTRIBUTE_NORMAL, GENERIC_ALL, GENERIC_EXECUTE, GENERIC_READ, + GENERIC_WRITE, + }, + }; + + let result = map_kernel_to_user_create_file_flags( + FILE_ALL_ACCESS, + FILE_ATTRIBUTE_NORMAL, + FILE_WRITE_THROUGH, + FILE_OPEN, + ); + assert_eq!( + result.desired_access, + GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE | GENERIC_ALL + ); + assert_eq!( + result.flags_and_attributes, + FILE_FLAG_WRITE_THROUGH | FILE_ATTRIBUTE_NORMAL + ); + assert_eq!(result.creation_disposition, OPEN_EXISTING); +} + +/// Unmounts a Dokan volume from the specified mount point. +/// +/// Returns whether it succeeded. #[must_use] pub fn unmount(mount_point: impl AsRef) -> bool { unsafe { DokanRemoveMountPoint(mount_point.as_ref().as_ptr()) == TRUE } } -/// Mount point information. -#[derive(Debug, Clone)] -pub struct MountPointInfo { - /// File system type of the mounted volume. - /// - /// Value can be `FILE_DEVICE_DISK_FILE_SYSTEM` or `FILE_DEVICE_NETWORK_FILE_SYSTEM`, which are - /// defined in `ntifs.h`. - pub device_type: u32, - - /// Mount point path. - pub mount_point: Option, - - /// UNC name of the network volume. - pub unc_name: Option, - - /// Device name of the mounted volume. - pub device_name: U16CString, - - /// The session in which the volume is mounted. - /// - /// It will be `-1` if the volume is mounted globally. - pub session_id: u32, -} - -struct MountPointListWrapper { - list_ptr: PDOKAN_MOUNT_POINT_INFO, -} - -impl Drop for MountPointListWrapper { - fn drop(&mut self) { - if !self.list_ptr.is_null() { - unsafe { - DokanReleaseMountPointList(self.list_ptr); - } - } - } -} - -/// Gets a list of active Dokan mount points. -/// -/// Returns `None` in case of error. -pub fn get_mount_point_list(unc_only: bool) -> Option> { - unsafe { - let mut count: ULONG = 0; - let ffi_list = MountPointListWrapper { - list_ptr: DokanGetMountPointList(unc_only.into(), &mut count), - }; - if ffi_list.list_ptr.is_null() { - None - } else { - let count = count as usize; - let mut list = Vec::with_capacity(count); - for control in slice::from_raw_parts(ffi_list.list_ptr, count) { - let mount_point = if control.MountPoint[0] == 0 { - None - } else { - Some( - U16CStr::from_slice_with_nul(&control.MountPoint) - .unwrap() - .to_owned(), - ) - }; - let unc_name = if control.UNCName[0] == 0 { - None - } else { - Some( - U16CStr::from_slice_with_nul(&control.UNCName) - .unwrap() - .to_owned(), - ) - }; - list.push(MountPointInfo { - device_type: control.Type, - mount_point, - unc_name, - device_name: U16CStr::from_slice_with_nul(&control.DeviceName) - .unwrap() - .to_owned(), - session_id: control.SessionId, - }) - } - Some(list) - } - } -} - -/// Notifies Dokan that a file or directory has been created. -/// -/// Returns `true` on success. -#[must_use] -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( - 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(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(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. -/// -/// `is_same_dir` indicates if the new file or directory is in the same directory as the old one. -/// -/// 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, - is_same_dir: bool, -) -> bool { - unsafe { - DokanNotifyRename( - dokan_instance, - old_path.as_ref().as_ptr(), - new_path.as_ref().as_ptr(), - is_dir.into(), - is_same_dir.into(), - ) == TRUE - } -} - -/// The output stream to write debug messages to. +/// Output stream to write debug messages to. /// /// Used by [`set_debug_stream`]. -/// -/// [`set_debug_stream`]: fn.set_debug_stream.html pub enum DebugStream { /// The standard output stream. Stdout, @@ -333,7 +290,7 @@ pub enum DebugStream { Stderr, } -/// Set the output stream to write debug messages to. +/// Sets the output stream to write debug messages to. pub fn set_debug_stream(stream: DebugStream) { unsafe { DokanUseStdErr(if let DebugStream::Stdout = stream { @@ -344,14 +301,14 @@ pub fn set_debug_stream(stream: DebugStream) { } } -/// Enable or disable debug mode of the user mode library. +/// Enables or disables debug mode of the user mode library. pub fn set_lib_debug_mode(enabled: bool) { unsafe { DokanDebugMode(if enabled { TRUE } else { FALSE }); } } -/// Enable or disable debug mode of the kernel driver; +/// Enables or disables debug mode of the kernel driver; /// /// Returns `true` on success. #[must_use] @@ -359,1825 +316,12 @@ pub fn set_driver_debug_mode(enabled: bool) -> bool { unsafe { DokanSetDebugMode(if enabled { TRUE } else { FALSE }) == TRUE } } -bitflags! { - /// Flags that control behavior of the mounted volume. - pub struct MountFlags : u32 { - /// Enable debug message output. - const DEBUG = DOKAN_OPTION_DEBUG; - - /// Write debug messages to stderr. - const STDERR = DOKAN_OPTION_STDERR; - - /// Enable support for alternative streams. - /// - /// The driver will fail any attempts to access a path with a colon (`:`). - const ALT_STREAM = DOKAN_OPTION_ALT_STREAM; - - /// Make the mounted volume write-protected (i.e. read-only). - const WRITE_PROTECT = DOKAN_OPTION_WRITE_PROTECT; - - /// Mount as a network drive. - /// - /// Dokan network provider must be installed for this to work. - const NETWORK = DOKAN_OPTION_NETWORK; - - /// Mount as a removable device. - const REMOVABLE = DOKAN_OPTION_REMOVABLE; - - /// Use Mount Manager to mount the volume. - const MOUNT_MANAGER = DOKAN_OPTION_MOUNT_MANAGER; - - /// Mount the volume on current session only. - const CURRENT_SESSION = DOKAN_OPTION_CURRENT_SESSION; - - /// Use [`FileSystemHandler::lock_file`] and [`FileSystemHandler::unlock_file`] to handle - /// file locking. - /// - /// Dokan will take care of file locking if this flags is not present. - /// - /// [`FileSystemHandler::lock_file`]: trait.FileSystemHandler.html#method.lock_file - /// [`FileSystemHandler::unlock_file`]: trait.FileSystemHandler.html#method.unlock_file - const FILELOCK_USER_MODE = DOKAN_OPTION_FILELOCK_USER_MODE; - - /// Enable notification API support. - /// - /// Notification functions like [`notify_create`] require this flag to be present, otherwise - /// they will always fail and return `false`. - /// - /// [`notify_create`]: fn.notify_create.html - const CASE_SENSITIVE = DOKAN_OPTION_CASE_SENSITIVE; - - /// Allow unmounting network drives from Windows Explorer. - const ENABLE_UNOUNT_NETWORK_DRIVE = DOKAN_OPTION_ENABLE_UNMOUNT_NETWORK_DRIVE; - - /// 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; - } -} - -/// A simple wrapper struct that holds the Win32 handle returned by -/// [`OperationInfo::requester_token`]. -/// -/// It calls [CloseHandle] automatically when dropped. -/// -/// [`OperationInfo::requester_token`]: struct.OperationInfo.html#method.requester_token -/// [CloseHandle]: https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle -#[derive(Debug, Eq, PartialEq)] -pub struct TokenHandle { - value: HANDLE, -} - -impl AsRawHandle for TokenHandle { - fn as_raw_handle(&self) -> HANDLE { - self.value - } -} - -impl FromRawHandle for TokenHandle { - unsafe fn from_raw_handle(handle: HANDLE) -> TokenHandle { - TokenHandle { value: handle } - } -} - -impl IntoRawHandle for TokenHandle { - fn into_raw_handle(mut self) -> HANDLE { - let value = self.value; - self.value = INVALID_HANDLE_VALUE; - value - } -} - -impl Drop for TokenHandle { - fn drop(&mut self) { - if self.value != INVALID_HANDLE_VALUE { - unsafe { - CloseHandle(self.value); - } - } - } -} - -/// Information about the current operation. -#[derive(Debug)] -pub struct OperationInfo<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b> { - file_info: PDOKAN_FILE_INFO, - phantom_handler: PhantomData<&'b T>, - phantom_context: PhantomData<&'a T::Context>, -} - -impl<'a, 'b: 'a, 'c: 'b, T: FileSystemHandler<'b, 'c> + 'c> OperationInfo<'b, 'c, T> { - fn new(file_info: PDOKAN_FILE_INFO) -> Self { - OperationInfo { - file_info, - phantom_handler: PhantomData, - phantom_context: PhantomData, - } - } - - fn file_info(&self) -> &DOKAN_FILE_INFO { - unsafe { &*self.file_info } - } - - fn mount_options(&self) -> &DOKAN_OPTIONS { - unsafe { &*self.file_info().DokanOptions } - } - - fn handler(&'a self) -> &'c T { - unsafe { &*(self.mount_options().GlobalContext as *const _) } - } - - fn context(&'a self) -> &'b T::Context { - unsafe { &*(self.file_info().Context as *const _) } - } - - fn drop_context(&mut self) { - unsafe { - let info = &mut *self.file_info; - let ptr = info.Context as *mut T::Context; - if !ptr.is_null() { - mem::drop(Box::from_raw(ptr)); - info.Context = 0; - } - } - } - - /// Gets process ID of the calling process. - pub fn pid(&self) -> u32 { - self.file_info().ProcessId - } - - /// Gets whether the target file is a directory. - pub fn is_dir(&self) -> bool { - self.file_info().IsDirectory != 0 - } - - /// Gets whether the file should be deleted when it is closed. - pub fn delete_on_close(&self) -> bool { - self.file_info().DeleteOnClose != 0 - } - - /// Gets whether it is a paging I/O operation. - pub fn paging_io(&self) -> bool { - self.file_info().PagingIo != 0 - } - - /// Gets whether it is a synchronous I/O operation. - pub fn synchronous_io(&self) -> bool { - self.file_info().SynchronousIo != 0 - } - - /// Gets whether it is a non-cached I/O operation. - pub fn no_cache(&self) -> bool { - self.file_info().Nocache != 0 - } - - /// Gets whether the current write operation should write to end of file instead of the - /// position specified by the offset argument. - pub fn write_to_eof(&self) -> bool { - self.file_info().WriteToEndOfFile != 0 - } - - /// Gets the number of threads used to handle file system operations. - pub fn single_thread(&self) -> bool { - self.mount_options().SingleThread != 0 - } - - /// Gets flags that controls behavior of the mounted volume. - pub fn mount_flags(&self) -> MountFlags { - MountFlags::from_bits_truncate(self.mount_options().Options) - } - - /// Gets mount point path. - pub fn mount_point(&self) -> Option<&U16CStr> { - let ptr = self.mount_options().MountPoint; - if ptr.is_null() { - None - } else { - unsafe { Some(U16CStr::from_ptr_str(ptr)) } - } - } - - /// Gets UNC name of the network drive. - pub fn unc_name(&self) -> Option<&U16CStr> { - let ptr = self.mount_options().UNCName; - if ptr.is_null() { - None - } else { - unsafe { Some(U16CStr::from_ptr_str(ptr)) } - } - } - - /// Gets the time that Dokan will wait for an operation to complete. - /// - /// See [`Drive::timeout`] for more information. - /// - /// [`Drive::timeout`]: struct.Drive.html#method.timeout - pub fn timeout(&self) -> Duration { - Duration::from_millis(self.mount_options().Timeout.into()) - } - - /// Gets allocation unit size of the volume. - pub fn allocation_unit_size(&self) -> u32 { - self.mount_options().AllocationUnitSize - } - - /// Gets sector size of the volume. - pub fn sector_size(&self) -> u32 { - self.mount_options().SectorSize - } - - /// Temporarily extend the timeout of the current operation. - /// - /// Returns `true` on success. - #[must_use] - pub fn reset_timeout(&self, timeout: Duration) -> bool { - unsafe { DokanResetTimeout(timeout.as_millis() as u32, self.file_info) == TRUE } - } - - /// Gets the access token associated with the calling process. - /// - /// Returns `None` on error. - pub fn requester_token(&self) -> Option { - unsafe { - let value = DokanOpenRequestorToken(self.file_info); - if value == INVALID_HANDLE_VALUE { - None - } else { - Some(TokenHandle::from_raw_handle(value)) - } - } - } -} - -/// The error type for callbacks of [`FileSystemHandler`]. -/// -/// This enum represents either an NTSTATUS code or a Win32 error code. Dokan only accepts NTSTATUS -/// codes, so if a Win32 error code is present, it will be automatically converted to the -/// corresponding NTSTATUS value. -/// -/// Note that although `STATUS_SUCCESS` and `ERROR_SUCCESS` are used to indicate successes in the -/// Windows world, they are not expected to appear in this enum and will be converted to -/// `STATUS_INTERNAL_ERROR` if detected. This error type is always used along with `Result`s in this -/// crate and `Ok` should be returned to indicate successes instead. -/// -/// [`FileSystemHandler`]: trait.FileSystemHandler.html -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum OperationError { - NtStatus(NTSTATUS), - Win32(DWORD), -} - -impl Error for OperationError {} - -impl Display for OperationError { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "Dokan operation failed: ")?; - match self { - OperationError::NtStatus(e) => write!(f, "NTSTATUS 0x{:08x}", e), - OperationError::Win32(e) => write!( - f, - "Win32 error {} (converted to NTSTATUS 0x{:08x})", - e, - self.ntstatus() - ), - } - } -} - -impl OperationError { - pub fn ntstatus(&self) -> NTSTATUS { - let status = match self { - OperationError::NtStatus(e) => *e, - OperationError::Win32(e) => unsafe { DokanNtStatusFromWin32(*e) }, - }; - match status { - STATUS_SUCCESS => STATUS_INTERNAL_ERROR, - _ => status, - } - } -} - -trait OperationResultExt { - fn ntstatus(&self) -> NTSTATUS; -} - -impl OperationResultExt for Result { - fn ntstatus(&self) -> NTSTATUS { - match self { - Ok(_) => STATUS_SUCCESS, - Err(e) => e.ntstatus(), - } - } -} - -const FILETIME_OFFSET: Duration = Duration::from_secs(11644473600); - -trait ToFileTime { - fn to_filetime(&self) -> FILETIME; -} - -impl ToFileTime for SystemTime { - fn to_filetime(&self) -> FILETIME { - let intervals = self - .duration_since(UNIX_EPOCH - FILETIME_OFFSET) - .unwrap_or(Duration::from_secs(0)) - .as_nanos() / 100; - FILETIME { - dwLowDateTime: intervals as u32, - dwHighDateTime: (intervals >> 32) as u32, - } - } -} - -/// The operation to perform on a file's corresponding time information. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum FileTimeInfo { - /// Set corresponding time information of the file. - SetTime(SystemTime), - /// Don't change corresponding time information of the file. - DontChange, - /// Disable update of corresponding time information caused by further operations on the file handle. - DisableUpdate, - /// Resume update of corresponding time information caused by further operations on the file handle. - ResumeUpdate, -} - -impl FileTimeInfo { - fn from_filetime(time: FILETIME) -> Self { - unsafe { - let time_val = mem::transmute_copy::<_, i64>(&time); - match time_val { - 0 => FileTimeInfo::DontChange, - -1 => FileTimeInfo::DisableUpdate, - -2 => FileTimeInfo::ResumeUpdate, - _ => { - let time_val = time_val as u64; - FileTimeInfo::SetTime( - UNIX_EPOCH - FILETIME_OFFSET - + Duration::from_micros(time_val / 10) - + Duration::from_nanos(time_val % 10 * 100), - ) - } - } - } - } -} - -/// The file information returned by [`FileSystemHandler::get_file_information`]. -/// -/// [`FileSystemHandler::get_file_information`]: trait.FileSystemHandler.html#method.get_file_information -#[derive(Debug, Clone)] -pub struct FileInfo { - /// Attribute flags of the files. - /// - /// It can be combination of one or more [file attribute constants] defined by Windows. - /// - /// [file attribute constants]: https://docs.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants - pub attributes: u32, - - /// The time when the file was created. - pub creation_time: SystemTime, - - /// The time when the file was last accessed. - pub last_access_time: SystemTime, - - /// The time when the file was last written to. - pub last_write_time: SystemTime, - - /// Size of the file. - pub file_size: u64, - - /// Number of hardlinks to the file. - pub number_of_links: u32, - - /// The index that uniquely identifies the file in a volume. - pub file_index: u64, -} - -impl FileInfo { - fn to_raw_struct(&self) -> BY_HANDLE_FILE_INFORMATION { - BY_HANDLE_FILE_INFORMATION { - dwFileAttributes: self.attributes, - ftCreationTime: self.creation_time.to_filetime(), - ftLastAccessTime: self.last_access_time.to_filetime(), - ftLastWriteTime: self.last_write_time.to_filetime(), - dwVolumeSerialNumber: 0, - nFileSizeHigh: (self.file_size >> 32) as u32, - nFileSizeLow: self.file_size as u32, - nNumberOfLinks: self.number_of_links, - nFileIndexHigh: (self.file_index >> 32) as u32, - nFileIndexLow: self.file_index as u32, - } - } -} - -trait ToRawStruct { - fn to_raw_struct(&self) -> Option; -} - -/// File information provided by [`FileSystemHandler::find_files`] or -/// [`FileSystemHandler::find_files_with_pattern`]. -/// -/// [`FileSystemHandler::find_files`]: trait.FileSystemHandler.html#method.find_files -/// [`FileSystemHandler::find_files_with_pattern`]: trait.FileSystemHandler.html#method.find_files_with_pattern -#[derive(Debug, Clone)] -pub struct FindData { - /// Attribute flags of the files. - /// - /// It can be combination of one or more [file attribute constants] defined by Windows. - /// - /// [file attribute constants]: https://docs.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants - pub attributes: u32, - - /// The time when the file was created. - pub creation_time: SystemTime, - - /// The time when the file was last accessed. - pub last_access_time: SystemTime, - - /// The time when the file was last written to. - pub last_write_time: SystemTime, - - /// Size of the file. - pub file_size: u64, - - /// Name of the file. - pub file_name: U16CString, -} - -impl ToRawStruct for FindData { - fn to_raw_struct(&self) -> Option { - let mut data = WIN32_FIND_DATAW { - dwFileAttributes: self.attributes, - ftCreationTime: self.creation_time.to_filetime(), - ftLastAccessTime: self.last_access_time.to_filetime(), - ftLastWriteTime: self.last_write_time.to_filetime(), - nFileSizeHigh: (self.file_size >> 32) as u32, - nFileSizeLow: self.file_size as u32, - dwReserved0: 0, - dwReserved1: 0, - cFileName: [0; MAX_PATH], - cAlternateFileName: [0; 14], - }; - let name_slice = self.file_name.as_slice_with_nul(); - if name_slice.len() <= data.cFileName.len() { - data.cFileName[..name_slice.len()].copy_from_slice(name_slice); - Some(data) - } else { - None - } - } -} - -/// Alternative stream information provided by [`FileSystemHandler::find_streams`]. -/// -/// [`FileSystemHandler::find_streams`]: trait.FileSystemHandler.html#method.find_streams -#[derive(Debug, Clone)] -pub struct FindStreamData { - /// Size of the stream. - pub size: i64, - - /// Name of stream. - /// - /// The format of this name should be `:streamname:$streamtype`. See [NTFS Streams] for more - /// information. - /// - /// [NTFS Streams]: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/c54dec26-1551-4d3a-a0ea-4fa40f848eb3 - pub name: U16CString, -} - -impl ToRawStruct for FindStreamData { - fn to_raw_struct(&self) -> Option { - let mut data = WIN32_FIND_STREAM_DATA { - StreamSize: unsafe { mem::transmute(self.size) }, - cStreamName: [0; MAX_PATH + 36], - }; - let name_slice = self.name.as_slice_with_nul(); - if name_slice.len() <= data.cStreamName.len() { - data.cStreamName[..name_slice.len()].copy_from_slice(name_slice); - Some(data) - } else { - None - } - } -} - -/// The error type for the fill-data callbacks. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum FillDataError { - /// File name exceeds the limit of `MAX_PATH`. - NameTooLong, - - /// Buffer is full. - BufferFull, -} - -impl Error for FillDataError {} - -impl Display for FillDataError { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let msg = match self { - FillDataError::NameTooLong => "File name exceeds the limit of MAX_PATH.", - FillDataError::BufferFull => "Buffer is full.", - }; - write!(f, "{}", msg) - } -} - -impl From for OperationError { - fn from(_: FillDataError) -> OperationError { - OperationError::NtStatus(STATUS_INTERNAL_ERROR) - } -} - -/// Disk space information returned by [`FileSystemHandler::get_disk_free_space`]. -/// -/// [`FileSystemHandler::get_disk_free_space`]: trait.FileSystemHandler.html#method.get_disk_free_space -#[derive(Debug, Clone)] -pub struct DiskSpaceInfo { - /// Total number of bytes that are available to the calling user. - pub byte_count: u64, - - /// Total number of free bytes on the disk. - pub free_byte_count: u64, - - /// Total number of free bytes that are available to the calling user. - pub available_byte_count: u64, -} - -/// Volume information returned by [`FileSystemHandler::get_volume_information`]. -/// -/// [`FileSystemHandler::get_volume_information`]: trait.FileSystemHandler.html#method.get_volume_information -#[derive(Debug, Clone)] -pub struct VolumeInfo { - /// Name of the volume. - pub name: U16CString, - - /// Serial number of the volume. - pub serial_number: u32, - - /// The maximum length of a path component that is supported. - pub max_component_length: u32, - - /// The flags associated with the file system. - /// - /// It can be combination of one or more [flags] defined by Windows. - /// - /// `FILE_READ_ONLY_VOLUME` is automatically added if - /// [`MountFlags::WRITE_PROTECT`] was specified when mounting the volume. - /// - /// [flags]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getvolumeinformationw#parameters - /// [`MountFlags::WRITE_PROTECT`]: struct.MountFlags.html#associatedconstant.WRITE_PROTECT - pub fs_flags: u32, - - /// Name of the file system. - /// - /// Windows checks feature availability based on file system name, so it is recommended to set - /// it to well-known names like NTFS or FAT. - pub fs_name: U16CString, -} - -/// Information about the opened file returned by [`FileSystemHandler::create_file`]. -/// -/// [`FileSystemHandler::create_file`]: trait.FileSystemHandler.html#method.create_file -#[derive(Debug, Clone)] -pub struct CreateFileInfo { - /// The context to be associated with the new file object. - pub context: T, - - /// Indicates whether the file is a directory. - pub is_dir: bool, - - /// Indicates whether a new file has been created. - pub new_file_created: bool, -} - -/// Types that implements this trait can handle file system operations for a mounted volume. -/// -/// Dokan invokes the callback functions in this trait to handle file system operations. These -/// functions has similar semantics to that of corresponding Windows API functions. -/// -/// Implementation of most callback functions can be omitted by returning `STATUS_NOT_IMPLEMENTED` -/// if the corresponding feature is not supported. To make things flexible, all of the functions are -/// provided with a default implementation which is a no-op and returns `STATUS_NOT_IMPLEMENTED` -/// (except [`cleanup`] and [`close_file`] which don't have return values). However, omitting the -/// implementation of some important callbacks such as [`create_file`] will make the file system -/// unusable. -/// -/// [`cleanup`]: #method.cleanup -/// [`close_file`]: #method.close_file -/// [`create_file`]: #method.create_file -pub trait FileSystemHandler<'a, 'b: 'a>: Sync + Sized + 'b { - /// Type of the context associated with an open file object. - type Context: Sync + 'a; - - /// Called when a file object is created. - /// - /// The flags passed to this function has similar meaning to that of [ZwCreateFile]. You can - /// convert them to flags accepted by [CreateFile] using the - /// [`map_kernel_to_user_create_file_flags`] helper function. - /// - /// [ZwCreateFile]: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-zwcreatefile - /// [CreateFile]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew - /// [`map_kernel_to_user_create_file_flags`]: fn.map_kernel_to_user_create_file_flags.html - fn create_file( - &'b self, - _file_name: &U16CStr, - _security_context: &DOKAN_IO_SECURITY_CONTEXT, - _desired_access: ACCESS_MASK, - _file_attributes: u32, - _share_access: u32, - _create_disposition: u32, - _create_options: u32, - _info: &mut OperationInfo<'a, 'b, Self>, - ) -> Result, OperationError> { - Err(OperationError::NtStatus(STATUS_NOT_IMPLEMENTED)) - } - - /// Called when the last handle for the file object has been closed. - /// - /// If [`info.delete_on_close`] returns `true`, the file should be deleted in this function. As the function doesn't - /// have a return value, you should make sure the file is deletable in [`delete_file`] or [`delete_directory`]. - /// - /// Note that the file object hasn't been released and there might be more I/O operations before - /// [`close_file`] gets called. (This typically happens when the file is memory-mapped.) - /// - /// Normally [`close_file`] will be called shortly after this function. However, the file object - /// may also be reused, and in that case [`create_file`] will be called instead. - /// - /// [`info.delete_on_close`]: struct.OperationInfo.html#method.delete_on_close - /// [`delete_file`]: #method.delete_file - /// [`delete_directory`]: #method.delete_directory - /// [`close_file`]: #method.close_file - /// [`create_file`]: #method.create_file - fn cleanup( - &'b self, - _file_name: &U16CStr, - _info: &OperationInfo<'a, 'b, Self>, - _context: &'a Self::Context, - ) { - } - - /// Called when the last handle for the handle object has been closed and released. - /// - /// This is the last function called during the lifetime of the file object. You can safely - /// release any resources allocated for it (such as file handles, buffers, etc.). The associated - /// [context] object will also be dropped once this function returns. In case the file object is - /// reused and thus this function isn't called, the [context] will be dropped before - /// [`create_file`] gets called. - /// - /// [context]: #associatedtype.Context - /// [`create_file`]: #method.create_file - fn close_file( - &'b self, - _file_name: &U16CStr, - _info: &OperationInfo<'a, 'b, Self>, - _context: &'a Self::Context, - ) { - } - - /// Reads data from the file. - /// - /// The number of bytes that actually gets read should be returned. - /// - /// See [ReadFile] for more information. - /// - /// [ReadFile]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile - fn read_file( - &'b self, - _file_name: &U16CStr, - _offset: i64, - _buffer: &mut [u8], - _info: &OperationInfo<'a, 'b, Self>, - _context: &'a Self::Context, - ) -> Result { - Err(OperationError::NtStatus(STATUS_NOT_IMPLEMENTED)) - } - - /// Writes data to the file. - /// - /// The number of bytes that actually gets written should be returned. - /// - /// If [`info.write_to_eof`] returns `true`, data should be written to the end of file and the - /// `offset` parameter should be ignored. - /// - /// See [WriteFile] for more information. - /// - /// [`info.write_to_eof`]: struct.OperationInfo.html#method.write_to_eof - /// [WriteFile]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile - fn write_file( - &'b self, - _file_name: &U16CStr, - _offset: i64, - _buffer: &[u8], - _info: &OperationInfo<'a, 'b, Self>, - _context: &'a Self::Context, - ) -> Result { - Err(OperationError::NtStatus(STATUS_NOT_IMPLEMENTED)) - } - - /// Flushes the buffer of the file and causes all buffered data to be written to the file. - /// - /// See [FlushFileBuffers] for more information. - /// - /// [FlushFileBuffers]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-flushfilebuffers - fn flush_file_buffers( - &'b self, - _file_name: &U16CStr, - _info: &OperationInfo<'a, 'b, Self>, - _context: &'a Self::Context, - ) -> Result<(), OperationError> { - Err(OperationError::NtStatus(STATUS_NOT_IMPLEMENTED)) - } - - /// Gets information about the file. - /// - /// See [GetFileInformationByHandle] for more information. - /// - /// [GetFileInformationByHandle]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileinformationbyhandle - fn get_file_information( - &'b self, - _file_name: &U16CStr, - _info: &OperationInfo<'a, 'b, Self>, - _context: &'a Self::Context, - ) -> Result { - Err(OperationError::NtStatus(STATUS_NOT_IMPLEMENTED)) - } - - /// Lists all child items in the directory. - /// - /// `fill_find_data` should be called for every child item in the directory. - /// - /// It will only be called if [`find_files_with_pattern`] returns `STATUS_NOT_IMPLEMENTED`. - /// - /// See [FindFirstFile] for more information. - /// - /// [`find_files_with_pattern`]: #method.find_files_with_pattern - /// [FindFirstFile]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirstfilew - fn find_files( - &'b self, - _file_name: &U16CStr, - _fill_find_data: impl FnMut(&FindData) -> Result<(), FillDataError>, - _info: &OperationInfo<'a, 'b, Self>, - _context: &'a Self::Context, - ) -> Result<(), OperationError> { - Err(OperationError::NtStatus(STATUS_NOT_IMPLEMENTED)) - } - - /// Lists all child items that matches the specified `pattern` in the directory. - /// - /// `fill_find_data` should be called for every matching child item in the directory. - /// - /// [`is_name_in_expression`] can be used to determine if a file name matches the pattern. - /// - /// If this function returns `STATUS_NOT_IMPLEMENTED`, [`find_files`] will be called instead and - /// pattern matching will be handled directly by Dokan. - /// - /// See [FindFirstFile] for more information. - /// - /// [`is_name_in_expression`]: fn.is_name_in_expression.html - /// [`find_files`]: #method.find_files - /// [FindFirstFile]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirstfilew - fn find_files_with_pattern( - &'b self, - _file_name: &U16CStr, - _pattern: &U16CStr, - _fill_find_data: impl FnMut(&FindData) -> Result<(), FillDataError>, - _info: &OperationInfo<'a, 'b, Self>, - _context: &'a Self::Context, - ) -> Result<(), OperationError> { - Err(OperationError::NtStatus(STATUS_NOT_IMPLEMENTED)) - } - - /// Sets attributes of the file. - /// - /// `file_attributes` can be combination of one or more [file attribute constants] defined by - /// Windows. - /// - /// See [SetFileAttributes] for more information. - /// - /// [file attribute constants]: https://docs.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants - /// [SetFileAttributes]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfileattributesw - fn set_file_attributes( - &'b self, - _file_name: &U16CStr, - _file_attributes: u32, - _info: &OperationInfo<'a, 'b, Self>, - _context: &'a Self::Context, - ) -> Result<(), OperationError> { - Err(OperationError::NtStatus(STATUS_NOT_IMPLEMENTED)) - } - - /// Sets the time when the file was created, last accessed and last written. - /// - /// See [SetFileTime] for more information. - /// - /// [SetFileTime]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfiletime - fn set_file_time( - &'b self, - _file_name: &U16CStr, - _creation_time: FileTimeInfo, - _last_access_time: FileTimeInfo, - _last_write_time: FileTimeInfo, - _info: &OperationInfo<'a, 'b, Self>, - _context: &'a Self::Context, - ) -> Result<(), OperationError> { - Err(OperationError::NtStatus(STATUS_NOT_IMPLEMENTED)) - } - - /// Checks if the file can be deleted. - /// - /// The file should not be deleted in this function. Instead, it should only check if the file - /// can be deleted and return `Ok` if that is possible. - /// - /// It will also be called with [`info.delete_on_close`] returning `false` to notify that the - /// file is no longer requested to be deleted. - /// - /// [`info.delete_on_close`]: struct.OperationInfo.html#method.delete_on_close - fn delete_file( - &'b self, - _file_name: &U16CStr, - _info: &OperationInfo<'a, 'b, Self>, - _context: &'a Self::Context, - ) -> Result<(), OperationError> { - Err(OperationError::NtStatus(STATUS_NOT_IMPLEMENTED)) - } - - /// Checks if the directory can be deleted. - /// - /// Similar to [`delete_file`], it should only check if the directory can be deleted and delay - /// the actual deletion to the [`cleanup`] function. - /// - /// It will also be called with [`info.delete_on_close`] returning `false` to notify that the - /// directory is no longer requested to be deleted. - /// - /// [`delete_file`]: #method.delete_file - /// [`cleanup`]: #method.cleanup - /// [`info.delete_on_close`]: struct.OperationInfo.html#method.delete_on_close - fn delete_directory( - &'b self, - _file_name: &U16CStr, - _info: &OperationInfo<'a, 'b, Self>, - _context: &'a Self::Context, - ) -> Result<(), OperationError> { - Err(OperationError::NtStatus(STATUS_NOT_IMPLEMENTED)) - } - - /// Moves the file. - /// - /// If the `new_file_name` already exists, the function should only replace the existing file - /// when `replace_if_existing` is `true`, otherwise it should return appropriate error. - /// - /// Note that renaming is a special kind of moving and is also handled by this function. - /// - /// See [MoveFileEx] for more information. - /// - /// [MoveFileEx]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefileexw - fn move_file( - &'b self, - _file_name: &U16CStr, - _new_file_name: &U16CStr, - _replace_if_existing: bool, - _info: &OperationInfo<'a, 'b, Self>, - _context: &'a Self::Context, - ) -> Result<(), OperationError> { - Err(OperationError::NtStatus(STATUS_NOT_IMPLEMENTED)) - } - - /// Sets end-of-file position of the file. - /// - /// The `offset` value is zero-based, so it actually refers to the offset to the byte - /// immediately following the last valid byte in the file. - /// - /// See [FILE_END_OF_FILE_INFORMATION] for more information. - /// - /// [FILE_END_OF_FILE_INFORMATION]: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-_file_end_of_file_information - fn set_end_of_file( - &'b self, - _file_name: &U16CStr, - _offset: i64, - _info: &OperationInfo<'a, 'b, Self>, - _context: &'a Self::Context, - ) -> Result<(), OperationError> { - Err(OperationError::NtStatus(STATUS_NOT_IMPLEMENTED)) - } - - /// Sets allocation size of the file. - /// - /// The allocation size is the number of bytes allocated in the underlying physical device for - /// the file. - /// - /// See [FILE_ALLOCATION_INFORMATION] for more information. - /// - /// [FILE_ALLOCATION_INFORMATION]: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_file_allocation_information - fn set_allocation_size( - &'b self, - _file_name: &U16CStr, - _alloc_size: i64, - _info: &OperationInfo<'a, 'b, Self>, - _context: &'a Self::Context, - ) -> Result<(), OperationError> { - Err(OperationError::NtStatus(STATUS_NOT_IMPLEMENTED)) - } - - /// Locks the file for exclusive access. - /// - /// It will only be called if [`MountFlags::FILELOCK_USER_MODE`] was specified when mounting the - /// volume, otherwise Dokan will take care of file locking. - /// - /// See [LockFile] for more information. - /// - /// [`MountFlags::FILELOCK_USER_MODE`]: struct.MountFlags.html#associatedconstant.FILELOCK_USER_MODE - /// [LockFile]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-lockfile - fn lock_file( - &'b self, - _file_name: &U16CStr, - _offset: i64, - _length: i64, - _info: &OperationInfo<'a, 'b, Self>, - _context: &'a Self::Context, - ) -> Result<(), OperationError> { - Err(OperationError::NtStatus(STATUS_NOT_IMPLEMENTED)) - } - - /// Unlocks the previously locked file. - /// - /// It will only be called if [`MountFlags::FILELOCK_USER_MODE`] was specified when mounting the - /// volume, otherwise Dokan will take care of file locking. - /// - /// See [UnlockFile] for more information. - /// - /// [`MountFlags::FILELOCK_USER_MODE`]: struct.MountFlags.html#associatedconstant.FILELOCK_USER_MODE - /// [UnlockFile]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-unlockfile - fn unlock_file( - &'b self, - _file_name: &U16CStr, - _offset: i64, - _length: i64, - _info: &OperationInfo<'a, 'b, Self>, - _context: &'a Self::Context, - ) -> Result<(), OperationError> { - Err(OperationError::NtStatus(STATUS_NOT_IMPLEMENTED)) - } - - /// Gets free space information about the disk. - /// - /// See [GetDiskFreeSpaceEx] for more information. - /// - /// [GetDiskFreeSpaceEx]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getdiskfreespaceexw - fn get_disk_free_space( - &'b self, - _info: &OperationInfo<'a, 'b, Self>, - ) -> Result { - Err(OperationError::NtStatus(STATUS_NOT_IMPLEMENTED)) - } - - /// Gets information about the volume and file system. - /// - /// See [GetVolumeInformation] for more information. - /// - /// [GetVolumeInformation]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getvolumeinformationbyhandlew - fn get_volume_information( - &'b self, - _info: &OperationInfo<'a, 'b, Self>, - ) -> Result { - Err(OperationError::NtStatus(STATUS_NOT_IMPLEMENTED)) - } - - /// Called when Dokan has successfully mounted the volume. - fn mounted( - &'b self, - _mount_point: &U16CStr, - _info: &OperationInfo<'a, 'b, Self>, - ) -> Result<(), OperationError> { - Err(OperationError::NtStatus(STATUS_NOT_IMPLEMENTED)) - } - - /// Called when Dokan is unmounting the volume. - fn unmounted(&'b self, _info: &OperationInfo<'a, 'b, Self>) -> Result<(), OperationError> { - Err(OperationError::NtStatus(STATUS_NOT_IMPLEMENTED)) - } - - /// Gets security information of a file. - /// - /// Size of the security descriptor in bytes should be returned on success. If the buffer is not - /// large enough, the number should still be returned, and `STATUS_BUFFER_OVERFLOW` will be - /// automatically passed to Dokan if it is larger than `buffer_length`. - /// - /// See [GetFileSecurity] for more information. - /// - /// [GetFileSecurity]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getfilesecuritya - fn get_file_security( - &'b self, - _file_name: &U16CStr, - _security_information: u32, - _security_descriptor: PSECURITY_DESCRIPTOR, - _buffer_length: u32, - _info: &OperationInfo<'a, 'b, Self>, - _context: &'a Self::Context, - ) -> Result { - Err(OperationError::NtStatus(STATUS_NOT_IMPLEMENTED)) - } - - /// Sets security information of a file. - /// - /// See [SetFileSecurity] for more information. - /// - /// [SetFileSecurity]: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setfilesecuritya - fn set_file_security( - &'b self, - _file_name: &U16CStr, - _security_information: u32, - _security_descriptor: PSECURITY_DESCRIPTOR, - _buffer_length: u32, - _info: &OperationInfo<'a, 'b, Self>, - _context: &'a Self::Context, - ) -> Result<(), OperationError> { - Err(OperationError::NtStatus(STATUS_NOT_IMPLEMENTED)) - } - - /// Lists all alternative streams of the file. - /// - /// `fill_find_stream_data` should be called for every stream of the file, including the default - /// data stream `::$DATA`. - /// - /// See [FindFirstStream] for more information. - /// - /// [FindFirstStream]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirststreamw - fn find_streams( - &'b self, - _file_name: &U16CStr, - _fill_find_stream_data: impl FnMut(&FindStreamData) -> Result<(), FillDataError>, - _info: &OperationInfo<'a, 'b, Self>, - _context: &'a Self::Context, - ) -> Result<(), OperationError> { - Err(OperationError::NtStatus(STATUS_NOT_IMPLEMENTED)) - } -} - -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) == success_value } { - Ok(()) - } else { - Err(FillDataError::BufferFull) - } - } -} - -const FILE_SUPERSEDE: u32 = 0; -const FILE_OPEN_IF: u32 = 3; -const FILE_OVERWRITE_IF: u32 = 5; - -extern "stdcall" fn create_file<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( - file_name: LPCWSTR, - security_context: PDOKAN_IO_SECURITY_CONTEXT, - desired_access: ACCESS_MASK, - file_attributes: ULONG, - share_access: ULONG, - create_disposition: ULONG, - create_options: ULONG, - dokan_file_info: PDOKAN_FILE_INFO, -) -> NTSTATUS { - panic::catch_unwind(|| unsafe { - let file_name = U16CStr::from_ptr_str(file_name); - let mut info = OperationInfo::<'a, 'b, T>::new(dokan_file_info); - info.drop_context(); - info.handler() - .create_file( - file_name, - &*security_context, - desired_access, - file_attributes, - share_access, - create_disposition, - create_options, - &mut info, - ) - .and_then(|create_info| { - (&mut *dokan_file_info).Context = - Box::into_raw(Box::new(create_info.context)) as u64; - (&mut *dokan_file_info).IsDirectory = create_info.is_dir.into(); - if (create_disposition == FILE_OPEN_IF - || create_disposition == FILE_OVERWRITE_IF - || create_disposition == FILE_SUPERSEDE) - && !create_info.new_file_created - { - Err(OperationError::NtStatus(STATUS_OBJECT_NAME_COLLISION)) - } else { - Ok(()) - } - }) - .ntstatus() - }) - .unwrap_or(STATUS_INTERNAL_ERROR) -} - -#[allow(unused_must_use)] -extern "stdcall" fn cleanup<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( - file_name: LPCWSTR, - dokan_file_info: PDOKAN_FILE_INFO, -) { - panic::catch_unwind(|| unsafe { - let file_name = U16CStr::from_ptr_str(file_name); - let info = OperationInfo::<'a, 'b, T>::new(dokan_file_info); - info.handler().cleanup(file_name, &info, info.context()); - }); -} - -#[allow(unused_must_use)] -extern "stdcall" fn close_file<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( - file_name: LPCWSTR, - dokan_file_info: PDOKAN_FILE_INFO, -) { - panic::catch_unwind(|| unsafe { - let file_name = U16CStr::from_ptr_str(file_name); - let mut info = OperationInfo::<'a, 'b, T>::new(dokan_file_info); - info.handler().close_file(file_name, &info, info.context()); - info.drop_context(); - }); -} - -extern "stdcall" fn read_file<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( - file_name: LPCWSTR, - buffer: LPVOID, - buffer_length: DWORD, - read_length: LPDWORD, - offset: LONGLONG, - dokan_file_info: PDOKAN_FILE_INFO, -) -> NTSTATUS { - panic::catch_unwind(|| unsafe { - *read_length = 0; - let file_name = U16CStr::from_ptr_str(file_name); - let info = OperationInfo::<'a, 'b, T>::new(dokan_file_info); - let buffer = slice::from_raw_parts_mut(buffer as *mut _, buffer_length as usize); - let result = info - .handler() - .read_file(file_name, offset, buffer, &info, info.context()); - if let Ok(bytes_read) = result { - *read_length = bytes_read; - } - result.ntstatus() - }) - .unwrap_or(STATUS_INTERNAL_ERROR) -} - -extern "stdcall" fn write_file<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( - file_name: LPCWSTR, - buffer: LPCVOID, - number_of_bytes_to_write: DWORD, - number_of_bytes_written: LPDWORD, - offset: LONGLONG, - dokan_file_info: PDOKAN_FILE_INFO, -) -> NTSTATUS { - panic::catch_unwind(|| unsafe { - *number_of_bytes_written = 0; - let file_name = U16CStr::from_ptr_str(file_name); - let info = OperationInfo::<'a, 'b, T>::new(dokan_file_info); - let buffer = slice::from_raw_parts(buffer as *mut _, number_of_bytes_to_write as usize); - let result = info - .handler() - .write_file(file_name, offset, buffer, &info, info.context()); - if let Ok(bytes_written) = result { - *number_of_bytes_written = bytes_written; - } - result.ntstatus() - }) - .unwrap_or(STATUS_INTERNAL_ERROR) -} - -extern "stdcall" fn flush_file_buffers<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( - file_name: LPCWSTR, - dokan_file_info: PDOKAN_FILE_INFO, -) -> NTSTATUS { - panic::catch_unwind(|| unsafe { - let file_name = U16CStr::from_ptr_str(file_name); - let info = OperationInfo::<'a, 'b, T>::new(dokan_file_info); - info.handler() - .flush_file_buffers(file_name, &info, info.context()) - .ntstatus() - }) - .unwrap_or(STATUS_INTERNAL_ERROR) -} - -extern "stdcall" fn get_file_information<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( - file_name: LPCWSTR, - buffer: LPBY_HANDLE_FILE_INFORMATION, - dokan_file_info: PDOKAN_FILE_INFO, -) -> NTSTATUS { - panic::catch_unwind(|| unsafe { - let file_name = U16CStr::from_ptr_str(file_name); - let info = OperationInfo::<'a, 'b, T>::new(dokan_file_info); - info.handler() - .get_file_information(file_name, &info, info.context()) - .and_then(|file_info| { - *buffer = file_info.to_raw_struct(); - Ok(()) - }) - .ntstatus() - }) - .unwrap_or(STATUS_INTERNAL_ERROR) -} - -extern "stdcall" fn find_files<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( - file_name: LPCWSTR, - fill_find_data: PFillFindData, - 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_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()) - .ntstatus() - }) - .unwrap_or(STATUS_INTERNAL_ERROR) -} - -extern "stdcall" fn find_files_with_pattern<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( - file_name: LPCWSTR, - search_pattern: LPCWSTR, - fill_find_data: PFillFindData, - dokan_file_info: PDOKAN_FILE_INFO, -) -> NTSTATUS { - 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, 0); - let info = OperationInfo::<'a, 'b, T>::new(dokan_file_info); - info.handler() - .find_files_with_pattern( - file_name, - search_pattern, - fill_wrapper, - &info, - info.context(), - ) - .ntstatus() - }) - .unwrap_or(STATUS_INTERNAL_ERROR) -} - -extern "stdcall" fn set_file_attributes<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( - file_name: LPCWSTR, - file_attributes: DWORD, - dokan_file_info: PDOKAN_FILE_INFO, -) -> NTSTATUS { - panic::catch_unwind(|| unsafe { - let file_name = U16CStr::from_ptr_str(file_name); - let info = OperationInfo::<'a, 'b, T>::new(dokan_file_info); - info.handler() - .set_file_attributes(file_name, file_attributes, &info, info.context()) - .ntstatus() - }) - .unwrap_or(STATUS_INTERNAL_ERROR) -} - -extern "stdcall" fn set_file_time<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( - file_name: LPCWSTR, - creation_time: *const FILETIME, - last_access_time: *const FILETIME, - last_write_time: *const FILETIME, - dokan_file_info: PDOKAN_FILE_INFO, -) -> NTSTATUS { - panic::catch_unwind(|| unsafe { - let file_name = U16CStr::from_ptr_str(file_name); - let info = OperationInfo::<'a, 'b, T>::new(dokan_file_info); - let creation_time = FileTimeInfo::from_filetime(*creation_time); - let last_access_time = FileTimeInfo::from_filetime(*last_access_time); - let last_write_time = FileTimeInfo::from_filetime(*last_write_time); - info.handler() - .set_file_time( - file_name, - creation_time, - last_access_time, - last_write_time, - &info, - info.context(), - ) - .ntstatus() - }) - .unwrap_or(STATUS_INTERNAL_ERROR) -} - -extern "stdcall" fn delete_file<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( - file_name: LPCWSTR, - dokan_file_info: PDOKAN_FILE_INFO, -) -> NTSTATUS { - panic::catch_unwind(|| unsafe { - let file_name = U16CStr::from_ptr_str(file_name); - let info = OperationInfo::<'a, 'b, T>::new(dokan_file_info); - info.handler() - .delete_file(file_name, &info, info.context()) - .ntstatus() - }) - .unwrap_or(STATUS_INTERNAL_ERROR) -} - -extern "stdcall" fn delete_directory<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( - file_name: LPCWSTR, - dokan_file_info: PDOKAN_FILE_INFO, -) -> NTSTATUS { - panic::catch_unwind(|| unsafe { - let file_name = U16CStr::from_ptr_str(file_name); - let info = OperationInfo::<'a, 'b, T>::new(dokan_file_info); - info.handler() - .delete_directory(file_name, &info, info.context()) - .ntstatus() - }) - .unwrap_or(STATUS_INTERNAL_ERROR) -} - -extern "stdcall" fn move_file<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( - file_name: LPCWSTR, - new_file_name: LPCWSTR, - replace_if_existing: BOOL, - dokan_file_info: PDOKAN_FILE_INFO, -) -> NTSTATUS { - panic::catch_unwind(|| unsafe { - let file_name = U16CStr::from_ptr_str(file_name); - let new_file_name = U16CStr::from_ptr_str(new_file_name); - let info = OperationInfo::<'a, 'b, T>::new(dokan_file_info); - info.handler() - .move_file( - file_name, - new_file_name, - replace_if_existing == TRUE, - &info, - info.context(), - ) - .ntstatus() - }) - .unwrap_or(STATUS_INTERNAL_ERROR) -} - -extern "stdcall" fn set_end_of_file<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( - file_name: LPCWSTR, - byte_offset: LONGLONG, - dokan_file_info: PDOKAN_FILE_INFO, -) -> NTSTATUS { - panic::catch_unwind(|| unsafe { - let file_name = U16CStr::from_ptr_str(file_name); - let info = OperationInfo::<'a, 'b, T>::new(dokan_file_info); - info.handler() - .set_end_of_file(file_name, byte_offset, &info, info.context()) - .ntstatus() - }) - .unwrap_or(STATUS_INTERNAL_ERROR) -} - -extern "stdcall" fn set_allocation_size<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( - file_name: LPCWSTR, - alloc_size: LONGLONG, - dokan_file_info: PDOKAN_FILE_INFO, -) -> NTSTATUS { - panic::catch_unwind(|| unsafe { - let file_name = U16CStr::from_ptr_str(file_name); - let info = OperationInfo::<'a, 'b, T>::new(dokan_file_info); - info.handler() - .set_allocation_size(file_name, alloc_size, &info, info.context()) - .ntstatus() - }) - .unwrap_or(STATUS_INTERNAL_ERROR) -} - -// Extern stdcall functions with similar bodies but not called directly with trigger a compiler bug when built in -// release mode. It seems that extracting the function bodies into a common function works around this bug. -// See https://github.com/rust-lang/rust/issues/72212 -fn lock_unlock_file<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( - file_name: LPCWSTR, - byte_offset: LONGLONG, - length: LONGLONG, - dokan_file_info: PDOKAN_FILE_INFO, - func: fn( - &'b T, - &U16CStr, - i64, - i64, - &OperationInfo<'a, 'b, T>, - &'a T::Context, - ) -> Result<(), OperationError>, -) -> NTSTATUS { - panic::catch_unwind(|| unsafe { - let file_name = U16CStr::from_ptr_str(file_name); - let info = OperationInfo::<'a, 'b, T>::new(dokan_file_info); - func( - info.handler(), - file_name, - byte_offset, - length, - &info, - info.context(), - ) - .ntstatus() - }) - .unwrap_or(STATUS_INTERNAL_ERROR) -} - -extern "stdcall" fn lock_file<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( - file_name: LPCWSTR, - byte_offset: LONGLONG, - length: LONGLONG, - dokan_file_info: PDOKAN_FILE_INFO, -) -> NTSTATUS { - lock_unlock_file( - file_name, - byte_offset, - length, - dokan_file_info, - T::lock_file, - ) -} - -extern "stdcall" fn unlock_file<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( - file_name: LPCWSTR, - byte_offset: LONGLONG, - length: LONGLONG, - dokan_file_info: PDOKAN_FILE_INFO, -) -> NTSTATUS { - lock_unlock_file( - file_name, - byte_offset, - length, - dokan_file_info, - T::unlock_file, - ) -} - -extern "stdcall" fn get_disk_free_space<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( - free_bytes_available: PULONGLONG, - total_number_of_bytes: PULONGLONG, - total_number_of_free_bytes: PULONGLONG, - dokan_file_info: PDOKAN_FILE_INFO, -) -> NTSTATUS { - panic::catch_unwind(|| { - let info = OperationInfo::<'a, 'b, T>::new(dokan_file_info); - info.handler() - .get_disk_free_space(&info) - .and_then(|space_info| unsafe { - if !free_bytes_available.is_null() { - *free_bytes_available = space_info.available_byte_count; - } - if !total_number_of_bytes.is_null() { - *total_number_of_bytes = space_info.byte_count; - } - if !total_number_of_free_bytes.is_null() { - *total_number_of_free_bytes = space_info.free_byte_count; - } - Ok(()) - }) - .ntstatus() - }) - .unwrap_or(STATUS_INTERNAL_ERROR) -} - -extern "stdcall" fn get_volume_information<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( - volume_name_buffer: LPWSTR, - volume_name_size: DWORD, - volume_serial_number: LPDWORD, - maximum_component_length: LPDWORD, - file_system_flags: LPDWORD, - file_system_name_buffer: LPWSTR, - file_system_name_size: DWORD, - dokan_file_info: PDOKAN_FILE_INFO, -) -> NTSTATUS { - panic::catch_unwind(|| { - let info = OperationInfo::<'a, 'b, T>::new(dokan_file_info); - info.handler() - .get_volume_information(&info) - .and_then(|volume_info| unsafe { - volume_name_buffer.copy_from_nonoverlapping( - volume_info.name.as_ptr(), - (volume_info.name.len() + 1).min(volume_name_size as usize), - ); - if !volume_serial_number.is_null() { - *volume_serial_number = volume_info.serial_number; - } - if !maximum_component_length.is_null() { - *maximum_component_length = volume_info.max_component_length; - } - if !file_system_flags.is_null() { - *file_system_flags = volume_info.fs_flags; - } - file_system_name_buffer.copy_from_nonoverlapping( - volume_info.fs_name.as_ptr(), - (volume_info.fs_name.len() + 1).min(file_system_name_size as usize), - ); - Ok(()) - }) - .ntstatus() - }) - .unwrap_or(STATUS_INTERNAL_ERROR) -} - -extern "stdcall" fn mounted<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( - mount_point: LPCWSTR, - dokan_file_info: PDOKAN_FILE_INFO, -) -> NTSTATUS { - panic::catch_unwind(|| unsafe { - let mount_point = U16CStr::from_ptr_str(mount_point); - let info = OperationInfo::<'a, 'b, T>::new(dokan_file_info); - info.handler().mounted(mount_point, &info).ntstatus() - }) - .unwrap_or(STATUS_INTERNAL_ERROR) -} - -extern "stdcall" fn unmounted<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( - dokan_file_info: PDOKAN_FILE_INFO, -) -> NTSTATUS { - 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>( - file_name: LPCWSTR, - security_information: PSECURITY_INFORMATION, - security_descriptor: PSECURITY_DESCRIPTOR, - buffer_length: ULONG, - length_needed: PULONG, - dokan_file_info: PDOKAN_FILE_INFO, -) -> NTSTATUS { - panic::catch_unwind(|| unsafe { - let file_name = U16CStr::from_ptr_str(file_name); - let info = OperationInfo::<'a, 'b, T>::new(dokan_file_info); - let result = info.handler().get_file_security( - file_name, - *security_information, - security_descriptor, - buffer_length, - &info, - info.context(), - ); - if let Ok(needed) = result { - *length_needed = needed; - if needed <= buffer_length { - STATUS_SUCCESS - } else { - STATUS_BUFFER_OVERFLOW - } - } else { - result.ntstatus() - } - }) - .unwrap_or(STATUS_INTERNAL_ERROR) -} - -extern "stdcall" fn set_file_security<'a, 'b: 'a, T: FileSystemHandler<'a, 'b> + 'b>( - file_name: LPCWSTR, - security_information: PSECURITY_INFORMATION, - security_descriptor: PSECURITY_DESCRIPTOR, - buffer_length: ULONG, - dokan_file_info: PDOKAN_FILE_INFO, -) -> NTSTATUS { - panic::catch_unwind(|| unsafe { - let file_name = U16CStr::from_ptr_str(file_name); - let info = OperationInfo::<'a, 'b, T>::new(dokan_file_info); - info.handler() - .set_file_security( - file_name, - *security_information, - security_descriptor, - buffer_length, - &info, - info.context(), - ) - .ntstatus() - }) - .unwrap_or(STATUS_INTERNAL_ERROR) -} - -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, 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()) - .ntstatus() - }) - .unwrap_or(STATUS_INTERNAL_ERROR) -} - -/// The error type for [`Drive::mount`]. -/// -/// [`Drive::mount`]: struct.Drive.html#method.mount -#[repr(i32)] -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum MountError { - /// A general error. - Error = DOKAN_ERROR, - - /// Bad drive letter. - DriveLetterError = DOKAN_DRIVE_LETTER_ERROR, - - /// Can't install the Dokan driver. - DriverInstallError = DOKAN_DRIVER_INSTALL_ERROR, - - /// The driver responds that something is wrong. - StartError = DOKAN_START_ERROR, - - /// Can't assign a drive letter or mount point. - /// - /// This probably means that the mount point is already used by another volume. - MountError = DOKAN_MOUNT_ERROR, - - /// The mount point is invalid. - MountPointError = DOKAN_MOUNT_POINT_ERROR, - - /// The Dokan version that this wrapper is targeting is incompatible with the loaded Dokan - /// library. - VersionError = DOKAN_VERSION_ERROR, -} - -impl Error for MountError {} - -impl Display for MountError { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let msg = match self { - MountError::Error => "Dokan mount error.", - MountError::DriveLetterError => "Bad drive letter.", - MountError::DriverInstallError => "Can't install driver.", - MountError::StartError => "The driver responds that something is wrong.", - MountError::MountError => "Can't assign a drive letter or mount point. Probably already used by another volume.", - MountError::MountPointError => "The mount point is invalid.", - MountError::VersionError => "Requested an incompatible version.", - }; - write!(f, "{}", msg) - } -} - -/// A builder that allows configuring and mounting a volume. -#[derive(Debug)] -pub struct Drive<'a> { - options: DOKAN_OPTIONS, - phantom: PhantomData<&'a U16CStr>, -} - -impl<'a> Drive<'a> { - /// Creates a new instance of this builder with default settings. - pub fn new() -> Self { - Drive { - options: DOKAN_OPTIONS { - Version: WRAPPER_VERSION as u16, - SingleThread: 0, - Options: 0, - GlobalContext: 0, - MountPoint: ptr::null(), - UNCName: ptr::null(), - 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 single_thread(&mut self, value: bool) -> &mut Self { - self.options.SingleThread = value.into(); - self - } - - /// Sets flags that controls behavior of the volume. - pub fn flags(&mut self, value: MountFlags) -> &mut Self { - self.options.Options = value.bits(); - self - } - - /// Sets mount point path. - pub fn mount_point(&mut self, value: &'a impl AsRef) -> &mut Self { - self.options.MountPoint = value.as_ref().as_ptr(); - self - } - - /// Sets UNC name of the network drive. - pub fn unc_name(&mut self, value: &'a impl AsRef) -> &mut Self { - self.options.UNCName = value.as_ref().as_ptr(); - self - } - - /// Sets the time that Dokan will wait for an operation to complete. - /// - /// If an operation times out, the user mode implementation is considered to be unable to handle - /// file system operations properly, and the driver will therefore unmount the volume in order - /// to keep the system stable. - /// - /// This timeout can be temporarily extended for an operation with - /// [`OperationInfo::reset_timeout`]. - /// - /// [`OperationInfo::reset_timeout`]: struct.OperationInfo.html#method.reset_timeout - pub fn timeout(&mut self, value: Duration) -> &mut Self { - self.options.Timeout = value.as_millis() as u32; - self - } - - /// Sets allocation unit size of the volume. - /// - /// This value will affect file sizes. - pub fn allocation_unit_size(&mut self, value: u32) -> &mut Self { - self.options.AllocationUnitSize = value; - self - } - - /// Sets sector size of the volume. - /// - /// This value will affect file sizes. - pub fn sector_size(&mut self, value: u32) -> &mut Self { - self.options.SectorSize = value; - self - } - - /// Mounts the volume and blocks the current thread until the volume gets unmounted. - pub fn mount<'b, 'c: 'b, T: FileSystemHandler<'b, 'c> + 'c>( - &mut self, - handler: &'c T, - ) -> Result { - let mut operations = DOKAN_OPERATIONS { - ZwCreateFile: Some(create_file::<'b, 'c, T>), - Cleanup: Some(cleanup::<'b, 'c, T>), - CloseFile: Some(close_file::<'b, 'c, T>), - ReadFile: Some(read_file::<'b, 'c, T>), - WriteFile: Some(write_file::<'b, 'c, T>), - FlushFileBuffers: Some(flush_file_buffers::<'b, 'c, T>), - GetFileInformation: Some(get_file_information::<'b, 'c, T>), - FindFiles: Some(find_files::<'b, 'c, T>), - FindFilesWithPattern: Some(find_files_with_pattern::<'b, 'c, T>), - SetFileAttributes: Some(set_file_attributes::<'b, 'c, T>), - SetFileTime: Some(set_file_time::<'b, 'c, T>), - DeleteFile: Some(delete_file::<'b, 'c, T>), - DeleteDirectory: Some(delete_directory::<'b, 'c, T>), - MoveFile: Some(move_file::<'b, 'c, T>), - SetEndOfFile: Some(set_end_of_file::<'b, 'c, T>), - SetAllocationSize: Some(set_allocation_size::<'b, 'c, T>), - LockFile: Some(lock_file::<'b, 'c, T>), - UnlockFile: Some(unlock_file::<'b, 'c, T>), - GetDiskFreeSpace: Some(get_disk_free_space::<'b, 'c, T>), - GetVolumeInformation: Some(get_volume_information::<'b, 'c, T>), - Mounted: Some(mounted::<'b, 'c, T>), - Unmounted: Some(unmounted::<'b, 'c, T>), - GetFileSecurity: Some(get_file_security::<'b, 'c, T>), - SetFileSecurity: Some(set_file_security::<'b, 'c, T>), - FindStreams: Some(find_streams::<'b, 'c, T>), - }; - - self.options.GlobalContext = handler as *const _ as u64; - - 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(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; - } - } +#[test] +fn test_debug_mode() { + set_debug_stream(DebugStream::Stdout); + set_debug_stream(DebugStream::Stderr); + set_lib_debug_mode(true); + set_lib_debug_mode(false); + assert!(set_driver_debug_mode(true)); + assert!(set_driver_debug_mode(false)); } diff --git a/dokan/src/notify.rs b/dokan/src/notify.rs new file mode 100644 index 0000000..9a710af --- /dev/null +++ b/dokan/src/notify.rs @@ -0,0 +1,64 @@ +use dokan_sys::{ + DokanNotifyCreate, DokanNotifyDelete, DokanNotifyRename, DokanNotifyUpdate, + DokanNotifyXAttrUpdate, +}; +use widestring::U16CStr; +use winapi::shared::minwindef::TRUE; + +use crate::FileSystemHandle; + +/// Notifies Dokan that a file or directory has been created. +/// +/// Returns `true` on success. +#[must_use] +pub fn notify_create(instance: FileSystemHandle, path: impl AsRef, is_dir: bool) -> bool { + unsafe { DokanNotifyCreate(instance.0, 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(instance: FileSystemHandle, path: impl AsRef, is_dir: bool) -> bool { + unsafe { DokanNotifyDelete(instance.0, 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(instance: FileSystemHandle, path: impl AsRef) -> bool { + unsafe { DokanNotifyUpdate(instance.0, 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(instance: FileSystemHandle, path: impl AsRef) -> bool { + unsafe { DokanNotifyXAttrUpdate(instance.0, path.as_ref().as_ptr()) == TRUE } +} + +/// Notifies Dokan that a file or directory has been renamed. +/// +/// `is_same_dir` indicates if the new file or directory is in the same directory as the old one. +/// +/// Returns `true` on success. +#[must_use] +pub fn notify_rename( + instance: FileSystemHandle, + old_path: impl AsRef, + new_path: impl AsRef, + is_dir: bool, + is_same_dir: bool, +) -> bool { + unsafe { + DokanNotifyRename( + instance.0, + old_path.as_ref().as_ptr(), + new_path.as_ref().as_ptr(), + is_dir.into(), + is_same_dir.into(), + ) == TRUE + } +} diff --git a/dokan/src/operations.rs b/dokan/src/operations.rs new file mode 100644 index 0000000..b5e6eea --- /dev/null +++ b/dokan/src/operations.rs @@ -0,0 +1,505 @@ +use std::slice; + +use dokan_sys::{ + win32::{FILE_OPEN_IF, FILE_OVERWRITE_IF, FILE_SUPERSEDE}, + PFillFindData, PFillFindStreamData, PDOKAN_FILE_INFO, PDOKAN_IO_SECURITY_CONTEXT, +}; +use widestring::U16CStr; +use winapi::{ + shared::{ + minwindef::{BOOL, DWORD, FILETIME, LPCVOID, LPDWORD, LPVOID, PULONG, TRUE, ULONG}, + ntdef::{LONGLONG, LPCWSTR, LPWSTR, NTSTATUS, PULONGLONG, PVOID}, + ntstatus::{STATUS_BUFFER_OVERFLOW, STATUS_OBJECT_NAME_COLLISION}, + }, + um::{ + fileapi::LPBY_HANDLE_FILE_INFORMATION, + winnt::{ACCESS_MASK, PSECURITY_DESCRIPTOR, PSECURITY_INFORMATION}, + }, +}; + +use crate::{ + data::{wrap_fill_data, OperationInfo}, + file_system_handler::FileSystemHandler, + operations_helpers::{wrap_nt_result, wrap_unit, NtResult}, +}; + +pub extern "stdcall" fn create_file<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h>( + file_name: LPCWSTR, + security_context: PDOKAN_IO_SECURITY_CONTEXT, + desired_access: ACCESS_MASK, + file_attributes: ULONG, + share_access: ULONG, + create_disposition: ULONG, + create_options: ULONG, + dokan_file_info: PDOKAN_FILE_INFO, +) -> NTSTATUS { + wrap_nt_result(|| unsafe { + let file_name = U16CStr::from_ptr_str(file_name); + let mut info = OperationInfo::<'c, 'h, FSH>::new(dokan_file_info); + info.drop_context(); + info.handler() + .create_file( + file_name, + &*security_context, + desired_access, + file_attributes, + share_access, + create_disposition, + create_options, + &mut info, + ) + .and_then(|create_info| { + (&mut *dokan_file_info).Context = + Box::into_raw(Box::new(create_info.context)) as u64; + (&mut *dokan_file_info).IsDirectory = create_info.is_dir.into(); + if (create_disposition == FILE_OPEN_IF + || create_disposition == FILE_OVERWRITE_IF + || create_disposition == FILE_SUPERSEDE) + && !create_info.new_file_created + { + Err(STATUS_OBJECT_NAME_COLLISION) + } else { + Ok(()) + } + }) + }) +} + +pub extern "stdcall" fn cleanup<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h>( + file_name: LPCWSTR, + dokan_file_info: PDOKAN_FILE_INFO, +) { + wrap_unit(|| unsafe { + let file_name = U16CStr::from_ptr_str(file_name); + let info = OperationInfo::<'c, 'h, FSH>::new(dokan_file_info); + info.handler().cleanup(file_name, &info, info.context()); + }); +} + +pub extern "stdcall" fn close_file<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h>( + file_name: LPCWSTR, + dokan_file_info: PDOKAN_FILE_INFO, +) { + wrap_unit(|| unsafe { + let file_name = U16CStr::from_ptr_str(file_name); + let mut info = OperationInfo::<'c, 'h, FSH>::new(dokan_file_info); + info.handler().close_file(file_name, &info, info.context()); + info.drop_context(); + }); +} + +pub extern "stdcall" fn read_file<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h>( + file_name: LPCWSTR, + buffer: LPVOID, + buffer_length: DWORD, + read_length: LPDWORD, + offset: LONGLONG, + dokan_file_info: PDOKAN_FILE_INFO, +) -> NTSTATUS { + wrap_nt_result(|| unsafe { + *read_length = 0; + let file_name = U16CStr::from_ptr_str(file_name); + let info = OperationInfo::<'c, 'h, FSH>::new(dokan_file_info); + let buffer = slice::from_raw_parts_mut(buffer as *mut _, buffer_length as usize); + info.handler() + .read_file(file_name, offset, buffer, &info, info.context()) + .map(|bytes_read| { + *read_length = bytes_read; + }) + }) +} + +pub extern "stdcall" fn write_file<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h>( + file_name: LPCWSTR, + buffer: LPCVOID, + number_of_bytes_to_write: DWORD, + number_of_bytes_written: LPDWORD, + offset: LONGLONG, + dokan_file_info: PDOKAN_FILE_INFO, +) -> NTSTATUS { + wrap_nt_result(|| unsafe { + *number_of_bytes_written = 0; + let file_name = U16CStr::from_ptr_str(file_name); + let info = OperationInfo::<'c, 'h, FSH>::new(dokan_file_info); + let buffer = slice::from_raw_parts(buffer as *mut _, number_of_bytes_to_write as usize); + info.handler() + .write_file(file_name, offset, buffer, &info, info.context()) + .map(|bytes_written| { + *number_of_bytes_written = bytes_written; + }) + }) +} + +pub extern "stdcall" fn flush_file_buffers<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h>( + file_name: LPCWSTR, + dokan_file_info: PDOKAN_FILE_INFO, +) -> NTSTATUS { + wrap_nt_result(|| unsafe { + let file_name = U16CStr::from_ptr_str(file_name); + let info = OperationInfo::<'c, 'h, FSH>::new(dokan_file_info); + info.handler() + .flush_file_buffers(file_name, &info, info.context()) + }) +} + +pub extern "stdcall" fn get_file_information<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h>( + file_name: LPCWSTR, + buffer: LPBY_HANDLE_FILE_INFORMATION, + dokan_file_info: PDOKAN_FILE_INFO, +) -> NTSTATUS { + wrap_nt_result(|| unsafe { + let file_name = U16CStr::from_ptr_str(file_name); + let info = OperationInfo::<'c, 'h, FSH>::new(dokan_file_info); + info.handler() + .get_file_information(file_name, &info, info.context()) + .map(|file_info| { + *buffer = file_info.to_raw_struct(); + }) + }) +} + +pub extern "stdcall" fn find_files<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h>( + file_name: LPCWSTR, + fill_find_data: PFillFindData, + dokan_file_info: PDOKAN_FILE_INFO, +) -> NTSTATUS { + wrap_nt_result(|| unsafe { + let file_name = U16CStr::from_ptr_str(file_name); + let fill_wrapper = wrap_fill_data(fill_find_data, dokan_file_info, 0); + let info = OperationInfo::<'c, 'h, FSH>::new(dokan_file_info); + info.handler() + .find_files(file_name, fill_wrapper, &info, info.context()) + }) +} + +pub extern "stdcall" fn find_files_with_pattern<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h>( + file_name: LPCWSTR, + search_pattern: LPCWSTR, + fill_find_data: PFillFindData, + dokan_file_info: PDOKAN_FILE_INFO, +) -> NTSTATUS { + wrap_nt_result(|| unsafe { + let file_name = U16CStr::from_ptr_str(file_name); + let search_pattern = U16CStr::from_ptr_str(search_pattern); + let fill_wrapper = wrap_fill_data(fill_find_data, dokan_file_info, 0); + let info = OperationInfo::<'c, 'h, FSH>::new(dokan_file_info); + info.handler().find_files_with_pattern( + file_name, + search_pattern, + fill_wrapper, + &info, + info.context(), + ) + }) +} + +pub extern "stdcall" fn set_file_attributes<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h>( + file_name: LPCWSTR, + file_attributes: DWORD, + dokan_file_info: PDOKAN_FILE_INFO, +) -> NTSTATUS { + wrap_nt_result(|| unsafe { + let file_name = U16CStr::from_ptr_str(file_name); + let info = OperationInfo::<'c, 'h, FSH>::new(dokan_file_info); + info.handler() + .set_file_attributes(file_name, file_attributes, &info, info.context()) + }) +} + +pub extern "stdcall" fn set_file_time<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h>( + file_name: LPCWSTR, + creation_time: *const FILETIME, + last_access_time: *const FILETIME, + last_write_time: *const FILETIME, + dokan_file_info: PDOKAN_FILE_INFO, +) -> NTSTATUS { + wrap_nt_result(|| unsafe { + let file_name = U16CStr::from_ptr_str(file_name); + let info = OperationInfo::<'c, 'h, FSH>::new(dokan_file_info); + info.handler().set_file_time( + file_name, + creation_time.into(), + last_access_time.into(), + last_write_time.into(), + &info, + info.context(), + ) + }) +} + +pub extern "stdcall" fn delete_file<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h>( + file_name: LPCWSTR, + dokan_file_info: PDOKAN_FILE_INFO, +) -> NTSTATUS { + wrap_nt_result(|| unsafe { + let file_name = U16CStr::from_ptr_str(file_name); + let info = OperationInfo::<'c, 'h, FSH>::new(dokan_file_info); + info.handler().delete_file(file_name, &info, info.context()) + }) +} + +pub extern "stdcall" fn delete_directory<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h>( + file_name: LPCWSTR, + dokan_file_info: PDOKAN_FILE_INFO, +) -> NTSTATUS { + wrap_nt_result(|| unsafe { + let file_name = U16CStr::from_ptr_str(file_name); + let info = OperationInfo::<'c, 'h, FSH>::new(dokan_file_info); + info.handler() + .delete_directory(file_name, &info, info.context()) + }) +} + +pub extern "stdcall" fn move_file<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h>( + file_name: LPCWSTR, + new_file_name: LPCWSTR, + replace_if_existing: BOOL, + dokan_file_info: PDOKAN_FILE_INFO, +) -> NTSTATUS { + wrap_nt_result(|| unsafe { + let file_name = U16CStr::from_ptr_str(file_name); + let new_file_name = U16CStr::from_ptr_str(new_file_name); + let info = OperationInfo::<'c, 'h, FSH>::new(dokan_file_info); + info.handler().move_file( + file_name, + new_file_name, + replace_if_existing == TRUE, + &info, + info.context(), + ) + }) +} + +pub extern "stdcall" fn set_end_of_file<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h>( + file_name: LPCWSTR, + byte_offset: LONGLONG, + dokan_file_info: PDOKAN_FILE_INFO, +) -> NTSTATUS { + wrap_nt_result(|| unsafe { + let file_name = U16CStr::from_ptr_str(file_name); + let info = OperationInfo::<'c, 'h, FSH>::new(dokan_file_info); + info.handler() + .set_end_of_file(file_name, byte_offset, &info, info.context()) + }) +} + +pub extern "stdcall" fn set_allocation_size<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h>( + file_name: LPCWSTR, + alloc_size: LONGLONG, + dokan_file_info: PDOKAN_FILE_INFO, +) -> NTSTATUS { + wrap_nt_result(|| unsafe { + let file_name = U16CStr::from_ptr_str(file_name); + let info = OperationInfo::<'c, 'h, FSH>::new(dokan_file_info); + info.handler() + .set_allocation_size(file_name, alloc_size, &info, info.context()) + }) +} + +// Extern stdcall functions with similar bodies but not called directly with trigger a compiler bug when built in +// release mode. It seems that extracting the function bodies into a common function works around this bug. +// See https://github.com/rust-lang/rust/issues/72212 +fn lock_unlock_file<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h>( + file_name: LPCWSTR, + byte_offset: LONGLONG, + length: LONGLONG, + dokan_file_info: PDOKAN_FILE_INFO, + func: fn( + &'h FSH, + &U16CStr, + i64, + i64, + &OperationInfo<'c, 'h, FSH>, + &'c FSH::Context, + ) -> NtResult, +) -> NTSTATUS { + wrap_nt_result(|| unsafe { + let file_name = U16CStr::from_ptr_str(file_name); + let info = OperationInfo::<'c, 'h, FSH>::new(dokan_file_info); + func( + info.handler(), + file_name, + byte_offset, + length, + &info, + info.context(), + ) + }) +} + +pub extern "stdcall" fn lock_file<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h>( + file_name: LPCWSTR, + byte_offset: LONGLONG, + length: LONGLONG, + dokan_file_info: PDOKAN_FILE_INFO, +) -> NTSTATUS { + lock_unlock_file( + file_name, + byte_offset, + length, + dokan_file_info, + FSH::lock_file, + ) +} + +pub extern "stdcall" fn unlock_file<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h>( + file_name: LPCWSTR, + byte_offset: LONGLONG, + length: LONGLONG, + dokan_file_info: PDOKAN_FILE_INFO, +) -> NTSTATUS { + lock_unlock_file( + file_name, + byte_offset, + length, + dokan_file_info, + FSH::unlock_file, + ) +} + +pub extern "stdcall" fn get_disk_free_space<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h>( + free_bytes_available: PULONGLONG, + total_number_of_bytes: PULONGLONG, + total_number_of_free_bytes: PULONGLONG, + dokan_file_info: PDOKAN_FILE_INFO, +) -> NTSTATUS { + wrap_nt_result(|| { + let info = OperationInfo::<'c, 'h, FSH>::new(dokan_file_info); + info.handler() + .get_disk_free_space(&info) + .map(|space_info| unsafe { + if !free_bytes_available.is_null() { + *free_bytes_available = space_info.available_byte_count; + } + if !total_number_of_bytes.is_null() { + *total_number_of_bytes = space_info.byte_count; + } + if !total_number_of_free_bytes.is_null() { + *total_number_of_free_bytes = space_info.free_byte_count; + } + }) + }) +} + +pub extern "stdcall" fn get_volume_information<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h>( + volume_name_buffer: LPWSTR, + volume_name_size: DWORD, + volume_serial_number: LPDWORD, + maximum_component_length: LPDWORD, + file_system_flags: LPDWORD, + file_system_name_buffer: LPWSTR, + file_system_name_size: DWORD, + dokan_file_info: PDOKAN_FILE_INFO, +) -> NTSTATUS { + wrap_nt_result(|| { + let info = OperationInfo::<'c, 'h, FSH>::new(dokan_file_info); + info.handler() + .get_volume_information(&info) + .map(|volume_info| unsafe { + volume_name_buffer.copy_from_nonoverlapping( + volume_info.name.as_ptr(), + (volume_info.name.len() + 1).min(volume_name_size as usize), + ); + if !volume_serial_number.is_null() { + *volume_serial_number = volume_info.serial_number; + } + if !maximum_component_length.is_null() { + *maximum_component_length = volume_info.max_component_length; + } + if !file_system_flags.is_null() { + *file_system_flags = volume_info.fs_flags; + } + file_system_name_buffer.copy_from_nonoverlapping( + volume_info.fs_name.as_ptr(), + (volume_info.fs_name.len() + 1).min(file_system_name_size as usize), + ); + }) + }) +} + +pub extern "stdcall" fn mounted<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h>( + mount_point: LPCWSTR, + dokan_file_info: PDOKAN_FILE_INFO, +) -> NTSTATUS { + wrap_nt_result(|| unsafe { + let mount_point = U16CStr::from_ptr_str(mount_point); + let info = OperationInfo::<'c, 'h, FSH>::new(dokan_file_info); + info.handler().mounted(mount_point, &info) + }) +} + +pub extern "stdcall" fn unmounted<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h>( + dokan_file_info: PDOKAN_FILE_INFO, +) -> NTSTATUS { + wrap_nt_result(|| { + let info = OperationInfo::<'c, 'h, FSH>::new(dokan_file_info); + info.handler().unmounted(&info) + }) +} + +pub extern "stdcall" fn get_file_security<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h>( + file_name: LPCWSTR, + security_information: PSECURITY_INFORMATION, + security_descriptor: PSECURITY_DESCRIPTOR, + buffer_length: ULONG, + length_needed: PULONG, + dokan_file_info: PDOKAN_FILE_INFO, +) -> NTSTATUS { + wrap_nt_result(|| unsafe { + let file_name = U16CStr::from_ptr_str(file_name); + let info = OperationInfo::<'c, 'h, FSH>::new(dokan_file_info); + info.handler() + .get_file_security( + file_name, + *security_information, + security_descriptor, + buffer_length, + &info, + info.context(), + ) + .and_then(|needed| { + *length_needed = needed; + if needed <= buffer_length { + Ok(()) + } else { + Err(STATUS_BUFFER_OVERFLOW) + } + }) + }) +} + +pub extern "stdcall" fn set_file_security<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h>( + file_name: LPCWSTR, + security_information: PSECURITY_INFORMATION, + security_descriptor: PSECURITY_DESCRIPTOR, + buffer_length: ULONG, + dokan_file_info: PDOKAN_FILE_INFO, +) -> NTSTATUS { + wrap_nt_result(|| unsafe { + let file_name = U16CStr::from_ptr_str(file_name); + let info = OperationInfo::<'c, 'h, FSH>::new(dokan_file_info); + info.handler().set_file_security( + file_name, + *security_information, + security_descriptor, + buffer_length, + &info, + info.context(), + ) + }) +} + +pub extern "stdcall" fn find_streams<'c, 'h: 'c, FSH: FileSystemHandler<'c, 'h> + 'h>( + file_name: LPCWSTR, + fill_find_stream_data: PFillFindStreamData, + find_stream_context: PVOID, + dokan_file_info: PDOKAN_FILE_INFO, +) -> NTSTATUS { + wrap_nt_result(|| unsafe { + let file_name = U16CStr::from_ptr_str(file_name); + let fill_wrapper = wrap_fill_data(fill_find_stream_data, find_stream_context, 1); + let info = OperationInfo::<'c, 'h, FSH>::new(dokan_file_info); + info.handler() + .find_streams(file_name, fill_wrapper, &info, info.context()) + }) +} diff --git a/dokan/src/operations_helpers.rs b/dokan/src/operations_helpers.rs new file mode 100644 index 0000000..0f10980 --- /dev/null +++ b/dokan/src/operations_helpers.rs @@ -0,0 +1,22 @@ +use std::panic::{self, UnwindSafe}; + +use winapi::shared::{ + ntdef::NTSTATUS, + ntstatus::{STATUS_INTERNAL_ERROR, STATUS_SUCCESS}, +}; + +pub type NtResult = Result<(), NTSTATUS>; + +pub fn wrap_nt_result NtResult + UnwindSafe>(f: F) -> NTSTATUS { + panic::catch_unwind(f) + .map(|result| match result { + Ok(_) => STATUS_SUCCESS, + Err(nt_status) => nt_status, + }) + .unwrap_or(STATUS_INTERNAL_ERROR) +} + +#[allow(unused_must_use)] +pub fn wrap_unit(f: F) { + panic::catch_unwind(f); +} diff --git a/dokan/src/to_file_time.rs b/dokan/src/to_file_time.rs new file mode 100644 index 0000000..b6a794d --- /dev/null +++ b/dokan/src/to_file_time.rs @@ -0,0 +1,22 @@ +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +use winapi::shared::minwindef::FILETIME; + +pub const FILETIME_OFFSET: Duration = Duration::from_secs(11644473600); + +pub trait ToFileTime { + fn to_filetime(&self) -> FILETIME; +} + +impl ToFileTime for SystemTime { + fn to_filetime(&self) -> FILETIME { + let intervals = self + .duration_since(UNIX_EPOCH - FILETIME_OFFSET) + .unwrap_or(Duration::from_secs(0)) + .as_nanos() / 100; + FILETIME { + dwLowDateTime: intervals as u32, + dwHighDateTime: (intervals >> 32) as u32, + } + } +} diff --git a/dokan/src/tests.rs b/dokan/src/usage_tests.rs similarity index 67% rename from dokan/src/tests.rs rename to dokan/src/usage_tests.rs index 60bba29..337b7ad 100644 --- a/dokan/src/tests.rs +++ b/dokan/src/usage_tests.rs @@ -2,138 +2,106 @@ extern crate lazy_static; extern crate parking_lot; extern crate regex; -use std::pin::Pin; -use std::process; -use std::sync::mpsc::{self, Receiver, SyncSender}; -use std::thread; +use std::{ + cell::RefCell, + fmt::Debug, + mem, + os::windows::prelude::{AsRawHandle, FromRawHandle, OwnedHandle}, + pin::Pin, + process, ptr, + sync::mpsc::{self, Receiver, SyncSender}, + thread, + time::{Duration, UNIX_EPOCH}, +}; +use dokan_sys::win32::{ + FILE_NON_DIRECTORY_FILE, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, FILE_WRITE_THROUGH, + WIN32_FIND_STREAM_DATA, +}; 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}; -use winapi::shared::sddl::ConvertSidToStringSidW; -use winapi::shared::winerror::{ - ERROR_HANDLE_EOF, ERROR_INSUFFICIENT_BUFFER, ERROR_INTERNAL_ERROR, ERROR_IO_PENDING, - ERROR_NO_MORE_FILES, ERROR_SUCCESS, +use widestring::{U16CStr, U16CString}; +use winapi::{ + shared::{ + minwindef::{BOOL, FALSE, HLOCAL, LPCVOID, LPVOID, MAX_PATH, TRUE}, + ntdef::{HANDLE, NTSTATUS, NULL}, + ntstatus::{STATUS_ACCESS_DENIED, STATUS_NOT_IMPLEMENTED, STATUS_SUCCESS}, + sddl::ConvertSidToStringSidW, + winerror::{ + ERROR_HANDLE_EOF, ERROR_INSUFFICIENT_BUFFER, ERROR_INTERNAL_ERROR, ERROR_IO_PENDING, + ERROR_NO_MORE_FILES, + }, + }, + um::{ + errhandlingapi::GetLastError, + fileapi::*, + handleapi::{CloseHandle, INVALID_HANDLE_VALUE}, + ioapiset::GetOverlappedResult, + minwinbase::OVERLAPPED, + processthreadsapi::{GetCurrentProcess, OpenProcessToken}, + securitybaseapi::*, + synchapi::CreateEventW, + winbase::*, + winnt::*, + }, }; -use winapi::um::errhandlingapi::GetLastError; -use winapi::um::fileapi::*; -use winapi::um::ioapiset::GetOverlappedResult; -use winapi::um::minwinbase::OVERLAPPED; -use winapi::um::processthreadsapi::{GetCurrentProcess, OpenProcessToken, ProcessIdToSessionId}; -use winapi::um::securitybaseapi::*; -use winapi::um::synchapi::CreateEventW; -use winapi::um::winbase::*; -use winapi::um::winnt::*; - -use super::*; - -const FILE_OPEN: u32 = 1; -const FILE_WRITE_THROUGH: u32 = 2; -const FILE_SYNCHRONOUS_IO_NONALERT: u32 = 32; -const FILE_NON_DIRECTORY_FILE: u32 = 64; -const FILE_DEVICE_DISK_FILE_SYSTEM: u32 = 8; -#[test] -fn test_version() { - assert_eq!(MAJOR_API_VERSION, (lib_version() / 100).to_string()); - assert!(driver_version() < 1000); - assert_eq!(DRIVER_NAME, format!("dokan{}.sys", MAJOR_API_VERSION)); - assert_eq!(NP_NAME, format!("Dokan{}", MAJOR_API_VERSION)); -} +use crate::{ + data::{ + CreateFileInfo, DiskSpaceInfo, FileInfo, FileTimeOperation, FillDataResult, FindData, + FindStreamData, OperationInfo, VolumeInfo, + }, + file_system_handler::OperationResult, + init, notify_create, notify_delete, notify_rename, notify_update, notify_xattr_update, + operations_helpers::NtResult, + shutdown, + to_file_time::ToFileTime, + unmount, FileSystemHandle, FileSystemHandler, FileSystemMounter, MountFlags, MountOptions, + IO_SECURITY_CONTEXT, +}; -fn convert_str(s: impl AsRef) -> U16CString { +pub fn convert_str(s: impl AsRef) -> U16CString { unsafe { U16CString::from_str_unchecked(s) } } -#[test] -fn test_name_in_expression() { - assert!(is_name_in_expression( - convert_str("foo"), - convert_str("foo"), - true - )); - assert!(is_name_in_expression( - convert_str("*"), - convert_str("foo"), - true - )); - assert!(is_name_in_expression( - convert_str("?"), - convert_str("x"), - true - )); - assert!(!is_name_in_expression( - convert_str("?"), - convert_str("foo"), - true - )); - assert!(is_name_in_expression( - convert_str("F*"), - convert_str("foo"), - true - )); - assert!(!is_name_in_expression( - convert_str("F*"), - convert_str("foo"), - false - )); -} - -#[test] -fn test_map_flags() { - let result = map_kernel_to_user_create_file_flags( - FILE_ALL_ACCESS, - FILE_ATTRIBUTE_NORMAL, - FILE_WRITE_THROUGH, - FILE_OPEN, - ); - assert_eq!( - result.desired_access, - GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE | GENERIC_ALL - ); - assert_eq!( - result.flags_and_attributes, - FILE_FLAG_WRITE_THROUGH | FILE_ATTRIBUTE_NORMAL - ); - assert_eq!(result.creation_disposition, OPEN_EXISTING); -} - -#[test] -fn test_ntstatus() { - assert_eq!( - OperationError::NtStatus(STATUS_SUCCESS).ntstatus(), - STATUS_INTERNAL_ERROR - ); - assert_eq!( - OperationError::Win32(ERROR_SUCCESS).ntstatus(), - STATUS_INTERNAL_ERROR - ); - - let err_nt = OperationError::NtStatus(STATUS_INTERNAL_ERROR); - let err_win32 = OperationError::Win32(ERROR_INTERNAL_ERROR); - assert_eq!(err_nt.ntstatus(), err_win32.ntstatus()); - - assert_eq!(Ok::<(), OperationError>(()).ntstatus(), STATUS_SUCCESS); - assert_eq!( - Err::<(), OperationError>(err_nt).ntstatus(), - STATUS_INTERNAL_ERROR - ); +macro_rules! assert_eq_win32 { + ($left:expr, $right:expr) => { + match (&$left, &$right) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let last_error = GetLastError(); + panic!( + "assert_eq_win32 failed + left: {:?} + right: {:?} +last error: {:#x}", + &*left_val, &*right_val, last_error + ); + } + } + } + }; } -#[test] -fn test_debug_mode() { - set_debug_stream(DebugStream::Stdout); - set_debug_stream(DebugStream::Stderr); - set_lib_debug_mode(true); - set_lib_debug_mode(false); - assert!(set_driver_debug_mode(true)); - assert!(set_driver_debug_mode(false)); +macro_rules! assert_ne_win32 { + ($left:expr, $right:expr) => { + match (&$left, &$right) { + (left_val, right_val) => { + if *left_val == *right_val { + let last_error = GetLastError(); + panic!( + "assert_ne_win32 failed + left: {:?} + right: {:?} +last error: {:#x}", + &*left_val, &*right_val, last_error + ); + } + } + } + }; } -struct TestContext { +pub struct TestContext { tx: SyncSender, } @@ -144,25 +112,25 @@ impl Drop for TestContext { } #[derive(Debug, Clone, Eq, PartialEq)] -struct OperationInfoDump { - pid: u32, - is_dir: bool, - delete_on_close: bool, - paging_io: bool, - synchronous_io: bool, - no_cache: bool, - write_to_eof: bool, - single_thread: bool, - mount_flags: MountFlags, - mount_point: Option, - unc_name: Option, - timeout: Duration, - allocation_unit_size: u32, - sector_size: u32, +pub struct OperationInfoDump { + pub pid: u32, + pub is_dir: bool, + pub delete_on_close: bool, + pub paging_io: bool, + pub synchronous_io: bool, + pub no_cache: bool, + pub write_to_eof: bool, + pub single_thread: bool, + pub mount_flags: MountFlags, + pub mount_point: Option, + pub unc_name: Option, + pub timeout: Duration, + pub allocation_unit_size: u32, + pub sector_size: u32, } #[derive(Debug, Clone, Eq, PartialEq)] -enum HandlerSignal { +pub enum HandlerSignal { Mounted, Unmounted, CreateFile(u32, u32, u32, u32, u32), @@ -174,7 +142,7 @@ enum HandlerSignal { FlushFileBuffers, FindFilesWithPattern(U16CString), SetFileAttributes(u32), - SetFileTime(FileTimeInfo, FileTimeInfo, FileTimeInfo), + SetFileTime(FileTimeOperation, FileTimeOperation, FileTimeOperation), DeleteFile(bool), DeleteDirectory(bool), MoveFile(U16CString, bool), @@ -188,20 +156,22 @@ enum HandlerSignal { OperationInfo(OperationInfoDump), } -struct DokanInstance(DOKAN_HANDLE); - -unsafe impl Send for DokanInstance {} - #[derive(Debug)] -struct TestHandler { +pub struct TestHandler { tx: SyncSender, } -fn check_pid(pid: u32) -> Result<(), OperationError> { +impl TestHandler { + pub fn new(tx: SyncSender) -> Self { + Self { tx } + } +} + +fn check_pid(pid: u32) -> NtResult { if process::id() == pid { Ok(()) } else { - Err(OperationError::NtStatus(STATUS_ACCESS_DENIED)) + Err(STATUS_ACCESS_DENIED) } } @@ -211,9 +181,9 @@ fn get_descriptor_owner(desc: PSECURITY_DESCRIPTOR) -> (U16CString, BOOL) { let mut owner_defaulted = 0; GetSecurityDescriptorOwner(desc, &mut psid, &mut owner_defaulted); let mut ps = ptr::null_mut(); - assert_eq!(ConvertSidToStringSidW(psid, &mut ps), TRUE); + assert_eq_win32!(ConvertSidToStringSidW(psid, &mut ps), TRUE); let sid = U16CStr::from_ptr_str(ps).to_owned(); - assert_eq!(LocalFree(ps as HLOCAL), NULL); + assert_eq_win32!(LocalFree(ps as HLOCAL), NULL); (sid, owner_defaulted) } } @@ -221,13 +191,13 @@ fn get_descriptor_owner(desc: PSECURITY_DESCRIPTOR) -> (U16CString, BOOL) { fn get_user_info(token: HANDLE) -> Pin>> { unsafe { let mut user_info_len = 0; - assert_eq!( + assert_eq_win32!( GetTokenInformation(token, TokenUser, ptr::null_mut(), 0, &mut user_info_len), FALSE ); assert_eq!(GetLastError(), ERROR_INSUFFICIENT_BUFFER); let mut user_info_buffer = Box::pin(vec![0; user_info_len as usize]); - assert_eq!( + assert_eq_win32!( GetTokenInformation( token, TokenUser, @@ -245,12 +215,12 @@ fn get_user_info(token: HANDLE) -> Pin>> { fn get_current_user_info() -> Pin>> { unsafe { let mut token = ptr::null_mut(); - assert_eq!( + assert_eq_win32!( OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token), TRUE ); let info = get_user_info(token); - assert_eq!(CloseHandle(token), TRUE); + assert_eq_win32!(CloseHandle(token), TRUE); info } } @@ -261,22 +231,22 @@ fn create_test_descriptor() -> Vec { let user_info = &*(user_info_buffer.as_mut_ptr() as PTOKEN_USER); let mut abs_desc = mem::zeroed::(); let abs_desc_ptr = &mut abs_desc as *mut _ as PSECURITY_DESCRIPTOR; - assert_eq!( + assert_eq_win32!( InitializeSecurityDescriptor(abs_desc_ptr, SECURITY_DESCRIPTOR_REVISION), TRUE ); - assert_eq!( + assert_eq_win32!( SetSecurityDescriptorOwner(abs_desc_ptr, user_info.User.Sid, FALSE), TRUE ); let mut rel_desc_len = 0; - assert_eq!( + assert_eq_win32!( MakeSelfRelativeSD(abs_desc_ptr, ptr::null_mut(), &mut rel_desc_len), FALSE ); assert_eq!(GetLastError(), ERROR_INSUFFICIENT_BUFFER); let mut rel_desc_buffer = vec![0; rel_desc_len as usize]; - assert_eq!( + assert_eq_win32!( MakeSelfRelativeSD( abs_desc_ptr, rel_desc_buffer.as_mut_ptr() as PSECURITY_DESCRIPTOR, @@ -295,14 +265,14 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { fn create_file( &'b self, file_name: &U16CStr, - _security_context: &DOKAN_IO_SECURITY_CONTEXT, + _security_context: &IO_SECURITY_CONTEXT, desired_access: u32, file_attributes: u32, share_access: u32, create_disposition: u32, create_options: u32, info: &mut OperationInfo<'a, 'b, Self>, - ) -> Result, OperationError> { + ) -> Result, NTSTATUS> { let file_name = file_name.to_string_lossy(); match file_name.as_ref() { "\\test_file_io" @@ -402,7 +372,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { is_dir: false, new_file_created: false, }), - _ => Err(OperationError::NtStatus(STATUS_ACCESS_DENIED)), + _ => Err(STATUS_ACCESS_DENIED), } } @@ -437,7 +407,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { buffer: &mut [u8], info: &OperationInfo<'a, 'b, Self>, _context: &'a Self::Context, - ) -> Result { + ) -> OperationResult { check_pid(info.pid())?; let file_name = file_name.to_string_lossy(); if &file_name == "\\test_file_io" { @@ -449,7 +419,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { .unwrap(); Ok(data.len() as u32) } else { - Err(OperationError::NtStatus(STATUS_ACCESS_DENIED)) + Err(STATUS_ACCESS_DENIED) } } @@ -460,7 +430,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { buffer: &[u8], info: &OperationInfo<'a, 'b, Self>, _context: &'a Self::Context, - ) -> Result { + ) -> OperationResult { check_pid(info.pid())?; let file_name = file_name.to_string_lossy(); if &file_name == "\\test_file_io" { @@ -469,7 +439,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { .unwrap(); Ok(buffer.len() as u32) } else { - Err(OperationError::NtStatus(STATUS_ACCESS_DENIED)) + Err(STATUS_ACCESS_DENIED) } } @@ -478,14 +448,14 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { file_name: &U16CStr, info: &OperationInfo<'a, 'b, Self>, _context: &'a Self::Context, - ) -> Result<(), OperationError> { + ) -> OperationResult<()> { check_pid(info.pid())?; let file_name = file_name.to_string_lossy(); if &file_name == "\\test_file_io" { self.tx.send(HandlerSignal::FlushFileBuffers).unwrap(); Ok(()) } else { - Err(OperationError::NtStatus(STATUS_ACCESS_DENIED)) + Err(STATUS_ACCESS_DENIED) } } @@ -494,7 +464,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { _file_name: &U16CStr, info: &OperationInfo<'a, 'b, Self>, _context: &'a Self::Context, - ) -> Result { + ) -> OperationResult { check_pid(info.pid())?; Ok(FileInfo { attributes: if info.is_dir() { @@ -514,25 +484,23 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { fn find_files( &'b self, file_name: &U16CStr, - mut fill_find_data: impl FnMut(&FindData) -> Result<(), FillDataError>, + mut fill_find_data: impl FnMut(&FindData) -> FillDataResult, info: &OperationInfo<'a, 'b, Self>, _context: &'a Self::Context, - ) -> Result<(), OperationError> { + ) -> OperationResult<()> { check_pid(info.pid())?; let file_name = file_name.to_string_lossy(); match file_name.as_ref() { - "\\test_find_files" => { - fill_find_data(&FindData { - attributes: FILE_ATTRIBUTE_NORMAL, - creation_time: UNIX_EPOCH, - last_access_time: UNIX_EPOCH + Duration::from_secs(1), - last_write_time: UNIX_EPOCH + Duration::from_secs(2), - file_size: (1 << 32) + 2, - file_name: convert_str("test_inner_file"), - })?; - Ok(()) - } - _ => Err(OperationError::NtStatus(STATUS_ACCESS_DENIED)), + "\\test_find_files" => fill_find_data(&FindData { + attributes: FILE_ATTRIBUTE_NORMAL, + creation_time: UNIX_EPOCH, + last_access_time: UNIX_EPOCH + Duration::from_secs(1), + last_write_time: UNIX_EPOCH + Duration::from_secs(2), + file_size: (1 << 32) + 2, + file_name: convert_str("test_inner_file"), + }) + .map_err(Into::into), + _ => Err(STATUS_ACCESS_DENIED), } } @@ -540,29 +508,29 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { &'b self, file_name: &U16CStr, pattern: &U16CStr, - mut fill_find_data: impl FnMut(&FindData) -> Result<(), FillDataError>, + mut fill_find_data: impl FnMut(&FindData) -> FillDataResult, info: &OperationInfo<'a, 'b, Self>, _context: &'a Self::Context, - ) -> Result<(), OperationError> { + ) -> OperationResult<()> { check_pid(info.pid())?; let file_name = file_name.to_string_lossy(); match file_name.as_ref() { - "\\test_find_files" => Err(OperationError::NtStatus(STATUS_NOT_IMPLEMENTED)), - "\\test_find_files_with_pattern" => { - fill_find_data(&FindData { - attributes: FILE_ATTRIBUTE_NORMAL, - creation_time: UNIX_EPOCH, - last_access_time: UNIX_EPOCH + Duration::from_secs(1), - last_write_time: UNIX_EPOCH + Duration::from_secs(2), - file_size: (1 << 32) + 2, - file_name: convert_str("test_inner_file_with_pattern"), - })?; + "\\test_find_files" => Err(STATUS_NOT_IMPLEMENTED), + "\\test_find_files_with_pattern" => fill_find_data(&FindData { + attributes: FILE_ATTRIBUTE_NORMAL, + creation_time: UNIX_EPOCH, + last_access_time: UNIX_EPOCH + Duration::from_secs(1), + last_write_time: UNIX_EPOCH + Duration::from_secs(2), + file_size: (1 << 32) + 2, + file_name: convert_str("test_inner_file_with_pattern"), + }) + .map(|_| { self.tx .send(HandlerSignal::FindFilesWithPattern(pattern.to_owned())) .unwrap(); - Ok(()) - } - _ => Err(OperationError::NtStatus(STATUS_ACCESS_DENIED)), + }) + .map_err(Into::into), + _ => Err(STATUS_ACCESS_DENIED), } } @@ -572,7 +540,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { file_attributes: u32, info: &OperationInfo<'a, 'b, Self>, _context: &'a Self::Context, - ) -> Result<(), OperationError> { + ) -> OperationResult<()> { check_pid(info.pid())?; let file_name = file_name.to_string_lossy(); match file_name.as_ref() { @@ -583,19 +551,19 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { Ok(()) } "\\test_set_file_time" => Ok(()), - _ => Err(OperationError::NtStatus(STATUS_ACCESS_DENIED)), + _ => Err(STATUS_ACCESS_DENIED), } } fn set_file_time( &'b self, file_name: &U16CStr, - creation_time: FileTimeInfo, - last_access_time: FileTimeInfo, - last_write_time: FileTimeInfo, + creation_time: FileTimeOperation, + last_access_time: FileTimeOperation, + last_write_time: FileTimeOperation, info: &OperationInfo<'a, 'b, Self>, _context: &'a Self::Context, - ) -> Result<(), OperationError> { + ) -> OperationResult<()> { check_pid(info.pid())?; let file_name = file_name.to_string_lossy(); match file_name.as_ref() { @@ -610,7 +578,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { Ok(()) } "\\test_set_file_attributes" => Ok(()), - _ => Err(OperationError::NtStatus(STATUS_ACCESS_DENIED)), + _ => Err(STATUS_ACCESS_DENIED), } } @@ -619,7 +587,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { file_name: &U16CStr, info: &OperationInfo<'a, 'b, Self>, _context: &'a Self::Context, - ) -> Result<(), OperationError> { + ) -> OperationResult<()> { check_pid(info.pid())?; let file_name = file_name.to_string_lossy(); if &file_name == "\\test_delete_file" { @@ -628,7 +596,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { .unwrap(); Ok(()) } else { - Err(OperationError::NtStatus(STATUS_ACCESS_DENIED)) + Err(STATUS_ACCESS_DENIED) } } @@ -637,7 +605,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { file_name: &U16CStr, info: &OperationInfo<'a, 'b, Self>, _context: &'a Self::Context, - ) -> Result<(), OperationError> { + ) -> OperationResult<()> { check_pid(info.pid())?; let file_name = file_name.to_string_lossy(); if &file_name == "\\test_delete_directory" { @@ -646,7 +614,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { .unwrap(); Ok(()) } else { - Err(OperationError::NtStatus(STATUS_ACCESS_DENIED)) + Err(STATUS_ACCESS_DENIED) } } @@ -657,7 +625,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { replace_if_existing: bool, info: &OperationInfo<'a, 'b, Self>, _context: &'a Self::Context, - ) -> Result<(), OperationError> { + ) -> OperationResult<()> { check_pid(info.pid())?; let file_name = file_name.to_string_lossy(); if &file_name == "\\test_move_file" { @@ -669,7 +637,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { .unwrap(); Ok(()) } else { - Err(OperationError::NtStatus(STATUS_ACCESS_DENIED)) + Err(STATUS_ACCESS_DENIED) } } @@ -679,7 +647,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { offset: i64, info: &OperationInfo<'a, 'b, Self>, _context: &'a Self::Context, - ) -> Result<(), OperationError> { + ) -> OperationResult<()> { check_pid(info.pid())?; let file_name = file_name.to_string_lossy(); match file_name.as_ref() { @@ -688,7 +656,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { Ok(()) } "\\test_set_allocation_size" => Ok(()), - _ => Err(OperationError::NtStatus(STATUS_ACCESS_DENIED)), + _ => Err(STATUS_ACCESS_DENIED), } } @@ -698,7 +666,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { alloc_size: i64, info: &OperationInfo<'a, 'b, Self>, _context: &'a Self::Context, - ) -> Result<(), OperationError> { + ) -> OperationResult<()> { check_pid(info.pid())?; let file_name = file_name.to_string_lossy(); if &file_name == "\\test_set_allocation_size" { @@ -707,7 +675,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { .unwrap(); Ok(()) } else { - Err(OperationError::NtStatus(STATUS_ACCESS_DENIED)) + Err(STATUS_ACCESS_DENIED) } } @@ -718,7 +686,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { length: i64, info: &OperationInfo<'a, 'b, Self>, _context: &'a Self::Context, - ) -> Result<(), OperationError> { + ) -> OperationResult<()> { check_pid(info.pid())?; let file_name = file_name.to_string_lossy(); if &file_name == "\\test_lock_unlock_file" { @@ -727,7 +695,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { .unwrap(); Ok(()) } else { - Err(OperationError::NtStatus(STATUS_ACCESS_DENIED)) + Err(STATUS_ACCESS_DENIED) } } @@ -738,7 +706,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { length: i64, info: &OperationInfo<'a, 'b, Self>, _context: &'a Self::Context, - ) -> Result<(), OperationError> { + ) -> OperationResult<()> { check_pid(info.pid())?; let file_name = file_name.to_string_lossy(); if &file_name == "\\test_lock_unlock_file" { @@ -747,14 +715,14 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { .unwrap(); Ok(()) } else { - Err(OperationError::NtStatus(STATUS_ACCESS_DENIED)) + Err(STATUS_ACCESS_DENIED) } } fn get_disk_free_space( &'b self, _info: &OperationInfo<'a, 'b, Self>, - ) -> Result { + ) -> OperationResult { Ok(DiskSpaceInfo { byte_count: 2 * 1024 * 1024, free_byte_count: 1024 * 1024, @@ -765,7 +733,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { fn get_volume_information( &'b self, _info: &OperationInfo<'a, 'b, Self>, - ) -> Result { + ) -> OperationResult { Ok(VolumeInfo { name: convert_str("Test Drive"), serial_number: 1, @@ -782,12 +750,12 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { &'b self, _mount_point: &U16CStr, _info: &OperationInfo<'a, 'b, Self>, - ) -> Result<(), OperationError> { + ) -> OperationResult<()> { self.tx.send(HandlerSignal::Mounted).unwrap(); Ok(()) } - fn unmounted(&'b self, _info: &OperationInfo<'a, 'b, Self>) -> Result<(), OperationError> { + fn unmounted(&'b self, _info: &OperationInfo<'a, 'b, Self>) -> OperationResult<()> { self.tx.send(HandlerSignal::Unmounted).unwrap(); Ok(()) } @@ -800,7 +768,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { buffer_length: u32, info: &OperationInfo<'a, 'b, Self>, _context: &'a Self::Context, - ) -> Result { + ) -> OperationResult { check_pid(info.pid())?; let file_name = file_name.to_string_lossy(); match file_name.as_ref() { @@ -822,7 +790,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { result } "\\test_get_file_security_overflow" => Ok(buffer_length + 1), - _ => Err(OperationError::NtStatus(STATUS_ACCESS_DENIED)), + _ => Err(STATUS_ACCESS_DENIED), } } @@ -834,7 +802,7 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { buffer_length: u32, info: &OperationInfo<'a, 'b, Self>, _context: &'a Self::Context, - ) -> Result<(), OperationError> { + ) -> OperationResult<()> { check_pid(info.pid())?; let file_name = file_name.to_string_lossy(); if &file_name == "\\test_set_file_security" { @@ -849,113 +817,120 @@ impl<'a, 'b: 'a> FileSystemHandler<'a, 'b> for TestHandler { .unwrap(); Ok(()) } else { - Err(OperationError::NtStatus(STATUS_ACCESS_DENIED)) + Err(STATUS_ACCESS_DENIED) } } fn find_streams( &'b self, file_name: &U16CStr, - mut fill_find_stream_data: impl FnMut(&FindStreamData) -> Result<(), FillDataError>, + mut fill_find_stream_data: impl FnMut(&FindStreamData) -> FillDataResult, info: &OperationInfo<'a, 'b, Self>, _context: &'a Self::Context, - ) -> Result<(), OperationError> { + ) -> OperationResult<()> { check_pid(info.pid())?; let file_name = file_name.to_string_lossy(); if &file_name == "\\test_find_streams" { fill_find_stream_data(&FindStreamData { size: 42, name: convert_str("::$DATA"), - })?; - Ok(()) + }) + .map_err(Into::into) } else { - Err(OperationError::NtStatus(STATUS_ACCESS_DENIED)) + Err(STATUS_ACCESS_DENIED) } } } -#[test] -fn test_mount_error() { - let (tx, _rx) = mpsc::sync_channel(1024); +lazy_static::lazy_static! { + static ref TEST_DRIVE_LOCK: Mutex<()> = Mutex::new(()); +} - init(); +pub struct TestDriveContext<'a> { + rx_instance: &'a Receiver, + rx_signal: &'a Receiver, - let result = Drive::new() - .mount_point(&convert_str("0")) - .mount(&TestHandler { tx }); - assert_eq!(result, Err(MountError::MountError)); + instance: RefCell>, +} - shutdown(); +impl<'a> TestDriveContext<'a> { + pub fn signal(&self) -> HandlerSignal { + self.rx_signal.recv().unwrap() + } + + pub fn instance(&self) -> FileSystemHandle { + *self + .instance + .borrow_mut() + .get_or_insert_with(|| self.rx_instance.recv().unwrap()) + } } -lazy_static::lazy_static! { - static ref TEST_DRIVE_LOCK: Mutex<()> = Mutex::new(()); +// Errors might happen on CICD only. To facilitate debugging, output debug messages on console. +pub fn test_flags() -> MountFlags { + let mut flags = + MountFlags::CURRENT_SESSION | MountFlags::FILELOCK_USER_MODE | MountFlags::ALT_STREAM; + + let enable_console_debug_log = + std::env::var_os("DOKAN_CONSOLE_DEBUG_LOG").map_or(false, |x| &x != "0"); + if enable_console_debug_log { + flags = flags | MountFlags::DEBUG | MountFlags::STDERR; + } + + flags } #[allow(unused_must_use)] -fn with_test_drive(f: impl FnOnce(&Receiver, DOKAN_HANDLE)) { +pub fn with_test_drive(scope: Scope) { let _guard = TEST_DRIVE_LOCK.lock(); + init(); + // 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() - .single_thread(false) - .flags( - MountFlags::CURRENT_SESSION - | MountFlags::FILELOCK_USER_MODE - | MountFlags::ALT_STREAM, - ) - .mount_point(&convert_str("Z:\\")) - // Min value specified by DOKAN_IRP_PENDING_TIMEOUT. - .timeout(Duration::from_secs(15)) - .allocation_unit_size(1024) - .sector_size(1024) - .mount(&TestHandler { tx }) - .map(|handle| { - tx_instance.send(DokanInstance(handle.instance())).unwrap(); - }) + + let (tx_signal, rx_signal) = mpsc::sync_channel(1024); + + let drive_thread_handle = thread::spawn(move || { + let mount_point = convert_str("Z:\\"); + let handler = TestHandler::new(tx_signal); + let options = MountOptions { + flags: test_flags(), + timeout: Duration::from_secs(15), + allocation_unit_size: 1024, + sector_size: 1024, + ..Default::default() + }; + let mut file_system = FileSystemMounter::new(&handler, &mount_point, &options); + let mount_handle = file_system.mount().unwrap(); + tx_instance.send(mount_handle.instance()).unwrap(); + drop(mount_handle); + drop(mount_point); }); - let instance = rx_instance.recv().unwrap().0; - assert_eq!(rx.recv().unwrap(), HandlerSignal::Mounted); - f(&rx, instance); - assert!(unmount(convert_str("Z:\\"))); - assert_eq!(rx.recv().unwrap(), HandlerSignal::Unmounted); - handle.join().unwrap().unwrap(); - shutdown(); -} + assert_eq!(rx_signal.recv().unwrap(), HandlerSignal::Mounted); -#[test] -fn test_get_mount_point_list() { - with_test_drive(|_rx, _instance| unsafe { - let list = get_mount_point_list(false).unwrap(); - assert_eq!(list.len(), 1); - let info = &list[0]; - assert_eq!(info.device_type, FILE_DEVICE_DISK_FILE_SYSTEM); - assert_eq!(info.mount_point, Some(convert_str("\\DosDevices\\Z:"))); - assert_eq!(info.unc_name, None); - assert!( - Regex::new("^\\\\Device\\\\Volume\\{[0-9a-z]{8}-([0-9a-z]{4}-){3}[0-9a-z]{12}}$") - .unwrap() - .is_match(&info.device_name.to_string_lossy()) - ); - let mut session_id = 0; - assert_eq!(ProcessIdToSessionId(process::id(), &mut session_id), TRUE); - assert_eq!(info.session_id, session_id); + scope(TestDriveContext { + rx_signal: &rx_signal, + rx_instance: &rx_instance, + instance: RefCell::new(None), }); + + assert!(unmount(convert_str("Z:\\"))); + assert_eq!(rx_signal.recv().unwrap(), HandlerSignal::Unmounted); + + drive_thread_handle.join().unwrap(); + + shutdown(); } #[test] -fn test_panic() { - with_test_drive(|_rx, _instance| unsafe { +fn supports_panic_in_handler() { + with_test_drive(|_| unsafe { let path = convert_str("Z:\\test_panic"); - assert_eq!( + assert_eq_win32!( CreateFileW( path.as_ptr(), 0, @@ -972,15 +947,15 @@ fn test_panic() { } #[test] -fn test_get_volume_information() { - with_test_drive(|_rx, _instance| unsafe { +fn can_retrieve_volume_information() { + with_test_drive(|_| unsafe { let path = convert_str("Z:\\"); let mut volume_name = [0; MAX_PATH + 1]; let mut fs_name = [0; MAX_PATH + 1]; let mut serial_number = 0; let mut max_component_length = 0; let mut fs_flags = 0; - assert_ne!( + assert_ne_win32!( GetVolumeInformationW( path.as_ptr(), volume_name.as_mut_ptr(), @@ -1014,13 +989,13 @@ fn test_get_volume_information() { } #[test] -fn test_get_disk_free_space() { - with_test_drive(|_rx, _instance| unsafe { +fn can_retrieve_disk_space() { + with_test_drive(|_| unsafe { let path = convert_str("Z:\\"); let mut free_bytes_available = 0u64; let mut total_number_of_bytes = 0u64; let mut total_number_of_free_bytes = 0u64; - assert_eq!( + assert_eq_win32!( GetDiskFreeSpaceExW( path.as_ptr(), &mut free_bytes_available as *mut _ as PULARGE_INTEGER, @@ -1047,18 +1022,18 @@ fn open_file(path: impl AsRef) -> HANDLE { FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, ptr::null_mut(), ); - assert_ne!(hf, INVALID_HANDLE_VALUE); + assert_ne_win32!(hf, INVALID_HANDLE_VALUE); hf } } #[test] -fn test_create_file() { - with_test_drive(|rx, _instance| unsafe { +fn can_create_file() { + with_test_drive(|context| unsafe { let hf = open_file("Z:\\test_create_file"); - assert_eq!(CloseHandle(hf), TRUE); + assert_eq_win32!(CloseHandle(hf), TRUE); assert_eq!( - rx.recv().unwrap(), + context.signal(), HandlerSignal::CreateFile( FILE_ALL_ACCESS, FILE_ATTRIBUTE_NORMAL, @@ -1071,23 +1046,23 @@ fn test_create_file() { } #[test] -fn test_close_file() { - with_test_drive(|rx, _instance| unsafe { +fn can_close_file() { + with_test_drive(|context| unsafe { let hf = open_file("Z:\\test_close_file"); - assert_eq!(CloseHandle(hf), TRUE); - assert_eq!(rx.recv().unwrap(), HandlerSignal::Cleanup); - assert_eq!(rx.recv().unwrap(), HandlerSignal::CloseFile); - assert_eq!(rx.recv().unwrap(), HandlerSignal::ContextDropped); + assert_eq_win32!(CloseHandle(hf), TRUE); + assert_eq!(context.signal(), HandlerSignal::Cleanup); + assert_eq!(context.signal(), HandlerSignal::CloseFile); + assert_eq!(context.signal(), HandlerSignal::ContextDropped); }); } #[test] -fn test_file_io() { - with_test_drive(|rx, _instance| unsafe { +fn can_read_from_and_write_to_file() { + with_test_drive(|context| unsafe { let hf = open_file("Z:\\test_file_io"); let mut buf = [0u8; 255]; let mut len = 0; - assert_eq!( + assert_eq_win32!( ReadFile( hf, buf.as_mut_ptr() as LPVOID, @@ -1101,9 +1076,9 @@ fn test_file_io() { String::from_utf8(Vec::from(&buf[..len as usize])).unwrap(), "test data" ); - assert_eq!(rx.recv().unwrap(), HandlerSignal::ReadFile(0, buf.len())); + assert_eq!(context.signal(), HandlerSignal::ReadFile(0, buf.len())); let mut bytes_written = 0; - assert_eq!( + assert_eq_win32!( WriteFile( hf, buf.as_ptr() as LPCVOID, @@ -1115,22 +1090,22 @@ fn test_file_io() { ); assert_eq!(bytes_written, len); assert_eq!( - rx.recv().unwrap(), + context.signal(), HandlerSignal::WriteFile(len as i64, Vec::from(&buf[0..len as usize])) ); - assert_eq!(FlushFileBuffers(hf), TRUE); - assert_eq!(rx.recv().unwrap(), HandlerSignal::FlushFileBuffers); - assert_eq!(CloseHandle(hf), TRUE); + assert_eq_win32!(FlushFileBuffers(hf), TRUE); + assert_eq!(context.signal(), HandlerSignal::FlushFileBuffers); + assert_eq_win32!(CloseHandle(hf), TRUE); }); } #[test] -fn test_get_file_information() { - with_test_drive(|_rx, _instance| unsafe { +fn can_get_file_information() { + with_test_drive(|_context| unsafe { let hf = open_file("Z:\\test_get_file_information"); let mut info = mem::zeroed(); - assert_eq!(GetFileInformationByHandle(hf, &mut info), TRUE); - assert_eq!(CloseHandle(hf), TRUE); + assert_eq_win32!(GetFileInformationByHandle(hf, &mut info), TRUE); + assert_eq_win32!(CloseHandle(hf), TRUE); let ft_epoch = UNIX_EPOCH.to_filetime(); assert_eq!(info.dwFileAttributes, FILE_ATTRIBUTE_NORMAL); @@ -1164,17 +1139,7 @@ fn check_dir_content(pattern: &str, file_name: &str) { let mut data = mem::zeroed(); let hf = FindFirstFileW(pattern.as_ptr(), &mut data); let ft_epoch = UNIX_EPOCH.to_filetime(); - assert_ne!(hf, INVALID_HANDLE_VALUE); - assert_eq!( - U16CStr::from_slice_with_nul(&data.cFileName).unwrap(), - convert_str(".").as_ref() - ); - assert_eq!(FindNextFileW(hf, &mut data), TRUE); - assert_eq!( - U16CStr::from_slice_with_nul(&data.cFileName).unwrap(), - convert_str("..").as_ref() - ); - assert_eq!(FindNextFileW(hf, &mut data), TRUE); + assert_ne_win32!(hf, INVALID_HANDLE_VALUE); assert_eq!(data.dwFileAttributes, FILE_ATTRIBUTE_NORMAL); assert_eq!(data.ftCreationTime.dwLowDateTime, ft_epoch.dwLowDateTime); assert_eq!(data.ftCreationTime.dwHighDateTime, ft_epoch.dwHighDateTime); @@ -1203,50 +1168,60 @@ fn check_dir_content(pattern: &str, file_name: &str) { U16CStr::from_slice_with_nul(&data.cAlternateFileName).unwrap(), convert_str("").as_ref() ); - assert_eq!(FindNextFileW(hf, &mut data), FALSE); + assert_eq_win32!(FindNextFileW(hf, &mut data), TRUE); + assert_eq!( + U16CStr::from_slice_with_nul(&data.cFileName).unwrap(), + convert_str("..").as_ref() + ); + assert_eq_win32!(FindNextFileW(hf, &mut data), TRUE); + assert_eq!( + U16CStr::from_slice_with_nul(&data.cFileName).unwrap(), + convert_str(".").as_ref() + ); + assert_eq_win32!(FindNextFileW(hf, &mut data), FALSE); assert_eq!(GetLastError(), ERROR_NO_MORE_FILES); - assert_eq!(FindClose(hf), TRUE); + assert_eq_win32!(FindClose(hf), TRUE); } } #[test] -fn test_find_files() { - with_test_drive(|rx, _instance| { +fn can_find_files() { + with_test_drive(|context| { check_dir_content("Z:\\test_find_files\\*", "test_inner_file"); check_dir_content( "Z:\\test_find_files_with_pattern\\*", "test_inner_file_with_pattern", ); assert_eq!( - rx.recv().unwrap(), + context.signal(), HandlerSignal::FindFilesWithPattern(convert_str("*")) ); }); } #[test] -fn test_set_file_attributes() { - with_test_drive(|rx, _instance| unsafe { +fn can_set_file_attributes() { + with_test_drive(|context| unsafe { let path = convert_str("Z:\\test_set_file_attributes"); - assert_eq!( + assert_eq_win32!( SetFileAttributesW(path.as_ptr(), FILE_ATTRIBUTE_READONLY), TRUE ); assert_eq!( - rx.recv().unwrap(), + context.signal(), HandlerSignal::SetFileAttributes(FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_READONLY) ); }); } #[test] -fn test_set_file_time() { - with_test_drive(|rx, _instance| unsafe { +fn can_set_file_time() { + with_test_drive(|context| unsafe { let hf = open_file("Z:\\test_set_file_time"); let ctime = UNIX_EPOCH; let atime = UNIX_EPOCH + Duration::from_secs(1); let mtime = UNIX_EPOCH + Duration::from_secs(2); - assert_eq!( + assert_eq_win32!( SetFileTime( hf, &ctime.to_filetime(), @@ -1256,17 +1231,17 @@ fn test_set_file_time() { TRUE ); assert_eq!( - rx.recv().unwrap(), + context.signal(), HandlerSignal::SetFileTime( - FileTimeInfo::SetTime(ctime), - FileTimeInfo::SetTime(atime), - FileTimeInfo::SetTime(mtime), + FileTimeOperation::SetTime(ctime), + FileTimeOperation::SetTime(atime), + FileTimeOperation::SetTime(mtime), ) ); let time_dont_change = mem::transmute(0i64); let time_disable_update = mem::transmute(-1i64); let time_resume_update = mem::transmute(-2i64); - assert_eq!( + assert_eq_win32!( SetFileTime( hf, &time_dont_change, @@ -1276,97 +1251,97 @@ fn test_set_file_time() { TRUE ); assert_eq!( - rx.recv().unwrap(), + context.signal(), HandlerSignal::SetFileTime( - FileTimeInfo::DontChange, - FileTimeInfo::DisableUpdate, - FileTimeInfo::ResumeUpdate, + FileTimeOperation::DontChange, + FileTimeOperation::DisableUpdate, + FileTimeOperation::ResumeUpdate, ) ); - assert_eq!(CloseHandle(hf), TRUE); + assert_eq_win32!(CloseHandle(hf), TRUE); }); } #[test] -fn test_delete_file() { - with_test_drive(|rx, _instance| unsafe { +fn can_delete_file() { + with_test_drive(|context| unsafe { let path = convert_str("Z:\\test_delete_file"); - assert_eq!(DeleteFileW(path.as_ptr()), TRUE); - assert_eq!(rx.recv().unwrap(), HandlerSignal::DeleteFile(true)); + assert_eq_win32!(DeleteFileW(path.as_ptr()), TRUE); + assert_eq!(context.signal(), HandlerSignal::DeleteFile(true)); }); } #[test] -fn test_delete_directory() { - with_test_drive(|rx, _instance| unsafe { +fn can_delete_directory() { + with_test_drive(|context| unsafe { let path = convert_str("Z:\\test_delete_directory"); - assert_eq!(RemoveDirectoryW(path.as_ptr()), TRUE); - assert_eq!(rx.recv().unwrap(), HandlerSignal::DeleteDirectory(true)); + assert_eq_win32!(RemoveDirectoryW(path.as_ptr()), TRUE); + assert_eq!(context.signal(), HandlerSignal::DeleteDirectory(true)); }); } #[test] -fn test_move_file() { - with_test_drive(|rx, _instance| unsafe { +fn can_move_file() { + with_test_drive(|context| unsafe { let path = convert_str("Z:\\test_move_file"); let new_path = convert_str("Z:\\test_move_file_new"); - assert_eq!( + assert_eq_win32!( MoveFileExW(path.as_ptr(), new_path.as_ptr(), MOVEFILE_REPLACE_EXISTING), TRUE ); assert_eq!( - rx.recv().unwrap(), + context.signal(), HandlerSignal::MoveFile(convert_str("\\test_move_file_new"), true) ); }); } #[test] -fn test_set_end_of_file() { - with_test_drive(|rx, _instance| unsafe { +fn can_set_end_of_file() { + with_test_drive(|context| 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)); - assert_eq!(CloseHandle(hf), TRUE); + assert_eq_win32!(SetFileValidData(hf, i64::MAX), TRUE); + assert_eq!(context.signal(), HandlerSignal::SetEndOfFile(i64::MAX)); + assert_eq_win32!(CloseHandle(hf), TRUE); }); } #[test] -fn test_set_allocation_size() { - with_test_drive(|rx, _instance| unsafe { +fn can_set_allocation_size() { + with_test_drive(|context| unsafe { let hf = open_file("Z:\\test_set_allocation_size"); let dist_low = 42; let mut dist_high = 42; - assert_eq!(SetFilePointer(hf, dist_low, &mut dist_high, FILE_BEGIN), 42); + assert_eq_win32!(SetFilePointer(hf, dist_low, &mut dist_high, FILE_BEGIN), 42); assert_eq!(dist_high, 42); - assert_eq!(SetEndOfFile(hf), TRUE); + assert_eq_win32!(SetEndOfFile(hf), TRUE); assert_eq!( - rx.recv().unwrap(), + context.signal(), HandlerSignal::SetAllocationSize(dist_low as i64 + ((dist_high as i64) << 32)) ); - assert_eq!(CloseHandle(hf), TRUE); + assert_eq_win32!(CloseHandle(hf), TRUE); }); } #[test] -fn test_lock_unlock_file() { - with_test_drive(|rx, _instance| unsafe { +fn can_lock_unlock_file() { + with_test_drive(|context| 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)); - assert_eq!(UnlockFile(hf, 0, 0, 1, 0), TRUE); - assert_eq!(rx.recv().unwrap(), HandlerSignal::UnlockFile(0, 1)); - assert_eq!(CloseHandle(hf), TRUE); + assert_eq_win32!(LockFile(hf, 0, 0, 1, 0), TRUE); + assert_eq!(context.signal(), HandlerSignal::LockFile(0, 1)); + assert_eq_win32!(UnlockFile(hf, 0, 0, 1, 0), TRUE); + assert_eq!(context.signal(), HandlerSignal::UnlockFile(0, 1)); + assert_eq_win32!(CloseHandle(hf), TRUE); }); } #[test] -fn test_get_file_security() { - with_test_drive(|rx, _instance| unsafe { +fn can_get_file_security() { + with_test_drive(|context| unsafe { let expected_desc = create_test_descriptor(); let path = convert_str("Z:\\test_get_file_security"); let mut desc_len = 0; - assert_eq!( + assert_eq_win32!( GetFileSecurityW( path.as_ptr(), OWNER_SECURITY_INFORMATION, @@ -1378,11 +1353,11 @@ fn test_get_file_security() { ); assert_eq!(GetLastError(), ERROR_INSUFFICIENT_BUFFER); assert_eq!( - rx.recv().unwrap(), + context.signal(), HandlerSignal::GetFileSecurity(OWNER_SECURITY_INFORMATION, 0) ); let mut desc = vec![0u8; desc_len as usize]; - assert_eq!( + assert_eq_win32!( GetFileSecurityW( path.as_ptr(), OWNER_SECURITY_INFORMATION, @@ -1394,7 +1369,7 @@ fn test_get_file_security() { ); assert_eq!(desc.len(), desc_len as usize); assert_eq!( - rx.recv().unwrap(), + context.signal(), HandlerSignal::GetFileSecurity(OWNER_SECURITY_INFORMATION, desc_len) ); assert_eq!(desc, expected_desc); @@ -1402,11 +1377,11 @@ fn test_get_file_security() { } #[test] -fn test_get_file_security_overflow() { - with_test_drive(|_rx, _instance| unsafe { +fn can_get_file_security_overflow() { + with_test_drive(|_context| unsafe { let path = convert_str("Z:\\test_get_file_security_overflow"); let mut ret_len = 0; - assert_eq!( + assert_eq_win32!( GetFileSecurityW( path.as_ptr(), OWNER_SECURITY_INFORMATION, @@ -1422,18 +1397,18 @@ fn test_get_file_security_overflow() { } #[test] -fn test_set_file_security() { - with_test_drive(|rx, _instance| unsafe { +fn can_set_file_security() { + with_test_drive(|context| 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; - assert_eq!( + assert_eq_win32!( SetFileSecurityW(path.as_ptr(), OWNER_SECURITY_INFORMATION, desc_ptr), TRUE ); let (sid, owner_defaulted) = get_descriptor_owner(desc_ptr); assert_eq!( - rx.recv().unwrap(), + context.signal(), HandlerSignal::SetFileSecurity( desc.len() as u32, OWNER_SECURITY_INFORMATION, @@ -1445,8 +1420,8 @@ fn test_set_file_security() { } #[test] -fn test_find_streams() { - with_test_drive(|_rx, _instance| unsafe { +fn can_find_streams() { + with_test_drive(|_context| unsafe { let path = convert_str("Z:\\test_find_streams"); let mut data = mem::zeroed::(); let hf = FindFirstStreamW( @@ -1455,22 +1430,22 @@ fn test_find_streams() { &mut data as *mut _ as LPVOID, 0, ); - assert_ne!(hf, INVALID_HANDLE_VALUE); + assert_ne_win32!(hf, INVALID_HANDLE_VALUE); assert_eq!(data.StreamSize.QuadPart(), &42); assert_eq!( U16CStr::from_slice_with_nul(&data.cStreamName).unwrap(), convert_str("::$DATA").as_ref() ); - assert_eq!(FindNextStreamW(hf, &mut data as *mut _ as LPVOID), FALSE); + assert_eq_win32!(FindNextStreamW(hf, &mut data as *mut _ as LPVOID), FALSE); assert_eq!(GetLastError(), ERROR_HANDLE_EOF); - assert_eq!(FindClose(hf), TRUE); + assert_eq_win32!(FindClose(hf), TRUE); }); } #[test] #[ignore] -fn test_reset_timeout() { - with_test_drive(|_rx, _instance| unsafe { +fn can_reset_timeout() { + with_test_drive(|_context| unsafe { let path = convert_str("Z:\\test_reset_timeout"); let hf = CreateFileW( path.as_ptr(), @@ -1481,21 +1456,21 @@ fn test_reset_timeout() { 0, ptr::null_mut(), ); - assert_ne!(hf, INVALID_HANDLE_VALUE); - assert_eq!(CloseHandle(hf), TRUE); + assert_ne_win32!(hf, INVALID_HANDLE_VALUE); + assert_eq_win32!(CloseHandle(hf), TRUE); }); } #[test] -fn test_open_requester_token() { - with_test_drive(|rx, _instance| unsafe { +fn can_open_requester_token() { + with_test_drive(|context| unsafe { let expected_info_buffer = get_current_user_info(); let hf = open_file("Z:\\test_open_requester_token"); - assert_eq!(CloseHandle(hf), TRUE); - if let HandlerSignal::OpenRequesterToken(info_buffer) = rx.recv().unwrap() { + assert_eq_win32!(CloseHandle(hf), TRUE); + if let HandlerSignal::OpenRequesterToken(info_buffer) = context.signal() { let expected_info = &*(expected_info_buffer.as_ptr() as *const TOKEN_USER); let info = &*(info_buffer.as_ptr() as *const TOKEN_USER); - assert_eq!(EqualSid(info.User.Sid, expected_info.User.Sid), TRUE); + assert_eq_win32!(EqualSid(info.User.Sid, expected_info.User.Sid), TRUE); assert_eq!(info.User.Attributes, expected_info.User.Attributes); } else { panic!("unexpected signal type"); @@ -1504,12 +1479,12 @@ fn test_open_requester_token() { } #[test] -fn test_operation_info() { - with_test_drive(|rx, _instance| unsafe { +fn can_get_operation_info() { + with_test_drive(|context| unsafe { let hf = open_file("Z:\\test_operation_info"); - assert_eq!(CloseHandle(hf), TRUE); + assert_eq_win32!(CloseHandle(hf), TRUE); assert_eq!( - rx.recv().unwrap(), + context.signal(), HandlerSignal::OperationInfo(OperationInfoDump { pid: process::id(), is_dir: false, @@ -1519,9 +1494,7 @@ fn test_operation_info() { no_cache: false, write_to_eof: false, single_thread: false, - mount_flags: MountFlags::CURRENT_SESSION - | MountFlags::FILELOCK_USER_MODE - | MountFlags::ALT_STREAM, + mount_flags: test_flags(), mount_point: Some(convert_str("Z:\\")), unc_name: None, timeout: Duration::from_secs(15), @@ -1533,10 +1506,10 @@ fn test_operation_info() { } #[test] -fn test_output_ptr_null() { - with_test_drive(|_rx, _instance| unsafe { +fn supports_null_ptrs() { + with_test_drive(|_context| unsafe { let path = convert_str("Z:\\"); - assert_eq!( + assert_eq_win32!( GetDiskFreeSpaceExW( path.as_ptr(), ptr::null_mut(), @@ -1545,7 +1518,7 @@ fn test_output_ptr_null() { ), TRUE ); - assert_eq!( + assert_eq_win32!( GetVolumeInformationW( path.as_ptr(), ptr::null_mut(), @@ -1561,48 +1534,12 @@ fn test_output_ptr_null() { }) } -struct ToRawStructStub { - should_fail: bool, -} - -impl ToRawStruct<()> for ToRawStructStub { - fn to_raw_struct(&self) -> Option<()> { - if self.should_fail { - None - } else { - Some(()) - } - } -} - -extern "stdcall" fn fill_data_stub(_data: *mut (), _info: PDOKAN_FILE_INFO) -> c_int { - 0 -} - -extern "stdcall" fn failing_fill_data_stub(_data: *mut (), _info: PDOKAN_FILE_INFO) -> c_int { - 1 -} - -#[test] -fn test_fill_data_error() { - 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(), 0); - assert_eq!( - wrapper(&ToRawStructStub { should_fail: false }), - Err(FillDataError::BufferFull) - ); -} - struct DirectoryChangeIterator { - hd: TokenHandle, + hd: OwnedHandle, buf: Pin>>, offset: usize, // Simply reuse the safe handle type as events are closed by CloseHandle as well. - he: TokenHandle, + he: OwnedHandle, overlapped: Pin>, } @@ -1618,17 +1555,17 @@ impl DirectoryChangeIterator { FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, ptr::null_mut(), ); - assert_ne!(hd, INVALID_HANDLE_VALUE); + assert_ne_win32!(hd, INVALID_HANDLE_VALUE); let he = CreateEventW(ptr::null_mut(), FALSE, FALSE, ptr::null()); - assert_ne!(he, INVALID_HANDLE_VALUE); + assert_ne_win32!(he, INVALID_HANDLE_VALUE); let mut result = DirectoryChangeIterator { - hd: TokenHandle::from_raw_handle(hd), + hd: OwnedHandle::from_raw_handle(hd), buf: Box::pin(vec![ 0; mem::size_of::() + MAX_PATH ]), offset: 0, - he: TokenHandle::from_raw_handle(he), + he: OwnedHandle::from_raw_handle(he), overlapped: Box::pin(mem::zeroed()), }; result.begin_read(); @@ -1664,7 +1601,7 @@ impl Iterator for DirectoryChangeIterator { unsafe { if self.offset == 0 { let mut ret_len = 0; - assert_eq!( + assert_eq_win32!( GetOverlappedResult( self.hd.as_raw_handle(), &mut *self.overlapped, @@ -1696,8 +1633,8 @@ impl Iterator for DirectoryChangeIterator { } #[test] -fn test_notify() { - with_test_drive(|_rx, instance| { +fn can_notify() { + with_test_drive(|context| { let (tx, rx) = mpsc::channel(); let handle = thread::spawn(move || { let mut iter = DirectoryChangeIterator::new(convert_str("Z:\\")); @@ -1709,7 +1646,7 @@ fn test_notify() { }); assert_eq!(rx.recv().unwrap(), None); assert!(notify_create( - instance, + context.instance(), convert_str("Z:\\test_notify_create"), false )); @@ -1718,7 +1655,7 @@ fn test_notify() { Some((FILE_ACTION_ADDED, convert_str("test_notify_create"))) ); assert!(notify_delete( - instance, + context.instance(), convert_str("Z:\\test_notify_delete"), false )); @@ -1727,7 +1664,7 @@ fn test_notify() { Some((FILE_ACTION_REMOVED, convert_str("test_notify_delete"))) ); assert!(notify_update( - instance, + context.instance(), convert_str("Z:\\test_notify_update") )); assert_eq!( @@ -1735,7 +1672,7 @@ fn test_notify() { Some((FILE_ACTION_MODIFIED, convert_str("test_notify_update"))) ); assert!(notify_xattr_update( - instance, + context.instance(), convert_str("Z:\\test_notify_xattr_update") )); assert_eq!( @@ -1746,7 +1683,7 @@ fn test_notify() { )) ); assert!(notify_rename( - instance, + context.instance(), convert_str("Z:\\test_notify_rename_old"), convert_str("Z:\\test_notify_rename_new"), false,