Skip to main content

hdf5_pure_rust/hl/mutable_file/
group_mutation.rs

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    /// Create a child group by rewriting compact v2/v3 metadata.
74    ///
75    /// Root children rewrite the root header and superblock. Nested compact
76    /// children rewrite each compact hard-link ancestor up to the root so
77    /// moved object-header addresses remain reachable.
78    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    /// Create a soft link in the root group of an existing compact v2/v3 file.
96    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    /// Create an external link in the root group of an existing compact v2/v3 file.
118    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    /// Create a hard link in the root group of an existing compact v2/v3 file.
146    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    /// Create a contiguous simple dataset in the root group of an existing compact v2/v3 file.
277    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    /// Delete a compact link from a group.
548    ///
549    /// This is an in-place hdf5-metno compatibility mutation. It currently
550    /// supports only v2 object headers with compact link messages.
551    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    /// Rename a compact link in a group without changing the encoded name size.
683    ///
684    /// Moving between groups or changing the encoded message length needs
685    /// object-header growth and is rejected explicitly for now.
686    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}