refactor(uffs-mft): introduce Lcn(i64) newtype (Phase 4 sub-phase 5d, refs #191)#258
Merged
Merged
Conversation
…mbers
Replaces ad-hoc `i64` LCN values across MFT extents, NTFS data runs,
and their downstream consumers with a typed `uffs_mft::platform::Lcn`
newtype so the two distinct sparse conventions stop being open-coded
as raw integer comparisons that any caller could mis-interpret.
The newtype is `Copy + Eq + Ord + Hash + Display` and exposes
`Lcn::ZERO`, `Lcn::HOLE`, `Lcn::new`, `Lcn::raw`, `Lcn::raw_unsigned`,
`Lcn::is_hole`, and `Lcn::is_zero` so:
* sparse-extent detection (`extent.lcn < 0`) goes through
`Lcn::is_hole` everywhere — extent map, chunking, bitmap reader,
benchmark CLI;
* data-run sparse encoding ("no LCN offset emitted" → running total
unchanged) keeps the distinct `Lcn::ZERO` / `Lcn::is_zero`
predicate, so the two NTFS conventions remain surgically separated;
* the unsigned byte-offset arithmetic (`raw_unsigned() *
bytes_per_cluster`) is a single documented bit-pattern reinterpret
helper instead of an open-coded `cast_unsigned()` at every site;
* tracing fields use `%`-Display so logs render `42` not `Lcn(42)`.
## Wire format unchanged
Pinned by `platform::extents::tests::byte_offset_*` /
`ntfs::data_runs::tests::parse_data_runs_marks_sparse_runs_with_zero_lcn`
/ `platform::lcn::tests::raw_roundtrip_preserves_i64_exactly`:
* `FSCTL_GET_RETRIEVAL_POINTERS` decode still reads a signed 64-bit
`LcnPosition` and wraps it via `Lcn::new(read_i64)` — the
`RETRIEVAL_POINTERS_BUFFER` byte layout is untouched;
* on-disk `$DATA` mapping-pair decode keeps emitting `Lcn::ZERO`
when `offset_size == 0` (sparse run) and a positive running total
otherwise — `parse_data_runs` round-trips bit-for-bit;
* `MftExtent::byte_offset` / `DataRun::byte_offset` produce the same
unsigned offsets as the prior `lcn.cast_unsigned() *
bytes_per_cluster` arithmetic, including the historic
`nonneg_to_u64` clamp for hole sentinels;
* `#[repr(C)]` Win32 ABI mirrors are not touched.
## Untouched on purpose
`VOLUME_DATA.mft_start_lcn: u64` (from `FSCTL_GET_NTFS_VOLUME_DATA`)
stays a raw `u64` — it is the unsigned-by-spec starting cluster, not
an LCN that can carry a sparse marker; consumers wrap it into `Lcn`
at the construction boundary (`Lcn::new(mft_start_lcn.cast_signed())`)
so the type discipline lives where MftExtent values are actually
manipulated.
Refs #191
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 4 sub-phase 5d — introduces a typed
uffs_mft::platform::Lcnnewtype wrapping the raw
i64cluster identifiers that NTFS handsback from
FSCTL_GET_RETRIEVAL_POINTERSand$DATAmapping-pairdecode. All MFT-extent and data-run consumers now go through the
newtype, so the two distinct sparse conventions stop being
open-coded as raw integer comparisons at every call site.
What changes
New
Lcnnewtype (crates/uffs-mft/src/platform/lcn.rs)Copy + Eq + Ord + Hash + Display + Debugso it slots intoHashMap/BTreeMapkeys, comparisons, and tracing fields.Lcn::ZERO(data-run sparse: "no LCN offset emitted")and
Lcn::HOLE = -1(retrieval-pointer sparse:LCN_HOLE).new,raw,raw_unsigned(documented bit-patternreinterpret),
is_hole(any negative — matches the historiclcn < 0guard),is_zero(data-run sparse predicate).Field migrations
MftExtent.lcn: i64→Lcn—byte_offsetnow branches onis_hole()and multiplies viaraw_unsigned().DataRun.lcn: i64→Lcn—is_sparse()checksis_zero(),byte_offsetclamps any hole viais_hole()(defensive againstcorrupt buffers) and multiplies via
raw_unsigned().parse_data_runsemitsLcn::ZEROfor runs without an encodedoffset and
Lcn::new(current_lcn)otherwise — wire bytesunchanged.
parse_retrieval_pointerswraps each decodedi64LCN withLcn::newat the FFI boundary —RETRIEVAL_POINTERS_BUFFERbyte layout untouched.
Call-site migrations
io/extent_map.rs,io/chunking.rs,platform/upcase.rs,platform/volume.rs,reader/{dataframe_read, dataframe_timing, index_read, index_timing, persistence, persistence_capture}.rs,commands/windows/{benchmark_index, benchmark_mft, info}.rs,ntfs/tests.rs.Each callsite went from one of these patterns:
lcn < 0/lcn == 0→lcn.is_hole()/lcn.is_zero()lcn.cast_unsigned() * bytes_per_cluster→lcn.raw_unsigned() * bytes_per_clusterlcn * bpc.cast_signed()→lcn.raw() * bpc.cast_signed()lcn: kernel_value.cast_signed()→lcn: Lcn::new(kernel_value.cast_signed())lcn = ext.lcn→lcn = %ext.lcnso logs render42not
Lcn(42).Wire format unchanged
Pinned by:
platform::lcn::tests::raw_roundtrip_preserves_i64_exactly— anyi64survivesLcn::new→.raw()byte-identically.platform::lcn::tests::raw_unsigned_reinterprets_bit_pattern—raw_unsigned()matchesi64::cast_unsigned()bit-for-bit,including the
Lcn::new(-1).raw_unsigned() == u64::MAXcorner.platform::extents::tests::byte_offset_returns_zero_for_sparse_extents/
byte_offset_matches_lcn_times_bytes_per_cluster/byte_size_independent_of_lcn.ntfs::data_runs::tests::is_sparse_matches_only_zero_sentinel/
byte_offset_clamps_holes_and_yields_lcn_times_bpc_otherwise/parse_data_runs_marks_sparse_runs_with_zero_lcn.#[repr(C)]Win32 ABI mirrors and on-disk byte layouts are nottouched.
Untouched on purpose
VOLUME_DATA.mft_start_lcn: u64(fromFSCTL_GET_NTFS_VOLUME_DATA) stays a rawu64— it's theunsigned-by-spec starting cluster of
$MFT, not an LCN that cancarry a sparse marker. Consumers wrap it into
Lcnat theMftExtentconstruction boundary(
Lcn::new(mft_start_lcn.cast_signed())), so the type disciplinelives where extents are actually walked.
Verification
just lint-pre-push(full local gate includinglint-ci-windows) — 124s green.cargo xwin check --target x86_64-pc-windows-msvc --workspace --all-targets.cargo test --workspace --lib --bins— 163 lib tests pass(was 157 + 6 new
Lcn/MftExtent/DataRunregression tests).Refs #191