Skip to content

Commit 7b68ae4

Browse files
committed
chore: development v0.3.40 - comprehensive testing complete [auto-commit]
1 parent 7f352eb commit 7b68ae4

File tree

17 files changed

+303
-142
lines changed

17 files changed

+303
-142
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ exclude = [
3636
# Workspace Package Metadata (inherited by all crates)
3737
# ─────────────────────────────────────────────────────────────────────────────
3838
[workspace.package]
39-
version = "0.3.39"
39+
version = "0.3.40"
4040
edition = "2024"
4141
rust-version = "1.85"
4242
license = "MPL-2.0 OR LicenseRef-UFFS-Commercial"

crates/uffs-cli/src/commands/raw_io.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use std::path::{Path, PathBuf};
99

1010
use anyhow::{Context, Result, bail};
11-
use tracing::info;
11+
use tracing::{debug, info};
1212
use uffs_core::MftQuery;
1313
use uffs_core::extensions::ExtensionFilter;
1414
use uffs_core::pattern::ParsedPattern;
@@ -144,6 +144,7 @@ pub(super) fn load_and_filter_native_from_mft_file(
144144
// Use ChaosMftReader for deterministic chaos-order testing
145145
// Works on all platforms when reading offline MFT files
146146
use uffs_mft::io::readers::parallel::{ChaosMftReader, ChaosStrategy};
147+
debug!(seed, "[PARITY_TRACE] CHAOS path");
147148
info!(
148149
seed = seed,
149150
"Loading MFT with chaos-order (randomized chunks)"
@@ -159,6 +160,7 @@ pub(super) fn load_and_filter_native_from_mft_file(
159160
// IOCP capture: use load_iocp_to_index which replays the exact Windows LIVE
160161
// pipeline (parallel parse → MftRecordMerger → merge → from_parsed_records)
161162
// Skip this path if debug_tree is set (use sequential debug path instead)
163+
debug!("[PARITY_TRACE] IOCP path -> load_iocp_to_index()");
162164
info!("IOCP capture detected - using LIVE pipeline replay for exact parity");
163165
uffs_mft::load_iocp_to_index(mft_path)
164166
.with_context(|| format!("Failed to load IOCP capture: {}", mft_path.display()))?
@@ -169,17 +171,27 @@ pub(super) fn load_and_filter_native_from_mft_file(
169171
};
170172

171173
if debug_tree {
174+
debug!("[PARITY_TRACE] DEBUG_TREE path");
172175
load_raw_mft_with_debug(mft_path, &options)?
173176
} else {
177+
debug!("[PARITY_TRACE] RAW MFT path -> load_raw_to_index_with_options()");
174178
MftReader::load_raw_to_index_with_options(mft_path, &options)
175179
.with_context(|| format!("Failed to load raw MFT: {}", mft_path.display()))?
176180
}
177181
};
178182
let load_ms = t_load.elapsed().as_millis();
183+
debug!(
184+
records = index.records.len(),
185+
load_ms, "[PARITY_TRACE] loaded"
186+
);
179187

180188
let t_query = std::time::Instant::now();
181189
let results = execute_index_query_native(&index, filters, needs_paths)?;
182190
let query_ms = t_query.elapsed().as_millis();
191+
debug!(
192+
results = results.len(),
193+
query_ms, "[PARITY_TRACE] query returned"
194+
);
183195

184196
Ok(NativeOfflineQueryResults {
185197
index,

crates/uffs-mft/src/index/builder.rs

Lines changed: 3 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -124,66 +124,11 @@ impl MftIndex {
124124
record.fn_accessed = parsed.fn_accessed;
125125
record.fn_mft_changed = parsed.fn_mft_changed;
126126

127-
// Set $STANDARD_INFORMATION timestamps and flags
128-
record.stdinfo.created = parsed.std_info.created;
129-
record.stdinfo.modified = parsed.std_info.modified;
130-
record.stdinfo.accessed = parsed.std_info.accessed;
131-
record.stdinfo.mft_changed = parsed.std_info.mft_changed;
132-
record.stdinfo.usn = parsed.std_info.usn;
133-
record.stdinfo.security_id = parsed.std_info.security_id;
134-
record.stdinfo.owner_id = parsed.std_info.owner_id;
127+
// Set $STANDARD_INFORMATION using the canonical conversion method.
128+
// This is the single source of truth for ExtendedStandardInfo → StandardInfo.
129+
record.stdinfo = StandardInfo::from_extended(&parsed.std_info);
135130
record.stdinfo.set_directory(parsed.is_directory);
136131

137-
// Set attribute flags from ExtendedStandardInfo
138-
if parsed.std_info.is_readonly {
139-
record.stdinfo.flags |= StandardInfo::IS_READONLY;
140-
}
141-
if parsed.std_info.is_archive {
142-
record.stdinfo.flags |= StandardInfo::IS_ARCHIVE;
143-
}
144-
if parsed.std_info.is_system {
145-
record.stdinfo.flags |= StandardInfo::IS_SYSTEM;
146-
}
147-
if parsed.std_info.is_hidden {
148-
record.stdinfo.flags |= StandardInfo::IS_HIDDEN;
149-
}
150-
if parsed.std_info.is_offline {
151-
record.stdinfo.flags |= StandardInfo::IS_OFFLINE;
152-
}
153-
if parsed.std_info.is_not_content_indexed {
154-
record.stdinfo.flags |= StandardInfo::IS_NOT_INDEXED;
155-
}
156-
if parsed.std_info.is_compressed {
157-
record.stdinfo.flags |= StandardInfo::IS_COMPRESSED;
158-
}
159-
if parsed.std_info.is_encrypted {
160-
record.stdinfo.flags |= StandardInfo::IS_ENCRYPTED;
161-
}
162-
if parsed.std_info.is_sparse {
163-
record.stdinfo.flags |= StandardInfo::IS_SPARSE;
164-
}
165-
if parsed.std_info.is_reparse {
166-
record.stdinfo.flags |= StandardInfo::IS_REPARSE;
167-
}
168-
if parsed.std_info.is_temporary {
169-
record.stdinfo.flags |= StandardInfo::IS_TEMPORARY;
170-
}
171-
if parsed.std_info.is_integrity_stream {
172-
record.stdinfo.flags |= StandardInfo::IS_INTEGRITY_STREAM;
173-
}
174-
if parsed.std_info.is_no_scrub_data {
175-
record.stdinfo.flags |= StandardInfo::IS_NO_SCRUB_DATA;
176-
}
177-
if parsed.std_info.is_pinned {
178-
record.stdinfo.flags |= StandardInfo::IS_PINNED;
179-
}
180-
if parsed.std_info.is_unpinned {
181-
record.stdinfo.flags |= StandardInfo::IS_UNPINNED;
182-
}
183-
if parsed.std_info.is_virtual {
184-
record.stdinfo.flags |= StandardInfo::IS_VIRTUAL;
185-
}
186-
187132
// Set name info (offset and extension_id were computed before borrowing record)
188133
record.first_name.name =
189134
IndexNameRef::new(name_offset, name_len, is_ascii, extension_id);

crates/uffs-mft/src/index/types.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,102 @@ impl StandardInfo {
7676
/// Virtual file attribute flag.
7777
pub const IS_VIRTUAL: u32 = 1 << 16;
7878

79+
/// Create from [`ExtendedStandardInfo`] - the canonical conversion point.
80+
///
81+
/// This is the **single source of truth** for converting parsed NTFS
82+
/// attributes to compact index storage. All code paths should use:
83+
/// 1. [`ExtendedStandardInfo::from_attributes()`] to parse raw flags
84+
/// 2. This method to convert to compact [`StandardInfo`]
85+
///
86+
/// [`ExtendedStandardInfo`]: crate::ntfs::ExtendedStandardInfo
87+
/// [`ExtendedStandardInfo::from_attributes()`]: crate::ntfs::ExtendedStandardInfo::from_attributes
88+
#[must_use]
89+
pub const fn from_extended(ext: &crate::ntfs::ExtendedStandardInfo) -> Self {
90+
let mut flags = 0_u32;
91+
92+
// Core file attributes
93+
if ext.is_readonly {
94+
flags |= Self::IS_READONLY;
95+
}
96+
if ext.is_archive {
97+
flags |= Self::IS_ARCHIVE;
98+
}
99+
if ext.is_system {
100+
flags |= Self::IS_SYSTEM;
101+
}
102+
if ext.is_hidden {
103+
flags |= Self::IS_HIDDEN;
104+
}
105+
if ext.is_offline {
106+
flags |= Self::IS_OFFLINE;
107+
}
108+
if ext.is_not_content_indexed {
109+
flags |= Self::IS_NOT_INDEXED;
110+
}
111+
if ext.is_compressed {
112+
flags |= Self::IS_COMPRESSED;
113+
}
114+
if ext.is_encrypted {
115+
flags |= Self::IS_ENCRYPTED;
116+
}
117+
if ext.is_sparse {
118+
flags |= Self::IS_SPARSE;
119+
}
120+
if ext.is_reparse {
121+
flags |= Self::IS_REPARSE;
122+
}
123+
if ext.is_temporary {
124+
flags |= Self::IS_TEMPORARY;
125+
}
126+
127+
// Extended attributes (NTFS 3.1+ / Windows 8+)
128+
if ext.is_integrity_stream {
129+
flags |= Self::IS_INTEGRITY_STREAM;
130+
}
131+
if ext.is_no_scrub_data {
132+
flags |= Self::IS_NO_SCRUB_DATA;
133+
}
134+
if ext.is_pinned {
135+
flags |= Self::IS_PINNED;
136+
}
137+
if ext.is_unpinned {
138+
flags |= Self::IS_UNPINNED;
139+
}
140+
if ext.is_virtual {
141+
flags |= Self::IS_VIRTUAL;
142+
}
143+
144+
// Note: is_directory is set separately via set_directory() based on
145+
// MFT record flags, not $STANDARD_INFORMATION attributes.
146+
147+
Self {
148+
created: ext.created,
149+
modified: ext.modified,
150+
accessed: ext.accessed,
151+
mft_changed: ext.mft_changed,
152+
flags,
153+
usn: ext.usn,
154+
security_id: ext.security_id,
155+
owner_id: ext.owner_id,
156+
}
157+
}
158+
79159
/// Create from Windows `FILE_ATTRIBUTE_*` flags.
160+
///
161+
/// # Deprecated
162+
///
163+
/// This method is **incomplete** - it does not parse all NTFS 3.1+ flags
164+
/// (integrity, `no_scrub`, pinned, unpinned, virtual). Use the canonical
165+
/// two-step approach instead:
166+
///
167+
/// ```ignore
168+
/// let ext = ExtendedStandardInfo::from_attributes(raw_attrs);
169+
/// let info = StandardInfo::from_extended(&ext);
170+
/// ```
171+
#[deprecated(
172+
since = "0.1.0",
173+
note = "Use StandardInfo::from_extended(&ExtendedStandardInfo::from_attributes(attrs)) instead"
174+
)]
80175
#[must_use]
81176
pub fn from_attributes(attrs: u32) -> Self {
82177
let mut flags = 0_u32;

crates/uffs-mft/src/io/parser/fragment.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,12 @@ pub fn parse_record_to_fragment(
117117
Ok((si, _)) => si,
118118
Err(_) => break,
119119
};
120-
let mut info = StandardInfo::from_attributes(si.file_attributes);
120+
// Two-step canonical approach:
121+
// 1. Parse raw attrs to ExtendedStandardInfo (complete parsing)
122+
// 2. Convert to compact StandardInfo (single source of truth)
123+
let ext =
124+
crate::ntfs::ExtendedStandardInfo::from_attributes(si.file_attributes);
125+
let mut info = StandardInfo::from_extended(&ext);
121126
info.created = filetime_to_unix_micros(si.creation_time);
122127
info.modified = filetime_to_unix_micros(si.modification_time);
123128
info.accessed = filetime_to_unix_micros(si.access_time);

crates/uffs-mft/src/io/parser/index.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,13 @@ pub fn parse_record_to_index(data: &[u8], frs: u64, index: &mut crate::index::Mf
166166
Ok((si, _)) => si,
167167
Err(_) => break,
168168
};
169-
// Build StandardInfo with proper flags
170-
let mut info = StandardInfo::from_attributes(si.file_attributes);
169+
// Two-step canonical approach:
170+
// 1. Parse raw attrs to ExtendedStandardInfo (complete parsing)
171+
// 2. Convert to compact StandardInfo (single source of truth)
172+
let ext =
173+
crate::ntfs::ExtendedStandardInfo::from_attributes(si.file_attributes);
174+
let mut info = StandardInfo::from_extended(&ext);
175+
// Override timestamps from actual NTFS values
171176
info.created = filetime_to_unix_micros(si.creation_time);
172177
info.modified = filetime_to_unix_micros(si.modification_time);
173178
info.accessed = filetime_to_unix_micros(si.access_time);

crates/uffs-mft/src/io/readers/parallel/to_index.rs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,13 @@ impl ParallelMftReader {
8080
classify_wait_error_code, wait_deadline_exceeded,
8181
};
8282

83+
debug!("[PARITY_TRACE] to_index.rs: read_all_sliding_window_iocp_to_index ENTER");
8384
let record_size = self.extent_map.bytes_per_record as usize;
8485
let total_records = self.extent_map.total_records() as usize;
86+
debug!(
87+
record_size,
88+
total_records, "[PARITY_TRACE] to_index.rs: config"
89+
);
8590

8691
// Use provided values or adaptive defaults based on drive type
8792
// M1: Adaptive concurrency and I/O size based on drive type
@@ -505,6 +510,11 @@ impl ParallelMftReader {
505510
0.0
506511
};
507512

513+
debug!(
514+
records_parsed,
515+
index_entries = index.records.len(),
516+
"[PARITY_TRACE] to_index.rs: I/O complete"
517+
);
508518
info!(
509519
total_ms,
510520
wait_ms,
@@ -530,15 +540,19 @@ impl ParallelMftReader {
530540
files_with_zero_size += 1;
531541
}
532542
}
533-
eprintln!(
534-
"[PARITY_DEBUG] Summary: total_records={}, directories={}, files_with_size={}, files_with_zero_size={}",
535-
index.records.len(),
536-
dirs,
543+
debug!(
544+
total_records = index.records.len(),
545+
directories = dirs,
537546
files_with_size,
538-
files_with_zero_size
547+
files_with_zero_size,
548+
"[PARITY_DEBUG] Summary"
539549
);
540550
}
541551

552+
debug!(
553+
records = index.records.len(),
554+
"[PARITY_TRACE] to_index.rs: EXIT (NO compute_tree_metrics yet)"
555+
);
542556
Ok(index)
543557
}
544558
}

crates/uffs-mft/src/parse/direct_index.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,12 @@ pub fn parse_record_to_index(data: &[u8], frs: u64, index: &mut crate::index::Mf
161161
Ok((si, _)) => si,
162162
Err(_) => break,
163163
};
164-
// Build StandardInfo with proper flags
165-
let mut info = StandardInfo::from_attributes(si.file_attributes);
164+
// Two-step canonical approach:
165+
// 1. Parse raw attrs to ExtendedStandardInfo (complete parsing)
166+
// 2. Convert to compact StandardInfo (single source of truth)
167+
let ext =
168+
crate::ntfs::ExtendedStandardInfo::from_attributes(si.file_attributes);
169+
let mut info = StandardInfo::from_extended(&ext);
166170
info.created = filetime_to_unix_micros(si.creation_time);
167171
info.modified = filetime_to_unix_micros(si.modification_time);
168172
info.accessed = filetime_to_unix_micros(si.access_time);

0 commit comments

Comments
 (0)