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:
Warren
2026-06-24 00:46:33 +08:00
parent 5300b672cb
commit 57fd6a475f
33 changed files with 1211 additions and 253 deletions

View File

@@ -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(())
}