macOS Time Machine AFP monitoring: backup_time update on file modification
- Added afp_monitor.rs module to track AFP_AfpInfo backup_time - Open struct now has 'modified' flag to track file modifications - write.rs sets modified=true on successful write - close.rs calls AfpMonitor::update_backup_time() on modified files - create.rs calls AfpMonitor::init_afp_info() on new file creation - AFP_AfpInfo stored as xattr com.apple.aapl.AfpInfo - backup_time updated to current epoch time on modification Also includes: - LZ4 compression using lz4_flex crate - Case sensitivity conditional on backend capabilities - LDAP cfg feature gate fix - RAID rebuild reconstruction implementation - DOS attributes xattr persistence - Snapshot disk persistence Tests: 201 smb-server, 452 markbase-core (653 total)
This commit is contained in:
94
vendor/smb-server/src/snapshot.rs
vendored
94
vendor/smb-server/src/snapshot.rs
vendored
@@ -4,6 +4,8 @@
|
||||
//! for Windows VSS (Volume Shadow Copy Service) support.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::time::SystemTime;
|
||||
|
||||
@@ -77,19 +79,109 @@ pub enum SnapshotResponse {
|
||||
},
|
||||
}
|
||||
|
||||
const SNAPSHOTS_DIR: &str = ".snapshots";
|
||||
const SNAPSHOTS_FILE: &str = "snapshots.json";
|
||||
|
||||
/// Snapshot manager - manages share snapshots
|
||||
pub struct SnapshotManager {
|
||||
/// Snapshots indexed by (share_name, snapshot_id)
|
||||
snapshots: RwLock<HashMap<(String, String), SnapshotEntry>>,
|
||||
/// Optional file-system path for persistence
|
||||
storage_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl SnapshotManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
snapshots: RwLock::new(HashMap::new()),
|
||||
storage_path: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_storage_path(path: PathBuf) -> Self {
|
||||
let manager = Self {
|
||||
snapshots: RwLock::new(HashMap::new()),
|
||||
storage_path: Some(path),
|
||||
};
|
||||
manager.load_snapshots();
|
||||
manager
|
||||
}
|
||||
|
||||
fn snapshots_file_path(&self) -> Option<PathBuf> {
|
||||
self.storage_path.as_ref().map(|p| p.join(SNAPSHOTS_DIR).join(SNAPSHOTS_FILE))
|
||||
}
|
||||
|
||||
fn load_snapshots(&self) {
|
||||
let path = match self.snapshots_file_path() {
|
||||
Some(p) => p,
|
||||
None => return,
|
||||
};
|
||||
let data = match std::fs::read_to_string(&path) {
|
||||
Ok(d) => d,
|
||||
Err(_) => return,
|
||||
};
|
||||
let mut map = self.snapshots.write().unwrap();
|
||||
for line in data.lines() {
|
||||
let parts: Vec<&str> = line.splitn(5, '|').collect();
|
||||
if parts.len() < 4 {
|
||||
continue;
|
||||
}
|
||||
let share_name = parts[0].to_string();
|
||||
let snapshot_id = parts[1].to_string();
|
||||
let secs: u64 = match parts[2].parse() {
|
||||
Ok(s) => s,
|
||||
Err(_) => continue,
|
||||
};
|
||||
let state = match parts[3] {
|
||||
"Created" => SnapshotState::Created,
|
||||
"Active" => SnapshotState::Active,
|
||||
"Deleting" => SnapshotState::Deleting,
|
||||
"Deleted" => SnapshotState::Deleted,
|
||||
_ => continue,
|
||||
};
|
||||
let metadata = parts.get(4).filter(|m| !m.is_empty()).map(|m| m.to_string());
|
||||
let created_at = std::time::UNIX_EPOCH + std::time::Duration::from_secs(secs);
|
||||
let entry = SnapshotEntry {
|
||||
snapshot_id,
|
||||
share_name: share_name.clone(),
|
||||
created_at,
|
||||
state,
|
||||
metadata,
|
||||
};
|
||||
map.insert((share_name, entry.snapshot_id.clone()), entry);
|
||||
}
|
||||
}
|
||||
|
||||
fn save_snapshots(&self) {
|
||||
let path = match self.snapshots_file_path() {
|
||||
Some(p) => p,
|
||||
None => return,
|
||||
};
|
||||
if let Some(parent) = path.parent() {
|
||||
let _ = std::fs::create_dir_all(parent);
|
||||
}
|
||||
let mut output = String::new();
|
||||
{
|
||||
let map = self.snapshots.read().unwrap();
|
||||
for entry in map.values() {
|
||||
let secs = entry.created_at
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or(std::time::Duration::ZERO)
|
||||
.as_secs();
|
||||
let state_str = match entry.state {
|
||||
SnapshotState::Created => "Created",
|
||||
SnapshotState::Active => "Active",
|
||||
SnapshotState::Deleting => "Deleting",
|
||||
SnapshotState::Deleted => "Deleted",
|
||||
};
|
||||
let meta = entry.metadata.as_deref().unwrap_or("");
|
||||
writeln!(output, "{}|{}|{}|{}|{}",
|
||||
entry.share_name, entry.snapshot_id, secs, state_str, meta).ok();
|
||||
}
|
||||
}
|
||||
let _ = std::fs::write(&path, &output);
|
||||
}
|
||||
|
||||
/// Create a new snapshot for a share
|
||||
pub fn create_snapshot(
|
||||
&self,
|
||||
@@ -115,6 +207,7 @@ impl SnapshotManager {
|
||||
.unwrap()
|
||||
.insert((share_name.to_string(), snapshot_id.clone()), entry.clone());
|
||||
|
||||
self.save_snapshots();
|
||||
Ok(entry)
|
||||
}
|
||||
|
||||
@@ -151,6 +244,7 @@ impl SnapshotManager {
|
||||
entry.state = SnapshotState::Deleted;
|
||||
snapshots.remove(&(share_name.to_string(), snapshot_id.to_string()));
|
||||
|
||||
self.save_snapshots();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user