Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wasi: implements fd_renumber #1095

Merged
merged 3 commits into from
Feb 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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))
})
}