Repository: tetratelabs/wazero
Pull request #1427 (2023-05-02 06:00:45)

Adds Datasync to platform.File
See #1417


Patch:
diff --git a/imports/wasi_snapshot_preview1/fs.go b/imports/wasi_snapshot_preview1/fs.go
index aff60fa5..127f0049 100644
--- a/imports/wasi_snapshot_preview1/fs.go
+++ b/imports/wasi_snapshot_preview1/fs.go
@@ -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()
 	}
 }
 
diff --git a/internal/platform/file.go b/internal/platform/file.go
index e5e45130..0676c600 100644
--- a/internal/platform/file.go
+++ b/internal/platform/file.go
@@ -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())
diff --git a/internal/platform/sync.go b/internal/platform/sync.go
deleted file mode 100644
index a93e24ba..00000000
--- a/internal/platform/sync.go
+++ /dev/null
@@ -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)
-}
diff --git a/internal/sys/fs.go b/internal/sys/fs.go
index 735dacfd..3e8dda9c 100644
--- a/internal/sys/fs.go
+++ b/internal/sys/fs.go
@@ -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 {
diff --git a/internal/sysfs/sysfs.go b/internal/sysfs/sysfs.go
index 178ccf25..78cfdee6 100644
--- a/internal/sysfs/sysfs.go
+++ b/internal/sysfs/sysfs.go
@@ -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


Test patch:
diff --git a/internal/platform/file_test.go b/internal/platform/file_test.go
index c65a47d0..27cf99e8 100644
--- a/internal/platform/file_test.go
+++ b/internal/platform/file_test.go
@@ -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())
+}
diff --git a/internal/platform/sync_test.go b/internal/platform/sync_test.go
deleted file mode 100644
index ac2afb00..00000000
--- a/internal/platform/sync_test.go
+++ /dev/null
@@ -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())
-}
diff --git a/internal/sysfs/sysfs_test.go b/internal/sysfs/sysfs_test.go
index 23338025..246ea40a 100644
--- a/internal/sysfs/sysfs_test.go
+++ b/internal/sysfs/sysfs_test.go
@@ -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 {