Adds Datasync to platform.File (#1427)
Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user