wasi: implements fd_renumber (#1095)
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user