Repository: tetratelabs/wazero
Pull request #1422 (2023-05-01 08:47:54)

Adds platform.File.Chown
This migrates the function `platform.ChownFile` to the work-in-progress `platform.File` type without changing its implementation.

See #1417


Patch:
diff --git a/internal/gojs/fs.go b/internal/gojs/fs.go
index 6576c1fb..d414a85d 100644
--- a/internal/gojs/fs.go
+++ b/internal/gojs/fs.go
@@ -543,7 +543,7 @@ func (jsfsFchown) invoke(ctx context.Context, mod api.Module, args ...interface{
 	if f, ok := fsc.LookupFile(fd); !ok {
 		errno = syscall.EBADF
 	} else {
-		errno = platform.ChownFile(f.File.File(), int(uid), int(gid))
+		errno = f.File.Chown(int(uid), int(gid))
 	}
 
 	return jsfsInvoke(ctx, mod, callback, errno)
diff --git a/internal/platform/chown.go b/internal/platform/chown.go
index d8c273d5..dfbf6bdb 100644
--- a/internal/platform/chown.go
+++ b/internal/platform/chown.go
@@ -1,7 +1,6 @@
 package platform
 
 import (
-	"io/fs"
 	"os"
 	"syscall"
 )
@@ -27,15 +26,3 @@ func Lchown(path string, uid, gid int) syscall.Errno {
 	err := os.Lchown(path, uid, gid)
 	return UnwrapOSError(err)
 }
-
-// ChownFile is like syscall.Fchown, but for nanosecond precision and
-// fs.File instead of a file descriptor. This returns syscall.EBADF if the file
-// or directory was closed. See https://linux.die.net/man/3/fchown
-//
-// Note: This always returns syscall.ENOSYS on windows.
-func ChownFile(f fs.File, uid, gid int) syscall.Errno {
-	if f, ok := f.(fdFile); ok {
-		return fchown(f.Fd(), uid, gid)
-	}
-	return syscall.ENOSYS
-}
diff --git a/internal/platform/file.go b/internal/platform/file.go
index 0f994d4a..2796b72d 100644
--- a/internal/platform/file.go
+++ b/internal/platform/file.go
@@ -36,11 +36,27 @@ type File interface {
 	//
 	// # 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
 	//     same value.
 	//   - Windows allows you to stat a closed directory.
 	Stat() (Stat_t, syscall.Errno)
 
+	// Chown is like syscall.Fchown, but for nanosecond precision.
+	//
+	// # Errors
+	//
+	// The following errors are expected:
+	//   - syscall.EBADF if the file or directory was closed.
+	//
+	// # 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
+
 	// Close closes the underlying file.
 	Close() syscall.Errno
 
@@ -57,6 +73,11 @@ func (UnimplementedFile) Stat() (Stat_t, syscall.Errno) {
 	return Stat_t{}, syscall.ENOSYS
 }
 
+// Chown implements File.Chown
+func (UnimplementedFile) Chown() syscall.Errno {
+	return syscall.ENOSYS
+}
+
 type DefaultFile struct {
 	F fs.File
 }
@@ -70,6 +91,14 @@ func (f *DefaultFile) Stat() (Stat_t, syscall.Errno) {
 	return st, errno
 }
 
+// Chown implements File.Chown
+func (f *DefaultFile) Chown(uid, gid int) syscall.Errno {
+	if f, ok := f.F.(fdFile); ok {
+		return fchown(f.Fd(), uid, gid)
+	}
+	return syscall.ENOSYS
+}
+
 // Close implements File.Close
 func (f *DefaultFile) Close() syscall.Errno {
 	return UnwrapOSError(f.F.Close())
diff --git a/internal/sys/fs.go b/internal/sys/fs.go
index 91229da7..e1f9a08a 100644
--- a/internal/sys/fs.go
+++ b/internal/sys/fs.go
@@ -162,35 +162,52 @@ type lazyDir struct {
 
 // Stat implements the same method as documented on platform.File
 func (r *lazyDir) Stat() (platform.Stat_t, syscall.Errno) {
-	if f, err := r.file(); err != 0 {
-		return platform.Stat_t{}, err
+	if f, ok := r.file(); !ok {
+		return platform.Stat_t{}, syscall.EBADF
 	} else {
 		return f.Stat()
 	}
 }
 
+// Chown implements the same method as documented on platform.File
+func (r *lazyDir) Chown(uid, gid int) syscall.Errno {
+	if f, ok := r.file(); !ok {
+		return syscall.EBADF
+	} else {
+		return f.Chown(uid, gid)
+	}
+}
+
 // File implements the same method as documented on platform.File
 func (r *lazyDir) File() fs.File {
-	if f, err := r.file(); err != 0 {
-		panic(err) // Bad, but temporary
+	if f, ok := r.file(); !ok {
+		panic("path doesn't exist")
 	} else {
 		return f.File()
 	}
 }
 
-func (r *lazyDir) file() (f platform.File, errno syscall.Errno) {
-	if f = r.f; r.f != nil {
-		return
+// file returns the underlying file or false if it doesn't exist.
+func (r *lazyDir) file() (platform.File, bool) {
+	if f := r.f; r.f != nil {
+		return f, true
 	}
+	var errno syscall.Errno
 	r.f, errno = r.fs.OpenFile(".", os.O_RDONLY, 0)
-	f = r.f
-	return
+	switch errno {
+	case 0:
+		return r.f, true
+	case syscall.ENOENT:
+		return nil, false
+	default:
+		panic(errno) // unexpected
+	}
 }
 
 // Read implements fs.File
 func (r *lazyDir) Read(p []byte) (n int, err error) {
-	if f, errno := r.file(); errno != 0 {
-		return 0, errno
+	if f, ok := r.file(); !ok {
+		return 0, syscall.EBADF
 	} else {
 		return f.File().Read(p)
 	}
@@ -257,7 +274,7 @@ func (f *FileEntry) CachedStat() (ino uint64, fileType fs.FileMode, errno syscal
 func (f *FileEntry) Stat() (st platform.Stat_t, errno syscall.Errno) {
 	if ld, ok := f.File.(*lazyDir); ok {
 		var sf platform.File
-		if sf, errno = ld.file(); errno == 0 {
+		if sf, ok = ld.file(); ok {
 			st, errno = sf.Stat()
 		}
 	} else {


Test patch:
diff --git a/internal/platform/chown_unix_test.go b/internal/platform/chown_unix_test.go
index 83654638..89e8559d 100644
--- a/internal/platform/chown_unix_test.go
+++ b/internal/platform/chown_unix_test.go
@@ -60,7 +60,7 @@ func TestChown(t *testing.T) {
 	})
 }
 
-func TestChownFile(t *testing.T) {
+func TestDefaultFileChown(t *testing.T) {
 	tmpDir := t.TempDir()
 
 	dir := path.Join(tmpDir, "dir")
@@ -81,12 +81,12 @@ func TestChownFile(t *testing.T) {
 	require.NoError(t, err)
 
 	t.Run("-1 parameters means leave alone", func(t *testing.T) {
-		require.Zero(t, ChownFile(dirF.File(), -1, -1))
+		require.Zero(t, dirF.Chown(-1, -1))
 		checkUidGid(t, dir, dirSys.Uid, dirSys.Gid)
 	})
 
 	t.Run("change gid, but not uid", func(t *testing.T) {
-		require.Zero(t, ChownFile(dirF.File(), -1, gid))
+		require.Zero(t, dirF.Chown(-1, gid))
 		checkUidGid(t, dir, dirSys.Uid, uint32(gid))
 	})
 
@@ -94,8 +94,8 @@ func TestChownFile(t *testing.T) {
 	for _, g := range groups {
 		g := g
 		t.Run(fmt.Sprintf("change to gid %d", g), func(t *testing.T) {
-			// Test using our ChownFile
-			require.Zero(t, ChownFile(dirF.File(), -1, g))
+			// Test using our Chown
+			require.Zero(t, dirF.Chown(-1, g))
 			checkUidGid(t, dir, dirSys.Uid, uint32(g))
 
 			// Revert back with os.File.Chown
@@ -106,7 +106,7 @@ func TestChownFile(t *testing.T) {
 
 	t.Run("closed", func(t *testing.T) {
 		require.Zero(t, dirF.Close())
-		require.EqualErrno(t, syscall.EBADF, ChownFile(dirF.File(), -1, gid))
+		require.EqualErrno(t, syscall.EBADF, dirF.Chown(-1, gid))
 	})
 }
 
diff --git a/internal/sysfs/dirfs_unix_test.go b/internal/sysfs/dirfs_unix_test.go
index 85893216..1b5f08e3 100644
--- a/internal/sysfs/dirfs_unix_test.go
+++ b/internal/sysfs/dirfs_unix_test.go
@@ -9,7 +9,6 @@ import (
 	"syscall"
 	"testing"
 
-	"github.com/tetratelabs/wazero/internal/platform"
 	"github.com/tetratelabs/wazero/internal/testing/require"
 )
 
@@ -50,8 +49,8 @@ func TestDirFS_Chown(t *testing.T) {
 			require.Zero(t, testFS.Chown("dir", -1, g))
 			checkUidGid(t, path.Join(tmpDir, "dir"), dirSys.Uid, uint32(g))
 
-			// Revert back with platform.ChownFile
-			require.Zero(t, platform.ChownFile(dirF.File(), -1, gid))
+			// Revert back with File.Chown
+			require.Zero(t, dirF.Chown(-1, gid))
 			checkUidGid(t, path.Join(tmpDir, "dir"), dirSys.Uid, uint32(gid))
 		})
 	}