1use crate::error::{Error, Result};
31use crate::hash::{name_hash, HashVersion, NameHash};
32use crate::htree::{DxCountLimit, DxEntry};
33
34#[derive(Debug, Clone, PartialEq, Eq)]
37pub enum HtreeMutation {
38 WriteDirBlock { logical_block: u32, bytes: Vec<u8> },
41 AppendNewDirBlock { bytes: Vec<u8> },
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct LeafSplit {
54 pub left_bytes: Vec<u8>,
57 pub right_bytes: Vec<u8>,
60 pub split_out_hash: u32,
64}
65
66fn read_leaf_entries(
73 leaf: &[u8],
74 has_file_type: bool,
75 hash_version: HashVersion,
76 hash_seed: &[u32; 4],
77 reserved_tail: usize,
78) -> Result<Vec<(NameHash, Vec<u8>)>> {
79 let usable = leaf
80 .len()
81 .checked_sub(reserved_tail)
82 .ok_or(Error::OutOfBounds)?;
83 let mut out = Vec::new();
84 let mut off = 0usize;
85 while off + 8 <= usable {
86 let cur_inode = u32::from_le_bytes(leaf[off..off + 4].try_into().unwrap());
87 let rec_len = u16::from_le_bytes(leaf[off + 4..off + 6].try_into().unwrap()) as usize;
88 if rec_len < 8 || !rec_len.is_multiple_of(4) || off + rec_len > usable {
89 return Err(Error::CorruptDirEntry(
90 "bad rec_len during htree leaf split",
91 ));
92 }
93
94 if cur_inode != 0 {
95 let name_len_lo = leaf[off + 6];
96 let type_or_hi = leaf[off + 7];
97 let name_len = if has_file_type {
98 name_len_lo as usize
99 } else {
100 ((type_or_hi as usize) << 8) | name_len_lo as usize
101 };
102 if 8 + name_len > rec_len {
103 return Err(Error::CorruptDirEntry(
104 "name overflows rec_len in htree split",
105 ));
106 }
107 let name = &leaf[off + 8..off + 8 + name_len];
108 let h = name_hash(name, hash_version, hash_seed);
109 let padded = 8 + ((name_len + 3) & !3);
111 let mut entry_bytes = vec![0u8; padded];
112 entry_bytes[..8].copy_from_slice(&leaf[off..off + 8]);
113 entry_bytes[8..8 + name_len].copy_from_slice(name);
114 out.push((h, entry_bytes));
117 }
118 off += rec_len;
119 }
120 Ok(out)
121}
122
123fn write_packed_leaf(
126 entries: &[&[u8]],
127 block_size: usize,
128 reserved_tail: usize,
129) -> Result<Vec<u8>> {
130 let usable = block_size
131 .checked_sub(reserved_tail)
132 .ok_or(Error::OutOfBounds)?;
133 let mut out = vec![0u8; block_size];
134 let mut off = 0usize;
135 for (i, raw) in entries.iter().enumerate() {
136 let is_last = i + 1 == entries.len();
137 let padded = raw.len();
138 if off + padded > usable {
139 return Err(Error::OutOfBounds);
140 }
141 let rec_len = if is_last { usable - off } else { padded };
144 out[off..off + raw.len()].copy_from_slice(raw);
145 out[off + 4..off + 6].copy_from_slice(&(rec_len as u16).to_le_bytes());
147 off += rec_len;
148 }
149 Ok(out)
152}
153
154pub fn plan_leaf_split(
161 leaf_bytes: &[u8],
162 hash_version: HashVersion,
163 hash_seed: &[u32; 4],
164 has_file_type: bool,
165 block_size: usize,
166 reserved_tail: usize,
167) -> Result<LeafSplit> {
168 let mut entries = read_leaf_entries(
169 leaf_bytes,
170 has_file_type,
171 hash_version,
172 hash_seed,
173 reserved_tail,
174 )?;
175 if entries.len() < 2 {
176 return Err(Error::Corrupt(
177 "htree leaf split needs >=2 entries; caller's overflow may be spurious",
178 ));
179 }
180 entries.sort_by_key(|(h, _)| h.major);
182
183 let half = entries.len() / 2;
188 let mid = (0..entries.len())
189 .flat_map(|d| [half + d, half.wrapping_sub(d)])
190 .find(|&m| m > 0 && m < entries.len() && entries[m - 1].0.major != entries[m].0.major)
191 .ok_or(Error::Corrupt(
192 "htree leaf split: every name in the leaf has the same hash",
193 ))?;
194 let split_out_hash = entries[mid].0.major;
195 let (left, right) = entries.split_at(mid);
196 let left_bytes = write_packed_leaf(
197 &left.iter().map(|(_, b)| b.as_slice()).collect::<Vec<_>>(),
198 block_size,
199 reserved_tail,
200 )?;
201 let right_bytes = write_packed_leaf(
202 &right.iter().map(|(_, b)| b.as_slice()).collect::<Vec<_>>(),
203 block_size,
204 reserved_tail,
205 )?;
206 Ok(LeafSplit {
207 left_bytes,
208 right_bytes,
209 split_out_hash,
210 })
211}
212
213pub fn plan_insert_dx_entry_root(
220 root_bytes: &[u8],
221 cl_offset: usize,
222 new_hash: u32,
223 new_block: u32,
224) -> Result<Vec<u8>> {
225 plan_insert_dx_entry_generic(root_bytes, cl_offset, new_hash, new_block)
226}
227
228pub fn plan_insert_dx_entry_node(
232 node_bytes: &[u8],
233 new_hash: u32,
234 new_block: u32,
235) -> Result<Vec<u8>> {
236 plan_insert_dx_entry_generic(node_bytes, 8, new_hash, new_block)
237}
238
239fn plan_insert_dx_entry_generic(
240 block: &[u8],
241 cl_offset: usize,
242 new_hash: u32,
243 new_block: u32,
244) -> Result<Vec<u8>> {
245 if cl_offset + DxEntry::SIZE > block.len() {
246 return Err(Error::Corrupt("dx cl_offset out of range"));
247 }
248 let cl = DxCountLimit::parse(&block[cl_offset..cl_offset + 4])?;
249 if cl.count >= cl.limit {
250 return Err(Error::CorruptExtentTree("NEEDS_PARENT_SPLIT: dx node full"));
251 }
252 if cl.count == 0 {
253 return Err(Error::Corrupt("dx node has zero entries"));
254 }
255
256 let entry = |i: usize| -> Result<DxEntry> {
262 let off = cl_offset + i * DxEntry::SIZE;
263 if off + DxEntry::SIZE > block.len() {
264 return Err(Error::Corrupt("dx entries overrun block"));
265 }
266 DxEntry::parse(&block[off..off + DxEntry::SIZE])
267 };
268 let mut entries = (0..cl.count as usize)
269 .map(entry)
270 .collect::<Result<Vec<_>>>()?;
271 entries[0].hash = 0;
272
273 if entries[1..].iter().any(|e| e.hash == new_hash) {
277 return Err(Error::CorruptExtentTree("duplicate dx hash insert"));
278 }
279
280 let pos = entries
282 .iter()
283 .skip(1)
284 .position(|e| e.hash > new_hash)
285 .map(|p| p + 1)
286 .unwrap_or(entries.len());
287 entries.insert(
288 pos,
289 DxEntry {
290 hash: new_hash,
291 block: new_block,
292 },
293 );
294
295 let mut out = block.to_vec();
296 if cl_offset + entries.len() * DxEntry::SIZE > out.len() {
297 return Err(Error::Corrupt("dx serialized entries exceed block"));
298 }
299 for (i, e) in entries.iter().enumerate() {
300 let off = cl_offset + i * DxEntry::SIZE;
301 if i > 0 {
302 out[off..off + 4].copy_from_slice(&e.hash.to_le_bytes());
303 }
304 out[off + 4..off + 8].copy_from_slice(&e.block.to_le_bytes());
305 }
306 out[cl_offset + 2..cl_offset + 4].copy_from_slice(&(cl.count + 1).to_le_bytes());
307 Ok(out)
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313 use crate::dir::{add_entry_to_block, DirEntryType};
314
315 fn make_populated_leaf(count: usize, block_size: usize, reserved_tail: usize) -> Vec<u8> {
318 let mut buf = vec![0u8; block_size];
319 let usable = block_size - reserved_tail;
322 buf[0..4].copy_from_slice(&1u32.to_le_bytes());
323 buf[4..6].copy_from_slice(&(usable as u16).to_le_bytes());
324 buf[6] = 1;
325 buf[7] = DirEntryType::RegFile as u8;
326 buf[8] = b'a';
327 for i in 0..count {
328 let name = format!("file_{}", i);
329 add_entry_to_block(
330 &mut buf,
331 100 + i as u32,
332 name.as_bytes(),
333 DirEntryType::RegFile,
334 true,
335 reserved_tail,
336 )
337 .unwrap();
338 }
339 buf
340 }
341
342 #[test]
343 fn leaf_split_balances_entries() {
344 let leaf = make_populated_leaf(40, 4096, 0);
345 let split = plan_leaf_split(&leaf, HashVersion::HalfMd4, &[0; 4], true, 4096, 0).unwrap();
346 assert_eq!(split.left_bytes.len(), 4096);
348 assert_eq!(split.right_bytes.len(), 4096);
349 let left_entries =
352 read_leaf_entries(&split.left_bytes, true, HashVersion::HalfMd4, &[0; 4], 0).unwrap();
353 let right_entries =
354 read_leaf_entries(&split.right_bytes, true, HashVersion::HalfMd4, &[0; 4], 0).unwrap();
355 for (h, _) in &left_entries {
356 assert!(
357 h.major < split.split_out_hash,
358 "left entry hash {} >= split_out {}",
359 h.major,
360 split.split_out_hash
361 );
362 }
363 for (h, _) in &right_entries {
364 assert!(
365 h.major >= split.split_out_hash,
366 "right entry hash {} < split_out {}",
367 h.major,
368 split.split_out_hash
369 );
370 }
371 assert!(!left_entries.is_empty());
373 assert!(!right_entries.is_empty());
374 }
375
376 #[test]
377 fn leaf_split_rejects_trivial() {
378 let leaf = make_populated_leaf(0, 4096, 0); let err = plan_leaf_split(&leaf, HashVersion::HalfMd4, &[0; 4], true, 4096, 0).unwrap_err();
381 match err {
382 Error::Corrupt(msg) => assert!(msg.contains(">=2 entries")),
383 _ => panic!("wrong error kind"),
384 }
385 }
386
387 fn leaf_of(names: &[&[u8]]) -> Vec<u8> {
390 let mut buf = vec![0u8; 4096];
391 buf[4..6].copy_from_slice(&4096u16.to_le_bytes());
392 for (i, name) in names.iter().enumerate() {
393 add_entry_to_block(
394 &mut buf,
395 100 + i as u32,
396 name,
397 DirEntryType::RegFile,
398 true,
399 0,
400 )
401 .unwrap();
402 }
403 buf
404 }
405
406 #[test]
409 fn leaf_split_does_not_divide_a_hash() {
410 let seed = [0u32; 4];
411 let leaf = leaf_of(&[b"same", b"same", b"same", b"same", b"other"]);
414 let split = plan_leaf_split(&leaf, HashVersion::HalfMd4, &seed, true, 4096, 0).unwrap();
415 let same = name_hash(b"same", HashVersion::HalfMd4, &seed).major;
416 let left = crate::dir::parse_block(&split.left_bytes, true).unwrap();
417 let right = crate::dir::parse_block(&split.right_bytes, true).unwrap();
418 let sides =
419 |entries: &[crate::dir::DirEntry]| entries.iter().filter(|e| e.name == b"same").count();
420 assert!(
421 sides(&left) == 0 || sides(&right) == 0,
422 "the four names with hash {same:#x} were divided between the leaves"
423 );
424
425 let leaf = leaf_of(&[b"same", b"same", b"same"]);
426 assert!(plan_leaf_split(&leaf, HashVersion::HalfMd4, &seed, true, 4096, 0).is_err());
427 }
428
429 fn make_dx_root(count: u16, limit: u16) -> Vec<u8> {
430 let mut buf = vec![0u8; 4096];
431 buf[0..4].copy_from_slice(&2u32.to_le_bytes());
433 buf[4..6].copy_from_slice(&12u16.to_le_bytes());
434 buf[6] = 1;
435 buf[7] = DirEntryType::Directory as u8;
436 buf[8] = b'.';
437 buf[12..16].copy_from_slice(&2u32.to_le_bytes());
439 buf[16..18].copy_from_slice(&(4096u16 - 12).to_le_bytes());
440 buf[18] = 2;
441 buf[19] = DirEntryType::Directory as u8;
442 buf[20] = b'.';
443 buf[21] = b'.';
444 buf[28] = HashVersion::HalfMd4 as u8;
446 buf[29] = 8;
447 buf[32..34].copy_from_slice(&limit.to_le_bytes());
449 buf[34..36].copy_from_slice(&count.to_le_bytes());
450 buf[36..40].copy_from_slice(&1u32.to_le_bytes());
451 for i in 1..count as usize {
453 let off = 32 + i * DxEntry::SIZE;
454 let hash: u32 = (i as u32) * 1000;
455 buf[off..off + 4].copy_from_slice(&hash.to_le_bytes());
456 buf[off + 4..off + 8].copy_from_slice(&((i as u32) + 1).to_le_bytes());
457 }
458 buf
459 }
460
461 fn entries(root: &[u8]) -> Vec<(u32, u32)> {
464 let (_, entries) = crate::htree::parse_root_entries(root).unwrap();
465 entries.iter().map(|e| (e.hash, e.block)).collect()
466 }
467
468 #[test]
469 fn dx_entry_insert_root_preserves_sort() {
470 let root = make_dx_root(3, 500);
472 let updated = plan_insert_dx_entry_root(&root, 32, 1500, 42).unwrap();
473 assert_eq!(
474 entries(&updated),
475 [(0, 1), (1000, 2), (1500, 42), (2000, 3)]
476 );
477 let limit = u16::from_le_bytes(updated[32..34].try_into().unwrap());
478 assert_eq!(limit, 500, "the limit in entry 0's hash slot is kept");
479 }
480
481 #[test]
482 fn dx_entry_insert_at_end() {
483 let root = make_dx_root(3, 500);
484 let updated = plan_insert_dx_entry_root(&root, 32, 9999, 77).unwrap();
485 assert_eq!(
486 entries(&updated),
487 [(0, 1), (1000, 2), (2000, 3), (9999, 77)]
488 );
489 }
490
491 #[test]
492 fn dx_entry_insert_rejects_full_node() {
493 let root = make_dx_root(5, 5); let err = plan_insert_dx_entry_root(&root, 32, 1234, 10).unwrap_err();
495 match err {
496 Error::CorruptExtentTree(msg) => assert!(msg.contains("NEEDS_PARENT_SPLIT")),
497 _ => panic!("wrong error kind"),
498 }
499 }
500
501 #[test]
502 fn dx_entry_insert_rejects_duplicate_hash() {
503 let root = make_dx_root(3, 500);
504 let err = plan_insert_dx_entry_root(&root, 32, 1000, 42).unwrap_err();
505 match err {
506 Error::CorruptExtentTree(msg) => assert!(msg.contains("duplicate")),
507 _ => panic!("wrong error kind"),
508 }
509 }
510}