Adds Sync to platform.File (#1426)

Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
Crypt Keeper
2023-05-02 13:16:50 +08:00
committed by GitHub
parent d5a2d3c7b4
commit b79c45b91c
7 changed files with 175 additions and 63 deletions

View File

@@ -22,8 +22,6 @@ import (
// The following interfaces are used until we finalize our own FD-scoped file.
type (
// syncFile is implemented by os.File in file_posix.go
syncFile interface{ Sync() error }
// truncateFile is implemented by os.File in file_posix.go
truncateFile interface{ Truncate(size int64) error }
)
@@ -1215,12 +1213,9 @@ func fdSyncFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno
// Check to see if the file descriptor is available
if f, ok := fsc.LookupFile(fd); !ok {
return syscall.EBADF
} else if syncFile, ok := f.File.File().(syncFile); !ok {
return syscall.EBADF // possibly a fake file
} else if err := syncFile.Sync(); err != nil {
return platform.UnwrapOSError(err)
} else {
return f.File.Sync()
}
return 0
}
// fdTell is the WASI function named FdTellName which returns the current

View File

@@ -50,8 +50,6 @@ var (
// The following interfaces are used until we finalize our own FD-scoped file.
type (
// syncFile is implemented by os.File in file_posix.go
syncFile interface{ Sync() error }
// truncateFile is implemented by os.File in file_posix.go
truncateFile interface{ Truncate(size int64) error }
)
@@ -673,10 +671,8 @@ func (jsfsFsync) invoke(ctx context.Context, mod api.Module, args ...interface{}
var errno syscall.Errno
if f, ok := fsc.LookupFile(fd); !ok {
errno = syscall.EBADF
} else if syncFile, ok := f.File.File().(syncFile); !ok {
errno = syscall.EBADF // possibly a fake file
} else {
errno = platform.UnwrapOSError(syncFile.Sync())
errno = f.File.Sync()
}
return jsfsInvoke(ctx, mod, callback, errno)

View File

@@ -32,34 +32,51 @@ type File interface {
// # Errors
//
// A zero syscall.Errno is success. The below are expected otherwise:
// - syscall.ENOSYS the implementation does not support this function.
// - syscall.EBADF if the file or directory was closed.
//
// # Notes
//
// - This is like `fstatat` with `AT_FDCWD` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/stat.html
// - An fs.FileInfo backed implementation sets atim, mtim and ctim to the
// - This is like syscall.Fstat and `fstatat` with `AT_FDCWD` in POSIX.
// See https://pubs.opengroup.org/onlinepubs/9699919799/functions/stat.html
// - A fs.FileInfo backed implementation sets atim, mtim and ctim to the
// same value.
// - Windows allows you to stat a closed directory.
Stat() (Stat_t, syscall.Errno)
// Chmod is like syscall.Fchmod.
// Chmod changes the mode of the file.
//
// # Errors
//
// A zero syscall.Errno is success. The below are expected otherwise:
// - syscall.ENOSYS the implementation does not support this function.
// - syscall.EBADF if the file or directory was closed.
//
// # Notes
//
// - This is like `fchmod` in POSIX. See
// - This is like syscall.Fchmod and `fchmod` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchmod.html
// - Windows ignores the execute bit, and any permissions come back as
// group and world. For example, chmod of 0400 reads back as 0444, and
// 0700 0666. Also, permissions on directories aren't supported at all.
Chmod(fs.FileMode) syscall.Errno
// Chown is like syscall.Fchown, but for nanosecond precision.
// Chown changes the owner and group of a file.
//
// # Errors
//
// A zero syscall.Errno is success. The below are expected otherwise:
// - syscall.ENOSYS the implementation does not support this function.
// - syscall.EBADF if the file or directory was closed.
//
// # Notes
//
// - This is like syscall.Fchown and `fchown` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchown.html
// - This always returns syscall.ENOSYS on windows.
Chown(uid, gid int) syscall.Errno
// Sync synchronizes changes to the file.
//
// # Errors
//
@@ -68,12 +85,21 @@ type File interface {
//
// # Notes
//
// - This is like `fchown` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchown.html
// - This always returns syscall.ENOSYS on windows.
Chown(uid, gid int) syscall.Errno
// - This is like syscall.Fsync and `fsync` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/fsync.html
// - This returns with no error instead of syscall.ENOSYS when
// unimplemented. This prevents fake filesystems from erring.
Sync() syscall.Errno
// Close closes the underlying file.
//
// A zero syscall.Errno is success. The below are expected otherwise:
// - syscall.ENOSYS the implementation does not support this function.
//
// # Notes
//
// - This is like syscall.Close and `close` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html
Close() syscall.Errno
// File is temporary until we port other methods.
@@ -99,6 +125,11 @@ func (UnimplementedFile) Chown(int, int) syscall.Errno {
return syscall.ENOSYS
}
// Sync implements File.Sync
func (UnimplementedFile) Sync() syscall.Errno {
return 0 // not syscall.ENOSYS
}
type DefaultFile struct {
F fs.File
}
@@ -128,6 +159,14 @@ func (f *DefaultFile) Chown(uid, gid int) syscall.Errno {
return syscall.ENOSYS
}
// Sync implements File.Sync
func (f *DefaultFile) Sync() syscall.Errno {
if f, ok := f.F.(syncFile); ok {
return UnwrapOSError(f.Sync())
}
return 0 // don't error
}
// Close implements File.Close
func (f *DefaultFile) Close() syscall.Errno {
return UnwrapOSError(f.F.Close())

View File

@@ -1,8 +1,14 @@
package platform
import (
"embed"
"io/fs"
"os"
"path"
"syscall"
"testing"
"github.com/tetratelabs/wazero/internal/testing/require"
)
var _ File = NoopFile{}
@@ -15,7 +21,46 @@ type NoopFile struct {
// The current design requires the user to consciously implement Close.
// However, we could change UnimplementedFile to return zero.
func (n NoopFile) Close() (errno syscall.Errno) { return }
func (NoopFile) Close() (errno syscall.Errno) { return }
// Once File.File is removed, it will be possible to implement NoopFile.
func (n NoopFile) File() fs.File { panic("noop") }
func (NoopFile) File() fs.File { panic("noop") }
//go:embed file_test.go
var embedFS embed.FS
func TestFileSync(t *testing.T) {
ro, err := embedFS.Open("file_test.go")
require.NoError(t, err)
defer ro.Close()
rw, err := os.Create(path.Join(t.TempDir(), "sync"))
require.NoError(t, err)
defer rw.Close()
tests := []struct {
name string
f File
}{
{
name: "UnimplementedFile",
f: NoopFile{},
},
{
name: "File of read-only fs.File",
f: &DefaultFile{F: ro},
},
{
name: "File of os.File",
f: &DefaultFile{F: rw},
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(b *testing.T) {
require.Zero(t, tc.f.Sync())
})
}
}

View File

@@ -187,6 +187,15 @@ func (r *lazyDir) Chown(uid, gid int) syscall.Errno {
}
}
// Sync implements the same method as documented on platform.File
func (r *lazyDir) Sync() syscall.Errno {
if f, ok := r.file(); !ok {
return syscall.EBADF
} else {
return f.Sync()
}
}
// File implements the same method as documented on platform.File
func (r *lazyDir) File() fs.File {
if f, ok := r.file(); !ok {

View File

@@ -43,12 +43,12 @@ type FS interface {
// human-readable name. e.g. "virtual"
String() string
// OpenFile is similar to os.OpenFile, except the path is relative to this
// file system, and syscall.Errno is returned instead of os.PathError.
// OpenFile opens a file. It should be closed via Close on platform.File.
//
// # Errors
//
// A zero syscall.Errno is success. The below are expected otherwise:
// - syscall.ENOSYS the implementation does not support this function.
// - syscall.EINVAL: `path` or `flag` is invalid.
// - syscall.ENOENT: `path` doesn't exist and `flag` doesn't contain
// os.O_CREATE.
@@ -67,25 +67,29 @@ type FS interface {
//
// # Notes
//
// - This is like `open` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html
// - flag are the same as OpenFile, for example, os.O_CREATE.
// - This is like os.OpenFile, except the path is relative to this file
// system, and syscall.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.
// - This is like `open` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html
OpenFile(path string, flag int, perm fs.FileMode) (platform.File, syscall.Errno)
// ^^ TODO: Consider syscall.Open, though this implies defining and
// coercing flags and perms similar to what is done in os.OpenFile.
// Lstat is similar to syscall.Lstat, except the path is relative to this
// file system.
// Lstat gets file status without following symbolic links.
//
// # Errors
//
// A zero syscall.Errno is success. The below are expected otherwise:
// - syscall.ENOSYS the implementation does not support this function.
// - syscall.ENOENT: `path` doesn't exist.
//
// # Notes
//
// - This is like syscall.Lstat, except the `path` is relative to this
// file system.
// - This is like `lstat` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/lstat.html
// - An fs.FileInfo backed implementation sets atim, mtim and ctim to the
@@ -94,16 +98,18 @@ type FS interface {
// not the file it refers to.
Lstat(path string) (platform.Stat_t, syscall.Errno)
// Stat is similar to syscall.Stat, except the path is relative to this
// file system.
// Stat gets file status.
//
// # Errors
//
// A zero syscall.Errno is success. The below are expected otherwise:
// - syscall.ENOSYS the implementation does not support this function.
// - syscall.ENOENT: `path` doesn't exist.
//
// # Notes
//
// - This is like syscall.Stat, except the `path` is relative to this
// file system.
// - This is like `stat` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/stat.html
// - An fs.FileInfo backed implementation sets atim, mtim and ctim to the
@@ -112,18 +118,20 @@ type FS interface {
// it refers to.
Stat(path string) (platform.Stat_t, syscall.Errno)
// Mkdir is similar to os.Mkdir, except the path is relative to this file
// system, and syscall.Errno is returned instead of os.PathError.
// Mkdir makes a directory.
//
// # Errors
//
// A zero syscall.Errno is success. The below are expected otherwise:
// - syscall.ENOSYS the implementation does not support this function.
// - syscall.EINVAL: `path` is invalid.
// - syscall.EEXIST: `path` exists and is a directory.
// - syscall.ENOTDIR: `path` exists and is a file.
//
// # Notes
//
// - This is like syscall.Mkdir, except the `path` is relative to this
// file system.
// - This is like `mkdir` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkdir.html
// - Implications of permissions are described in Chmod notes.
@@ -131,17 +139,19 @@ type FS interface {
// ^^ TODO: Consider syscall.Mkdir, though this implies defining and
// coercing flags and perms similar to what is done in os.Mkdir.
// Chmod is similar to os.Chmod, except the path is relative to this file
// system, and syscall.Errno are returned instead of a os.PathError.
// Chmod changes the mode of the file.
//
// # Errors
//
// A zero syscall.Errno is success. The below are expected otherwise:
// - syscall.ENOSYS the implementation does not support this function.
// - syscall.EINVAL: `path` is invalid.
// - syscall.ENOENT: `path` does not exist.
//
// # Notes
//
// - This is like syscall.Chmod, except the `path` is relative to this
// file system.
// - This is like `chmod` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/chmod.html
// - Windows ignores the execute bit, and any permissions come back as
@@ -149,46 +159,48 @@ type FS interface {
// 0700 0666. Also, permissions on directories aren't supported at all.
Chmod(path string, perm fs.FileMode) syscall.Errno
// Chown is like os.Chown except the path is relative to this file
// system, and syscall.Errno are returned instead of an os.PathError.
// A zero syscall.Errno is success.
// Chown changes the owner and group of a file.
//
// # Errors
//
// A zero syscall.Errno is success. The below are expected otherwise:
// - syscall.ENOSYS the implementation does not support this function.
// - syscall.EINVAL: `path` is invalid.
// - syscall.ENOENT: `path` does not exist.
//
// # Notes
//
// - This is like syscall.Chown, except the `path` is relative to this
// file system.
// - This is like `chown` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/chown.html
// - This always returns syscall.ENOSYS on windows.
Chown(path string, uid, gid int) syscall.Errno
// Lchown is like os.Lchown except the path is relative to this file
// system, and syscall.Errno are returned instead of an os.PathError. A
// zero syscall.Errno is success.
// Lchown changes the owner and group of a symbolic link.
//
// # Errors
//
// A zero syscall.Errno is success. The below are expected otherwise:
// - syscall.ENOSYS the implementation does not support this function.
// - syscall.EINVAL: `path` is invalid.
// - syscall.ENOENT: `path` does not exist.
//
// # Notes
//
// - This is like syscall.Lchown, except the `path` is relative to this
// file system.
// - This is like `lchown` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/lchown.html
// - Windows will always return syscall.ENOSYS
Lchown(path string, uid, gid int) syscall.Errno
// Rename is similar to syscall.Rename, except the path is relative to this
// file system.
// Rename renames file or directory.
//
// # Errors
//
// A zero syscall.Errno is success. The below are expected otherwise:
// - syscall.ENOSYS the implementation does not support this function.
// - syscall.EINVAL: `from` or `to` is invalid.
// - syscall.ENOENT: `from` or `to` don't exist.
// - syscall.ENOTDIR: `from` is a directory and `to` exists as a file.
@@ -198,17 +210,19 @@ type FS interface {
//
// # Notes
//
// - This is like syscall.Rename, except the paths are relative to this
// file system.
// - This is like `rename` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/rename.html
// - Windows doesn't let you overwrite an existing directory.
Rename(from, to string) syscall.Errno
// Rmdir is similar to syscall.Rmdir, except the path is relative to this
// file system.
// Rmdir removes a directory.
//
// # Errors
//
// A zero syscall.Errno is success. The below are expected otherwise:
// - syscall.ENOSYS the implementation does not support this function.
// - syscall.EINVAL: `path` is invalid.
// - syscall.ENOENT: `path` doesn't exist.
// - syscall.ENOTDIR: `path` exists, but isn't a directory.
@@ -216,23 +230,27 @@ type FS interface {
//
// # Notes
//
// - This is like syscall.Rmdir, except the `path` is relative to this
// 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 syscall.ENOTDIR to syscall.ENOENT.
Rmdir(path string) syscall.Errno
// Unlink is similar to syscall.Unlink, except the path is relative to this
// file system.
// Unlink removes a directory entry.
//
// # Errors
//
// A zero syscall.Errno is success. The below are expected otherwise:
// - syscall.ENOSYS the implementation does not support this function.
// - syscall.EINVAL: `path` is invalid.
// - syscall.ENOENT: `path` doesn't exist.
// - syscall.EISDIR: `path` exists, but is a directory.
//
// # Notes
//
// - This is like syscall.Unlink, except the `path` is relative to this
// file system.
// - This is like `unlink` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlink.html
// - On Windows, syscall.Unlink doesn't delete symlink to directory unlike other platforms. Implementations might
@@ -240,35 +258,39 @@ type FS interface {
// See https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-removedirectorya
Unlink(path string) syscall.Errno
// Link is similar to syscall.Link, except the path is relative to this
// file system. This creates "hard" link from oldPath to newPath, in
// contrast to soft link as in Symlink.
// Link creates a "hard" link from oldPath to newPath, in contrast to a
// soft link (via Symlink).
//
// # Errors
//
// A zero syscall.Errno is success. The below are expected otherwise:
// - syscall.ENOSYS the implementation does not support this function.
// - syscall.EPERM: `oldPath` is invalid.
// - syscall.ENOENT: `oldPath` doesn't exist.
// - syscall.EISDIR: `newPath` exists, but is a directory.
//
// # Notes
//
// - This is like syscall.Link, except the `oldPath` is relative to this
// file system.
// - This is like `link` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/link.html
Link(oldPath, newPath string) syscall.Errno
// Symlink is similar to syscall.Symlink, except the `oldPath` is relative
// to this file system. This creates "soft" link from oldPath to newPath,
// in contrast to hard link as in Link.
// Symlink creates a "soft" link from oldPath to newPath, in contrast to a
// hard link (via Link).
//
// # Errors
//
// A zero syscall.Errno is success. The below are expected otherwise:
// - syscall.ENOSYS the implementation does not support this function.
// - syscall.EPERM: `oldPath` or `newPath` is invalid.
// - syscall.EEXIST: `newPath` exists.
//
// # Notes
//
// - This is like syscall.Symlink, except the `oldPath` is relative to
// this file system.
// - This is like `symlink` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/symlink.html
// - Only `newPath` is relative to this file system and `oldPath` is kept
@@ -281,16 +303,18 @@ type FS interface {
// See https://learn.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/create-symbolic-links
Symlink(oldPath, linkName string) syscall.Errno
// Readlink is similar to syscall.Readlink, except the path is relative to
// this file system.
// Readlink reads the contents of a symbolic link.
//
// # Errors
//
// A zero syscall.Errno is success. The below are expected otherwise:
// - syscall.ENOSYS the implementation does not support this function.
// - syscall.EINVAL: `path` is invalid.
//
// # Notes
//
// - This is like syscall.Readlink, except the path is relative to this
// filesystem.
// - This is like `readlink` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/readlink.html
// - On Windows, the path separator is different from other platforms,
@@ -298,18 +322,20 @@ type FS interface {
// separator.
Readlink(path string) (string, syscall.Errno)
// Truncate is similar to syscall.Truncate, except the path is relative to
// this file system.
// Truncate truncates a file to a specified length.
//
// # Errors
//
// A zero syscall.Errno is success. The below are expected otherwise:
// - syscall.ENOSYS the implementation does not support this function.
// - syscall.EINVAL: `path` is invalid or size is negative.
// - syscall.ENOENT: `path` doesn't exist
// - syscall.EACCES: `path` doesn't have write access.
//
// # Notes
//
// - This is like syscall.Truncate, except the path is relative to this
// filesystem.
// - This is like `truncate` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/truncate.html
Truncate(path string, size int64) syscall.Errno
@@ -330,17 +356,19 @@ type FS interface {
// # Errors
//
// A zero syscall.Errno is success. The below are expected otherwise:
// - syscall.ENOSYS the implementation does not support this function.
// - syscall.EINVAL: `path` is invalid.
// - syscall.EEXIST: `path` exists and is a directory.
// - syscall.ENOTDIR: `path` exists and is a file.
//
// # Notes
//
// - This is like syscall.Utimes, except the path is relative to this
// filesystem. It also doesn't have flags to control expansion of
// symbolic links. Neither does this support the support special values
// UTIME_NOW or UTIME_NOW.
// - This is like `utimensat` with `AT_FDCWD` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html
// - This is similar to syscall.Utimens, except that doesn't have flags to
// control expansion of symbolic links. It also doesn't support special
// values UTIME_NOW or UTIME_NOW.
Utimens(path string, times *[2]syscall.Timespec, symlinkFollow bool) syscall.Errno
}

View File

@@ -691,7 +691,7 @@ func TestWriterAtOffset_Unsupported(t *testing.T) {
// to below. Effectively, this only tests that things don't error.
func Test_FileSync(t *testing.T) {
testSync(t, func(f fs.File) syscall.Errno {
return platform.UnwrapOSError(f.(interface{ Sync() error }).Sync())
return (&platform.DefaultFile{F: f}).Sync()
})
}