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