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