osbase/fat32: fix corrupted directories
Previously, after writing the contents of a file or directory, any
remaining space in the last cluster was left as is instead of zeroed.
For files this may be acceptable, but directories do not have an
explicit size in bytes. The result is that, if the device was not zeroed
before writing the FAT32, each directory contains some number of
additional garbage entries, with a random name, size, and initial
cluster.
The fix is to ensure that the last cluster of a directory is filled up
with zeroes, following the FAT32 spec (page 24): "initialize all bytes
of that cluster to 0". The change also applies to files, where zeroing
the remainder of the last cluster should not be necessary, but also
doesn't hurt. At most 32 KiB of zeroes (the maximum cluster size) will
be written for each file.
The fsck test is extended to initialize the file with random bytes.
Without the fix, this results in fsck reporting a large number of
problems.
Change-Id: I7729e028c92bfad9b879a4256f3fa4f7af25553a
Reviewed-on: https://review.monogon.dev/c/monogon/+/3607
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
Tested-by: Jenkins CI
diff --git a/osbase/fat32/fat32.go b/osbase/fat32/fat32.go
index e0d460f..65f03e5 100644
--- a/osbase/fat32/fat32.go
+++ b/osbase/fat32/fat32.go
@@ -399,7 +399,7 @@
if err := i.writeData(wb, bs.Label); err != nil {
return fmt.Errorf("failed to write inode %q: %w", i.Name, err)
}
- if err := wb.FinishBlock(int64(opts.BlockSize)*int64(bs.BlocksPerCluster), false); err != nil {
+ if err := wb.FinishBlock(int64(opts.BlockSize)*int64(bs.BlocksPerCluster), true); err != nil {
return err
}
}
diff --git a/osbase/fat32/fsck_test.go b/osbase/fat32/fsck_test.go
index dde3978..4b5f97d 100644
--- a/osbase/fat32/fsck_test.go
+++ b/osbase/fat32/fsck_test.go
@@ -2,6 +2,8 @@
import (
"fmt"
+ "io"
+ "math/rand"
"os"
"os/exec"
"strings"
@@ -41,6 +43,22 @@
t.Fatal(err)
}
defer os.Remove(testFile.Name())
+ sizeBlocks, err := SizeFS(rootInode, opts)
+ if err != nil {
+ t.Fatalf("failed to calculate size: %v", err)
+ }
+ sizeBytes := sizeBlocks * int64(opts.BlockSize)
+
+ // Fill the file with random bytes before writing the FS.
+ _, err = io.CopyN(testFile, rand.New(rand.NewSource(sizeBytes)), sizeBytes)
+ if err != nil {
+ t.Fatalf("write failed: %v", err)
+ }
+ _, err = testFile.Seek(0, io.SeekStart)
+ if err != nil {
+ t.Fatalf("seek failed: %v", err)
+ }
+
if err := WriteFS(testFile, rootInode, opts); err != nil {
t.Fatalf("failed to write test FS: %v", err)
}