blob: 5043003abdf778758e54b67a26ae2328670f3e3e [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
2// SPDX-License-Identifier: Apache-2.0
3
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +00004package fat32
5
6import (
7 "fmt"
Jan Schärd589b6a2024-11-11 14:55:38 +01008 "io"
9 "math/rand"
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000010 "os"
11 "os/exec"
12 "strings"
13 "testing"
14 "time"
15
Tim Windelschmidt2a1d1b22024-02-06 07:07:42 +010016 "github.com/bazelbuild/rules_go/go/runfiles"
Jan Schärc1b6df42025-03-20 08:52:18 +000017
18 "source.monogon.dev/osbase/structfs"
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000019)
20
Tim Windelschmidt82e6af72024-07-23 00:05:42 +000021var (
22 // These are filled by bazel at linking time with the canonical path of
23 // their corresponding file. Inside the init function we resolve it
24 // with the rules_go runfiles package to the real path.
25 xFsckPath string
26)
27
28func init() {
29 if os.Getenv("IN_KTEST") == "true" {
30 return
31 }
32
33 var err error
34 for _, path := range []*string{
35 &xFsckPath,
36 } {
37 *path, err = runfiles.Rlocation(*path)
38 if err != nil {
39 panic(err)
40 }
41 }
42}
43
Jan Schärc1b6df42025-03-20 08:52:18 +000044func testWithFsck(t *testing.T, root structfs.Tree, opts Options) {
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000045 t.Helper()
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000046 testFile, err := os.CreateTemp("", "fat32-fsck-test")
47 if err != nil {
48 t.Fatal(err)
49 }
50 defer os.Remove(testFile.Name())
Jan Schärc1b6df42025-03-20 08:52:18 +000051 sizeBlocks, err := SizeFS(root, opts)
Jan Schärd589b6a2024-11-11 14:55:38 +010052 if err != nil {
53 t.Fatalf("failed to calculate size: %v", err)
54 }
55 sizeBytes := sizeBlocks * int64(opts.BlockSize)
56
57 // Fill the file with random bytes before writing the FS.
58 _, err = io.CopyN(testFile, rand.New(rand.NewSource(sizeBytes)), sizeBytes)
59 if err != nil {
60 t.Fatalf("write failed: %v", err)
61 }
62 _, err = testFile.Seek(0, io.SeekStart)
63 if err != nil {
64 t.Fatalf("seek failed: %v", err)
65 }
66
Jan Schärc1b6df42025-03-20 08:52:18 +000067 if err := WriteFS(testFile, root, opts); err != nil {
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000068 t.Fatalf("failed to write test FS: %v", err)
69 }
70 // Run fsck non-interactively (-n), disallow spaces in short file names (-S)
71 // as well as perform deep verification (-V)
72 // If the file system is OK (i.e. fsck does not want to fix it) it returns
73 // 0, otherwise 1.
Tim Windelschmidt82e6af72024-07-23 00:05:42 +000074 fsckCmd := exec.Command(xFsckPath, "-n", "-S", "-V", testFile.Name())
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000075 result, err := fsckCmd.CombinedOutput()
76 if err != nil {
77 t.Errorf("fsck failed: %v", string(result))
78 }
79}
80
81func TestBasicFsck(t *testing.T) {
82 if os.Getenv("IN_KTEST") == "true" {
83 t.Skip("In ktest")
84 }
85 var largeString strings.Builder
86 for i := 0; i < 16384; i++ {
87 fmt.Fprintf(&largeString, "part%d", i)
88 }
89 // Test both common block sizes (512 and 4096 bytes) as well as the largest
90 // supported one (32K)
91 for _, blockSize := range []uint16{512, 4096, 32768} {
92 for _, fixed := range []string{"", "Fixed"} {
93 t.Run(fmt.Sprintf("BlockSize%d%v", blockSize, fixed), func(t *testing.T) {
Jan Schärc1b6df42025-03-20 08:52:18 +000094 var root structfs.Tree
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000095 files := []struct {
96 name string
97 path string
98 content string
99 }{
100 {"FileInRoot", "test1.txt", "test1 content"},
101 {"LongFileInRoot", "verylongtest1.txt", "test1 content long"},
102 {"LongPath", "test1/test2/test3/test4/longdirname.ext/hello", "long path test content"},
103 {"LargeFile", "test1/largefile.txt", largeString.String()},
104 }
105 for _, c := range files {
Jan Schärc1b6df42025-03-20 08:52:18 +0000106 err := root.PlaceFile(c.path, structfs.Bytes(c.content))
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000107 if err != nil {
108 t.Errorf("failed to place file: %v", err)
109 }
110 }
111 opts := Options{ID: 1234, Label: "TEST", BlockSize: blockSize}
112 if fixed == "Fixed" {
113 // Use a block count that is slightly higher than the minimum
114 opts.BlockCount = 67000
115 }
Jan Schärc1b6df42025-03-20 08:52:18 +0000116 testWithFsck(t, root, opts)
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000117 })
118 }
119 }
120}
121
122func TestLotsOfFilesFsck(t *testing.T) {
123 if os.Getenv("IN_KTEST") == "true" {
124 t.Skip("In ktest")
125 }
Jan Schärc1b6df42025-03-20 08:52:18 +0000126 var root structfs.Tree
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000127 for i := 0; i < (32*1024)-2; i++ {
Jan Schärc1b6df42025-03-20 08:52:18 +0000128 root = append(root, &structfs.Node{
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000129 Name: fmt.Sprintf("test%d", i),
Jan Schärc1b6df42025-03-20 08:52:18 +0000130 Content: structfs.Bytes("random test content"),
131 // Add a random ModTime
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000132 ModTime: time.Date(2022, 03, 04, 5, 6, 7, 8, time.UTC),
Jan Schärc1b6df42025-03-20 08:52:18 +0000133 Sys: &DirEntrySys{
134 // Add some random attributes
135 Attrs: AttrHidden | AttrSystem,
136 },
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000137 })
138 }
Jan Schärc1b6df42025-03-20 08:52:18 +0000139 testWithFsck(t, root, Options{ID: 1234, Label: "TEST"})
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000140}