From b4f3371a160b65c6c655e4b836526d62bf06c652 Mon Sep 17 00:00:00 2001 From: Crypt Keeper <64215+codefromthecrypt@users.noreply.github.com> Date: Thu, 1 Dec 2022 10:19:21 +0800 Subject: [PATCH] wasi: adds fd_readdir (#865) This adds an implementation of `fd_readdir` for WASI, which ensures a very large directory is not kept in host memory until its directory is closed. Original implementation and test data are with thanks from @jerbob92. Signed-off-by: Adrian Cole Co-authored-by: Takeshi Yoneda Co-authored-by: jerbob92 --- Makefile | 5 +- .../example/testdata/zig-cc/cat.wasm | Bin 38385 -> 38386 bytes imports/wasi_snapshot_preview1/fs.go | 268 ++++++- imports/wasi_snapshot_preview1/fs_test.go | 715 +++++++++++++++++- .../testdata/cargo-wasi/.gitignore | 2 + .../testdata/cargo-wasi/Cargo.toml | 8 + .../testdata/cargo-wasi/ls.rs | 7 + .../testdata/cargo-wasi/ls.wasm | Bin 0 -> 84613 bytes .../testdata/zig-cc/ls.c | 15 + .../testdata/zig-cc/ls.wasm | Bin 0 -> 41185 bytes .../wasi_stdlib_test.go | 103 +++ internal/sys/fs.go | 17 + 12 files changed, 1125 insertions(+), 15 deletions(-) create mode 100644 imports/wasi_snapshot_preview1/testdata/cargo-wasi/.gitignore create mode 100644 imports/wasi_snapshot_preview1/testdata/cargo-wasi/Cargo.toml create mode 100644 imports/wasi_snapshot_preview1/testdata/cargo-wasi/ls.rs create mode 100644 imports/wasi_snapshot_preview1/testdata/cargo-wasi/ls.wasm create mode 100644 imports/wasi_snapshot_preview1/testdata/zig-cc/ls.c create mode 100755 imports/wasi_snapshot_preview1/testdata/zig-cc/ls.wasm create mode 100644 imports/wasi_snapshot_preview1/wasi_stdlib_test.go diff --git a/Makefile b/Makefile index d3ec41fa93..0413695f24 100644 --- a/Makefile +++ b/Makefile @@ -68,7 +68,7 @@ build.examples.tinygo: $(tinygo_sources) done # We use zig to build C as it is easy to install and embeds a copy of zig-cc. -c_sources := imports/wasi_snapshot_preview1/example/testdata/zig-cc/cat.c +c_sources := imports/wasi_snapshot_preview1/example/testdata/zig-cc/cat.c imports/wasi_snapshot_preview1/testdata/zig-cc/ls.c .PHONY: build.examples.zig-cc build.examples.zig-cc: $(c_sources) @for f in $^; do \ @@ -103,9 +103,10 @@ build.examples.emscripten: $(emscripten_sources) %/greet.wasm : cargo_target := wasm32-unknown-unknown %/cat.wasm : cargo_target := wasm32-wasi +%/ls.wasm : cargo_target := wasm32-wasi .PHONY: build.examples.rust -build.examples.rust: examples/allocation/rust/testdata/greet.wasm imports/wasi_snapshot_preview1/example/testdata/cargo-wasi/cat.wasm +build.examples.rust: examples/allocation/rust/testdata/greet.wasm imports/wasi_snapshot_preview1/example/testdata/cargo-wasi/cat.wasm imports/wasi_snapshot_preview1/testdata/cargo-wasi/ls.wasm # Builds rust using cargo normally, or cargo-wasi. %.wasm: %.rs diff --git a/imports/wasi_snapshot_preview1/example/testdata/zig-cc/cat.wasm b/imports/wasi_snapshot_preview1/example/testdata/zig-cc/cat.wasm index 82088713e942b306ff9e8d59d4d958d23edd2578..bcfc20b479394c463dc8ac66b46820e8884f40dc 100755 GIT binary patch delta 167 zcmeykn(5POrVVTQM5-AW7_zofXND6wGk#t9pl bn2Zc2r%VoEGBw`3Yw}Y@qBT$6I6(^lP`W+l delta 170 zcmeygn(5Ifg}iU0x?t36s-CK%7yUq?eqZYgLt*o|BlDt`DSjlk)RRic5+T3-o{rCZCw9wfWA3 Yb|xl6 diff --git a/imports/wasi_snapshot_preview1/fs.go b/imports/wasi_snapshot_preview1/fs.go index fe0238ab69..6ab67110a2 100644 --- a/imports/wasi_snapshot_preview1/fs.go +++ b/imports/wasi_snapshot_preview1/fs.go @@ -6,6 +6,7 @@ import ( "errors" "io" "io/fs" + "math" "path" "syscall" @@ -643,11 +644,268 @@ func fdRead_shouldContinueRead(n, l uint32, err error) (bool, Errno) { // entries from a directory. // // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_readdirfd-fd-buf-pointeru8-buf_len-size-cookie-dircookie---errno-size -var fdReaddir = stubFunction( - functionFdReaddir, - []wasm.ValueType{i32, i32, i32, i64, i32}, - []string{"fd", "buf", "buf_len", "cookie", "result.bufused"}, -) +var fdReaddir = &wasm.HostFunc{ + ExportNames: []string{functionFdReaddir}, + Name: functionFdReaddir, + ParamTypes: []wasm.ValueType{i32, i32, i32, i64, i32}, + ParamNames: []string{"fd", "buf", "buf_len", "cookie", "result.bufused"}, + ResultTypes: []api.ValueType{i32}, + Code: &wasm.Code{ + IsHostFunction: true, + GoFunc: wasiFunc(fdReaddirFn), + }, +} + +func fdReaddirFn(ctx context.Context, mod api.Module, params []uint64) Errno { + fd := uint32(params[0]) + buf := uint32(params[1]) + bufLen := uint32(params[2]) + // We control the value of the cookie, and it should never be negative. + // However, we coerce it to signed to ensure the caller doesn't manipulate + // it in such a way that becomes negative. + cookie := int64(params[3]) + resultBufused := uint32(params[4]) + + // Validate the FD is a directory + rd, dir, errno := openedDir(ctx, mod, fd) + if errno != ErrnoSuccess { + return errno + } + + // expect a cookie only if we are continuing a read. + if cookie == 0 && dir.CountRead > 0 { + return ErrnoInval // invalid as a cookie is minimally one. + } + + // First, determine the maximum directory entries that can be encoded as + // dirents. The total size is direntSize(24) + nameSize, for each file. + // Since a zero-length file name is invalid, the minimum size entry is + // 25 (direntSize + 1 character). + maxDirEntries := int(bufLen/direntSize + 1) + + // While unlikely maxDirEntries will fit into bufLen, add one more just in + // case, as we need to know if we hit the end of the directory or not to + // write the correct bufused (e.g. == bufLen unless EOF). + // >> If less than the size of the read buffer, the end of the + // >> directory has been reached. + maxDirEntries += 1 + + // The host keeps state for any unread entries from the prior call because + // we cannot seek to a previous directory position. Collect these entries. + entries, errno := lastDirEntries(dir, cookie) + if errno != ErrnoSuccess { + return errno + } + + // Check if we have maxDirEntries, and read more from the FS as needed. + if entryCount := len(entries); entryCount < maxDirEntries { + if l, err := rd.ReadDir(maxDirEntries - entryCount); err != io.EOF { + if err != nil { + return ErrnoIo + } + dir.CountRead += uint64(len(l)) + entries = append(entries, l...) + // Replace the cache with up to maxDirEntries, starting at cookie. + dir.Entries = entries + } + } + + mem := mod.Memory() + + // Determine how many dirents we can write, excluding a potentially + // truncated entry. + bufused, direntCount, writeTruncatedEntry := maxDirents(entries, bufLen) + + // Now, write entries to the underlying buffer. + if bufused > 0 { + + // d_next is the index of the next file in the list, so it should + // always be one higher than the requested cookie. + d_next := uint64(cookie + 1) + // ^^ yes this can overflow to negative, which means our implementation + // doesn't support writing greater than max int64 entries. + + dirents, ok := mem.Read(ctx, buf, bufused) + if !ok { + return ErrnoFault + } + + writeDirents(entries, direntCount, writeTruncatedEntry, dirents, d_next) + } + + if !mem.WriteUint32Le(ctx, resultBufused, bufused) { + return ErrnoFault + } + return ErrnoSuccess +} + +const largestDirent = int64(math.MaxUint32 - direntSize) + +// lastDirEntries is broken out from fdReaddirFn for testability. +func lastDirEntries(dir *internalsys.ReadDir, cookie int64) (entries []fs.DirEntry, errno Errno) { + if cookie < 0 { + errno = ErrnoInval // invalid as we will never send a negative cookie. + return + } + + entryCount := int64(len(dir.Entries)) + if entryCount == 0 { // there was no prior call + if cookie != 0 { + errno = ErrnoInval // invalid as we haven't sent that cookie + } + return + } + + // Get the first absolute position in our window of results + firstPos := int64(dir.CountRead) - entryCount + cookiePos := cookie - firstPos + + switch { + case cookiePos < 0: // cookie is asking for results outside our window. + errno = ErrnoNosys // we can't implement directory seeking backwards. + case cookiePos == 0: // cookie is asking for the next page. + case cookiePos > entryCount: + errno = ErrnoInval // invalid as we read that far, yet. + case cookiePos > 0: // truncate so to avoid large lists. + entries = dir.Entries[cookiePos:] + default: + entries = dir.Entries + } + if len(entries) == 0 { + entries = nil + } + return +} + +// direntSize is the size of the dirent struct, which should be followed by the +// length of a file name. +const direntSize = uint32(24) + +// maxDirents returns the maximum count and total entries that can fit in +// maxLen bytes. +// +// truncatedEntryLen is the amount of bytes past bufLen needed to write the +// next entry. We have to return bufused == bufLen unless the directory is +// exhausted. +// +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_readdir +// See https://github.com/WebAssembly/wasi-libc/blob/659ff414560721b1660a19685110e484a081c3d4/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c#L44 +func maxDirents(entries []fs.DirEntry, bufLen uint32) (bufused, direntCount uint32, writeTruncatedEntry bool) { + lenRemaining := bufLen + for _, e := range entries { + if lenRemaining < direntSize { + // We don't have enough space in bufLen for another struct, + // entry. A caller who wants more will retry. + + // bufused == bufLen means more entries exist, which is the case + // when the dirent is larger than bytes remaining. + bufused = bufLen + break + } + + // use int64 to guard against huge filenames + nameLen := int64(len(e.Name())) + var entryLen uint32 + + // Check to see if direntSize + nameLen overflows, or if it would be + // larger than possible to encode. + if el := int64(direntSize) + nameLen; el < 0 || el > largestDirent { + // panic, as testing is difficult. ex we would have to extract a + // function to get size of a string or allocate a 2^32 size one! + panic("invalid filename: too large") + } else { // we know this can fit into a uint32 + entryLen = uint32(el) + } + + if entryLen > lenRemaining { + // We haven't room to write the entry, and docs say to write the + // header. This helps especially when there is an entry with a very + // long filename. Ex if bufLen is 4096 and the filename is 4096, + // we need to write direntSize(24) + 4096 bytes to write the entry. + // In this case, we only write up to direntSize(24) to allow the + // caller to resize. + + // bufused == bufLen means more entries exist, which is the case + // when the next entry is larger than bytes remaining. + bufused = bufLen + + // We do have enough space to write the header, this value will be + // passed on to writeDirents to only write the header for this entry. + writeTruncatedEntry = true + break + } + + // This won't go negative because we checked entryLen <= lenRemaining. + lenRemaining -= entryLen + bufused += entryLen + direntCount++ + } + return +} + +// writeDirents writes the directory entries to the buffer, which is pre-sized +// based on maxDirents. truncatedEntryLen means write one past entryCount, +// without its name. See maxDirents for why +func writeDirents( + entries []fs.DirEntry, + entryCount uint32, + writeTruncatedEntry bool, + dirents []byte, + d_next uint64, +) { + pos, i := uint32(0), uint32(0) + for ; i < entryCount; i++ { + e := entries[i] + nameLen := uint32(len(e.Name())) + + writeDirent(dirents[pos:], d_next, nameLen, e.IsDir()) + pos += direntSize + + copy(dirents[pos:], e.Name()) + pos += nameLen + d_next++ + } + + if !writeTruncatedEntry { + return + } + + // Write a dirent without its name + dirent := make([]byte, direntSize) + e := entries[i] + writeDirent(dirent, d_next, uint32(len(e.Name())), e.IsDir()) + + // Potentially truncate it + copy(dirents[pos:], dirent) +} + +// writeDirent writes direntSize bytes +func writeDirent(buf []byte, dNext uint64, dNamlen uint32, dType bool) { + binary.LittleEndian.PutUint64(buf, dNext) // d_next + binary.LittleEndian.PutUint64(buf[8:], 0) // no d_ino + binary.LittleEndian.PutUint32(buf[16:], dNamlen) // d_namlen + + filetype := wasiFiletypeRegularFile + if dType { + filetype = wasiFiletypeDirectory + } + binary.LittleEndian.PutUint32(buf[20:], uint32(filetype)) // d_type +} + +// openedDir returns the directory and ErrnoSuccess if the fd points to a readable directory. +func openedDir(ctx context.Context, mod api.Module, fd uint32) (fs.ReadDirFile, *internalsys.ReadDir, Errno) { + fsc := mod.(*wasm.CallContext).Sys.FS(ctx) + if f, ok := fsc.OpenedFile(ctx, fd); !ok { + return nil, nil, ErrnoBadf + } else if d, ok := f.File.(fs.ReadDirFile); !ok { + return nil, nil, ErrnoNotdir + } else { + if f.ReadDir == nil { + f.ReadDir = &internalsys.ReadDir{} + } + return d, f.ReadDir, ErrnoSuccess + } +} // fdRenumber is the WASI function named functionFdRenumber which atomically // replaces a file descriptor by renumbering another file descriptor. diff --git a/imports/wasi_snapshot_preview1/fs_test.go b/imports/wasi_snapshot_preview1/fs_test.go index cb6a686a26..6931971d37 100644 --- a/imports/wasi_snapshot_preview1/fs_test.go +++ b/imports/wasi_snapshot_preview1/fs_test.go @@ -2,6 +2,7 @@ package wasi_snapshot_preview1 import ( "bytes" + _ "embed" "io" "io/fs" "math" @@ -13,6 +14,7 @@ import ( "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/api" + internalsys "github.com/tetratelabs/wazero/internal/sys" "github.com/tetratelabs/wazero/internal/testing/require" "github.com/tetratelabs/wazero/internal/wasm" ) @@ -1004,15 +1006,712 @@ func Test_fdRead_shouldContinueRead(t *testing.T) { } } -// Test_fdReaddir only tests it is stubbed for GrainLang per #271 +var ( + fdReadDirFs = fstest.MapFS{ + "notdir": {}, + "emptydir": {Mode: fs.ModeDir}, + "dir": {Mode: fs.ModeDir}, + "dir/-": {}, // len = 24+1 = 25 + "dir/a-": {Mode: fs.ModeDir}, // len = 24+2 = 26 + "dir/ab-": {}, // len = 24+3 = 27 + } + + testDirEntries = func() []fs.DirEntry { + entries, err := fdReadDirFs.ReadDir("dir") + if err != nil { + panic(err) + } + return entries + }() + + dirent1 = []byte{ + 1, 0, 0, 0, 0, 0, 0, 0, // d_next = 1 + 0, 0, 0, 0, 0, 0, 0, 0, // d_ino = 0 + 1, 0, 0, 0, // d_namlen = 1 character + 4, 0, 0, 0, // d_type = regular_file + '-', // name + } + dirent2 = []byte{ + 2, 0, 0, 0, 0, 0, 0, 0, // d_next = 2 + 0, 0, 0, 0, 0, 0, 0, 0, // d_ino = 0 + 2, 0, 0, 0, // d_namlen = 1 character + 3, 0, 0, 0, // d_type = directory + 'a', '-', // name + } + dirent3 = []byte{ + 3, 0, 0, 0, 0, 0, 0, 0, // d_next = 3 + 0, 0, 0, 0, 0, 0, 0, 0, // d_ino = 0 + 3, 0, 0, 0, // d_namlen = 3 characters + 4, 0, 0, 0, // d_type = regular_file + 'a', 'b', '-', // name + } +) + func Test_fdReaddir(t *testing.T) { - log := requireErrnoNosys(t, functionFdReaddir, 0, 0, 0, 0, 0) - require.Equal(t, ` ---> proxy.fd_readdir(fd=0,buf=0,buf_len=0,cookie=0,result.bufused=0) - --> wasi_snapshot_preview1.fd_readdir(fd=0,buf=0,buf_len=0,cookie=0,result.bufused=0) - <-- ENOSYS -<-- (52) -`, log) + mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(fdReadDirFs)) + defer r.Close(testCtx) + + fsc := mod.(*wasm.CallContext).Sys.FS(testCtx) + + fd, err := fsc.OpenFile(testCtx, "dir") + require.NoError(t, err) + + tests := []struct { + name string + dir func() *internalsys.FileEntry + buf, bufLen uint32 + cookie int64 + expectedMem []byte + expectedMemSize int + expectedBufused uint32 + expectedReadDir *internalsys.ReadDir + }{ + { + name: "empty dir", + dir: func() *internalsys.FileEntry { + dir, err := fdReadDirFs.Open("emptydir") + require.NoError(t, err) + + return &internalsys.FileEntry{File: dir} + }, + buf: 0, bufLen: 1, + cookie: 0, + expectedBufused: 0, + expectedMem: []byte{}, + expectedReadDir: &internalsys.ReadDir{}, + }, + { + name: "full read", + dir: func() *internalsys.FileEntry { + dir, err := fdReadDirFs.Open("dir") + require.NoError(t, err) + + return &internalsys.FileEntry{File: dir} + }, + buf: 0, bufLen: 4096, + cookie: 0, + expectedBufused: 78, // length of all entries + expectedMem: append(append(dirent1, dirent2...), dirent3...), + expectedReadDir: &internalsys.ReadDir{ + CountRead: 3, + Entries: testDirEntries, + }, + }, + { + name: "can't read", + dir: func() *internalsys.FileEntry { + dir, err := fdReadDirFs.Open("dir") + require.NoError(t, err) + + return &internalsys.FileEntry{File: dir} + }, + buf: 0, bufLen: 23, // length is too short for header + cookie: 0, + expectedBufused: 23, // == bufLen which is the size of the dirent + expectedMem: nil, + expectedReadDir: &internalsys.ReadDir{ + CountRead: 2, + Entries: testDirEntries[:2], + }, + }, + { + name: "can't read name", + dir: func() *internalsys.FileEntry { + dir, err := fdReadDirFs.Open("dir") + require.NoError(t, err) + + return &internalsys.FileEntry{File: dir} + }, + buf: 0, bufLen: 24, // length is long enough for first, but not the name. + cookie: 0, + expectedBufused: 24, // == bufLen which is the size of the dirent + expectedMem: dirent1[:24], // header without name + expectedReadDir: &internalsys.ReadDir{ + CountRead: 3, + Entries: testDirEntries, + }, + }, + { + name: "read exactly first", + dir: func() *internalsys.FileEntry { + dir, err := fdReadDirFs.Open("dir") + require.NoError(t, err) + + return &internalsys.FileEntry{File: dir} + }, + buf: 0, bufLen: 25, // length is long enough for first + the name, but not more. + cookie: 0, + expectedBufused: 25, // length to read exactly first. + expectedMem: dirent1, + expectedReadDir: &internalsys.ReadDir{ + CountRead: 3, + Entries: testDirEntries, + }, + }, + { + name: "read exactly second", + dir: func() *internalsys.FileEntry { + dir, err := fdReadDirFs.Open("dir") + require.NoError(t, err) + entry, err := dir.(fs.ReadDirFile).ReadDir(1) + require.NoError(t, err) + + return &internalsys.FileEntry{ + File: dir, + ReadDir: &internalsys.ReadDir{ + CountRead: 1, + Entries: entry, + }, + } + }, + buf: 0, bufLen: 26, // length is long enough for exactly second. + cookie: 1, // d_next of first + expectedBufused: 26, // length to read exactly second. + expectedMem: dirent2, + expectedReadDir: &internalsys.ReadDir{ + CountRead: 3, + Entries: testDirEntries[1:], + }, + }, + { + name: "read second and a little more", + dir: func() *internalsys.FileEntry { + dir, err := fdReadDirFs.Open("dir") + require.NoError(t, err) + entry, err := dir.(fs.ReadDirFile).ReadDir(1) + require.NoError(t, err) + + return &internalsys.FileEntry{ + File: dir, + ReadDir: &internalsys.ReadDir{ + CountRead: 1, + Entries: entry, + }, + } + }, + buf: 0, bufLen: 30, // length is longer than the second entry, but not long enough for a header. + cookie: 1, // d_next of first + expectedBufused: 30, // length to read some more, but not enough for a header, so buf was exhausted. + expectedMem: dirent2, + expectedMemSize: len(dirent2), // we do not want to compare the full buffer since we don't know what the leftover 4 bytes will contain. + expectedReadDir: &internalsys.ReadDir{ + CountRead: 3, + Entries: testDirEntries[1:], + }, + }, + { + name: "read second and header of third", + dir: func() *internalsys.FileEntry { + dir, err := fdReadDirFs.Open("dir") + require.NoError(t, err) + entry, err := dir.(fs.ReadDirFile).ReadDir(1) + require.NoError(t, err) + + return &internalsys.FileEntry{ + File: dir, + ReadDir: &internalsys.ReadDir{ + CountRead: 1, + Entries: entry, + }, + } + }, + buf: 0, bufLen: 50, // length is longer than the second entry + enough for the header of third. + cookie: 1, // d_next of first + expectedBufused: 50, // length to read exactly second and the header of third. + expectedMem: append(dirent2, dirent3[0:24]...), + expectedReadDir: &internalsys.ReadDir{ + CountRead: 3, + Entries: testDirEntries[1:], + }, + }, + { + name: "read second and third", + dir: func() *internalsys.FileEntry { + dir, err := fdReadDirFs.Open("dir") + require.NoError(t, err) + entry, err := dir.(fs.ReadDirFile).ReadDir(1) + require.NoError(t, err) + + return &internalsys.FileEntry{ + File: dir, + ReadDir: &internalsys.ReadDir{ + CountRead: 1, + Entries: entry, + }, + } + }, + buf: 0, bufLen: 53, // length is long enough for second and third. + cookie: 1, // d_next of first + expectedBufused: 53, // length to read exactly one second and third. + expectedMem: append(dirent2, dirent3...), + expectedReadDir: &internalsys.ReadDir{ + CountRead: 3, + Entries: testDirEntries[1:], + }, + }, + { + name: "read exactly third", + dir: func() *internalsys.FileEntry { + dir, err := fdReadDirFs.Open("dir") + require.NoError(t, err) + two, err := dir.(fs.ReadDirFile).ReadDir(2) + require.NoError(t, err) + + return &internalsys.FileEntry{ + File: dir, + ReadDir: &internalsys.ReadDir{ + CountRead: 2, + Entries: two[1:], + }, + } + }, + buf: 0, bufLen: 27, // length is long enough for exactly third. + cookie: 2, // d_next of second. + expectedBufused: 27, // length to read exactly third. + expectedMem: dirent3, + expectedReadDir: &internalsys.ReadDir{ + CountRead: 3, + Entries: testDirEntries[2:], + }, + }, + { + name: "read third and beyond", + dir: func() *internalsys.FileEntry { + dir, err := fdReadDirFs.Open("dir") + require.NoError(t, err) + two, err := dir.(fs.ReadDirFile).ReadDir(2) + require.NoError(t, err) + + return &internalsys.FileEntry{ + File: dir, + ReadDir: &internalsys.ReadDir{ + CountRead: 2, + Entries: two[1:], + }, + } + }, + buf: 0, bufLen: 100, // length is long enough for third and more, but there is nothing more. + cookie: 2, // d_next of second. + expectedBufused: 27, // length to read exactly third. + expectedMem: dirent3, + expectedReadDir: &internalsys.ReadDir{ + CountRead: 3, + Entries: testDirEntries[2:], + }, + }, + } + + for _, tt := range tests { + tc := tt + t.Run(tc.name, func(t *testing.T) { + defer log.Reset() + + // Assign the state we are testing + file, ok := fsc.OpenedFile(testCtx, fd) + require.True(t, ok) + dir := tc.dir() + defer dir.File.Close() + + file.File = dir.File + file.ReadDir = dir.ReadDir + + maskMemory(t, testCtx, mod, int(tc.bufLen)) + + // use an arbitrarily high value for the buf used position. + resultBufused := uint32(16192) + requireErrno(t, ErrnoSuccess, mod, functionFdReaddir, + uint64(fd), uint64(tc.buf), uint64(tc.bufLen), uint64(tc.cookie), uint64(resultBufused)) + + // read back the bufused and compare memory against it + bufUsed, ok := mod.Memory().ReadUint32Le(testCtx, resultBufused) + require.True(t, ok) + require.Equal(t, tc.expectedBufused, bufUsed) + + mem, ok := mod.Memory().Read(testCtx, tc.buf, bufUsed) + require.True(t, ok) + + if tc.expectedMem != nil { + if tc.expectedMemSize == 0 { + tc.expectedMemSize = len(tc.expectedMem) + } + require.Equal(t, tc.expectedMem, mem[:tc.expectedMemSize]) + } + + require.Equal(t, tc.expectedReadDir, file.ReadDir) + }) + } +} + +func Test_fdReaddir_Errors(t *testing.T) { + mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(fdReadDirFs)) + defer r.Close(testCtx) + memLen := mod.Memory().Size(testCtx) + + fsc := mod.(*wasm.CallContext).Sys.FS(testCtx) + + dirFD, err := fsc.OpenFile(testCtx, "dir") + require.NoError(t, err) + + fileFD, err := fsc.OpenFile(testCtx, "notdir") + require.NoError(t, err) + + tests := []struct { + name string + dir func() *internalsys.FileEntry + fd, buf, bufLen, resultBufused uint32 + cookie int64 + readDir *internalsys.ReadDir + expectedErrno Errno + expectedLog string + }{ + { + name: "out-of-memory reading buf", + fd: dirFD, + buf: memLen, + bufLen: 1000, + expectedErrno: ErrnoFault, + expectedLog: ` +--> proxy.fd_readdir(fd=4,buf=65536,buf_len=1000,cookie=0,result.bufused=0) + ==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=65536,buf_len=1000,cookie=0,result.bufused=0) + <== EFAULT +<-- (21) +`, + }, + { + name: "invalid fd", + fd: 42, // arbitrary invalid fd + expectedErrno: ErrnoBadf, + expectedLog: ` +--> proxy.fd_readdir(fd=42,buf=0,buf_len=0,cookie=0,result.bufused=0) + ==> wasi_snapshot_preview1.fd_readdir(fd=42,buf=0,buf_len=0,cookie=0,result.bufused=0) + <== EBADF +<-- (8) +`, + }, + { + name: "not a dir", + fd: fileFD, + expectedErrno: ErrnoNotdir, + expectedLog: ` +--> proxy.fd_readdir(fd=5,buf=0,buf_len=0,cookie=0,result.bufused=0) + ==> wasi_snapshot_preview1.fd_readdir(fd=5,buf=0,buf_len=0,cookie=0,result.bufused=0) + <== ENOTDIR +<-- (54) +`, + }, + { + name: "out-of-memory reading buf", + fd: dirFD, + buf: memLen, + bufLen: 1000, + expectedErrno: ErrnoFault, + expectedLog: ` +--> proxy.fd_readdir(fd=4,buf=65536,buf_len=1000,cookie=0,result.bufused=0) + ==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=65536,buf_len=1000,cookie=0,result.bufused=0) + <== EFAULT +<-- (21) +`, + }, + { + name: "out-of-memory reading bufLen", + fd: dirFD, + buf: memLen - 1, + bufLen: 1000, + expectedErrno: ErrnoFault, + expectedLog: ` +--> proxy.fd_readdir(fd=4,buf=65535,buf_len=1000,cookie=0,result.bufused=0) + ==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=65535,buf_len=1000,cookie=0,result.bufused=0) + <== EFAULT +<-- (21) +`, + }, + { + name: "resultBufused is outside memory", + fd: dirFD, + buf: 0, bufLen: 1, + resultBufused: memLen, + expectedErrno: ErrnoFault, + expectedLog: ` +--> proxy.fd_readdir(fd=4,buf=0,buf_len=1,cookie=0,result.bufused=65536) + ==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=0,buf_len=1,cookie=0,result.bufused=65536) + <== EFAULT +<-- (21) +`, + }, + { + name: "cookie invalid when no prior state", + fd: dirFD, + buf: 0, bufLen: 1000, + cookie: 1, + resultBufused: 2000, + expectedErrno: ErrnoInval, + expectedLog: ` +--> proxy.fd_readdir(fd=4,buf=0,buf_len=1000,cookie=1,result.bufused=2000) + ==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=0,buf_len=1000,cookie=1,result.bufused=2000) + <== EINVAL +<-- (28) +`, + }, + { + name: "negative cookie invalid", + fd: dirFD, + buf: 0, bufLen: 1000, + cookie: -1, + readDir: &internalsys.ReadDir{CountRead: 1}, + resultBufused: 2000, + expectedErrno: ErrnoInval, + expectedLog: ` +--> proxy.fd_readdir(fd=4,buf=0,buf_len=1000,cookie=18446744073709551615,result.bufused=2000) + ==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=0,buf_len=1000,cookie=18446744073709551615,result.bufused=2000) + <== EINVAL +<-- (28) +`, + }, + } + + for _, tt := range tests { + tc := tt + t.Run(tc.name, func(t *testing.T) { + defer log.Reset() + + // Reset the directory so that tests don't taint each other. + if file, ok := fsc.OpenedFile(testCtx, tc.fd); ok && tc.fd == dirFD { + dir, err := fdReadDirFs.Open("dir") + require.NoError(t, err) + defer dir.Close() + + file.File = dir + file.ReadDir = nil + } + + requireErrno(t, tc.expectedErrno, mod, functionFdReaddir, + uint64(tc.fd), uint64(tc.buf), uint64(tc.bufLen), uint64(tc.cookie), uint64(tc.resultBufused)) + require.Equal(t, tc.expectedLog, "\n"+log.String()) + }) + } +} + +func Test_lastDirEntries(t *testing.T) { + tests := []struct { + name string + f *internalsys.ReadDir + cookie int64 + expectedEntries []fs.DirEntry + expectedErrno Errno + }{ + { + name: "no prior call", + }, + { + name: "no prior call, but passed a cookie", + cookie: 1, + expectedErrno: ErrnoInval, + }, + { + name: "cookie is negative", + f: &internalsys.ReadDir{ + CountRead: 3, + Entries: testDirEntries, + }, + cookie: -1, + expectedErrno: ErrnoInval, + }, + { + name: "cookie is greater than last d_next", + f: &internalsys.ReadDir{ + CountRead: 3, + Entries: testDirEntries, + }, + cookie: 5, + expectedErrno: ErrnoInval, + }, + { + name: "cookie is last pos", + f: &internalsys.ReadDir{ + CountRead: 3, + Entries: testDirEntries, + }, + cookie: 3, + expectedEntries: nil, + }, + { + name: "cookie is one before last pos", + f: &internalsys.ReadDir{ + CountRead: 3, + Entries: testDirEntries, + }, + cookie: 2, + expectedEntries: testDirEntries[2:], + }, + { + name: "cookie is before current entries", + f: &internalsys.ReadDir{ + CountRead: 5, + Entries: testDirEntries, + }, + cookie: 1, + expectedErrno: ErrnoNosys, // not implemented + }, + } + + for _, tt := range tests { + tc := tt + + t.Run(tc.name, func(t *testing.T) { + f := tc.f + if f == nil { + f = &internalsys.ReadDir{} + } + entries, errno := lastDirEntries(f, tc.cookie) + require.Equal(t, tc.expectedErrno, errno) + require.Equal(t, tc.expectedEntries, entries) + }) + } +} + +func Test_maxDirents(t *testing.T) { + tests := []struct { + name string + entries []fs.DirEntry + maxLen uint32 + expectedCount uint32 + expectedwriteTruncatedEntry bool + expectedBufused uint32 + }{ + { + name: "no entries", + }, + { + name: "can't fit one", + entries: testDirEntries, + maxLen: 23, + expectedBufused: 23, + expectedwriteTruncatedEntry: false, + }, + { + name: "only fits header", + entries: testDirEntries, + maxLen: 24, + expectedBufused: 24, + expectedwriteTruncatedEntry: true, + }, + { + name: "one", + entries: testDirEntries, + maxLen: 25, + expectedCount: 1, + expectedBufused: 25, + }, + { + name: "one but not room for two's name", + entries: testDirEntries, + maxLen: 25 + 25, + expectedCount: 1, + expectedwriteTruncatedEntry: true, // can write direntSize + expectedBufused: 25 + 25, + }, + { + name: "two", + entries: testDirEntries, + maxLen: 25 + 26, + expectedCount: 2, + expectedBufused: 25 + 26, + }, + { + name: "two but not three's dirent", + entries: testDirEntries, + maxLen: 25 + 26 + 20, + expectedCount: 2, + expectedwriteTruncatedEntry: false, // 20 + 4 == direntSize + expectedBufused: 25 + 26 + 20, + }, + { + name: "two but not three's name", + entries: testDirEntries, + maxLen: 25 + 26 + 26, + expectedCount: 2, + expectedwriteTruncatedEntry: true, // can write direntSize + expectedBufused: 25 + 26 + 26, + }, + { + name: "three", + entries: testDirEntries, + maxLen: 25 + 26 + 27, + expectedCount: 3, + expectedwriteTruncatedEntry: false, // end of dir + expectedBufused: 25 + 26 + 27, + }, + { + name: "max", + entries: testDirEntries, + maxLen: 100, + expectedCount: 3, + expectedwriteTruncatedEntry: false, // end of dir + expectedBufused: 25 + 26 + 27, + }, + } + + for _, tt := range tests { + tc := tt + + t.Run(tc.name, func(t *testing.T) { + bufused, direntCount, writeTruncatedEntry := maxDirents(tc.entries, tc.maxLen) + require.Equal(t, tc.expectedCount, direntCount) + require.Equal(t, tc.expectedwriteTruncatedEntry, writeTruncatedEntry) + require.Equal(t, tc.expectedBufused, bufused) + }) + } +} + +func Test_writeDirents(t *testing.T) { + tests := []struct { + name string + entries []fs.DirEntry + entryCount uint32 + writeTruncatedEntry bool + expectedEntriesBuf []byte + }{ + { + name: "none", + entries: testDirEntries, + }, + { + name: "one", + entries: testDirEntries, + entryCount: 1, + expectedEntriesBuf: dirent1, + }, + { + name: "two", + entries: testDirEntries, + entryCount: 2, + expectedEntriesBuf: append(dirent1, dirent2...), + }, + { + name: "two with truncated", + entries: testDirEntries, + entryCount: 2, + writeTruncatedEntry: true, + expectedEntriesBuf: append(append(dirent1, dirent2...), dirent3[0:10]...), + }, + { + name: "three", + entries: testDirEntries, + entryCount: 3, + expectedEntriesBuf: append(append(dirent1, dirent2...), dirent3...), + }, + } + + for _, tt := range tests { + tc := tt + + t.Run(tc.name, func(t *testing.T) { + cookie := uint64(1) + entriesBuf := make([]byte, len(tc.expectedEntriesBuf)) + writeDirents(tc.entries, tc.entryCount, tc.writeTruncatedEntry, entriesBuf, cookie) + require.Equal(t, tc.expectedEntriesBuf, entriesBuf) + }) + } } // Test_fdRenumber only tests it is stubbed for GrainLang per #271 diff --git a/imports/wasi_snapshot_preview1/testdata/cargo-wasi/.gitignore b/imports/wasi_snapshot_preview1/testdata/cargo-wasi/.gitignore new file mode 100644 index 0000000000..96ef6c0b94 --- /dev/null +++ b/imports/wasi_snapshot_preview1/testdata/cargo-wasi/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/imports/wasi_snapshot_preview1/testdata/cargo-wasi/Cargo.toml b/imports/wasi_snapshot_preview1/testdata/cargo-wasi/Cargo.toml new file mode 100644 index 0000000000..0ee71c53bd --- /dev/null +++ b/imports/wasi_snapshot_preview1/testdata/cargo-wasi/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "ls" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "ls" +path = "ls.rs" diff --git a/imports/wasi_snapshot_preview1/testdata/cargo-wasi/ls.rs b/imports/wasi_snapshot_preview1/testdata/cargo-wasi/ls.rs new file mode 100644 index 0000000000..b5b6435c01 --- /dev/null +++ b/imports/wasi_snapshot_preview1/testdata/cargo-wasi/ls.rs @@ -0,0 +1,7 @@ +use std::fs; + +fn main() { + for path in fs::read_dir(".").unwrap() { + println!("{}", path.unwrap().path().display()) + } +} diff --git a/imports/wasi_snapshot_preview1/testdata/cargo-wasi/ls.wasm b/imports/wasi_snapshot_preview1/testdata/cargo-wasi/ls.wasm new file mode 100644 index 0000000000000000000000000000000000000000..babbb7af0ab74d88ecd4de3607b4fe7a268c08ab GIT binary patch literal 84613 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 E08<2hUH||9 literal 0 HcmV?d00001 diff --git a/imports/wasi_snapshot_preview1/testdata/zig-cc/ls.c b/imports/wasi_snapshot_preview1/testdata/zig-cc/ls.c new file mode 100644 index 0000000000..6077d3061f --- /dev/null +++ b/imports/wasi_snapshot_preview1/testdata/zig-cc/ls.c @@ -0,0 +1,15 @@ +#include +#include + +int main(void) { + DIR *d; + struct dirent *dir; + d = opendir("."); + if (d) { + while ((dir = readdir(d)) != NULL) { + printf("./%s\n", dir->d_name); + } + closedir(d); + } + return(0); +} diff --git a/imports/wasi_snapshot_preview1/testdata/zig-cc/ls.wasm b/imports/wasi_snapshot_preview1/testdata/zig-cc/ls.wasm new file mode 100755 index 0000000000000000000000000000000000000000..79059e7cab9ee355e8c1d3af96d017c6b546ba37 GIT binary patch literal 41185 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 HPR0HIYLR=> literal 0 HcmV?d00001 diff --git a/imports/wasi_snapshot_preview1/wasi_stdlib_test.go b/imports/wasi_snapshot_preview1/wasi_stdlib_test.go new file mode 100644 index 0000000000..60bc9f5fc5 --- /dev/null +++ b/imports/wasi_snapshot_preview1/wasi_stdlib_test.go @@ -0,0 +1,103 @@ +package wasi_snapshot_preview1 + +import ( + "bytes" + _ "embed" + "io/fs" + "strconv" + "strings" + "testing" + "testing/fstest" + + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/internal/testing/require" + "github.com/tetratelabs/wazero/sys" +) + +// lsWasmCargoWasi was compiled from testdata/cargo-wasi/ls.rs +// +//go:embed testdata/cargo-wasi/ls.wasm +var lsWasmCargoWasi []byte + +// lsZigCc was compiled from testdata/zig-cc/ls.c +// +//go:embed testdata/zig-cc/ls.wasm +var lsZigCc []byte + +// Test_fdReaddir_ls ensures that the behavior we've implemented not only +// matches the wasi spec, but also at least two compilers use of sdks. +func Test_fdReaddir_ls(t *testing.T) { + for toolchain, bin := range map[string][]byte{ + "cargo-wasi": lsWasmCargoWasi, + "zig-cc": lsZigCc, + } { + toolchain := toolchain + bin := bin + t.Run(toolchain, func(t *testing.T) { + testFdReaddirLs(t, bin) + }) + } +} + +func testFdReaddirLs(t *testing.T, bin []byte) { + t.Run("empty directory", func(t *testing.T) { + stdout, stderr := compileAndRun(t, wazero.NewModuleConfig(). + WithFS(fstest.MapFS{}), bin) + + require.Zero(t, stderr) + require.Zero(t, stdout) + }) + + t.Run("directory with entries", func(t *testing.T) { + stdout, stderr := compileAndRun(t, wazero.NewModuleConfig(). + WithFS(fstest.MapFS{ + "-": {}, + "a-": {Mode: fs.ModeDir}, + "ab-": {}, + }), bin) + + require.Zero(t, stderr) + require.Equal(t, `./- +./a- +./ab- +`, stdout) + }) + + t.Run("directory with tons of entries", func(t *testing.T) { + testFS := fstest.MapFS{} + count := 8096 + for i := 0; i < count; i++ { + testFS[strconv.Itoa(i)] = &fstest.MapFile{} + } + stdout, stderr := compileAndRun(t, wazero.NewModuleConfig(). + WithFS(testFS), bin) + + require.Zero(t, stderr) + lines := strings.Split(stdout, "\n") + require.Equal(t, count+1 /* trailing newline */, len(lines)) + }) +} + +func compileAndRun(t *testing.T, config wazero.ModuleConfig, bin []byte) (stdout, stderr string) { + var stdoutBuf, stderrBuf bytes.Buffer + + r := wazero.NewRuntime(testCtx) + defer r.Close(testCtx) + + _, err := Instantiate(testCtx, r) + require.NoError(t, err) + + compiled, err := r.CompileModule(testCtx, bin) + require.NoError(t, err) + + _, err = r.InstantiateModule(testCtx, compiled, config.WithStdout(&stdoutBuf).WithStderr(&stderrBuf)) + if exitErr, ok := err.(*sys.ExitError); ok { + require.Zero(t, exitErr.ExitCode()) + } else { + require.NoError(t, err) + } + + stdout = stdoutBuf.String() + stderr = stderrBuf.String() + return +} diff --git a/internal/sys/fs.go b/internal/sys/fs.go index e24f06bc70..4ace070d34 100644 --- a/internal/sys/fs.go +++ b/internal/sys/fs.go @@ -39,9 +39,26 @@ func (f *emptyFS) Open(name string) (fs.File, error) { // FileEntry maps a path to an open file in a file system. type FileEntry struct { + // Path was the argument to FSContext.OpenFile Path string + // File when nil this is the root "/" (fd=3) File fs.File + + // ReadDir is present when this File is a fs.ReadDirFile and `ReadDir` + // was called. + ReadDir *ReadDir +} + +// ReadDir is the status of a prior fs.ReadDirFile call. +type ReadDir struct { + // CountRead is the total count of files read including Entries. + CountRead uint64 + + // Entries is the contents of the last fs.ReadDirFile call. Notably, + // directory listing are not rewindable, so we keep entries around in case + // the caller mis-estimated their buffer and needs a few still cached. + Entries []fs.DirEntry } type FSContext struct {