Implement SMB Byte-range Lock (Phase 7)
- Add LockManager to oplock.rs: - LockRange struct for tracking byte-range locks - acquire() - check conflicts before granting lock - release() - remove specific lock by offset/length - clear() - clear all locks when file closed - ranges_overlap() - helper for conflict detection - Add LockManager to ServerState - Update handlers/lock.rs: - Parse LockRequest and LockElement - Process each lock element (acquire/release) - Support FLAG_EXCLUSIVE_LOCK, FLAG_SHARED_LOCK, FLAG_UNLOCK - Return STATUS_LOCK_NOT_GRANTED on conflict - Update handlers/close.rs: - Clear all locks when file closed - Add STATUS_LOCK_NOT_GRANTED to ntstatus.rs All 229 tests pass.
This commit is contained in:
128
vendor/smb-server/src/oplock.rs
vendored
128
vendor/smb-server/src/oplock.rs
vendored
@@ -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<HashMap<FileId, Vec<LockRange>>>,
|
||||
}
|
||||
|
||||
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<LockRange> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user