wa-lang.org/wazero@v1.0.2/imports/wasi_snapshot_preview1/fs_test.go (about) 1 package wasi_snapshot_preview1 2 3 import ( 4 "bytes" 5 _ "embed" 6 "io" 7 "io/fs" 8 "math" 9 "os" 10 "path" 11 "testing" 12 "testing/fstest" 13 "time" 14 15 "wa-lang.org/wazero" 16 "wa-lang.org/wazero/api" 17 internalsys "wa-lang.org/wazero/internal/sys" 18 "wa-lang.org/wazero/internal/testing/require" 19 "wa-lang.org/wazero/internal/wasm" 20 ) 21 22 // Test_fdAdvise only tests it is stubbed for GrainLang per #271 23 func Test_fdAdvise(t *testing.T) { 24 log := requireErrnoNosys(t, functionFdAdvise, 0, 0, 0, 0) 25 require.Equal(t, ` 26 --> proxy.fd_advise(fd=0,offset=0,len=0,result.advice=0) 27 --> wasi_snapshot_preview1.fd_advise(fd=0,offset=0,len=0,result.advice=0) 28 <-- ENOSYS 29 <-- (52) 30 `, log) 31 } 32 33 // Test_fdAllocate only tests it is stubbed for GrainLang per #271 34 func Test_fdAllocate(t *testing.T) { 35 log := requireErrnoNosys(t, functionFdAllocate, 0, 0, 0) 36 require.Equal(t, ` 37 --> proxy.fd_allocate(fd=0,offset=0,len=0) 38 --> wasi_snapshot_preview1.fd_allocate(fd=0,offset=0,len=0) 39 <-- ENOSYS 40 <-- (52) 41 `, log) 42 } 43 44 func Test_fdClose(t *testing.T) { 45 // fd_close needs to close an open file descriptor. Open two files so that we can tell which is closed. 46 path1, path2 := "a", "b" 47 testFS := fstest.MapFS{path1: {Data: make([]byte, 0)}, path2: {Data: make([]byte, 0)}} 48 49 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(testFS)) 50 defer r.Close(testCtx) 51 52 // open both paths without using WASI 53 fsc := mod.(*wasm.CallContext).Sys.FS(testCtx) 54 55 fdToClose, err := fsc.OpenFile(testCtx, path1) 56 require.NoError(t, err) 57 58 fdToKeep, err := fsc.OpenFile(testCtx, path2) 59 require.NoError(t, err) 60 61 // Close 62 requireErrno(t, ErrnoSuccess, mod, functionFdClose, uint64(fdToClose)) 63 require.Equal(t, ` 64 --> proxy.fd_close(fd=4) 65 ==> wasi_snapshot_preview1.fd_close(fd=4) 66 <== ESUCCESS 67 <-- (0) 68 `, "\n"+log.String()) 69 70 // Verify fdToClose is closed and removed from the opened FDs. 71 _, ok := fsc.OpenedFile(testCtx, fdToClose) 72 require.False(t, ok) 73 74 // Verify fdToKeep is not closed 75 _, ok = fsc.OpenedFile(testCtx, fdToKeep) 76 require.True(t, ok) 77 78 log.Reset() 79 t.Run("ErrnoBadF for an invalid FD", func(t *testing.T) { 80 requireErrno(t, ErrnoBadf, mod, functionFdClose, uint64(42)) // 42 is an arbitrary invalid FD 81 require.Equal(t, ` 82 --> proxy.fd_close(fd=42) 83 ==> wasi_snapshot_preview1.fd_close(fd=42) 84 <== EBADF 85 <-- (8) 86 `, "\n"+log.String()) 87 }) 88 } 89 90 // Test_fdDatasync only tests it is stubbed for GrainLang per #271 91 func Test_fdDatasync(t *testing.T) { 92 log := requireErrnoNosys(t, functionFdDatasync, 0) 93 require.Equal(t, ` 94 --> proxy.fd_datasync(fd=0) 95 --> wasi_snapshot_preview1.fd_datasync(fd=0) 96 <-- ENOSYS 97 <-- (52) 98 `, log) 99 } 100 101 func Test_fdFdstatGet(t *testing.T) { 102 file, dir := "a", "b" 103 testFS := fstest.MapFS{file: {Data: make([]byte, 0)}, dir: {Mode: fs.ModeDir}} 104 105 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(testFS)) 106 defer r.Close(testCtx) 107 memorySize := mod.Memory().Size(testCtx) 108 109 // open both paths without using WASI 110 fsc := mod.(*wasm.CallContext).Sys.FS(testCtx) 111 112 fileFd, err := fsc.OpenFile(testCtx, file) 113 require.NoError(t, err) 114 115 dirFd, err := fsc.OpenFile(testCtx, dir) 116 require.NoError(t, err) 117 118 tests := []struct { 119 name string 120 fd, resultStat uint32 121 // TODO: expectedMem 122 expectedErrno Errno 123 expectedLog string 124 }{ 125 { 126 name: "file", 127 fd: fileFd, 128 // TODO: expectedMem for a file 129 expectedLog: ` 130 --> proxy.fd_fdstat_get(fd=4,result.stat=0) 131 ==> wasi_snapshot_preview1.fd_fdstat_get(fd=4,result.stat=0) 132 <== ESUCCESS 133 <-- (0) 134 `, 135 }, 136 { 137 name: "dir", 138 fd: dirFd, 139 // TODO: expectedMem for a dir 140 expectedLog: ` 141 --> proxy.fd_fdstat_get(fd=5,result.stat=0) 142 ==> wasi_snapshot_preview1.fd_fdstat_get(fd=5,result.stat=0) 143 <== ESUCCESS 144 <-- (0) 145 `, 146 }, 147 { 148 name: "bad FD", 149 fd: math.MaxUint32, 150 expectedErrno: ErrnoBadf, 151 expectedLog: ` 152 --> proxy.fd_fdstat_get(fd=4294967295,result.stat=0) 153 ==> wasi_snapshot_preview1.fd_fdstat_get(fd=4294967295,result.stat=0) 154 <== EBADF 155 <-- (8) 156 `, 157 }, 158 { 159 name: "resultStat exceeds the maximum valid address by 1", 160 fd: dirFd, 161 resultStat: memorySize - 24 + 1, 162 // TODO: ErrnoFault 163 expectedLog: ` 164 --> proxy.fd_fdstat_get(fd=5,result.stat=65513) 165 ==> wasi_snapshot_preview1.fd_fdstat_get(fd=5,result.stat=65513) 166 <== ESUCCESS 167 <-- (0) 168 `, 169 }, 170 } 171 172 for _, tt := range tests { 173 tc := tt 174 175 t.Run(tc.name, func(t *testing.T) { 176 defer log.Reset() 177 178 requireErrno(t, tc.expectedErrno, mod, functionFdFdstatGet, uint64(tc.fd), uint64(tc.resultStat)) 179 require.Equal(t, tc.expectedLog, "\n"+log.String()) 180 }) 181 } 182 } 183 184 // Test_fdFdstatSetFlags only tests it is stubbed for GrainLang per #271 185 func Test_fdFdstatSetFlags(t *testing.T) { 186 log := requireErrnoNosys(t, functionFdFdstatSetFlags, 0, 0) 187 require.Equal(t, ` 188 --> proxy.fd_fdstat_set_flags(fd=0,flags=0) 189 --> wasi_snapshot_preview1.fd_fdstat_set_flags(fd=0,flags=0) 190 <-- ENOSYS 191 <-- (52) 192 `, log) 193 } 194 195 // Test_fdFdstatSetRights only tests it is stubbed for GrainLang per #271 196 func Test_fdFdstatSetRights(t *testing.T) { 197 log := requireErrnoNosys(t, functionFdFdstatSetRights, 0, 0, 0) 198 require.Equal(t, ` 199 --> proxy.fd_fdstat_set_rights(fd=0,fs_rights_base=0,fs_rights_inheriting=0) 200 --> wasi_snapshot_preview1.fd_fdstat_set_rights(fd=0,fs_rights_base=0,fs_rights_inheriting=0) 201 <-- ENOSYS 202 <-- (52) 203 `, log) 204 } 205 206 func Test_fdFilestatGet(t *testing.T) { 207 file, dir := "a", "b" 208 testFS := fstest.MapFS{file: {Data: make([]byte, 10), ModTime: time.Unix(1667482413, 0)}, dir: {Mode: fs.ModeDir, ModTime: time.Unix(1667482413, 0)}} 209 210 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(testFS)) 211 defer r.Close(testCtx) 212 memorySize := mod.Memory().Size(testCtx) 213 214 // open both paths without using WASI 215 fsc := mod.(*wasm.CallContext).Sys.FS(testCtx) 216 217 fileFd, err := fsc.OpenFile(testCtx, file) 218 require.NoError(t, err) 219 220 dirFd, err := fsc.OpenFile(testCtx, dir) 221 require.NoError(t, err) 222 223 tests := []struct { 224 name string 225 fd, resultFilestat uint32 226 expectedMemory []byte 227 expectedErrno Errno 228 expectedLog string 229 }{ 230 { 231 name: "file", 232 fd: fileFd, 233 expectedMemory: []byte{ 234 '?', '?', '?', '?', '?', '?', '?', '?', // dev 235 '?', '?', '?', '?', '?', '?', '?', '?', // ino 236 4, '?', '?', '?', '?', '?', '?', '?', // filetype + padding 237 '?', '?', '?', '?', '?', '?', '?', '?', // nlink 238 10, 0, 0, 0, 0, 0, 0, 0, // size 239 0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // atim 240 0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // mtim 241 0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // ctim 242 }, 243 expectedLog: ` 244 --> proxy.fd_filestat_get(fd=4,result.buf=0) 245 ==> wasi_snapshot_preview1.fd_filestat_get(fd=4,result.buf=0) 246 <== ESUCCESS 247 <-- (0) 248 `, 249 }, 250 { 251 name: "dir", 252 fd: dirFd, 253 expectedMemory: []byte{ 254 '?', '?', '?', '?', '?', '?', '?', '?', // dev 255 '?', '?', '?', '?', '?', '?', '?', '?', // ino 256 3, '?', '?', '?', '?', '?', '?', '?', // filetype + padding 257 '?', '?', '?', '?', '?', '?', '?', '?', // nlink 258 0, 0, 0, 0, 0, 0, 0, 0, // size 259 0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // atim 260 0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // mtim 261 0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // ctim 262 }, 263 expectedLog: ` 264 --> proxy.fd_filestat_get(fd=5,result.buf=0) 265 ==> wasi_snapshot_preview1.fd_filestat_get(fd=5,result.buf=0) 266 <== ESUCCESS 267 <-- (0) 268 `, 269 }, 270 { 271 name: "bad FD", 272 fd: math.MaxUint32, 273 expectedErrno: ErrnoBadf, 274 expectedLog: ` 275 --> proxy.fd_filestat_get(fd=4294967295,result.buf=0) 276 ==> wasi_snapshot_preview1.fd_filestat_get(fd=4294967295,result.buf=0) 277 <== EBADF 278 <-- (8) 279 `, 280 }, 281 { 282 name: "resultFilestat exceeds the maximum valid address by 1", 283 fd: dirFd, 284 resultFilestat: memorySize - 64 + 1, 285 expectedErrno: ErrnoFault, 286 expectedLog: ` 287 --> proxy.fd_filestat_get(fd=5,result.buf=65473) 288 ==> wasi_snapshot_preview1.fd_filestat_get(fd=5,result.buf=65473) 289 <== EFAULT 290 <-- (21) 291 `, 292 }, 293 } 294 295 for _, tt := range tests { 296 tc := tt 297 298 t.Run(tc.name, func(t *testing.T) { 299 defer log.Reset() 300 301 maskMemory(t, testCtx, mod, len(tc.expectedMemory)) 302 303 requireErrno(t, tc.expectedErrno, mod, functionFdFilestatGet, uint64(tc.fd), uint64(tc.resultFilestat)) 304 require.Equal(t, tc.expectedLog, "\n"+log.String()) 305 306 actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(tc.expectedMemory))) 307 require.True(t, ok) 308 require.Equal(t, tc.expectedMemory, actual) 309 }) 310 } 311 } 312 313 // Test_fdFilestatSetSize only tests it is stubbed for GrainLang per #271 314 func Test_fdFilestatSetSize(t *testing.T) { 315 log := requireErrnoNosys(t, functionFdFilestatSetSize, 0, 0) 316 require.Equal(t, ` 317 --> proxy.fd_filestat_set_size(fd=0,size=0) 318 --> wasi_snapshot_preview1.fd_filestat_set_size(fd=0,size=0) 319 <-- ENOSYS 320 <-- (52) 321 `, log) 322 } 323 324 // Test_fdFilestatSetTimes only tests it is stubbed for GrainLang per #271 325 func Test_fdFilestatSetTimes(t *testing.T) { 326 log := requireErrnoNosys(t, functionFdFilestatSetTimes, 0, 0, 0, 0) 327 require.Equal(t, ` 328 --> proxy.fd_filestat_set_times(fd=0,atim=0,mtim=0,fst_flags=0) 329 --> wasi_snapshot_preview1.fd_filestat_set_times(fd=0,atim=0,mtim=0,fst_flags=0) 330 <-- ENOSYS 331 <-- (52) 332 `, log) 333 } 334 335 func Test_fdPread(t *testing.T) { 336 mod, fd, log, r := requireOpenFile(t, "/test_path", []byte("wazero")) 337 defer r.Close(testCtx) 338 339 iovs := uint32(1) // arbitrary offset 340 initialMemory := []byte{ 341 '?', // `iovs` is after this 342 18, 0, 0, 0, // = iovs[0].offset 343 4, 0, 0, 0, // = iovs[0].length 344 23, 0, 0, 0, // = iovs[1].offset 345 2, 0, 0, 0, // = iovs[1].length 346 '?', 347 } 348 349 iovsCount := uint32(2) // The count of iovs 350 resultSize := uint32(26) // arbitrary offset 351 352 tests := []struct { 353 name string 354 offset int64 355 expectedMemory []byte 356 expectedLog string 357 }{ 358 { 359 name: "offset zero", 360 offset: 0, 361 expectedMemory: append( 362 initialMemory, 363 'w', 'a', 'z', 'e', // iovs[0].length bytes 364 '?', // iovs[1].offset is after this 365 'r', 'o', // iovs[1].length bytes 366 '?', // resultSize is after this 367 6, 0, 0, 0, // sum(iovs[...].length) == length of "wazero" 368 '?', 369 ), 370 expectedLog: ` 371 --> proxy.fd_pread(fd=4,iovs=1,iovs_len=2,offset=0,result.size=26) 372 ==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=1,iovs_len=2,offset=0,result.size=26) 373 <== ESUCCESS 374 <-- (0) 375 `, 376 }, 377 { 378 name: "offset 2", 379 offset: 2, 380 expectedMemory: append( 381 initialMemory, 382 'z', 'e', 'r', 'o', // iovs[0].length bytes 383 '?', '?', '?', '?', // resultSize is after this 384 4, 0, 0, 0, // sum(iovs[...].length) == length of "zero" 385 '?', 386 ), 387 expectedLog: ` 388 --> proxy.fd_pread(fd=4,iovs=1,iovs_len=2,offset=2,result.size=26) 389 ==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=1,iovs_len=2,offset=2,result.size=26) 390 <== ESUCCESS 391 <-- (0) 392 `, 393 }, 394 } 395 396 for _, tt := range tests { 397 tc := tt 398 t.Run(tc.name, func(t *testing.T) { 399 defer log.Reset() 400 401 maskMemory(t, testCtx, mod, len(tc.expectedMemory)) 402 403 ok := mod.Memory().Write(testCtx, 0, initialMemory) 404 require.True(t, ok) 405 406 requireErrno(t, ErrnoSuccess, mod, functionFdPread, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(tc.offset), uint64(resultSize)) 407 require.Equal(t, tc.expectedLog, "\n"+log.String()) 408 409 actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(tc.expectedMemory))) 410 require.True(t, ok) 411 require.Equal(t, tc.expectedMemory, actual) 412 }) 413 } 414 } 415 416 func Test_fdPread_Errors(t *testing.T) { 417 contents := []byte("wazero") 418 mod, fd, log, r := requireOpenFile(t, "/test_path", contents) 419 defer r.Close(testCtx) 420 421 tests := []struct { 422 name string 423 fd, iovs, iovsCount, resultSize uint32 424 offset int64 425 memory []byte 426 expectedErrno Errno 427 expectedLog string 428 }{ 429 { 430 name: "invalid fd", 431 fd: 42, // arbitrary invalid fd 432 expectedErrno: ErrnoBadf, 433 expectedLog: ` 434 --> proxy.fd_pread(fd=42,iovs=65536,iovs_len=65536,offset=0,result.size=65536) 435 ==> wasi_snapshot_preview1.fd_pread(fd=42,iovs=65536,iovs_len=65536,offset=0,result.size=65536) 436 <== EBADF 437 <-- (8) 438 `, 439 }, 440 { 441 name: "seek past file", 442 fd: fd, 443 offset: int64(len(contents) + 1), 444 expectedErrno: ErrnoFault, 445 expectedLog: ` 446 --> proxy.fd_pread(fd=4,iovs=65536,iovs_len=65536,offset=7,result.size=65536) 447 ==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65536,iovs_len=65536,offset=7,result.size=65536) 448 <== EFAULT 449 <-- (21) 450 `, 451 }, 452 { 453 name: "out-of-memory reading iovs[0].offset", 454 fd: fd, 455 iovs: 1, 456 memory: []byte{'?'}, 457 expectedErrno: ErrnoFault, 458 expectedLog: ` 459 --> proxy.fd_pread(fd=4,iovs=65536,iovs_len=65535,offset=0,result.size=65535) 460 ==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65536,iovs_len=65535,offset=0,result.size=65535) 461 <== EFAULT 462 <-- (21) 463 `, 464 }, 465 { 466 name: "out-of-memory reading iovs[0].length", 467 fd: fd, 468 iovs: 1, iovsCount: 1, 469 memory: []byte{ 470 '?', // `iovs` is after this 471 9, 0, 0, 0, // = iovs[0].offset 472 }, 473 expectedErrno: ErrnoFault, 474 expectedLog: ` 475 --> proxy.fd_pread(fd=4,iovs=65532,iovs_len=65532,offset=0,result.size=65531) 476 ==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65532,iovs_len=65532,offset=0,result.size=65531) 477 <== EFAULT 478 <-- (21) 479 `, 480 }, 481 { 482 name: "iovs[0].offset is outside memory", 483 fd: fd, 484 iovs: 1, iovsCount: 1, 485 memory: []byte{ 486 '?', // `iovs` is after this 487 0, 0, 0x1, 0, // = iovs[0].offset on the second page 488 1, 0, 0, 0, // = iovs[0].length 489 }, 490 expectedErrno: ErrnoFault, 491 expectedLog: ` 492 --> proxy.fd_pread(fd=4,iovs=65528,iovs_len=65528,offset=0,result.size=65527) 493 ==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65528,iovs_len=65528,offset=0,result.size=65527) 494 <== EFAULT 495 <-- (21) 496 `, 497 }, 498 { 499 name: "length to read exceeds memory by 1", 500 fd: fd, 501 iovs: 1, iovsCount: 1, 502 memory: []byte{ 503 '?', // `iovs` is after this 504 9, 0, 0, 0, // = iovs[0].offset 505 0, 0, 0x1, 0, // = iovs[0].length on the second page 506 '?', 507 }, 508 expectedErrno: ErrnoFault, 509 expectedLog: ` 510 --> proxy.fd_pread(fd=4,iovs=65527,iovs_len=65527,offset=0,result.size=65526) 511 ==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65527,iovs_len=65527,offset=0,result.size=65526) 512 <== EFAULT 513 <-- (21) 514 `, 515 }, 516 { 517 name: "resultSize offset is outside memory", 518 fd: fd, 519 iovs: 1, iovsCount: 1, 520 resultSize: 10, // 1 past memory 521 memory: []byte{ 522 '?', // `iovs` is after this 523 9, 0, 0, 0, // = iovs[0].offset 524 1, 0, 0, 0, // = iovs[0].length 525 '?', 526 }, 527 expectedErrno: ErrnoFault, 528 expectedLog: ` 529 --> proxy.fd_pread(fd=4,iovs=65527,iovs_len=65527,offset=0,result.size=65536) 530 ==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65527,iovs_len=65527,offset=0,result.size=65536) 531 <== EFAULT 532 <-- (21) 533 `, 534 }, 535 } 536 537 for _, tt := range tests { 538 tc := tt 539 t.Run(tc.name, func(t *testing.T) { 540 defer log.Reset() 541 542 offset := uint32(wasm.MemoryPagesToBytesNum(testMemoryPageSize) - uint64(len(tc.memory))) 543 544 memoryWriteOK := mod.Memory().Write(testCtx, offset, tc.memory) 545 require.True(t, memoryWriteOK) 546 547 requireErrno(t, tc.expectedErrno, mod, functionFdPread, uint64(tc.fd), uint64(tc.iovs+offset), uint64(tc.iovsCount+offset), uint64(tc.offset), uint64(tc.resultSize+offset)) 548 require.Equal(t, tc.expectedLog, "\n"+log.String()) 549 }) 550 } 551 } 552 553 func Test_fdPrestatGet(t *testing.T) { 554 pathName := "/tmp" 555 mod, fd, log, r := requireOpenFile(t, pathName, nil) 556 defer r.Close(testCtx) 557 558 resultPrestat := uint32(1) // arbitrary offset 559 expectedMemory := []byte{ 560 '?', // resultPrestat after this 561 0, // 8-bit tag indicating `prestat_dir`, the only available tag 562 0, 0, 0, // 3-byte padding 563 // the result path length field after this 564 byte(len(pathName)), 0, 0, 0, // = in little endian encoding 565 '?', 566 } 567 568 maskMemory(t, testCtx, mod, len(expectedMemory)) 569 570 requireErrno(t, ErrnoSuccess, mod, functionFdPrestatGet, uint64(fd), uint64(resultPrestat)) 571 require.Equal(t, ` 572 --> proxy.fd_prestat_get(fd=4,result.prestat=1) 573 ==> wasi_snapshot_preview1.fd_prestat_get(fd=4,result.prestat=1) 574 <== ESUCCESS 575 <-- (0) 576 `, "\n"+log.String()) 577 578 actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory))) 579 require.True(t, ok) 580 require.Equal(t, expectedMemory, actual) 581 } 582 583 func Test_fdPrestatGet_Errors(t *testing.T) { 584 pathName := "/tmp" 585 mod, fd, log, r := requireOpenFile(t, pathName, nil) 586 defer r.Close(testCtx) 587 588 memorySize := mod.Memory().Size(testCtx) 589 tests := []struct { 590 name string 591 fd uint32 592 resultPrestat uint32 593 expectedErrno Errno 594 expectedLog string 595 }{ 596 { 597 name: "invalid FD", 598 fd: 42, // arbitrary invalid FD 599 resultPrestat: 0, // valid offset 600 expectedErrno: ErrnoBadf, 601 expectedLog: ` 602 --> proxy.fd_prestat_get(fd=42,result.prestat=0) 603 ==> wasi_snapshot_preview1.fd_prestat_get(fd=42,result.prestat=0) 604 <== EBADF 605 <-- (8) 606 `, 607 }, 608 { 609 name: "out-of-memory resultPrestat", 610 fd: fd, 611 resultPrestat: memorySize, 612 expectedErrno: ErrnoFault, 613 expectedLog: ` 614 --> proxy.fd_prestat_get(fd=4,result.prestat=65536) 615 ==> wasi_snapshot_preview1.fd_prestat_get(fd=4,result.prestat=65536) 616 <== EFAULT 617 <-- (21) 618 `, 619 }, 620 // TODO: non pre-opened file == api.ErrnoBadf 621 } 622 623 for _, tt := range tests { 624 tc := tt 625 626 t.Run(tc.name, func(t *testing.T) { 627 defer log.Reset() 628 629 requireErrno(t, tc.expectedErrno, mod, functionFdPrestatGet, uint64(tc.fd), uint64(tc.resultPrestat)) 630 require.Equal(t, tc.expectedLog, "\n"+log.String()) 631 }) 632 } 633 } 634 635 func Test_fdPrestatDirName(t *testing.T) { 636 pathName := "/tmp" 637 mod, fd, log, r := requireOpenFile(t, pathName, nil) 638 defer r.Close(testCtx) 639 640 path := uint32(1) // arbitrary offset 641 pathLen := uint32(3) // shorter than len("/tmp") to test the path is written for the length of pathLen 642 expectedMemory := []byte{ 643 '?', 644 '/', 't', 'm', 645 '?', '?', '?', 646 } 647 648 maskMemory(t, testCtx, mod, len(expectedMemory)) 649 650 requireErrno(t, ErrnoSuccess, mod, functionFdPrestatDirName, uint64(fd), uint64(path), uint64(pathLen)) 651 require.Equal(t, ` 652 --> proxy.fd_prestat_dir_name(fd=4,path=1,path_len=3) 653 ==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=4,path=1,path_len=3) 654 <== ESUCCESS 655 <-- (0) 656 `, "\n"+log.String()) 657 658 actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory))) 659 require.True(t, ok) 660 require.Equal(t, expectedMemory, actual) 661 } 662 663 func Test_fdPrestatDirName_Errors(t *testing.T) { 664 pathName := "/tmp" 665 mod, fd, log, r := requireOpenFile(t, pathName, nil) 666 defer r.Close(testCtx) 667 668 memorySize := mod.Memory().Size(testCtx) 669 validAddress := uint32(0) // Arbitrary valid address as arguments to fd_prestat_dir_name. We chose 0 here. 670 pathLen := uint32(len("/tmp")) 671 672 tests := []struct { 673 name string 674 fd uint32 675 path uint32 676 pathLen uint32 677 expectedErrno Errno 678 expectedLog string 679 }{ 680 { 681 name: "out-of-memory path", 682 fd: fd, 683 path: memorySize, 684 pathLen: pathLen, 685 expectedErrno: ErrnoFault, 686 expectedLog: ` 687 --> proxy.fd_prestat_dir_name(fd=4,path=65536,path_len=4) 688 ==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=4,path=65536,path_len=4) 689 <== EFAULT 690 <-- (21) 691 `, 692 }, 693 { 694 name: "path exceeds the maximum valid address by 1", 695 fd: fd, 696 path: memorySize - pathLen + 1, 697 pathLen: pathLen, 698 expectedErrno: ErrnoFault, 699 expectedLog: ` 700 --> proxy.fd_prestat_dir_name(fd=4,path=65533,path_len=4) 701 ==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=4,path=65533,path_len=4) 702 <== EFAULT 703 <-- (21) 704 `, 705 }, 706 { 707 name: "pathLen exceeds the length of the dir name", 708 fd: fd, 709 path: validAddress, 710 pathLen: pathLen + 1, 711 expectedErrno: ErrnoNametoolong, 712 expectedLog: ` 713 --> proxy.fd_prestat_dir_name(fd=4,path=0,path_len=5) 714 ==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=4,path=0,path_len=5) 715 <== ENAMETOOLONG 716 <-- (37) 717 `, 718 }, 719 { 720 name: "invalid fd", 721 fd: 42, // arbitrary invalid fd 722 path: validAddress, 723 pathLen: pathLen, 724 expectedErrno: ErrnoBadf, 725 expectedLog: ` 726 --> proxy.fd_prestat_dir_name(fd=42,path=0,path_len=4) 727 ==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=42,path=0,path_len=4) 728 <== EBADF 729 <-- (8) 730 `, 731 }, 732 // TODO: non pre-opened file == ErrnoBadf 733 } 734 735 for _, tt := range tests { 736 tc := tt 737 738 t.Run(tc.name, func(t *testing.T) { 739 defer log.Reset() 740 741 requireErrno(t, tc.expectedErrno, mod, functionFdPrestatDirName, uint64(tc.fd), uint64(tc.path), uint64(tc.pathLen)) 742 require.Equal(t, tc.expectedLog, "\n"+log.String()) 743 }) 744 } 745 } 746 747 // Test_fdPwrite only tests it is stubbed for GrainLang per #271 748 func Test_fdPwrite(t *testing.T) { 749 log := requireErrnoNosys(t, functionFdPwrite, 0, 0, 0, 0, 0) 750 require.Equal(t, ` 751 --> proxy.fd_pwrite(fd=0,iovs=0,iovs_len=0,offset=0,result.nwritten=0) 752 --> wasi_snapshot_preview1.fd_pwrite(fd=0,iovs=0,iovs_len=0,offset=0,result.nwritten=0) 753 <-- ENOSYS 754 <-- (52) 755 `, log) 756 } 757 758 func Test_fdRead(t *testing.T) { 759 mod, fd, log, r := requireOpenFile(t, "/test_path", []byte("wazero")) 760 defer r.Close(testCtx) 761 762 iovs := uint32(1) // arbitrary offset 763 initialMemory := []byte{ 764 '?', // `iovs` is after this 765 18, 0, 0, 0, // = iovs[0].offset 766 4, 0, 0, 0, // = iovs[0].length 767 23, 0, 0, 0, // = iovs[1].offset 768 2, 0, 0, 0, // = iovs[1].length 769 '?', 770 } 771 iovsCount := uint32(2) // The count of iovs 772 resultSize := uint32(26) // arbitrary offset 773 expectedMemory := append( 774 initialMemory, 775 'w', 'a', 'z', 'e', // iovs[0].length bytes 776 '?', // iovs[1].offset is after this 777 'r', 'o', // iovs[1].length bytes 778 '?', // resultSize is after this 779 6, 0, 0, 0, // sum(iovs[...].length) == length of "wazero" 780 '?', 781 ) 782 783 maskMemory(t, testCtx, mod, len(expectedMemory)) 784 785 ok := mod.Memory().Write(testCtx, 0, initialMemory) 786 require.True(t, ok) 787 788 requireErrno(t, ErrnoSuccess, mod, functionFdRead, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(resultSize)) 789 require.Equal(t, ` 790 --> proxy.fd_read(fd=4,iovs=1,iovs_len=2,result.size=26) 791 ==> wasi_snapshot_preview1.fd_read(fd=4,iovs=1,iovs_len=2,result.size=26) 792 <== ESUCCESS 793 <-- (0) 794 `, "\n"+log.String()) 795 796 actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory))) 797 require.True(t, ok) 798 require.Equal(t, expectedMemory, actual) 799 } 800 801 func Test_fdRead_Errors(t *testing.T) { 802 mod, fd, log, r := requireOpenFile(t, "/test_path", []byte("wazero")) 803 defer r.Close(testCtx) 804 805 tests := []struct { 806 name string 807 fd, iovs, iovsCount, resultSize uint32 808 memory []byte 809 expectedErrno Errno 810 expectedLog string 811 }{ 812 { 813 name: "invalid fd", 814 fd: 42, // arbitrary invalid fd 815 expectedErrno: ErrnoBadf, 816 expectedLog: ` 817 --> proxy.fd_read(fd=42,iovs=65536,iovs_len=65536,result.size=65536) 818 ==> wasi_snapshot_preview1.fd_read(fd=42,iovs=65536,iovs_len=65536,result.size=65536) 819 <== EBADF 820 <-- (8) 821 `, 822 }, 823 { 824 name: "out-of-memory reading iovs[0].offset", 825 fd: fd, 826 iovs: 1, 827 memory: []byte{'?'}, 828 expectedErrno: ErrnoFault, 829 expectedLog: ` 830 --> proxy.fd_read(fd=4,iovs=65536,iovs_len=65535,result.size=65535) 831 ==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65536,iovs_len=65535,result.size=65535) 832 <== EFAULT 833 <-- (21) 834 `, 835 }, 836 { 837 name: "out-of-memory reading iovs[0].length", 838 fd: fd, 839 iovs: 1, iovsCount: 1, 840 memory: []byte{ 841 '?', // `iovs` is after this 842 9, 0, 0, 0, // = iovs[0].offset 843 }, 844 expectedErrno: ErrnoFault, 845 expectedLog: ` 846 --> proxy.fd_read(fd=4,iovs=65532,iovs_len=65532,result.size=65531) 847 ==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65532,iovs_len=65532,result.size=65531) 848 <== EFAULT 849 <-- (21) 850 `, 851 }, 852 { 853 name: "iovs[0].offset is outside memory", 854 fd: fd, 855 iovs: 1, iovsCount: 1, 856 memory: []byte{ 857 '?', // `iovs` is after this 858 0, 0, 0x1, 0, // = iovs[0].offset on the second page 859 1, 0, 0, 0, // = iovs[0].length 860 }, 861 expectedErrno: ErrnoFault, 862 expectedLog: ` 863 --> proxy.fd_read(fd=4,iovs=65528,iovs_len=65528,result.size=65527) 864 ==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65528,iovs_len=65528,result.size=65527) 865 <== EFAULT 866 <-- (21) 867 `, 868 }, 869 { 870 name: "length to read exceeds memory by 1", 871 fd: fd, 872 iovs: 1, iovsCount: 1, 873 memory: []byte{ 874 '?', // `iovs` is after this 875 9, 0, 0, 0, // = iovs[0].offset 876 0, 0, 0x1, 0, // = iovs[0].length on the second page 877 '?', 878 }, 879 expectedErrno: ErrnoFault, 880 expectedLog: ` 881 --> proxy.fd_read(fd=4,iovs=65527,iovs_len=65527,result.size=65526) 882 ==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65527,iovs_len=65527,result.size=65526) 883 <== EFAULT 884 <-- (21) 885 `, 886 }, 887 { 888 name: "resultSize offset is outside memory", 889 fd: fd, 890 iovs: 1, iovsCount: 1, 891 resultSize: 10, // 1 past memory 892 memory: []byte{ 893 '?', // `iovs` is after this 894 9, 0, 0, 0, // = iovs[0].offset 895 1, 0, 0, 0, // = iovs[0].length 896 '?', 897 }, 898 expectedErrno: ErrnoFault, 899 expectedLog: ` 900 --> proxy.fd_read(fd=4,iovs=65527,iovs_len=65527,result.size=65536) 901 ==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65527,iovs_len=65527,result.size=65536) 902 <== EFAULT 903 <-- (21) 904 `, 905 }, 906 } 907 908 for _, tt := range tests { 909 tc := tt 910 t.Run(tc.name, func(t *testing.T) { 911 defer log.Reset() 912 913 offset := uint32(wasm.MemoryPagesToBytesNum(testMemoryPageSize) - uint64(len(tc.memory))) 914 915 memoryWriteOK := mod.Memory().Write(testCtx, offset, tc.memory) 916 require.True(t, memoryWriteOK) 917 918 requireErrno(t, tc.expectedErrno, mod, functionFdRead, uint64(tc.fd), uint64(tc.iovs+offset), uint64(tc.iovsCount+offset), uint64(tc.resultSize+offset)) 919 require.Equal(t, tc.expectedLog, "\n"+log.String()) 920 }) 921 } 922 } 923 924 func Test_fdRead_shouldContinueRead(t *testing.T) { 925 tests := []struct { 926 name string 927 n, l uint32 928 err error 929 expectedOk bool 930 expectedErrno Errno 931 }{ 932 { 933 name: "break when nothing to read", 934 n: 0, 935 l: 0, 936 }, 937 { 938 name: "break when nothing read", 939 n: 0, 940 l: 4, 941 }, 942 { 943 name: "break on partial read", 944 n: 3, 945 l: 4, 946 }, 947 { 948 name: "continue on full read", 949 n: 4, 950 l: 4, 951 expectedOk: true, 952 }, 953 { 954 name: "break on EOF on nothing to read", 955 err: io.EOF, 956 }, 957 { 958 name: "break on EOF on nothing read", 959 l: 4, 960 err: io.EOF, 961 }, 962 { 963 name: "break on EOF on partial read", 964 n: 3, 965 l: 4, 966 err: io.EOF, 967 }, 968 { 969 name: "break on EOF on full read", 970 n: 4, 971 l: 4, 972 err: io.EOF, 973 }, 974 { 975 name: "return ErrnoIo on error on nothing to read", 976 err: io.ErrClosedPipe, 977 expectedErrno: ErrnoIo, 978 }, 979 { 980 name: "return ErrnoIo on error on nothing read", 981 l: 4, 982 err: io.ErrClosedPipe, 983 expectedErrno: ErrnoIo, 984 }, 985 { // Special case, allows processing data before err 986 name: "break on error on partial read", 987 n: 3, 988 l: 4, 989 err: io.ErrClosedPipe, 990 }, 991 { // Special case, allows processing data before err 992 name: "break on error on full read", 993 n: 4, 994 l: 4, 995 err: io.ErrClosedPipe, 996 }, 997 } 998 for _, tt := range tests { 999 tc := tt 1000 1001 t.Run(tc.name, func(t *testing.T) { 1002 ok, errno := fdRead_shouldContinueRead(tc.n, tc.l, tc.err) 1003 require.Equal(t, tc.expectedOk, ok) 1004 require.Equal(t, tc.expectedErrno, errno) 1005 }) 1006 } 1007 } 1008 1009 var ( 1010 fdReadDirFs = fstest.MapFS{ 1011 "notdir": {}, 1012 "emptydir": {Mode: fs.ModeDir}, 1013 "dir": {Mode: fs.ModeDir}, 1014 "dir/-": {}, // len = 24+1 = 25 1015 "dir/a-": {Mode: fs.ModeDir}, // len = 24+2 = 26 1016 "dir/ab-": {}, // len = 24+3 = 27 1017 } 1018 1019 testDirEntries = func() []fs.DirEntry { 1020 entries, err := fdReadDirFs.ReadDir("dir") 1021 if err != nil { 1022 panic(err) 1023 } 1024 return entries 1025 }() 1026 1027 dirent1 = []byte{ 1028 1, 0, 0, 0, 0, 0, 0, 0, // d_next = 1 1029 0, 0, 0, 0, 0, 0, 0, 0, // d_ino = 0 1030 1, 0, 0, 0, // d_namlen = 1 character 1031 4, 0, 0, 0, // d_type = regular_file 1032 '-', // name 1033 } 1034 dirent2 = []byte{ 1035 2, 0, 0, 0, 0, 0, 0, 0, // d_next = 2 1036 0, 0, 0, 0, 0, 0, 0, 0, // d_ino = 0 1037 2, 0, 0, 0, // d_namlen = 1 character 1038 3, 0, 0, 0, // d_type = directory 1039 'a', '-', // name 1040 } 1041 dirent3 = []byte{ 1042 3, 0, 0, 0, 0, 0, 0, 0, // d_next = 3 1043 0, 0, 0, 0, 0, 0, 0, 0, // d_ino = 0 1044 3, 0, 0, 0, // d_namlen = 3 characters 1045 4, 0, 0, 0, // d_type = regular_file 1046 'a', 'b', '-', // name 1047 } 1048 ) 1049 1050 func Test_fdReaddir(t *testing.T) { 1051 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(fdReadDirFs)) 1052 defer r.Close(testCtx) 1053 1054 fsc := mod.(*wasm.CallContext).Sys.FS(testCtx) 1055 1056 fd, err := fsc.OpenFile(testCtx, "dir") 1057 require.NoError(t, err) 1058 1059 tests := []struct { 1060 name string 1061 dir func() *internalsys.FileEntry 1062 buf, bufLen uint32 1063 cookie int64 1064 expectedMem []byte 1065 expectedMemSize int 1066 expectedBufused uint32 1067 expectedReadDir *internalsys.ReadDir 1068 }{ 1069 { 1070 name: "empty dir", 1071 dir: func() *internalsys.FileEntry { 1072 dir, err := fdReadDirFs.Open("emptydir") 1073 require.NoError(t, err) 1074 1075 return &internalsys.FileEntry{File: dir} 1076 }, 1077 buf: 0, bufLen: 1, 1078 cookie: 0, 1079 expectedBufused: 0, 1080 expectedMem: []byte{}, 1081 expectedReadDir: &internalsys.ReadDir{}, 1082 }, 1083 { 1084 name: "full read", 1085 dir: func() *internalsys.FileEntry { 1086 dir, err := fdReadDirFs.Open("dir") 1087 require.NoError(t, err) 1088 1089 return &internalsys.FileEntry{File: dir} 1090 }, 1091 buf: 0, bufLen: 4096, 1092 cookie: 0, 1093 expectedBufused: 78, // length of all entries 1094 expectedMem: append(append(dirent1, dirent2...), dirent3...), 1095 expectedReadDir: &internalsys.ReadDir{ 1096 CountRead: 3, 1097 Entries: testDirEntries, 1098 }, 1099 }, 1100 { 1101 name: "can't read", 1102 dir: func() *internalsys.FileEntry { 1103 dir, err := fdReadDirFs.Open("dir") 1104 require.NoError(t, err) 1105 1106 return &internalsys.FileEntry{File: dir} 1107 }, 1108 buf: 0, bufLen: 23, // length is too short for header 1109 cookie: 0, 1110 expectedBufused: 23, // == bufLen which is the size of the dirent 1111 expectedMem: nil, 1112 expectedReadDir: &internalsys.ReadDir{ 1113 CountRead: 2, 1114 Entries: testDirEntries[:2], 1115 }, 1116 }, 1117 { 1118 name: "can't read name", 1119 dir: func() *internalsys.FileEntry { 1120 dir, err := fdReadDirFs.Open("dir") 1121 require.NoError(t, err) 1122 1123 return &internalsys.FileEntry{File: dir} 1124 }, 1125 buf: 0, bufLen: 24, // length is long enough for first, but not the name. 1126 cookie: 0, 1127 expectedBufused: 24, // == bufLen which is the size of the dirent 1128 expectedMem: dirent1[:24], // header without name 1129 expectedReadDir: &internalsys.ReadDir{ 1130 CountRead: 3, 1131 Entries: testDirEntries, 1132 }, 1133 }, 1134 { 1135 name: "read exactly first", 1136 dir: func() *internalsys.FileEntry { 1137 dir, err := fdReadDirFs.Open("dir") 1138 require.NoError(t, err) 1139 1140 return &internalsys.FileEntry{File: dir} 1141 }, 1142 buf: 0, bufLen: 25, // length is long enough for first + the name, but not more. 1143 cookie: 0, 1144 expectedBufused: 25, // length to read exactly first. 1145 expectedMem: dirent1, 1146 expectedReadDir: &internalsys.ReadDir{ 1147 CountRead: 3, 1148 Entries: testDirEntries, 1149 }, 1150 }, 1151 { 1152 name: "read exactly second", 1153 dir: func() *internalsys.FileEntry { 1154 dir, err := fdReadDirFs.Open("dir") 1155 require.NoError(t, err) 1156 entry, err := dir.(fs.ReadDirFile).ReadDir(1) 1157 require.NoError(t, err) 1158 1159 return &internalsys.FileEntry{ 1160 File: dir, 1161 ReadDir: &internalsys.ReadDir{ 1162 CountRead: 1, 1163 Entries: entry, 1164 }, 1165 } 1166 }, 1167 buf: 0, bufLen: 26, // length is long enough for exactly second. 1168 cookie: 1, // d_next of first 1169 expectedBufused: 26, // length to read exactly second. 1170 expectedMem: dirent2, 1171 expectedReadDir: &internalsys.ReadDir{ 1172 CountRead: 3, 1173 Entries: testDirEntries[1:], 1174 }, 1175 }, 1176 { 1177 name: "read second and a little more", 1178 dir: func() *internalsys.FileEntry { 1179 dir, err := fdReadDirFs.Open("dir") 1180 require.NoError(t, err) 1181 entry, err := dir.(fs.ReadDirFile).ReadDir(1) 1182 require.NoError(t, err) 1183 1184 return &internalsys.FileEntry{ 1185 File: dir, 1186 ReadDir: &internalsys.ReadDir{ 1187 CountRead: 1, 1188 Entries: entry, 1189 }, 1190 } 1191 }, 1192 buf: 0, bufLen: 30, // length is longer than the second entry, but not long enough for a header. 1193 cookie: 1, // d_next of first 1194 expectedBufused: 30, // length to read some more, but not enough for a header, so buf was exhausted. 1195 expectedMem: dirent2, 1196 expectedMemSize: len(dirent2), // we do not want to compare the full buffer since we don't know what the leftover 4 bytes will contain. 1197 expectedReadDir: &internalsys.ReadDir{ 1198 CountRead: 3, 1199 Entries: testDirEntries[1:], 1200 }, 1201 }, 1202 { 1203 name: "read second and header of third", 1204 dir: func() *internalsys.FileEntry { 1205 dir, err := fdReadDirFs.Open("dir") 1206 require.NoError(t, err) 1207 entry, err := dir.(fs.ReadDirFile).ReadDir(1) 1208 require.NoError(t, err) 1209 1210 return &internalsys.FileEntry{ 1211 File: dir, 1212 ReadDir: &internalsys.ReadDir{ 1213 CountRead: 1, 1214 Entries: entry, 1215 }, 1216 } 1217 }, 1218 buf: 0, bufLen: 50, // length is longer than the second entry + enough for the header of third. 1219 cookie: 1, // d_next of first 1220 expectedBufused: 50, // length to read exactly second and the header of third. 1221 expectedMem: append(dirent2, dirent3[0:24]...), 1222 expectedReadDir: &internalsys.ReadDir{ 1223 CountRead: 3, 1224 Entries: testDirEntries[1:], 1225 }, 1226 }, 1227 { 1228 name: "read second and third", 1229 dir: func() *internalsys.FileEntry { 1230 dir, err := fdReadDirFs.Open("dir") 1231 require.NoError(t, err) 1232 entry, err := dir.(fs.ReadDirFile).ReadDir(1) 1233 require.NoError(t, err) 1234 1235 return &internalsys.FileEntry{ 1236 File: dir, 1237 ReadDir: &internalsys.ReadDir{ 1238 CountRead: 1, 1239 Entries: entry, 1240 }, 1241 } 1242 }, 1243 buf: 0, bufLen: 53, // length is long enough for second and third. 1244 cookie: 1, // d_next of first 1245 expectedBufused: 53, // length to read exactly one second and third. 1246 expectedMem: append(dirent2, dirent3...), 1247 expectedReadDir: &internalsys.ReadDir{ 1248 CountRead: 3, 1249 Entries: testDirEntries[1:], 1250 }, 1251 }, 1252 { 1253 name: "read exactly third", 1254 dir: func() *internalsys.FileEntry { 1255 dir, err := fdReadDirFs.Open("dir") 1256 require.NoError(t, err) 1257 two, err := dir.(fs.ReadDirFile).ReadDir(2) 1258 require.NoError(t, err) 1259 1260 return &internalsys.FileEntry{ 1261 File: dir, 1262 ReadDir: &internalsys.ReadDir{ 1263 CountRead: 2, 1264 Entries: two[1:], 1265 }, 1266 } 1267 }, 1268 buf: 0, bufLen: 27, // length is long enough for exactly third. 1269 cookie: 2, // d_next of second. 1270 expectedBufused: 27, // length to read exactly third. 1271 expectedMem: dirent3, 1272 expectedReadDir: &internalsys.ReadDir{ 1273 CountRead: 3, 1274 Entries: testDirEntries[2:], 1275 }, 1276 }, 1277 { 1278 name: "read third and beyond", 1279 dir: func() *internalsys.FileEntry { 1280 dir, err := fdReadDirFs.Open("dir") 1281 require.NoError(t, err) 1282 two, err := dir.(fs.ReadDirFile).ReadDir(2) 1283 require.NoError(t, err) 1284 1285 return &internalsys.FileEntry{ 1286 File: dir, 1287 ReadDir: &internalsys.ReadDir{ 1288 CountRead: 2, 1289 Entries: two[1:], 1290 }, 1291 } 1292 }, 1293 buf: 0, bufLen: 100, // length is long enough for third and more, but there is nothing more. 1294 cookie: 2, // d_next of second. 1295 expectedBufused: 27, // length to read exactly third. 1296 expectedMem: dirent3, 1297 expectedReadDir: &internalsys.ReadDir{ 1298 CountRead: 3, 1299 Entries: testDirEntries[2:], 1300 }, 1301 }, 1302 } 1303 1304 for _, tt := range tests { 1305 tc := tt 1306 t.Run(tc.name, func(t *testing.T) { 1307 defer log.Reset() 1308 1309 // Assign the state we are testing 1310 file, ok := fsc.OpenedFile(testCtx, fd) 1311 require.True(t, ok) 1312 dir := tc.dir() 1313 defer dir.File.Close() 1314 1315 file.File = dir.File 1316 file.ReadDir = dir.ReadDir 1317 1318 maskMemory(t, testCtx, mod, int(tc.bufLen)) 1319 1320 // use an arbitrarily high value for the buf used position. 1321 resultBufused := uint32(16192) 1322 requireErrno(t, ErrnoSuccess, mod, functionFdReaddir, 1323 uint64(fd), uint64(tc.buf), uint64(tc.bufLen), uint64(tc.cookie), uint64(resultBufused)) 1324 1325 // read back the bufused and compare memory against it 1326 bufUsed, ok := mod.Memory().ReadUint32Le(testCtx, resultBufused) 1327 require.True(t, ok) 1328 require.Equal(t, tc.expectedBufused, bufUsed) 1329 1330 mem, ok := mod.Memory().Read(testCtx, tc.buf, bufUsed) 1331 require.True(t, ok) 1332 1333 if tc.expectedMem != nil { 1334 if tc.expectedMemSize == 0 { 1335 tc.expectedMemSize = len(tc.expectedMem) 1336 } 1337 require.Equal(t, tc.expectedMem, mem[:tc.expectedMemSize]) 1338 } 1339 1340 require.Equal(t, tc.expectedReadDir, file.ReadDir) 1341 }) 1342 } 1343 } 1344 1345 func Test_fdReaddir_Errors(t *testing.T) { 1346 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(fdReadDirFs)) 1347 defer r.Close(testCtx) 1348 memLen := mod.Memory().Size(testCtx) 1349 1350 fsc := mod.(*wasm.CallContext).Sys.FS(testCtx) 1351 1352 dirFD, err := fsc.OpenFile(testCtx, "dir") 1353 require.NoError(t, err) 1354 1355 fileFD, err := fsc.OpenFile(testCtx, "notdir") 1356 require.NoError(t, err) 1357 1358 tests := []struct { 1359 name string 1360 dir func() *internalsys.FileEntry 1361 fd, buf, bufLen, resultBufused uint32 1362 cookie int64 1363 readDir *internalsys.ReadDir 1364 expectedErrno Errno 1365 expectedLog string 1366 }{ 1367 { 1368 name: "out-of-memory reading buf", 1369 fd: dirFD, 1370 buf: memLen, 1371 bufLen: 1000, 1372 expectedErrno: ErrnoFault, 1373 expectedLog: ` 1374 --> proxy.fd_readdir(fd=4,buf=65536,buf_len=1000,cookie=0,result.bufused=0) 1375 ==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=65536,buf_len=1000,cookie=0,result.bufused=0) 1376 <== EFAULT 1377 <-- (21) 1378 `, 1379 }, 1380 { 1381 name: "invalid fd", 1382 fd: 42, // arbitrary invalid fd 1383 expectedErrno: ErrnoBadf, 1384 expectedLog: ` 1385 --> proxy.fd_readdir(fd=42,buf=0,buf_len=0,cookie=0,result.bufused=0) 1386 ==> wasi_snapshot_preview1.fd_readdir(fd=42,buf=0,buf_len=0,cookie=0,result.bufused=0) 1387 <== EBADF 1388 <-- (8) 1389 `, 1390 }, 1391 { 1392 name: "not a dir", 1393 fd: fileFD, 1394 expectedErrno: ErrnoNotdir, 1395 expectedLog: ` 1396 --> proxy.fd_readdir(fd=5,buf=0,buf_len=0,cookie=0,result.bufused=0) 1397 ==> wasi_snapshot_preview1.fd_readdir(fd=5,buf=0,buf_len=0,cookie=0,result.bufused=0) 1398 <== ENOTDIR 1399 <-- (54) 1400 `, 1401 }, 1402 { 1403 name: "out-of-memory reading buf", 1404 fd: dirFD, 1405 buf: memLen, 1406 bufLen: 1000, 1407 expectedErrno: ErrnoFault, 1408 expectedLog: ` 1409 --> proxy.fd_readdir(fd=4,buf=65536,buf_len=1000,cookie=0,result.bufused=0) 1410 ==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=65536,buf_len=1000,cookie=0,result.bufused=0) 1411 <== EFAULT 1412 <-- (21) 1413 `, 1414 }, 1415 { 1416 name: "out-of-memory reading bufLen", 1417 fd: dirFD, 1418 buf: memLen - 1, 1419 bufLen: 1000, 1420 expectedErrno: ErrnoFault, 1421 expectedLog: ` 1422 --> proxy.fd_readdir(fd=4,buf=65535,buf_len=1000,cookie=0,result.bufused=0) 1423 ==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=65535,buf_len=1000,cookie=0,result.bufused=0) 1424 <== EFAULT 1425 <-- (21) 1426 `, 1427 }, 1428 { 1429 name: "resultBufused is outside memory", 1430 fd: dirFD, 1431 buf: 0, bufLen: 1, 1432 resultBufused: memLen, 1433 expectedErrno: ErrnoFault, 1434 expectedLog: ` 1435 --> proxy.fd_readdir(fd=4,buf=0,buf_len=1,cookie=0,result.bufused=65536) 1436 ==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=0,buf_len=1,cookie=0,result.bufused=65536) 1437 <== EFAULT 1438 <-- (21) 1439 `, 1440 }, 1441 { 1442 name: "cookie invalid when no prior state", 1443 fd: dirFD, 1444 buf: 0, bufLen: 1000, 1445 cookie: 1, 1446 resultBufused: 2000, 1447 expectedErrno: ErrnoInval, 1448 expectedLog: ` 1449 --> proxy.fd_readdir(fd=4,buf=0,buf_len=1000,cookie=1,result.bufused=2000) 1450 ==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=0,buf_len=1000,cookie=1,result.bufused=2000) 1451 <== EINVAL 1452 <-- (28) 1453 `, 1454 }, 1455 { 1456 name: "negative cookie invalid", 1457 fd: dirFD, 1458 buf: 0, bufLen: 1000, 1459 cookie: -1, 1460 readDir: &internalsys.ReadDir{CountRead: 1}, 1461 resultBufused: 2000, 1462 expectedErrno: ErrnoInval, 1463 expectedLog: ` 1464 --> proxy.fd_readdir(fd=4,buf=0,buf_len=1000,cookie=18446744073709551615,result.bufused=2000) 1465 ==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=0,buf_len=1000,cookie=18446744073709551615,result.bufused=2000) 1466 <== EINVAL 1467 <-- (28) 1468 `, 1469 }, 1470 } 1471 1472 for _, tt := range tests { 1473 tc := tt 1474 t.Run(tc.name, func(t *testing.T) { 1475 defer log.Reset() 1476 1477 // Reset the directory so that tests don't taint each other. 1478 if file, ok := fsc.OpenedFile(testCtx, tc.fd); ok && tc.fd == dirFD { 1479 dir, err := fdReadDirFs.Open("dir") 1480 require.NoError(t, err) 1481 defer dir.Close() 1482 1483 file.File = dir 1484 file.ReadDir = nil 1485 } 1486 1487 requireErrno(t, tc.expectedErrno, mod, functionFdReaddir, 1488 uint64(tc.fd), uint64(tc.buf), uint64(tc.bufLen), uint64(tc.cookie), uint64(tc.resultBufused)) 1489 require.Equal(t, tc.expectedLog, "\n"+log.String()) 1490 }) 1491 } 1492 } 1493 1494 func Test_lastDirEntries(t *testing.T) { 1495 tests := []struct { 1496 name string 1497 f *internalsys.ReadDir 1498 cookie int64 1499 expectedEntries []fs.DirEntry 1500 expectedErrno Errno 1501 }{ 1502 { 1503 name: "no prior call", 1504 }, 1505 { 1506 name: "no prior call, but passed a cookie", 1507 cookie: 1, 1508 expectedErrno: ErrnoInval, 1509 }, 1510 { 1511 name: "cookie is negative", 1512 f: &internalsys.ReadDir{ 1513 CountRead: 3, 1514 Entries: testDirEntries, 1515 }, 1516 cookie: -1, 1517 expectedErrno: ErrnoInval, 1518 }, 1519 { 1520 name: "cookie is greater than last d_next", 1521 f: &internalsys.ReadDir{ 1522 CountRead: 3, 1523 Entries: testDirEntries, 1524 }, 1525 cookie: 5, 1526 expectedErrno: ErrnoInval, 1527 }, 1528 { 1529 name: "cookie is last pos", 1530 f: &internalsys.ReadDir{ 1531 CountRead: 3, 1532 Entries: testDirEntries, 1533 }, 1534 cookie: 3, 1535 expectedEntries: nil, 1536 }, 1537 { 1538 name: "cookie is one before last pos", 1539 f: &internalsys.ReadDir{ 1540 CountRead: 3, 1541 Entries: testDirEntries, 1542 }, 1543 cookie: 2, 1544 expectedEntries: testDirEntries[2:], 1545 }, 1546 { 1547 name: "cookie is before current entries", 1548 f: &internalsys.ReadDir{ 1549 CountRead: 5, 1550 Entries: testDirEntries, 1551 }, 1552 cookie: 1, 1553 expectedErrno: ErrnoNosys, // not implemented 1554 }, 1555 } 1556 1557 for _, tt := range tests { 1558 tc := tt 1559 1560 t.Run(tc.name, func(t *testing.T) { 1561 f := tc.f 1562 if f == nil { 1563 f = &internalsys.ReadDir{} 1564 } 1565 entries, errno := lastDirEntries(f, tc.cookie) 1566 require.Equal(t, tc.expectedErrno, errno) 1567 require.Equal(t, tc.expectedEntries, entries) 1568 }) 1569 } 1570 } 1571 1572 func Test_maxDirents(t *testing.T) { 1573 tests := []struct { 1574 name string 1575 entries []fs.DirEntry 1576 maxLen uint32 1577 expectedCount uint32 1578 expectedwriteTruncatedEntry bool 1579 expectedBufused uint32 1580 }{ 1581 { 1582 name: "no entries", 1583 }, 1584 { 1585 name: "can't fit one", 1586 entries: testDirEntries, 1587 maxLen: 23, 1588 expectedBufused: 23, 1589 expectedwriteTruncatedEntry: false, 1590 }, 1591 { 1592 name: "only fits header", 1593 entries: testDirEntries, 1594 maxLen: 24, 1595 expectedBufused: 24, 1596 expectedwriteTruncatedEntry: true, 1597 }, 1598 { 1599 name: "one", 1600 entries: testDirEntries, 1601 maxLen: 25, 1602 expectedCount: 1, 1603 expectedBufused: 25, 1604 }, 1605 { 1606 name: "one but not room for two's name", 1607 entries: testDirEntries, 1608 maxLen: 25 + 25, 1609 expectedCount: 1, 1610 expectedwriteTruncatedEntry: true, // can write direntSize 1611 expectedBufused: 25 + 25, 1612 }, 1613 { 1614 name: "two", 1615 entries: testDirEntries, 1616 maxLen: 25 + 26, 1617 expectedCount: 2, 1618 expectedBufused: 25 + 26, 1619 }, 1620 { 1621 name: "two but not three's dirent", 1622 entries: testDirEntries, 1623 maxLen: 25 + 26 + 20, 1624 expectedCount: 2, 1625 expectedwriteTruncatedEntry: false, // 20 + 4 == direntSize 1626 expectedBufused: 25 + 26 + 20, 1627 }, 1628 { 1629 name: "two but not three's name", 1630 entries: testDirEntries, 1631 maxLen: 25 + 26 + 26, 1632 expectedCount: 2, 1633 expectedwriteTruncatedEntry: true, // can write direntSize 1634 expectedBufused: 25 + 26 + 26, 1635 }, 1636 { 1637 name: "three", 1638 entries: testDirEntries, 1639 maxLen: 25 + 26 + 27, 1640 expectedCount: 3, 1641 expectedwriteTruncatedEntry: false, // end of dir 1642 expectedBufused: 25 + 26 + 27, 1643 }, 1644 { 1645 name: "max", 1646 entries: testDirEntries, 1647 maxLen: 100, 1648 expectedCount: 3, 1649 expectedwriteTruncatedEntry: false, // end of dir 1650 expectedBufused: 25 + 26 + 27, 1651 }, 1652 } 1653 1654 for _, tt := range tests { 1655 tc := tt 1656 1657 t.Run(tc.name, func(t *testing.T) { 1658 bufused, direntCount, writeTruncatedEntry := maxDirents(tc.entries, tc.maxLen) 1659 require.Equal(t, tc.expectedCount, direntCount) 1660 require.Equal(t, tc.expectedwriteTruncatedEntry, writeTruncatedEntry) 1661 require.Equal(t, tc.expectedBufused, bufused) 1662 }) 1663 } 1664 } 1665 1666 func Test_writeDirents(t *testing.T) { 1667 tests := []struct { 1668 name string 1669 entries []fs.DirEntry 1670 entryCount uint32 1671 writeTruncatedEntry bool 1672 expectedEntriesBuf []byte 1673 }{ 1674 { 1675 name: "none", 1676 entries: testDirEntries, 1677 }, 1678 { 1679 name: "one", 1680 entries: testDirEntries, 1681 entryCount: 1, 1682 expectedEntriesBuf: dirent1, 1683 }, 1684 { 1685 name: "two", 1686 entries: testDirEntries, 1687 entryCount: 2, 1688 expectedEntriesBuf: append(dirent1, dirent2...), 1689 }, 1690 { 1691 name: "two with truncated", 1692 entries: testDirEntries, 1693 entryCount: 2, 1694 writeTruncatedEntry: true, 1695 expectedEntriesBuf: append(append(dirent1, dirent2...), dirent3[0:10]...), 1696 }, 1697 { 1698 name: "three", 1699 entries: testDirEntries, 1700 entryCount: 3, 1701 expectedEntriesBuf: append(append(dirent1, dirent2...), dirent3...), 1702 }, 1703 } 1704 1705 for _, tt := range tests { 1706 tc := tt 1707 1708 t.Run(tc.name, func(t *testing.T) { 1709 cookie := uint64(1) 1710 entriesBuf := make([]byte, len(tc.expectedEntriesBuf)) 1711 writeDirents(tc.entries, tc.entryCount, tc.writeTruncatedEntry, entriesBuf, cookie) 1712 require.Equal(t, tc.expectedEntriesBuf, entriesBuf) 1713 }) 1714 } 1715 } 1716 1717 // Test_fdRenumber only tests it is stubbed for GrainLang per #271 1718 func Test_fdRenumber(t *testing.T) { 1719 log := requireErrnoNosys(t, functionFdRenumber, 0, 0) 1720 require.Equal(t, ` 1721 --> proxy.fd_renumber(fd=0,to=0) 1722 --> wasi_snapshot_preview1.fd_renumber(fd=0,to=0) 1723 <-- ENOSYS 1724 <-- (52) 1725 `, log) 1726 } 1727 1728 func Test_fdSeek(t *testing.T) { 1729 mod, fd, log, r := requireOpenFile(t, "/test_path", []byte("wazero")) 1730 defer r.Close(testCtx) 1731 1732 resultNewoffset := uint32(1) // arbitrary offset in api.Memory for the new offset value 1733 1734 tests := []struct { 1735 name string 1736 offset int64 1737 whence int 1738 expectedOffset int64 1739 expectedMemory []byte 1740 expectedLog string 1741 }{ 1742 { 1743 name: "SeekStart", 1744 offset: 4, // arbitrary offset 1745 whence: io.SeekStart, 1746 expectedOffset: 4, // = offset 1747 expectedMemory: []byte{ 1748 '?', // resultNewoffset is after this 1749 4, 0, 0, 0, 0, 0, 0, 0, // = expectedOffset 1750 '?', 1751 }, 1752 expectedLog: ` 1753 --> proxy.fd_seek(fd=4,offset=4,whence=0,result.newoffset=1) 1754 ==> wasi_snapshot_preview1.fd_seek(fd=4,offset=4,whence=0,result.newoffset=1) 1755 <== ESUCCESS 1756 <-- (0) 1757 `, 1758 }, 1759 { 1760 name: "SeekCurrent", 1761 offset: 1, // arbitrary offset 1762 whence: io.SeekCurrent, 1763 expectedOffset: 2, // = 1 (the initial offset of the test file) + 1 (offset) 1764 expectedMemory: []byte{ 1765 '?', // resultNewoffset is after this 1766 2, 0, 0, 0, 0, 0, 0, 0, // = expectedOffset 1767 '?', 1768 }, 1769 expectedLog: ` 1770 --> proxy.fd_seek(fd=4,offset=1,whence=1,result.newoffset=1) 1771 ==> wasi_snapshot_preview1.fd_seek(fd=4,offset=1,whence=1,result.newoffset=1) 1772 <== ESUCCESS 1773 <-- (0) 1774 `, 1775 }, 1776 { 1777 name: "SeekEnd", 1778 offset: -1, // arbitrary offset, note that offset can be negative 1779 whence: io.SeekEnd, 1780 expectedOffset: 5, // = 6 (the size of the test file with content "wazero") + -1 (offset) 1781 expectedMemory: []byte{ 1782 '?', // resultNewoffset is after this 1783 5, 0, 0, 0, 0, 0, 0, 0, // = expectedOffset 1784 '?', 1785 }, 1786 expectedLog: ` 1787 --> proxy.fd_seek(fd=4,offset=18446744073709551615,whence=2,result.newoffset=1) 1788 ==> wasi_snapshot_preview1.fd_seek(fd=4,offset=18446744073709551615,whence=2,result.newoffset=1) 1789 <== ESUCCESS 1790 <-- (0) 1791 `, 1792 }, 1793 } 1794 1795 for _, tt := range tests { 1796 tc := tt 1797 t.Run(tc.name, func(t *testing.T) { 1798 defer log.Reset() 1799 1800 maskMemory(t, testCtx, mod, len(tc.expectedMemory)) 1801 1802 // Since we initialized this file, we know it is a seeker (because it is a MapFile) 1803 fsc := mod.(*wasm.CallContext).Sys.FS(testCtx) 1804 f, ok := fsc.OpenedFile(testCtx, fd) 1805 require.True(t, ok) 1806 seeker := f.File.(io.Seeker) 1807 1808 // set the initial offset of the file to 1 1809 offset, err := seeker.Seek(1, io.SeekStart) 1810 require.NoError(t, err) 1811 require.Equal(t, int64(1), offset) 1812 1813 requireErrno(t, ErrnoSuccess, mod, functionFdSeek, uint64(fd), uint64(tc.offset), uint64(tc.whence), uint64(resultNewoffset)) 1814 require.Equal(t, tc.expectedLog, "\n"+log.String()) 1815 1816 actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(tc.expectedMemory))) 1817 require.True(t, ok) 1818 require.Equal(t, tc.expectedMemory, actual) 1819 1820 offset, err = seeker.Seek(0, io.SeekCurrent) 1821 require.NoError(t, err) 1822 require.Equal(t, tc.expectedOffset, offset) // test that the offset of file is actually updated. 1823 }) 1824 } 1825 } 1826 1827 func Test_fdSeek_Errors(t *testing.T) { 1828 mod, fd, log, r := requireOpenFile(t, "/test_path", []byte("wazero")) 1829 defer r.Close(testCtx) 1830 1831 memorySize := mod.Memory().Size(testCtx) 1832 1833 tests := []struct { 1834 name string 1835 fd uint32 1836 offset uint64 1837 whence, resultNewoffset uint32 1838 expectedErrno Errno 1839 expectedLog string 1840 }{ 1841 { 1842 name: "invalid fd", 1843 fd: 42, // arbitrary invalid fd 1844 expectedErrno: ErrnoBadf, 1845 expectedLog: ` 1846 --> proxy.fd_seek(fd=42,offset=0,whence=0,result.newoffset=0) 1847 ==> wasi_snapshot_preview1.fd_seek(fd=42,offset=0,whence=0,result.newoffset=0) 1848 <== EBADF 1849 <-- (8) 1850 `, 1851 }, 1852 { 1853 name: "invalid whence", 1854 fd: fd, 1855 whence: 3, // invalid whence, the largest whence io.SeekEnd(2) + 1 1856 expectedErrno: ErrnoInval, 1857 expectedLog: ` 1858 --> proxy.fd_seek(fd=4,offset=0,whence=3,result.newoffset=0) 1859 ==> wasi_snapshot_preview1.fd_seek(fd=4,offset=0,whence=3,result.newoffset=0) 1860 <== EINVAL 1861 <-- (28) 1862 `, 1863 }, 1864 { 1865 name: "out-of-memory writing resultNewoffset", 1866 fd: fd, 1867 resultNewoffset: memorySize, 1868 expectedErrno: ErrnoFault, 1869 expectedLog: ` 1870 --> proxy.fd_seek(fd=4,offset=0,whence=0,result.newoffset=65536) 1871 ==> wasi_snapshot_preview1.fd_seek(fd=4,offset=0,whence=0,result.newoffset=65536) 1872 <== EFAULT 1873 <-- (21) 1874 `, 1875 }, 1876 } 1877 1878 for _, tt := range tests { 1879 tc := tt 1880 t.Run(tc.name, func(t *testing.T) { 1881 defer log.Reset() 1882 1883 requireErrno(t, tc.expectedErrno, mod, functionFdSeek, uint64(tc.fd), tc.offset, uint64(tc.whence), uint64(tc.resultNewoffset)) 1884 require.Equal(t, tc.expectedLog, "\n"+log.String()) 1885 }) 1886 } 1887 } 1888 1889 // Test_fdSync only tests it is stubbed for GrainLang per #271 1890 func Test_fdSync(t *testing.T) { 1891 log := requireErrnoNosys(t, functionFdSync, 0) 1892 require.Equal(t, ` 1893 --> proxy.fd_sync(fd=0) 1894 --> wasi_snapshot_preview1.fd_sync(fd=0) 1895 <-- ENOSYS 1896 <-- (52) 1897 `, log) 1898 } 1899 1900 // Test_fdTell only tests it is stubbed for GrainLang per #271 1901 func Test_fdTell(t *testing.T) { 1902 log := requireErrnoNosys(t, functionFdTell, 0, 0) 1903 require.Equal(t, ` 1904 --> proxy.fd_tell(fd=0,result.offset=0) 1905 --> wasi_snapshot_preview1.fd_tell(fd=0,result.offset=0) 1906 <-- ENOSYS 1907 <-- (52) 1908 `, log) 1909 } 1910 1911 func Test_fdWrite(t *testing.T) { 1912 tmpDir := t.TempDir() // open before loop to ensure no locking problems. 1913 pathName := "test_path" 1914 mod, fd, log, r := requireOpenWritableFile(t, tmpDir, pathName) 1915 defer r.Close(testCtx) 1916 1917 iovs := uint32(1) // arbitrary offset 1918 initialMemory := []byte{ 1919 '?', // `iovs` is after this 1920 18, 0, 0, 0, // = iovs[0].offset 1921 4, 0, 0, 0, // = iovs[0].length 1922 23, 0, 0, 0, // = iovs[1].offset 1923 2, 0, 0, 0, // = iovs[1].length 1924 '?', // iovs[0].offset is after this 1925 'w', 'a', 'z', 'e', // iovs[0].length bytes 1926 '?', // iovs[1].offset is after this 1927 'r', 'o', // iovs[1].length bytes 1928 '?', 1929 } 1930 iovsCount := uint32(2) // The count of iovs 1931 resultSize := uint32(26) // arbitrary offset 1932 expectedMemory := append( 1933 initialMemory, 1934 6, 0, 0, 0, // sum(iovs[...].length) == length of "wazero" 1935 '?', 1936 ) 1937 1938 maskMemory(t, testCtx, mod, len(expectedMemory)) 1939 ok := mod.Memory().Write(testCtx, 0, initialMemory) 1940 require.True(t, ok) 1941 1942 requireErrno(t, ErrnoSuccess, mod, functionFdWrite, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(resultSize)) 1943 require.Equal(t, ` 1944 --> proxy.fd_write(fd=4,iovs=1,iovs_len=2,result.size=26) 1945 ==> wasi_snapshot_preview1.fd_write(fd=4,iovs=1,iovs_len=2,result.size=26) 1946 <== ESUCCESS 1947 <-- (0) 1948 `, "\n"+log.String()) 1949 1950 actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory))) 1951 require.True(t, ok) 1952 require.Equal(t, expectedMemory, actual) 1953 1954 // Since we initialized this file, we know we can read it by path 1955 buf, err := os.ReadFile(path.Join(tmpDir, pathName)) 1956 require.NoError(t, err) 1957 1958 require.Equal(t, []byte("wazero"), buf) // verify the file was actually written 1959 } 1960 1961 // Test_fdWrite_discard ensures default configuration doesn't add needless 1962 // overhead, but still returns valid data. For example, writing to STDOUT when 1963 // it is io.Discard. 1964 func Test_fdWrite_discard(t *testing.T) { 1965 // Default has io.Discard as stdout/stderr 1966 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig()) 1967 defer r.Close(testCtx) 1968 1969 iovs := uint32(1) // arbitrary offset 1970 initialMemory := []byte{ 1971 '?', // `iovs` is after this 1972 18, 0, 0, 0, // = iovs[0].offset 1973 4, 0, 0, 0, // = iovs[0].length 1974 23, 0, 0, 0, // = iovs[1].offset 1975 2, 0, 0, 0, // = iovs[1].length 1976 '?', // iovs[0].offset is after this 1977 'w', 'a', 'z', 'e', // iovs[0].length bytes 1978 '?', // iovs[1].offset is after this 1979 'r', 'o', // iovs[1].length bytes 1980 '?', 1981 } 1982 iovsCount := uint32(2) // The count of iovs 1983 resultSize := uint32(26) // arbitrary offset 1984 expectedMemory := append( 1985 initialMemory, 1986 6, 0, 0, 0, // sum(iovs[...].length) == length of "wazero" 1987 '?', 1988 ) 1989 1990 maskMemory(t, testCtx, mod, len(expectedMemory)) 1991 ok := mod.Memory().Write(testCtx, 0, initialMemory) 1992 require.True(t, ok) 1993 1994 fd := 1 // stdout 1995 requireErrno(t, ErrnoSuccess, mod, functionFdWrite, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(resultSize)) 1996 require.Equal(t, ` 1997 --> proxy.fd_write(fd=1,iovs=1,iovs_len=2,result.size=26) 1998 ==> wasi_snapshot_preview1.fd_write(fd=1,iovs=1,iovs_len=2,result.size=26) 1999 <== ESUCCESS 2000 <-- (0) 2001 `, "\n"+log.String()) 2002 2003 actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory))) 2004 require.True(t, ok) 2005 require.Equal(t, expectedMemory, actual) 2006 } 2007 2008 func Test_fdWrite_Errors(t *testing.T) { 2009 tmpDir := t.TempDir() // open before loop to ensure no locking problems. 2010 pathName := "test_path" 2011 mod, fd, log, r := requireOpenWritableFile(t, tmpDir, pathName) 2012 defer r.Close(testCtx) 2013 2014 // Setup valid test memory 2015 iovs, iovsCount := uint32(0), uint32(1) 2016 memory := []byte{ 2017 8, 0, 0, 0, // = iovs[0].offset (where the data "hi" begins) 2018 2, 0, 0, 0, // = iovs[0].length (how many bytes are in "hi") 2019 'h', 'i', // iovs[0].length bytes 2020 } 2021 2022 tests := []struct { 2023 name string 2024 fd, resultSize uint32 2025 memory []byte 2026 expectedErrno Errno 2027 expectedLog string 2028 }{ 2029 { 2030 name: "invalid fd", 2031 fd: 42, // arbitrary invalid fd 2032 expectedErrno: ErrnoBadf, 2033 expectedLog: ` 2034 --> proxy.fd_write(fd=42,iovs=0,iovs_len=1,result.size=0) 2035 ==> wasi_snapshot_preview1.fd_write(fd=42,iovs=0,iovs_len=1,result.size=0) 2036 <== EBADF 2037 <-- (8) 2038 `, 2039 }, 2040 { 2041 name: "out-of-memory reading iovs[0].offset", 2042 fd: fd, 2043 memory: []byte{}, 2044 expectedErrno: ErrnoFault, 2045 expectedLog: ` 2046 --> proxy.fd_write(fd=4,iovs=0,iovs_len=1,result.size=0) 2047 ==> wasi_snapshot_preview1.fd_write(fd=4,iovs=0,iovs_len=1,result.size=0) 2048 <== EFAULT 2049 <-- (21) 2050 `, 2051 }, 2052 { 2053 name: "out-of-memory reading iovs[0].length", 2054 fd: fd, 2055 memory: memory[0:4], // iovs[0].offset was 4 bytes and iovs[0].length next, but not enough mod.Memory()! 2056 expectedErrno: ErrnoFault, 2057 expectedLog: ` 2058 --> proxy.fd_write(fd=4,iovs=0,iovs_len=1,result.size=0) 2059 ==> wasi_snapshot_preview1.fd_write(fd=4,iovs=0,iovs_len=1,result.size=0) 2060 <== EFAULT 2061 <-- (21) 2062 `, 2063 }, 2064 { 2065 name: "iovs[0].offset is outside memory", 2066 fd: fd, 2067 memory: memory[0:8], // iovs[0].offset (where to read "hi") is outside memory. 2068 expectedErrno: ErrnoFault, 2069 expectedLog: ` 2070 --> proxy.fd_write(fd=4,iovs=0,iovs_len=1,result.size=0) 2071 ==> wasi_snapshot_preview1.fd_write(fd=4,iovs=0,iovs_len=1,result.size=0) 2072 <== EFAULT 2073 <-- (21) 2074 `, 2075 }, 2076 { 2077 name: "length to read exceeds memory by 1", 2078 fd: fd, 2079 memory: memory[0:9], // iovs[0].offset (where to read "hi") is in memory, but truncated. 2080 expectedErrno: ErrnoFault, 2081 expectedLog: ` 2082 --> proxy.fd_write(fd=4,iovs=0,iovs_len=1,result.size=0) 2083 ==> wasi_snapshot_preview1.fd_write(fd=4,iovs=0,iovs_len=1,result.size=0) 2084 <== EFAULT 2085 <-- (21) 2086 `, 2087 }, 2088 { 2089 name: "resultSize offset is outside memory", 2090 fd: fd, 2091 memory: memory, 2092 resultSize: uint32(len(memory)), // read was ok, but there wasn't enough memory to write the result. 2093 expectedErrno: ErrnoFault, 2094 expectedLog: ` 2095 --> proxy.fd_write(fd=4,iovs=0,iovs_len=1,result.size=10) 2096 ==> wasi_snapshot_preview1.fd_write(fd=4,iovs=0,iovs_len=1,result.size=10) 2097 <== EFAULT 2098 <-- (21) 2099 `, 2100 }, 2101 } 2102 2103 for _, tt := range tests { 2104 tc := tt 2105 t.Run(tc.name, func(t *testing.T) { 2106 defer log.Reset() 2107 2108 mod.Memory().(*wasm.MemoryInstance).Buffer = tc.memory 2109 2110 requireErrno(t, tc.expectedErrno, mod, functionFdWrite, uint64(tc.fd), uint64(iovs), uint64(iovsCount), 2111 uint64(tc.resultSize)) 2112 require.Equal(t, tc.expectedLog, "\n"+log.String()) 2113 }) 2114 } 2115 } 2116 2117 // Test_pathCreateDirectory only tests it is stubbed for GrainLang per #271 2118 func Test_pathCreateDirectory(t *testing.T) { 2119 log := requireErrnoNosys(t, functionPathCreateDirectory, 0, 0, 0) 2120 require.Equal(t, ` 2121 --> proxy.path_create_directory(fd=0,path=0,path_len=0) 2122 --> wasi_snapshot_preview1.path_create_directory(fd=0,path=0,path_len=0) 2123 <-- ENOSYS 2124 <-- (52) 2125 `, log) 2126 } 2127 2128 func Test_pathFilestatGet(t *testing.T) { 2129 file, dir := "a", "b" 2130 testFS := fstest.MapFS{ 2131 file: {Data: make([]byte, 10), ModTime: time.Unix(1667482413, 0)}, 2132 dir: {Mode: fs.ModeDir, ModTime: time.Unix(1667482413, 0)}, 2133 dir + "/" + file: {Data: make([]byte, 20), ModTime: time.Unix(1667482413, 0)}, 2134 } 2135 2136 initialMemoryFile := append([]byte{'?'}, file...) 2137 initialMemoryDir := append([]byte{'?'}, dir...) 2138 initialMemoryNotExists := []byte{'?', '?'} 2139 2140 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(testFS)) 2141 defer r.Close(testCtx) 2142 memorySize := mod.Memory().Size(testCtx) 2143 2144 // open both paths without using WASI 2145 fsc := mod.(*wasm.CallContext).Sys.FS(testCtx) 2146 2147 rootFd := uint32(3) // after stderr 2148 2149 fileFd, err := fsc.OpenFile(testCtx, file) 2150 require.NoError(t, err) 2151 2152 dirFd, err := fsc.OpenFile(testCtx, dir) 2153 require.NoError(t, err) 2154 2155 tests := []struct { 2156 name string 2157 fd, pathLen, resultFilestat uint32 2158 memory, expectedMemory []byte 2159 expectedErrno Errno 2160 expectedLog string 2161 }{ 2162 { 2163 name: "file under root", 2164 fd: rootFd, 2165 memory: initialMemoryFile, 2166 pathLen: 1, 2167 resultFilestat: 2, 2168 expectedMemory: append( 2169 initialMemoryFile, 2170 '?', '?', '?', '?', '?', '?', '?', '?', // dev 2171 '?', '?', '?', '?', '?', '?', '?', '?', // ino 2172 4, '?', '?', '?', '?', '?', '?', '?', // filetype + padding 2173 '?', '?', '?', '?', '?', '?', '?', '?', // nlink 2174 10, 0, 0, 0, 0, 0, 0, 0, // size 2175 0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // atim 2176 0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // mtim 2177 0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // ctim 2178 ), 2179 expectedLog: ` 2180 --> proxy.path_filestat_get(fd=3,flags=0,path=1,path_len=1,result.buf=2) 2181 ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=0,path=1,path_len=1,result.buf=2) 2182 <== ESUCCESS 2183 <-- (0) 2184 `, 2185 }, 2186 { 2187 name: "file under dir", 2188 fd: dirFd, // root 2189 memory: initialMemoryFile, 2190 pathLen: 1, 2191 resultFilestat: 2, 2192 expectedMemory: append( 2193 initialMemoryFile, 2194 '?', '?', '?', '?', '?', '?', '?', '?', // dev 2195 '?', '?', '?', '?', '?', '?', '?', '?', // ino 2196 4, '?', '?', '?', '?', '?', '?', '?', // filetype + padding 2197 '?', '?', '?', '?', '?', '?', '?', '?', // nlink 2198 20, 0, 0, 0, 0, 0, 0, 0, // size 2199 0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // atim 2200 0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // mtim 2201 0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // ctim 2202 ), 2203 expectedLog: ` 2204 --> proxy.path_filestat_get(fd=5,flags=0,path=1,path_len=1,result.buf=2) 2205 ==> wasi_snapshot_preview1.path_filestat_get(fd=5,flags=0,path=1,path_len=1,result.buf=2) 2206 <== ESUCCESS 2207 <-- (0) 2208 `, 2209 }, 2210 { 2211 name: "dir under root", 2212 fd: rootFd, 2213 memory: initialMemoryDir, 2214 pathLen: 1, 2215 resultFilestat: 2, 2216 expectedMemory: append( 2217 initialMemoryDir, 2218 '?', '?', '?', '?', '?', '?', '?', '?', // dev 2219 '?', '?', '?', '?', '?', '?', '?', '?', // ino 2220 3, '?', '?', '?', '?', '?', '?', '?', // filetype + padding 2221 '?', '?', '?', '?', '?', '?', '?', '?', // nlink 2222 0, 0, 0, 0, 0, 0, 0, 0, // size 2223 0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // atim 2224 0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // mtim 2225 0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // ctim 2226 ), 2227 expectedLog: ` 2228 --> proxy.path_filestat_get(fd=3,flags=0,path=1,path_len=1,result.buf=2) 2229 ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=0,path=1,path_len=1,result.buf=2) 2230 <== ESUCCESS 2231 <-- (0) 2232 `, 2233 }, 2234 { 2235 name: "bad FD - not opened", 2236 fd: math.MaxUint32, 2237 expectedErrno: ErrnoBadf, 2238 expectedLog: ` 2239 --> proxy.path_filestat_get(fd=4294967295,flags=0,path=1,path_len=0,result.buf=0) 2240 ==> wasi_snapshot_preview1.path_filestat_get(fd=4294967295,flags=0,path=1,path_len=0,result.buf=0) 2241 <== EBADF 2242 <-- (8) 2243 `, 2244 }, 2245 { 2246 name: "bad FD - not dir", 2247 fd: fileFd, 2248 memory: initialMemoryFile, 2249 pathLen: 1, 2250 resultFilestat: 2, 2251 expectedErrno: ErrnoNotdir, 2252 expectedLog: ` 2253 --> proxy.path_filestat_get(fd=4,flags=0,path=1,path_len=1,result.buf=2) 2254 ==> wasi_snapshot_preview1.path_filestat_get(fd=4,flags=0,path=1,path_len=1,result.buf=2) 2255 <== ENOTDIR 2256 <-- (54) 2257 `, 2258 }, 2259 { 2260 name: "path under root doesn't exist", 2261 fd: rootFd, 2262 memory: initialMemoryNotExists, 2263 pathLen: 1, 2264 resultFilestat: 2, 2265 expectedErrno: ErrnoNoent, 2266 expectedLog: ` 2267 --> proxy.path_filestat_get(fd=3,flags=0,path=1,path_len=1,result.buf=2) 2268 ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=0,path=1,path_len=1,result.buf=2) 2269 <== ENOENT 2270 <-- (44) 2271 `, 2272 }, 2273 { 2274 name: "path under dir doesn't exist", 2275 fd: dirFd, 2276 memory: initialMemoryNotExists, 2277 pathLen: 1, 2278 resultFilestat: 2, 2279 expectedErrno: ErrnoNoent, 2280 expectedLog: ` 2281 --> proxy.path_filestat_get(fd=5,flags=0,path=1,path_len=1,result.buf=2) 2282 ==> wasi_snapshot_preview1.path_filestat_get(fd=5,flags=0,path=1,path_len=1,result.buf=2) 2283 <== ENOENT 2284 <-- (44) 2285 `, 2286 }, 2287 { 2288 name: "path invalid", 2289 fd: dirFd, 2290 memory: []byte("?../foo"), 2291 pathLen: 6, 2292 resultFilestat: 7, 2293 expectedErrno: ErrnoNoent, 2294 expectedLog: ` 2295 --> proxy.path_filestat_get(fd=5,flags=0,path=1,path_len=6,result.buf=7) 2296 ==> wasi_snapshot_preview1.path_filestat_get(fd=5,flags=0,path=1,path_len=6,result.buf=7) 2297 <== ENOENT 2298 <-- (44) 2299 `, 2300 }, 2301 { 2302 name: "path is out of memory", 2303 fd: rootFd, 2304 memory: initialMemoryFile, 2305 pathLen: memorySize, 2306 expectedErrno: ErrnoNametoolong, 2307 expectedLog: ` 2308 --> proxy.path_filestat_get(fd=3,flags=0,path=1,path_len=65536,result.buf=0) 2309 ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=0,path=1,path_len=65536,result.buf=0) 2310 <== ENAMETOOLONG 2311 <-- (37) 2312 `, 2313 }, 2314 { 2315 name: "resultFilestat exceeds the maximum valid address by 1", 2316 fd: rootFd, 2317 memory: initialMemoryFile, 2318 pathLen: 1, 2319 resultFilestat: memorySize - 64 + 1, 2320 expectedErrno: ErrnoFault, 2321 expectedLog: ` 2322 --> proxy.path_filestat_get(fd=3,flags=0,path=1,path_len=1,result.buf=65473) 2323 ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=0,path=1,path_len=1,result.buf=65473) 2324 <== EFAULT 2325 <-- (21) 2326 `, 2327 }, 2328 } 2329 2330 for _, tt := range tests { 2331 tc := tt 2332 2333 t.Run(tc.name, func(t *testing.T) { 2334 defer log.Reset() 2335 2336 maskMemory(t, testCtx, mod, len(tc.expectedMemory)) 2337 mod.Memory().Write(testCtx, 0, tc.memory) 2338 2339 requireErrno(t, tc.expectedErrno, mod, functionPathFilestatGet, uint64(tc.fd), uint64(0), uint64(1), uint64(tc.pathLen), uint64(tc.resultFilestat)) 2340 require.Equal(t, tc.expectedLog, "\n"+log.String()) 2341 2342 actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(tc.expectedMemory))) 2343 require.True(t, ok) 2344 require.Equal(t, tc.expectedMemory, actual) 2345 }) 2346 } 2347 } 2348 2349 // Test_pathFilestatSetTimes only tests it is stubbed for GrainLang per #271 2350 func Test_pathFilestatSetTimes(t *testing.T) { 2351 log := requireErrnoNosys(t, functionPathFilestatSetTimes, 0, 0, 0, 0, 0, 0, 0) 2352 require.Equal(t, ` 2353 --> proxy.path_filestat_set_times(fd=0,flags=0,path=0,path_len=0,atim=0,mtim=0,fst_flags=0) 2354 --> wasi_snapshot_preview1.path_filestat_set_times(fd=0,flags=0,path=0,path_len=0,atim=0,mtim=0,fst_flags=0) 2355 <-- ENOSYS 2356 <-- (52) 2357 `, log) 2358 } 2359 2360 // Test_pathLink only tests it is stubbed for GrainLang per #271 2361 func Test_pathLink(t *testing.T) { 2362 log := requireErrnoNosys(t, functionPathLink, 0, 0, 0, 0, 0, 0, 0) 2363 require.Equal(t, ` 2364 --> proxy.path_link(old_fd=0,old_flags=0,old_path=0,old_path_len=0,new_fd=0,new_path=0,new_path_len=0) 2365 --> wasi_snapshot_preview1.path_link(old_fd=0,old_flags=0,old_path=0,old_path_len=0,new_fd=0,new_path=0,new_path_len=0) 2366 <-- ENOSYS 2367 <-- (52) 2368 `, log) 2369 } 2370 2371 func Test_pathOpen(t *testing.T) { 2372 rootFD := uint32(3) // after 0, 1, and 2, that are stdin/out/err 2373 expectedFD := rootFD + 1 2374 // set up the initial memory to include the path name starting at an offset. 2375 pathName := "wazero" 2376 initialMemory := append([]byte{'?'}, pathName...) 2377 2378 expectedMemory := append( 2379 initialMemory, 2380 '?', // `resultOpenedFd` is after this 2381 byte(expectedFD), 0, 0, 0, 2382 '?', 2383 ) 2384 2385 dirflags := uint32(0) 2386 path := uint32(1) 2387 pathLen := uint32(len(pathName)) 2388 oflags := uint32(0) 2389 fsRightsBase := uint64(1) // ignored: rights were removed from WASI. 2390 fsRightsInheriting := uint64(2) // ignored: rights were removed from WASI. 2391 fdflags := uint32(0) 2392 resultOpenedFd := uint32(len(initialMemory) + 1) 2393 2394 testFS := fstest.MapFS{pathName: &fstest.MapFile{Mode: os.ModeDir}} 2395 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(testFS)) 2396 defer r.Close(testCtx) 2397 2398 maskMemory(t, testCtx, mod, len(expectedMemory)) 2399 ok := mod.Memory().Write(testCtx, 0, initialMemory) 2400 require.True(t, ok) 2401 2402 requireErrno(t, ErrnoSuccess, mod, functionPathOpen, uint64(rootFD), uint64(dirflags), uint64(path), 2403 uint64(pathLen), uint64(oflags), fsRightsBase, fsRightsInheriting, uint64(fdflags), uint64(resultOpenedFd)) 2404 require.Equal(t, ` 2405 --> proxy.path_open(fd=3,dirflags=0,path=1,path_len=6,oflags=0,fs_rights_base=1,fs_rights_inheriting=2,fdflags=0,result.opened_fd=8) 2406 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=0,path=1,path_len=6,oflags=0,fs_rights_base=1,fs_rights_inheriting=2,fdflags=0,result.opened_fd=8) 2407 <== ESUCCESS 2408 <-- (0) 2409 `, "\n"+log.String()) 2410 2411 actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory))) 2412 require.True(t, ok) 2413 require.Equal(t, expectedMemory, actual) 2414 2415 // verify the file was actually opened 2416 fsc := mod.(*wasm.CallContext).Sys.FS(testCtx) 2417 f, ok := fsc.OpenedFile(testCtx, expectedFD) 2418 require.True(t, ok) 2419 require.Equal(t, pathName, f.Path) 2420 } 2421 2422 func Test_pathOpen_Errors(t *testing.T) { 2423 validFD := uint32(3) // arbitrary valid fd after 0, 1, and 2, that are stdin/out/err 2424 pathName := "wazero" 2425 testFS := fstest.MapFS{pathName: &fstest.MapFile{Mode: os.ModeDir}} 2426 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(testFS)) 2427 defer r.Close(testCtx) 2428 2429 validPath := uint32(0) // arbitrary offset 2430 validPathLen := uint32(6) // the length of "wazero" 2431 2432 tests := []struct { 2433 name, pathName string 2434 fd, path, pathLen, oflags, resultOpenedFd uint32 2435 expectedErrno Errno 2436 expectedLog string 2437 }{ 2438 { 2439 name: "invalid fd", 2440 fd: 42, // arbitrary invalid fd 2441 expectedErrno: ErrnoBadf, 2442 expectedLog: ` 2443 --> proxy.path_open(fd=42,dirflags=0,path=0,path_len=0,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0) 2444 ==> wasi_snapshot_preview1.path_open(fd=42,dirflags=0,path=0,path_len=0,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0) 2445 <== EBADF 2446 <-- (8) 2447 `, 2448 }, 2449 { 2450 name: "out-of-memory reading path", 2451 fd: validFD, 2452 path: mod.Memory().Size(testCtx), 2453 pathLen: validPathLen, 2454 expectedErrno: ErrnoFault, 2455 expectedLog: ` 2456 --> proxy.path_open(fd=3,dirflags=0,path=65536,path_len=6,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0) 2457 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=0,path=65536,path_len=6,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0) 2458 <== EFAULT 2459 <-- (21) 2460 `, 2461 }, 2462 { 2463 name: "path invalid", 2464 fd: validFD, 2465 pathName: "../foo", 2466 pathLen: 6, 2467 // fstest.MapFS returns file not found instead of invalid on invalid path 2468 expectedErrno: ErrnoNoent, 2469 expectedLog: ` 2470 --> proxy.path_open(fd=3,dirflags=0,path=0,path_len=6,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0) 2471 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=0,path=0,path_len=6,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0) 2472 <== ENOENT 2473 <-- (44) 2474 `, 2475 }, 2476 { 2477 name: "out-of-memory reading pathLen", 2478 fd: validFD, 2479 path: validPath, 2480 pathLen: mod.Memory().Size(testCtx) + 1, // path is in the valid memory range, but pathLen is out-of-memory for path 2481 expectedErrno: ErrnoFault, 2482 expectedLog: ` 2483 --> proxy.path_open(fd=3,dirflags=0,path=0,path_len=65537,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0) 2484 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=0,path=0,path_len=65537,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0) 2485 <== EFAULT 2486 <-- (21) 2487 `, 2488 }, 2489 { 2490 name: "no such file exists", 2491 fd: validFD, 2492 pathName: pathName, 2493 path: validPath, 2494 pathLen: validPathLen - 1, // this make the path "wazer", which doesn't exit 2495 expectedErrno: ErrnoNoent, 2496 expectedLog: ` 2497 --> proxy.path_open(fd=3,dirflags=0,path=0,path_len=5,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0) 2498 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=0,path=0,path_len=5,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0) 2499 <== ENOENT 2500 <-- (44) 2501 `, 2502 }, 2503 { 2504 name: "out-of-memory writing resultOpenedFd", 2505 fd: validFD, 2506 pathName: pathName, 2507 path: validPath, 2508 pathLen: validPathLen, 2509 resultOpenedFd: mod.Memory().Size(testCtx), // path and pathLen correctly point to the right path, but where to write the opened FD is outside memory. 2510 expectedErrno: ErrnoFault, 2511 expectedLog: ` 2512 --> proxy.path_open(fd=3,dirflags=0,path=0,path_len=6,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=65536) 2513 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=0,path=0,path_len=6,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=65536) 2514 <== EFAULT 2515 <-- (21) 2516 `, 2517 }, 2518 } 2519 2520 for _, tt := range tests { 2521 tc := tt 2522 t.Run(tc.name, func(t *testing.T) { 2523 defer log.Reset() 2524 2525 mod.Memory().Write(testCtx, validPath, []byte(tc.pathName)) 2526 2527 requireErrno(t, tc.expectedErrno, mod, functionPathOpen, uint64(tc.fd), uint64(0), uint64(tc.path), 2528 uint64(tc.pathLen), uint64(tc.oflags), 0, 0, 0, uint64(tc.resultOpenedFd)) 2529 require.Equal(t, tc.expectedLog, "\n"+log.String()) 2530 }) 2531 } 2532 } 2533 2534 // Test_pathReadlink only tests it is stubbed for GrainLang per #271 2535 func Test_pathReadlink(t *testing.T) { 2536 log := requireErrnoNosys(t, functionPathReadlink, 0, 0, 0, 0, 0, 0) 2537 require.Equal(t, ` 2538 --> proxy.path_readlink(fd=0,path=0,path_len=0,buf=0,buf_len=0,result.bufused=0) 2539 --> wasi_snapshot_preview1.path_readlink(fd=0,path=0,path_len=0,buf=0,buf_len=0,result.bufused=0) 2540 <-- ENOSYS 2541 <-- (52) 2542 `, log) 2543 } 2544 2545 // Test_pathRemoveDirectory only tests it is stubbed for GrainLang per #271 2546 func Test_pathRemoveDirectory(t *testing.T) { 2547 log := requireErrnoNosys(t, functionPathRemoveDirectory, 0, 0, 0) 2548 require.Equal(t, ` 2549 --> proxy.path_remove_directory(fd=0,path=0,path_len=0) 2550 --> wasi_snapshot_preview1.path_remove_directory(fd=0,path=0,path_len=0) 2551 <-- ENOSYS 2552 <-- (52) 2553 `, log) 2554 } 2555 2556 // Test_pathRename only tests it is stubbed for GrainLang per #271 2557 func Test_pathRename(t *testing.T) { 2558 log := requireErrnoNosys(t, functionPathRename, 0, 0, 0, 0, 0, 0) 2559 require.Equal(t, ` 2560 --> proxy.path_rename(fd=0,old_path=0,old_path_len=0,new_fd=0,new_path=0,new_path_len=0) 2561 --> wasi_snapshot_preview1.path_rename(fd=0,old_path=0,old_path_len=0,new_fd=0,new_path=0,new_path_len=0) 2562 <-- ENOSYS 2563 <-- (52) 2564 `, log) 2565 } 2566 2567 // Test_pathSymlink only tests it is stubbed for GrainLang per #271 2568 func Test_pathSymlink(t *testing.T) { 2569 log := requireErrnoNosys(t, functionPathSymlink, 0, 0, 0, 0, 0) 2570 require.Equal(t, ` 2571 --> proxy.path_symlink(old_path=0,old_path_len=0,fd=0,new_path=0,new_path_len=0) 2572 --> wasi_snapshot_preview1.path_symlink(old_path=0,old_path_len=0,fd=0,new_path=0,new_path_len=0) 2573 <-- ENOSYS 2574 <-- (52) 2575 `, log) 2576 } 2577 2578 // Test_pathUnlinkFile only tests it is stubbed for GrainLang per #271 2579 func Test_pathUnlinkFile(t *testing.T) { 2580 log := requireErrnoNosys(t, functionPathUnlinkFile, 0, 0, 0) 2581 require.Equal(t, ` 2582 --> proxy.path_unlink_file(fd=0,path=0,path_len=0) 2583 --> wasi_snapshot_preview1.path_unlink_file(fd=0,path=0,path_len=0) 2584 <-- ENOSYS 2585 <-- (52) 2586 `, log) 2587 } 2588 2589 func requireOpenFile(t *testing.T, pathName string, data []byte) (api.Module, uint32, *bytes.Buffer, api.Closer) { 2590 mapFile := &fstest.MapFile{Data: data} 2591 if data == nil { 2592 mapFile.Mode = os.ModeDir 2593 } 2594 testFS := fstest.MapFS{pathName[1:]: mapFile} // strip the leading slash 2595 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(testFS)) 2596 fsc := mod.(*wasm.CallContext).Sys.FS(testCtx) 2597 fd, err := fsc.OpenFile(testCtx, pathName) 2598 require.NoError(t, err) 2599 return mod, fd, log, r 2600 } 2601 2602 // requireOpenWritableFile is temporary until we add the ability to open files for writing. 2603 func requireOpenWritableFile(t *testing.T, tmpDir string, pathName string) (api.Module, uint32, *bytes.Buffer, api.Closer) { 2604 writeable, testFS := createWriteableFile(t, tmpDir, pathName, []byte{}) 2605 mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(testFS)) 2606 fsc := mod.(*wasm.CallContext).Sys.FS(testCtx) 2607 fd, err := fsc.OpenFile(testCtx, pathName) 2608 require.NoError(t, err) 2609 2610 // Swap the read-only file with a writeable one until #390 2611 f, ok := fsc.OpenedFile(testCtx, fd) 2612 require.True(t, ok) 2613 f.File.Close() 2614 f.File = writeable 2615 2616 return mod, fd, log, r 2617 } 2618 2619 // createWriteableFile uses real files when io.Writer tests are needed. 2620 func createWriteableFile(t *testing.T, tmpDir string, pathName string, data []byte) (fs.File, fs.FS) { 2621 require.NotNil(t, data) 2622 absolutePath := path.Join(tmpDir, pathName) 2623 require.NoError(t, os.WriteFile(absolutePath, data, 0o600)) 2624 2625 // open the file for writing in a custom way until #390 2626 f, err := os.OpenFile(absolutePath, os.O_RDWR, 0o600) 2627 require.NoError(t, err) 2628 return f, os.DirFS(tmpDir) 2629 }