1 package sysfs
2
3 import (
4 _ "embed"
5 "io"
6 "io/fs"
7 "os"
8 "path"
9 "runtime"
10 "sort"
11 "testing"
12
13 experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
14 "github.com/tetratelabs/wazero/internal/platform"
15 "github.com/tetratelabs/wazero/internal/testing/require"
16 "github.com/tetratelabs/wazero/sys"
17 )
18
19 func testOpen_O_RDWR(t *testing.T, tmpDir string, testFS experimentalsys.FS) {
20 file := "file"
21 realPath := path.Join(tmpDir, file)
22 err := os.WriteFile(realPath, []byte{}, 0o600)
23 require.NoError(t, err)
24
25 f, errno := testFS.OpenFile(file, experimentalsys.O_RDWR, 0)
26 require.EqualErrno(t, 0, errno)
27 defer f.Close()
28
29
30 fileContents := []byte{1, 2, 3, 4}
31 n, errno := f.Write(fileContents)
32 require.EqualErrno(t, 0, errno)
33 require.Equal(t, len(fileContents), n)
34
35
36 b, err := os.ReadFile(realPath)
37 require.NoError(t, err)
38 require.Equal(t, fileContents, b)
39
40 require.EqualErrno(t, 0, f.Close())
41
42
43 require.NoError(t, os.Remove(realPath))
44 f, errno = testFS.OpenFile(file, experimentalsys.O_RDONLY|experimentalsys.O_CREAT, 0o444)
45 require.EqualErrno(t, 0, errno)
46 defer f.Close()
47
48 if runtime.GOOS != "windows" {
49
50 _, err = f.Write(fileContents)
51 require.EqualErrno(t, experimentalsys.EBADF, experimentalsys.UnwrapOSError(err))
52 }
53
54
55 stat, errno := f.Stat()
56 require.EqualErrno(t, 0, errno)
57 require.Equal(t, fs.FileMode(0o444), stat.Mode.Perm())
58
59
60 if runtime.GOOS != "windows" {
61 t.Run("strange name", func(t *testing.T) {
62 f, errno = testFS.OpenFile(`e:xperi\ment.txt`, experimentalsys.O_WRONLY|experimentalsys.O_CREAT|experimentalsys.O_TRUNC, 0o600)
63 require.EqualErrno(t, 0, errno)
64 defer f.Close()
65
66 _, errno = f.Stat()
67 require.EqualErrno(t, 0, errno)
68 })
69 }
70
71 t.Run("O_TRUNC", func(t *testing.T) {
72 tmpDir := t.TempDir()
73 testFS := DirFS(tmpDir)
74
75 name := "truncate"
76 realPath := path.Join(tmpDir, name)
77 require.NoError(t, os.WriteFile(realPath, []byte("123456"), 0o0666))
78
79 f, errno = testFS.OpenFile(name, experimentalsys.O_RDWR|experimentalsys.O_TRUNC, 0o444)
80 require.EqualErrno(t, 0, errno)
81 require.EqualErrno(t, 0, f.Close())
82
83 actual, err := os.ReadFile(realPath)
84 require.NoError(t, err)
85 require.Equal(t, 0, len(actual))
86 })
87 }
88
89 func testOpen_Read(t *testing.T, testFS experimentalsys.FS, requireFileIno, expectDirIno bool) {
90 t.Helper()
91
92 t.Run("doesn't exist", func(t *testing.T) {
93 _, errno := testFS.OpenFile("nope", experimentalsys.O_RDONLY, 0)
94
95
96 require.EqualErrno(t, experimentalsys.ENOENT, errno)
97 })
98
99 t.Run("readdir . opens root", func(t *testing.T) {
100 f, errno := testFS.OpenFile(".", experimentalsys.O_RDONLY, 0)
101 require.EqualErrno(t, 0, errno)
102 defer f.Close()
103
104 dirents := requireReaddir(t, f, -1, expectDirIno)
105
106
107 for i := range dirents {
108 dirents[i].Ino = 0
109 }
110
111 require.Equal(t, []experimentalsys.Dirent{
112 {Name: "animals.txt", Type: 0},
113 {Name: "dir", Type: fs.ModeDir},
114 {Name: "empty.txt", Type: 0},
115 {Name: "emptydir", Type: fs.ModeDir},
116 {Name: "sub", Type: fs.ModeDir},
117 }, dirents)
118 })
119
120 t.Run("readdir empty", func(t *testing.T) {
121 f, errno := testFS.OpenFile("emptydir", experimentalsys.O_RDONLY, 0)
122 require.EqualErrno(t, 0, errno)
123 defer f.Close()
124
125 entries := requireReaddir(t, f, -1, expectDirIno)
126 require.Zero(t, len(entries))
127 })
128
129 t.Run("readdir partial", func(t *testing.T) {
130 dirF, errno := testFS.OpenFile("dir", experimentalsys.O_RDONLY, 0)
131 require.EqualErrno(t, 0, errno)
132 defer dirF.Close()
133
134 dirents1, errno := dirF.Readdir(1)
135 require.EqualErrno(t, 0, errno)
136 require.Equal(t, 1, len(dirents1))
137
138 dirents2, errno := dirF.Readdir(1)
139 require.EqualErrno(t, 0, errno)
140 require.Equal(t, 1, len(dirents2))
141
142
143 dirents3, errno := dirF.Readdir(1)
144 require.EqualErrno(t, 0, errno)
145 require.Equal(t, 1, len(dirents3))
146
147 dirents := []experimentalsys.Dirent{dirents1[0], dirents2[0], dirents3[0]}
148 sort.Slice(dirents, func(i, j int) bool { return dirents[i].Name < dirents[j].Name })
149
150 requireIno(t, dirents, expectDirIno)
151
152
153 for i := range dirents {
154 dirents[i].Ino = 0
155 }
156
157 require.Equal(t, []experimentalsys.Dirent{
158 {Name: "-", Type: 0},
159 {Name: "a-", Type: fs.ModeDir},
160 {Name: "ab-", Type: 0},
161 }, dirents)
162
163
164 _, errno = dirF.Readdir(1)
165 require.EqualErrno(t, 0, errno)
166 })
167
168 t.Run("file exists", func(t *testing.T) {
169 f, errno := testFS.OpenFile("animals.txt", experimentalsys.O_RDONLY, 0)
170 require.EqualErrno(t, 0, errno)
171 defer f.Close()
172
173 fileContents := []byte(`bear
174 cat
175 shark
176 dinosaur
177 human
178 `)
179
180 lenToRead := len(fileContents) - 1
181 buf := make([]byte, lenToRead)
182 n, errno := f.Pread(buf, 1)
183 require.EqualErrno(t, 0, errno)
184 require.Equal(t, lenToRead, n)
185 require.Equal(t, fileContents[1:], buf)
186
187
188 offset, errno := f.Seek(1, io.SeekStart)
189 require.EqualErrno(t, 0, errno)
190 require.Equal(t, int64(1), offset)
191
192
193 n, errno = f.Read(buf)
194 require.EqualErrno(t, 0, errno)
195 require.Equal(t, lenToRead, n)
196 require.Equal(t, fileContents[1:], buf)
197 })
198
199 t.Run("file stat includes inode", func(t *testing.T) {
200 f, errno := testFS.OpenFile("empty.txt", experimentalsys.O_RDONLY, 0)
201 require.EqualErrno(t, 0, errno)
202 defer f.Close()
203
204 st, errno := f.Stat()
205 require.EqualErrno(t, 0, errno)
206
207
208 if requireFileIno {
209 require.NotEqual(t, uint64(0), st.Ino, "%+v", st)
210 }
211 })
212
213
214 t.Run("or'd flag", func(t *testing.T) {
215
216
217 const O_NOATIME = experimentalsys.Oflag(0x40000)
218
219 f, errno := testFS.OpenFile("animals.txt", experimentalsys.O_RDONLY|O_NOATIME, 0)
220 require.EqualErrno(t, 0, errno)
221 defer f.Close()
222 })
223
224 t.Run("writing to a read-only file is EBADF", func(t *testing.T) {
225 f, errno := testFS.OpenFile("animals.txt", experimentalsys.O_RDONLY, 0)
226 require.EqualErrno(t, 0, errno)
227 defer f.Close()
228
229 _, errno = f.Write([]byte{1, 2, 3, 4})
230 require.EqualErrno(t, experimentalsys.EBADF, errno)
231 })
232
233 t.Run("opening a directory with O_RDWR is EISDIR", func(t *testing.T) {
234 _, errno := testFS.OpenFile("sub", experimentalsys.O_DIRECTORY|experimentalsys.O_RDWR, 0)
235 require.EqualErrno(t, experimentalsys.EISDIR, errno)
236 })
237 }
238
239 func testLstat(t *testing.T, testFS experimentalsys.FS) {
240 _, errno := testFS.Lstat("cat")
241 require.EqualErrno(t, experimentalsys.ENOENT, errno)
242 _, errno = testFS.Lstat("sub/cat")
243 require.EqualErrno(t, experimentalsys.ENOENT, errno)
244
245 var st sys.Stat_t
246
247 t.Run("dir", func(t *testing.T) {
248 st, errno = testFS.Lstat(".")
249 require.EqualErrno(t, 0, errno)
250 require.True(t, st.Mode.IsDir())
251 require.NotEqual(t, uint64(0), st.Ino)
252 })
253
254 var stFile sys.Stat_t
255
256 t.Run("file", func(t *testing.T) {
257 stFile, errno = testFS.Lstat("animals.txt")
258 require.EqualErrno(t, 0, errno)
259
260 require.Zero(t, stFile.Mode.Type())
261 require.Equal(t, int64(30), stFile.Size)
262 require.NotEqual(t, uint64(0), st.Ino)
263 })
264
265 t.Run("link to file", func(t *testing.T) {
266 requireLinkStat(t, testFS, "animals.txt", stFile)
267 })
268
269 var stSubdir sys.Stat_t
270 t.Run("subdir", func(t *testing.T) {
271 stSubdir, errno = testFS.Lstat("sub")
272 require.EqualErrno(t, 0, errno)
273
274 require.True(t, stSubdir.Mode.IsDir())
275 require.NotEqual(t, uint64(0), st.Ino)
276 })
277
278 t.Run("link to dir", func(t *testing.T) {
279 requireLinkStat(t, testFS, "sub", stSubdir)
280 })
281
282 t.Run("link to dir link", func(t *testing.T) {
283 pathLink := "sub-link"
284 stLink, errno := testFS.Lstat(pathLink)
285 require.EqualErrno(t, 0, errno)
286
287 requireLinkStat(t, testFS, pathLink, stLink)
288 })
289 }
290
291 func requireLinkStat(t *testing.T, testFS experimentalsys.FS, path string, stat sys.Stat_t) {
292 link := path + "-link"
293 stLink, errno := testFS.Lstat(link)
294 require.EqualErrno(t, 0, errno)
295
296 require.NotEqual(t, stat.Ino, stLink.Ino)
297 require.Equal(t, fs.ModeSymlink, stLink.Mode.Type())
298
299
300
301 if runtime.GOOS == "windows" {
302 require.Zero(t, stLink.Size)
303 } else {
304 require.Equal(t, int64(len(path)), stLink.Size)
305 }
306 }
307
308 func testStat(t *testing.T, testFS experimentalsys.FS) {
309 _, errno := testFS.Stat("cat")
310 require.EqualErrno(t, experimentalsys.ENOENT, errno)
311 _, errno = testFS.Stat("sub/cat")
312 require.EqualErrno(t, experimentalsys.ENOENT, errno)
313
314 st, errno := testFS.Stat("sub/test.txt")
315 require.EqualErrno(t, 0, errno)
316
317 require.False(t, st.Mode.IsDir())
318 require.NotEqual(t, uint64(0), st.Dev)
319 require.NotEqual(t, uint64(0), st.Ino)
320
321 st, errno = testFS.Stat("sub")
322 require.EqualErrno(t, 0, errno)
323
324 require.True(t, st.Mode.IsDir())
325
326 if runtime.GOOS != "windows" || platform.IsAtLeastGo120 {
327 require.NotEqual(t, uint64(0), st.Dev)
328 require.NotEqual(t, uint64(0), st.Ino)
329 }
330 }
331
332
333
334 func requireReaddir(t *testing.T, f experimentalsys.File, n int, expectDirIno bool) []experimentalsys.Dirent {
335 entries, errno := f.Readdir(n)
336 require.EqualErrno(t, 0, errno)
337
338 sort.Slice(entries, func(i, j int) bool { return entries[i].Name < entries[j].Name })
339 requireIno(t, entries, expectDirIno)
340 return entries
341 }
342
343 func testReadlink(t *testing.T, readFS, writeFS experimentalsys.FS) {
344 testLinks := []struct {
345 old, dst string
346 }{
347
348 {old: "animals.txt", dst: "symlinked-animals.txt"},
349 {old: "sub/test.txt", dst: "sub/symlinked-test.txt"},
350
351 {old: "animals.txt", dst: "sub/symlinked-animals.txt"},
352
353 {old: "sub/test.txt", dst: "symlinked-zoo.txt"},
354 }
355
356 for _, tl := range testLinks {
357 errno := writeFS.Symlink(tl.old, tl.dst)
358 require.Zero(t, errno, "%v", tl)
359
360 dst, errno := readFS.Readlink(tl.dst)
361 require.EqualErrno(t, 0, errno)
362 require.Equal(t, tl.old, dst)
363 }
364
365 t.Run("errors", func(t *testing.T) {
366 _, err := readFS.Readlink("sub/test.txt")
367 require.Error(t, err)
368 _, err = readFS.Readlink("")
369 require.Error(t, err)
370 _, err = readFS.Readlink("animals.txt")
371 require.Error(t, err)
372 })
373 }
374
375 func requireIno(t *testing.T, dirents []experimentalsys.Dirent, expectDirIno bool) {
376 for i := range dirents {
377 d := dirents[i]
378 if expectDirIno {
379 require.NotEqual(t, uint64(0), d.Ino, "%+v", d)
380 d.Ino = 0
381 } else {
382 require.Zero(t, d.Ino, "%+v", d)
383 }
384 }
385 }
386
387
388
389 func joinPath(dirName, baseName string) string {
390 return path.Join(dirName, baseName)
391 }
392
View as plain text