Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions crates/uffs-core/src/aggregate/duplicates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,18 +347,18 @@ mod tests {

// Root directory.
let root_off = idx.add_name(".");
let root = idx.get_or_create(ROOT_FRS);
let root = idx.get_or_create(ROOT_FRS.into());
root.stdinfo.set_directory(true);
root.first_name.name = IndexNameRef::new(root_off, 1, true, IndexNameRef::NO_EXTENSION);
root.first_name.parent_frs = ROOT_FRS;
root.first_name.parent_frs = Into::into(ROOT_FRS);

let add_file = |index: &mut MftIndex, frs: u64, name: &str, size: u64| {
let off = index.add_name(name);
let ext = index.intern_extension(name);
let rec = index.get_or_create(frs);
let rec = index.get_or_create(frs.into());
rec.first_name.name =
IndexNameRef::new(off, uffs_mft::len_to_u16(name.len()), true, ext);
rec.first_name.parent_frs = ROOT_FRS;
rec.first_name.parent_frs = Into::into(ROOT_FRS);
rec.first_stream.size = SizeInfo {
length: size,
allocated: size,
Expand Down Expand Up @@ -591,20 +591,20 @@ mod tests {

// Root.
let root_off = idx.add_name(".");
let root = idx.get_or_create(ROOT_FRS);
let root = idx.get_or_create(ROOT_FRS.into());
root.stdinfo.set_directory(true);
root.first_name.name = IndexNameRef::new(root_off, 1, true, IndexNameRef::NO_EXTENSION);
root.first_name.parent_frs = ROOT_FRS;
root.first_name.parent_frs = Into::into(ROOT_FRS);

// 10 unique files — all different names and sizes.
for i in 0..10_u64 {
let name = format!("file_{i}.dat");
let off = idx.add_name(&name);
let ext = idx.intern_extension(&name);
let rec = idx.get_or_create(100 + i);
let rec = idx.get_or_create((100 + i).into());
rec.first_name.name =
IndexNameRef::new(off, uffs_mft::len_to_u16(name.len()), true, ext);
rec.first_name.parent_frs = ROOT_FRS;
rec.first_name.parent_frs = Into::into(ROOT_FRS);
rec.first_stream.size = SizeInfo {
length: (i + 1) * 100,
allocated: (i + 1) * 512,
Expand Down Expand Up @@ -638,20 +638,20 @@ mod tests {
let mut idx = MftIndex::new(uffs_mft::platform::DriveLetter::T);

let root_off = idx.add_name(".");
let root = idx.get_or_create(ROOT_FRS);
let root = idx.get_or_create(ROOT_FRS.into());
root.stdinfo.set_directory(true);
root.first_name.name = IndexNameRef::new(root_off, 1, true, IndexNameRef::NO_EXTENSION);
root.first_name.parent_frs = ROOT_FRS;
root.first_name.parent_frs = Into::into(ROOT_FRS);

// Two zero-byte files with same name — should NOT be duplicates.
for i in 0..2_u64 {
let name = "empty.txt";
let off = idx.add_name(name);
let ext = idx.intern_extension(name);
let rec = idx.get_or_create(100 + i);
let rec = idx.get_or_create((100 + i).into());
rec.first_name.name =
IndexNameRef::new(off, uffs_mft::len_to_u16(name.len()), true, ext);
rec.first_name.parent_frs = ROOT_FRS;
rec.first_name.parent_frs = Into::into(ROOT_FRS);
rec.first_stream.size = SizeInfo {
length: 0,
allocated: 0,
Expand Down Expand Up @@ -680,22 +680,22 @@ mod tests {
let mut idx = MftIndex::new(uffs_mft::platform::DriveLetter::T);

let root_off = idx.add_name(".");
let root = idx.get_or_create(ROOT_FRS);
let root = idx.get_or_create(ROOT_FRS.into());
root.stdinfo.set_directory(true);
root.first_name.name = IndexNameRef::new(root_off, 1, true, IndexNameRef::NO_EXTENSION);
root.first_name.parent_frs = ROOT_FRS;
root.first_name.parent_frs = Into::into(ROOT_FRS);

// Two directories with same name — should NOT be duplicates.
for i in 0..2_u64 {
let name = "subdir";
let off = idx.add_name(name);
let ext = idx.intern_extension(name);
let rec = idx.get_or_create(100 + i);
let rec = idx.get_or_create((100 + i).into());
rec.stdinfo.set_directory(true);
rec.stdinfo.flags = 0x10; // directory
rec.first_name.name =
IndexNameRef::new(off, uffs_mft::len_to_u16(name.len()), true, ext);
rec.first_name.parent_frs = ROOT_FRS;
rec.first_name.parent_frs = Into::into(ROOT_FRS);
rec.first_stream.size = SizeInfo {
length: 4096,
allocated: 4096,
Expand Down
12 changes: 6 additions & 6 deletions crates/uffs-core/src/aggregate/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,21 @@ fn build_agg_test_drive() -> DriveCompactIndex {

// Root directory.
let root_off = idx.add_name(".");
let root = idx.get_or_create(ROOT_FRS);
let root = idx.get_or_create(ROOT_FRS.into());
root.stdinfo.set_directory(true);
root.first_name.name = IndexNameRef::new(root_off, 1, true, IndexNameRef::NO_EXTENSION);
root.first_name.parent_frs = ROOT_FRS;
root.first_name.parent_frs = Into::into(ROOT_FRS);

// Projects directory.
let dir_name = "Projects";
let dir_off = idx.add_name(dir_name);
let dir_ext = idx.intern_extension(dir_name);
let dir = idx.get_or_create(100);
let dir = idx.get_or_create(100.into());
dir.stdinfo.set_directory(true);
dir.stdinfo.flags = 0x10;
dir.first_name.name =
IndexNameRef::new(dir_off, uffs_mft::len_to_u16(dir_name.len()), true, dir_ext);
dir.first_name.parent_frs = ROOT_FRS;
dir.first_name.parent_frs = Into::into(ROOT_FRS);

// Files: (name, frs, size, allocated, modified_timestamp)
let files: &[(&str, u64, u64, u64, i64)] = &[
Expand All @@ -71,9 +71,9 @@ fn build_agg_test_drive() -> DriveCompactIndex {
for &(name, frs, size, allocated, modified) in files {
let off = idx.add_name(name);
let ext = idx.intern_extension(name);
let rec = idx.get_or_create(frs);
let rec = idx.get_or_create(frs.into());
rec.first_name.name = IndexNameRef::new(off, uffs_mft::len_to_u16(name.len()), true, ext);
rec.first_name.parent_frs = 100;
rec.first_name.parent_frs = Into::into(100);
rec.first_stream.size = SizeInfo {
length: size,
allocated,
Expand Down
57 changes: 39 additions & 18 deletions crates/uffs-core/src/compact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,7 @@ impl DriveCompactIndex {
fn expand_ads_streams(
index: &MftIndex,
record: &uffs_mft::index::FileRecord,
resolve_parent: &dyn Fn(u64, u64) -> u32,
resolve_parent: &dyn Fn(uffs_mft::ParentFrs, uffs_mft::Frs) -> u32,
names: &mut Vec<u8>,
extra: &mut Vec<CompactRecord>,
) {
Expand Down Expand Up @@ -599,6 +599,36 @@ fn expand_ads_streams(
}
}

/// Resolve a typed `ParentFrs` (vs an own typed `Frs`) into a compact-record
/// index, returning `u32::MAX` for the "no real parent" cases (self-reference,
/// `NO_ENTRY` sentinel, or root).
///
/// Extracted as a free helper so the typed `ParentFrs`/`Frs` signature is
/// enforced at every call site AND so `build_compact_index` stays under
/// the clippy `too_many_lines` budget.
#[expect(
clippy::single_call_fn,
reason = "Wrapped by a closure in build_compact_index; kept free-standing \
for clippy::too_many_lines budget headroom"
)]
fn resolve_parent_compact_idx(
index: &MftIndex,
parent_frs: uffs_mft::ParentFrs,
own_frs: uffs_mft::Frs,
) -> u32 {
let parent = parent_frs.as_frs();
if parent == own_frs || parent_frs.raw() == u64::from(uffs_mft::NO_ENTRY) || parent.is_root() {
return u32::MAX;
}
let parent_usize = uffs_mft::frs_to_usize(parent.raw());
index
.frs_to_idx
.get(parent_usize)
.copied()
.filter(|&idx| idx != uffs_mft::NO_ENTRY)
.unwrap_or(u32::MAX)
}

/// Expand hardlinks and ADS into additional `CompactRecord` entries.
///
/// Phase 2 (hardlinks): for each valid record with `name_count > 1`, walks the
Expand All @@ -613,7 +643,7 @@ fn expand_ads_streams(
fn expand_links_and_ads(
index: &MftIndex,
resolver: &uffs_mft::index::PathResolver,
resolve_parent: &dyn Fn(u64, u64) -> u32,
resolve_parent: &dyn Fn(uffs_mft::ParentFrs, uffs_mft::Frs) -> u32,
names: &mut Vec<u8>,
) -> Vec<CompactRecord> {
let mut extra: Vec<CompactRecord> = Vec::new();
Expand Down Expand Up @@ -773,22 +803,13 @@ pub fn build_compact_index(
// propagates invalidity to descendants (e.g., $Extend children).
let resolver = PathResolver::build(index, false);

// Helper: resolve parent_frs → compact index.
let resolve_parent = |parent_frs: u64, own_frs: u64| -> u32 {
if parent_frs == own_frs
|| parent_frs == u64::from(uffs_mft::NO_ENTRY)
|| parent_frs == uffs_mft::ROOT_FRS
{
u32::MAX
} else {
let parent_usize = uffs_mft::frs_to_usize(parent_frs);
index
.frs_to_idx
.get(parent_usize)
.copied()
.filter(|&idx| idx != uffs_mft::NO_ENTRY)
.unwrap_or(u32::MAX)
}
// Closure wraps the free helper `resolve_parent_compact_idx` so the
// typed `ParentFrs`/`Frs` signature is enforced at every call site
// (own↔parent swap becomes a compile error). Keeping the helper
// free-standing also keeps `build_compact_index` under the
// clippy::too_many_lines budget.
let resolve_parent = |parent_frs: uffs_mft::ParentFrs, own_frs: uffs_mft::Frs| -> u32 {
resolve_parent_compact_idx(index, parent_frs, own_frs)
};

// Phase 1: build primary compact records (parallel).
Expand Down
15 changes: 12 additions & 3 deletions crates/uffs-core/src/compact_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,12 @@ pub fn apply_usn_patch(
let mut stats = PatchStats::default();

for change in changes {
let frs_usize = uffs_mft::frs_to_usize(change.frs);
// Typed `Frs` → raw `u64` lift at the frs_to_compact CSR lookup
// boundary. The mapping table is `Vec<u32>` indexed by
// `usize`, so demoting once per change keeps the inner index
// arithmetic on raw values without leaking raw FRS into the
// outer `FileChange` API.
let frs_usize = uffs_mft::frs_to_usize(change.frs.raw());
let compact_idx = drive
.frs_to_compact
.get(frs_usize)
Expand Down Expand Up @@ -552,7 +557,10 @@ pub fn apply_usn_patch(
.as_mut_vec()
.extend_from_slice(change.filename.as_bytes());

let parent_frs_usize = uffs_mft::frs_to_usize(change.parent_frs);
// Typed `ParentFrs` → raw `u64` lift at the
// frs_to_compact CSR lookup boundary (same rationale as
// the `change.frs.raw()` lift above).
let parent_frs_usize = uffs_mft::frs_to_usize(change.parent_frs.raw());
let parent_compact = drive
.frs_to_compact
.get(parent_frs_usize)
Expand Down Expand Up @@ -618,7 +626,8 @@ pub fn apply_usn_patch(
rec.name_len = uffs_mft::len_to_u16(change.filename.len());
}

let new_parent_frs = uffs_mft::frs_to_usize(change.parent_frs);
// Typed `ParentFrs` → raw lift on the rename path.
let new_parent_frs = uffs_mft::frs_to_usize(change.parent_frs.raw());
let new_parent_compact = drive
.frs_to_compact
.get(new_parent_frs)
Expand Down
40 changes: 20 additions & 20 deletions crates/uffs-core/src/compact_loader_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,29 +125,29 @@ fn apply_usn_patch_handles_create_delete_rename_skip() {
let changes = vec![
// Delete FRS 10 ("foo.txt").
FileChange {
frs: 10,
frs: 10_u64.into(),
deleted: true,
..FileChange::default()
},
// Rename FRS 11 ("bar.rs" → "bar2.rs"), parent unchanged.
FileChange {
frs: 11,
parent_frs: 5,
frs: 11_u64.into(),
parent_frs: 5_u64.into(),
filename: "bar2.rs".to_owned(),
renamed: true,
..FileChange::default()
},
// Create FRS 13 ("new.txt", parent=root).
FileChange {
frs: 13,
parent_frs: 5,
frs: 13_u64.into(),
parent_frs: 5_u64.into(),
filename: "new.txt".to_owned(),
created: true,
..FileChange::default()
},
// Skip: FRS 99 doesn't map to any compact record.
FileChange {
frs: 99,
frs: 99_u64.into(),
deleted: true,
..FileChange::default()
},
Expand Down Expand Up @@ -180,7 +180,7 @@ fn apply_usn_patch_handles_create_delete_rename_skip() {
fn apply_usn_patch_marks_deleted_record_with_zero_name_len() {
let mut drive = make_synthetic_drive();
let changes = vec![FileChange {
frs: 10,
frs: 10_u64.into(),
deleted: true,
..FileChange::default()
}];
Expand All @@ -207,8 +207,8 @@ fn apply_usn_patch_marks_deleted_record_with_zero_name_len() {
fn apply_usn_patch_renamed_record_has_new_name_in_blob() {
let mut drive = make_synthetic_drive();
let changes = vec![FileChange {
frs: 11,
parent_frs: 5,
frs: 11_u64.into(),
parent_frs: 5_u64.into(),
filename: "bar2.rs".to_owned(),
renamed: true,
..FileChange::default()
Expand Down Expand Up @@ -246,8 +246,8 @@ fn apply_usn_patch_created_record_appended_with_correct_parent() {
let initial_record_count = drive.records.len();

let changes = vec![FileChange {
frs: 13,
parent_frs: 5,
frs: 13_u64.into(),
parent_frs: 5_u64.into(),
filename: "new.txt".to_owned(),
created: true,
..FileChange::default()
Expand Down Expand Up @@ -315,7 +315,7 @@ fn apply_usn_patch_rebuilds_children_csr_excluding_deletes() {
);

let changes = vec![FileChange {
frs: 10,
frs: 10_u64.into(),
deleted: true,
..FileChange::default()
}];
Expand Down Expand Up @@ -375,8 +375,8 @@ fn apply_usn_patch_keeps_frs_to_compact_in_lockstep() {
);

apply_usn_patch(&mut drive, &[FileChange {
frs: 13,
parent_frs: 5,
frs: 13_u64.into(),
parent_frs: 5_u64.into(),
filename: "n1.txt".to_owned(),
created: true,
..FileChange::default()
Expand All @@ -399,7 +399,7 @@ fn apply_usn_patch_keeps_frs_to_compact_in_lockstep() {

// ── 2. Delete existing FRS 10 → expect slot reset to u32::MAX.
apply_usn_patch(&mut drive, &[FileChange {
frs: 10,
frs: 10_u64.into(),
deleted: true,
..FileChange::default()
}]);
Expand All @@ -415,7 +415,7 @@ fn apply_usn_patch_keeps_frs_to_compact_in_lockstep() {

// ── 3. Reuse round-trip: delete FRS 13, then create FRS 13 again.
apply_usn_patch(&mut drive, &[FileChange {
frs: 13,
frs: 13_u64.into(),
deleted: true,
..FileChange::default()
}]);
Expand All @@ -431,8 +431,8 @@ fn apply_usn_patch_keeps_frs_to_compact_in_lockstep() {

let pre_recreate_records = drive.records.len();
apply_usn_patch(&mut drive, &[FileChange {
frs: 13,
parent_frs: 5,
frs: 13_u64.into(),
parent_frs: 5_u64.into(),
filename: "n2.txt".to_owned(),
created: true,
..FileChange::default()
Expand All @@ -453,8 +453,8 @@ fn apply_usn_patch_keeps_frs_to_compact_in_lockstep() {

// ── 4. Out-of-range create grows the mapping.
apply_usn_patch(&mut drive, &[FileChange {
frs: 200,
parent_frs: 5,
frs: 200_u64.into(),
parent_frs: 5_u64.into(),
filename: "far.txt".to_owned(),
created: true,
..FileChange::default()
Expand Down
Loading
Loading