diff --git a/experimental/sys/errno.go b/experimental/sys/errno.go index a07c21c29d..81baadd0d9 100644 --- a/experimental/sys/errno.go +++ b/experimental/sys/errno.go @@ -14,7 +14,7 @@ type Errno uint16 // the Errno it returns, and we export fs.FS. This is not in /internal/sys as // that would introduce a package cycle. -// This is a subset of errors to reduce implementation burden. `wasip` defines +// This is a subset of errors to reduce implementation burden. `wasip1` defines // almost all POSIX error numbers, but not all are used in practice. wazero // will add ones needed in POSIX order, as needed by functions that explicitly // document returning them. diff --git a/imports/wasi_snapshot_preview1/fs.go b/imports/wasi_snapshot_preview1/fs.go index bec2f037c0..f0985a7961 100644 --- a/imports/wasi_snapshot_preview1/fs.go +++ b/imports/wasi_snapshot_preview1/fs.go @@ -1687,7 +1687,7 @@ func preopenPath(fsc *sys.FSContext, fd int32) (string, experimentalsys.Errno) { } } -func openFlags(dirflags, oflags, fdflags uint16, rights uint32) (openFlags int) { +func openFlags(dirflags, oflags, fdflags uint16, rights uint32) (openFlags fsapi.Oflag) { if dirflags&wasip1.LOOKUP_SYMLINK_FOLLOW == 0 { openFlags |= fsapi.O_NOFOLLOW } @@ -1695,45 +1695,55 @@ func openFlags(dirflags, oflags, fdflags uint16, rights uint32) (openFlags int) openFlags |= fsapi.O_DIRECTORY return // Early return for directories as the rest of flags doesn't make sense for it. } else if oflags&wasip1.O_EXCL != 0 { - openFlags |= syscall.O_EXCL + openFlags |= fsapi.O_EXCL } - // Because we don't implement rights, we paritally rely on the open flags + // Because we don't implement rights, we partially rely on the open flags // to determine the mode in which the file will be opened. This will create // divergent behavior compared to WASI runtimes which have a more strict // interpretation of the WASI capabilities model; for example, a program // which sets O_CREAT but does not give read or write permissions will // successfully create a file when running with wazero, but might get a // permission denied error on other runtimes. - defaultMode := syscall.O_RDONLY + defaultMode := fsapi.O_RDONLY if oflags&wasip1.O_TRUNC != 0 { - openFlags |= syscall.O_TRUNC - defaultMode = syscall.O_RDWR + openFlags |= fsapi.O_TRUNC + defaultMode = fsapi.O_RDWR } if oflags&wasip1.O_CREAT != 0 { - openFlags |= syscall.O_CREAT - defaultMode = syscall.O_RDWR + openFlags |= fsapi.O_CREAT + defaultMode = fsapi.O_RDWR } if fdflags&wasip1.FD_NONBLOCK != 0 { openFlags |= fsapi.O_NONBLOCK } if fdflags&wasip1.FD_APPEND != 0 { - openFlags |= syscall.O_APPEND - defaultMode = syscall.O_RDWR + openFlags |= fsapi.O_APPEND + defaultMode = fsapi.O_RDWR } + if fdflags&wasip1.FD_DSYNC != 0 { + openFlags |= fsapi.O_DSYNC + } + if fdflags&wasip1.FD_RSYNC != 0 { + openFlags |= fsapi.O_RSYNC + } + if fdflags&wasip1.FD_SYNC != 0 { + openFlags |= fsapi.O_SYNC + } + // Since rights were discontinued in wasi, we only interpret RIGHT_FD_WRITE // because it is the only way to know that we need to set write permissions - // on a file if the application did not pass any of O_CREATE, O_APPEND, nor + // on a file if the application did not pass any of O_CREAT, O_APPEND, nor // O_TRUNC. const r = wasip1.RIGHT_FD_READ const w = wasip1.RIGHT_FD_WRITE const rw = r | w switch { case (rights & rw) == rw: - openFlags |= syscall.O_RDWR + openFlags |= fsapi.O_RDWR case (rights & w) == w: - openFlags |= syscall.O_WRONLY + openFlags |= fsapi.O_WRONLY case (rights & r) == r: - openFlags |= syscall.O_RDONLY + openFlags |= fsapi.O_RDONLY default: openFlags |= defaultMode } diff --git a/imports/wasi_snapshot_preview1/fs_test.go b/imports/wasi_snapshot_preview1/fs_test.go index a86e563979..80d375df6e 100644 --- a/imports/wasi_snapshot_preview1/fs_test.go +++ b/imports/wasi_snapshot_preview1/fs_test.go @@ -11,7 +11,6 @@ import ( "path" "runtime" "strings" - "syscall" "testing" gofstest "testing/fstest" "time" @@ -60,7 +59,7 @@ func Test_fdAllocate(t *testing.T) { preopen := fsc.RootFS() defer r.Close(testCtx) - fd, errno := fsc.OpenFile(preopen, fileName, os.O_RDWR, 0) + fd, errno := fsc.OpenFile(preopen, fileName, fsapi.O_RDWR, 0) require.EqualErrno(t, 0, errno) f, ok := fsc.LookupFile(fd) @@ -138,10 +137,10 @@ func Test_fdClose(t *testing.T) { fsc := mod.(*wasm.ModuleInstance).Sys.FS() preopen := fsc.RootFS() - fdToClose, errno := fsc.OpenFile(preopen, path1, os.O_RDONLY, 0) + fdToClose, errno := fsc.OpenFile(preopen, path1, fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) - fdToKeep, errno := fsc.OpenFile(preopen, path2, os.O_RDONLY, 0) + fdToKeep, errno := fsc.OpenFile(preopen, path2, fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) // Close @@ -253,10 +252,10 @@ func Test_fdFdstatGet(t *testing.T) { stdin.File = stdinFile // Make this file writeable, to ensure flags read-back correctly. - fileFD, errno := fsc.OpenFile(preopen, file, os.O_RDWR, 0) + fileFD, errno := fsc.OpenFile(preopen, file, fsapi.O_RDWR, 0) require.EqualErrno(t, 0, errno) - dirFD, errno := fsc.OpenFile(preopen, dir, os.O_RDONLY, 0) + dirFD, errno := fsc.OpenFile(preopen, dir, fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) tests := []struct { @@ -503,12 +502,12 @@ func Test_fdFdstatSetFlags(t *testing.T) { preopen := fsc.RootFS() defer r.Close(testCtx) - // First, O_CREATE the file with O_APPEND. We use O_EXCL because that - // triggers an EEXIST error if called a second time with O_CREATE. Our - // logic should clear O_CREATE preventing this. + // First, O_CREAT the file with O_APPEND. We use O_EXCL because that + // triggers an EEXIST error if called a second time with O_CREAT. Our + // logic should clear O_CREAT preventing this. const fileName = "file.txt" // Create the target file. - fd, errno := fsc.OpenFile(preopen, fileName, os.O_RDWR|os.O_APPEND|os.O_CREATE|syscall.O_EXCL, 0o600) + fd, errno := fsc.OpenFile(preopen, fileName, fsapi.O_RDWR|fsapi.O_APPEND|fsapi.O_CREAT|fsapi.O_EXCL, 0o600) require.EqualErrno(t, 0, errno) // Write the initial text to the file. @@ -615,10 +614,10 @@ func Test_fdFilestatGet(t *testing.T) { fsc := mod.(*wasm.ModuleInstance).Sys.FS() preopen := fsc.RootFS() - fileFD, errno := fsc.OpenFile(preopen, file, os.O_RDONLY, 0) + fileFD, errno := fsc.OpenFile(preopen, file, fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) - dirFD, errno := fsc.OpenFile(preopen, dir, os.O_RDONLY, 0) + dirFD, errno := fsc.OpenFile(preopen, dir, fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) tests := []struct { @@ -2157,7 +2156,7 @@ func Test_fdReaddir(t *testing.T) { t.Run(tc.name, func(t *testing.T) { defer log.Reset() - fd, errno := fsc.OpenFile(preopen, tc.initialDir, os.O_RDONLY, 0) + fd, errno := fsc.OpenFile(preopen, tc.initialDir, fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) defer fsc.CloseFile(fd) // nolint @@ -2199,7 +2198,7 @@ func Test_fdReaddir_Rewind(t *testing.T) { fsc := mod.(*wasm.ModuleInstance).Sys.FS() - fd, errno := fsc.OpenFile(fsc.RootFS(), ".", os.O_RDONLY, 0) + fd, errno := fsc.OpenFile(fsc.RootFS(), ".", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) mem := mod.Memory() @@ -2248,7 +2247,7 @@ func Test_fdReaddir_Errors(t *testing.T) { fsc := mod.(*wasm.ModuleInstance).Sys.FS() preopen := fsc.RootFS() - fileFD, errno := fsc.OpenFile(preopen, "animals.txt", os.O_RDONLY, 0) + fileFD, errno := fsc.OpenFile(preopen, "animals.txt", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) // Directories are stateful, so we open them during the test. @@ -2352,7 +2351,7 @@ func Test_fdReaddir_Errors(t *testing.T) { // Reset the directory so that tests don't taint each other. if tc.fd == dirFD { - dirFD, errno = fsc.OpenFile(preopen, "dir", os.O_RDONLY, 0) + dirFD, errno = fsc.OpenFile(preopen, "dir", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) defer fsc.CloseFile(dirFD) // nolint } @@ -2465,11 +2464,11 @@ func Test_fdRenumber(t *testing.T) { preopen := fsc.RootFS() // Sanity check of the file descriptor assignment. - fileFDAssigned, errno := fsc.OpenFile(preopen, "animals.txt", os.O_RDONLY, 0) + fileFDAssigned, errno := fsc.OpenFile(preopen, "animals.txt", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) require.Equal(t, int32(fileFD), fileFDAssigned) - dirFDAssigned, errno := fsc.OpenFile(preopen, "dir", os.O_RDONLY, 0) + dirFDAssigned, errno := fsc.OpenFile(preopen, "dir", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) require.Equal(t, int32(dirFD), dirFDAssigned) @@ -3605,7 +3604,7 @@ func Test_pathLink(t *testing.T) { uint64(newFd), uint64(destination), uint64(len(destinationName))) require.Contains(t, log.String(), wasip1.ErrnoName(wasip1.ErrnoSuccess)) - f := openFile(t, destinationRealPath, os.O_RDONLY, 0) + f := openFile(t, destinationRealPath, fsapi.O_RDONLY, 0) defer f.Close() st, errno := f.Stat() @@ -3927,7 +3926,7 @@ func requireOpenFD(t *testing.T, mod api.Module, path string) int32 { fsc := mod.(*wasm.ModuleInstance).Sys.FS() preopen := fsc.RootFS() - fd, errno := fsc.OpenFile(preopen, path, os.O_RDONLY, 0) + fd, errno := fsc.OpenFile(preopen, path, fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) return fd } @@ -4856,14 +4855,14 @@ func Test_pathUnlinkFile_Errors(t *testing.T) { } func requireOpenFile(t *testing.T, tmpDir string, pathName string, data []byte, readOnly bool) (api.Module, int32, *bytes.Buffer, api.Closer) { - oflags := os.O_RDWR + oflags := fsapi.O_RDWR if readOnly { - oflags = os.O_RDONLY + oflags = fsapi.O_RDONLY } realPath := joinPath(tmpDir, pathName) if data == nil { - oflags = os.O_RDONLY + oflags = fsapi.O_RDONLY require.NoError(t, os.Mkdir(realPath, 0o700)) } else { require.NoError(t, os.WriteFile(realPath, data, 0o600)) @@ -4910,7 +4909,7 @@ func Test_fdReaddir_dotEntryHasARealInode(t *testing.T) { uint64(sys.FdPreopen), uint64(0), uint64(len(readDirTarget))) // Open the directory, before writing files! - fd, errno := fsc.OpenFile(preopen, readDirTarget, os.O_RDONLY, 0) + fd, errno := fsc.OpenFile(preopen, readDirTarget, fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) // get the real inode of the current directory @@ -4968,11 +4967,11 @@ func Test_fdReaddir_opened_file_written(t *testing.T) { uint64(sys.FdPreopen), uint64(0), uint64(len(dirName))) // Open the directory, before writing files! - dirFD, errno := fsc.OpenFile(preopen, dirName, os.O_RDONLY, 0) + dirFD, errno := fsc.OpenFile(preopen, dirName, fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) // Then write a file to the directory. - f := openFile(t, joinPath(dirPath, "file"), os.O_CREATE, 0) + f := openFile(t, joinPath(dirPath, "file"), fsapi.O_CREAT, 0) defer f.Close() // get the real inode of the current directory @@ -5021,7 +5020,7 @@ func joinPath(dirName, baseName string) string { return path.Join(dirName, baseName) } -func openFile(t *testing.T, path string, flag int, perm fs.FileMode) fsapi.File { +func openFile(t *testing.T, path string, flag fsapi.Oflag, perm fs.FileMode) fsapi.File { f, errno := sysfs.OpenOSFile(path, flag, perm) require.EqualErrno(t, 0, errno) return f diff --git a/imports/wasi_snapshot_preview1/fs_unit_test.go b/imports/wasi_snapshot_preview1/fs_unit_test.go index c5a9c8e4a3..a65506087e 100644 --- a/imports/wasi_snapshot_preview1/fs_unit_test.go +++ b/imports/wasi_snapshot_preview1/fs_unit_test.go @@ -2,7 +2,6 @@ package wasi_snapshot_preview1 import ( "os" - "syscall" "testing" "github.com/tetratelabs/wazero/internal/fsapi" @@ -110,7 +109,7 @@ func Test_maxDirents(t *testing.T) { var ( testDirents = func() []fsapi.Dirent { dPath := "dir" - d, errno := sysfs.OpenFSFile(fstest.FS, dPath, syscall.O_RDONLY, 0) + d, errno := sysfs.OpenFSFile(fstest.FS, dPath, fsapi.O_RDONLY, 0) if errno != 0 { panic(errno) } @@ -208,16 +207,16 @@ func Test_openFlags(t *testing.T) { name string dirflags, oflags, fdflags uint16 rights uint32 - expectedOpenFlags int + expectedOpenFlags fsapi.Oflag }{ { name: "oflags=0", - expectedOpenFlags: fsapi.O_NOFOLLOW | syscall.O_RDONLY, + expectedOpenFlags: fsapi.O_NOFOLLOW | fsapi.O_RDONLY, }, { name: "oflags=O_CREAT", oflags: wasip1.O_CREAT, - expectedOpenFlags: fsapi.O_NOFOLLOW | syscall.O_RDWR | syscall.O_CREAT, + expectedOpenFlags: fsapi.O_NOFOLLOW | fsapi.O_RDWR | fsapi.O_CREAT, }, { name: "oflags=O_DIRECTORY", @@ -227,42 +226,42 @@ func Test_openFlags(t *testing.T) { { name: "oflags=O_EXCL", oflags: wasip1.O_EXCL, - expectedOpenFlags: fsapi.O_NOFOLLOW | syscall.O_RDONLY | syscall.O_EXCL, + expectedOpenFlags: fsapi.O_NOFOLLOW | fsapi.O_RDONLY | fsapi.O_EXCL, }, { name: "oflags=O_TRUNC", oflags: wasip1.O_TRUNC, - expectedOpenFlags: fsapi.O_NOFOLLOW | syscall.O_RDWR | syscall.O_TRUNC, + expectedOpenFlags: fsapi.O_NOFOLLOW | fsapi.O_RDWR | fsapi.O_TRUNC, }, { name: "fdflags=FD_APPEND", fdflags: wasip1.FD_APPEND, - expectedOpenFlags: fsapi.O_NOFOLLOW | syscall.O_RDWR | syscall.O_APPEND, + expectedOpenFlags: fsapi.O_NOFOLLOW | fsapi.O_RDWR | fsapi.O_APPEND, }, { name: "oflags=O_TRUNC|O_CREAT", oflags: wasip1.O_TRUNC | wasip1.O_CREAT, - expectedOpenFlags: fsapi.O_NOFOLLOW | syscall.O_RDWR | syscall.O_TRUNC | syscall.O_CREAT, + expectedOpenFlags: fsapi.O_NOFOLLOW | fsapi.O_RDWR | fsapi.O_TRUNC | fsapi.O_CREAT, }, { name: "dirflags=LOOKUP_SYMLINK_FOLLOW", dirflags: wasip1.LOOKUP_SYMLINK_FOLLOW, - expectedOpenFlags: syscall.O_RDONLY, + expectedOpenFlags: fsapi.O_RDONLY, }, { name: "rights=FD_READ", rights: wasip1.RIGHT_FD_READ, - expectedOpenFlags: fsapi.O_NOFOLLOW | syscall.O_RDONLY, + expectedOpenFlags: fsapi.O_NOFOLLOW | fsapi.O_RDONLY, }, { name: "rights=FD_WRITE", rights: wasip1.RIGHT_FD_WRITE, - expectedOpenFlags: fsapi.O_NOFOLLOW | syscall.O_WRONLY, + expectedOpenFlags: fsapi.O_NOFOLLOW | fsapi.O_WRONLY, }, { name: "rights=FD_READ|FD_WRITE", rights: wasip1.RIGHT_FD_READ | wasip1.RIGHT_FD_WRITE, - expectedOpenFlags: fsapi.O_NOFOLLOW | syscall.O_RDWR, + expectedOpenFlags: fsapi.O_NOFOLLOW | fsapi.O_RDWR, }, } diff --git a/imports/wasi_snapshot_preview1/wasi_bench_test.go b/imports/wasi_snapshot_preview1/wasi_bench_test.go index b03f99445b..41dd396729 100644 --- a/imports/wasi_snapshot_preview1/wasi_bench_test.go +++ b/imports/wasi_snapshot_preview1/wasi_bench_test.go @@ -9,6 +9,7 @@ import ( "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" + "github.com/tetratelabs/wazero/internal/fsapi" "github.com/tetratelabs/wazero/internal/sys" "github.com/tetratelabs/wazero/internal/testing/proxy" "github.com/tetratelabs/wazero/internal/wasip1" @@ -198,7 +199,7 @@ func Benchmark_fdReaddir(b *testing.B) { // Open the root directory as a file-descriptor. fsc := mod.(*wasm.ModuleInstance).Sys.FS() - fd, errno := fsc.OpenFile(fsc.RootFS(), ".", os.O_RDONLY, 0) + fd, errno := fsc.OpenFile(fsc.RootFS(), ".", fsapi.O_RDONLY, 0) if errno != 0 { b.Fatal(errno) } @@ -307,7 +308,7 @@ func Benchmark_pathFilestat(b *testing.B) { fd := sys.FdPreopen if bc.fd != sys.FdPreopen { fsc := mod.(*wasm.ModuleInstance).Sys.FS() - fd, errno := fsc.OpenFile(fsc.RootFS(), "zig", os.O_RDONLY, 0) + fd, errno := fsc.OpenFile(fsc.RootFS(), "zig", fsapi.O_RDONLY, 0) if errno != 0 { b.Fatal(errno) } diff --git a/internal/fsapi/constants.go b/internal/fsapi/constants.go deleted file mode 100644 index 868b9c16a3..0000000000 --- a/internal/fsapi/constants.go +++ /dev/null @@ -1,13 +0,0 @@ -//go:build !windows && !js && !illumos && !solaris - -package fsapi - -import "syscall" - -// Simple aliases to constants in the syscall package for portability with -// platforms which do not have them (e.g. windows) -const ( - O_DIRECTORY = syscall.O_DIRECTORY - O_NOFOLLOW = syscall.O_NOFOLLOW - O_NONBLOCK = syscall.O_NONBLOCK -) diff --git a/internal/fsapi/constants_js.go b/internal/fsapi/constants_js.go deleted file mode 100644 index af73ddb66c..0000000000 --- a/internal/fsapi/constants_js.go +++ /dev/null @@ -1,8 +0,0 @@ -package fsapi - -// See the comments on the same constants in constants_windows.go -const ( - O_DIRECTORY = 1 << 29 - O_NOFOLLOW = 1 << 30 - O_NONBLOCK = 1 << 31 -) diff --git a/internal/fsapi/constants_sun.go b/internal/fsapi/constants_sun.go deleted file mode 100644 index a0de49b73f..0000000000 --- a/internal/fsapi/constants_sun.go +++ /dev/null @@ -1,12 +0,0 @@ -//go:build illumos || solaris - -package fsapi - -import "syscall" - -// See https://github.com/illumos/illumos-gate/blob/edd580643f2cf1434e252cd7779e83182ea84945/usr/src/uts/common/sys/fcntl.h#L90 -const ( - O_DIRECTORY = 0x1000000 - O_NOFOLLOW = syscall.O_NOFOLLOW - O_NONBLOCK = syscall.O_NONBLOCK -) diff --git a/internal/fsapi/constants_windows.go b/internal/fsapi/constants_windows.go deleted file mode 100644 index 33aed87052..0000000000 --- a/internal/fsapi/constants_windows.go +++ /dev/null @@ -1,24 +0,0 @@ -package fsapi - -import "syscall" - -// Windows does not have these constants, we declare placeholders which should -// not conflict with other open flags. These placeholders are not declared as -// value zero so code written in a way which expects them to be bit flags still -// works as expected. -// -// Since those placeholder are not interpreted by the open function, the unix -// features they represent are also not implemented on windows: -// -// - O_DIRECTORY allows programs to ensure that the opened file is a directory. -// This could be emulated by doing a stat call on the file after opening it -// to verify that it is in fact a directory, then closing it and returning an -// error if it is not. -// -// - O_NOFOLLOW allows programs to ensure that if the opened file is a symbolic -// link, the link itself is opened instead of its target. -const ( - O_DIRECTORY = 1 << 29 - O_NOFOLLOW = 1 << 30 - O_NONBLOCK = syscall.O_NONBLOCK -) diff --git a/internal/fsapi/file.go b/internal/fsapi/file.go index b4ee85b83e..f40d4155a8 100644 --- a/internal/fsapi/file.go +++ b/internal/fsapi/file.go @@ -96,7 +96,7 @@ type File interface { // POSIX. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/fcntl.html SetNonblock(enable bool) experimentalsys.Errno - // IsAppend returns true if the file was opened with syscall.O_APPEND, or + // IsAppend returns true if the file was opened with fsapi.O_APPEND, or // SetAppend was successfully enabled on this file. // // # Notes @@ -105,7 +105,7 @@ type File interface { // the file was not opened via OpenFile. IsAppend() bool - // SetAppend toggles the append mode (syscall.O_APPEND) of this file. + // SetAppend toggles the append mode (fsapi.O_APPEND) of this file. // // # Errors // @@ -351,7 +351,7 @@ type File interface { // // - This is like syscall.UtimesNano and `futimens` in POSIX. See // https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html - // - Windows requires files to be open with syscall.O_RDWR, which means you + // - Windows requires files to be open with fsapi.O_RDWR, which means you // cannot use this to update timestamps on a directory (EPERM). Utimens(times *[2]syscall.Timespec) experimentalsys.Errno diff --git a/internal/fsapi/fs.go b/internal/fsapi/fs.go index 50dfd1e6ea..25cc030273 100644 --- a/internal/fsapi/fs.go +++ b/internal/fsapi/fs.go @@ -34,18 +34,17 @@ type FS interface { // # Errors // // A zero Errno is success. The below are expected otherwise: - // - ENOSYS: the implementation does not support this function. - // - EINVAL: `path` or `flag` is invalid. - // - EISDIR: the path was a directory, but flag included - // syscall.O_RDWR or syscall.O_WRONLY - // - ENOENT: `path` doesn't exist and `flag` doesn't contain - // os.O_CREATE. + // - sys.ENOSYS: the implementation does not support this function. + // - sys.EINVAL: `path` or `flag` is invalid. + // - sys.EISDIR: the path was a directory, but flag included O_RDWR or + // O_WRONLY + // - sys.ENOENT: `path` doesn't exist and `flag` doesn't contain O_CREAT. // // # Constraints on the returned file // // Implementations that can read flags should enforce them regardless of // the type returned. For example, while os.File implements io.Writer, - // attempts to write to a directory or a file opened with os.O_RDONLY fail + // attempts to write to a directory or a file opened with O_RDONLY fail // with a EBADF. // // Some implementations choose whether to enforce read-only opens, namely @@ -57,21 +56,18 @@ type FS interface { // // - This is like os.OpenFile, except the path is relative to this file // system, and Errno is returned instead of os.PathError. - // - flag are the same as os.OpenFile, for example, os.O_CREATE. - // - Implications of permissions when os.O_CREATE are described in Chmod - // notes. + // - Implications of permissions when O_CREAT are described in Chmod notes. // - This is like `open` in POSIX. See // https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html - OpenFile(path string, flag int, perm fs.FileMode) (File, experimentalsys.Errno) - // TODO: make sure all flags are not in the syscall package + OpenFile(path string, flag Oflag, perm fs.FileMode) (File, experimentalsys.Errno) // Lstat gets file status without following symbolic links. // // # Errors // // A zero Errno is success. The below are expected otherwise: - // - ENOSYS: the implementation does not support this function. - // - ENOENT: `path` doesn't exist. + // - sys.ENOSYS: the implementation does not support this function. + // - sys.ENOENT: `path` doesn't exist. // // # Notes // @@ -90,8 +86,8 @@ type FS interface { // # Errors // // A zero Errno is success. The below are expected otherwise: - // - ENOSYS: the implementation does not support this function. - // - ENOENT: `path` doesn't exist. + // - sys.ENOSYS: the implementation does not support this function. + // - sys.ENOENT: `path` doesn't exist. // // # Notes // @@ -110,10 +106,10 @@ type FS interface { // # Errors // // A zero Errno is success. The below are expected otherwise: - // - ENOSYS: the implementation does not support this function. - // - EINVAL: `path` is invalid. - // - EEXIST: `path` exists and is a directory. - // - ENOTDIR: `path` exists and is a file. + // - sys.ENOSYS: the implementation does not support this function. + // - sys.EINVAL: `path` is invalid. + // - sys.EEXIST: `path` exists and is a directory. + // - sys.ENOTDIR: `path` exists and is a file. // // # Notes // @@ -129,9 +125,9 @@ type FS interface { // # Errors // // A zero Errno is success. The below are expected otherwise: - // - ENOSYS: the implementation does not support this function. - // - EINVAL: `path` is invalid. - // - ENOENT: `path` does not exist. + // - sys.ENOSYS: the implementation does not support this function. + // - sys.EINVAL: `path` is invalid. + // - sys.ENOENT: `path` does not exist. // // # Notes // @@ -149,12 +145,12 @@ type FS interface { // # Errors // // A zero Errno is success. The below are expected otherwise: - // - ENOSYS: the implementation does not support this function. - // - EINVAL: `from` or `to` is invalid. - // - ENOENT: `from` or `to` don't exist. - // - ENOTDIR: `from` is a directory and `to` exists as a file. - // - EISDIR: `from` is a file and `to` exists as a directory. - // - ENOTEMPTY: `both from` and `to` are existing directory, but + // - sys.ENOSYS: the implementation does not support this function. + // - sys.EINVAL: `from` or `to` is invalid. + // - sys.ENOENT: `from` or `to` don't exist. + // - sys.ENOTDIR: `from` is a directory and `to` exists as a file. + // - sys.EISDIR: `from` is a file and `to` exists as a directory. + // - sys.ENOTEMPTY: `both from` and `to` are existing directory, but // `to` is not empty. // // # Notes @@ -171,11 +167,11 @@ type FS interface { // # Errors // // A zero Errno is success. The below are expected otherwise: - // - ENOSYS: the implementation does not support this function. - // - EINVAL: `path` is invalid. - // - ENOENT: `path` doesn't exist. - // - ENOTDIR: `path` exists, but isn't a directory. - // - ENOTEMPTY: `path` exists, but isn't empty. + // - sys.ENOSYS: the implementation does not support this function. + // - sys.EINVAL: `path` is invalid. + // - sys.ENOENT: `path` doesn't exist. + // - sys.ENOTDIR: `path` exists, but isn't a directory. + // - sys.ENOTEMPTY: `path` exists, but isn't empty. // // # Notes // @@ -183,7 +179,7 @@ type FS interface { // file system. // - This is like `rmdir` in POSIX. See // https://pubs.opengroup.org/onlinepubs/9699919799/functions/rmdir.html - // - As of Go 1.19, Windows maps ENOTDIR to ENOENT. + // - As of Go 1.19, Windows maps sys.ENOTDIR to sys.ENOENT. Rmdir(path string) experimentalsys.Errno // Unlink removes a directory entry. @@ -191,10 +187,10 @@ type FS interface { // # Errors // // A zero Errno is success. The below are expected otherwise: - // - ENOSYS: the implementation does not support this function. - // - EINVAL: `path` is invalid. - // - ENOENT: `path` doesn't exist. - // - EISDIR: `path` exists, but is a directory. + // - sys.ENOSYS: the implementation does not support this function. + // - sys.EINVAL: `path` is invalid. + // - sys.ENOENT: `path` doesn't exist. + // - sys.EISDIR: `path` exists, but is a directory. // // # Notes // @@ -213,10 +209,10 @@ type FS interface { // # Errors // // A zero Errno is success. The below are expected otherwise: - // - ENOSYS: the implementation does not support this function. - // - EPERM: `oldPath` is invalid. - // - ENOENT: `oldPath` doesn't exist. - // - EISDIR: `newPath` exists, but is a directory. + // - sys.ENOSYS: the implementation does not support this function. + // - sys.EPERM: `oldPath` is invalid. + // - sys.ENOENT: `oldPath` doesn't exist. + // - sys.EISDIR: `newPath` exists, but is a directory. // // # Notes // @@ -232,9 +228,9 @@ type FS interface { // # Errors // // A zero Errno is success. The below are expected otherwise: - // - ENOSYS: the implementation does not support this function. - // - EPERM: `oldPath` or `newPath` is invalid. - // - EEXIST: `newPath` exists. + // - sys.ENOSYS: the implementation does not support this function. + // - sys.EPERM: `oldPath` or `newPath` is invalid. + // - sys.EEXIST: `newPath` exists. // // # Notes // @@ -248,7 +244,7 @@ type FS interface { // See https://github.com/bytecodealliance/cap-std/blob/v1.0.4/cap-std/src/fs/dir.rs#L404-L409 // for how others implement this. // - Symlinks in Windows requires `SeCreateSymbolicLinkPrivilege`. - // Otherwise, EPERM results. + // Otherwise, sys.EPERM results. // See https://learn.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/create-symbolic-links Symlink(oldPath, linkName string) experimentalsys.Errno @@ -257,8 +253,8 @@ type FS interface { // # Errors // // A zero Errno is success. The below are expected otherwise: - // - ENOSYS: the implementation does not support this function. - // - EINVAL: `path` is invalid. + // - sys.ENOSYS: the implementation does not support this function. + // - sys.EINVAL: `path` is invalid. // // # Notes // @@ -287,10 +283,10 @@ type FS interface { // # Errors // // A zero Errno is success. The below are expected otherwise: - // - ENOSYS: the implementation does not support this function. - // - EINVAL: `path` is invalid. - // - EEXIST: `path` exists and is a directory. - // - ENOTDIR: `path` exists and is a file. + // - sys.ENOSYS: the implementation does not support this function. + // - sys.EINVAL: `path` is invalid. + // - sys.EEXIST: `path` exists and is a directory. + // - sys.ENOTDIR: `path` exists and is a file. // // # Notes // diff --git a/internal/fsapi/oflag.go b/internal/fsapi/oflag.go new file mode 100644 index 0000000000..eac4fc9874 --- /dev/null +++ b/internal/fsapi/oflag.go @@ -0,0 +1,70 @@ +package fsapi + +// Oflag are flags used for FS.OpenFile. Values, including zero, should not be +// interpreted numerically. Instead, use by constants prefixed with 'O_' with +// special casing noted below. +// +// # Notes +// +// - O_RDONLY, O_RDWR and O_WRONLY are mutually exclusive, while the other +// flags can coexist bitwise. +// - This is like `flag` in os.OpenFile and `oflag` in POSIX. See +// https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html +type Oflag uint32 + +// This is a subset of oflags to reduce implementation burden. `wasip1` splits +// these across `oflags` and `fdflags`. We can't rely on the Go `os` package, +// as it is missing some values. Any flags added will be defined in POSIX +// order, as needed by functions that explicitly document accepting them. +// +// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-oflags-flagsu16 +// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fdflags-flagsu16 +const ( + // O_RDONLY is like os.O_RDONLY + O_RDONLY Oflag = iota + + // O_RDWR is like os.O_RDWR + O_RDWR + + // O_WRONLY is like os.O_WRONLY + O_WRONLY + + // Define bitflags as they are in POSIX `open`: alphabetically + + // O_APPEND is like os.O_APPEND + O_APPEND Oflag = 1 << iota + + // O_CREAT is link os.O_CREATE + O_CREAT + + // O_DIRECTORY is defined on some platforms as syscall.O_DIRECTORY. + // + // Note: This ensures that the opened file is a directory. Those emulating + // on platforms that don't support the O_DIRECTORY, can double-check the + // result with File.IsDir (or stat) and err if not a directory. + O_DIRECTORY + + // O_DSYNC is defined on some platforms as syscall.O_DSYNC. + O_DSYNC + + // O_EXCL is defined on some platforms as syscall.O_EXCL. + O_EXCL + + // O_NOFOLLOW is defined on some platforms as syscall.O_NOFOLLOW. + // + // Note: This allows programs to ensure that if the opened file is a + // symbolic link, the link itself is opened instead of its target. + O_NOFOLLOW + + // O_NONBLOCK is defined on some platforms as syscall.O_NONBLOCK. + O_NONBLOCK + + // O_RSYNC is defined on some platforms as syscall.O_RSYNC. + O_RSYNC + + // O_SYNC is defined on some platforms as syscall.O_SYNC. + O_SYNC + + // O_TRUNC is defined on some platforms as syscall.O_TRUNC. + O_TRUNC +) diff --git a/internal/fsapi/unimplemented.go b/internal/fsapi/unimplemented.go index 81a636f34c..6f2ab1f1c8 100644 --- a/internal/fsapi/unimplemented.go +++ b/internal/fsapi/unimplemented.go @@ -24,7 +24,7 @@ func (UnimplementedFS) Open(name string) (fs.File, error) { } // OpenFile implements FS.OpenFile -func (UnimplementedFS) OpenFile(path string, flag int, perm fs.FileMode) (File, experimentalsys.Errno) { +func (UnimplementedFS) OpenFile(path string, flag Oflag, perm fs.FileMode) (File, experimentalsys.Errno) { return nil, experimentalsys.ENOSYS } diff --git a/internal/gojs/fs.go b/internal/gojs/fs.go index eeabecfdc9..1422665355 100644 --- a/internal/gojs/fs.go +++ b/internal/gojs/fs.go @@ -3,11 +3,11 @@ package gojs import ( "context" "fmt" - "os" "syscall" "github.com/tetratelabs/wazero/api" experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" "github.com/tetratelabs/wazero/internal/gojs/custom" "github.com/tetratelabs/wazero/internal/gojs/goos" "github.com/tetratelabs/wazero/internal/gojs/util" @@ -29,22 +29,22 @@ var ( }) // oWRONLY = jsfsConstants Get("O_WRONLY").Int() // fs_js.go init - oWRONLY = float64(os.O_WRONLY) + oWRONLY = float64(fsapi.O_WRONLY) // oRDWR = jsfsConstants Get("O_RDWR").Int() // fs_js.go init - oRDWR = float64(os.O_RDWR) + oRDWR = float64(fsapi.O_RDWR) // o CREAT = jsfsConstants Get("O_CREAT").Int() // fs_js.go init - oCREAT = float64(os.O_CREATE) + oCREAT = float64(fsapi.O_CREAT) // oTRUNC = jsfsConstants Get("O_TRUNC").Int() // fs_js.go init - oTRUNC = float64(os.O_TRUNC) + oTRUNC = float64(fsapi.O_TRUNC) // oAPPEND = jsfsConstants Get("O_APPEND").Int() // fs_js.go init - oAPPEND = float64(os.O_APPEND) + oAPPEND = float64(fsapi.O_APPEND) // oEXCL = jsfsConstants Get("O_EXCL").Int() // fs_js.go init - oEXCL = float64(os.O_EXCL) + oEXCL = float64(fsapi.O_EXCL) ) // jsfs = js.Global().Get("fs") // fs_js.go init @@ -92,13 +92,15 @@ type jsfsOpen struct { func (o *jsfsOpen) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) { path := util.ResolvePath(o.proc.cwd, args[0].(string)) - flags := toUint64(args[1]) // flags are derived from constants like oWRONLY + // Note: these are already fsapi.Flag because Go uses constants we define: + // https://github.com/golang/go/blob/go1.20/src/syscall/fs_js.go#L24-L31 + flags := fsapi.Oflag(toUint64(args[1])) perm := custom.FromJsMode(goos.ValueToUint32(args[2]), o.proc.umask) callback := args[3].(funcWrapper) fsc := mod.(*wasm.ModuleInstance).Sys.FS() - fd, errno := fsc.OpenFile(fsc.RootFS(), path, int(flags), perm) + fd, errno := fsc.OpenFile(fsc.RootFS(), path, flags, perm) return callback.invoke(ctx, mod, goos.RefJsfs, maybeError(errno), fd) // note: error first } @@ -318,7 +320,7 @@ func syscallReaddir(_ context.Context, mod api.Module, name string) (*objectArra fsc := mod.(*wasm.ModuleInstance).Sys.FS() // don't allocate a file descriptor - f, errno := fsc.RootFS().OpenFile(name, os.O_RDONLY, 0) + f, errno := fsc.RootFS().OpenFile(name, fsapi.O_RDONLY, 0) if errno != 0 { return nil, errno } @@ -357,7 +359,7 @@ func (m *jsfsMkdir) invoke(ctx context.Context, mod api.Module, args ...interfac perm = 0o0500 } if errno = root.Mkdir(path, perm); errno == 0 { - fd, errno = fsc.OpenFile(root, path, os.O_RDONLY, 0) + fd, errno = fsc.OpenFile(root, path, fsapi.O_RDONLY, 0) } return callback.invoke(ctx, mod, goos.RefJsfs, maybeError(errno), fd) // note: error first diff --git a/internal/gojs/testdata/writefs/main.go b/internal/gojs/testdata/writefs/main.go index 1766044e56..d06b95cce9 100644 --- a/internal/gojs/testdata/writefs/main.go +++ b/internal/gojs/testdata/writefs/main.go @@ -24,6 +24,7 @@ func Main() { // Create a test file in that directory file := path.Join(dir, "file") file1 := path.Join(os.TempDir(), "file1") + if err := os.WriteFile(file, []byte{}, 0o600); err != nil { log.Panicln(err) return diff --git a/internal/sys/fs.go b/internal/sys/fs.go index 32e2e7b4e9..f85e2ff24e 100644 --- a/internal/sys/fs.go +++ b/internal/sys/fs.go @@ -284,7 +284,7 @@ func (c *FSContext) LookupFile(fd int32) (*FileEntry, bool) { // OpenFile opens the file into the table and returns its file descriptor. // The result must be closed by CloseFile or Close. -func (c *FSContext) OpenFile(fs fsapi.FS, path string, flag int, perm fs.FileMode) (int32, sys.Errno) { +func (c *FSContext) OpenFile(fs fsapi.FS, path string, flag fsapi.Oflag, perm fs.FileMode) (int32, sys.Errno) { if f, errno := fs.OpenFile(path, flag, perm); errno != 0 { return 0, errno } else { diff --git a/internal/sys/fs_test.go b/internal/sys/fs_test.go index b8b7a1b749..b3e581405b 100644 --- a/internal/sys/fs_test.go +++ b/internal/sys/fs_test.go @@ -105,10 +105,10 @@ func TestFSContext_CloseFile(t *testing.T) { fsc := c.fsc defer fsc.Close() - fdToClose, errno := fsc.OpenFile(testFS, "empty.txt", os.O_RDONLY, 0) + fdToClose, errno := fsc.OpenFile(testFS, "empty.txt", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) - fdToKeep, errno := fsc.OpenFile(testFS, "test.txt", os.O_RDONLY, 0) + fdToKeep, errno := fsc.OpenFile(testFS, "test.txt", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) // Close @@ -165,7 +165,7 @@ func TestContext_Close(t *testing.T) { // Verify base case require.Equal(t, 1+FdPreopen, int32(fsc.openedFiles.Len())) - _, errno := fsc.OpenFile(testFS, "foo", os.O_RDONLY, 0) + _, errno := fsc.OpenFile(testFS, "foo", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) require.Equal(t, 2+FdPreopen, int32(fsc.openedFiles.Len())) @@ -190,7 +190,7 @@ func TestContext_Close_Error(t *testing.T) { fsc := c.fsc // open another file - _, errno := fsc.OpenFile(testFS, "foo", os.O_RDONLY, 0) + _, errno := fsc.OpenFile(testFS, "foo", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) // arbitrary errors coerce to EIO @@ -216,7 +216,7 @@ func TestFSContext_Renumber(t *testing.T) { defer fsc.Close() for _, toFd := range []int32{10, 100, 100} { - fromFd, errno := fsc.OpenFile(dirFS, dirName, os.O_RDONLY, 0) + fromFd, errno := fsc.OpenFile(dirFS, dirName, fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) prevDirFile, ok := fsc.LookupFile(fromFd) @@ -405,7 +405,7 @@ func TestDirentCache_Read(t *testing.T) { for _, tt := range tests { tc := tt t.Run(tc.name, func(t *testing.T) { - fd, errno := fsc.OpenFile(fsc.RootFS(), tc.initialDir, os.O_RDONLY, 0) + fd, errno := fsc.OpenFile(fsc.RootFS(), tc.initialDir, fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) defer fsc.CloseFile(fd) // nolint f, _ := fsc.LookupFile(fd) @@ -436,7 +436,7 @@ func TestDirentCache_ReadNewFile(t *testing.T) { fsc := c.fsc defer fsc.Close() - fd, errno := fsc.OpenFile(fsc.RootFS(), ".", os.O_RDONLY, 0) + fd, errno := fsc.OpenFile(fsc.RootFS(), ".", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) defer fsc.CloseFile(fd) // nolint f, _ := fsc.LookupFile(fd) diff --git a/internal/sys/lazy.go b/internal/sys/lazy.go index 718398b920..011a5a4dc5 100644 --- a/internal/sys/lazy.go +++ b/internal/sys/lazy.go @@ -1,7 +1,6 @@ package sys import ( - "os" "syscall" experimentalsys "github.com/tetratelabs/wazero/experimental/sys" @@ -118,7 +117,7 @@ func (r *lazyDir) file() (fsapi.File, bool) { return f, true } var errno experimentalsys.Errno - r.f, errno = r.fs.OpenFile(".", os.O_RDONLY, 0) + r.f, errno = r.fs.OpenFile(".", fsapi.O_RDONLY, 0) switch errno { case 0: return r.f, true diff --git a/internal/sys/stdio.go b/internal/sys/stdio.go index 59ffd084a7..b153e32c19 100644 --- a/internal/sys/stdio.go +++ b/internal/sys/stdio.go @@ -3,7 +3,6 @@ package sys import ( "io" "os" - "syscall" "time" experimentalsys "github.com/tetratelabs/wazero/experimental/sys" @@ -45,11 +44,6 @@ type noopStdinFile struct { noopStdioFile } -// AccessMode implements the same method as documented on fsapi.File -func (noopStdinFile) AccessMode() int { - return syscall.O_RDONLY -} - // Read implements the same method as documented on fsapi.File func (noopStdinFile) Read([]byte) (int, experimentalsys.Errno) { return 0, 0 // Always EOF @@ -66,11 +60,6 @@ type noopStdoutFile struct { noopStdioFile } -// AccessMode implements the same method as documented on fsapi.File -func (noopStdoutFile) AccessMode() int { - return syscall.O_WRONLY -} - // Write implements the same method as documented on fsapi.File func (noopStdoutFile) Write(buf []byte) (int, experimentalsys.Errno) { return len(buf), 0 // same as io.Discard diff --git a/internal/sysfs/adapter.go b/internal/sysfs/adapter.go index 06b5369d81..75d70888c8 100644 --- a/internal/sysfs/adapter.go +++ b/internal/sysfs/adapter.go @@ -4,7 +4,6 @@ import ( "fmt" "io/fs" "path" - "syscall" experimentalsys "github.com/tetratelabs/wazero/experimental/sys" "github.com/tetratelabs/wazero/internal/fsapi" @@ -39,13 +38,13 @@ func (a *adapter) String() string { } // OpenFile implements the same method as documented on fsapi.FS -func (a *adapter) OpenFile(path string, flag int, perm fs.FileMode) (fsapi.File, experimentalsys.Errno) { +func (a *adapter) OpenFile(path string, flag fsapi.Oflag, perm fs.FileMode) (fsapi.File, experimentalsys.Errno) { return OpenFSFile(a.fs, cleanPath(path), flag, perm) } // Stat implements the same method as documented on fsapi.FS func (a *adapter) Stat(path string) (sys.Stat_t, experimentalsys.Errno) { - f, errno := a.OpenFile(path, syscall.O_RDONLY, 0) + f, errno := a.OpenFile(path, fsapi.O_RDONLY, 0) if errno != 0 { return sys.Stat_t{}, errno } diff --git a/internal/sysfs/adapter_test.go b/internal/sysfs/adapter_test.go index 6c6ce917fa..9cd77660d0 100644 --- a/internal/sysfs/adapter_test.go +++ b/internal/sysfs/adapter_test.go @@ -107,7 +107,7 @@ func TestAdapt_Open_Read(t *testing.T) { testOpen_Read(t, testFS, statSetsIno(), runtime.GOOS != "windows") t.Run("path outside root invalid", func(t *testing.T) { - _, err := testFS.OpenFile("../foo", os.O_RDONLY, 0) + _, err := testFS.OpenFile("../foo", fsapi.O_RDONLY, 0) // fsapi.FS doesn't allow relative path lookups require.EqualErrno(t, experimentalsys.EINVAL, err) @@ -137,7 +137,7 @@ func TestAdapt_Stat(t *testing.T) { testStat(t, testFS) } -// hackFS cheats the api.FS contract by opening for write (os.O_RDWR). +// hackFS cheats the api.FS contract by opening for write (fsapi.O_RDWR). // // Until we have an alternate public interface for filesystems, some users will // rely on this. Via testing, we ensure we don't accidentally break them. diff --git a/internal/sysfs/bench_test.go b/internal/sysfs/bench_test.go index fd7e1a288e..b5c34fd14d 100644 --- a/internal/sysfs/bench_test.go +++ b/internal/sysfs/bench_test.go @@ -9,10 +9,11 @@ import ( "testing" "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" ) func BenchmarkFsFileUtimesNs(b *testing.B) { - f, errno := OpenOSFile(path.Join(b.TempDir(), "file"), syscall.O_CREAT, 0) + f, errno := OpenOSFile(path.Join(b.TempDir(), "file"), fsapi.O_CREAT, 0) if errno != 0 { b.Fatal(errno) } @@ -56,7 +57,7 @@ func BenchmarkFsFileRead(b *testing.B) { b.Run(bc.name, func(b *testing.B) { name := "wazero.txt" - f, errno := OpenFSFile(bc.fs, name, syscall.O_RDONLY, 0) + f, errno := OpenFSFile(bc.fs, name, fsapi.O_RDONLY, 0) if errno != 0 { b.Fatal(errno) } diff --git a/internal/sysfs/dir_test.go b/internal/sysfs/dir_test.go index 5aafb89ddb..ed0cd20f68 100644 --- a/internal/sysfs/dir_test.go +++ b/internal/sysfs/dir_test.go @@ -6,7 +6,6 @@ import ( "os" "runtime" "sort" - "syscall" "testing" "github.com/tetratelabs/wazero/experimental/sys" @@ -42,7 +41,7 @@ func TestFSFileReaddir(t *testing.T) { tc := tc t.Run(tc.name, func(t *testing.T) { - dotF, errno := sysfs.OpenFSFile(tc.fs, ".", syscall.O_RDONLY, 0) + dotF, errno := sysfs.OpenFSFile(tc.fs, ".", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) defer dotF.Close() @@ -76,7 +75,7 @@ func TestFSFileReaddir(t *testing.T) { require.EqualErrno(t, sys.EBADF, errno) }) - fileF, errno := sysfs.OpenFSFile(tc.fs, "empty.txt", syscall.O_RDONLY, 0) + fileF, errno := sysfs.OpenFSFile(tc.fs, "empty.txt", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) defer fileF.Close() @@ -85,7 +84,7 @@ func TestFSFileReaddir(t *testing.T) { require.EqualErrno(t, sys.EBADF, errno) }) - dirF, errno := sysfs.OpenFSFile(tc.fs, "dir", syscall.O_RDONLY, 0) + dirF, errno := sysfs.OpenFSFile(tc.fs, "dir", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) defer dirF.Close() @@ -124,7 +123,7 @@ func TestFSFileReaddir(t *testing.T) { require.EqualErrno(t, 0, errno) }) - subdirF, errno := sysfs.OpenFSFile(tc.fs, "sub", syscall.O_RDONLY, 0) + subdirF, errno := sysfs.OpenFSFile(tc.fs, "sub", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) defer subdirF.Close() diff --git a/internal/sysfs/dirfs.go b/internal/sysfs/dirfs.go index 281381271a..5f3f01eb78 100644 --- a/internal/sysfs/dirfs.go +++ b/internal/sysfs/dirfs.go @@ -39,7 +39,7 @@ func (d *dirFS) String() string { } // OpenFile implements the same method as documented on fsapi.FS -func (d *dirFS) OpenFile(path string, flag int, perm fs.FileMode) (fsapi.File, experimentalsys.Errno) { +func (d *dirFS) OpenFile(path string, flag fsapi.Oflag, perm fs.FileMode) (fsapi.File, experimentalsys.Errno) { return OpenOSFile(d.join(path), flag, perm) } diff --git a/internal/sysfs/dirfs_test.go b/internal/sysfs/dirfs_test.go index 632efcd609..7f3de3baf8 100644 --- a/internal/sysfs/dirfs_test.go +++ b/internal/sysfs/dirfs_test.go @@ -22,20 +22,20 @@ func TestNewDirFS(t *testing.T) { testFS := NewDirFS(".") // Guest can look up / - f, errno := testFS.OpenFile("/", os.O_RDONLY, 0) + f, errno := testFS.OpenFile("/", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) require.EqualErrno(t, 0, f.Close()) t.Run("host path not found", func(t *testing.T) { testFS := NewDirFS("a") - _, errno = testFS.OpenFile(".", os.O_RDONLY, 0) + _, errno = testFS.OpenFile(".", fsapi.O_RDONLY, 0) require.EqualErrno(t, sys.ENOENT, errno) }) t.Run("host path not a directory", func(t *testing.T) { arg0 := os.Args[0] // should be safe in scratch tests which don't have the source mounted. testFS := NewDirFS(arg0) - d, errno := testFS.OpenFile(".", os.O_RDONLY, 0) + d, errno := testFS.OpenFile(".", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) _, errno = d.Readdir(-1) require.EqualErrno(t, sys.EBADF, errno) @@ -696,7 +696,7 @@ func TestDirFS_OpenFile(t *testing.T) { testOpen_O_RDWR(t, tmpDir, testFS) t.Run("path outside root valid", func(t *testing.T) { - _, err := testFS.OpenFile("../foo", os.O_RDONLY, 0) + _, err := testFS.OpenFile("../foo", fsapi.O_RDONLY, 0) // fsapi.FS allows relative path lookups require.True(t, errors.Is(err, fs.ErrNotExist)) @@ -731,7 +731,7 @@ func TestDirFS_Readdir(t *testing.T) { require.EqualErrno(t, 0, errno) // Open the empty directory - dirFile, errno := testFS.OpenFile(readDirTarget, os.O_RDONLY, 0) + dirFile, errno := testFS.OpenFile(readDirTarget, fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) defer dirFile.Close() diff --git a/internal/sysfs/file.go b/internal/sysfs/file.go index 72d0886827..b3285cab65 100644 --- a/internal/sysfs/file.go +++ b/internal/sysfs/file.go @@ -4,7 +4,6 @@ import ( "io" "io/fs" "os" - "syscall" experimentalsys "github.com/tetratelabs/wazero/experimental/sys" "github.com/tetratelabs/wazero/internal/fsapi" @@ -21,11 +20,11 @@ func NewStdioFile(stdin bool, f fs.File) (fsapi.File, error) { } else { mode = st.Mode() } - var flag int + var flag fsapi.Oflag if stdin { - flag = syscall.O_RDONLY + flag = fsapi.O_RDONLY } else { - flag = syscall.O_WRONLY + flag = fsapi.O_WRONLY } var file fsapi.File if of, ok := f.(*os.File); ok { @@ -37,14 +36,14 @@ func NewStdioFile(stdin bool, f fs.File) (fsapi.File, error) { return &stdioFile{File: file, st: sys.Stat_t{Mode: mode, Nlink: 1}}, nil } -func OpenFile(path string, flag int, perm fs.FileMode) (*os.File, experimentalsys.Errno) { - if flag&fsapi.O_DIRECTORY != 0 && flag&(syscall.O_WRONLY|syscall.O_RDWR) != 0 { +func OpenFile(path string, flag fsapi.Oflag, perm fs.FileMode) (*os.File, experimentalsys.Errno) { + if flag&fsapi.O_DIRECTORY != 0 && flag&(fsapi.O_WRONLY|fsapi.O_RDWR) != 0 { return nil, experimentalsys.EISDIR // invalid to open a directory writeable } return openFile(path, flag, perm) } -func OpenOSFile(path string, flag int, perm fs.FileMode) (fsapi.File, experimentalsys.Errno) { +func OpenOSFile(path string, flag fsapi.Oflag, perm fs.FileMode) (fsapi.File, experimentalsys.Errno) { f, errno := OpenFile(path, flag, perm) if errno != 0 { return nil, errno @@ -52,8 +51,8 @@ func OpenOSFile(path string, flag int, perm fs.FileMode) (fsapi.File, experiment return newOsFile(path, flag, perm, f), 0 } -func OpenFSFile(fs fs.FS, path string, flag int, perm fs.FileMode) (fsapi.File, experimentalsys.Errno) { - if flag&fsapi.O_DIRECTORY != 0 && flag&(syscall.O_WRONLY|syscall.O_RDWR) != 0 { +func OpenFSFile(fs fs.FS, path string, flag fsapi.Oflag, perm fs.FileMode) (fsapi.File, experimentalsys.Errno) { + if flag&fsapi.O_DIRECTORY != 0 && flag&(fsapi.O_WRONLY|fsapi.O_RDWR) != 0 { return nil, experimentalsys.EISDIR // invalid to open a directory writeable } f, err := fs.Open(path) diff --git a/internal/sysfs/file_test.go b/internal/sysfs/file_test.go index 16c5f41581..6023fa9b76 100644 --- a/internal/sysfs/file_test.go +++ b/internal/sysfs/file_test.go @@ -7,7 +7,6 @@ import ( "os" "path" "runtime" - "syscall" "testing" gofstest "testing/fstest" "time" @@ -55,7 +54,7 @@ func TestRegularFileSetNonblock(t *testing.T) { defer r.Close() defer w.Close() - rF := newOsFile("", syscall.O_RDONLY, 0, r) + rF := newOsFile("", fsapi.O_RDONLY, 0, r) errno := rF.SetNonblock(true) require.EqualErrno(t, 0, errno) @@ -126,7 +125,7 @@ func TestFileSetAppend(t *testing.T) { require.NoError(t, os.WriteFile(fPath, []byte("0123456789"), 0o600)) // Open without APPEND. - f, errno := OpenOSFile(fPath, os.O_RDWR, 0o600) + f, errno := OpenOSFile(fPath, fsapi.O_RDWR, 0o600) require.EqualErrno(t, 0, errno) require.False(t, f.IsAppend()) @@ -187,7 +186,7 @@ func TestFileIno(t *testing.T) { tc := tc t.Run(tc.name, func(t *testing.T) { - d, errno := OpenFSFile(tc.fs, ".", syscall.O_RDONLY, 0) + d, errno := OpenFSFile(tc.fs, ".", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) defer d.Close() @@ -201,7 +200,7 @@ func TestFileIno(t *testing.T) { } t.Run("OS", func(t *testing.T) { - d, errno := OpenOSFile(tmpDir, syscall.O_RDONLY, 0) + d, errno := OpenOSFile(tmpDir, fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) defer d.Close() @@ -245,7 +244,7 @@ func TestFileIsDir(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Run("file", func(t *testing.T) { - f, errno := OpenFSFile(tc.fs, wazeroFile, syscall.O_RDONLY, 0) + f, errno := OpenFSFile(tc.fs, wazeroFile, fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) defer f.Close() @@ -255,7 +254,7 @@ func TestFileIsDir(t *testing.T) { }) t.Run("dir", func(t *testing.T) { - d, errno := OpenFSFile(tc.fs, ".", syscall.O_RDONLY, 0) + d, errno := OpenFSFile(tc.fs, ".", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) defer d.Close() @@ -267,7 +266,7 @@ func TestFileIsDir(t *testing.T) { } t.Run("OS dir", func(t *testing.T) { - d, errno := OpenOSFile(t.TempDir(), syscall.O_RDONLY, 0) + d, errno := OpenOSFile(t.TempDir(), fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) defer d.Close() @@ -295,7 +294,7 @@ func TestFileReadAndPread(t *testing.T) { tc := tc t.Run(tc.name, func(t *testing.T) { - f, errno := OpenFSFile(tc.fs, wazeroFile, syscall.O_RDONLY, 0) + f, errno := OpenFSFile(tc.fs, wazeroFile, fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) defer f.Close() @@ -385,7 +384,7 @@ func TestFileRead_empty(t *testing.T) { tc := tc t.Run(tc.name, func(t *testing.T) { - f, errno := OpenFSFile(tc.fs, emptyFile, syscall.O_RDONLY, 0) + f, errno := OpenFSFile(tc.fs, emptyFile, fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) defer f.Close() @@ -418,7 +417,7 @@ func TestFilePread_Unsupported(t *testing.T) { embedFS, err := fs.Sub(testdata, "testdata") require.NoError(t, err) - f, errno := OpenFSFile(&maskFS{embedFS}, emptyFile, syscall.O_RDONLY, 0) + f, errno := OpenFSFile(&maskFS{embedFS}, emptyFile, fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) defer f.Close() @@ -432,7 +431,7 @@ func TestFileRead_Errors(t *testing.T) { path := path.Join(t.TempDir(), emptyFile) // Open the file write-only - flag := syscall.O_WRONLY | syscall.O_CREAT + flag := fsapi.O_WRONLY | fsapi.O_CREAT f := requireOpenFile(t, path, flag, 0o600) defer f.Close() buf := make([]byte, 5) @@ -474,16 +473,16 @@ func TestFileSeek(t *testing.T) { openFile func(string) (fsapi.File, experimentalsys.Errno) }{ {name: "fsFile os.DirFS", openFile: func(name string) (fsapi.File, experimentalsys.Errno) { - return OpenFSFile(dirFS, name, syscall.O_RDONLY, 0) + return OpenFSFile(dirFS, name, fsapi.O_RDONLY, 0) }}, {name: "fsFile embed.api.FS", openFile: func(name string) (fsapi.File, experimentalsys.Errno) { - return OpenFSFile(embedFS, name, syscall.O_RDONLY, 0) + return OpenFSFile(embedFS, name, fsapi.O_RDONLY, 0) }}, {name: "fsFile fstest.MapFS", openFile: func(name string) (fsapi.File, experimentalsys.Errno) { - return OpenFSFile(mapFS, name, syscall.O_RDONLY, 0) + return OpenFSFile(mapFS, name, fsapi.O_RDONLY, 0) }}, {name: "osFile", openFile: func(name string) (fsapi.File, experimentalsys.Errno) { - return OpenOSFile(path.Join(tmpDir, name), syscall.O_RDONLY, 0o666) + return OpenOSFile(path.Join(tmpDir, name), fsapi.O_RDONLY, 0o666) }}, } @@ -592,7 +591,7 @@ func TestFileSeek_empty(t *testing.T) { tc := tc t.Run(tc.name, func(t *testing.T) { - f, errno := OpenFSFile(tc.fs, emptyFile, syscall.O_RDONLY, 0) + f, errno := OpenFSFile(tc.fs, emptyFile, fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) defer f.Close() @@ -615,7 +614,7 @@ func TestFileSeek_Unsupported(t *testing.T) { embedFS, err := fs.Sub(testdata, "testdata") require.NoError(t, err) - f, errno := OpenFSFile(&maskFS{embedFS}, emptyFile, syscall.O_RDONLY, 0) + f, errno := OpenFSFile(&maskFS{embedFS}, emptyFile, fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) defer f.Close() @@ -627,7 +626,7 @@ func TestFileWriteAndPwrite(t *testing.T) { // fsapi.FS doesn't support writes, and there is no other built-in // implementation except os.File. path := path.Join(t.TempDir(), wazeroFile) - f := requireOpenFile(t, path, syscall.O_RDWR|os.O_CREATE, 0o600) + f := requireOpenFile(t, path, fsapi.O_RDWR|fsapi.O_CREAT, 0o600) defer f.Close() text := "wazero" @@ -680,7 +679,7 @@ func TestFileWrite_empty(t *testing.T) { // fsapi.FS doesn't support writes, and there is no other built-in // implementation except os.File. path := path.Join(t.TempDir(), emptyFile) - f := requireOpenFile(t, path, syscall.O_RDWR|os.O_CREATE, 0o600) + f := requireOpenFile(t, path, fsapi.O_RDWR|fsapi.O_CREAT, 0o600) defer f.Close() tests := []struct { @@ -720,8 +719,8 @@ func TestFileWrite_Unsupported(t *testing.T) { embedFS, err := fs.Sub(testdata, "testdata") require.NoError(t, err) - // Use syscall.O_RDWR so that it fails due to type not flags - f, errno := OpenFSFile(&maskFS{embedFS}, wazeroFile, syscall.O_RDWR, 0) + // Use fsapi.O_RDWR so that it fails due to type not flags + f, errno := OpenFSFile(&maskFS{embedFS}, wazeroFile, fsapi.O_RDWR, 0) require.EqualErrno(t, 0, errno) defer f.Close() @@ -757,7 +756,7 @@ func TestFileWrite_Errors(t *testing.T) { require.NoError(t, of.Close()) // Open the file read-only - flag := syscall.O_RDONLY + flag := fsapi.O_RDONLY f := requireOpenFile(t, path, flag, 0o600) defer f.Close() buf := []byte("wazero") @@ -800,12 +799,12 @@ func TestFileDatasync_NoError(t *testing.T) { func testSync_NoError(t *testing.T, sync func(fsapi.File) experimentalsys.Errno) { roPath := "file_test.go" - ro, errno := OpenFSFile(embedFS, roPath, syscall.O_RDONLY, 0) + ro, errno := OpenFSFile(embedFS, roPath, fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) defer ro.Close() rwPath := path.Join(t.TempDir(), "datasync") - rw, errno := OpenOSFile(rwPath, syscall.O_CREAT|syscall.O_RDWR, 0o600) + rw, errno := OpenOSFile(rwPath, fsapi.O_CREAT|fsapi.O_RDWR, 0o600) require.EqualErrno(t, 0, errno) defer rw.Close() @@ -841,7 +840,7 @@ func TestFileDatasync(t *testing.T) { func testSync(t *testing.T, sync func(fsapi.File) experimentalsys.Errno) { // Even though it is invalid, try to sync a directory dPath := t.TempDir() - d := requireOpenFile(t, dPath, syscall.O_RDONLY, 0) + d := requireOpenFile(t, dPath, fsapi.O_RDONLY, 0) defer d.Close() errno := sync(d) @@ -849,7 +848,7 @@ func testSync(t *testing.T, sync func(fsapi.File) experimentalsys.Errno) { fPath := path.Join(dPath, t.Name()) - f := requireOpenFile(t, fPath, syscall.O_RDWR|os.O_CREATE, 0o600) + f := requireOpenFile(t, fPath, fsapi.O_RDWR|fsapi.O_CREAT, 0o600) defer f.Close() expected := "hello world!" @@ -1045,7 +1044,7 @@ func TestNewStdioFile(t *testing.T) { func testEBADFIfDirClosed(t *testing.T, fn func(fsapi.File) experimentalsys.Errno) bool { return t.Run("EBADF if dir closed", func(t *testing.T) { - d := requireOpenFile(t, t.TempDir(), syscall.O_RDONLY, 0o755) + d := requireOpenFile(t, t.TempDir(), fsapi.O_RDONLY, 0o755) // close the directory underneath require.EqualErrno(t, 0, d.Close()) @@ -1069,7 +1068,7 @@ func testEBADFIfFileClosed(t *testing.T, fn func(fsapi.File) experimentalsys.Err func testEISDIR(t *testing.T, fn func(fsapi.File) experimentalsys.Errno) bool { return t.Run("EISDIR if directory", func(t *testing.T) { - f := requireOpenFile(t, os.TempDir(), syscall.O_RDONLY|fsapi.O_DIRECTORY, 0o666) + f := requireOpenFile(t, os.TempDir(), fsapi.O_RDONLY|fsapi.O_DIRECTORY, 0o666) defer f.Close() require.EqualErrno(t, experimentalsys.EISDIR, fn(f)) @@ -1078,13 +1077,13 @@ func testEISDIR(t *testing.T, fn func(fsapi.File) experimentalsys.Errno) bool { func openForWrite(t *testing.T, path string, content []byte) fsapi.File { require.NoError(t, os.WriteFile(path, content, 0o0666)) - f := requireOpenFile(t, path, syscall.O_RDWR, 0o666) + f := requireOpenFile(t, path, fsapi.O_RDWR, 0o666) _, errno := f.Write(content) require.EqualErrno(t, 0, errno) return f } -func requireOpenFile(t *testing.T, path string, flag int, perm fs.FileMode) fsapi.File { +func requireOpenFile(t *testing.T, path string, flag fsapi.Oflag, perm fs.FileMode) fsapi.File { f, errno := OpenOSFile(path, flag, perm) require.EqualErrno(t, 0, errno) return f diff --git a/internal/sysfs/futimens_test.go b/internal/sysfs/futimens_test.go index 8292147e86..3cd2210839 100644 --- a/internal/sysfs/futimens_test.go +++ b/internal/sysfs/futimens_test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" "github.com/tetratelabs/wazero/internal/platform" "github.com/tetratelabs/wazero/internal/testing/require" ) @@ -160,9 +161,9 @@ func testUtimens(t *testing.T, futimes bool) { } require.EqualErrno(t, 0, errno) } else { - flag := syscall.O_RDWR + flag := fsapi.O_RDWR if path == dir { - flag = syscall.O_RDONLY + flag = fsapi.O_RDONLY if runtime.GOOS == "windows" { // windows requires O_RDWR, which is invalid for directories t.Skip("windows cannot update timestamps on a dir") diff --git a/internal/sysfs/oflag.go b/internal/sysfs/oflag.go new file mode 100644 index 0000000000..e6c3b06c28 --- /dev/null +++ b/internal/sysfs/oflag.go @@ -0,0 +1,38 @@ +package sysfs + +import ( + "os" + + "github.com/tetratelabs/wazero/internal/fsapi" +) + +// toOsOpenFlag converts the input to the flag parameter of os.OpenFile +func toOsOpenFlag(oflag fsapi.Oflag) (flag int) { + // First flags are exclusive + switch oflag & (fsapi.O_RDONLY | fsapi.O_RDWR | fsapi.O_WRONLY) { + case fsapi.O_RDONLY: + flag |= os.O_RDONLY + case fsapi.O_RDWR: + flag |= os.O_RDWR + case fsapi.O_WRONLY: + flag |= os.O_WRONLY + } + + // Run down the flags defined in the os package + if oflag&fsapi.O_APPEND != 0 { + flag |= os.O_APPEND + } + if oflag&fsapi.O_CREAT != 0 { + flag |= os.O_CREATE + } + if oflag&fsapi.O_EXCL != 0 { + flag |= os.O_EXCL + } + if oflag&fsapi.O_SYNC != 0 { + flag |= os.O_SYNC + } + if oflag&fsapi.O_TRUNC != 0 { + flag |= os.O_TRUNC + } + return withSyscallOflag(oflag, flag) +} diff --git a/internal/sysfs/oflag_test.go b/internal/sysfs/oflag_test.go new file mode 100644 index 0000000000..e1c9beb364 --- /dev/null +++ b/internal/sysfs/oflag_test.go @@ -0,0 +1,55 @@ +package sysfs + +import ( + "os" + "testing" + + "github.com/tetratelabs/wazero/internal/fsapi" + "github.com/tetratelabs/wazero/internal/testing/require" +) + +// Test_toOsOpenFlag doesn't use subtests to reduce volume of verbose output, +// and in recognition we have tens of thousands of tests, which can hit IDE +// limits. +func Test_toOsOpenFlag(t *testing.T) { + tests := []struct { + name string + flag fsapi.Oflag + expected int + }{ + {name: "O_RDONLY", flag: fsapi.O_RDONLY, expected: os.O_RDONLY}, + {name: "O_RDWR", flag: fsapi.O_RDWR, expected: os.O_RDWR}, + {name: "O_WRONLY", flag: fsapi.O_WRONLY, expected: os.O_WRONLY}, + {name: "O_CREAT", flag: fsapi.O_CREAT, expected: os.O_RDONLY | os.O_CREATE}, + {name: "O_APPEND", flag: fsapi.O_APPEND, expected: os.O_RDONLY | os.O_APPEND}, + { + name: "all portable", + flag: fsapi.O_RDWR | fsapi.O_APPEND | fsapi.O_CREAT | fsapi.O_EXCL | fsapi.O_SYNC | fsapi.O_TRUNC, + expected: os.O_RDWR | os.O_APPEND | os.O_CREATE | os.O_EXCL | os.O_SYNC | os.O_TRUNC, + }, + {name: "undefined", flag: 1 << 15, expected: os.O_RDONLY}, + } + + for _, tc := range tests { + require.Equal(t, tc.expected, toOsOpenFlag(tc.flag), tc.name) + } + + // Tests any supported syscall flags + for n, f := range map[string]fsapi.Oflag{ + "O_DIRECTORY": fsapi.O_DIRECTORY, + "O_DSYNC": fsapi.O_DSYNC, + "O_NOFOLLOW": fsapi.O_NOFOLLOW, + "O_NONBLOCK": fsapi.O_NONBLOCK, + "O_RSYNC": fsapi.O_RSYNC, + } { + if supportedSyscallOflag&f == 0 { + continue + } + require.NotEqual(t, 0, toOsOpenFlag(f), n) + } + + // Example of a flag that can be or'd into O_RDONLY even if not + // currently supported in WASI or GOOS=js + const O_NOATIME = fsapi.Oflag(0x40000) + require.Zero(t, 0, toOsOpenFlag(O_NOATIME)) +} diff --git a/internal/sysfs/open_file.go b/internal/sysfs/open_file.go deleted file mode 100644 index 61ced344ea..0000000000 --- a/internal/sysfs/open_file.go +++ /dev/null @@ -1,20 +0,0 @@ -//go:build !windows && !js && !illumos && !solaris - -package sysfs - -import ( - "io/fs" - "os" - - "github.com/tetratelabs/wazero/experimental/sys" -) - -// OpenFile is like os.OpenFile except it returns sys.Errno. A zero -// sys.Errno is success. -func openFile(path string, flag int, perm fs.FileMode) (*os.File, sys.Errno) { - f, err := os.OpenFile(path, flag, perm) - // Note: This does not return a fsapi.File because fsapi.FS that returns - // one may want to hide the real OS path. For example, this is needed for - // pre-opens. - return f, sys.UnwrapOSError(err) -} diff --git a/internal/sysfs/open_file_darwin.go b/internal/sysfs/open_file_darwin.go new file mode 100644 index 0000000000..82275393b5 --- /dev/null +++ b/internal/sysfs/open_file_darwin.go @@ -0,0 +1,26 @@ +package sysfs + +import ( + "syscall" + + "github.com/tetratelabs/wazero/internal/fsapi" +) + +const supportedSyscallOflag = fsapi.O_DIRECTORY | fsapi.O_DSYNC | fsapi.O_NOFOLLOW | fsapi.O_NONBLOCK + +func withSyscallOflag(oflag fsapi.Oflag, flag int) int { + if oflag&fsapi.O_DIRECTORY != 0 { + flag |= syscall.O_DIRECTORY + } + if oflag&fsapi.O_DSYNC != 0 { + flag |= syscall.O_DSYNC + } + if oflag&fsapi.O_NOFOLLOW != 0 { + flag |= syscall.O_NOFOLLOW + } + if oflag&fsapi.O_NONBLOCK != 0 { + flag |= syscall.O_NONBLOCK + } + // syscall.O_RSYNC not defined on darwin + return flag +} diff --git a/internal/sysfs/open_file_freebsd.go b/internal/sysfs/open_file_freebsd.go new file mode 100644 index 0000000000..e91da95dfa --- /dev/null +++ b/internal/sysfs/open_file_freebsd.go @@ -0,0 +1,24 @@ +package sysfs + +import ( + "syscall" + + "github.com/tetratelabs/wazero/internal/fsapi" +) + +const supportedSyscallOflag = fsapi.O_DIRECTORY | fsapi.O_NOFOLLOW | fsapi.O_NONBLOCK + +func withSyscallOflag(oflag fsapi.Oflag, flag int) int { + if oflag&fsapi.O_DIRECTORY != 0 { + flag |= syscall.O_DIRECTORY + } + // syscall.O_DSYNC not defined on darwin + if oflag&fsapi.O_NOFOLLOW != 0 { + flag |= syscall.O_NOFOLLOW + } + if oflag&fsapi.O_NONBLOCK != 0 { + flag |= syscall.O_NONBLOCK + } + // syscall.O_RSYNC not defined on darwin + return flag +} diff --git a/internal/sysfs/open_file_js.go b/internal/sysfs/open_file_js.go deleted file mode 100644 index 613b06f87c..0000000000 --- a/internal/sysfs/open_file_js.go +++ /dev/null @@ -1,14 +0,0 @@ -package sysfs - -import ( - "io/fs" - "os" - - "github.com/tetratelabs/wazero/experimental/sys" -) - -func openFile(path string, flag int, perm fs.FileMode) (*os.File, sys.Errno) { - flag &= ^(O_DIRECTORY | O_NOFOLLOW) // erase placeholders - f, err := os.OpenFile(path, flag, perm) - return f, sys.UnwrapOSError(err) -} diff --git a/internal/sysfs/open_file_linux.go b/internal/sysfs/open_file_linux.go new file mode 100644 index 0000000000..bfa9a23e19 --- /dev/null +++ b/internal/sysfs/open_file_linux.go @@ -0,0 +1,28 @@ +package sysfs + +import ( + "syscall" + + "github.com/tetratelabs/wazero/internal/fsapi" +) + +const supportedSyscallOflag = fsapi.O_DIRECTORY | fsapi.O_DSYNC | fsapi.O_NOFOLLOW | fsapi.O_NONBLOCK | fsapi.O_RSYNC + +func withSyscallOflag(oflag fsapi.Oflag, flag int) int { + if oflag&fsapi.O_DIRECTORY != 0 { + flag |= syscall.O_DIRECTORY + } + if oflag&fsapi.O_DSYNC != 0 { + flag |= syscall.O_DSYNC + } + if oflag&fsapi.O_NOFOLLOW != 0 { + flag |= syscall.O_NOFOLLOW + } + if oflag&fsapi.O_NONBLOCK != 0 { + flag |= syscall.O_NONBLOCK + } + if oflag&fsapi.O_RSYNC != 0 { + flag |= syscall.O_RSYNC + } + return flag +} diff --git a/internal/sysfs/open_file_notwindows.go b/internal/sysfs/open_file_notwindows.go new file mode 100644 index 0000000000..4e886fb550 --- /dev/null +++ b/internal/sysfs/open_file_notwindows.go @@ -0,0 +1,21 @@ +//go:build !windows + +package sysfs + +import ( + "io/fs" + "os" + + "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" +) + +// openFile is like os.OpenFile except it accepts a fsapi.Oflag and returns +// sys.Errno. A zero sys.Errno is success. +func openFile(path string, oflag fsapi.Oflag, perm fs.FileMode) (*os.File, sys.Errno) { + f, err := os.OpenFile(path, toOsOpenFlag(oflag), perm) + // Note: This does not return a fsapi.File because fsapi.FS that returns + // one may want to hide the real OS path. For example, this is needed for + // pre-opens. + return f, sys.UnwrapOSError(err) +} diff --git a/internal/sysfs/open_file_sun.go b/internal/sysfs/open_file_sun.go index a75ab3937c..6589ddac3f 100644 --- a/internal/sysfs/open_file_sun.go +++ b/internal/sysfs/open_file_sun.go @@ -3,13 +3,29 @@ package sysfs import ( - "io/fs" - "os" + "syscall" - "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" ) -func openFile(path string, flag int, perm fs.FileMode) (*os.File, sys.Errno) { - f, err := os.OpenFile(path, flag, perm) - return f, sys.UnwrapOSError(err) +const supportedSyscallOflag = fsapi.O_DIRECTORY | fsapi.O_DSYNC | fsapi.O_NOFOLLOW | fsapi.O_NONBLOCK | fsapi.O_RSYNC + +func withSyscallOflag(oflag fsapi.Oflag, flag int) int { + if oflag&fsapi.O_DIRECTORY != 0 { + // See https://github.com/illumos/illumos-gate/blob/edd580643f2cf1434e252cd7779e83182ea84945/usr/src/uts/common/sys/fcntl.h#L90 + flag |= 0x1000000 + } + if oflag&fsapi.O_DSYNC != 0 { + flag |= syscall.O_DSYNC + } + if oflag&fsapi.O_NOFOLLOW != 0 { + flag |= syscall.O_NOFOLLOW + } + if oflag&fsapi.O_NONBLOCK != 0 { + flag |= syscall.O_NONBLOCK + } + if oflag&fsapi.O_RSYNC != 0 { + flag |= syscall.O_RSYNC + } + return flag } diff --git a/internal/sysfs/open_file_test.go b/internal/sysfs/open_file_test.go index ab1d3a661d..3381e6ba9e 100644 --- a/internal/sysfs/open_file_test.go +++ b/internal/sysfs/open_file_test.go @@ -4,7 +4,6 @@ import ( "os" path "path/filepath" "runtime" - "syscall" "testing" "github.com/tetratelabs/wazero/experimental/sys" @@ -20,7 +19,7 @@ func TestOpenFile(t *testing.T) { err := os.MkdirAll(path, 0o700) require.NoError(t, err) - f, errno := OpenFile(path+"/", os.O_RDONLY, 0) + f, errno := OpenFile(path+"/", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) require.NoError(t, f.Close()) }) @@ -28,7 +27,7 @@ func TestOpenFile(t *testing.T) { // from os.TestDirFSPathsValid if runtime.GOOS != "windows" { t.Run("strange name", func(t *testing.T) { - f, errno := OpenFile(path.Join(tmpDir, `e:xperi\ment.txt`), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) + f, errno := OpenFile(path.Join(tmpDir, `e:xperi\ment.txt`), fsapi.O_WRONLY|fsapi.O_CREAT|fsapi.O_TRUNC, 0o600) require.EqualErrno(t, 0, errno) require.NoError(t, f.Close()) }) @@ -47,23 +46,23 @@ func TestOpenFile_Errors(t *testing.T) { err = os.WriteFile(nestedFile, nil, 0o700) require.NoError(t, err) - _, errno := OpenFile(nestedFile+"/", os.O_RDONLY, 0) + _, errno := OpenFile(nestedFile+"/", fsapi.O_RDONLY, 0) require.EqualErrno(t, sys.ENOTDIR, errno) }) t.Run("not found must be ENOENT", func(t *testing.T) { - _, errno := OpenFile(path.Join(tmpDir, "not-really-exist.txt"), os.O_RDONLY, 0o600) + _, errno := OpenFile(path.Join(tmpDir, "not-really-exist.txt"), fsapi.O_RDONLY, 0o600) require.EqualErrno(t, sys.ENOENT, errno) }) // This is the same as https://github.com/ziglang/zig/blob/d24ebf1d12cf66665b52136a2807f97ff021d78d/lib/std/os/test.zig#L105-L112 t.Run("try creating on existing file must be EEXIST", func(t *testing.T) { filepath := path.Join(tmpDir, "file.txt") - f, errno := OpenFile(filepath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o666) + f, errno := OpenFile(filepath, fsapi.O_RDWR|fsapi.O_CREAT|fsapi.O_EXCL, 0o666) require.EqualErrno(t, 0, errno) defer f.Close() - _, err := OpenFile(filepath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o666) + _, err := OpenFile(filepath, fsapi.O_RDWR|fsapi.O_CREAT|fsapi.O_EXCL, 0o666) require.EqualErrno(t, sys.EEXIST, err) }) @@ -71,7 +70,7 @@ func TestOpenFile_Errors(t *testing.T) { path := path.Join(tmpDir, "file") require.NoError(t, os.WriteFile(path, nil, 0o600)) - f := requireOpenFile(t, path, os.O_RDONLY, 0) + f := requireOpenFile(t, path, fsapi.O_RDONLY, 0) defer f.Close() _, errno := f.Write([]byte{1, 2, 3, 4}) @@ -82,7 +81,7 @@ func TestOpenFile_Errors(t *testing.T) { path := path.Join(tmpDir, "diragain") require.NoError(t, os.Mkdir(path, 0o755)) - f := requireOpenFile(t, path, os.O_RDONLY, 0) + f := requireOpenFile(t, path, fsapi.O_RDONLY, 0) defer f.Close() _, errno := f.Write([]byte{1, 2, 3, 4}) @@ -105,10 +104,10 @@ func TestOpenFile_Errors(t *testing.T) { }) t.Run("opening a directory writeable is EISDIR", func(t *testing.T) { - _, errno := OpenFile(tmpDir, fsapi.O_DIRECTORY|syscall.O_WRONLY, 0o0666) + _, errno := OpenFile(tmpDir, fsapi.O_DIRECTORY|fsapi.O_WRONLY, 0o0666) require.EqualErrno(t, sys.EISDIR, errno) - _, errno = OpenFile(tmpDir, fsapi.O_DIRECTORY|syscall.O_WRONLY, 0o0666) + _, errno = OpenFile(tmpDir, fsapi.O_DIRECTORY|fsapi.O_WRONLY, 0o0666) require.EqualErrno(t, sys.EISDIR, errno) }) } diff --git a/internal/sysfs/open_file_unsupported.go b/internal/sysfs/open_file_unsupported.go new file mode 100644 index 0000000000..9b925949aa --- /dev/null +++ b/internal/sysfs/open_file_unsupported.go @@ -0,0 +1,16 @@ +//go:build !darwin && !linux && !windows && !illumos && !solaris && !freebsd + +package sysfs + +import "github.com/tetratelabs/wazero/internal/fsapi" + +const supportedSyscallOflag = fsapi.Oflag(0) + +func withSyscallOflag(oflag fsapi.Oflag, flag int) int { + // O_DIRECTORY not defined + // O_DSYNC not defined + // O_NOFOLLOW not defined + // O_NONBLOCK not defined + // O_RSYNC not defined + return flag +} diff --git a/internal/sysfs/open_file_windows.go b/internal/sysfs/open_file_windows.go index 9190fefadd..bc7a7f7d0c 100644 --- a/internal/sysfs/open_file_windows.go +++ b/internal/sysfs/open_file_windows.go @@ -12,9 +12,9 @@ import ( "github.com/tetratelabs/wazero/internal/platform" ) -func openFile(path string, flag int, perm fs.FileMode) (*os.File, sys.Errno) { - isDir := flag&fsapi.O_DIRECTORY > 0 - flag &= ^(fsapi.O_DIRECTORY | fsapi.O_NOFOLLOW) // erase placeholders +func openFile(path string, oflag fsapi.Oflag, perm fs.FileMode) (*os.File, sys.Errno) { + isDir := oflag&fsapi.O_DIRECTORY > 0 + flag := toOsOpenFlag(oflag) // TODO: document why we are opening twice fd, err := open(path, flag|syscall.O_CLOEXEC, uint32(perm)) @@ -55,6 +55,20 @@ func openFile(path string, flag int, perm fs.FileMode) (*os.File, sys.Errno) { return f, errno } +const supportedSyscallOflag = fsapi.O_NONBLOCK + +// Map to synthetic values here https://github.com/golang/go/blob/go1.20/src/syscall/types_windows.go#L34-L48 +func withSyscallOflag(oflag fsapi.Oflag, flag int) int { + // O_DIRECTORY not defined in windows + // O_DSYNC not defined in windows + // O_NOFOLLOW not defined in windows + if oflag&fsapi.O_NONBLOCK != 0 { + flag |= syscall.O_NONBLOCK + } + // O_RSYNC not defined in windows + return flag +} + func isSymlink(path string) bool { if st, e := os.Lstat(path); e == nil && st.Mode()&os.ModeSymlink != 0 { return true diff --git a/internal/sysfs/osfile.go b/internal/sysfs/osfile.go index b64f030a87..e39c92566c 100644 --- a/internal/sysfs/osfile.go +++ b/internal/sysfs/osfile.go @@ -14,19 +14,19 @@ import ( "github.com/tetratelabs/wazero/sys" ) -func newOsFile(openPath string, openFlag int, openPerm fs.FileMode, f *os.File) fsapi.File { +func newOsFile(path string, flag fsapi.Oflag, perm fs.FileMode, f *os.File) fsapi.File { // Windows cannot read files written to a directory after it was opened. // This was noticed in #1087 in zig tests. Use a flag instead of a // different type. reopenDir := runtime.GOOS == "windows" - return &osFile{path: openPath, flag: openFlag, perm: openPerm, reopenDir: reopenDir, file: f, fd: f.Fd()} + return &osFile{path: path, flag: flag, perm: perm, reopenDir: reopenDir, file: f, fd: f.Fd()} } // osFile is a file opened with this package, and uses os.File or syscalls to // implement api.File. type osFile struct { path string - flag int + flag fsapi.Oflag perm fs.FileMode file *os.File fd uintptr @@ -75,19 +75,19 @@ func (f *osFile) IsDir() (bool, experimentalsys.Errno) { // IsAppend implements File.IsAppend func (f *osFile) IsAppend() bool { - return f.flag&syscall.O_APPEND == syscall.O_APPEND + return f.flag&fsapi.O_APPEND == fsapi.O_APPEND } // SetAppend implements the same method as documented on fsapi.File func (f *osFile) SetAppend(enable bool) (errno experimentalsys.Errno) { if enable { - f.flag |= syscall.O_APPEND + f.flag |= fsapi.O_APPEND } else { - f.flag &= ^syscall.O_APPEND + f.flag &= ^fsapi.O_APPEND } // Clear any create flag, as we are re-opening, not re-creating. - f.flag &= ^syscall.O_CREAT + f.flag &= ^fsapi.O_CREAT // appendMode (bool) cannot be changed later, so we have to re-open the // file. https://github.com/golang/go/blob/go1.20/src/os/file_unix.go#L60 @@ -99,7 +99,7 @@ var _ reopenFile = (*fsFile)(nil).reopen func (f *osFile) reopen() (errno experimentalsys.Errno) { // Clear any create flag, as we are re-opening, not re-creating. - f.flag &= ^syscall.O_CREAT + f.flag &= ^fsapi.O_CREAT _ = f.close() f.file, errno = OpenFile(f.path, f.flag, f.perm) diff --git a/internal/sysfs/readfs.go b/internal/sysfs/readfs.go index 6150a0968b..76d182c1b8 100644 --- a/internal/sysfs/readfs.go +++ b/internal/sysfs/readfs.go @@ -2,13 +2,10 @@ package sysfs import ( "io/fs" - "os" "syscall" - "time" experimentalsys "github.com/tetratelabs/wazero/experimental/sys" "github.com/tetratelabs/wazero/internal/fsapi" - "github.com/tetratelabs/wazero/sys" ) // NewReadFS is used to mask an existing fsapi.FS for reads. Notably, this allows @@ -21,15 +18,15 @@ func NewReadFS(fs fsapi.FS) fsapi.FS { } else if _, ok = fs.(fsapi.UnimplementedFS); ok { return fs // unimplemented is read-only } - return &readFS{fs: fs} + return &readFS{fs} } type readFS struct { - fs fsapi.FS + fsapi.FS } // OpenFile implements the same method as documented on fsapi.FS -func (r *readFS) OpenFile(path string, flag int, perm fs.FileMode) (fsapi.File, experimentalsys.Errno) { +func (r *readFS) OpenFile(path string, flag fsapi.Oflag, perm fs.FileMode) (fsapi.File, experimentalsys.Errno) { // TODO: Once the real implementation is complete, move the below to // /RATIONALE.md. Doing this while the type is unstable creates // documentation drift as we expect a lot of reshaping meanwhile. @@ -49,87 +46,67 @@ func (r *readFS) OpenFile(path string, flag int, perm fs.FileMode) (fsapi.File, // there isn't a current flag to OR in with that, there may be in the // future. What we do instead is mask the flags about read/write mode and // check if they are the opposite of read or not. - switch flag & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR) { - case os.O_WRONLY, os.O_RDWR: + switch flag & (fsapi.O_RDONLY | fsapi.O_WRONLY | fsapi.O_RDWR) { + case fsapi.O_WRONLY, fsapi.O_RDWR: if flag&fsapi.O_DIRECTORY != 0 { return nil, experimentalsys.EISDIR } return nil, experimentalsys.ENOSYS - default: // os.O_RDONLY (or no flag) so we are ok! + default: // fsapi.O_RDONLY (or no flag) so we are ok! } - f, errno := r.fs.OpenFile(path, flag, perm) + f, errno := r.FS.OpenFile(path, flag, perm) if errno != 0 { return nil, errno } - return &readFile{f: f}, 0 + return &readFile{f}, 0 } -// compile-time check to ensure readFile implements api.File. -var _ fsapi.File = (*readFile)(nil) - -type readFile struct { - f fsapi.File -} - -// Dev implements the same method as documented on fsapi.File. -func (r *readFile) Dev() (uint64, experimentalsys.Errno) { - return r.f.Dev() -} - -// Ino implements the same method as documented on fsapi.File. -func (r *readFile) Ino() (sys.Inode, experimentalsys.Errno) { - return r.f.Ino() -} - -// IsDir implements the same method as documented on fsapi.File. -func (r *readFile) IsDir() (bool, experimentalsys.Errno) { - return r.f.IsDir() +// Mkdir implements the same method as documented on fsapi.FS +func (r *readFS) Mkdir(path string, perm fs.FileMode) experimentalsys.Errno { + return experimentalsys.EROFS } -// IsNonblock implements the same method as documented on fsapi.File. -func (r *readFile) IsNonblock() bool { - return r.f.IsNonblock() +// Chmod implements the same method as documented on fsapi.FS +func (r *readFS) Chmod(path string, perm fs.FileMode) experimentalsys.Errno { + return experimentalsys.EROFS } -// SetNonblock implements the same method as documented on fsapi.File. -func (r *readFile) SetNonblock(enabled bool) experimentalsys.Errno { - return r.f.SetNonblock(enabled) +// Rename implements the same method as documented on fsapi.FS +func (r *readFS) Rename(from, to string) experimentalsys.Errno { + return experimentalsys.EROFS } -// IsAppend implements the same method as documented on fsapi.File. -func (r *readFile) IsAppend() bool { - return r.f.IsAppend() +// Rmdir implements the same method as documented on fsapi.FS +func (r *readFS) Rmdir(path string) experimentalsys.Errno { + return experimentalsys.EROFS } -// SetAppend implements the same method as documented on fsapi.File. -func (r *readFile) SetAppend(enabled bool) experimentalsys.Errno { - return r.f.SetAppend(enabled) +// Link implements the same method as documented on fsapi.FS +func (r *readFS) Link(_, _ string) experimentalsys.Errno { + return experimentalsys.EROFS } -// Stat implements the same method as documented on fsapi.File. -func (r *readFile) Stat() (sys.Stat_t, experimentalsys.Errno) { - return r.f.Stat() +// Symlink implements the same method as documented on fsapi.FS +func (r *readFS) Symlink(_, _ string) experimentalsys.Errno { + return experimentalsys.EROFS } -// Read implements the same method as documented on fsapi.File. -func (r *readFile) Read(buf []byte) (int, experimentalsys.Errno) { - return r.f.Read(buf) +// Unlink implements the same method as documented on fsapi.FS +func (r *readFS) Unlink(path string) experimentalsys.Errno { + return experimentalsys.EROFS } -// Pread implements the same method as documented on fsapi.File. -func (r *readFile) Pread(buf []byte, offset int64) (int, experimentalsys.Errno) { - return r.f.Pread(buf, offset) +// Utimens implements the same method as documented on fsapi.FS +func (r *readFS) Utimens(path string, times *[2]syscall.Timespec, symlinkFollow bool) experimentalsys.Errno { + return experimentalsys.EROFS } -// Seek implements the same method as documented on fsapi.File. -func (r *readFile) Seek(offset int64, whence int) (int64, experimentalsys.Errno) { - return r.f.Seek(offset, whence) -} +// compile-time check to ensure readFile implements api.File. +var _ fsapi.File = (*readFile)(nil) -// Readdir implements the same method as documented on fsapi.File. -func (r *readFile) Readdir(n int) (dirents []fsapi.Dirent, errno experimentalsys.Errno) { - return r.f.Readdir(n) +type readFile struct { + fsapi.File } // Write implements the same method as documented on fsapi.File. @@ -170,68 +147,3 @@ func (r *readFile) writeErr() experimentalsys.Errno { } return experimentalsys.EBADF } - -// Close implements the same method as documented on fsapi.File. -func (r *readFile) Close() experimentalsys.Errno { - return r.f.Close() -} - -// PollRead implements File.PollRead -func (r *readFile) PollRead(timeout *time.Duration) (ready bool, errno experimentalsys.Errno) { - return r.f.PollRead(timeout) -} - -// Lstat implements the same method as documented on fsapi.FS -func (r *readFS) Lstat(path string) (sys.Stat_t, experimentalsys.Errno) { - return r.fs.Lstat(path) -} - -// Stat implements the same method as documented on fsapi.FS -func (r *readFS) Stat(path string) (sys.Stat_t, experimentalsys.Errno) { - return r.fs.Stat(path) -} - -// Readlink implements the same method as documented on fsapi.FS -func (r *readFS) Readlink(path string) (dst string, err experimentalsys.Errno) { - return r.fs.Readlink(path) -} - -// Mkdir implements the same method as documented on fsapi.FS -func (r *readFS) Mkdir(path string, perm fs.FileMode) experimentalsys.Errno { - return experimentalsys.EROFS -} - -// Chmod implements the same method as documented on fsapi.FS -func (r *readFS) Chmod(path string, perm fs.FileMode) experimentalsys.Errno { - return experimentalsys.EROFS -} - -// Rename implements the same method as documented on fsapi.FS -func (r *readFS) Rename(from, to string) experimentalsys.Errno { - return experimentalsys.EROFS -} - -// Rmdir implements the same method as documented on fsapi.FS -func (r *readFS) Rmdir(path string) experimentalsys.Errno { - return experimentalsys.EROFS -} - -// Link implements the same method as documented on fsapi.FS -func (r *readFS) Link(_, _ string) experimentalsys.Errno { - return experimentalsys.EROFS -} - -// Symlink implements the same method as documented on fsapi.FS -func (r *readFS) Symlink(_, _ string) experimentalsys.Errno { - return experimentalsys.EROFS -} - -// Unlink implements the same method as documented on fsapi.FS -func (r *readFS) Unlink(path string) experimentalsys.Errno { - return experimentalsys.EROFS -} - -// Utimens implements the same method as documented on fsapi.FS -func (r *readFS) Utimens(path string, times *[2]syscall.Timespec, symlinkFollow bool) experimentalsys.Errno { - return experimentalsys.EROFS -} diff --git a/internal/sysfs/readfs_test.go b/internal/sysfs/readfs_test.go index dd6d6092d1..f1667fd498 100644 --- a/internal/sysfs/readfs_test.go +++ b/internal/sysfs/readfs_test.go @@ -119,46 +119,51 @@ func TestReadFS_UtimesNano(t *testing.T) { } func TestReadFS_Open_Read(t *testing.T) { - t.Parallel() - - tmpDir := t.TempDir() - require.NoError(t, fstest.WriteTestFiles(tmpDir)) - type test struct { name string - fs fsapi.FS + fs func(tmpDir string) fsapi.FS expectFileIno bool expectDirIno bool } tests := []test{ { - name: "DirFS", - fs: NewReadFS(NewDirFS(tmpDir)), + name: "DirFS", + fs: func(tmpDir string) fsapi.FS { + return NewDirFS(tmpDir) + }, expectFileIno: true, expectDirIno: true, }, { - name: "fstest.MapFS", - fs: NewReadFS(Adapt(fstest.FS)), + name: "fstest.MapFS", + fs: func(tmpDir string) fsapi.FS { + return Adapt(fstest.FS) + }, expectFileIno: false, expectDirIno: false, }, { - name: "os.DirFS", - fs: NewReadFS(Adapt(os.DirFS(tmpDir))), + name: "os.DirFS", + fs: func(tmpDir string) fsapi.FS { + return Adapt(os.DirFS(tmpDir)) + }, expectFileIno: statSetsIno(), expectDirIno: runtime.GOOS != "windows", }, { - name: "mask(os.DirFS)", - fs: NewReadFS(Adapt(&MaskOsFS{os.DirFS(tmpDir), false})), + name: "mask(os.DirFS)", + fs: func(tmpDir string) fsapi.FS { + return Adapt(&MaskOsFS{Fs: os.DirFS(tmpDir)}) + }, expectFileIno: statSetsIno(), expectDirIno: runtime.GOOS != "windows", }, { - name: "mask(os.DirFS) ZeroIno", - fs: NewReadFS(Adapt(&MaskOsFS{os.DirFS(tmpDir), true})), + name: "mask(os.DirFS) ZeroIno", + fs: func(tmpDir string) fsapi.FS { + return Adapt(&MaskOsFS{Fs: os.DirFS(tmpDir), ZeroIno: true}) + }, expectFileIno: false, expectDirIno: false, }, @@ -168,7 +173,13 @@ func TestReadFS_Open_Read(t *testing.T) { tc := tc t.Run(tc.name, func(t *testing.T) { - testOpen_Read(t, tc.fs, tc.expectFileIno, tc.expectDirIno) + t.Parallel() + + // Ensure tests don't conflict on the same directory + tmpDir := t.TempDir() + require.NoError(t, fstest.WriteTestFiles(tmpDir)) + + testOpen_Read(t, NewReadFS(tc.fs(tmpDir)), tc.expectFileIno, tc.expectDirIno) }) } } diff --git a/internal/sysfs/stat_test.go b/internal/sysfs/stat_test.go index 36d42564b1..81df9b1e44 100644 --- a/internal/sysfs/stat_test.go +++ b/internal/sysfs/stat_test.go @@ -4,7 +4,6 @@ import ( "os" "path" "runtime" - "syscall" "testing" "time" @@ -105,7 +104,7 @@ func TestStat(t *testing.T) { func TestStatFile(t *testing.T) { tmpDir := t.TempDir() - tmpDirF := requireOpenFile(t, tmpDir, syscall.O_RDONLY, 0) + tmpDirF := requireOpenFile(t, tmpDir, fsapi.O_RDONLY, 0) defer tmpDirF.Close() t.Run("dir", func(t *testing.T) { @@ -127,7 +126,7 @@ func TestStatFile(t *testing.T) { file := path.Join(tmpDir, "file") require.NoError(t, os.WriteFile(file, nil, 0o400)) - fileF := requireOpenFile(t, file, syscall.O_RDONLY, 0) + fileF := requireOpenFile(t, file, fsapi.O_RDONLY, 0) defer fileF.Close() t.Run("file", func(t *testing.T) { @@ -146,7 +145,7 @@ func TestStatFile(t *testing.T) { subdir := path.Join(tmpDir, "sub") require.NoError(t, os.Mkdir(subdir, 0o500)) - subdirF := requireOpenFile(t, subdir, syscall.O_RDONLY, 0) + subdirF := requireOpenFile(t, subdir, fsapi.O_RDONLY, 0) defer subdirF.Close() t.Run("subdir", func(t *testing.T) { @@ -204,7 +203,7 @@ func Test_StatFile_times(t *testing.T) { err := os.Chtimes(file, time.UnixMicro(tc.atimeNsec/1e3), time.UnixMicro(tc.mtimeNsec/1e3)) require.NoError(t, err) - f := requireOpenFile(t, file, syscall.O_RDONLY, 0) + f := requireOpenFile(t, file, fsapi.O_RDONLY, 0) defer f.Close() st, errno := f.Stat() @@ -218,21 +217,21 @@ func Test_StatFile_times(t *testing.T) { func TestStatFile_dev_inode(t *testing.T) { tmpDir := t.TempDir() - d := requireOpenFile(t, tmpDir, os.O_RDONLY, 0) + d := requireOpenFile(t, tmpDir, fsapi.O_RDONLY, 0) defer d.Close() path1 := path.Join(tmpDir, "1") - f1 := requireOpenFile(t, path1, os.O_CREATE, 0o666) + f1 := requireOpenFile(t, path1, fsapi.O_CREAT, 0o666) defer f1.Close() path2 := path.Join(tmpDir, "2") - f2 := requireOpenFile(t, path2, os.O_CREATE, 0o666) + f2 := requireOpenFile(t, path2, fsapi.O_CREAT, 0o666) defer f2.Close() pathLink2 := path.Join(tmpDir, "link2") err := os.Symlink(path2, pathLink2) require.NoError(t, err) - l2 := requireOpenFile(t, pathLink2, os.O_RDONLY, 0) + l2 := requireOpenFile(t, pathLink2, fsapi.O_RDONLY, 0) defer l2.Close() // First, stat the directory @@ -275,7 +274,7 @@ func TestStatFile_dev_inode(t *testing.T) { // Renaming a file shouldn't change its inodes. require.EqualErrno(t, 0, rename(path1, path2)) - f1 = requireOpenFile(t, path2, os.O_RDONLY, 0) + f1 = requireOpenFile(t, path2, fsapi.O_RDONLY, 0) defer f1.Close() st1Again, errno = f1.Stat() diff --git a/internal/sysfs/sysfs_test.go b/internal/sysfs/sysfs_test.go index 667a12de21..0b8149d3a6 100644 --- a/internal/sysfs/sysfs_test.go +++ b/internal/sysfs/sysfs_test.go @@ -23,7 +23,7 @@ func testOpen_O_RDWR(t *testing.T, tmpDir string, testFS fsapi.FS) { err := os.WriteFile(realPath, []byte{}, 0o600) require.NoError(t, err) - f, errno := testFS.OpenFile(file, os.O_RDWR, 0) + f, errno := testFS.OpenFile(file, fsapi.O_RDWR, 0) require.EqualErrno(t, 0, errno) defer f.Close() @@ -42,7 +42,7 @@ func testOpen_O_RDWR(t *testing.T, tmpDir string, testFS fsapi.FS) { // re-create as read-only, using 0444 to allow read-back on windows. require.NoError(t, os.Remove(realPath)) - f, errno = testFS.OpenFile(file, os.O_RDONLY|os.O_CREATE, 0o444) + f, errno = testFS.OpenFile(file, fsapi.O_RDONLY|fsapi.O_CREAT, 0o444) require.EqualErrno(t, 0, errno) defer f.Close() @@ -60,7 +60,7 @@ func testOpen_O_RDWR(t *testing.T, tmpDir string, testFS fsapi.FS) { // from os.TestDirFSPathsValid if runtime.GOOS != "windows" { t.Run("strange name", func(t *testing.T) { - f, errno = testFS.OpenFile(`e:xperi\ment.txt`, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) + f, errno = testFS.OpenFile(`e:xperi\ment.txt`, fsapi.O_WRONLY|fsapi.O_CREAT|fsapi.O_TRUNC, 0o600) require.EqualErrno(t, 0, errno) defer f.Close() @@ -77,7 +77,7 @@ func testOpen_O_RDWR(t *testing.T, tmpDir string, testFS fsapi.FS) { realPath := path.Join(tmpDir, name) require.NoError(t, os.WriteFile(realPath, []byte("123456"), 0o0666)) - f, errno = testFS.OpenFile(name, os.O_RDWR|os.O_TRUNC, 0o444) + f, errno = testFS.OpenFile(name, fsapi.O_RDWR|fsapi.O_TRUNC, 0o444) require.EqualErrno(t, 0, errno) require.EqualErrno(t, 0, f.Close()) @@ -91,14 +91,14 @@ func testOpen_Read(t *testing.T, testFS fsapi.FS, requireFileIno, expectDirIno b t.Helper() t.Run("doesn't exist", func(t *testing.T) { - _, errno := testFS.OpenFile("nope", os.O_RDONLY, 0) + _, errno := testFS.OpenFile("nope", fsapi.O_RDONLY, 0) // We currently follow os.Open not syscall.Open, so the error is wrapped. require.EqualErrno(t, experimentalsys.ENOENT, errno) }) t.Run("readdir . opens root", func(t *testing.T) { - f, errno := testFS.OpenFile(".", os.O_RDONLY, 0) + f, errno := testFS.OpenFile(".", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) defer f.Close() @@ -119,7 +119,7 @@ func testOpen_Read(t *testing.T, testFS fsapi.FS, requireFileIno, expectDirIno b }) t.Run("readdir empty", func(t *testing.T) { - f, errno := testFS.OpenFile("emptydir", os.O_RDONLY, 0) + f, errno := testFS.OpenFile("emptydir", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) defer f.Close() @@ -128,7 +128,7 @@ func testOpen_Read(t *testing.T, testFS fsapi.FS, requireFileIno, expectDirIno b }) t.Run("readdir partial", func(t *testing.T) { - dirF, errno := testFS.OpenFile("dir", os.O_RDONLY, 0) + dirF, errno := testFS.OpenFile("dir", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) defer dirF.Close() @@ -167,7 +167,7 @@ func testOpen_Read(t *testing.T, testFS fsapi.FS, requireFileIno, expectDirIno b }) t.Run("file exists", func(t *testing.T) { - f, errno := testFS.OpenFile("animals.txt", os.O_RDONLY, 0) + f, errno := testFS.OpenFile("animals.txt", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) defer f.Close() @@ -198,7 +198,7 @@ human }) t.Run("file stat includes inode", func(t *testing.T) { - f, errno := testFS.OpenFile("empty.txt", os.O_RDONLY, 0) + f, errno := testFS.OpenFile("empty.txt", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) defer f.Close() @@ -215,15 +215,15 @@ human t.Run("or'd flag", func(t *testing.T) { // Example of a flag that can be or'd into O_RDONLY even if not // currently supported in WASI or GOOS=js - const O_NOATIME = 0x40000 + const O_NOATIME = fsapi.Oflag(0x40000) - f, errno := testFS.OpenFile("animals.txt", os.O_RDONLY|O_NOATIME, 0) + f, errno := testFS.OpenFile("animals.txt", fsapi.O_RDONLY|O_NOATIME, 0) require.EqualErrno(t, 0, errno) defer f.Close() }) t.Run("writing to a read-only file is EBADF", func(t *testing.T) { - f, errno := testFS.OpenFile("animals.txt", os.O_RDONLY, 0) + f, errno := testFS.OpenFile("animals.txt", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) defer f.Close() @@ -232,7 +232,7 @@ human }) t.Run("opening a directory with O_RDWR is EISDIR", func(t *testing.T) { - _, errno := testFS.OpenFile("sub", fsapi.O_DIRECTORY|os.O_RDWR, 0) + _, errno := testFS.OpenFile("sub", fsapi.O_DIRECTORY|fsapi.O_RDWR, 0) require.EqualErrno(t, experimentalsys.EISDIR, errno) }) } diff --git a/internal/wasip1/rights.go b/internal/wasip1/rights.go index 3b6919cf07..2ab56c6041 100644 --- a/internal/wasip1/rights.go +++ b/internal/wasip1/rights.go @@ -41,7 +41,7 @@ const ( RIGHT_PATH_CREATE_DIRECTORY // RIGHT_PATH_CREATE_FILE when RIGHT_PATH_OPEN is set, the right to invoke - // path_open with O_CREATE. + // path_open with O_CREAT. RIGHT_PATH_CREATE_FILE // RIGHT_PATH_LINK_SOURCE is the right to invoke path_link with the file diff --git a/internal/wasm/module_instance_test.go b/internal/wasm/module_instance_test.go index 761bdf42c7..1c33588a44 100644 --- a/internal/wasm/module_instance_test.go +++ b/internal/wasm/module_instance_test.go @@ -4,13 +4,13 @@ import ( "context" "errors" "fmt" - "os" "sync" "sync/atomic" "testing" "time" "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" internalsys "github.com/tetratelabs/wazero/internal/sys" "github.com/tetratelabs/wazero/internal/sysfs" testfs "github.com/tetratelabs/wazero/internal/testing/fs" @@ -117,7 +117,7 @@ func TestModuleInstance_Close(t *testing.T) { sysCtx := internalsys.DefaultContext(testFS) fsCtx := sysCtx.FS() - _, errno := fsCtx.OpenFile(testFS, "/foo", os.O_RDONLY, 0) + _, errno := fsCtx.OpenFile(testFS, "/foo", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) m, err := s.Instantiate(testCtx, &Module{}, t.Name(), sysCtx, nil) @@ -152,7 +152,7 @@ func TestModuleInstance_Close(t *testing.T) { sysCtx := internalsys.DefaultContext(testFS) fsCtx := sysCtx.FS() - _, errno := fsCtx.OpenFile(testFS, "/foo", os.O_RDONLY, 0) + _, errno := fsCtx.OpenFile(testFS, "/foo", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) m, err := s.Instantiate(testCtx, &Module{}, t.Name(), sysCtx, nil) @@ -222,7 +222,7 @@ func TestModuleInstance_CallDynamic(t *testing.T) { sysCtx := internalsys.DefaultContext(testFS) fsCtx := sysCtx.FS() - _, errno := fsCtx.OpenFile(testFS, "/foo", os.O_RDONLY, 0) + _, errno := fsCtx.OpenFile(testFS, "/foo", fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) m, err := s.Instantiate(testCtx, &Module{}, t.Name(), sysCtx, nil) @@ -251,7 +251,7 @@ func TestModuleInstance_CallDynamic(t *testing.T) { fsCtx := sysCtx.FS() path := "/foo" - _, errno := fsCtx.OpenFile(testFS, path, os.O_RDONLY, 0) + _, errno := fsCtx.OpenFile(testFS, path, fsapi.O_RDONLY, 0) require.EqualErrno(t, 0, errno) m, err := s.Instantiate(testCtx, &Module{}, t.Name(), sysCtx, nil)