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/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/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 { 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)) }) }