@@ -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