Adds Datasync to platform.File (#1427)

Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
Crypt Keeper
2023-05-02 14:12:47 +08:00
committed by GitHub
parent b79c45b91c
commit c5871c772c
8 changed files with 89 additions and 109 deletions

View File

@@ -152,7 +152,7 @@ func fdDatasyncFn(_ context.Context, mod api.Module, params []uint64) syscall.Er
if f, ok := fsc.LookupFile(fd); !ok {
return syscall.EBADF
} else {
return sysfs.FileDatasync(f.File.File())
return f.File.Datasync()
}
}

View File

@@ -91,6 +91,21 @@ type File interface {
// unimplemented. This prevents fake filesystems from erring.
Sync() syscall.Errno
// Datasync synchronizes the data of a file.
//
// # Errors
//
// A zero syscall.Errno is success. The below are expected otherwise:
// - syscall.EBADF if the file or directory was closed.
//
// # Notes
//
// - This is like syscall.Fdatasync and `fdatasync` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/fdatasync.html
// - This returns with no error instead of syscall.ENOSYS when
// unimplemented. This prevents fake filesystems from erring.
Datasync() syscall.Errno
// Close closes the underlying file.
//
// A zero syscall.Errno is success. The below are expected otherwise:
@@ -130,6 +145,11 @@ func (UnimplementedFile) Sync() syscall.Errno {
return 0 // not syscall.ENOSYS
}
// Datasync implements File.Datasync
func (UnimplementedFile) Datasync() syscall.Errno {
return 0 // not syscall.ENOSYS
}
type DefaultFile struct {
F fs.File
}
@@ -167,6 +187,11 @@ func (f *DefaultFile) Sync() syscall.Errno {
return 0 // don't error
}
// Datasync implements File.Datasync
func (f *DefaultFile) Datasync() syscall.Errno {
return fdatasync(f.F)
}
// Close implements File.Close
func (f *DefaultFile) Close() syscall.Errno {
return UnwrapOSError(f.F.Close())

View File

@@ -1,7 +1,9 @@
package platform
import (
"bytes"
"embed"
"io"
"io/fs"
"os"
"path"
@@ -29,12 +31,20 @@ func (NoopFile) File() fs.File { panic("noop") }
//go:embed file_test.go
var embedFS embed.FS
func TestFileSync(t *testing.T) {
func TestFileSync_NoError(t *testing.T) {
testSync_NoError(t, File.Sync)
}
func TestFileDatasync_NoError(t *testing.T) {
testSync_NoError(t, File.Datasync)
}
func testSync_NoError(t *testing.T, sync func(File) syscall.Errno) {
ro, err := embedFS.Open("file_test.go")
require.NoError(t, err)
defer ro.Close()
rw, err := os.Create(path.Join(t.TempDir(), "sync"))
rw, err := os.Create(path.Join(t.TempDir(), "datasync"))
require.NoError(t, err)
defer rw.Close()
@@ -60,7 +70,48 @@ func TestFileSync(t *testing.T) {
tc := tt
t.Run(tc.name, func(b *testing.T) {
require.Zero(t, tc.f.Sync())
require.Zero(t, sync(tc.f))
})
}
}
func TestFileSync(t *testing.T) {
testSync(t, File.Sync)
}
func TestFileDatasync(t *testing.T) {
testSync(t, File.Datasync)
}
// testSync doesn't guarantee sync works because the operating system may
// sync anyway. There is no test in Go for syscall.Fdatasync, but closest is
// similar to below. Effectively, this only tests that things don't error.
func testSync(t *testing.T, sync func(File) syscall.Errno) {
f, errno := os.CreateTemp("", t.Name())
require.NoError(t, errno)
defer f.Close()
expected := "hello world!"
// Write the expected data
_, errno = f.Write([]byte(expected))
require.NoError(t, errno)
// Sync the data.
if errno = sync(&DefaultFile{F: f}); errno == syscall.ENOSYS {
return // don't continue if it isn't supported.
}
require.Zero(t, errno)
// Rewind while the file is still open.
_, err := f.Seek(0, io.SeekStart)
require.NoError(t, err)
// Read data from the file
var buf bytes.Buffer
_, errno = io.Copy(&buf, f)
require.NoError(t, errno)
// It may be the case that sync worked.
require.Equal(t, expected, buf.String())
}

View File

@@ -1,14 +0,0 @@
package platform
import (
"io/fs"
"syscall"
)
// Fdatasync is like syscall.Fdatasync except that's only defined in linux.
//
// Note: This returns with no error instead of syscall.ENOSYS when
// unimplemented. This prevents fake filesystems from erring.
func Fdatasync(f fs.File) syscall.Errno {
return fdatasync(f)
}

View File

@@ -1,44 +0,0 @@
package platform
import (
"bytes"
"io"
"os"
"syscall"
"testing"
"github.com/tetratelabs/wazero/internal/testing/require"
)
// Test_Fdatasync doesn't guarantee sync works because the operating system may
// sync anyway. There is no test in Go for syscall.Fdatasync, but closest is
// similar to below. Effectively, this only tests that things don't error.
func Test_Fdatasync(t *testing.T) {
f, errno := os.CreateTemp("", t.Name())
require.NoError(t, errno)
defer f.Close()
expected := "hello world!"
// Write the expected data
_, errno = f.Write([]byte(expected))
require.NoError(t, errno)
// Sync the data.
if errno = Fdatasync(f); errno == syscall.ENOSYS {
return // don't continue if it isn't supported.
}
require.Zero(t, errno)
// Rewind while the file is still open.
_, err := f.Seek(0, io.SeekStart)
require.NoError(t, err)
// Read data from the file
var buf bytes.Buffer
_, errno = io.Copy(&buf, f)
require.NoError(t, errno)
// It may be the case that sync worked.
require.Equal(t, expected, buf.String())
}

View File

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

View File

@@ -389,11 +389,6 @@ func ReaderAtOffset(f fs.File, offset int64) io.Reader {
}
}
// FileDatasync is like syscall.Fdatasync except that's only defined in linux.
func FileDatasync(f fs.File) (err syscall.Errno) {
return platform.Fdatasync(f)
}
type enosysReader struct{}
// enosysReader implements io.Reader

View File

@@ -1,7 +1,6 @@
package sysfs
import (
"bytes"
"embed"
_ "embed"
"io"
@@ -686,47 +685,6 @@ func TestWriterAtOffset_Unsupported(t *testing.T) {
require.Equal(t, syscall.ENOSYS, err)
}
// Test_FileSync doesn't guarantee sync works because the operating system may
// sync anyway. There is no test in Go for os.File Sync, but closest is similar
// 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.DefaultFile{F: f}).Sync()
})
}
// Test_FileDatasync has same issues as Test_Sync.
func Test_FileDatasync(t *testing.T) {
testSync(t, FileDatasync)
}
func testSync(t *testing.T, sync func(fs.File) syscall.Errno) {
f, err := os.CreateTemp("", t.Name())
require.NoError(t, err)
defer f.Close()
expected := "hello world!"
// Write the expected data
_, err = f.Write([]byte(expected))
require.NoError(t, err)
// Sync the data.
require.Zero(t, sync(f))
// Rewind while the file is still open.
_, err = f.Seek(0, io.SeekStart)
require.NoError(t, err)
// Read data from the file
var buf bytes.Buffer
_, err = io.Copy(&buf, f)
require.NoError(t, err)
// It may be the case that sync worked.
require.Equal(t, expected, buf.String())
}
func requireIno(t *testing.T, dirents []*platform.Dirent, expectIno bool) {
for _, e := range dirents {
if expectIno {