1use byteorder::{BigEndian, ByteOrder};
8use std::cmp::Ordering;
9use std::sync::OnceLock;
10
11use disk_core::filesystem::FilesystemError;
12
13pub use disk_core::bits::{
19 bitmap_clear_bit_be, bitmap_collect_clear_runs_be, bitmap_find_clear_bit_be,
20 bitmap_find_clear_run_be, bitmap_find_set_bit_be, bitmap_set_bit_be, bitmap_test_bit_be,
21};
22
23const MAC_EPOCH_DELTA: u64 = 2_082_844_800;
29
30pub fn hfs_now() -> u32 {
32 let unix_secs = std::time::SystemTime::now()
33 .duration_since(std::time::UNIX_EPOCH)
34 .unwrap_or_default()
35 .as_secs();
36 u32::try_from(unix_secs.saturating_add(MAC_EPOCH_DELTA)).unwrap_or(u32::MAX)
38}
39
40pub fn format_mac_date(mac_secs: u32) -> Option<String> {
45 if mac_secs == 0 {
46 return None;
47 }
48 let unix = mac_secs as i64 - MAC_EPOCH_DELTA as i64;
49 Some(disk_core::unix_common::inode::format_unix_timestamp(unix))
50}
51
52pub fn mac_date_to_unix(mac_secs: u32) -> Option<u64> {
57 if mac_secs == 0 || (mac_secs as u64) < MAC_EPOCH_DELTA {
58 return None;
59 }
60 Some(mac_secs as u64 - MAC_EPOCH_DELTA)
61}
62
63pub fn parse_mac_date(s: &str) -> Option<u32> {
68 parse_mac_date_checked(s).ok()
69}
70
71pub fn parse_mac_date_checked(s: &str) -> Result<u32, String> {
76 let s = s.trim();
77 if s.is_empty() || s == "0" {
78 return Ok(0);
79 }
80 let fmt = "expected YYYY-MM-DD HH:MM:SS";
81 let (date, time) = s.split_once(' ').ok_or(fmt)?;
82 let d: Vec<&str> = date.split('-').collect();
83 let t: Vec<&str> = time.split(':').collect();
84 if d.len() != 3 || t.len() != 3 {
85 return Err(fmt.into());
86 }
87 let num = |x: &str, what: &str| -> Result<i64, String> {
88 x.trim()
89 .parse::<i64>()
90 .map_err(|_| format!("'{}' is not a number ({what})", x.trim()))
91 };
92 let (y, mo, day) = (num(d[0], "year")?, num(d[1], "month")?, num(d[2], "day")?);
93 let (h, mi, se) = (
94 num(t[0], "hour")?,
95 num(t[1], "minute")?,
96 num(t[2], "second")?,
97 );
98 if !(1..=12).contains(&mo) {
99 return Err(format!("month {mo} is out of range (1-12)"));
100 }
101 let nd = chrono::NaiveDate::from_ymd_opt(y as i32, mo as u32, day as u32)
102 .ok_or_else(|| format!("day {day} is invalid for {y}-{mo:02}"))?;
103 let nt = chrono::NaiveTime::from_hms_opt(h as u32, mi as u32, se as u32)
104 .ok_or_else(|| format!("time {h:02}:{mi:02}:{se:02} is out of range"))?;
105 let mac = chrono::NaiveDateTime::new(nd, nt).and_utc().timestamp() + MAC_EPOCH_DELTA as i64;
106 if (0..=u32::MAX as i64).contains(&mac) {
107 Ok(mac as u32)
108 } else {
109 Err("date is outside the representable range".into())
110 }
111}
112
113struct ExtensionMap {
119 entries: Vec<(String, [u8; 4], [u8; 4])>, }
121
122fn load_extension_map() -> &'static ExtensionMap {
123 static MAP: OnceLock<ExtensionMap> = OnceLock::new();
124 MAP.get_or_init(|| {
125 let json_bytes = include_str!("../assets/hfs_file_types.json");
126 let parsed: serde_json::Value = serde_json::from_str(json_bytes).unwrap_or_default();
127 let mut entries = Vec::new();
128 if let Some(exts) = parsed.get("extensions").and_then(|v| v.as_object()) {
129 for (ext, val) in exts {
130 if ext.starts_with('_') {
131 continue;
132 }
133 if let (Some(tc), Some(cc)) = (
134 val.get("type").and_then(|v| v.as_str()),
135 val.get("creator").and_then(|v| v.as_str()),
136 ) {
137 entries.push((ext.to_lowercase(), encode_fourcc(tc), encode_fourcc(cc)));
138 }
139 }
140 }
141 ExtensionMap { entries }
142 })
143}
144
145#[derive(Debug, Clone)]
148pub struct TypeCreatorEntry {
149 pub type_code: [u8; 4],
150 pub creator_code: [u8; 4],
151 pub description: String,
152}
153
154impl TypeCreatorEntry {
155 pub fn type_str(&self) -> String {
156 String::from_utf8_lossy(&self.type_code).to_string()
157 }
158 pub fn creator_str(&self) -> String {
159 String::from_utf8_lossy(&self.creator_code).to_string()
160 }
161}
162
163fn load_type_creator_entries() -> &'static Vec<TypeCreatorEntry> {
164 static ENTRIES: OnceLock<Vec<TypeCreatorEntry>> = OnceLock::new();
165 ENTRIES.get_or_init(|| {
166 let json_bytes = include_str!("../assets/hfs_file_types.json");
167 let parsed: serde_json::Value = serde_json::from_str(json_bytes).unwrap_or_default();
168
169 let mut type_desc: std::collections::HashMap<String, String> =
171 std::collections::HashMap::new();
172 if let Some(types) = parsed.get("types").and_then(|v| v.as_object()) {
173 for (tc, val) in types {
174 if let Some(desc) = val.get("description").and_then(|v| v.as_str()) {
175 type_desc.insert(tc.clone(), desc.to_string());
176 }
177 }
178 }
179
180 let mut seen: std::collections::HashSet<(String, String)> =
183 std::collections::HashSet::new();
184 let mut out: Vec<TypeCreatorEntry> = Vec::new();
185 if let Some(arr) = parsed.get("entries").and_then(|v| v.as_array()) {
186 for item in arr {
187 let (Some(tc_str), Some(cc_str), Some(desc)) = (
188 item.get("type").and_then(|v| v.as_str()),
189 item.get("creator").and_then(|v| v.as_str()),
190 item.get("description").and_then(|v| v.as_str()),
191 ) else {
192 continue;
193 };
194 if !seen.insert((tc_str.to_string(), cc_str.to_string())) {
195 continue;
196 }
197 out.push(TypeCreatorEntry {
198 type_code: encode_fourcc(tc_str),
199 creator_code: encode_fourcc(cc_str),
200 description: desc.to_string(),
201 });
202 }
203 }
204 if let Some(exts) = parsed.get("extensions").and_then(|v| v.as_object()) {
207 for (ext, val) in exts {
208 if ext.starts_with('_') {
209 continue;
210 }
211 let (Some(tc_str), Some(cc_str)) = (
212 val.get("type").and_then(|v| v.as_str()),
213 val.get("creator").and_then(|v| v.as_str()),
214 ) else {
215 continue;
216 };
217 if !seen.insert((tc_str.to_string(), cc_str.to_string())) {
218 continue;
219 }
220 let desc = type_desc
221 .get(tc_str)
222 .cloned()
223 .unwrap_or_else(|| format!(".{ext}"));
224 out.push(TypeCreatorEntry {
225 type_code: encode_fourcc(tc_str),
226 creator_code: encode_fourcc(cc_str),
227 description: desc,
228 });
229 }
230 }
231 out.sort_by(|a, b| {
232 a.description
233 .to_lowercase()
234 .cmp(&b.description.to_lowercase())
235 .then_with(|| a.type_code.cmp(&b.type_code))
236 .then_with(|| a.creator_code.cmp(&b.creator_code))
237 });
238 out
239 })
240}
241
242pub fn known_type_creators() -> &'static [TypeCreatorEntry] {
244 load_type_creator_entries().as_slice()
245}
246
247pub fn describe_type_creator(type_code: &[u8; 4], creator_code: &[u8; 4]) -> Option<&'static str> {
249 load_type_creator_entries()
250 .iter()
251 .find(|e| &e.type_code == type_code && &e.creator_code == creator_code)
252 .map(|e| e.description.as_str())
253}
254
255pub fn uses_hfs_type_dictionary(fs_type: &str) -> bool {
262 fs_type.starts_with("HFS") || fs_type == "MFS" || fs_type == "BeOS OFS"
263}
264
265pub fn is_blank_ostype(code: &[u8; 4]) -> bool {
273 code == &[0u8; 4]
274}
275
276pub fn type_creator_for_filename(name: &str) -> Option<([u8; 4], [u8; 4])> {
280 let ext = name.rsplit('.').next().unwrap_or("");
281 if ext == name {
282 return None; }
284 type_creator_for_extension(ext)
285}
286
287pub fn resolve_create_type_creator(
306 name: &str,
307 os_type: Option<[u8; 4]>,
308 os_creator: Option<[u8; 4]>,
309 type_code: Option<&str>,
310 creator_code: Option<&str>,
311) -> ([u8; 4], [u8; 4]) {
312 let (dict_t, dict_c) = type_creator_for_filename(name).unwrap_or(([0; 4], [0; 4]));
313 let resolve = |raw: Option<[u8; 4]>, text: Option<&str>, dict: [u8; 4]| {
317 raw.filter(|c| !is_blank_ostype(c))
318 .or_else(|| text.filter(|s| !s.trim().is_empty()).map(encode_fourcc))
319 .unwrap_or(dict)
320 };
321 (
322 resolve(os_type, type_code, dict_t),
323 resolve(os_creator, creator_code, dict_c),
324 )
325}
326
327pub fn type_creator_for_extension(ext: &str) -> Option<([u8; 4], [u8; 4])> {
330 let lower = ext.to_lowercase();
331 let map = load_extension_map();
332 for (e, tc, cc) in &map.entries {
333 if e == &lower {
334 return Some((*tc, *cc));
335 }
336 }
337 None
338}
339
340pub fn encode_fourcc(s: &str) -> [u8; 4] {
342 let bytes = s.as_bytes();
343 let mut out = [b' '; 4];
344 for (i, &b) in bytes.iter().take(4).enumerate() {
345 out[i] = b;
346 }
347 out
348}
349
350pub use disk_core::display::decode_ostype;
352
353pub use fs_mac_unicode::collate::{compare_hfs_keys, compare_hfsplus_keys};
359
360pub fn compare_classic_extents_keys(a: &[u8], b: &[u8]) -> Ordering {
363 if a.len() < 8 || b.len() < 8 {
364 return a.len().cmp(&b.len());
365 }
366 let file_a = BigEndian::read_u32(&a[2..6]);
367 let file_b = BigEndian::read_u32(&b[2..6]);
368 match file_a.cmp(&file_b) {
369 Ordering::Equal => {}
370 other => return other,
371 }
372 match a[1].cmp(&b[1]) {
373 Ordering::Equal => {}
374 other => return other,
375 }
376 let start_a = BigEndian::read_u16(&a[6..8]);
377 let start_b = BigEndian::read_u16(&b[6..8]);
378 start_a.cmp(&start_b)
379}
380
381pub const BTREE_MIN_NODE_SIZE: usize = 512;
387
388pub const BTREE_LEAF_NODE: i8 = -1;
390pub const BTREE_INDEX_NODE: i8 = 0;
391#[allow(dead_code)]
392pub const BTREE_HEADER_NODE: i8 = 1;
393#[allow(dead_code)]
394pub const BTREE_MAP_NODE: i8 = 2;
395
396pub const HFS_CAT_MAX_KEY_LEN: u8 = 0x25;
399
400pub fn normalize_catalog_index_key(key: &[u8]) -> Vec<u8> {
408 let max = HFS_CAT_MAX_KEY_LEN as usize; let mut result = vec![0u8; 1 + max]; result[0] = HFS_CAT_MAX_KEY_LEN;
411 if key.len() > 1 {
413 let copy_len = (key.len() - 1).min(max);
414 result[1..1 + copy_len].copy_from_slice(&key[1..1 + copy_len]);
415 }
416 result
417}
418
419pub const KBT_BIG_KEYS_MASK: u32 = 0x0000_0002;
423pub const KBT_VARIABLE_INDEX_KEYS_MASK: u32 = 0x0000_0004;
428
429#[derive(Clone, Copy, Debug, PartialEq, Eq)]
441pub struct BTreeKeyFormat {
442 pub big_keys: bool,
444 pub variable_index_keys: bool,
448 pub max_key_len: u16,
451}
452
453impl BTreeKeyFormat {
454 pub const CLASSIC_CATALOG: BTreeKeyFormat = BTreeKeyFormat {
456 big_keys: false,
457 variable_index_keys: false,
458 max_key_len: HFS_CAT_MAX_KEY_LEN as u16,
459 };
460
461 pub const HFSPLUS_CATALOG: BTreeKeyFormat = BTreeKeyFormat {
464 big_keys: true,
465 variable_index_keys: true,
466 max_key_len: 516,
467 };
468
469 pub const HFSPLUS_EXTENTS: BTreeKeyFormat = BTreeKeyFormat {
472 big_keys: true,
473 variable_index_keys: false,
474 max_key_len: 10,
475 };
476
477 pub const HFSPLUS_ATTRIBUTES: BTreeKeyFormat = BTreeKeyFormat {
480 big_keys: true,
481 variable_index_keys: true,
482 max_key_len: 264,
483 };
484
485 pub const CLASSIC_EXTENTS: BTreeKeyFormat = BTreeKeyFormat {
487 big_keys: false,
488 variable_index_keys: false,
489 max_key_len: 7,
490 };
491
492 pub fn from_header(attributes: u32, max_key_len: u16) -> Self {
498 BTreeKeyFormat {
499 big_keys: attributes & KBT_BIG_KEYS_MASK != 0,
500 variable_index_keys: attributes & KBT_VARIABLE_INDEX_KEYS_MASK != 0,
501 max_key_len,
502 }
503 }
504
505 pub fn header_attributes(&self) -> u32 {
507 let mut a = 0;
508 if self.big_keys {
509 a |= KBT_BIG_KEYS_MASK;
510 }
511 if self.variable_index_keys {
512 a |= KBT_VARIABLE_INDEX_KEYS_MASK;
513 }
514 a
515 }
516
517 #[inline]
519 pub fn len_prefix(&self) -> usize {
520 if self.big_keys {
521 2
522 } else {
523 1
524 }
525 }
526
527 #[inline]
529 pub fn key_len(&self, record: &[u8]) -> usize {
530 if self.big_keys {
531 if record.len() < 2 {
532 0
533 } else {
534 BigEndian::read_u16(&record[0..2]) as usize
535 }
536 } else if record.is_empty() {
537 0
538 } else {
539 record[0] as usize
540 }
541 }
542
543 pub fn key_portion(&self, record: &[u8]) -> Vec<u8> {
547 let end = (self.len_prefix() + self.key_len(record)).min(record.len());
548 record[..end].to_vec()
549 }
550
551 pub fn make_index_key(&self, first_key: &[u8]) -> Vec<u8> {
561 if !self.big_keys {
562 if self.max_key_len == HFS_CAT_MAX_KEY_LEN as u16 {
563 return normalize_catalog_index_key(first_key);
564 }
565 let max = self.max_key_len as usize;
567 let mut result = vec![0u8; 1 + max];
568 result[0] = max as u8;
569 let copy = first_key.len().saturating_sub(1).min(max);
570 result[1..1 + copy].copy_from_slice(&first_key[1..1 + copy]);
571 return result;
572 }
573 if self.variable_index_keys {
574 return first_key.to_vec();
575 }
576 let max = self.max_key_len as usize;
577 let copy = self.key_len(first_key).min(max);
578 let mut result = vec![0u8; 2 + max];
579 BigEndian::write_u16(&mut result[0..2], max as u16);
580 if first_key.len() >= 2 + copy {
581 result[2..2 + copy].copy_from_slice(&first_key[2..2 + copy]);
582 }
583 result
584 }
585}
586
587pub struct BTreeHeader {
591 pub depth: u16,
592 pub root_node: u32,
593 pub leaf_records: u32,
594 pub first_leaf_node: u32,
595 pub last_leaf_node: u32,
596 pub node_size: u16,
597 pub total_nodes: u32,
598 pub free_nodes: u32,
599}
600
601impl BTreeHeader {
602 pub fn read(catalog_data: &[u8]) -> Self {
603 let d = &catalog_data[14..];
605 BTreeHeader {
606 depth: BigEndian::read_u16(&d[0..2]),
607 root_node: BigEndian::read_u32(&d[2..6]),
608 leaf_records: BigEndian::read_u32(&d[6..10]),
609 first_leaf_node: BigEndian::read_u32(&d[10..14]),
610 last_leaf_node: BigEndian::read_u32(&d[14..18]),
611 node_size: BigEndian::read_u16(&d[18..20]),
612 total_nodes: BigEndian::read_u32(&d[22..26]),
613 free_nodes: BigEndian::read_u32(&d[26..30]),
614 }
615 }
616
617 pub fn write(&self, catalog_data: &mut [u8]) {
618 let d = &mut catalog_data[14..];
619 BigEndian::write_u16(&mut d[0..2], self.depth);
620 BigEndian::write_u32(&mut d[2..6], self.root_node);
621 BigEndian::write_u32(&mut d[6..10], self.leaf_records);
622 BigEndian::write_u32(&mut d[10..14], self.first_leaf_node);
623 BigEndian::write_u32(&mut d[14..18], self.last_leaf_node);
624 BigEndian::write_u32(&mut d[22..26], self.total_nodes);
625 BigEndian::write_u32(&mut d[26..30], self.free_nodes);
626 }
627}
628
629pub fn btree_node_free_space(node: &[u8], node_size: usize) -> usize {
634 let num_records = BigEndian::read_u16(&node[10..12]) as usize;
635 if num_records == 0 {
636 let data_end = 14;
639 let table_start = node_size - 2 * (num_records + 1);
640 if table_start <= data_end + 2 {
641 return 0;
642 }
643 return table_start - data_end - 2;
644 }
645 let free_offset_pos = node_size - 2 * (num_records + 1);
647 let last_rec_end = BigEndian::read_u16(&node[free_offset_pos..free_offset_pos + 2]) as usize;
648 let table_start = free_offset_pos;
651 if table_start <= last_rec_end + 2 {
652 return 0;
653 }
654 table_start - last_rec_end - 2
655}
656
657fn index_child_ptr(node: &[u8], end: usize) -> u32 {
660 if end < 4 || end > node.len() {
661 return 0;
662 }
663 BigEndian::read_u32(&node[end - 4..end])
664}
665
666pub fn btree_record_range(node: &[u8], node_size: usize, rec_idx: usize) -> (usize, usize) {
669 let node_size = node_size.min(node.len());
672 let (Some(offset_pos), Some(next_pos)) = (
673 node_size.checked_sub(2 * (rec_idx + 1)),
674 node_size.checked_sub(2 * (rec_idx + 2)),
675 ) else {
676 return (0, 0);
677 };
678 let start = (BigEndian::read_u16(&node[offset_pos..offset_pos + 2]) as usize).min(node_size);
679 let end = (BigEndian::read_u16(&node[next_pos..next_pos + 2]) as usize).min(node_size);
680 (start, start.max(end))
681}
682
683pub fn walk_leaf_records<T, F>(
692 catalog_data: &[u8],
693 first_leaf: u32,
694 node_size: usize,
695 mut visit: F,
696) -> Option<T>
697where
698 F: FnMut(u32, usize, usize, &[u8]) -> Option<T>,
699{
700 if node_size < BTREE_MIN_NODE_SIZE || catalog_data.len() < node_size {
701 return None;
702 }
703 let mut node_idx = first_leaf;
704 let mut visited = std::collections::HashSet::new();
705 while node_idx != 0 {
706 if !visited.insert(node_idx) {
707 return None;
708 }
709 let offset = (node_idx as usize).checked_mul(node_size)?;
710 if offset + node_size > catalog_data.len() {
711 return None;
712 }
713 let node = &catalog_data[offset..offset + node_size];
714 let next = BigEndian::read_u32(&node[0..4]);
715 let kind = node[8] as i8;
716 if kind != BTREE_LEAF_NODE {
717 node_idx = next;
718 continue;
719 }
720 let num_records = BigEndian::read_u16(&node[10..12]) as usize;
721 for i in 0..num_records {
722 let (rec_start, rec_end) = btree_record_range(node, node_size, i);
723 if rec_start >= rec_end || rec_end > node_size {
724 continue;
725 }
726 if let Some(t) = visit(node_idx, i, offset + rec_start, &node[rec_start..rec_end]) {
727 return Some(t);
728 }
729 }
730 node_idx = next;
731 }
732 None
733}
734
735pub fn btree_insert_record<F>(
742 node: &mut [u8],
743 node_size: usize,
744 key_record_bytes: &[u8],
745 compare_fn: &F,
746) -> Result<usize, FilesystemError>
747where
748 F: Fn(&[u8], &[u8]) -> Ordering,
749{
750 let num_records = BigEndian::read_u16(&node[10..12]) as usize;
751 let total_needed = key_record_bytes.len() + 2; let free = btree_node_free_space(node, node_size);
754 if free < total_needed {
755 return Err(FilesystemError::InvalidData("B-tree node full".into()));
756 }
757
758 let mut records: Vec<Vec<u8>> = Vec::with_capacity(num_records + 1);
760 for i in 0..num_records {
761 let (rec_start, rec_end) = btree_record_range(node, node_size, i);
762 if rec_start < rec_end && rec_end <= node_size {
763 records.push(node[rec_start..rec_end].to_vec());
764 }
765 }
766
767 let mut insert_pos = records.len();
769 for (i, rec) in records.iter().enumerate() {
770 if compare_fn(rec, key_record_bytes) == Ordering::Greater {
771 insert_pos = i;
772 break;
773 }
774 }
775
776 records.insert(insert_pos, key_record_bytes.to_vec());
778
779 let mut write_pos = 14usize;
782 for (i, rec) in records.iter().enumerate() {
783 node[write_pos..write_pos + rec.len()].copy_from_slice(rec);
784 let opos = node_size - 2 * (i + 1);
785 BigEndian::write_u16(&mut node[opos..opos + 2], write_pos as u16);
786 write_pos += rec.len();
787 }
788
789 let fpos = node_size - 2 * (records.len() + 1);
791 BigEndian::write_u16(&mut node[fpos..fpos + 2], write_pos as u16);
792
793 let offset_table_start = node_size - 2 * (records.len() + 1);
795 if write_pos < offset_table_start {
796 node[write_pos..offset_table_start].fill(0);
797 }
798
799 BigEndian::write_u16(&mut node[10..12], records.len() as u16);
801
802 Ok(insert_pos)
803}
804
805pub fn btree_remove_record(node: &mut [u8], node_size: usize, rec_idx: usize) {
812 let num_records = BigEndian::read_u16(&node[10..12]) as usize;
813 if rec_idx >= num_records {
814 return;
815 }
816
817 let mut records: Vec<Vec<u8>> = Vec::with_capacity(num_records - 1);
819 for i in 0..num_records {
820 if i == rec_idx {
821 continue;
822 }
823 let (rec_start, rec_end) = btree_record_range(node, node_size, i);
824 if rec_start < rec_end && rec_end <= node_size {
825 records.push(node[rec_start..rec_end].to_vec());
826 }
827 }
828
829 let mut write_pos = 14usize;
831 for (i, rec) in records.iter().enumerate() {
832 node[write_pos..write_pos + rec.len()].copy_from_slice(rec);
833 let opos = node_size - 2 * (i + 1);
834 BigEndian::write_u16(&mut node[opos..opos + 2], write_pos as u16);
835 write_pos += rec.len();
836 }
837
838 let fpos = node_size - 2 * (records.len() + 1);
840 BigEndian::write_u16(&mut node[fpos..fpos + 2], write_pos as u16);
841
842 let offset_table_start = node_size - 2 * (records.len() + 1);
844 if write_pos < offset_table_start {
845 node[write_pos..offset_table_start].fill(0);
846 }
847
848 let clear_pos = node_size - 2 * (num_records + 1);
850 BigEndian::write_u16(&mut node[clear_pos..clear_pos + 2], 0);
851
852 BigEndian::write_u16(&mut node[10..12], records.len() as u16);
854}
855
856pub fn btree_node_bitmap_range(catalog_data: &[u8], node_size: usize) -> (usize, usize) {
874 let offset_pos = node_size - 2 * 3; let bitmap_start = BigEndian::read_u16(&catalog_data[offset_pos..offset_pos + 2]) as usize;
878 let next_pos = node_size - 2 * 4; let bitmap_end = BigEndian::read_u16(&catalog_data[next_pos..next_pos + 2]) as usize;
880 (bitmap_start, bitmap_end.saturating_sub(bitmap_start))
881}
882
883pub fn map_node_bitmap_bytes(node_size: usize) -> usize {
885 node_size.saturating_sub(18)
887}
888
889pub fn map_nodes_required(total_nodes: u32, node_size: usize) -> u32 {
896 let header_cap = ((node_size.saturating_sub(256)) * 8) as u32;
897 if total_nodes <= header_cap {
898 return 0;
899 }
900 let per_map = (map_node_bitmap_bytes(node_size) * 8) as u32;
901 if per_map == 0 {
902 return 0;
903 }
904 (total_nodes - header_cap).div_ceil(per_map)
905}
906
907#[derive(Debug, Clone, Copy)]
909pub struct BitmapSegment {
910 pub byte_off: usize,
912 pub len: usize,
914 pub base_bit: u32,
917}
918
919pub fn btree_bitmap_segments(catalog_data: &[u8], node_size: usize) -> Vec<BitmapSegment> {
925 let mut segs = Vec::with_capacity(1);
926 if catalog_data.len() < node_size {
927 return segs;
928 }
929
930 let (hdr_off, hdr_len) = btree_node_bitmap_range(catalog_data, node_size);
931 segs.push(BitmapSegment {
932 byte_off: hdr_off,
933 len: hdr_len,
934 base_bit: 0,
935 });
936 let mut next_bit = (hdr_len as u32) * 8;
937
938 let mut next_node = BigEndian::read_u32(&catalog_data[0..4]);
941 let mut visited: std::collections::HashSet<u32> = std::collections::HashSet::with_capacity(8);
942
943 while next_node != 0 {
944 if !visited.insert(next_node) {
945 break;
947 }
948 let off = next_node as usize * node_size;
949 if off + node_size > catalog_data.len() {
950 break;
951 }
952 let kind = catalog_data[off + 8] as i8;
954 let num_records = BigEndian::read_u16(&catalog_data[off + 10..off + 12]);
955 if kind != 2 || num_records < 1 {
956 break;
957 }
958 let rec0_pos = off + node_size - 2;
960 let rec0_off = BigEndian::read_u16(&catalog_data[rec0_pos..rec0_pos + 2]) as usize;
961 let free_pos = off + node_size - 4;
963 let free_off = BigEndian::read_u16(&catalog_data[free_pos..free_pos + 2]) as usize;
964 if rec0_off < 14 || free_off <= rec0_off || free_off > node_size {
965 break;
966 }
967 let len = free_off - rec0_off;
968 segs.push(BitmapSegment {
969 byte_off: off + rec0_off,
970 len,
971 base_bit: next_bit,
972 });
973 next_bit += (len as u32) * 8;
974 next_node = BigEndian::read_u32(&catalog_data[off..off + 4]);
976 }
977
978 segs
979}
980
981fn locate_bit_in_segments(segs: &[BitmapSegment], global_bit: u32) -> Option<(usize, u32)> {
982 for (i, seg) in segs.iter().enumerate() {
983 let cap = (seg.len as u32) * 8;
984 if global_bit >= seg.base_bit && global_bit < seg.base_bit + cap {
985 return Some((i, global_bit - seg.base_bit));
986 }
987 }
988 None
989}
990
991pub fn btree_bitmap_test(catalog_data: &[u8], node_size: usize, bit: u32) -> bool {
993 let segs = btree_bitmap_segments(catalog_data, node_size);
994 let Some((idx, local)) = locate_bit_in_segments(&segs, bit) else {
995 return false;
996 };
997 let seg = segs[idx];
998 let slice = &catalog_data[seg.byte_off..seg.byte_off + seg.len];
999 bitmap_test_bit_be(slice, local)
1000}
1001
1002pub fn btree_alloc_node(
1006 catalog_data: &mut [u8],
1007 node_size: usize,
1008 total_nodes: u32,
1009) -> Result<u32, FilesystemError> {
1010 let segs = btree_bitmap_segments(catalog_data, node_size);
1011 for seg in &segs {
1012 let bitmap = &catalog_data[seg.byte_off..seg.byte_off + seg.len];
1013 let cap = (seg.len as u32) * 8;
1014 let upper = total_nodes.saturating_sub(seg.base_bit).min(cap);
1015 for local in 0..upper {
1016 if !bitmap_test_bit_be(bitmap, local) {
1017 let global = seg.base_bit + local;
1018 bitmap_set_bit_be(
1019 &mut catalog_data[seg.byte_off..seg.byte_off + seg.len],
1020 local,
1021 );
1022 return Ok(global);
1023 }
1024 }
1025 }
1026 Err(FilesystemError::DiskFull("no free B-tree nodes".into()))
1027}
1028
1029pub fn btree_detach_freed_node(data: &mut [u8], node_size: usize, freed: u32) -> u32 {
1032 let mut header = BTreeHeader::read(data);
1033 let max_nodes = (data.len() / node_size) as u32;
1034 let mut freed_count = 0u32;
1035 if header.root_node == freed {
1036 header.root_node = 0;
1038 header.depth = 0;
1039 header.write(data);
1040 return 0;
1041 }
1042 let mut target = freed;
1043 loop {
1044 let mut found = None;
1045 'scan: for idx in 1..max_nodes {
1046 if idx == target || !btree_bitmap_test(data, node_size, idx) {
1047 continue;
1048 }
1049 let off = idx as usize * node_size;
1050 let node = &data[off..off + node_size];
1051 if node[8] as i8 != BTREE_INDEX_NODE {
1052 continue;
1053 }
1054 let n = BigEndian::read_u16(&node[10..12]) as usize;
1055 for r in 0..n {
1056 let (_, end) = btree_record_range(node, node_size, r);
1057 if index_child_ptr(node, end) == target {
1058 found = Some((idx, r));
1059 break 'scan;
1060 }
1061 }
1062 }
1063 let Some((idx, r)) = found else {
1064 break;
1065 };
1066 let off = idx as usize * node_size;
1067 btree_remove_record(&mut data[off..off + node_size], node_size, r);
1068 if BigEndian::read_u16(&data[off + 10..off + 12]) > 0 {
1069 break;
1070 }
1071 btree_free_node(data, node_size, idx);
1072 freed_count += 1;
1073 if header.root_node == idx {
1074 header.root_node = 0;
1075 header.depth = 0;
1076 header.free_nodes += freed_count;
1077 header.write(data);
1078 return freed_count;
1079 }
1080 target = idx;
1081 }
1082 while header.depth > 1 && header.root_node != 0 {
1084 let off = header.root_node as usize * node_size;
1085 let node = &data[off..off + node_size];
1086 if node[8] as i8 != BTREE_INDEX_NODE || BigEndian::read_u16(&node[10..12]) != 1 {
1087 break;
1088 }
1089 let (_, end) = btree_record_range(node, node_size, 0);
1090 let child = index_child_ptr(node, end);
1091 if child == 0 {
1092 break;
1093 }
1094 btree_free_node(data, node_size, header.root_node);
1095 freed_count += 1;
1096 header.root_node = child;
1097 header.depth -= 1;
1098 }
1099 header.free_nodes += freed_count;
1100 header.write(data);
1101 freed_count
1102}
1103
1104pub fn btree_find_parent(data: &[u8], node_size: usize, child: u32) -> Option<(u32, usize)> {
1107 let max_nodes = (data.len() / node_size) as u32;
1108 for idx in 1..max_nodes {
1109 if idx == child || !btree_bitmap_test(data, node_size, idx) {
1110 continue;
1111 }
1112 let off = idx as usize * node_size;
1113 let node = &data[off..off + node_size];
1114 if node[8] as i8 != BTREE_INDEX_NODE {
1115 continue;
1116 }
1117 let n = BigEndian::read_u16(&node[10..12]) as usize;
1118 for r in 0..n {
1119 let (_, end) = btree_record_range(node, node_size, r);
1120 if index_child_ptr(node, end) == child {
1121 return Some((idx, r));
1122 }
1123 }
1124 }
1125 None
1126}
1127
1128fn btree_parent_chain(data: &[u8], node_size: usize, node: u32) -> Vec<(u32, u32)> {
1131 let mut chain = Vec::new();
1132 let mut cur = node;
1133 while cur != 0 && chain.len() < 64 {
1134 let parent = btree_find_parent(data, node_size, cur).map_or(0, |(p, _)| p);
1135 chain.push((cur, parent));
1136 cur = parent;
1137 }
1138 chain.reverse();
1139 chain
1140}
1141
1142pub fn btree_refresh_index_keys<F>(
1148 data: &mut [u8],
1149 node_size: usize,
1150 mut node_idx: u32,
1151 kf: &BTreeKeyFormat,
1152 cmp: &F,
1153) -> Result<(), FilesystemError>
1154where
1155 F: Fn(&[u8], &[u8]) -> Ordering,
1156{
1157 let max_nodes = (data.len() / node_size) as u32;
1158 for _ in 0..64 {
1160 if node_idx == 0 || node_idx >= max_nodes {
1161 return Ok(());
1162 }
1163 let first_key = {
1164 let off = node_idx as usize * node_size;
1165 let node = &data[off..off + node_size];
1166 if BigEndian::read_u16(&node[10..12]) == 0 {
1167 return Ok(());
1168 }
1169 let (s, e) = btree_record_range(node, node_size, 0);
1170 kf.key_portion(&node[s..e])
1171 };
1172 let Some((parent, rec_idx)) = btree_find_parent(data, node_size, node_idx) else {
1173 return Ok(());
1174 };
1175 let mut wanted = kf.make_index_key(&first_key);
1176 let mut ptr = [0u8; 4];
1177 BigEndian::write_u32(&mut ptr, node_idx);
1178 wanted.extend_from_slice(&ptr);
1179
1180 let parent_off = parent as usize * node_size;
1181 let replaced = {
1182 let pnode = &mut data[parent_off..parent_off + node_size];
1183 let (s, e) = btree_record_range(pnode, node_size, rec_idx);
1184 if pnode[s..e] == wanted[..] {
1185 return Ok(());
1186 }
1187 btree_remove_record(pnode, node_size, rec_idx);
1188 btree_insert_record(pnode, node_size, &wanted, cmp).is_ok()
1189 };
1190 if !replaced {
1191 let mut header = BTreeHeader::read(data);
1193 let chain = btree_parent_chain(data, node_size, parent);
1194 btree_insert_into_index(
1195 data,
1196 node_size,
1197 parent,
1198 node_idx,
1199 &first_key,
1200 &mut header,
1201 kf,
1202 cmp,
1203 &chain,
1204 )?;
1205 header.write(data);
1206 }
1207 node_idx = parent;
1208 }
1209 Ok(())
1210}
1211
1212pub fn btree_detach_freed_node_and_refresh<F>(
1215 data: &mut [u8],
1216 node_size: usize,
1217 freed: u32,
1218 kf: &BTreeKeyFormat,
1219 cmp: &F,
1220) -> Result<u32, FilesystemError>
1221where
1222 F: Fn(&[u8], &[u8]) -> Ordering,
1223{
1224 let parent = btree_find_parent(data, node_size, freed);
1225 let freed_count = btree_detach_freed_node(data, node_size, freed);
1226 if let Some((parent, 0)) = parent {
1227 if btree_bitmap_test(data, node_size, parent) {
1228 btree_refresh_index_keys(data, node_size, parent, kf, cmp)?;
1229 }
1230 }
1231 Ok(freed_count)
1232}
1233
1234pub fn btree_stale_index_keys<F>(
1238 data: &[u8],
1239 node_size: usize,
1240 kf: &BTreeKeyFormat,
1241 cmp: &F,
1242) -> Vec<(u32, usize, u32, Ordering)>
1243where
1244 F: Fn(&[u8], &[u8]) -> Ordering,
1245{
1246 let mut stale = Vec::new();
1247 if node_size < BTREE_MIN_NODE_SIZE || data.len() < node_size {
1248 return stale;
1249 }
1250 let max_nodes = (data.len() / node_size) as u32;
1251 for idx in 1..max_nodes {
1252 if !btree_bitmap_test(data, node_size, idx) {
1253 continue;
1254 }
1255 let off = idx as usize * node_size;
1256 let node = &data[off..off + node_size];
1257 if node[8] as i8 != BTREE_INDEX_NODE {
1258 continue;
1259 }
1260 let n = BigEndian::read_u16(&node[10..12]) as usize;
1261 for r in 0..n {
1262 let (s, e) = btree_record_range(node, node_size, r);
1263 if e < s + 4 || e > node_size {
1264 continue;
1265 }
1266 let child = index_child_ptr(node, e);
1267 if child == 0 || child >= max_nodes || !btree_bitmap_test(data, node_size, child) {
1268 continue;
1269 }
1270 let coff = child as usize * node_size;
1271 let cnode = &data[coff..coff + node_size];
1272 if BigEndian::read_u16(&cnode[10..12]) == 0 {
1273 continue;
1274 }
1275 let (cs, ce) = btree_record_range(cnode, node_size, 0);
1276 let wanted = kf.make_index_key(&kf.key_portion(&cnode[cs..ce]));
1277 let order = cmp(&node[s..e - 4], &wanted);
1278 if order != Ordering::Equal {
1279 stale.push((idx, r, child, order));
1280 }
1281 }
1282 }
1283 stale
1284}
1285
1286pub fn btree_retire_empty_leaf<F>(
1291 data: &mut [u8],
1292 node_size: usize,
1293 node_idx: u32,
1294 kf: &BTreeKeyFormat,
1295 cmp: &F,
1296) -> Result<u32, FilesystemError>
1297where
1298 F: Fn(&[u8], &[u8]) -> Ordering,
1299{
1300 let off = node_idx as usize * node_size;
1301 if off + node_size > data.len() || BigEndian::read_u16(&data[off + 10..off + 12]) != 0 {
1302 return Ok(0);
1303 }
1304 let flink = BigEndian::read_u32(&data[off..off + 4]);
1305 let blink = BigEndian::read_u32(&data[off + 4..off + 8]);
1306 if blink != 0 {
1307 let b = blink as usize * node_size;
1308 BigEndian::write_u32(&mut data[b..b + 4], flink);
1309 }
1310 if flink != 0 {
1311 let f = flink as usize * node_size;
1312 BigEndian::write_u32(&mut data[f + 4..f + 8], blink);
1313 }
1314 let mut h = BTreeHeader::read(data);
1315 if h.first_leaf_node == node_idx {
1316 h.first_leaf_node = flink;
1317 }
1318 if h.last_leaf_node == node_idx {
1319 h.last_leaf_node = blink;
1320 }
1321 h.free_nodes += 1;
1322 h.write(data);
1323 btree_free_node(data, node_size, node_idx);
1324 let freed = btree_detach_freed_node_and_refresh(data, node_size, node_idx, kf, cmp)?;
1325 Ok(freed + 1)
1326}
1327
1328pub fn btree_lonely_empty_leaves(data: &[u8], node_size: usize) -> Vec<u32> {
1331 let mut lonely = Vec::new();
1332 if node_size < BTREE_MIN_NODE_SIZE || data.len() < node_size {
1333 return lonely;
1334 }
1335 let max_nodes = (data.len() / node_size) as u32;
1336 for idx in 1..max_nodes {
1337 if !btree_bitmap_test(data, node_size, idx) {
1338 continue;
1339 }
1340 let off = idx as usize * node_size;
1341 let node = &data[off..off + node_size];
1342 if node[8] as i8 == BTREE_LEAF_NODE
1343 && BigEndian::read_u16(&node[10..12]) == 0
1344 && BigEndian::read_u32(&node[0..4]) == 0
1345 && BigEndian::read_u32(&node[4..8]) == 0
1346 {
1347 lonely.push(idx);
1348 }
1349 }
1350 lonely
1351}
1352
1353pub fn btree_free_node(catalog_data: &mut [u8], node_size: usize, node_idx: u32) {
1356 let segs = btree_bitmap_segments(catalog_data, node_size);
1357 let Some((idx, local)) = locate_bit_in_segments(&segs, node_idx) else {
1358 return;
1359 };
1360 let seg = segs[idx];
1361 bitmap_clear_bit_be(
1362 &mut catalog_data[seg.byte_off..seg.byte_off + seg.len],
1363 local,
1364 );
1365 let off = node_idx as usize * node_size;
1366 if node_idx != 0 && off + node_size <= catalog_data.len() {
1367 catalog_data[off..off + node_size].fill(0);
1368 }
1369}
1370
1371pub fn btree_unerased_free_nodes(data: &[u8], node_size: usize) -> Vec<u32> {
1374 let mut dirty = Vec::new();
1375 if node_size < BTREE_MIN_NODE_SIZE || data.len() < node_size {
1376 return dirty;
1377 }
1378 let max_nodes = (data.len() / node_size) as u32;
1379 for idx in 1..max_nodes {
1380 if btree_bitmap_test(data, node_size, idx) {
1381 continue;
1382 }
1383 let off = idx as usize * node_size;
1384 if data[off..off + node_size].iter().any(|&b| b != 0) {
1385 dirty.push(idx);
1386 }
1387 }
1388 dirty
1389}
1390
1391pub fn btree_erase_free_nodes(data: &mut [u8], node_size: usize) -> usize {
1393 let dirty = btree_unerased_free_nodes(data, node_size);
1394 for &idx in &dirty {
1395 let off = idx as usize * node_size;
1396 data[off..off + node_size].fill(0);
1397 }
1398 dirty.len()
1399}
1400
1401pub fn btree_bitmap_clear(catalog_data: &mut [u8], node_size: usize, node_idx: u32) {
1403 let segs = btree_bitmap_segments(catalog_data, node_size);
1404 let Some((idx, local)) = locate_bit_in_segments(&segs, node_idx) else {
1405 return;
1406 };
1407 let seg = segs[idx];
1408 bitmap_clear_bit_be(
1409 &mut catalog_data[seg.byte_off..seg.byte_off + seg.len],
1410 local,
1411 );
1412}
1413
1414pub fn btree_bitmap_set(catalog_data: &mut [u8], node_size: usize, node_idx: u32) {
1417 let segs = btree_bitmap_segments(catalog_data, node_size);
1418 let Some((idx, local)) = locate_bit_in_segments(&segs, node_idx) else {
1419 return;
1420 };
1421 let seg = segs[idx];
1422 bitmap_set_bit_be(
1423 &mut catalog_data[seg.byte_off..seg.byte_off + seg.len],
1424 local,
1425 );
1426}
1427
1428pub fn btree_map_chain_tail(buf: &[u8], node_size: usize) -> u32 {
1432 let mut next = BigEndian::read_u32(&buf[0..4]); let mut last = 0u32;
1434 let mut guard = 0;
1435 while next != 0 && guard < 1_000_000 {
1436 last = next;
1437 let off = next as usize * node_size;
1438 if off + 4 > buf.len() {
1439 break;
1440 }
1441 next = BigEndian::read_u32(&buf[off..off + 4]);
1442 guard += 1;
1443 }
1444 last
1445}
1446
1447pub fn btree_grow_blocks(
1451 total_nodes: u32,
1452 add_slots: u32,
1453 node_size: usize,
1454 block_size: u32,
1455) -> u32 {
1456 let node_bytes = node_size as u64;
1457 let cur_blocks = (total_nodes as u64 * node_bytes).div_ceil(block_size as u64);
1458 let new_blocks = ((total_nodes + add_slots) as u64 * node_bytes).div_ceil(block_size as u64);
1459 (new_blocks - cur_blocks) as u32
1460}
1461
1462pub fn init_map_node(
1465 catalog_data: &mut [u8],
1466 node_size: usize,
1467 node_idx: u32,
1468 prev_link: u32,
1469 next_link: u32,
1470) {
1471 let off = node_idx as usize * node_size;
1472 let node = &mut catalog_data[off..off + node_size];
1473 node.fill(0);
1475 BigEndian::write_u32(&mut node[0..4], next_link);
1477 BigEndian::write_u32(&mut node[4..8], prev_link);
1478 node[8] = 2; node[9] = 0;
1480 BigEndian::write_u16(&mut node[10..12], 1);
1481 let bitmap_bytes = map_node_bitmap_bytes(node_size) as u16;
1483 let free_off = 14u16 + bitmap_bytes;
1484 BigEndian::write_u16(&mut node[node_size - 2..node_size], 14);
1486 BigEndian::write_u16(&mut node[node_size - 4..node_size - 2], free_off);
1487}
1488
1489pub(crate) fn init_node(
1496 catalog_data: &mut [u8],
1497 node_size: usize,
1498 node_idx: u32,
1499 kind: i8,
1500 height: u8,
1501) {
1502 let offset = node_idx as usize * node_size;
1503 let node = &mut catalog_data[offset..offset + node_size];
1504 node.fill(0);
1506 node[8] = kind as u8; node[9] = height; BigEndian::write_u16(&mut node[10..12], 0); BigEndian::write_u16(&mut node[node_size - 2..node_size], 14);
1512}
1513
1514pub fn btree_split_leaf_with_insert<F>(
1530 catalog_data: &mut [u8],
1531 node_size: usize,
1532 node_idx: u32,
1533 header: &mut BTreeHeader,
1534 new_record: &[u8],
1535 kf: &BTreeKeyFormat,
1536 compare_fn: &F,
1537) -> Result<Vec<(u32, Vec<u8>)>, FilesystemError>
1538where
1539 F: Fn(&[u8], &[u8]) -> Ordering,
1540{
1541 let old_offset = node_idx as usize * node_size;
1542 let old_num = BigEndian::read_u16(&catalog_data[old_offset + 10..old_offset + 12]) as usize;
1543 let old_height = catalog_data[old_offset + 9];
1544
1545 let mut records: Vec<Vec<u8>> = Vec::with_capacity(old_num + 1);
1547 for i in 0..old_num {
1548 let (s, e) = btree_record_range(
1549 &catalog_data[old_offset..old_offset + node_size],
1550 node_size,
1551 i,
1552 );
1553 records.push(catalog_data[old_offset + s..old_offset + e].to_vec());
1554 }
1555 let mut insert_pos = records.len();
1556 for (i, rec) in records.iter().enumerate() {
1557 if compare_fn(rec, new_record) == Ordering::Greater {
1558 insert_pos = i;
1559 break;
1560 }
1561 }
1562 records.insert(insert_pos, new_record.to_vec());
1563
1564 let payload = node_size.saturating_sub(16);
1569 for rec in &records {
1570 if rec.len() + 2 > payload {
1571 return Err(FilesystemError::InvalidData(format!(
1572 "btree_split_leaf_with_insert: record of {} bytes exceeds node payload {}",
1573 rec.len(),
1574 payload
1575 )));
1576 }
1577 }
1578
1579 let mut cuts: Vec<usize> = Vec::new();
1582 let mut cur_bytes: usize = 0;
1583 for (i, rec) in records.iter().enumerate() {
1584 let cost = rec.len() + 2;
1585 if i > 0 && cur_bytes + cost > payload {
1586 cuts.push(i);
1587 cur_bytes = 0;
1588 }
1589 cur_bytes += cost;
1590 }
1591 if cuts.is_empty() {
1597 cuts.push(
1598 (records.len() / 2)
1599 .max(1)
1600 .min(records.len().saturating_sub(1)),
1601 );
1602 }
1603
1604 let is_append = insert_pos == old_num;
1614 if cuts.len() == 1 && !is_append {
1615 let total: usize = records.iter().map(|r| r.len() + 2).sum();
1616 let mut prefix = 0usize;
1617 let mut best_k = cuts[0];
1618 let mut best_diff = usize::MAX;
1619 for k in 1..records.len() {
1620 prefix += records[k - 1].len() + 2;
1621 let right = total - prefix;
1622 if prefix <= payload && right <= payload {
1623 let diff = prefix.abs_diff(total / 2);
1624 if diff < best_diff {
1625 best_diff = diff;
1626 best_k = k;
1627 }
1628 }
1629 }
1630 cuts[0] = best_k;
1631 }
1632
1633 let mut chunks: Vec<Vec<Vec<u8>>> = Vec::with_capacity(cuts.len() + 1);
1635 let mut prev = 0usize;
1636 for &cut in &cuts {
1637 chunks.push(records.drain(0..cut - prev).collect());
1638 prev = cut;
1639 }
1640 chunks.push(records);
1641
1642 let new_count = chunks.len() - 1;
1644 let mut new_indices: Vec<u32> = Vec::with_capacity(new_count);
1645 for _ in 0..new_count {
1646 let idx = btree_alloc_node(catalog_data, node_size, header.total_nodes)?;
1647 header.free_nodes -= 1;
1648 init_node(catalog_data, node_size, idx, BTREE_LEAF_NODE, old_height);
1649 new_indices.push(idx);
1650 }
1651
1652 let write_chunk = |data: &mut [u8], target_idx: u32, chunk: &[Vec<u8>]| {
1656 let off = target_idx as usize * node_size;
1657 let node = &mut data[off..off + node_size];
1658 BigEndian::write_u16(&mut node[10..12], 0);
1659 BigEndian::write_u16(&mut node[node_size - 2..node_size], 14);
1660 let mut write_pos = 14usize;
1661 for (i, rec) in chunk.iter().enumerate() {
1662 node[write_pos..write_pos + rec.len()].copy_from_slice(rec);
1663 let opos = node_size - 2 * (i + 1);
1664 BigEndian::write_u16(&mut node[opos..opos + 2], write_pos as u16);
1665 write_pos += rec.len();
1666 }
1667 let fpos = node_size - 2 * (chunk.len() + 1);
1668 BigEndian::write_u16(&mut node[fpos..fpos + 2], write_pos as u16);
1669 BigEndian::write_u16(&mut node[10..12], chunk.len() as u16);
1670 };
1671
1672 write_chunk(catalog_data, node_idx, &chunks[0]);
1674 for (i, idx) in new_indices.iter().enumerate() {
1675 write_chunk(catalog_data, *idx, &chunks[i + 1]);
1676 }
1677
1678 let old_next = BigEndian::read_u32(&catalog_data[old_offset..old_offset + 4]);
1680 let mut prev_idx = node_idx;
1681 for &idx in &new_indices {
1682 let prev_off = prev_idx as usize * node_size;
1683 let cur_off = idx as usize * node_size;
1684 BigEndian::write_u32(&mut catalog_data[prev_off..prev_off + 4], idx);
1685 BigEndian::write_u32(&mut catalog_data[cur_off + 4..cur_off + 8], prev_idx);
1686 prev_idx = idx;
1687 }
1688 let last_idx = *new_indices.last().expect("at least one new node");
1690 let last_off = last_idx as usize * node_size;
1691 BigEndian::write_u32(&mut catalog_data[last_off..last_off + 4], old_next);
1692 if old_next != 0 {
1693 let next_offset = old_next as usize * node_size;
1694 BigEndian::write_u32(
1695 &mut catalog_data[next_offset + 4..next_offset + 8],
1696 last_idx,
1697 );
1698 }
1699 if header.last_leaf_node == node_idx {
1700 header.last_leaf_node = last_idx;
1701 }
1702
1703 let mut results: Vec<(u32, Vec<u8>)> = Vec::with_capacity(new_count);
1708 for (i, idx) in new_indices.iter().enumerate() {
1709 let split_key = kf.key_portion(&chunks[i + 1][0]);
1710 results.push((*idx, split_key));
1711 }
1712 Ok(results)
1713}
1714
1715fn btree_write_node_records(data: &mut [u8], node_size: usize, idx: u32, recs: &[Vec<u8>]) {
1721 let off = idx as usize * node_size;
1722 let node = &mut data[off..off + node_size];
1723 let mut write_pos = 14usize;
1724 for (i, rec) in recs.iter().enumerate() {
1725 node[write_pos..write_pos + rec.len()].copy_from_slice(rec);
1726 let opos = node_size - 2 * (i + 1);
1727 BigEndian::write_u16(&mut node[opos..opos + 2], write_pos as u16);
1728 write_pos += rec.len();
1729 }
1730 let fpos = node_size - 2 * (recs.len() + 1);
1731 BigEndian::write_u16(&mut node[fpos..fpos + 2], write_pos as u16);
1732 if write_pos < fpos {
1733 node[write_pos..fpos].fill(0);
1734 }
1735 BigEndian::write_u16(&mut node[10..12], recs.len() as u16);
1736}
1737
1738fn index_node_has_child(data: &[u8], node_size: usize, parent_idx: u32, child: u32) -> bool {
1742 if parent_idx == 0 {
1743 return false;
1744 }
1745 let off = parent_idx as usize * node_size;
1746 if off + node_size > data.len() {
1747 return false;
1748 }
1749 let num = BigEndian::read_u16(&data[off + 10..off + 12]) as usize;
1750 for i in 0..num {
1751 let (s, e) = btree_record_range(&data[off..off + node_size], node_size, i);
1752 if e < s + 4 || e > node_size {
1753 continue;
1754 }
1755 if BigEndian::read_u32(&data[off + e - 4..off + e]) == child {
1756 return true;
1757 }
1758 }
1759 false
1760}
1761
1762#[must_use]
1776fn btree_update_index_separator(
1777 data: &mut [u8],
1778 node_size: usize,
1779 parent_idx: u32,
1780 child: u32,
1781 new_first_key: &[u8],
1782 kf: &BTreeKeyFormat,
1783) -> bool {
1784 let off = parent_idx as usize * node_size;
1785 if off + node_size > data.len() {
1786 return false;
1787 }
1788 let num = BigEndian::read_u16(&data[off + 10..off + 12]) as usize;
1789 for i in 0..num {
1790 let (s, e) = btree_record_range(&data[off..off + node_size], node_size, i);
1791 if e < s + 4 || e > node_size {
1792 continue;
1793 }
1794 if BigEndian::read_u32(&data[off + e - 4..off + e]) == child {
1795 let new_key = kf.make_index_key(new_first_key);
1804 let key_bytes = e - s - 4;
1805 if new_key.len() != key_bytes {
1806 return false;
1807 }
1808 data[off + s..off + s + key_bytes].copy_from_slice(&new_key);
1809 return true;
1810 }
1811 }
1812 false
1813}
1814
1815fn btree_try_rotate_leaf<F>(
1839 data: &mut [u8],
1840 node_size: usize,
1841 leaf_idx: u32,
1842 parent_idx: u32,
1843 new_record: &[u8],
1844 kf: &BTreeKeyFormat,
1845 cmp: &F,
1846) -> bool
1847where
1848 F: Fn(&[u8], &[u8]) -> Ordering,
1849{
1850 let payload = node_size.saturating_sub(16);
1851 let cost = |r: &Vec<u8>| r.len() + 2;
1852 let first_key = |r: &[u8]| -> Vec<u8> { kf.key_portion(r) };
1853 let read_records = |data: &[u8], idx: u32| -> Vec<Vec<u8>> {
1854 let off = idx as usize * node_size;
1855 let num = BigEndian::read_u16(&data[off + 10..off + 12]) as usize;
1856 let mut v = Vec::with_capacity(num);
1857 for i in 0..num {
1858 let (s, e) = btree_record_range(&data[off..off + node_size], node_size, i);
1859 v.push(data[off + s..off + e].to_vec());
1860 }
1861 v
1862 };
1863
1864 let loff = leaf_idx as usize * node_size;
1865 let rsib = BigEndian::read_u32(&data[loff..loff + 4]);
1866 let lsib = BigEndian::read_u32(&data[loff + 4..loff + 8]);
1867 let leaf_recs = read_records(data, leaf_idx);
1868
1869 for (sib_is_right, sib) in [(true, rsib), (false, lsib)] {
1873 if sib == 0 || !index_node_has_child(data, node_size, parent_idx, sib) {
1874 continue;
1875 }
1876 let sib_recs = read_records(data, sib);
1877 let (left_idx, right_idx, left_recs, right_recs) = if sib_is_right {
1878 (leaf_idx, sib, &leaf_recs, &sib_recs)
1879 } else {
1880 (sib, leaf_idx, &sib_recs, &leaf_recs)
1881 };
1882 let Some(left_first) = left_recs.first() else {
1883 continue;
1884 };
1885 let mut pool: Vec<Vec<u8>> = left_recs.iter().chain(right_recs.iter()).cloned().collect();
1887 let mut p = pool.len();
1888 for (i, r) in pool.iter().enumerate() {
1889 if cmp(r, new_record) == Ordering::Greater {
1890 p = i;
1891 break;
1892 }
1893 }
1894 pool.insert(p, new_record.to_vec());
1895 if cmp(&pool[0], left_first) != Ordering::Equal {
1900 continue;
1901 }
1902 let total: usize = pool.iter().map(cost).sum();
1903 if total > 2 * payload {
1904 continue; }
1906 let mut prefix = 0usize;
1908 let mut best_p = 0usize;
1909 let mut best_diff = usize::MAX;
1910 for cut in 1..pool.len() {
1911 prefix += cost(&pool[cut - 1]);
1912 let right = total - prefix;
1913 if prefix <= payload && right <= payload {
1914 let diff = prefix.abs_diff(total / 2);
1915 if diff < best_diff {
1916 best_diff = diff;
1917 best_p = cut;
1918 }
1919 }
1920 }
1921 if best_p == 0 {
1922 continue;
1923 }
1924 let left: Vec<Vec<u8>> = pool[..best_p].to_vec();
1925 let right: Vec<Vec<u8>> = pool[best_p..].to_vec();
1926 let right_first = first_key(&right[0]);
1927 if !btree_update_index_separator(data, node_size, parent_idx, right_idx, &right_first, kf) {
1932 continue;
1933 }
1934 btree_write_node_records(data, node_size, left_idx, &left);
1935 btree_write_node_records(data, node_size, right_idx, &right);
1936 return true;
1937 }
1938 false
1939}
1940
1941fn btree_split_index_with_insert<F>(
1956 catalog_data: &mut [u8],
1957 node_size: usize,
1958 node_idx: u32,
1959 header: &mut BTreeHeader,
1960 new_record: &[u8],
1961 kf: &BTreeKeyFormat,
1962 compare_fn: &F,
1963) -> Result<(u32, Vec<u8>), FilesystemError>
1964where
1965 F: Fn(&[u8], &[u8]) -> Ordering,
1966{
1967 let old_offset = node_idx as usize * node_size;
1968 let old_num = BigEndian::read_u16(&catalog_data[old_offset + 10..old_offset + 12]) as usize;
1969 let old_height = catalog_data[old_offset + 9];
1970
1971 let mut records: Vec<Vec<u8>> = Vec::with_capacity(old_num + 1);
1973 for i in 0..old_num {
1974 let (s, e) = btree_record_range(
1975 &catalog_data[old_offset..old_offset + node_size],
1976 node_size,
1977 i,
1978 );
1979 records.push(catalog_data[old_offset + s..old_offset + e].to_vec());
1980 }
1981 let mut insert_pos = records.len();
1982 for (i, rec) in records.iter().enumerate() {
1983 if compare_fn(rec, new_record) == Ordering::Greater {
1984 insert_pos = i;
1985 break;
1986 }
1987 }
1988 records.insert(insert_pos, new_record.to_vec());
1989
1990 let payload = node_size.saturating_sub(16);
1991 let cost = |r: &Vec<u8>| r.len() + 2;
1992 let total: usize = records.iter().map(cost).sum();
1993
1994 let mut cut = {
1996 let mut acc = 0usize;
1997 let mut k = 1usize;
1998 for (i, r) in records.iter().enumerate() {
1999 let c = cost(r);
2000 if i > 0 && acc + c > payload {
2001 break;
2002 }
2003 acc += c;
2004 k = i + 1;
2005 }
2006 k.clamp(1, records.len() - 1)
2007 };
2008 let is_append = insert_pos == old_num;
2011 if !is_append {
2012 let mut prefix = 0usize;
2013 let mut best = cut;
2014 let mut best_diff = usize::MAX;
2015 for k in 1..records.len() {
2016 prefix += cost(&records[k - 1]);
2017 let right = total - prefix;
2018 if prefix <= payload && right <= payload {
2019 let diff = prefix.abs_diff(total / 2);
2020 if diff < best_diff {
2021 best_diff = diff;
2022 best = k;
2023 }
2024 }
2025 }
2026 cut = best;
2027 }
2028
2029 let new_idx = btree_alloc_node(catalog_data, node_size, header.total_nodes)?;
2030 header.free_nodes -= 1;
2031 init_node(
2032 catalog_data,
2033 node_size,
2034 new_idx,
2035 BTREE_INDEX_NODE,
2036 old_height,
2037 );
2038
2039 let left: Vec<Vec<u8>> = records[..cut].to_vec();
2040 let right: Vec<Vec<u8>> = records[cut..].to_vec();
2041 btree_write_node_records(catalog_data, node_size, node_idx, &left);
2042 btree_write_node_records(catalog_data, node_size, new_idx, &right);
2043
2044 let old_next = BigEndian::read_u32(&catalog_data[old_offset..old_offset + 4]);
2047 let new_offset = new_idx as usize * node_size;
2048 BigEndian::write_u32(&mut catalog_data[old_offset..old_offset + 4], new_idx);
2049 BigEndian::write_u32(&mut catalog_data[new_offset + 4..new_offset + 8], node_idx);
2050 BigEndian::write_u32(&mut catalog_data[new_offset..new_offset + 4], old_next);
2051 if old_next != 0 {
2052 let next_off = old_next as usize * node_size;
2053 BigEndian::write_u32(&mut catalog_data[next_off + 4..next_off + 8], new_idx);
2054 }
2055
2056 let split_key = kf.key_portion(&right[0]);
2058 Ok((new_idx, split_key))
2059}
2060
2061#[allow(clippy::too_many_arguments)] pub fn btree_insert_into_index<F>(
2070 catalog_data: &mut [u8],
2071 node_size: usize,
2072 index_node_idx: u32,
2073 child_node: u32,
2074 split_key: &[u8],
2075 header: &mut BTreeHeader,
2076 kf: &BTreeKeyFormat,
2077 compare_fn: &F,
2078 parent_chain: &[(u32, u32)], ) -> Result<(), FilesystemError>
2080where
2081 F: Fn(&[u8], &[u8]) -> Ordering,
2082{
2083 let mut index_record = kf.make_index_key(split_key);
2090 let mut ptr = [0u8; 4];
2091 BigEndian::write_u32(&mut ptr, child_node);
2092 index_record.extend_from_slice(&ptr);
2093
2094 let offset = index_node_idx as usize * node_size;
2095 let node = &mut catalog_data[offset..offset + node_size];
2096
2097 match btree_insert_record(node, node_size, &index_record, compare_fn) {
2099 Ok(_) => Ok(()),
2100 Err(_) => {
2101 if let Some(&(_, parent_idx)) = parent_chain
2110 .iter()
2111 .find(|&&(nidx, _)| nidx == index_node_idx)
2112 {
2113 if parent_idx != 0
2114 && btree_try_rotate_leaf(
2115 catalog_data,
2116 node_size,
2117 index_node_idx,
2118 parent_idx,
2119 &index_record,
2120 kf,
2121 compare_fn,
2122 )
2123 {
2124 return Ok(());
2125 }
2126 }
2127
2128 let (new_idx, new_split_key) = btree_split_index_with_insert(
2132 catalog_data,
2133 node_size,
2134 index_node_idx,
2135 header,
2136 &index_record,
2137 kf,
2138 compare_fn,
2139 )?;
2140
2141 if let Some(&(_, parent_idx)) = parent_chain
2144 .iter()
2145 .find(|&&(nidx, _)| nidx == index_node_idx)
2146 {
2147 if parent_idx == 0 && index_node_idx == header.root_node {
2148 btree_grow_root(
2150 catalog_data,
2151 node_size,
2152 header,
2153 index_node_idx,
2154 new_idx,
2155 &new_split_key,
2156 kf,
2157 )?;
2158 } else {
2159 btree_insert_into_index(
2161 catalog_data,
2162 node_size,
2163 parent_idx,
2164 new_idx,
2165 &new_split_key,
2166 header,
2167 kf,
2168 compare_fn,
2169 parent_chain,
2170 )?;
2171 }
2172 } else {
2173 btree_grow_root(
2175 catalog_data,
2176 node_size,
2177 header,
2178 index_node_idx,
2179 new_idx,
2180 &new_split_key,
2181 kf,
2182 )?;
2183 }
2184 Ok(())
2185 }
2186 }
2187}
2188
2189pub fn btree_grow_root(
2191 catalog_data: &mut [u8],
2192 node_size: usize,
2193 header: &mut BTreeHeader,
2194 old_root: u32,
2195 new_sibling: u32,
2196 split_key: &[u8],
2197 kf: &BTreeKeyFormat,
2198) -> Result<(), FilesystemError> {
2199 let new_root = btree_alloc_node(catalog_data, node_size, header.total_nodes)?;
2200 header.free_nodes -= 1;
2201
2202 let old_root_offset = old_root as usize * node_size;
2203 let old_height = catalog_data[old_root_offset + 9];
2204
2205 init_node(
2206 catalog_data,
2207 node_size,
2208 new_root,
2209 BTREE_INDEX_NODE,
2210 old_height + 1,
2211 );
2212
2213 let new_root_offset = new_root as usize * node_size;
2214
2215 let (first_rec_start, first_rec_end) = btree_record_range(
2222 &catalog_data[old_root_offset..old_root_offset + node_size],
2223 node_size,
2224 0,
2225 );
2226 let first_key = kf.key_portion(
2227 &catalog_data[old_root_offset + first_rec_start..old_root_offset + first_rec_end],
2228 );
2229
2230 let mut rec1 = kf.make_index_key(&first_key);
2232 let mut ptr1 = [0u8; 4];
2233 BigEndian::write_u32(&mut ptr1, old_root);
2234 rec1.extend_from_slice(&ptr1);
2235
2236 let mut rec2 = kf.make_index_key(split_key);
2238 let mut ptr2 = [0u8; 4];
2239 BigEndian::write_u32(&mut ptr2, new_sibling);
2240 rec2.extend_from_slice(&ptr2);
2241
2242 let node = &mut catalog_data[new_root_offset..new_root_offset + node_size];
2244 let mut write_pos = 14usize;
2245 node[write_pos..write_pos + rec1.len()].copy_from_slice(&rec1);
2247 BigEndian::write_u16(&mut node[node_size - 2..node_size], 14);
2248 write_pos += rec1.len();
2249 node[write_pos..write_pos + rec2.len()].copy_from_slice(&rec2);
2251 BigEndian::write_u16(&mut node[node_size - 4..node_size - 2], write_pos as u16);
2252 write_pos += rec2.len();
2253 BigEndian::write_u16(&mut node[node_size - 6..node_size - 4], write_pos as u16);
2255 BigEndian::write_u16(&mut node[10..12], 2); header.root_node = new_root;
2258 header.depth += 1;
2259
2260 Ok(())
2261}
2262
2263#[allow(dead_code)]
2277#[allow(clippy::type_complexity)] pub fn btree_find_record<K, C>(
2279 catalog_data: &[u8],
2280 header: &BTreeHeader,
2281 search_key: &[u8],
2282 key_extract_fn: &K,
2283 compare_fn: &C,
2284) -> (Option<(u32, usize, usize)>, Vec<(u32, u32)>)
2285where
2286 K: Fn(&[u8]) -> &[u8],
2287 C: Fn(&[u8], &[u8]) -> Ordering,
2288{
2289 let node_size = header.node_size as usize;
2290 let mut parent_chain: Vec<(u32, u32)> = Vec::new();
2291 let mut current_node = header.root_node;
2292 let mut parent_node = 0u32;
2293 let mut visited = std::collections::HashSet::new();
2294
2295 loop {
2296 if !visited.insert(current_node) {
2298 return (None, parent_chain);
2299 }
2300 let offset = current_node as usize * node_size;
2301 if offset + node_size > catalog_data.len() {
2302 return (None, parent_chain);
2303 }
2304 let node = &catalog_data[offset..offset + node_size];
2305 let kind = node[8] as i8;
2306 let num_records = BigEndian::read_u16(&node[10..12]) as usize;
2307
2308 parent_chain.push((current_node, parent_node));
2309
2310 if kind == BTREE_LEAF_NODE {
2311 for i in 0..num_records {
2313 let (start, end) = btree_record_range(node, node_size, i);
2314 if start >= end || end > node_size {
2315 continue;
2316 }
2317 let rec_bytes = &node[start..end];
2318 let key = key_extract_fn(rec_bytes);
2319 match compare_fn(key, search_key) {
2320 Ordering::Equal => {
2321 return (Some((current_node, i, offset + start)), parent_chain);
2322 }
2323 Ordering::Greater => return (None, parent_chain),
2324 _ => {}
2325 }
2326 }
2327 return (None, parent_chain);
2328 } else if kind == BTREE_INDEX_NODE {
2329 let mut next_child = 0u32;
2332 for i in 0..num_records {
2333 let (start, end) = btree_record_range(node, node_size, i);
2334 if end < start + 4 || end > node_size {
2335 continue;
2336 }
2337 let rec_bytes = &node[start..end];
2338 let key = key_extract_fn(rec_bytes);
2339 let child_ptr = index_child_ptr(node, end);
2341
2342 match compare_fn(key, search_key) {
2343 Ordering::Greater => {
2344 break;
2346 }
2347 _ => {
2348 next_child = child_ptr;
2349 }
2350 }
2351 }
2352 if next_child == 0 && num_records > 0 {
2353 let (_, end) = btree_record_range(node, node_size, 0);
2356 next_child = index_child_ptr(node, end);
2357 }
2358 parent_node = current_node;
2359 current_node = next_child;
2360 } else {
2361 return (None, parent_chain);
2362 }
2363 }
2364}
2365
2366pub fn btree_find_insert_leaf<C>(
2369 catalog_data: &[u8],
2370 header: &BTreeHeader,
2371 search_key: &[u8],
2372 compare_fn: &C,
2373) -> (u32, Vec<(u32, u32)>)
2374where
2375 C: Fn(&[u8], &[u8]) -> Ordering,
2376{
2377 let node_size = header.node_size as usize;
2378 let mut parent_chain: Vec<(u32, u32)> = Vec::new();
2379 let mut current_node = header.root_node;
2380 let mut parent_node = 0u32;
2381 let mut visited = std::collections::HashSet::new();
2382
2383 loop {
2384 if !visited.insert(current_node) {
2386 return (header.first_leaf_node, parent_chain);
2387 }
2388 let offset = current_node as usize * node_size;
2389 if offset + node_size > catalog_data.len() {
2390 return (current_node, parent_chain);
2391 }
2392 let node = &catalog_data[offset..offset + node_size];
2393 let kind = node[8] as i8;
2394 let num_records = BigEndian::read_u16(&node[10..12]) as usize;
2395
2396 parent_chain.push((current_node, parent_node));
2397
2398 if kind == BTREE_LEAF_NODE {
2399 return (current_node, parent_chain);
2400 }
2401
2402 let mut next_child = 0u32;
2404 for i in 0..num_records {
2405 let (start, end) = btree_record_range(node, node_size, i);
2406 if end < start + 4 || end > node_size {
2407 continue;
2408 }
2409 let key_portion = &node[start..end - 4];
2411 let child_ptr = index_child_ptr(node, end);
2412
2413 if compare_fn(key_portion, search_key) != Ordering::Greater {
2414 next_child = child_ptr;
2415 } else {
2416 break;
2417 }
2418 }
2419 if next_child == 0 && num_records > 0 {
2420 let (_, end) = btree_record_range(node, node_size, 0);
2422 next_child = index_child_ptr(node, end);
2423 }
2424
2425 parent_node = current_node;
2426 current_node = next_child;
2427 }
2428}
2429
2430pub fn btree_insert_full<F>(
2452 data: &mut [u8],
2453 key_record: &[u8],
2454 kf: &BTreeKeyFormat,
2455 cmp: &F,
2456) -> Result<(), disk_core::filesystem::FilesystemError>
2457where
2458 F: Fn(&[u8], &[u8]) -> Ordering,
2459{
2460 let mut header = BTreeHeader::read(data);
2461 let node_size = header.node_size as usize;
2462 if node_size < BTREE_MIN_NODE_SIZE {
2463 return Err(disk_core::filesystem::FilesystemError::InvalidData(
2464 format!("B-tree has invalid node_size {node_size}"),
2465 ));
2466 }
2467
2468 if header.depth == 0 || header.root_node == 0 {
2474 let leaf = btree_alloc_node(data, node_size, header.total_nodes)?;
2475 let off = leaf as usize * node_size;
2476 let node = &mut data[off..off + node_size];
2477 node.fill(0);
2478 node[8] = 0xFF; node[9] = 1; BigEndian::write_u16(&mut node[10..12], 0); BigEndian::write_u16(&mut node[node_size - 2..node_size], 14); header.depth = 1;
2483 header.root_node = leaf;
2484 header.first_leaf_node = leaf;
2485 header.last_leaf_node = leaf;
2486 header.free_nodes = header.free_nodes.saturating_sub(1);
2487 header.write(data);
2488 }
2489
2490 let (leaf_idx, parent_chain) = btree_find_insert_leaf(data, &header, key_record, cmp);
2491
2492 let off = leaf_idx as usize * node_size;
2493 let node = &mut data[off..off + node_size];
2494 match btree_insert_record(node, node_size, key_record, cmp) {
2495 Ok(_) => {
2496 let mut h = BTreeHeader::read(data);
2497 h.leaf_records += 1;
2498 h.write(data);
2499 Ok(())
2500 }
2501 Err(_) => {
2502 let mut h = BTreeHeader::read(data);
2503 if h.depth > 1 {
2509 if let Some(&(_, parent_idx)) =
2510 parent_chain.iter().find(|&&(nidx, _)| nidx == leaf_idx)
2511 {
2512 if parent_idx != 0
2513 && btree_try_rotate_leaf(
2514 data, node_size, leaf_idx, parent_idx, key_record, kf, cmp,
2515 )
2516 {
2517 h.leaf_records += 1;
2518 h.write(data);
2519 return Ok(());
2520 }
2521 }
2522 }
2523 if h.free_nodes < h.depth as u32 + 1 {
2537 return Err(disk_core::filesystem::FilesystemError::DiskFull(
2538 "no free B-tree nodes".into(),
2539 ));
2540 }
2541 let splits = btree_split_leaf_with_insert(
2542 data, node_size, leaf_idx, &mut h, key_record, kf, cmp,
2543 )?;
2544 h.leaf_records += 1;
2545 let first = &splits[0];
2550 if h.depth == 1 {
2551 btree_grow_root(data, node_size, &mut h, leaf_idx, first.0, &first.1, kf)?;
2552 } else if let Some(&(_, parent_idx)) =
2553 parent_chain.iter().find(|&&(nidx, _)| nidx == leaf_idx)
2554 {
2555 btree_insert_into_index(
2556 data,
2557 node_size,
2558 parent_idx,
2559 first.0,
2560 &first.1,
2561 &mut h,
2562 kf,
2563 cmp,
2564 &parent_chain,
2565 )?;
2566 } else {
2567 btree_grow_root(data, node_size, &mut h, leaf_idx, first.0, &first.1, kf)?;
2568 }
2569 for split in &splits[1..] {
2570 let header_now = BTreeHeader::read(data);
2575 let (_, fresh_chain) = btree_find_insert_leaf(data, &header_now, &split.1, cmp);
2576 let parent_for_sep = fresh_chain
2581 .last()
2582 .map(|&(_, p)| p)
2583 .unwrap_or(header_now.root_node);
2584 btree_insert_into_index(
2585 data,
2586 node_size,
2587 parent_for_sep,
2588 split.0,
2589 &split.1,
2590 &mut h,
2591 kf,
2592 cmp,
2593 &fresh_chain,
2594 )?;
2595 }
2596 h.write(data);
2597 Ok(())
2598 }
2599 }
2600}
2601
2602#[cfg(test)]
2607mod tests {
2608 use super::*;
2609
2610 #[test]
2611 fn test_format_mac_date() {
2612 assert_eq!(format_mac_date(0), None);
2614 assert_eq!(
2616 format_mac_date(MAC_EPOCH_DELTA as u32).as_deref(),
2617 Some("1970-01-01 00:00:00")
2618 );
2619 assert_eq!(
2621 format_mac_date((MAC_EPOCH_DELTA + 946_684_800) as u32).as_deref(),
2622 Some("2000-01-01 00:00:00")
2623 );
2624 }
2625
2626 #[test]
2627 fn test_parse_mac_date_round_trips() {
2628 assert_eq!(parse_mac_date(""), Some(0));
2630 assert_eq!(parse_mac_date("0"), Some(0));
2631 let mac = (MAC_EPOCH_DELTA + 946_684_800) as u32; let s = format_mac_date(mac).unwrap();
2634 assert_eq!(parse_mac_date(&s), Some(mac));
2635 assert_eq!(parse_mac_date("not a date"), None);
2637 }
2638
2639 #[test]
2640 fn test_parse_mac_date_checked_specific_errors() {
2641 assert!(parse_mac_date_checked("1997-13-01 11:09:34")
2642 .unwrap_err()
2643 .contains("month 13"));
2644 assert!(parse_mac_date_checked("1997-02-29 11:09:34")
2645 .unwrap_err()
2646 .contains("day 29 is invalid for 1997-02"));
2647 assert!(parse_mac_date_checked("2000-01-01 25:00:00")
2648 .unwrap_err()
2649 .contains("out of range"));
2650 assert!(parse_mac_date_checked("garbage")
2651 .unwrap_err()
2652 .contains("expected YYYY-MM-DD"));
2653 assert!(parse_mac_date_checked("2000-02-29 12:00:00").is_ok());
2655 }
2656
2657 #[test]
2660 fn test_bitmap_set_clear_test_be() {
2661 let mut data = [0u8; 2];
2662 bitmap_set_bit_be(&mut data, 0);
2664 assert_eq!(data[0], 0b10000000);
2665 assert!(bitmap_test_bit_be(&data, 0));
2666 assert!(!bitmap_test_bit_be(&data, 1));
2667
2668 bitmap_set_bit_be(&mut data, 7);
2669 assert_eq!(data[0], 0b10000001);
2670
2671 bitmap_set_bit_be(&mut data, 8);
2672 assert_eq!(data[1], 0b10000000);
2673
2674 bitmap_clear_bit_be(&mut data, 0);
2675 assert_eq!(data[0], 0b00000001);
2676 assert!(!bitmap_test_bit_be(&data, 0));
2677 }
2678
2679 #[test]
2680 fn test_bitmap_find_clear_bit_be() {
2681 let data = [0b11111110, 0b11111111]; assert_eq!(bitmap_find_clear_bit_be(&data, 16), Some(7));
2683
2684 let full = [0xFF, 0xFF];
2685 assert_eq!(bitmap_find_clear_bit_be(&full, 16), None);
2686
2687 let empty = [0x00, 0x00];
2688 assert_eq!(bitmap_find_clear_bit_be(&empty, 16), Some(0));
2689 }
2690
2691 #[test]
2692 fn test_bitmap_find_clear_run_be() {
2693 let data = [0b11100011, 0b00000000];
2695 assert_eq!(bitmap_find_clear_run_be(&data, 16, 3), Some(3));
2698 assert_eq!(bitmap_find_clear_run_be(&data, 16, 4), Some(8));
2700 assert_eq!(bitmap_find_clear_run_be(&data, 16, 8), Some(8));
2701 assert_eq!(bitmap_find_clear_run_be(&data, 16, 9), None);
2702 }
2703
2704 #[test]
2705 fn test_bitmap_be_roundtrip() {
2706 let mut data = [0u8; 4];
2707 for i in 0..32u32 {
2708 bitmap_set_bit_be(&mut data, i);
2709 }
2710 assert_eq!(data, [0xFF; 4]);
2711 for i in 0..32u32 {
2712 assert!(bitmap_test_bit_be(&data, i));
2713 bitmap_clear_bit_be(&mut data, i);
2714 }
2715 assert_eq!(data, [0x00; 4]);
2716 }
2717
2718 #[test]
2719 fn test_bitmap_single_bit() {
2720 let mut data = [0u8; 1];
2721 bitmap_set_bit_be(&mut data, 3);
2722 assert_eq!(data[0], 0b00010000);
2723 assert!(bitmap_test_bit_be(&data, 3));
2724 assert!(!bitmap_test_bit_be(&data, 2));
2725 assert!(!bitmap_test_bit_be(&data, 4));
2726 }
2727
2728 #[test]
2731 fn test_hfs_now_sanity() {
2732 let now = hfs_now();
2733 assert!(now > 3_786_912_000);
2736 }
2737
2738 #[test]
2741 fn test_type_creator_for_extension_known() {
2742 let (tc, cc) = type_creator_for_extension("txt").unwrap();
2743 assert_eq!(&tc, b"TEXT");
2744 assert_eq!(&cc, b"ttxt");
2745 }
2746
2747 #[test]
2748 fn test_type_creator_for_extension_case_insensitive() {
2749 let (tc, cc) = type_creator_for_extension("TXT").unwrap();
2750 assert_eq!(&tc, b"TEXT");
2751 assert_eq!(&cc, b"ttxt");
2752 }
2753
2754 #[test]
2755 fn test_uses_hfs_type_dictionary() {
2756 for t in ["HFS", "HFS+", "HFSX", "MFS"] {
2757 assert!(uses_hfs_type_dictionary(t), "{t} should use the dictionary");
2758 }
2759 for t in ["ProDOS", "FAT16", "ext4", ""] {
2761 assert!(!uses_hfs_type_dictionary(t), "{t} should not");
2762 }
2763 }
2764
2765 #[test]
2766 fn test_type_creator_for_filename_needs_a_real_extension() {
2767 assert!(type_creator_for_filename("readme.txt").is_some());
2768 assert!(type_creator_for_filename("txt").is_none());
2771 assert!(type_creator_for_filename("README").is_none());
2772 }
2773
2774 #[test]
2775 fn test_resolve_blank_ostype_falls_through_to_dictionary() {
2776 let (t, c) =
2780 resolve_create_type_creator("readme.txt", Some([0; 4]), Some([0; 4]), None, None);
2781 assert_eq!(&t, b"TEXT");
2782 assert_eq!(&c, b"ttxt");
2783
2784 let (t, c) = resolve_create_type_creator("pic.gif", None, None, Some(""), Some(" "));
2786 assert_eq!(&t, b"GIFf");
2787 assert_eq!(&c, b"ogle");
2788 }
2789
2790 #[test]
2791 fn test_resolve_real_ostype_wins_over_dictionary() {
2792 let (t, c) = resolve_create_type_creator(
2795 "poi.txt",
2796 Some(*b"APPL"),
2797 Some([0x50, 0x6f, 0xC4, 0x50]),
2798 None,
2799 None,
2800 );
2801 assert_eq!(&t, b"APPL");
2802 assert_eq!(c, [0x50, 0x6f, 0xC4, 0x50]);
2803
2804 let (t, _) =
2807 resolve_create_type_creator("spc.txt", Some(*b" "), Some(*b" "), None, None);
2808 assert_eq!(&t, b" ");
2809 }
2810
2811 #[test]
2812 fn test_resolve_fills_only_the_missing_half() {
2813 let (t, c) = resolve_create_type_creator("pic.gif", None, Some(*b"MINE"), None, None);
2816 assert_eq!(&t, b"GIFf");
2817 assert_eq!(&c, b"MINE");
2818 }
2819
2820 #[test]
2821 fn test_resolve_unknown_extension_stays_blank() {
2822 let (t, c) = resolve_create_type_creator("thing.xyz123", None, None, None, None);
2825 assert_eq!(t, [0; 4]);
2826 assert_eq!(c, [0; 4]);
2827 }
2828
2829 #[test]
2830 fn test_resolve_text_type_keeps_trailing_space_codes() {
2831 let (t, _) = resolve_create_type_creator("x.unknown", None, None, Some("ZIP "), None);
2834 assert_eq!(&t, b"ZIP ");
2835 }
2836
2837 #[test]
2838 fn test_type_creator_for_extension_unknown() {
2839 assert!(type_creator_for_extension("xyz123").is_none());
2840 }
2841
2842 #[test]
2843 fn test_encode_fourcc() {
2844 assert_eq!(encode_fourcc("TEXT"), *b"TEXT");
2845 assert_eq!(encode_fourcc("AB"), *b"AB ");
2846 assert_eq!(encode_fourcc("SIT!"), *b"SIT!");
2847 }
2848
2849 #[test]
2850 fn test_encode_fourcc_roundtrip() {
2851 let code = encode_fourcc("JPEG");
2852 assert_eq!(&code, b"JPEG");
2853 }
2854
2855 fn make_test_btree(node_size: usize) -> Vec<u8> {
2859 let mut data = vec![0u8; node_size * 4]; data[8] = BTREE_HEADER_NODE as u8; data[9] = 0; BigEndian::write_u16(&mut data[10..12], 3); BigEndian::write_u16(&mut data[14..16], 1); BigEndian::write_u32(&mut data[16..20], 1); BigEndian::write_u32(&mut data[20..24], 0); BigEndian::write_u32(&mut data[24..28], 1); BigEndian::write_u32(&mut data[28..32], 1); BigEndian::write_u16(&mut data[32..34], node_size as u16); BigEndian::write_u16(&mut data[34..36], 256); BigEndian::write_u32(&mut data[36..40], 4); BigEndian::write_u32(&mut data[40..44], 2); let header_rec_offset = 14u16;
2879 let user_rec_offset = 120u16; let bitmap_rec_offset = 128u16; let free_offset = 256u16; BigEndian::write_u16(&mut data[node_size - 2..node_size], header_rec_offset);
2884 BigEndian::write_u16(&mut data[node_size - 4..node_size - 2], user_rec_offset);
2885 BigEndian::write_u16(&mut data[node_size - 6..node_size - 4], bitmap_rec_offset);
2886 BigEndian::write_u16(&mut data[node_size - 8..node_size - 6], free_offset);
2887
2888 data[bitmap_rec_offset as usize] = 0b11000000; let n1 = node_size;
2894 data[n1 + 8] = BTREE_LEAF_NODE as u8; data[n1 + 9] = 1; BigEndian::write_u16(&mut data[n1 + 10..n1 + 12], 0); BigEndian::write_u16(&mut data[n1 + node_size - 2..n1 + node_size], 14); data
2900 }
2901
2902 #[test]
2903 fn test_btree_node_free_space_empty_leaf() {
2904 let data = make_test_btree(512);
2905 let node = &data[512..1024]; let free = btree_node_free_space(node, 512);
2907 assert_eq!(free, 494);
2910 }
2911
2912 #[test]
2913 fn test_btree_insert_and_remove() {
2914 let mut data = make_test_btree(512);
2915 let node_size = 512;
2916
2917 let rec1 = vec![0u8; 20]; let rec2 = vec![1u8; 20];
2920 let compare = |a: &[u8], b: &[u8]| a.cmp(b);
2921
2922 let node = &mut data[node_size..node_size * 2];
2923 let pos = btree_insert_record(node, node_size, &rec1, &compare).unwrap();
2924 assert_eq!(pos, 0);
2925 assert_eq!(BigEndian::read_u16(&node[10..12]), 1); let pos2 = btree_insert_record(node, node_size, &rec2, &compare).unwrap();
2928 assert_eq!(pos2, 1); assert_eq!(BigEndian::read_u16(&node[10..12]), 2);
2930
2931 btree_remove_record(node, node_size, 0);
2933 assert_eq!(BigEndian::read_u16(&node[10..12]), 1);
2934 }
2935
2936 #[test]
2937 fn test_btree_alloc_free_node() {
2938 let mut data = make_test_btree(512);
2939 let node_size = 512;
2940
2941 let idx = btree_alloc_node(&mut data, node_size, 4).unwrap();
2943 assert_eq!(idx, 2);
2944
2945 let idx2 = btree_alloc_node(&mut data, node_size, 4).unwrap();
2947 assert_eq!(idx2, 3);
2948
2949 assert!(btree_alloc_node(&mut data, node_size, 4).is_err());
2951
2952 btree_free_node(&mut data, node_size, 2);
2954 let idx3 = btree_alloc_node(&mut data, node_size, 4).unwrap();
2955 assert_eq!(idx3, 2);
2956 }
2957
2958 #[test]
2959 fn test_btree_header_read_write() {
2960 let mut data = make_test_btree(512);
2961 let header = BTreeHeader::read(&data);
2962 assert_eq!(header.depth, 1);
2963 assert_eq!(header.root_node, 1);
2964 assert_eq!(header.total_nodes, 4);
2965 assert_eq!(header.free_nodes, 2);
2966
2967 let mut header = header;
2969 header.leaf_records = 42;
2970 header.write(&mut data);
2971
2972 let header2 = BTreeHeader::read(&data);
2973 assert_eq!(header2.leaf_records, 42);
2974 }
2975
2976 #[test]
2977 fn test_btree_split_leaf_with_insert() {
2978 let mut data = make_test_btree(512);
2979 let node_size = 512;
2980
2981 let compare = |a: &[u8], b: &[u8]| a.cmp(b);
2985 for i in 0..10u8 {
2986 let mut rec = vec![0u8; 45];
2987 rec[0] = i * 2;
2988 let node = &mut data[node_size..node_size * 2];
2989 btree_insert_record(node, node_size, &rec, &compare).unwrap();
2990 }
2991
2992 let node = &data[node_size..node_size * 2];
2993 assert_eq!(BigEndian::read_u16(&node[10..12]), 10);
2994
2995 let mut new_rec = vec![0u8; 45];
2997 new_rec[0] = 5;
2998 let mut header = BTreeHeader::read(&data);
2999 let splits = btree_split_leaf_with_insert(
3000 &mut data,
3001 node_size,
3002 1,
3003 &mut header,
3004 &new_rec,
3005 &BTreeKeyFormat::CLASSIC_CATALOG,
3006 &compare,
3007 )
3008 .unwrap();
3009 assert_eq!(splits.len(), 1, "30-byte records: 2-way split is enough");
3010 let new_idx = splits[0].0;
3011 assert_eq!(new_idx, 2);
3012
3013 let old_node = &data[node_size..node_size * 2];
3014 let new_node = &data[new_idx as usize * node_size..(new_idx as usize + 1) * node_size];
3015 let old_count = BigEndian::read_u16(&old_node[10..12]);
3016 let new_count = BigEndian::read_u16(&new_node[10..12]);
3017 assert_eq!(old_count + new_count, 11); }
3019}