blob: ffe767281690b3ad1c5bde5c170483e8bbe6d3c3 [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 "bytes"
8 "fmt"
9 "io"
10 "math/rand"
11 "os"
12 "strings"
13 "testing"
14 "time"
15
16 "github.com/stretchr/testify/assert"
17 "github.com/stretchr/testify/require"
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000018 "golang.org/x/sys/unix"
19)
20
21func TestKernelInterop(t *testing.T) {
22 if os.Getenv("IN_KTEST") != "true" {
23 t.Skip("Not in ktest")
24 }
25
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000026 type testCase struct {
27 name string
28 setup func(root *Inode) error
29 validate func(t *testing.T) error
30 }
31
32 // Random timestamp in UTC, divisible by 10ms
Jan Schär79595192024-11-11 14:55:56 +010033 testTimestamp1 := time.Date(2022, 03, 04, 5, 6, 7, 10_000_000, time.UTC)
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000034 // Random timestamp in UTC, divisible by 2s
35 testTimestamp2 := time.Date(2022, 03, 04, 5, 6, 8, 0, time.UTC)
36 // Random timestamp in UTC, divisible by 10ms
Jan Schär79595192024-11-11 14:55:56 +010037 testTimestamp3 := time.Date(2052, 03, 02, 5, 6, 7, 10_000_000, time.UTC)
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000038 // Random timestamp in UTC, divisible by 2s
39 testTimestamp4 := time.Date(2052, 10, 04, 5, 3, 4, 0, time.UTC)
40
41 testContent1 := "testcontent1"
42
43 tests := []testCase{
44 {
45 name: "SimpleFolder",
46 setup: func(root *Inode) error {
47 root.Children = []*Inode{{
48 Name: "testdir",
49 Attrs: AttrDirectory,
50 CreateTime: testTimestamp1,
51 ModTime: testTimestamp2,
52 }}
53 return nil
54 },
55 validate: func(t *testing.T) error {
56 var stat unix.Statx_t
57 if err := unix.Statx(0, "/dut/testdir", 0, unix.STATX_TYPE|unix.STATX_MTIME|unix.STATX_BTIME, &stat); err != nil {
58 availableFiles, err := os.ReadDir("/dut")
59 var availableFileNames []string
60 for _, f := range availableFiles {
61 availableFileNames = append(availableFileNames, f.Name())
62 }
63 if err != nil {
64 t.Fatalf("Failed to list filesystem root directory: %v", err)
65 }
66 t.Fatalf("Failed to stat output: %v (available: %v)", err, strings.Join(availableFileNames, ", "))
67 }
68 if stat.Mode&unix.S_IFDIR == 0 {
69 t.Errorf("testdir is expected to be a directory, but has mode %v", stat.Mode)
70 }
71 btime := time.Unix(stat.Btime.Sec, int64(stat.Btime.Nsec))
Jan Schär79595192024-11-11 14:55:56 +010072 if !btime.Equal(testTimestamp1) {
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000073 t.Errorf("testdir btime expected %v, got %v", testTimestamp1, btime)
74 }
75 mtime := time.Unix(stat.Mtime.Sec, int64(stat.Mtime.Nsec))
76 if !mtime.Equal(testTimestamp2) {
77 t.Errorf("testdir mtime expected %v, got %v", testTimestamp2, mtime)
78 }
79 return nil
80 },
81 },
82 {
83 name: "SimpleFile",
84 setup: func(root *Inode) error {
85 root.Children = []*Inode{{
86 Name: "testfile",
87 CreateTime: testTimestamp3,
88 ModTime: testTimestamp4,
89 Content: strings.NewReader(testContent1),
90 }}
91 return nil
92 },
93 validate: func(t *testing.T) error {
94 var stat unix.Statx_t
95 if err := unix.Statx(0, "/dut/testfile", 0, unix.STATX_TYPE|unix.STATX_MTIME|unix.STATX_BTIME, &stat); err != nil {
96 t.Fatalf("failed to stat output: %v", err)
97 }
98 if stat.Mode&unix.S_IFREG == 0 {
99 t.Errorf("testfile is expected to be a file, but has mode %v", stat.Mode)
100 }
101 btime := time.Unix(stat.Btime.Sec, int64(stat.Btime.Nsec))
Jan Schär79595192024-11-11 14:55:56 +0100102 if !btime.Equal(testTimestamp3) {
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000103 t.Errorf("testfile ctime expected %v, got %v", testTimestamp3, btime)
104 }
105 mtime := time.Unix(stat.Mtime.Sec, int64(stat.Mtime.Nsec))
106 if !mtime.Equal(testTimestamp4) {
107 t.Errorf("testfile mtime expected %v, got %v", testTimestamp3, mtime)
108 }
109 contents, err := os.ReadFile("/dut/testfile")
110 if err != nil {
111 t.Fatalf("failed to read back test file: %v", err)
112 }
113 if string(contents) != testContent1 {
114 t.Errorf("testfile contains %x, got %x", contents, []byte(testContent1))
115 }
116 return nil
117 },
118 },
119 {
120 name: "FolderHierarchy",
121 setup: func(i *Inode) error {
122 i.Children = []*Inode{{
123 Name: "l1",
124 Attrs: AttrDirectory,
125 CreateTime: testTimestamp1,
126 ModTime: testTimestamp2,
127 Children: []*Inode{{
128 Name: "l2",
129 Attrs: AttrDirectory,
130 CreateTime: testTimestamp1,
131 ModTime: testTimestamp2,
132 }},
133 }}
134 return nil
135 },
136 validate: func(t *testing.T) error {
137 dirInfo, err := os.ReadDir("/dut/l1")
138 if err != nil {
139 t.Fatalf("Failed to read top-level directory: %v", err)
140 }
141 require.Len(t, dirInfo, 1, "more subdirs than expected")
142 require.Equal(t, "l2", dirInfo[0].Name(), "unexpected subdir")
143 require.True(t, dirInfo[0].IsDir(), "l1 not a directory")
144 subdirInfo, err := os.ReadDir("/dut/l1/l2")
145 assert.NoError(t, err, "cannot read empty subdir")
146 require.Len(t, subdirInfo, 0, "unexpected subdirs in empty directory")
147 return nil
148 },
149 },
150 {
151 name: "LargeFile",
152 setup: func(i *Inode) error {
153 content := make([]byte, 6500)
154 io.ReadFull(rand.New(rand.NewSource(1)), content)
155 i.Children = []*Inode{{
156 Name: "test.bin",
157 Content: bytes.NewReader(content),
158 }}
159 return nil
160 },
161 validate: func(t *testing.T) error {
162 var stat unix.Stat_t
163 err := unix.Stat("/dut/test.bin", &stat)
164 assert.NoError(t, err, "failed to stat file")
165 require.EqualValues(t, 6500, stat.Size, "wrong size")
166 file, err := os.Open("/dut/test.bin")
167 assert.NoError(t, err, "failed to open test file")
168 defer file.Close()
169 r := io.LimitReader(rand.New(rand.NewSource(1)), 6500) // Random but deterministic data
170 expected, _ := io.ReadAll(r)
171 actual, err := io.ReadAll(file)
172 assert.NoError(t, err, "failed to read test file")
173 assert.Equal(t, expected, actual, "content not identical")
174 return nil
175 },
176 },
177 {
178 name: "Unicode",
179 setup: func(i *Inode) error {
180 i.Children = []*Inode{{
181 Name: "✨😂", // Really exercise that UTF-16 conversion
182 Content: strings.NewReader("😂"),
183 }}
184 return nil
185 },
186 validate: func(t *testing.T) error {
187 file, err := os.Open("/dut/✨😂")
188 if err != nil {
189 availableFiles, err := os.ReadDir("/dut")
190 var availableFileNames []string
191 for _, f := range availableFiles {
192 availableFileNames = append(availableFileNames, f.Name())
193 }
194 if err != nil {
195 t.Fatalf("Failed to list filesystem root directory: %v", err)
196 }
197 t.Fatalf("Failed to open unicode file: %v (available files: %v)", err, strings.Join(availableFileNames, ", "))
198 }
199 defer file.Close()
200 contents, err := io.ReadAll(file)
201 if err != nil {
202 t.Errorf("Wrong content: expected %x, got %x", []byte("😂"), contents)
203 }
204 return nil
205 },
206 },
207 {
208 name: "MultipleMetaClusters",
209 setup: func(root *Inode) error {
210 // Only test up to 2048 files as Linux gets VERY slow if going
211 // up to the maximum of approximately 32K
212 for i := 0; i < 2048; i++ {
213 root.Children = append(root.Children, &Inode{
214 Name: fmt.Sprintf("verylongtestfilename%d", i),
215 Content: strings.NewReader("random test content"),
216 })
217 }
218 return nil
219 },
220 validate: func(t *testing.T) error {
221 files, err := os.ReadDir("/dut")
222 if err != nil {
223 t.Errorf("failed to list directory: %v", err)
224 }
225 if len(files) != 2048 {
226 t.Errorf("wrong number of files: expected %d, got %d", 2048, len(files))
227 }
228 return nil
229 },
230 },
231 }
232
233 for _, test := range tests {
234 t.Run(test.name, func(t *testing.T) {
235 file, err := os.OpenFile("/dev/ram0", os.O_WRONLY|os.O_TRUNC, 0644)
236 if err != nil {
237 t.Fatalf("failed to create test image: %v", err)
238 }
239 size, err := unix.IoctlGetInt(int(file.Fd()), unix.BLKGETSIZE64)
240 if err != nil {
241 t.Fatalf("failed to get ramdisk size: %v", err)
242 }
243 blockSize, err := unix.IoctlGetInt(int(file.Fd()), unix.BLKBSZGET)
244 if err != nil {
245 t.Fatalf("failed to get ramdisk block size: %v", err)
246 }
247 defer file.Close()
248 rootInode := Inode{
249 Attrs: AttrDirectory,
250 }
251 if err := test.setup(&rootInode); err != nil {
252 t.Fatalf("setup failed: %v", err)
253 }
254 if err := WriteFS(file, rootInode, Options{
255 ID: 1234,
256 Label: "KTEST",
257 BlockSize: uint16(blockSize),
258 BlockCount: uint32(size / blockSize),
259 }); err != nil {
260 t.Fatalf("failed to write fileystem: %v", err)
261 }
262 _ = file.Close()
263 if err := os.MkdirAll("/dut", 0755); err != nil {
264 t.Error(err)
265 }
266 // TODO(lorenz): Set CONFIG_FAT_DEFAULT_UTF8 for Monogon Kernel
267 if err := unix.Mount("/dev/ram0", "/dut", "vfat", unix.MS_NOEXEC|unix.MS_NODEV, "utf8=1"); err != nil {
268 t.Fatal(err)
269 }
270 defer unix.Unmount("/dut", 0)
271 test.validate(t)
272 })
273
274 }
275}