Skip to content

Commit 51cd173

Browse files
committed
Merge remote-tracking branch 'origin/fix-f-drive-parity'
2 parents 44ef3f9 + 2e0abd1 commit 51cd173

File tree

1 file changed

+123
-2
lines changed

1 file changed

+123
-2
lines changed

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

Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,16 @@ pub fn parse_record_to_index(data: &[u8], frs: u64, index: &mut crate::index::Mf
714714

715715
// Now create the record and set up streams
716716
let record = index.get_or_create(frs);
717+
718+
// Snapshot existing extension data before overwriting (extension may
719+
// have been processed first on a fragmented MFT).
720+
let ext_stream_head = record.first_stream.next_entry;
721+
let ext_stream_count = record.stream_count.saturating_sub(1);
722+
let ext_total_extra = record.total_stream_count.saturating_sub(1);
723+
let ext_internal_head = record.first_internal_stream;
724+
let ext_internal_size = record.internal_streams_size;
725+
let ext_internal_alloc = record.internal_streams_allocated;
726+
717727
record.stdinfo = std_info;
718728
record.first_stream.size = SizeInfo {
719729
length: default_size,
@@ -750,8 +760,46 @@ pub fn parse_record_to_index(data: &[u8], frs: u64, index: &mut crate::index::Mf
750760
record.total_stream_count =
751761
1 + additional_stream_count as u16 + internal_stream_count as u16;
752762

753-
// Leave first_name empty - extension record will fill it
754-
return false;
763+
// Merge extension streams that were processed before this base record.
764+
if ext_stream_count > 0 {
765+
let base_stream_tail = if !stream_indices.is_empty() {
766+
*stream_indices.last().expect("stream_indices is non-empty")
767+
} else {
768+
NO_ENTRY
769+
};
770+
if base_stream_tail != NO_ENTRY {
771+
index.streams[base_stream_tail as usize].next_entry = ext_stream_head;
772+
} else {
773+
let record = index.get_or_create(frs);
774+
record.first_stream.next_entry = ext_stream_head;
775+
}
776+
let record = index.get_or_create(frs);
777+
record.stream_count += ext_stream_count;
778+
record.total_stream_count += ext_stream_count;
779+
}
780+
781+
if ext_internal_head != NO_ENTRY {
782+
if first_internal != NO_ENTRY {
783+
let mut tail = first_internal;
784+
while index.internal_streams[tail as usize].next_entry != NO_ENTRY {
785+
tail = index.internal_streams[tail as usize].next_entry;
786+
}
787+
index.internal_streams[tail as usize].next_entry = ext_internal_head;
788+
} else {
789+
let record = index.get_or_create(frs);
790+
record.first_internal_stream = ext_internal_head;
791+
}
792+
let record = index.get_or_create(frs);
793+
record.internal_streams_size += ext_internal_size;
794+
record.internal_streams_allocated += ext_internal_alloc;
795+
record.total_stream_count += ext_total_extra.saturating_sub(ext_stream_count);
796+
}
797+
798+
// Emit the record even though first_name is empty - extension record will fill
799+
// it. The ADS streams have been correctly stored in the index and
800+
// must be counted to match C++ behavior (which emits all streams
801+
// during traversal regardless of when $FILE_NAME was added).
802+
return true;
755803
}
756804
};
757805

@@ -854,6 +902,24 @@ pub fn parse_record_to_index(data: &[u8], frs: u64, index: &mut crate::index::Mf
854902
// Now get or create the record in the index - no more index mutations after
855903
// this
856904
let record = index.get_or_create(frs);
905+
906+
// Snapshot existing extension data BEFORE overwriting.
907+
// On a fragmented MFT, extension records may be processed before their base
908+
// record. The extension parser adds names/ADS/streams to a placeholder
909+
// record. We must preserve that data when the base record arrives.
910+
let ext_stream_head = record.first_stream.next_entry;
911+
let ext_stream_count = record.stream_count.saturating_sub(1); // exclude default
912+
let ext_total_extra = record.total_stream_count.saturating_sub(1);
913+
let ext_name_next = record.first_name.next_entry;
914+
let ext_name_count = if record.first_name.name.is_valid() {
915+
record.name_count
916+
} else {
917+
0
918+
};
919+
let ext_internal_head = record.first_internal_stream;
920+
let ext_internal_size = record.internal_streams_size;
921+
let ext_internal_alloc = record.internal_streams_allocated;
922+
857923
record.stdinfo = std_info;
858924
record.first_stream.size = SizeInfo {
859925
length: default_size,
@@ -911,6 +977,61 @@ pub fn parse_record_to_index(data: &[u8], frs: u64, index: &mut crate::index::Mf
911977
index.streams[current_idx].next_entry = next_idx;
912978
}
913979

980+
// Merge extension data that was processed before this base record.
981+
// Reconnect extension stream/name/internal chains at the end of base chains.
982+
if ext_stream_count > 0 {
983+
// Find the tail of the base user-visible stream chain
984+
let base_stream_tail = if !stream_indices.is_empty() {
985+
*stream_indices.last().expect("stream_indices is non-empty")
986+
} else {
987+
NO_ENTRY // first_stream itself is the tail
988+
};
989+
if base_stream_tail != NO_ENTRY {
990+
index.streams[base_stream_tail as usize].next_entry = ext_stream_head;
991+
} else {
992+
let record = index.get_or_create(frs);
993+
record.first_stream.next_entry = ext_stream_head;
994+
}
995+
let record = index.get_or_create(frs);
996+
record.stream_count += ext_stream_count;
997+
record.total_stream_count += ext_stream_count;
998+
}
999+
1000+
if ext_name_count > 0 {
1001+
// Find the tail of the base name chain
1002+
let base_name_tail = if !link_indices.is_empty() {
1003+
*link_indices.last().expect("link_indices is non-empty")
1004+
} else {
1005+
NO_ENTRY // first_name itself is the tail
1006+
};
1007+
if base_name_tail != NO_ENTRY {
1008+
index.links[base_name_tail as usize].next_entry = ext_name_next;
1009+
} else {
1010+
let record = index.get_or_create(frs);
1011+
record.first_name.next_entry = ext_name_next;
1012+
}
1013+
let record = index.get_or_create(frs);
1014+
record.name_count += ext_name_count;
1015+
}
1016+
1017+
if ext_internal_head != NO_ENTRY {
1018+
// Find the tail of the base internal stream chain
1019+
if first_internal != NO_ENTRY {
1020+
let mut tail = first_internal;
1021+
while index.internal_streams[tail as usize].next_entry != NO_ENTRY {
1022+
tail = index.internal_streams[tail as usize].next_entry;
1023+
}
1024+
index.internal_streams[tail as usize].next_entry = ext_internal_head;
1025+
} else {
1026+
let record = index.get_or_create(frs);
1027+
record.first_internal_stream = ext_internal_head;
1028+
}
1029+
let record = index.get_or_create(frs);
1030+
record.internal_streams_size += ext_internal_size;
1031+
record.internal_streams_allocated += ext_internal_alloc;
1032+
record.total_stream_count += ext_total_extra.saturating_sub(ext_stream_count);
1033+
}
1034+
9141035
// Build parent-child relationship for tree metrics computation
9151036
// This is critical for compute_tree_metrics() to work correctly.
9161037
// Each name (primary + additional) creates a child entry in its parent.

0 commit comments

Comments
 (0)