wasi: implements fd_fdstat_get and adds stdio support to fd_filestat_get (#895)

This implements fd_fdstat_get and furthers implementation of
fd_fdstat_get. Notably, this does the following:

* Ensures stdio are treated as character devices.
* Ensures someone can stat the root FD (4).
* Fixes encoding as we didn't clear zeros when encoding numbers.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
Crypt Keeper
2022-12-07 14:28:34 +09:00
committed by GitHub
parent a129cd7fd5
commit 78ac85cce1
17 changed files with 512 additions and 157 deletions

View File

@@ -70,7 +70,7 @@ build.examples.tinygo: $(tinygo_sources)
tinygo build -o internal/testing/dwarftestdata/testdata/main.wasm -scheduler=none --target=wasi internal/testing/dwarftestdata/testdata/main.go
# 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 imports/wasi_snapshot_preview1/testdata/zig-cc/ls.c
c_sources := imports/wasi_snapshot_preview1/example/testdata/zig-cc/cat.c imports/wasi_snapshot_preview1/testdata/zig-cc/wasi.c
.PHONY: build.examples.zig-cc
build.examples.zig-cc: $(c_sources)
@for f in $^; do \
@@ -105,10 +105,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
%/wasi.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 imports/wasi_snapshot_preview1/testdata/cargo-wasi/ls.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/wasi.wasm
# Builds rust using cargo normally, or cargo-wasi.
%.wasm: %.rs

View File

@@ -2,7 +2,6 @@ package wasi_snapshot_preview1
import (
"context"
"encoding/binary"
"errors"
"io"
"io/fs"
@@ -50,7 +49,7 @@ const (
pathUnlinkFileName = "path_unlink_file"
)
// fdAdvise is the WASI function named functionFdAdvise which provides file
// fdAdvise is the WASI function named fdAdviseName which provides file
// advisory information on a file descriptor.
//
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_advisefd-fd-offset-filesize-len-filesize-advice-advice---errno
@@ -60,7 +59,7 @@ var fdAdvise = stubFunction(
"fd", "offset", "len", "result.advice",
)
// fdAllocate is the WASI function named functionFdAllocate which forces the
// fdAllocate is the WASI function named fdAllocateName which forces the
// allocation of space in a file.
//
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_allocatefd-fd-offset-filesize-len-filesize---errno
@@ -70,7 +69,7 @@ var fdAllocate = stubFunction(
"fd", "offset", "len",
)
// fdClose is the WASI function named functionFdClose which closes a file
// fdClose is the WASI function named fdCloseName which closes a file
// descriptor.
//
// # Parameters
@@ -97,13 +96,13 @@ func fdCloseFn(ctx context.Context, mod api.Module, params []uint64) Errno {
return ErrnoSuccess
}
// fdDatasync is the WASI function named functionFdDatasync which synchronizes
// fdDatasync is the WASI function named fdDatasyncName which synchronizes
// the data of a file to disk.
//
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_datasyncfd-fd---errno
var fdDatasync = stubFunction(fdDatasyncName, []wasm.ValueType{i32}, "fd")
// fdFdstatGet is the WASI function named functionFdFdstatGet which returns the
// fdFdstatGet is the WASI function named fdFdstatGetName which returns the
// attributes of a file descriptor.
//
// # Parameters
@@ -143,17 +142,84 @@ var fdDatasync = stubFunction(fdDatasyncName, []wasm.ValueType{i32}, "fd")
var fdFdstatGet = newHostFunc(fdFdstatGetName, fdFdstatGetFn, []api.ValueType{i32, i32}, "fd", "result.stat")
func fdFdstatGetFn(ctx context.Context, mod api.Module, params []uint64) Errno {
sysCtx := mod.(*wasm.CallContext).Sys
// TODO: actually write the fdstat!
fd, _ := uint32(params[0]), uint32(params[1])
fd, resultFdstat := uint32(params[0]), uint32(params[1])
if _, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd); !ok {
// Ensure we can write the fdstat
buf, ok := mod.Memory().Read(ctx, resultFdstat, 24)
if !ok {
return ErrnoFault
}
// Special-case the stdio character devices
if fd <= internalsys.FdStderr {
switch fd {
case internalsys.FdStdin:
copy(buf, charFdstat)
case internalsys.FdStdout, internalsys.FdStderr:
copy(buf, charOutFdstat)
}
return ErrnoSuccess
}
// Otherwise, look up the file corresponding to the file descriptor.
sysCtx := mod.(*wasm.CallContext).Sys
file, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd)
if !ok {
return ErrnoBadf
}
// see if the file is writable
fdflags := wasiFdflagsNone
filetype := wasiFiletypeDirectory // root
if f := file.File; f != nil { // not root
if _, ok := f.(io.Writer); ok {
fdflags = wasiFdflagsAppend
}
if fdstat, err := f.Stat(); err != nil {
return ErrnoIo
} else {
filetype = getWasiFiletype(fdstat.Mode())
}
}
writeFdstat(buf, filetype, fdflags)
return ErrnoSuccess
}
// fdFdstatSetFlags is the WASI function named functionFdFdstatSetFlags which
type wasiFdflags = byte // actually 16-bit, but there aren't that many.
const (
wasiFdflagsNone wasiFdflags = iota
wasiFdflagsAppend
wasiFdflagsDsync
wasiFdflagsNonblock
wasiFdflagsRsync
wasiFdflagsSync
)
var charFdstat = []byte{
byte(wasiFiletypeCharacterDevice), 0, // filetype
0, 0, 0, 0, 0, 0, // fdflags
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_base
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_inheriting
}
var charOutFdstat = []byte{
byte(wasiFiletypeCharacterDevice), 0, // filetype
wasiFdflagsAppend, 0, 0, 0, 0, 0, // fdflags
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_base
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_inheriting
}
func writeFdstat(buf []byte, filetype wasiFiletype, fdflags wasiFdflags) {
// memory is re-used, so ensure the result is defaulted.
copy(buf, charFdstat)
buf[0] = uint8(filetype)
buf[2] = fdflags
}
// fdFdstatSetFlags is the WASI function named fdFdstatSetFlagsName which
// adjusts the flags associated with a file descriptor.
//
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_fdstat_set_flagsfd-fd-flags-fdflags---errnoand is stubbed for GrainLang per #271
@@ -168,7 +234,7 @@ var fdFdstatSetRights = stubFunction(
"fd", "fs_rights_base", "fs_rights_inheriting",
)
// fdFilestatGet is the WASI function named functionFdFilestatGet which returns
// fdFilestatGet is the WASI function named fdFilestatGetName which returns
// the stat attributes of an open file.
//
// # Parameters
@@ -208,7 +274,7 @@ var fdFdstatSetRights = stubFunction(
// The following properties of filestat are not implemented:
// - dev: not supported by Golang FS
// - ino: not supported by Golang FS
// - nlink: not supported by Golang FS
// - nlink: not supported by Golang FS, we use 1
// - atime: not supported by Golang FS, we use mtim for this
// - ctim: not supported by Golang FS, we use mtim for this
//
@@ -222,6 +288,12 @@ type wasiFiletype uint8
const (
wasiFiletypeUnknown wasiFiletype = iota
wasiFiletypeBlockDevice
// wasiFiletypeCharacterDevice is set when the FD is a character device.
//
// Note: wazero currently returns this for stdio descriptors even if the
// actual file is not a TTY, to ensure python can work. This avoids
// dependencies needed to be more precise.
// See https://github.com/mattn/go-isatty
wasiFiletypeCharacterDevice
wasiFiletypeDirectory
wasiFiletypeRegularFile
@@ -235,49 +307,85 @@ func fdFilestatGetFn(ctx context.Context, mod api.Module, params []uint64) Errno
}
func fdFilestatGetFunc(ctx context.Context, mod api.Module, fd, resultBuf uint32) Errno {
// Ensure we can write the filestat
buf, ok := mod.Memory().Read(ctx, resultBuf, 64)
if !ok {
return ErrnoFault
}
// Special-case the stdio character devices
switch fd {
case internalsys.FdStdin, internalsys.FdStdout, internalsys.FdStderr:
copy(buf, charFilestat)
return ErrnoSuccess
}
// Otherwise, look up the file corresponding to the file descriptor.
sysCtx := mod.(*wasm.CallContext).Sys
file, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd)
if !ok {
return ErrnoBadf
}
fileStat, err := file.File.Stat()
if err != nil {
return ErrnoIo
var filetype wasiFiletype
var filesize uint64
var mtim int64
if f := file.File; f != nil { // not root
if stat, err := f.Stat(); err != nil {
return ErrnoIo
} else {
filetype = getWasiFiletype(stat.Mode())
filesize = uint64(stat.Size())
mtim = stat.ModTime().UnixNano()
}
} else { // root
filetype = wasiFiletypeDirectory
// TODO: support filesize and mtime when root
}
fileMode := fileStat.Mode()
wasiFileMode := wasiFiletypeUnknown
if fileMode&fs.ModeDevice != 0 {
wasiFileMode = wasiFiletypeBlockDevice
} else if fileMode&fs.ModeCharDevice != 0 {
wasiFileMode = wasiFiletypeCharacterDevice
} else if fileMode&fs.ModeDir != 0 {
wasiFileMode = wasiFiletypeDirectory
} else if fileMode&fs.ModeType == 0 {
wasiFileMode = wasiFiletypeRegularFile
} else if fileMode&fs.ModeSymlink != 0 {
wasiFileMode = wasiFiletypeSymbolicLink
}
buf, ok := mod.Memory().Read(ctx, resultBuf, 64)
if !ok {
return ErrnoFault
}
buf[16] = uint8(wasiFileMode)
size := uint64(fileStat.Size())
binary.LittleEndian.PutUint64(buf[32:], size)
mtim := uint64(fileStat.ModTime().UnixNano())
binary.LittleEndian.PutUint64(buf[40:], mtim)
binary.LittleEndian.PutUint64(buf[48:], mtim)
binary.LittleEndian.PutUint64(buf[56:], mtim)
writeFilestat(buf, filetype, filesize, mtim)
return ErrnoSuccess
}
// fdFilestatSetSize is the WASI function named functionFdFilestatSetSize which
func getWasiFiletype(fileMode fs.FileMode) wasiFiletype {
wasiFileType := wasiFiletypeUnknown
if fileMode&fs.ModeDevice != 0 {
wasiFileType = wasiFiletypeBlockDevice
} else if fileMode&fs.ModeCharDevice != 0 {
wasiFileType = wasiFiletypeCharacterDevice
} else if fileMode&fs.ModeDir != 0 {
wasiFileType = wasiFiletypeDirectory
} else if fileMode&fs.ModeType == 0 {
wasiFileType = wasiFiletypeRegularFile
} else if fileMode&fs.ModeSymlink != 0 {
wasiFileType = wasiFiletypeSymbolicLink
}
return wasiFileType
}
var charFilestat = []byte{
0, 0, 0, 0, 0, 0, 0, 0, // device
0, 0, 0, 0, 0, 0, 0, 0, // inode
byte(wasiFiletypeCharacterDevice), 0, 0, 0, 0, 0, 0, 0, // filetype
1, 0, 0, 0, 0, 0, 0, 0, // nlink
0, 0, 0, 0, 0, 0, 0, 0, // filesize
0, 0, 0, 0, 0, 0, 0, 0, // atim
0, 0, 0, 0, 0, 0, 0, 0, // mtim
0, 0, 0, 0, 0, 0, 0, 0, // ctim
}
func writeFilestat(buf []byte, filetype wasiFiletype, filesize uint64, mtim int64) {
// memory is re-used, so ensure the result is defaulted.
copy(buf, charFilestat[:32])
buf[16] = uint8(filetype)
le.PutUint64(buf[32:], filesize) // filesize
le.PutUint64(buf[40:], uint64(mtim)) // atim
le.PutUint64(buf[48:], uint64(mtim)) // mtim
le.PutUint64(buf[56:], uint64(mtim)) // ctim
}
// fdFilestatSetSize is the WASI function named fdFilestatSetSizeName which
// adjusts the size of an open file.
//
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_filestat_set_sizefd-fd-size-filesize---errno
@@ -293,7 +401,7 @@ var fdFilestatSetTimes = stubFunction(
"fd", "atim", "mtim", "fst_flags",
)
// fdPread is the WASI function named functionFdPread which reads from a file
// fdPread is the WASI function named fdPreadName which reads from a file
// descriptor, without using and updating the file descriptor's offset.
//
// Except for handling offset, this implementation is identical to fdRead.
@@ -309,7 +417,7 @@ func fdPreadFn(ctx context.Context, mod api.Module, params []uint64) Errno {
return fdReadOrPread(ctx, mod, params, true)
}
// fdPrestatGet is the WASI function named functionFdPrestatGet which returns
// fdPrestatGet is the WASI function named fdPrestatGetName which returns
// the prestat data of a file descriptor.
//
// # Parameters
@@ -363,7 +471,7 @@ func fdPrestatGetFn(ctx context.Context, mod api.Module, params []uint64) Errno
return ErrnoSuccess
}
// fdPrestatDirName is the WASI function named functionFdPrestatDirName which
// fdPrestatDirName is the WASI function named fdPrestatDirNameName which
// returns the path of the pre-opened directory of a file descriptor.
//
// # Parameters
@@ -421,7 +529,7 @@ func fdPrestatDirNameFn(ctx context.Context, mod api.Module, params []uint64) Er
return ErrnoSuccess
}
// fdPwrite is the WASI function named functionFdPwrite which writes to a file
// fdPwrite is the WASI function named fdPwriteName which writes to a file
// descriptor, without using and updating the file descriptor's offset.
//
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_pwritefd-fd-iovs-ciovec_array-offset-filesize---errno-size
@@ -431,7 +539,7 @@ var fdPwrite = stubFunction(
"fd", "iovs", "iovs_len", "offset", "result.nwritten",
)
// fdRead is the WASI function named functionFdRead which reads from a file
// fdRead is the WASI function named fdReadName which reads from a file
// descriptor.
//
// # Parameters
@@ -571,7 +679,7 @@ func fdRead_shouldContinueRead(n, l uint32, err error) (bool, Errno) {
return n == l && n != 0, ErrnoSuccess
}
// fdReaddir is the WASI function named functionFdReaddir which reads directory
// fdReaddir is the WASI function named fdReaddirName which reads directory
// 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
@@ -806,15 +914,15 @@ func writeDirents(
// 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
le.PutUint64(buf, dNext) // d_next
le.PutUint64(buf[8:], 0) // no d_ino
le.PutUint32(buf[16:], dNamlen) // d_namlen
filetype := wasiFiletypeRegularFile
if dType {
filetype = wasiFiletypeDirectory
}
binary.LittleEndian.PutUint32(buf[20:], uint32(filetype)) // d_type
le.PutUint32(buf[20:], uint32(filetype)) // d_type
}
// openedDir returns the directory and ErrnoSuccess if the fd points to a readable directory.
@@ -832,13 +940,13 @@ func openedDir(ctx context.Context, mod api.Module, fd uint32) (fs.ReadDirFile,
}
}
// fdRenumber is the WASI function named functionFdRenumber which atomically
// fdRenumber is the WASI function named fdRenumberName which atomically
// replaces a file descriptor by renumbering another file descriptor.
//
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_renumberfd-fd-to-fd---errno
var fdRenumber = stubFunction(fdRenumberName, []wasm.ValueType{i32, i32}, "fd", "to")
// fdSeek is the WASI function named functionFdSeek which moves the offset of a
// fdSeek is the WASI function named fdSeekName which moves the offset of a
// file descriptor.
//
// # Parameters
@@ -911,19 +1019,19 @@ func fdSeekFn(ctx context.Context, mod api.Module, params []uint64) Errno {
return ErrnoSuccess
}
// fdSync is the WASI function named functionFdSync which synchronizes the data
// fdSync is the WASI function named fdSyncName which synchronizes the data
// and metadata of a file to disk.
//
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_syncfd-fd---errno
var fdSync = stubFunction(fdSyncName, []wasm.ValueType{i32}, "fd")
// fdTell is the WASI function named functionFdTell which returns the current
// fdTell is the WASI function named fdTellName which returns the current
// offset of a file descriptor.
//
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_tellfd-fd---errno-filesize
var fdTell = stubFunction(fdTellName, []wasm.ValueType{i32, i32}, "fd", "result.offset")
// fdWrite is the WASI function named functionFdWrite which writes to a file
// fdWrite is the WASI function named fdWriteName which writes to a file
// descriptor.
//
// # Parameters
@@ -1034,7 +1142,7 @@ func fdWriteFn(ctx context.Context, mod api.Module, params []uint64) Errno {
return ErrnoSuccess
}
// pathCreateDirectory is the WASI function named functionPathCreateDirectory
// pathCreateDirectory is the WASI function named pathCreateDirectoryName
// which creates a directory.
//
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_create_directoryfd-fd-path-string---errno
@@ -1044,7 +1152,7 @@ var pathCreateDirectory = stubFunction(
"fd", "path", "path_len",
)
// pathFilestatGet is the WASI function named functionPathFilestatGet which
// pathFilestatGet is the WASI function named pathFilestatGetName which
// returns the stat attributes of a file or directory.
//
// # Parameters
@@ -1119,7 +1227,7 @@ func pathFilestatGetFn(ctx context.Context, mod api.Module, params []uint64) Err
return fdFilestatGetFunc(ctx, mod, pathFd, resultBuf)
}
// pathFilestatSetTimes is the WASI function named functionPathFilestatSetTimes
// pathFilestatSetTimes is the WASI function named pathFilestatSetTimesName
// which adjusts the timestamps of a file or directory.
//
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_filestat_set_timesfd-fd-flags-lookupflags-path-string-atim-timestamp-mtim-timestamp-fst_flags-fstflags---errno
@@ -1129,7 +1237,7 @@ var pathFilestatSetTimes = stubFunction(
"fd", "flags", "path", "path_len", "atim", "mtim", "fst_flags",
)
// pathLink is the WASI function named functionPathLink which adjusts the
// pathLink is the WASI function named pathLinkName which adjusts the
// timestamps of a file or directory.
//
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#path_link
@@ -1139,7 +1247,7 @@ var pathLink = stubFunction(
"old_fd", "old_flags", "old_path", "old_path_len", "new_fd", "new_path", "new_path_len",
)
// pathOpen is the WASI function named functionPathOpen which opens a file or
// pathOpen is the WASI function named pathOpenName which opens a file or
// directory. This returns ErrnoBadf if the fd is invalid.
//
// # Parameters
@@ -1233,7 +1341,7 @@ func pathOpenFn(ctx context.Context, mod api.Module, params []uint64) Errno {
return ErrnoSuccess
}
// pathReadlink is the WASI function named functionPathReadlink that reads the
// pathReadlink is the WASI function named pathReadlinkName that reads the
// contents of a symbolic link.
//
// See: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_readlinkfd-fd-path-string-buf-pointeru8-buf_len-size---errno-size
@@ -1243,7 +1351,7 @@ var pathReadlink = stubFunction(
"fd", "path", "path_len", "buf", "buf_len", "result.bufused",
)
// pathRemoveDirectory is the WASI function named functionPathRemoveDirectory
// pathRemoveDirectory is the WASI function named pathRemoveDirectoryName
// which removes a directory.
//
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_remove_directoryfd-fd-path-string---errno
@@ -1253,7 +1361,7 @@ var pathRemoveDirectory = stubFunction(
"fd", "path", "path_len",
)
// pathRename is the WASI function named functionPathRename which renames a
// pathRename is the WASI function named pathRenameName which renames a
// file or directory.
var pathRename = stubFunction(
pathRenameName,
@@ -1261,7 +1369,7 @@ var pathRename = stubFunction(
"fd", "old_path", "old_path_len", "new_fd", "new_path", "new_path_len",
)
// pathSymlink is the WASI function named functionPathSymlink which creates a
// pathSymlink is the WASI function named pathSymlinkName which creates a
// symbolic link.
//
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#path_symlink
@@ -1271,7 +1379,7 @@ var pathSymlink = stubFunction(
"old_path", "old_path_len", "fd", "new_path", "new_path_len",
)
// pathUnlinkFile is the WASI function named functionPathUnlinkFile which
// pathUnlinkFile is the WASI function named pathUnlinkFileName which
// unlinks a file.
//
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_unlink_filefd-fd-path-string---errno

View File

@@ -100,7 +100,7 @@ func Test_fdDatasync(t *testing.T) {
func Test_fdFdstatGet(t *testing.T) {
file, dir := "a", "b"
testFS := fstest.MapFS{file: {Data: make([]byte, 0)}, dir: {Mode: fs.ModeDir}}
testFS := fstest.MapFS{file: {Data: make([]byte, 10), ModTime: time.Unix(1667482413, 0)}, dir: {Mode: fs.ModeDir, ModTime: time.Unix(1667482413, 0)}}
mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(testFS))
defer r.Close(testCtx)
@@ -116,16 +116,85 @@ func Test_fdFdstatGet(t *testing.T) {
require.NoError(t, err)
tests := []struct {
name string
fd, resultStat uint32
// TODO: expectedMem
expectedErrno Errno
expectedLog string
name string
fd, resultFdstat uint32
expectedMemory []byte
expectedErrno Errno
expectedLog string
}{
{
name: "stdin",
fd: internalsys.FdStdin,
expectedMemory: []byte{
2, 0, // fs_filetype
0, 0, 0, 0, 0, 0, // fs_flags
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_base
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_inheriting
},
expectedLog: `
--> proxy.fd_fdstat_get(fd=0,result.stat=0)
==> wasi_snapshot_preview1.fd_fdstat_get(fd=0,result.stat=0)
<== ESUCCESS
<-- 0
`,
},
{
name: "stdout",
fd: internalsys.FdStdout,
expectedMemory: []byte{
2, 0, // fs_filetype
1, 0, 0, 0, 0, 0, // fs_flags
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_base
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_inheriting
},
expectedLog: `
--> proxy.fd_fdstat_get(fd=1,result.stat=0)
==> wasi_snapshot_preview1.fd_fdstat_get(fd=1,result.stat=0)
<== ESUCCESS
<-- 0
`,
},
{
name: "stderr",
fd: internalsys.FdStderr,
expectedMemory: []byte{
2, 0, // fs_filetype
1, 0, 0, 0, 0, 0, // fs_flags
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_base
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_inheriting
},
expectedLog: `
--> proxy.fd_fdstat_get(fd=2,result.stat=0)
==> wasi_snapshot_preview1.fd_fdstat_get(fd=2,result.stat=0)
<== ESUCCESS
<-- 0
`,
},
{
name: "root",
fd: internalsys.FdStderr + 1,
expectedMemory: []byte{
3, 0, // fs_filetype
0, 0, 0, 0, 0, 0, // fs_flags
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_base
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_inheriting
},
expectedLog: `
--> proxy.fd_fdstat_get(fd=3,result.stat=0)
==> wasi_snapshot_preview1.fd_fdstat_get(fd=3,result.stat=0)
<== ESUCCESS
<-- 0
`,
},
{
name: "file",
fd: fileFd,
// TODO: expectedMem for a file
expectedMemory: []byte{
4, 0, // fs_filetype
0, 0, 0, 0, 0, 0, // fs_flags
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_base
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_inheriting
},
expectedLog: `
--> proxy.fd_fdstat_get(fd=4,result.stat=0)
==> wasi_snapshot_preview1.fd_fdstat_get(fd=4,result.stat=0)
@@ -136,7 +205,12 @@ func Test_fdFdstatGet(t *testing.T) {
{
name: "dir",
fd: dirFd,
// TODO: expectedMem for a dir
expectedMemory: []byte{
3, 0, // fs_filetype
0, 0, 0, 0, 0, 0, // fs_flags
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_base
0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_inheriting
},
expectedLog: `
--> proxy.fd_fdstat_get(fd=5,result.stat=0)
==> wasi_snapshot_preview1.fd_fdstat_get(fd=5,result.stat=0)
@@ -156,15 +230,15 @@ func Test_fdFdstatGet(t *testing.T) {
`,
},
{
name: "resultStat exceeds the maximum valid address by 1",
fd: dirFd,
resultStat: memorySize - 24 + 1,
// TODO: ErrnoFault
name: "resultFdstat exceeds the maximum valid address by 1",
fd: dirFd,
resultFdstat: memorySize - 24 + 1,
expectedErrno: ErrnoFault,
expectedLog: `
--> proxy.fd_fdstat_get(fd=5,result.stat=65513)
==> wasi_snapshot_preview1.fd_fdstat_get(fd=5,result.stat=65513)
<== ESUCCESS
<-- 0
<== EFAULT
<-- 21
`,
},
}
@@ -175,8 +249,14 @@ func Test_fdFdstatGet(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
defer log.Reset()
requireErrno(t, tc.expectedErrno, mod, fdFdstatGetName, uint64(tc.fd), uint64(tc.resultStat))
maskMemory(t, testCtx, mod, len(tc.expectedMemory))
requireErrno(t, tc.expectedErrno, mod, fdFdstatGetName, uint64(tc.fd), uint64(tc.resultFdstat))
require.Equal(t, tc.expectedLog, "\n"+log.String())
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(tc.expectedMemory)))
require.True(t, ok)
require.Equal(t, tc.expectedMemory, actual)
})
}
}
@@ -227,14 +307,94 @@ func Test_fdFilestatGet(t *testing.T) {
expectedErrno Errno
expectedLog string
}{
{
name: "stdin",
fd: internalsys.FdStdin,
expectedMemory: []byte{
0, 0, 0, 0, 0, 0, 0, 0, // dev
0, 0, 0, 0, 0, 0, 0, 0, // ino
2, 0, 0, 0, 0, 0, 0, 0, // filetype + padding
1, 0, 0, 0, 0, 0, 0, 0, // nlink
0, 0, 0, 0, 0, 0, 0, 0, // size
0, 0, 0, 0, 0, 0, 0, 0, // atim
0, 0, 0, 0, 0, 0, 0, 0, // mtim
0, 0, 0, 0, 0, 0, 0, 0, // ctim
},
expectedLog: `
--> proxy.fd_filestat_get(fd=0,result.buf=0)
==> wasi_snapshot_preview1.fd_filestat_get(fd=0,result.buf=0)
<== ESUCCESS
<-- 0
`,
},
{
name: "stdout",
fd: internalsys.FdStdout,
expectedMemory: []byte{
0, 0, 0, 0, 0, 0, 0, 0, // dev
0, 0, 0, 0, 0, 0, 0, 0, // ino
2, 0, 0, 0, 0, 0, 0, 0, // filetype + padding
1, 0, 0, 0, 0, 0, 0, 0, // nlink
0, 0, 0, 0, 0, 0, 0, 0, // size
0, 0, 0, 0, 0, 0, 0, 0, // atim
0, 0, 0, 0, 0, 0, 0, 0, // mtim
0, 0, 0, 0, 0, 0, 0, 0, // ctim
},
expectedLog: `
--> proxy.fd_filestat_get(fd=1,result.buf=0)
==> wasi_snapshot_preview1.fd_filestat_get(fd=1,result.buf=0)
<== ESUCCESS
<-- 0
`,
},
{
name: "stderr",
fd: internalsys.FdStderr,
expectedMemory: []byte{
0, 0, 0, 0, 0, 0, 0, 0, // dev
0, 0, 0, 0, 0, 0, 0, 0, // ino
2, 0, 0, 0, 0, 0, 0, 0, // filetype + padding
1, 0, 0, 0, 0, 0, 0, 0, // nlink
0, 0, 0, 0, 0, 0, 0, 0, // size
0, 0, 0, 0, 0, 0, 0, 0, // atim
0, 0, 0, 0, 0, 0, 0, 0, // mtim
0, 0, 0, 0, 0, 0, 0, 0, // ctim
},
expectedLog: `
--> proxy.fd_filestat_get(fd=2,result.buf=0)
==> wasi_snapshot_preview1.fd_filestat_get(fd=2,result.buf=0)
<== ESUCCESS
<-- 0
`,
},
{
name: "root",
fd: internalsys.FdStderr + 1,
expectedMemory: []byte{
0, 0, 0, 0, 0, 0, 0, 0, // dev
0, 0, 0, 0, 0, 0, 0, 0, // ino
3, 0, 0, 0, 0, 0, 0, 0, // filetype + padding
1, 0, 0, 0, 0, 0, 0, 0, // nlink
0, 0, 0, 0, 0, 0, 0, 0, // TODO: size
0, 0, 0, 0, 0, 0, 0, 0, // TODO: atim
0, 0, 0, 0, 0, 0, 0, 0, // TODO: mtim
0, 0, 0, 0, 0, 0, 0, 0, // TODO: ctim
},
expectedLog: `
--> proxy.fd_filestat_get(fd=3,result.buf=0)
==> wasi_snapshot_preview1.fd_filestat_get(fd=3,result.buf=0)
<== ESUCCESS
<-- 0
`,
},
{
name: "file",
fd: fileFd,
expectedMemory: []byte{
'?', '?', '?', '?', '?', '?', '?', '?', // dev
'?', '?', '?', '?', '?', '?', '?', '?', // ino
4, '?', '?', '?', '?', '?', '?', '?', // filetype + padding
'?', '?', '?', '?', '?', '?', '?', '?', // nlink
0, 0, 0, 0, 0, 0, 0, 0, // dev
0, 0, 0, 0, 0, 0, 0, 0, // ino
4, 0, 0, 0, 0, 0, 0, 0, // filetype + padding
1, 0, 0, 0, 0, 0, 0, 0, // nlink
10, 0, 0, 0, 0, 0, 0, 0, // size
0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // atim
0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // mtim
@@ -251,10 +411,10 @@ func Test_fdFilestatGet(t *testing.T) {
name: "dir",
fd: dirFd,
expectedMemory: []byte{
'?', '?', '?', '?', '?', '?', '?', '?', // dev
'?', '?', '?', '?', '?', '?', '?', '?', // ino
3, '?', '?', '?', '?', '?', '?', '?', // filetype + padding
'?', '?', '?', '?', '?', '?', '?', '?', // nlink
0, 0, 0, 0, 0, 0, 0, 0, // dev
0, 0, 0, 0, 0, 0, 0, 0, // ino
3, 0, 0, 0, 0, 0, 0, 0, // filetype + padding
1, 0, 0, 0, 0, 0, 0, 0, // nlink
0, 0, 0, 0, 0, 0, 0, 0, // size
0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // atim
0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // mtim
@@ -2167,10 +2327,10 @@ func Test_pathFilestatGet(t *testing.T) {
resultFilestat: 2,
expectedMemory: append(
initialMemoryFile,
'?', '?', '?', '?', '?', '?', '?', '?', // dev
'?', '?', '?', '?', '?', '?', '?', '?', // ino
4, '?', '?', '?', '?', '?', '?', '?', // filetype + padding
'?', '?', '?', '?', '?', '?', '?', '?', // nlink
0, 0, 0, 0, 0, 0, 0, 0, // dev
0, 0, 0, 0, 0, 0, 0, 0, // ino
4, 0, 0, 0, 0, 0, 0, 0, // filetype + padding
1, 0, 0, 0, 0, 0, 0, 0, // nlink
10, 0, 0, 0, 0, 0, 0, 0, // size
0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // atim
0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // mtim
@@ -2191,10 +2351,10 @@ func Test_pathFilestatGet(t *testing.T) {
resultFilestat: 2,
expectedMemory: append(
initialMemoryFile,
'?', '?', '?', '?', '?', '?', '?', '?', // dev
'?', '?', '?', '?', '?', '?', '?', '?', // ino
4, '?', '?', '?', '?', '?', '?', '?', // filetype + padding
'?', '?', '?', '?', '?', '?', '?', '?', // nlink
0, 0, 0, 0, 0, 0, 0, 0, // dev
0, 0, 0, 0, 0, 0, 0, 0, // ino
4, 0, 0, 0, 0, 0, 0, 0, // filetype + padding
1, 0, 0, 0, 0, 0, 0, 0, // nlink
20, 0, 0, 0, 0, 0, 0, 0, // size
0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // atim
0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // mtim
@@ -2215,10 +2375,10 @@ func Test_pathFilestatGet(t *testing.T) {
resultFilestat: 2,
expectedMemory: append(
initialMemoryDir,
'?', '?', '?', '?', '?', '?', '?', '?', // dev
'?', '?', '?', '?', '?', '?', '?', '?', // ino
3, '?', '?', '?', '?', '?', '?', '?', // filetype + padding
'?', '?', '?', '?', '?', '?', '?', '?', // nlink
0, 0, 0, 0, 0, 0, 0, 0, // dev
0, 0, 0, 0, 0, 0, 0, 0, // ino
3, 0, 0, 0, 0, 0, 0, 0, // filetype + padding
1, 0, 0, 0, 0, 0, 0, 0, // nlink
0, 0, 0, 0, 0, 0, 0, 0, // size
0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // atim
0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // mtim

View File

@@ -2,7 +2,6 @@ package wasi_snapshot_preview1
import (
"context"
"encoding/binary"
"github.com/tetratelabs/wazero/api"
internalsys "github.com/tetratelabs/wazero/internal/sys"
@@ -104,7 +103,7 @@ func pollOneoffFn(ctx context.Context, mod api.Module, params []uint64) Errno {
copy(outBuf, inBuf[inOffset:inOffset+8]) // userdata
outBuf[outOffset+8] = byte(errno) // uint16, but safe as < 255
outBuf[outOffset+9] = 0
binary.LittleEndian.PutUint32(outBuf[outOffset+10:], uint32(eventType))
le.PutUint32(outBuf[outOffset+10:], uint32(eventType))
// TODO: When FD events are supported, write outOffset+16
}
return ErrnoSuccess
@@ -113,10 +112,10 @@ func pollOneoffFn(ctx context.Context, mod api.Module, params []uint64) Errno {
// processClockEvent supports only relative name events, as that's what's used
// to implement sleep in various compilers including Rust, Zig and TinyGo.
func processClockEvent(ctx context.Context, mod api.Module, inBuf []byte) Errno {
_ /* ID */ = binary.LittleEndian.Uint32(inBuf[0:8]) // See below
timeout := binary.LittleEndian.Uint64(inBuf[8:16]) // nanos if relative
_ /* precision */ = binary.LittleEndian.Uint64(inBuf[16:24]) // Unused
flags := binary.LittleEndian.Uint16(inBuf[24:32])
_ /* ID */ = le.Uint32(inBuf[0:8]) // See below
timeout := le.Uint64(inBuf[8:16]) // nanos if relative
_ /* precision */ = le.Uint64(inBuf[16:24]) // Unused
flags := le.Uint16(inBuf[24:32])
// subclockflags has only one flag defined: subscription_clock_abstime
switch flags {
@@ -139,7 +138,7 @@ func processClockEvent(ctx context.Context, mod api.Module, inBuf []byte) Errno
// processFDEvent returns a validation error or ErrnoNotsup as file or socket
// subscriptions are not yet supported.
func processFDEvent(ctx context.Context, mod api.Module, eventType byte, inBuf []byte) Errno {
fd := binary.LittleEndian.Uint32(inBuf)
fd := le.Uint32(inBuf)
sysCtx := mod.(*wasm.CallContext).Sys
// Choose the best error, which falls back to unsupported, until we support

View File

@@ -1,8 +1,11 @@
[package]
name = "ls"
name = "wasi"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "ls"
path = "ls.rs"
name = "wasi"
path = "wasi.rs"
[dependencies]
libc = "0.2"

View File

@@ -1,7 +0,0 @@
use std::fs;
fn main() {
for path in fs::read_dir(".").unwrap() {
println!("{}", path.unwrap().path().display())
}
}

View File

@@ -0,0 +1,39 @@
use std::env;
use std::fs;
use std::io;
use std::io::Write;
use std::process::exit;
fn main() {
let args: Vec<String> = env::args().collect();
match args[1].as_str() {
"ls" => {
main_ls();
},
"stat" => {
main_stat();
},
_ => {
writeln!(io::stderr(), "unknown command: {}", args[1]).unwrap();
exit(1);
}
}
}
fn main_ls() {
for path in fs::read_dir(".").unwrap() {
println!("{}", path.unwrap().path().display())
}
}
extern crate libc;
fn main_stat() {
unsafe{
println!("stdin isatty: {}", libc::isatty(0) != 0);
println!("stdout isatty: {}", libc::isatty(1) != 0);
println!("stderr isatty: {}", libc::isatty(2) != 0);
println!("/ isatty: {}", libc::isatty(3) != 0);
}
}

Binary file not shown.

View File

@@ -1,15 +0,0 @@
#include <dirent.h>
#include <stdio.h>
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);
}

View File

@@ -0,0 +1,37 @@
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define formatBool(b) ((b) ? "true" : "false")
void main_ls() {
DIR *d;
struct dirent *dir;
d = opendir(".");
if (d) {
while ((dir = readdir(d)) != NULL) {
printf("./%s\n", dir->d_name);
}
closedir(d);
}
}
void main_stat() {
printf("stdin isatty: %s\n", formatBool(isatty(0)));
printf("stdout isatty: %s\n", formatBool(isatty(1)));
printf("stderr isatty: %s\n", formatBool(isatty(2)));
printf("/ isatty: %s\n", formatBool(isatty(3)));
}
int main(int argc, char** argv) {
if (strcmp(argv[1],"ls")==0) {
main_ls();
} else if (strcmp(argv[1],"stat")==0) {
main_stat();
} else {
fprintf(stderr, "unknown command: %s\n", argv[1]);
return 1;
}
return 0;
}

Binary file not shown.

View File

@@ -18,6 +18,7 @@ package wasi_snapshot_preview1
import (
"context"
"encoding/binary"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
@@ -32,6 +33,8 @@ const (
i32, i64 = wasm.ValueTypeI32, wasm.ValueTypeI64
)
var le = binary.LittleEndian
// MustInstantiate calls Instantiate or panics on error.
//
// This is a simpler function for those who know the module ModuleName is not

View File

@@ -1,3 +1,5 @@
// package wasi_snapshot_preview1 ensures that the behavior we've implemented
// not only matches the wasi spec, but also at least two compilers use of sdks.
package wasi_snapshot_preview1
import (
@@ -14,22 +16,20 @@ import (
"github.com/tetratelabs/wazero/sys"
)
// lsWasmCargoWasi was compiled from testdata/cargo-wasi/ls.rs
// wasmCargoWasi was compiled from testdata/cargo-wasi/wasi.rs
//
//go:embed testdata/cargo-wasi/ls.wasm
var lsWasmCargoWasi []byte
//go:embed testdata/cargo-wasi/wasi.wasm
var wasmCargoWasi []byte
// lsZigCc was compiled from testdata/zig-cc/ls.c
// wasmZigCc was compiled from testdata/zig-cc/wasi.c
//
//go:embed testdata/zig-cc/ls.wasm
var lsZigCc []byte
//go:embed testdata/zig-cc/wasi.wasm
var wasmZigCc []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,
"cargo-wasi": wasmCargoWasi,
"zig-cc": wasmZigCc,
} {
toolchain := toolchain
bin := bin
@@ -40,16 +40,17 @@ func Test_fdReaddir_ls(t *testing.T) {
}
func testFdReaddirLs(t *testing.T, bin []byte) {
moduleConfig := wazero.NewModuleConfig().WithArgs("wasi", "ls")
t.Run("empty directory", func(t *testing.T) {
stdout, stderr := compileAndRun(t, wazero.NewModuleConfig().
WithFS(fstest.MapFS{}), bin)
stdout, stderr := compileAndRun(t, moduleConfig.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().
stdout, stderr := compileAndRun(t, moduleConfig.
WithFS(fstest.MapFS{
"-": {},
"a-": {Mode: fs.ModeDir},
@@ -57,10 +58,11 @@ func testFdReaddirLs(t *testing.T, bin []byte) {
}), bin)
require.Zero(t, stderr)
require.Equal(t, `./-
require.Equal(t, `
./-
./a-
./ab-
`, stdout)
`, "\n"+stdout)
})
t.Run("directory with tons of entries", func(t *testing.T) {
@@ -69,8 +71,7 @@ func testFdReaddirLs(t *testing.T, bin []byte) {
for i := 0; i < count; i++ {
testFS[strconv.Itoa(i)] = &fstest.MapFile{}
}
stdout, stderr := compileAndRun(t, wazero.NewModuleConfig().
WithFS(testFS), bin)
stdout, stderr := compileAndRun(t, moduleConfig.WithFS(testFS), bin)
require.Zero(t, stderr)
lines := strings.Split(stdout, "\n")
@@ -78,6 +79,33 @@ func testFdReaddirLs(t *testing.T, bin []byte) {
})
}
func Test_fdReaddir_stat(t *testing.T) {
for toolchain, bin := range map[string][]byte{
"cargo-wasi": wasmCargoWasi,
"zig-cc": wasmZigCc,
} {
toolchain := toolchain
bin := bin
t.Run(toolchain, func(t *testing.T) {
testFdReaddirStat(t, bin)
})
}
}
func testFdReaddirStat(t *testing.T, bin []byte) {
moduleConfig := wazero.NewModuleConfig().WithArgs("wasi", "stat")
stdout, stderr := compileAndRun(t, moduleConfig.WithFS(fstest.MapFS{}), bin)
require.Zero(t, stderr)
require.Equal(t, `
stdin isatty: true
stdout isatty: true
stderr isatty: true
/ isatty: false
`, "\n"+stdout)
}
func compileAndRun(t *testing.T, config wazero.ModuleConfig, bin []byte) (stdout, stderr string) {
var stdoutBuf, stderrBuf bytes.Buffer
@@ -92,7 +120,7 @@ func compileAndRun(t *testing.T, config wazero.ModuleConfig, bin []byte) (stdout
_, err = r.InstantiateModule(testCtx, compiled, config.WithStdout(&stdoutBuf).WithStderr(&stderrBuf))
if exitErr, ok := err.(*sys.ExitError); ok {
require.Zero(t, exitErr.ExitCode())
require.Zero(t, exitErr.ExitCode(), stderrBuf.String())
} else {
require.NoError(t, err)
}