diff --git a/filesystem/fat32/common_internal_test.go b/filesystem/fat32/common_internal_test.go
deleted file mode 100644
index a7feffb1..00000000
--- a/filesystem/fat32/common_internal_test.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package fat32
-
-const Fat32File = "./testdata/fat32.img"
diff --git a/filesystem/fat32/common_test.go b/filesystem/fat32/common_test.go
new file mode 100644
index 00000000..3b2a1449
--- /dev/null
+++ b/filesystem/fat32/common_test.go
@@ -0,0 +1,509 @@
+package fat32
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "os"
+ "os/exec"
+ "regexp"
+ "strconv"
+ "strings"
+ "testing"
+ "time"
+)
+
+const (
+ Fat32File = "./testdata/dist/fat32.img"
+ fsckFile = "./testdata/dist/fsck.txt"
+ rootdirFile = "./testdata/dist/root_dir.txt"
+ rootdirFileFLS = "./testdata/dist/root_dir_fls.txt"
+ rootdirEntryPattern = "./testdata/dist/root_dir_istat_%d.txt"
+ foodirFile = "./testdata/dist/foo_dir.txt"
+ foodirEntryPattern = "./testdata/dist/foo_dir_istat_%d.txt"
+ serialFile = "./testdata/dist/serial.txt"
+ fsstatFile = "./testdata/dist/fsstat.txt"
+)
+
+type testFSInfo struct {
+ bytesPerCluster uint32
+ dataStartBytes uint32
+ dataStartSector uint32
+ bytesPerSector uint32
+ reservedSectors uint32
+ sectorsPerFAT uint32
+ label string
+ serial uint32
+ sectorsPerTrack uint32
+ heads uint32
+ hiddenSectors uint32
+ freeSectorCount uint32
+ nextFreeSector uint32
+ firstFAT uint32
+ table *table
+}
+
+var (
+ testVolumeLabelRE = regexp.MustCompile(`^\s*Volume in drive\s+:\s+is\s+(.+)\s*$`)
+ testDirectoryEntryRE = regexp.MustCompile(`^\s*(\S+)\s+
\s+(\d{4}-\d\d-\d\d\s+\d+:\d+)\s*(.*)\s*$`)
+ testFileEntryRE = regexp.MustCompile(`^\s*(\S+)\s*(\S*)\s+(\d+)\s+(\d{4}-\d\d-\d\d\s+\d+:\d+)\s*(.*)\s*$`)
+ testWrittenTimeRE = regexp.MustCompile(`\s*Written:\s+(\d{4}-\d\d-\d\d\s+\d\d:\d\d:\d\d)`)
+ testAccessedTimeRE = regexp.MustCompile(`\s*Accessed:\s+(\d{4}-\d\d-\d\d\s+\d\d:\d\d:\d\d)`)
+ testCreatedTimeRE = regexp.MustCompile(`\s*Created:\s+(\d{4}-\d\d-\d\d\s+\d\d:\d\d:\d\d)`)
+ testSectorListStartRE = regexp.MustCompile(`\s*Sectors:\s*$`)
+ testFSCKDataStart = regexp.MustCompile(`Data area starts at byte (\d+) \(sector (\d+)\)`)
+ testFSCKBytesPerSector = regexp.MustCompile(`^\s*(\d+) bytes per logical sector\s*$`)
+ testFSCKBytesPerCluster = regexp.MustCompile(`^\s*(\d+) bytes per cluster\s*$`)
+ testFSCKReservedSectors = regexp.MustCompile(`^\s*(\d+) reserved sectors\s*$`)
+ testFSCKSectorsPerFat = regexp.MustCompile(`^\s*(\d+) bytes per FAT \(= (\d+) sectors\)\s*$`)
+ testFSCKHeadsSectors = regexp.MustCompile(`^\s*(\d+) sectors/track, (\d+) heads\s*$`)
+ testFSCKHiddenSectors = regexp.MustCompile(`^\s*(\d+) hidden sectors\s*$`)
+ testFSCKFirstFAT = regexp.MustCompile(`^\s*First FAT starts at byte (\d+) \(sector (\d+)\)\s*$`)
+ testFSCKFATSize = regexp.MustCompile(`^\s*(\d+) bytes per FAT \(= (\d+) sectors\)\s*$`)
+ testFLSEntryPattern = regexp.MustCompile(`d/d (\d+):\s+(\S+)\s*.*$`)
+ testFSSTATFreeSectorCountRE = regexp.MustCompile(`^\s*Free Sector Count.*: (\d+)\s*$`)
+ testFSSTATNextFreeSectorRE = regexp.MustCompile(`^\s*Next Free Sector.*: (\d+)\s*`)
+ testFSSTATClustersStartRE = regexp.MustCompile(`\s*FAT CONTENTS \(in sectors\)\s*$`)
+ testFSSTATClusterLineRE = regexp.MustCompile(`\s*(\d+)-(\d+) \((\d+)\)\s+->\s+(\S+)\s*$`)
+
+ fsInfo *testFSInfo
+)
+
+// TestMain sets up the test environment and runs the tests
+func TestMain(m *testing.M) {
+ // Check and generate artifacts if necessary
+ if _, err := os.Stat(Fat32File); os.IsNotExist(err) {
+ // Run the genartifacts.sh script
+ cmd := exec.Command("sh", "mkfat32.sh")
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ cmd.Dir = "testdata"
+
+ // Execute the command
+ if err := cmd.Run(); err != nil {
+ println("error generating test artifacts for ext4", err)
+ os.Exit(1)
+ }
+ }
+
+ // common info
+ var err error
+ fsInfo, err = testReadFilesystemData()
+ if err != nil {
+ println("Error reading fsck file", err)
+ os.Exit(1)
+ }
+
+ // Run the tests
+ code := m.Run()
+
+ // Exit with the appropriate code
+ os.Exit(code)
+}
+
+// GetValidDirectoryEntries get directory entries for the root directory
+//
+//nolint:revive // yes we are returning an exported type, but that is ok for the tests
+func GetValidDirectoryEntries() (entries []*directoryEntry, b []byte, err error) {
+ // read correct bytes off of disk
+
+ input, err := os.ReadFile(Fat32File)
+ if err != nil {
+ return nil, nil, fmt.Errorf("error reading data from fat32 test fixture %s: %v", Fat32File, err)
+ }
+ start := fsInfo.dataStartBytes // start of root directory in fat32.img
+ // we only have 9 actual 32-byte entries, of which 4 are real and 3 are VFAT extensionBytes
+ // the rest are all 0s (as they should be), so we will include to exercise it
+ b = make([]byte, fsInfo.bytesPerCluster)
+ copy(b, input[start:start+fsInfo.bytesPerCluster])
+
+ entries, err = testGetValidDirectoryEntriesFromFile(rootdirFile, rootdirEntryPattern, fsInfo)
+
+ // in the root directory, add the label entry
+ if fsInfo.label != "" {
+ filenameShort := fsInfo.label
+ extension := ""
+ if len(fsInfo.label) > 8 {
+ filenameShort = fsInfo.label[:8]
+ extension = fsInfo.label[8:]
+ }
+ de := &directoryEntry{filenameShort: filenameShort, fileExtension: extension, isVolumeLabel: true}
+ filename := fmt.Sprintf(rootdirEntryPattern, len(entries))
+ if err := testPopulateDirectoryEntryFromIstatFile(de, filename, fsInfo); err != nil {
+ return nil, nil, err
+ }
+ entries = append(entries, de)
+ }
+
+ return entries, b, err
+}
+
+// getValidDirectoryEntriesExtended get directory entries for a directory where there are so many,
+// it has to use the extended structure. Will look for the provided dir,
+// but only one step down from root. If you want more, look for it elsewhere.
+//
+//nolint:revive // yes we are returning an exported type, but that is ok for the tests
+func GetValidDirectoryEntriesExtended(dir string) (entries []*directoryEntry, b []byte, err error) {
+ // read correct bytes off of disk
+
+ // find the cluster for the given directory
+ dir = strings.TrimPrefix(dir, "/")
+ dir = strings.TrimPrefix(dir, "\\")
+ dir = strings.TrimSuffix(dir, "/")
+ dir = strings.TrimSuffix(dir, "\\")
+
+ flsData, err := os.ReadFile(rootdirFileFLS)
+ if err != nil {
+ return nil, nil, fmt.Errorf("error reading fls data from %s: %w", rootdirFileFLS, err)
+ }
+ scanner := bufio.NewScanner(bytes.NewReader(flsData))
+ var cluster int
+ for scanner.Scan() {
+ text := scanner.Text()
+ match := testFLSEntryPattern.FindStringSubmatch(text)
+ if len(match) != 3 || match[2] != dir {
+ continue
+ }
+ cluster, err = strconv.Atoi(match[1])
+ if err != nil {
+ return nil, nil, fmt.Errorf("error parsing cluster number %s: %w", match[1], err)
+ }
+ break
+ }
+
+ input, err := os.ReadFile(Fat32File)
+ if err != nil {
+ return nil, nil, fmt.Errorf("error reading data from fat32 test fixture %s: %v", Fat32File, err)
+ }
+ start := fsInfo.dataStartBytes + 1 // start of foo directory in fat32.img
+ // we only have 9 actual 32-byte entries, of which 4 are real and 3 are VFAT extensionBytes
+ // the rest are all 0s (as they should be), so we will include to exercise it
+ b = make([]byte, fsInfo.bytesPerCluster)
+ copy(b, input[start:start+fsInfo.bytesPerCluster])
+
+ entries, err = testGetValidDirectoryEntriesFromFile(foodirFile, foodirEntryPattern, fsInfo)
+ // handle . and ..
+ if len(entries) > 0 && entries[0].filenameShort == "." {
+ entries[0].clusterLocation = uint32(cluster)
+ }
+ if len(entries) > 1 && entries[1].filenameShort == ".." {
+ // root always is 2, but it seems to store it as 0, for reasons I do not know
+ entries[1].clusterLocation = 0
+ }
+ return entries, b, err
+}
+
+func testGetValidDirectoryEntriesFromFile(dirFilePath, dirEntryPattern string, fsInfo *testFSInfo) (dirEntries []*directoryEntry, err error) {
+ dirInfo, err := os.ReadFile(dirFilePath)
+ if err != nil {
+ return nil, fmt.Errorf("error opening directory info file %s: %w", dirInfo, err)
+ }
+ scanner := bufio.NewScanner(bytes.NewReader(dirInfo))
+ for scanner.Scan() {
+ text := scanner.Text()
+ dirEntryMatch := testDirectoryEntryRE.FindStringSubmatch(text)
+ fileEntryMatch := testFileEntryRE.FindStringSubmatch(text)
+ var (
+ de *directoryEntry
+ )
+ switch {
+ case len(dirEntryMatch) == 4:
+ filenameShort := dirEntryMatch[1]
+ de = &directoryEntry{
+ filenameShort: strings.ToUpper(filenameShort),
+ isSubdirectory: true,
+ }
+ if dirEntryMatch[3] != "" {
+ de.filenameLong = strings.TrimSpace(dirEntryMatch[3])
+ de.longFilenameSlots = calculateSlots(de.filenameLong)
+ }
+ if filenameShort != "." && filenameShort != ".." && strings.ToLower(filenameShort) == filenameShort {
+ de.lowercaseShortname = true
+ }
+ case len(fileEntryMatch) == 6:
+ size, err := strconv.Atoi(fileEntryMatch[3])
+ if err != nil {
+ return nil, fmt.Errorf("error parsing file size %s: %w", fileEntryMatch[3], err)
+ }
+ de = &directoryEntry{
+ filenameShort: strings.ToUpper(fileEntryMatch[1]),
+ fileExtension: strings.ToUpper(fileEntryMatch[2]),
+ fileSize: uint32(size),
+ isArchiveDirty: true,
+ }
+ if strings.ToLower(fileEntryMatch[1]) == fileEntryMatch[1] {
+ de.lowercaseShortname = true
+ }
+ if fileEntryMatch[2] != "" && strings.ToLower(fileEntryMatch[2]) == fileEntryMatch[2] {
+ de.lowercaseExtension = true
+ }
+ if fileEntryMatch[5] != "" {
+ de.filenameLong = strings.TrimSpace(fileEntryMatch[5])
+ de.longFilenameSlots = calculateSlots(de.filenameLong)
+ }
+ default:
+ continue
+ }
+ dirEntries = append(dirEntries, de)
+ }
+ // now need to go through the more detailed info from istat and find the dates
+ // ignore entries for . and ..
+ dirEntriesSubset := dirEntries
+ for {
+ if len(dirEntriesSubset) == 0 || (dirEntriesSubset[0].filenameShort != "." && dirEntriesSubset[0].filenameShort != "..") {
+ break
+ }
+ dirEntriesSubset = dirEntriesSubset[1:]
+ }
+ for i, de := range dirEntriesSubset {
+ filename := fmt.Sprintf(dirEntryPattern, i)
+ if err := testPopulateDirectoryEntryFromIstatFile(de, filename, fsInfo); err != nil {
+ return nil, err
+ }
+ }
+ return dirEntries, nil
+}
+
+func testPopulateDirectoryEntryFromIstatFile(de *directoryEntry, filename string, fsInfo *testFSInfo) error {
+ dirInfo, err := os.ReadFile(filename)
+ if err != nil {
+ return fmt.Errorf("error opening directory entry info file %s: %w", filename, err)
+ }
+ scanner := bufio.NewScanner(bytes.NewReader(dirInfo))
+ var inSectors bool
+ for scanner.Scan() {
+ text := scanner.Text()
+ sectorStartMatch := testSectorListStartRE.FindStringSubmatch(text)
+ writtenTimeMatch := testWrittenTimeRE.FindStringSubmatch(text)
+ accessedTimeMatch := testAccessedTimeRE.FindStringSubmatch(text)
+ createdTimeMatch := testCreatedTimeRE.FindStringSubmatch(text)
+ switch {
+ case inSectors:
+ // just split the line and use all non-whitespace as numbers
+ if de.clusterLocation != 0 {
+ continue
+ }
+ sectors := strings.Fields(text)
+ for _, sector := range sectors {
+ sectorNum, err := strconv.Atoi(sector)
+ if err != nil {
+ return fmt.Errorf("error parsing sector number %s: %w", sector, err)
+ }
+ de.clusterLocation = uint32(sectorNum) - fsInfo.dataStartSector + 2
+ break
+ }
+ case len(sectorStartMatch) > 0:
+ inSectors = true
+ case len(writtenTimeMatch) == 2:
+ date, err := time.Parse("2006-01-02 15:04:05", strings.TrimSpace(writtenTimeMatch[1]))
+ if err != nil {
+ return fmt.Errorf("error parsing written time %s: %w", writtenTimeMatch[1], err)
+ }
+ de.modifyTime = date
+ case len(accessedTimeMatch) == 2:
+ date, err := time.Parse("2006-01-02 15:04:05", strings.TrimSpace(accessedTimeMatch[1]))
+ if err != nil {
+ return fmt.Errorf("error parsing accessed time %s: %w", accessedTimeMatch[1], err)
+ }
+ de.accessTime = date
+ case len(createdTimeMatch) == 2:
+ date, err := time.Parse("2006-01-02 15:04:05", strings.TrimSpace(createdTimeMatch[1]))
+ if err != nil {
+ return fmt.Errorf("error parsing accessed time %s: %w", createdTimeMatch[1], err)
+ }
+ de.createTime = date
+ }
+ }
+ return nil
+}
+
+//nolint:gocyclo // we need to call this function from the test, do not care that it is too complex
+func testReadFilesystemData() (info *testFSInfo, err error) {
+ info = &testFSInfo{}
+ fsckInfo, err := os.ReadFile(fsckFile)
+ if err != nil {
+ return nil, fmt.Errorf("error opening fsck info file %s: %v", fsckFile, err)
+ }
+ scanner := bufio.NewScanner(bytes.NewReader(fsckInfo))
+ for scanner.Scan() {
+ text := scanner.Text()
+ dataStartMatch := testFSCKDataStart.FindStringSubmatch(text)
+ bytesPerClusterMatch := testFSCKBytesPerCluster.FindStringSubmatch(text)
+ bytesPerSectorMatch := testFSCKBytesPerSector.FindStringSubmatch(text)
+ reservedSectorsMatch := testFSCKReservedSectors.FindStringSubmatch(text)
+ sectorsPerFATMatch := testFSCKSectorsPerFat.FindStringSubmatch(text)
+ headsSectorMatch := testFSCKHeadsSectors.FindStringSubmatch(text)
+ hiddenSectorsMatch := testFSCKHiddenSectors.FindStringSubmatch(text)
+ firstFATMatch := testFSCKFirstFAT.FindStringSubmatch(text)
+ switch {
+ case len(headsSectorMatch) == 3:
+ sectorsPerTrack, err := strconv.Atoi(headsSectorMatch[1])
+ if err != nil {
+ return nil, fmt.Errorf("error parsing sectors per track %s: %v", headsSectorMatch[1], err)
+ }
+ heads, err := strconv.Atoi(headsSectorMatch[2])
+ if err != nil {
+ return nil, fmt.Errorf("error parsing heads %s: %v", headsSectorMatch[2], err)
+ }
+ info.sectorsPerTrack = uint32(sectorsPerTrack)
+ info.heads = uint32(heads)
+ case len(hiddenSectorsMatch) == 2:
+ hiddenSectors, err := strconv.Atoi(hiddenSectorsMatch[1])
+ if err != nil {
+ return nil, fmt.Errorf("error parsing hidden sectors %s: %v", hiddenSectorsMatch[1], err)
+ }
+ info.hiddenSectors = uint32(hiddenSectors)
+ case len(dataStartMatch) == 3:
+ byteStart, err := strconv.Atoi(dataStartMatch[1])
+ if err != nil {
+ return nil, fmt.Errorf("error parsing data start byte %s: %v", dataStartMatch[1], err)
+ }
+ sectorStart, err := strconv.Atoi(dataStartMatch[2])
+ if err != nil {
+ return nil, fmt.Errorf("error parsing data start sector %s: %v", dataStartMatch[2], err)
+ }
+ info.dataStartBytes = uint32(byteStart)
+ info.dataStartSector = uint32(sectorStart)
+
+ case len(bytesPerClusterMatch) == 2:
+ bytesPerCluster, err := strconv.Atoi(bytesPerClusterMatch[1])
+ if err != nil {
+ return nil, fmt.Errorf("error parsing bytes per cluster %s: %v", bytesPerClusterMatch[1], err)
+ }
+ info.bytesPerCluster = uint32(bytesPerCluster)
+ case len(bytesPerSectorMatch) == 2:
+ bytesPerSector, err := strconv.Atoi(bytesPerSectorMatch[1])
+ if err != nil {
+ return nil, fmt.Errorf("error parsing bytes per sector %s: %v", bytesPerSectorMatch[1], err)
+ }
+ info.bytesPerSector = uint32(bytesPerSector)
+ case len(reservedSectorsMatch) == 2:
+ reservedSectors, err := strconv.Atoi(reservedSectorsMatch[1])
+ if err != nil {
+ return nil, fmt.Errorf("error parsing reserved sectors %s: %v", reservedSectorsMatch[1], err)
+ }
+ info.reservedSectors = uint32(reservedSectors)
+ case len(sectorsPerFATMatch) == 3:
+ sectorsPerFAT, err := strconv.Atoi(sectorsPerFATMatch[2])
+ if err != nil {
+ return nil, fmt.Errorf("error parsing sectors per FAT %s: %v", sectorsPerFATMatch[2], err)
+ }
+ info.sectorsPerFAT = uint32(sectorsPerFAT)
+ case len(firstFATMatch) == 3:
+ firstFAT, err := strconv.Atoi(firstFATMatch[1])
+ if err != nil {
+ return nil, fmt.Errorf("error parsing first FAT byte %s: %v", firstFATMatch[1], err)
+ }
+ info.firstFAT = uint32(firstFAT)
+ }
+ }
+
+ // get the filesystem label
+ dirInfo, err := os.ReadFile(rootdirFile)
+ if err != nil {
+ println("Error opening directory info file", rootdirFile, err)
+ os.Exit(1)
+ }
+
+ scanner = bufio.NewScanner(bytes.NewReader(dirInfo))
+ for scanner.Scan() {
+ text := scanner.Text()
+ volLabelMatch := testVolumeLabelRE.FindStringSubmatch(text)
+ if len(volLabelMatch) == 2 {
+ info.label = strings.TrimSpace(volLabelMatch[1])
+ break
+ }
+ }
+
+ serial, err := os.ReadFile(serialFile)
+ if err != nil {
+ println("Error reading serial file", serialFile, err)
+ os.Exit(1)
+ }
+ decimal, err := strconv.ParseInt(strings.TrimSpace(string(serial)), 16, 64)
+ if err != nil {
+ println("Error converting contents of serial file to integer:", err)
+ os.Exit(1)
+ }
+ info.serial = uint32(decimal)
+
+ fsstat, err := os.ReadFile(fsstatFile)
+ if err != nil {
+ println("Error reading fsstat file", fsstatFile, err)
+ os.Exit(1)
+ }
+ scanner = bufio.NewScanner(bytes.NewReader(fsstat))
+ var inClusters bool
+ for scanner.Scan() {
+ text := scanner.Text()
+ freeSectorsMatch := testFSSTATFreeSectorCountRE.FindStringSubmatch(text)
+ nextFreeSectorMatch := testFSSTATNextFreeSectorRE.FindStringSubmatch(text)
+ clusterStartMatch := testFSSTATClustersStartRE.FindStringSubmatch(text)
+ clusterLineMatch := testFSSTATClusterLineRE.FindStringSubmatch(text)
+ switch {
+ case len(freeSectorsMatch) == 2:
+ freeSectors, err := strconv.Atoi(freeSectorsMatch[1])
+ if err != nil {
+ println("Error parsing free sectors count", freeSectorsMatch[1], err)
+ os.Exit(1)
+ }
+ info.freeSectorCount = uint32(freeSectors)
+ case len(nextFreeSectorMatch) == 2:
+ nextFreeSector, err := strconv.Atoi(nextFreeSectorMatch[1])
+ if err != nil {
+ println("Error parsing next free sector", nextFreeSectorMatch[1], err)
+ os.Exit(1)
+ }
+ // make sure to drop by the data start sector, and add 2 for the root and FAT
+ info.nextFreeSector = uint32(nextFreeSector) - info.dataStartSector + 2
+ case len(clusterStartMatch) > 0:
+ inClusters = true
+ sectorsPerFat := info.sectorsPerFAT
+ sizeInBytes := sectorsPerFat * info.bytesPerSector
+ numClusters := sizeInBytes / 4
+ info.table = &table{
+ fatID: 268435448, // 0x0ffffff8
+ eocMarker: eoc, // 0x0fffffff
+ rootDirCluster: 2, // root is at cluster 2
+ size: sizeInBytes,
+ maxCluster: numClusters,
+ clusters: make(map[uint32]uint32),
+ }
+ case inClusters && len(clusterLineMatch) > 4:
+ start, err := strconv.Atoi(clusterLineMatch[1])
+ if err != nil {
+ println("Error parsing cluster start", clusterLineMatch[1], err)
+ os.Exit(1)
+ }
+ end, err := strconv.Atoi(clusterLineMatch[2])
+ if err != nil {
+ println("Error parsing cluster end", clusterLineMatch[2], err)
+ os.Exit(1)
+ }
+ var target uint32
+ if clusterLineMatch[4] == "EOF" {
+ target = eoc
+ } else {
+ targetInt, err := strconv.Atoi(clusterLineMatch[4])
+ if err != nil {
+ println("Error parsing cluster target", clusterLineMatch[4], err)
+ os.Exit(1)
+ }
+ target = uint32(targetInt) - info.dataStartSector + 2
+ }
+ // 2 is a special case that fsstat does not handle well
+ // the start and end might be the same, or it might be a continual chain,
+ // with only the last pointing at the target
+ for i := start; i < end; i++ {
+ startCluster := uint32(i) - info.dataStartSector + 2
+ info.table.clusters[startCluster] = startCluster + 1
+ }
+ endCluster := uint32(end) - info.dataStartSector + 2
+ if endCluster == 2 {
+ target = eocMin
+ }
+ info.table.clusters[endCluster] = target
+ }
+ }
+ return info, err
+}
diff --git a/filesystem/fat32/directory_internal_test.go b/filesystem/fat32/directory_internal_test.go
index c0442086..d32be321 100644
--- a/filesystem/fat32/directory_internal_test.go
+++ b/filesystem/fat32/directory_internal_test.go
@@ -5,20 +5,18 @@ import (
"fmt"
"testing"
"time"
+
+ "github.com/diskfs/go-diskfs/util"
+ "github.com/google/go-cmp/cmp"
)
// TestDirectoryEntriesFromBytes largely a duplicate of TestdirectoryEntryParseDirEntries
// it just loads it into the Directory structure
func TestDirectoryEntriesFromBytes(t *testing.T) {
- validDe, validBytes, err := getValidDirectoryEntries()
+ validDe, b, err := GetValidDirectoryEntries()
if err != nil {
t.Fatal(err)
}
- // validBytes is ordered [][]byte - just string them all together
- b := make([]byte, 0)
- for _, b2 := range validBytes {
- b = append(b, b2...)
- }
d := &Directory{}
err = d.entriesFromBytes(b)
@@ -34,24 +32,18 @@ func TestDirectoryEntriesFromBytes(t *testing.T) {
for i, de := range d.entries {
if *de != *validDe[i] {
t.Errorf("%d: directoryEntry mismatch, actual then valid:", i)
- t.Logf("%#v", *de)
- t.Logf("%#v", *validDe[i])
+ t.Log(cmp.Diff(*de, *validDe[i], cmp.AllowUnexported(directoryEntry{})))
}
}
}
}
func TestDirectoryEntriesToBytes(t *testing.T) {
- validDe, validBytes, err := getValidDirectoryEntries()
- bytesPerCluster := 2048
+ validDe, b, err := GetValidDirectoryEntries()
+ bytesPerCluster := 512
if err != nil {
t.Fatal(err)
}
- // validBytes is ordered [][]byte - just string them all together
- b := make([]byte, 0)
- for _, b2 := range validBytes {
- b = append(b, b2...)
- }
d := &Directory{
entries: validDe,
directoryEntry: directoryEntry{
@@ -71,9 +63,10 @@ func TestDirectoryEntriesToBytes(t *testing.T) {
case len(output) != len(b):
t.Errorf("mismatched byte slice length actual %d, expected %d", len(output), len(b))
case !bytes.Equal(output, b):
- t.Errorf("Mismatched output of bytes. Actual then expected:")
- t.Logf("%v", output)
- t.Logf("%v", b)
+ diff, diffString := util.DumpByteSlicesWithDiffs(output, b, 32, false, true, true)
+ if diff {
+ t.Errorf("directory.toBytes() mismatched, actual then expected\n%s", diffString)
+ }
}
}
diff --git a/filesystem/fat32/directoryentry_internal_test.go b/filesystem/fat32/directoryentry_internal_test.go
index 3e741c4b..d0ea9d15 100644
--- a/filesystem/fat32/directoryentry_internal_test.go
+++ b/filesystem/fat32/directoryentry_internal_test.go
@@ -3,10 +3,11 @@ package fat32
import (
"bytes"
"fmt"
- "os"
"strings"
"testing"
"time"
+
+ "github.com/diskfs/go-diskfs/util"
)
var (
@@ -84,271 +85,6 @@ func compareDirectoryEntriesIgnoreDates(a, b *directoryEntry) bool {
return *c == *d
}
-func getValidDirectoryEntries() ([]*directoryEntry, [][]byte, error) {
- // these are taken from the file ./testdata/fat32.img, see ./testdata/README.md
- t1, _ := time.Parse(time.RFC3339, "2017-11-26T07:53:16Z")
- t10, _ := time.Parse(time.RFC3339, "2017-11-26T00:00:00Z")
- t2, _ := time.Parse(time.RFC3339, "2017-11-26T08:01:02Z")
- t20, _ := time.Parse(time.RFC3339, "2017-11-26T00:00:00Z")
- t3, _ := time.Parse(time.RFC3339, "2017-11-26T08:01:38Z")
- t30, _ := time.Parse(time.RFC3339, "2017-11-26T00:00:00Z")
- t4, _ := time.Parse(time.RFC3339, "2017-11-26T08:01:44Z")
- t40, _ := time.Parse(time.RFC3339, "2017-11-26T00:00:00Z")
- t5, _ := time.Parse(time.RFC3339, "2022-10-14T17:38:42Z")
- t50, _ := time.Parse(time.RFC3339, "2022-10-14T00:00:00Z")
- entries := []*directoryEntry{
- {
- filenameShort: "FOO",
- fileExtension: "",
- filenameLong: "",
- isReadOnly: false,
- isHidden: false,
- isSystem: false,
- isVolumeLabel: false,
- isSubdirectory: true,
- isArchiveDirty: false,
- isDevice: false,
- lowercaseShortname: true,
- createTime: t1,
- modifyTime: t1,
- accessTime: t10,
- acccessRights: accessRightsUnlimited,
- clusterLocation: 3,
- fileSize: 0,
- // start: uint32,
- longFilenameSlots: 0,
- isNew: false,
- },
- {
- filenameShort: "TERCER~1",
- fileExtension: "",
- filenameLong: "tercer_archivo",
- isReadOnly: false,
- isHidden: false,
- isSystem: false,
- isVolumeLabel: false,
- isSubdirectory: false,
- isArchiveDirty: true,
- isDevice: false,
- createTime: t2,
- modifyTime: t2,
- accessTime: t20,
- acccessRights: accessRightsUnlimited,
- clusterLocation: 5,
- fileSize: 6144,
- // start: uint32,
- longFilenameSlots: 2,
- isNew: false,
- },
- {
- filenameShort: "CORTO1",
- fileExtension: "TXT",
- filenameLong: "",
- isReadOnly: false,
- isHidden: false,
- isSystem: false,
- isVolumeLabel: false,
- isSubdirectory: false,
- isArchiveDirty: true,
- isDevice: false,
- createTime: t3,
- modifyTime: t3,
- accessTime: t30,
- acccessRights: accessRightsUnlimited,
- clusterLocation: 17,
- fileSize: 25,
- // start: uint32,
- longFilenameSlots: 0,
- isNew: false,
- },
- {
- filenameShort: "UNARCH~1",
- fileExtension: "DAT",
- filenameLong: "Un archivo con nombre largo.dat",
- isReadOnly: false,
- isHidden: false,
- isSystem: false,
- isVolumeLabel: false,
- isSubdirectory: false,
- isArchiveDirty: true,
- isDevice: false,
- createTime: t4,
- modifyTime: t4,
- accessTime: t40,
- acccessRights: accessRightsUnlimited,
- clusterLocation: 18,
- fileSize: 7168,
- // start: uint32,
- longFilenameSlots: 3,
- isNew: false,
- },
- {
- filenameShort: "go-diskf",
- fileExtension: "s",
- filenameLong: "",
- isReadOnly: false,
- isHidden: false,
- isSystem: false,
- isVolumeLabel: true,
- isSubdirectory: false,
- isArchiveDirty: false,
- isDevice: false,
- lowercaseShortname: false,
- createTime: t5,
- modifyTime: t5,
- accessTime: t50,
- acccessRights: accessRightsUnlimited,
- clusterLocation: 0,
- fileSize: 0,
- // start: uint32,
- longFilenameSlots: 0,
- isNew: false,
- },
- }
-
- // read correct bytes off of disk
- input, err := os.ReadFile(Fat32File)
- if err != nil {
- return nil, nil, fmt.Errorf("error reading data from fat32 test fixture %s: %v", Fat32File, err)
- }
-
- // start of root directory in fat32.img - sector 348
- // sector 0 - boot sector ; indicates: 32 reserved sectors (0-31); 158 sectors per fat
- // sector 1 - FS Information Sector
- // sector 2-31 - more reserved sectors
- // sector 32-189 - FAT1
- // sector 190-347 - FAT2
- // sector 348 - start of root directory
- // 348*512 = 178176
- start := 178176 // 0x0002b800 - start of root directory in fat32.img
-
- // we only have 9 actual 32-byte entries, of which 4 are real and 3 are VFAT extensionBytes
- // the rest are all 0s (as they should be), so we will include to exercise it
- b := make([][]byte, 8)
- //
- b[0] = input[start : start+32]
- b[1] = input[start+32 : start+4*32]
- b[2] = input[start+4*32 : start+5*32]
- b[3] = input[start+5*32 : start+9*32]
- b[4] = input[start+9*32 : start+10*32]
- b[5] = input[start+10*32 : start+11*32]
- b[6] = input[start+11*32 : start+12*32]
- // how many zeroes will come from cluster?
- remainder := 2048 - (12 * 32)
- b[7] = make([]byte, remainder)
- return entries, b, nil
-}
-
-//nolint:unparam // nothing uses the content, but we want to keep it for future usage
-func getValidDirectoryEntriesExtended() (entries []*directoryEntry, content [][]byte, err error) {
- // these are taken from the file ./testdata/fat32.img, see ./testdata/README.md
- t1, _ := time.Parse(time.RFC3339, "2017-11-26T07:53:16Z")
- t10, _ := time.Parse(time.RFC3339, "2017-11-26T00:00:00Z")
- entries = []*directoryEntry{
- // FilenameShort, FileExtension,FilenameLong,IsReadOnly,IsHidden,IsSystem,IsVolumeLabel,IsSubdirectory,IsArchiveDirty,IsDevice,LowercaseShortname,LowercaseExtension,CreateTime,ModifyTime,AccessTime,AcccessRights,clusterLocation,FileSize,filesystem,start,longFilenameSlots,isNew,
- {".", "", "", false, false, false, false, true, false, false, false, false, t1, t1, t10, accessRightsUnlimited, 3, 0, nil, 0, false},
- {"..", "", "", false, false, false, false, true, false, false, false, false, t1, t1, t10, accessRightsUnlimited, 0, 0, nil, 0, false},
- {"BAR", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 4, 0, nil, 0, false},
- {"DIR", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x2e, 0, nil, 0, false},
-
- {"DIR0", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x2f, 0, nil, 0, false},
- {"DIR1", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x30, 0, nil, 0, false},
- {"DIR2", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x31, 0, nil, 0, false},
- {"DIR3", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x32, 0, nil, 0, false},
- {"DIR4", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x33, 0, nil, 0, false},
- {"DIR5", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x34, 0, nil, 0, false},
- {"DIR6", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x35, 0, nil, 0, false},
- {"DIR7", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x36, 0, nil, 0, false},
- {"DIR8", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x37, 0, nil, 0, false},
- {"DIR9", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x38, 0, nil, 0, false},
- {"DIR10", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x39, 0, nil, 0, false},
- {"DIR11", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x3a, 0, nil, 0, false},
-
- {"DIR12", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x3b, 0, nil, 0, false},
- {"DIR13", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x3d, 0, nil, 0, false},
- {"DIR14", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x3e, 0, nil, 0, false},
- {"DIR15", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x3f, 0, nil, 0, false},
- {"DIR16", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x40, 0, nil, 0, false},
- {"DIR17", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x41, 0, nil, 0, false},
- {"DIR18", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x42, 0, nil, 0, false},
- {"DIR19", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x43, 0, nil, 0, false},
- {"DIR20", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x44, 0, nil, 0, false},
- {"DIR21", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x45, 0, nil, 0, false},
- {"DIR22", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x46, 0, nil, 0, false},
- {"DIR23", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x47, 0, nil, 0, false},
- {"DIR24", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x48, 0, nil, 0, false},
- {"DIR25", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x49, 0, nil, 0, false},
- {"DIR26", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x4a, 0, nil, 0, false},
- {"DIR27", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x4b, 0, nil, 0, false},
-
- {"DIR28", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x4c, 0, nil, 0, false},
- {"DIR29", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x4e, 0, nil, 0, false},
- {"DIR30", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x4f, 0, nil, 0, false},
- {"DIR31", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x50, 0, nil, 0, false},
- {"DIR32", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x51, 0, nil, 0, false},
- {"DIR33", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x52, 0, nil, 0, false},
- {"DIR34", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x53, 0, nil, 0, false},
- {"DIR35", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x54, 0, nil, 0, false},
- {"DIR36", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x55, 0, nil, 0, false},
- {"DIR37", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x56, 0, nil, 0, false},
- {"DIR38", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x57, 0, nil, 0, false},
- {"DIR39", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x58, 0, nil, 0, false},
- {"DIR40", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x59, 0, nil, 0, false},
- {"DIR41", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x5a, 0, nil, 0, false},
- {"DIR42", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x5b, 0, nil, 0, false},
- {"DIR43", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x5c, 0, nil, 0, false},
-
- {"DIR44", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x5d, 0, nil, 0, false},
- {"DIR45", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x5f, 0, nil, 0, false},
- {"DIR46", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x60, 0, nil, 0, false},
- {"DIR47", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x61, 0, nil, 0, false},
- {"DIR48", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x62, 0, nil, 0, false},
- {"DIR49", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x63, 0, nil, 0, false},
- {"DIR50", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x64, 0, nil, 0, false},
- {"DIR51", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x65, 0, nil, 0, false},
- {"DIR52", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x66, 0, nil, 0, false},
- {"DIR53", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x67, 0, nil, 0, false},
- {"DIR54", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x68, 0, nil, 0, false},
- {"DIR55", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x69, 0, nil, 0, false},
- {"DIR56", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x6a, 0, nil, 0, false},
- {"DIR57", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x6b, 0, nil, 0, false},
- {"DIR58", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x6c, 0, nil, 0, false},
- {"DIR59", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x6d, 0, nil, 0, false},
-
- {"DIR60", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x6e, 0, nil, 0, false},
- {"DIR61", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x70, 0, nil, 0, false},
- {"DIR62", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x71, 0, nil, 0, false},
- {"DIR63", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x72, 0, nil, 0, false},
- {"DIR64", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x73, 0, nil, 0, false},
- {"DIR65", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x74, 0, nil, 0, false},
- {"DIR66", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x75, 0, nil, 0, false},
- {"DIR67", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x76, 0, nil, 0, false},
- {"DIR68", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x77, 0, nil, 0, false},
- {"DIR69", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x78, 0, nil, 0, false},
- {"DIR70", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x79, 0, nil, 0, false},
- {"DIR71", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x7a, 0, nil, 0, false},
- {"DIR72", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x7b, 0, nil, 0, false},
- {"DIR73", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x7c, 0, nil, 0, false},
- {"DIR74", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x7d, 0, nil, 0, false},
- {"DIR75", "", "", false, false, false, false, true, false, false, true, false, t1, t1, t10, accessRightsUnlimited, 0x7e, 0, nil, 0, false},
- }
-
- // read correct bytes off of disk
- input, err := os.ReadFile(Fat32File)
- if err != nil {
- return nil, nil, fmt.Errorf("error reading data from fat32 test fixture %s: %v", Fat32File, err)
- }
-
- // start of foo directory in fat32.img - cluster 3 = sector 349 = bytes 349*512 = 178688 = 0x0002ba00
- start := 178688
-
- b := make([][]byte, len(entries))
- for i := 0; i < len(entries); i++ {
- b[i] = input[start+i*32 : start+i*32+32]
- }
- return entries, b, nil
-}
-
func TestDirectoryEntryLongFilenameBytes(t *testing.T) {
for _, tt := range sfnBytesTests {
output, err := longFilenameBytes(tt.lfn, tt.shortName, tt.extension)
@@ -530,12 +266,7 @@ func TestDirectoryEntryUCaseValid(t *testing.T) {
}
func TestDirectoryEntryParseDirEntries(t *testing.T) {
- validDe, validBytes, err := getValidDirectoryEntries()
- // validBytes is ordered [][]byte - just string them all together
- b := make([]byte, 0)
- for _, b2 := range validBytes {
- b = append(b, b2...)
- }
+ validDe, b, err := GetValidDirectoryEntries()
if err != nil {
t.Fatal(err)
}
@@ -571,19 +302,23 @@ func TestDirectoryEntryParseDirEntries(t *testing.T) {
}
func TestDirectoryEntryToBytes(t *testing.T) {
- validDe, validBytes, err := getValidDirectoryEntries()
+ validDe, validBytes, err := GetValidDirectoryEntries()
if err != nil {
t.Fatal(err)
}
- for i, de := range validDe {
+ i := 0
+ for _, de := range validDe {
b, err := de.toBytes()
+ expected := validBytes[i*32 : (i+1+de.longFilenameSlots)*32]
if err != nil {
t.Errorf("error converting directory entry to bytes: %v", err)
t.Logf("%v", de)
- } else if !bytes.Equal(b, validBytes[i]) {
- t.Errorf("Mismatched bytes %s, actual vs expected", de.filenameShort)
- t.Log(b)
- t.Log(validBytes[i])
+ } else {
+ diff, diffString := util.DumpByteSlicesWithDiffs(b, expected, 32, false, true, true)
+ if diff {
+ t.Errorf("directory.toBytes() %s mismatched, actual then expected\n%s", de.filenameShort, diffString)
+ }
}
+ i += de.longFilenameSlots + 1
}
}
diff --git a/filesystem/fat32/dos331bpb_internal_test.go b/filesystem/fat32/dos331bpb_internal_test.go
index eeb966e9..02ad982e 100644
--- a/filesystem/fat32/dos331bpb_internal_test.go
+++ b/filesystem/fat32/dos331bpb_internal_test.go
@@ -11,9 +11,9 @@ import (
func getValidDos331BPB() *dos331BPB {
return &dos331BPB{
dos20BPB: getValidDos20BPB(),
- sectorsPerTrack: 32,
- heads: 64,
- hiddenSectors: 0,
+ sectorsPerTrack: uint16(fsInfo.sectorsPerTrack),
+ heads: uint16(fsInfo.heads),
+ hiddenSectors: fsInfo.hiddenSectors,
totalSectors: 0,
}
}
diff --git a/filesystem/fat32/dos71bpb_internal_test.go b/filesystem/fat32/dos71bpb_internal_test.go
index 4f0fab4a..3b1f6336 100644
--- a/filesystem/fat32/dos71bpb_internal_test.go
+++ b/filesystem/fat32/dos71bpb_internal_test.go
@@ -11,7 +11,7 @@ import (
func getValidDos71EBPB() *dos71EBPB {
return &dos71EBPB{
dos331BPB: getValidDos331BPB(),
- sectorsPerFat: 158,
+ sectorsPerFat: fsInfo.sectorsPerFAT,
mirrorFlags: 0,
version: 0,
rootDirectoryCluster: 2,
@@ -21,8 +21,8 @@ func getValidDos71EBPB() *dos71EBPB {
driveNumber: 128,
reservedFlags: 0x00,
extendedBootSignature: 0x29,
- volumeSerialNumber: 2712131608,
- volumeLabel: "go-diskfs",
+ volumeSerialNumber: fsInfo.serial,
+ volumeLabel: fsInfo.label,
fileSystemType: "FAT32",
}
}
@@ -118,8 +118,8 @@ func TestDos71EBPBFromBytes(t *testing.T) {
valid.volumeLabel = ""
valid.fileSystemType = ""
if !bpb.equal(valid) {
- t.Log(bpb)
- t.Log(valid)
+ t.Logf("%#v", bpb)
+ t.Logf("%#v", valid)
t.Fatalf("Mismatched BPB")
}
})
diff --git a/filesystem/fat32/fat32.go b/filesystem/fat32/fat32.go
index 1f968f07..b13c42aa 100644
--- a/filesystem/fat32/fat32.go
+++ b/filesystem/fat32/fat32.go
@@ -65,6 +65,12 @@ type FileSystem struct {
// Equal compare if two filesystems are equal
func (fs *FileSystem) Equal(a *FileSystem) bool {
+ if fs == nil && a == nil {
+ return true
+ }
+ if fs == nil || a == nil {
+ return false
+ }
localMatch := fs.file == a.file && fs.dataStart == a.dataStart && fs.bytesPerCluster == a.bytesPerCluster
tableMatch := fs.table.equal(&a.table)
bsMatch := fs.bootSector.equal(&a.bootSector)
diff --git a/filesystem/fat32/fat32_internal_test.go b/filesystem/fat32/fat32_internal_test.go
index 4b300061..919141e8 100644
--- a/filesystem/fat32/fat32_internal_test.go
+++ b/filesystem/fat32/fat32_internal_test.go
@@ -8,6 +8,7 @@ import (
"testing"
"github.com/diskfs/go-diskfs/testhelper"
+ "github.com/google/go-cmp/cmp"
)
/*
@@ -58,6 +59,7 @@ func getValidFat32FSSmall() *FileSystem {
bytesPerCluster: 512,
dataStart: 178176,
file: &testhelper.FileImpl{
+ //nolint:revive // unused parameter, keeping name makes it easier to use in the future
Writer: func(b []byte, offset int64) (int, error) {
return len(b), nil
},
@@ -122,14 +124,14 @@ func TestFat32ReadDirectory(t *testing.T) {
fs := &FileSystem{
table: *getValidFat32Table(),
file: file,
- bytesPerCluster: 512,
- dataStart: 178176,
+ bytesPerCluster: int(fsInfo.bytesPerCluster),
+ dataStart: fsInfo.dataStartBytes,
}
- validDe, _, err := getValidDirectoryEntries()
+ validDe, _, err := GetValidDirectoryEntries()
if err != nil {
t.Fatalf("unable to read valid directory entries: %v", err)
}
- validDeExtended, _, err := getValidDirectoryEntriesExtended()
+ validDeExtended, _, err := GetValidDirectoryEntriesExtended("/foo")
if err != nil {
t.Fatalf("unable to read valid directory entries extended: %v", err)
}
@@ -159,8 +161,7 @@ func TestFat32ReadDirectory(t *testing.T) {
for i, entry := range entries {
if !compareDirectoryEntriesIgnoreDates(entry, tt.entries[i]) {
t.Errorf("fs.readDirectory(%s) %d: entries do not match, actual then expected", tt.path, i)
- t.Log(entry)
- t.Log(tt.entries[i])
+ t.Log(cmp.Diff(*entry, *tt.entries[i], cmp.AllowUnexported(directoryEntry{})))
}
}
}
@@ -274,11 +275,11 @@ func TestFat32ReadDirWithMkdir(t *testing.T) {
if err != nil {
t.Fatalf("unable to read data from file %s: %v", Fat32File, err)
}
- validDe, _, err := getValidDirectoryEntries()
+ validDe, _, err := GetValidDirectoryEntries()
if err != nil {
t.Fatalf("unable to read valid directory entries: %v", err)
}
- validDeLong, _, err := getValidDirectoryEntriesExtended()
+ validDeLong, _, err := GetValidDirectoryEntriesExtended("/foo")
if err != nil {
t.Fatalf("unable to read valid directory entries extended: %v", err)
}
@@ -321,6 +322,7 @@ func TestFat32ReadDirWithMkdir(t *testing.T) {
for _, tt := range tests {
fs.file = &testhelper.FileImpl{
+ //nolint:revive // unused parameter, keeping name makes it easier to use in the future
Writer: func(b []byte, offset int64) (int, error) {
return len(b), nil
},
diff --git a/filesystem/fat32/fat32_test.go b/filesystem/fat32/fat32_test.go
index 3217f431..62c6e3ad 100644
--- a/filesystem/fat32/fat32_test.go
+++ b/filesystem/fat32/fat32_test.go
@@ -297,6 +297,17 @@ func TestFat32ReadDir(t *testing.T) {
} else {
fmt.Println(f.Name())
}
+ // determine entries from the actual data
+ rootEntries, _, err := fat32.GetValidDirectoryEntries()
+ if err != nil {
+ t.Fatalf("error getting valid directory entries: %v", err)
+ }
+ // ignore volume entry when public-facing root entries
+ rootEntries = rootEntries[:len(rootEntries)-1]
+ fooEntries, _, err := fat32.GetValidDirectoryEntriesExtended("/foo")
+ if err != nil {
+ t.Fatalf("error getting valid directory entries for /foo: %v", err)
+ }
tests := []struct {
path string
count int
@@ -304,20 +315,8 @@ func TestFat32ReadDir(t *testing.T) {
isDir bool
err error
}{
- // should have 4 entries (Volume Label is not returned)
- // foo
- // TERCER~1
- // CORTO1.TXT
- // UNARCH~1.DAT
- {"/", 4, "foo", true, nil},
- // should have 80 entries:
- // dir0-75 = 76 entries
- // dir = 1 entry
- // bar = 1 entry
- // . = 1 entry
- // .. = 1 entry
- // total = 80 entries
- {"/foo", 80, ".", true, nil},
+ {"/", len(rootEntries), "foo", true, nil},
+ {"/foo", len(fooEntries), ".", true, nil},
// 0 entries because the directory does not exist
{"/a/b/c", 0, "", false, fmt.Errorf("error reading directory /a/b/c")},
}
diff --git a/filesystem/fat32/fileinfo.go b/filesystem/fat32/fileinfo.go
index ca0b1b26..cf319ac1 100644
--- a/filesystem/fat32/fileinfo.go
+++ b/filesystem/fat32/fileinfo.go
@@ -17,16 +17,22 @@ type FileInfo struct {
}
// IsDir abbreviation for Mode().IsDir()
+//
+//nolint:gocritic // we need this to comply with fs.FileInfo
func (fi FileInfo) IsDir() bool {
return fi.isDir
}
// ModTime modification time
+//
+//nolint:gocritic // we need this to comply with fs.FileInfo
func (fi FileInfo) ModTime() time.Time {
return fi.modTime
}
// Mode returns file mode
+//
+//nolint:gocritic // we need this to comply with fs.FileInfo
func (fi FileInfo) Mode() os.FileMode {
return fi.mode
}
@@ -34,6 +40,8 @@ func (fi FileInfo) Mode() os.FileMode {
// Name base name of the file
//
// will return the long name of the file. If none exists, returns the shortname and extension
+//
+//nolint:gocritic // we need this to comply with fs.FileInfo
func (fi FileInfo) Name() string {
if fi.name != "" {
return fi.name
@@ -42,16 +50,22 @@ func (fi FileInfo) Name() string {
}
// ShortName just the 8.3 short name of the file
+//
+//nolint:gocritic // we need this to comply with fs.FileInfo
func (fi FileInfo) ShortName() string {
return fi.shortName
}
// Size length in bytes for regular files
+//
+//nolint:gocritic // we need this to comply with fs.FileInfo
func (fi FileInfo) Size() int64 {
return fi.size
}
// Sys underlying data source - not supported yet and so will return nil
+//
+//nolint:gocritic // we need this to comply with fs.FileInfo
func (fi FileInfo) Sys() interface{} {
return nil
}
diff --git a/filesystem/fat32/fsinfosector_internal_test.go b/filesystem/fat32/fsinfosector_internal_test.go
index a6cdb39c..ced877ab 100644
--- a/filesystem/fat32/fsinfosector_internal_test.go
+++ b/filesystem/fat32/fsinfosector_internal_test.go
@@ -10,8 +10,8 @@ import (
func getValidFSInfoSector() *FSInformationSector {
return &FSInformationSector{
- freeDataClustersCount: 20007,
- lastAllocatedCluster: 126,
+ freeDataClustersCount: fsInfo.freeSectorCount,
+ lastAllocatedCluster: fsInfo.nextFreeSector,
}
}
diff --git a/filesystem/fat32/table_internal_test.go b/filesystem/fat32/table_internal_test.go
index 50e1cf07..119f05ff 100644
--- a/filesystem/fat32/table_internal_test.go
+++ b/filesystem/fat32/table_internal_test.go
@@ -3,8 +3,10 @@ package fat32
import (
"bytes"
"os"
- "sort"
"testing"
+
+ "github.com/diskfs/go-diskfs/util"
+ "github.com/google/go-cmp/cmp"
)
const (
@@ -13,147 +15,16 @@ const (
)
func getValidFat32Table() *table {
- sectorsPerFat := 158 // 158 sectors per FAT given in DOS20BPB
- sizeInBytes := sectorsPerFat * 512 // 512 bytes per sector,
- numClusters := sizeInBytes / 4
- // table as read from fat32.img using
- // xxd -c 4 ./testdata/fat32.img
- // directory "\" is at cluster 2 (first data cluster) at byte 0x02b800 = 178176
- // directory "\foo" is at cluster 3 (second data cluster)
- return &table{
- fatID: 268435448, // 0x0ffffff8
- eocMarker: eoc, // 0x0fffffff
- clusters: map[uint32]uint32{
- 2: eocMin,
- 3: 60,
- 4: eoc,
- 5: 6,
- 6: 7,
- 7: 8,
- 8: 9,
- 9: 10,
- 10: 11,
- 11: 12,
- 12: 13,
- 13: 14,
- 14: 15,
- 15: 16,
- 16: eoc,
- 17: eoc,
- 18: 19,
- 19: 20,
- 20: 21,
- 21: 22,
- 22: 23,
- 23: 24,
- 24: 25,
- 25: 26,
- 26: 27,
- 27: 28,
- 28: 29,
- 29: 30,
- 30: 31,
- 31: eoc,
- 32: 33,
- 33: 34,
- 34: 35,
- 35: 36,
- 36: 37,
- 37: 38,
- 38: 39,
- 39: 40,
- 40: 41,
- 41: 42,
- 42: 43,
- 43: 44,
- 44: 45,
- 45: eoc,
- 46: eoc,
- 47: eoc,
- 48: eoc,
- 49: eoc,
- 50: eoc,
- 51: eoc,
- 52: eoc,
- 53: eoc,
- 54: eoc,
- 55: eoc,
- 56: eoc,
- 57: eoc,
- 58: eoc,
- 59: eoc,
- 60: 77,
- 61: eoc,
- 62: eoc,
- 63: eoc,
- 64: eoc,
- 65: eoc,
- 66: eoc,
- 67: eoc,
- 68: eoc,
- 69: eoc,
- 70: eoc,
- 71: eoc,
- 72: eoc,
- 73: eoc,
- 74: eoc,
- 75: eoc,
- 76: eoc,
- 77: 94,
- 78: eoc,
- 79: eoc,
- 80: eoc,
- 81: eoc,
- 82: eoc,
- 83: eoc,
- 84: eoc,
- 85: eoc,
- 86: eoc,
- 87: eoc,
- 88: eoc,
- 89: eoc,
- 90: eoc,
- 91: eoc,
- 92: eoc,
- 93: eoc,
- 94: 111,
- 95: eoc,
- 96: eoc,
- 97: eoc,
- 98: eoc,
- 99: eoc,
- 100: eoc,
- 101: eoc,
- 102: eoc,
- 103: eoc,
- 104: eoc,
- 105: eoc,
- 106: eoc,
- 107: eoc,
- 108: eoc,
- 109: eoc,
- 110: eoc,
- 111: eoc,
- 112: eoc,
- 113: eoc,
- 114: eoc,
- 115: eoc,
- 116: eoc,
- 117: eoc,
- 118: eoc,
- 119: eoc,
- 120: eoc,
- 121: eoc,
- 122: eoc,
- 123: eoc,
- 124: eoc,
- 125: eoc,
- 126: eoc,
- },
- rootDirCluster: 2,
- size: uint32(sizeInBytes),
- maxCluster: uint32(numClusters),
+ // make a duplicate, in case someone modifies what we return
+ var t = &table{}
+ *t = *fsInfo.table
+ // and because the clusters are copied by reference
+ t.clusters = make(map[uint32]uint32)
+ for k, v := range fsInfo.table.clusters {
+ t.clusters[k] = v
}
+
+ return t
}
func TestFat32TableFromBytes(t *testing.T) {
@@ -162,25 +33,15 @@ func TestFat32TableFromBytes(t *testing.T) {
if err != nil {
t.Fatalf("error reading test fixture data from %s: %v", Fat32File, err)
}
- b := input[16384 : 158*512+16384]
- table := tableFromBytes(b)
- if table == nil {
+ b := input[fsInfo.firstFAT : fsInfo.firstFAT+fsInfo.sectorsPerFAT*fsInfo.bytesPerSector]
+ result := tableFromBytes(b)
+ if result == nil {
t.Fatalf("returned FAT32 Table was nil unexpectedly")
}
valid := getValidFat32Table()
- if !table.equal(valid) {
- keys := make([]uint32, 0)
- for _, k := range valid.clusters {
- keys = append(keys, k)
- }
- sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] })
- t.Log(keys)
- keys = make([]uint32, 0)
- for _, k := range table.clusters {
- keys = append(keys, k)
- }
- sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] })
- t.Log(keys)
+ if !result.equal(valid) {
+ diff := cmp.Diff(result, valid, cmp.AllowUnexported(table{}))
+ t.Log(diff)
t.Fatalf("Mismatched FAT32 Table")
}
})
@@ -197,9 +58,10 @@ func TestFat32TableToBytes(t *testing.T) {
if err != nil {
t.Fatalf("error reading test fixture data from %s: %v", Fat32File, err)
}
- validBytes := valid[16384 : 158*512+16384]
+ validBytes := valid[fsInfo.firstFAT : fsInfo.firstFAT+fsInfo.sectorsPerFAT*fsInfo.bytesPerSector]
if !bytes.Equal(validBytes, b) {
- t.Error("Mismatched bytes")
+ _, diffString := util.DumpByteSlicesWithDiffs(validBytes, b, 32, false, true, true)
+ t.Errorf("directory.toBytes() mismatched, actual then expected\n%s", diffString)
}
})
}
diff --git a/filesystem/fat32/testdata/.gitignore b/filesystem/fat32/testdata/.gitignore
new file mode 100644
index 00000000..849ddff3
--- /dev/null
+++ b/filesystem/fat32/testdata/.gitignore
@@ -0,0 +1 @@
+dist/
diff --git a/filesystem/fat32/testdata/README.md b/filesystem/fat32/testdata/README.md
index 7ae1a654..ec6acd14 100644
--- a/filesystem/fat32/testdata/README.md
+++ b/filesystem/fat32/testdata/README.md
@@ -1,49 +1,20 @@
# FAT32 Test Fixtures
This directory contains test fixtures for FAT32 filesystems. Specifically, it contains the following files:
-* `fat32.img`: A 10MB filesystem img
-* `calcsfn_checksum.c`: A C program that generates correct short filename checksums
+* `calcsfn_checksum.c`: A C program that generates correct short filename checksums.
+* `fat32.go`: A Go program that creates a basic filesystem; see below.
-To generate the `fat32.img`, based roughly on https://commons.wikimedia.org/wiki/File:VFAT_directory_entries.png :
+All of the actual test artifacts are in `dist/`, which is excluded from version control
+via `.gitignore` in this directory.
-```
-$ docker run -it --rm -v $PWD:/data alpine:3.6
-# apk --update add dosfstools mtools
-# dd if=/dev/zero of=/data/fat32.img bs=1M count=10
-# mkfs.vfat -v -F 32 /data/fat32.img
-# echo "mtools_skip_check=1" >> /etc/mtools.conf
-# mmd -i /data/fat32.img ::/foo
-# mmd -i /data/fat32.img ::/foo/bar
-# echo 'Tenemos un archivo corto' > /CORTO1.TXT
-# dd if=/dev/zero 'of=/Un archivo con nombre largo.dat' bs=1024 count=2
-# dd if=/dev/zero 'of=/tercer_archivo' bs=1024 count=6
-# dd if=/dev/zero 'of=/Un archivo con nombre largo.dat' bs=1024 count=7
-# dd if=/dev/zero 'of=/some_long_embedded_nameא' bs=1024 count=7
-# mcopy -i /data/fat32.img /CORTO1.TXT ::/
-# mcopy -i /data/fat32.img '/Un archivo con nombre largo.dat' ::/
-# mcopy -i /data/fat32.img '/tercer_archivo' ::/
-# mcopy -i /data/fat32.img '/Un archivo con nombre largo.dat' ::/
-# mcopy -i /data/fat32.img '/some_long_embedded_nameא' ::/foo/bar
-#
-# i=0
-# until [ $i -gt 75 ]; do mmd -i /data/fat32.img ::/foo/dir${i}; i=$(( $i+1 )); done
-
-# fatlabel /data/fat32.img go-diskfs
-# exit
-$
-```
-
-We make the `\foo` directory with sufficient entries to exceed a single cluster. This allows us to test reading. Since each directory entry is 32 bytes, and a cluster is 4*512 = 2048 bytes, we need 2048/32 = 64 entries to fill the cluster and one more to get to the next one.
-
-You now have the exact fat32 files in `$PWD`
-
-**Note:** Certain data should be copied over to your "valid" test information in test files, notably:
-
-* volume serial number is generated by creation date/time; you cannot "pre-plan" it. Thus, it should be copied into the appropriate place in `getValidDos71EBPB()` [dos71bpb_test.go](../dos71bpb_test.go)
-* free data cluster count, should be copied over to the appropriate place in `getValidFSInfoSector` [fsinfosector_test.go](../fsinfosector_test.go)
-* last allocated cluster, should be copied over to the appropriate place in `getValidFSInfoSector` [fsinfosector_test.go](../fsinfosector_test.go)
-* allocated FAT clusters
+To generate the artifacts, run `mkfat32.sh`. This will generate a `fat32.img` file in the `dist/`
+directory, as well as all sorts of information files about the filesystem and its contents,
+generated using standard tooling.
+The go tests for fat32 automatically generate those if `dist/fat32.img` is not there.
+if it is and you need to regenerate it, you can run `mkfat32.sh` and then run the tests,
+or just remove the `dist/` directory and it will regenerate everything the next time you run the
+tests.
## Basic Builds
In addition to the usual test harnesses, this directory contains a file that generates a basic fat32 filesystem. To build it:
diff --git a/filesystem/fat32/testdata/fat32.img b/filesystem/fat32/testdata/fat32.img
deleted file mode 100644
index 9443498f..00000000
Binary files a/filesystem/fat32/testdata/fat32.img and /dev/null differ
diff --git a/filesystem/fat32/testdata/mkfat32.sh b/filesystem/fat32/testdata/mkfat32.sh
index da35aabc..77cbdf87 100755
--- a/filesystem/fat32/testdata/mkfat32.sh
+++ b/filesystem/fat32/testdata/mkfat32.sh
@@ -2,19 +2,82 @@
set -e
set +x
-cat <<"EOF" | docker run -i --rm -v $PWD:/data alpine:3.8
+mkdir -p dist
+cat <<"EOF" | docker run -i --rm -v $PWD/dist:/data alpine:3.20
set -e
-set +x
-apk --update add mtools dosfstools
-mkfs.vfat -F 32 -v -C /tmp/boot.img 10000
-mmd -i /tmp/boot.img ::/A
-mmd -i /tmp/boot.img ::/b
-echo testfile > testfile
-echo sub > sub
-dd if=/dev/random of=large bs=1M count=5
-mcopy -i /tmp/boot.img testfile ::/
-mcopy -i /tmp/boot.img sub ::/b
-mcopy -i /tmp/boot.img large ::/b
-cp /tmp/boot.img /data
-EOF
+set -x
+
+apk --update add dosfstools mtools sleuthkit
+dd if=/dev/zero of=/data/fat32.img bs=1M count=10
+mkfs.vfat -v -F 32 /data/fat32.img
+echo "mtools_skip_check=1" >> /etc/mtools.conf
+mmd -i /data/fat32.img ::/foo
+mmd -i /data/fat32.img ::/foo/bar
+echo 'Tenemos un archivo corto' > /CORTO1.TXT
+dd if=/dev/zero 'of=/Un archivo con nombre largo.dat' bs=1024 count=2
+dd if=/dev/zero 'of=/tercer_archivo' bs=1024 count=6
+dd if=/dev/zero 'of=/Un archivo con nombre largo.dat' bs=1024 count=7
+dd if=/dev/zero 'of=/some_long_embedded_nameא' bs=1024 count=7
+mcopy -i /data/fat32.img /CORTO1.TXT ::/
+mcopy -i /data/fat32.img '/Un archivo con nombre largo.dat' ::/
+mcopy -i /data/fat32.img '/tercer_archivo' ::/
+mcopy -i /data/fat32.img '/some_long_embedded_nameא' ::/foo/bar
+
+i=0
+until [ $i -gt 75 ]; do mmd -i /data/fat32.img ::/foo/dir${i}; i=$(( $i+1 )); done
+
+fatlabel /data/fat32.img go-diskfs
+
+# now get the information we need to build the testdata
+# root dir info
+mdir -a -i /data/fat32.img ::/ > /data/root_dir.txt
+fls -f fat32 -p /data/fat32.img > /data/root_dir_fls.txt
+i=0
+cat /data/root_dir_fls.txt | while read line; do
+ if [ -z "$line" ]; then
+ continue
+ fi
+ type=$(echo $line | awk '{print $1}')
+ if [ "$type" != "r/r" -a "$type" != "d/d" ]; then
+ continue
+ fi
+ inode=$(echo $line | awk '{print $2}')
+ # remove a trailing :
+ inode=${inode%:}
+ # save the istat info per the order in the directory, so we can find it later
+ istat -f fat32 /data/fat32.img $inode > /data/root_dir_istat_${i}.txt
+ i=$(( $i+1 ))
+done
+
+# foo dir info
+# usual mdir info is helpful
+mdir -a -i /data/fat32.img ::/foo/ > /data/foo_dir.txt
+# to use fls to list the references, first, find the "inode" of /foo/
+foo_inode=$(awk '$3 == "foo" {print $2}' /data/root_dir_fls.txt | tr -d ':')
+fls -f fat32 -p /data/fat32.img $foo_inode > /data/foo_dir_fls.txt
+i=0
+cat /data/foo_dir_fls.txt | while read line; do
+ if [ -z "$line" ]; then
+ continue
+ fi
+ type=$(echo $line | awk '{print $1}')
+ if [ "$type" != "r/r" -a "$type" != "d/d" ]; then
+ continue
+ fi
+ inode=$(echo $line | awk '{print $2}')
+ # remove a trailing :
+ inode=${inode%:}
+ # save the istat info per the order in the directory, so we can find it later
+ istat -f fat32 /data/fat32.img $inode > /data/foo_dir_istat_${i}.txt
+ i=$(( $i+1 ))
+done
+
+dosfsck -v -l /data/fat32.img > /data/fsck.txt
+
+fsstat -f fat32 /data/fat32.img > /data/fsstat.txt
+
+# get the serial number
+dd if=/data/fat32.img bs=1 skip=67 count=4 2>/dev/null| hexdump -e '4/1 "%02x"' > /data/serial.txt
+
+EOF
diff --git a/go.mod b/go.mod
index 4516a458..c3fd0694 100644
--- a/go.mod
+++ b/go.mod
@@ -14,4 +14,7 @@ require (
golang.org/x/sys v0.5.0
)
-require github.com/klauspost/compress v1.17.4
+require (
+ github.com/google/go-cmp v0.6.0
+ github.com/klauspost/compress v1.17.4
+)
diff --git a/go.sum b/go.sum
index 52b0b56d..ebd27995 100644
--- a/go.sum
+++ b/go.sum
@@ -7,6 +7,8 @@ github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab h1:h1Ugj
github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab/go.mod h1:GLo/8fDswSAniFG+BFIaiSPcK610jyzgEhWYPQwuQdw=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=