Skip to content

Commit eb92bdc

Browse files
CopilotKSXGitHub
andcommitted
fix(reflection): address doc and comment accuracy for Reflection and TryFrom
Co-authored-by: KSXGitHub <11488886+KSXGitHub@users.noreply.github.com>
1 parent 38aacab commit eb92bdc

1 file changed

Lines changed: 21 additions & 9 deletions

File tree

src/hardlink/hardlink_list/reflection.rs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@ use serde::{Deserialize, Serialize};
1212
/// internal content.
1313
///
1414
/// **Guarantees:**
15-
/// * Every inode number is unique within the scope of a single scan (files are keyed by
16-
/// `(device, inode)` in the underlying [`HardlinkList`], but the reflection stores
17-
/// only the inode number; entries from different filesystems with the same inode number
18-
/// are an unsupported edge case in JSON round-trips).
19-
/// * The internal list is always sorted by inode numbers.
15+
/// * Every `(device, inode)` pair is unique within the scope of a single scan, but inode
16+
/// numbers alone are **not** guaranteed to be unique: when scanning multiple filesystems,
17+
/// two unrelated files on different devices can share the same inode number and will each
18+
/// produce a separate entry. The reflection stores only the inode number (the JSON format
19+
/// does not carry device information), so round-tripping a multi-filesystem scan through
20+
/// JSON is an unsupported edge case.
21+
/// * The internal list is always sorted by inode numbers (and by device number as a
22+
/// tie-breaker when two entries share the same inode number).
2023
///
2124
/// **Equality:** `Reflection` implements `PartialEq` and `Eq` traits.
2225
///
@@ -98,10 +101,17 @@ impl<Size> From<Vec<ReflectionEntry<Size>>> for Reflection<Size> {
98101

99102
impl<Size> From<HardlinkList<Size>> for Reflection<Size> {
100103
fn from(HardlinkList(list): HardlinkList<Size>) -> Self {
101-
list.into_iter()
104+
// Collect to a vec, sort by (ino, dev) for a stable, deterministic order, then
105+
// strip dev before wrapping. Sorting here (with dev still available) avoids the
106+
// nondeterminism that would arise from an unstable sort on ino alone when two
107+
// entries from different filesystems share the same inode number.
108+
let mut pairs: Vec<(InodeKey, Value<Size>)> = list.into_iter().collect();
109+
pairs.sort_unstable_by_key(|(key, _)| (u64::from(key.ino), key.dev));
110+
pairs
111+
.into_iter()
102112
.map(|(key, value)| ReflectionEntry::new(key.ino, value))
103113
.collect::<Vec<_>>()
104-
.pipe(Reflection::from)
114+
.pipe(Reflection)
105115
}
106116
}
107117

@@ -125,8 +135,10 @@ impl<Size> TryFrom<Reflection<Size>> for HardlinkList<Size> {
125135
// Device number is unknown when loading from a reflection (e.g. JSON input);
126136
// use dev=0 as a placeholder. This means that when reloading JSON output that
127137
// was produced by scanning multiple filesystems, files from different devices
128-
// sharing the same inode number will be incorrectly merged into a single entry.
129-
// This is an unsupported edge case: the JSON format does not carry device info.
138+
// sharing the same inode number cannot be distinguished and therefore cannot
139+
// all be represented. Such duplicates cause a ConversionError::DuplicatedInode
140+
// and are treated as an unsupported edge case, since the JSON format does not
141+
// carry device information.
130142
let key = InodeKey { dev: 0, ino };
131143
if map.insert(key, value).is_some() {
132144
return ino.pipe(ConversionError::DuplicatedInode).pipe(Err);

0 commit comments

Comments
 (0)