@@ -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-
2816type 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
4637var (
@@ -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
7399func 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
268342func 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