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.
|
// 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
|
// 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
|
// fdSeek is the WASI function named FdSeekName which moves the offset of a
|
||||||
// file descriptor.
|
// file descriptor.
|
||||||
|
|||||||
@@ -2098,11 +2098,98 @@ func Test_fdReaddir_Errors(t *testing.T) {
|
|||||||
|
|
||||||
// Test_fdRenumber only tests it is stubbed for GrainLang per #271
|
// Test_fdRenumber only tests it is stubbed for GrainLang per #271
|
||||||
func Test_fdRenumber(t *testing.T) {
|
func Test_fdRenumber(t *testing.T) {
|
||||||
log := requireErrnoNosys(t, FdRenumberName, 0, 0)
|
const preopenFd, fileFd, dirFd = 3, 4, 5
|
||||||
require.Equal(t, `
|
|
||||||
--> wasi_snapshot_preview1.fd_renumber(fd=0,to=0)
|
tests := []struct {
|
||||||
<-- errno=ENOSYS
|
name string
|
||||||
`, log)
|
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) {
|
func Test_fdSeek(t *testing.T) {
|
||||||
|
|||||||
@@ -92,6 +92,17 @@ func (t *FileTable) Lookup(fd uint32) (file *FileEntry, found bool) {
|
|||||||
return
|
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.
|
// Delete deletes the file stored at the given fd from the table.
|
||||||
func (t *FileTable) Delete(fd uint32) {
|
func (t *FileTable) Delete(fd uint32) {
|
||||||
if index, shift := fd/64, fd%64; int(index) < len(t.masks) {
|
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
|
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.
|
// CloseFile returns any error closing the existing file.
|
||||||
func (c *FSContext) CloseFile(fd uint32) error {
|
func (c *FSContext) CloseFile(fd uint32) error {
|
||||||
f, ok := c.openedFiles.Lookup(fd)
|
f, ok := c.openedFiles.Lookup(fd)
|
||||||
|
|||||||
@@ -262,3 +262,53 @@ func TestFSContext_ReOpenDir(t *testing.T) {
|
|||||||
require.ErrorIs(t, err, syscall.EISDIR)
|
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