//! SMB Share Snapshot Manager (MS-SMB2 §2.2.31 / MS-FSCC §2.3.7) //! //! Implements FSCTL_SRV_SNAPSHOT_CREATE/READ/WRITE/DELETE operations //! for Windows VSS (Volume Shadow Copy Service) support. use std::collections::HashMap; use std::sync::{Arc, RwLock}; use std::time::SystemTime; use crate::ntstatus; /// Snapshot entry stored in SnapshotManager #[derive(Debug, Clone)] pub struct SnapshotEntry { /// Unique snapshot ID (GMT token format: @GMT-YYYY.MM.DD-HH.MM.SS) pub snapshot_id: String, /// Share name this snapshot belongs to pub share_name: String, /// Creation timestamp pub created_at: SystemTime, /// Snapshot state pub state: SnapshotState, /// Snapshot metadata (JSON) pub metadata: Option, } /// Snapshot state enum #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SnapshotState { Created, Active, Deleting, Deleted, } /// SMB Snapshot request types #[derive(Debug, Clone)] pub enum SnapshotRequest { Create { share_name: String, snapshot_name: Option, }, Read { share_name: String, snapshot_id: String, }, Write { share_name: String, snapshot_id: String, data: Vec, }, Delete { share_name: String, snapshot_id: String, }, } /// SMB Snapshot response types #[derive(Debug, Clone)] pub enum SnapshotResponse { Create { snapshot_id: String, created_at: SystemTime, }, Read { snapshot_id: String, data: Vec, }, Write { bytes_written: u32, }, Delete { success: bool, }, List { snapshots: Vec, }, } /// Snapshot manager - manages share snapshots pub struct SnapshotManager { /// Snapshots indexed by (share_name, snapshot_id) snapshots: RwLock>, } impl SnapshotManager { pub fn new() -> Self { Self { snapshots: RwLock::new(HashMap::new()), } } /// Create a new snapshot for a share pub fn create_snapshot( &self, share_name: &str, snapshot_name: Option<&str>, ) -> Result { let now = SystemTime::now(); let snapshot_id = match snapshot_name { Some(name) => name.to_string(), None => Self::gmt_token_from_time(now), }; let entry = SnapshotEntry { snapshot_id: snapshot_id.clone(), share_name: share_name.to_string(), created_at: now, state: SnapshotState::Active, metadata: None, }; self.snapshots .write() .unwrap() .insert((share_name.to_string(), snapshot_id.clone()), entry.clone()); Ok(entry) } /// Read snapshot metadata pub fn read_snapshot( &self, share_name: &str, snapshot_id: &str, ) -> Result { self.snapshots .read() .unwrap() .get(&(share_name.to_string(), snapshot_id.to_string())) .cloned() .ok_or(ntstatus::STATUS_OBJECT_NAME_NOT_FOUND) } /// Delete a snapshot pub fn delete_snapshot( &self, share_name: &str, snapshot_id: &str, ) -> Result<(), u32> { let mut snapshots = self.snapshots.write().unwrap(); let entry = snapshots .get_mut(&(share_name.to_string(), snapshot_id.to_string())) .ok_or(ntstatus::STATUS_OBJECT_NAME_NOT_FOUND)?; if entry.state == SnapshotState::Deleting { return Err(ntstatus::STATUS_SNAPSHOT_OPERATION_IN_PROGRESS); } entry.state = SnapshotState::Deleted; snapshots.remove(&(share_name.to_string(), snapshot_id.to_string())); Ok(()) } /// List all snapshots for a share pub fn list_snapshots(&self, share_name: &str) -> Vec { self.snapshots .read() .unwrap() .iter() .filter(|((s, _), _)| s == share_name) .map(|(_, entry)| entry.clone()) .collect() } /// Convert SystemTime to GMT token format (@GMT-YYYY.MM.DD-HH.MM.SS) pub fn gmt_token_from_time(time: SystemTime) -> String { use std::time::UNIX_EPOCH; let duration = time .duration_since(UNIX_EPOCH) .unwrap_or(std::time::Duration::ZERO); let total_secs = duration.as_secs(); let hours = total_secs / 3600; let minutes = (total_secs % 3600) / 60; let seconds = total_secs % 60; // Simplified: use days since epoch (not calendar date) let days = hours / 24; let year = 1970 + days / 365; let remaining_days = days % 365; let month = remaining_days / 30 + 1; let day = remaining_days % 30 + 1; let hour = hours % 24; format!( "@GMT-{:04}.{:02}.{:02}-{:02}.{:02}.{:02}", year, month, day, hour, minutes, seconds ) } /// Parse GMT token to SystemTime pub fn time_from_gmt_token(token: &str) -> Option { use std::time::UNIX_EPOCH; if !token.starts_with("@GMT-") { return None; } let parts: Vec<&str> = token[5..].split('-').collect(); if parts.len() != 2 { return None; } let date_parts: Vec<&str> = parts[0].split('.').collect(); let time_parts: Vec<&str> = parts[1].split('.').collect(); if date_parts.len() != 3 || time_parts.len() != 3 { return None; } let year: u64 = date_parts[0].parse().ok()?; let month: u64 = date_parts[1].parse().ok()?; let day: u64 = date_parts[2].parse().ok()?; let hour: u64 = time_parts[0].parse().ok()?; let minute: u64 = time_parts[1].parse().ok()?; let second: u64 = time_parts[2].parse().ok()?; // Simplified calculation (not calendar-aware) let days_since_epoch = (year - 1970) * 365 + month * 30 + day; let total_secs = days_since_epoch * 86400 + hour * 3600 + minute * 60 + second; Some(UNIX_EPOCH + std::time::Duration::from_secs(total_secs)) } } impl Default for SnapshotManager { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; use std::time::UNIX_EPOCH; #[test] fn test_create_snapshot() { let manager = SnapshotManager::new(); let entry = manager.create_snapshot("test_share", Some("snapshot1")).unwrap(); assert_eq!(entry.share_name, "test_share"); assert_eq!(entry.snapshot_id, "snapshot1"); assert_eq!(entry.state, SnapshotState::Active); } #[test] fn test_read_snapshot() { let manager = SnapshotManager::new(); manager.create_snapshot("test_share", Some("snapshot1")).unwrap(); let entry = manager.read_snapshot("test_share", "snapshot1").unwrap(); assert_eq!(entry.snapshot_id, "snapshot1"); } #[test] fn test_delete_snapshot() { let manager = SnapshotManager::new(); manager.create_snapshot("test_share", Some("snapshot1")).unwrap(); manager.delete_snapshot("test_share", "snapshot1").unwrap(); assert!(manager.read_snapshot("test_share", "snapshot1").is_err()); } #[test] fn test_list_snapshots() { let manager = SnapshotManager::new(); manager.create_snapshot("share1", Some("snap1")).unwrap(); manager.create_snapshot("share1", Some("snap2")).unwrap(); manager.create_snapshot("share2", Some("snap3")).unwrap(); let snapshots = manager.list_snapshots("share1"); assert_eq!(snapshots.len(), 2); } #[test] fn test_gmt_token_format() { let time = UNIX_EPOCH + std::time::Duration::from_secs(1609459200); // 2021-01-01 00:00:00 let token = SnapshotManager::gmt_token_from_time(time); assert!(token.starts_with("@GMT-")); assert!(token.contains("2021")); } #[test] fn test_gmt_token_parse() { let token = "@GMT-2021.01.01-00.00.00"; let time = SnapshotManager::time_from_gmt_token(token).unwrap(); let token2 = SnapshotManager::gmt_token_from_time(time); assert!(token2.starts_with("@GMT-")); } }