1use std::collections::BTreeMap;
2use std::io::{Read, Seek, SeekFrom, Write};
3use std::str;
4
5use crate::engine::writer::{DatasetSpec, FillValueSpec};
6use crate::error::{Error, Result};
7use crate::format::btree_v2::BTreeV2Header;
8use crate::format::checksum::checksum_metadata;
9use crate::format::fractal_heap::FractalHeapHeader;
10use crate::format::messages::link::{LinkMessage, LinkType};
11use crate::format::messages::link_info::LinkInfoMessage;
12use crate::format::object_header::{
13 self, ObjectHeader, ObjectHeaderMessageRef, HDR_ATTR_CRT_ORDER_TRACKED,
14 HDR_ATTR_STORE_PHASE_CHANGE, HDR_CHUNK0_SIZE_MASK, HDR_STORE_TIMES, HDR_V2_KNOWN_FLAGS,
15};
16use crate::format::superblock::Superblock;
17use crate::io::reader::HdfReader;
18
19use super::MutableFile;
20
21const OBJECT_HEADER_CHUNK_DATA_LIMIT: usize = 128 * 1024;
22const MAX_DATASPACE_RANK: usize = 32;
23
24#[derive(Debug)]
25struct CompactLinkMessageLocation {
26 msg_type_offset: u64,
27 msg_data_offset: u64,
28 oh_start: u64,
29 oh_check_len: usize,
30 name_offset: usize,
31 name_size: usize,
32 link_type: LinkType,
33 hard_link_addr: Option<u64>,
34}
35
36#[derive(Debug)]
37struct ObjectRefcountLocation {
38 value_offset: u64,
39 oh_start: Option<u64>,
40 oh_check_len: Option<usize>,
41 refcount: u32,
42}
43
44#[derive(Debug)]
45struct DenseLinkLocation {
46 btree_header_addr: u64,
47 heap: FractalHeapHeader,
48 btree: BTreeV2Header,
49 creation_order_indexed: bool,
50 leaf_records: Vec<u8>,
51 record_index: usize,
52 direct_block_addr: u64,
53 direct_block_size: u64,
54 object_offset: u64,
55 raw_data: Vec<u8>,
56}
57
58#[derive(Debug)]
59enum ParentChainLinkStorage {
60 Compact,
61 Dense,
62}
63
64#[derive(Debug)]
65struct ParentChainLink {
66 ancestor_addr: u64,
67 link_name: String,
68 child_addr: u64,
69 storage: ParentChainLinkStorage,
70}
71
72impl MutableFile {
73 pub fn create_root_group_link(&mut self, name: &str) -> Result<()> {
79 self.create_compact_group_link("/", name)
80 }
81
82 pub fn create_compact_group_link(&mut self, parent_path: &str, name: &str) -> Result<()> {
83 validate_direct_child_link_name(name)?;
84 if self.superblock.version < 2 {
85 return Err(Error::Unsupported(
86 "group creation in existing files requires a v2/v3 superblock".into(),
87 ));
88 }
89
90 let child_addr = self.append_v2_object_header(&[], 0)?;
91 let link = encode_hard_link_message(name, child_addr, self.superblock.sizeof_addr)?;
92 self.append_compact_parent_link(parent_path, name, "group creation", link)
93 }
94
95 pub fn create_root_soft_link(&mut self, name: &str, target_path: &str) -> Result<()> {
97 self.create_compact_soft_link("/", name, target_path)
98 }
99
100 pub fn create_compact_soft_link(
101 &mut self,
102 parent_path: &str,
103 name: &str,
104 target_path: &str,
105 ) -> Result<()> {
106 validate_direct_child_link_name(name)?;
107 if self.superblock.version < 2 {
108 return Err(Error::Unsupported(
109 "soft-link creation in existing files requires a v2/v3 superblock".into(),
110 ));
111 }
112
113 let link = encode_soft_link_message(name, target_path)?;
114 self.append_compact_parent_link(parent_path, name, "soft-link creation", link)
115 }
116
117 pub fn create_root_external_link(
119 &mut self,
120 name: &str,
121 filename: &str,
122 object_path: &str,
123 ) -> Result<()> {
124 self.create_compact_external_link("/", name, filename, object_path)
125 }
126
127 pub fn create_compact_external_link(
128 &mut self,
129 parent_path: &str,
130 name: &str,
131 filename: &str,
132 object_path: &str,
133 ) -> Result<()> {
134 validate_direct_child_link_name(name)?;
135 if self.superblock.version < 2 {
136 return Err(Error::Unsupported(
137 "external-link creation in existing files requires a v2/v3 superblock".into(),
138 ));
139 }
140
141 let link = encode_external_link_message(name, filename, object_path)?;
142 self.append_compact_parent_link(parent_path, name, "external-link creation", link)
143 }
144
145 pub fn create_root_hard_link(&mut self, name: &str, target_addr: u64) -> Result<()> {
147 self.create_compact_hard_link("/", name, target_addr)
148 }
149
150 pub fn create_compact_hard_link(
151 &mut self,
152 parent_path: &str,
153 name: &str,
154 target_addr: u64,
155 ) -> Result<()> {
156 validate_direct_child_link_name(name)?;
157 if self.superblock.version < 2 {
158 return Err(Error::Unsupported(
159 "hard-link creation in existing files requires a v2/v3 superblock".into(),
160 ));
161 }
162
163 self.increment_object_refcount_if_present(target_addr)?;
164 let link = encode_hard_link_message(name, target_addr, self.superblock.sizeof_addr)?;
165 self.append_compact_parent_link(parent_path, name, "hard-link creation", link)
166 }
167
168 pub fn create_compact_hard_link_to_target_path(
169 &mut self,
170 parent_path: &str,
171 name: &str,
172 target_path: &str,
173 target_addr: u64,
174 ) -> Result<()> {
175 validate_direct_child_link_name(name)?;
176 if self.superblock.version < 2 {
177 return Err(Error::Unsupported(
178 "hard-link creation in existing files requires a v2/v3 superblock".into(),
179 ));
180 }
181
182 let operation = "hard-link creation";
183 let (target_parent_path, target_name) =
184 split_normalized_absolute_child_path(target_path, operation)?;
185
186 let mut path_addrs = BTreeMap::new();
187 path_addrs.insert(Vec::<String>::new(), self.superblock.root_addr);
188 self.record_compact_path_addrs(parent_path, operation, &mut path_addrs)?;
189 self.record_compact_path_addrs(target_parent_path, operation, &mut path_addrs)?;
190
191 let dest_components = owned_compact_absolute_path_components(parent_path, operation)?;
192 let target_parent_components =
193 owned_compact_absolute_path_components(target_parent_path, operation)?;
194 let target_components = owned_compact_absolute_path_components(target_path, operation)?;
195 let target_is_destination_ancestor = target_components.len() <= dest_components.len()
196 && target_components == dest_components[..target_components.len()];
197 let dest_addr = *path_addrs.get(&dest_components).ok_or_else(|| {
198 Error::InvalidFormat(format!("destination group '{parent_path}' not found"))
199 })?;
200 let target_parent_addr = *path_addrs.get(&target_parent_components).ok_or_else(|| {
201 Error::InvalidFormat(format!(
202 "target parent group '{target_parent_path}' not found"
203 ))
204 })?;
205
206 let (dest_flags, mut dest_messages) =
207 self.messages_for_append(dest_addr, name, operation)?;
208 if target_is_destination_ancestor
209 && self.find_object_refcount_location(target_addr)?.is_none()
210 {
211 return Err(Error::Unsupported(
212 "hard-link creation cannot yet materialize a refcount for an ancestor of the destination group"
213 .into(),
214 ));
215 }
216 let new_target_addr = self.ensure_explicit_object_refcount(target_addr, 2)?;
217 dest_messages.push(OwnedObjectHeaderMessage {
218 msg_type: object_header::MSG_LINK,
219 flags: 0,
220 creation_index: None,
221 data: encode_hard_link_message(name, new_target_addr, self.superblock.sizeof_addr)?,
222 });
223
224 if new_target_addr == target_addr {
225 return self.append_compact_parent_link(
226 parent_path,
227 name,
228 operation,
229 encode_hard_link_message(name, new_target_addr, self.superblock.sizeof_addr)?,
230 );
231 }
232
233 let mut modified = BTreeMap::new();
234 if target_parent_components == dest_components {
235 let (flags, messages) = self.compact_messages_replacing_and_appending_hard_link(
236 target_parent_addr,
237 target_name,
238 new_target_addr,
239 name,
240 operation,
241 )?;
242 modified.insert(target_parent_components, (flags, messages));
243 } else {
244 let (target_flags, target_messages) = self.compact_messages_replacing_hard_link(
245 target_parent_addr,
246 target_name,
247 new_target_addr,
248 operation,
249 )?;
250 modified.insert(target_parent_components, (target_flags, target_messages));
251 modified.insert(dest_components, (dest_flags, dest_messages));
252 }
253 self.append_rebuilt_compact_paths(modified, path_addrs, operation)
254 }
255
256 pub fn create_compact_same_group_hard_link(
257 &mut self,
258 parent_path: &str,
259 name: &str,
260 target_name: &str,
261 target_addr: u64,
262 ) -> Result<()> {
263 validate_direct_child_link_name(name)?;
264 validate_direct_child_link_name(target_name)?;
265 if self.superblock.version < 2 {
266 return Err(Error::Unsupported(
267 "same-group hard-link creation in existing files requires a v2/v3 superblock"
268 .into(),
269 ));
270 }
271
272 let target_addr = self.ensure_explicit_object_refcount(target_addr, 2)?;
273 self.append_compact_same_group_hard_link(parent_path, name, target_name, target_addr)
274 }
275
276 pub fn create_root_contiguous_dataset(
278 &mut self,
279 spec: &DatasetSpec<'_>,
280 fill: Option<FillValueSpec<'_>>,
281 ) -> Result<u64> {
282 self.create_compact_contiguous_dataset("/", spec, fill)
283 }
284
285 pub fn create_compact_contiguous_dataset(
286 &mut self,
287 parent_path: &str,
288 spec: &DatasetSpec<'_>,
289 fill: Option<FillValueSpec<'_>>,
290 ) -> Result<u64> {
291 validate_direct_child_link_name(spec.name)?;
292 validate_dataset_data_len(spec)?;
293 if spec.max_shape.is_some() {
294 return Err(Error::Unsupported(
295 "existing-file dataset creation does not support max dimensions yet".into(),
296 ));
297 }
298 if self.superblock.version < 2 {
299 return Err(Error::Unsupported(
300 "dataset creation in existing files requires a v2/v3 superblock".into(),
301 ));
302 }
303
304 let data_addr = if spec.data.is_empty() {
305 crate::io::reader::UNDEF_ADDR
306 } else {
307 let addr = self.append_aligned_zeros(spec.data.len(), 8)?;
308 self.write_handle.seek(SeekFrom::Start(addr))?;
309 self.write_handle.write_all(spec.data)?;
310 addr
311 };
312 let data_size = Self::usize_to_u64(spec.data.len(), "dataset data size")?;
313
314 let mut dtype_bytes = Vec::new();
315 spec.dtype.encode_into(&mut dtype_bytes)?;
316 let mut ds_bytes = Vec::new();
317 encode_dataspace_for_spec_into(&mut ds_bytes, spec)?;
318 let mut fill_value_bytes = Vec::new();
319 encode_fill_value_message_into(&mut fill_value_bytes, fill)?;
320 let mut layout_bytes = Vec::new();
321 encode_contiguous_layout_into(
322 &mut layout_bytes,
323 data_addr,
324 data_size,
325 self.superblock.sizeof_addr,
326 self.superblock.sizeof_size,
327 )?;
328
329 let messages = [
330 ObjectHeaderMessageRef {
331 msg_type: object_header::MSG_DATASPACE,
332 flags: 0,
333 creation_index: None,
334 data: &ds_bytes,
335 },
336 ObjectHeaderMessageRef {
337 msg_type: object_header::MSG_DATATYPE,
338 flags: 0,
339 creation_index: None,
340 data: &dtype_bytes,
341 },
342 ObjectHeaderMessageRef {
343 msg_type: object_header::MSG_FILL_VALUE,
344 flags: 0,
345 creation_index: None,
346 data: &fill_value_bytes,
347 },
348 ObjectHeaderMessageRef {
349 msg_type: object_header::MSG_LAYOUT,
350 flags: 0,
351 creation_index: None,
352 data: &layout_bytes,
353 },
354 ];
355 let dataset_addr = self.append_v2_object_header(&messages, 0)?;
356 let link = encode_hard_link_message(spec.name, dataset_addr, self.superblock.sizeof_addr)?;
357 self.append_compact_parent_link(parent_path, spec.name, "dataset creation", link)?;
358 Ok(dataset_addr)
359 }
360
361 fn append_compact_parent_link(
362 &mut self,
363 parent_path: &str,
364 name: &str,
365 operation: &str,
366 link: Vec<u8>,
367 ) -> Result<()> {
368 let root_addr = self.superblock.root_addr;
369 if parent_path == "/" {
370 let (root_flags, root_messages) =
371 self.messages_for_append(root_addr, name, operation)?;
372 return self.append_rebuilt_root_link_message(root_messages, root_flags, link);
373 }
374
375 let parent_chain = self.compact_or_dense_hard_link_parent_chain(parent_path, operation)?;
376 let old_parent_addr = parent_chain
377 .last()
378 .map(|link| link.child_addr)
379 .ok_or_else(|| {
380 Error::InvalidFormat(format!("parent group '{parent_path}' not found"))
381 })?;
382 let (parent_flags, mut parent_messages) =
383 self.messages_for_append(old_parent_addr, name, operation)?;
384 parent_messages.push(OwnedObjectHeaderMessage {
385 msg_type: object_header::MSG_LINK,
386 flags: 0,
387 creation_index: None,
388 data: link,
389 });
390
391 let mut new_child_addr =
392 self.append_owned_v2_object_header(&parent_messages, parent_flags)?;
393 let mut root_update = None;
394 let mut dense_update = false;
395 for link in parent_chain.iter().rev() {
396 match link.storage {
397 ParentChainLinkStorage::Compact => {
398 let (ancestor_flags, ancestor_messages) = self
399 .compact_messages_replacing_hard_link(
400 link.ancestor_addr,
401 &link.link_name,
402 new_child_addr,
403 operation,
404 )?;
405 if link.ancestor_addr == root_addr {
406 root_update = Some((ancestor_flags, ancestor_messages));
407 break;
408 }
409 new_child_addr =
410 self.append_owned_v2_object_header(&ancestor_messages, ancestor_flags)?;
411 }
412 ParentChainLinkStorage::Dense => {
413 self.rewrite_dense_hard_link_addr(
414 link.ancestor_addr,
415 &link.link_name,
416 new_child_addr,
417 operation,
418 )?;
419 dense_update = true;
420 break;
421 }
422 }
423 }
424 if dense_update {
425 let eof_addr = self.write_handle.seek(SeekFrom::End(0))?;
426 self.rewrite_v2_v3_superblock(self.superblock.root_addr, eof_addr)?;
427 self.write_handle.flush()?;
428 self.reopen_reader()?;
429 return Ok(());
430 }
431 let (root_flags, root_messages) = root_update.ok_or_else(|| {
432 Error::InvalidFormat(format!(
433 "parent group '{parent_path}' is not linked from the root group"
434 ))
435 })?;
436 self.append_rebuilt_root_link_message(root_messages, root_flags, Vec::new())
437 }
438
439 fn append_compact_same_group_hard_link(
440 &mut self,
441 parent_path: &str,
442 name: &str,
443 target_name: &str,
444 target_addr: u64,
445 ) -> Result<()> {
446 let operation = "same-group hard-link creation";
447 let root_addr = self.superblock.root_addr;
448 if parent_path == "/" {
449 let (root_flags, root_messages) = self
450 .compact_messages_replacing_and_appending_hard_link(
451 root_addr,
452 target_name,
453 target_addr,
454 name,
455 operation,
456 )?;
457 return self.append_rebuilt_root_link_message(root_messages, root_flags, Vec::new());
458 }
459
460 let parent_chain = self.compact_or_dense_hard_link_parent_chain(parent_path, operation)?;
461 let old_parent_addr = parent_chain
462 .last()
463 .map(|link| link.child_addr)
464 .ok_or_else(|| {
465 Error::InvalidFormat(format!("parent group '{parent_path}' not found"))
466 })?;
467 let (parent_flags, parent_messages) = self
468 .compact_messages_replacing_and_appending_hard_link(
469 old_parent_addr,
470 target_name,
471 target_addr,
472 name,
473 operation,
474 )?;
475
476 let mut new_child_addr =
477 self.append_owned_v2_object_header(&parent_messages, parent_flags)?;
478 let mut root_update = None;
479 let mut dense_update = false;
480 for link in parent_chain.iter().rev() {
481 match link.storage {
482 ParentChainLinkStorage::Compact => {
483 let (ancestor_flags, ancestor_messages) = self
484 .compact_messages_replacing_hard_link(
485 link.ancestor_addr,
486 &link.link_name,
487 new_child_addr,
488 operation,
489 )?;
490 if link.ancestor_addr == root_addr {
491 root_update = Some((ancestor_flags, ancestor_messages));
492 break;
493 }
494 new_child_addr =
495 self.append_owned_v2_object_header(&ancestor_messages, ancestor_flags)?;
496 }
497 ParentChainLinkStorage::Dense => {
498 self.rewrite_dense_hard_link_addr(
499 link.ancestor_addr,
500 &link.link_name,
501 new_child_addr,
502 operation,
503 )?;
504 dense_update = true;
505 break;
506 }
507 }
508 }
509 if dense_update {
510 let eof_addr = self.write_handle.seek(SeekFrom::End(0))?;
511 self.rewrite_v2_v3_superblock(self.superblock.root_addr, eof_addr)?;
512 self.write_handle.flush()?;
513 self.reopen_reader()?;
514 return Ok(());
515 }
516 let (root_flags, root_messages) = root_update.ok_or_else(|| {
517 Error::InvalidFormat(format!(
518 "parent group '{parent_path}' is not linked from the root group"
519 ))
520 })?;
521 self.append_rebuilt_root_link_message(root_messages, root_flags, Vec::new())
522 }
523
524 fn append_rebuilt_root_link_message(
525 &mut self,
526 mut root_messages: Vec<OwnedObjectHeaderMessage>,
527 root_flags: u8,
528 link: Vec<u8>,
529 ) -> Result<()> {
530 if !link.is_empty() {
531 root_messages.push(OwnedObjectHeaderMessage {
532 msg_type: object_header::MSG_LINK,
533 flags: 0,
534 creation_index: None,
535 data: link,
536 });
537 }
538
539 let new_root_addr = self.append_owned_v2_object_header(&root_messages, root_flags)?;
540 let eof_addr = self.write_handle.seek(SeekFrom::End(0))?;
541 self.rewrite_v2_v3_superblock(new_root_addr, eof_addr)?;
542 self.write_handle.flush()?;
543 self.reopen_reader()?;
544 Ok(())
545 }
546
547 pub fn unlink_group_link(&mut self, group_path: &str, name: &str) -> Result<()> {
552 let group = self.group(group_path)?;
553 let location = match self.find_compact_link_in_oh(group.addr(), name, None) {
554 Ok(location) => location,
555 Err(Error::Unsupported(msg))
556 if msg.contains("dense or creation-order indexed links") =>
557 {
558 return self.unlink_dense_link(group.addr(), name);
559 }
560 Err(err) => return Err(err),
561 };
562 if location.link_type == LinkType::Hard {
563 let target_addr = location.hard_link_addr.ok_or_else(|| {
564 Error::InvalidFormat("hard link message is missing target address".into())
565 })?;
566 if !self.decrement_object_refcount_if_present(target_addr)? {
567 let same_parent_links =
568 self.count_compact_hard_links_to_addr(group.addr(), target_addr)?;
569 if same_parent_links <= 1 {
570 return Err(Error::Unsupported(
571 "hard-link deletion without explicit object refcount requires another compact hard link to the same target in the parent group"
572 .into(),
573 ));
574 }
575 }
576 }
577 self.write_handle
578 .seek(SeekFrom::Start(location.msg_type_offset))?;
579 self.write_handle
580 .write_all(&[object_header::MSG_NIL as u8])?;
581 self.rewrite_oh_checksum(location.oh_start, location.oh_check_len)?;
582 self.write_handle.flush()?;
583 self.reopen_reader()?;
584 Ok(())
585 }
586
587 fn unlink_dense_link(&mut self, group_addr: u64, name: &str) -> Result<()> {
588 let mut location = self.find_dense_link_location(group_addr, name, None, false)?;
589 let link = compact_link_view(&location.raw_data, self.superblock.sizeof_addr)?;
590 if link.link_type == LinkType::Hard {
591 let target_addr = link.hard_link_addr.ok_or_else(|| {
592 Error::InvalidFormat("hard link message is missing target address".into())
593 })?;
594 if !self.decrement_object_refcount_if_present(target_addr)? {
595 let same_parent_links =
596 self.count_dense_hard_links_to_addr(&location, target_addr)?;
597 if same_parent_links <= 1 {
598 return Err(Error::Unsupported(
599 "dense hard-link deletion without explicit object refcount requires another dense hard link to the same target in the parent group"
600 .into(),
601 ));
602 }
603 }
604 }
605
606 let record_size = usize::from(location.btree.record_size);
607 if record_size == 0 || !location.leaf_records.len().is_multiple_of(record_size) {
608 return Err(Error::InvalidFormat(
609 "dense link records have inconsistent sizes".into(),
610 ));
611 }
612 let record_count = location.leaf_records.len() / record_size;
613 if record_count <= 1 {
614 return Err(Error::Unsupported(
615 "dense link deletion cannot collapse the final dense name-index record yet".into(),
616 ));
617 }
618 if location.record_index >= record_count {
619 return Err(Error::InvalidFormat(
620 "dense link record index is invalid".into(),
621 ));
622 }
623
624 let object_addr = location
625 .direct_block_addr
626 .checked_add(location.object_offset)
627 .ok_or_else(|| Error::InvalidFormat("dense link object address overflow".into()))?;
628 let tombstone = vec![0u8; location.raw_data.len()];
629 self.write_handle.seek(SeekFrom::Start(object_addr))?;
630 self.write_handle.write_all(&tombstone)?;
631
632 let record_start = location
633 .record_index
634 .checked_mul(record_size)
635 .ok_or_else(|| Error::InvalidFormat("dense link record offset overflow".into()))?;
636 let record_end = record_start
637 .checked_add(record_size)
638 .ok_or_else(|| Error::InvalidFormat("dense link record offset overflow".into()))?;
639 location.leaf_records.drain(record_start..record_end);
640
641 self.rewrite_dense_link_direct_block_checksum(
642 &location.heap,
643 location.direct_block_addr,
644 location.direct_block_size,
645 object_addr,
646 &tombstone,
647 )?;
648 self.rewrite_dense_link_name_index(&location)?;
649 self.write_handle.flush()?;
650 self.reopen_reader()?;
651 Ok(())
652 }
653
654 fn count_dense_hard_links_to_addr(
655 &self,
656 location: &DenseLinkLocation,
657 target_addr: u64,
658 ) -> Result<usize> {
659 let record_size = usize::from(location.btree.record_size);
660 if record_size == 0 || !location.leaf_records.len().is_multiple_of(record_size) {
661 return Err(Error::InvalidFormat(
662 "dense link records have inconsistent sizes".into(),
663 ));
664 }
665
666 let heap_id_len = usize::from(location.heap.heap_id_len);
667 let mut guard = self.inner.lock();
668 let mut count = 0usize;
669 for record in location.leaf_records.chunks_exact(record_size) {
670 let heap_id = checked_window(record, 4, heap_id_len, "dense link heap ID")?;
671 let raw_data = location
672 .heap
673 .read_managed_object(&mut guard.reader, heap_id)?;
674 let link = compact_link_view(&raw_data, guard.superblock.sizeof_addr)?;
675 if link.link_type == LinkType::Hard && link.hard_link_addr == Some(target_addr) {
676 count += 1;
677 }
678 }
679 Ok(count)
680 }
681
682 pub fn rename_group_link(
687 &mut self,
688 group_path: &str,
689 old_name: &str,
690 new_name: &str,
691 ) -> Result<()> {
692 if new_name.is_empty() {
693 return Err(Error::InvalidFormat("link name cannot be empty".into()));
694 }
695 if old_name == new_name {
696 return Ok(());
697 }
698
699 let group = self.group(group_path)?;
700 let location = match self.find_compact_link_in_oh(group.addr(), old_name, Some(new_name)) {
701 Ok(location) => location,
702 Err(Error::Unsupported(msg))
703 if msg.contains("dense or creation-order indexed links") =>
704 {
705 return self.rename_dense_link(group_path, group.addr(), old_name, new_name);
706 }
707 Err(err) => return Err(err),
708 };
709 if new_name.len() != location.name_size {
710 if group_path == "/" {
711 return self.rename_root_compact_link_by_rebuild(old_name, new_name);
712 }
713 return self.rename_nested_link_by_rebuild(group_path, old_name, new_name);
714 }
715
716 let name_offset_u64 = Self::usize_to_u64(location.name_offset, "link name offset")?;
717 let file_name_offset = location
718 .msg_data_offset
719 .checked_add(name_offset_u64)
720 .ok_or_else(|| Error::InvalidFormat("link name offset overflow".into()))?;
721 self.write_handle.seek(SeekFrom::Start(file_name_offset))?;
722 self.write_handle.write_all(new_name.as_bytes())?;
723 self.rewrite_oh_checksum(location.oh_start, location.oh_check_len)?;
724 self.write_handle.flush()?;
725 self.reopen_reader()?;
726 Ok(())
727 }
728
729 pub fn move_group_link(
730 &mut self,
731 source_group_path: &str,
732 old_name: &str,
733 dest_group_path: &str,
734 new_name: &str,
735 ) -> Result<()> {
736 validate_direct_child_link_name(old_name)?;
737 validate_direct_child_link_name(new_name)?;
738 if source_group_path == dest_group_path {
739 return self.rename_group_link(source_group_path, old_name, new_name);
740 }
741 if self.superblock.version < 2 {
742 return Err(Error::Unsupported(
743 "cross-group compact relink requires a v2/v3 superblock".into(),
744 ));
745 }
746
747 let operation = "cross-group compact relink";
748 let mut path_addrs = BTreeMap::new();
749 path_addrs.insert(Vec::<String>::new(), self.superblock.root_addr);
750 self.record_compact_path_addrs(source_group_path, operation, &mut path_addrs)?;
751 self.record_compact_path_addrs(dest_group_path, operation, &mut path_addrs)?;
752
753 let source_components =
754 owned_compact_absolute_path_components(source_group_path, operation)?;
755 let dest_components = owned_compact_absolute_path_components(dest_group_path, operation)?;
756 let source_addr = *path_addrs.get(&source_components).ok_or_else(|| {
757 Error::InvalidFormat(format!("source group '{source_group_path}' not found"))
758 })?;
759 let dest_addr = *path_addrs.get(&dest_components).ok_or_else(|| {
760 Error::InvalidFormat(format!("destination group '{dest_group_path}' not found"))
761 })?;
762
763 let (source_flags, source_messages, moved_link) =
764 self.messages_removing_link(source_addr, old_name, new_name, operation)?;
765 let (dest_flags, mut dest_messages) =
766 self.messages_for_append(dest_addr, new_name, operation)?;
767 dest_messages.push(OwnedObjectHeaderMessage {
768 msg_type: object_header::MSG_LINK,
769 flags: 0,
770 creation_index: None,
771 data: moved_link,
772 });
773
774 let mut modified = BTreeMap::new();
775 modified.insert(source_components, (source_flags, source_messages));
776 modified.insert(dest_components, (dest_flags, dest_messages));
777 self.append_rebuilt_compact_paths(modified, path_addrs, operation)
778 }
779
780 fn rename_root_compact_link_by_rebuild(
781 &mut self,
782 old_name: &str,
783 new_name: &str,
784 ) -> Result<()> {
785 validate_direct_child_link_name(old_name)?;
786 validate_direct_child_link_name(new_name)?;
787 if self.superblock.version < 2 {
788 return Err(Error::Unsupported(
789 "root compact relink with changed name length requires a v2/v3 superblock".into(),
790 ));
791 }
792
793 let root_addr = self.superblock.root_addr;
794 let mut guard = self.inner.lock();
795 let sizeof_addr = guard.superblock.sizeof_addr;
796 let oh = ObjectHeader::read_at(&mut guard.reader, root_addr)?;
797 if oh.version != 2 {
798 return Err(Error::Unsupported(
799 "root compact relink with changed name length currently supports only v2 object headers"
800 .into(),
801 ));
802 }
803 if oh.flags & HDR_ATTR_CRT_ORDER_TRACKED != 0 {
804 return Err(Error::Unsupported(
805 "root compact relink with creation-order tracking is not implemented".into(),
806 ));
807 }
808
809 let mut messages = Vec::new();
810 let mut renamed_link = None;
811 for msg in &oh.messages {
812 match msg.msg_type {
813 object_header::MSG_LINK => {
814 let link = LinkMessage::decode(&msg.data, sizeof_addr)?;
815 if link.name == new_name {
816 return Err(Error::InvalidFormat(format!(
817 "link '{new_name}' already exists"
818 )));
819 }
820 if link.name == old_name {
821 if renamed_link.is_some() {
822 return Err(Error::InvalidFormat(format!(
823 "link '{old_name}' appears more than once"
824 )));
825 }
826 renamed_link =
827 Some(encode_renamed_link_message(&link, new_name, sizeof_addr)?);
828 } else {
829 messages.push(OwnedObjectHeaderMessage::from_raw(msg));
830 }
831 }
832 object_header::MSG_LINK_INFO => {
833 let link_info = LinkInfoMessage::decode(&msg.data, sizeof_addr)?;
834 if link_info.has_dense_storage() || link_info.corder_btree_addr.is_some() {
835 return Err(Error::Unsupported(
836 "root compact relink for dense or creation-order indexed links is not implemented"
837 .into(),
838 ));
839 }
840 messages.push(OwnedObjectHeaderMessage::from_raw(msg));
841 }
842 object_header::MSG_SYMBOL_TABLE => {
843 return Err(Error::Unsupported(
844 "root compact relink for v1 symbol-table groups is not implemented".into(),
845 ));
846 }
847 object_header::MSG_HEADER_CONTINUATION | object_header::MSG_NIL => {}
848 _ => messages.push(OwnedObjectHeaderMessage::from_raw(msg)),
849 }
850 }
851 drop(guard);
852
853 let renamed_link = renamed_link
854 .ok_or_else(|| Error::InvalidFormat(format!("link '{old_name}' not found")))?;
855 self.append_rebuilt_root_link_message(messages, oh.flags, renamed_link)
856 }
857
858 fn rename_nested_link_by_rebuild(
859 &mut self,
860 group_path: &str,
861 old_name: &str,
862 new_name: &str,
863 ) -> Result<()> {
864 validate_direct_child_link_name(old_name)?;
865 validate_direct_child_link_name(new_name)?;
866 if self.superblock.version < 2 {
867 return Err(Error::Unsupported(
868 "nested compact relink with changed name length requires a v2/v3 superblock".into(),
869 ));
870 }
871
872 let operation = "nested compact relink";
873 let root_addr = self.superblock.root_addr;
874 let parent_chain = self.compact_or_dense_hard_link_parent_chain(group_path, operation)?;
875 let old_group_addr = parent_chain
876 .last()
877 .map(|link| link.child_addr)
878 .ok_or_else(|| {
879 Error::InvalidFormat(format!("group '{group_path}' is not linked from the root"))
880 })?;
881 let (group_flags, group_messages) =
882 self.messages_renaming_link(old_group_addr, old_name, new_name, operation)?;
883
884 let mut new_child_addr =
885 self.append_owned_v2_object_header(&group_messages, group_flags)?;
886 let mut root_update = None;
887 let mut dense_update = false;
888 for link in parent_chain.iter().rev() {
889 match link.storage {
890 ParentChainLinkStorage::Compact => {
891 let (ancestor_flags, ancestor_messages) = self
892 .compact_messages_replacing_hard_link(
893 link.ancestor_addr,
894 &link.link_name,
895 new_child_addr,
896 operation,
897 )?;
898 if link.ancestor_addr == root_addr {
899 root_update = Some((ancestor_flags, ancestor_messages));
900 break;
901 }
902 new_child_addr =
903 self.append_owned_v2_object_header(&ancestor_messages, ancestor_flags)?;
904 }
905 ParentChainLinkStorage::Dense => {
906 self.rewrite_dense_hard_link_addr(
907 link.ancestor_addr,
908 &link.link_name,
909 new_child_addr,
910 operation,
911 )?;
912 dense_update = true;
913 break;
914 }
915 }
916 }
917 if dense_update {
918 let eof_addr = self.write_handle.seek(SeekFrom::End(0))?;
919 self.rewrite_v2_v3_superblock(self.superblock.root_addr, eof_addr)?;
920 self.write_handle.flush()?;
921 self.reopen_reader()?;
922 return Ok(());
923 }
924 let (root_flags, root_messages) = root_update.ok_or_else(|| {
925 Error::InvalidFormat(format!(
926 "group '{group_path}' is not linked from the root group"
927 ))
928 })?;
929 self.append_rebuilt_root_link_message(root_messages, root_flags, Vec::new())
930 }
931
932 fn compact_messages_renaming_link(
933 &self,
934 group_addr: u64,
935 old_name: &str,
936 new_name: &str,
937 operation: &str,
938 ) -> Result<(u8, Vec<OwnedObjectHeaderMessage>)> {
939 let mut guard = self.inner.lock();
940 let sizeof_addr = guard.superblock.sizeof_addr;
941 let oh = ObjectHeader::read_at(&mut guard.reader, group_addr)?;
942 if oh.version != 2 {
943 return Err(Error::Unsupported(format!(
944 "{operation} currently supports only v2 object headers"
945 )));
946 }
947 if oh.flags & HDR_ATTR_CRT_ORDER_TRACKED != 0 {
948 return Err(Error::Unsupported(format!(
949 "{operation} with object-header creation-order tracking is not implemented"
950 )));
951 }
952
953 let mut renamed = false;
954 let mut messages = Vec::new();
955 for msg in &oh.messages {
956 match msg.msg_type {
957 object_header::MSG_LINK => {
958 let link = LinkMessage::decode(&msg.data, sizeof_addr)?;
959 if link.name == new_name {
960 return Err(Error::InvalidFormat(format!(
961 "link '{new_name}' already exists"
962 )));
963 }
964 if link.name == old_name {
965 if renamed {
966 return Err(Error::InvalidFormat(format!(
967 "link '{old_name}' appears more than once"
968 )));
969 }
970 messages.push(OwnedObjectHeaderMessage {
971 msg_type: object_header::MSG_LINK,
972 flags: msg.flags,
973 creation_index: msg.creation_index,
974 data: encode_renamed_link_message(&link, new_name, sizeof_addr)?,
975 });
976 renamed = true;
977 } else {
978 messages.push(OwnedObjectHeaderMessage::from_raw(msg));
979 }
980 }
981 object_header::MSG_LINK_INFO => {
982 let link_info = LinkInfoMessage::decode(&msg.data, sizeof_addr)?;
983 if link_info.has_dense_storage() || link_info.corder_btree_addr.is_some() {
984 return Err(Error::Unsupported(format!(
985 "{operation} for dense or creation-order indexed links is not implemented"
986 )));
987 }
988 messages.push(OwnedObjectHeaderMessage::from_raw(msg));
989 }
990 object_header::MSG_SYMBOL_TABLE => {
991 return Err(Error::Unsupported(format!(
992 "{operation} for v1 symbol-table groups is not implemented"
993 )));
994 }
995 object_header::MSG_HEADER_CONTINUATION | object_header::MSG_NIL => {}
996 _ => messages.push(OwnedObjectHeaderMessage::from_raw(msg)),
997 }
998 }
999 if !renamed {
1000 return Err(Error::InvalidFormat(format!("link '{old_name}' not found")));
1001 }
1002 Ok((oh.flags, messages))
1003 }
1004
1005 fn messages_renaming_link(
1006 &self,
1007 group_addr: u64,
1008 old_name: &str,
1009 new_name: &str,
1010 operation: &str,
1011 ) -> Result<(u8, Vec<OwnedObjectHeaderMessage>)> {
1012 match self.compact_messages_renaming_link(group_addr, old_name, new_name, operation) {
1013 Err(Error::Unsupported(msg))
1014 if msg.contains("dense or creation-order indexed links") =>
1015 {
1016 self.compact_messages_from_dense_links_renaming_link(
1017 group_addr, old_name, new_name, operation,
1018 )
1019 }
1020 other => other,
1021 }
1022 }
1023
1024 fn compact_messages_from_dense_links_renaming_link(
1025 &self,
1026 group_addr: u64,
1027 old_name: &str,
1028 new_name: &str,
1029 operation: &str,
1030 ) -> Result<(u8, Vec<OwnedObjectHeaderMessage>)> {
1031 let mut guard = self.inner.lock();
1032 let sizeof_addr = guard.superblock.sizeof_addr;
1033 let oh = ObjectHeader::read_at(&mut guard.reader, group_addr)?;
1034 if oh.version != 2 {
1035 return Err(Error::Unsupported(format!(
1036 "{operation} currently supports only v2 object headers"
1037 )));
1038 }
1039 if oh.flags & HDR_ATTR_CRT_ORDER_TRACKED != 0 {
1040 return Err(Error::Unsupported(format!(
1041 "{operation} with object-header creation-order tracking is not implemented"
1042 )));
1043 }
1044
1045 let mut renamed = false;
1046 let mut messages = Vec::new();
1047 for msg in &oh.messages {
1048 match msg.msg_type {
1049 object_header::MSG_LINK => {
1050 let link = LinkMessage::decode(&msg.data, sizeof_addr)?;
1051 if link.name == new_name {
1052 return Err(Error::InvalidFormat(format!(
1053 "link '{new_name}' already exists"
1054 )));
1055 }
1056 if link.name == old_name {
1057 if renamed {
1058 return Err(Error::InvalidFormat(format!(
1059 "link '{old_name}' appears more than once"
1060 )));
1061 }
1062 messages.push(OwnedObjectHeaderMessage {
1063 msg_type: object_header::MSG_LINK,
1064 flags: msg.flags,
1065 creation_index: msg.creation_index,
1066 data: encode_renamed_link_message(&link, new_name, sizeof_addr)?,
1067 });
1068 renamed = true;
1069 } else {
1070 messages.push(OwnedObjectHeaderMessage::from_raw(msg));
1071 }
1072 }
1073 object_header::MSG_LINK_INFO => {
1074 let link_info = LinkInfoMessage::decode(&msg.data, sizeof_addr)?;
1075 if link_info.corder_btree_addr.is_some() {
1076 return Err(Error::Unsupported(format!(
1077 "{operation} for creation-order indexed links is not implemented"
1078 )));
1079 }
1080 if !link_info.has_dense_storage() {
1081 messages.push(OwnedObjectHeaderMessage::from_raw(msg));
1082 continue;
1083 }
1084
1085 let heap =
1086 FractalHeapHeader::read_at(&mut guard.reader, link_info.fractal_heap_addr)?;
1087 if heap.io_filter_len != 0 {
1088 return Err(Error::Unsupported(
1089 "mutating filtered dense link heaps is not implemented".into(),
1090 ));
1091 }
1092 let btree =
1093 BTreeV2Header::read_at(&mut guard.reader, link_info.name_btree_addr)?;
1094 if btree.depth != 0 {
1095 return Err(Error::Unsupported(
1096 "mutating non-leaf dense link name indexes is not implemented".into(),
1097 ));
1098 }
1099 let mut leaf_records = Vec::new();
1100 Self::read_dense_link_leaf_records_into(
1101 &mut guard.reader,
1102 &btree,
1103 &mut leaf_records,
1104 )?;
1105 let heap_id_len = usize::from(heap.heap_id_len);
1106 let record_size = usize::from(btree.record_size);
1107 for record in leaf_records.chunks_exact(record_size) {
1108 let heap_id = checked_window(record, 4, heap_id_len, "dense link heap ID")?;
1109 let raw_data = heap.read_managed_object(&mut guard.reader, heap_id)?;
1110 let link = LinkMessage::decode(&raw_data, sizeof_addr)?;
1111 if link.name == new_name {
1112 return Err(Error::InvalidFormat(format!(
1113 "link '{new_name}' already exists"
1114 )));
1115 }
1116 if link.name == old_name {
1117 if renamed {
1118 return Err(Error::InvalidFormat(format!(
1119 "link '{old_name}' appears more than once"
1120 )));
1121 }
1122 messages.push(OwnedObjectHeaderMessage {
1123 msg_type: object_header::MSG_LINK,
1124 flags: 0,
1125 creation_index: None,
1126 data: encode_renamed_link_message(&link, new_name, sizeof_addr)?,
1127 });
1128 renamed = true;
1129 } else {
1130 messages.push(OwnedObjectHeaderMessage {
1131 msg_type: object_header::MSG_LINK,
1132 flags: 0,
1133 creation_index: None,
1134 data: raw_data,
1135 });
1136 }
1137 }
1138 }
1139 object_header::MSG_SYMBOL_TABLE => {
1140 return Err(Error::Unsupported(format!(
1141 "{operation} for v1 symbol-table groups is not implemented"
1142 )));
1143 }
1144 object_header::MSG_HEADER_CONTINUATION | object_header::MSG_NIL => {}
1145 _ => messages.push(OwnedObjectHeaderMessage::from_raw(msg)),
1146 }
1147 }
1148 if !renamed {
1149 return Err(Error::InvalidFormat(format!("link '{old_name}' not found")));
1150 }
1151 Ok((oh.flags, messages))
1152 }
1153
1154 fn compact_messages_removing_link(
1155 &self,
1156 group_addr: u64,
1157 old_name: &str,
1158 new_name: &str,
1159 operation: &str,
1160 ) -> Result<(u8, Vec<OwnedObjectHeaderMessage>, Vec<u8>)> {
1161 let mut guard = self.inner.lock();
1162 let sizeof_addr = guard.superblock.sizeof_addr;
1163 let oh = ObjectHeader::read_at(&mut guard.reader, group_addr)?;
1164 if oh.version != 2 {
1165 return Err(Error::Unsupported(format!(
1166 "{operation} currently supports only v2 object headers"
1167 )));
1168 }
1169 if oh.flags & HDR_ATTR_CRT_ORDER_TRACKED != 0 {
1170 return Err(Error::Unsupported(format!(
1171 "{operation} with object-header creation-order tracking is not implemented"
1172 )));
1173 }
1174
1175 let mut removed_link = None;
1176 let mut messages = Vec::new();
1177 for msg in &oh.messages {
1178 match msg.msg_type {
1179 object_header::MSG_LINK => {
1180 let link = LinkMessage::decode(&msg.data, sizeof_addr)?;
1181 if link.name == old_name {
1182 if removed_link.is_some() {
1183 return Err(Error::InvalidFormat(format!(
1184 "link '{old_name}' appears more than once"
1185 )));
1186 }
1187 removed_link =
1188 Some(encode_renamed_link_message(&link, new_name, sizeof_addr)?);
1189 } else {
1190 messages.push(OwnedObjectHeaderMessage::from_raw(msg));
1191 }
1192 }
1193 object_header::MSG_LINK_INFO => {
1194 let link_info = LinkInfoMessage::decode(&msg.data, sizeof_addr)?;
1195 if link_info.has_dense_storage() || link_info.corder_btree_addr.is_some() {
1196 return Err(Error::Unsupported(format!(
1197 "{operation} for dense or creation-order indexed links is not implemented"
1198 )));
1199 }
1200 messages.push(OwnedObjectHeaderMessage::from_raw(msg));
1201 }
1202 object_header::MSG_SYMBOL_TABLE => {
1203 return Err(Error::Unsupported(format!(
1204 "{operation} for v1 symbol-table groups is not implemented"
1205 )));
1206 }
1207 object_header::MSG_HEADER_CONTINUATION | object_header::MSG_NIL => {}
1208 _ => messages.push(OwnedObjectHeaderMessage::from_raw(msg)),
1209 }
1210 }
1211
1212 let removed_link = removed_link
1213 .ok_or_else(|| Error::InvalidFormat(format!("link '{old_name}' not found")))?;
1214 Ok((oh.flags, messages, removed_link))
1215 }
1216
1217 fn messages_removing_link(
1218 &self,
1219 group_addr: u64,
1220 old_name: &str,
1221 new_name: &str,
1222 operation: &str,
1223 ) -> Result<(u8, Vec<OwnedObjectHeaderMessage>, Vec<u8>)> {
1224 match self.compact_messages_removing_link(group_addr, old_name, new_name, operation) {
1225 Err(Error::Unsupported(msg))
1226 if msg.contains("dense or creation-order indexed links") =>
1227 {
1228 self.compact_messages_from_dense_links_removing_link(
1229 group_addr, old_name, new_name, operation,
1230 )
1231 }
1232 other => other,
1233 }
1234 }
1235
1236 fn compact_messages_from_dense_links_removing_link(
1237 &self,
1238 group_addr: u64,
1239 old_name: &str,
1240 new_name: &str,
1241 operation: &str,
1242 ) -> Result<(u8, Vec<OwnedObjectHeaderMessage>, Vec<u8>)> {
1243 let mut guard = self.inner.lock();
1244 let sizeof_addr = guard.superblock.sizeof_addr;
1245 let oh = ObjectHeader::read_at(&mut guard.reader, group_addr)?;
1246 if oh.version != 2 {
1247 return Err(Error::Unsupported(format!(
1248 "{operation} currently supports only v2 object headers"
1249 )));
1250 }
1251 if oh.flags & HDR_ATTR_CRT_ORDER_TRACKED != 0 {
1252 return Err(Error::Unsupported(format!(
1253 "{operation} with object-header creation-order tracking is not implemented"
1254 )));
1255 }
1256
1257 let mut removed_link = None;
1258 let mut messages = Vec::new();
1259 for msg in &oh.messages {
1260 match msg.msg_type {
1261 object_header::MSG_LINK => {
1262 let link = LinkMessage::decode(&msg.data, sizeof_addr)?;
1263 if link.name == old_name {
1264 if removed_link.is_some() {
1265 return Err(Error::InvalidFormat(format!(
1266 "link '{old_name}' appears more than once"
1267 )));
1268 }
1269 removed_link =
1270 Some(encode_renamed_link_message(&link, new_name, sizeof_addr)?);
1271 } else {
1272 messages.push(OwnedObjectHeaderMessage::from_raw(msg));
1273 }
1274 }
1275 object_header::MSG_LINK_INFO => {
1276 let link_info = LinkInfoMessage::decode(&msg.data, sizeof_addr)?;
1277 if link_info.corder_btree_addr.is_some() {
1278 return Err(Error::Unsupported(format!(
1279 "{operation} for creation-order indexed links is not implemented"
1280 )));
1281 }
1282 if !link_info.has_dense_storage() {
1283 messages.push(OwnedObjectHeaderMessage::from_raw(msg));
1284 continue;
1285 }
1286
1287 let heap =
1288 FractalHeapHeader::read_at(&mut guard.reader, link_info.fractal_heap_addr)?;
1289 if heap.io_filter_len != 0 {
1290 return Err(Error::Unsupported(
1291 "mutating filtered dense link heaps is not implemented".into(),
1292 ));
1293 }
1294 let btree =
1295 BTreeV2Header::read_at(&mut guard.reader, link_info.name_btree_addr)?;
1296 if btree.depth != 0 {
1297 return Err(Error::Unsupported(
1298 "mutating non-leaf dense link name indexes is not implemented".into(),
1299 ));
1300 }
1301 let mut leaf_records = Vec::new();
1302 Self::read_dense_link_leaf_records_into(
1303 &mut guard.reader,
1304 &btree,
1305 &mut leaf_records,
1306 )?;
1307 let heap_id_len = usize::from(heap.heap_id_len);
1308 let record_size = usize::from(btree.record_size);
1309 for record in leaf_records.chunks_exact(record_size) {
1310 let heap_id = checked_window(record, 4, heap_id_len, "dense link heap ID")?;
1311 let raw_data = heap.read_managed_object(&mut guard.reader, heap_id)?;
1312 let link = LinkMessage::decode(&raw_data, sizeof_addr)?;
1313 if link.name == old_name {
1314 if removed_link.is_some() {
1315 return Err(Error::InvalidFormat(format!(
1316 "link '{old_name}' appears more than once"
1317 )));
1318 }
1319 removed_link =
1320 Some(encode_renamed_link_message(&link, new_name, sizeof_addr)?);
1321 } else {
1322 messages.push(OwnedObjectHeaderMessage {
1323 msg_type: object_header::MSG_LINK,
1324 flags: 0,
1325 creation_index: None,
1326 data: raw_data,
1327 });
1328 }
1329 }
1330 }
1331 object_header::MSG_SYMBOL_TABLE => {
1332 return Err(Error::Unsupported(format!(
1333 "{operation} for v1 symbol-table groups is not implemented"
1334 )));
1335 }
1336 object_header::MSG_HEADER_CONTINUATION | object_header::MSG_NIL => {}
1337 _ => messages.push(OwnedObjectHeaderMessage::from_raw(msg)),
1338 }
1339 }
1340
1341 let removed_link = removed_link
1342 .ok_or_else(|| Error::InvalidFormat(format!("link '{old_name}' not found")))?;
1343 Ok((oh.flags, messages, removed_link))
1344 }
1345
1346 fn record_compact_path_addrs(
1347 &self,
1348 parent_path: &str,
1349 operation: &str,
1350 path_addrs: &mut BTreeMap<Vec<String>, u64>,
1351 ) -> Result<()> {
1352 let mut components = Vec::new();
1353 let mut ancestor_addr = self.superblock.root_addr;
1354 for name in owned_compact_absolute_path_components(parent_path, operation)? {
1355 let child_addr = self.compact_hard_link_addr(ancestor_addr, &name, operation)?;
1356 components.push(name);
1357 path_addrs.insert(components.clone(), child_addr);
1358 ancestor_addr = child_addr;
1359 }
1360 Ok(())
1361 }
1362
1363 fn append_rebuilt_compact_paths(
1364 &mut self,
1365 mut modified: BTreeMap<Vec<String>, (u8, Vec<OwnedObjectHeaderMessage>)>,
1366 path_addrs: BTreeMap<Vec<String>, u64>,
1367 operation: &str,
1368 ) -> Result<()> {
1369 let max_depth = modified.keys().map(Vec::len).max().unwrap_or(0);
1370 let mut replacements: BTreeMap<Vec<String>, Vec<(String, u64)>> = BTreeMap::new();
1371
1372 for depth in (1..=max_depth).rev() {
1373 let paths: Vec<Vec<String>> = modified
1374 .keys()
1375 .filter(|path| path.len() == depth)
1376 .cloned()
1377 .collect();
1378 for path in paths {
1379 let (flags, messages) = modified.remove(&path).ok_or_else(|| {
1380 Error::InvalidFormat("compact path rebuild state is inconsistent".into())
1381 })?;
1382 let new_addr = self.append_owned_v2_object_header(&messages, flags)?;
1383 let link_name = path.last().cloned().ok_or_else(|| {
1384 Error::InvalidFormat("compact path rebuild missing link name".into())
1385 })?;
1386 let parent_path = path[..path.len() - 1].to_vec();
1387 replacements
1388 .entry(parent_path.clone())
1389 .or_default()
1390 .push((link_name, new_addr));
1391
1392 let Some(parent_replacements) = replacements.remove(&parent_path) else {
1393 continue;
1394 };
1395 let parent_addr = *path_addrs.get(&parent_path).ok_or_else(|| {
1396 Error::InvalidFormat(format!(
1397 "{operation} parent path is not linked from the root group"
1398 ))
1399 })?;
1400 let (parent_flags, parent_messages) =
1401 if let Some((flags, messages)) = modified.remove(&parent_path) {
1402 replace_hard_links_in_messages(
1403 messages,
1404 flags,
1405 &parent_replacements,
1406 self.superblock.sizeof_addr,
1407 operation,
1408 )?
1409 } else {
1410 self.compact_messages_replacing_hard_links(
1411 parent_addr,
1412 &parent_replacements,
1413 operation,
1414 )?
1415 };
1416 modified.insert(parent_path, (parent_flags, parent_messages));
1417 }
1418 }
1419
1420 let (root_flags, root_messages) =
1421 modified.remove(&Vec::<String>::new()).ok_or_else(|| {
1422 Error::InvalidFormat(format!(
1423 "{operation} did not produce a rebuilt root object header"
1424 ))
1425 })?;
1426 self.append_rebuilt_root_link_message(root_messages, root_flags, Vec::new())
1427 }
1428
1429 fn rename_dense_link(
1430 &mut self,
1431 group_path: &str,
1432 group_addr: u64,
1433 old_name: &str,
1434 new_name: &str,
1435 ) -> Result<()> {
1436 let mut location =
1437 self.find_dense_link_location(group_addr, old_name, Some(new_name), true)?;
1438 let link = compact_link_view(&location.raw_data, self.superblock.sizeof_addr)?;
1439 let name_offset = link.name_offset;
1440 let name_size = link.name_size;
1441 if new_name.len() != name_size {
1442 if location.creation_order_indexed {
1443 return Err(Error::Unsupported(
1444 "dense relink with changed message size for creation-order indexed links is not implemented"
1445 .into(),
1446 ));
1447 }
1448 let link = LinkMessage::decode(&location.raw_data, self.superblock.sizeof_addr)?;
1449 let mut renamed =
1450 encode_renamed_link_message(&link, new_name, self.superblock.sizeof_addr)?;
1451 if renamed.len() > location.raw_data.len() {
1452 if group_path == "/" {
1453 let operation = "root dense relink";
1454 let (root_flags, root_messages) =
1455 self.messages_renaming_link(group_addr, old_name, new_name, operation)?;
1456 return self.append_rebuilt_root_link_message(
1457 root_messages,
1458 root_flags,
1459 Vec::new(),
1460 );
1461 }
1462 return self.rename_nested_link_by_rebuild(group_path, old_name, new_name);
1463 }
1464 renamed.resize(location.raw_data.len(), 0);
1465
1466 let object_addr = location
1467 .direct_block_addr
1468 .checked_add(location.object_offset)
1469 .ok_or_else(|| Error::InvalidFormat("dense link object address overflow".into()))?;
1470 self.write_handle.seek(SeekFrom::Start(object_addr))?;
1471 self.write_handle.write_all(&renamed)?;
1472
1473 let record_size = usize::from(location.btree.record_size);
1474 let record_start = location
1475 .record_index
1476 .checked_mul(record_size)
1477 .ok_or_else(|| Error::InvalidFormat("dense link record offset overflow".into()))?;
1478 let record_end = record_start
1479 .checked_add(record_size)
1480 .ok_or_else(|| Error::InvalidFormat("dense link record offset overflow".into()))?;
1481 let record = location
1482 .leaf_records
1483 .get_mut(record_start..record_end)
1484 .ok_or_else(|| Error::InvalidFormat("dense link record index is invalid".into()))?;
1485 record[..4].copy_from_slice(&dense_link_name_hash(new_name).to_le_bytes());
1486 Self::reposition_dense_link_record_by_hash(
1487 &mut location.leaf_records,
1488 location.record_index,
1489 record_size,
1490 )?;
1491
1492 self.rewrite_dense_link_direct_block_checksum(
1493 &location.heap,
1494 location.direct_block_addr,
1495 location.direct_block_size,
1496 object_addr,
1497 &renamed,
1498 )?;
1499 self.rewrite_dense_link_name_index(&location)?;
1500 self.write_handle.flush()?;
1501 self.reopen_reader()?;
1502 return Ok(());
1503 }
1504
1505 let name_offset_u64 = Self::usize_to_u64(name_offset, "dense link name offset")?;
1506 let file_name_offset = location
1507 .direct_block_addr
1508 .checked_add(location.object_offset)
1509 .and_then(|offset| offset.checked_add(name_offset_u64))
1510 .ok_or_else(|| Error::InvalidFormat("dense link name offset overflow".into()))?;
1511 let encoded_name =
1512 encode_link_name_in_place(&mut location.raw_data, name_offset, name_size, new_name)?;
1513 let encoded_name = encoded_name.to_vec();
1514 self.write_handle.seek(SeekFrom::Start(file_name_offset))?;
1515 self.write_handle.write_all(&encoded_name)?;
1516
1517 let record_size = usize::from(location.btree.record_size);
1518 let record_start = location
1519 .record_index
1520 .checked_mul(record_size)
1521 .ok_or_else(|| Error::InvalidFormat("dense link record offset overflow".into()))?;
1522 let record_end = record_start
1523 .checked_add(record_size)
1524 .ok_or_else(|| Error::InvalidFormat("dense link record offset overflow".into()))?;
1525 let record = location
1526 .leaf_records
1527 .get_mut(record_start..record_end)
1528 .ok_or_else(|| Error::InvalidFormat("dense link record index is invalid".into()))?;
1529 record[..4].copy_from_slice(&dense_link_name_hash(new_name).to_le_bytes());
1530 Self::reposition_dense_link_record_by_hash(
1531 &mut location.leaf_records,
1532 location.record_index,
1533 record_size,
1534 )?;
1535
1536 self.rewrite_dense_link_direct_block_checksum(
1537 &location.heap,
1538 location.direct_block_addr,
1539 location.direct_block_size,
1540 file_name_offset,
1541 &encoded_name,
1542 )?;
1543 self.rewrite_dense_link_name_index(&location)?;
1544 self.write_handle.flush()?;
1545 self.reopen_reader()?;
1546 Ok(())
1547 }
1548
1549 fn rewrite_dense_hard_link_addr(
1550 &mut self,
1551 group_addr: u64,
1552 name: &str,
1553 new_addr: u64,
1554 operation: &str,
1555 ) -> Result<()> {
1556 let mut location = self.find_dense_link_location(group_addr, name, None, false)?;
1557 let link = compact_link_view(&location.raw_data, self.superblock.sizeof_addr)?;
1558 if link.link_type != LinkType::Hard {
1559 return Err(Error::Unsupported(format!(
1560 "{operation} address propagation requires hard-link ancestors"
1561 )));
1562 }
1563 let addr_offset = link.hard_link_addr_offset.ok_or_else(|| {
1564 Error::InvalidFormat("hard link message is missing target address".into())
1565 })?;
1566 patch_le_uint_width(
1567 &mut location.raw_data,
1568 addr_offset,
1569 new_addr,
1570 usize::from(self.superblock.sizeof_addr),
1571 "hard link address",
1572 )?;
1573 let object_addr = location
1574 .direct_block_addr
1575 .checked_add(location.object_offset)
1576 .ok_or_else(|| Error::InvalidFormat("dense link object address overflow".into()))?;
1577 self.write_handle.seek(SeekFrom::Start(object_addr))?;
1578 self.write_handle.write_all(&location.raw_data)?;
1579 self.rewrite_dense_link_direct_block_checksum(
1580 &location.heap,
1581 location.direct_block_addr,
1582 location.direct_block_size,
1583 object_addr,
1584 &location.raw_data,
1585 )
1586 }
1587
1588 fn find_compact_link_in_oh(
1589 &self,
1590 oh_addr: u64,
1591 target_name: &str,
1592 reject_duplicate_name: Option<&str>,
1593 ) -> Result<CompactLinkMessageLocation> {
1594 let mut guard = self.inner.lock();
1595 let sizeof_addr = guard.superblock.sizeof_addr;
1596 let reader = &mut guard.reader;
1597 reader.seek(oh_addr)?;
1598
1599 let mut first_bytes = [0u8; 4];
1600 reader.read_bytes_into(&mut first_bytes)?;
1601 if first_bytes != [b'O', b'H', b'D', b'R'] {
1602 return Err(Error::Unsupported(
1603 "link mutation currently supports only v2 object headers".into(),
1604 ));
1605 }
1606
1607 let version = reader.read_u8()?;
1608 if version != 2 {
1609 return Err(Error::Unsupported(
1610 "link mutation currently supports only v2 object headers".into(),
1611 ));
1612 }
1613
1614 let flags = reader.read_u8()?;
1615 if flags & !HDR_V2_KNOWN_FLAGS != 0 {
1616 return Err(Error::InvalidFormat(format!(
1617 "object header v2 flags contain reserved bits: {flags:#04x}"
1618 )));
1619 }
1620 if flags & HDR_STORE_TIMES != 0 {
1621 reader.skip(16)?;
1622 }
1623 if flags & HDR_ATTR_STORE_PHASE_CHANGE != 0 {
1624 reader.skip(4)?;
1625 }
1626
1627 let chunk0_size_bytes = 1u8 << (flags & HDR_CHUNK0_SIZE_MASK);
1628 let chunk0_data_size = reader.read_uint(chunk0_size_bytes)?;
1629 let chunk0_data_start = reader.position()?;
1630 let chunk0_data_end = chunk0_data_start
1631 .checked_add(chunk0_data_size)
1632 .ok_or_else(|| Error::InvalidFormat("object-header chunk range overflow".into()))?;
1633 let oh_check_len = usize::try_from(chunk0_data_end - oh_addr)
1634 .map_err(|_| Error::InvalidFormat("object-header checksum range overflow".into()))?;
1635
1636 let mut has_dense_links = false;
1637 let mut found = None;
1638 let mut msg_buf = Vec::new();
1639 while reader.position()? < chunk0_data_end {
1640 let msg_header_pos = reader.position()?;
1641 if msg_header_pos
1642 .checked_add(4)
1643 .is_none_or(|end| end > chunk0_data_end)
1644 {
1645 break;
1646 }
1647
1648 let msg_type = u16::from(reader.read_u8()?);
1649 let msg_size = usize::from(reader.read_u16()?);
1650 let _msg_flags = reader.read_u8()?;
1651 if flags & HDR_ATTR_CRT_ORDER_TRACKED != 0 {
1652 reader.skip(2)?;
1653 }
1654
1655 let msg_data_offset = reader.position()?;
1656 let msg_size_u64 = Self::usize_to_u64(msg_size, "object-header message size")?;
1657 if msg_data_offset
1658 .checked_add(msg_size_u64)
1659 .is_none_or(|end| end > chunk0_data_end)
1660 {
1661 return Err(Error::InvalidFormat(
1662 "object-header message payload exceeds chunk".into(),
1663 ));
1664 }
1665
1666 if msg_type == object_header::MSG_LINK {
1667 read_message_into(reader, &mut msg_buf, msg_size)?;
1668 let link = compact_link_view(&msg_buf, sizeof_addr)?;
1669 if let Some(duplicate_name) = reject_duplicate_name {
1670 if link.name == duplicate_name {
1671 return Err(Error::InvalidFormat(format!(
1672 "link '{duplicate_name}' already exists"
1673 )));
1674 }
1675 }
1676 if link.name == target_name {
1677 let location = CompactLinkMessageLocation {
1678 msg_type_offset: msg_header_pos,
1679 msg_data_offset,
1680 oh_start: oh_addr,
1681 oh_check_len,
1682 name_offset: link.name_offset,
1683 name_size: link.name_size,
1684 link_type: link.link_type,
1685 hard_link_addr: link.hard_link_addr,
1686 };
1687 if reject_duplicate_name.is_none() {
1688 return Ok(location);
1689 }
1690 found = Some(location);
1691 }
1692 } else if msg_type == object_header::MSG_SYMBOL_TABLE {
1693 return Err(Error::Unsupported(
1694 "mutating v1 symbol-table group links is not implemented".into(),
1695 ));
1696 } else if msg_type == object_header::MSG_LINK_INFO {
1697 read_message_into(reader, &mut msg_buf, msg_size)?;
1698 let link_info = LinkInfoMessage::decode(&msg_buf, sizeof_addr)?;
1699 if link_info.has_dense_storage() || link_info.corder_btree_addr.is_some() {
1700 has_dense_links = true;
1701 }
1702 } else {
1703 reader.skip(msg_size_u64)?;
1704 }
1705 }
1706
1707 if has_dense_links {
1708 return Err(Error::Unsupported(
1709 "mutating dense or creation-order indexed links is not implemented".into(),
1710 ));
1711 }
1712 found.ok_or_else(|| Error::InvalidFormat(format!("link '{target_name}' not found")))
1713 }
1714
1715 fn find_dense_link_location(
1716 &self,
1717 group_addr: u64,
1718 target_name: &str,
1719 reject_duplicate_name: Option<&str>,
1720 allow_creation_order_indexed: bool,
1721 ) -> Result<DenseLinkLocation> {
1722 let mut guard = self.inner.lock();
1723 let link_info = Self::read_dense_link_info_message(&mut guard.reader, group_addr)?;
1724 if !link_info.has_dense_storage() {
1725 return Err(Error::InvalidFormat(format!(
1726 "link '{target_name}' not found"
1727 )));
1728 }
1729 let creation_order_indexed = link_info.corder_btree_addr.is_some();
1730 if creation_order_indexed && !allow_creation_order_indexed {
1731 return Err(Error::Unsupported(
1732 "mutating creation-order indexed dense links is not implemented".into(),
1733 ));
1734 }
1735
1736 let heap = FractalHeapHeader::read_at(&mut guard.reader, link_info.fractal_heap_addr)?;
1737 if heap.io_filter_len != 0 {
1738 return Err(Error::Unsupported(
1739 "mutating filtered dense link heaps is not implemented".into(),
1740 ));
1741 }
1742
1743 let btree = BTreeV2Header::read_at(&mut guard.reader, link_info.name_btree_addr)?;
1744 if btree.depth != 0 {
1745 return Err(Error::Unsupported(
1746 "mutating non-leaf dense link name indexes is not implemented".into(),
1747 ));
1748 }
1749 let mut leaf_records = Vec::new();
1750 Self::read_dense_link_leaf_records_into(&mut guard.reader, &btree, &mut leaf_records)?;
1751 let heap_id_len = usize::from(heap.heap_id_len);
1752 let record_size = usize::from(btree.record_size);
1753 let mut found = None;
1754 for (idx, record) in leaf_records.chunks_exact(record_size).enumerate() {
1755 let heap_id = checked_window(record, 4, heap_id_len, "dense link heap ID")?;
1756 let raw_data = heap.read_managed_object(&mut guard.reader, heap_id)?;
1757 let link = compact_link_view(&raw_data, guard.superblock.sizeof_addr)?;
1758 if reject_duplicate_name.is_some_and(|name| link.name == name) {
1759 return Err(Error::InvalidFormat(format!(
1760 "link '{}' already exists",
1761 link.name
1762 )));
1763 }
1764 if link.name == target_name {
1765 let object_location = heap.managed_object_location(&mut guard.reader, heap_id)?;
1766 found = Some((idx, object_location, raw_data));
1767 }
1768 }
1769
1770 let (record_index, object_location, raw_data) =
1771 found.ok_or_else(|| Error::InvalidFormat(format!("link '{target_name}' not found")))?;
1772 Ok(DenseLinkLocation {
1773 btree_header_addr: link_info.name_btree_addr,
1774 heap,
1775 btree,
1776 creation_order_indexed,
1777 leaf_records,
1778 record_index,
1779 direct_block_addr: object_location.block_addr,
1780 direct_block_size: object_location.block_size,
1781 object_offset: object_location.object_offset,
1782 raw_data,
1783 })
1784 }
1785
1786 fn read_dense_link_info_message<R: Read + Seek>(
1787 reader: &mut crate::io::reader::HdfReader<R>,
1788 oh_addr: u64,
1789 ) -> Result<LinkInfoMessage> {
1790 reader.seek(oh_addr)?;
1791
1792 let mut first_bytes = [0u8; 4];
1793 reader.read_bytes_into(&mut first_bytes)?;
1794 if first_bytes != [b'O', b'H', b'D', b'R'] {
1795 return Err(Error::Unsupported(
1796 "link mutation currently supports only v2 object headers".into(),
1797 ));
1798 }
1799
1800 let version = reader.read_u8()?;
1801 if version != 2 {
1802 return Err(Error::Unsupported(
1803 "link mutation currently supports only v2 object headers".into(),
1804 ));
1805 }
1806
1807 let flags = reader.read_u8()?;
1808 if flags & !HDR_V2_KNOWN_FLAGS != 0 {
1809 return Err(Error::InvalidFormat(format!(
1810 "object header v2 flags contain reserved bits: {flags:#04x}"
1811 )));
1812 }
1813 if flags & HDR_STORE_TIMES != 0 {
1814 reader.skip(16)?;
1815 }
1816 if flags & HDR_ATTR_STORE_PHASE_CHANGE != 0 {
1817 reader.skip(4)?;
1818 }
1819
1820 let chunk0_size_bytes = 1u8 << (flags & HDR_CHUNK0_SIZE_MASK);
1821 let chunk0_data_size = reader.read_uint(chunk0_size_bytes)?;
1822 let chunk0_data_start = reader.position()?;
1823 let chunk0_data_end = chunk0_data_start
1824 .checked_add(chunk0_data_size)
1825 .ok_or_else(|| Error::InvalidFormat("object-header chunk range overflow".into()))?;
1826
1827 while reader.position()? < chunk0_data_end {
1828 let msg_header_pos = reader.position()?;
1829 if msg_header_pos
1830 .checked_add(4)
1831 .is_none_or(|end| end > chunk0_data_end)
1832 {
1833 break;
1834 }
1835
1836 let msg_type = u16::from(reader.read_u8()?);
1837 let msg_size = usize::from(reader.read_u16()?);
1838 let _msg_flags = reader.read_u8()?;
1839 if flags & HDR_ATTR_CRT_ORDER_TRACKED != 0 {
1840 reader.skip(2)?;
1841 }
1842
1843 let msg_data_offset = reader.position()?;
1844 let msg_size_u64 = Self::usize_to_u64(msg_size, "object-header message size")?;
1845 if msg_data_offset
1846 .checked_add(msg_size_u64)
1847 .is_none_or(|end| end > chunk0_data_end)
1848 {
1849 return Err(Error::InvalidFormat(
1850 "object-header message payload exceeds chunk".into(),
1851 ));
1852 }
1853
1854 if msg_type == object_header::MSG_LINK_INFO {
1855 let mut data = vec![0u8; msg_size];
1856 reader.read_bytes_into(&mut data)?;
1857 return LinkInfoMessage::decode(&data, reader.sizeof_addr());
1858 }
1859 reader.skip(msg_size_u64)?;
1860 }
1861
1862 Err(Error::InvalidFormat(
1863 "dense link info message not found".into(),
1864 ))
1865 }
1866
1867 fn read_dense_link_leaf_records_into<R: Read + Seek>(
1868 reader: &mut crate::io::reader::HdfReader<R>,
1869 btree: &BTreeV2Header,
1870 records: &mut Vec<u8>,
1871 ) -> Result<()> {
1872 let record_size = usize::from(btree.record_size);
1873 let record_count = usize::from(btree.root_nrecords);
1874 let records_len = record_size
1875 .checked_mul(record_count)
1876 .ok_or_else(|| Error::InvalidFormat("dense link leaf records overflow".into()))?;
1877 let check_len = 6usize
1878 .checked_add(records_len)
1879 .ok_or_else(|| Error::InvalidFormat("dense link leaf size overflow".into()))?;
1880 let total_len = check_len
1881 .checked_add(4)
1882 .ok_or_else(|| Error::InvalidFormat("dense link leaf size overflow".into()))?;
1883
1884 records.clear();
1885 records.resize(total_len, 0);
1886 reader.seek(btree.root_addr)?;
1887 reader.read_bytes_into(records)?;
1888 if records.get(..4) != Some(&b"BTLF"[..]) {
1889 return Err(Error::InvalidFormat(
1890 "invalid dense link B-tree leaf magic".into(),
1891 ));
1892 }
1893 if records.get(4).copied() != Some(0) || records.get(5).copied() != Some(btree.tree_type) {
1894 return Err(Error::InvalidFormat(
1895 "dense link B-tree leaf header does not match index".into(),
1896 ));
1897 }
1898 let stored = read_u32_le_at(records, check_len, "dense link leaf checksum")?;
1899 let computed = checksum_metadata(&records[..check_len]);
1900 if stored != computed {
1901 return Err(Error::InvalidFormat(format!(
1902 "dense link leaf checksum mismatch: stored={stored:#010x}, computed={computed:#010x}"
1903 )));
1904 }
1905 records.drain(..6);
1906 records.truncate(records_len);
1907 Ok(())
1908 }
1909
1910 fn rewrite_dense_link_name_index(&mut self, location: &DenseLinkLocation) -> Result<()> {
1911 let record_size = usize::from(location.btree.record_size);
1912 if record_size == 0 || !location.leaf_records.len().is_multiple_of(record_size) {
1913 return Err(Error::InvalidFormat(
1914 "dense link records have inconsistent sizes".into(),
1915 ));
1916 }
1917 let record_count = location.leaf_records.len() / record_size;
1918
1919 let mut leaf = Vec::with_capacity(
1920 6usize
1921 .checked_add(location.leaf_records.len())
1922 .and_then(|len| len.checked_add(4))
1923 .ok_or_else(|| Error::InvalidFormat("dense link leaf size overflow".into()))?,
1924 );
1925 leaf.extend_from_slice(b"BTLF");
1926 leaf.push(0);
1927 leaf.push(location.btree.tree_type);
1928 leaf.extend_from_slice(&location.leaf_records);
1929 let checksum = checksum_metadata(&leaf);
1930 leaf.extend_from_slice(&checksum.to_le_bytes());
1931 self.write_handle
1932 .seek(SeekFrom::Start(location.btree.root_addr))?;
1933 self.write_handle.write_all(&leaf)?;
1934
1935 let record_count_u16 = Self::usize_to_u16(record_count, "dense link record count")?;
1936 let record_count_u64 = Self::usize_to_u64(record_count, "dense link record count")?;
1937 let sa = usize::from(self.superblock.sizeof_addr);
1938 let ss = usize::from(self.superblock.sizeof_size);
1939 let header_capacity = 22usize
1940 .checked_add(sa)
1941 .and_then(|len| len.checked_add(ss))
1942 .ok_or_else(|| Error::InvalidFormat("dense link B-tree header size overflow".into()))?;
1943 let mut header = Vec::with_capacity(header_capacity);
1944 header.extend_from_slice(b"BTHD");
1945 header.push(0);
1946 header.push(location.btree.tree_type);
1947 header.extend_from_slice(&location.btree.node_size.to_le_bytes());
1948 header.extend_from_slice(&location.btree.record_size.to_le_bytes());
1949 header.extend_from_slice(&0u16.to_le_bytes());
1950 header.push(location.btree.split_pct);
1951 header.push(location.btree.merge_pct);
1952 let mut scratch = [0u8; 8];
1953 header.extend_from_slice(Self::encode_uint_le_into(
1954 location.btree.root_addr,
1955 &mut scratch,
1956 sa,
1957 "dense link B-tree root address",
1958 )?);
1959 header.extend_from_slice(&record_count_u16.to_le_bytes());
1960 header.extend_from_slice(Self::encode_uint_le_into(
1961 record_count_u64,
1962 &mut scratch,
1963 ss,
1964 "dense link B-tree total record count",
1965 )?);
1966 let checksum = checksum_metadata(&header);
1967 header.extend_from_slice(&checksum.to_le_bytes());
1968 self.write_handle
1969 .seek(SeekFrom::Start(location.btree_header_addr))?;
1970 self.write_handle.write_all(&header)?;
1971 Ok(())
1972 }
1973
1974 fn reposition_dense_link_record_by_hash(
1975 records: &mut [u8],
1976 record_index: usize,
1977 record_size: usize,
1978 ) -> Result<()> {
1979 if record_size == 0 || !records.len().is_multiple_of(record_size) {
1980 return Err(Error::InvalidFormat(
1981 "dense link records have inconsistent sizes".into(),
1982 ));
1983 }
1984 let record_count = records.len() / record_size;
1985 if record_index >= record_count {
1986 return Err(Error::InvalidFormat(
1987 "dense link record index is invalid".into(),
1988 ));
1989 }
1990 let start = record_index
1991 .checked_mul(record_size)
1992 .ok_or_else(|| Error::InvalidFormat("dense link record offset overflow".into()))?;
1993 let end = start
1994 .checked_add(record_size)
1995 .ok_or_else(|| Error::InvalidFormat("dense link record offset overflow".into()))?;
1996 let changed_hash = read_u32_le_at(&records[start..end], 0, "dense link record hash")?;
1997
1998 let mut insert_index = record_count - 1;
1999 let mut logical_idx = 0usize;
2000 for (idx, record) in records.chunks_exact(record_size).enumerate() {
2001 if idx == record_index {
2002 continue;
2003 }
2004 let hash = read_u32_le_at(record, 0, "dense link record hash")?;
2005 if changed_hash < hash {
2006 insert_index = logical_idx;
2007 break;
2008 }
2009 logical_idx += 1;
2010 }
2011
2012 if insert_index == record_index {
2013 return Ok(());
2014 }
2015 let insert_pos = insert_index
2016 .checked_mul(record_size)
2017 .ok_or_else(|| Error::InvalidFormat("dense link record offset overflow".into()))?;
2018 if insert_index < record_index {
2019 records[insert_pos..end].rotate_right(record_size);
2020 } else {
2021 let rotate_end = insert_pos
2022 .checked_add(record_size)
2023 .ok_or_else(|| Error::InvalidFormat("dense link record offset overflow".into()))?;
2024 records[start..rotate_end].rotate_left(record_size);
2025 }
2026 Ok(())
2027 }
2028
2029 fn rewrite_dense_link_direct_block_checksum(
2030 &mut self,
2031 heap: &FractalHeapHeader,
2032 block_addr: u64,
2033 block_size: u64,
2034 patched_addr: u64,
2035 patched_data: &[u8],
2036 ) -> Result<()> {
2037 if !heap.has_checksum {
2038 return Ok(());
2039 }
2040 let block_size = usize::try_from(block_size)
2041 .map_err(|_| Error::InvalidFormat("dense link direct block too large".into()))?;
2042 let checksum_pos = direct_block_checksum_pos(heap, self.superblock.sizeof_addr)?;
2043 let checksum_end = checksum_pos
2044 .checked_add(4)
2045 .ok_or_else(|| Error::InvalidFormat("direct block checksum offset overflow".into()))?;
2046 let mut guard = self.inner.lock();
2047 guard.reader.seek(block_addr)?;
2048 let mut block = vec![0u8; block_size];
2049 guard.reader.read_bytes_into(&mut block)?;
2050 drop(guard);
2051
2052 let patch_start = patched_addr
2053 .checked_sub(block_addr)
2054 .ok_or_else(|| Error::InvalidFormat("direct block patch address underflow".into()))?;
2055 let patch_start = usize::try_from(patch_start)
2056 .map_err(|_| Error::InvalidFormat("direct block patch offset too large".into()))?;
2057 let patch_end = patch_start
2058 .checked_add(patched_data.len())
2059 .ok_or_else(|| Error::InvalidFormat("direct block patch range overflow".into()))?;
2060 block
2061 .get_mut(patch_start..patch_end)
2062 .ok_or_else(|| Error::InvalidFormat("direct block patch exceeds block".into()))?
2063 .copy_from_slice(patched_data);
2064 let checksum_window = block.get_mut(checksum_pos..checksum_end).ok_or_else(|| {
2065 Error::InvalidFormat("direct block checksum field is truncated".into())
2066 })?;
2067 checksum_window.fill(0);
2068 let checksum = checksum_metadata(&block);
2069 let checksum_pos_u64 = Self::usize_to_u64(checksum_pos, "direct block checksum position")?;
2070 self.write_handle.seek(SeekFrom::Start(
2071 block_addr.checked_add(checksum_pos_u64).ok_or_else(|| {
2072 Error::InvalidFormat("direct block checksum address overflow".into())
2073 })?,
2074 ))?;
2075 self.write_handle.write_all(&checksum.to_le_bytes())?;
2076 Ok(())
2077 }
2078
2079 fn decrement_object_refcount_if_present(&mut self, object_addr: u64) -> Result<bool> {
2080 let Some(location) = self.find_object_refcount_location(object_addr)? else {
2081 return Ok(false);
2082 };
2083 if location.refcount <= 1 {
2084 return Err(Error::Unsupported(
2085 "hard-link deletion would drop explicit object refcount below one".into(),
2086 ));
2087 }
2088 let new_refcount = location.refcount - 1;
2089 self.write_handle
2090 .seek(SeekFrom::Start(location.value_offset))?;
2091 self.write_handle.write_all(&new_refcount.to_le_bytes())?;
2092 if let (Some(oh_start), Some(oh_check_len)) = (location.oh_start, location.oh_check_len) {
2093 self.rewrite_oh_checksum(oh_start, oh_check_len)?;
2094 }
2095 Ok(true)
2096 }
2097
2098 fn increment_object_refcount_if_present(&mut self, object_addr: u64) -> Result<bool> {
2099 let Some(location) = self.find_object_refcount_location(object_addr)? else {
2100 return Ok(false);
2101 };
2102 let new_refcount = location
2103 .refcount
2104 .checked_add(1)
2105 .ok_or_else(|| Error::InvalidFormat("object refcount overflow".into()))?;
2106 self.write_handle
2107 .seek(SeekFrom::Start(location.value_offset))?;
2108 self.write_handle.write_all(&new_refcount.to_le_bytes())?;
2109 if let (Some(oh_start), Some(oh_check_len)) = (location.oh_start, location.oh_check_len) {
2110 self.rewrite_oh_checksum(oh_start, oh_check_len)?;
2111 }
2112 Ok(true)
2113 }
2114
2115 fn ensure_explicit_object_refcount(
2116 &mut self,
2117 object_addr: u64,
2118 minimum_refcount: u32,
2119 ) -> Result<u64> {
2120 if self.increment_object_refcount_if_present(object_addr)? {
2121 return Ok(object_addr);
2122 }
2123
2124 let mut guard = self.inner.lock();
2125 let oh = ObjectHeader::read_at(&mut guard.reader, object_addr)?;
2126 if oh.version != 2 {
2127 return Err(Error::Unsupported(
2128 "creating persistent hard-link refcounts currently supports only v2 object headers"
2129 .into(),
2130 ));
2131 }
2132 if oh.flags & HDR_ATTR_CRT_ORDER_TRACKED != 0 {
2133 return Err(Error::Unsupported(
2134 "creating persistent hard-link refcounts with object-header creation-order tracking is not implemented"
2135 .into(),
2136 ));
2137 }
2138
2139 let mut messages = Vec::new();
2140 for msg in &oh.messages {
2141 match msg.msg_type {
2142 object_header::MSG_HEADER_CONTINUATION | object_header::MSG_NIL => {}
2143 _ => messages.push(OwnedObjectHeaderMessage::from_raw(msg)),
2144 }
2145 }
2146 drop(guard);
2147
2148 messages.push(OwnedObjectHeaderMessage {
2149 msg_type: object_header::MSG_OBJ_REF_COUNT,
2150 flags: 0,
2151 creation_index: None,
2152 data: minimum_refcount.to_le_bytes().to_vec(),
2153 });
2154 self.append_owned_v2_object_header(&messages, oh.flags)
2155 }
2156
2157 fn find_object_refcount_location(
2158 &self,
2159 object_addr: u64,
2160 ) -> Result<Option<ObjectRefcountLocation>> {
2161 let mut guard = self.inner.lock();
2162 let reader = &mut guard.reader;
2163 reader.seek(object_addr)?;
2164
2165 let mut first_bytes = [0u8; 4];
2166 reader.read_bytes_into(&mut first_bytes)?;
2167 if first_bytes != [b'O', b'H', b'D', b'R'] {
2168 if first_bytes[0] != 1 {
2169 return Err(Error::Unsupported(
2170 "hard-link deletion can update only v1/v2 object refcounts".into(),
2171 ));
2172 }
2173 let value_offset = object_addr.checked_add(4).ok_or_else(|| {
2174 Error::InvalidFormat("object-header refcount offset overflow".into())
2175 })?;
2176 reader.seek(value_offset)?;
2177 let refcount = reader.read_u32()?;
2178 return Ok(Some(ObjectRefcountLocation {
2179 value_offset,
2180 oh_start: None,
2181 oh_check_len: None,
2182 refcount,
2183 }));
2184 }
2185
2186 let version = reader.read_u8()?;
2187 if version != 2 {
2188 return Err(Error::Unsupported(
2189 "hard-link deletion can update only v1/v2 object refcounts".into(),
2190 ));
2191 }
2192
2193 let flags = reader.read_u8()?;
2194 if flags & !HDR_V2_KNOWN_FLAGS != 0 {
2195 return Err(Error::InvalidFormat(format!(
2196 "object header v2 flags contain reserved bits: {flags:#04x}"
2197 )));
2198 }
2199 if flags & HDR_STORE_TIMES != 0 {
2200 reader.skip(16)?;
2201 }
2202 if flags & HDR_ATTR_STORE_PHASE_CHANGE != 0 {
2203 reader.skip(4)?;
2204 }
2205
2206 let chunk0_size_bytes = 1u8 << (flags & HDR_CHUNK0_SIZE_MASK);
2207 let chunk0_data_size = reader.read_uint(chunk0_size_bytes)?;
2208 let chunk0_data_start = reader.position()?;
2209 let chunk0_data_end = chunk0_data_start
2210 .checked_add(chunk0_data_size)
2211 .ok_or_else(|| Error::InvalidFormat("object-header chunk range overflow".into()))?;
2212 let oh_check_len = usize::try_from(chunk0_data_end - object_addr)
2213 .map_err(|_| Error::InvalidFormat("object-header checksum range overflow".into()))?;
2214
2215 while reader.position()? < chunk0_data_end {
2216 let msg_header_pos = reader.position()?;
2217 if msg_header_pos
2218 .checked_add(4)
2219 .is_none_or(|end| end > chunk0_data_end)
2220 {
2221 break;
2222 }
2223
2224 let msg_type = u16::from(reader.read_u8()?);
2225 let msg_size = usize::from(reader.read_u16()?);
2226 let _msg_flags = reader.read_u8()?;
2227 if flags & HDR_ATTR_CRT_ORDER_TRACKED != 0 {
2228 reader.skip(2)?;
2229 }
2230
2231 let msg_data_offset = reader.position()?;
2232 let msg_size_u64 = Self::usize_to_u64(msg_size, "object-header message size")?;
2233 if msg_data_offset
2234 .checked_add(msg_size_u64)
2235 .is_none_or(|end| end > chunk0_data_end)
2236 {
2237 return Err(Error::InvalidFormat(
2238 "object-header message payload exceeds chunk".into(),
2239 ));
2240 }
2241
2242 if msg_type == object_header::MSG_OBJ_REF_COUNT {
2243 if msg_size < 4 {
2244 return Err(Error::InvalidFormat(
2245 "object refcount message is truncated".into(),
2246 ));
2247 }
2248 let refcount = reader.read_u32()?;
2249 return Ok(Some(ObjectRefcountLocation {
2250 value_offset: msg_data_offset,
2251 oh_start: Some(object_addr),
2252 oh_check_len: Some(oh_check_len),
2253 refcount,
2254 }));
2255 }
2256 reader.skip(msg_size_u64)?;
2257 }
2258
2259 Ok(None)
2260 }
2261
2262 fn count_compact_hard_links_to_addr(&self, oh_addr: u64, target_addr: u64) -> Result<usize> {
2263 let mut guard = self.inner.lock();
2264 let sizeof_addr = guard.superblock.sizeof_addr;
2265 let reader = &mut guard.reader;
2266 reader.seek(oh_addr)?;
2267
2268 let mut first_bytes = [0u8; 4];
2269 reader.read_bytes_into(&mut first_bytes)?;
2270 if first_bytes != [b'O', b'H', b'D', b'R'] {
2271 return Err(Error::Unsupported(
2272 "hard-link deletion without explicit refcount supports only v2 compact parent groups"
2273 .into(),
2274 ));
2275 }
2276
2277 let version = reader.read_u8()?;
2278 if version != 2 {
2279 return Err(Error::Unsupported(
2280 "hard-link deletion without explicit refcount supports only v2 compact parent groups"
2281 .into(),
2282 ));
2283 }
2284
2285 let flags = reader.read_u8()?;
2286 if flags & !HDR_V2_KNOWN_FLAGS != 0 {
2287 return Err(Error::InvalidFormat(format!(
2288 "object header v2 flags contain reserved bits: {flags:#04x}"
2289 )));
2290 }
2291 if flags & HDR_STORE_TIMES != 0 {
2292 reader.skip(16)?;
2293 }
2294 if flags & HDR_ATTR_STORE_PHASE_CHANGE != 0 {
2295 reader.skip(4)?;
2296 }
2297
2298 let chunk0_size_bytes = 1u8 << (flags & HDR_CHUNK0_SIZE_MASK);
2299 let chunk0_data_size = reader.read_uint(chunk0_size_bytes)?;
2300 let chunk0_data_start = reader.position()?;
2301 let chunk0_data_end = chunk0_data_start
2302 .checked_add(chunk0_data_size)
2303 .ok_or_else(|| Error::InvalidFormat("object-header chunk range overflow".into()))?;
2304
2305 let mut count = 0usize;
2306 let mut msg_buf = Vec::new();
2307 while reader.position()? < chunk0_data_end {
2308 let msg_header_pos = reader.position()?;
2309 if msg_header_pos
2310 .checked_add(4)
2311 .is_none_or(|end| end > chunk0_data_end)
2312 {
2313 break;
2314 }
2315
2316 let msg_type = u16::from(reader.read_u8()?);
2317 let msg_size = usize::from(reader.read_u16()?);
2318 let _msg_flags = reader.read_u8()?;
2319 if flags & HDR_ATTR_CRT_ORDER_TRACKED != 0 {
2320 reader.skip(2)?;
2321 }
2322
2323 let msg_data_offset = reader.position()?;
2324 let msg_size_u64 = Self::usize_to_u64(msg_size, "object-header message size")?;
2325 if msg_data_offset
2326 .checked_add(msg_size_u64)
2327 .is_none_or(|end| end > chunk0_data_end)
2328 {
2329 return Err(Error::InvalidFormat(
2330 "object-header message payload exceeds chunk".into(),
2331 ));
2332 }
2333
2334 if msg_type == object_header::MSG_LINK {
2335 read_message_into(reader, &mut msg_buf, msg_size)?;
2336 let link = compact_link_view(&msg_buf, sizeof_addr)?;
2337 if link.link_type == LinkType::Hard && link.hard_link_addr == Some(target_addr) {
2338 count += 1;
2339 }
2340 } else if msg_type == object_header::MSG_LINK_INFO {
2341 read_message_into(reader, &mut msg_buf, msg_size)?;
2342 let link_info = LinkInfoMessage::decode(&msg_buf, sizeof_addr)?;
2343 if link_info.has_dense_storage() || link_info.corder_btree_addr.is_some() {
2344 return Err(Error::Unsupported(
2345 "hard-link deletion without explicit refcount does not support dense parent links"
2346 .into(),
2347 ));
2348 }
2349 } else if msg_type == object_header::MSG_SYMBOL_TABLE {
2350 return Err(Error::Unsupported(
2351 "hard-link deletion without explicit refcount does not support v1 symbol-table parent groups"
2352 .into(),
2353 ));
2354 } else {
2355 reader.skip(msg_size_u64)?;
2356 }
2357 }
2358
2359 Ok(count)
2360 }
2361
2362 fn compact_messages_for_append(
2363 &self,
2364 group_addr: u64,
2365 new_name: &str,
2366 operation: &str,
2367 ) -> Result<(u8, Vec<OwnedObjectHeaderMessage>)> {
2368 let mut guard = self.inner.lock();
2369 let sizeof_addr = guard.superblock.sizeof_addr;
2370 let oh = ObjectHeader::read_at(&mut guard.reader, group_addr)?;
2371 if oh.version != 2 {
2372 return Err(Error::Unsupported(format!(
2373 "{operation} currently supports only v2 object headers"
2374 )));
2375 }
2376 if oh.flags & HDR_ATTR_CRT_ORDER_TRACKED != 0 {
2377 return Err(Error::Unsupported(format!(
2378 "{operation} with object-header creation-order tracking is not implemented"
2379 )));
2380 }
2381
2382 let mut messages = Vec::new();
2383 for msg in &oh.messages {
2384 match msg.msg_type {
2385 object_header::MSG_LINK => {
2386 let link = LinkMessage::decode(&msg.data, sizeof_addr)?;
2387 if link.name == new_name {
2388 return Err(Error::InvalidFormat(format!(
2389 "link '{new_name}' already exists"
2390 )));
2391 }
2392 messages.push(OwnedObjectHeaderMessage::from_raw(msg));
2393 }
2394 object_header::MSG_LINK_INFO => {
2395 let link_info = LinkInfoMessage::decode(&msg.data, sizeof_addr)?;
2396 if link_info.has_dense_storage() || link_info.corder_btree_addr.is_some() {
2397 return Err(Error::Unsupported(
2398 format!("{operation} for dense or creation-order indexed links is not implemented"),
2399 ));
2400 }
2401 messages.push(OwnedObjectHeaderMessage::from_raw(msg));
2402 }
2403 object_header::MSG_SYMBOL_TABLE => {
2404 return Err(Error::Unsupported(format!(
2405 "{operation} for v1 symbol-table groups is not implemented"
2406 )));
2407 }
2408 object_header::MSG_HEADER_CONTINUATION | object_header::MSG_NIL => {}
2409 _ => messages.push(OwnedObjectHeaderMessage::from_raw(msg)),
2410 }
2411 }
2412
2413 Ok((oh.flags, messages))
2414 }
2415
2416 fn messages_for_append(
2417 &self,
2418 group_addr: u64,
2419 new_name: &str,
2420 operation: &str,
2421 ) -> Result<(u8, Vec<OwnedObjectHeaderMessage>)> {
2422 match self.compact_messages_for_append(group_addr, new_name, operation) {
2423 Err(Error::Unsupported(msg))
2424 if msg.contains("dense or creation-order indexed links") =>
2425 {
2426 self.compact_messages_from_dense_links_for_append(group_addr, new_name, operation)
2427 }
2428 other => other,
2429 }
2430 }
2431
2432 fn compact_messages_from_dense_links_for_append(
2433 &self,
2434 group_addr: u64,
2435 new_name: &str,
2436 operation: &str,
2437 ) -> Result<(u8, Vec<OwnedObjectHeaderMessage>)> {
2438 let mut guard = self.inner.lock();
2439 let sizeof_addr = guard.superblock.sizeof_addr;
2440 let oh = ObjectHeader::read_at(&mut guard.reader, group_addr)?;
2441 if oh.version != 2 {
2442 return Err(Error::Unsupported(format!(
2443 "{operation} currently supports only v2 object headers"
2444 )));
2445 }
2446 if oh.flags & HDR_ATTR_CRT_ORDER_TRACKED != 0 {
2447 return Err(Error::Unsupported(format!(
2448 "{operation} with object-header creation-order tracking is not implemented"
2449 )));
2450 }
2451
2452 let mut messages = Vec::new();
2453 for msg in &oh.messages {
2454 match msg.msg_type {
2455 object_header::MSG_LINK => {
2456 let link = LinkMessage::decode(&msg.data, sizeof_addr)?;
2457 if link.name == new_name {
2458 return Err(Error::InvalidFormat(format!(
2459 "link '{new_name}' already exists"
2460 )));
2461 }
2462 messages.push(OwnedObjectHeaderMessage::from_raw(msg));
2463 }
2464 object_header::MSG_LINK_INFO => {
2465 let link_info = LinkInfoMessage::decode(&msg.data, sizeof_addr)?;
2466 if link_info.corder_btree_addr.is_some() {
2467 return Err(Error::Unsupported(format!(
2468 "{operation} for creation-order indexed links is not implemented"
2469 )));
2470 }
2471 if !link_info.has_dense_storage() {
2472 messages.push(OwnedObjectHeaderMessage::from_raw(msg));
2473 continue;
2474 }
2475
2476 let heap =
2477 FractalHeapHeader::read_at(&mut guard.reader, link_info.fractal_heap_addr)?;
2478 if heap.io_filter_len != 0 {
2479 return Err(Error::Unsupported(
2480 "mutating filtered dense link heaps is not implemented".into(),
2481 ));
2482 }
2483 let btree =
2484 BTreeV2Header::read_at(&mut guard.reader, link_info.name_btree_addr)?;
2485 if btree.depth != 0 {
2486 return Err(Error::Unsupported(
2487 "mutating non-leaf dense link name indexes is not implemented".into(),
2488 ));
2489 }
2490 let mut leaf_records = Vec::new();
2491 Self::read_dense_link_leaf_records_into(
2492 &mut guard.reader,
2493 &btree,
2494 &mut leaf_records,
2495 )?;
2496 let heap_id_len = usize::from(heap.heap_id_len);
2497 let record_size = usize::from(btree.record_size);
2498 for record in leaf_records.chunks_exact(record_size) {
2499 let heap_id = checked_window(record, 4, heap_id_len, "dense link heap ID")?;
2500 let raw_data = heap.read_managed_object(&mut guard.reader, heap_id)?;
2501 let link = LinkMessage::decode(&raw_data, sizeof_addr)?;
2502 if link.name == new_name {
2503 return Err(Error::InvalidFormat(format!(
2504 "link '{new_name}' already exists"
2505 )));
2506 }
2507 messages.push(OwnedObjectHeaderMessage {
2508 msg_type: object_header::MSG_LINK,
2509 flags: 0,
2510 creation_index: None,
2511 data: raw_data,
2512 });
2513 }
2514 }
2515 object_header::MSG_SYMBOL_TABLE => {
2516 return Err(Error::Unsupported(format!(
2517 "{operation} for v1 symbol-table groups is not implemented"
2518 )));
2519 }
2520 object_header::MSG_HEADER_CONTINUATION | object_header::MSG_NIL => {}
2521 _ => messages.push(OwnedObjectHeaderMessage::from_raw(msg)),
2522 }
2523 }
2524
2525 Ok((oh.flags, messages))
2526 }
2527
2528 fn compact_or_dense_hard_link_parent_chain(
2529 &self,
2530 parent_path: &str,
2531 operation: &str,
2532 ) -> Result<Vec<ParentChainLink>> {
2533 let names = compact_absolute_path_components(parent_path, operation)?;
2534 let mut ancestor_addr = self.superblock.root_addr;
2535 let mut chain = Vec::with_capacity(names.len());
2536 for name in names {
2537 let (child_addr, storage) =
2538 self.compact_or_dense_hard_link_addr(ancestor_addr, name, operation)?;
2539 chain.push(ParentChainLink {
2540 ancestor_addr,
2541 link_name: name.to_string(),
2542 child_addr,
2543 storage,
2544 });
2545 ancestor_addr = child_addr;
2546 }
2547 Ok(chain)
2548 }
2549
2550 fn compact_or_dense_hard_link_addr(
2551 &self,
2552 group_addr: u64,
2553 name: &str,
2554 operation: &str,
2555 ) -> Result<(u64, ParentChainLinkStorage)> {
2556 match self.compact_hard_link_addr(group_addr, name, operation) {
2557 Ok(addr) => Ok((addr, ParentChainLinkStorage::Compact)),
2558 Err(Error::Unsupported(msg))
2559 if msg.contains("dense or creation-order indexed links") =>
2560 {
2561 let location = self.find_dense_link_location(group_addr, name, None, false)?;
2562 let link = compact_link_view(&location.raw_data, self.superblock.sizeof_addr)?;
2563 if link.link_type != LinkType::Hard {
2564 return Err(Error::Unsupported(format!(
2565 "{operation} under a nested group requires hard-link ancestors"
2566 )));
2567 }
2568 let addr = link.hard_link_addr.ok_or_else(|| {
2569 Error::InvalidFormat(format!("hard link '{name}' is missing object address"))
2570 })?;
2571 Ok((addr, ParentChainLinkStorage::Dense))
2572 }
2573 Err(err) => Err(err),
2574 }
2575 }
2576
2577 fn compact_hard_link_addr(&self, group_addr: u64, name: &str, operation: &str) -> Result<u64> {
2578 let mut guard = self.inner.lock();
2579 let sizeof_addr = guard.superblock.sizeof_addr;
2580 let oh = ObjectHeader::read_at(&mut guard.reader, group_addr)?;
2581 if oh.version != 2 {
2582 return Err(Error::Unsupported(format!(
2583 "{operation} under a nested group currently supports only v2 compact object headers"
2584 )));
2585 }
2586 if oh.flags & HDR_ATTR_CRT_ORDER_TRACKED != 0 {
2587 return Err(Error::Unsupported(format!(
2588 "{operation} under a nested group with creation-order tracking is not implemented"
2589 )));
2590 }
2591
2592 for msg in &oh.messages {
2593 match msg.msg_type {
2594 object_header::MSG_LINK => {
2595 let link = LinkMessage::decode(&msg.data, sizeof_addr)?;
2596 if link.name == name {
2597 if link.link_type != LinkType::Hard {
2598 return Err(Error::Unsupported(format!(
2599 "{operation} under a nested group requires compact hard-link ancestors"
2600 )));
2601 }
2602 return link.hard_link_addr.ok_or_else(|| {
2603 Error::InvalidFormat(format!(
2604 "hard link '{name}' is missing object address"
2605 ))
2606 });
2607 }
2608 }
2609 object_header::MSG_LINK_INFO => {
2610 let link_info = LinkInfoMessage::decode(&msg.data, sizeof_addr)?;
2611 if link_info.has_dense_storage() || link_info.corder_btree_addr.is_some() {
2612 return Err(Error::Unsupported(format!(
2613 "{operation} under a nested group with dense or creation-order indexed links is not implemented"
2614 )));
2615 }
2616 }
2617 object_header::MSG_SYMBOL_TABLE => {
2618 return Err(Error::Unsupported(format!(
2619 "{operation} under a nested group with v1 symbol-table links is not implemented"
2620 )));
2621 }
2622 _ => {}
2623 }
2624 }
2625 Err(Error::InvalidFormat(format!("link '{name}' not found")))
2626 }
2627
2628 fn compact_messages_replacing_hard_link(
2629 &self,
2630 group_addr: u64,
2631 target_name: &str,
2632 new_addr: u64,
2633 operation: &str,
2634 ) -> Result<(u8, Vec<OwnedObjectHeaderMessage>)> {
2635 let mut guard = self.inner.lock();
2636 let sizeof_addr = guard.superblock.sizeof_addr;
2637 let oh = ObjectHeader::read_at(&mut guard.reader, group_addr)?;
2638 if oh.version != 2 {
2639 return Err(Error::Unsupported(format!(
2640 "{operation} address propagation currently supports only v2 compact object headers"
2641 )));
2642 }
2643 if oh.flags & HDR_ATTR_CRT_ORDER_TRACKED != 0 {
2644 return Err(Error::Unsupported(format!(
2645 "{operation} address propagation with creation-order tracking is not implemented"
2646 )));
2647 }
2648
2649 let mut replaced = false;
2650 let mut messages = Vec::new();
2651 for msg in &oh.messages {
2652 match msg.msg_type {
2653 object_header::MSG_LINK => {
2654 let link = LinkMessage::decode(&msg.data, sizeof_addr)?;
2655 if link.name == target_name {
2656 if replaced {
2657 return Err(Error::InvalidFormat(format!(
2658 "duplicate root link '{target_name}'"
2659 )));
2660 }
2661 if link.link_type != LinkType::Hard {
2662 return Err(Error::Unsupported(format!(
2663 "{operation} address propagation requires compact hard-link ancestors"
2664 )));
2665 }
2666 messages.push(OwnedObjectHeaderMessage {
2667 msg_type: object_header::MSG_LINK,
2668 flags: msg.flags,
2669 creation_index: msg.creation_index,
2670 data: encode_hard_link_message(
2671 target_name,
2672 new_addr,
2673 self.superblock.sizeof_addr,
2674 )?,
2675 });
2676 replaced = true;
2677 } else {
2678 messages.push(OwnedObjectHeaderMessage::from_raw(msg));
2679 }
2680 }
2681 object_header::MSG_LINK_INFO => {
2682 let link_info = LinkInfoMessage::decode(&msg.data, sizeof_addr)?;
2683 if link_info.has_dense_storage() || link_info.corder_btree_addr.is_some() {
2684 return Err(Error::Unsupported(format!(
2685 "{operation} address propagation for dense or creation-order indexed links is not implemented"
2686 )));
2687 }
2688 messages.push(OwnedObjectHeaderMessage::from_raw(msg));
2689 }
2690 object_header::MSG_SYMBOL_TABLE => {
2691 return Err(Error::Unsupported(format!(
2692 "{operation} address propagation for v1 symbol-table links is not implemented"
2693 )));
2694 }
2695 object_header::MSG_HEADER_CONTINUATION | object_header::MSG_NIL => {}
2696 _ => messages.push(OwnedObjectHeaderMessage::from_raw(msg)),
2697 }
2698 }
2699 if !replaced {
2700 return Err(Error::InvalidFormat(format!(
2701 "link '{target_name}' not found"
2702 )));
2703 }
2704 Ok((oh.flags, messages))
2705 }
2706
2707 fn compact_messages_replacing_hard_links(
2708 &self,
2709 group_addr: u64,
2710 replacements: &[(String, u64)],
2711 operation: &str,
2712 ) -> Result<(u8, Vec<OwnedObjectHeaderMessage>)> {
2713 let mut guard = self.inner.lock();
2714 let sizeof_addr = guard.superblock.sizeof_addr;
2715 let oh = ObjectHeader::read_at(&mut guard.reader, group_addr)?;
2716 if oh.version != 2 {
2717 return Err(Error::Unsupported(format!(
2718 "{operation} address propagation currently supports only v2 compact object headers"
2719 )));
2720 }
2721 if oh.flags & HDR_ATTR_CRT_ORDER_TRACKED != 0 {
2722 return Err(Error::Unsupported(format!(
2723 "{operation} address propagation with creation-order tracking is not implemented"
2724 )));
2725 }
2726
2727 let messages: Vec<_> = oh
2728 .messages
2729 .iter()
2730 .map(OwnedObjectHeaderMessage::from_raw)
2731 .collect();
2732 replace_hard_links_in_messages(messages, oh.flags, replacements, sizeof_addr, operation)
2733 }
2734
2735 fn compact_messages_replacing_and_appending_hard_link(
2736 &self,
2737 group_addr: u64,
2738 target_name: &str,
2739 target_addr: u64,
2740 new_name: &str,
2741 operation: &str,
2742 ) -> Result<(u8, Vec<OwnedObjectHeaderMessage>)> {
2743 match self.compact_messages_replacing_and_appending_compact_hard_link(
2744 group_addr,
2745 target_name,
2746 target_addr,
2747 new_name,
2748 operation,
2749 ) {
2750 Err(Error::Unsupported(msg))
2751 if msg.contains("dense or creation-order indexed links") =>
2752 {
2753 let (flags, messages) = self.compact_messages_from_dense_links_for_append(
2754 group_addr, new_name, operation,
2755 )?;
2756 let replacement = [(target_name.to_string(), target_addr)];
2757 let (flags, mut messages) = replace_hard_links_in_messages(
2758 messages,
2759 flags,
2760 &replacement,
2761 self.superblock.sizeof_addr,
2762 operation,
2763 )?;
2764 messages.push(OwnedObjectHeaderMessage {
2765 msg_type: object_header::MSG_LINK,
2766 flags: 0,
2767 creation_index: None,
2768 data: encode_hard_link_message(
2769 new_name,
2770 target_addr,
2771 self.superblock.sizeof_addr,
2772 )?,
2773 });
2774 Ok((flags, messages))
2775 }
2776 other => other,
2777 }
2778 }
2779
2780 fn compact_messages_replacing_and_appending_compact_hard_link(
2781 &self,
2782 group_addr: u64,
2783 target_name: &str,
2784 target_addr: u64,
2785 new_name: &str,
2786 operation: &str,
2787 ) -> Result<(u8, Vec<OwnedObjectHeaderMessage>)> {
2788 let mut guard = self.inner.lock();
2789 let sizeof_addr = guard.superblock.sizeof_addr;
2790 let oh = ObjectHeader::read_at(&mut guard.reader, group_addr)?;
2791 if oh.version != 2 {
2792 return Err(Error::Unsupported(format!(
2793 "{operation} currently supports only v2 compact object headers"
2794 )));
2795 }
2796 if oh.flags & HDR_ATTR_CRT_ORDER_TRACKED != 0 {
2797 return Err(Error::Unsupported(format!(
2798 "{operation} with object-header creation-order tracking is not implemented"
2799 )));
2800 }
2801
2802 let mut replaced = false;
2803 let mut messages = Vec::new();
2804 for msg in &oh.messages {
2805 match msg.msg_type {
2806 object_header::MSG_LINK => {
2807 let link = LinkMessage::decode(&msg.data, sizeof_addr)?;
2808 if link.name == new_name {
2809 return Err(Error::InvalidFormat(format!(
2810 "link '{new_name}' already exists"
2811 )));
2812 }
2813 if link.name == target_name {
2814 if replaced {
2815 return Err(Error::InvalidFormat(format!(
2816 "link '{target_name}' appears more than once"
2817 )));
2818 }
2819 if link.link_type != LinkType::Hard {
2820 return Err(Error::Unsupported(format!(
2821 "{operation} requires a compact hard-link target"
2822 )));
2823 }
2824 messages.push(OwnedObjectHeaderMessage {
2825 msg_type: object_header::MSG_LINK,
2826 flags: msg.flags,
2827 creation_index: msg.creation_index,
2828 data: encode_hard_link_message(
2829 target_name,
2830 target_addr,
2831 self.superblock.sizeof_addr,
2832 )?,
2833 });
2834 replaced = true;
2835 } else {
2836 messages.push(OwnedObjectHeaderMessage::from_raw(msg));
2837 }
2838 }
2839 object_header::MSG_LINK_INFO => {
2840 let link_info = LinkInfoMessage::decode(&msg.data, sizeof_addr)?;
2841 if link_info.has_dense_storage() || link_info.corder_btree_addr.is_some() {
2842 return Err(Error::Unsupported(format!(
2843 "{operation} for dense or creation-order indexed links is not implemented"
2844 )));
2845 }
2846 messages.push(OwnedObjectHeaderMessage::from_raw(msg));
2847 }
2848 object_header::MSG_SYMBOL_TABLE => {
2849 return Err(Error::Unsupported(format!(
2850 "{operation} for v1 symbol-table groups is not implemented"
2851 )));
2852 }
2853 object_header::MSG_HEADER_CONTINUATION | object_header::MSG_NIL => {}
2854 _ => messages.push(OwnedObjectHeaderMessage::from_raw(msg)),
2855 }
2856 }
2857 if !replaced {
2858 return Err(Error::InvalidFormat(format!(
2859 "link '{target_name}' not found"
2860 )));
2861 }
2862 messages.push(OwnedObjectHeaderMessage {
2863 msg_type: object_header::MSG_LINK,
2864 flags: 0,
2865 creation_index: None,
2866 data: encode_hard_link_message(new_name, target_addr, self.superblock.sizeof_addr)?,
2867 });
2868 Ok((oh.flags, messages))
2869 }
2870
2871 fn append_v2_object_header(
2872 &mut self,
2873 messages: &[ObjectHeaderMessageRef<'_>],
2874 flags: u8,
2875 ) -> Result<u64> {
2876 let encoded = object_header::encode_v2_with_continuations(
2877 messages,
2878 flags,
2879 &[],
2880 OBJECT_HEADER_CHUNK_DATA_LIMIT,
2881 OBJECT_HEADER_CHUNK_DATA_LIMIT,
2882 self.superblock.sizeof_addr,
2883 self.superblock.sizeof_size,
2884 )?;
2885 if !encoded.continuation_chunks.is_empty() {
2886 return Err(Error::Unsupported(
2887 "root group creation cannot append continuation object-header chunks yet".into(),
2888 ));
2889 }
2890 let addr = self.append_aligned_zeros(encoded.prefix.len(), 8)?;
2891 self.write_handle.seek(SeekFrom::Start(addr))?;
2892 self.write_handle.write_all(&encoded.prefix)?;
2893 Ok(addr)
2894 }
2895
2896 fn append_owned_v2_object_header(
2897 &mut self,
2898 messages: &[OwnedObjectHeaderMessage],
2899 flags: u8,
2900 ) -> Result<u64> {
2901 let refs: Vec<_> = messages
2902 .iter()
2903 .map(OwnedObjectHeaderMessage::as_ref)
2904 .collect();
2905 self.append_v2_object_header(&refs, flags)
2906 }
2907
2908 fn rewrite_v2_v3_superblock(&mut self, root_addr: u64, eof_addr: u64) -> Result<()> {
2909 let sb = Superblock {
2910 root_addr,
2911 root_entry_obj_header_addr: root_addr,
2912 eof_addr,
2913 ..self.superblock.clone()
2914 };
2915 let mut sb_bytes = Vec::new();
2916 sb.write_v2(&mut sb_bytes)?;
2917 self.write_handle.seek(SeekFrom::Start(0))?;
2918 self.write_handle.write_all(&sb_bytes)?;
2919 self.superblock = sb;
2920 Ok(())
2921 }
2922}
2923
2924#[derive(Debug)]
2925struct CompactLinkView<'a> {
2926 name: &'a str,
2927 name_offset: usize,
2928 name_size: usize,
2929 link_type: LinkType,
2930 hard_link_addr: Option<u64>,
2931 hard_link_addr_offset: Option<usize>,
2932}
2933
2934#[derive(Debug)]
2935struct OwnedObjectHeaderMessage {
2936 msg_type: u16,
2937 flags: u8,
2938 creation_index: Option<u16>,
2939 data: Vec<u8>,
2940}
2941
2942impl OwnedObjectHeaderMessage {
2943 fn from_raw(message: &object_header::RawMessage) -> Self {
2944 Self {
2945 msg_type: message.msg_type,
2946 flags: message.flags,
2947 creation_index: message.creation_index,
2948 data: message.data.clone(),
2949 }
2950 }
2951
2952 fn as_ref(&self) -> ObjectHeaderMessageRef<'_> {
2953 ObjectHeaderMessageRef {
2954 msg_type: self.msg_type,
2955 flags: self.flags,
2956 creation_index: self.creation_index,
2957 data: &self.data,
2958 }
2959 }
2960}
2961
2962fn validate_direct_child_link_name(name: &str) -> Result<()> {
2963 if name.is_empty() {
2964 return Err(Error::InvalidFormat("link name cannot be empty".into()));
2965 }
2966 if name == "." || name == ".." || name.contains('/') {
2967 return Err(Error::Unsupported(
2968 "existing-file link creation currently supports only a direct child name".into(),
2969 ));
2970 }
2971 Ok(())
2972}
2973
2974fn compact_absolute_path_components<'a>(path: &'a str, operation: &str) -> Result<Vec<&'a str>> {
2975 let rest = path.strip_prefix('/').ok_or_else(|| {
2976 Error::Unsupported(format!(
2977 "{operation} currently supports only absolute compact parent paths"
2978 ))
2979 })?;
2980 if rest.is_empty() {
2981 return Err(Error::Unsupported(format!(
2982 "{operation} currently supports only non-root paths here"
2983 )));
2984 }
2985 let names: Vec<_> = rest.split('/').collect();
2986 if names
2987 .iter()
2988 .any(|name| name.is_empty() || *name == "." || *name == "..")
2989 {
2990 return Err(Error::Unsupported(format!(
2991 "{operation} currently supports only normalized compact parent paths"
2992 )));
2993 }
2994 Ok(names)
2995}
2996
2997fn split_normalized_absolute_child_path<'a>(
2998 path: &'a str,
2999 operation: &str,
3000) -> Result<(&'a str, &'a str)> {
3001 if path == "/" || !path.starts_with('/') {
3002 return Err(Error::Unsupported(format!(
3003 "{operation} currently supports only absolute compact target paths"
3004 )));
3005 }
3006 if path.contains("/.") || path.contains("//") {
3007 return Err(Error::Unsupported(format!(
3008 "{operation} currently supports only normalized compact target paths"
3009 )));
3010 }
3011 let (parent, name) = path.rsplit_once('/').ok_or_else(|| {
3012 Error::InvalidFormat(format!(
3013 "{operation} target path '{path}' is not a child path"
3014 ))
3015 })?;
3016 if name.is_empty() {
3017 return Err(Error::InvalidFormat(format!(
3018 "{operation} target path cannot end with '/'"
3019 )));
3020 }
3021 Ok((if parent.is_empty() { "/" } else { parent }, name))
3022}
3023
3024fn owned_compact_absolute_path_components(path: &str, operation: &str) -> Result<Vec<String>> {
3025 let rest = path.strip_prefix('/').ok_or_else(|| {
3026 Error::Unsupported(format!(
3027 "{operation} currently supports only absolute compact parent paths"
3028 ))
3029 })?;
3030 if rest.is_empty() {
3031 return Ok(Vec::new());
3032 }
3033 let names: Vec<_> = rest.split('/').collect();
3034 if names
3035 .iter()
3036 .any(|name| name.is_empty() || *name == "." || *name == "..")
3037 {
3038 return Err(Error::Unsupported(format!(
3039 "{operation} currently supports only normalized compact parent paths"
3040 )));
3041 }
3042 Ok(names.into_iter().map(str::to_string).collect())
3043}
3044
3045fn replace_hard_links_in_messages(
3046 messages: Vec<OwnedObjectHeaderMessage>,
3047 flags: u8,
3048 replacements: &[(String, u64)],
3049 sizeof_addr: u8,
3050 operation: &str,
3051) -> Result<(u8, Vec<OwnedObjectHeaderMessage>)> {
3052 let mut replaced = vec![false; replacements.len()];
3053 let mut out = Vec::with_capacity(messages.len());
3054
3055 for msg in messages {
3056 match msg.msg_type {
3057 object_header::MSG_LINK => {
3058 let link = LinkMessage::decode(&msg.data, sizeof_addr)?;
3059 if let Some((idx, (_, new_addr))) = replacements
3060 .iter()
3061 .enumerate()
3062 .find(|(_, (name, _))| link.name == *name)
3063 {
3064 if replaced[idx] {
3065 return Err(Error::InvalidFormat(format!(
3066 "duplicate root link '{}'",
3067 link.name
3068 )));
3069 }
3070 if link.link_type != LinkType::Hard {
3071 return Err(Error::Unsupported(format!(
3072 "{operation} address propagation requires compact hard-link ancestors"
3073 )));
3074 }
3075 out.push(OwnedObjectHeaderMessage {
3076 msg_type: object_header::MSG_LINK,
3077 flags: msg.flags,
3078 creation_index: msg.creation_index,
3079 data: encode_hard_link_message(&link.name, *new_addr, sizeof_addr)?,
3080 });
3081 replaced[idx] = true;
3082 } else {
3083 out.push(msg);
3084 }
3085 }
3086 object_header::MSG_LINK_INFO => {
3087 let link_info = LinkInfoMessage::decode(&msg.data, sizeof_addr)?;
3088 if link_info.has_dense_storage() || link_info.corder_btree_addr.is_some() {
3089 return Err(Error::Unsupported(format!(
3090 "{operation} address propagation for dense or creation-order indexed links is not implemented"
3091 )));
3092 }
3093 out.push(msg);
3094 }
3095 object_header::MSG_SYMBOL_TABLE => {
3096 return Err(Error::Unsupported(format!(
3097 "{operation} address propagation for v1 symbol-table links is not implemented"
3098 )));
3099 }
3100 object_header::MSG_HEADER_CONTINUATION | object_header::MSG_NIL => {}
3101 _ => out.push(msg),
3102 }
3103 }
3104
3105 if let Some((idx, (name, _))) = replacements
3106 .iter()
3107 .enumerate()
3108 .find(|(idx, _)| !replaced[*idx])
3109 {
3110 return Err(Error::InvalidFormat(format!(
3111 "link '{name}' not found for replacement {idx}"
3112 )));
3113 }
3114 Ok((flags, out))
3115}
3116
3117fn encode_hard_link_message(name: &str, target_addr: u64, sizeof_addr: u8) -> Result<Vec<u8>> {
3118 let mut buf = Vec::new();
3119 let name_bytes = name.as_bytes();
3120 let size_flag = link_name_size_flag(name_bytes.len())?;
3121 buf.push(1);
3122 buf.push(size_flag | 0x10);
3123 buf.push(1);
3124 encode_link_name_len(&mut buf, name_bytes.len(), size_flag)?;
3125 buf.extend_from_slice(name_bytes);
3126 append_le_uint_width(
3127 &mut buf,
3128 target_addr,
3129 usize::from(sizeof_addr),
3130 "hard link address",
3131 )?;
3132 Ok(buf)
3133}
3134
3135fn encode_soft_link_message(name: &str, target_path: &str) -> Result<Vec<u8>> {
3136 let mut buf = Vec::new();
3137 let name_bytes = name.as_bytes();
3138 let target_bytes = target_path.as_bytes();
3139 let target_len = u16::try_from(target_bytes.len()).map_err(|_| {
3140 Error::InvalidFormat(format!(
3141 "soft link target is {} bytes, maximum is {}",
3142 target_bytes.len(),
3143 u16::MAX
3144 ))
3145 })?;
3146 let size_flag = link_name_size_flag(name_bytes.len())?;
3147
3148 buf.push(1);
3149 buf.push(size_flag | 0x08 | 0x10);
3150 buf.push(1);
3151 buf.push(1);
3152 encode_link_name_len(&mut buf, name_bytes.len(), size_flag)?;
3153 buf.extend_from_slice(name_bytes);
3154 buf.extend_from_slice(&target_len.to_le_bytes());
3155 buf.extend_from_slice(target_bytes);
3156 Ok(buf)
3157}
3158
3159fn encode_external_link_message(name: &str, filename: &str, object_path: &str) -> Result<Vec<u8>> {
3160 let mut buf = Vec::new();
3161 let name_bytes = name.as_bytes();
3162 let size_flag = link_name_size_flag(name_bytes.len())?;
3163
3164 let info_len = 1usize
3165 .checked_add(filename.len())
3166 .and_then(|len| len.checked_add(1))
3167 .and_then(|len| len.checked_add(object_path.len()))
3168 .and_then(|len| len.checked_add(1))
3169 .ok_or_else(|| Error::InvalidFormat("external link info length overflow".into()))?;
3170 let info_len = u16::try_from(info_len).map_err(|_| {
3171 Error::InvalidFormat(format!(
3172 "external link info is {info_len} bytes, maximum is {}",
3173 u16::MAX
3174 ))
3175 })?;
3176
3177 buf.push(1);
3178 buf.push(size_flag | 0x08 | 0x10);
3179 buf.push(64);
3180 buf.push(1);
3181 encode_link_name_len(&mut buf, name_bytes.len(), size_flag)?;
3182 buf.extend_from_slice(name_bytes);
3183 buf.extend_from_slice(&info_len.to_le_bytes());
3184 buf.push(0);
3185 buf.extend_from_slice(filename.as_bytes());
3186 buf.push(0);
3187 buf.extend_from_slice(object_path.as_bytes());
3188 buf.push(0);
3189 Ok(buf)
3190}
3191
3192fn encode_renamed_link_message(
3193 link: &LinkMessage,
3194 new_name: &str,
3195 sizeof_addr: u8,
3196) -> Result<Vec<u8>> {
3197 match link.link_type {
3198 LinkType::Hard => {
3199 let target_addr = link.hard_link_addr.ok_or_else(|| {
3200 Error::InvalidFormat("hard link message is missing target address".into())
3201 })?;
3202 encode_hard_link_message(new_name, target_addr, sizeof_addr)
3203 }
3204 LinkType::Soft => {
3205 let target = link.soft_link_target.as_deref().ok_or_else(|| {
3206 Error::InvalidFormat("soft link message is missing target path".into())
3207 })?;
3208 encode_soft_link_message(new_name, target)
3209 }
3210 LinkType::External => {
3211 let (filename, object_path) = link.external_link.as_ref().ok_or_else(|| {
3212 Error::InvalidFormat("external link message is missing target".into())
3213 })?;
3214 encode_external_link_message(new_name, filename, object_path)
3215 }
3216 LinkType::UserDefined(kind) => Err(Error::Unsupported(format!(
3217 "root compact relink for user-defined link type {kind} is not implemented"
3218 ))),
3219 }
3220}
3221
3222fn link_name_size_flag(len: usize) -> Result<u8> {
3223 match len {
3224 0 => Err(Error::InvalidFormat("link name cannot be empty".into())),
3225 1..=0xff => Ok(0),
3226 0x100..=0xffff => Ok(1),
3227 0x1_0000..=0xffff_ffff => Ok(2),
3228 _ => Ok(3),
3229 }
3230}
3231
3232fn encode_link_name_len(out: &mut Vec<u8>, len: usize, size_flag: u8) -> Result<()> {
3233 let width = 1usize << (size_flag & 0x03);
3234 let len = u64::try_from(len)
3235 .map_err(|_| Error::InvalidFormat("link name length exceeds u64".into()))?;
3236 append_le_uint_width(out, len, width, "link name length")
3237}
3238
3239fn append_le_uint_width(out: &mut Vec<u8>, value: u64, width: usize, context: &str) -> Result<()> {
3240 if !(1..=8).contains(&width) {
3241 return Err(Error::InvalidFormat(format!("{context} width is invalid")));
3242 }
3243 if width < 8 {
3244 let bits = width
3245 .checked_mul(8)
3246 .ok_or_else(|| Error::InvalidFormat(format!("{context} width overflow")))?;
3247 if value >= (1u64 << bits) {
3248 return Err(Error::InvalidFormat(format!(
3249 "{context} does not fit in {width} bytes"
3250 )));
3251 }
3252 }
3253 out.extend_from_slice(&value.to_le_bytes()[..width]);
3254 Ok(())
3255}
3256
3257fn patch_le_uint_width(
3258 raw: &mut [u8],
3259 pos: usize,
3260 value: u64,
3261 width: usize,
3262 context: &str,
3263) -> Result<()> {
3264 if !(1..=8).contains(&width) {
3265 return Err(Error::InvalidFormat(format!("{context} width is invalid")));
3266 }
3267 if width < 8 {
3268 let bits = width
3269 .checked_mul(8)
3270 .ok_or_else(|| Error::InvalidFormat(format!("{context} width overflow")))?;
3271 if value >= (1u64 << bits) {
3272 return Err(Error::InvalidFormat(format!(
3273 "{context} does not fit in {width} bytes"
3274 )));
3275 }
3276 }
3277 let end = pos
3278 .checked_add(width)
3279 .ok_or_else(|| Error::InvalidFormat(format!("{context} offset overflow")))?;
3280 let window = raw
3281 .get_mut(pos..end)
3282 .ok_or_else(|| Error::InvalidFormat(format!("{context} field is truncated")))?;
3283 window.copy_from_slice(&value.to_le_bytes()[..width]);
3284 Ok(())
3285}
3286
3287fn encode_dataspace_for_spec_into(out: &mut Vec<u8>, spec: &DatasetSpec<'_>) -> Result<()> {
3288 if spec.shape.len() > MAX_DATASPACE_RANK {
3289 return Err(Error::InvalidFormat(format!(
3290 "dataspace rank {} exceeds supported maximum {MAX_DATASPACE_RANK}",
3291 spec.shape.len(),
3292 )));
3293 }
3294 if let Some(max_shape) = spec.max_shape {
3295 if max_shape.len() != spec.shape.len() {
3296 return Err(Error::InvalidFormat(format!(
3297 "max shape rank {} does not match dataset rank {}",
3298 max_shape.len(),
3299 spec.shape.len()
3300 )));
3301 }
3302 for (idx, (&dim, &max_dim)) in spec.shape.iter().zip(max_shape.iter()).enumerate() {
3303 if max_dim != u64::MAX && dim > max_dim {
3304 return Err(Error::InvalidFormat(format!(
3305 "dataset dimension {idx} size {dim} exceeds max dimension {max_dim}"
3306 )));
3307 }
3308 }
3309 }
3310
3311 if spec.shape.is_empty() {
3312 out.extend_from_slice(&[2, 0, 0, 0]);
3313 } else {
3314 let ndims = u8::try_from(spec.shape.len())
3315 .map_err(|_| Error::InvalidFormat("dataspace rank exceeds u8".into()))?;
3316 out.push(2);
3317 out.push(ndims);
3318 out.push(if spec.max_shape.is_some() { 0x01 } else { 0 });
3319 out.push(1);
3320 for &dim in spec.shape {
3321 out.extend_from_slice(&dim.to_le_bytes());
3322 }
3323 if let Some(max_shape) = spec.max_shape {
3324 for &dim in max_shape {
3325 out.extend_from_slice(&dim.to_le_bytes());
3326 }
3327 }
3328 }
3329 Ok(())
3330}
3331
3332fn encode_contiguous_layout_into(
3333 out: &mut Vec<u8>,
3334 data_addr: u64,
3335 data_size: u64,
3336 sizeof_addr: u8,
3337 sizeof_size: u8,
3338) -> Result<()> {
3339 out.push(3);
3340 out.push(1);
3341 append_encoded_addr(out, data_addr, sizeof_addr)?;
3342 append_le_uint_width(
3343 out,
3344 data_size,
3345 usize::from(sizeof_size),
3346 "dataset data size",
3347 )
3348}
3349
3350fn encode_fill_value_message_into(
3351 out: &mut Vec<u8>,
3352 fill: Option<FillValueSpec<'_>>,
3353) -> Result<()> {
3354 let Some(fill) = fill else {
3355 out.extend_from_slice(&[3, 0x09]);
3356 return Ok(());
3357 };
3358 if fill.alloc_time > 3 {
3359 return Err(Error::InvalidFormat(format!(
3360 "fill allocation time {} exceeds 2-bit field",
3361 fill.alloc_time
3362 )));
3363 }
3364 if fill.fill_time > 3 {
3365 return Err(Error::InvalidFormat(format!(
3366 "fill write time {} exceeds 2-bit field",
3367 fill.fill_time
3368 )));
3369 }
3370
3371 let mut flags = fill.alloc_time | (fill.fill_time << 2);
3372 out.push(3);
3373 if let Some(value) = fill.value {
3374 flags |= 0x20;
3375 out.push(flags);
3376 out.extend_from_slice(
3377 &u32::try_from(value.len())
3378 .map_err(|_| Error::InvalidFormat("fill-value payload length exceeds u32".into()))?
3379 .to_le_bytes(),
3380 );
3381 out.extend_from_slice(value);
3382 } else {
3383 flags |= 0x10;
3384 out.push(flags);
3385 }
3386 Ok(())
3387}
3388
3389fn append_encoded_addr(out: &mut Vec<u8>, value: u64, sizeof_addr: u8) -> Result<()> {
3390 let width = usize::from(sizeof_addr);
3391 if !(1..=8).contains(&width) {
3392 return Err(Error::InvalidFormat(format!(
3393 "address field width {sizeof_addr} is invalid"
3394 )));
3395 }
3396 if value == crate::io::reader::UNDEF_ADDR {
3397 out.extend(std::iter::repeat_n(0xff, width));
3398 return Ok(());
3399 }
3400 append_le_uint_width(out, value, width, "address value")
3401}
3402
3403fn validate_dataset_data_len(spec: &DatasetSpec<'_>) -> Result<()> {
3404 let dtype_size = usize::try_from(spec.dtype.size())
3405 .map_err(|_| Error::InvalidFormat("dataset datatype size exceeds usize".into()))?;
3406 if dtype_size == 0 {
3407 return Err(Error::InvalidFormat(
3408 "dataset datatype size must be nonzero".into(),
3409 ));
3410 }
3411 let expected_count = shape_element_count(spec.shape)?;
3412 let expected_bytes = usize::try_from(expected_count)
3413 .map_err(|_| Error::InvalidFormat("dataset element count exceeds usize".into()))?
3414 .checked_mul(dtype_size)
3415 .ok_or_else(|| Error::InvalidFormat("dataset byte size overflow".into()))?;
3416 if expected_bytes != spec.data.len() {
3417 return Err(Error::InvalidFormat(format!(
3418 "dataset byte length {} does not match shape element count {expected_count} * datatype size {dtype_size}",
3419 spec.data.len()
3420 )));
3421 }
3422 Ok(())
3423}
3424
3425fn shape_element_count(shape: &[u64]) -> Result<u64> {
3426 if shape.is_empty() {
3427 return Ok(1);
3428 }
3429 shape.iter().try_fold(1u64, |acc, &dim| {
3430 acc.checked_mul(dim)
3431 .ok_or_else(|| Error::InvalidFormat("dataset shape element count overflow".into()))
3432 })
3433}
3434
3435fn read_message_into<R>(
3436 reader: &mut HdfReader<R>,
3437 scratch: &mut Vec<u8>,
3438 msg_size: usize,
3439) -> Result<()>
3440where
3441 R: Read + Seek,
3442{
3443 scratch.clear();
3444 if scratch.capacity() < msg_size {
3445 scratch.reserve_exact(msg_size - scratch.capacity());
3446 }
3447 scratch.resize(msg_size, 0);
3448 reader.read_bytes_into(scratch)
3449}
3450
3451fn compact_link_view(raw: &[u8], sizeof_addr: u8) -> Result<CompactLinkView<'_>> {
3452 let mut pos = 0usize;
3453 let version = read_u8(raw, &mut pos, "link message version")?;
3454 if version != 1 {
3455 return Err(Error::InvalidFormat(format!(
3456 "link message version {version}"
3457 )));
3458 }
3459 let flags = read_u8(raw, &mut pos, "link message flags")?;
3460 if flags & !0x1f != 0 {
3461 return Err(Error::InvalidFormat(format!(
3462 "link message flags {flags:#x} are invalid"
3463 )));
3464 }
3465 let name_len_size = 1usize << (flags & 0x03);
3466 let link_type = if flags & 0x08 != 0 {
3467 match read_u8(raw, &mut pos, "link message link type")? {
3468 0 => LinkType::Hard,
3469 1 => LinkType::Soft,
3470 64 => LinkType::External,
3471 65..=u8::MAX => LinkType::UserDefined(raw[pos - 1]),
3472 other => return Err(Error::InvalidFormat(format!("invalid link type {other}"))),
3473 }
3474 } else {
3475 LinkType::Hard
3476 };
3477 if flags & 0x04 != 0 {
3478 advance_pos(raw, &mut pos, 8, "link creation order")?;
3479 }
3480 let char_encoding = if flags & 0x10 != 0 {
3481 read_u8(raw, &mut pos, "link message character encoding")?
3482 } else {
3483 0
3484 };
3485 if char_encoding > 1 {
3486 return Err(Error::InvalidFormat(format!(
3487 "invalid link character encoding {char_encoding}"
3488 )));
3489 }
3490 let name_size = usize::try_from(read_le_u64(
3491 raw,
3492 &mut pos,
3493 name_len_size,
3494 "link name length",
3495 )?)
3496 .map_err(|_| Error::InvalidFormat("link name length overflows usize".into()))?;
3497 if name_size == 0 {
3498 return Err(Error::InvalidFormat("invalid link name length".into()));
3499 }
3500 ensure_available(raw, pos, name_size, "link name")?;
3501 let name_offset = pos;
3502 let name = str::from_utf8(&raw[name_offset..name_offset + name_size])
3503 .map_err(|_| Error::InvalidFormat("link name is not valid UTF-8".into()))?;
3504 advance_pos(raw, &mut pos, name_size, "link name")?;
3505
3506 let mut hard_link_addr_offset = None;
3507 let hard_link_addr = match link_type {
3508 LinkType::Hard => {
3509 hard_link_addr_offset = Some(pos);
3510 Some(read_le_u64(
3511 raw,
3512 &mut pos,
3513 usize::from(sizeof_addr),
3514 "hard link address",
3515 )?)
3516 }
3517 LinkType::Soft => {
3518 let target_len =
3519 usize::try_from(read_le_u64(raw, &mut pos, 2, "soft link target length")?)
3520 .map_err(|_| Error::InvalidFormat("soft link target length overflow".into()))?;
3521 if target_len == 0 {
3522 return Err(Error::InvalidFormat("invalid soft link length".into()));
3523 }
3524 ensure_available(raw, pos, target_len, "soft link target")?;
3525 str::from_utf8(&raw[pos..pos + target_len])
3526 .map_err(|_| Error::InvalidFormat("soft link target is not valid UTF-8".into()))?;
3527 advance_pos(raw, &mut pos, target_len, "soft link target")?;
3528 None
3529 }
3530 LinkType::External => {
3531 let info_len =
3532 usize::try_from(read_le_u64(raw, &mut pos, 2, "external link info length")?)
3533 .map_err(|_| {
3534 Error::InvalidFormat("external link info length overflow".into())
3535 })?;
3536 if info_len < 3 {
3537 return Err(Error::InvalidFormat(
3538 "external link info is too short".into(),
3539 ));
3540 }
3541 validate_external_link_info(&raw[pos..], info_len)?;
3542 advance_pos(raw, &mut pos, info_len, "external link info")?;
3543 None
3544 }
3545 LinkType::UserDefined(_) => None,
3546 };
3547
3548 Ok(CompactLinkView {
3549 name,
3550 name_offset,
3551 name_size,
3552 link_type,
3553 hard_link_addr,
3554 hard_link_addr_offset,
3555 })
3556}
3557
3558fn encode_link_name_in_place<'a>(
3559 raw: &'a mut [u8],
3560 name_offset: usize,
3561 name_size: usize,
3562 name: &str,
3563) -> Result<&'a [u8]> {
3564 let name_field = raw
3565 .get_mut(
3566 name_offset
3567 ..name_offset.checked_add(name_size).ok_or_else(|| {
3568 Error::InvalidFormat("link name field offset overflow".into())
3569 })?,
3570 )
3571 .ok_or_else(|| Error::InvalidFormat("link name field exceeds message".into()))?;
3572 if name.len() != name_size {
3573 return Err(Error::Unsupported(
3574 "in-place link rename cannot grow or shrink the encoded name field".into(),
3575 ));
3576 }
3577 name_field.copy_from_slice(name.as_bytes());
3578 Ok(name_field)
3579}
3580
3581fn validate_external_link_info(raw: &[u8], info_len: usize) -> Result<()> {
3582 ensure_available(raw, 0, info_len, "external link info")?;
3583 let info = &raw[..info_len];
3584 if info[0] != 0 {
3585 return Err(Error::InvalidFormat(format!(
3586 "external link version {}",
3587 info[0]
3588 )));
3589 }
3590 let rest = &info[1..];
3591 let first_nul = rest.iter().position(|&byte| byte == 0).ok_or_else(|| {
3592 Error::InvalidFormat("external link filename is missing terminator".into())
3593 })?;
3594 let filename = &rest[..first_nul];
3595 let obj_path = &rest[first_nul + 1..];
3596 if obj_path.last() != Some(&0) {
3597 return Err(Error::InvalidFormat(
3598 "external link object path is missing terminator".into(),
3599 ));
3600 }
3601 str::from_utf8(filename)
3602 .map_err(|_| Error::InvalidFormat("external link filename is not valid UTF-8".into()))?;
3603 str::from_utf8(&obj_path[..obj_path.len() - 1])
3604 .map_err(|_| Error::InvalidFormat("external link object path is not valid UTF-8".into()))?;
3605 Ok(())
3606}
3607
3608fn checked_window<'a>(data: &'a [u8], pos: usize, len: usize, context: &str) -> Result<&'a [u8]> {
3609 let end = pos
3610 .checked_add(len)
3611 .ok_or_else(|| Error::InvalidFormat(format!("{context} length overflow")))?;
3612 data.get(pos..end)
3613 .ok_or_else(|| Error::InvalidFormat(format!("{context} is truncated")))
3614}
3615
3616fn read_u32_le_at(raw: &[u8], pos: usize, context: &str) -> Result<u32> {
3617 let bytes = checked_window(raw, pos, 4, context)?;
3618 let bytes: [u8; 4] = bytes
3619 .try_into()
3620 .map_err(|_| Error::InvalidFormat(format!("{context} is truncated")))?;
3621 Ok(u32::from_le_bytes(bytes))
3622}
3623
3624fn direct_block_checksum_pos(heap: &FractalHeapHeader, sizeof_addr: u8) -> Result<usize> {
3625 let offset_bytes = fractal_heap_offset_width(heap)?;
3626 5usize
3627 .checked_add(usize::from(sizeof_addr))
3628 .and_then(|pos| pos.checked_add(offset_bytes))
3629 .ok_or_else(|| Error::InvalidFormat("direct block checksum position overflow".into()))
3630}
3631
3632fn fractal_heap_offset_width(heap: &FractalHeapHeader) -> Result<usize> {
3633 let max_heap_size = usize::from(heap.max_heap_size);
3634 let offset_bytes = max_heap_size
3635 .checked_add(7)
3636 .ok_or_else(|| Error::InvalidFormat("dense link heap offset width overflow".into()))?
3637 / 8;
3638 if offset_bytes == 0 || offset_bytes > 8 {
3639 return Err(Error::Unsupported(format!(
3640 "dense link heap offset width {offset_bytes} is unsupported"
3641 )));
3642 }
3643 Ok(offset_bytes)
3644}
3645
3646fn dense_link_name_hash(name: &str) -> u32 {
3647 crate::format::checksum::checksum_lookup3(name.as_bytes(), 0)
3648}
3649
3650fn ensure_available(data: &[u8], pos: usize, len: usize, context: &str) -> Result<()> {
3651 let end = pos
3652 .checked_add(len)
3653 .ok_or_else(|| Error::InvalidFormat(format!("{context} length overflow")))?;
3654 if end > data.len() {
3655 return Err(Error::InvalidFormat(format!("{context} is truncated")));
3656 }
3657 Ok(())
3658}
3659
3660fn advance_pos(data: &[u8], pos: &mut usize, len: usize, context: &str) -> Result<()> {
3661 ensure_available(data, *pos, len, context)?;
3662 *pos = pos
3663 .checked_add(len)
3664 .ok_or_else(|| Error::InvalidFormat(format!("{context} position overflow")))?;
3665 Ok(())
3666}
3667
3668fn read_u8(data: &[u8], pos: &mut usize, context: &str) -> Result<u8> {
3669 ensure_available(data, *pos, 1, context)?;
3670 let value = data[*pos];
3671 advance_pos(data, pos, 1, context)?;
3672 Ok(value)
3673}
3674
3675fn read_le_u64(data: &[u8], pos: &mut usize, size: usize, context: &str) -> Result<u64> {
3676 if !(1..=8).contains(&size) {
3677 return Err(Error::InvalidFormat(format!(
3678 "{context} has invalid byte width {size}"
3679 )));
3680 }
3681 ensure_available(data, *pos, size, context)?;
3682 let mut val = 0u64;
3683 for (idx, byte) in data[*pos..*pos + size].iter().enumerate() {
3684 val |= u64::from(*byte) << (idx * 8);
3685 }
3686 advance_pos(data, pos, size, context)?;
3687 Ok(val)
3688}