From 88e862307cc2dda93c1de013a6ef41c235a630b4 Mon Sep 17 00:00:00 2001 From: veeso Date: Fri, 1 Nov 2024 14:59:44 +0100 Subject: [PATCH] feat: working on windows driver --- remotefs-fuse/Cargo.toml | 3 +- remotefs-fuse/src/driver.rs | 37 +- remotefs-fuse/src/driver/unix.rs | 29 + remotefs-fuse/src/driver/windows.rs | 578 ++++++++++++++++++- remotefs-fuse/src/driver/windows/entry.rs | 109 ++++ remotefs-fuse/src/driver/windows/security.rs | 293 ++++++++++ 6 files changed, 1000 insertions(+), 49 deletions(-) create mode 100644 remotefs-fuse/src/driver/windows/entry.rs create mode 100644 remotefs-fuse/src/driver/windows/security.rs diff --git a/remotefs-fuse/Cargo.toml b/remotefs-fuse/Cargo.toml index 9df12b3..4b8466f 100644 --- a/remotefs-fuse/Cargo.toml +++ b/remotefs-fuse/Cargo.toml @@ -20,6 +20,7 @@ path = "src/lib.rs" [dependencies] log = "^0.4" remotefs = "0.3" +tempfile = "^3" [target.'cfg(unix)'.dependencies] @@ -27,9 +28,9 @@ fuser = "0.14" libc = "^0.2" nix = { version = "0.29", features = ["fs"] } seahash = "4" -tempfile = "^3" [target.'cfg(windows)'.dependencies] +dashmap = "6" dokan = "0.3.1" dokan-sys = "0.3.1" widestring = "0.4.3" diff --git a/remotefs-fuse/src/driver.rs b/remotefs-fuse/src/driver.rs index 1fde2c9..0c2a393 100644 --- a/remotefs-fuse/src/driver.rs +++ b/remotefs-fuse/src/driver.rs @@ -30,6 +30,10 @@ pub struct Driver { #[cfg(windows)] /// [`RemoteFs`] instance usable as `Sync` in immutable references remote: std::sync::Arc>, + #[cfg(windows)] + /// [`windows::DirEntry`] foor directory + file_handlers: + dashmap::DashMap>>, } impl Driver @@ -55,37 +59,8 @@ where remote, #[cfg(windows)] remote: std::sync::Arc::new(std::sync::Mutex::new(remote)), + #[cfg(windows)] + file_handlers: dashmap::DashMap::new(), } } - - /// Get the specified uid from the mount options. - #[cfg(unix)] - fn uid(&self) -> Option { - self.options.iter().find_map(|opt| match opt { - MountOption::Uid(uid) => Some(*uid), - _ => None, - }) - } - - /// Get the specified gid from the mount options. - #[cfg(unix)] - fn gid(&self) -> Option { - self.options.iter().find_map(|opt| match opt { - MountOption::Gid(gid) => Some(*gid), - _ => None, - }) - } - - /// Get the specified default mode from the mount options. - /// If not set, the default is 0755. - #[cfg(unix)] - fn default_mode(&self) -> u32 { - self.options - .iter() - .find_map(|opt| match opt { - MountOption::DefaultMode(mode) => Some(*mode), - _ => None, - }) - .unwrap_or(0o755) - } } diff --git a/remotefs-fuse/src/driver/unix.rs b/remotefs-fuse/src/driver/unix.rs index 0878ad3..6bad080 100644 --- a/remotefs-fuse/src/driver/unix.rs +++ b/remotefs-fuse/src/driver/unix.rs @@ -27,6 +27,7 @@ use remotefs::{File, RemoteError, RemoteErrorType, RemoteFs, RemoteResult}; pub use self::file_handle::FileHandlersDb; pub use self::inode::InodeDb; use super::Driver; +use crate::MountOption; const BLOCK_SIZE: usize = 512; const FMODE_EXEC: c_int = 0x20; @@ -390,6 +391,34 @@ where .create_file(file.path(), file.metadata(), Box::new(reader)) .map(|len| len as u32) } + + /// Get the specified uid from the mount options. + fn uid(&self) -> Option { + self.options.iter().find_map(|opt| match opt { + MountOption::Uid(uid) => Some(*uid), + _ => None, + }) + } + + /// Get the specified gid from the mount options. + fn gid(&self) -> Option { + self.options.iter().find_map(|opt| match opt { + MountOption::Gid(gid) => Some(*gid), + _ => None, + }) + } + + /// Get the specified default mode from the mount options. + /// If not set, the default is 0755. + fn default_mode(&self) -> u32 { + self.options + .iter() + .find_map(|opt| match opt { + MountOption::DefaultMode(mode) => Some(*mode), + _ => None, + }) + .unwrap_or(0o755) + } } impl Filesystem for Driver diff --git a/remotefs-fuse/src/driver/windows.rs b/remotefs-fuse/src/driver/windows.rs index b9a3b5d..27a21c6 100644 --- a/remotefs-fuse/src/driver/windows.rs +++ b/remotefs-fuse/src/driver/windows.rs @@ -1,24 +1,301 @@ +mod entry; +mod security; #[cfg(test)] mod test; +use std::io::{Cursor, Read as _, Seek as _}; +use std::path::{Path, PathBuf}; +use std::sync::atomic::AtomicBool; +use std::sync::{Arc, Mutex, RwLock}; + +use dashmap::mapref::one::Ref; use dokan::{ CreateFileInfo, DiskSpaceInfo, FileInfo, FileSystemHandler, FileTimeOperation, FillDataResult, FindData, FindStreamData, OperationInfo, OperationResult, VolumeInfo, }; -use remotefs::{File, RemoteFs}; -use widestring::U16CStr; -use winapi::shared::ntstatus::{self, STATUS_NOT_IMPLEMENTED}; -use winapi::um::winnt::ACCESS_MASK; +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 entry::{EntryName, StatHandle}; +use remotefs::fs::{Metadata, UnixPex}; +use remotefs::{File, RemoteError, RemoteErrorType, RemoteFs, RemoteResult}; +use widestring::{U16CStr, U16CString}; +use winapi::shared::ntstatus::{ + self, STATUS_ACCESS_DENIED, STATUS_CANNOT_DELETE, STATUS_DELETE_PENDING, + STATUS_FILE_IS_A_DIRECTORY, STATUS_INVALID_DEVICE_REQUEST, STATUS_INVALID_PARAMETER, + STATUS_NOT_A_DIRECTORY, STATUS_NOT_IMPLEMENTED, STATUS_OBJECT_NAME_COLLISION, + STATUS_OBJECT_NAME_NOT_FOUND, +}; +use winapi::um::winnt::{self, ACCESS_MASK}; +pub use self::entry::Stat; +use self::security::SecurityDescriptor; use super::Driver; +struct PathInfo { + path: PathBuf, + file_name: U16CString, + parent: PathBuf, +} + +#[derive(Debug)] +struct AltStream { + handle_count: u32, + delete_pending: bool, +} + +impl AltStream { + fn new() -> Self { + Self { + handle_count: 0, + delete_pending: false, + } + } +} + +impl Driver +where + T: RemoteFs + Sync + Send, +{ + fn path_to_u16string(path: &Path) -> U16CString { + U16CString::from_str(path.to_string_lossy()).expect("failed to convert path to U16CString") + } + + fn stat(&self, file_name: &U16CStr) -> RemoteResult>>> { + let key = file_name.to_ucstring(); + if let Some(stat) = self.file_handlers.get(&key) { + return Ok(stat); + } + + let path_info = self.path_info(file_name); + + let Ok(mut lock) = self.remote.lock() else { + error!("mutex poisoned"); + return Err(RemoteError::new(remotefs::RemoteErrorType::ProtocolError)); + }; + + let file = lock.stat(&path_info.path)?; + + // insert the file into the file handlers + self.file_handlers.insert( + key.clone(), + Arc::new(RwLock::new(Stat::new( + file, + SecurityDescriptor::new_default() + .map_err(|_| RemoteError::new(remotefs::RemoteErrorType::ProtocolError))?, + ))), + ); + + Ok(self.file_handlers.get(&key).unwrap()) + } + + fn path_info(&self, file_name: &U16CStr) -> PathInfo { + let p = PathBuf::from(file_name.to_string_lossy()); + let parent = p + .parent() + .map(|p| p.to_path_buf()) + .unwrap_or_else(|| PathBuf::from("/")); + + PathInfo { + path: p, + parent, + file_name: file_name.to_ucstring(), + } + } + + /// Read data from a file. + /// + /// If possible, this system will use the stream from remotefs directly, + /// otherwise it will use a temporary file (*sigh*). + /// Note that most of remotefs supports streaming, so this should be rare. + fn read(&self, path: &Path, buffer: &mut [u8], offset: u64) -> RemoteResult { + let Ok(mut remote) = self.remote.lock() else { + error!("mutex poisoned"); + return Err(RemoteError::new(remotefs::RemoteErrorType::ProtocolError)); + }; + match remote.open(path) { + Ok(mut reader) => { + debug!("Reading file from stream: {:?} at {offset}", path); + if offset > 0 { + // read file until offset + let mut offset_buff = vec![0; offset as usize]; + reader.read_exact(&mut offset_buff).map_err(|err| { + remotefs::RemoteError::new_ex( + remotefs::RemoteErrorType::IoError, + err.to_string(), + ) + })?; + } + + // read file + let bytes_read = reader.read(buffer).map_err(|err| { + remotefs::RemoteError::new_ex( + remotefs::RemoteErrorType::IoError, + err.to_string(), + ) + })?; + debug!("Read {bytes_read} bytes from stream; closing stream"); + + // close file + remote.on_read(reader)?; + + Ok(bytes_read) + } + Err(RemoteError { + kind: RemoteErrorType::UnsupportedFeature, + .. + }) => { + drop(remote); + self.read_tempfile(path, buffer, offset) + } + Err(err) => Err(err), + } + } + + /// Read data from a file using a temporary file. + fn read_tempfile(&self, path: &Path, buffer: &mut [u8], offset: u64) -> RemoteResult { + let Ok(mut remote) = self.remote.lock() else { + error!("mutex poisoned"); + return Err(RemoteError::new(remotefs::RemoteErrorType::ProtocolError)); + }; + + let Ok(tempfile) = tempfile::NamedTempFile::new() else { + return Err(remotefs::RemoteError::new( + remotefs::RemoteErrorType::IoError, + )); + }; + let Ok(writer) = std::fs::OpenOptions::new() + .write(true) + .open(tempfile.path()) + else { + error!("Failed to open temporary file"); + return Err(remotefs::RemoteError::new( + remotefs::RemoteErrorType::IoError, + )); + }; + + // transfer to tempfile + remote.open_file(path, Box::new(writer))?; + + let Ok(mut reader) = std::fs::File::open(tempfile.path()) else { + error!("Failed to open temporary file"); + return Err(remotefs::RemoteError::new( + remotefs::RemoteErrorType::IoError, + )); + }; + + // skip to offset + if offset > 0 { + let mut offset_buff = vec![0; offset as usize]; + if let Err(err) = reader.read_exact(&mut offset_buff) { + error!("Failed to read file: {err}"); + return Err(remotefs::RemoteError::new( + remotefs::RemoteErrorType::IoError, + )); + } + } + + // read file + reader.read_exact(buffer).map_err(|err| { + remotefs::RemoteError::new_ex(remotefs::RemoteErrorType::IoError, err.to_string()) + })?; + + if let Err(err) = tempfile.close() { + error!("Failed to close temporary file: {err}"); + } + + Ok(buffer.len()) + } + + /// Write data to a file. + fn write(&self, file: &File, data: &[u8], offset: u64) -> RemoteResult { + // write data + let Ok(mut remote) = self.remote.lock() else { + error!("mutex poisoned"); + return Err(RemoteError::new(remotefs::RemoteErrorType::ProtocolError)); + }; + + let mut reader = Cursor::new(data); + let mut writer = match remote.create(file.path(), file.metadata()) { + Ok(writer) => writer, + Err(RemoteError { + kind: RemoteErrorType::UnsupportedFeature, + .. + }) if offset > 0 => { + error!("remote file system doesn't support stream, so it is not possible to write at offset"); + return Err(RemoteError::new_ex( + RemoteErrorType::UnsupportedFeature, + "remote file system doesn't support stream, so it is not possible to write at offset".to_string(), + )); + } + Err(RemoteError { + kind: RemoteErrorType::UnsupportedFeature, + .. + }) => { + drop(remote); + return self.write_wno_stream(file, data); + } + Err(err) => { + error!("Failed to write file: {err}"); + return Err(err); + } + }; + if offset > 0 { + // try to seek + if let Err(err) = writer.seek(std::io::SeekFrom::Start(offset)) { + error!("Failed to seek file: {err}. Not that not all the remote filesystems support seeking"); + return Err(RemoteError::new_ex( + RemoteErrorType::IoError, + err.to_string(), + )); + } + } + // write + let bytes_written = match std::io::copy(&mut reader, &mut writer) { + Ok(bytes) => bytes as u32, + Err(err) => { + error!("Failed to write file: {err}"); + return Err(RemoteError::new_ex( + RemoteErrorType::IoError, + err.to_string(), + )); + } + }; + // on write + remote + .on_written(writer) + .map_err(|err| RemoteError::new_ex(RemoteErrorType::IoError, err.to_string()))?; + + Ok(bytes_written) + } + + /// Write data to a file without using a stream. + fn write_wno_stream(&self, file: &File, data: &[u8]) -> RemoteResult { + debug!( + "Writing file without stream: {:?} {} bytes", + file.path(), + data.len() + ); + let Ok(mut remote) = self.remote.lock() else { + error!("mutex poisoned"); + return Err(RemoteError::new(remotefs::RemoteErrorType::ProtocolError)); + }; + let reader = Cursor::new(data.to_vec()); + remote + .create_file(file.path(), file.metadata(), Box::new(reader)) + .map(|len| len as u32) + } +} + // For reference impl<'c, 'h: 'c, T> FileSystemHandler<'c, 'h> for Driver where T: RemoteFs + Sync + Send + 'h, { /// Type of the context associated with an open file object. - type Context = File; + type Context = StatHandle; /// Called when Dokan has successfully mounted the volume. fn mounted( @@ -67,15 +344,250 @@ where fn create_file( &'h self, file_name: &U16CStr, - security_context: &dokan_sys::DOKAN_IO_SECURITY_CONTEXT, + _security_context: &dokan_sys::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>, + _info: &mut OperationInfo<'c, 'h, Self>, ) -> OperationResult> { - todo!() + info!("create_file({file_name:?}, {desired_access:?}, {file_attributes:?}, {share_access:?}, {create_disposition:?}, {create_options:?})"); + + let stat = self.stat(file_name).ok(); + + if create_disposition > FILE_MAXIMUM_DISPOSITION { + error!("invalid create disposition: {create_disposition}"); + return Err(STATUS_INVALID_PARAMETER); + } + let delete_on_close = create_options & FILE_DELETE_ON_CLOSE > 0; + if let Some(stat) = stat { + let stat = stat.value(); + let read = stat.read().unwrap(); + + let is_readonly = read + .file + .metadata() + .mode + .map(|m| (u32::from(m)) & 0o222 == 0) + .unwrap_or_default(); + + if is_readonly + && (desired_access & winnt::FILE_WRITE_DATA > 0 + || desired_access & winnt::FILE_APPEND_DATA > 0) + { + error!("file {file_name:?} is readonly"); + return Err(STATUS_ACCESS_DENIED); + } + if read.delete_pending { + error!("delete pending: {file_name:?}"); + return Err(STATUS_DELETE_PENDING); + } + if is_readonly && delete_on_close { + error!("delete on close: {file_name:?}"); + return Err(STATUS_CANNOT_DELETE); + } + std::mem::drop(read); + + let stream_name = EntryName(file_name.to_ustring()); + let ret = { + let mut stat = stat.write().unwrap(); + if let Some(stream) = stat.alt_streams.get(&stream_name).map(|s| Arc::clone(s)) { + if stream.read().unwrap().delete_pending { + error!("delete pending: {file_name:?}"); + return Err(STATUS_DELETE_PENDING); + } + match create_disposition { + FILE_SUPERSEDE | FILE_OVERWRITE | FILE_OVERWRITE_IF => { + if create_disposition != FILE_SUPERSEDE && is_readonly { + error!("file {file_name:?} is readonly"); + return Err(STATUS_ACCESS_DENIED); + } + } + FILE_CREATE => return Err(ntstatus::STATUS_OBJECT_NAME_COLLISION), + _ => (), + } + Some((stream, false)) + } else { + if create_disposition == FILE_OPEN || create_disposition == FILE_OVERWRITE { + error!("alt stream not found: {file_name:?}"); + return Err(STATUS_OBJECT_NAME_NOT_FOUND); + } + if is_readonly { + error!("file {file_name:?} is readonly"); + return Err(STATUS_ACCESS_DENIED); + } + let stream = Arc::new(RwLock::new(AltStream::new())); + stat.alt_streams.insert(stream_name, Arc::clone(&stream)); + + Some((stream, true)) + } + }; + + if let Some((stream, new_file_created)) = ret { + let handle = StatHandle { + stat: stat.clone(), + alt_stream: RwLock::new(Some(stream)), + delete_on_close, + mtime_delayed: Mutex::new(None), + atime_delayed: Mutex::new(None), + ctime_enabled: AtomicBool::new(false), + mtime_enabled: AtomicBool::new(false), + atime_enabled: AtomicBool::new(false), + }; + return Ok(CreateFileInfo { + context: handle, + is_dir: false, + new_file_created, + }); + } + let is_file = stat + .read() + .ok() + .map(|r| r.file.is_file()) + .unwrap_or_default(); + match is_file { + true => { + if create_options & FILE_DIRECTORY_FILE > 0 { + error!("file is not a directory: {file_name:?}"); + return Err(STATUS_NOT_A_DIRECTORY); + } + match create_disposition { + FILE_SUPERSEDE | FILE_OVERWRITE | FILE_OVERWRITE_IF => { + if create_disposition != FILE_SUPERSEDE && is_readonly { + error!("file {file_name:?} is readonly"); + return Err(STATUS_ACCESS_DENIED); + } + } + FILE_CREATE => return Err(STATUS_OBJECT_NAME_COLLISION), + _ => (), + } + debug!("open file: {file_name:?}"); + let handle = StatHandle { + stat: stat.clone(), + alt_stream: RwLock::new(None), + delete_on_close, + mtime_delayed: Mutex::new(None), + atime_delayed: Mutex::new(None), + ctime_enabled: AtomicBool::new(false), + mtime_enabled: AtomicBool::new(false), + atime_enabled: AtomicBool::new(false), + }; + return Ok(CreateFileInfo { + context: handle, + is_dir: false, + new_file_created: false, + }); + } + false => { + if create_options & FILE_NON_DIRECTORY_FILE > 0 { + return Err(STATUS_FILE_IS_A_DIRECTORY); + } + match create_disposition { + FILE_OPEN | FILE_OPEN_IF => { + debug!("open directory: {file_name:?}"); + let handle = StatHandle { + stat: stat.clone(), + alt_stream: RwLock::new(None), + delete_on_close, + mtime_delayed: Mutex::new(None), + atime_delayed: Mutex::new(None), + ctime_enabled: AtomicBool::new(false), + mtime_enabled: AtomicBool::new(false), + atime_enabled: AtomicBool::new(false), + }; + Ok(CreateFileInfo { + context: handle, + is_dir: true, + new_file_created: false, + }) + } + FILE_CREATE => Err(STATUS_OBJECT_NAME_COLLISION), + _ => Err(STATUS_INVALID_PARAMETER), + } + } + } + } else { + if create_disposition == FILE_OPEN || create_disposition == FILE_OPEN_IF { + if create_options & FILE_NON_DIRECTORY_FILE > 0 { + debug!("create file: {file_name:?}"); + let path_info = self.path_info(file_name); + if let Err(err) = self.write( + &File { + path: path_info.path, + metadata: Metadata::default().mode(UnixPex::from(0o644)).size(0), + }, + &[], + 0, + ) { + error!("write failed: {err}"); + return Err(ntstatus::STATUS_CONNECTION_DISCONNECTED); + } + + let stat = match self.stat(file_name) { + Ok(stat) => stat, + Err(err) => { + error!("stat failed: {err}"); + return Err(ntstatus::STATUS_CONNECTION_DISCONNECTED); + } + }; + + let handle = StatHandle { + stat: stat.value().clone(), + alt_stream: RwLock::new(None), + delete_on_close, + mtime_delayed: Mutex::new(None), + atime_delayed: Mutex::new(None), + ctime_enabled: AtomicBool::new(false), + mtime_enabled: AtomicBool::new(false), + atime_enabled: AtomicBool::new(false), + }; + + Ok(CreateFileInfo { + context: handle, + is_dir: false, + new_file_created: true, + }) + } else { + // create directory + debug!("create directory: {file_name:?}"); + let stat = { + let path_info = self.path_info(file_name); + let mut lock = self.remote.lock().unwrap(); + if let Err(err) = lock.create_dir(&path_info.path, UnixPex::from(0o755)) { + error!("create_dir failed: {err}"); + return Err(ntstatus::STATUS_CONNECTION_DISCONNECTED); + } + + match self.stat(file_name) { + Ok(stat) => stat, + Err(err) => { + error!("stat failed: {err}"); + return Err(ntstatus::STATUS_CONNECTION_DISCONNECTED); + } + } + }; + + let handle = StatHandle { + stat: stat.value().clone(), + alt_stream: RwLock::new(None), + delete_on_close, + mtime_delayed: Mutex::new(None), + atime_delayed: Mutex::new(None), + ctime_enabled: AtomicBool::new(false), + mtime_enabled: AtomicBool::new(false), + atime_enabled: AtomicBool::new(false), + }; + Ok(CreateFileInfo { + context: handle, + is_dir: true, + new_file_created: true, + }) + } + } else { + Err(STATUS_INVALID_PARAMETER) + } + } } /// Called when the last handle for the file object has been closed. @@ -100,7 +612,9 @@ where info: &OperationInfo<'c, 'h, Self>, context: &'c Self::Context, ) { - todo!() + // TODO: everything necessary, finaly remove key + + todo!(); } /// Called when the last handle for the handle object has been closed and released. @@ -109,17 +623,20 @@ where /// 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. + /// [`FileSystemHandler::create_file`] gets called. /// - /// [`context`]: Self::Context - /// [`create_file`]: Self::create_file + /// [`context`]: [`Self::Context`] + /// [`create_file`]: [`FileSystemHandler::create_file`] fn close_file( &'h self, file_name: &U16CStr, - info: &OperationInfo<'c, 'h, Self>, + _info: &OperationInfo<'c, 'h, Self>, context: &'c Self::Context, ) { - todo!() + info!("close_file({file_name:?}, {context:?})"); + + let key = file_name.to_ucstring(); + self.file_handlers.remove(&key); } /// Reads data from the file. @@ -134,10 +651,25 @@ where file_name: &U16CStr, offset: i64, buffer: &mut [u8], - info: &OperationInfo<'c, 'h, Self>, + _info: &OperationInfo<'c, 'h, Self>, context: &'c Self::Context, ) -> OperationResult { - todo!() + info!("read_file({file_name:?}, {offset})"); + // read file + let file = match context.stat.read() { + Err(_) => { + error!("mutex poisoned"); + return Err(STATUS_INVALID_DEVICE_REQUEST); + } + Ok(stat) => stat.file.clone(), + }; + + self.read(&file.path, buffer, offset as u64) + .map_err(|err| { + error!("read failed: {err}"); + STATUS_INVALID_DEVICE_REQUEST + }) + .map(|len| len as u32) } /// Writes data to the file. @@ -286,7 +818,19 @@ where info: &OperationInfo<'c, 'h, Self>, context: &'c Self::Context, ) -> OperationResult<()> { - todo!() + info!("delete_file({file_name:?}, {context:?})"); + if context.stat.read().expect("failed to read").file.is_dir() { + error!("file is a directory: {file_name:?}"); + return Err(STATUS_CANNOT_DELETE); + } + let alt_stream = context.alt_stream.read().unwrap(); + if let Some(stream) = alt_stream.as_ref() { + stream.write().unwrap().delete_pending = info.delete_on_close(); + } else { + context.stat.write().unwrap().delete_pending = info.delete_on_close(); + } + + Ok(()) } /// Checks if the directory can be deleted. diff --git a/remotefs-fuse/src/driver/windows/entry.rs b/remotefs-fuse/src/driver/windows/entry.rs new file mode 100644 index 0000000..66bf5cc --- /dev/null +++ b/remotefs-fuse/src/driver/windows/entry.rs @@ -0,0 +1,109 @@ +use std::borrow::Borrow; +use std::collections::HashMap; +use std::hash::{Hash, Hasher}; +use std::sync::atomic::AtomicBool; +use std::sync::{Arc, Mutex, RwLock}; +use std::time::SystemTime; + +use remotefs::File; +use widestring::{U16Str, U16String}; + +use super::security::SecurityDescriptor; +use super::AltStream; + +#[derive(Debug)] +pub struct StatHandle { + pub stat: Arc>, + pub alt_stream: RwLock>>>, + pub delete_on_close: bool, + pub mtime_delayed: Mutex>, + pub atime_delayed: Mutex>, + pub ctime_enabled: AtomicBool, + pub mtime_enabled: AtomicBool, + pub atime_enabled: AtomicBool, +} + +#[derive(Debug)] +pub struct Stat { + pub file: File, + pub sec_desc: SecurityDescriptor, + pub handle_count: u32, + pub delete_pending: bool, + pub delete_on_close: bool, + pub alt_streams: HashMap>>, +} + +impl Stat { + pub fn new(file: File, sec_desc: SecurityDescriptor) -> Self { + Self { + file, + sec_desc, + handle_count: 0, + delete_pending: false, + delete_on_close: false, + alt_streams: HashMap::new(), + } + } +} + +#[derive(Debug, Eq)] +pub struct EntryNameRef(U16Str); + +fn u16_tolower(c: u16) -> u16 { + if c >= 'A' as u16 && c <= 'Z' as u16 { + c + 'a' as u16 - 'A' as u16 + } else { + c + } +} + +impl Hash for EntryNameRef { + fn hash(&self, state: &mut H) { + for c in self.0.as_slice() { + state.write_u16(u16_tolower(*c)); + } + } +} + +impl PartialEq for EntryNameRef { + fn eq(&self, other: &Self) -> bool { + if self.0.len() != other.0.len() { + false + } else { + self.0 + .as_slice() + .iter() + .zip(other.0.as_slice()) + .all(|(c1, c2)| u16_tolower(*c1) == u16_tolower(*c2)) + } + } +} + +impl EntryNameRef { + pub fn new(s: &U16Str) -> &Self { + unsafe { &*(s as *const _ as *const Self) } + } +} + +#[derive(Debug, Clone)] +pub struct EntryName(pub U16String); + +impl Borrow for EntryName { + fn borrow(&self) -> &EntryNameRef { + EntryNameRef::new(&self.0) + } +} + +impl Hash for EntryName { + fn hash(&self, state: &mut H) { + Borrow::::borrow(self).hash(state) + } +} + +impl PartialEq for EntryName { + fn eq(&self, other: &Self) -> bool { + Borrow::::borrow(self).eq(other.borrow()) + } +} + +impl Eq for EntryName {} diff --git a/remotefs-fuse/src/driver/windows/security.rs b/remotefs-fuse/src/driver/windows/security.rs new file mode 100644 index 0000000..262b9f6 --- /dev/null +++ b/remotefs-fuse/src/driver/windows/security.rs @@ -0,0 +1,293 @@ +use std::pin::Pin; +use std::{mem, ptr}; + +use dokan::{map_win32_error_to_ntstatus, win32_ensure, OperationResult}; +use winapi::shared::ntstatus::*; +use winapi::shared::{minwindef, ntdef, winerror}; +use winapi::um::errhandlingapi::GetLastError; +use winapi::um::{heapapi, securitybaseapi, winnt}; + +#[derive(Debug)] +struct PrivateObjectSecurity { + value: winnt::PSECURITY_DESCRIPTOR, +} + +impl PrivateObjectSecurity { + unsafe fn from_raw(ptr: winnt::PSECURITY_DESCRIPTOR) -> Self { + Self { value: ptr } + } +} + +impl Drop for PrivateObjectSecurity { + fn drop(&mut self) { + unsafe { + securitybaseapi::DestroyPrivateObjectSecurity(&mut self.value); + } + } +} + +#[derive(Debug)] +pub struct SecurityDescriptor { + desc_ptr: winnt::PSECURITY_DESCRIPTOR, +} + +unsafe impl Sync for SecurityDescriptor {} + +unsafe impl Send for SecurityDescriptor {} + +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; + 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() -> OperationResult> { + unsafe { + let admins_sid = get_well_known_sid(winnt::WinBuiltinAdministratorsSid)?; + let system_sid = get_well_known_sid(winnt::WinLocalSystemSid)?; + let auth_sid = get_well_known_sid(winnt::WinAuthenticatedUserSid)?; + let users_sid = get_well_known_sid(winnt::WinBuiltinUsersSid)?; + + let acl_len = mem::size_of::() + + (mem::size_of::() - mem::size_of::()) * 4 + + admins_sid.len() + + system_sid.len() + + auth_sid.len() + + users_sid.len(); + let mut acl = vec![0u8; acl_len].into_boxed_slice(); + 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; + 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) + } +} + +const FILE_GENERIC_MAPPING: winnt::GENERIC_MAPPING = winnt::GENERIC_MAPPING { + GenericRead: winnt::FILE_GENERIC_READ, + GenericWrite: winnt::FILE_GENERIC_WRITE, + GenericExecute: winnt::FILE_GENERIC_EXECUTE, + GenericAll: winnt::FILE_ALL_ACCESS, +}; + +impl SecurityDescriptor { + pub fn new_inherited( + parent_desc: &SecurityDescriptor, + creator_desc: winnt::PSECURITY_DESCRIPTOR, + token: ntdef::HANDLE, + is_dir: bool, + ) -> OperationResult { + unsafe { + if !creator_desc.is_null() + && securitybaseapi::IsValidSecurityDescriptor(creator_desc) == minwindef::FALSE + { + return Err(STATUS_INVALID_PARAMETER); + } + + let mut priv_desc = ptr::null_mut(); + 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(); + win32_ensure(!heap.is_null())?; + + let len = securitybaseapi::GetSecurityDescriptorLength(priv_desc.value) as usize; + let buf = heapapi::HeapAlloc(heap, 0, len); + 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() -> 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()?); + + unsafe { + let mut abs_desc = mem::zeroed::(); + let abs_desc_ptr = &mut abs_desc as *mut _ as winnt::PSECURITY_DESCRIPTOR; + + 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 = GetLastError(); + if ret != minwindef::FALSE || err != winerror::ERROR_INSUFFICIENT_BUFFER { + return Err(map_win32_error_to_ntstatus(err)); + } + + let heap = heapapi::GetProcessHeap(); + win32_ensure(!heap.is_null())?; + + let buf = heapapi::HeapAlloc(heap, 0, len as usize); + win32_ensure(!buf.is_null())?; + + win32_ensure( + securitybaseapi::MakeSelfRelativeSD(abs_desc_ptr, buf, &mut len) == minwindef::TRUE, + )?; + + Ok(Self { desc_ptr: buf }) + } + } + + pub fn get_security_info( + &self, + sec_info: winnt::SECURITY_INFORMATION, + sec_desc: winnt::PSECURITY_DESCRIPTOR, + sec_desc_len: u32, + ) -> OperationResult { + unsafe { + let len = securitybaseapi::GetSecurityDescriptorLength(self.desc_ptr); + if len > sec_desc_len { + return Ok(len); + } + + let mut ret_len = 0; + win32_ensure( + securitybaseapi::GetPrivateObjectSecurity( + self.desc_ptr, + sec_info, + sec_desc, + sec_desc_len, + &mut ret_len, + ) == minwindef::TRUE, + )?; + + Ok(len) + } + } + + pub fn set_security_info( + &mut self, + sec_info: winnt::SECURITY_INFORMATION, + sec_desc: winnt::PSECURITY_DESCRIPTOR, + ) -> OperationResult<()> { + unsafe { + if securitybaseapi::IsValidSecurityDescriptor(sec_desc) == minwindef::FALSE { + return Err(STATUS_INVALID_PARAMETER); + } + + 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(()) + } + } +} + +impl Drop for SecurityDescriptor { + fn drop(&mut self) { + unsafe { + heapapi::HeapFree(heapapi::GetProcessHeap(), 0, self.desc_ptr); + } + } +}