Skip to content

Commit 2b2ce47

Browse files
committed
Merge branch 'fix-f-drive-parity'
2 parents 1fa9b76 + 8541af1 commit 2b2ce47

File tree

2 files changed

+80
-1
lines changed

2 files changed

+80
-1
lines changed

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

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ pub fn parse_record_to_index(data: &[u8], frs: u64, index: &mut crate::index::Mf
5757
StandardInformation, file_reference_to_frs, filetime_to_unix_micros,
5858
};
5959

60+
// Check for parity debug logging
61+
let debug_parity = std::env::var("UFFS_PARITY_DEBUG").is_ok();
62+
6063
if data.len() < size_of::<FileRecordSegmentHeader>() {
6164
return false;
6265
}
@@ -97,6 +100,7 @@ pub fn parse_record_to_index(data: &[u8], frs: u64, index: &mut crate::index::Mf
97100
let mut name_parse_counter: u16 = 0;
98101
let mut default_size = 0u64;
99102
let mut default_allocated = 0u64;
103+
let mut found_data_attr = false;
100104
// User-visible ADS: (stream_name, size, allocated)
101105
let mut additional_streams: SmallVec<[(String, u64, u64); 4]> = SmallVec::new();
102106
// Internal NTFS streams (e.g. $REPARSE, $EA, $OBJECT_ID) — not emitted as
@@ -221,10 +225,23 @@ pub fn parse_record_to_index(data: &[u8], frs: u64, index: &mut crate::index::Mf
221225
let lowest_vcn = i64::from_le_bytes(
222226
data[nr_offset..nr_offset + 8].try_into().unwrap_or([0; 8]),
223227
);
224-
lowest_vcn == 0
228+
let is_primary = lowest_vcn == 0;
229+
if debug_parity && !is_primary {
230+
eprintln!(
231+
"[PARITY_DEBUG] FRS={}: $DATA LowestVCN={}, SKIPPED (continuation extent)",
232+
frs, lowest_vcn
233+
);
234+
}
235+
is_primary
225236
} else {
226237
// Can't read LowestVCN — assume primary (LowestVCN == 0 is common case)
227238
// This prevents skipping valid $DATA attributes near record boundaries
239+
if debug_parity {
240+
eprintln!(
241+
"[PARITY_DEBUG] FRS={}: $DATA can't read LowestVCN (nr_offset + 8 > data.len()), assuming primary",
242+
frs
243+
);
244+
}
228245
true
229246
}
230247
};
@@ -252,6 +269,12 @@ pub fn parse_record_to_index(data: &[u8], frs: u64, index: &mut crate::index::Mf
252269
.try_into()
253270
.unwrap_or([0; 8]),
254271
);
272+
if debug_parity && name_len == 0 {
273+
eprintln!(
274+
"[PARITY_DEBUG] FRS={}: $DATA non-resident, size={}, allocated={}",
275+
frs, size, allocated
276+
);
277+
}
255278
(size, allocated)
256279
} else if alloc_offset + 8 <= data.len() {
257280
// Can read AllocatedSize but not DataSize — use AllocatedSize for both
@@ -261,8 +284,20 @@ pub fn parse_record_to_index(data: &[u8], frs: u64, index: &mut crate::index::Mf
261284
.try_into()
262285
.unwrap_or([0; 8]),
263286
);
287+
if debug_parity && name_len == 0 {
288+
eprintln!(
289+
"[PARITY_DEBUG] FRS={}: $DATA non-resident (truncated), allocated={}",
290+
frs, allocated
291+
);
292+
}
264293
(allocated, allocated)
265294
} else {
295+
if debug_parity && name_len == 0 {
296+
eprintln!(
297+
"[PARITY_DEBUG] FRS={}: $DATA non-resident, but can't read size/allocated (offset beyond data.len()), size=0",
298+
frs
299+
);
300+
}
266301
(0, 0)
267302
}
268303
} else {
@@ -276,8 +311,17 @@ pub fn parse_record_to_index(data: &[u8], frs: u64, index: &mut crate::index::Mf
276311
.try_into()
277312
.unwrap_or([0; 4]),
278313
) as u64;
314+
if debug_parity && name_len == 0 {
315+
eprintln!("[PARITY_DEBUG] FRS={}: $DATA resident, size={}", frs, len);
316+
}
279317
(len, 0) // allocated_size = 0 for resident files
280318
} else {
319+
if debug_parity && name_len == 0 {
320+
eprintln!(
321+
"[PARITY_DEBUG] FRS={}: $DATA resident, but can't read value_length, size=0",
322+
frs
323+
);
324+
}
281325
(0, 0)
282326
}
283327
};
@@ -286,6 +330,13 @@ pub fn parse_record_to_index(data: &[u8], frs: u64, index: &mut crate::index::Mf
286330
// Default stream
287331
default_size = size;
288332
default_allocated = allocated;
333+
found_data_attr = true;
334+
if debug_parity && size == 0 {
335+
eprintln!(
336+
"[PARITY_DEBUG] FRS={}: Default $DATA stream has ZERO size",
337+
frs
338+
);
339+
}
289340
} else {
290341
// Alternate Data Stream (ADS)
291342
let name_offset = offset + attr_header.name_offset as usize;
@@ -554,6 +605,11 @@ pub fn parse_record_to_index(data: &[u8], frs: u64, index: &mut crate::index::Mf
554605
default_size = dir_index_size;
555606
default_allocated = dir_index_allocated;
556607
}
608+
} else if debug_parity && !found_data_attr && default_size == 0 {
609+
eprintln!(
610+
"[PARITY_DEBUG] FRS={}: Non-directory file has NO $DATA attribute, size will be 0",
611+
frs
612+
);
557613
}
558614

559615
// Handle records without a filename in the base record

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,29 @@ impl ParallelMftReader {
486486
"✅ Sliding window IOCP with direct-to-index parsing complete (I/O overlap analysis)"
487487
);
488488

489+
// Parity debug: count files with size=0 vs size>0
490+
if std::env::var("UFFS_PARITY_DEBUG").is_ok() {
491+
let mut files_with_size = 0usize;
492+
let mut files_with_zero_size = 0usize;
493+
let mut dirs = 0usize;
494+
for record in &index.records {
495+
if record.stdinfo.is_directory() {
496+
dirs += 1;
497+
} else if record.first_stream.size.length > 0 {
498+
files_with_size += 1;
499+
} else {
500+
files_with_zero_size += 1;
501+
}
502+
}
503+
eprintln!(
504+
"[PARITY_DEBUG] Summary: total_records={}, directories={}, files_with_size={}, files_with_zero_size={}",
505+
index.records.len(),
506+
dirs,
507+
files_with_size,
508+
files_with_zero_size
509+
);
510+
}
511+
489512
Ok(index)
490513
}
491514
}

0 commit comments

Comments
 (0)