diff --git a/markbase-core/src/vfs/local_fs.rs b/markbase-core/src/vfs/local_fs.rs index 2ffabf9..3ea8383 100644 --- a/markbase-core/src/vfs/local_fs.rs +++ b/markbase-core/src/vfs/local_fs.rs @@ -3,7 +3,7 @@ use super::util; use super::{VfsAce, VfsAceFlag, VfsAceMask, VfsAceType, VfsAcl, VfsBackend, VfsDirEntry, VfsError, VfsFile, VfsPreviousVersion, VfsQuota, VfsQuotaUsage, VfsSnapshotInfo, VfsStat}; use std::fs::{self, File, OpenOptions}; use std::io::{Read, Seek, SeekFrom, Write}; -use std::os::unix::fs::{MetadataExt, PermissionsExt}; +use std::os::unix::fs::{FileExt, MetadataExt, PermissionsExt}; use std::path::{Path, PathBuf}; use std::time::SystemTime; @@ -42,6 +42,14 @@ impl VfsFile for LocalFile { self.file.seek(pos).map_err(|e| VfsError::Io(e.to_string())) } + fn read_at(&mut self, buf: &mut [u8], offset: u64) -> Result { + self.file.read_at(buf, offset).map_err(|e| VfsError::Io(e.to_string())) + } + + fn write_at(&mut self, buf: &[u8], offset: u64) -> Result { + self.file.write_at(buf, offset).map_err(|e| VfsError::Io(e.to_string())) + } + fn flush(&mut self) -> Result<(), VfsError> { self.file.flush().map_err(|e| VfsError::Io(e.to_string())) } diff --git a/markbase-core/src/vfs/mod.rs b/markbase-core/src/vfs/mod.rs index 22f8919..0fdbe9d 100644 --- a/markbase-core/src/vfs/mod.rs +++ b/markbase-core/src/vfs/mod.rs @@ -103,6 +103,20 @@ pub trait VfsFile: Send { fn stat(&mut self) -> Result; fn set_len(&mut self, size: u64) -> Result<(), VfsError>; + /// Read at `offset` without changing the seek position (like pread). + /// Default implementation does seek + read. + fn read_at(&mut self, buf: &mut [u8], offset: u64) -> Result { + self.seek(std::io::SeekFrom::Start(offset))?; + self.read(buf) + } + + /// Write at `offset` without changing the seek position (like pwrite). + /// Default implementation does seek + write. + fn write_at(&mut self, buf: &[u8], offset: u64) -> Result { + self.seek(std::io::SeekFrom::Start(offset))?; + self.write(buf) + } + /// Write all bytes (convenience, default loops write() until done) fn write_all(&mut self, mut buf: &[u8]) -> Result<(), VfsError> { while !buf.is_empty() { diff --git a/markbase-core/src/vfs/smb_server_backend.rs b/markbase-core/src/vfs/smb_server_backend.rs index addea0d..62cbade 100644 --- a/markbase-core/src/vfs/smb_server_backend.rs +++ b/markbase-core/src/vfs/smb_server_backend.rs @@ -1,7 +1,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; -use std::sync::Mutex; use std::time::SystemTime; +use tokio::sync::Mutex; use async_trait::async_trait; use bytes::Bytes; @@ -211,11 +211,9 @@ impl Handle for VfsHandle { async fn read(&self, offset: u64, len: u32) -> Result { match self { Self::File { file, .. } => { - let mut file = file.lock().unwrap(); - file.seek(std::io::SeekFrom::Start(offset)) - .map_err(vfs_error_to_io)?; + let mut file = file.lock().await; let mut buf = vec![0u8; len as usize]; - let n = file.read(&mut buf).map_err(map_error)?; + let n = file.read_at(&mut buf, offset).map_err(map_error)?; buf.truncate(n); Ok(Bytes::from(buf)) } @@ -226,10 +224,8 @@ impl Handle for VfsHandle { async fn write(&self, offset: u64, data: &[u8]) -> Result { match self { Self::File { file, .. } => { - let mut file = file.lock().unwrap(); - file.seek(std::io::SeekFrom::Start(offset)) - .map_err(vfs_error_to_io)?; - let n = file.write(data).map_err(map_error)?; + let mut file = file.lock().await; + let n = file.write_at(data, offset).map_err(map_error)?; Ok(n as u32) } Self::Directory { .. } => Err(SmbError::NotSupported), @@ -239,7 +235,7 @@ impl Handle for VfsHandle { async fn flush(&self) -> Result<(), SmbError> { match self { Self::File { file, .. } => { - let mut file = file.lock().unwrap(); + let mut file = file.lock().await; file.flush().map_err(map_error) } Self::Directory { .. } => Ok(()), @@ -249,7 +245,7 @@ impl Handle for VfsHandle { async fn stat(&self) -> Result { match self { Self::File { file, path, .. } => { - let mut f = file.lock().unwrap(); + let mut f = file.lock().await; let vfs_stat = f.stat().map_err(map_error)?; Ok(vfs_stat_to_file_info(&vfs_stat, "", path)) } @@ -278,7 +274,7 @@ impl Handle for VfsHandle { async fn truncate(&self, len: u64) -> Result<(), SmbError> { match self { Self::File { file, .. } => { - let mut file = file.lock().unwrap(); + let mut file = file.lock().await; file.set_len(len).map_err(map_error) } Self::Directory { .. } => Err(SmbError::NotSupported), diff --git a/vendor/smb-server/src/backend.rs b/vendor/smb-server/src/backend.rs index f7ef707..01dfab1 100644 --- a/vendor/smb-server/src/backend.rs +++ b/vendor/smb-server/src/backend.rs @@ -207,8 +207,8 @@ pub trait Handle: Send + Sync { /// Write `data` at `offset`. Returns bytes written. async fn write(&self, offset: u64, data: &[u8]) -> SmbResult; - /// Write owned `data` at `offset`. Backends that need ownership across a - /// blocking boundary can override this to avoid an extra copy. + /// Write owned `data` at `offset`. Backends needing ownership across a + /// blocking boundary should override to avoid an extra copy. async fn write_owned(&self, offset: u64, data: Vec) -> SmbResult { self.write(offset, &data).await } diff --git a/vendor/smb-server/src/handlers/read.rs b/vendor/smb-server/src/handlers/read.rs index 49578d4..1d7e97d 100644 --- a/vendor/smb-server/src/handlers/read.rs +++ b/vendor/smb-server/src/handlers/read.rs @@ -91,16 +91,15 @@ pub async fn handle( if bytes.is_empty() && req.length > 0 { return HandlerResponse::err(ntstatus::STATUS_END_OF_FILE); } - let resp = ReadResponse { - structure_size: 17, - data_offset: ReadResponse::STANDARD_DATA_OFFSET, - reserved: 0, - data_length: bytes.len() as u32, - data_remaining: 0, - flags: 0, - data: bytes.to_vec(), - }; - let mut buf = Vec::new(); - resp.write_to(&mut buf).expect("encode"); + // Build response directly to avoid Bytes→Vec copy and intermediate struct + let data_len = bytes.len() as u32; + let mut buf = Vec::with_capacity(16 + bytes.len()); + buf.extend_from_slice(&17u16.to_le_bytes()); // structure_size + buf.push(ReadResponse::STANDARD_DATA_OFFSET); // data_offset + buf.push(0); // reserved + buf.extend_from_slice(&data_len.to_le_bytes()); // data_length + buf.extend_from_slice(&0u32.to_le_bytes()); // data_remaining + buf.extend_from_slice(&0u32.to_le_bytes()); // flags + buf.extend_from_slice(&bytes); // data HandlerResponse::ok(buf) } diff --git a/vendor/smb-server/src/oplock.rs b/vendor/smb-server/src/oplock.rs index 820e1bd..6f882f4 100644 --- a/vendor/smb-server/src/oplock.rs +++ b/vendor/smb-server/src/oplock.rs @@ -112,17 +112,23 @@ impl OplockManager { new_share_access: u32, new_granted_access: Access, ) -> Vec { + // Fast-path: no entries or single entry can't conflict with itself + let entry_count = { + let file_opens = self.file_opens.read().await; + file_opens.get(path).map_or(0, |e| e.len()) + }; + if entry_count <= 1 { + return Vec::new(); + } + let mut notifications = Vec::new(); let mut file_opens = self.file_opens.write().await; if let Some(entries) = file_opens.get_mut(path) { for entry in entries.iter_mut() { - // Check if new open conflicts with existing oplock if !share_access_compatible(entry.share_access, new_share_access) { - // Need to break the oplock - let new_level = OplockLevel::Ii as u8; // Downgrade to Level II + let new_level = OplockLevel::Ii as u8; - // Build notification (MS-SMB2 §2.2.23.1) notifications.push(OplockBreakNotification { structure_size: 24, oplock_level: new_level, @@ -131,7 +137,6 @@ impl OplockManager { file_id: entry.file_id, }); - // Update entry's oplock level entry.oplock_level = new_level; } } @@ -266,6 +271,11 @@ impl LeaseManager { /// Break lease when conflicting access occurs (MS-SMB2 §3.3.5.10). pub async fn break_lease(&self, requested_state: u32) -> Vec { + // Fast-path: no leases to break + if self.leases.read().await.is_empty() { + return Vec::new(); + } + let mut leases = self.leases.write().await; let mut notifications = Vec::new();