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