From e7cf1de03c089b0216df8cd819d9c6393e2ee271 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Mon, 7 Aug 2023 08:10:31 +0800 Subject: [PATCH] Re-introduces internal fsapi.File with non-blocking methods Folks aren't currently on the same page on whether or not users should be able to implement the timeout parameter on `File.Poll` or whether it should always be externally controlled via a sleep loop which uses poll (immediate). Until we are sure internally, we shouldn't expose this for implementation, as it would be confusing for the same reason. This migrates the non-blocking functionality back into the internal package. One future way out could be to allow the user to decide if the backend is allowed to use the timeout parameter or not. Just as they control the FS, they could control a poller, which would then be able to span across both normal files and sockets. One could then be able to choose in the case of a single file and a specific known wasm, simply use the native timeout. In other words, since there are valid alternate answers, even though Go doesn't support custom pollers we could, if we decide that native polling is basically not allowed. Alternatively, we could force it to never be allowed by removing the timeout parameter (making it a poll immediate). Anyway, I will leave that decision up to @ncruces and @evacchi who will take responsibility of whichever decision is made. 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 +}