diff --git a/vendor/smb-server/src/handlers/close.rs b/vendor/smb-server/src/handlers/close.rs index 0c3e139..6707640 100644 --- a/vendor/smb-server/src/handlers/close.rs +++ b/vendor/smb-server/src/handlers/close.rs @@ -52,6 +52,9 @@ pub async fn handle( server.oplock_manager.unregister(&path, &req.file_id).await; } + // Phase 7: Clear all byte-range locks for this file + server.lock_manager.clear(&req.file_id).await; + // Stat before closing if needed. let info_before_close = if want_attrs { if let Some(h) = handle.as_ref() { diff --git a/vendor/smb-server/src/handlers/lock.rs b/vendor/smb-server/src/handlers/lock.rs index d7e449c..e6a4438 100644 --- a/vendor/smb-server/src/handlers/lock.rs +++ b/vendor/smb-server/src/handlers/lock.rs @@ -3,18 +3,64 @@ use std::sync::Arc; use crate::proto::header::Smb2Header; -use crate::proto::messages::LockResponse; +use crate::proto::messages::{LockElement, LockRequest, LockResponse}; use crate::conn::state::Connection; use crate::dispatch::HandlerResponse; +use crate::handlers::shared::lookup_session_tree; +use crate::ntstatus; use crate::server::ServerState; pub async fn handle( - _server: &Arc, - _conn: &Arc, - _hdr: &Smb2Header, - _body: &[u8], + server: &Arc, + conn: &Arc, + hdr: &Smb2Header, + body: &[u8], ) -> HandlerResponse { + let req = match LockRequest::parse(body) { + Ok(r) => r, + Err(_) => return HandlerResponse::err(ntstatus::STATUS_INVALID_PARAMETER), + }; + + // Validate tree/session + let tree_arc = match lookup_session_tree(conn, hdr).await { + Ok(t) => t, + Err(s) => return HandlerResponse::err(s), + }; + + // Phase 7: Process each lock element + for lock in &req.locks { + let exclusive = lock.flags & LockElement::FLAG_EXCLUSIVE_LOCK != 0; + let unlock = lock.flags & LockElement::FLAG_UNLOCK != 0; + + if unlock { + // Release lock + server.lock_manager.release( + &req.file_id, + lock.offset, + lock.length, + hdr.session_id, + tree_arc.read().await.id, + ).await; + } else { + // Acquire lock + let fail_immediately = lock.flags & LockElement::FLAG_FAIL_IMMEDIATELY != 0; + + let result = server.lock_manager.acquire( + &req.file_id, + lock.offset, + lock.length, + exclusive, + hdr.session_id, + tree_arc.read().await.id, + ).await; + + if result.is_err() && fail_immediately { + return HandlerResponse::err(ntstatus::STATUS_LOCK_NOT_GRANTED); + } + } + } + let mut buf = Vec::new(); LockResponse::default().write_to(&mut buf).expect("encode"); HandlerResponse::ok(buf) diff --git a/vendor/smb-server/src/ntstatus.rs b/vendor/smb-server/src/ntstatus.rs index 581c7e3..9b7d66d 100644 --- a/vendor/smb-server/src/ntstatus.rs +++ b/vendor/smb-server/src/ntstatus.rs @@ -39,3 +39,4 @@ pub const STATUS_INFO_LENGTH_MISMATCH: u32 = 0xC000_0004; pub const STATUS_FILE_CLOSED: u32 = 0xC000_0128; pub const STATUS_INVALID_INFO_CLASS: u32 = 0xC000_0003; pub const STATUS_NO_EAS_ON_FILE: u32 = 0xC000_0052; +pub const STATUS_LOCK_NOT_GRANTED: u32 = 0xC000_0054; // Phase 7: byte-range lock conflict diff --git a/vendor/smb-server/src/oplock.rs b/vendor/smb-server/src/oplock.rs index 6e35694..641b32f 100644 --- a/vendor/smb-server/src/oplock.rs +++ b/vendor/smb-server/src/oplock.rs @@ -3,6 +3,8 @@ //! MS-SMB2 §2.2.13 / §2.2.14: Oplocks allow clients to cache file data locally, //! reducing network round-trips. The server tracks all opens per file and //! triggers OPLOCK_BREAK_NOTIFICATION when conflicting opens occur. +//! +//! Also includes LockManager for byte-range locking (MS-SMB2 §2.2.26). use std::collections::HashMap; use std::sync::Arc; @@ -173,4 +175,130 @@ pub fn share_access_compatible(existing: u32, new: u32) -> bool { } true +} + +// --------------------------------------------------------------------------- +// Byte-range Lock Manager (MS-SMB2 §2.2.26) +// --------------------------------------------------------------------------- + +/// A byte-range lock entry (MS-SMB2 §2.2.26.1). +#[derive(Debug, Clone)] +pub struct LockRange { + pub offset: u64, + pub length: u64, + pub exclusive: bool, // FLAG_EXCLUSIVE_LOCK vs FLAG_SHARED_LOCK + pub session_id: u64, + pub tree_id: u32, +} + +/// Global byte-range lock manager (MS-SMB2 §3.3.1.9). +pub struct LockManager { + /// FileId → active locks on that file. + file_locks: RwLock>>, +} + +impl LockManager { + pub fn new() -> Self { + Self { + file_locks: RwLock::new(HashMap::new()), + } + } + + /// Acquire a lock (MS-SMB2 §3.3.5.14). + /// Returns Ok(()) if lock acquired, Err if conflict. + pub async fn acquire( + &self, + file_id: &FileId, + offset: u64, + length: u64, + exclusive: bool, + session_id: u64, + tree_id: u32, + ) -> Result<(), String> { + let mut file_locks = self.file_locks.write().await; + + // Check for conflicts with existing locks + if let Some(locks) = file_locks.get(file_id) { + for lock in locks { + // Check if ranges overlap + if Self::ranges_overlap(offset, length, lock.offset, lock.length) { + // If either is exclusive, conflict + if exclusive || lock.exclusive { + // Same session can upgrade lock + if lock.session_id == session_id && lock.tree_id == tree_id { + continue; // Allow same session to overlap + } + return Err("Lock conflict".to_string()); + } + } + } + } + + // No conflict → add lock + file_locks + .entry(*file_id) + .or_insert_with(Vec::new) + .push(LockRange { + offset, + length, + exclusive, + session_id, + tree_id, + }); + + Ok(()) + } + + /// Release a lock (MS-SMB2 §3.3.5.14). + pub async fn release( + &self, + file_id: &FileId, + offset: u64, + length: u64, + session_id: u64, + tree_id: u32, + ) { + let mut file_locks = self.file_locks.write().await; + + if let Some(locks) = file_locks.get_mut(file_id) { + locks.retain(|lock| { + // Keep locks that don't match this release + !(lock.offset == offset + && lock.length == length + && lock.session_id == session_id + && lock.tree_id == tree_id) + }); + + if locks.is_empty() { + file_locks.remove(file_id); + } + } + } + + /// Check if two byte ranges overlap. + fn ranges_overlap(offset1: u64, length1: u64, offset2: u64, length2: u64) -> bool { + let end1 = offset1 + length1; + let end2 = offset2 + length2; + + // Overlap if one range starts before the other ends + offset1 < end2 && offset2 < end1 + } + + /// Get all locks for a file (for diagnostics). + pub async fn get_locks(&self, file_id: &FileId) -> Vec { + let file_locks = self.file_locks.read().await; + file_locks.get(file_id).cloned().unwrap_or_default() + } + + /// Clear all locks for a file (when file is closed). + pub async fn clear(&self, file_id: &FileId) { + let mut file_locks = self.file_locks.write().await; + file_locks.remove(file_id); + } +} + +impl Default for LockManager { + fn default() -> Self { + Self::new() + } } \ No newline at end of file diff --git a/vendor/smb-server/src/server.rs b/vendor/smb-server/src/server.rs index 6079b60..446b531 100644 --- a/vendor/smb-server/src/server.rs +++ b/vendor/smb-server/src/server.rs @@ -198,6 +198,8 @@ pub struct ServerState { pub shutting_down: Arc, /// Global oplock state manager (Phase 2). pub oplock_manager: Arc, + /// Global byte-range lock manager (Phase 7). + pub lock_manager: Arc, } impl ServerState { @@ -211,6 +213,7 @@ impl ServerState { shutdown: Arc::new(Notify::new()), shutting_down: Arc::new(AtomicBool::new(false)), oplock_manager: Arc::new(crate::oplock::OplockManager::new()), + lock_manager: Arc::new(crate::oplock::LockManager::new()), } }