From 009ee7056c1f7afb597781851cc8b0f660c5bbc5 Mon Sep 17 00:00:00 2001 From: Crypt Keeper <64215+codefromthecrypt@users.noreply.github.com> Date: Mon, 7 Aug 2023 15:50:38 +0800 Subject: [PATCH] Re-introduces internal fsapi.File with non-blocking methods (#1613) Signed-off-by: Adrian Cole --- experimental/sys/dir.go | 15 ---- experimental/sys/file.go | 54 ------------- experimental/sys/unimplemented.go | 15 ---- imports/wasi_snapshot_preview1/fs_test.go | 3 +- imports/wasi_snapshot_preview1/poll.go | 3 +- imports/wasi_snapshot_preview1/poll_test.go | 13 ++-- .../wasi_stdlib_test.go | 4 +- internal/fsapi/file.go | 69 +++++++++++++++++ {experimental/sys => internal/fsapi}/poll.go | 2 +- internal/fsapi/unimplemented.go | 27 +++++++ internal/sys/fs.go | 21 ++--- internal/sys/lazy.go | 68 +++++++++------- internal/sys/stdio.go | 22 +++++- internal/sysfs/file.go | 22 +++++- internal/sysfs/file_test.go | 5 +- internal/sysfs/osfile.go | 11 +-- internal/sysfs/poll.go | 5 +- internal/sysfs/poll_unsupported.go | 5 +- internal/sysfs/sock_test.go | 8 +- internal/sysfs/sock_unix.go | 52 ++++++++----- internal/sysfs/sock_windows.go | 77 ++++++++++--------- 21 files changed, 295 insertions(+), 206 deletions(-) create mode 100644 internal/fsapi/file.go rename {experimental/sys => internal/fsapi}/poll.go (97%) create mode 100644 internal/fsapi/unimplemented.go diff --git a/experimental/sys/dir.go b/experimental/sys/dir.go index 047e5daab4..0b997cb8fc 100644 --- a/experimental/sys/dir.go +++ b/experimental/sys/dir.go @@ -61,16 +61,6 @@ func (DirFile) SetAppend(bool) Errno { return EISDIR } -// IsNonblock implements File.IsNonblock -func (DirFile) IsNonblock() bool { - return false -} - -// SetNonblock implements File.SetNonblock -func (DirFile) SetNonblock(bool) Errno { - return EISDIR -} - // IsDir implements File.IsDir func (DirFile) IsDir() (bool, Errno) { return true, 0 @@ -86,11 +76,6 @@ func (DirFile) Pread([]byte, int64) (int, Errno) { return 0, EISDIR } -// Poll implements File.Poll -func (DirFile) Poll(Pflag, int32) (ready bool, errno Errno) { - return false, ENOSYS -} - // Write implements File.Write func (DirFile) Write([]byte) (int, Errno) { return 0, EISDIR diff --git a/experimental/sys/file.go b/experimental/sys/file.go index d1abfc6a46..f8f2e5b128 100644 --- a/experimental/sys/file.go +++ b/experimental/sys/file.go @@ -67,29 +67,6 @@ type File interface { // - Implementations should cache this result. IsDir() (bool, Errno) - // IsNonblock returns true if the file was opened with O_NONBLOCK, or - // SetNonblock was successfully enabled on this file. - // - // # Notes - // - // - This might not match the underlying state of the file descriptor if - // the file was not opened via OpenFile. - IsNonblock() bool - - // SetNonblock toggles the non-blocking mode (O_NONBLOCK) of this file. - // - // # Errors - // - // A zero Errno is success. The below are expected otherwise: - // - ENOSYS: the implementation does not support this function. - // - EBADF: the file or directory was closed. - // - // # Notes - // - // - This is like syscall.SetNonblock and `fcntl` with O_NONBLOCK in - // POSIX. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/fcntl.html - SetNonblock(enable bool) Errno - // IsAppend returns true if the file was opened with O_APPEND, or // SetAppend was successfully enabled on this file. // @@ -200,37 +177,6 @@ type File interface { // of io.Seeker. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/fseek.html Seek(offset int64, whence int) (newOffset int64, errno Errno) - // Poll returns if the file has data ready to be read or written. - // - // # Parameters - // - // The `flag` parameter determines which event to await, such as POLLIN, - // POLLOUT, or a combination like `POLLIN|POLLOUT`. - // - // The `timeoutMillis` parameter is how long to block for an event, or - // interrupted, in milliseconds. There are two special values: - // - zero returns immediately - // - any negative value blocks any amount of time - // - // # Results - // - // `ready` means there was data ready to read or written. False can mean no - // event was ready or `errno` is not zero. - // - // A zero `errno` is success. The below are expected otherwise: - // - ENOSYS: the implementation does not support this function. - // - ENOTSUP: the implementation does not the flag combination. - // - EINTR: the call was interrupted prior to an event. - // - // # Notes - // - // - This is like `poll` in POSIX, for a single file. - // See https://pubs.opengroup.org/onlinepubs/9699919799/functions/poll.html - // - No-op files, such as those which read from /dev/null, should return - // immediately true, as data will never become available. - // - See /RATIONALE.md for detailed notes including impact of blocking. - Poll(flag Pflag, timeoutMillis int32) (ready bool, errno Errno) - // Readdir reads the contents of the directory associated with file and // returns a slice of up to n Dirent values in an arbitrary order. This is // a stateful function, so subsequent calls return any next values. diff --git a/experimental/sys/unimplemented.go b/experimental/sys/unimplemented.go index 764fba6a24..d853d9e8f4 100644 --- a/experimental/sys/unimplemented.go +++ b/experimental/sys/unimplemented.go @@ -101,16 +101,6 @@ func (UnimplementedFile) SetAppend(bool) Errno { return ENOSYS } -// IsNonblock implements File.IsNonblock -func (UnimplementedFile) IsNonblock() bool { - return false -} - -// SetNonblock implements File.SetNonblock -func (UnimplementedFile) SetNonblock(bool) Errno { - return ENOSYS -} - // Stat implements File.Stat func (UnimplementedFile) Stat() (sys.Stat_t, Errno) { return sys.Stat_t{}, ENOSYS @@ -136,11 +126,6 @@ func (UnimplementedFile) Readdir(int) (dirents []Dirent, errno Errno) { return nil, ENOSYS } -// Poll implements File.Poll -func (UnimplementedFile) Poll(Pflag, int32) (ready bool, errno Errno) { - return false, ENOSYS -} - // Write implements File.Write func (UnimplementedFile) Write([]byte) (int, Errno) { return 0, ENOSYS diff --git a/imports/wasi_snapshot_preview1/fs_test.go b/imports/wasi_snapshot_preview1/fs_test.go index e8883b2169..76ad32bcfc 100644 --- a/imports/wasi_snapshot_preview1/fs_test.go +++ b/imports/wasi_snapshot_preview1/fs_test.go @@ -17,6 +17,7 @@ import ( "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/api" experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" "github.com/tetratelabs/wazero/internal/fstest" "github.com/tetratelabs/wazero/internal/platform" "github.com/tetratelabs/wazero/internal/sys" @@ -247,7 +248,7 @@ func Test_fdFdstatGet(t *testing.T) { }}}).OpenFile("stdin", 0, 0) require.EqualErrno(t, 0, errno) - stdin.File = stdinFile + stdin.File = fsapi.Adapt(stdinFile) // Make this file writeable, to ensure flags read-back correctly. fileFD, errno := fsc.OpenFile(preopen, file, experimentalsys.O_RDWR, 0) diff --git a/imports/wasi_snapshot_preview1/poll.go b/imports/wasi_snapshot_preview1/poll.go index a3eca018b3..d09f30245b 100644 --- a/imports/wasi_snapshot_preview1/poll.go +++ b/imports/wasi_snapshot_preview1/poll.go @@ -6,6 +6,7 @@ import ( "github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" internalsys "github.com/tetratelabs/wazero/internal/sys" "github.com/tetratelabs/wazero/internal/wasip1" "github.com/tetratelabs/wazero/internal/wasm" @@ -177,7 +178,7 @@ func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) sys.Errno } // Wait for the timeout to expire, or for some data to become available on Stdin. - if stdinReady, errno := stdin.File.Poll(sys.POLLIN, int32(timeout.Milliseconds())); errno != 0 { + if stdinReady, errno := stdin.File.Poll(fsapi.POLLIN, int32(timeout.Milliseconds())); errno != 0 { return errno } else if stdinReady { // stdin has data ready to for reading, write back all the events diff --git a/imports/wasi_snapshot_preview1/poll_test.go b/imports/wasi_snapshot_preview1/poll_test.go index e077ec65b9..a5357e7966 100644 --- a/imports/wasi_snapshot_preview1/poll_test.go +++ b/imports/wasi_snapshot_preview1/poll_test.go @@ -10,6 +10,7 @@ import ( "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/api" experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" "github.com/tetratelabs/wazero/internal/sys" "github.com/tetratelabs/wazero/internal/testing/require" "github.com/tetratelabs/wazero/internal/wasip1" @@ -161,7 +162,7 @@ func Test_pollOneoff_Stdin(t *testing.T) { name string in, out, nsubscriptions, resultNevents uint32 mem []byte // at offset in - stdin experimentalsys.File + stdin fsapi.File expectedErrno wasip1.Errno expectedMem []byte // at offset out expectedLog string @@ -442,7 +443,7 @@ func Test_pollOneoff_Stdin(t *testing.T) { } } -func setStdin(t *testing.T, mod api.Module, stdin experimentalsys.File) { +func setStdin(t *testing.T, mod api.Module, stdin fsapi.File) { fsc := mod.(*wasm.ModuleInstance).Sys.FS() f, ok := fsc.LookupFile(sys.FdStdin) require.True(t, ok) @@ -613,8 +614,8 @@ type neverReadyTtyStdinFile struct { } // Poll implements the same method as documented on sys.File -func (neverReadyTtyStdinFile) Poll(flag experimentalsys.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) { - if flag != experimentalsys.POLLIN { +func (neverReadyTtyStdinFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) { + if flag != fsapi.POLLIN { return false, experimentalsys.ENOTSUP } switch { @@ -632,8 +633,8 @@ type pollStdinFile struct { } // Poll implements the same method as documented on sys.File -func (p *pollStdinFile) Poll(flag experimentalsys.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) { - if flag != experimentalsys.POLLIN { +func (p *pollStdinFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) { + if flag != fsapi.POLLIN { return false, experimentalsys.ENOTSUP } return p.ready, 0 diff --git a/imports/wasi_snapshot_preview1/wasi_stdlib_test.go b/imports/wasi_snapshot_preview1/wasi_stdlib_test.go index 3e4933ff37..93ac1a5670 100644 --- a/imports/wasi_snapshot_preview1/wasi_stdlib_test.go +++ b/imports/wasi_snapshot_preview1/wasi_stdlib_test.go @@ -20,8 +20,8 @@ import ( "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/api" experimentalsock "github.com/tetratelabs/wazero/experimental/sock" - experimentalsys "github.com/tetratelabs/wazero/experimental/sys" "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" + "github.com/tetratelabs/wazero/internal/fsapi" "github.com/tetratelabs/wazero/internal/fstest" internalsys "github.com/tetratelabs/wazero/internal/sys" "github.com/tetratelabs/wazero/internal/testing/require" @@ -329,7 +329,7 @@ func Test_Poll(t *testing.T) { tests := []struct { name string args []string - stdin experimentalsys.File + stdin fsapi.File expectedOutput string expectedTimeout time.Duration }{ diff --git a/internal/fsapi/file.go b/internal/fsapi/file.go new file mode 100644 index 0000000000..0640b22712 --- /dev/null +++ b/internal/fsapi/file.go @@ -0,0 +1,69 @@ +package fsapi + +import experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + +// File includes methods not yet ready to document for end users, notably +// non-blocking functionality. +// +// Particularly, Poll is subject to debate. For example, whether a user should +// be able to choose how to implement timeout or not. Currently, this interface +// allows the user to choose to sleep or use native polling, and which choice +// they make impacts thread behavior as summarized here: +// https://github.com/tetratelabs/wazero/pull/1606#issuecomment-1665475516 +type File interface { + experimentalsys.File + + // IsNonblock returns true if the file was opened with O_NONBLOCK, or + // SetNonblock was successfully enabled on this file. + // + // # Notes + // + // - This might not match the underlying state of the file descriptor if + // the file was not opened via OpenFile. + IsNonblock() bool + + // SetNonblock toggles the non-blocking mode (O_NONBLOCK) of this file. + // + // # Errors + // + // A zero Errno is success. The below are expected otherwise: + // - ENOSYS: the implementation does not support this function. + // - EBADF: the file or directory was closed. + // + // # Notes + // + // - This is like syscall.SetNonblock and `fcntl` with O_NONBLOCK in + // POSIX. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/fcntl.html + SetNonblock(enable bool) experimentalsys.Errno + + // Poll returns if the file has data ready to be read or written. + // + // # Parameters + // + // The `flag` parameter determines which event to await, such as POLLIN, + // POLLOUT, or a combination like `POLLIN|POLLOUT`. + // + // The `timeoutMillis` parameter is how long to block for an event, or + // interrupted, in milliseconds. There are two special values: + // - zero returns immediately + // - any negative value blocks any amount of time + // + // # Results + // + // `ready` means there was data ready to read or written. False can mean no + // event was ready or `errno` is not zero. + // + // A zero `errno` is success. The below are expected otherwise: + // - ENOSYS: the implementation does not support this function. + // - ENOTSUP: the implementation does not the flag combination. + // - EINTR: the call was interrupted prior to an event. + // + // # Notes + // + // - This is like `poll` in POSIX, for a single file. + // See https://pubs.opengroup.org/onlinepubs/9699919799/functions/poll.html + // - No-op files, such as those which read from /dev/null, should return + // immediately true, as data will never become available. + // - See /RATIONALE.md for detailed notes including impact of blocking. + Poll(flag Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) +} diff --git a/experimental/sys/poll.go b/internal/fsapi/poll.go similarity index 97% rename from experimental/sys/poll.go rename to internal/fsapi/poll.go index c63e55b958..25f7c5711b 100644 --- a/experimental/sys/poll.go +++ b/internal/fsapi/poll.go @@ -1,4 +1,4 @@ -package sys +package fsapi // Pflag are bit flags used for File.Poll. Values, including zero, should not // be interpreted numerically. Instead, use by constants prefixed with 'POLL'. diff --git a/internal/fsapi/unimplemented.go b/internal/fsapi/unimplemented.go new file mode 100644 index 0000000000..99d9c2db34 --- /dev/null +++ b/internal/fsapi/unimplemented.go @@ -0,0 +1,27 @@ +package fsapi + +import experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + +func Adapt(f experimentalsys.File) File { + if f, ok := f.(File); ok { + return f + } + return unimplementedFile{f} +} + +type unimplementedFile struct{ experimentalsys.File } + +// IsNonblock implements File.IsNonblock +func (unimplementedFile) IsNonblock() bool { + return false +} + +// SetNonblock implements File.SetNonblock +func (unimplementedFile) SetNonblock(bool) experimentalsys.Errno { + return experimentalsys.ENOSYS +} + +// Poll implements File.Poll +func (unimplementedFile) Poll(Pflag, int32) (ready bool, errno experimentalsys.Errno) { + return false, experimentalsys.ENOSYS +} diff --git a/internal/sys/fs.go b/internal/sys/fs.go index 2d1ce51941..332a952626 100644 --- a/internal/sys/fs.go +++ b/internal/sys/fs.go @@ -7,6 +7,7 @@ import ( "github.com/tetratelabs/wazero/experimental/sys" "github.com/tetratelabs/wazero/internal/descriptor" + "github.com/tetratelabs/wazero/internal/fsapi" socketapi "github.com/tetratelabs/wazero/internal/sock" "github.com/tetratelabs/wazero/internal/sysfs" ) @@ -50,7 +51,7 @@ type FileEntry struct { FS sys.FS // File is always non-nil. - File sys.File + File fsapi.File // direntCache is nil until DirentCache was called. direntCache *DirentCache @@ -287,7 +288,7 @@ func (c *FSContext) OpenFile(fs sys.FS, path string, flag sys.Oflag, perm fs.Fil if f, errno := fs.OpenFile(path, flag, perm); errno != 0 { return 0, errno } else { - fe := &FileEntry{FS: fs, File: f} + fe := &FileEntry{FS: fs, File: fsapi.Adapt(f)} if path == "/" || path == "." { fe.Name = "" } else { @@ -339,18 +340,20 @@ func (c *FSContext) SockAccept(sockFD int32, nonblock bool) (int32, sys.Errno) { return 0, sys.EBADF // Not a sock } - var conn socketapi.TCPConn - var errno sys.Errno - if conn, errno = sock.Accept(); errno != 0 { + conn, errno := sock.Accept() + if errno != 0 { return 0, errno - } else if nonblock { - if errno = conn.SetNonblock(true); errno != 0 { + } + + fe := &FileEntry{File: fsapi.Adapt(conn)} + + if nonblock { + if errno = fe.File.SetNonblock(true); errno != 0 { _ = conn.Close() return 0, errno } } - fe := &FileEntry{File: conn} if newFD, ok := c.openedFiles.Insert(fe); !ok { return 0, sys.EBADF } else { @@ -426,7 +429,7 @@ func (c *Context) InitFSContext( } for _, tl := range tcpListeners { - c.fsc.openedFiles.Insert(&FileEntry{IsPreopen: true, File: sysfs.NewTCPListenerFile(tl)}) + c.fsc.openedFiles.Insert(&FileEntry{IsPreopen: true, File: fsapi.Adapt(sysfs.NewTCPListenerFile(tl))}) } return nil } diff --git a/internal/sys/lazy.go b/internal/sys/lazy.go index 0deb79c589..fe233d29ea 100644 --- a/internal/sys/lazy.go +++ b/internal/sys/lazy.go @@ -2,6 +2,7 @@ package sys import ( experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" "github.com/tetratelabs/wazero/sys" ) @@ -16,8 +17,8 @@ type lazyDir struct { } // Dev implements the same method as documented on sys.File -func (r *lazyDir) Dev() (uint64, experimentalsys.Errno) { - if f, ok := r.file(); !ok { +func (d *lazyDir) Dev() (uint64, experimentalsys.Errno) { + if f, ok := d.file(); !ok { return 0, experimentalsys.EBADF } else { return f.Dev() @@ -25,8 +26,8 @@ func (r *lazyDir) Dev() (uint64, experimentalsys.Errno) { } // Ino implements the same method as documented on sys.File -func (r *lazyDir) Ino() (sys.Inode, experimentalsys.Errno) { - if f, ok := r.file(); !ok { +func (d *lazyDir) Ino() (sys.Inode, experimentalsys.Errno) { + if f, ok := d.file(); !ok { return 0, experimentalsys.EBADF } else { return f.Ino() @@ -34,10 +35,10 @@ func (r *lazyDir) Ino() (sys.Inode, experimentalsys.Errno) { } // IsDir implements the same method as documented on sys.File -func (r *lazyDir) IsDir() (bool, experimentalsys.Errno) { +func (d *lazyDir) IsDir() (bool, experimentalsys.Errno) { // Note: we don't return a constant because we don't know if this is really // backed by a dir, until the first call. - if f, ok := r.file(); !ok { + if f, ok := d.file(); !ok { return false, experimentalsys.EBADF } else { return f.IsDir() @@ -45,18 +46,18 @@ func (r *lazyDir) IsDir() (bool, experimentalsys.Errno) { } // IsAppend implements the same method as documented on sys.File -func (r *lazyDir) IsAppend() bool { +func (d *lazyDir) IsAppend() bool { return false } // SetAppend implements the same method as documented on sys.File -func (r *lazyDir) SetAppend(bool) experimentalsys.Errno { +func (d *lazyDir) SetAppend(bool) experimentalsys.Errno { return experimentalsys.EISDIR } // Seek implements the same method as documented on sys.File -func (r *lazyDir) Seek(offset int64, whence int) (newOffset int64, errno experimentalsys.Errno) { - if f, ok := r.file(); !ok { +func (d *lazyDir) Seek(offset int64, whence int) (newOffset int64, errno experimentalsys.Errno) { + if f, ok := d.file(); !ok { return 0, experimentalsys.EBADF } else { return f.Seek(offset, whence) @@ -64,8 +65,8 @@ func (r *lazyDir) Seek(offset int64, whence int) (newOffset int64, errno experim } // Stat implements the same method as documented on sys.File -func (r *lazyDir) Stat() (sys.Stat_t, experimentalsys.Errno) { - if f, ok := r.file(); !ok { +func (d *lazyDir) Stat() (sys.Stat_t, experimentalsys.Errno) { + if f, ok := d.file(); !ok { return sys.Stat_t{}, experimentalsys.EBADF } else { return f.Stat() @@ -73,8 +74,8 @@ func (r *lazyDir) Stat() (sys.Stat_t, experimentalsys.Errno) { } // Readdir implements the same method as documented on sys.File -func (r *lazyDir) Readdir(n int) (dirents []experimentalsys.Dirent, errno experimentalsys.Errno) { - if f, ok := r.file(); !ok { +func (d *lazyDir) Readdir(n int) (dirents []experimentalsys.Dirent, errno experimentalsys.Errno) { + if f, ok := d.file(); !ok { return nil, experimentalsys.EBADF } else { return f.Readdir(n) @@ -82,8 +83,8 @@ func (r *lazyDir) Readdir(n int) (dirents []experimentalsys.Dirent, errno experi } // Sync implements the same method as documented on sys.File -func (r *lazyDir) Sync() experimentalsys.Errno { - if f, ok := r.file(); !ok { +func (d *lazyDir) Sync() experimentalsys.Errno { + if f, ok := d.file(); !ok { return experimentalsys.EBADF } else { return f.Sync() @@ -91,8 +92,8 @@ func (r *lazyDir) Sync() experimentalsys.Errno { } // Datasync implements the same method as documented on sys.File -func (r *lazyDir) Datasync() experimentalsys.Errno { - if f, ok := r.file(); !ok { +func (d *lazyDir) Datasync() experimentalsys.Errno { + if f, ok := d.file(); !ok { return experimentalsys.EBADF } else { return f.Datasync() @@ -100,8 +101,8 @@ func (r *lazyDir) Datasync() experimentalsys.Errno { } // Utimens implements the same method as documented on sys.File -func (r *lazyDir) Utimens(atim, mtim int64) experimentalsys.Errno { - if f, ok := r.file(); !ok { +func (d *lazyDir) Utimens(atim, mtim int64) experimentalsys.Errno { + if f, ok := d.file(); !ok { return experimentalsys.EBADF } else { return f.Utimens(atim, mtim) @@ -109,15 +110,15 @@ func (r *lazyDir) Utimens(atim, mtim int64) experimentalsys.Errno { } // file returns the underlying file or false if it doesn't exist. -func (r *lazyDir) file() (experimentalsys.File, bool) { - if f := r.f; r.f != nil { +func (d *lazyDir) file() (experimentalsys.File, bool) { + if f := d.f; d.f != nil { return f, true } var errno experimentalsys.Errno - r.f, errno = r.fs.OpenFile(".", experimentalsys.O_RDONLY, 0) + d.f, errno = d.fs.OpenFile(".", experimentalsys.O_RDONLY, 0) switch errno { case 0: - return r.f, true + return d.f, true case experimentalsys.ENOENT: return nil, false default: @@ -126,10 +127,25 @@ func (r *lazyDir) file() (experimentalsys.File, bool) { } // Close implements fs.File -func (r *lazyDir) Close() experimentalsys.Errno { - f := r.f +func (d *lazyDir) Close() experimentalsys.Errno { + f := d.f if f == nil { return 0 // never opened } return f.Close() } + +// IsNonblock implements the same method as documented on fsapi.File +func (d *lazyDir) IsNonblock() bool { + return false +} + +// SetNonblock implements the same method as documented on fsapi.File +func (d *lazyDir) SetNonblock(bool) experimentalsys.Errno { + return experimentalsys.EISDIR +} + +// Poll implements the same method as documented on fsapi.File +func (d *lazyDir) Poll(fsapi.Pflag, int32) (ready bool, errno experimentalsys.Errno) { + return false, experimentalsys.ENOSYS +} diff --git a/internal/sys/stdio.go b/internal/sys/stdio.go index 6a249c3804..32c33661eb 100644 --- a/internal/sys/stdio.go +++ b/internal/sys/stdio.go @@ -5,6 +5,7 @@ import ( "os" experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" "github.com/tetratelabs/wazero/internal/sysfs" "github.com/tetratelabs/wazero/sys" ) @@ -47,9 +48,9 @@ func (noopStdinFile) Read([]byte) (int, experimentalsys.Errno) { return 0, 0 // Always EOF } -// Poll implements the same method as documented on sys.File -func (noopStdinFile) Poll(flag experimentalsys.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) { - if flag != experimentalsys.POLLIN { +// Poll implements the same method as documented on fsapi.File +func (noopStdinFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) { + if flag != fsapi.POLLIN { return false, experimentalsys.ENOTSUP } return true, 0 // always ready to read nothing @@ -83,6 +84,21 @@ func (noopStdioFile) IsDir() (bool, experimentalsys.Errno) { // Close implements the same method as documented on sys.File func (noopStdioFile) Close() (errno experimentalsys.Errno) { return } +// IsNonblock implements the same method as documented on fsapi.File +func (noopStdioFile) IsNonblock() bool { + return false +} + +// SetNonblock implements the same method as documented on fsapi.File +func (noopStdioFile) SetNonblock(bool) experimentalsys.Errno { + return experimentalsys.ENOSYS +} + +// Poll implements the same method as documented on fsapi.File +func (noopStdioFile) Poll(fsapi.Pflag, int32) (ready bool, errno experimentalsys.Errno) { + return false, experimentalsys.ENOSYS +} + func stdinFileEntry(r io.Reader) (*FileEntry, error) { if r == nil { return &FileEntry{Name: "stdin", IsPreopen: true, File: &noopStdinFile{}}, nil diff --git a/internal/sysfs/file.go b/internal/sysfs/file.go index a2258c3fff..9a77205bb5 100644 --- a/internal/sysfs/file.go +++ b/internal/sysfs/file.go @@ -7,10 +7,11 @@ import ( "time" experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" "github.com/tetratelabs/wazero/sys" ) -func NewStdioFile(stdin bool, f fs.File) (experimentalsys.File, error) { +func NewStdioFile(stdin bool, f fs.File) (fsapi.File, error) { // Return constant stat, which has fake times, but keep the underlying // file mode. Fake times are needed to pass wasi-testsuite. // https://github.com/WebAssembly/wasi-testsuite/blob/af57727/tests/rust/src/bin/fd_filestat_get.rs#L1-L19 @@ -26,7 +27,7 @@ func NewStdioFile(stdin bool, f fs.File) (experimentalsys.File, error) { } else { flag = experimentalsys.O_WRONLY } - var file experimentalsys.File + var file fsapi.File if of, ok := f.(*os.File); ok { // This is ok because functions that need path aren't used by stdioFile file = newOsFile("", flag, 0, of) @@ -65,7 +66,7 @@ func OpenFSFile(fs fs.FS, path string, flag experimentalsys.Oflag, perm fs.FileM } type stdioFile struct { - experimentalsys.File + fsapi.File st sys.Stat_t } @@ -343,6 +344,21 @@ func (f *fsFile) close() experimentalsys.Errno { return experimentalsys.UnwrapOSError(f.file.Close()) } +// IsNonblock implements the same method as documented on fsapi.File +func (f *fsFile) IsNonblock() bool { + return false +} + +// SetNonblock implements the same method as documented on fsapi.File +func (f *fsFile) SetNonblock(bool) experimentalsys.Errno { + return experimentalsys.ENOSYS +} + +// Poll implements the same method as documented on fsapi.File +func (f *fsFile) Poll(fsapi.Pflag, int32) (ready bool, errno experimentalsys.Errno) { + return false, experimentalsys.ENOSYS +} + // dirError is used for commands that work against a directory, but not a file. func dirError(f experimentalsys.File, isClosed bool, errno experimentalsys.Errno) experimentalsys.Errno { if vErrno := validate(f, isClosed, false, true); vErrno != 0 { diff --git a/internal/sysfs/file_test.go b/internal/sysfs/file_test.go index 00501154a0..1b97eb860a 100644 --- a/internal/sysfs/file_test.go +++ b/internal/sysfs/file_test.go @@ -11,6 +11,7 @@ import ( gofstest "testing/fstest" experimentalsys "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" "github.com/tetratelabs/wazero/sys" @@ -319,7 +320,7 @@ func TestFileReadAndPread(t *testing.T) { } func TestFilePoll_POLLIN(t *testing.T) { - pflag := experimentalsys.POLLIN + pflag := fsapi.POLLIN // Test using os.Pipe as it is known to support poll. r, w, err := os.Pipe() @@ -355,7 +356,7 @@ func TestFilePoll_POLLIN(t *testing.T) { } func TestFilePoll_POLLOUT(t *testing.T) { - pflag := experimentalsys.POLLOUT + pflag := fsapi.POLLOUT // Test using os.Pipe as it is known to support poll. r, w, err := os.Pipe() diff --git a/internal/sysfs/osfile.go b/internal/sysfs/osfile.go index adb664fc81..ac0df777a9 100644 --- a/internal/sysfs/osfile.go +++ b/internal/sysfs/osfile.go @@ -7,10 +7,11 @@ import ( "runtime" experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" "github.com/tetratelabs/wazero/sys" ) -func newOsFile(path string, flag experimentalsys.Oflag, perm fs.FileMode, f *os.File) experimentalsys.File { +func newOsFile(path string, flag experimentalsys.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. @@ -102,12 +103,12 @@ func (f *osFile) reopen() (errno experimentalsys.Errno) { return } -// IsNonblock implements the same method as documented on sys.File +// IsNonblock implements the same method as documented on fsapi.File func (f *osFile) IsNonblock() bool { return isNonblock(f) } -// SetNonblock implements the same method as documented on sys.File +// SetNonblock implements the same method as documented on fsapi.File func (f *osFile) SetNonblock(enable bool) (errno experimentalsys.Errno) { if enable { f.flag |= experimentalsys.O_NONBLOCK @@ -178,8 +179,8 @@ func (f *osFile) Seek(offset int64, whence int) (newOffset int64, errno experime return } -// Poll implements the same method as documented on sys.File -func (f *osFile) Poll(flag experimentalsys.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) { +// Poll implements the same method as documented on fsapi.File +func (f *osFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) { return poll(f.fd, flag, timeoutMillis) } diff --git a/internal/sysfs/poll.go b/internal/sysfs/poll.go index 4041699fcf..f5c9829529 100644 --- a/internal/sysfs/poll.go +++ b/internal/sysfs/poll.go @@ -4,11 +4,12 @@ package sysfs import ( "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" ) // poll implements `Poll` as documented on sys.File via a file descriptor. -func poll(fd uintptr, flag sys.Pflag, timeoutMillis int32) (ready bool, errno sys.Errno) { - if flag != sys.POLLIN { +func poll(fd uintptr, flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno sys.Errno) { + if flag != fsapi.POLLIN { return false, sys.ENOTSUP } fds := []pollFd{newPollFd(fd, _POLLIN, 0)} diff --git a/internal/sysfs/poll_unsupported.go b/internal/sysfs/poll_unsupported.go index f546e1549a..ebe8a6fa92 100644 --- a/internal/sysfs/poll_unsupported.go +++ b/internal/sysfs/poll_unsupported.go @@ -4,9 +4,10 @@ package sysfs import ( "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" ) -// poll implements `Poll` as documented on sys.File via a file descriptor. -func poll(fd uintptr, flag sys.Pflag, timeoutMillis int32) (ready bool, errno sys.Errno) { +// poll implements `Poll` as documented on fsapi.File via a file descriptor. +func poll(uintptr, fsapi.Pflag, int32) (bool, sys.Errno) { return false, sys.ENOSYS } diff --git a/internal/sysfs/sock_test.go b/internal/sysfs/sock_test.go index bdcb721e97..82b8ab56d4 100644 --- a/internal/sysfs/sock_test.go +++ b/internal/sysfs/sock_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" "github.com/tetratelabs/wazero/internal/testing/require" ) @@ -117,15 +118,16 @@ func TestTcpConnFile_SetNonblock(t *testing.T) { require.NoError(t, err) defer tcp.Close() //nolint - errno := lf.SetNonblock(true) + nblf := fsapi.Adapt(lf) + errno := nblf.SetNonblock(true) require.EqualErrno(t, 0, errno) - require.True(t, lf.IsNonblock()) + require.True(t, nblf.IsNonblock()) conn, errno := lf.Accept() require.EqualErrno(t, 0, errno) defer conn.Close() - file := newTcpConn(tcp) + file := fsapi.Adapt(newTcpConn(tcp)) errno = file.SetNonblock(true) require.EqualErrno(t, 0, errno) require.True(t, file.IsNonblock()) diff --git a/internal/sysfs/sock_unix.go b/internal/sysfs/sock_unix.go index 86db1ef051..3698f560e0 100644 --- a/internal/sysfs/sock_unix.go +++ b/internal/sysfs/sock_unix.go @@ -7,6 +7,7 @@ import ( "syscall" "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" socketapi "github.com/tetratelabs/wazero/internal/sock" ) @@ -56,24 +57,30 @@ func (f *tcpListenerFile) Accept() (socketapi.TCPConn, sys.Errno) { return &tcpConnFile{fd: uintptr(nfd)}, 0 } -// SetNonblock implements the same method as documented on sys.File +// Close implements the same method as documented on sys.File +func (f *tcpListenerFile) Close() sys.Errno { + return sys.UnwrapOSError(syscall.Close(int(f.fd))) +} + +// Addr is exposed for testing. +func (f *tcpListenerFile) Addr() *net.TCPAddr { + return f.addr +} + +// SetNonblock implements the same method as documented on fsapi.File func (f *tcpListenerFile) SetNonblock(enabled bool) sys.Errno { f.nonblock = enabled return sys.UnwrapOSError(setNonblock(f.fd, enabled)) } +// IsNonblock implements the same method as documented on fsapi.File func (f *tcpListenerFile) IsNonblock() bool { return f.nonblock } -// Close implements the same method as documented on sys.File -func (f *tcpListenerFile) Close() sys.Errno { - return sys.UnwrapOSError(syscall.Close(int(f.fd))) -} - -// Addr is exposed for testing. -func (f *tcpListenerFile) Addr() *net.TCPAddr { - return f.addr +// Poll implements the same method as documented on fsapi.File +func (f *tcpListenerFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno sys.Errno) { + return false, sys.ENOSYS } var _ socketapi.TCPConn = (*tcpConnFile)(nil) @@ -96,17 +103,6 @@ func newTcpConn(tc *net.TCPConn) socketapi.TCPConn { return &tcpConnFile{fd: f.Fd()} } -// SetNonblock implements the same method as documented on sys.File -func (f *tcpConnFile) SetNonblock(enabled bool) (errno sys.Errno) { - f.nonblock = enabled - return sys.UnwrapOSError(setNonblock(f.fd, enabled)) -} - -// IsNonblock implements the same method as documented on sys.File -func (f *tcpConnFile) IsNonblock() bool { - return f.nonblock -} - // Read implements the same method as documented on sys.File func (f *tcpConnFile) Read(buf []byte) (n int, errno sys.Errno) { n, err := syscall.Read(int(f.fd), buf) @@ -166,3 +162,19 @@ func (f *tcpConnFile) close() sys.Errno { f.closed = true return sys.UnwrapOSError(syscall.Shutdown(int(f.fd), syscall.SHUT_RDWR)) } + +// SetNonblock implements the same method as documented on fsapi.File +func (f *tcpConnFile) SetNonblock(enabled bool) (errno sys.Errno) { + f.nonblock = enabled + return sys.UnwrapOSError(setNonblock(f.fd, enabled)) +} + +// IsNonblock implements the same method as documented on fsapi.File +func (f *tcpConnFile) IsNonblock() bool { + return f.nonblock +} + +// Poll implements the same method as documented on fsapi.File +func (f *tcpConnFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno sys.Errno) { + return false, sys.ENOSYS +} diff --git a/internal/sysfs/sock_windows.go b/internal/sysfs/sock_windows.go index c289559a12..ed275e6290 100644 --- a/internal/sysfs/sock_windows.go +++ b/internal/sysfs/sock_windows.go @@ -8,6 +8,7 @@ import ( "unsafe" "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" socketapi "github.com/tetratelabs/wazero/internal/sock" ) @@ -17,8 +18,6 @@ const ( MSG_PEEK = 0x2 // _FIONBIO is the flag to set the O_NONBLOCK flag on socket handles using ioctlsocket. _FIONBIO = 0x8004667e - // _WASWOULDBLOCK corresponds to syscall.EWOULDBLOCK in WinSock. - _WASWOULDBLOCK = 10035 ) var ( @@ -26,12 +25,8 @@ var ( modws2_32 = syscall.NewLazyDLL("ws2_32.dll") // procrecvfrom exposes recvfrom from WinSock. procrecvfrom = modws2_32.NewProc("recvfrom") - // procaccept exposes accept from WinSock. - procaccept = modws2_32.NewProc("accept") // procioctlsocket exposes ioctlsocket from WinSock. procioctlsocket = modws2_32.NewProc("ioctlsocket") - // procselect exposes select from WinSock. - procselect = modws2_32.NewProc("select") ) // recvfrom exposes the underlying syscall in Windows. @@ -90,8 +85,8 @@ func syscallConnControl(conn syscall.Conn, fn func(fd uintptr) (int, sys.Errno)) return } -func _pollSock(conn syscall.Conn, flag sys.Pflag, timeoutMillis int32) (bool, sys.Errno) { - if flag != sys.POLLIN { +func _pollSock(conn syscall.Conn, flag fsapi.Pflag, timeoutMillis int32) (bool, sys.Errno) { + if flag != fsapi.POLLIN { return false, sys.ENOTSUP } n, errno := syscallConnControl(conn, func(fd uintptr) (int, sys.Errno) { @@ -126,7 +121,7 @@ type winTcpListenerFile struct { func (f *winTcpListenerFile) Accept() (socketapi.TCPConn, sys.Errno) { // Ensure we have an incoming connection using winsock_select, otherwise return immediately. if f.nonblock { - if ready, errno := _pollSock(f.tl, sys.POLLIN, 0); !ready || errno != 0 { + if ready, errno := _pollSock(f.tl, fsapi.POLLIN, 0); !ready || errno != 0 { return nil, sys.EAGAIN } } @@ -141,12 +136,25 @@ func (f *winTcpListenerFile) Accept() (socketapi.TCPConn, sys.Errno) { } } -// IsNonblock implements File.IsNonblock +// Close implements the same method as documented on sys.File +func (f *winTcpListenerFile) Close() sys.Errno { + if !f.closed { + return sys.UnwrapOSError(f.tl.Close()) + } + return 0 +} + +// Addr is exposed for testing. +func (f *winTcpListenerFile) Addr() *net.TCPAddr { + return f.tl.Addr().(*net.TCPAddr) +} + +// IsNonblock implements the same method as documented on fsapi.File func (f *winTcpListenerFile) IsNonblock() bool { return f.nonblock } -// SetNonblock implements the same method as documented on sys.File +// SetNonblock implements the same method as documented on fsapi.File func (f *winTcpListenerFile) SetNonblock(enabled bool) sys.Errno { f.nonblock = enabled _, errno := syscallConnControl(f.tl, func(fd uintptr) (int, sys.Errno) { @@ -155,17 +163,9 @@ func (f *winTcpListenerFile) SetNonblock(enabled bool) sys.Errno { return errno } -// Close implements the same method as documented on sys.File -func (f *winTcpListenerFile) Close() sys.Errno { - if !f.closed { - return sys.UnwrapOSError(f.tl.Close()) - } - return 0 -} - -// Addr is exposed for testing. -func (f *winTcpListenerFile) Addr() *net.TCPAddr { - return f.tl.Addr().(*net.TCPAddr) +// Poll implements the same method as documented on fsapi.File +func (f *winTcpListenerFile) Poll(fsapi.Pflag, int32) (ready bool, errno sys.Errno) { + return false, sys.ENOSYS } var _ socketapi.TCPConn = (*winTcpConnFile)(nil) @@ -189,20 +189,6 @@ func newTcpConn(tc *net.TCPConn) socketapi.TCPConn { return &winTcpConnFile{tc: tc} } -// SetNonblock implements the same method as documented on sys.File -func (f *winTcpConnFile) SetNonblock(enabled bool) (errno sys.Errno) { - f.nonblock = true - _, errno = syscallConnControl(f.tc, func(fd uintptr) (int, sys.Errno) { - return 0, sys.UnwrapOSError(setNonblockSocket(syscall.Handle(fd), enabled)) - }) - return -} - -// IsNonblock implements File.IsNonblock -func (f *winTcpConnFile) IsNonblock() bool { - return f.nonblock -} - // Read implements the same method as documented on sys.File func (f *winTcpConnFile) Read(buf []byte) (n int, errno sys.Errno) { if len(buf) == 0 { @@ -278,3 +264,22 @@ func (f *winTcpConnFile) close() sys.Errno { f.closed = true return f.Shutdown(syscall.SHUT_RDWR) } + +// IsNonblock implements the same method as documented on fsapi.File +func (f *winTcpConnFile) IsNonblock() bool { + return f.nonblock +} + +// SetNonblock implements the same method as documented on fsapi.File +func (f *winTcpConnFile) SetNonblock(enabled bool) (errno sys.Errno) { + f.nonblock = true + _, errno = syscallConnControl(f.tc, func(fd uintptr) (int, sys.Errno) { + return 0, sys.UnwrapOSError(setNonblockSocket(syscall.Handle(fd), enabled)) + }) + return +} + +// Poll implements the same method as documented on fsapi.File +func (f *winTcpConnFile) Poll(fsapi.Pflag, int32) (ready bool, errno sys.Errno) { + return false, sys.ENOSYS +}