blob: ca62b7689eb3927c2f2339a1d3e3568cbd26a028 [file] [log] [blame]
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +00001package fat32
2
3import (
4 "bytes"
5 "fmt"
6 "io"
7 "math/rand"
8 "os"
9 "strings"
10 "testing"
11 "time"
12
13 "github.com/stretchr/testify/assert"
14 "github.com/stretchr/testify/require"
15 "golang.org/x/mod/semver"
16 "golang.org/x/sys/unix"
17)
18
19func TestKernelInterop(t *testing.T) {
20 if os.Getenv("IN_KTEST") != "true" {
21 t.Skip("Not in ktest")
22 }
23
24 // ONCHANGE(//third_party/linux): Drop this once we move to a Kernel version
25 // newer than 5.19 which will have FAT btime support.
26 kernelVersion, err := os.ReadFile("/proc/sys/kernel/osrelease")
27 if err != nil {
28 t.Fatalf("unable to determine kernel version: %v", err)
29 }
30 haveBtime := semver.Compare("v"+string(kernelVersion), "v5.19.0") >= 0
31
32 type testCase struct {
33 name string
34 setup func(root *Inode) error
35 validate func(t *testing.T) error
36 }
37
38 // Random timestamp in UTC, divisible by 10ms
39 testTimestamp1 := time.Date(2022, 03, 04, 5, 6, 7, 10, time.UTC)
40 // Random timestamp in UTC, divisible by 2s
41 testTimestamp2 := time.Date(2022, 03, 04, 5, 6, 8, 0, time.UTC)
42 // Random timestamp in UTC, divisible by 10ms
43 testTimestamp3 := time.Date(2052, 03, 02, 5, 6, 7, 10, time.UTC)
44 // Random timestamp in UTC, divisible by 2s
45 testTimestamp4 := time.Date(2052, 10, 04, 5, 3, 4, 0, time.UTC)
46
47 testContent1 := "testcontent1"
48
49 tests := []testCase{
50 {
51 name: "SimpleFolder",
52 setup: func(root *Inode) error {
53 root.Children = []*Inode{{
54 Name: "testdir",
55 Attrs: AttrDirectory,
56 CreateTime: testTimestamp1,
57 ModTime: testTimestamp2,
58 }}
59 return nil
60 },
61 validate: func(t *testing.T) error {
62 var stat unix.Statx_t
63 if err := unix.Statx(0, "/dut/testdir", 0, unix.STATX_TYPE|unix.STATX_MTIME|unix.STATX_BTIME, &stat); err != nil {
64 availableFiles, err := os.ReadDir("/dut")
65 var availableFileNames []string
66 for _, f := range availableFiles {
67 availableFileNames = append(availableFileNames, f.Name())
68 }
69 if err != nil {
70 t.Fatalf("Failed to list filesystem root directory: %v", err)
71 }
72 t.Fatalf("Failed to stat output: %v (available: %v)", err, strings.Join(availableFileNames, ", "))
73 }
74 if stat.Mode&unix.S_IFDIR == 0 {
75 t.Errorf("testdir is expected to be a directory, but has mode %v", stat.Mode)
76 }
77 btime := time.Unix(stat.Btime.Sec, int64(stat.Btime.Nsec))
78 if !btime.Equal(testTimestamp1) && haveBtime {
79 t.Errorf("testdir btime expected %v, got %v", testTimestamp1, btime)
80 }
81 mtime := time.Unix(stat.Mtime.Sec, int64(stat.Mtime.Nsec))
82 if !mtime.Equal(testTimestamp2) {
83 t.Errorf("testdir mtime expected %v, got %v", testTimestamp2, mtime)
84 }
85 return nil
86 },
87 },
88 {
89 name: "SimpleFile",
90 setup: func(root *Inode) error {
91 root.Children = []*Inode{{
92 Name: "testfile",
93 CreateTime: testTimestamp3,
94 ModTime: testTimestamp4,
95 Content: strings.NewReader(testContent1),
96 }}
97 return nil
98 },
99 validate: func(t *testing.T) error {
100 var stat unix.Statx_t
101 if err := unix.Statx(0, "/dut/testfile", 0, unix.STATX_TYPE|unix.STATX_MTIME|unix.STATX_BTIME, &stat); err != nil {
102 t.Fatalf("failed to stat output: %v", err)
103 }
104 if stat.Mode&unix.S_IFREG == 0 {
105 t.Errorf("testfile is expected to be a file, but has mode %v", stat.Mode)
106 }
107 btime := time.Unix(stat.Btime.Sec, int64(stat.Btime.Nsec))
108 if !btime.Equal(testTimestamp3) && haveBtime {
109 t.Errorf("testfile ctime expected %v, got %v", testTimestamp3, btime)
110 }
111 mtime := time.Unix(stat.Mtime.Sec, int64(stat.Mtime.Nsec))
112 if !mtime.Equal(testTimestamp4) {
113 t.Errorf("testfile mtime expected %v, got %v", testTimestamp3, mtime)
114 }
115 contents, err := os.ReadFile("/dut/testfile")
116 if err != nil {
117 t.Fatalf("failed to read back test file: %v", err)
118 }
119 if string(contents) != testContent1 {
120 t.Errorf("testfile contains %x, got %x", contents, []byte(testContent1))
121 }
122 return nil
123 },
124 },
125 {
126 name: "FolderHierarchy",
127 setup: func(i *Inode) error {
128 i.Children = []*Inode{{
129 Name: "l1",
130 Attrs: AttrDirectory,
131 CreateTime: testTimestamp1,
132 ModTime: testTimestamp2,
133 Children: []*Inode{{
134 Name: "l2",
135 Attrs: AttrDirectory,
136 CreateTime: testTimestamp1,
137 ModTime: testTimestamp2,
138 }},
139 }}
140 return nil
141 },
142 validate: func(t *testing.T) error {
143 dirInfo, err := os.ReadDir("/dut/l1")
144 if err != nil {
145 t.Fatalf("Failed to read top-level directory: %v", err)
146 }
147 require.Len(t, dirInfo, 1, "more subdirs than expected")
148 require.Equal(t, "l2", dirInfo[0].Name(), "unexpected subdir")
149 require.True(t, dirInfo[0].IsDir(), "l1 not a directory")
150 subdirInfo, err := os.ReadDir("/dut/l1/l2")
151 assert.NoError(t, err, "cannot read empty subdir")
152 require.Len(t, subdirInfo, 0, "unexpected subdirs in empty directory")
153 return nil
154 },
155 },
156 {
157 name: "LargeFile",
158 setup: func(i *Inode) error {
159 content := make([]byte, 6500)
160 io.ReadFull(rand.New(rand.NewSource(1)), content)
161 i.Children = []*Inode{{
162 Name: "test.bin",
163 Content: bytes.NewReader(content),
164 }}
165 return nil
166 },
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",
185 setup: func(i *Inode) error {
186 i.Children = []*Inode{{
187 Name: "✨😂", // Really exercise that UTF-16 conversion
188 Content: strings.NewReader("😂"),
189 }}
190 return nil
191 },
192 validate: func(t *testing.T) error {
193 file, err := os.Open("/dut/✨😂")
194 if err != nil {
195 availableFiles, err := os.ReadDir("/dut")
196 var availableFileNames []string
197 for _, f := range availableFiles {
198 availableFileNames = append(availableFileNames, f.Name())
199 }
200 if err != nil {
201 t.Fatalf("Failed to list filesystem root directory: %v", err)
202 }
203 t.Fatalf("Failed to open unicode file: %v (available files: %v)", err, strings.Join(availableFileNames, ", "))
204 }
205 defer file.Close()
206 contents, err := io.ReadAll(file)
207 if err != nil {
208 t.Errorf("Wrong content: expected %x, got %x", []byte("😂"), contents)
209 }
210 return nil
211 },
212 },
213 {
214 name: "MultipleMetaClusters",
215 setup: func(root *Inode) error {
216 // Only test up to 2048 files as Linux gets VERY slow if going
217 // up to the maximum of approximately 32K
218 for i := 0; i < 2048; i++ {
219 root.Children = append(root.Children, &Inode{
220 Name: fmt.Sprintf("verylongtestfilename%d", i),
221 Content: strings.NewReader("random test content"),
222 })
223 }
224 return nil
225 },
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()
254 rootInode := Inode{
255 Attrs: AttrDirectory,
256 }
257 if err := test.setup(&rootInode); err != nil {
258 t.Fatalf("setup failed: %v", err)
259 }
260 if err := WriteFS(file, rootInode, Options{
261 ID: 1234,
262 Label: "KTEST",
263 BlockSize: uint16(blockSize),
264 BlockCount: uint32(size / blockSize),
265 }); err != nil {
266 t.Fatalf("failed to write fileystem: %v", err)
267 }
268 _ = file.Close()
269 if err := os.MkdirAll("/dut", 0755); err != nil {
270 t.Error(err)
271 }
272 // TODO(lorenz): Set CONFIG_FAT_DEFAULT_UTF8 for Monogon Kernel
273 if err := unix.Mount("/dev/ram0", "/dut", "vfat", unix.MS_NOEXEC|unix.MS_NODEV, "utf8=1"); err != nil {
274 t.Fatal(err)
275 }
276 defer unix.Unmount("/dut", 0)
277 test.validate(t)
278 })
279
280 }
281}