Skip to content

Commit

Permalink
wasi: implements fd_renumber (#1095)
Browse files Browse the repository at this point in the history
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
  • Loading branch information
mathetake authored Feb 3, 2023
1 parent 8338a0c commit f719b49
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 6 deletions.
14 changes: 13 additions & 1 deletion imports/wasi_snapshot_preview1/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -958,7 +958,19 @@ func openedDir(fsc *sys.FSContext, fd uint32) (fs.ReadDirFile, *sys.ReadDir, Err
// 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")
var fdRenumber = newHostFunc(FdRenumberName, fdRenumberFn, []wasm.ValueType{i32, i32}, "fd", "to")

func fdRenumberFn(_ context.Context, mod api.Module, params []uint64) Errno {
fsc := mod.(*wasm.CallContext).Sys.FS()

from := uint32(params[0])
to := uint32(params[1])

if err := fsc.Renumber(from, to); err != nil {
return ToErrno(err)
}
return ErrnoSuccess
}

// fdSeek is the WASI function named FdSeekName which moves the offset of a
// file descriptor.
Expand Down
97 changes: 92 additions & 5 deletions imports/wasi_snapshot_preview1/fs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2098,11 +2098,98 @@ func Test_fdReaddir_Errors(t *testing.T) {

// Test_fdRenumber only tests it is stubbed for GrainLang per #271
func Test_fdRenumber(t *testing.T) {
log := requireErrnoNosys(t, FdRenumberName, 0, 0)
require.Equal(t, `
--> wasi_snapshot_preview1.fd_renumber(fd=0,to=0)
<-- errno=ENOSYS
`, log)
const preopenFd, fileFd, dirFd = 3, 4, 5

tests := []struct {
name string
from, to uint32
expectedErrno Errno
expectedLog string
}{
{
name: "from=preopen",
from: preopenFd,
to: dirFd,
expectedErrno: ErrnoNotsup,
expectedLog: `
==> wasi_snapshot_preview1.fd_renumber(fd=3,to=5)
<== errno=ENOTSUP
`,
},
{
name: "to=preopen",
from: dirFd,
to: 3,
expectedErrno: ErrnoNotsup,
expectedLog: `
==> wasi_snapshot_preview1.fd_renumber(fd=5,to=3)
<== errno=ENOTSUP
`,
},
{
name: "file to dir",
from: fileFd,
to: dirFd,
expectedErrno: ErrnoSuccess,
expectedLog: `
==> wasi_snapshot_preview1.fd_renumber(fd=4,to=5)
<== errno=ESUCCESS
`,
},
{
name: "dir to file",
from: dirFd,
to: fileFd,
expectedErrno: ErrnoSuccess,
expectedLog: `
==> wasi_snapshot_preview1.fd_renumber(fd=5,to=4)
<== errno=ESUCCESS
`,
},
{
name: "dir to any",
from: dirFd,
to: 12345,
expectedErrno: ErrnoSuccess,
expectedLog: `
==> wasi_snapshot_preview1.fd_renumber(fd=5,to=12345)
<== errno=ESUCCESS
`,
},
{
name: "file to any",
from: fileFd,
to: 54,
expectedErrno: ErrnoSuccess,
expectedLog: `
==> wasi_snapshot_preview1.fd_renumber(fd=4,to=54)
<== errno=ESUCCESS
`,
},
}

for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(fstest.FS))
defer r.Close(testCtx)

fsc := mod.(*wasm.CallContext).Sys.FS()
preopen := fsc.RootFS()

// Sanity check of the file descriptor assignment.
fileFdAssigned, err := fsc.OpenFile(preopen, "animals.txt", os.O_RDONLY, 0)
require.NoError(t, err)
require.Equal(t, uint32(fileFd), fileFdAssigned)

dirFdAssigned, err := fsc.OpenFile(preopen, "dir", os.O_RDONLY, 0)
require.NoError(t, err)
require.Equal(t, uint32(dirFd), dirFdAssigned)

requireErrno(t, tc.expectedErrno, mod, FdRenumberName, uint64(tc.from), uint64(tc.to))
require.Equal(t, tc.expectedLog, "\n"+log.String())
})
}
}

func Test_fdSeek(t *testing.T) {
Expand Down
11 changes: 11 additions & 0 deletions internal/sys/file_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,17 @@ func (t *FileTable) Lookup(fd uint32) (file *FileEntry, found bool) {
return
}

// InsertAt inserts the given `file` at the file descriptor `fd`.
func (t *FileTable) InsertAt(file *FileEntry, fd uint32) {
if diff := int(fd) - t.Len(); diff > 0 {
t.Grow(diff)
}
index := uint(fd) / 64
shift := uint(fd) % 64
t.masks[index] |= 1 << shift
t.files[fd] = file
}

// Delete deletes the file stored at the given fd from the table.
func (t *FileTable) Delete(fd uint32) {
if index, shift := fd/64, fd%64; int(index) < len(t.masks) {
Expand Down
23 changes: 23 additions & 0 deletions internal/sys/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,29 @@ func (c *FSContext) LookupFile(fd uint32) (*FileEntry, bool) {
return f, ok
}

// Renumber assigns the file pointed by the descriptor `from` to `to`.
func (c *FSContext) Renumber(from, to uint32) error {
fromFile, ok := c.openedFiles.Lookup(from)
if !ok {
return syscall.EBADF
} else if fromFile.IsPreopen {
return syscall.ENOTSUP
}

toFile, ok := c.openedFiles.Lookup(to)
if ok && toFile.IsPreopen {
return syscall.ENOTSUP
}

// TODO: What should we do to the dangling file `toFile` if `to` is already opened?
// The doc is unclear and other implementations does nothing for already-opened To FDs.
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_renumberfd-fd-to-fd---errno
// https://github.com/bytecodealliance/wasmtime/blob/main/crates/wasi-common/src/snapshots/preview_1.rs#L531-L546
c.openedFiles.Delete(from)
c.openedFiles.InsertAt(fromFile, to)
return nil
}

// CloseFile returns any error closing the existing file.
func (c *FSContext) CloseFile(fd uint32) error {
f, ok := c.openedFiles.Lookup(fd)
Expand Down
50 changes: 50 additions & 0 deletions internal/sys/fs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,3 +262,53 @@ func TestFSContext_ReOpenDir(t *testing.T) {
require.ErrorIs(t, err, syscall.EISDIR)
})
}

func TestFSContext_Renumber(t *testing.T) {
tmpDir := t.TempDir()
dirFs := sysfs.NewDirFS(tmpDir)

const dirName = "dir"
err := dirFs.Mkdir(dirName, 0o700)
require.NoError(t, err)

c, err := NewFSContext(nil, nil, nil, dirFs)
require.NoError(t, err)
defer func() {
require.NoError(t, c.Close(context.Background()))
}()

for _, toFd := range []uint32{10, 100, 100} {
fromFd, err := c.OpenFile(dirFs, dirName, os.O_RDONLY, 0)
require.NoError(t, err)

prevDirFile, ok := c.LookupFile(fromFd)
require.True(t, ok)

require.Equal(t, nil, c.Renumber(fromFd, toFd))

renumberedDirFile, ok := c.LookupFile(toFd)
require.True(t, ok)

require.Equal(t, prevDirFile, renumberedDirFile)

// Previous file descriptor shouldn't be used.
_, ok = c.LookupFile(fromFd)
require.False(t, ok)
}

t.Run("errors", func(t *testing.T) {
// Sanity check for 3 being preopen.
preopen, ok := c.LookupFile(3)
require.True(t, ok)
require.True(t, preopen.IsPreopen)

// From is preopen.
require.Equal(t, syscall.ENOTSUP, c.Renumber(3, 100))

// From does not exist.
require.Equal(t, syscall.EBADF, c.Renumber(12345, 3))

// Both are preopen.
require.Equal(t, syscall.ENOTSUP, c.Renumber(3, 3))
})
}

0 comments on commit f719b49

Please sign in to comment.