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