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: prepares for native support of preopens #1067

Merged
merged 2 commits into from
Jan 29, 2023
Merged
Changes from 1 commit
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
Next Next commit
wasi: prepares for native support of preopens
This makes all the code changes necessary to enable multiple pre-opens
once #1077 is fixed.

Signed-off-by: Adrian Cole <[email protected]>
Adrian Cole committed Jan 29, 2023
commit fd45f0c9b61e69dd7bf3a14059bae3d9eb2ebe4a
6 changes: 4 additions & 2 deletions RATIONALE.md
Original file line number Diff line number Diff line change
@@ -633,8 +633,10 @@ STDERR (1) and invokes `fd_prestat_dir_name` to learn any path prefixes they
correspond to. Zig's `preopensAlloc` does similar. These pre-open functions are
not used again after initialization.

wazero currently supports only one pre-opened file, "/" and so that is the name
returned by `fd_prestat_dir_name` for file descriptor 3 (STDERR+1).
wazero supports stdio pre-opens followed by any mounts e.g `.:/`. The guest
path is a directory and its name, e.g. "/" is returned by `fd_prestat_dir_name`
for file descriptor 3 (STDERR+1). The first longest match wins on multiple
pre-opens, which allows a path like "/tmp" to match regardless of order vs "/".

See
* https://github.com/WebAssembly/wasi-libc/blob/a02298043ff551ce1157bc2ee7ab74c3bffe7144/libc-bottom-half/sources/preopens.c
8 changes: 4 additions & 4 deletions imports/wasi_snapshot_preview1/example/cat_test.go
Original file line number Diff line number Diff line change
@@ -81,15 +81,15 @@ func Test_cli(t *testing.T) {
}

cmdArgs = append(cmdArgs, "run",
"-hostlogging=filesystem",
fmt.Sprintf("-mount=%s:/", filepath.Dir(testTxtPath)),
fmt.Sprintf("-mount=%s:/testcases", filepath.Dir(testTxtPath)),
wasmPath, testPath)

stdOut := &bytes.Buffer{}
stdErr := &bytes.Buffer{}
var stdOut, stdErr bytes.Buffer
cmd := exec.Command(cmdExe, cmdArgs...)
cmd.Stdout = stdOut
cmd.Stderr = stdErr
cmd.Stdout = &stdOut
cmd.Stderr = &stdErr
require.NoError(t, cmd.Run(), stdErr.String())
require.Equal(t, "greet filesystem\n", stdOut.String())
})
47 changes: 26 additions & 21 deletions imports/wasi_snapshot_preview1/fs.go
Original file line number Diff line number Diff line change
@@ -1114,12 +1114,12 @@ func pathCreateDirectoryFn(_ context.Context, mod api.Module, params []uint64) E
path := uint32(params[1])
pathLen := uint32(params[2])

pathName, errno := atPath(fsc, mod.Memory(), dirFD, path, pathLen)
preopen, pathName, errno := atPath(fsc, mod.Memory(), dirFD, path, pathLen)
if errno != ErrnoSuccess {
return errno
}

if err := fsc.FS().Mkdir(pathName, 0o700); err != nil {
if err := preopen.Mkdir(pathName, 0o700); err != nil {
return ToErrno(err)
}

@@ -1172,15 +1172,15 @@ func pathFilestatGetFn(_ context.Context, mod api.Module, params []uint64) Errno
path := uint32(params[2])
pathLen := uint32(params[3])

pathName, errno := atPath(fsc, mod.Memory(), dirFD, path, pathLen)
preopen, pathName, errno := atPath(fsc, mod.Memory(), dirFD, path, pathLen)
if errno != ErrnoSuccess {
return errno
}

resultBuf := uint32(params[4])

// Stat the file without allocating a file descriptor
stat, err := sysfs.StatPath(fsc.FS(), pathName)
stat, err := sysfs.StatPath(preopen, pathName)
if err != nil {
return ToErrno(err)
}
@@ -1294,7 +1294,7 @@ func pathOpenFn(_ context.Context, mod api.Module, params []uint64) Errno {
fdflags := uint16(params[7])
resultOpenedFd := uint32(params[8])

pathName, errno := atPath(fsc, mod.Memory(), preopenFD, path, pathLen)
preopen, pathName, errno := atPath(fsc, mod.Memory(), preopenFD, path, pathLen)
if errno != ErrnoSuccess {
return errno
}
@@ -1305,7 +1305,7 @@ func pathOpenFn(_ context.Context, mod api.Module, params []uint64) Errno {
return ErrnoInval // use pathCreateDirectory!
}

newFD, err := fsc.OpenFile(pathName, fileOpenFlags, 0o600)
newFD, err := fsc.OpenFile(preopen, pathName, fileOpenFlags, 0o600)
if err != nil {
return ToErrno(err)
}
@@ -1343,19 +1343,21 @@ func pathOpenFn(_ context.Context, mod api.Module, params []uint64) Errno {
//
// See https://github.com/WebAssembly/wasi-libc/blob/659ff414560721b1660a19685110e484a081c3d4/libc-bottom-half/sources/at_fdcwd.c
// See https://linux.die.net/man/2/openat
func atPath(fsc *sys.FSContext, mem api.Memory, dirFD, path, pathLen uint32) (string, Errno) {
func atPath(fsc *sys.FSContext, mem api.Memory, dirFD, path, pathLen uint32) (sysfs.FS, string, Errno) {
b, ok := mem.Read(path, pathLen)
if !ok {
return "", ErrnoFault
return nil, "", ErrnoFault
}
pathName := string(b)

if f, ok := fsc.LookupFile(dirFD); !ok {
return "", ErrnoBadf // closed
} else if f.IsDir() {
return pathutil.Join(f.Name, pathName), ErrnoSuccess
return nil, "", ErrnoBadf // closed
} else if !f.IsDir() {
return nil, "", ErrnoNotdir
} else if f.IsPreopen { // don't append the pre-open name
return f.FS, pathName, ErrnoSuccess
} else {
return "", ErrnoNotdir
return f.FS, pathutil.Join(f.Name, pathName), ErrnoSuccess
}
}

@@ -1365,8 +1367,7 @@ func preopenPath(fsc *sys.FSContext, dirFD uint32) (string, Errno) {
} else if !f.IsPreopen {
return "", ErrnoBadf
} else {
// TODO: multiple pre-opens
return "/", ErrnoSuccess
return f.Name, ErrnoSuccess
}
}

@@ -1438,12 +1439,12 @@ func pathRemoveDirectoryFn(_ context.Context, mod api.Module, params []uint64) E
path := uint32(params[1])
pathLen := uint32(params[2])

pathName, errno := atPath(fsc, mod.Memory(), dirFD, path, pathLen)
preopen, pathName, errno := atPath(fsc, mod.Memory(), dirFD, path, pathLen)
if errno != ErrnoSuccess {
return errno
}

if err := fsc.FS().Rmdir(pathName); err != nil {
if err := preopen.Rmdir(pathName); err != nil {
return ToErrno(err)
}

@@ -1492,17 +1493,21 @@ func pathRenameFn(_ context.Context, mod api.Module, params []uint64) Errno {
newPath := uint32(params[4])
newPathLen := uint32(params[5])

oldPathName, errno := atPath(fsc, mod.Memory(), olddirFD, oldPath, oldPathLen)
oldFS, oldPathName, errno := atPath(fsc, mod.Memory(), olddirFD, oldPath, oldPathLen)
if errno != ErrnoSuccess {
return errno
}

newPathName, errno := atPath(fsc, mod.Memory(), newdirFD, newPath, newPathLen)
newFS, newPathName, errno := atPath(fsc, mod.Memory(), newdirFD, newPath, newPathLen)
if errno != ErrnoSuccess {
return errno
}

if err := fsc.FS().Rename(oldPathName, newPathName); err != nil {
if oldFS != newFS { // TODO: handle renames across filesystems
return ErrnoNosys
}

if err := oldFS.Rename(oldPathName, newPathName); err != nil {
return ToErrno(err)
}

@@ -1553,12 +1558,12 @@ func pathUnlinkFileFn(_ context.Context, mod api.Module, params []uint64) Errno
path := uint32(params[1])
pathLen := uint32(params[2])

pathName, errno := atPath(fsc, mod.Memory(), dirFD, path, pathLen)
preopen, pathName, errno := atPath(fsc, mod.Memory(), dirFD, path, pathLen)
if errno != ErrnoSuccess {
return errno
}

if err := fsc.FS().Unlink(pathName); err != nil {
if err := preopen.Unlink(pathName); err != nil {
return ToErrno(err)
}

29 changes: 18 additions & 11 deletions imports/wasi_snapshot_preview1/fs_test.go
Original file line number Diff line number Diff line change
@@ -49,11 +49,12 @@ func Test_fdClose(t *testing.T) {

// open both paths without using WASI
fsc := mod.(*wasm.CallContext).Sys.FS()
preopen := fsc.RootFS()

fdToClose, err := fsc.OpenFile(path1, os.O_RDONLY, 0)
fdToClose, err := fsc.OpenFile(preopen, path1, os.O_RDONLY, 0)
require.NoError(t, err)

fdToKeep, err := fsc.OpenFile(path2, os.O_RDONLY, 0)
fdToKeep, err := fsc.OpenFile(preopen, path2, os.O_RDONLY, 0)
require.NoError(t, err)

// Close
@@ -98,11 +99,12 @@ func Test_fdFdstatGet(t *testing.T) {

// open both paths without using WASI
fsc := mod.(*wasm.CallContext).Sys.FS()
preopen := fsc.RootFS()

fileFD, err := fsc.OpenFile(file, os.O_RDONLY, 0)
fileFD, err := fsc.OpenFile(preopen, file, os.O_RDONLY, 0)
require.NoError(t, err)

dirFD, err := fsc.OpenFile(dir, os.O_RDONLY, 0)
dirFD, err := fsc.OpenFile(preopen, dir, os.O_RDONLY, 0)
require.NoError(t, err)

tests := []struct {
@@ -261,11 +263,12 @@ func Test_fdFilestatGet(t *testing.T) {

// open both paths without using WASI
fsc := mod.(*wasm.CallContext).Sys.FS()
preopen := fsc.RootFS()

fileFD, err := fsc.OpenFile(file, os.O_RDONLY, 0)
fileFD, err := fsc.OpenFile(preopen, file, os.O_RDONLY, 0)
require.NoError(t, err)

dirFD, err := fsc.OpenFile(dir, os.O_RDONLY, 0)
dirFD, err := fsc.OpenFile(preopen, dir, os.O_RDONLY, 0)
require.NoError(t, err)

tests := []struct {
@@ -1401,8 +1404,9 @@ func Test_fdReaddir(t *testing.T) {
defer r.Close(testCtx)

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

fd, err := fsc.OpenFile("dir", os.O_RDONLY, 0)
fd, err := fsc.OpenFile(preopen, "dir", os.O_RDONLY, 0)
require.NoError(t, err)

tests := []struct {
@@ -1680,11 +1684,12 @@ func Test_fdReaddir_Errors(t *testing.T) {
memLen := mod.Memory().Size()

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

fileFD, err := fsc.OpenFile("animals.txt", os.O_RDONLY, 0)
fileFD, err := fsc.OpenFile(preopen, "animals.txt", os.O_RDONLY, 0)
require.NoError(t, err)

dirFD, err := fsc.OpenFile("dir", os.O_RDONLY, 0)
dirFD, err := fsc.OpenFile(preopen, "dir", os.O_RDONLY, 0)
require.NoError(t, err)

tests := []struct {
@@ -2849,8 +2854,9 @@ func Test_pathOpen(t *testing.T) {

func requireOpenFD(t *testing.T, mod api.Module, path string) uint32 {
fsc := mod.(*wasm.CallContext).Sys.FS()
preopen := fsc.RootFS()

fd, err := fsc.OpenFile(path, os.O_RDONLY, 0)
fd, err := fsc.OpenFile(preopen, path, os.O_RDONLY, 0)
require.NoError(t, err)
return fd
}
@@ -3561,8 +3567,9 @@ func requireOpenFile(t *testing.T, tmpDir string, pathName string, data []byte,

mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(fsConfig))
fsc := mod.(*wasm.CallContext).Sys.FS()
preopen := fsc.RootFS()

fd, err := fsc.OpenFile(pathName, oflags, 0)
fd, err := fsc.OpenFile(preopen, pathName, oflags, 0)
require.NoError(t, err)

return mod, fd, log, r
Binary file modified imports/wasi_snapshot_preview1/testdata/zig/wasi.wasm
Binary file not shown.
11 changes: 10 additions & 1 deletion imports/wasi_snapshot_preview1/testdata/zig/wasi.zig
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const std = @import("std");
const os = std.os;
const fs = std.fs;
const allocator = std.heap.page_allocator;
const preopensAlloc = std.fs.wasi.preopensAlloc;
const stdout = std.io.getStdOut().writer();

pub fn main() !void {
@@ -9,7 +11,7 @@ pub fn main() !void {
defer std.process.argsFree(allocator, args);

if (std.mem.eql(u8, args[1], "ls")) {
var dir = std.fs.cwd().openIterableDir(args[2], .{}) catch |err| switch (err) {
var dir = std.fs.openIterableDir(args[2], .{}) catch |err| switch (err) {
error.NotDir => {
try stdout.print("ENOTDIR\n", .{});
return;
@@ -28,5 +30,12 @@ pub fn main() !void {
try stdout.print("stdout isatty: {}\n", .{os.isatty(1)});
try stdout.print("stderr isatty: {}\n", .{os.isatty(2)});
try stdout.print("/ isatty: {}\n", .{os.isatty(3)});
} else if (std.mem.eql(u8, args[1], "preopen")) {
var wasi_preopens = try preopensAlloc(allocator);
// fs.wasi.Preopens does not have a free function

for (wasi_preopens.names) |preopen, i| {
try stdout.print("{}: {s}\n", .{ i, preopen });
}
}
}
6 changes: 4 additions & 2 deletions imports/wasi_snapshot_preview1/wasi_bench_test.go
Original file line number Diff line number Diff line change
@@ -174,7 +174,8 @@ func Benchmark_fdReaddir(b *testing.B) {

// Open the root directory as a file-descriptor.
fsc := mod.(*wasm.CallContext).Sys.FS()
fd, err := fsc.OpenFile(".", os.O_RDONLY, 0)
preopen := fsc.RootFS()
fd, err := fsc.OpenFile(preopen, ".", os.O_RDONLY, 0)
if err != nil {
b.Fatal(err)
}
@@ -280,7 +281,8 @@ func Benchmark_pathFilestat(b *testing.B) {
fd := sys.FdPreopen
if bc.fd != sys.FdPreopen {
fsc := mod.(*wasm.CallContext).Sys.FS()
fd, err = fsc.OpenFile("zig", os.O_RDONLY, 0)
preopen := fsc.RootFS()
fd, err = fsc.OpenFile(preopen, "zig", os.O_RDONLY, 0)
if err != nil {
b.Fatal(err)
}
28 changes: 28 additions & 0 deletions imports/wasi_snapshot_preview1/wasi_stdlib_test.go
Original file line number Diff line number Diff line change
@@ -122,6 +122,34 @@ stderr isatty: false
`, "\n"+console)
}

func Test_preopen(t *testing.T) {
for toolchain, bin := range map[string][]byte{
"zig": wasmZig,
} {
toolchain := toolchain
bin := bin
t.Run(toolchain, func(t *testing.T) {
testPreopen(t, bin)
})
}
}

func testPreopen(t *testing.T, bin []byte) {
moduleConfig := wazero.NewModuleConfig().WithArgs("wasi", "preopen")

console := compileAndRun(t, moduleConfig.
WithFSConfig(wazero.NewFSConfig().
WithDirMount(".", "/").
WithFSMount(fstest.MapFS{}, "/tmp")), bin)

require.Equal(t, `
0: stdin
1: stdout
2: stderr
3: /
`, "\n"+console)
}

func compileAndRun(t *testing.T, config wazero.ModuleConfig, bin []byte) (console string) {
// same for console and stderr as sometimes the stack trace is in one or the other.
var consoleBuf bytes.Buffer
Loading