Skip to content

Commit 76c07f9

Browse files
committed
fix: store stdinfo for base records without $FILE_NAME
When base record has no $FILE_NAME (it's in extension record), still store stdinfo including is_directory flag. This fixes: - 50 directories showing as files without trailing backslash - Timestamps showing as epoch instead of real values - Size/allocated showing as 0 instead of real values
1 parent 8e378fa commit 76c07f9

File tree

2 files changed

+111
-14
lines changed

2 files changed

+111
-14
lines changed

LOG/2026_01_29_22_00_CHANGELOG_HEALING.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,74 @@ error[E0502]: cannot borrow `*fragment` as mutable because it is also borrowed a
5959
- Windows cross-compilation succeeded
6060
- Committed and pushed as v0.2.144
6161

62+
---
63+
64+
## Additional Fix - 2026-01-29 (Continued)
65+
66+
### What Failed
67+
68+
After the extension record name merging fix, 99.8% of missing paths were recovered. However, ~120 paths remained different:
69+
- 50 directories showing as files (no trailing backslash)
70+
- 94 ADS entries missing
71+
- 26 files missing
72+
73+
**Root Cause**: When the base record has NO `$FILE_NAME` attribute (it's entirely in the extension record), the base record parsing returned early without storing `stdinfo` (including `is_directory` flag).
74+
75+
This caused:
76+
1. `is_directory = false` (default) even for directories
77+
2. No trailing backslash added to directory paths
78+
3. Timestamps showing as epoch (1969-12-31)
79+
80+
### The Fix
81+
82+
In `parse_record_to_index` and `parse_record_to_fragment`:
83+
84+
**Before**:
85+
```rust
86+
// Skip records without a filename
87+
let (name, parent_frs, _namespace) = match primary_name {
88+
Some(n) => n,
89+
None => return false, // <-- BUG: Never stores stdinfo!
90+
};
91+
92+
// Set directory flag in std_info
93+
if is_directory {
94+
std_info.set_directory(true);
95+
}
96+
```
97+
98+
**After**:
99+
```rust
100+
// Set directory flag in std_info BEFORE checking for filename
101+
if is_directory {
102+
std_info.set_directory(true);
103+
}
104+
105+
// Handle records without a filename in the base record
106+
let (name, parent_frs, _namespace) = match primary_name {
107+
Some(n) => n,
108+
None => {
109+
// No $FILE_NAME in base record - store stdinfo anyway
110+
let record = index.get_or_create(frs);
111+
record.stdinfo = std_info;
112+
record.first_stream.size = SizeInfo {
113+
length: default_size,
114+
allocated: default_allocated,
115+
};
116+
return false;
117+
}
118+
};
119+
```
120+
121+
### Files Changed
122+
123+
- `crates/uffs-mft/src/io.rs`: Fixed `parse_record_to_index` and `parse_record_to_fragment`
124+
125+
### Expected Impact
126+
127+
- Fixes 50 directories showing as files (now have correct `is_directory` flag)
128+
- Trailing backslash will be added to directory paths
129+
- Timestamps will be correct (not epoch)
130+
131+
### CI Pipeline Status
132+

crates/uffs-mft/src/io.rs

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -727,17 +727,30 @@ pub fn parse_record_to_index(data: &[u8], frs: u64, index: &mut crate::index::Mf
727727
offset += attr_header.length as usize;
728728
}
729729

730-
// Skip records without a filename
731-
let (name, parent_frs, _namespace) = match primary_name {
732-
Some(n) => n,
733-
None => return false,
734-
};
735-
736-
// Set directory flag in std_info
730+
// Set directory flag in std_info BEFORE checking for filename
731+
// This ensures is_directory is set even when $FILE_NAME is in extension record
737732
if is_directory {
738733
std_info.set_directory(true);
739734
}
740735

736+
// Handle records without a filename in the base record
737+
// The $FILE_NAME may be in an extension record - we still need to store stdinfo
738+
let (name, parent_frs, _namespace) = match primary_name {
739+
Some(n) => n,
740+
None => {
741+
// No $FILE_NAME in base record - store stdinfo anyway
742+
// The extension record will add the name later
743+
let record = index.get_or_create(frs);
744+
record.stdinfo = std_info;
745+
record.first_stream.size = SizeInfo {
746+
length: default_size,
747+
allocated: default_allocated,
748+
};
749+
// Leave first_name empty - extension record will fill it
750+
return false;
751+
}
752+
};
753+
741754
// Add primary name to names buffer and get reference
742755
let name_offset = index.add_name(&name);
743756
let name_len = name.len();
@@ -1360,17 +1373,30 @@ pub fn parse_record_to_fragment(
13601373
offset += attr_header.length as usize;
13611374
}
13621375

1363-
// Skip records without a filename
1364-
let (name, parent_frs, _namespace) = match primary_name {
1365-
Some(n) => n,
1366-
None => return false,
1367-
};
1368-
1369-
// Set directory flag in std_info
1376+
// Set directory flag in std_info BEFORE checking for filename
1377+
// This ensures is_directory is set even when $FILE_NAME is in extension record
13701378
if is_directory {
13711379
std_info.set_directory(true);
13721380
}
13731381

1382+
// Handle records without a filename in the base record
1383+
// The $FILE_NAME may be in an extension record - we still need to store stdinfo
1384+
let (name, parent_frs, _namespace) = match primary_name {
1385+
Some(n) => n,
1386+
None => {
1387+
// No $FILE_NAME in base record - store stdinfo anyway
1388+
// The extension record will add the name later
1389+
let record = fragment.get_or_create(frs);
1390+
record.stdinfo = std_info;
1391+
record.first_stream.size = SizeInfo {
1392+
length: default_size,
1393+
allocated: default_allocated,
1394+
};
1395+
// Leave first_name empty - extension record will fill it
1396+
return false;
1397+
}
1398+
};
1399+
13741400
// Add primary name to names buffer and get reference
13751401
let name_offset = fragment.add_name(&name);
13761402
let name_len = name.len();

0 commit comments

Comments
 (0)