1 package wasi_snapshot_preview1_test
2
3 import (
4 "bytes"
5 _ "embed"
6 "fmt"
7 "io"
8 "io/fs"
9 "math"
10 "os"
11 "path"
12 "runtime"
13 "testing"
14 gofstest "testing/fstest"
15 "time"
16
17 "github.com/tetratelabs/wazero"
18 "github.com/tetratelabs/wazero/api"
19 experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
20 "github.com/tetratelabs/wazero/internal/fsapi"
21 "github.com/tetratelabs/wazero/internal/fstest"
22 "github.com/tetratelabs/wazero/internal/platform"
23 "github.com/tetratelabs/wazero/internal/sys"
24 "github.com/tetratelabs/wazero/internal/sysfs"
25 "github.com/tetratelabs/wazero/internal/testing/require"
26 "github.com/tetratelabs/wazero/internal/u64"
27 "github.com/tetratelabs/wazero/internal/wasip1"
28 "github.com/tetratelabs/wazero/internal/wasm"
29 sysapi "github.com/tetratelabs/wazero/sys"
30 )
31
32 func Test_fdAdvise(t *testing.T) {
33 mod, r, _ := requireProxyModule(t, wazero.NewModuleConfig().WithFS(fstest.FS))
34 defer r.Close(testCtx)
35 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdAdviseName, uint64(3), 0, 0, uint64(wasip1.FdAdviceNormal))
36 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdAdviseName, uint64(3), 0, 0, uint64(wasip1.FdAdviceSequential))
37 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdAdviseName, uint64(3), 0, 0, uint64(wasip1.FdAdviceRandom))
38 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdAdviseName, uint64(3), 0, 0, uint64(wasip1.FdAdviceWillNeed))
39 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdAdviseName, uint64(3), 0, 0, uint64(wasip1.FdAdviceDontNeed))
40 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdAdviseName, uint64(3), 0, 0, uint64(wasip1.FdAdviceNoReuse))
41 requireErrnoResult(t, wasip1.ErrnoInval, mod, wasip1.FdAdviseName, uint64(3), 0, 0, uint64(wasip1.FdAdviceNoReuse+1))
42 requireErrnoResult(t, wasip1.ErrnoBadf, mod, wasip1.FdAdviseName, uint64(1111111), 0, 0, uint64(wasip1.FdAdviceNoReuse+1))
43 }
44
45
46 func Test_fdAllocate(t *testing.T) {
47 tmpDir := t.TempDir()
48 const fileName = "file.txt"
49
50
51 realPath := joinPath(tmpDir, fileName)
52 require.NoError(t, os.WriteFile(realPath, []byte("0123456789"), 0o600))
53
54 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(
55 wazero.NewFSConfig().WithDirMount(tmpDir, "/"),
56 ))
57 fsc := mod.(*wasm.ModuleInstance).Sys.FS()
58 preopen := fsc.RootFS()
59 defer r.Close(testCtx)
60
61 fd, errno := fsc.OpenFile(preopen, fileName, experimentalsys.O_RDWR, 0)
62 require.EqualErrno(t, 0, errno)
63
64 f, ok := fsc.LookupFile(fd)
65 require.True(t, ok)
66
67 requireSizeEqual := func(exp int64) {
68 st, errno := f.File.Stat()
69 require.EqualErrno(t, 0, errno)
70 require.Equal(t, exp, st.Size)
71 }
72
73 t.Run("errors", func(t *testing.T) {
74 requireErrnoResult(t, wasip1.ErrnoBadf, mod, wasip1.FdAllocateName, uint64(12345), 0, 0)
75 minusOne := int64(-1)
76 requireErrnoResult(t, wasip1.ErrnoInval, mod, wasip1.FdAllocateName, uint64(fd), uint64(minusOne), uint64(minusOne))
77 requireErrnoResult(t, wasip1.ErrnoInval, mod, wasip1.FdAllocateName, uint64(fd), 0, uint64(minusOne))
78 requireErrnoResult(t, wasip1.ErrnoInval, mod, wasip1.FdAllocateName, uint64(fd), uint64(minusOne), 0)
79 })
80
81 t.Run("do not change size", func(t *testing.T) {
82 for _, tc := range []struct{ offset, length uint64 }{
83 {offset: 0, length: 10},
84 {offset: 5, length: 5},
85 {offset: 4, length: 0},
86 {offset: 10, length: 0},
87 } {
88
89 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdAllocateName,
90 uint64(fd), tc.offset, tc.length)
91 requireSizeEqual(10)
92 }
93 })
94
95 t.Run("increase", func(t *testing.T) {
96
97 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdAllocateName,
98 uint64(fd), 10, 10)
99 requireSizeEqual(20)
100
101
102 buf, err := os.ReadFile(realPath)
103 require.NoError(t, err)
104 require.Equal(t, "0123456789", string(buf[:10]))
105 })
106
107 require.Equal(t, `
108 ==> wasi_snapshot_preview1.fd_allocate(fd=12345,offset=0,len=0)
109 <== errno=EBADF
110 ==> wasi_snapshot_preview1.fd_allocate(fd=4,offset=-1,len=-1)
111 <== errno=EINVAL
112 ==> wasi_snapshot_preview1.fd_allocate(fd=4,offset=0,len=-1)
113 <== errno=EINVAL
114 ==> wasi_snapshot_preview1.fd_allocate(fd=4,offset=-1,len=0)
115 <== errno=EINVAL
116 ==> wasi_snapshot_preview1.fd_allocate(fd=4,offset=0,len=10)
117 <== errno=ESUCCESS
118 ==> wasi_snapshot_preview1.fd_allocate(fd=4,offset=5,len=5)
119 <== errno=ESUCCESS
120 ==> wasi_snapshot_preview1.fd_allocate(fd=4,offset=4,len=0)
121 <== errno=ESUCCESS
122 ==> wasi_snapshot_preview1.fd_allocate(fd=4,offset=10,len=0)
123 <== errno=ESUCCESS
124 ==> wasi_snapshot_preview1.fd_allocate(fd=4,offset=10,len=10)
125 <== errno=ESUCCESS
126 `, "\n"+log.String())
127 }
128
129 func Test_fdClose(t *testing.T) {
130
131 path1, path2 := "dir/-", "dir/a-"
132 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(fstest.FS))
133 defer r.Close(testCtx)
134
135
136 fsc := mod.(*wasm.ModuleInstance).Sys.FS()
137 preopen := fsc.RootFS()
138
139 fdToClose, errno := fsc.OpenFile(preopen, path1, experimentalsys.O_RDONLY, 0)
140 require.EqualErrno(t, 0, errno)
141
142 fdToKeep, errno := fsc.OpenFile(preopen, path2, experimentalsys.O_RDONLY, 0)
143 require.EqualErrno(t, 0, errno)
144
145
146 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdCloseName, uint64(fdToClose))
147 require.Equal(t, `
148 ==> wasi_snapshot_preview1.fd_close(fd=4)
149 <== errno=ESUCCESS
150 `, "\n"+log.String())
151
152
153 _, ok := fsc.LookupFile(fdToClose)
154 require.False(t, ok)
155
156
157 _, ok = fsc.LookupFile(fdToKeep)
158 require.True(t, ok)
159
160 log.Reset()
161 t.Run("ErrnoBadF for an invalid FD", func(t *testing.T) {
162 requireErrnoResult(t, wasip1.ErrnoBadf, mod, wasip1.FdCloseName, uint64(42))
163 require.Equal(t, `
164 ==> wasi_snapshot_preview1.fd_close(fd=42)
165 <== errno=EBADF
166 `, "\n"+log.String())
167 })
168 log.Reset()
169 t.Run("Can close a pre-open", func(t *testing.T) {
170 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdCloseName, uint64(sys.FdPreopen))
171 require.Equal(t, `
172 ==> wasi_snapshot_preview1.fd_close(fd=3)
173 <== errno=ESUCCESS
174 `, "\n"+log.String())
175 })
176 }
177
178
179 func Test_fdDatasync(t *testing.T) {
180 tmpDir := t.TempDir()
181 pathName := "test_path"
182 mod, fd, log, r := requireOpenFile(t, tmpDir, pathName, []byte{}, false)
183 defer r.Close(testCtx)
184
185 tests := []struct {
186 name string
187 fd int32
188 expectedErrno wasip1.Errno
189 expectedLog string
190 }{
191 {
192 name: "invalid FD",
193 fd: 42,
194 expectedErrno: wasip1.ErrnoBadf,
195 expectedLog: `
196 ==> wasi_snapshot_preview1.fd_datasync(fd=42)
197 <== errno=EBADF
198 `,
199 },
200 {
201 name: "valid FD",
202 fd: fd,
203 expectedErrno: wasip1.ErrnoSuccess,
204 expectedLog: `
205 ==> wasi_snapshot_preview1.fd_datasync(fd=4)
206 <== errno=ESUCCESS
207 `,
208 },
209 }
210
211 for _, tt := range tests {
212 tc := tt
213 t.Run(tc.name, func(t *testing.T) {
214 defer log.Reset()
215
216 requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdDatasyncName, uint64(tc.fd))
217 require.Equal(t, tc.expectedLog, "\n"+log.String())
218 })
219 }
220 }
221
222 func openPipe(t *testing.T) (*os.File, *os.File) {
223 r, w, err := os.Pipe()
224 require.NoError(t, err)
225 return r, w
226 }
227
228 func closePipe(r, w *os.File) {
229 r.Close()
230 w.Close()
231 }
232
233 func Test_fdFdstatGet(t *testing.T) {
234 file, dir := "animals.txt", "sub"
235 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(fstest.FS))
236 defer r.Close(testCtx)
237 memorySize := mod.Memory().Size()
238
239
240 fsc := mod.(*wasm.ModuleInstance).Sys.FS()
241 preopen := fsc.RootFS()
242
243
244
245 stdin, _ := fsc.LookupFile(sys.FdStdin)
246 stdinFile, errno := (&sysfs.AdaptFS{FS: &gofstest.MapFS{"stdin": &gofstest.MapFile{
247 Mode: fs.ModeDevice | fs.ModeCharDevice | 0o600,
248 }}}).OpenFile("stdin", 0, 0)
249 require.EqualErrno(t, 0, errno)
250
251 stdin.File = fsapi.Adapt(stdinFile)
252
253
254 fileFD, errno := fsc.OpenFile(preopen, file, experimentalsys.O_RDWR, 0)
255 require.EqualErrno(t, 0, errno)
256
257 dirFD, errno := fsc.OpenFile(preopen, dir, experimentalsys.O_RDONLY, 0)
258 require.EqualErrno(t, 0, errno)
259
260 tests := []struct {
261 name string
262 fd int32
263 resultFdstat uint32
264 expectedMemory []byte
265 expectedErrno wasip1.Errno
266 expectedLog string
267 }{
268 {
269 name: "stdin is a tty",
270 fd: sys.FdStdin,
271 expectedMemory: []byte{
272 2, 0,
273 0, 0, 0, 0, 0, 0,
274 0xdb, 0x1, 0xe0, 0x8, 0x0, 0x0, 0x0, 0x0,
275 0, 0, 0, 0, 0, 0, 0, 0,
276 },
277 expectedLog: `
278 ==> wasi_snapshot_preview1.fd_fdstat_get(fd=0)
279 <== (stat={filetype=CHARACTER_DEVICE,fdflags=,fs_rights_base=FD_DATASYNC|FD_READ|FDSTAT_SET_FLAGS|FD_SYNC|FD_WRITE|FD_ADVISE|FD_ALLOCATE,fs_rights_inheriting=},errno=ESUCCESS)
280 `,
281 },
282 {
283 name: "stdout",
284 fd: sys.FdStdout,
285 expectedMemory: []byte{
286 1, 0,
287 0, 0, 0, 0, 0, 0,
288 0xff, 0x1, 0xe0, 0x8, 0x0, 0x0, 0x0, 0x0,
289 0, 0, 0, 0, 0, 0, 0, 0,
290 },
291 expectedLog: `
292 ==> wasi_snapshot_preview1.fd_fdstat_get(fd=1)
293 <== (stat={filetype=BLOCK_DEVICE,fdflags=,fs_rights_base=FD_DATASYNC|FD_READ|FD_SEEK|FDSTAT_SET_FLAGS|FD_SYNC|FD_TELL|FD_WRITE|FD_ADVISE|FD_ALLOCATE,fs_rights_inheriting=},errno=ESUCCESS)
294 `,
295 },
296 {
297 name: "stderr",
298 fd: sys.FdStderr,
299 expectedMemory: []byte{
300 1, 0,
301 0, 0, 0, 0, 0, 0,
302 0xff, 0x1, 0xe0, 0x8, 0x0, 0x0, 0x0, 0x0,
303 0, 0, 0, 0, 0, 0, 0, 0,
304 },
305 expectedLog: `
306 ==> wasi_snapshot_preview1.fd_fdstat_get(fd=2)
307 <== (stat={filetype=BLOCK_DEVICE,fdflags=,fs_rights_base=FD_DATASYNC|FD_READ|FD_SEEK|FDSTAT_SET_FLAGS|FD_SYNC|FD_TELL|FD_WRITE|FD_ADVISE|FD_ALLOCATE,fs_rights_inheriting=},errno=ESUCCESS)
308 `,
309 },
310 {
311 name: "root",
312 fd: sys.FdPreopen,
313 expectedMemory: []byte{
314 3, 0,
315 0, 0, 0, 0, 0, 0,
316 0x19, 0xfe, 0xbf, 0x7, 0x0, 0x0, 0x0, 0x0,
317 0xff, 0xff, 0xff, 0xf, 0x0, 0x0, 0x0, 0x0,
318 },
319 expectedLog: `
320 ==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
321 <== (stat={filetype=DIRECTORY,fdflags=,fs_rights_base=FD_DATASYNC|FDSTAT_SET_FLAGS|FD_SYNC|PATH_CREATE_DIRECTORY|PATH_CREATE_FILE|PATH_LINK_SOURCE|PATH_LINK_TARGET|PATH_OPEN|FD_READDIR|PATH_READLINK,fs_rights_inheriting=FD_DATASYNC|FD_READ|FD_SEEK|FDSTAT_SET_FLAGS|FD_SYNC|FD_TELL|FD_WRITE|FD_ADVISE|FD_ALLOCATE|PATH_CREATE_DIRECTORY|PATH_CREATE_FILE|PATH_LINK_SOURCE|PATH_LINK_TARGET|PATH_OPEN|FD_READDIR|PATH_READLINK},errno=ESUCCESS)
322 `,
323 },
324 {
325 name: "file",
326 fd: fileFD,
327 expectedMemory: []byte{
328 4, 0,
329 0, 0, 0, 0, 0, 0,
330 0xff, 0x1, 0xe0, 0x8, 0x0, 0x0, 0x0, 0x0,
331 0, 0, 0, 0, 0, 0, 0, 0,
332 },
333 expectedLog: `
334 ==> wasi_snapshot_preview1.fd_fdstat_get(fd=4)
335 <== (stat={filetype=REGULAR_FILE,fdflags=,fs_rights_base=FD_DATASYNC|FD_READ|FD_SEEK|FDSTAT_SET_FLAGS|FD_SYNC|FD_TELL|FD_WRITE|FD_ADVISE|FD_ALLOCATE,fs_rights_inheriting=},errno=ESUCCESS)
336 `,
337 },
338 {
339 name: "dir",
340 fd: dirFD,
341 expectedMemory: []byte{
342 3, 0,
343 0, 0, 0, 0, 0, 0,
344 0x19, 0xfe, 0xbf, 0x7, 0x0, 0x0, 0x0, 0x0,
345 0xff, 0xff, 0xff, 0xf, 0x0, 0x0, 0x0, 0x0,
346 },
347 expectedLog: `
348 ==> wasi_snapshot_preview1.fd_fdstat_get(fd=5)
349 <== (stat={filetype=DIRECTORY,fdflags=,fs_rights_base=FD_DATASYNC|FDSTAT_SET_FLAGS|FD_SYNC|PATH_CREATE_DIRECTORY|PATH_CREATE_FILE|PATH_LINK_SOURCE|PATH_LINK_TARGET|PATH_OPEN|FD_READDIR|PATH_READLINK,fs_rights_inheriting=FD_DATASYNC|FD_READ|FD_SEEK|FDSTAT_SET_FLAGS|FD_SYNC|FD_TELL|FD_WRITE|FD_ADVISE|FD_ALLOCATE|PATH_CREATE_DIRECTORY|PATH_CREATE_FILE|PATH_LINK_SOURCE|PATH_LINK_TARGET|PATH_OPEN|FD_READDIR|PATH_READLINK},errno=ESUCCESS)
350 `,
351 },
352 {
353 name: "bad FD",
354 fd: -1,
355 expectedErrno: wasip1.ErrnoBadf,
356 expectedLog: `
357 ==> wasi_snapshot_preview1.fd_fdstat_get(fd=-1)
358 <== (stat=,errno=EBADF)
359 `,
360 },
361 {
362 name: "resultFdstat exceeds the maximum valid address by 1",
363 fd: dirFD,
364 resultFdstat: memorySize - 24 + 1,
365 expectedErrno: wasip1.ErrnoFault,
366 expectedLog: `
367 ==> wasi_snapshot_preview1.fd_fdstat_get(fd=5)
368 <== (stat=,errno=EFAULT)
369 `,
370 },
371 }
372
373 for _, tt := range tests {
374 tc := tt
375
376 t.Run(tc.name, func(t *testing.T) {
377 defer log.Reset()
378
379 maskMemory(t, mod, len(tc.expectedMemory))
380
381 requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdFdstatGetName, uint64(tc.fd), uint64(tc.resultFdstat))
382 require.Equal(t, tc.expectedLog, "\n"+log.String())
383
384 actual, ok := mod.Memory().Read(0, uint32(len(tc.expectedMemory)))
385 require.True(t, ok)
386 require.Equal(t, tc.expectedMemory, actual)
387 })
388 }
389 }
390
391 func Test_fdFdstatGet_StdioNonblock(t *testing.T) {
392 stdinR, stdinW := openPipe(t)
393 defer closePipe(stdinR, stdinW)
394
395 stdoutR, stdoutW := openPipe(t)
396 defer closePipe(stdoutR, stdoutW)
397
398 stderrR, stderrW := openPipe(t)
399 defer closePipe(stderrR, stderrW)
400
401 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().
402 WithStdin(stdinR).
403 WithStdout(stdoutW).
404 WithStderr(stderrW))
405 defer r.Close(testCtx)
406
407 stdin, stdout, stderr := uint64(0), uint64(1), uint64(2)
408 requireErrnoResult(t, 0, mod, wasip1.FdFdstatSetFlagsName, stdin, uint64(wasip1.FD_NONBLOCK))
409 requireErrnoResult(t, 0, mod, wasip1.FdFdstatSetFlagsName, stdout, uint64(wasip1.FD_NONBLOCK))
410 requireErrnoResult(t, 0, mod, wasip1.FdFdstatSetFlagsName, stderr, uint64(wasip1.FD_NONBLOCK))
411 log.Reset()
412
413 tests := []struct {
414 name string
415 fd int32
416 resultFdstat uint32
417 expectedMemory []byte
418 expectedErrno wasip1.Errno
419 expectedLog string
420 }{
421 {
422 name: "stdin",
423 fd: sys.FdStdin,
424 expectedMemory: []byte{
425 0, 0,
426 5, 0, 0, 0, 0, 0,
427 0xff, 0x1, 0xe0, 0x8, 0x0, 0x0, 0x0, 0x0,
428 0, 0, 0, 0, 0, 0, 0, 0,
429 },
430 expectedLog: `
431 ==> wasi_snapshot_preview1.fd_fdstat_get(fd=0)
432 <== (stat={filetype=UNKNOWN,fdflags=APPEND|NONBLOCK,fs_rights_base=FD_DATASYNC|FD_READ|FD_SEEK|FDSTAT_SET_FLAGS|FD_SYNC|FD_TELL|FD_WRITE|FD_ADVISE|FD_ALLOCATE,fs_rights_inheriting=},errno=ESUCCESS)
433 `,
434 },
435 {
436 name: "stdout",
437 fd: sys.FdStdout,
438 expectedMemory: []byte{
439 0, 0,
440 5, 0, 0, 0, 0, 0,
441 0xff, 0x1, 0xe0, 0x8, 0x0, 0x0, 0x0, 0x0,
442 0, 0, 0, 0, 0, 0, 0, 0,
443 },
444 expectedLog: `
445 ==> wasi_snapshot_preview1.fd_fdstat_get(fd=1)
446 <== (stat={filetype=UNKNOWN,fdflags=APPEND|NONBLOCK,fs_rights_base=FD_DATASYNC|FD_READ|FD_SEEK|FDSTAT_SET_FLAGS|FD_SYNC|FD_TELL|FD_WRITE|FD_ADVISE|FD_ALLOCATE,fs_rights_inheriting=},errno=ESUCCESS)
447 `,
448 },
449 {
450 name: "stderr",
451 fd: sys.FdStderr,
452 expectedMemory: []byte{
453 0, 0,
454 5, 0, 0, 0, 0, 0,
455 0xff, 0x1, 0xe0, 0x8, 0x0, 0x0, 0x0, 0x0,
456 0, 0, 0, 0, 0, 0, 0, 0,
457 },
458 expectedLog: `
459 ==> wasi_snapshot_preview1.fd_fdstat_get(fd=2)
460 <== (stat={filetype=UNKNOWN,fdflags=APPEND|NONBLOCK,fs_rights_base=FD_DATASYNC|FD_READ|FD_SEEK|FDSTAT_SET_FLAGS|FD_SYNC|FD_TELL|FD_WRITE|FD_ADVISE|FD_ALLOCATE,fs_rights_inheriting=},errno=ESUCCESS)
461 `,
462 },
463 }
464
465 for _, tt := range tests {
466 tc := tt
467
468 t.Run(tc.name, func(t *testing.T) {
469 defer log.Reset()
470
471 maskMemory(t, mod, len(tc.expectedMemory))
472
473 requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdFdstatGetName, uint64(tc.fd), uint64(tc.resultFdstat))
474 require.Equal(t, tc.expectedLog, "\n"+log.String())
475
476 actual, ok := mod.Memory().Read(0, uint32(len(tc.expectedMemory)))
477 require.True(t, ok)
478 require.Equal(t, tc.expectedMemory, actual)
479 })
480 }
481 }
482
483 func Test_fdFdstatSetFlagsWithTrunc(t *testing.T) {
484 tmpDir := t.TempDir()
485 fileName := "test"
486
487 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().
488 WithFSConfig(wazero.NewFSConfig().WithDirMount(tmpDir, "/")))
489 defer r.Close(testCtx)
490
491 fsc := mod.(*wasm.ModuleInstance).Sys.FS()
492 preopen := fsc.RootFS()
493
494 fd, errno := fsc.OpenFile(preopen, fileName, experimentalsys.O_RDWR|experimentalsys.O_CREAT|experimentalsys.O_EXCL|experimentalsys.O_TRUNC, 0o600)
495 require.EqualErrno(t, 0, errno)
496
497
498 f, ok := fsc.LookupFile(fd)
499 require.True(t, ok)
500 n, _ := f.File.Write([]byte("abc"))
501 require.Equal(t, n, 3)
502
503 buf, err := os.ReadFile(joinPath(tmpDir, fileName))
504 require.NoError(t, err)
505 require.Equal(t, "abc", string(buf))
506
507 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdFdstatSetFlagsName, uint64(fd), uint64(0))
508 require.Equal(t, `
509 ==> wasi_snapshot_preview1.fd_fdstat_set_flags(fd=4,flags=)
510 <== errno=ESUCCESS
511 `, "\n"+log.String())
512 log.Reset()
513
514 buf, err = os.ReadFile(joinPath(tmpDir, fileName))
515 require.NoError(t, err)
516 require.Equal(t, "abc", string(buf))
517 }
518
519 func Test_fdFdstatSetFlags(t *testing.T) {
520 tmpDir := t.TempDir()
521
522 stdinR, stdinW := openPipe(t)
523 defer closePipe(stdinR, stdinW)
524
525 stdoutR, stdoutW := openPipe(t)
526 defer closePipe(stdoutR, stdoutW)
527
528 stderrR, stderrW := openPipe(t)
529 defer closePipe(stderrR, stderrW)
530
531 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().
532 WithStdin(stdinR).
533 WithStdout(stdoutW).
534 WithStderr(stderrW).
535 WithFSConfig(wazero.NewFSConfig().WithDirMount(tmpDir, "/")))
536 fsc := mod.(*wasm.ModuleInstance).Sys.FS()
537 preopen := fsc.RootFS()
538 defer r.Close(testCtx)
539
540
541
542
543 const fileName = "file.txt"
544
545 fd, errno := fsc.OpenFile(preopen, fileName, experimentalsys.O_RDWR|experimentalsys.O_APPEND|experimentalsys.O_CREAT|experimentalsys.O_EXCL, 0o600)
546 require.EqualErrno(t, 0, errno)
547
548
549 f, ok := fsc.LookupFile(fd)
550 require.True(t, ok)
551 _, errno = f.File.Write([]byte("0123456789"))
552 require.EqualErrno(t, 0, errno)
553
554 writeWazero := func() {
555 iovs := uint32(1)
556 initialMemory := []byte{
557 '?',
558 18, 0, 0, 0,
559 4, 0, 0, 0,
560 23, 0, 0, 0,
561 2, 0, 0, 0,
562 '?',
563 'w', 'a', 'z', 'e',
564 '?',
565 'r', 'o',
566 '?',
567 }
568 iovsCount := uint32(2)
569 resultNwritten := uint32(26)
570
571 ok := mod.Memory().Write(0, initialMemory)
572 require.True(t, ok)
573
574 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdWriteName, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(resultNwritten))
575 require.Equal(t, `
576 ==> wasi_snapshot_preview1.fd_write(fd=4,iovs=1,iovs_len=2)
577 <== (nwritten=6,errno=ESUCCESS)
578 `, "\n"+log.String())
579 log.Reset()
580 }
581
582 requireFileContent := func(exp string) {
583 buf, err := os.ReadFile(joinPath(tmpDir, fileName))
584 require.NoError(t, err)
585 require.Equal(t, exp, string(buf))
586 }
587
588
589 writeWazero()
590 requireFileContent("0123456789" + "wazero")
591
592
593 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdFdstatSetFlagsName, uint64(fd), uint64(0))
594 require.Equal(t, `
595 ==> wasi_snapshot_preview1.fd_fdstat_set_flags(fd=4,flags=)
596 <== errno=ESUCCESS
597 `, "\n"+log.String())
598 log.Reset()
599
600 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdSeekName, uint64(fd), uint64(0), uint64(0), uint64(1024))
601 require.Equal(t, `
602 ==> wasi_snapshot_preview1.fd_seek(fd=4,offset=0,whence=0)
603 <== (newoffset=0,errno=ESUCCESS)
604 `, "\n"+log.String())
605 log.Reset()
606
607
608 writeWazero()
609 requireFileContent("wazero6789" + "wazero")
610
611
612 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdFdstatSetFlagsName, uint64(fd), uint64(wasip1.FD_APPEND))
613 require.Equal(t, `
614 ==> wasi_snapshot_preview1.fd_fdstat_set_flags(fd=4,flags=APPEND)
615 <== errno=ESUCCESS
616 `, "\n"+log.String())
617 log.Reset()
618
619
620 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdTellName, uint64(fd), uint64(1024))
621 require.Equal(t, `
622 ==> wasi_snapshot_preview1.fd_tell(fd=4,result.offset=1024)
623 <== errno=ESUCCESS
624 `, "\n"+log.String())
625 log.Reset()
626 offset, _ := mod.Memory().Read(1024, 4)
627 require.Equal(t, offset, []byte{6, 0, 0, 0})
628
629
630 writeWazero()
631 requireFileContent("wazero6789" + "wazero" + "wazero")
632
633 t.Run("nonblock", func(t *testing.T) {
634 stdin, stdout, stderr := uint64(0), uint64(1), uint64(2)
635 requireErrnoResult(t, 0, mod, wasip1.FdFdstatSetFlagsName, stdin, uint64(wasip1.FD_NONBLOCK))
636 requireErrnoResult(t, 0, mod, wasip1.FdFdstatSetFlagsName, stdout, uint64(wasip1.FD_NONBLOCK))
637 requireErrnoResult(t, 0, mod, wasip1.FdFdstatSetFlagsName, stderr, uint64(wasip1.FD_NONBLOCK))
638 })
639
640 t.Run("errors", func(t *testing.T) {
641 requireErrnoResult(t, wasip1.ErrnoInval, mod, wasip1.FdFdstatSetFlagsName, uint64(fd), uint64(wasip1.FD_DSYNC))
642 requireErrnoResult(t, wasip1.ErrnoInval, mod, wasip1.FdFdstatSetFlagsName, uint64(fd), uint64(wasip1.FD_RSYNC))
643 requireErrnoResult(t, wasip1.ErrnoInval, mod, wasip1.FdFdstatSetFlagsName, uint64(fd), uint64(wasip1.FD_SYNC))
644 requireErrnoResult(t, wasip1.ErrnoBadf, mod, wasip1.FdFdstatSetFlagsName, uint64(12345), uint64(wasip1.FD_APPEND))
645 requireErrnoResult(t, wasip1.ErrnoIsdir, mod, wasip1.FdFdstatSetFlagsName, uint64(3) , uint64(wasip1.FD_APPEND))
646 requireErrnoResult(t, wasip1.ErrnoIsdir, mod, wasip1.FdFdstatSetFlagsName, uint64(3), uint64(wasip1.FD_NONBLOCK))
647 })
648 }
649
650
651 func Test_fdFdstatSetRights(t *testing.T) {
652 log := requireErrnoNosys(t, wasip1.FdFdstatSetRightsName, 0, 0, 0)
653 require.Equal(t, `
654 ==> wasi_snapshot_preview1.fd_fdstat_set_rights(fd=0,fs_rights_base=,fs_rights_inheriting=)
655 <== errno=ENOSYS
656 `, log)
657 }
658
659 func Test_fdFilestatGet(t *testing.T) {
660 file, dir := "animals.txt", "sub"
661 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(fstest.FS))
662 defer r.Close(testCtx)
663 memorySize := mod.Memory().Size()
664
665
666 fsc := mod.(*wasm.ModuleInstance).Sys.FS()
667 preopen := fsc.RootFS()
668
669 fileFD, errno := fsc.OpenFile(preopen, file, experimentalsys.O_RDONLY, 0)
670 require.EqualErrno(t, 0, errno)
671
672 dirFD, errno := fsc.OpenFile(preopen, dir, experimentalsys.O_RDONLY, 0)
673 require.EqualErrno(t, 0, errno)
674
675 tests := []struct {
676 name string
677 fd int32
678 resultFilestat uint32
679 expectedMemory []byte
680 expectedErrno wasip1.Errno
681 expectedLog string
682 }{
683 {
684 name: "stdin",
685 fd: sys.FdStdin,
686 expectedMemory: []byte{
687 0, 0, 0, 0, 0, 0, 0, 0,
688 0, 0, 0, 0, 0, 0, 0, 0,
689
690 1, 0, 0, 0, 0, 0, 0, 0,
691 1, 0, 0, 0, 0, 0, 0, 0,
692 0, 0, 0, 0, 0, 0, 0, 0,
693 0, 0, 0, 0, 0, 0, 0, 0,
694 0, 0, 0, 0, 0, 0, 0, 0,
695 0, 0, 0, 0, 0, 0, 0, 0,
696 },
697 expectedLog: `
698 ==> wasi_snapshot_preview1.fd_filestat_get(fd=0)
699 <== (filestat={filetype=BLOCK_DEVICE,size=0,mtim=0},errno=ESUCCESS)
700 `,
701 },
702 {
703 name: "stdout",
704 fd: sys.FdStdout,
705 expectedMemory: []byte{
706 0, 0, 0, 0, 0, 0, 0, 0,
707 0, 0, 0, 0, 0, 0, 0, 0,
708
709 1, 0, 0, 0, 0, 0, 0, 0,
710 1, 0, 0, 0, 0, 0, 0, 0,
711 0, 0, 0, 0, 0, 0, 0, 0,
712 0, 0, 0, 0, 0, 0, 0, 0,
713 0, 0, 0, 0, 0, 0, 0, 0,
714 0, 0, 0, 0, 0, 0, 0, 0,
715 },
716 expectedLog: `
717 ==> wasi_snapshot_preview1.fd_filestat_get(fd=1)
718 <== (filestat={filetype=BLOCK_DEVICE,size=0,mtim=0},errno=ESUCCESS)
719 `,
720 },
721 {
722 name: "stderr",
723 fd: sys.FdStderr,
724 expectedMemory: []byte{
725 0, 0, 0, 0, 0, 0, 0, 0,
726 0, 0, 0, 0, 0, 0, 0, 0,
727
728 1, 0, 0, 0, 0, 0, 0, 0,
729 1, 0, 0, 0, 0, 0, 0, 0,
730 0, 0, 0, 0, 0, 0, 0, 0,
731 0, 0, 0, 0, 0, 0, 0, 0,
732 0, 0, 0, 0, 0, 0, 0, 0,
733 0, 0, 0, 0, 0, 0, 0, 0,
734 },
735 expectedLog: `
736 ==> wasi_snapshot_preview1.fd_filestat_get(fd=2)
737 <== (filestat={filetype=BLOCK_DEVICE,size=0,mtim=0},errno=ESUCCESS)
738 `,
739 },
740 {
741 name: "root",
742 fd: sys.FdPreopen,
743 expectedMemory: []byte{
744 0, 0, 0, 0, 0, 0, 0, 0,
745 0, 0, 0, 0, 0, 0, 0, 0,
746 3, 0, 0, 0, 0, 0, 0, 0,
747 1, 0, 0, 0, 0, 0, 0, 0,
748 0, 0, 0, 0, 0, 0, 0, 0,
749 0x0, 0x0, 0x7c, 0x78, 0x9d, 0xf2, 0x55, 0x16,
750 0x0, 0x0, 0x7c, 0x78, 0x9d, 0xf2, 0x55, 0x16,
751 0x0, 0x0, 0x7c, 0x78, 0x9d, 0xf2, 0x55, 0x16,
752 },
753 expectedLog: `
754 ==> wasi_snapshot_preview1.fd_filestat_get(fd=3)
755 <== (filestat={filetype=DIRECTORY,size=0,mtim=1609459200000000000},errno=ESUCCESS)
756 `,
757 },
758 {
759 name: "file",
760 fd: fileFD,
761 expectedMemory: []byte{
762 0, 0, 0, 0, 0, 0, 0, 0,
763 0, 0, 0, 0, 0, 0, 0, 0,
764 4, 0, 0, 0, 0, 0, 0, 0,
765 1, 0, 0, 0, 0, 0, 0, 0,
766 30, 0, 0, 0, 0, 0, 0, 0,
767 0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17,
768 0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17,
769 0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17,
770 },
771 expectedLog: `
772 ==> wasi_snapshot_preview1.fd_filestat_get(fd=4)
773 <== (filestat={filetype=REGULAR_FILE,size=30,mtim=1667482413000000000},errno=ESUCCESS)
774 `,
775 },
776 {
777 name: "dir",
778 fd: dirFD,
779 expectedMemory: []byte{
780 0, 0, 0, 0, 0, 0, 0, 0,
781 0, 0, 0, 0, 0, 0, 0, 0,
782 3, 0, 0, 0, 0, 0, 0, 0,
783 1, 0, 0, 0, 0, 0, 0, 0,
784 0, 0, 0, 0, 0, 0, 0, 0,
785 0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16,
786 0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16,
787 0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16,
788 },
789 expectedLog: `
790 ==> wasi_snapshot_preview1.fd_filestat_get(fd=5)
791 <== (filestat={filetype=DIRECTORY,size=0,mtim=1640995200000000000},errno=ESUCCESS)
792 `,
793 },
794 {
795 name: "bad FD",
796 fd: -1,
797 expectedErrno: wasip1.ErrnoBadf,
798 expectedLog: `
799 ==> wasi_snapshot_preview1.fd_filestat_get(fd=-1)
800 <== (filestat=,errno=EBADF)
801 `,
802 },
803 {
804 name: "resultFilestat exceeds the maximum valid address by 1",
805 fd: dirFD,
806 resultFilestat: memorySize - 64 + 1,
807 expectedErrno: wasip1.ErrnoFault,
808 expectedLog: `
809 ==> wasi_snapshot_preview1.fd_filestat_get(fd=5)
810 <== (filestat=,errno=EFAULT)
811 `,
812 },
813 }
814
815 for _, tt := range tests {
816 tc := tt
817
818 t.Run(tc.name, func(t *testing.T) {
819 defer log.Reset()
820
821 maskMemory(t, mod, len(tc.expectedMemory))
822
823 requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdFilestatGetName, uint64(tc.fd), uint64(tc.resultFilestat))
824 require.Equal(t, tc.expectedLog, "\n"+log.String())
825
826 actual, ok := mod.Memory().Read(0, uint32(len(tc.expectedMemory)))
827 require.True(t, ok)
828 require.Equal(t, tc.expectedMemory, actual)
829 })
830 }
831 }
832
833 func Test_fdFilestatSetSize(t *testing.T) {
834 tmpDir := t.TempDir()
835
836 tests := []struct {
837 name string
838 size uint64
839 content, expectedContent []byte
840 expectedLog string
841 expectedErrno wasip1.Errno
842 }{
843 {
844 name: "badf",
845 content: []byte("badf"),
846 expectedContent: []byte("badf"),
847 expectedErrno: wasip1.ErrnoBadf,
848 expectedLog: `
849 ==> wasi_snapshot_preview1.fd_filestat_set_size(fd=5,size=0)
850 <== errno=EBADF
851 `,
852 },
853 {
854 name: "truncate",
855 content: []byte("123456"),
856 expectedContent: []byte("12345"),
857 size: 5,
858 expectedErrno: wasip1.ErrnoSuccess,
859 expectedLog: `
860 ==> wasi_snapshot_preview1.fd_filestat_set_size(fd=4,size=5)
861 <== errno=ESUCCESS
862 `,
863 },
864 {
865 name: "truncate to zero",
866 content: []byte("123456"),
867 expectedContent: []byte(""),
868 size: 0,
869 expectedErrno: wasip1.ErrnoSuccess,
870 expectedLog: `
871 ==> wasi_snapshot_preview1.fd_filestat_set_size(fd=4,size=0)
872 <== errno=ESUCCESS
873 `,
874 },
875 {
876 name: "truncate to expand",
877 content: []byte("123456"),
878 expectedContent: append([]byte("123456"), make([]byte, 100)...),
879 size: 106,
880 expectedErrno: wasip1.ErrnoSuccess,
881 expectedLog: `
882 ==> wasi_snapshot_preview1.fd_filestat_set_size(fd=4,size=106)
883 <== errno=ESUCCESS
884 `,
885 },
886 {
887 name: "large size",
888 content: []byte(""),
889 expectedContent: []byte(""),
890 size: math.MaxUint64,
891 expectedErrno: wasip1.ErrnoInval,
892 expectedLog: `
893 ==> wasi_snapshot_preview1.fd_filestat_set_size(fd=4,size=-1)
894 <== errno=EINVAL
895 `,
896 },
897 }
898
899 for _, tt := range tests {
900 tc := tt
901 t.Run(tc.name, func(t *testing.T) {
902 filepath := path.Base(t.Name())
903 mod, fd, log, r := requireOpenFile(t, tmpDir, filepath, tc.content, false)
904 defer r.Close(testCtx)
905
906 if filepath == "badf" {
907 fd++
908 }
909 requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdFilestatSetSizeName, uint64(fd), uint64(tc.size))
910
911 actual, err := os.ReadFile(joinPath(tmpDir, filepath))
912 require.NoError(t, err)
913 require.Equal(t, tc.expectedContent, actual)
914
915 require.Equal(t, tc.expectedLog, "\n"+log.String())
916 })
917 }
918 }
919
920 func Test_fdFilestatSetTimes(t *testing.T) {
921 tmpDir := t.TempDir()
922
923 tests := []struct {
924 name string
925 mtime, atime int64
926 flags uint16
927 expectedLog string
928 expectedErrno wasip1.Errno
929 }{
930 {
931 name: "badf",
932 expectedErrno: wasip1.ErrnoBadf,
933 expectedLog: `
934 ==> wasi_snapshot_preview1.fd_filestat_set_times(fd=-1,atim=0,mtim=0,fst_flags=)
935 <== errno=EBADF
936 `,
937 },
938 {
939 name: "a=omit,m=omit",
940 mtime: 1234,
941 atime: 123451,
942 expectedErrno: wasip1.ErrnoSuccess,
943 expectedLog: `
944 ==> wasi_snapshot_preview1.fd_filestat_set_times(fd=4,atim=123451,mtim=1234,fst_flags=)
945 <== errno=ESUCCESS
946 `,
947 },
948 {
949 name: "a=now,m=omit",
950 expectedErrno: wasip1.ErrnoSuccess,
951 mtime: 1234,
952 atime: 123451,
953 flags: wasip1.FstflagsAtimNow,
954 expectedLog: `
955 ==> wasi_snapshot_preview1.fd_filestat_set_times(fd=4,atim=123451,mtim=1234,fst_flags=ATIM_NOW)
956 <== errno=ESUCCESS
957 `,
958 },
959 {
960 name: "a=omit,m=now",
961 expectedErrno: wasip1.ErrnoSuccess,
962 mtime: 1234,
963 atime: 123451,
964 flags: wasip1.FstflagsMtimNow,
965 expectedLog: `
966 ==> wasi_snapshot_preview1.fd_filestat_set_times(fd=4,atim=123451,mtim=1234,fst_flags=MTIM_NOW)
967 <== errno=ESUCCESS
968 `,
969 },
970 {
971 name: "a=now,m=now",
972 expectedErrno: wasip1.ErrnoSuccess,
973 mtime: 1234,
974 atime: 123451,
975 flags: wasip1.FstflagsAtimNow | wasip1.FstflagsMtimNow,
976 expectedLog: `
977 ==> wasi_snapshot_preview1.fd_filestat_set_times(fd=4,atim=123451,mtim=1234,fst_flags=ATIM_NOW|MTIM_NOW)
978 <== errno=ESUCCESS
979 `,
980 },
981 {
982 name: "a=now,m=set",
983 expectedErrno: wasip1.ErrnoSuccess,
984 mtime: 55555500,
985 atime: 1234,
986 flags: wasip1.FstflagsAtimNow | wasip1.FstflagsMtim,
987 expectedLog: `
988 ==> wasi_snapshot_preview1.fd_filestat_set_times(fd=4,atim=1234,mtim=55555500,fst_flags=ATIM_NOW|MTIM)
989 <== errno=ESUCCESS
990 `,
991 },
992 {
993 name: "a=set,m=now",
994 expectedErrno: wasip1.ErrnoSuccess,
995 mtime: 1234,
996 atime: 55555500,
997 flags: wasip1.FstflagsAtim | wasip1.FstflagsMtimNow,
998 expectedLog: `
999 ==> wasi_snapshot_preview1.fd_filestat_set_times(fd=4,atim=55555500,mtim=1234,fst_flags=ATIM|MTIM_NOW)
1000 <== errno=ESUCCESS
1001 `,
1002 },
1003 {
1004 name: "a=set,m=omit",
1005 expectedErrno: wasip1.ErrnoSuccess,
1006 mtime: 1234,
1007 atime: 55555500,
1008 flags: wasip1.FstflagsAtim,
1009 expectedLog: `
1010 ==> wasi_snapshot_preview1.fd_filestat_set_times(fd=4,atim=55555500,mtim=1234,fst_flags=ATIM)
1011 <== errno=ESUCCESS
1012 `,
1013 },
1014
1015 {
1016 name: "a=omit,m=set",
1017 expectedErrno: wasip1.ErrnoSuccess,
1018 mtime: 55555500,
1019 atime: 1234,
1020 flags: wasip1.FstflagsMtim,
1021 expectedLog: `
1022 ==> wasi_snapshot_preview1.fd_filestat_set_times(fd=4,atim=1234,mtim=55555500,fst_flags=MTIM)
1023 <== errno=ESUCCESS
1024 `,
1025 },
1026
1027 {
1028 name: "a=set,m=set",
1029 expectedErrno: wasip1.ErrnoSuccess,
1030 mtime: 55555500,
1031 atime: 6666666600,
1032 flags: wasip1.FstflagsAtim | wasip1.FstflagsMtim,
1033 expectedLog: `
1034 ==> wasi_snapshot_preview1.fd_filestat_set_times(fd=4,atim=6666666600,mtim=55555500,fst_flags=ATIM|MTIM)
1035 <== errno=ESUCCESS
1036 `,
1037 },
1038 }
1039
1040 for _, tt := range tests {
1041 tc := tt
1042 t.Run(tc.name, func(t *testing.T) {
1043 filepath := path.Base(t.Name())
1044 mod, fd, log, r := requireOpenFile(t, tmpDir, filepath, []byte("anything"), false)
1045 defer r.Close(testCtx)
1046
1047 sys := mod.(*wasm.ModuleInstance).Sys
1048 fsc := sys.FS()
1049
1050 paramFd := fd
1051 if filepath == "badf" {
1052 paramFd = -1
1053 }
1054
1055 f, ok := fsc.LookupFile(fd)
1056 require.True(t, ok)
1057
1058 st, errno := f.File.Stat()
1059 require.EqualErrno(t, 0, errno)
1060 prevAtime, prevMtime := st.Atim, st.Mtim
1061
1062 requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdFilestatSetTimesName,
1063 uint64(paramFd), uint64(tc.atime), uint64(tc.mtime),
1064 uint64(tc.flags),
1065 )
1066
1067 if tc.expectedErrno == wasip1.ErrnoSuccess {
1068 f, ok := fsc.LookupFile(fd)
1069 require.True(t, ok)
1070
1071 st, errno = f.File.Stat()
1072 require.EqualErrno(t, 0, errno)
1073 if tc.flags&wasip1.FstflagsAtim != 0 {
1074 require.Equal(t, tc.atime, st.Atim)
1075 } else if tc.flags&wasip1.FstflagsAtimNow != 0 {
1076 require.True(t, (sys.WalltimeNanos()-st.Atim) < time.Second.Nanoseconds())
1077 } else {
1078 require.Equal(t, prevAtime, st.Atim)
1079 }
1080 if tc.flags&wasip1.FstflagsMtim != 0 {
1081 require.Equal(t, tc.mtime, st.Mtim)
1082 } else if tc.flags&wasip1.FstflagsMtimNow != 0 {
1083 require.True(t, (sys.WalltimeNanos()-st.Mtim) < time.Second.Nanoseconds())
1084 } else {
1085 require.Equal(t, prevMtime, st.Mtim)
1086 }
1087 }
1088 require.Equal(t, tc.expectedLog, "\n"+log.String())
1089 })
1090 }
1091 }
1092
1093 func Test_fdPread(t *testing.T) {
1094 tmpDir := t.TempDir()
1095 mod, fd, log, r := requireOpenFile(t, tmpDir, "test_path", []byte("wazero"), true)
1096 defer r.Close(testCtx)
1097
1098 iovs := uint32(1)
1099 initialMemory := []byte{
1100 '?',
1101 18, 0, 0, 0,
1102 4, 0, 0, 0,
1103 23, 0, 0, 0,
1104 2, 0, 0, 0,
1105 '?',
1106 }
1107
1108 iovsCount := uint32(2)
1109 resultNread := uint32(26)
1110
1111 tests := []struct {
1112 name string
1113 offset int64
1114 expectedMemory []byte
1115 expectedLog string
1116 }{
1117 {
1118 name: "offset zero",
1119 offset: 0,
1120 expectedMemory: append(
1121 initialMemory,
1122 'w', 'a', 'z', 'e',
1123 '?',
1124 'r', 'o',
1125 '?',
1126 6, 0, 0, 0,
1127 '?',
1128 ),
1129 expectedLog: `
1130 ==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=1,iovs_len=2,offset=0)
1131 <== (nread=6,errno=ESUCCESS)
1132 `,
1133 },
1134 {
1135 name: "offset 2",
1136 offset: 2,
1137 expectedMemory: append(
1138 initialMemory,
1139 'z', 'e', 'r', 'o',
1140 '?', '?', '?', '?',
1141 4, 0, 0, 0,
1142 '?',
1143 ),
1144 expectedLog: `
1145 ==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=1,iovs_len=2,offset=2)
1146 <== (nread=4,errno=ESUCCESS)
1147 `,
1148 },
1149 }
1150
1151 for _, tt := range tests {
1152 tc := tt
1153 t.Run(tc.name, func(t *testing.T) {
1154 defer log.Reset()
1155
1156 maskMemory(t, mod, len(tc.expectedMemory))
1157
1158 ok := mod.Memory().Write(0, initialMemory)
1159 require.True(t, ok)
1160
1161 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdPreadName, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(tc.offset), uint64(resultNread))
1162 require.Equal(t, tc.expectedLog, "\n"+log.String())
1163
1164 actual, ok := mod.Memory().Read(0, uint32(len(tc.expectedMemory)))
1165 require.True(t, ok)
1166 require.Equal(t, tc.expectedMemory, actual)
1167 })
1168 }
1169 }
1170
1171 func Test_fdPread_offset(t *testing.T) {
1172 tmpDir := t.TempDir()
1173 mod, fd, log, r := requireOpenFile(t, tmpDir, "test_path", []byte("wazero"), true)
1174 defer r.Close(testCtx)
1175
1176
1177
1178 iovs := uint32(1)
1179 initialMemory := []byte{
1180 '?',
1181 18, 0, 0, 0,
1182 4, 0, 0, 0,
1183 23, 0, 0, 0,
1184 2, 0, 0, 0,
1185 '?',
1186 }
1187 iovsCount := uint32(2)
1188 resultNread := uint32(26)
1189
1190 expectedMemory := append(
1191 initialMemory,
1192 'z', 'e', 'r', 'o',
1193 '?', '?', '?', '?',
1194 4, 0, 0, 0,
1195 '?',
1196 )
1197
1198 maskMemory(t, mod, len(expectedMemory))
1199
1200 ok := mod.Memory().Write(0, initialMemory)
1201 require.True(t, ok)
1202
1203 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdPreadName, uint64(fd), uint64(iovs), uint64(iovsCount), 2, uint64(resultNread))
1204 actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
1205 require.True(t, ok)
1206 require.Equal(t, expectedMemory, actual)
1207
1208
1209
1210 expectedMemory = append(
1211 initialMemory,
1212 'w', 'a', 'z', 'e',
1213 '?',
1214 'r', 'o',
1215 '?',
1216 6, 0, 0, 0,
1217 '?',
1218 )
1219
1220 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdReadName, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(resultNread))
1221 actual, ok = mod.Memory().Read(0, uint32(len(expectedMemory)))
1222 require.True(t, ok)
1223 require.Equal(t, expectedMemory, actual)
1224
1225 expectedLog := `
1226 ==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=1,iovs_len=2,offset=2)
1227 <== (nread=4,errno=ESUCCESS)
1228 ==> wasi_snapshot_preview1.fd_read(fd=4,iovs=1,iovs_len=2)
1229 <== (nread=6,errno=ESUCCESS)
1230 `
1231 require.Equal(t, expectedLog, "\n"+log.String())
1232 }
1233
1234 func Test_fdPread_Errors(t *testing.T) {
1235 tmpDir := t.TempDir()
1236 contents := []byte("wazero")
1237 mod, fd, log, r := requireOpenFile(t, tmpDir, "test_path", contents, true)
1238 defer r.Close(testCtx)
1239
1240 tests := []struct {
1241 name string
1242 fd int32
1243 iovs, iovsCount, resultNread uint32
1244 offset int64
1245 memory []byte
1246 expectedErrno wasip1.Errno
1247 expectedLog string
1248 }{
1249 {
1250 name: "invalid FD",
1251 fd: 42,
1252 memory: []byte{'?', '?', '?', '?'},
1253 expectedErrno: wasip1.ErrnoBadf,
1254 expectedLog: `
1255 ==> wasi_snapshot_preview1.fd_pread(fd=42,iovs=65532,iovs_len=0,offset=0)
1256 <== (nread=,errno=EBADF)
1257 `,
1258 },
1259 {
1260 name: "out-of-memory reading iovs[0].offset",
1261 fd: fd,
1262 iovs: 1,
1263 memory: []byte{'?'},
1264 expectedErrno: wasip1.ErrnoFault,
1265 expectedLog: `
1266 ==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65536,iovs_len=0,offset=0)
1267 <== (nread=,errno=EFAULT)
1268 `,
1269 },
1270 {
1271 name: "out-of-memory reading iovs[0].length",
1272 fd: fd,
1273 iovs: 1, iovsCount: 1,
1274 memory: []byte{
1275 '?',
1276 9, 0, 0, 0,
1277 },
1278 expectedErrno: wasip1.ErrnoFault,
1279 expectedLog: `
1280 ==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65532,iovs_len=1,offset=0)
1281 <== (nread=,errno=EFAULT)
1282 `,
1283 },
1284 {
1285 name: "iovs[0].offset is outside memory",
1286 fd: fd,
1287 iovs: 1, iovsCount: 1,
1288 memory: []byte{
1289 '?',
1290 0, 0, 0x1, 0,
1291 1, 0, 0, 0,
1292 },
1293 expectedErrno: wasip1.ErrnoFault,
1294 expectedLog: `
1295 ==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65528,iovs_len=1,offset=0)
1296 <== (nread=,errno=EFAULT)
1297 `,
1298 },
1299 {
1300 name: "length to read exceeds memory by 1",
1301 fd: fd,
1302 iovs: 1, iovsCount: 1,
1303 memory: []byte{
1304 '?',
1305 9, 0, 0, 0,
1306 0, 0, 0x1, 0,
1307 '?',
1308 },
1309 expectedErrno: wasip1.ErrnoFault,
1310 expectedLog: `
1311 ==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65527,iovs_len=1,offset=0)
1312 <== (nread=,errno=EFAULT)
1313 `,
1314 },
1315 {
1316 name: "resultNread offset is outside memory",
1317 fd: fd,
1318 iovs: 1, iovsCount: 1,
1319 resultNread: 10,
1320 memory: []byte{
1321 '?',
1322 9, 0, 0, 0,
1323 1, 0, 0, 0,
1324 '?',
1325 },
1326 expectedErrno: wasip1.ErrnoFault,
1327 expectedLog: `
1328 ==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65527,iovs_len=1,offset=0)
1329 <== (nread=,errno=EFAULT)
1330 `,
1331 },
1332 {
1333 name: "offset negative",
1334 fd: fd,
1335 iovs: 1, iovsCount: 1,
1336 resultNread: 10,
1337 memory: []byte{
1338 '?',
1339 9, 0, 0, 0,
1340 1, 0, 0, 0,
1341 '?',
1342 '?', '?', '?', '?',
1343 },
1344 offset: int64(-1),
1345 expectedErrno: wasip1.ErrnoIo,
1346 expectedLog: `
1347 ==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65523,iovs_len=1,offset=-1)
1348 <== (nread=,errno=EIO)
1349 `,
1350 },
1351 }
1352
1353 for _, tt := range tests {
1354 tc := tt
1355 t.Run(tc.name, func(t *testing.T) {
1356 defer log.Reset()
1357
1358 offset := uint32(wasm.MemoryPagesToBytesNum(testMemoryPageSize) - uint64(len(tc.memory)))
1359
1360 memoryWriteOK := mod.Memory().Write(offset, tc.memory)
1361 require.True(t, memoryWriteOK)
1362
1363 requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdPreadName, uint64(tc.fd), uint64(tc.iovs+offset), uint64(tc.iovsCount), uint64(tc.offset), uint64(tc.resultNread+offset))
1364 require.Equal(t, tc.expectedLog, "\n"+log.String())
1365 })
1366 }
1367 }
1368
1369 func Test_fdPrestatGet(t *testing.T) {
1370 fsConfig := wazero.NewFSConfig().WithDirMount(t.TempDir(), "/")
1371 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(fsConfig))
1372 defer r.Close(testCtx)
1373
1374 resultPrestat := uint32(1)
1375 expectedMemory := []byte{
1376 '?',
1377 0,
1378 0, 0, 0,
1379
1380 1, 0, 0, 0,
1381 '?',
1382 }
1383
1384 maskMemory(t, mod, len(expectedMemory))
1385
1386 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdPrestatGetName, uint64(sys.FdPreopen), uint64(resultPrestat))
1387 require.Equal(t, `
1388 ==> wasi_snapshot_preview1.fd_prestat_get(fd=3)
1389 <== (prestat={pr_name_len=1},errno=ESUCCESS)
1390 `, "\n"+log.String())
1391
1392 actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
1393 require.True(t, ok)
1394 require.Equal(t, expectedMemory, actual)
1395 }
1396
1397 func Test_fdPrestatGet_Errors(t *testing.T) {
1398 mod, dirFD, log, r := requireOpenFile(t, t.TempDir(), "tmp", nil, true)
1399 defer r.Close(testCtx)
1400
1401 memorySize := mod.Memory().Size()
1402 tests := []struct {
1403 name string
1404 fd int32
1405 resultPrestat uint32
1406 expectedErrno wasip1.Errno
1407 expectedLog string
1408 }{
1409 {
1410 name: "unopened FD",
1411 fd: 42,
1412 resultPrestat: 0,
1413 expectedErrno: wasip1.ErrnoBadf,
1414 expectedLog: `
1415 ==> wasi_snapshot_preview1.fd_prestat_get(fd=42)
1416 <== (prestat=,errno=EBADF)
1417 `,
1418 },
1419 {
1420 name: "not pre-opened FD",
1421 fd: dirFD,
1422 resultPrestat: 0,
1423 expectedErrno: wasip1.ErrnoBadf,
1424 expectedLog: `
1425 ==> wasi_snapshot_preview1.fd_prestat_get(fd=4)
1426 <== (prestat=,errno=EBADF)
1427 `,
1428 },
1429 {
1430 name: "out-of-memory resultPrestat",
1431 fd: sys.FdPreopen,
1432 resultPrestat: memorySize,
1433 expectedErrno: wasip1.ErrnoFault,
1434 expectedLog: `
1435 ==> wasi_snapshot_preview1.fd_prestat_get(fd=3)
1436 <== (prestat=,errno=EFAULT)
1437 `,
1438 },
1439 }
1440
1441 for _, tt := range tests {
1442 tc := tt
1443
1444 t.Run(tc.name, func(t *testing.T) {
1445 defer log.Reset()
1446
1447 requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdPrestatGetName, uint64(tc.fd), uint64(tc.resultPrestat))
1448 require.Equal(t, tc.expectedLog, "\n"+log.String())
1449 })
1450 }
1451 }
1452
1453 func Test_fdPrestatDirName(t *testing.T) {
1454 fsConfig := wazero.NewFSConfig().WithDirMount(t.TempDir(), "/")
1455 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(fsConfig))
1456 defer r.Close(testCtx)
1457
1458 path := uint32(1)
1459 pathLen := uint32(0)
1460 expectedMemory := []byte{
1461 '?', '?', '?', '?',
1462 }
1463
1464 maskMemory(t, mod, len(expectedMemory))
1465
1466 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdPrestatDirNameName, uint64(sys.FdPreopen), uint64(path), uint64(pathLen))
1467 require.Equal(t, `
1468 ==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=3)
1469 <== (path=,errno=ESUCCESS)
1470 `, "\n"+log.String())
1471
1472 actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
1473 require.True(t, ok)
1474 require.Equal(t, expectedMemory, actual)
1475 }
1476
1477 func Test_fdPrestatDirName_Errors(t *testing.T) {
1478 mod, dirFD, log, r := requireOpenFile(t, t.TempDir(), "tmp", nil, true)
1479 defer r.Close(testCtx)
1480
1481 memorySize := mod.Memory().Size()
1482 maskMemory(t, mod, 10)
1483
1484 validAddress := uint32(0)
1485 pathLen := uint32(len("/"))
1486
1487 tests := []struct {
1488 name string
1489 fd int32
1490 path uint32
1491 pathLen uint32
1492 expectedErrno wasip1.Errno
1493 expectedLog string
1494 }{
1495 {
1496 name: "out-of-memory path",
1497 fd: sys.FdPreopen,
1498 path: memorySize,
1499 pathLen: pathLen,
1500 expectedErrno: wasip1.ErrnoFault,
1501 expectedLog: `
1502 ==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=3)
1503 <== (path=,errno=EFAULT)
1504 `,
1505 },
1506 {
1507 name: "path exceeds the maximum valid address by 1",
1508 fd: sys.FdPreopen,
1509 path: memorySize - pathLen + 1,
1510 pathLen: pathLen,
1511 expectedErrno: wasip1.ErrnoFault,
1512 expectedLog: `
1513 ==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=3)
1514 <== (path=,errno=EFAULT)
1515 `,
1516 },
1517 {
1518 name: "pathLen exceeds the length of the dir name",
1519 fd: sys.FdPreopen,
1520 path: validAddress,
1521 pathLen: pathLen + 1,
1522 expectedErrno: wasip1.ErrnoNametoolong,
1523 expectedLog: `
1524 ==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=3)
1525 <== (path=,errno=ENAMETOOLONG)
1526 `,
1527 },
1528 {
1529 name: "unopened FD",
1530 fd: 42,
1531 path: validAddress,
1532 pathLen: pathLen,
1533 expectedErrno: wasip1.ErrnoBadf,
1534 expectedLog: `
1535 ==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=42)
1536 <== (path=,errno=EBADF)
1537 `,
1538 },
1539 {
1540 name: "not pre-opened FD",
1541 fd: dirFD,
1542 path: validAddress,
1543 pathLen: pathLen,
1544 expectedErrno: wasip1.ErrnoBadf,
1545 expectedLog: `
1546 ==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=4)
1547 <== (path=,errno=EBADF)
1548 `,
1549 },
1550 }
1551
1552 for _, tt := range tests {
1553 tc := tt
1554
1555 t.Run(tc.name, func(t *testing.T) {
1556 defer log.Reset()
1557
1558 requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdPrestatDirNameName, uint64(tc.fd), uint64(tc.path), uint64(tc.pathLen))
1559 require.Equal(t, tc.expectedLog, "\n"+log.String())
1560 })
1561 }
1562 }
1563
1564 func Test_fdPwrite(t *testing.T) {
1565 tmpDir := t.TempDir()
1566 pathName := "test_path"
1567 mod, fd, log, r := requireOpenFile(t, tmpDir, pathName, []byte{}, false)
1568 defer r.Close(testCtx)
1569
1570 iovs := uint32(1)
1571 initialMemory := []byte{
1572 '?',
1573 18, 0, 0, 0,
1574 4, 0, 0, 0,
1575 23, 0, 0, 0,
1576 2, 0, 0, 0,
1577 '?',
1578 'w', 'a', 'z', 'e',
1579 '?',
1580 'r', 'o',
1581 }
1582
1583 iovsCount := uint32(2)
1584 resultNwritten := len(initialMemory) + 1
1585
1586 tests := []struct {
1587 name string
1588 offset int64
1589 expectedMemory []byte
1590 expectedContents string
1591 expectedLog string
1592 }{
1593 {
1594 name: "offset zero",
1595 offset: 0,
1596 expectedMemory: append(
1597 initialMemory,
1598 '?',
1599 6, 0, 0, 0,
1600 '?',
1601 ),
1602 expectedContents: "wazero",
1603 expectedLog: `
1604 ==> wasi_snapshot_preview1.fd_pwrite(fd=4,iovs=1,iovs_len=2,offset=0)
1605 <== (nwritten=6,errno=ESUCCESS)
1606 `,
1607 },
1608 {
1609 name: "offset 2",
1610 offset: 2,
1611 expectedMemory: append(
1612 initialMemory,
1613 '?',
1614 6, 0, 0, 0,
1615 '?',
1616 ),
1617 expectedContents: "wawazero",
1618 expectedLog: `
1619 ==> wasi_snapshot_preview1.fd_pwrite(fd=4,iovs=1,iovs_len=2,offset=2)
1620 <== (nwritten=6,errno=ESUCCESS)
1621 `,
1622 },
1623 }
1624
1625 for _, tt := range tests {
1626 tc := tt
1627 t.Run(tc.name, func(t *testing.T) {
1628 defer log.Reset()
1629
1630 maskMemory(t, mod, len(tc.expectedMemory))
1631
1632 ok := mod.Memory().Write(0, initialMemory)
1633 require.True(t, ok)
1634
1635 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdPwriteName, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(tc.offset), uint64(resultNwritten))
1636 require.Equal(t, tc.expectedLog, "\n"+log.String())
1637
1638 actual, ok := mod.Memory().Read(0, uint32(len(tc.expectedMemory)))
1639 require.True(t, ok)
1640 require.Equal(t, tc.expectedMemory, actual)
1641
1642
1643 b, err := os.ReadFile(joinPath(tmpDir, pathName))
1644 require.NoError(t, err)
1645 require.Equal(t, tc.expectedContents, string(b))
1646 })
1647 }
1648 }
1649
1650 func Test_fdPwrite_offset(t *testing.T) {
1651 tmpDir := t.TempDir()
1652 pathName := "test_path"
1653 mod, fd, log, r := requireOpenFile(t, tmpDir, pathName, []byte{}, false)
1654 defer r.Close(testCtx)
1655
1656
1657
1658 iovs := uint32(1)
1659 pwriteMemory := []byte{
1660 '?',
1661 10, 0, 0, 0,
1662 3, 0, 0, 0,
1663 '?',
1664 'e', 'r', 'o',
1665 '?',
1666 }
1667 iovsCount := uint32(1)
1668 resultNwritten := len(pwriteMemory) + 4
1669
1670 expectedMemory := append(
1671 pwriteMemory,
1672 '?', '?', '?', '?',
1673 3, 0, 0, 0,
1674 '?',
1675 )
1676
1677 maskMemory(t, mod, len(expectedMemory))
1678
1679 ok := mod.Memory().Write(0, pwriteMemory)
1680 require.True(t, ok)
1681
1682
1683 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdPwriteName, uint64(fd), uint64(iovs), uint64(iovsCount), 3, uint64(resultNwritten))
1684 actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
1685 require.True(t, ok)
1686 require.Equal(t, expectedMemory, actual)
1687
1688
1689 writeMemory := []byte{
1690 '?',
1691 10, 0, 0, 0,
1692 3, 0, 0, 0,
1693 '?',
1694 'w', 'a', 'z',
1695 '?',
1696 }
1697 expectedMemory = append(
1698 writeMemory,
1699 '?', '?', '?', '?',
1700 3, 0, 0, 0,
1701 '?',
1702 )
1703
1704 ok = mod.Memory().Write(0, writeMemory)
1705 require.True(t, ok)
1706
1707 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdWriteName, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(resultNwritten))
1708 actual, ok = mod.Memory().Read(0, uint32(len(expectedMemory)))
1709 require.True(t, ok)
1710 require.Equal(t, expectedMemory, actual)
1711
1712 expectedLog := `
1713 ==> wasi_snapshot_preview1.fd_pwrite(fd=4,iovs=1,iovs_len=1,offset=3)
1714 <== (nwritten=3,errno=ESUCCESS)
1715 ==> wasi_snapshot_preview1.fd_write(fd=4,iovs=1,iovs_len=1)
1716 <== (nwritten=3,errno=ESUCCESS)
1717 `
1718 require.Equal(t, expectedLog, "\n"+log.String())
1719
1720
1721 b, err := os.ReadFile(joinPath(tmpDir, pathName))
1722 require.NoError(t, err)
1723 require.Equal(t, "wazero", string(b))
1724 }
1725
1726 func Test_fdPwrite_Errors(t *testing.T) {
1727 tmpDir := t.TempDir()
1728 pathName := "test_path"
1729 mod, fd, log, r := requireOpenFile(t, tmpDir, pathName, []byte{}, false)
1730 defer r.Close(testCtx)
1731
1732 tests := []struct {
1733 name string
1734 fd int32
1735 iovs, iovsCount, resultNwritten uint32
1736 offset int64
1737 memory []byte
1738 expectedErrno wasip1.Errno
1739 expectedLog string
1740 }{
1741 {
1742 name: "invalid FD",
1743 fd: 42,
1744 memory: []byte{'?', '?', '?', '?'},
1745 expectedErrno: wasip1.ErrnoBadf,
1746 expectedLog: `
1747 ==> wasi_snapshot_preview1.fd_pwrite(fd=42,iovs=65532,iovs_len=0,offset=0)
1748 <== (nwritten=,errno=EBADF)
1749 `,
1750 },
1751 {
1752 name: "out-of-memory writing iovs[0].offset",
1753 fd: fd,
1754 iovs: 1,
1755 memory: []byte{'?'},
1756 expectedErrno: wasip1.ErrnoFault,
1757 expectedLog: `
1758 ==> wasi_snapshot_preview1.fd_pwrite(fd=4,iovs=65536,iovs_len=0,offset=0)
1759 <== (nwritten=,errno=EFAULT)
1760 `,
1761 },
1762 {
1763 name: "out-of-memory writing iovs[0].length",
1764 fd: fd,
1765 iovs: 1, iovsCount: 1,
1766 memory: []byte{
1767 '?',
1768 9, 0, 0, 0,
1769 },
1770 expectedErrno: wasip1.ErrnoFault,
1771 expectedLog: `
1772 ==> wasi_snapshot_preview1.fd_pwrite(fd=4,iovs=65532,iovs_len=1,offset=0)
1773 <== (nwritten=,errno=EFAULT)
1774 `,
1775 },
1776 {
1777 name: "iovs[0].offset is outside memory",
1778 fd: fd,
1779 iovs: 1, iovsCount: 1,
1780 memory: []byte{
1781 '?',
1782 0, 0, 0x1, 0,
1783 1, 0, 0, 0,
1784 },
1785 expectedErrno: wasip1.ErrnoFault,
1786 expectedLog: `
1787 ==> wasi_snapshot_preview1.fd_pwrite(fd=4,iovs=65528,iovs_len=1,offset=0)
1788 <== (nwritten=,errno=EFAULT)
1789 `,
1790 },
1791 {
1792 name: "length to write exceeds memory by 1",
1793 fd: fd,
1794 iovs: 1, iovsCount: 1,
1795 memory: []byte{
1796 '?',
1797 9, 0, 0, 0,
1798 0, 0, 0x1, 0,
1799 '?',
1800 },
1801 expectedErrno: wasip1.ErrnoFault,
1802 expectedLog: `
1803 ==> wasi_snapshot_preview1.fd_pwrite(fd=4,iovs=65527,iovs_len=1,offset=0)
1804 <== (nwritten=,errno=EFAULT)
1805 `,
1806 },
1807 {
1808 name: "resultNwritten offset is outside memory",
1809 fd: fd,
1810 iovs: 1, iovsCount: 1,
1811 resultNwritten: 10,
1812 memory: []byte{
1813 '?',
1814 9, 0, 0, 0,
1815 1, 0, 0, 0,
1816 '?',
1817 },
1818 expectedErrno: wasip1.ErrnoFault,
1819 expectedLog: `
1820 ==> wasi_snapshot_preview1.fd_pwrite(fd=4,iovs=65527,iovs_len=1,offset=0)
1821 <== (nwritten=,errno=EFAULT)
1822 `,
1823 },
1824 {
1825 name: "offset negative",
1826 fd: fd,
1827 iovs: 1, iovsCount: 1,
1828 resultNwritten: 10,
1829 memory: []byte{
1830 '?',
1831 9, 0, 0, 0,
1832 1, 0, 0, 0,
1833 '?',
1834 '?', '?', '?', '?',
1835 },
1836 offset: int64(-1),
1837 expectedErrno: wasip1.ErrnoIo,
1838 expectedLog: `
1839 ==> wasi_snapshot_preview1.fd_pwrite(fd=4,iovs=65523,iovs_len=1,offset=-1)
1840 <== (nwritten=,errno=EIO)
1841 `,
1842 },
1843 }
1844
1845 for _, tt := range tests {
1846 tc := tt
1847 t.Run(tc.name, func(t *testing.T) {
1848 defer log.Reset()
1849
1850 offset := uint32(wasm.MemoryPagesToBytesNum(testMemoryPageSize) - uint64(len(tc.memory)))
1851
1852 memoryWriteOK := mod.Memory().Write(offset, tc.memory)
1853 require.True(t, memoryWriteOK)
1854
1855 requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdPwriteName, uint64(tc.fd), uint64(tc.iovs+offset), uint64(tc.iovsCount), uint64(tc.offset), uint64(tc.resultNwritten+offset))
1856 require.Equal(t, tc.expectedLog, "\n"+log.String())
1857 })
1858 }
1859 }
1860
1861 func Test_fdRead(t *testing.T) {
1862 mod, fd, log, r := requireOpenFile(t, t.TempDir(), "test_path", []byte("wazero"), true)
1863 defer r.Close(testCtx)
1864
1865 iovs := uint32(1)
1866 initialMemory := []byte{
1867 '?',
1868 26, 0, 0, 0,
1869 4, 0, 0, 0,
1870 31, 0, 0, 0,
1871 0, 0, 0, 0,
1872 31, 0, 0, 0,
1873 2, 0, 0, 0,
1874 '?',
1875 }
1876 iovsCount := uint32(3)
1877 resultNread := uint32(34)
1878 expectedMemory := append(
1879 initialMemory,
1880 'w', 'a', 'z', 'e',
1881 '?',
1882 'r', 'o',
1883 '?',
1884 6, 0, 0, 0,
1885 '?',
1886 )
1887
1888 maskMemory(t, mod, len(expectedMemory))
1889
1890 ok := mod.Memory().Write(0, initialMemory)
1891 require.True(t, ok)
1892
1893 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdReadName, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(resultNread))
1894 require.Equal(t, `
1895 ==> wasi_snapshot_preview1.fd_read(fd=4,iovs=1,iovs_len=3)
1896 <== (nread=6,errno=ESUCCESS)
1897 `, "\n"+log.String())
1898
1899 actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
1900 require.True(t, ok)
1901 require.Equal(t, expectedMemory, actual)
1902 }
1903
1904 func Test_fdRead_Errors(t *testing.T) {
1905 mod, fd, log, r := requireOpenFile(t, t.TempDir(), "test_path", []byte("wazero"), true)
1906 defer r.Close(testCtx)
1907
1908 tests := []struct {
1909 name string
1910 fd int32
1911 iovs, iovsCount, resultNread uint32
1912 memory []byte
1913 expectedErrno wasip1.Errno
1914 expectedLog string
1915 }{
1916 {
1917 name: "invalid FD",
1918 fd: 42,
1919 memory: []byte{'?', '?', '?', '?'},
1920 expectedErrno: wasip1.ErrnoBadf,
1921 expectedLog: `
1922 ==> wasi_snapshot_preview1.fd_read(fd=42,iovs=65532,iovs_len=65532)
1923 <== (nread=,errno=EBADF)
1924 `,
1925 },
1926 {
1927 name: "out-of-memory reading iovs[0].offset",
1928 fd: fd,
1929 iovs: 1,
1930 memory: []byte{'?'},
1931 expectedErrno: wasip1.ErrnoFault,
1932 expectedLog: `
1933 ==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65536,iovs_len=65535)
1934 <== (nread=,errno=EFAULT)
1935 `,
1936 },
1937 {
1938 name: "out-of-memory reading iovs[0].length",
1939 fd: fd,
1940 iovs: 1, iovsCount: 1,
1941 memory: []byte{
1942 '?',
1943 9, 0, 0, 0,
1944 },
1945 expectedErrno: wasip1.ErrnoFault,
1946 expectedLog: `
1947 ==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65532,iovs_len=65532)
1948 <== (nread=,errno=EFAULT)
1949 `,
1950 },
1951 {
1952 name: "iovs[0].offset is outside memory",
1953 fd: fd,
1954 iovs: 1, iovsCount: 1,
1955 memory: []byte{
1956 '?',
1957 0, 0, 0x1, 0,
1958 1, 0, 0, 0,
1959 },
1960 expectedErrno: wasip1.ErrnoFault,
1961 expectedLog: `
1962 ==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65528,iovs_len=65528)
1963 <== (nread=,errno=EFAULT)
1964 `,
1965 },
1966 {
1967 name: "length to read exceeds memory by 1",
1968 fd: fd,
1969 iovs: 1, iovsCount: 1,
1970 memory: []byte{
1971 '?',
1972 9, 0, 0, 0,
1973 0, 0, 0x1, 0,
1974 '?',
1975 },
1976 expectedErrno: wasip1.ErrnoFault,
1977 expectedLog: `
1978 ==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65527,iovs_len=65527)
1979 <== (nread=,errno=EFAULT)
1980 `,
1981 },
1982 {
1983 name: "resultNread offset is outside memory",
1984 fd: fd,
1985 iovs: 1, iovsCount: 1,
1986 resultNread: 10,
1987 memory: []byte{
1988 '?',
1989 9, 0, 0, 0,
1990 1, 0, 0, 0,
1991 '?',
1992 },
1993 expectedErrno: wasip1.ErrnoFault,
1994 expectedLog: `
1995 ==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65527,iovs_len=65527)
1996 <== (nread=,errno=EFAULT)
1997 `,
1998 },
1999 }
2000
2001 for _, tt := range tests {
2002 tc := tt
2003 t.Run(tc.name, func(t *testing.T) {
2004 defer log.Reset()
2005
2006 offset := uint32(wasm.MemoryPagesToBytesNum(testMemoryPageSize) - uint64(len(tc.memory)))
2007
2008 memoryWriteOK := mod.Memory().Write(offset, tc.memory)
2009 require.True(t, memoryWriteOK)
2010
2011 requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdReadName, uint64(tc.fd), uint64(tc.iovs+offset), uint64(tc.iovsCount+offset), uint64(tc.resultNread+offset))
2012 require.Equal(t, tc.expectedLog, "\n"+log.String())
2013 })
2014 }
2015 }
2016
2017 var (
2018 direntDot = []byte{
2019 1, 0, 0, 0, 0, 0, 0, 0,
2020 0, 0, 0, 0, 0, 0, 0, 0,
2021 1, 0, 0, 0,
2022 3, 0, 0, 0,
2023 '.',
2024 }
2025 direntDotDot = []byte{
2026 2, 0, 0, 0, 0, 0, 0, 0,
2027 0, 0, 0, 0, 0, 0, 0, 0,
2028 2, 0, 0, 0,
2029 3, 0, 0, 0,
2030 '.', '.',
2031 }
2032 dirent1 = []byte{
2033 3, 0, 0, 0, 0, 0, 0, 0,
2034 0, 0, 0, 0, 0, 0, 0, 0,
2035 1, 0, 0, 0,
2036 4, 0, 0, 0,
2037 '-',
2038 }
2039 dirent2 = []byte{
2040 4, 0, 0, 0, 0, 0, 0, 0,
2041 0, 0, 0, 0, 0, 0, 0, 0,
2042 2, 0, 0, 0,
2043 3, 0, 0, 0,
2044 'a', '-',
2045 }
2046 dirent3 = []byte{
2047 5, 0, 0, 0, 0, 0, 0, 0,
2048 0, 0, 0, 0, 0, 0, 0, 0,
2049 3, 0, 0, 0,
2050 4, 0, 0, 0,
2051 'a', 'b', '-',
2052 }
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064 dirents = bytes.Join([][]byte{
2065 direntDot,
2066 direntDotDot,
2067 dirent1,
2068 dirent2,
2069 dirent3,
2070
2071 }, nil)
2072 )
2073
2074 func Test_fdReaddir(t *testing.T) {
2075 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(fstest.FS))
2076 defer r.Close(testCtx)
2077
2078 fsc := mod.(*wasm.ModuleInstance).Sys.FS()
2079 preopen := fsc.RootFS()
2080 fd := sys.FdPreopen + 1
2081
2082 tests := []struct {
2083 name string
2084 initialDir string
2085 dir func()
2086 bufLen uint32
2087 cookie int64
2088 expectedMem []byte
2089 expectedMemSize int
2090 expectedBufused uint32
2091 }{
2092 {
2093 name: "empty dir",
2094 initialDir: "emptydir",
2095 bufLen: wasip1.DirentSize + 1,
2096 cookie: 0,
2097 expectedBufused: wasip1.DirentSize + 1,
2098 expectedMem: direntDot,
2099 },
2100 {
2101 name: "full read",
2102 initialDir: "dir",
2103 bufLen: 4096,
2104 cookie: 0,
2105 expectedBufused: 129,
2106 expectedMem: dirents,
2107 },
2108 {
2109 name: "can't read name",
2110 initialDir: "dir",
2111 bufLen: wasip1.DirentSize,
2112 cookie: 0,
2113 expectedBufused: wasip1.DirentSize,
2114 expectedMem: direntDot[:wasip1.DirentSize],
2115 },
2116 {
2117 name: "read exactly first",
2118 initialDir: "dir",
2119 bufLen: 25,
2120 cookie: 0,
2121 expectedBufused: 25,
2122 expectedMem: direntDot,
2123 },
2124 {
2125 name: "read exactly second",
2126 initialDir: "dir",
2127 dir: func() {
2128 f, _ := fsc.LookupFile(fd)
2129 rdd, _ := f.DirentCache()
2130 _, _ = rdd.Read(0, 1)
2131 },
2132 bufLen: 27,
2133 cookie: 1,
2134 expectedBufused: 27,
2135 expectedMem: direntDotDot,
2136 },
2137 {
2138 name: "read second and a little more",
2139 initialDir: "dir",
2140 dir: func() {
2141 f, _ := fsc.LookupFile(fd)
2142 rdd, _ := f.DirentCache()
2143 _, _ = rdd.Read(0, 1)
2144 },
2145 bufLen: 30,
2146 cookie: 1,
2147 expectedBufused: 30,
2148 expectedMem: direntDotDot,
2149 expectedMemSize: len(direntDotDot),
2150 },
2151 {
2152 name: "read second and header of third",
2153 initialDir: "dir",
2154 dir: func() {
2155 f, _ := fsc.LookupFile(fd)
2156 rdd, _ := f.DirentCache()
2157 _, _ = rdd.Read(0, 1)
2158 },
2159 bufLen: 50,
2160 cookie: 1,
2161 expectedBufused: 50,
2162 expectedMem: append(direntDotDot, dirent1[0:24]...),
2163 },
2164 {
2165 name: "read second and third",
2166 initialDir: "dir",
2167 dir: func() {
2168 f, _ := fsc.LookupFile(fd)
2169 rdd, _ := f.DirentCache()
2170 _, _ = rdd.Read(0, 1)
2171 },
2172 bufLen: 53,
2173 cookie: 1,
2174 expectedBufused: 53,
2175 expectedMem: append(direntDotDot, dirent1...),
2176 },
2177 {
2178 name: "read exactly third",
2179 initialDir: "dir",
2180 dir: func() {
2181 f, _ := fsc.LookupFile(fd)
2182 rdd, _ := f.DirentCache()
2183 _, _ = rdd.Read(0, 2)
2184 },
2185 bufLen: 27,
2186 cookie: 2,
2187 expectedBufused: 27,
2188 expectedMem: dirent1,
2189 },
2190 {
2191 name: "read third and beyond",
2192 initialDir: "dir",
2193 dir: func() {
2194 f, _ := fsc.LookupFile(fd)
2195 rdd, _ := f.DirentCache()
2196 _, _ = rdd.Read(0, 2)
2197 },
2198 bufLen: 300,
2199 cookie: 2,
2200 expectedBufused: 78,
2201 expectedMem: append(dirent1, dirent2...),
2202 },
2203 {
2204 name: "read exhausted directory",
2205 initialDir: "dir",
2206 dir: func() {
2207 f, _ := fsc.LookupFile(fd)
2208 rdd, _ := f.DirentCache()
2209 _, _ = rdd.Read(0, 5)
2210 },
2211 bufLen: 300,
2212 cookie: 5,
2213 expectedBufused: 0,
2214 },
2215 }
2216
2217 for _, tt := range tests {
2218 tc := tt
2219 t.Run(tc.name, func(t *testing.T) {
2220 defer log.Reset()
2221
2222 fd, errno := fsc.OpenFile(preopen, tc.initialDir, experimentalsys.O_RDONLY, 0)
2223 require.EqualErrno(t, 0, errno)
2224 defer fsc.CloseFile(fd)
2225
2226 if tc.dir != nil {
2227 tc.dir()
2228 }
2229
2230 maskMemory(t, mod, int(tc.bufLen))
2231
2232 resultBufused := uint32(0)
2233 buf := uint32(8)
2234 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdReaddirName,
2235 uint64(fd), uint64(buf), uint64(tc.bufLen), uint64(tc.cookie), uint64(resultBufused))
2236
2237
2238 bufused, ok := mod.Memory().ReadUint32Le(resultBufused)
2239 require.True(t, ok)
2240 require.Equal(t, tc.expectedBufused, bufused)
2241
2242 mem, ok := mod.Memory().Read(buf, bufused)
2243 require.True(t, ok)
2244
2245 if tc.expectedMem != nil {
2246 if tc.expectedMemSize == 0 {
2247 tc.expectedMemSize = len(tc.expectedMem)
2248 }
2249 require.Equal(t, tc.expectedMem, mem[:tc.expectedMemSize])
2250 }
2251 })
2252 }
2253 }
2254
2255
2256 func Test_fdReaddir_Rewind(t *testing.T) {
2257 tmpDir := t.TempDir()
2258
2259 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(os.DirFS(tmpDir)))
2260 defer r.Close(testCtx)
2261
2262 fsc := mod.(*wasm.ModuleInstance).Sys.FS()
2263
2264 fd, errno := fsc.OpenFile(fsc.RootFS(), ".", experimentalsys.O_RDONLY, 0)
2265 require.EqualErrno(t, 0, errno)
2266
2267 mem := mod.Memory()
2268 const resultBufused, buf = 0, 8
2269 fdReaddir := func(cookie uint64) uint32 {
2270 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdReaddirName,
2271 uint64(fd), buf, 256, cookie, uint64(resultBufused))
2272 bufused, ok := mem.ReadUint32Le(resultBufused)
2273 require.True(t, ok)
2274 return bufused
2275 }
2276
2277
2278 bufused := fdReaddir(0)
2279 dotDirentsLen := (wasip1.DirentSize + 1) + (wasip1.DirentSize + 2)
2280 require.Equal(t, dotDirentsLen, bufused)
2281
2282
2283 fileName := "file"
2284 require.NoError(t, os.WriteFile(path.Join(tmpDir, fileName), nil, 0o0666))
2285 fileDirentLen := wasip1.DirentSize + uint32(len(fileName))
2286
2287
2288 bufused = fdReaddir(0)
2289 require.Equal(t, dotDirentsLen+fileDirentLen, bufused)
2290
2291
2292 bufused = fdReaddir(2)
2293 require.Equal(t, fileDirentLen, bufused)
2294
2295 require.Equal(t, `
2296 ==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=8,buf_len=256,cookie=0)
2297 <== (bufused=51,errno=ESUCCESS)
2298 ==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=8,buf_len=256,cookie=0)
2299 <== (bufused=79,errno=ESUCCESS)
2300 ==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=8,buf_len=256,cookie=2)
2301 <== (bufused=28,errno=ESUCCESS)
2302 `, "\n"+log.String())
2303 }
2304
2305 func Test_fdReaddir_Errors(t *testing.T) {
2306 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(fstest.FS))
2307 defer r.Close(testCtx)
2308 memLen := mod.Memory().Size()
2309
2310 fsc := mod.(*wasm.ModuleInstance).Sys.FS()
2311 preopen := fsc.RootFS()
2312
2313 fileFD, errno := fsc.OpenFile(preopen, "animals.txt", experimentalsys.O_RDONLY, 0)
2314 require.EqualErrno(t, 0, errno)
2315
2316
2317 dirFD := fileFD + 1
2318
2319 tests := []struct {
2320 name string
2321 fd int32
2322 buf, bufLen, resultBufused uint32
2323 cookie int64
2324 expectedErrno wasip1.Errno
2325 expectedLog string
2326 }{
2327 {
2328 name: "out-of-memory reading buf",
2329 fd: dirFD,
2330 buf: memLen,
2331 bufLen: 1000,
2332 expectedErrno: wasip1.ErrnoFault,
2333 expectedLog: `
2334 ==> wasi_snapshot_preview1.fd_readdir(fd=5,buf=65536,buf_len=1000,cookie=0)
2335 <== (bufused=,errno=EFAULT)
2336 `,
2337 },
2338 {
2339 name: "invalid FD",
2340 fd: 42,
2341 buf: 0, bufLen: wasip1.DirentSize,
2342 resultBufused: 1000,
2343 expectedErrno: wasip1.ErrnoBadf,
2344 expectedLog: `
2345 ==> wasi_snapshot_preview1.fd_readdir(fd=42,buf=0,buf_len=24,cookie=0)
2346 <== (bufused=,errno=EBADF)
2347 `,
2348 },
2349 {
2350 name: "not a dir",
2351 fd: fileFD,
2352 buf: 0, bufLen: wasip1.DirentSize,
2353 resultBufused: 1000,
2354 expectedErrno: wasip1.ErrnoBadf,
2355 expectedLog: `
2356 ==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=0,buf_len=24,cookie=0)
2357 <== (bufused=,errno=EBADF)
2358 `,
2359 },
2360 {
2361 name: "out-of-memory reading bufLen",
2362 fd: dirFD,
2363 buf: memLen - 1,
2364 bufLen: 1000,
2365 expectedErrno: wasip1.ErrnoFault,
2366 expectedLog: `
2367 ==> wasi_snapshot_preview1.fd_readdir(fd=5,buf=65535,buf_len=1000,cookie=0)
2368 <== (bufused=,errno=EFAULT)
2369 `,
2370 },
2371 {
2372 name: "bufLen must be enough to write a struct",
2373 fd: dirFD,
2374 buf: 0, bufLen: 1,
2375 resultBufused: 1000,
2376 expectedErrno: wasip1.ErrnoInval,
2377 expectedLog: `
2378 ==> wasi_snapshot_preview1.fd_readdir(fd=5,buf=0,buf_len=1,cookie=0)
2379 <== (bufused=,errno=EINVAL)
2380 `,
2381 },
2382 {
2383 name: "cookie invalid when no prior state",
2384 fd: dirFD,
2385 buf: 0, bufLen: 1000,
2386 cookie: 1,
2387 resultBufused: 2000,
2388 expectedErrno: wasip1.ErrnoNoent,
2389 expectedLog: `
2390 ==> wasi_snapshot_preview1.fd_readdir(fd=5,buf=0,buf_len=1000,cookie=1)
2391 <== (bufused=,errno=ENOENT)
2392 `,
2393 },
2394 {
2395
2396
2397 name: "negative cookie invalid",
2398 fd: dirFD,
2399 buf: 0, bufLen: 1000,
2400 cookie: -1,
2401 resultBufused: 2000,
2402 expectedErrno: wasip1.ErrnoNoent,
2403 expectedLog: `
2404 ==> wasi_snapshot_preview1.fd_readdir(fd=5,buf=0,buf_len=1000,cookie=-1)
2405 <== (bufused=,errno=ENOENT)
2406 `,
2407 },
2408 }
2409
2410 for _, tt := range tests {
2411 tc := tt
2412 t.Run(tc.name, func(t *testing.T) {
2413 defer log.Reset()
2414
2415
2416 if tc.fd == dirFD {
2417 dirFD, errno = fsc.OpenFile(preopen, "dir", experimentalsys.O_RDONLY, 0)
2418 require.EqualErrno(t, 0, errno)
2419 defer fsc.CloseFile(dirFD)
2420 }
2421
2422 requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdReaddirName,
2423 uint64(tc.fd), uint64(tc.buf), uint64(tc.bufLen), uint64(tc.cookie), uint64(tc.resultBufused))
2424 require.Equal(t, tc.expectedLog, "\n"+log.String())
2425 })
2426 }
2427 }
2428
2429 func Test_fdRenumber(t *testing.T) {
2430 const fileFD, dirFD = 4, 5
2431
2432 tests := []struct {
2433 name string
2434 from, to int32
2435 expectedErrno wasip1.Errno
2436 expectedLog string
2437 }{
2438 {
2439 name: "from=preopen",
2440 from: sys.FdPreopen,
2441 to: dirFD,
2442 expectedErrno: wasip1.ErrnoNotsup,
2443 expectedLog: `
2444 ==> wasi_snapshot_preview1.fd_renumber(fd=3,to=5)
2445 <== errno=ENOTSUP
2446 `,
2447 },
2448 {
2449 name: "from=badf",
2450 from: -1,
2451 to: sys.FdPreopen,
2452 expectedErrno: wasip1.ErrnoBadf,
2453 expectedLog: `
2454 ==> wasi_snapshot_preview1.fd_renumber(fd=-1,to=3)
2455 <== errno=EBADF
2456 `,
2457 },
2458 {
2459 name: "to=badf",
2460 from: sys.FdPreopen,
2461 to: -1,
2462 expectedErrno: wasip1.ErrnoBadf,
2463 expectedLog: `
2464 ==> wasi_snapshot_preview1.fd_renumber(fd=3,to=-1)
2465 <== errno=EBADF
2466 `,
2467 },
2468 {
2469 name: "to=preopen",
2470 from: dirFD,
2471 to: sys.FdPreopen,
2472 expectedErrno: wasip1.ErrnoNotsup,
2473 expectedLog: `
2474 ==> wasi_snapshot_preview1.fd_renumber(fd=5,to=3)
2475 <== errno=ENOTSUP
2476 `,
2477 },
2478 {
2479 name: "file to dir",
2480 from: fileFD,
2481 to: dirFD,
2482 expectedErrno: wasip1.ErrnoSuccess,
2483 expectedLog: `
2484 ==> wasi_snapshot_preview1.fd_renumber(fd=4,to=5)
2485 <== errno=ESUCCESS
2486 `,
2487 },
2488 {
2489 name: "dir to file",
2490 from: dirFD,
2491 to: fileFD,
2492 expectedErrno: wasip1.ErrnoSuccess,
2493 expectedLog: `
2494 ==> wasi_snapshot_preview1.fd_renumber(fd=5,to=4)
2495 <== errno=ESUCCESS
2496 `,
2497 },
2498 {
2499 name: "dir to any",
2500 from: dirFD,
2501 to: 12345,
2502 expectedErrno: wasip1.ErrnoSuccess,
2503 expectedLog: `
2504 ==> wasi_snapshot_preview1.fd_renumber(fd=5,to=12345)
2505 <== errno=ESUCCESS
2506 `,
2507 },
2508 {
2509 name: "file to any",
2510 from: fileFD,
2511 to: 54,
2512 expectedErrno: wasip1.ErrnoSuccess,
2513 expectedLog: `
2514 ==> wasi_snapshot_preview1.fd_renumber(fd=4,to=54)
2515 <== errno=ESUCCESS
2516 `,
2517 },
2518 }
2519
2520 for _, tt := range tests {
2521 tc := tt
2522 t.Run(tc.name, func(t *testing.T) {
2523 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(fstest.FS))
2524 defer r.Close(testCtx)
2525
2526 fsc := mod.(*wasm.ModuleInstance).Sys.FS()
2527 preopen := fsc.RootFS()
2528
2529
2530 fileFDAssigned, errno := fsc.OpenFile(preopen, "animals.txt", experimentalsys.O_RDONLY, 0)
2531 require.EqualErrno(t, 0, errno)
2532 require.Equal(t, int32(fileFD), fileFDAssigned)
2533
2534 dirFDAssigned, errno := fsc.OpenFile(preopen, "dir", experimentalsys.O_RDONLY, 0)
2535 require.EqualErrno(t, 0, errno)
2536 require.Equal(t, int32(dirFD), dirFDAssigned)
2537
2538 requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdRenumberName, uint64(tc.from), uint64(tc.to))
2539 require.Equal(t, tc.expectedLog, "\n"+log.String())
2540 })
2541 }
2542 }
2543
2544 func Test_fdSeek(t *testing.T) {
2545 mod, fd, log, r := requireOpenFile(t, t.TempDir(), "test_path", []byte("wazero"), true)
2546 defer r.Close(testCtx)
2547
2548 resultNewoffset := uint32(1)
2549
2550 tests := []struct {
2551 name string
2552 offset int64
2553 whence int
2554 expectedOffset int64
2555 expectedMemory []byte
2556 expectedLog string
2557 }{
2558 {
2559 name: "SeekStart",
2560 offset: 4,
2561 whence: io.SeekStart,
2562 expectedOffset: 4,
2563 expectedMemory: []byte{
2564 '?',
2565 4, 0, 0, 0, 0, 0, 0, 0,
2566 '?',
2567 },
2568 expectedLog: `
2569 ==> wasi_snapshot_preview1.fd_seek(fd=4,offset=4,whence=0)
2570 <== (newoffset=4,errno=ESUCCESS)
2571 `,
2572 },
2573 {
2574 name: "SeekCurrent",
2575 offset: 1,
2576 whence: io.SeekCurrent,
2577 expectedOffset: 2,
2578 expectedMemory: []byte{
2579 '?',
2580 2, 0, 0, 0, 0, 0, 0, 0,
2581 '?',
2582 },
2583 expectedLog: `
2584 ==> wasi_snapshot_preview1.fd_seek(fd=4,offset=1,whence=1)
2585 <== (newoffset=2,errno=ESUCCESS)
2586 `,
2587 },
2588 {
2589 name: "SeekEnd",
2590 offset: -1,
2591 whence: io.SeekEnd,
2592 expectedOffset: 5,
2593 expectedMemory: []byte{
2594 '?',
2595 5, 0, 0, 0, 0, 0, 0, 0,
2596 '?',
2597 },
2598 expectedLog: `
2599 ==> wasi_snapshot_preview1.fd_seek(fd=4,offset=-1,whence=2)
2600 <== (newoffset=5,errno=ESUCCESS)
2601 `,
2602 },
2603 }
2604
2605 for _, tt := range tests {
2606 tc := tt
2607 t.Run(tc.name, func(t *testing.T) {
2608 defer log.Reset()
2609
2610 maskMemory(t, mod, len(tc.expectedMemory))
2611
2612
2613 fsc := mod.(*wasm.ModuleInstance).Sys.FS()
2614 f, ok := fsc.LookupFile(fd)
2615 require.True(t, ok)
2616
2617
2618 offset, errno := f.File.Seek(1, io.SeekStart)
2619 require.EqualErrno(t, 0, errno)
2620 require.Equal(t, int64(1), offset)
2621
2622 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdSeekName, uint64(fd), uint64(tc.offset), uint64(tc.whence), uint64(resultNewoffset))
2623 require.Equal(t, tc.expectedLog, "\n"+log.String())
2624
2625 actual, ok := mod.Memory().Read(0, uint32(len(tc.expectedMemory)))
2626 require.True(t, ok)
2627 require.Equal(t, tc.expectedMemory, actual)
2628
2629 offset, errno = f.File.Seek(0, io.SeekCurrent)
2630 require.EqualErrno(t, 0, errno)
2631 require.Equal(t, tc.expectedOffset, offset)
2632 })
2633 }
2634 }
2635
2636 func Test_fdSeek_Errors(t *testing.T) {
2637 mod, fileFD, log, r := requireOpenFile(t, t.TempDir(), "test_path", []byte("wazero"), false)
2638 defer r.Close(testCtx)
2639
2640 fsc := mod.(*wasm.ModuleInstance).Sys.FS()
2641 require.Zero(t, fsc.RootFS().Mkdir("dir", 0o0777))
2642 dirFD := requireOpenFD(t, mod, "dir")
2643
2644 memorySize := mod.Memory().Size()
2645
2646 tests := []struct {
2647 name string
2648 fd int32
2649 offset uint64
2650 whence, resultNewoffset uint32
2651 expectedErrno wasip1.Errno
2652 expectedLog string
2653 }{
2654 {
2655 name: "invalid FD",
2656 fd: 42,
2657 expectedErrno: wasip1.ErrnoBadf,
2658 expectedLog: `
2659 ==> wasi_snapshot_preview1.fd_seek(fd=42,offset=0,whence=0)
2660 <== (newoffset=,errno=EBADF)
2661 `,
2662 },
2663 {
2664 name: "invalid whence",
2665 fd: fileFD,
2666 whence: 3,
2667 expectedErrno: wasip1.ErrnoInval,
2668 expectedLog: `
2669 ==> wasi_snapshot_preview1.fd_seek(fd=4,offset=0,whence=3)
2670 <== (newoffset=,errno=EINVAL)
2671 `,
2672 },
2673 {
2674 name: "dir not file",
2675 fd: dirFD,
2676 expectedErrno: wasip1.ErrnoIsdir,
2677 expectedLog: `
2678 ==> wasi_snapshot_preview1.fd_seek(fd=5,offset=0,whence=0)
2679 <== (newoffset=,errno=EISDIR)
2680 `,
2681 },
2682 {
2683 name: "out-of-memory writing resultNewoffset",
2684 fd: fileFD,
2685 resultNewoffset: memorySize,
2686 expectedErrno: wasip1.ErrnoFault,
2687 expectedLog: `
2688 ==> wasi_snapshot_preview1.fd_seek(fd=4,offset=0,whence=0)
2689 <== (newoffset=,errno=EFAULT)
2690 `,
2691 },
2692 }
2693
2694 for _, tt := range tests {
2695 tc := tt
2696 t.Run(tc.name, func(t *testing.T) {
2697 defer log.Reset()
2698
2699 requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdSeekName, uint64(tc.fd), tc.offset, uint64(tc.whence), uint64(tc.resultNewoffset))
2700 require.Equal(t, tc.expectedLog, "\n"+log.String())
2701 })
2702 }
2703 }
2704
2705
2706 func Test_fdSync(t *testing.T) {
2707 tmpDir := t.TempDir()
2708 pathName := "test_path"
2709 mod, fd, log, r := requireOpenFile(t, tmpDir, pathName, []byte{}, false)
2710 defer r.Close(testCtx)
2711
2712 tests := []struct {
2713 name string
2714 fd int32
2715 expectedErrno wasip1.Errno
2716 expectedLog string
2717 }{
2718 {
2719 name: "invalid FD",
2720 fd: 42,
2721 expectedErrno: wasip1.ErrnoBadf,
2722 expectedLog: `
2723 ==> wasi_snapshot_preview1.fd_sync(fd=42)
2724 <== errno=EBADF
2725 `,
2726 },
2727 {
2728 name: "valid FD",
2729 fd: fd,
2730 expectedErrno: wasip1.ErrnoSuccess,
2731 expectedLog: `
2732 ==> wasi_snapshot_preview1.fd_sync(fd=4)
2733 <== errno=ESUCCESS
2734 `,
2735 },
2736 }
2737
2738 for _, tt := range tests {
2739 tc := tt
2740 t.Run(tc.name, func(t *testing.T) {
2741 defer log.Reset()
2742
2743 requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdSyncName, uint64(tc.fd))
2744 require.Equal(t, tc.expectedLog, "\n"+log.String())
2745 })
2746 }
2747 }
2748
2749 func Test_fdTell(t *testing.T) {
2750 mod, fd, log, r := requireOpenFile(t, t.TempDir(), "test_path", []byte("wazero"), true)
2751 defer r.Close(testCtx)
2752 defer log.Reset()
2753
2754 resultNewoffset := uint32(1)
2755
2756 expectedOffset := int64(1)
2757 expectedMemory := []byte{
2758 '?',
2759 1, 0, 0, 0, 0, 0, 0, 0,
2760 '?',
2761 }
2762 expectedLog := `
2763 ==> wasi_snapshot_preview1.fd_tell(fd=4,result.offset=1)
2764 <== errno=ESUCCESS
2765 `
2766
2767 maskMemory(t, mod, len(expectedMemory))
2768
2769
2770 fsc := mod.(*wasm.ModuleInstance).Sys.FS()
2771 f, ok := fsc.LookupFile(fd)
2772 require.True(t, ok)
2773
2774
2775 offset, errno := f.File.Seek(1, io.SeekStart)
2776 require.EqualErrno(t, 0, errno)
2777 require.Equal(t, int64(1), offset)
2778
2779 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdTellName, uint64(fd), uint64(resultNewoffset))
2780 require.Equal(t, expectedLog, "\n"+log.String())
2781
2782 actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
2783 require.True(t, ok)
2784 require.Equal(t, expectedMemory, actual)
2785
2786 offset, errno = f.File.Seek(0, io.SeekCurrent)
2787 require.EqualErrno(t, 0, errno)
2788 require.Equal(t, expectedOffset, offset)
2789 }
2790
2791 func Test_fdTell_Errors(t *testing.T) {
2792 mod, fd, log, r := requireOpenFile(t, t.TempDir(), "test_path", []byte("wazero"), true)
2793 defer r.Close(testCtx)
2794
2795 memorySize := mod.Memory().Size()
2796
2797 tests := []struct {
2798 name string
2799 fd int32
2800 resultNewoffset uint32
2801 expectedErrno wasip1.Errno
2802 expectedLog string
2803 }{
2804 {
2805 name: "invalid FD",
2806 fd: 42,
2807 expectedErrno: wasip1.ErrnoBadf,
2808 expectedLog: `
2809 ==> wasi_snapshot_preview1.fd_tell(fd=42,result.offset=0)
2810 <== errno=EBADF
2811 `,
2812 },
2813 {
2814 name: "out-of-memory writing resultNewoffset",
2815 fd: fd,
2816 resultNewoffset: memorySize,
2817 expectedErrno: wasip1.ErrnoFault,
2818 expectedLog: `
2819 ==> wasi_snapshot_preview1.fd_tell(fd=4,result.offset=65536)
2820 <== errno=EFAULT
2821 `,
2822 },
2823 }
2824
2825 for _, tt := range tests {
2826 tc := tt
2827 t.Run(tc.name, func(t *testing.T) {
2828 defer log.Reset()
2829
2830 requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdTellName, uint64(tc.fd), uint64(tc.resultNewoffset))
2831 require.Equal(t, tc.expectedLog, "\n"+log.String())
2832 })
2833 }
2834 }
2835
2836 func Test_fdWrite(t *testing.T) {
2837 tmpDir := t.TempDir()
2838 pathName := "test_path"
2839 mod, fd, log, r := requireOpenFile(t, tmpDir, pathName, []byte{}, false)
2840 defer r.Close(testCtx)
2841
2842 iovs := uint32(1)
2843 initialMemory := []byte{
2844 '?',
2845 18, 0, 0, 0,
2846 4, 0, 0, 0,
2847 23, 0, 0, 0,
2848 2, 0, 0, 0,
2849 '?',
2850 'w', 'a', 'z', 'e',
2851 '?',
2852 'r', 'o',
2853 '?',
2854 }
2855 iovsCount := uint32(2)
2856 resultNwritten := uint32(26)
2857 expectedMemory := append(
2858 initialMemory,
2859 6, 0, 0, 0,
2860 '?',
2861 )
2862
2863 maskMemory(t, mod, len(expectedMemory))
2864 ok := mod.Memory().Write(0, initialMemory)
2865 require.True(t, ok)
2866
2867 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdWriteName, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(resultNwritten))
2868 require.Equal(t, `
2869 ==> wasi_snapshot_preview1.fd_write(fd=4,iovs=1,iovs_len=2)
2870 <== (nwritten=6,errno=ESUCCESS)
2871 `, "\n"+log.String())
2872
2873 actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
2874 require.True(t, ok)
2875 require.Equal(t, expectedMemory, actual)
2876
2877
2878 buf, err := os.ReadFile(joinPath(tmpDir, pathName))
2879 require.NoError(t, err)
2880
2881 require.Equal(t, []byte("wazero"), buf)
2882 }
2883
2884 func Test_fdWrite_Errors(t *testing.T) {
2885 tmpDir := t.TempDir()
2886 pathName := "test_path"
2887 mod, fd, log, r := requireOpenFile(t, tmpDir, pathName, []byte{1, 2, 3, 4}, false)
2888 defer r.Close(testCtx)
2889
2890
2891 iovsCount := uint32(1)
2892 memSize := mod.Memory().Size()
2893
2894 tests := []struct {
2895 name string
2896 fd int32
2897 iovs, resultNwritten uint32
2898 expectedErrno wasip1.Errno
2899 expectedLog string
2900 }{
2901 {
2902 name: "invalid FD",
2903 fd: 42,
2904 expectedErrno: wasip1.ErrnoBadf,
2905 expectedLog: `
2906 ==> wasi_snapshot_preview1.fd_write(fd=42,iovs=0,iovs_len=1)
2907 <== (nwritten=,errno=EBADF)
2908 `,
2909 },
2910 {
2911 name: "not writable FD",
2912 fd: sys.FdStdin,
2913 expectedErrno: wasip1.ErrnoBadf,
2914 expectedLog: "\n",
2915 },
2916 {
2917 name: "out-of-memory reading iovs[0].offset",
2918 fd: fd,
2919 iovs: memSize - 2,
2920 expectedErrno: wasip1.ErrnoFault,
2921 expectedLog: `
2922 ==> wasi_snapshot_preview1.fd_write(fd=4,iovs=65534,iovs_len=1)
2923 <== (nwritten=,errno=EFAULT)
2924 `,
2925 },
2926 {
2927 name: "out-of-memory reading iovs[0].length",
2928 fd: fd,
2929 iovs: memSize - 4,
2930 expectedErrno: wasip1.ErrnoFault,
2931 expectedLog: `
2932 ==> wasi_snapshot_preview1.fd_write(fd=4,iovs=65532,iovs_len=1)
2933 <== (nwritten=,errno=EFAULT)
2934 `,
2935 },
2936 {
2937 name: "iovs[0].offset is outside memory",
2938 fd: fd,
2939 iovs: memSize - 5,
2940 expectedErrno: wasip1.ErrnoFault,
2941 expectedLog: `
2942 ==> wasi_snapshot_preview1.fd_write(fd=4,iovs=65531,iovs_len=1)
2943 <== (nwritten=,errno=EFAULT)
2944 `,
2945 },
2946 {
2947 name: "length to read exceeds memory by 1",
2948 fd: fd,
2949 iovs: memSize - 7,
2950 expectedErrno: wasip1.ErrnoFault,
2951 expectedLog: `
2952 ==> wasi_snapshot_preview1.fd_write(fd=4,iovs=65529,iovs_len=1)
2953 <== (nwritten=,errno=EFAULT)
2954 `,
2955 },
2956 {
2957 name: "resultNwritten offset is outside memory",
2958 fd: fd,
2959 resultNwritten: memSize,
2960 expectedErrno: wasip1.ErrnoFault,
2961 expectedLog: `
2962 ==> wasi_snapshot_preview1.fd_write(fd=4,iovs=0,iovs_len=1)
2963 <== (nwritten=,errno=EFAULT)
2964 `,
2965 },
2966 }
2967
2968 for _, tt := range tests {
2969 tc := tt
2970 t.Run(tc.name, func(t *testing.T) {
2971 defer log.Reset()
2972
2973 mod.Memory().Write(tc.iovs, append(
2974 u64.LeBytes(uint64(tc.iovs+8)),
2975
2976 2, 0, 0, 0,
2977 'h', 'i',
2978 ))
2979
2980 requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdWriteName, uint64(tc.fd), uint64(tc.iovs), uint64(iovsCount),
2981 uint64(tc.resultNwritten))
2982 require.Equal(t, tc.expectedLog, "\n"+log.String())
2983 })
2984 }
2985 }
2986
2987 func Test_pathCreateDirectory(t *testing.T) {
2988 tmpDir := t.TempDir()
2989 fsConfig := wazero.NewFSConfig().WithDirMount(tmpDir, "/")
2990 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(fsConfig))
2991 defer r.Close(testCtx)
2992
2993
2994 pathName := "wazero"
2995 realPath := joinPath(tmpDir, pathName)
2996 ok := mod.Memory().Write(0, append([]byte{'?'}, pathName...))
2997 require.True(t, ok)
2998
2999 fd := sys.FdPreopen
3000 name := 1
3001 nameLen := len(pathName)
3002
3003 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.PathCreateDirectoryName, uint64(fd), uint64(name), uint64(nameLen))
3004 require.Equal(t, `
3005 ==> wasi_snapshot_preview1.path_create_directory(fd=3,path=wazero)
3006 <== errno=ESUCCESS
3007 `, "\n"+log.String())
3008
3009
3010 stat, err := os.Stat(realPath)
3011 require.NoError(t, err)
3012 require.True(t, stat.IsDir())
3013 require.Equal(t, pathName, stat.Name())
3014 }
3015
3016 func Test_pathCreateDirectory_Errors(t *testing.T) {
3017 tmpDir := t.TempDir()
3018 fsConfig := wazero.NewFSConfig().WithDirMount(tmpDir, "/")
3019 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(fsConfig))
3020 defer r.Close(testCtx)
3021
3022 file := "file"
3023 err := os.WriteFile(joinPath(tmpDir, file), []byte{}, 0o700)
3024 require.NoError(t, err)
3025 fileFD := requireOpenFD(t, mod, file)
3026
3027 dir := "dir"
3028 err = os.Mkdir(joinPath(tmpDir, dir), 0o700)
3029 require.NoError(t, err)
3030
3031 tests := []struct {
3032 name, pathName string
3033 fd int32
3034 path, pathLen uint32
3035 expectedErrno wasip1.Errno
3036 expectedLog string
3037 }{
3038 {
3039 name: "unopened FD",
3040 fd: 42,
3041 expectedErrno: wasip1.ErrnoBadf,
3042 expectedLog: `
3043 ==> wasi_snapshot_preview1.path_create_directory(fd=42,path=)
3044 <== errno=EBADF
3045 `,
3046 },
3047 {
3048 name: "Fd not a directory",
3049 fd: fileFD,
3050 pathName: file,
3051 path: 0,
3052 pathLen: uint32(len(file)),
3053 expectedErrno: wasip1.ErrnoNotdir,
3054 expectedLog: `
3055 ==> wasi_snapshot_preview1.path_create_directory(fd=4,path=file)
3056 <== errno=ENOTDIR
3057 `,
3058 },
3059 {
3060 name: "out-of-memory reading path",
3061 fd: sys.FdPreopen,
3062 path: mod.Memory().Size(),
3063 pathLen: 1,
3064 expectedErrno: wasip1.ErrnoFault,
3065 expectedLog: `
3066 ==> wasi_snapshot_preview1.path_create_directory(fd=3,path=OOM(65536,1))
3067 <== errno=EFAULT
3068 `,
3069 },
3070 {
3071 name: "out-of-memory reading pathLen",
3072 fd: sys.FdPreopen,
3073 path: 0,
3074 pathLen: mod.Memory().Size() + 1,
3075 expectedErrno: wasip1.ErrnoFault,
3076 expectedLog: `
3077 ==> wasi_snapshot_preview1.path_create_directory(fd=3,path=OOM(0,65537))
3078 <== errno=EFAULT
3079 `,
3080 },
3081 {
3082 name: "file exists",
3083 fd: sys.FdPreopen,
3084 pathName: file,
3085 path: 0,
3086 pathLen: uint32(len(file)),
3087 expectedErrno: wasip1.ErrnoExist,
3088 expectedLog: `
3089 ==> wasi_snapshot_preview1.path_create_directory(fd=3,path=file)
3090 <== errno=EEXIST
3091 `,
3092 },
3093 {
3094 name: "dir exists",
3095 fd: sys.FdPreopen,
3096 pathName: dir,
3097 path: 0,
3098 pathLen: uint32(len(dir)),
3099 expectedErrno: wasip1.ErrnoExist,
3100 expectedLog: `
3101 ==> wasi_snapshot_preview1.path_create_directory(fd=3,path=dir)
3102 <== errno=EEXIST
3103 `,
3104 },
3105 }
3106
3107 for _, tt := range tests {
3108 tc := tt
3109 t.Run(tc.name, func(t *testing.T) {
3110 defer log.Reset()
3111
3112 mod.Memory().Write(tc.path, []byte(tc.pathName))
3113
3114 requireErrnoResult(t, tc.expectedErrno, mod, wasip1.PathCreateDirectoryName, uint64(tc.fd), uint64(tc.path), uint64(tc.pathLen))
3115 require.Equal(t, tc.expectedLog, "\n"+log.String())
3116 })
3117 }
3118 }
3119
3120 func Test_pathFilestatGet(t *testing.T) {
3121 file, dir, fileInDir := "animals.txt", "sub", "sub/test.txt"
3122
3123 initialMemoryFile := append([]byte{'?'}, file...)
3124 initialMemoryDir := append([]byte{'?'}, dir...)
3125 initialMemoryFileInDir := append([]byte{'?'}, fileInDir...)
3126 initialMemoryNotExists := []byte{'?', '?'}
3127
3128 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(fstest.FS))
3129 defer r.Close(testCtx)
3130 memorySize := mod.Memory().Size()
3131
3132 fileFD := requireOpenFD(t, mod, file)
3133
3134 tests := []struct {
3135 name string
3136 fd int32
3137 pathLen, resultFilestat uint32
3138 flags uint16
3139 memory, expectedMemory []byte
3140 expectedErrno wasip1.Errno
3141 expectedLog string
3142 }{
3143 {
3144 name: "file under root",
3145 fd: sys.FdPreopen,
3146 memory: initialMemoryFile,
3147 pathLen: uint32(len(file)),
3148 resultFilestat: uint32(len(file)) + 1,
3149 expectedMemory: append(
3150 initialMemoryFile,
3151 0, 0, 0, 0, 0, 0, 0, 0,
3152 0, 0, 0, 0, 0, 0, 0, 0,
3153 4, 0, 0, 0, 0, 0, 0, 0,
3154 1, 0, 0, 0, 0, 0, 0, 0,
3155 30, 0, 0, 0, 0, 0, 0, 0,
3156 0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17,
3157 0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17,
3158 0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17,
3159 ),
3160 expectedLog: `
3161 ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=,path=animals.txt)
3162 <== (filestat={filetype=REGULAR_FILE,size=30,mtim=1667482413000000000},errno=ESUCCESS)
3163 `,
3164 },
3165 {
3166 name: "file under dir",
3167 fd: sys.FdPreopen,
3168 memory: initialMemoryFileInDir,
3169 pathLen: uint32(len(fileInDir)),
3170 resultFilestat: uint32(len(fileInDir)) + 1,
3171 expectedMemory: append(
3172 initialMemoryFileInDir,
3173 0, 0, 0, 0, 0, 0, 0, 0,
3174 0, 0, 0, 0, 0, 0, 0, 0,
3175 4, 0, 0, 0, 0, 0, 0, 0,
3176 1, 0, 0, 0, 0, 0, 0, 0,
3177 14, 0, 0, 0, 0, 0, 0, 0,
3178 0x0, 0x0, 0xc2, 0xd3, 0x43, 0x6, 0x36, 0x17,
3179 0x0, 0x0, 0xc2, 0xd3, 0x43, 0x6, 0x36, 0x17,
3180 0x0, 0x0, 0xc2, 0xd3, 0x43, 0x6, 0x36, 0x17,
3181 ),
3182 expectedLog: `
3183 ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=,path=sub/test.txt)
3184 <== (filestat={filetype=REGULAR_FILE,size=14,mtim=1672531200000000000},errno=ESUCCESS)
3185 `,
3186 },
3187 {
3188 name: "dir under root",
3189 fd: sys.FdPreopen,
3190 memory: initialMemoryDir,
3191 pathLen: uint32(len(dir)),
3192 resultFilestat: uint32(len(dir)) + 1,
3193 expectedMemory: append(
3194 initialMemoryDir,
3195 0, 0, 0, 0, 0, 0, 0, 0,
3196 0, 0, 0, 0, 0, 0, 0, 0,
3197 3, 0, 0, 0, 0, 0, 0, 0,
3198 1, 0, 0, 0, 0, 0, 0, 0,
3199 0, 0, 0, 0, 0, 0, 0, 0,
3200 0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16,
3201 0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16,
3202 0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16,
3203 ),
3204 expectedLog: `
3205 ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=,path=sub)
3206 <== (filestat={filetype=DIRECTORY,size=0,mtim=1640995200000000000},errno=ESUCCESS)
3207 `,
3208 },
3209 {
3210 name: "unopened FD",
3211 fd: -1,
3212 expectedErrno: wasip1.ErrnoBadf,
3213 expectedLog: `
3214 ==> wasi_snapshot_preview1.path_filestat_get(fd=-1,flags=,path=)
3215 <== (filestat=,errno=EBADF)
3216 `,
3217 },
3218 {
3219 name: "Fd not a directory",
3220 fd: fileFD,
3221 memory: initialMemoryFile,
3222 pathLen: uint32(len(file)),
3223 resultFilestat: 2,
3224 expectedErrno: wasip1.ErrnoNotdir,
3225 expectedLog: `
3226 ==> wasi_snapshot_preview1.path_filestat_get(fd=4,flags=,path=animals.txt)
3227 <== (filestat=,errno=ENOTDIR)
3228 `,
3229 },
3230 {
3231 name: "path under root doesn't exist",
3232 fd: sys.FdPreopen,
3233 memory: initialMemoryNotExists,
3234 pathLen: 1,
3235 resultFilestat: 2,
3236 expectedErrno: wasip1.ErrnoNoent,
3237 expectedLog: `
3238 ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=,path=?)
3239 <== (filestat=,errno=ENOENT)
3240 `,
3241 },
3242 {
3243 name: "path is out of memory",
3244 fd: sys.FdPreopen,
3245 memory: initialMemoryFile,
3246 pathLen: memorySize,
3247 expectedErrno: wasip1.ErrnoFault,
3248 expectedLog: `
3249 ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=,path=OOM(1,65536))
3250 <== (filestat=,errno=EFAULT)
3251 `,
3252 },
3253 {
3254 name: "resultFilestat exceeds the maximum valid address by 1",
3255 fd: sys.FdPreopen,
3256 memory: initialMemoryFile,
3257 pathLen: uint32(len(file)),
3258 resultFilestat: memorySize - 64 + 1,
3259 expectedErrno: wasip1.ErrnoFault,
3260 expectedLog: `
3261 ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=,path=animals.txt)
3262 <== (filestat=,errno=EFAULT)
3263 `,
3264 },
3265 {
3266 name: "file under root (follow symlinks)",
3267 fd: sys.FdPreopen,
3268 flags: wasip1.LOOKUP_SYMLINK_FOLLOW,
3269 memory: initialMemoryFile,
3270 pathLen: uint32(len(file)),
3271 resultFilestat: uint32(len(file)) + 1,
3272 expectedMemory: append(
3273 initialMemoryFile,
3274 0, 0, 0, 0, 0, 0, 0, 0,
3275 0, 0, 0, 0, 0, 0, 0, 0,
3276 4, 0, 0, 0, 0, 0, 0, 0,
3277 1, 0, 0, 0, 0, 0, 0, 0,
3278 30, 0, 0, 0, 0, 0, 0, 0,
3279 0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17,
3280 0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17,
3281 0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17,
3282 ),
3283 expectedLog: `
3284 ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=SYMLINK_FOLLOW,path=animals.txt)
3285 <== (filestat={filetype=REGULAR_FILE,size=30,mtim=1667482413000000000},errno=ESUCCESS)
3286 `,
3287 },
3288 {
3289 name: "file under dir (follow symlinks)",
3290 fd: sys.FdPreopen,
3291 flags: wasip1.LOOKUP_SYMLINK_FOLLOW,
3292 memory: initialMemoryFileInDir,
3293 pathLen: uint32(len(fileInDir)),
3294 resultFilestat: uint32(len(fileInDir)) + 1,
3295 expectedMemory: append(
3296 initialMemoryFileInDir,
3297 0, 0, 0, 0, 0, 0, 0, 0,
3298 0, 0, 0, 0, 0, 0, 0, 0,
3299 4, 0, 0, 0, 0, 0, 0, 0,
3300 1, 0, 0, 0, 0, 0, 0, 0,
3301 14, 0, 0, 0, 0, 0, 0, 0,
3302 0x0, 0x0, 0xc2, 0xd3, 0x43, 0x6, 0x36, 0x17,
3303 0x0, 0x0, 0xc2, 0xd3, 0x43, 0x6, 0x36, 0x17,
3304 0x0, 0x0, 0xc2, 0xd3, 0x43, 0x6, 0x36, 0x17,
3305 ),
3306 expectedLog: `
3307 ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=SYMLINK_FOLLOW,path=sub/test.txt)
3308 <== (filestat={filetype=REGULAR_FILE,size=14,mtim=1672531200000000000},errno=ESUCCESS)
3309 `,
3310 },
3311 {
3312 name: "dir under root (follow symlinks)",
3313 fd: sys.FdPreopen,
3314 flags: wasip1.LOOKUP_SYMLINK_FOLLOW,
3315 memory: initialMemoryDir,
3316 pathLen: uint32(len(dir)),
3317 resultFilestat: uint32(len(dir)) + 1,
3318 expectedMemory: append(
3319 initialMemoryDir,
3320 0, 0, 0, 0, 0, 0, 0, 0,
3321 0, 0, 0, 0, 0, 0, 0, 0,
3322 3, 0, 0, 0, 0, 0, 0, 0,
3323 1, 0, 0, 0, 0, 0, 0, 0,
3324 0, 0, 0, 0, 0, 0, 0, 0,
3325 0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16,
3326 0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16,
3327 0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16,
3328 ),
3329 expectedLog: `
3330 ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=SYMLINK_FOLLOW,path=sub)
3331 <== (filestat={filetype=DIRECTORY,size=0,mtim=1640995200000000000},errno=ESUCCESS)
3332 `,
3333 },
3334 {
3335 name: "unopened FD (follow symlinks)",
3336 fd: -1,
3337 flags: wasip1.LOOKUP_SYMLINK_FOLLOW,
3338 expectedErrno: wasip1.ErrnoBadf,
3339 expectedLog: `
3340 ==> wasi_snapshot_preview1.path_filestat_get(fd=-1,flags=SYMLINK_FOLLOW,path=)
3341 <== (filestat=,errno=EBADF)
3342 `,
3343 },
3344 {
3345 name: "Fd not a directory (follow symlinks)",
3346 fd: fileFD,
3347 flags: wasip1.LOOKUP_SYMLINK_FOLLOW,
3348 memory: initialMemoryFile,
3349 pathLen: uint32(len(file)),
3350 resultFilestat: 2,
3351 expectedErrno: wasip1.ErrnoNotdir,
3352 expectedLog: `
3353 ==> wasi_snapshot_preview1.path_filestat_get(fd=4,flags=SYMLINK_FOLLOW,path=animals.txt)
3354 <== (filestat=,errno=ENOTDIR)
3355 `,
3356 },
3357 {
3358 name: "path under root doesn't exist (follow symlinks)",
3359 fd: sys.FdPreopen,
3360 flags: wasip1.LOOKUP_SYMLINK_FOLLOW,
3361 memory: initialMemoryNotExists,
3362 pathLen: 1,
3363 resultFilestat: 2,
3364 expectedErrno: wasip1.ErrnoNoent,
3365 expectedLog: `
3366 ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=SYMLINK_FOLLOW,path=?)
3367 <== (filestat=,errno=ENOENT)
3368 `,
3369 },
3370 {
3371 name: "path is out of memory (follow symlinks)",
3372 fd: sys.FdPreopen,
3373 flags: wasip1.LOOKUP_SYMLINK_FOLLOW,
3374 memory: initialMemoryFile,
3375 pathLen: memorySize,
3376 expectedErrno: wasip1.ErrnoFault,
3377 expectedLog: `
3378 ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=SYMLINK_FOLLOW,path=OOM(1,65536))
3379 <== (filestat=,errno=EFAULT)
3380 `,
3381 },
3382 {
3383 name: "resultFilestat exceeds the maximum valid address by 1 (follow symlinks)",
3384 fd: sys.FdPreopen,
3385 flags: wasip1.LOOKUP_SYMLINK_FOLLOW,
3386 memory: initialMemoryFile,
3387 pathLen: uint32(len(file)),
3388 resultFilestat: memorySize - 64 + 1,
3389 expectedErrno: wasip1.ErrnoFault,
3390 expectedLog: `
3391 ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=SYMLINK_FOLLOW,path=animals.txt)
3392 <== (filestat=,errno=EFAULT)
3393 `,
3394 },
3395 }
3396
3397 for _, tt := range tests {
3398 tc := tt
3399
3400 t.Run(tc.name, func(t *testing.T) {
3401 defer log.Reset()
3402
3403 maskMemory(t, mod, len(tc.expectedMemory))
3404 mod.Memory().Write(0, tc.memory)
3405
3406 requireErrnoResult(t, tc.expectedErrno, mod, wasip1.PathFilestatGetName, uint64(tc.fd), uint64(tc.flags), uint64(1), uint64(tc.pathLen), uint64(tc.resultFilestat))
3407 require.Equal(t, tc.expectedLog, "\n"+log.String())
3408
3409 actual, ok := mod.Memory().Read(0, uint32(len(tc.expectedMemory)))
3410 require.True(t, ok)
3411 require.Equal(t, tc.expectedMemory, actual)
3412 })
3413 }
3414 }
3415
3416 func Test_pathFilestatSetTimes(t *testing.T) {
3417 tmpDir := t.TempDir()
3418
3419 file := "file"
3420 writeFile(t, tmpDir, file, []byte("012"))
3421 link := file + "-link"
3422 require.NoError(t, os.Symlink(joinPath(tmpDir, file), joinPath(tmpDir, link)))
3423
3424 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().
3425 WithSysWalltime().
3426 WithFSConfig(wazero.NewFSConfig().WithDirMount(tmpDir, "")))
3427 defer r.Close(testCtx)
3428
3429 tests := []struct {
3430 name string
3431 flags uint16
3432 pathName string
3433 mtime, atime int64
3434 fstFlags uint16
3435 expectedLog string
3436 expectedErrno wasip1.Errno
3437 }{
3438 {
3439 name: "a=omit,m=omit",
3440 flags: wasip1.LOOKUP_SYMLINK_FOLLOW,
3441 atime: 123451,
3442 mtime: 1234,
3443 expectedLog: `
3444 ==> wasi_snapshot_preview1.path_filestat_set_times(fd=3,flags=SYMLINK_FOLLOW,path=file,atim=123451,mtim=1234,fst_flags=)
3445 <== errno=ESUCCESS
3446 `,
3447 },
3448 {
3449 name: "a=now,m=omit",
3450 flags: wasip1.LOOKUP_SYMLINK_FOLLOW,
3451 atime: 123451,
3452 mtime: 1234,
3453 fstFlags: wasip1.FstflagsAtimNow,
3454 expectedLog: `
3455 ==> wasi_snapshot_preview1.path_filestat_set_times(fd=3,flags=SYMLINK_FOLLOW,path=file,atim=123451,mtim=1234,fst_flags=ATIM_NOW)
3456 <== errno=ESUCCESS
3457 `,
3458 },
3459 {
3460 name: "a=omit,m=now",
3461 flags: wasip1.LOOKUP_SYMLINK_FOLLOW,
3462 atime: 123451,
3463 mtime: 1234,
3464 fstFlags: wasip1.FstflagsMtimNow,
3465 expectedLog: `
3466 ==> wasi_snapshot_preview1.path_filestat_set_times(fd=3,flags=SYMLINK_FOLLOW,path=file,atim=123451,mtim=1234,fst_flags=MTIM_NOW)
3467 <== errno=ESUCCESS
3468 `,
3469 },
3470 {
3471 name: "a=now,m=now",
3472 flags: wasip1.LOOKUP_SYMLINK_FOLLOW,
3473 atime: 123451,
3474 mtime: 1234,
3475 fstFlags: wasip1.FstflagsAtimNow | wasip1.FstflagsMtimNow,
3476 expectedLog: `
3477 ==> wasi_snapshot_preview1.path_filestat_set_times(fd=3,flags=SYMLINK_FOLLOW,path=file,atim=123451,mtim=1234,fst_flags=ATIM_NOW|MTIM_NOW)
3478 <== errno=ESUCCESS
3479 `,
3480 },
3481 {
3482 name: "a=now,m=set",
3483 flags: wasip1.LOOKUP_SYMLINK_FOLLOW,
3484 atime: 1234,
3485 mtime: 55555500,
3486 fstFlags: wasip1.FstflagsAtimNow | wasip1.FstflagsMtim,
3487 expectedLog: `
3488 ==> wasi_snapshot_preview1.path_filestat_set_times(fd=3,flags=SYMLINK_FOLLOW,path=file,atim=1234,mtim=55555500,fst_flags=ATIM_NOW|MTIM)
3489 <== errno=ESUCCESS
3490 `,
3491 },
3492 {
3493 name: "a=set,m=now",
3494 flags: wasip1.LOOKUP_SYMLINK_FOLLOW,
3495 atime: 55555500,
3496 mtime: 1234,
3497 fstFlags: wasip1.FstflagsAtim | wasip1.FstflagsMtimNow,
3498 expectedLog: `
3499 ==> wasi_snapshot_preview1.path_filestat_set_times(fd=3,flags=SYMLINK_FOLLOW,path=file,atim=55555500,mtim=1234,fst_flags=ATIM|MTIM_NOW)
3500 <== errno=ESUCCESS
3501 `,
3502 },
3503 {
3504 name: "a=set,m=omit",
3505 flags: wasip1.LOOKUP_SYMLINK_FOLLOW,
3506 atime: 55555500,
3507 mtime: 1234,
3508 fstFlags: wasip1.FstflagsAtim,
3509 expectedLog: `
3510 ==> wasi_snapshot_preview1.path_filestat_set_times(fd=3,flags=SYMLINK_FOLLOW,path=file,atim=55555500,mtim=1234,fst_flags=ATIM)
3511 <== errno=ESUCCESS
3512 `,
3513 },
3514 {
3515 name: "a=omit,m=set",
3516 flags: wasip1.LOOKUP_SYMLINK_FOLLOW,
3517 atime: 1234,
3518 mtime: 55555500,
3519 fstFlags: wasip1.FstflagsMtim,
3520 expectedLog: `
3521 ==> wasi_snapshot_preview1.path_filestat_set_times(fd=3,flags=SYMLINK_FOLLOW,path=file,atim=1234,mtim=55555500,fst_flags=MTIM)
3522 <== errno=ESUCCESS
3523 `,
3524 },
3525 {
3526 name: "a=set,m=set",
3527 flags: wasip1.LOOKUP_SYMLINK_FOLLOW,
3528 atime: 6666666600,
3529 mtime: 55555500,
3530 fstFlags: wasip1.FstflagsAtim | wasip1.FstflagsMtim,
3531 expectedLog: `
3532 ==> wasi_snapshot_preview1.path_filestat_set_times(fd=3,flags=SYMLINK_FOLLOW,path=file,atim=6666666600,mtim=55555500,fst_flags=ATIM|MTIM)
3533 <== errno=ESUCCESS
3534 `,
3535 },
3536 {
3537 name: "not found",
3538 pathName: "nope",
3539 flags: wasip1.LOOKUP_SYMLINK_FOLLOW,
3540 fstFlags: wasip1.FstflagsAtimNow,
3541 expectedLog: `
3542 ==> wasi_snapshot_preview1.path_filestat_set_times(fd=3,flags=SYMLINK_FOLLOW,path=nope,atim=0,mtim=0,fst_flags=ATIM_NOW)
3543 <== errno=ENOENT
3544 `,
3545 expectedErrno: wasip1.ErrnoNoent,
3546 },
3547 {
3548 name: "no_symlink_follow",
3549 pathName: link,
3550 flags: 0,
3551 atime: 123451,
3552 mtime: 1234,
3553 fstFlags: wasip1.FstflagsMtimNow,
3554 expectedLog: `
3555 ==> wasi_snapshot_preview1.path_filestat_set_times(fd=3,flags=,path=file-link,atim=123451,mtim=1234,fst_flags=MTIM_NOW)
3556 <== errno=ESUCCESS
3557 `,
3558 },
3559 }
3560
3561 if runtime.GOOS == "windows" && !platform.IsAtLeastGo120 {
3562
3563 tests = tests[:len(tests)-1]
3564 }
3565
3566 for _, tt := range tests {
3567 tc := tt
3568
3569 t.Run(tc.name, func(t *testing.T) {
3570 defer log.Reset()
3571
3572 pathName := tc.pathName
3573 if pathName == "" {
3574 pathName = file
3575 }
3576 mod.Memory().Write(0, []byte(pathName))
3577
3578 fd := sys.FdPreopen
3579 path := 0
3580 pathLen := uint32(len(pathName))
3581
3582 sys := mod.(*wasm.ModuleInstance).Sys
3583 fsc := sys.FS()
3584
3585 var oldSt sysapi.Stat_t
3586 var errno experimentalsys.Errno
3587 if tc.expectedErrno == wasip1.ErrnoSuccess {
3588 oldSt, errno = fsc.RootFS().Stat(pathName)
3589 require.EqualErrno(t, 0, errno)
3590 }
3591
3592 requireErrnoResult(t, tc.expectedErrno, mod, wasip1.PathFilestatSetTimesName, uint64(fd), uint64(tc.flags),
3593 uint64(path), uint64(pathLen), uint64(tc.atime), uint64(tc.mtime), uint64(tc.fstFlags))
3594 require.Equal(t, tc.expectedLog, "\n"+log.String())
3595
3596 if tc.expectedErrno != wasip1.ErrnoSuccess {
3597 return
3598 }
3599
3600 newSt, errno := fsc.RootFS().Stat(pathName)
3601 require.EqualErrno(t, 0, errno)
3602
3603 if platform.CompilerSupported() {
3604 if tc.fstFlags&wasip1.FstflagsAtim != 0 {
3605 require.Equal(t, tc.atime, newSt.Atim)
3606 } else if tc.fstFlags&wasip1.FstflagsAtimNow != 0 {
3607 now := time.Now().UnixNano()
3608 require.True(t, newSt.Atim <= now, "expected atim %d <= now %d", newSt.Atim, now)
3609 } else {
3610 require.Equal(t, oldSt.Atim, newSt.Atim)
3611 }
3612 }
3613
3614
3615 if tc.fstFlags&wasip1.FstflagsMtim != 0 {
3616 require.Equal(t, tc.mtime, newSt.Mtim)
3617 } else if tc.fstFlags&wasip1.FstflagsMtimNow != 0 {
3618 now := time.Now().UnixNano()
3619 require.True(t, newSt.Mtim <= now, "expected mtim %d <= now %d", newSt.Mtim, now)
3620 } else {
3621 require.Equal(t, oldSt.Mtim, newSt.Mtim)
3622 }
3623 })
3624 }
3625 }
3626
3627 func Test_pathLink(t *testing.T) {
3628 tmpDir := t.TempDir()
3629
3630 oldDirName := "my-old-dir"
3631 oldDirPath := joinPath(tmpDir, oldDirName)
3632 mod, oldFd, log, r := requireOpenFile(t, tmpDir, oldDirName, nil, false)
3633 defer r.Close(testCtx)
3634
3635 newDirName := "my-new-dir/sub"
3636 newDirPath := joinPath(tmpDir, newDirName)
3637 require.NoError(t, os.MkdirAll(joinPath(tmpDir, newDirName), 0o700))
3638 fsc := mod.(*wasm.ModuleInstance).Sys.FS()
3639 newFd, errno := fsc.OpenFile(fsc.RootFS(), newDirName, 0o600, 0)
3640 require.EqualErrno(t, 0, errno)
3641
3642 mem := mod.Memory()
3643
3644 fileName := "file"
3645 err := os.WriteFile(joinPath(oldDirPath, fileName), []byte{1, 2, 3, 4}, 0o700)
3646 require.NoError(t, err)
3647
3648 file := uint32(0xff)
3649 ok := mem.Write(file, []byte(fileName))
3650 require.True(t, ok)
3651
3652 notFoundFile := uint32(0xaa)
3653 notFoundFileName := "nope"
3654 ok = mem.Write(notFoundFile, []byte(notFoundFileName))
3655 require.True(t, ok)
3656
3657 destination := uint32(0xcc)
3658 destinationName := "hard-linked"
3659 ok = mem.Write(destination, []byte(destinationName))
3660 require.True(t, ok)
3661
3662 destinationRealPath := joinPath(newDirPath, destinationName)
3663
3664 t.Run("success", func(t *testing.T) {
3665 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.PathLinkName,
3666 uint64(oldFd), 0, uint64(file), uint64(len(fileName)),
3667 uint64(newFd), uint64(destination), uint64(len(destinationName)))
3668 require.Contains(t, log.String(), wasip1.ErrnoName(wasip1.ErrnoSuccess))
3669
3670 f := openFile(t, destinationRealPath, experimentalsys.O_RDONLY, 0)
3671 defer f.Close()
3672
3673 st, errno := f.Stat()
3674 require.EqualErrno(t, 0, errno)
3675 require.False(t, st.Mode&os.ModeSymlink == os.ModeSymlink)
3676 require.Equal(t, uint64(2), st.Nlink)
3677 })
3678
3679 t.Run("errors", func(t *testing.T) {
3680 for _, tc := range []struct {
3681 errno wasip1.Errno
3682 oldFd int32
3683 oldPath, oldPathLen uint32
3684 newFd int32
3685 newPath, newPathLen uint32
3686 }{
3687 {errno: wasip1.ErrnoBadf, oldFd: 1000},
3688 {errno: wasip1.ErrnoBadf, oldFd: oldFd, newFd: 1000},
3689 {errno: wasip1.ErrnoNotdir, oldFd: oldFd, newFd: 1},
3690 {errno: wasip1.ErrnoNotdir, oldFd: 1, newFd: 1},
3691 {errno: wasip1.ErrnoNotdir, oldFd: 1, newFd: newFd},
3692 {errno: wasip1.ErrnoFault, oldFd: oldFd, newFd: newFd, oldPathLen: math.MaxUint32},
3693 {errno: wasip1.ErrnoFault, oldFd: oldFd, newFd: newFd, newPathLen: math.MaxUint32},
3694 {
3695 errno: wasip1.ErrnoFault, oldFd: oldFd, newFd: newFd,
3696 oldPath: math.MaxUint32, oldPathLen: 100, newPathLen: 100,
3697 },
3698 {
3699 errno: wasip1.ErrnoFault, oldFd: oldFd, newFd: newFd,
3700 oldPath: 1, oldPathLen: 100, newPath: math.MaxUint32, newPathLen: 100,
3701 },
3702 } {
3703 name := wasip1.ErrnoName(tc.errno)
3704 t.Run(name, func(t *testing.T) {
3705 requireErrnoResult(t, tc.errno, mod, wasip1.PathLinkName,
3706 uint64(tc.oldFd), 0, uint64(tc.oldPath), uint64(tc.oldPathLen),
3707 uint64(tc.newFd), uint64(tc.newPath), uint64(tc.newPathLen))
3708 require.Contains(t, log.String(), name)
3709 })
3710 }
3711 })
3712 }
3713
3714 func Test_pathOpen(t *testing.T) {
3715 dir := t.TempDir()
3716 writeFS := sysfs.DirFS(dir)
3717 readFS := &sysfs.ReadFS{FS: writeFS}
3718
3719 fileName := "file"
3720 fileContents := []byte("012")
3721 writeFile(t, dir, fileName, fileContents)
3722
3723 appendName := "append"
3724 appendContents := []byte("345")
3725 writeFile(t, dir, appendName, appendContents)
3726
3727 truncName := "trunc"
3728 truncContents := []byte("678")
3729 writeFile(t, dir, truncName, truncContents)
3730
3731 dirName := "dir"
3732 mkdir(t, dir, dirName)
3733
3734 dirFileName := joinPath(dirName, fileName)
3735 dirFileContents := []byte("def")
3736 writeFile(t, dir, dirFileName, dirFileContents)
3737
3738 expectedOpenedFd := sys.FdPreopen + 1
3739
3740 tests := []struct {
3741 name string
3742 fs experimentalsys.FS
3743 path func(t *testing.T) string
3744 oflags uint16
3745 fdflags uint16
3746 rights uint32
3747 expected func(t *testing.T, fsc *sys.FSContext)
3748 expectedErrno wasip1.Errno
3749 expectedLog string
3750 }{
3751 {
3752 name: "sysfs.ReadFS",
3753 fs: readFS,
3754 path: func(*testing.T) string { return fileName },
3755 expected: func(t *testing.T, fsc *sys.FSContext) {
3756 requireContents(t, fsc, expectedOpenedFd, fileName, fileContents)
3757 },
3758 expectedLog: `
3759 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=file,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
3760 <== (opened_fd=4,errno=ESUCCESS)
3761 `,
3762 },
3763 {
3764 name: "sysfs.DirFS",
3765 fs: writeFS,
3766 path: func(*testing.T) string { return fileName },
3767 expected: func(t *testing.T, fsc *sys.FSContext) {
3768 requireContents(t, fsc, expectedOpenedFd, fileName, fileContents)
3769 },
3770 expectedLog: `
3771 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=file,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
3772 <== (opened_fd=4,errno=ESUCCESS)
3773 `,
3774 },
3775 {
3776 name: "sysfs.ReadFS FD_APPEND",
3777 fs: readFS,
3778 fdflags: wasip1.FD_APPEND,
3779 path: func(t *testing.T) (file string) { return appendName },
3780 expectedErrno: wasip1.ErrnoNosys,
3781 expectedLog: `
3782 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=append,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=APPEND)
3783 <== (opened_fd=,errno=ENOSYS)
3784 `,
3785 },
3786 {
3787 name: "sysfs.DirFS FD_APPEND",
3788 fs: writeFS,
3789 path: func(t *testing.T) (file string) { return appendName },
3790 fdflags: wasip1.FD_APPEND,
3791 expected: func(t *testing.T, fsc *sys.FSContext) {
3792 contents := writeAndCloseFile(t, fsc, expectedOpenedFd)
3793
3794
3795 b := readFile(t, dir, appendName)
3796 require.Equal(t, append(appendContents, contents...), b)
3797 },
3798 expectedLog: `
3799 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=append,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=APPEND)
3800 <== (opened_fd=4,errno=ESUCCESS)
3801 `,
3802 },
3803 {
3804 name: "sysfs.ReadFS O_CREAT",
3805 fs: readFS,
3806 oflags: wasip1.O_CREAT,
3807 expectedErrno: wasip1.ErrnoNosys,
3808 path: func(*testing.T) string { return "creat" },
3809 expectedLog: `
3810 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=creat,oflags=CREAT,fs_rights_base=,fs_rights_inheriting=,fdflags=)
3811 <== (opened_fd=,errno=ENOSYS)
3812 `,
3813 },
3814 {
3815 name: "sysfs.DirFS O_CREAT",
3816 fs: writeFS,
3817 path: func(t *testing.T) (file string) { return "creat" },
3818 oflags: wasip1.O_CREAT,
3819 expected: func(t *testing.T, fsc *sys.FSContext) {
3820
3821 contents := writeAndCloseFile(t, fsc, expectedOpenedFd)
3822
3823
3824 b := readFile(t, dir, "creat")
3825 require.Equal(t, contents, b)
3826 },
3827 expectedLog: `
3828 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=creat,oflags=CREAT,fs_rights_base=,fs_rights_inheriting=,fdflags=)
3829 <== (opened_fd=4,errno=ESUCCESS)
3830 `,
3831 },
3832 {
3833 name: "sysfs.ReadFS O_CREAT O_TRUNC",
3834 fs: readFS,
3835 oflags: wasip1.O_CREAT | wasip1.O_TRUNC,
3836 expectedErrno: wasip1.ErrnoNosys,
3837 path: func(t *testing.T) (file string) { return joinPath(dirName, "O_CREAT-O_TRUNC") },
3838 expectedLog: `
3839 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=dir/O_CREAT-O_TRUNC,oflags=CREAT|TRUNC,fs_rights_base=,fs_rights_inheriting=,fdflags=)
3840 <== (opened_fd=,errno=ENOSYS)
3841 `,
3842 },
3843 {
3844 name: "sysfs.DirFS O_CREAT O_TRUNC",
3845 fs: writeFS,
3846 path: func(t *testing.T) (file string) { return joinPath(dirName, "O_CREAT-O_TRUNC") },
3847 oflags: wasip1.O_CREAT | wasip1.O_TRUNC,
3848 expected: func(t *testing.T, fsc *sys.FSContext) {
3849
3850 contents := writeAndCloseFile(t, fsc, expectedOpenedFd)
3851
3852
3853 b := readFile(t, dir, joinPath(dirName, "O_CREAT-O_TRUNC"))
3854 require.Equal(t, contents, b)
3855 },
3856 expectedLog: `
3857 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=dir/O_CREAT-O_TRUNC,oflags=CREAT|TRUNC,fs_rights_base=,fs_rights_inheriting=,fdflags=)
3858 <== (opened_fd=4,errno=ESUCCESS)
3859 `,
3860 },
3861 {
3862 name: "sysfs.ReadFS O_DIRECTORY",
3863 fs: readFS,
3864 oflags: wasip1.O_DIRECTORY,
3865 path: func(*testing.T) string { return dirName },
3866 expected: func(t *testing.T, fsc *sys.FSContext) {
3867 f, ok := fsc.LookupFile(expectedOpenedFd)
3868 require.True(t, ok)
3869 isDir, errno := f.File.IsDir()
3870 require.EqualErrno(t, 0, errno)
3871 require.True(t, isDir)
3872 },
3873 expectedLog: `
3874 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=dir,oflags=DIRECTORY,fs_rights_base=,fs_rights_inheriting=,fdflags=)
3875 <== (opened_fd=4,errno=ESUCCESS)
3876 `,
3877 },
3878 {
3879 name: "sysfs.DirFS O_DIRECTORY",
3880 fs: writeFS,
3881 path: func(*testing.T) string { return dirName },
3882 oflags: wasip1.O_DIRECTORY,
3883 expected: func(t *testing.T, fsc *sys.FSContext) {
3884 f, ok := fsc.LookupFile(expectedOpenedFd)
3885 require.True(t, ok)
3886 isDir, errno := f.File.IsDir()
3887 require.EqualErrno(t, 0, errno)
3888 require.True(t, isDir)
3889 },
3890 expectedLog: `
3891 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=dir,oflags=DIRECTORY,fs_rights_base=,fs_rights_inheriting=,fdflags=)
3892 <== (opened_fd=4,errno=ESUCCESS)
3893 `,
3894 },
3895 {
3896 name: "sysfs.ReadFS O_TRUNC",
3897 fs: readFS,
3898 oflags: wasip1.O_TRUNC,
3899 expectedErrno: wasip1.ErrnoNosys,
3900 path: func(*testing.T) string { return "trunc" },
3901 expectedLog: `
3902 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=trunc,oflags=TRUNC,fs_rights_base=,fs_rights_inheriting=,fdflags=)
3903 <== (opened_fd=,errno=ENOSYS)
3904 `,
3905 },
3906 {
3907 name: "sysfs.DirFS O_TRUNC",
3908 fs: writeFS,
3909 path: func(t *testing.T) (file string) { return "trunc" },
3910 oflags: wasip1.O_TRUNC,
3911 expected: func(t *testing.T, fsc *sys.FSContext) {
3912 contents := writeAndCloseFile(t, fsc, expectedOpenedFd)
3913
3914
3915 b := readFile(t, dir, "trunc")
3916 require.Equal(t, contents, b)
3917 },
3918 expectedLog: `
3919 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=trunc,oflags=TRUNC,fs_rights_base=,fs_rights_inheriting=,fdflags=)
3920 <== (opened_fd=4,errno=ESUCCESS)
3921 `,
3922 },
3923 {
3924 name: "sysfs.DirFS RIGHT_FD_READ|RIGHT_FD_WRITE",
3925 fs: writeFS,
3926 path: func(*testing.T) string { return fileName },
3927 oflags: 0,
3928 rights: wasip1.RIGHT_FD_READ | wasip1.RIGHT_FD_WRITE,
3929 expected: func(t *testing.T, fsc *sys.FSContext) {
3930 requireContents(t, fsc, expectedOpenedFd, fileName, fileContents)
3931 },
3932 expectedLog: `
3933 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=file,oflags=,fs_rights_base=FD_READ|FD_WRITE,fs_rights_inheriting=,fdflags=)
3934 <== (opened_fd=4,errno=ESUCCESS)
3935 `,
3936 },
3937 }
3938
3939 for _, tt := range tests {
3940 tc := tt
3941
3942 t.Run(tc.name, func(t *testing.T) {
3943 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig())
3944 defer r.Close(testCtx)
3945
3946 mod.(*wasm.ModuleInstance).Sys = sys.DefaultContext(tc.fs)
3947
3948 pathName := tc.path(t)
3949 mod.Memory().Write(0, []byte(pathName))
3950
3951 path := uint32(0)
3952 pathLen := uint32(len(pathName))
3953 resultOpenedFd := pathLen
3954 fd := sys.FdPreopen
3955
3956
3957
3958 dirflags := 0
3959
3960
3961 fsRightsInheriting := uint64(0)
3962
3963 requireErrnoResult(t, tc.expectedErrno, mod, wasip1.PathOpenName, uint64(fd), uint64(dirflags), uint64(path),
3964 uint64(pathLen), uint64(tc.oflags), uint64(tc.rights), fsRightsInheriting, uint64(tc.fdflags), uint64(resultOpenedFd))
3965 require.Equal(t, tc.expectedLog, "\n"+log.String())
3966
3967 if tc.expectedErrno == wasip1.ErrnoSuccess {
3968 openedFd, ok := mod.Memory().ReadUint32Le(pathLen)
3969 require.True(t, ok)
3970 require.Equal(t, expectedOpenedFd, int32(openedFd))
3971
3972 tc.expected(t, mod.(*wasm.ModuleInstance).Sys.FS())
3973 }
3974 })
3975 }
3976 }
3977
3978 func writeAndCloseFile(t *testing.T, fsc *sys.FSContext, fd int32) []byte {
3979 contents := []byte("hello")
3980 f, ok := fsc.LookupFile(fd)
3981 require.True(t, ok)
3982 _, errno := f.File.Write([]byte("hello"))
3983 require.EqualErrno(t, 0, errno)
3984 require.EqualErrno(t, 0, fsc.CloseFile(fd))
3985 return contents
3986 }
3987
3988 func requireOpenFD(t *testing.T, mod api.Module, path string) int32 {
3989 fsc := mod.(*wasm.ModuleInstance).Sys.FS()
3990 preopen := fsc.RootFS()
3991
3992 fd, errno := fsc.OpenFile(preopen, path, experimentalsys.O_RDONLY, 0)
3993 require.EqualErrno(t, 0, errno)
3994 return fd
3995 }
3996
3997 func requireContents(t *testing.T, fsc *sys.FSContext, expectedOpenedFd int32, fileName string, fileContents []byte) {
3998
3999 f, ok := fsc.LookupFile(expectedOpenedFd)
4000 require.True(t, ok)
4001 require.Equal(t, fileName, f.Name)
4002
4003
4004 buf := readAll(t, f.File)
4005 require.Equal(t, fileContents, buf)
4006 }
4007
4008 func readAll(t *testing.T, f experimentalsys.File) []byte {
4009 st, errno := f.Stat()
4010 require.EqualErrno(t, 0, errno)
4011 buf := make([]byte, st.Size)
4012 _, errno = f.Read(buf)
4013 require.EqualErrno(t, 0, errno)
4014 return buf
4015 }
4016
4017 func mkdir(t *testing.T, tmpDir, dir string) {
4018 err := os.Mkdir(joinPath(tmpDir, dir), 0o700)
4019 require.NoError(t, err)
4020 }
4021
4022 func readFile(t *testing.T, tmpDir, file string) []byte {
4023 contents, err := os.ReadFile(joinPath(tmpDir, file))
4024 require.NoError(t, err)
4025 return contents
4026 }
4027
4028 func writeFile(t *testing.T, tmpDir, file string, contents []byte) {
4029 err := os.WriteFile(joinPath(tmpDir, file), contents, 0o600)
4030 require.NoError(t, err)
4031 }
4032
4033 func Test_pathOpen_Errors(t *testing.T) {
4034 tmpDir := t.TempDir()
4035 fsConfig := wazero.NewFSConfig().WithDirMount(tmpDir, "/")
4036 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(fsConfig))
4037 defer r.Close(testCtx)
4038
4039 file := "file"
4040 err := os.WriteFile(joinPath(tmpDir, file), []byte{}, 0o700)
4041 require.NoError(t, err)
4042 fileFD := requireOpenFD(t, mod, file)
4043
4044 dir := "dir"
4045 err = os.Mkdir(joinPath(tmpDir, dir), 0o700)
4046 require.NoError(t, err)
4047
4048 nested := "dir/nested"
4049 err = os.Mkdir(joinPath(tmpDir, nested), 0o700)
4050 require.NoError(t, err)
4051
4052 nestedFile := "dir/nested/file"
4053 err = os.WriteFile(joinPath(tmpDir, nestedFile), []byte{}, 0o700)
4054 require.NoError(t, err)
4055
4056 tests := []struct {
4057 name, pathName string
4058 fd int32
4059 path, pathLen, oflags, resultOpenedFd uint32
4060 expectedErrno wasip1.Errno
4061 expectedLog string
4062 }{
4063 {
4064 name: "unopened FD",
4065 fd: 42,
4066 expectedErrno: wasip1.ErrnoBadf,
4067 expectedLog: `
4068 ==> wasi_snapshot_preview1.path_open(fd=42,dirflags=,path=,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
4069 <== (opened_fd=,errno=EBADF)
4070 `,
4071 },
4072 {
4073 name: "Fd not a directory",
4074 fd: fileFD,
4075 pathName: file,
4076 path: 0,
4077 pathLen: uint32(len(file)),
4078 expectedErrno: wasip1.ErrnoNotdir,
4079 expectedLog: `
4080 ==> wasi_snapshot_preview1.path_open(fd=4,dirflags=,path=file,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
4081 <== (opened_fd=,errno=ENOTDIR)
4082 `,
4083 },
4084 {
4085 name: "out-of-memory reading path",
4086 fd: sys.FdPreopen,
4087 path: mod.Memory().Size(),
4088 pathLen: uint32(len(file)),
4089 expectedErrno: wasip1.ErrnoFault,
4090 expectedLog: `
4091 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=OOM(65536,4),oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
4092 <== (opened_fd=,errno=EFAULT)
4093 `,
4094 },
4095 {
4096 name: "out-of-memory reading pathLen",
4097 fd: sys.FdPreopen,
4098 path: 0,
4099 pathLen: mod.Memory().Size() + 1,
4100 expectedErrno: wasip1.ErrnoFault,
4101 expectedLog: `
4102 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=OOM(0,65537),oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
4103 <== (opened_fd=,errno=EFAULT)
4104 `,
4105 },
4106 {
4107 name: "no such file exists",
4108 fd: sys.FdPreopen,
4109 pathName: dir,
4110 path: 0,
4111 pathLen: uint32(len(dir)) - 1,
4112 expectedErrno: wasip1.ErrnoNoent,
4113 expectedLog: `
4114 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=di,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
4115 <== (opened_fd=,errno=ENOENT)
4116 `,
4117 },
4118 {
4119 name: "trailing slash on directory",
4120 fd: sys.FdPreopen,
4121 pathName: nested + "/",
4122 path: 0,
4123 pathLen: uint32(len(nested)) + 1,
4124 expectedErrno: wasip1.ErrnoSuccess,
4125 expectedLog: `
4126 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=dir/nested/,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
4127 <== (opened_fd=5,errno=ESUCCESS)
4128 `,
4129 },
4130 {
4131 name: "path under preopen",
4132 fd: sys.FdPreopen,
4133 pathName: "../" + file,
4134 path: 0,
4135 pathLen: uint32(len(file)) + 3,
4136 expectedErrno: wasip1.ErrnoPerm,
4137 expectedLog: `
4138 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=../file,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
4139 <== (opened_fd=,errno=EPERM)
4140 `,
4141 },
4142 {
4143 name: "rooted path",
4144 fd: sys.FdPreopen,
4145 pathName: "/" + file,
4146 path: 0,
4147 pathLen: uint32(len(file)) + 1,
4148 expectedErrno: wasip1.ErrnoPerm,
4149 expectedLog: `
4150 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=/file,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
4151 <== (opened_fd=,errno=EPERM)
4152 `,
4153 },
4154 {
4155 name: "trailing slash on file",
4156 fd: sys.FdPreopen,
4157 pathName: nestedFile + "/",
4158 path: 0,
4159 pathLen: uint32(len(nestedFile)) + 1,
4160 expectedErrno: wasip1.ErrnoNotdir,
4161 expectedLog: `
4162 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=dir/nested/file/,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
4163 <== (opened_fd=,errno=ENOTDIR)
4164 `,
4165 },
4166 {
4167 name: "out-of-memory writing resultOpenedFd",
4168 fd: sys.FdPreopen,
4169 pathName: dir,
4170 path: 0,
4171 pathLen: uint32(len(dir)),
4172 resultOpenedFd: mod.Memory().Size(),
4173 expectedErrno: wasip1.ErrnoFault,
4174 expectedLog: `
4175 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=dir,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
4176 <== (opened_fd=,errno=EFAULT)
4177 `,
4178 },
4179 {
4180 name: "O_DIRECTORY, but not a directory",
4181 oflags: uint32(wasip1.O_DIRECTORY),
4182 fd: sys.FdPreopen,
4183 pathName: file,
4184 path: 0,
4185 pathLen: uint32(len(file)),
4186 expectedErrno: wasip1.ErrnoNotdir,
4187 expectedLog: `
4188 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=file,oflags=DIRECTORY,fs_rights_base=,fs_rights_inheriting=,fdflags=)
4189 <== (opened_fd=,errno=ENOTDIR)
4190 `,
4191 },
4192 {
4193 name: "oflags=directory and create invalid",
4194 oflags: uint32(wasip1.O_DIRECTORY | wasip1.O_CREAT),
4195 fd: sys.FdPreopen,
4196 pathName: file,
4197 path: 0,
4198 pathLen: uint32(len(file)),
4199 expectedErrno: wasip1.ErrnoInval,
4200 expectedLog: `
4201 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=file,oflags=CREAT|DIRECTORY,fs_rights_base=,fs_rights_inheriting=,fdflags=)
4202 <== (opened_fd=,errno=EINVAL)
4203 `,
4204 },
4205 }
4206
4207 for _, tt := range tests {
4208 tc := tt
4209 t.Run(tc.name, func(t *testing.T) {
4210 defer log.Reset()
4211
4212 mod.Memory().Write(tc.path, []byte(tc.pathName))
4213
4214 requireErrnoResult(t, tc.expectedErrno, mod, wasip1.PathOpenName, uint64(tc.fd), uint64(0), uint64(tc.path),
4215 uint64(tc.pathLen), uint64(tc.oflags), 0, 0, 0, uint64(tc.resultOpenedFd))
4216 require.Equal(t, tc.expectedLog, "\n"+log.String())
4217 })
4218 }
4219 }
4220
4221 func Test_pathReadlink(t *testing.T) {
4222 tmpDir := t.TempDir()
4223
4224 dirName := "dir"
4225 dirPath := joinPath(tmpDir, dirName)
4226 mod, dirFD, log, r := requireOpenFile(t, tmpDir, dirName, nil, false)
4227 defer r.Close(testCtx)
4228
4229 subDirName := "sub-dir"
4230 require.NoError(t, os.Mkdir(joinPath(dirPath, subDirName), 0o700))
4231
4232 mem := mod.Memory()
4233
4234 originalFileName := "top-original-file"
4235 destinationPath := uint32(0x77)
4236 destinationPathName := "top-symlinked"
4237 ok := mem.Write(destinationPath, []byte(destinationPathName))
4238 require.True(t, ok)
4239
4240 originalSubDirFileName := joinPath(subDirName, "subdir-original-file")
4241 destinationSubDirFileName := joinPath(subDirName, "subdir-symlinked")
4242 destinationSubDirPathNamePtr := uint32(0xcc)
4243 ok = mem.Write(destinationSubDirPathNamePtr, []byte(destinationSubDirFileName))
4244 require.True(t, ok)
4245
4246
4247 originalRelativePath := joinPath(dirName, originalFileName)
4248 err := os.WriteFile(joinPath(tmpDir, originalRelativePath), []byte{4, 3, 2, 1}, 0o700)
4249 require.NoError(t, err)
4250 err = os.Symlink(originalRelativePath, joinPath(dirPath, destinationPathName))
4251 require.NoError(t, err)
4252 originalSubDirRelativePath := joinPath(dirName, originalSubDirFileName)
4253 err = os.WriteFile(joinPath(tmpDir, originalSubDirRelativePath), []byte{1, 2, 3, 4}, 0o700)
4254 require.NoError(t, err)
4255 err = os.Symlink(originalSubDirRelativePath, joinPath(dirPath, destinationSubDirFileName))
4256 require.NoError(t, err)
4257
4258 t.Run("ok", func(t *testing.T) {
4259 for _, tc := range []struct {
4260 name string
4261 path, pathLen uint32
4262 expectedBuf string
4263 }{
4264 {
4265 name: "top",
4266 path: destinationPath,
4267 pathLen: uint32(len(destinationPathName)),
4268 expectedBuf: originalRelativePath,
4269 },
4270 {
4271 name: "subdir",
4272 path: destinationSubDirPathNamePtr,
4273 pathLen: uint32(len(destinationSubDirFileName)),
4274 expectedBuf: originalSubDirRelativePath,
4275 },
4276 } {
4277 t.Run(tc.name, func(t *testing.T) {
4278 const buf, bufLen, resultBufused = 0x100, 0xff, 0x200
4279 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.PathReadlinkName,
4280 uint64(dirFD), uint64(tc.path), uint64(tc.pathLen),
4281 buf, bufLen, resultBufused)
4282 require.Contains(t, log.String(), wasip1.ErrnoName(wasip1.ErrnoSuccess))
4283
4284 size, ok := mem.ReadUint32Le(resultBufused)
4285 require.True(t, ok)
4286 actual, ok := mem.Read(buf, size)
4287 require.True(t, ok)
4288 require.Equal(t, tc.expectedBuf, string(actual))
4289 })
4290 }
4291 })
4292
4293 t.Run("errors", func(t *testing.T) {
4294 for _, tc := range []struct {
4295 name string
4296 fd int32
4297 path, pathLen, buf, bufLen, resultBufused uint32
4298 expectedErrno wasip1.Errno
4299 }{
4300 {expectedErrno: wasip1.ErrnoInval},
4301 {expectedErrno: wasip1.ErrnoInval, pathLen: 100},
4302 {expectedErrno: wasip1.ErrnoInval, bufLen: 100},
4303 {
4304 name: "bufLen too short",
4305 expectedErrno: wasip1.ErrnoRange,
4306 fd: dirFD,
4307 bufLen: 10,
4308 path: destinationPath,
4309 pathLen: uint32(len(destinationPathName)),
4310 buf: 0,
4311 },
4312 {
4313 name: "path past memory",
4314 expectedErrno: wasip1.ErrnoFault,
4315 bufLen: 100,
4316 pathLen: 100,
4317 buf: 50,
4318 path: math.MaxUint32,
4319 },
4320 {expectedErrno: wasip1.ErrnoNotdir, bufLen: 100, pathLen: 100, buf: 50, path: 50, fd: 1},
4321 {expectedErrno: wasip1.ErrnoBadf, bufLen: 100, pathLen: 100, buf: 50, path: 50, fd: 1000},
4322 {
4323 expectedErrno: wasip1.ErrnoNoent,
4324 bufLen: 100, buf: 50,
4325 path: destinationPath, pathLen: uint32(len(destinationPathName)) - 1,
4326 fd: dirFD,
4327 },
4328 } {
4329 name := tc.name
4330 if name == "" {
4331 name = wasip1.ErrnoName(tc.expectedErrno)
4332 }
4333 t.Run(name, func(t *testing.T) {
4334 requireErrnoResult(t, tc.expectedErrno, mod, wasip1.PathReadlinkName,
4335 uint64(tc.fd), uint64(tc.path), uint64(tc.pathLen), uint64(tc.buf),
4336 uint64(tc.bufLen), uint64(tc.resultBufused))
4337 require.Contains(t, log.String(), wasip1.ErrnoName(tc.expectedErrno))
4338 })
4339 }
4340 })
4341 }
4342
4343 func Test_pathRemoveDirectory(t *testing.T) {
4344 tmpDir := t.TempDir()
4345 fsConfig := wazero.NewFSConfig().WithDirMount(tmpDir, "/")
4346 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(fsConfig))
4347 defer r.Close(testCtx)
4348
4349
4350 pathName := "wazero"
4351 realPath := joinPath(tmpDir, pathName)
4352 ok := mod.Memory().Write(0, append([]byte{'?'}, pathName...))
4353 require.True(t, ok)
4354
4355
4356 err := os.Mkdir(realPath, 0o700)
4357 require.NoError(t, err)
4358
4359 fd := sys.FdPreopen
4360 name := 1
4361 nameLen := len(pathName)
4362
4363 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.PathRemoveDirectoryName, uint64(fd), uint64(name), uint64(nameLen))
4364 require.Equal(t, `
4365 ==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=wazero)
4366 <== errno=ESUCCESS
4367 `, "\n"+log.String())
4368
4369
4370 _, err = os.Stat(realPath)
4371 require.Error(t, err)
4372 }
4373
4374 func Test_pathRemoveDirectory_Errors(t *testing.T) {
4375 tmpDir := t.TempDir()
4376 fsConfig := wazero.NewFSConfig().WithDirMount(tmpDir, "/")
4377 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(fsConfig))
4378 defer r.Close(testCtx)
4379
4380 file := "file"
4381 err := os.WriteFile(joinPath(tmpDir, file), []byte{}, 0o700)
4382 require.NoError(t, err)
4383 fileFD := requireOpenFD(t, mod, file)
4384
4385 dirNotEmpty := "notempty"
4386 dirNotEmptyPath := joinPath(tmpDir, dirNotEmpty)
4387 err = os.Mkdir(dirNotEmptyPath, 0o700)
4388 require.NoError(t, err)
4389
4390 dir := "dir"
4391 err = os.Mkdir(joinPath(dirNotEmptyPath, dir), 0o700)
4392 require.NoError(t, err)
4393
4394 tests := []struct {
4395 name, pathName string
4396 fd int32
4397 path, pathLen uint32
4398 expectedErrno wasip1.Errno
4399 expectedLog string
4400 }{
4401 {
4402 name: "unopened FD",
4403 fd: 42,
4404 expectedErrno: wasip1.ErrnoBadf,
4405 expectedLog: `
4406 ==> wasi_snapshot_preview1.path_remove_directory(fd=42,path=)
4407 <== errno=EBADF
4408 `,
4409 },
4410 {
4411 name: "Fd not a directory",
4412 fd: fileFD,
4413 pathName: file,
4414 path: 0,
4415 pathLen: uint32(len(file)),
4416 expectedErrno: wasip1.ErrnoNotdir,
4417 expectedLog: `
4418 ==> wasi_snapshot_preview1.path_remove_directory(fd=4,path=file)
4419 <== errno=ENOTDIR
4420 `,
4421 },
4422 {
4423 name: "out-of-memory reading path",
4424 fd: sys.FdPreopen,
4425 path: mod.Memory().Size(),
4426 pathLen: 1,
4427 expectedErrno: wasip1.ErrnoFault,
4428 expectedLog: `
4429 ==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=OOM(65536,1))
4430 <== errno=EFAULT
4431 `,
4432 },
4433 {
4434 name: "out-of-memory reading pathLen",
4435 fd: sys.FdPreopen,
4436 path: 0,
4437 pathLen: mod.Memory().Size() + 1,
4438 expectedErrno: wasip1.ErrnoFault,
4439 expectedLog: `
4440 ==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=OOM(0,65537))
4441 <== errno=EFAULT
4442 `,
4443 },
4444 {
4445 name: "no such file exists",
4446 fd: sys.FdPreopen,
4447 pathName: file,
4448 path: 0,
4449 pathLen: uint32(len(file) - 1),
4450 expectedErrno: wasip1.ErrnoNoent,
4451 expectedLog: `
4452 ==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=fil)
4453 <== errno=ENOENT
4454 `,
4455 },
4456 {
4457 name: "file not dir",
4458 fd: sys.FdPreopen,
4459 pathName: file,
4460 path: 0,
4461 pathLen: uint32(len(file)),
4462 expectedErrno: wasip1.ErrnoNotdir,
4463 expectedLog: fmt.Sprintf(`
4464 ==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=file)
4465 <== errno=%s
4466 `, wasip1.ErrnoName(wasip1.ErrnoNotdir)),
4467 },
4468 {
4469 name: "dir not empty",
4470 fd: sys.FdPreopen,
4471 pathName: dirNotEmpty,
4472 path: 0,
4473 pathLen: uint32(len(dirNotEmpty)),
4474 expectedErrno: wasip1.ErrnoNotempty,
4475 expectedLog: `
4476 ==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=notempty)
4477 <== errno=ENOTEMPTY
4478 `,
4479 },
4480 }
4481
4482 for _, tt := range tests {
4483 tc := tt
4484 t.Run(tc.name, func(t *testing.T) {
4485 defer log.Reset()
4486
4487 mod.Memory().Write(tc.path, []byte(tc.pathName))
4488
4489 requireErrnoResult(t, tc.expectedErrno, mod, wasip1.PathRemoveDirectoryName, uint64(tc.fd), uint64(tc.path), uint64(tc.pathLen))
4490 require.Equal(t, tc.expectedLog, "\n"+log.String())
4491 })
4492 }
4493 }
4494
4495 func Test_pathSymlink_errors(t *testing.T) {
4496 tmpDir := t.TempDir()
4497
4498 dirName := "dir"
4499 dirPath := joinPath(tmpDir, dirName)
4500 mod, fd, log, r := requireOpenFile(t, tmpDir, dirName, nil, false)
4501 defer r.Close(testCtx)
4502
4503 mem := mod.Memory()
4504
4505 fileName := "file"
4506 err := os.WriteFile(joinPath(dirPath, fileName), []byte{1, 2, 3, 4}, 0o700)
4507 require.NoError(t, err)
4508
4509 file := uint32(0xff)
4510 ok := mem.Write(file, []byte(fileName))
4511 require.True(t, ok)
4512
4513 notFoundFile := uint32(0xaa)
4514 notFoundFileName := "nope"
4515 ok = mem.Write(notFoundFile, []byte(notFoundFileName))
4516 require.True(t, ok)
4517
4518 link := uint32(0xcc)
4519 linkName := fileName + "-link"
4520 ok = mem.Write(link, []byte(linkName))
4521 require.True(t, ok)
4522
4523 t.Run("success", func(t *testing.T) {
4524 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.PathSymlinkName,
4525 uint64(file), uint64(len(fileName)), uint64(fd), uint64(link), uint64(len(linkName)))
4526 require.Contains(t, log.String(), wasip1.ErrnoName(wasip1.ErrnoSuccess))
4527 st, err := os.Lstat(joinPath(dirPath, linkName))
4528 require.NoError(t, err)
4529 require.Equal(t, st.Mode()&os.ModeSymlink, os.ModeSymlink)
4530 })
4531
4532 t.Run("errors", func(t *testing.T) {
4533 for _, tc := range []struct {
4534 errno wasip1.Errno
4535 oldPath, oldPathLen uint32
4536 fd int32
4537 newPath, newPathLen uint32
4538 }{
4539 {errno: wasip1.ErrnoBadf, fd: 1000},
4540 {errno: wasip1.ErrnoNotdir, fd: 2},
4541
4542 {errno: wasip1.ErrnoInval, fd: fd},
4543 {errno: wasip1.ErrnoInval, oldPathLen: 100, fd: fd},
4544 {errno: wasip1.ErrnoInval, newPathLen: 100, fd: fd},
4545
4546 {errno: wasip1.ErrnoFault, oldPath: math.MaxUint32, oldPathLen: 100, newPathLen: 100, fd: fd},
4547 {errno: wasip1.ErrnoFault, newPath: math.MaxUint32, oldPathLen: 100, newPathLen: 100, fd: fd},
4548 {errno: wasip1.ErrnoFault, oldPath: math.MaxUint32, newPath: math.MaxUint32, oldPathLen: 100, newPathLen: 100, fd: fd},
4549
4550 {
4551 errno: wasip1.ErrnoInval, oldPath: notFoundFile, oldPathLen: uint32(len(notFoundFileName)),
4552 newPath: 0, newPathLen: 5, fd: fd,
4553 },
4554
4555 {
4556 errno: wasip1.ErrnoExist, oldPath: file, oldPathLen: uint32(len(fileName)),
4557 newPath: file, newPathLen: uint32(len(fileName)), fd: fd,
4558 },
4559 } {
4560 name := wasip1.ErrnoName(tc.errno)
4561 t.Run(name, func(t *testing.T) {
4562 requireErrnoResult(t, tc.errno, mod, wasip1.PathSymlinkName,
4563 uint64(tc.oldPath), uint64(tc.oldPathLen), uint64(tc.fd), uint64(tc.newPath), uint64(tc.newPathLen))
4564 require.Contains(t, log.String(), name)
4565 })
4566 }
4567 })
4568 }
4569
4570 func Test_pathRename(t *testing.T) {
4571 tmpDir := t.TempDir()
4572 fsConfig := wazero.NewFSConfig().WithDirMount(tmpDir, "/")
4573 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(fsConfig))
4574 defer r.Close(testCtx)
4575
4576
4577 oldfd := sys.FdPreopen
4578 oldPathName := "wazero"
4579 realOldPath := joinPath(tmpDir, oldPathName)
4580 oldPath := uint32(0)
4581 oldPathLen := len(oldPathName)
4582 ok := mod.Memory().Write(oldPath, []byte(oldPathName))
4583 require.True(t, ok)
4584
4585
4586 err := os.WriteFile(realOldPath, []byte{}, 0o600)
4587 require.NoError(t, err)
4588
4589 newfd := sys.FdPreopen
4590 newPathName := "wahzero"
4591 realNewPath := joinPath(tmpDir, newPathName)
4592 newPath := uint32(16)
4593 newPathLen := len(newPathName)
4594 ok = mod.Memory().Write(newPath, []byte(newPathName))
4595 require.True(t, ok)
4596
4597 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.PathRenameName,
4598 uint64(oldfd), uint64(oldPath), uint64(oldPathLen),
4599 uint64(newfd), uint64(newPath), uint64(newPathLen))
4600 require.Equal(t, `
4601 ==> wasi_snapshot_preview1.path_rename(fd=3,old_path=wazero,new_fd=3,new_path=wahzero)
4602 <== errno=ESUCCESS
4603 `, "\n"+log.String())
4604
4605
4606 _, err = os.Stat(realOldPath)
4607 require.Error(t, err)
4608 _, err = os.Stat(realNewPath)
4609 require.NoError(t, err)
4610 }
4611
4612 func Test_pathRename_Errors(t *testing.T) {
4613 tmpDir := t.TempDir()
4614 fsConfig := wazero.NewFSConfig().WithDirMount(tmpDir, "/")
4615 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(fsConfig))
4616 defer r.Close(testCtx)
4617
4618 file := "file"
4619 err := os.WriteFile(joinPath(tmpDir, file), []byte{}, 0o700)
4620 require.NoError(t, err)
4621
4622
4623
4624
4625 file1 := "file1"
4626 err = os.WriteFile(joinPath(tmpDir, file1), []byte{}, 0o700)
4627 require.NoError(t, err)
4628 fileFD := requireOpenFD(t, mod, file1)
4629
4630 dirNotEmpty := "notempty"
4631 err = os.Mkdir(joinPath(tmpDir, dirNotEmpty), 0o700)
4632 require.NoError(t, err)
4633
4634 dir := joinPath(dirNotEmpty, "dir")
4635 err = os.Mkdir(joinPath(tmpDir, dir), 0o700)
4636 require.NoError(t, err)
4637
4638 tests := []struct {
4639 name, oldPathName, newPathName string
4640 oldFd int32
4641 oldPath, oldPathLen uint32
4642 newFd int32
4643 newPath, newPathLen uint32
4644 expectedErrno wasip1.Errno
4645 expectedLog string
4646 }{
4647 {
4648 name: "unopened old FD",
4649 oldFd: 42,
4650 newFd: sys.FdPreopen,
4651 expectedErrno: wasip1.ErrnoBadf,
4652 expectedLog: `
4653 ==> wasi_snapshot_preview1.path_rename(fd=42,old_path=,new_fd=3,new_path=)
4654 <== errno=EBADF
4655 `,
4656 },
4657 {
4658 name: "old FD not a directory",
4659 oldFd: fileFD,
4660 newFd: sys.FdPreopen,
4661 expectedErrno: wasip1.ErrnoNotdir,
4662 expectedLog: `
4663 ==> wasi_snapshot_preview1.path_rename(fd=4,old_path=,new_fd=3,new_path=)
4664 <== errno=ENOTDIR
4665 `,
4666 },
4667 {
4668 name: "unopened new FD",
4669 oldFd: sys.FdPreopen,
4670 newFd: 42,
4671 expectedErrno: wasip1.ErrnoBadf,
4672 expectedLog: `
4673 ==> wasi_snapshot_preview1.path_rename(fd=3,old_path=,new_fd=42,new_path=)
4674 <== errno=EBADF
4675 `,
4676 },
4677 {
4678 name: "new FD not a directory",
4679 oldFd: sys.FdPreopen,
4680 newFd: fileFD,
4681 expectedErrno: wasip1.ErrnoNotdir,
4682 expectedLog: `
4683 ==> wasi_snapshot_preview1.path_rename(fd=3,old_path=,new_fd=4,new_path=)
4684 <== errno=ENOTDIR
4685 `,
4686 },
4687 {
4688 name: "out-of-memory reading old path",
4689 oldFd: sys.FdPreopen,
4690 newFd: sys.FdPreopen,
4691 oldPath: mod.Memory().Size(),
4692 oldPathLen: 1,
4693 expectedErrno: wasip1.ErrnoFault,
4694 expectedLog: `
4695 ==> wasi_snapshot_preview1.path_rename(fd=3,old_path=OOM(65536,1),new_fd=3,new_path=)
4696 <== errno=EFAULT
4697 `,
4698 },
4699 {
4700 name: "out-of-memory reading new path",
4701 oldFd: sys.FdPreopen,
4702 newFd: sys.FdPreopen,
4703 oldPath: 0,
4704 oldPathName: "a",
4705 oldPathLen: 1,
4706 newPath: mod.Memory().Size(),
4707 newPathLen: 1,
4708 expectedErrno: wasip1.ErrnoFault,
4709 expectedLog: `
4710 ==> wasi_snapshot_preview1.path_rename(fd=3,old_path=a,new_fd=3,new_path=OOM(65536,1))
4711 <== errno=EFAULT
4712 `,
4713 },
4714 {
4715 name: "out-of-memory reading old pathLen",
4716 oldFd: sys.FdPreopen,
4717 newFd: sys.FdPreopen,
4718 oldPath: 0,
4719 oldPathLen: mod.Memory().Size() + 1,
4720 expectedErrno: wasip1.ErrnoFault,
4721 expectedLog: `
4722 ==> wasi_snapshot_preview1.path_rename(fd=3,old_path=OOM(0,65537),new_fd=3,new_path=)
4723 <== errno=EFAULT
4724 `,
4725 },
4726 {
4727 name: "out-of-memory reading new pathLen",
4728 oldFd: sys.FdPreopen,
4729 newFd: sys.FdPreopen,
4730 oldPathName: file,
4731 oldPathLen: uint32(len(file)),
4732 newPath: 0,
4733 newPathLen: mod.Memory().Size() + 1,
4734 expectedErrno: wasip1.ErrnoFault,
4735 expectedLog: `
4736 ==> wasi_snapshot_preview1.path_rename(fd=3,old_path=file,new_fd=3,new_path=OOM(0,65537))
4737 <== errno=EFAULT
4738 `,
4739 },
4740 {
4741 name: "no such file exists",
4742 oldFd: sys.FdPreopen,
4743 newFd: sys.FdPreopen,
4744 oldPathName: file,
4745 oldPathLen: uint32(len(file)) - 1,
4746 newPath: 16,
4747 newPathName: file,
4748 newPathLen: uint32(len(file)),
4749 expectedErrno: wasip1.ErrnoNoent,
4750 expectedLog: `
4751 ==> wasi_snapshot_preview1.path_rename(fd=3,old_path=fil,new_fd=3,new_path=file)
4752 <== errno=ENOENT
4753 `,
4754 },
4755 {
4756 name: "dir not file",
4757 oldFd: sys.FdPreopen,
4758 newFd: sys.FdPreopen,
4759 oldPathName: file,
4760 oldPathLen: uint32(len(file)),
4761 newPath: 16,
4762 newPathName: dir,
4763 newPathLen: uint32(len(dir)),
4764 expectedErrno: wasip1.ErrnoIsdir,
4765 expectedLog: `
4766 ==> wasi_snapshot_preview1.path_rename(fd=3,old_path=file,new_fd=3,new_path=notempty/dir)
4767 <== errno=EISDIR
4768 `,
4769 },
4770 }
4771
4772 for _, tt := range tests {
4773 tc := tt
4774 t.Run(tc.name, func(t *testing.T) {
4775 defer log.Reset()
4776
4777 mod.Memory().Write(tc.oldPath, []byte(tc.oldPathName))
4778 mod.Memory().Write(tc.newPath, []byte(tc.newPathName))
4779
4780 requireErrnoResult(t, tc.expectedErrno, mod, wasip1.PathRenameName,
4781 uint64(tc.oldFd), uint64(tc.oldPath), uint64(tc.oldPathLen),
4782 uint64(tc.newFd), uint64(tc.newPath), uint64(tc.newPathLen))
4783 require.Equal(t, tc.expectedLog, "\n"+log.String())
4784 })
4785 }
4786 }
4787
4788 func Test_pathUnlinkFile(t *testing.T) {
4789 tmpDir := t.TempDir()
4790 fsConfig := wazero.NewFSConfig().WithDirMount(tmpDir, "/")
4791 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(fsConfig))
4792 defer r.Close(testCtx)
4793
4794
4795 pathName := "wazero"
4796 realPath := joinPath(tmpDir, pathName)
4797 ok := mod.Memory().Write(0, append([]byte{'?'}, pathName...))
4798 require.True(t, ok)
4799
4800
4801 err := os.WriteFile(realPath, []byte{}, 0o600)
4802 require.NoError(t, err)
4803
4804 fd := sys.FdPreopen
4805 name := 1
4806 nameLen := len(pathName)
4807
4808 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.PathUnlinkFileName, uint64(fd), uint64(name), uint64(nameLen))
4809 require.Equal(t, `
4810 ==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=wazero)
4811 <== errno=ESUCCESS
4812 `, "\n"+log.String())
4813
4814
4815 _, err = os.Stat(realPath)
4816 require.Error(t, err)
4817 }
4818
4819 func Test_pathUnlinkFile_Errors(t *testing.T) {
4820 tmpDir := t.TempDir()
4821 fsConfig := wazero.NewFSConfig().WithDirMount(tmpDir, "/")
4822 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(fsConfig))
4823 defer r.Close(testCtx)
4824
4825 file := "file"
4826 err := os.WriteFile(joinPath(tmpDir, file), []byte{}, 0o700)
4827 require.NoError(t, err)
4828 fileFD := requireOpenFD(t, mod, file)
4829
4830 dir := "dir"
4831 err = os.Mkdir(joinPath(tmpDir, dir), 0o700)
4832 require.NoError(t, err)
4833
4834 tests := []struct {
4835 name, pathName string
4836 fd int32
4837 path, pathLen uint32
4838 expectedErrno wasip1.Errno
4839 expectedLog string
4840 }{
4841 {
4842 name: "unopened FD",
4843 fd: 42,
4844 expectedErrno: wasip1.ErrnoBadf,
4845 expectedLog: `
4846 ==> wasi_snapshot_preview1.path_unlink_file(fd=42,path=)
4847 <== errno=EBADF
4848 `,
4849 },
4850 {
4851 name: "Fd not a directory",
4852 fd: fileFD,
4853 expectedErrno: wasip1.ErrnoNotdir,
4854 expectedLog: `
4855 ==> wasi_snapshot_preview1.path_unlink_file(fd=4,path=)
4856 <== errno=ENOTDIR
4857 `,
4858 },
4859 {
4860 name: "out-of-memory reading path",
4861 fd: sys.FdPreopen,
4862 path: mod.Memory().Size(),
4863 pathLen: 1,
4864 expectedErrno: wasip1.ErrnoFault,
4865 expectedLog: `
4866 ==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=OOM(65536,1))
4867 <== errno=EFAULT
4868 `,
4869 },
4870 {
4871 name: "out-of-memory reading pathLen",
4872 fd: sys.FdPreopen,
4873 path: 0,
4874 pathLen: mod.Memory().Size() + 1,
4875 expectedErrno: wasip1.ErrnoFault,
4876 expectedLog: `
4877 ==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=OOM(0,65537))
4878 <== errno=EFAULT
4879 `,
4880 },
4881 {
4882 name: "no such file exists",
4883 fd: sys.FdPreopen,
4884 pathName: file,
4885 path: 0,
4886 pathLen: uint32(len(file) - 1),
4887 expectedErrno: wasip1.ErrnoNoent,
4888 expectedLog: `
4889 ==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=fil)
4890 <== errno=ENOENT
4891 `,
4892 },
4893 {
4894 name: "dir not file",
4895 fd: sys.FdPreopen,
4896 pathName: dir,
4897 path: 0,
4898 pathLen: uint32(len(dir)),
4899 expectedErrno: wasip1.ErrnoIsdir,
4900 expectedLog: `
4901 ==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=dir)
4902 <== errno=EISDIR
4903 `,
4904 },
4905 }
4906
4907 for _, tt := range tests {
4908 tc := tt
4909 t.Run(tc.name, func(t *testing.T) {
4910 defer log.Reset()
4911
4912 mod.Memory().Write(tc.path, []byte(tc.pathName))
4913
4914 requireErrnoResult(t, tc.expectedErrno, mod, wasip1.PathUnlinkFileName, uint64(tc.fd), uint64(tc.path), uint64(tc.pathLen))
4915 require.Equal(t, tc.expectedLog, "\n"+log.String())
4916 })
4917 }
4918 }
4919
4920 func requireOpenFile(t *testing.T, tmpDir string, pathName string, data []byte, readOnly bool) (api.Module, int32, *bytes.Buffer, api.Closer) {
4921 oflags := experimentalsys.O_RDWR
4922 if readOnly {
4923 oflags = experimentalsys.O_RDONLY
4924 }
4925
4926 realPath := joinPath(tmpDir, pathName)
4927 if data == nil {
4928 oflags = experimentalsys.O_RDONLY
4929 require.NoError(t, os.Mkdir(realPath, 0o700))
4930 } else {
4931 require.NoError(t, os.WriteFile(realPath, data, 0o600))
4932 }
4933
4934 fsConfig := wazero.NewFSConfig()
4935
4936 if readOnly {
4937 fsConfig = fsConfig.WithReadOnlyDirMount(tmpDir, "/")
4938 } else {
4939 fsConfig = fsConfig.WithDirMount(tmpDir, "/")
4940 }
4941
4942 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(fsConfig))
4943 fsc := mod.(*wasm.ModuleInstance).Sys.FS()
4944 preopen := fsc.RootFS()
4945
4946 fd, errno := fsc.OpenFile(preopen, pathName, oflags, 0)
4947 require.EqualErrno(t, 0, errno)
4948
4949 return mod, fd, log, r
4950 }
4951
4952
4953 func Test_fdReaddir_dotEntryHasARealInode(t *testing.T) {
4954 if runtime.GOOS == "windows" && !platform.IsAtLeastGo120 {
4955 t.Skip("windows before go 1.20 has trouble reading the inode information on directories.")
4956 }
4957
4958 root := t.TempDir()
4959 mod, r, _ := requireProxyModule(t, wazero.NewModuleConfig().
4960 WithFSConfig(wazero.NewFSConfig().WithDirMount(root, "/")),
4961 )
4962 defer r.Close(testCtx)
4963
4964 mem := mod.Memory()
4965
4966 fsc := mod.(*wasm.ModuleInstance).Sys.FS()
4967 preopen := fsc.RootFS()
4968
4969 readDirTarget := "dir"
4970 mem.Write(0, []byte(readDirTarget))
4971 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.PathCreateDirectoryName,
4972 uint64(sys.FdPreopen), uint64(0), uint64(len(readDirTarget)))
4973
4974
4975 fd, errno := fsc.OpenFile(preopen, readDirTarget, experimentalsys.O_RDONLY, 0)
4976 require.EqualErrno(t, 0, errno)
4977
4978
4979 st, errno := preopen.Stat(readDirTarget)
4980 require.EqualErrno(t, 0, errno)
4981 dirents := []byte{1, 0, 0, 0, 0, 0, 0, 0}
4982 dirents = append(dirents, u64.LeBytes(st.Ino)...)
4983 dirents = append(dirents, 1, 0, 0, 0)
4984 dirents = append(dirents, 3, 0, 0, 0)
4985 dirents = append(dirents, '.')
4986
4987 require.EqualErrno(t, 0, errno)
4988 dirents = append(dirents, 2, 0, 0, 0, 0, 0, 0, 0)
4989
4990 dirents = append(dirents, 0, 0, 0, 0, 0, 0, 0, 0)
4991 dirents = append(dirents, 2, 0, 0, 0)
4992 dirents = append(dirents, 3, 0, 0, 0)
4993 dirents = append(dirents, '.', '.')
4994
4995
4996 resultBufused := uint32(0)
4997 buf := uint32(8)
4998 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdReaddirName,
4999 uint64(fd), uint64(buf), uint64(0x2000), 0, uint64(resultBufused))
5000
5001 used, _ := mem.ReadUint32Le(resultBufused)
5002
5003 results, _ := mem.Read(buf, used)
5004 require.Equal(t, dirents, results)
5005 }
5006
5007
5008
5009
5010 func Test_fdReaddir_opened_file_written(t *testing.T) {
5011 if runtime.GOOS == "windows" && !platform.IsAtLeastGo120 {
5012 t.Skip("windows before go 1.20 has trouble reading the inode information on directories.")
5013 }
5014
5015 tmpDir := t.TempDir()
5016 mod, r, _ := requireProxyModule(t, wazero.NewModuleConfig().
5017 WithFSConfig(wazero.NewFSConfig().WithDirMount(tmpDir, "/")),
5018 )
5019 defer r.Close(testCtx)
5020
5021 mem := mod.Memory()
5022
5023 fsc := mod.(*wasm.ModuleInstance).Sys.FS()
5024 preopen := fsc.RootFS()
5025
5026 dirName := "dir"
5027 dirPath := joinPath(tmpDir, dirName)
5028 mem.Write(0, []byte(dirName))
5029 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.PathCreateDirectoryName,
5030 uint64(sys.FdPreopen), uint64(0), uint64(len(dirName)))
5031
5032
5033 dirFD, errno := fsc.OpenFile(preopen, dirName, experimentalsys.O_RDONLY, 0)
5034 require.EqualErrno(t, 0, errno)
5035
5036
5037 f := openFile(t, joinPath(dirPath, "file"), experimentalsys.O_CREAT, 0)
5038 defer f.Close()
5039
5040
5041 st, errno := preopen.Stat(dirName)
5042 require.EqualErrno(t, 0, errno)
5043 dirents := []byte{1, 0, 0, 0, 0, 0, 0, 0}
5044 dirents = append(dirents, u64.LeBytes(st.Ino)...)
5045 dirents = append(dirents, 1, 0, 0, 0)
5046 dirents = append(dirents, 3, 0, 0, 0)
5047 dirents = append(dirents, '.')
5048
5049
5050 st, errno = preopen.Stat(".")
5051 require.EqualErrno(t, 0, errno)
5052 dirents = append(dirents, 2, 0, 0, 0, 0, 0, 0, 0)
5053
5054 dirents = append(dirents, 0, 0, 0, 0, 0, 0, 0, 0)
5055 dirents = append(dirents, 2, 0, 0, 0)
5056 dirents = append(dirents, 3, 0, 0, 0)
5057 dirents = append(dirents, '.', '.')
5058
5059
5060 st, errno = f.Stat()
5061 require.EqualErrno(t, 0, errno)
5062 dirents = append(dirents, 3, 0, 0, 0, 0, 0, 0, 0)
5063 dirents = append(dirents, u64.LeBytes(st.Ino)...)
5064 dirents = append(dirents, 4, 0, 0, 0)
5065 dirents = append(dirents, 4, 0, 0, 0)
5066 dirents = append(dirents, 'f', 'i', 'l', 'e')
5067
5068
5069 resultBufused := uint32(0)
5070 buf := uint32(8)
5071 requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdReaddirName,
5072 uint64(dirFD), uint64(buf), uint64(0x2000), 0, uint64(resultBufused))
5073
5074 used, _ := mem.ReadUint32Le(resultBufused)
5075
5076 results, _ := mem.Read(buf, used)
5077 require.Equal(t, dirents, results)
5078 }
5079
5080
5081
5082 func joinPath(dirName, baseName string) string {
5083 return path.Join(dirName, baseName)
5084 }
5085
5086 func openFile(t *testing.T, path string, flag experimentalsys.Oflag, perm fs.FileMode) experimentalsys.File {
5087 f, errno := sysfs.OpenOSFile(path, flag, perm)
5088 require.EqualErrno(t, 0, errno)
5089 return f
5090 }
5091
View as plain text