blob: 29699a4c20589f7047be6b3d266b475878617891 [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 (
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +00007 "fmt"
8 "io"
Jan Schärc1b6df42025-03-20 08:52:18 +00009 "io/fs"
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000010 "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"
Jan Schärc1b6df42025-03-20 08:52:18 +000019
20 "source.monogon.dev/osbase/structfs"
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000021)
22
23func TestKernelInterop(t *testing.T) {
24 if os.Getenv("IN_KTEST") != "true" {
25 t.Skip("Not in ktest")
26 }
27
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000028 type testCase struct {
29 name string
Jan Schärc1b6df42025-03-20 08:52:18 +000030 setup func() structfs.Tree
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000031 validate func(t *testing.T) error
32 }
33
34 // Random timestamp in UTC, divisible by 10ms
Jan Schär79595192024-11-11 14:55:56 +010035 testTimestamp1 := time.Date(2022, 03, 04, 5, 6, 7, 10_000_000, time.UTC)
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000036 // Random timestamp in UTC, divisible by 2s
37 testTimestamp2 := time.Date(2022, 03, 04, 5, 6, 8, 0, time.UTC)
38 // Random timestamp in UTC, divisible by 10ms
Jan Schär79595192024-11-11 14:55:56 +010039 testTimestamp3 := time.Date(2052, 03, 02, 5, 6, 7, 10_000_000, time.UTC)
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000040 // Random timestamp in UTC, divisible by 2s
41 testTimestamp4 := time.Date(2052, 10, 04, 5, 3, 4, 0, time.UTC)
42
43 testContent1 := "testcontent1"
44
45 tests := []testCase{
46 {
47 name: "SimpleFolder",
Jan Schärc1b6df42025-03-20 08:52:18 +000048 setup: func() structfs.Tree {
49 return structfs.Tree{{
50 Name: "testdir",
51 Mode: fs.ModeDir,
52 ModTime: testTimestamp2,
53 Sys: &DirEntrySys{
54 CreateTime: testTimestamp1,
55 },
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000056 }}
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000057 },
58 validate: func(t *testing.T) error {
59 var stat unix.Statx_t
60 if err := unix.Statx(0, "/dut/testdir", 0, unix.STATX_TYPE|unix.STATX_MTIME|unix.STATX_BTIME, &stat); err != nil {
61 availableFiles, err := os.ReadDir("/dut")
62 var availableFileNames []string
63 for _, f := range availableFiles {
64 availableFileNames = append(availableFileNames, f.Name())
65 }
66 if err != nil {
67 t.Fatalf("Failed to list filesystem root directory: %v", err)
68 }
69 t.Fatalf("Failed to stat output: %v (available: %v)", err, strings.Join(availableFileNames, ", "))
70 }
71 if stat.Mode&unix.S_IFDIR == 0 {
72 t.Errorf("testdir is expected to be a directory, but has mode %v", stat.Mode)
73 }
74 btime := time.Unix(stat.Btime.Sec, int64(stat.Btime.Nsec))
Jan Schär79595192024-11-11 14:55:56 +010075 if !btime.Equal(testTimestamp1) {
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000076 t.Errorf("testdir btime expected %v, got %v", testTimestamp1, btime)
77 }
78 mtime := time.Unix(stat.Mtime.Sec, int64(stat.Mtime.Nsec))
79 if !mtime.Equal(testTimestamp2) {
80 t.Errorf("testdir mtime expected %v, got %v", testTimestamp2, mtime)
81 }
82 return nil
83 },
84 },
85 {
86 name: "SimpleFile",
Jan Schärc1b6df42025-03-20 08:52:18 +000087 setup: func() structfs.Tree {
88 return structfs.Tree{{
89 Name: "testfile",
90 ModTime: testTimestamp4,
91 Sys: &DirEntrySys{
92 CreateTime: testTimestamp3,
93 },
94 Content: structfs.Bytes(testContent1),
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000095 }}
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000096 },
97 validate: func(t *testing.T) error {
98 var stat unix.Statx_t
99 if err := unix.Statx(0, "/dut/testfile", 0, unix.STATX_TYPE|unix.STATX_MTIME|unix.STATX_BTIME, &stat); err != nil {
100 t.Fatalf("failed to stat output: %v", err)
101 }
102 if stat.Mode&unix.S_IFREG == 0 {
103 t.Errorf("testfile is expected to be a file, but has mode %v", stat.Mode)
104 }
105 btime := time.Unix(stat.Btime.Sec, int64(stat.Btime.Nsec))
Jan Schär79595192024-11-11 14:55:56 +0100106 if !btime.Equal(testTimestamp3) {
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000107 t.Errorf("testfile ctime expected %v, got %v", testTimestamp3, btime)
108 }
109 mtime := time.Unix(stat.Mtime.Sec, int64(stat.Mtime.Nsec))
110 if !mtime.Equal(testTimestamp4) {
111 t.Errorf("testfile mtime expected %v, got %v", testTimestamp3, mtime)
112 }
113 contents, err := os.ReadFile("/dut/testfile")
114 if err != nil {
115 t.Fatalf("failed to read back test file: %v", err)
116 }
117 if string(contents) != testContent1 {
118 t.Errorf("testfile contains %x, got %x", contents, []byte(testContent1))
119 }
120 return nil
121 },
122 },
123 {
124 name: "FolderHierarchy",
Jan Schärc1b6df42025-03-20 08:52:18 +0000125 setup: func() structfs.Tree {
126 return structfs.Tree{{
127 Name: "l1",
128 Mode: fs.ModeDir,
129 ModTime: testTimestamp2,
130 Sys: &DirEntrySys{
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000131 CreateTime: testTimestamp1,
Jan Schärc1b6df42025-03-20 08:52:18 +0000132 },
133 Children: structfs.Tree{{
134 Name: "l2",
135 Mode: fs.ModeDir,
136 ModTime: testTimestamp2,
137 Sys: &DirEntrySys{
138 CreateTime: testTimestamp1,
139 },
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000140 }},
141 }}
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000142 },
143 validate: func(t *testing.T) error {
144 dirInfo, err := os.ReadDir("/dut/l1")
145 if err != nil {
146 t.Fatalf("Failed to read top-level directory: %v", err)
147 }
148 require.Len(t, dirInfo, 1, "more subdirs than expected")
149 require.Equal(t, "l2", dirInfo[0].Name(), "unexpected subdir")
150 require.True(t, dirInfo[0].IsDir(), "l1 not a directory")
151 subdirInfo, err := os.ReadDir("/dut/l1/l2")
152 assert.NoError(t, err, "cannot read empty subdir")
153 require.Len(t, subdirInfo, 0, "unexpected subdirs in empty directory")
154 return nil
155 },
156 },
157 {
158 name: "LargeFile",
Jan Schärc1b6df42025-03-20 08:52:18 +0000159 setup: func() structfs.Tree {
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000160 content := make([]byte, 6500)
161 io.ReadFull(rand.New(rand.NewSource(1)), content)
Jan Schärc1b6df42025-03-20 08:52:18 +0000162 return structfs.Tree{{
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000163 Name: "test.bin",
Jan Schärc1b6df42025-03-20 08:52:18 +0000164 Content: structfs.Bytes(content),
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000165 }}
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000166 },
167 validate: func(t *testing.T) error {
168 var stat unix.Stat_t
169 err := unix.Stat("/dut/test.bin", &stat)
170 assert.NoError(t, err, "failed to stat file")
171 require.EqualValues(t, 6500, stat.Size, "wrong size")
172 file, err := os.Open("/dut/test.bin")
173 assert.NoError(t, err, "failed to open test file")
174 defer file.Close()
175 r := io.LimitReader(rand.New(rand.NewSource(1)), 6500) // Random but deterministic data
176 expected, _ := io.ReadAll(r)
177 actual, err := io.ReadAll(file)
178 assert.NoError(t, err, "failed to read test file")
179 assert.Equal(t, expected, actual, "content not identical")
180 return nil
181 },
182 },
183 {
184 name: "Unicode",
Jan Schärc1b6df42025-03-20 08:52:18 +0000185 setup: func() structfs.Tree {
186 return structfs.Tree{{
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000187 Name: "✨😂", // Really exercise that UTF-16 conversion
Jan Schärc1b6df42025-03-20 08:52:18 +0000188 Content: structfs.Bytes("😂"),
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000189 }}
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000190 },
191 validate: func(t *testing.T) error {
192 file, err := os.Open("/dut/✨😂")
193 if err != nil {
194 availableFiles, err := os.ReadDir("/dut")
195 var availableFileNames []string
196 for _, f := range availableFiles {
197 availableFileNames = append(availableFileNames, f.Name())
198 }
199 if err != nil {
200 t.Fatalf("Failed to list filesystem root directory: %v", err)
201 }
202 t.Fatalf("Failed to open unicode file: %v (available files: %v)", err, strings.Join(availableFileNames, ", "))
203 }
204 defer file.Close()
Jan Schärc1b6df42025-03-20 08:52:18 +0000205 expected := []byte("😂")
206 actual, err := io.ReadAll(file)
207 assert.NoError(t, err, "failed to read test file")
208 assert.Equal(t, expected, actual, "content not identical")
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000209 return nil
210 },
211 },
212 {
213 name: "MultipleMetaClusters",
Jan Schärc1b6df42025-03-20 08:52:18 +0000214 setup: func() structfs.Tree {
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000215 // Only test up to 2048 files as Linux gets VERY slow if going
216 // up to the maximum of approximately 32K
Jan Schärc1b6df42025-03-20 08:52:18 +0000217 var root structfs.Tree
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000218 for i := 0; i < 2048; i++ {
Jan Schärc1b6df42025-03-20 08:52:18 +0000219 root = append(root, &structfs.Node{
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000220 Name: fmt.Sprintf("verylongtestfilename%d", i),
Jan Schärc1b6df42025-03-20 08:52:18 +0000221 Content: structfs.Bytes("random test content"),
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000222 })
223 }
Jan Schärc1b6df42025-03-20 08:52:18 +0000224 return root
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000225 },
226 validate: func(t *testing.T) error {
227 files, err := os.ReadDir("/dut")
228 if err != nil {
229 t.Errorf("failed to list directory: %v", err)
230 }
231 if len(files) != 2048 {
232 t.Errorf("wrong number of files: expected %d, got %d", 2048, len(files))
233 }
234 return nil
235 },
236 },
237 }
238
239 for _, test := range tests {
240 t.Run(test.name, func(t *testing.T) {
241 file, err := os.OpenFile("/dev/ram0", os.O_WRONLY|os.O_TRUNC, 0644)
242 if err != nil {
243 t.Fatalf("failed to create test image: %v", err)
244 }
245 size, err := unix.IoctlGetInt(int(file.Fd()), unix.BLKGETSIZE64)
246 if err != nil {
247 t.Fatalf("failed to get ramdisk size: %v", err)
248 }
249 blockSize, err := unix.IoctlGetInt(int(file.Fd()), unix.BLKBSZGET)
250 if err != nil {
251 t.Fatalf("failed to get ramdisk block size: %v", err)
252 }
253 defer file.Close()
Jan Schärc1b6df42025-03-20 08:52:18 +0000254 root := test.setup()
255 if err := WriteFS(file, root, Options{
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000256 ID: 1234,
257 Label: "KTEST",
258 BlockSize: uint16(blockSize),
259 BlockCount: uint32(size / blockSize),
260 }); err != nil {
261 t.Fatalf("failed to write fileystem: %v", err)
262 }
263 _ = file.Close()
264 if err := os.MkdirAll("/dut", 0755); err != nil {
Tim Windelschmidtd0d5d9d2025-03-26 22:07:11 +0100265 t.Fatal(err)
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000266 }
267 // TODO(lorenz): Set CONFIG_FAT_DEFAULT_UTF8 for Monogon Kernel
268 if err := unix.Mount("/dev/ram0", "/dut", "vfat", unix.MS_NOEXEC|unix.MS_NODEV, "utf8=1"); err != nil {
269 t.Fatal(err)
270 }
271 defer unix.Unmount("/dut", 0)
Tim Windelschmidtd0d5d9d2025-03-26 22:07:11 +0100272 if err := test.validate(t); err != nil {
273 t.Fatal(err)
274 }
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000275 })
276
277 }
278}