Skip to content

Commit fae1530

Browse files
committed
More work for Fat12 and Fat16...
1 parent fae1368 commit fae1530

19 files changed

+740
-442
lines changed

filesystem/fat32/common_test.go

Lines changed: 173 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -13,34 +13,25 @@ import (
1313
"time"
1414
)
1515

16-
const (
17-
Fat32File = "./testdata/dist/fat32.img"
18-
fsckFile = "./testdata/dist/fsck.txt"
19-
rootdirFile = "./testdata/dist/root_dir.txt"
20-
rootdirFileFLS = "./testdata/dist/root_dir_fls.txt"
21-
rootdirEntryPattern = "./testdata/dist/root_dir_istat_%d.txt"
22-
foodirFile = "./testdata/dist/foo_dir.txt"
23-
foodirEntryPattern = "./testdata/dist/foo_dir_istat_%d.txt"
24-
serialFile = "./testdata/dist/serial.txt"
25-
fsstatFile = "./testdata/dist/fsstat.txt"
26-
)
27-
2816
type testFSInfo struct {
29-
bytesPerCluster uint32
30-
dataStartBytes uint32
31-
dataStartSector uint32
32-
bytesPerSector uint32
33-
reservedSectors uint32
34-
sectorsPerFAT uint32
35-
label string
36-
serial uint32
37-
sectorsPerTrack uint32
38-
heads uint32
39-
hiddenSectors uint32
40-
freeSectorCount uint32
41-
nextFreeSector uint32
42-
firstFAT uint32
43-
table *table
17+
fatType int
18+
bytesPerCluster uint32
19+
dataStartBytes uint32
20+
dataStartSector uint32
21+
bytesPerSector uint32
22+
reservedSectors uint32
23+
sectorsPerFAT uint32
24+
label string
25+
serial uint32
26+
sectorsPerTrack uint32
27+
heads uint32
28+
hiddenSectors uint32
29+
freeSectorCount uint32
30+
nextFreeSector uint32
31+
firstFAT uint32
32+
numFATs uint32
33+
rootDirEntryCount uint32
34+
table *table
4435
}
4536

4637
var (
@@ -59,20 +50,55 @@ var (
5950
testFSCKHeadsSectors = regexp.MustCompile(`^\s*(\d+) sectors/track, (\d+) heads\s*$`)
6051
testFSCKHiddenSectors = regexp.MustCompile(`^\s*(\d+) hidden sectors\s*$`)
6152
testFSCKFirstFAT = regexp.MustCompile(`^\s*First FAT starts at byte (\d+) \(sector (\d+)\)\s*$`)
53+
testFSCKNumFATs = regexp.MustCompile(`^\s*(\d+) FATs, (\d+) bit entries\s*$`)
6254
testFSCKFATSize = regexp.MustCompile(`^\s*(\d+) bytes per FAT \(= (\d+) sectors\)\s*$`)
55+
testFSCKRootDirEntryCount = regexp.MustCompile(`^\s*(\d+) root directory entries\s*$`)
6356
testFLSEntryPattern = regexp.MustCompile(`d/d (\d+):\s+(\S+)\s*.*$`)
6457
testFSSTATFreeSectorCountRE = regexp.MustCompile(`^\s*Free Sector Count.*: (\d+)\s*$`)
6558
testFSSTATNextFreeSectorRE = regexp.MustCompile(`^\s*Next Free Sector.*: (\d+)\s*`)
6659
testFSSTATClustersStartRE = regexp.MustCompile(`\s*FAT CONTENTS \(in sectors\)\s*$`)
6760
testFSSTATClusterLineRE = regexp.MustCompile(`\s*(\d+)-(\d+) \((\d+)\)\s+->\s+(\S+)\s*$`)
6861

69-
fsInfo *testFSInfo
62+
FatTypes = []int{12, 16, 32}
63+
fsInfo12 *testFSInfo
64+
fsInfo16 *testFSInfo
65+
fsInfo32 *testFSInfo
7066
)
7167

68+
func GetFsInfo(fatType int) *testFSInfo {
69+
switch fatType {
70+
case 12:
71+
return fsInfo12
72+
case 16:
73+
return fsInfo16
74+
case 32:
75+
return fsInfo32
76+
default:
77+
panic(fmt.Sprintf("Invalid FAT type: %d", fatType))
78+
}
79+
}
80+
81+
func getTestFile(fileName string, fatType int) string {
82+
pattern := fmt.Sprintf("./testdata/dist/fat%d/%s", fatType, fileName)
83+
if _, err := os.Stat(pattern); os.IsNotExist(err) {
84+
panic(fmt.Sprintf("Fat%d %s file not found: %s", fatType, fileName, pattern))
85+
}
86+
87+
return pattern
88+
}
89+
90+
func getTestPattern(pattern string, fatType int) string {
91+
return fmt.Sprintf("./testdata/dist/fat%d/%s", fatType, pattern)
92+
}
93+
94+
func GetFatDiskImagePath(fatType int) string {
95+
return getTestFile("disk.img", fatType)
96+
}
97+
7298
// TestMain sets up the test environment and runs the tests
7399
func TestMain(m *testing.M) {
74100
// Check and generate artifacts if necessary
75-
if _, err := os.Stat(Fat32File); os.IsNotExist(err) {
101+
if _, err := os.Stat(GetFatDiskImagePath(32)); os.IsNotExist(err) {
76102
// Run the genartifacts.sh script
77103
cmd := exec.Command("sh", "mkfat32.sh")
78104
cmd.Stdout = os.Stdout
@@ -88,11 +114,18 @@ func TestMain(m *testing.M) {
88114

89115
// common info
90116
var err error
91-
fsInfo, err = testReadFilesystemData()
92-
if err != nil {
93-
println("Error reading fsck file", err)
94-
os.Exit(1)
117+
handleErr := func(err error) {
118+
if err != nil {
119+
println("Error reading fsck file", err)
120+
os.Exit(1)
121+
}
95122
}
123+
fsInfo12, err = testReadFilesystemData(12)
124+
handleErr(err)
125+
fsInfo16, err = testReadFilesystemData(16)
126+
handleErr(err)
127+
fsInfo32, err = testReadFilesystemData(32)
128+
handleErr(err)
96129

97130
// Run the tests
98131
code := m.Run()
@@ -101,22 +134,44 @@ func TestMain(m *testing.M) {
101134
os.Exit(code)
102135
}
103136

137+
func getRootDirectoryBytes(fatType int, fatDiskImageBytes []byte) []byte {
138+
fsInfo := GetFsInfo(fatType)
139+
140+
var b []byte
141+
switch fatType {
142+
case 12, 16:
143+
// start of the root directory in fat12/16
144+
fatRegionSize := fsInfo.sectorsPerFAT * fsInfo.numFATs
145+
rootDirStartSector := fsInfo.reservedSectors + fatRegionSize
146+
start := rootDirStartSector * fsInfo.bytesPerSector
147+
148+
rootDirSize := fsInfo.rootDirEntryCount * 32
149+
b = make([]byte, rootDirSize)
150+
copy(b, fatDiskImageBytes[start:start+rootDirSize])
151+
default:
152+
// start of root directory in FAT32
153+
start := fsInfo.dataStartBytes
154+
b = make([]byte, fsInfo.bytesPerCluster)
155+
copy(b, fatDiskImageBytes[start:start+fsInfo.bytesPerCluster])
156+
}
157+
158+
return b
159+
}
160+
104161
// GetValidDirectoryEntries get directory entries for the root directory
105162
//
106163
//nolint:revive // yes we are returning an exported type, but that is ok for the tests
107-
func GetValidDirectoryEntries() (entries []*directoryEntry, b []byte, err error) {
164+
func GetValidDirectoryEntries(fatType int) (entries []*directoryEntry, b []byte, err error) {
108165
// read correct bytes off of disk
109-
110-
input, err := os.ReadFile(Fat32File)
166+
input, err := os.ReadFile(GetFatDiskImagePath(fatType))
111167
if err != nil {
112-
return nil, nil, fmt.Errorf("error reading data from fat32 test fixture %s: %v", Fat32File, err)
168+
return nil, nil, fmt.Errorf("error reading data from fat32 test fixture %s: %v", GetFatDiskImagePath(fatType), err)
113169
}
114-
start := fsInfo.dataStartBytes // start of root directory in fat32.img
115-
// we only have 9 actual 32-byte entries, of which 4 are real and 3 are VFAT extensionBytes
116-
// the rest are all 0s (as they should be), so we will include to exercise it
117-
b = make([]byte, fsInfo.bytesPerCluster)
118-
copy(b, input[start:start+fsInfo.bytesPerCluster])
170+
fsInfo := GetFsInfo(fatType)
171+
b = getRootDirectoryBytes(fatType, input)
119172

173+
rootdirFile := getTestFile("root_dir.txt", fatType)
174+
rootdirEntryPattern := getTestPattern("root_dir_istat_%d.txt", fatType)
120175
entries, err = testGetValidDirectoryEntriesFromFile(rootdirFile, rootdirEntryPattern, fsInfo)
121176

122177
// in the root directory, add the label entry
@@ -143,7 +198,7 @@ func GetValidDirectoryEntries() (entries []*directoryEntry, b []byte, err error)
143198
// but only one step down from root. If you want more, look for it elsewhere.
144199
//
145200
//nolint:revive // yes we are returning an exported type, but that is ok for the tests
146-
func GetValidDirectoryEntriesExtended(dir string) (entries []*directoryEntry, b []byte, err error) {
201+
func GetValidDirectoryEntriesExtended(dir string, fatType int) (entries []*directoryEntry, b []byte, err error) {
147202
// read correct bytes off of disk
148203

149204
// find the cluster for the given directory
@@ -152,6 +207,7 @@ func GetValidDirectoryEntriesExtended(dir string) (entries []*directoryEntry, b
152207
dir = strings.TrimSuffix(dir, "/")
153208
dir = strings.TrimSuffix(dir, "\\")
154209

210+
rootdirFileFLS := getTestFile("root_dir_fls.txt", fatType)
155211
flsData, err := os.ReadFile(rootdirFileFLS)
156212
if err != nil {
157213
return nil, nil, fmt.Errorf("error reading fls data from %s: %w", rootdirFileFLS, err)
@@ -168,20 +224,38 @@ func GetValidDirectoryEntriesExtended(dir string) (entries []*directoryEntry, b
168224
if err != nil {
169225
return nil, nil, fmt.Errorf("error parsing cluster number %s: %w", match[1], err)
170226
}
227+
228+
// Sleuthkit seems to always report the root directory as cluster 2 and thus this /foo
229+
// directory as cluster 3 regardless of the FAT type. This is not correct for FAT12/16 since
230+
// the root directory is not in cluster but rather in a reserved sector.
231+
if fatType != 32 {
232+
cluster--
233+
}
234+
171235
break
172236
}
173237

174-
input, err := os.ReadFile(Fat32File)
238+
input, err := os.ReadFile(GetFatDiskImagePath(fatType))
175239
if err != nil {
176-
return nil, nil, fmt.Errorf("error reading data from fat32 test fixture %s: %v", Fat32File, err)
240+
return nil, nil, fmt.Errorf("error reading data from fat32 test fixture %s: %v", GetFatDiskImagePath(fatType), err)
241+
}
242+
fsInfo := GetFsInfo(fatType)
243+
244+
start := fsInfo.dataStartBytes
245+
// in fat32, the root is located in the data section, so we need to adjust it
246+
// in fat12/16, the root directory is located in the reserved sectors
247+
if fatType == 32 {
248+
start++
177249
}
178-
start := fsInfo.dataStartBytes + 1 // start of foo directory in fat32.img
179250
// we only have 9 actual 32-byte entries, of which 4 are real and 3 are VFAT extensionBytes
180251
// the rest are all 0s (as they should be), so we will include to exercise it
181252
b = make([]byte, fsInfo.bytesPerCluster)
182253
copy(b, input[start:start+fsInfo.bytesPerCluster])
183254

255+
foodirFile := getTestFile("foo_dir.txt", fatType)
256+
foodirEntryPattern := getTestPattern("foo_dir_istat_%d.txt", fatType)
184257
entries, err = testGetValidDirectoryEntriesFromFile(foodirFile, foodirEntryPattern, fsInfo)
258+
185259
// handle . and ..
186260
if len(entries) > 0 && entries[0].filenameShort == "." {
187261
entries[0].clusterLocation = uint32(cluster)
@@ -266,6 +340,8 @@ func testGetValidDirectoryEntriesFromFile(dirFilePath, dirEntryPattern string, f
266340
}
267341

268342
func testPopulateDirectoryEntryFromIstatFile(de *directoryEntry, filename string, fsInfo *testFSInfo) error {
343+
sectorsPerCluster := fsInfo.bytesPerCluster / fsInfo.bytesPerSector
344+
269345
dirInfo, err := os.ReadFile(filename)
270346
if err != nil {
271347
return fmt.Errorf("error opening directory entry info file %s: %w", filename, err)
@@ -290,7 +366,13 @@ func testPopulateDirectoryEntryFromIstatFile(de *directoryEntry, filename string
290366
if err != nil {
291367
return fmt.Errorf("error parsing sector number %s: %w", sector, err)
292368
}
293-
de.clusterLocation = uint32(sectorNum) - fsInfo.dataStartSector + 2
369+
370+
if fsInfo.fatType == 32 {
371+
de.clusterLocation = uint32(sectorNum) - fsInfo.dataStartSector + 2
372+
} else {
373+
de.clusterLocation = (uint32(sectorNum)-fsInfo.dataStartSector)/sectorsPerCluster + 2
374+
}
375+
294376
break
295377
}
296378
case len(sectorStartMatch) > 0:
@@ -319,8 +401,12 @@ func testPopulateDirectoryEntryFromIstatFile(de *directoryEntry, filename string
319401
}
320402

321403
//nolint:gocyclo // we need to call this function from the test, do not care that it is too complex
322-
func testReadFilesystemData() (info *testFSInfo, err error) {
323-
info = &testFSInfo{}
404+
func testReadFilesystemData(fatType int) (info *testFSInfo, err error) {
405+
info = &testFSInfo{
406+
fatType: fatType,
407+
}
408+
eoc, eocMin := getEoc(fatType)
409+
fsckFile := getTestFile("fsck.txt", fatType)
324410
fsckInfo, err := os.ReadFile(fsckFile)
325411
if err != nil {
326412
return nil, fmt.Errorf("error opening fsck info file %s: %v", fsckFile, err)
@@ -336,7 +422,15 @@ func testReadFilesystemData() (info *testFSInfo, err error) {
336422
headsSectorMatch := testFSCKHeadsSectors.FindStringSubmatch(text)
337423
hiddenSectorsMatch := testFSCKHiddenSectors.FindStringSubmatch(text)
338424
firstFATMatch := testFSCKFirstFAT.FindStringSubmatch(text)
425+
numFATsMatch := testFSCKNumFATs.FindStringSubmatch(text)
426+
rootDirEntryCountMatch := testFSCKRootDirEntryCount.FindStringSubmatch(text)
339427
switch {
428+
case len(rootDirEntryCountMatch) == 2:
429+
count, err := strconv.Atoi(rootDirEntryCountMatch[1])
430+
if err != nil {
431+
return nil, fmt.Errorf("error parsing root directory entry count %s: %v", rootDirEntryCountMatch[1], err)
432+
}
433+
info.rootDirEntryCount = uint32(count)
340434
case len(headsSectorMatch) == 3:
341435
sectorsPerTrack, err := strconv.Atoi(headsSectorMatch[1])
342436
if err != nil {
@@ -396,10 +490,17 @@ func testReadFilesystemData() (info *testFSInfo, err error) {
396490
return nil, fmt.Errorf("error parsing first FAT byte %s: %v", firstFATMatch[1], err)
397491
}
398492
info.firstFAT = uint32(firstFAT)
493+
case len(numFATsMatch) == 3:
494+
numFATs, err := strconv.Atoi(numFATsMatch[1])
495+
if err != nil {
496+
return nil, fmt.Errorf("error parsing number of FATs %s: %v", numFATsMatch[1], err)
497+
}
498+
info.numFATs = uint32(numFATs)
399499
}
400500
}
401501

402502
// get the filesystem label
503+
rootdirFile := getTestFile("root_dir.txt", fatType)
403504
dirInfo, err := os.ReadFile(rootdirFile)
404505
if err != nil {
405506
println("Error opening directory info file", rootdirFile, err)
@@ -416,6 +517,7 @@ func testReadFilesystemData() (info *testFSInfo, err error) {
416517
}
417518
}
418519

520+
serialFile := getTestFile("serial.txt", fatType)
419521
serial, err := os.ReadFile(serialFile)
420522
if err != nil {
421523
println("Error reading serial file", serialFile, err)
@@ -428,6 +530,7 @@ func testReadFilesystemData() (info *testFSInfo, err error) {
428530
}
429531
info.serial = uint32(decimal)
430532

533+
fsstatFile := getTestFile("fsstat.txt", fatType)
431534
fsstat, err := os.ReadFile(fsstatFile)
432535
if err != nil {
433536
println("Error reading fsstat file", fsstatFile, err)
@@ -462,10 +565,16 @@ func testReadFilesystemData() (info *testFSInfo, err error) {
462565
sectorsPerFat := info.sectorsPerFAT
463566
sizeInBytes := sectorsPerFat * info.bytesPerSector
464567
numClusters := sizeInBytes / 4
568+
569+
rootDirCluster := uint32(0)
570+
if fatType == 32 {
571+
rootDirCluster = 2
572+
}
573+
465574
info.table = &table{
466-
fatID: 268435448, // 0x0ffffff8
467-
eocMarker: eoc, // 0x0fffffff
468-
rootDirCluster: 2, // root is at cluster 2
575+
fatID: 268435448,
576+
eocMarker: eoc,
577+
rootDirCluster: rootDirCluster,
469578
size: sizeInBytes,
470579
maxCluster: numClusters,
471580
clusters: make([]uint32, numClusters+1),
@@ -481,6 +590,12 @@ func testReadFilesystemData() (info *testFSInfo, err error) {
481590
println("Error parsing cluster end", clusterLineMatch[2], err)
482591
os.Exit(1)
483592
}
593+
594+
sectorsPerCluster := (int(info.bytesPerCluster) / int(info.bytesPerSector))
595+
sectorToCluster := func(sector int) uint32 {
596+
return (uint32(sector)-info.dataStartSector)/uint32(sectorsPerCluster) + 2
597+
}
598+
484599
var target uint32
485600
if clusterLineMatch[4] == "EOF" {
486601
target = eoc
@@ -490,21 +605,20 @@ func testReadFilesystemData() (info *testFSInfo, err error) {
490605
println("Error parsing cluster target", clusterLineMatch[4], err)
491606
os.Exit(1)
492607
}
493-
target = uint32(targetInt) - info.dataStartSector + 2
608+
target = sectorToCluster(targetInt)
494609
}
495-
// 2 is a special case that fsstat does not handle well
496-
// the start and end might be the same, or it might be a continual chain,
497-
// with only the last pointing at the target
610+
498611
for i := start; i < end; i++ {
499-
startCluster := uint32(i) - info.dataStartSector + 2
612+
startCluster := sectorToCluster(i)
500613
info.table.clusters[startCluster] = startCluster + 1
501614
}
502-
endCluster := uint32(end) - info.dataStartSector + 2
503-
if endCluster == 2 {
615+
endCluster := sectorToCluster(end)
616+
if fatType == 32 && endCluster == 2 {
504617
target = eocMin
505618
}
506619
info.table.clusters[endCluster] = target
507620
}
508621
}
622+
509623
return info, err
510624
}

0 commit comments

Comments
 (0)