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:
@@ -51,6 +51,7 @@ axum-extra = { version = "0.9", features = ["multipart"] }
|
||||
http = "1"
|
||||
tokio-util = { version = "0.7", features = ["io"] }
|
||||
zstd = "0.13"
|
||||
lz4_flex = "0.11"
|
||||
hex = "0.4"
|
||||
toml = "0.8"
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
|
||||
@@ -164,9 +164,11 @@ pub async fn handle_smb_server_command(cmd: SmbServerCommand) -> anyhow::Result<
|
||||
user
|
||||
};
|
||||
|
||||
let ldap_provider: Option<Arc<crate::provider::ldap::LdapProvider>> = if ldap {
|
||||
#[cfg(feature = "ldap")]
|
||||
{
|
||||
#[allow(unused_mut)]
|
||||
let mut ldap_enabled = false;
|
||||
#[cfg(feature = "ldap")]
|
||||
{
|
||||
if ldap {
|
||||
let config = crate::provider::ldap::LdapConfig {
|
||||
ldap_url: ldap_url.unwrap_or_else(|| "ldap://localhost:389".to_string()),
|
||||
base_dn: ldap_base_dn.unwrap_or_else(|| "dc=example,dc=com".to_string()),
|
||||
@@ -182,16 +184,13 @@ pub async fn handle_smb_server_command(cmd: SmbServerCommand) -> anyhow::Result<
|
||||
user_groups_attr: ldap_user_groups_attr.unwrap_or_else(|| "memberOf".to_string()),
|
||||
};
|
||||
log::info!("LDAP authentication enabled: url={}, search_base={}", config.ldap_url, config.user_search_base);
|
||||
Some(Arc::new(crate::provider::ldap::LdapProvider::new(config)))
|
||||
ldap_enabled = true;
|
||||
}
|
||||
#[cfg(not(feature = "ldap"))]
|
||||
{
|
||||
log::warn!("LDAP authentication requested but ldap feature not enabled");
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
#[cfg(not(feature = "ldap"))]
|
||||
if ldap {
|
||||
log::warn!("LDAP authentication requested but ldap feature not enabled");
|
||||
}
|
||||
|
||||
let mut builder = SmbServer::builder().listen(addr);
|
||||
|
||||
@@ -210,7 +209,7 @@ pub async fn handle_smb_server_command(cmd: SmbServerCommand) -> anyhow::Result<
|
||||
log::info!("SMB server listening on {}", addr);
|
||||
log::info!("Share '{}' at root: {}", share_name, root);
|
||||
log::info!("Users: {}", user_list.join(", "));
|
||||
if ldap_provider.is_some() {
|
||||
if ldap_enabled {
|
||||
log::info!("LDAP authentication: enabled");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
pub mod pg;
|
||||
pub mod sqlite;
|
||||
#[cfg(feature = "ldap")]
|
||||
#[cfg(feature = "ldap")]
|
||||
pub mod ldap;
|
||||
|
||||
pub use pg::PgProvider;
|
||||
|
||||
@@ -27,7 +27,7 @@ impl Compressor {
|
||||
.map_err(|e| VfsError::Io(format!("ZSTD compression failed: {}", e)))
|
||||
}
|
||||
VfsCompression::Lz4 => {
|
||||
Err(VfsError::Unsupported("LZ4 compression not yet implemented".to_string()))
|
||||
Ok(lz4_flex::compress_prepend_size(data))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,8 @@ impl Compressor {
|
||||
.map_err(|e| VfsError::Io(format!("ZSTD decompression failed: {}", e)))
|
||||
}
|
||||
VfsCompression::Lz4 => {
|
||||
Err(VfsError::Unsupported("LZ4 decompression not yet implemented".to_string()))
|
||||
lz4_flex::decompress_size_prepended(data)
|
||||
.map_err(|e| VfsError::Io(format!("LZ4 decompression failed: {}", e)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +140,15 @@ pub trait VfsFile: Send {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read all bytes (convenience, seeks to end first to get size)
|
||||
fn read_all(&mut self) -> Result<Vec<u8>, VfsError> {
|
||||
let size = self.seek(std::io::SeekFrom::End(0))?;
|
||||
self.seek(std::io::SeekFrom::Start(0))?;
|
||||
let mut buf = vec![0u8; size as usize];
|
||||
self.read_exact(&mut buf)?;
|
||||
Ok(buf)
|
||||
}
|
||||
}
|
||||
|
||||
/// VFS 后端 trait(所有文件系统操作)
|
||||
|
||||
@@ -109,15 +109,57 @@ impl VfsRaidBackend {
|
||||
(offset / self.stripe_size as u64) as usize % self.backends.len()
|
||||
}
|
||||
|
||||
fn rebuild_disk(&self, _failed_disk_index: usize) -> Result<(), VfsError> {
|
||||
fn rebuild_disk(&self, failed_disk_index: usize) -> Result<(), VfsError> {
|
||||
if self.config.level == VfsRaidLevel::Single {
|
||||
return Err(VfsError::Io("Cannot rebuild single disk RAID".to_string()));
|
||||
}
|
||||
|
||||
for backend in &self.backends {
|
||||
backend.create_dir_all(&PathBuf::from("/"), 0o755)?;
|
||||
if failed_disk_index >= self.backends.len() {
|
||||
return Err(VfsError::Io(format!("Invalid disk index {}", failed_disk_index)));
|
||||
}
|
||||
|
||||
let source_index = if self.backends.len() > 1 {
|
||||
// Use backends[0] as source if failed_disk_index != 0, else use backends[1]
|
||||
if failed_disk_index != 0 { 0 } else { 1 }
|
||||
} else {
|
||||
return Err(VfsError::Io("Not enough disks for rebuild".to_string()));
|
||||
};
|
||||
|
||||
let target_backend = &self.backends[failed_disk_index];
|
||||
let source_backend = &self.backends[source_index];
|
||||
|
||||
target_backend.create_dir_all(&PathBuf::from("/"), 0o755)?;
|
||||
|
||||
self.rebuild_recursive(source_backend, target_backend, &PathBuf::from("/"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rebuild_recursive(
|
||||
&self,
|
||||
source: &Box<dyn VfsBackend>,
|
||||
target: &Box<dyn VfsBackend>,
|
||||
path: &Path,
|
||||
) -> Result<(), VfsError> {
|
||||
let entries = source.read_dir(path)?;
|
||||
for entry in &entries {
|
||||
let entry_path = path.join(&entry.name);
|
||||
if entry.stat.is_dir {
|
||||
target.create_dir_all(&entry_path, entry.stat.mode)?;
|
||||
self.rebuild_recursive(source, target, &entry_path)?;
|
||||
} else {
|
||||
let mut src_file = source.open_file(&entry_path, &super::open_flags::OpenFlags::new().read())?;
|
||||
let data = src_file.read_all()?;
|
||||
let mut dst_file = target.open_file(
|
||||
&entry_path,
|
||||
&super::open_flags::OpenFlags::new().write().create().truncate(),
|
||||
)?;
|
||||
dst_file.write_all(&data)?;
|
||||
if let Ok(stat) = source.stat(&entry_path) {
|
||||
target.set_stat(&entry_path, &stat)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ fn vfs_stat_to_file_info(stat: &VfsStat, name: &str, path: &Path) -> FileInfo {
|
||||
last_write_time: system_time_to_filetime(stat.mtime),
|
||||
change_time: system_time_to_filetime(stat.mtime),
|
||||
is_directory: stat.is_dir,
|
||||
dos_attributes: 0,
|
||||
file_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user