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_LEAF_NODE: i8 = -1;
387pub const BTREE_INDEX_NODE: i8 = 0;
388#[allow(dead_code)]
389pub const BTREE_HEADER_NODE: i8 = 1;
390#[allow(dead_code)]
391pub const BTREE_MAP_NODE: i8 = 2;
392
393pub const HFS_CAT_MAX_KEY_LEN: u8 = 0x25;
396
397pub fn normalize_catalog_index_key(key: &[u8]) -> Vec<u8> {
405 let max = HFS_CAT_MAX_KEY_LEN as usize; let mut result = vec![0u8; 1 + max]; result[0] = HFS_CAT_MAX_KEY_LEN;
408 if key.len() > 1 {
410 let copy_len = (key.len() - 1).min(max);
411 result[1..1 + copy_len].copy_from_slice(&key[1..1 + copy_len]);
412 }
413 result
414}
415
416pub const KBT_BIG_KEYS_MASK: u32 = 0x0000_0002;
420pub const KBT_VARIABLE_INDEX_KEYS_MASK: u32 = 0x0000_0004;
425
426#[derive(Clone, Copy, Debug, PartialEq, Eq)]
438pub struct BTreeKeyFormat {
439 pub big_keys: bool,
441 pub variable_index_keys: bool,
445 pub max_key_len: u16,
448}
449
450impl BTreeKeyFormat {
451 pub const CLASSIC_CATALOG: BTreeKeyFormat = BTreeKeyFormat {
453 big_keys: false,
454 variable_index_keys: false,
455 max_key_len: HFS_CAT_MAX_KEY_LEN as u16,
456 };
457
458 pub const HFSPLUS_CATALOG: BTreeKeyFormat = BTreeKeyFormat {
461 big_keys: true,
462 variable_index_keys: true,
463 max_key_len: 516,
464 };
465
466 pub const HFSPLUS_EXTENTS: BTreeKeyFormat = BTreeKeyFormat {
469 big_keys: true,
470 variable_index_keys: false,
471 max_key_len: 10,
472 };
473
474 pub const HFSPLUS_ATTRIBUTES: BTreeKeyFormat = BTreeKeyFormat {
477 big_keys: true,
478 variable_index_keys: true,
479 max_key_len: 264,
480 };
481
482 pub const CLASSIC_EXTENTS: BTreeKeyFormat = BTreeKeyFormat {
484 big_keys: false,
485 variable_index_keys: false,
486 max_key_len: 7,
487 };
488
489 pub fn from_header(attributes: u32, max_key_len: u16) -> Self {
495 BTreeKeyFormat {
496 big_keys: attributes & KBT_BIG_KEYS_MASK != 0,
497 variable_index_keys: attributes & KBT_VARIABLE_INDEX_KEYS_MASK != 0,
498 max_key_len,
499 }
500 }
501
502 pub fn header_attributes(&self) -> u32 {
504 let mut a = 0;
505 if self.big_keys {
506 a |= KBT_BIG_KEYS_MASK;
507 }
508 if self.variable_index_keys {
509 a |= KBT_VARIABLE_INDEX_KEYS_MASK;
510 }
511 a
512 }
513
514 #[inline]
516 pub fn len_prefix(&self) -> usize {
517 if self.big_keys {
518 2
519 } else {
520 1
521 }
522 }
523
524 #[inline]
526 pub fn key_len(&self, record: &[u8]) -> usize {
527 if self.big_keys {
528 if record.len() < 2 {
529 0
530 } else {
531 BigEndian::read_u16(&record[0..2]) as usize
532 }
533 } else if record.is_empty() {
534 0
535 } else {
536 record[0] as usize
537 }
538 }
539
540 pub fn key_portion(&self, record: &[u8]) -> Vec<u8> {
544 let end = (self.len_prefix() + self.key_len(record)).min(record.len());
545 record[..end].to_vec()
546 }
547
548 pub fn make_index_key(&self, first_key: &[u8]) -> Vec<u8> {
558 if !self.big_keys {
559 if self.max_key_len == HFS_CAT_MAX_KEY_LEN as u16 {
560 return normalize_catalog_index_key(first_key);
561 }
562 let max = self.max_key_len as usize;
564 let mut result = vec![0u8; 1 + max];
565 result[0] = max as u8;
566 let copy = first_key.len().saturating_sub(1).min(max);
567 result[1..1 + copy].copy_from_slice(&first_key[1..1 + copy]);
568 return result;
569 }
570 if self.variable_index_keys {
571 return first_key.to_vec();
572 }
573 let max = self.max_key_len as usize;
574 let copy = self.key_len(first_key).min(max);
575 let mut result = vec![0u8; 2 + max];
576 BigEndian::write_u16(&mut result[0..2], max as u16);
577 if first_key.len() >= 2 + copy {
578 result[2..2 + copy].copy_from_slice(&first_key[2..2 + copy]);
579 }
580 result
581 }
582}
583
584pub struct BTreeHeader {
588 pub depth: u16,
589 pub root_node: u32,
590 pub leaf_records: u32,
591 pub first_leaf_node: u32,
592 pub last_leaf_node: u32,
593 pub node_size: u16,
594 pub total_nodes: u32,
595 pub free_nodes: u32,
596}
597
598impl BTreeHeader {
599 pub fn read(catalog_data: &[u8]) -> Self {
600 let d = &catalog_data[14..];
602 BTreeHeader {
603 depth: BigEndian::read_u16(&d[0..2]),
604 root_node: BigEndian::read_u32(&d[2..6]),
605 leaf_records: BigEndian::read_u32(&d[6..10]),
606 first_leaf_node: BigEndian::read_u32(&d[10..14]),
607 last_leaf_node: BigEndian::read_u32(&d[14..18]),
608 node_size: BigEndian::read_u16(&d[18..20]),
609 total_nodes: BigEndian::read_u32(&d[22..26]),
610 free_nodes: BigEndian::read_u32(&d[26..30]),
611 }
612 }
613
614 pub fn write(&self, catalog_data: &mut [u8]) {
615 let d = &mut catalog_data[14..];
616 BigEndian::write_u16(&mut d[0..2], self.depth);
617 BigEndian::write_u32(&mut d[2..6], self.root_node);
618 BigEndian::write_u32(&mut d[6..10], self.leaf_records);
619 BigEndian::write_u32(&mut d[10..14], self.first_leaf_node);
620 BigEndian::write_u32(&mut d[14..18], self.last_leaf_node);
621 BigEndian::write_u32(&mut d[22..26], self.total_nodes);
622 BigEndian::write_u32(&mut d[26..30], self.free_nodes);
623 }
624}
625
626pub fn btree_node_free_space(node: &[u8], node_size: usize) -> usize {
631 let num_records = BigEndian::read_u16(&node[10..12]) as usize;
632 if num_records == 0 {
633 let data_end = 14;
636 let table_start = node_size - 2 * (num_records + 1);
637 if table_start <= data_end + 2 {
638 return 0;
639 }
640 return table_start - data_end - 2;
641 }
642 let free_offset_pos = node_size - 2 * (num_records + 1);
644 let last_rec_end = BigEndian::read_u16(&node[free_offset_pos..free_offset_pos + 2]) as usize;
645 let table_start = free_offset_pos;
648 if table_start <= last_rec_end + 2 {
649 return 0;
650 }
651 table_start - last_rec_end - 2
652}
653
654fn index_child_ptr(node: &[u8], end: usize) -> u32 {
657 if end < 4 || end > node.len() {
658 return 0;
659 }
660 BigEndian::read_u32(&node[end - 4..end])
661}
662
663pub fn btree_record_range(node: &[u8], node_size: usize, rec_idx: usize) -> (usize, usize) {
666 let node_size = node_size.min(node.len());
669 let (Some(offset_pos), Some(next_pos)) = (
670 node_size.checked_sub(2 * (rec_idx + 1)),
671 node_size.checked_sub(2 * (rec_idx + 2)),
672 ) else {
673 return (0, 0);
674 };
675 let start = (BigEndian::read_u16(&node[offset_pos..offset_pos + 2]) as usize).min(node_size);
676 let end = (BigEndian::read_u16(&node[next_pos..next_pos + 2]) as usize).min(node_size);
677 (start, start.max(end))
678}
679
680pub fn walk_leaf_records<T, F>(
689 catalog_data: &[u8],
690 first_leaf: u32,
691 node_size: usize,
692 mut visit: F,
693) -> Option<T>
694where
695 F: FnMut(u32, usize, usize, &[u8]) -> Option<T>,
696{
697 if node_size == 0 || catalog_data.len() < node_size {
698 return None;
699 }
700 let mut node_idx = first_leaf;
701 let mut visited = std::collections::HashSet::new();
702 while node_idx != 0 {
703 if !visited.insert(node_idx) {
704 return None;
705 }
706 let offset = (node_idx as usize).checked_mul(node_size)?;
707 if offset + node_size > catalog_data.len() {
708 return None;
709 }
710 let node = &catalog_data[offset..offset + node_size];
711 let next = BigEndian::read_u32(&node[0..4]);
712 let kind = node[8] as i8;
713 if kind != BTREE_LEAF_NODE {
714 node_idx = next;
715 continue;
716 }
717 let num_records = BigEndian::read_u16(&node[10..12]) as usize;
718 for i in 0..num_records {
719 let (rec_start, rec_end) = btree_record_range(node, node_size, i);
720 if rec_start >= rec_end || rec_end > node_size {
721 continue;
722 }
723 if let Some(t) = visit(node_idx, i, offset + rec_start, &node[rec_start..rec_end]) {
724 return Some(t);
725 }
726 }
727 node_idx = next;
728 }
729 None
730}
731
732pub fn btree_insert_record<F>(
739 node: &mut [u8],
740 node_size: usize,
741 key_record_bytes: &[u8],
742 compare_fn: &F,
743) -> Result<usize, FilesystemError>
744where
745 F: Fn(&[u8], &[u8]) -> Ordering,
746{
747 let num_records = BigEndian::read_u16(&node[10..12]) as usize;
748 let total_needed = key_record_bytes.len() + 2; let free = btree_node_free_space(node, node_size);
751 if free < total_needed {
752 return Err(FilesystemError::InvalidData("B-tree node full".into()));
753 }
754
755 let mut records: Vec<Vec<u8>> = Vec::with_capacity(num_records + 1);
757 for i in 0..num_records {
758 let (rec_start, rec_end) = btree_record_range(node, node_size, i);
759 if rec_start < rec_end && rec_end <= node_size {
760 records.push(node[rec_start..rec_end].to_vec());
761 }
762 }
763
764 let mut insert_pos = records.len();
766 for (i, rec) in records.iter().enumerate() {
767 if compare_fn(rec, key_record_bytes) == Ordering::Greater {
768 insert_pos = i;
769 break;
770 }
771 }
772
773 records.insert(insert_pos, key_record_bytes.to_vec());
775
776 let mut write_pos = 14usize;
779 for (i, rec) in records.iter().enumerate() {
780 node[write_pos..write_pos + rec.len()].copy_from_slice(rec);
781 let opos = node_size - 2 * (i + 1);
782 BigEndian::write_u16(&mut node[opos..opos + 2], write_pos as u16);
783 write_pos += rec.len();
784 }
785
786 let fpos = node_size - 2 * (records.len() + 1);
788 BigEndian::write_u16(&mut node[fpos..fpos + 2], write_pos as u16);
789
790 let offset_table_start = node_size - 2 * (records.len() + 1);
792 if write_pos < offset_table_start {
793 node[write_pos..offset_table_start].fill(0);
794 }
795
796 BigEndian::write_u16(&mut node[10..12], records.len() as u16);
798
799 Ok(insert_pos)
800}
801
802pub fn btree_remove_record(node: &mut [u8], node_size: usize, rec_idx: usize) {
809 let num_records = BigEndian::read_u16(&node[10..12]) as usize;
810 if rec_idx >= num_records {
811 return;
812 }
813
814 let mut records: Vec<Vec<u8>> = Vec::with_capacity(num_records - 1);
816 for i in 0..num_records {
817 if i == rec_idx {
818 continue;
819 }
820 let (rec_start, rec_end) = btree_record_range(node, node_size, i);
821 if rec_start < rec_end && rec_end <= node_size {
822 records.push(node[rec_start..rec_end].to_vec());
823 }
824 }
825
826 let mut write_pos = 14usize;
828 for (i, rec) in records.iter().enumerate() {
829 node[write_pos..write_pos + rec.len()].copy_from_slice(rec);
830 let opos = node_size - 2 * (i + 1);
831 BigEndian::write_u16(&mut node[opos..opos + 2], write_pos as u16);
832 write_pos += rec.len();
833 }
834
835 let fpos = node_size - 2 * (records.len() + 1);
837 BigEndian::write_u16(&mut node[fpos..fpos + 2], write_pos as u16);
838
839 let offset_table_start = node_size - 2 * (records.len() + 1);
841 if write_pos < offset_table_start {
842 node[write_pos..offset_table_start].fill(0);
843 }
844
845 let clear_pos = node_size - 2 * (num_records + 1);
847 BigEndian::write_u16(&mut node[clear_pos..clear_pos + 2], 0);
848
849 BigEndian::write_u16(&mut node[10..12], records.len() as u16);
851}
852
853pub fn btree_node_bitmap_range(catalog_data: &[u8], node_size: usize) -> (usize, usize) {
871 let offset_pos = node_size - 2 * 3; let bitmap_start = BigEndian::read_u16(&catalog_data[offset_pos..offset_pos + 2]) as usize;
875 let next_pos = node_size - 2 * 4; let bitmap_end = BigEndian::read_u16(&catalog_data[next_pos..next_pos + 2]) as usize;
877 (bitmap_start, bitmap_end.saturating_sub(bitmap_start))
878}
879
880pub fn map_node_bitmap_bytes(node_size: usize) -> usize {
882 node_size.saturating_sub(18)
884}
885
886pub fn map_nodes_required(total_nodes: u32, node_size: usize) -> u32 {
893 let header_cap = ((node_size.saturating_sub(256)) * 8) as u32;
894 if total_nodes <= header_cap {
895 return 0;
896 }
897 let per_map = (map_node_bitmap_bytes(node_size) * 8) as u32;
898 if per_map == 0 {
899 return 0;
900 }
901 (total_nodes - header_cap).div_ceil(per_map)
902}
903
904#[derive(Debug, Clone, Copy)]
906pub struct BitmapSegment {
907 pub byte_off: usize,
909 pub len: usize,
911 pub base_bit: u32,
914}
915
916pub fn btree_bitmap_segments(catalog_data: &[u8], node_size: usize) -> Vec<BitmapSegment> {
922 let mut segs = Vec::with_capacity(1);
923 if catalog_data.len() < node_size {
924 return segs;
925 }
926
927 let (hdr_off, hdr_len) = btree_node_bitmap_range(catalog_data, node_size);
928 segs.push(BitmapSegment {
929 byte_off: hdr_off,
930 len: hdr_len,
931 base_bit: 0,
932 });
933 let mut next_bit = (hdr_len as u32) * 8;
934
935 let mut next_node = BigEndian::read_u32(&catalog_data[0..4]);
938 let mut visited: std::collections::HashSet<u32> = std::collections::HashSet::with_capacity(8);
939
940 while next_node != 0 {
941 if !visited.insert(next_node) {
942 break;
944 }
945 let off = next_node as usize * node_size;
946 if off + node_size > catalog_data.len() {
947 break;
948 }
949 let kind = catalog_data[off + 8] as i8;
951 let num_records = BigEndian::read_u16(&catalog_data[off + 10..off + 12]);
952 if kind != 2 || num_records < 1 {
953 break;
954 }
955 let rec0_pos = off + node_size - 2;
957 let rec0_off = BigEndian::read_u16(&catalog_data[rec0_pos..rec0_pos + 2]) as usize;
958 let free_pos = off + node_size - 4;
960 let free_off = BigEndian::read_u16(&catalog_data[free_pos..free_pos + 2]) as usize;
961 if rec0_off < 14 || free_off <= rec0_off || free_off > node_size {
962 break;
963 }
964 let len = free_off - rec0_off;
965 segs.push(BitmapSegment {
966 byte_off: off + rec0_off,
967 len,
968 base_bit: next_bit,
969 });
970 next_bit += (len as u32) * 8;
971 next_node = BigEndian::read_u32(&catalog_data[off..off + 4]);
973 }
974
975 segs
976}
977
978fn locate_bit_in_segments(segs: &[BitmapSegment], global_bit: u32) -> Option<(usize, u32)> {
979 for (i, seg) in segs.iter().enumerate() {
980 let cap = (seg.len as u32) * 8;
981 if global_bit >= seg.base_bit && global_bit < seg.base_bit + cap {
982 return Some((i, global_bit - seg.base_bit));
983 }
984 }
985 None
986}
987
988pub fn btree_bitmap_test(catalog_data: &[u8], node_size: usize, bit: u32) -> bool {
990 let segs = btree_bitmap_segments(catalog_data, node_size);
991 let Some((idx, local)) = locate_bit_in_segments(&segs, bit) else {
992 return false;
993 };
994 let seg = segs[idx];
995 let slice = &catalog_data[seg.byte_off..seg.byte_off + seg.len];
996 bitmap_test_bit_be(slice, local)
997}
998
999pub fn btree_alloc_node(
1003 catalog_data: &mut [u8],
1004 node_size: usize,
1005 total_nodes: u32,
1006) -> Result<u32, FilesystemError> {
1007 let segs = btree_bitmap_segments(catalog_data, node_size);
1008 for seg in &segs {
1009 let bitmap = &catalog_data[seg.byte_off..seg.byte_off + seg.len];
1010 let cap = (seg.len as u32) * 8;
1011 let upper = total_nodes.saturating_sub(seg.base_bit).min(cap);
1012 for local in 0..upper {
1013 if !bitmap_test_bit_be(bitmap, local) {
1014 let global = seg.base_bit + local;
1015 bitmap_set_bit_be(
1016 &mut catalog_data[seg.byte_off..seg.byte_off + seg.len],
1017 local,
1018 );
1019 return Ok(global);
1020 }
1021 }
1022 }
1023 Err(FilesystemError::DiskFull("no free B-tree nodes".into()))
1024}
1025
1026pub fn btree_detach_freed_node(data: &mut [u8], node_size: usize, freed: u32) -> u32 {
1029 let mut header = BTreeHeader::read(data);
1030 let max_nodes = (data.len() / node_size) as u32;
1031 let mut freed_count = 0u32;
1032 if header.root_node == freed {
1033 header.root_node = 0;
1035 header.depth = 0;
1036 header.write(data);
1037 return 0;
1038 }
1039 let mut target = freed;
1040 loop {
1041 let mut found = None;
1042 'scan: for idx in 1..max_nodes {
1043 if idx == target || !btree_bitmap_test(data, node_size, idx) {
1044 continue;
1045 }
1046 let off = idx as usize * node_size;
1047 let node = &data[off..off + node_size];
1048 if node[8] as i8 != BTREE_INDEX_NODE {
1049 continue;
1050 }
1051 let n = BigEndian::read_u16(&node[10..12]) as usize;
1052 for r in 0..n {
1053 let (_, end) = btree_record_range(node, node_size, r);
1054 if index_child_ptr(node, end) == target {
1055 found = Some((idx, r));
1056 break 'scan;
1057 }
1058 }
1059 }
1060 let Some((idx, r)) = found else {
1061 break;
1062 };
1063 let off = idx as usize * node_size;
1064 btree_remove_record(&mut data[off..off + node_size], node_size, r);
1065 if BigEndian::read_u16(&data[off + 10..off + 12]) > 0 {
1066 break;
1067 }
1068 btree_free_node(data, node_size, idx);
1069 freed_count += 1;
1070 if header.root_node == idx {
1071 header.root_node = 0;
1072 header.depth = 0;
1073 header.free_nodes += freed_count;
1074 header.write(data);
1075 return freed_count;
1076 }
1077 target = idx;
1078 }
1079 while header.depth > 1 && header.root_node != 0 {
1081 let off = header.root_node as usize * node_size;
1082 let node = &data[off..off + node_size];
1083 if node[8] as i8 != BTREE_INDEX_NODE || BigEndian::read_u16(&node[10..12]) != 1 {
1084 break;
1085 }
1086 let (_, end) = btree_record_range(node, node_size, 0);
1087 let child = index_child_ptr(node, end);
1088 if child == 0 {
1089 break;
1090 }
1091 btree_free_node(data, node_size, header.root_node);
1092 freed_count += 1;
1093 header.root_node = child;
1094 header.depth -= 1;
1095 }
1096 header.free_nodes += freed_count;
1097 header.write(data);
1098 freed_count
1099}
1100
1101pub fn btree_find_parent(data: &[u8], node_size: usize, child: u32) -> Option<(u32, usize)> {
1104 let max_nodes = (data.len() / node_size) as u32;
1105 for idx in 1..max_nodes {
1106 if idx == child || !btree_bitmap_test(data, node_size, idx) {
1107 continue;
1108 }
1109 let off = idx as usize * node_size;
1110 let node = &data[off..off + node_size];
1111 if node[8] as i8 != BTREE_INDEX_NODE {
1112 continue;
1113 }
1114 let n = BigEndian::read_u16(&node[10..12]) as usize;
1115 for r in 0..n {
1116 let (_, end) = btree_record_range(node, node_size, r);
1117 if index_child_ptr(node, end) == child {
1118 return Some((idx, r));
1119 }
1120 }
1121 }
1122 None
1123}
1124
1125fn btree_parent_chain(data: &[u8], node_size: usize, node: u32) -> Vec<(u32, u32)> {
1128 let mut chain = Vec::new();
1129 let mut cur = node;
1130 while cur != 0 && chain.len() < 64 {
1131 let parent = btree_find_parent(data, node_size, cur).map_or(0, |(p, _)| p);
1132 chain.push((cur, parent));
1133 cur = parent;
1134 }
1135 chain.reverse();
1136 chain
1137}
1138
1139pub fn btree_refresh_index_keys<F>(
1145 data: &mut [u8],
1146 node_size: usize,
1147 mut node_idx: u32,
1148 kf: &BTreeKeyFormat,
1149 cmp: &F,
1150) -> Result<(), FilesystemError>
1151where
1152 F: Fn(&[u8], &[u8]) -> Ordering,
1153{
1154 let max_nodes = (data.len() / node_size) as u32;
1155 for _ in 0..64 {
1157 if node_idx == 0 || node_idx >= max_nodes {
1158 return Ok(());
1159 }
1160 let first_key = {
1161 let off = node_idx as usize * node_size;
1162 let node = &data[off..off + node_size];
1163 if BigEndian::read_u16(&node[10..12]) == 0 {
1164 return Ok(());
1165 }
1166 let (s, e) = btree_record_range(node, node_size, 0);
1167 kf.key_portion(&node[s..e])
1168 };
1169 let Some((parent, rec_idx)) = btree_find_parent(data, node_size, node_idx) else {
1170 return Ok(());
1171 };
1172 let mut wanted = kf.make_index_key(&first_key);
1173 let mut ptr = [0u8; 4];
1174 BigEndian::write_u32(&mut ptr, node_idx);
1175 wanted.extend_from_slice(&ptr);
1176
1177 let parent_off = parent as usize * node_size;
1178 let replaced = {
1179 let pnode = &mut data[parent_off..parent_off + node_size];
1180 let (s, e) = btree_record_range(pnode, node_size, rec_idx);
1181 if pnode[s..e] == wanted[..] {
1182 return Ok(());
1183 }
1184 btree_remove_record(pnode, node_size, rec_idx);
1185 btree_insert_record(pnode, node_size, &wanted, cmp).is_ok()
1186 };
1187 if !replaced {
1188 let mut header = BTreeHeader::read(data);
1190 let chain = btree_parent_chain(data, node_size, parent);
1191 btree_insert_into_index(
1192 data,
1193 node_size,
1194 parent,
1195 node_idx,
1196 &first_key,
1197 &mut header,
1198 kf,
1199 cmp,
1200 &chain,
1201 )?;
1202 header.write(data);
1203 }
1204 node_idx = parent;
1205 }
1206 Ok(())
1207}
1208
1209pub fn btree_detach_freed_node_and_refresh<F>(
1212 data: &mut [u8],
1213 node_size: usize,
1214 freed: u32,
1215 kf: &BTreeKeyFormat,
1216 cmp: &F,
1217) -> Result<u32, FilesystemError>
1218where
1219 F: Fn(&[u8], &[u8]) -> Ordering,
1220{
1221 let parent = btree_find_parent(data, node_size, freed);
1222 let freed_count = btree_detach_freed_node(data, node_size, freed);
1223 if let Some((parent, 0)) = parent {
1224 if btree_bitmap_test(data, node_size, parent) {
1225 btree_refresh_index_keys(data, node_size, parent, kf, cmp)?;
1226 }
1227 }
1228 Ok(freed_count)
1229}
1230
1231pub fn btree_stale_index_keys<F>(
1235 data: &[u8],
1236 node_size: usize,
1237 kf: &BTreeKeyFormat,
1238 cmp: &F,
1239) -> Vec<(u32, usize, u32, Ordering)>
1240where
1241 F: Fn(&[u8], &[u8]) -> Ordering,
1242{
1243 let mut stale = Vec::new();
1244 if node_size == 0 || data.len() < node_size {
1245 return stale;
1246 }
1247 let max_nodes = (data.len() / node_size) as u32;
1248 for idx in 1..max_nodes {
1249 if !btree_bitmap_test(data, node_size, idx) {
1250 continue;
1251 }
1252 let off = idx as usize * node_size;
1253 let node = &data[off..off + node_size];
1254 if node[8] as i8 != BTREE_INDEX_NODE {
1255 continue;
1256 }
1257 let n = BigEndian::read_u16(&node[10..12]) as usize;
1258 for r in 0..n {
1259 let (s, e) = btree_record_range(node, node_size, r);
1260 if e < s + 4 || e > node_size {
1261 continue;
1262 }
1263 let child = index_child_ptr(node, e);
1264 if child == 0 || child >= max_nodes || !btree_bitmap_test(data, node_size, child) {
1265 continue;
1266 }
1267 let coff = child as usize * node_size;
1268 let cnode = &data[coff..coff + node_size];
1269 if BigEndian::read_u16(&cnode[10..12]) == 0 {
1270 continue;
1271 }
1272 let (cs, ce) = btree_record_range(cnode, node_size, 0);
1273 let wanted = kf.make_index_key(&kf.key_portion(&cnode[cs..ce]));
1274 let order = cmp(&node[s..e - 4], &wanted);
1275 if order != Ordering::Equal {
1276 stale.push((idx, r, child, order));
1277 }
1278 }
1279 }
1280 stale
1281}
1282
1283pub fn btree_retire_empty_leaf<F>(
1288 data: &mut [u8],
1289 node_size: usize,
1290 node_idx: u32,
1291 kf: &BTreeKeyFormat,
1292 cmp: &F,
1293) -> Result<u32, FilesystemError>
1294where
1295 F: Fn(&[u8], &[u8]) -> Ordering,
1296{
1297 let off = node_idx as usize * node_size;
1298 if off + node_size > data.len() || BigEndian::read_u16(&data[off + 10..off + 12]) != 0 {
1299 return Ok(0);
1300 }
1301 let flink = BigEndian::read_u32(&data[off..off + 4]);
1302 let blink = BigEndian::read_u32(&data[off + 4..off + 8]);
1303 if blink != 0 {
1304 let b = blink as usize * node_size;
1305 BigEndian::write_u32(&mut data[b..b + 4], flink);
1306 }
1307 if flink != 0 {
1308 let f = flink as usize * node_size;
1309 BigEndian::write_u32(&mut data[f + 4..f + 8], blink);
1310 }
1311 let mut h = BTreeHeader::read(data);
1312 if h.first_leaf_node == node_idx {
1313 h.first_leaf_node = flink;
1314 }
1315 if h.last_leaf_node == node_idx {
1316 h.last_leaf_node = blink;
1317 }
1318 h.free_nodes += 1;
1319 h.write(data);
1320 btree_free_node(data, node_size, node_idx);
1321 let freed = btree_detach_freed_node_and_refresh(data, node_size, node_idx, kf, cmp)?;
1322 Ok(freed + 1)
1323}
1324
1325pub fn btree_lonely_empty_leaves(data: &[u8], node_size: usize) -> Vec<u32> {
1328 let mut lonely = Vec::new();
1329 if node_size == 0 || data.len() < node_size {
1330 return lonely;
1331 }
1332 let max_nodes = (data.len() / node_size) as u32;
1333 for idx in 1..max_nodes {
1334 if !btree_bitmap_test(data, node_size, idx) {
1335 continue;
1336 }
1337 let off = idx as usize * node_size;
1338 let node = &data[off..off + node_size];
1339 if node[8] as i8 == BTREE_LEAF_NODE
1340 && BigEndian::read_u16(&node[10..12]) == 0
1341 && BigEndian::read_u32(&node[0..4]) == 0
1342 && BigEndian::read_u32(&node[4..8]) == 0
1343 {
1344 lonely.push(idx);
1345 }
1346 }
1347 lonely
1348}
1349
1350pub fn btree_free_node(catalog_data: &mut [u8], node_size: usize, node_idx: u32) {
1353 let segs = btree_bitmap_segments(catalog_data, node_size);
1354 let Some((idx, local)) = locate_bit_in_segments(&segs, node_idx) else {
1355 return;
1356 };
1357 let seg = segs[idx];
1358 bitmap_clear_bit_be(
1359 &mut catalog_data[seg.byte_off..seg.byte_off + seg.len],
1360 local,
1361 );
1362 let off = node_idx as usize * node_size;
1363 if node_idx != 0 && off + node_size <= catalog_data.len() {
1364 catalog_data[off..off + node_size].fill(0);
1365 }
1366}
1367
1368pub fn btree_unerased_free_nodes(data: &[u8], node_size: usize) -> Vec<u32> {
1371 let mut dirty = Vec::new();
1372 if node_size == 0 || data.len() < node_size {
1373 return dirty;
1374 }
1375 let max_nodes = (data.len() / node_size) as u32;
1376 for idx in 1..max_nodes {
1377 if btree_bitmap_test(data, node_size, idx) {
1378 continue;
1379 }
1380 let off = idx as usize * node_size;
1381 if data[off..off + node_size].iter().any(|&b| b != 0) {
1382 dirty.push(idx);
1383 }
1384 }
1385 dirty
1386}
1387
1388pub fn btree_erase_free_nodes(data: &mut [u8], node_size: usize) -> usize {
1390 let dirty = btree_unerased_free_nodes(data, node_size);
1391 for &idx in &dirty {
1392 let off = idx as usize * node_size;
1393 data[off..off + node_size].fill(0);
1394 }
1395 dirty.len()
1396}
1397
1398pub fn btree_bitmap_clear(catalog_data: &mut [u8], node_size: usize, node_idx: u32) {
1400 let segs = btree_bitmap_segments(catalog_data, node_size);
1401 let Some((idx, local)) = locate_bit_in_segments(&segs, node_idx) else {
1402 return;
1403 };
1404 let seg = segs[idx];
1405 bitmap_clear_bit_be(
1406 &mut catalog_data[seg.byte_off..seg.byte_off + seg.len],
1407 local,
1408 );
1409}
1410
1411pub fn btree_bitmap_set(catalog_data: &mut [u8], node_size: usize, node_idx: u32) {
1414 let segs = btree_bitmap_segments(catalog_data, node_size);
1415 let Some((idx, local)) = locate_bit_in_segments(&segs, node_idx) else {
1416 return;
1417 };
1418 let seg = segs[idx];
1419 bitmap_set_bit_be(
1420 &mut catalog_data[seg.byte_off..seg.byte_off + seg.len],
1421 local,
1422 );
1423}
1424
1425pub fn btree_map_chain_tail(buf: &[u8], node_size: usize) -> u32 {
1429 let mut next = BigEndian::read_u32(&buf[0..4]); let mut last = 0u32;
1431 let mut guard = 0;
1432 while next != 0 && guard < 1_000_000 {
1433 last = next;
1434 let off = next as usize * node_size;
1435 if off + 4 > buf.len() {
1436 break;
1437 }
1438 next = BigEndian::read_u32(&buf[off..off + 4]);
1439 guard += 1;
1440 }
1441 last
1442}
1443
1444pub fn btree_grow_blocks(
1448 total_nodes: u32,
1449 add_slots: u32,
1450 node_size: usize,
1451 block_size: u32,
1452) -> u32 {
1453 let node_bytes = node_size as u64;
1454 let cur_blocks = (total_nodes as u64 * node_bytes).div_ceil(block_size as u64);
1455 let new_blocks = ((total_nodes + add_slots) as u64 * node_bytes).div_ceil(block_size as u64);
1456 (new_blocks - cur_blocks) as u32
1457}
1458
1459pub fn init_map_node(
1462 catalog_data: &mut [u8],
1463 node_size: usize,
1464 node_idx: u32,
1465 prev_link: u32,
1466 next_link: u32,
1467) {
1468 let off = node_idx as usize * node_size;
1469 let node = &mut catalog_data[off..off + node_size];
1470 node.fill(0);
1472 BigEndian::write_u32(&mut node[0..4], next_link);
1474 BigEndian::write_u32(&mut node[4..8], prev_link);
1475 node[8] = 2; node[9] = 0;
1477 BigEndian::write_u16(&mut node[10..12], 1);
1478 let bitmap_bytes = map_node_bitmap_bytes(node_size) as u16;
1480 let free_off = 14u16 + bitmap_bytes;
1481 BigEndian::write_u16(&mut node[node_size - 2..node_size], 14);
1483 BigEndian::write_u16(&mut node[node_size - 4..node_size - 2], free_off);
1484}
1485
1486pub(crate) fn init_node(
1493 catalog_data: &mut [u8],
1494 node_size: usize,
1495 node_idx: u32,
1496 kind: i8,
1497 height: u8,
1498) {
1499 let offset = node_idx as usize * node_size;
1500 let node = &mut catalog_data[offset..offset + node_size];
1501 node.fill(0);
1503 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);
1509}
1510
1511pub fn btree_split_leaf_with_insert<F>(
1527 catalog_data: &mut [u8],
1528 node_size: usize,
1529 node_idx: u32,
1530 header: &mut BTreeHeader,
1531 new_record: &[u8],
1532 kf: &BTreeKeyFormat,
1533 compare_fn: &F,
1534) -> Result<Vec<(u32, Vec<u8>)>, FilesystemError>
1535where
1536 F: Fn(&[u8], &[u8]) -> Ordering,
1537{
1538 let old_offset = node_idx as usize * node_size;
1539 let old_num = BigEndian::read_u16(&catalog_data[old_offset + 10..old_offset + 12]) as usize;
1540 let old_height = catalog_data[old_offset + 9];
1541
1542 let mut records: Vec<Vec<u8>> = Vec::with_capacity(old_num + 1);
1544 for i in 0..old_num {
1545 let (s, e) = btree_record_range(
1546 &catalog_data[old_offset..old_offset + node_size],
1547 node_size,
1548 i,
1549 );
1550 records.push(catalog_data[old_offset + s..old_offset + e].to_vec());
1551 }
1552 let mut insert_pos = records.len();
1553 for (i, rec) in records.iter().enumerate() {
1554 if compare_fn(rec, new_record) == Ordering::Greater {
1555 insert_pos = i;
1556 break;
1557 }
1558 }
1559 records.insert(insert_pos, new_record.to_vec());
1560
1561 let payload = node_size.saturating_sub(16);
1566 for rec in &records {
1567 if rec.len() + 2 > payload {
1568 return Err(FilesystemError::InvalidData(format!(
1569 "btree_split_leaf_with_insert: record of {} bytes exceeds node payload {}",
1570 rec.len(),
1571 payload
1572 )));
1573 }
1574 }
1575
1576 let mut cuts: Vec<usize> = Vec::new();
1579 let mut cur_bytes: usize = 0;
1580 for (i, rec) in records.iter().enumerate() {
1581 let cost = rec.len() + 2;
1582 if i > 0 && cur_bytes + cost > payload {
1583 cuts.push(i);
1584 cur_bytes = 0;
1585 }
1586 cur_bytes += cost;
1587 }
1588 if cuts.is_empty() {
1594 cuts.push(
1595 (records.len() / 2)
1596 .max(1)
1597 .min(records.len().saturating_sub(1)),
1598 );
1599 }
1600
1601 let is_append = insert_pos == old_num;
1611 if cuts.len() == 1 && !is_append {
1612 let total: usize = records.iter().map(|r| r.len() + 2).sum();
1613 let mut prefix = 0usize;
1614 let mut best_k = cuts[0];
1615 let mut best_diff = usize::MAX;
1616 for k in 1..records.len() {
1617 prefix += records[k - 1].len() + 2;
1618 let right = total - prefix;
1619 if prefix <= payload && right <= payload {
1620 let diff = prefix.abs_diff(total / 2);
1621 if diff < best_diff {
1622 best_diff = diff;
1623 best_k = k;
1624 }
1625 }
1626 }
1627 cuts[0] = best_k;
1628 }
1629
1630 let mut chunks: Vec<Vec<Vec<u8>>> = Vec::with_capacity(cuts.len() + 1);
1632 let mut prev = 0usize;
1633 for &cut in &cuts {
1634 chunks.push(records.drain(0..cut - prev).collect());
1635 prev = cut;
1636 }
1637 chunks.push(records);
1638
1639 let new_count = chunks.len() - 1;
1641 let mut new_indices: Vec<u32> = Vec::with_capacity(new_count);
1642 for _ in 0..new_count {
1643 let idx = btree_alloc_node(catalog_data, node_size, header.total_nodes)?;
1644 header.free_nodes -= 1;
1645 init_node(catalog_data, node_size, idx, BTREE_LEAF_NODE, old_height);
1646 new_indices.push(idx);
1647 }
1648
1649 let write_chunk = |data: &mut [u8], target_idx: u32, chunk: &[Vec<u8>]| {
1653 let off = target_idx as usize * node_size;
1654 let node = &mut data[off..off + node_size];
1655 BigEndian::write_u16(&mut node[10..12], 0);
1656 BigEndian::write_u16(&mut node[node_size - 2..node_size], 14);
1657 let mut write_pos = 14usize;
1658 for (i, rec) in chunk.iter().enumerate() {
1659 node[write_pos..write_pos + rec.len()].copy_from_slice(rec);
1660 let opos = node_size - 2 * (i + 1);
1661 BigEndian::write_u16(&mut node[opos..opos + 2], write_pos as u16);
1662 write_pos += rec.len();
1663 }
1664 let fpos = node_size - 2 * (chunk.len() + 1);
1665 BigEndian::write_u16(&mut node[fpos..fpos + 2], write_pos as u16);
1666 BigEndian::write_u16(&mut node[10..12], chunk.len() as u16);
1667 };
1668
1669 write_chunk(catalog_data, node_idx, &chunks[0]);
1671 for (i, idx) in new_indices.iter().enumerate() {
1672 write_chunk(catalog_data, *idx, &chunks[i + 1]);
1673 }
1674
1675 let old_next = BigEndian::read_u32(&catalog_data[old_offset..old_offset + 4]);
1677 let mut prev_idx = node_idx;
1678 for &idx in &new_indices {
1679 let prev_off = prev_idx as usize * node_size;
1680 let cur_off = idx as usize * node_size;
1681 BigEndian::write_u32(&mut catalog_data[prev_off..prev_off + 4], idx);
1682 BigEndian::write_u32(&mut catalog_data[cur_off + 4..cur_off + 8], prev_idx);
1683 prev_idx = idx;
1684 }
1685 let last_idx = *new_indices.last().expect("at least one new node");
1687 let last_off = last_idx as usize * node_size;
1688 BigEndian::write_u32(&mut catalog_data[last_off..last_off + 4], old_next);
1689 if old_next != 0 {
1690 let next_offset = old_next as usize * node_size;
1691 BigEndian::write_u32(
1692 &mut catalog_data[next_offset + 4..next_offset + 8],
1693 last_idx,
1694 );
1695 }
1696 if header.last_leaf_node == node_idx {
1697 header.last_leaf_node = last_idx;
1698 }
1699
1700 let mut results: Vec<(u32, Vec<u8>)> = Vec::with_capacity(new_count);
1705 for (i, idx) in new_indices.iter().enumerate() {
1706 let split_key = kf.key_portion(&chunks[i + 1][0]);
1707 results.push((*idx, split_key));
1708 }
1709 Ok(results)
1710}
1711
1712fn btree_write_node_records(data: &mut [u8], node_size: usize, idx: u32, recs: &[Vec<u8>]) {
1718 let off = idx as usize * node_size;
1719 let node = &mut data[off..off + node_size];
1720 let mut write_pos = 14usize;
1721 for (i, rec) in recs.iter().enumerate() {
1722 node[write_pos..write_pos + rec.len()].copy_from_slice(rec);
1723 let opos = node_size - 2 * (i + 1);
1724 BigEndian::write_u16(&mut node[opos..opos + 2], write_pos as u16);
1725 write_pos += rec.len();
1726 }
1727 let fpos = node_size - 2 * (recs.len() + 1);
1728 BigEndian::write_u16(&mut node[fpos..fpos + 2], write_pos as u16);
1729 if write_pos < fpos {
1730 node[write_pos..fpos].fill(0);
1731 }
1732 BigEndian::write_u16(&mut node[10..12], recs.len() as u16);
1733}
1734
1735fn index_node_has_child(data: &[u8], node_size: usize, parent_idx: u32, child: u32) -> bool {
1739 if parent_idx == 0 {
1740 return false;
1741 }
1742 let off = parent_idx as usize * node_size;
1743 if off + node_size > data.len() {
1744 return false;
1745 }
1746 let num = BigEndian::read_u16(&data[off + 10..off + 12]) as usize;
1747 for i in 0..num {
1748 let (s, e) = btree_record_range(&data[off..off + node_size], node_size, i);
1749 if e < s + 4 || e > node_size {
1750 continue;
1751 }
1752 if BigEndian::read_u32(&data[off + e - 4..off + e]) == child {
1753 return true;
1754 }
1755 }
1756 false
1757}
1758
1759#[must_use]
1773fn btree_update_index_separator(
1774 data: &mut [u8],
1775 node_size: usize,
1776 parent_idx: u32,
1777 child: u32,
1778 new_first_key: &[u8],
1779 kf: &BTreeKeyFormat,
1780) -> bool {
1781 let off = parent_idx as usize * node_size;
1782 if off + node_size > data.len() {
1783 return false;
1784 }
1785 let num = BigEndian::read_u16(&data[off + 10..off + 12]) as usize;
1786 for i in 0..num {
1787 let (s, e) = btree_record_range(&data[off..off + node_size], node_size, i);
1788 if e < s + 4 || e > node_size {
1789 continue;
1790 }
1791 if BigEndian::read_u32(&data[off + e - 4..off + e]) == child {
1792 let new_key = kf.make_index_key(new_first_key);
1801 let key_bytes = e - s - 4;
1802 if new_key.len() != key_bytes {
1803 return false;
1804 }
1805 data[off + s..off + s + key_bytes].copy_from_slice(&new_key);
1806 return true;
1807 }
1808 }
1809 false
1810}
1811
1812fn btree_try_rotate_leaf<F>(
1836 data: &mut [u8],
1837 node_size: usize,
1838 leaf_idx: u32,
1839 parent_idx: u32,
1840 new_record: &[u8],
1841 kf: &BTreeKeyFormat,
1842 cmp: &F,
1843) -> bool
1844where
1845 F: Fn(&[u8], &[u8]) -> Ordering,
1846{
1847 let payload = node_size.saturating_sub(16);
1848 let cost = |r: &Vec<u8>| r.len() + 2;
1849 let first_key = |r: &[u8]| -> Vec<u8> { kf.key_portion(r) };
1850 let read_records = |data: &[u8], idx: u32| -> Vec<Vec<u8>> {
1851 let off = idx as usize * node_size;
1852 let num = BigEndian::read_u16(&data[off + 10..off + 12]) as usize;
1853 let mut v = Vec::with_capacity(num);
1854 for i in 0..num {
1855 let (s, e) = btree_record_range(&data[off..off + node_size], node_size, i);
1856 v.push(data[off + s..off + e].to_vec());
1857 }
1858 v
1859 };
1860
1861 let loff = leaf_idx as usize * node_size;
1862 let rsib = BigEndian::read_u32(&data[loff..loff + 4]);
1863 let lsib = BigEndian::read_u32(&data[loff + 4..loff + 8]);
1864 let leaf_recs = read_records(data, leaf_idx);
1865
1866 for (sib_is_right, sib) in [(true, rsib), (false, lsib)] {
1870 if sib == 0 || !index_node_has_child(data, node_size, parent_idx, sib) {
1871 continue;
1872 }
1873 let sib_recs = read_records(data, sib);
1874 let (left_idx, right_idx, left_recs, right_recs) = if sib_is_right {
1875 (leaf_idx, sib, &leaf_recs, &sib_recs)
1876 } else {
1877 (sib, leaf_idx, &sib_recs, &leaf_recs)
1878 };
1879 let Some(left_first) = left_recs.first() else {
1880 continue;
1881 };
1882 let mut pool: Vec<Vec<u8>> = left_recs.iter().chain(right_recs.iter()).cloned().collect();
1884 let mut p = pool.len();
1885 for (i, r) in pool.iter().enumerate() {
1886 if cmp(r, new_record) == Ordering::Greater {
1887 p = i;
1888 break;
1889 }
1890 }
1891 pool.insert(p, new_record.to_vec());
1892 if cmp(&pool[0], left_first) != Ordering::Equal {
1897 continue;
1898 }
1899 let total: usize = pool.iter().map(cost).sum();
1900 if total > 2 * payload {
1901 continue; }
1903 let mut prefix = 0usize;
1905 let mut best_p = 0usize;
1906 let mut best_diff = usize::MAX;
1907 for cut in 1..pool.len() {
1908 prefix += cost(&pool[cut - 1]);
1909 let right = total - prefix;
1910 if prefix <= payload && right <= payload {
1911 let diff = prefix.abs_diff(total / 2);
1912 if diff < best_diff {
1913 best_diff = diff;
1914 best_p = cut;
1915 }
1916 }
1917 }
1918 if best_p == 0 {
1919 continue;
1920 }
1921 let left: Vec<Vec<u8>> = pool[..best_p].to_vec();
1922 let right: Vec<Vec<u8>> = pool[best_p..].to_vec();
1923 let right_first = first_key(&right[0]);
1924 if !btree_update_index_separator(data, node_size, parent_idx, right_idx, &right_first, kf) {
1929 continue;
1930 }
1931 btree_write_node_records(data, node_size, left_idx, &left);
1932 btree_write_node_records(data, node_size, right_idx, &right);
1933 return true;
1934 }
1935 false
1936}
1937
1938fn btree_split_index_with_insert<F>(
1953 catalog_data: &mut [u8],
1954 node_size: usize,
1955 node_idx: u32,
1956 header: &mut BTreeHeader,
1957 new_record: &[u8],
1958 kf: &BTreeKeyFormat,
1959 compare_fn: &F,
1960) -> Result<(u32, Vec<u8>), FilesystemError>
1961where
1962 F: Fn(&[u8], &[u8]) -> Ordering,
1963{
1964 let old_offset = node_idx as usize * node_size;
1965 let old_num = BigEndian::read_u16(&catalog_data[old_offset + 10..old_offset + 12]) as usize;
1966 let old_height = catalog_data[old_offset + 9];
1967
1968 let mut records: Vec<Vec<u8>> = Vec::with_capacity(old_num + 1);
1970 for i in 0..old_num {
1971 let (s, e) = btree_record_range(
1972 &catalog_data[old_offset..old_offset + node_size],
1973 node_size,
1974 i,
1975 );
1976 records.push(catalog_data[old_offset + s..old_offset + e].to_vec());
1977 }
1978 let mut insert_pos = records.len();
1979 for (i, rec) in records.iter().enumerate() {
1980 if compare_fn(rec, new_record) == Ordering::Greater {
1981 insert_pos = i;
1982 break;
1983 }
1984 }
1985 records.insert(insert_pos, new_record.to_vec());
1986
1987 let payload = node_size.saturating_sub(16);
1988 let cost = |r: &Vec<u8>| r.len() + 2;
1989 let total: usize = records.iter().map(cost).sum();
1990
1991 let mut cut = {
1993 let mut acc = 0usize;
1994 let mut k = 1usize;
1995 for (i, r) in records.iter().enumerate() {
1996 let c = cost(r);
1997 if i > 0 && acc + c > payload {
1998 break;
1999 }
2000 acc += c;
2001 k = i + 1;
2002 }
2003 k.clamp(1, records.len() - 1)
2004 };
2005 let is_append = insert_pos == old_num;
2008 if !is_append {
2009 let mut prefix = 0usize;
2010 let mut best = cut;
2011 let mut best_diff = usize::MAX;
2012 for k in 1..records.len() {
2013 prefix += cost(&records[k - 1]);
2014 let right = total - prefix;
2015 if prefix <= payload && right <= payload {
2016 let diff = prefix.abs_diff(total / 2);
2017 if diff < best_diff {
2018 best_diff = diff;
2019 best = k;
2020 }
2021 }
2022 }
2023 cut = best;
2024 }
2025
2026 let new_idx = btree_alloc_node(catalog_data, node_size, header.total_nodes)?;
2027 header.free_nodes -= 1;
2028 init_node(
2029 catalog_data,
2030 node_size,
2031 new_idx,
2032 BTREE_INDEX_NODE,
2033 old_height,
2034 );
2035
2036 let left: Vec<Vec<u8>> = records[..cut].to_vec();
2037 let right: Vec<Vec<u8>> = records[cut..].to_vec();
2038 btree_write_node_records(catalog_data, node_size, node_idx, &left);
2039 btree_write_node_records(catalog_data, node_size, new_idx, &right);
2040
2041 let old_next = BigEndian::read_u32(&catalog_data[old_offset..old_offset + 4]);
2044 let new_offset = new_idx as usize * node_size;
2045 BigEndian::write_u32(&mut catalog_data[old_offset..old_offset + 4], new_idx);
2046 BigEndian::write_u32(&mut catalog_data[new_offset + 4..new_offset + 8], node_idx);
2047 BigEndian::write_u32(&mut catalog_data[new_offset..new_offset + 4], old_next);
2048 if old_next != 0 {
2049 let next_off = old_next as usize * node_size;
2050 BigEndian::write_u32(&mut catalog_data[next_off + 4..next_off + 8], new_idx);
2051 }
2052
2053 let split_key = kf.key_portion(&right[0]);
2055 Ok((new_idx, split_key))
2056}
2057
2058#[allow(clippy::too_many_arguments)] pub fn btree_insert_into_index<F>(
2067 catalog_data: &mut [u8],
2068 node_size: usize,
2069 index_node_idx: u32,
2070 child_node: u32,
2071 split_key: &[u8],
2072 header: &mut BTreeHeader,
2073 kf: &BTreeKeyFormat,
2074 compare_fn: &F,
2075 parent_chain: &[(u32, u32)], ) -> Result<(), FilesystemError>
2077where
2078 F: Fn(&[u8], &[u8]) -> Ordering,
2079{
2080 let mut index_record = kf.make_index_key(split_key);
2087 let mut ptr = [0u8; 4];
2088 BigEndian::write_u32(&mut ptr, child_node);
2089 index_record.extend_from_slice(&ptr);
2090
2091 let offset = index_node_idx as usize * node_size;
2092 let node = &mut catalog_data[offset..offset + node_size];
2093
2094 match btree_insert_record(node, node_size, &index_record, compare_fn) {
2096 Ok(_) => Ok(()),
2097 Err(_) => {
2098 if let Some(&(_, parent_idx)) = parent_chain
2107 .iter()
2108 .find(|&&(nidx, _)| nidx == index_node_idx)
2109 {
2110 if parent_idx != 0
2111 && btree_try_rotate_leaf(
2112 catalog_data,
2113 node_size,
2114 index_node_idx,
2115 parent_idx,
2116 &index_record,
2117 kf,
2118 compare_fn,
2119 )
2120 {
2121 return Ok(());
2122 }
2123 }
2124
2125 let (new_idx, new_split_key) = btree_split_index_with_insert(
2129 catalog_data,
2130 node_size,
2131 index_node_idx,
2132 header,
2133 &index_record,
2134 kf,
2135 compare_fn,
2136 )?;
2137
2138 if let Some(&(_, parent_idx)) = parent_chain
2141 .iter()
2142 .find(|&&(nidx, _)| nidx == index_node_idx)
2143 {
2144 if parent_idx == 0 && index_node_idx == header.root_node {
2145 btree_grow_root(
2147 catalog_data,
2148 node_size,
2149 header,
2150 index_node_idx,
2151 new_idx,
2152 &new_split_key,
2153 kf,
2154 )?;
2155 } else {
2156 btree_insert_into_index(
2158 catalog_data,
2159 node_size,
2160 parent_idx,
2161 new_idx,
2162 &new_split_key,
2163 header,
2164 kf,
2165 compare_fn,
2166 parent_chain,
2167 )?;
2168 }
2169 } else {
2170 btree_grow_root(
2172 catalog_data,
2173 node_size,
2174 header,
2175 index_node_idx,
2176 new_idx,
2177 &new_split_key,
2178 kf,
2179 )?;
2180 }
2181 Ok(())
2182 }
2183 }
2184}
2185
2186pub fn btree_grow_root(
2188 catalog_data: &mut [u8],
2189 node_size: usize,
2190 header: &mut BTreeHeader,
2191 old_root: u32,
2192 new_sibling: u32,
2193 split_key: &[u8],
2194 kf: &BTreeKeyFormat,
2195) -> Result<(), FilesystemError> {
2196 let new_root = btree_alloc_node(catalog_data, node_size, header.total_nodes)?;
2197 header.free_nodes -= 1;
2198
2199 let old_root_offset = old_root as usize * node_size;
2200 let old_height = catalog_data[old_root_offset + 9];
2201
2202 init_node(
2203 catalog_data,
2204 node_size,
2205 new_root,
2206 BTREE_INDEX_NODE,
2207 old_height + 1,
2208 );
2209
2210 let new_root_offset = new_root as usize * node_size;
2211
2212 let (first_rec_start, first_rec_end) = btree_record_range(
2219 &catalog_data[old_root_offset..old_root_offset + node_size],
2220 node_size,
2221 0,
2222 );
2223 let first_key = kf.key_portion(
2224 &catalog_data[old_root_offset + first_rec_start..old_root_offset + first_rec_end],
2225 );
2226
2227 let mut rec1 = kf.make_index_key(&first_key);
2229 let mut ptr1 = [0u8; 4];
2230 BigEndian::write_u32(&mut ptr1, old_root);
2231 rec1.extend_from_slice(&ptr1);
2232
2233 let mut rec2 = kf.make_index_key(split_key);
2235 let mut ptr2 = [0u8; 4];
2236 BigEndian::write_u32(&mut ptr2, new_sibling);
2237 rec2.extend_from_slice(&ptr2);
2238
2239 let node = &mut catalog_data[new_root_offset..new_root_offset + node_size];
2241 let mut write_pos = 14usize;
2242 node[write_pos..write_pos + rec1.len()].copy_from_slice(&rec1);
2244 BigEndian::write_u16(&mut node[node_size - 2..node_size], 14);
2245 write_pos += rec1.len();
2246 node[write_pos..write_pos + rec2.len()].copy_from_slice(&rec2);
2248 BigEndian::write_u16(&mut node[node_size - 4..node_size - 2], write_pos as u16);
2249 write_pos += rec2.len();
2250 BigEndian::write_u16(&mut node[node_size - 6..node_size - 4], write_pos as u16);
2252 BigEndian::write_u16(&mut node[10..12], 2); header.root_node = new_root;
2255 header.depth += 1;
2256
2257 Ok(())
2258}
2259
2260#[allow(dead_code)]
2274#[allow(clippy::type_complexity)] pub fn btree_find_record<K, C>(
2276 catalog_data: &[u8],
2277 header: &BTreeHeader,
2278 search_key: &[u8],
2279 key_extract_fn: &K,
2280 compare_fn: &C,
2281) -> (Option<(u32, usize, usize)>, Vec<(u32, u32)>)
2282where
2283 K: Fn(&[u8]) -> &[u8],
2284 C: Fn(&[u8], &[u8]) -> Ordering,
2285{
2286 let node_size = header.node_size as usize;
2287 let mut parent_chain: Vec<(u32, u32)> = Vec::new();
2288 let mut current_node = header.root_node;
2289 let mut parent_node = 0u32;
2290 let mut visited = std::collections::HashSet::new();
2291
2292 loop {
2293 if !visited.insert(current_node) {
2295 return (None, parent_chain);
2296 }
2297 let offset = current_node as usize * node_size;
2298 if offset + node_size > catalog_data.len() {
2299 return (None, parent_chain);
2300 }
2301 let node = &catalog_data[offset..offset + node_size];
2302 let kind = node[8] as i8;
2303 let num_records = BigEndian::read_u16(&node[10..12]) as usize;
2304
2305 parent_chain.push((current_node, parent_node));
2306
2307 if kind == BTREE_LEAF_NODE {
2308 for i in 0..num_records {
2310 let (start, end) = btree_record_range(node, node_size, i);
2311 if start >= end || end > node_size {
2312 continue;
2313 }
2314 let rec_bytes = &node[start..end];
2315 let key = key_extract_fn(rec_bytes);
2316 match compare_fn(key, search_key) {
2317 Ordering::Equal => {
2318 return (Some((current_node, i, offset + start)), parent_chain);
2319 }
2320 Ordering::Greater => return (None, parent_chain),
2321 _ => {}
2322 }
2323 }
2324 return (None, parent_chain);
2325 } else if kind == BTREE_INDEX_NODE {
2326 let mut next_child = 0u32;
2329 for i in 0..num_records {
2330 let (start, end) = btree_record_range(node, node_size, i);
2331 if end < start + 4 || end > node_size {
2332 continue;
2333 }
2334 let rec_bytes = &node[start..end];
2335 let key = key_extract_fn(rec_bytes);
2336 let child_ptr = index_child_ptr(node, end);
2338
2339 match compare_fn(key, search_key) {
2340 Ordering::Greater => {
2341 break;
2343 }
2344 _ => {
2345 next_child = child_ptr;
2346 }
2347 }
2348 }
2349 if next_child == 0 && num_records > 0 {
2350 let (_, end) = btree_record_range(node, node_size, 0);
2353 next_child = index_child_ptr(node, end);
2354 }
2355 parent_node = current_node;
2356 current_node = next_child;
2357 } else {
2358 return (None, parent_chain);
2359 }
2360 }
2361}
2362
2363pub fn btree_find_insert_leaf<C>(
2366 catalog_data: &[u8],
2367 header: &BTreeHeader,
2368 search_key: &[u8],
2369 compare_fn: &C,
2370) -> (u32, Vec<(u32, u32)>)
2371where
2372 C: Fn(&[u8], &[u8]) -> Ordering,
2373{
2374 let node_size = header.node_size as usize;
2375 let mut parent_chain: Vec<(u32, u32)> = Vec::new();
2376 let mut current_node = header.root_node;
2377 let mut parent_node = 0u32;
2378 let mut visited = std::collections::HashSet::new();
2379
2380 loop {
2381 if !visited.insert(current_node) {
2383 return (header.first_leaf_node, parent_chain);
2384 }
2385 let offset = current_node as usize * node_size;
2386 if offset + node_size > catalog_data.len() {
2387 return (current_node, parent_chain);
2388 }
2389 let node = &catalog_data[offset..offset + node_size];
2390 let kind = node[8] as i8;
2391 let num_records = BigEndian::read_u16(&node[10..12]) as usize;
2392
2393 parent_chain.push((current_node, parent_node));
2394
2395 if kind == BTREE_LEAF_NODE {
2396 return (current_node, parent_chain);
2397 }
2398
2399 let mut next_child = 0u32;
2401 for i in 0..num_records {
2402 let (start, end) = btree_record_range(node, node_size, i);
2403 if end < start + 4 || end > node_size {
2404 continue;
2405 }
2406 let key_portion = &node[start..end - 4];
2408 let child_ptr = index_child_ptr(node, end);
2409
2410 if compare_fn(key_portion, search_key) != Ordering::Greater {
2411 next_child = child_ptr;
2412 } else {
2413 break;
2414 }
2415 }
2416 if next_child == 0 && num_records > 0 {
2417 let (_, end) = btree_record_range(node, node_size, 0);
2419 next_child = index_child_ptr(node, end);
2420 }
2421
2422 parent_node = current_node;
2423 current_node = next_child;
2424 }
2425}
2426
2427pub fn btree_insert_full<F>(
2449 data: &mut [u8],
2450 key_record: &[u8],
2451 kf: &BTreeKeyFormat,
2452 cmp: &F,
2453) -> Result<(), disk_core::filesystem::FilesystemError>
2454where
2455 F: Fn(&[u8], &[u8]) -> Ordering,
2456{
2457 let mut header = BTreeHeader::read(data);
2458 let node_size = header.node_size as usize;
2459 if node_size == 0 {
2460 return Err(disk_core::filesystem::FilesystemError::InvalidData(
2461 "B-tree has zero node_size".into(),
2462 ));
2463 }
2464
2465 if header.depth == 0 || header.root_node == 0 {
2471 let leaf = btree_alloc_node(data, node_size, header.total_nodes)?;
2472 let off = leaf as usize * node_size;
2473 let node = &mut data[off..off + node_size];
2474 node.fill(0);
2475 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;
2480 header.root_node = leaf;
2481 header.first_leaf_node = leaf;
2482 header.last_leaf_node = leaf;
2483 header.free_nodes = header.free_nodes.saturating_sub(1);
2484 header.write(data);
2485 }
2486
2487 let (leaf_idx, parent_chain) = btree_find_insert_leaf(data, &header, key_record, cmp);
2488
2489 let off = leaf_idx as usize * node_size;
2490 let node = &mut data[off..off + node_size];
2491 match btree_insert_record(node, node_size, key_record, cmp) {
2492 Ok(_) => {
2493 let mut h = BTreeHeader::read(data);
2494 h.leaf_records += 1;
2495 h.write(data);
2496 Ok(())
2497 }
2498 Err(_) => {
2499 let mut h = BTreeHeader::read(data);
2500 if h.depth > 1 {
2506 if let Some(&(_, parent_idx)) =
2507 parent_chain.iter().find(|&&(nidx, _)| nidx == leaf_idx)
2508 {
2509 if parent_idx != 0
2510 && btree_try_rotate_leaf(
2511 data, node_size, leaf_idx, parent_idx, key_record, kf, cmp,
2512 )
2513 {
2514 h.leaf_records += 1;
2515 h.write(data);
2516 return Ok(());
2517 }
2518 }
2519 }
2520 if h.free_nodes < h.depth as u32 + 1 {
2534 return Err(disk_core::filesystem::FilesystemError::DiskFull(
2535 "no free B-tree nodes".into(),
2536 ));
2537 }
2538 let splits = btree_split_leaf_with_insert(
2539 data, node_size, leaf_idx, &mut h, key_record, kf, cmp,
2540 )?;
2541 h.leaf_records += 1;
2542 let first = &splits[0];
2547 if h.depth == 1 {
2548 btree_grow_root(data, node_size, &mut h, leaf_idx, first.0, &first.1, kf)?;
2549 } else if let Some(&(_, parent_idx)) =
2550 parent_chain.iter().find(|&&(nidx, _)| nidx == leaf_idx)
2551 {
2552 btree_insert_into_index(
2553 data,
2554 node_size,
2555 parent_idx,
2556 first.0,
2557 &first.1,
2558 &mut h,
2559 kf,
2560 cmp,
2561 &parent_chain,
2562 )?;
2563 } else {
2564 btree_grow_root(data, node_size, &mut h, leaf_idx, first.0, &first.1, kf)?;
2565 }
2566 for split in &splits[1..] {
2567 let header_now = BTreeHeader::read(data);
2572 let (_, fresh_chain) = btree_find_insert_leaf(data, &header_now, &split.1, cmp);
2573 let parent_for_sep = fresh_chain
2578 .last()
2579 .map(|&(_, p)| p)
2580 .unwrap_or(header_now.root_node);
2581 btree_insert_into_index(
2582 data,
2583 node_size,
2584 parent_for_sep,
2585 split.0,
2586 &split.1,
2587 &mut h,
2588 kf,
2589 cmp,
2590 &fresh_chain,
2591 )?;
2592 }
2593 h.write(data);
2594 Ok(())
2595 }
2596 }
2597}
2598
2599#[cfg(test)]
2604mod tests {
2605 use super::*;
2606
2607 #[test]
2608 fn test_format_mac_date() {
2609 assert_eq!(format_mac_date(0), None);
2611 assert_eq!(
2613 format_mac_date(MAC_EPOCH_DELTA as u32).as_deref(),
2614 Some("1970-01-01 00:00:00")
2615 );
2616 assert_eq!(
2618 format_mac_date((MAC_EPOCH_DELTA + 946_684_800) as u32).as_deref(),
2619 Some("2000-01-01 00:00:00")
2620 );
2621 }
2622
2623 #[test]
2624 fn test_parse_mac_date_round_trips() {
2625 assert_eq!(parse_mac_date(""), Some(0));
2627 assert_eq!(parse_mac_date("0"), Some(0));
2628 let mac = (MAC_EPOCH_DELTA + 946_684_800) as u32; let s = format_mac_date(mac).unwrap();
2631 assert_eq!(parse_mac_date(&s), Some(mac));
2632 assert_eq!(parse_mac_date("not a date"), None);
2634 }
2635
2636 #[test]
2637 fn test_parse_mac_date_checked_specific_errors() {
2638 assert!(parse_mac_date_checked("1997-13-01 11:09:34")
2639 .unwrap_err()
2640 .contains("month 13"));
2641 assert!(parse_mac_date_checked("1997-02-29 11:09:34")
2642 .unwrap_err()
2643 .contains("day 29 is invalid for 1997-02"));
2644 assert!(parse_mac_date_checked("2000-01-01 25:00:00")
2645 .unwrap_err()
2646 .contains("out of range"));
2647 assert!(parse_mac_date_checked("garbage")
2648 .unwrap_err()
2649 .contains("expected YYYY-MM-DD"));
2650 assert!(parse_mac_date_checked("2000-02-29 12:00:00").is_ok());
2652 }
2653
2654 #[test]
2657 fn test_bitmap_set_clear_test_be() {
2658 let mut data = [0u8; 2];
2659 bitmap_set_bit_be(&mut data, 0);
2661 assert_eq!(data[0], 0b10000000);
2662 assert!(bitmap_test_bit_be(&data, 0));
2663 assert!(!bitmap_test_bit_be(&data, 1));
2664
2665 bitmap_set_bit_be(&mut data, 7);
2666 assert_eq!(data[0], 0b10000001);
2667
2668 bitmap_set_bit_be(&mut data, 8);
2669 assert_eq!(data[1], 0b10000000);
2670
2671 bitmap_clear_bit_be(&mut data, 0);
2672 assert_eq!(data[0], 0b00000001);
2673 assert!(!bitmap_test_bit_be(&data, 0));
2674 }
2675
2676 #[test]
2677 fn test_bitmap_find_clear_bit_be() {
2678 let data = [0b11111110, 0b11111111]; assert_eq!(bitmap_find_clear_bit_be(&data, 16), Some(7));
2680
2681 let full = [0xFF, 0xFF];
2682 assert_eq!(bitmap_find_clear_bit_be(&full, 16), None);
2683
2684 let empty = [0x00, 0x00];
2685 assert_eq!(bitmap_find_clear_bit_be(&empty, 16), Some(0));
2686 }
2687
2688 #[test]
2689 fn test_bitmap_find_clear_run_be() {
2690 let data = [0b11100011, 0b00000000];
2692 assert_eq!(bitmap_find_clear_run_be(&data, 16, 3), Some(3));
2695 assert_eq!(bitmap_find_clear_run_be(&data, 16, 4), Some(8));
2697 assert_eq!(bitmap_find_clear_run_be(&data, 16, 8), Some(8));
2698 assert_eq!(bitmap_find_clear_run_be(&data, 16, 9), None);
2699 }
2700
2701 #[test]
2702 fn test_bitmap_be_roundtrip() {
2703 let mut data = [0u8; 4];
2704 for i in 0..32u32 {
2705 bitmap_set_bit_be(&mut data, i);
2706 }
2707 assert_eq!(data, [0xFF; 4]);
2708 for i in 0..32u32 {
2709 assert!(bitmap_test_bit_be(&data, i));
2710 bitmap_clear_bit_be(&mut data, i);
2711 }
2712 assert_eq!(data, [0x00; 4]);
2713 }
2714
2715 #[test]
2716 fn test_bitmap_single_bit() {
2717 let mut data = [0u8; 1];
2718 bitmap_set_bit_be(&mut data, 3);
2719 assert_eq!(data[0], 0b00010000);
2720 assert!(bitmap_test_bit_be(&data, 3));
2721 assert!(!bitmap_test_bit_be(&data, 2));
2722 assert!(!bitmap_test_bit_be(&data, 4));
2723 }
2724
2725 #[test]
2728 fn test_hfs_now_sanity() {
2729 let now = hfs_now();
2730 assert!(now > 3_786_912_000);
2733 }
2734
2735 #[test]
2738 fn test_type_creator_for_extension_known() {
2739 let (tc, cc) = type_creator_for_extension("txt").unwrap();
2740 assert_eq!(&tc, b"TEXT");
2741 assert_eq!(&cc, b"ttxt");
2742 }
2743
2744 #[test]
2745 fn test_type_creator_for_extension_case_insensitive() {
2746 let (tc, cc) = type_creator_for_extension("TXT").unwrap();
2747 assert_eq!(&tc, b"TEXT");
2748 assert_eq!(&cc, b"ttxt");
2749 }
2750
2751 #[test]
2752 fn test_uses_hfs_type_dictionary() {
2753 for t in ["HFS", "HFS+", "HFSX", "MFS"] {
2754 assert!(uses_hfs_type_dictionary(t), "{t} should use the dictionary");
2755 }
2756 for t in ["ProDOS", "FAT16", "ext4", ""] {
2758 assert!(!uses_hfs_type_dictionary(t), "{t} should not");
2759 }
2760 }
2761
2762 #[test]
2763 fn test_type_creator_for_filename_needs_a_real_extension() {
2764 assert!(type_creator_for_filename("readme.txt").is_some());
2765 assert!(type_creator_for_filename("txt").is_none());
2768 assert!(type_creator_for_filename("README").is_none());
2769 }
2770
2771 #[test]
2772 fn test_resolve_blank_ostype_falls_through_to_dictionary() {
2773 let (t, c) =
2777 resolve_create_type_creator("readme.txt", Some([0; 4]), Some([0; 4]), None, None);
2778 assert_eq!(&t, b"TEXT");
2779 assert_eq!(&c, b"ttxt");
2780
2781 let (t, c) = resolve_create_type_creator("pic.gif", None, None, Some(""), Some(" "));
2783 assert_eq!(&t, b"GIFf");
2784 assert_eq!(&c, b"ogle");
2785 }
2786
2787 #[test]
2788 fn test_resolve_real_ostype_wins_over_dictionary() {
2789 let (t, c) = resolve_create_type_creator(
2792 "poi.txt",
2793 Some(*b"APPL"),
2794 Some([0x50, 0x6f, 0xC4, 0x50]),
2795 None,
2796 None,
2797 );
2798 assert_eq!(&t, b"APPL");
2799 assert_eq!(c, [0x50, 0x6f, 0xC4, 0x50]);
2800
2801 let (t, _) =
2804 resolve_create_type_creator("spc.txt", Some(*b" "), Some(*b" "), None, None);
2805 assert_eq!(&t, b" ");
2806 }
2807
2808 #[test]
2809 fn test_resolve_fills_only_the_missing_half() {
2810 let (t, c) = resolve_create_type_creator("pic.gif", None, Some(*b"MINE"), None, None);
2813 assert_eq!(&t, b"GIFf");
2814 assert_eq!(&c, b"MINE");
2815 }
2816
2817 #[test]
2818 fn test_resolve_unknown_extension_stays_blank() {
2819 let (t, c) = resolve_create_type_creator("thing.xyz123", None, None, None, None);
2822 assert_eq!(t, [0; 4]);
2823 assert_eq!(c, [0; 4]);
2824 }
2825
2826 #[test]
2827 fn test_resolve_text_type_keeps_trailing_space_codes() {
2828 let (t, _) = resolve_create_type_creator("x.unknown", None, None, Some("ZIP "), None);
2831 assert_eq!(&t, b"ZIP ");
2832 }
2833
2834 #[test]
2835 fn test_type_creator_for_extension_unknown() {
2836 assert!(type_creator_for_extension("xyz123").is_none());
2837 }
2838
2839 #[test]
2840 fn test_encode_fourcc() {
2841 assert_eq!(encode_fourcc("TEXT"), *b"TEXT");
2842 assert_eq!(encode_fourcc("AB"), *b"AB ");
2843 assert_eq!(encode_fourcc("SIT!"), *b"SIT!");
2844 }
2845
2846 #[test]
2847 fn test_encode_fourcc_roundtrip() {
2848 let code = encode_fourcc("JPEG");
2849 assert_eq!(&code, b"JPEG");
2850 }
2851
2852 fn make_test_btree(node_size: usize) -> Vec<u8> {
2856 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;
2876 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);
2881 BigEndian::write_u16(&mut data[node_size - 4..node_size - 2], user_rec_offset);
2882 BigEndian::write_u16(&mut data[node_size - 6..node_size - 4], bitmap_rec_offset);
2883 BigEndian::write_u16(&mut data[node_size - 8..node_size - 6], free_offset);
2884
2885 data[bitmap_rec_offset as usize] = 0b11000000; let n1 = node_size;
2891 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
2897 }
2898
2899 #[test]
2900 fn test_btree_node_free_space_empty_leaf() {
2901 let data = make_test_btree(512);
2902 let node = &data[512..1024]; let free = btree_node_free_space(node, 512);
2904 assert_eq!(free, 494);
2907 }
2908
2909 #[test]
2910 fn test_btree_insert_and_remove() {
2911 let mut data = make_test_btree(512);
2912 let node_size = 512;
2913
2914 let rec1 = vec![0u8; 20]; let rec2 = vec![1u8; 20];
2917 let compare = |a: &[u8], b: &[u8]| a.cmp(b);
2918
2919 let node = &mut data[node_size..node_size * 2];
2920 let pos = btree_insert_record(node, node_size, &rec1, &compare).unwrap();
2921 assert_eq!(pos, 0);
2922 assert_eq!(BigEndian::read_u16(&node[10..12]), 1); let pos2 = btree_insert_record(node, node_size, &rec2, &compare).unwrap();
2925 assert_eq!(pos2, 1); assert_eq!(BigEndian::read_u16(&node[10..12]), 2);
2927
2928 btree_remove_record(node, node_size, 0);
2930 assert_eq!(BigEndian::read_u16(&node[10..12]), 1);
2931 }
2932
2933 #[test]
2934 fn test_btree_alloc_free_node() {
2935 let mut data = make_test_btree(512);
2936 let node_size = 512;
2937
2938 let idx = btree_alloc_node(&mut data, node_size, 4).unwrap();
2940 assert_eq!(idx, 2);
2941
2942 let idx2 = btree_alloc_node(&mut data, node_size, 4).unwrap();
2944 assert_eq!(idx2, 3);
2945
2946 assert!(btree_alloc_node(&mut data, node_size, 4).is_err());
2948
2949 btree_free_node(&mut data, node_size, 2);
2951 let idx3 = btree_alloc_node(&mut data, node_size, 4).unwrap();
2952 assert_eq!(idx3, 2);
2953 }
2954
2955 #[test]
2956 fn test_btree_header_read_write() {
2957 let mut data = make_test_btree(512);
2958 let header = BTreeHeader::read(&data);
2959 assert_eq!(header.depth, 1);
2960 assert_eq!(header.root_node, 1);
2961 assert_eq!(header.total_nodes, 4);
2962 assert_eq!(header.free_nodes, 2);
2963
2964 let mut header = header;
2966 header.leaf_records = 42;
2967 header.write(&mut data);
2968
2969 let header2 = BTreeHeader::read(&data);
2970 assert_eq!(header2.leaf_records, 42);
2971 }
2972
2973 #[test]
2974 fn test_btree_split_leaf_with_insert() {
2975 let mut data = make_test_btree(512);
2976 let node_size = 512;
2977
2978 let compare = |a: &[u8], b: &[u8]| a.cmp(b);
2982 for i in 0..10u8 {
2983 let mut rec = vec![0u8; 45];
2984 rec[0] = i * 2;
2985 let node = &mut data[node_size..node_size * 2];
2986 btree_insert_record(node, node_size, &rec, &compare).unwrap();
2987 }
2988
2989 let node = &data[node_size..node_size * 2];
2990 assert_eq!(BigEndian::read_u16(&node[10..12]), 10);
2991
2992 let mut new_rec = vec![0u8; 45];
2994 new_rec[0] = 5;
2995 let mut header = BTreeHeader::read(&data);
2996 let splits = btree_split_leaf_with_insert(
2997 &mut data,
2998 node_size,
2999 1,
3000 &mut header,
3001 &new_rec,
3002 &BTreeKeyFormat::CLASSIC_CATALOG,
3003 &compare,
3004 )
3005 .unwrap();
3006 assert_eq!(splits.len(), 1, "30-byte records: 2-way split is enough");
3007 let new_idx = splits[0].0;
3008 assert_eq!(new_idx, 2);
3009
3010 let old_node = &data[node_size..node_size * 2];
3011 let new_node = &data[new_idx as usize * node_size..(new_idx as usize + 1) * node_size];
3012 let old_count = BigEndian::read_u16(&old_node[10..12]);
3013 let new_count = BigEndian::read_u16(&new_node[10..12]);
3014 assert_eq!(old_count + new_count, 11); }
3016}