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:
+79
-18
@@ -1,19 +1,34 @@
|
||||
//! macOS Unicode Private Range Mapping for SMB
|
||||
//!
|
||||
//! macOS SMB client maps NTFS illegal characters to Unicode private range.
|
||||
//! Reference: Samba vfs_fruit.c encoding handling
|
||||
//! Reference: Samba vfs_catia.c and vfs_fruit.c encoding handling
|
||||
//!
|
||||
//! Full mapping table (Samba catia standard):
|
||||
//! U+F001 → / (0x2F)
|
||||
//! U+F002 → : (0x3A)
|
||||
//! U+F003 → * (0x2A)
|
||||
//! U+F004 → ? (0x3F)
|
||||
//! U+F005 → " (0x22)
|
||||
//! U+F006 → < (0x3C)
|
||||
//! U+F007 → > (0x3E)
|
||||
//! U+F008 → | (0x7C)
|
||||
//! U+F009 → \ (0x5C)
|
||||
//! U+F02A → : (0x3A) — macOS Finder uses this for colon
|
||||
|
||||
pub const FRUIT_ENC_NATIVE: bool = true;
|
||||
pub const FRUIT_ENC_PRIVATE: bool = false;
|
||||
|
||||
const APPLE_SLASH: u16 = 0xF026;
|
||||
const APPLE_COLON: u16 = 0xF02A;
|
||||
const APPLE_ASTERISK: u16 = 0xF02B;
|
||||
const APPLE_QUESTION: u16 = 0xF03F;
|
||||
const APPLE_QUOTE: u16 = 0xF022;
|
||||
const APPLE_LESS_THAN: u16 = 0xF03C;
|
||||
const APPLE_GREATER_THAN: u16 = 0xF03E;
|
||||
const APPLE_PIPE: u16 = 0xF07C;
|
||||
// Apple private range code points (vfs_catia mapping)
|
||||
const APPLE_SLASH: u16 = 0xF001;
|
||||
const APPLE_COLON_ALT: u16 = 0xF002;
|
||||
const APPLE_ASTERISK: u16 = 0xF003;
|
||||
const APPLE_QUESTION: u16 = 0xF004;
|
||||
const APPLE_QUOTE: u16 = 0xF005;
|
||||
const APPLE_LESS_THAN: u16 = 0xF006;
|
||||
const APPLE_GREATER_THAN: u16 = 0xF007;
|
||||
const APPLE_PIPE: u16 = 0xF008;
|
||||
const APPLE_BACKSLASH: u16 = 0xF009;
|
||||
const APPLE_COLON: u16 = 0xF02A; // macOS Finder specific
|
||||
|
||||
const ASCII_SLASH: u16 = '/' as u16;
|
||||
const ASCII_COLON: u16 = ':' as u16;
|
||||
@@ -23,18 +38,30 @@ const ASCII_QUOTE: u16 = '"' as u16;
|
||||
const ASCII_LESS_THAN: u16 = '<' as u16;
|
||||
const ASCII_GREATER_THAN: u16 = '>' as u16;
|
||||
const ASCII_PIPE: u16 = '|' as u16;
|
||||
const ASCII_BACKSLASH: u16 = '\\' as u16;
|
||||
|
||||
/// Check if a UTF-16 code unit is in the macOS private range.
|
||||
pub fn is_private_range_char(u: u16) -> bool {
|
||||
matches!(u,
|
||||
APPLE_SLASH | APPLE_COLON_ALT | APPLE_ASTERISK |
|
||||
APPLE_QUESTION | APPLE_QUOTE | APPLE_LESS_THAN |
|
||||
APPLE_GREATER_THAN | APPLE_PIPE | APPLE_BACKSLASH |
|
||||
APPLE_COLON
|
||||
)
|
||||
}
|
||||
|
||||
pub fn map_private_to_ascii(units: &[u16]) -> Vec<u16> {
|
||||
units.iter().map(|u| {
|
||||
match *u {
|
||||
APPLE_SLASH => ASCII_SLASH,
|
||||
APPLE_COLON => ASCII_COLON,
|
||||
APPLE_COLON | APPLE_COLON_ALT => ASCII_COLON,
|
||||
APPLE_ASTERISK => ASCII_ASTERISK,
|
||||
APPLE_QUESTION => ASCII_QUESTION,
|
||||
APPLE_QUOTE => ASCII_QUOTE,
|
||||
APPLE_LESS_THAN => ASCII_LESS_THAN,
|
||||
APPLE_GREATER_THAN => ASCII_GREATER_THAN,
|
||||
APPLE_PIPE => ASCII_PIPE,
|
||||
APPLE_BACKSLASH => ASCII_BACKSLASH,
|
||||
_ => *u,
|
||||
}
|
||||
}).collect()
|
||||
@@ -51,19 +78,14 @@ pub fn map_ascii_to_private(units: &[u16]) -> Vec<u16> {
|
||||
ASCII_LESS_THAN => APPLE_LESS_THAN,
|
||||
ASCII_GREATER_THAN => APPLE_GREATER_THAN,
|
||||
ASCII_PIPE => APPLE_PIPE,
|
||||
ASCII_BACKSLASH => APPLE_BACKSLASH,
|
||||
_ => *u,
|
||||
}
|
||||
}).collect()
|
||||
}
|
||||
|
||||
pub fn has_private_range_chars(units: &[u16]) -> bool {
|
||||
units.iter().any(|u| {
|
||||
matches!(*u,
|
||||
APPLE_SLASH | APPLE_COLON | APPLE_ASTERISK |
|
||||
APPLE_QUESTION | APPLE_QUOTE | APPLE_LESS_THAN |
|
||||
APPLE_GREATER_THAN | APPLE_PIPE
|
||||
)
|
||||
})
|
||||
units.iter().any(|u| is_private_range_char(*u))
|
||||
}
|
||||
|
||||
pub fn has_ntfs_illegal_chars(units: &[u16]) -> bool {
|
||||
@@ -71,7 +93,7 @@ pub fn has_ntfs_illegal_chars(units: &[u16]) -> bool {
|
||||
matches!(*u,
|
||||
ASCII_SLASH | ASCII_COLON | ASCII_ASTERISK |
|
||||
ASCII_QUESTION | ASCII_QUOTE | ASCII_LESS_THAN |
|
||||
ASCII_GREATER_THAN | ASCII_PIPE
|
||||
ASCII_GREATER_THAN | ASCII_PIPE | ASCII_BACKSLASH
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -87,6 +109,23 @@ mod tests {
|
||||
assert_eq!(output, [ASCII_SLASH, ASCII_COLON, ASCII_QUESTION]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_map_private_to_ascii_all() {
|
||||
let input = [
|
||||
APPLE_SLASH, APPLE_COLON_ALT, APPLE_ASTERISK,
|
||||
APPLE_QUESTION, APPLE_QUOTE, APPLE_LESS_THAN,
|
||||
APPLE_GREATER_THAN, APPLE_PIPE, APPLE_BACKSLASH,
|
||||
APPLE_COLON,
|
||||
];
|
||||
let output = map_private_to_ascii(&input);
|
||||
assert_eq!(output, [
|
||||
ASCII_SLASH, ASCII_COLON, ASCII_ASTERISK,
|
||||
ASCII_QUESTION, ASCII_QUOTE, ASCII_LESS_THAN,
|
||||
ASCII_GREATER_THAN, ASCII_PIPE, ASCII_BACKSLASH,
|
||||
ASCII_COLON,
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_map_ascii_to_private() {
|
||||
let input = [ASCII_SLASH, ASCII_COLON, ASCII_ASTERISK];
|
||||
@@ -94,6 +133,21 @@ mod tests {
|
||||
assert_eq!(output, [APPLE_SLASH, APPLE_COLON, APPLE_ASTERISK]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_map_ascii_to_private_all() {
|
||||
let input = [
|
||||
ASCII_SLASH, ASCII_COLON, ASCII_ASTERISK,
|
||||
ASCII_QUESTION, ASCII_QUOTE, ASCII_LESS_THAN,
|
||||
ASCII_GREATER_THAN, ASCII_PIPE, ASCII_BACKSLASH,
|
||||
];
|
||||
let output = map_ascii_to_private(&input);
|
||||
assert_eq!(output, [
|
||||
APPLE_SLASH, APPLE_COLON, APPLE_ASTERISK,
|
||||
APPLE_QUESTION, APPLE_QUOTE, APPLE_LESS_THAN,
|
||||
APPLE_GREATER_THAN, APPLE_PIPE, APPLE_BACKSLASH,
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roundtrip() {
|
||||
let original = [ASCII_SLASH, ASCII_COLON, 'a' as u16];
|
||||
@@ -120,4 +174,11 @@ mod tests {
|
||||
let output = map_private_to_ascii(&input);
|
||||
assert_eq!(output, input);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_private_range_char() {
|
||||
assert!(is_private_range_char(APPLE_SLASH));
|
||||
assert!(is_private_range_char(APPLE_COLON));
|
||||
assert!(!is_private_range_char('a' as u16));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user