Adds IsDir and Seek to platform.File (#1441)

Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
Crypt Keeper
2023-05-09 07:47:25 +08:00
committed by GitHub
parent 99d45623c0
commit 29c7c7667b
18 changed files with 576 additions and 273 deletions

View File

@@ -925,11 +925,14 @@ func fdReaddirFn(_ context.Context, mod api.Module, params []uint64) syscall.Err
// dotDirents returns "." and "..", where "." because wasi-testsuite does inode // dotDirents returns "." and "..", where "." because wasi-testsuite does inode
// validation. // validation.
func dotDirents(f *sys.FileEntry) ([]*platform.Dirent, syscall.Errno) { func dotDirents(f *sys.FileEntry) ([]*platform.Dirent, syscall.Errno) {
dotIno, ft, errno := f.CachedStat() if isDir, errno := f.File.IsDir(); errno != 0 {
return nil, errno
} else if !isDir {
return nil, syscall.ENOTDIR
}
dotIno, errno := f.Inode()
if errno != 0 { if errno != 0 {
return nil, errno return nil, errno
} else if ft.Type() != fs.ModeDir {
return nil, syscall.ENOTDIR
} }
dotDotIno := uint64(0) dotDotIno := uint64(0)
if !f.IsPreopen && f.Name != "." { if !f.IsPreopen && f.Name != "." {
@@ -1093,9 +1096,9 @@ func writeDirent(buf []byte, dNext uint64, ino uint64, dNamlen uint32, dType fs.
func openedDir(fsc *sys.FSContext, fd int32) (fs.File, *sys.ReadDir, syscall.Errno) { func openedDir(fsc *sys.FSContext, fd int32) (fs.File, *sys.ReadDir, syscall.Errno) {
if f, ok := fsc.LookupFile(fd); !ok { if f, ok := fsc.LookupFile(fd); !ok {
return nil, nil, syscall.EBADF return nil, nil, syscall.EBADF
} else if _, ft, errno := f.CachedStat(); errno != 0 { } else if isDir, errno := f.File.IsDir(); errno != 0 {
return nil, nil, errno return nil, nil, errno
} else if ft.Type() != fs.ModeDir { } else if !isDir {
// fd_readdir docs don't indicate whether to return syscall.ENOTDIR or // fd_readdir docs don't indicate whether to return syscall.ENOTDIR or
// syscall.EBADF. It has been noticed that rust will crash on syscall.ENOTDIR, // syscall.EBADF. It has been noticed that rust will crash on syscall.ENOTDIR,
// and POSIX C ref seems to not return this, so we don't either. // and POSIX C ref seems to not return this, so we don't either.
@@ -1179,29 +1182,11 @@ func fdSeekFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno
whence := uint32(params[2]) whence := uint32(params[2])
resultNewoffset := uint32(params[3]) resultNewoffset := uint32(params[3])
var seeker io.Seeker
// Check to see if the file descriptor is available
if f, ok := fsc.LookupFile(fd); !ok { if f, ok := fsc.LookupFile(fd); !ok {
return syscall.EBADF return syscall.EBADF
// fs.FS doesn't declare io.Seeker, but implementations such as os.File implement it. } else if newOffset, errno := f.File.Seek(int64(offset), int(whence)); errno != 0 {
} else if _, ft, errno := f.CachedStat(); errno != 0 {
return errno return errno
} else if ft.Type() == fs.ModeDir { } else if !mod.Memory().WriteUint64Le(resultNewoffset, uint64(newOffset)) {
return syscall.EBADF
} else if seeker, ok = f.File.File().(io.Seeker); !ok {
return syscall.EBADF
}
if whence > io.SeekEnd /* exceeds the largest valid whence */ {
return syscall.EINVAL
}
newOffset, err := seeker.Seek(int64(offset), int(whence))
if err != nil {
return platform.UnwrapOSError(err)
}
if !mod.Memory().WriteUint64Le(resultNewoffset, uint64(newOffset)) {
return syscall.EFAULT return syscall.EFAULT
} }
return 0 return 0
@@ -1677,10 +1662,10 @@ func pathOpenFn(_ context.Context, mod api.Module, params []uint64) syscall.Errn
if isDir { if isDir {
if f, ok := fsc.LookupFile(newFD); !ok { if f, ok := fsc.LookupFile(newFD); !ok {
return syscall.EBADF // unexpected return syscall.EBADF // unexpected
} else if _, ft, errno := f.CachedStat(); errno != 0 { } else if isDir, errno := f.File.IsDir(); errno != 0 {
_ = fsc.CloseFile(newFD) _ = fsc.CloseFile(newFD)
return errno return errno
} else if ft.Type() != fs.ModeDir { } else if !isDir {
_ = fsc.CloseFile(newFD) _ = fsc.CloseFile(newFD)
return syscall.ENOTDIR return syscall.ENOTDIR
} }
@@ -1737,9 +1722,9 @@ func atPath(fsc *sys.FSContext, mem api.Memory, fd int32, p, pathLen uint32) (sy
if f, ok := fsc.LookupFile(fd); !ok { if f, ok := fsc.LookupFile(fd); !ok {
return nil, "", syscall.EBADF // closed or invalid return nil, "", syscall.EBADF // closed or invalid
} else if _, ft, errno := f.CachedStat(); errno != 0 { } else if isDir, errno := f.File.IsDir(); errno != 0 {
return nil, "", errno return nil, "", errno
} else if ft.Type() != fs.ModeDir { } else if !isDir {
return nil, "", syscall.ENOTDIR return nil, "", syscall.ENOTDIR
} else if f.IsPreopen { // don't append the pre-open name } else if f.IsPreopen { // don't append the pre-open name
return f.FS, pathName, 0 return f.FS, pathName, 0
@@ -1980,9 +1965,9 @@ func pathSymlinkFn(_ context.Context, mod api.Module, params []uint64) syscall.E
dir, ok := fsc.LookupFile(fd) dir, ok := fsc.LookupFile(fd)
if !ok { if !ok {
return syscall.EBADF // closed return syscall.EBADF // closed
} else if _, ft, errno := dir.CachedStat(); errno != 0 { } else if isDir, errno := dir.File.IsDir(); errno != 0 {
return errno return errno
} else if ft.Type() != fs.ModeDir { } else if !isDir {
return syscall.ENOTDIR return syscall.ENOTDIR
} }

View File

@@ -2690,11 +2690,10 @@ func Test_fdSeek(t *testing.T) {
fsc := mod.(*wasm.ModuleInstance).Sys.FS() fsc := mod.(*wasm.ModuleInstance).Sys.FS()
f, ok := fsc.LookupFile(fd) f, ok := fsc.LookupFile(fd)
require.True(t, ok) require.True(t, ok)
seeker := f.File.File().(io.Seeker)
// set the initial offset of the file to 1 // set the initial offset of the file to 1
offset, err := seeker.Seek(1, io.SeekStart) offset, errno := f.File.Seek(1, io.SeekStart)
require.NoError(t, err) require.EqualErrno(t, 0, errno)
require.Equal(t, int64(1), offset) require.Equal(t, int64(1), offset)
requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdSeekName, uint64(fd), uint64(tc.offset), uint64(tc.whence), uint64(resultNewoffset)) requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdSeekName, uint64(fd), uint64(tc.offset), uint64(tc.whence), uint64(resultNewoffset))
@@ -2704,8 +2703,8 @@ func Test_fdSeek(t *testing.T) {
require.True(t, ok) require.True(t, ok)
require.Equal(t, tc.expectedMemory, actual) require.Equal(t, tc.expectedMemory, actual)
offset, err = seeker.Seek(0, io.SeekCurrent) offset, errno = f.File.Seek(0, io.SeekCurrent)
require.NoError(t, err) require.EqualErrno(t, 0, errno)
require.Equal(t, tc.expectedOffset, offset) // test that the offset of file is actually updated. require.Equal(t, tc.expectedOffset, offset) // test that the offset of file is actually updated.
}) })
} }
@@ -2751,10 +2750,10 @@ func Test_fdSeek_Errors(t *testing.T) {
{ {
name: "dir not file", name: "dir not file",
fd: dirFD, fd: dirFD,
expectedErrno: wasip1.ErrnoBadf, expectedErrno: wasip1.ErrnoIsdir,
expectedLog: ` expectedLog: `
==> wasi_snapshot_preview1.fd_seek(fd=5,offset=0,whence=0) ==> wasi_snapshot_preview1.fd_seek(fd=5,offset=0,whence=0)
<== (newoffset=,errno=EBADF) <== (newoffset=,errno=EISDIR)
`, `,
}, },
{ {
@@ -2848,11 +2847,10 @@ func Test_fdTell(t *testing.T) {
fsc := mod.(*wasm.ModuleInstance).Sys.FS() fsc := mod.(*wasm.ModuleInstance).Sys.FS()
f, ok := fsc.LookupFile(fd) f, ok := fsc.LookupFile(fd)
require.True(t, ok) require.True(t, ok)
seeker := f.File.File().(io.Seeker)
// set the initial offset of the file to 1 // set the initial offset of the file to 1
offset, err := seeker.Seek(1, io.SeekStart) offset, errno := f.File.Seek(1, io.SeekStart)
require.NoError(t, err) require.EqualErrno(t, 0, errno)
require.Equal(t, int64(1), offset) require.Equal(t, int64(1), offset)
requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdTellName, uint64(fd), uint64(resultNewoffset)) requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdTellName, uint64(fd), uint64(resultNewoffset))
@@ -2862,8 +2860,8 @@ func Test_fdTell(t *testing.T) {
require.True(t, ok) require.True(t, ok)
require.Equal(t, expectedMemory, actual) require.Equal(t, expectedMemory, actual)
offset, err = seeker.Seek(0, io.SeekCurrent) offset, errno = f.File.Seek(0, io.SeekCurrent)
require.NoError(t, err) require.EqualErrno(t, 0, errno)
require.Equal(t, expectedOffset, offset) // test that the offset of file is actually updated. require.Equal(t, expectedOffset, offset) // test that the offset of file is actually updated.
} }
@@ -3945,9 +3943,9 @@ func Test_pathOpen(t *testing.T) {
expected: func(t *testing.T, fsc *sys.FSContext) { expected: func(t *testing.T, fsc *sys.FSContext) {
f, ok := fsc.LookupFile(expectedOpenedFd) f, ok := fsc.LookupFile(expectedOpenedFd)
require.True(t, ok) require.True(t, ok)
_, ft, errno := f.CachedStat() isDir, errno := f.File.IsDir()
require.EqualErrno(t, 0, errno) require.EqualErrno(t, 0, errno)
require.Equal(t, fs.ModeDir, ft) require.True(t, isDir)
}, },
expectedLog: ` expectedLog: `
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=dir,oflags=DIRECTORY,fs_rights_base=,fs_rights_inheriting=,fdflags=) ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=dir,oflags=DIRECTORY,fs_rights_base=,fs_rights_inheriting=,fdflags=)
@@ -3962,9 +3960,9 @@ func Test_pathOpen(t *testing.T) {
expected: func(t *testing.T, fsc *sys.FSContext) { expected: func(t *testing.T, fsc *sys.FSContext) {
f, ok := fsc.LookupFile(expectedOpenedFd) f, ok := fsc.LookupFile(expectedOpenedFd)
require.True(t, ok) require.True(t, ok)
_, ft, errno := f.CachedStat() isDir, errno := f.File.IsDir()
require.EqualErrno(t, 0, errno) require.EqualErrno(t, 0, errno)
require.Equal(t, fs.ModeDir, ft) require.True(t, isDir)
}, },
expectedLog: ` expectedLog: `
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=dir,oflags=DIRECTORY,fs_rights_base=,fs_rights_inheriting=,fdflags=) ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=dir,oflags=DIRECTORY,fs_rights_base=,fs_rights_inheriting=,fdflags=)

View File

@@ -67,15 +67,16 @@ func BenchmarkFsFileRead(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
b.StopTimer() b.StopTimer()
// Reset the read position back to the beginning of the file.
if _, err = f.(io.Seeker).Seek(0, io.SeekStart); err != nil {
b.Fatal(err)
}
fs := NewFsFile(name, syscall.O_RDONLY, f) fs := NewFsFile(name, syscall.O_RDONLY, f)
var n int var n int
var errno syscall.Errno var errno syscall.Errno
// Reset the read position back to the beginning of the file.
if _, errno = fs.Seek(0, io.SeekStart); errno != 0 {
b.Fatal(errno)
}
b.StartTimer() b.StartTimer()
if bc.pread { if bc.pread {
n, errno = fs.Pread(buf, 3) n, errno = fs.Pread(buf, 3)

View File

@@ -21,9 +21,8 @@ func TestChown(t *testing.T) {
dirF := openFsFile(t, dir, syscall.O_RDONLY, 0) dirF := openFsFile(t, dir, syscall.O_RDONLY, 0)
defer dirF.Close() defer dirF.Close()
dirStat, err := dirF.File().Stat() dirSt, errno := dirF.Stat()
require.NoError(t, err) require.EqualErrno(t, 0, errno)
dirSys := dirStat.Sys().(*syscall.Stat_t)
// Similar to TestChown in os_unix_test.go, we can't expect to change // Similar to TestChown in os_unix_test.go, we can't expect to change
// owner unless root, and with another user. Instead, test gid. // owner unless root, and with another user. Instead, test gid.
@@ -32,13 +31,13 @@ func TestChown(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
t.Run("-1 parameters means leave alone", func(t *testing.T) { t.Run("-1 parameters means leave alone", func(t *testing.T) {
require.Zero(t, Chown(dir, -1, -1)) require.EqualErrno(t, 0, Chown(dir, -1, -1))
checkUidGid(t, dir, dirSys.Uid, dirSys.Gid) checkUidGid(t, dir, dirSt.Uid, dirSt.Gid)
}) })
t.Run("change gid, but not uid", func(t *testing.T) { t.Run("change gid, but not uid", func(t *testing.T) {
require.Zero(t, Chown(dir, -1, gid)) require.EqualErrno(t, 0, Chown(dir, -1, gid))
checkUidGid(t, dir, dirSys.Uid, uint32(gid)) checkUidGid(t, dir, dirSt.Uid, uint32(gid))
}) })
// Now, try any other groups of the current user. // Now, try any other groups of the current user.
@@ -46,12 +45,12 @@ func TestChown(t *testing.T) {
g := g g := g
t.Run(fmt.Sprintf("change to gid %d", g), func(t *testing.T) { t.Run(fmt.Sprintf("change to gid %d", g), func(t *testing.T) {
// Test using our Chown // Test using our Chown
require.Zero(t, Chown(dir, -1, g)) require.EqualErrno(t, 0, Chown(dir, -1, g))
checkUidGid(t, dir, dirSys.Uid, uint32(g)) checkUidGid(t, dir, dirSt.Uid, uint32(g))
// Revert back with os.File.Chown // Revert back
require.NoError(t, dirF.File().(*os.File).Chown(-1, gid)) require.EqualErrno(t, 0, dirF.Chown(-1, gid))
checkUidGid(t, dir, dirSys.Uid, uint32(gid)) checkUidGid(t, dir, dirSt.Uid, uint32(gid))
}) })
} }
@@ -69,10 +68,8 @@ func TestDefaultFileChown(t *testing.T) {
dirF := openFsFile(t, dir, syscall.O_RDONLY, 0) dirF := openFsFile(t, dir, syscall.O_RDONLY, 0)
defer dirF.Close() defer dirF.Close()
dirStat, err := dirF.File().Stat() dirSt, errno := dirF.Stat()
require.NoError(t, err) require.EqualErrno(t, 0, errno)
dirSys := dirStat.Sys().(*syscall.Stat_t)
// Similar to TestChownFile in os_unix_test.go, we can't expect to change // Similar to TestChownFile in os_unix_test.go, we can't expect to change
// owner unless root, and with another user. Instead, test gid. // owner unless root, and with another user. Instead, test gid.
@@ -81,13 +78,13 @@ func TestDefaultFileChown(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
t.Run("-1 parameters means leave alone", func(t *testing.T) { t.Run("-1 parameters means leave alone", func(t *testing.T) {
require.Zero(t, dirF.Chown(-1, -1)) require.EqualErrno(t, 0, dirF.Chown(-1, -1))
checkUidGid(t, dir, dirSys.Uid, dirSys.Gid) checkUidGid(t, dir, dirSt.Uid, dirSt.Gid)
}) })
t.Run("change gid, but not uid", func(t *testing.T) { t.Run("change gid, but not uid", func(t *testing.T) {
require.Zero(t, dirF.Chown(-1, gid)) require.EqualErrno(t, 0, dirF.Chown(-1, gid))
checkUidGid(t, dir, dirSys.Uid, uint32(gid)) checkUidGid(t, dir, dirSt.Uid, uint32(gid))
}) })
// Now, try any other groups of the current user. // Now, try any other groups of the current user.
@@ -95,17 +92,17 @@ func TestDefaultFileChown(t *testing.T) {
g := g g := g
t.Run(fmt.Sprintf("change to gid %d", g), func(t *testing.T) { t.Run(fmt.Sprintf("change to gid %d", g), func(t *testing.T) {
// Test using our Chown // Test using our Chown
require.Zero(t, dirF.Chown(-1, g)) require.EqualErrno(t, 0, dirF.Chown(-1, g))
checkUidGid(t, dir, dirSys.Uid, uint32(g)) checkUidGid(t, dir, dirSt.Uid, uint32(g))
// Revert back with os.File.Chown // Revert back
require.NoError(t, dirF.File().(*os.File).Chown(-1, gid)) require.EqualErrno(t, 0, dirF.Chown(-1, gid))
checkUidGid(t, dir, dirSys.Uid, uint32(gid)) checkUidGid(t, dir, dirSt.Uid, uint32(gid))
}) })
} }
t.Run("closed", func(t *testing.T) { t.Run("closed", func(t *testing.T) {
require.Zero(t, dirF.Close()) require.EqualErrno(t, 0, dirF.Close())
require.EqualErrno(t, syscall.EBADF, dirF.Chown(-1, gid)) require.EqualErrno(t, syscall.EBADF, dirF.Chown(-1, gid))
}) })
} }
@@ -119,10 +116,8 @@ func TestLchown(t *testing.T) {
dirF := openFsFile(t, dir, syscall.O_RDONLY, 0) dirF := openFsFile(t, dir, syscall.O_RDONLY, 0)
defer dirF.Close() defer dirF.Close()
dirStat, err := dirF.File().Stat() dirSt, errno := dirF.Stat()
require.NoError(t, err) require.EqualErrno(t, 0, errno)
dirSys := dirStat.Sys().(*syscall.Stat_t)
link := path.Join(tmpDir, "link") link := path.Join(tmpDir, "link")
require.NoError(t, os.Symlink(dir, link)) require.NoError(t, os.Symlink(dir, link))
@@ -130,10 +125,8 @@ func TestLchown(t *testing.T) {
linkF := openFsFile(t, link, syscall.O_RDONLY, 0) linkF := openFsFile(t, link, syscall.O_RDONLY, 0)
defer linkF.Close() defer linkF.Close()
linkStat, err := linkF.File().Stat() linkSt, errno := linkF.Stat()
require.NoError(t, err) require.EqualErrno(t, 0, errno)
linkSys := linkStat.Sys().(*syscall.Stat_t)
// Similar to TestLchown in os_unix_test.go, we can't expect to change // Similar to TestLchown in os_unix_test.go, we can't expect to change
// owner unless root, and with another user. Instead, test gid. // owner unless root, and with another user. Instead, test gid.
@@ -142,15 +135,15 @@ func TestLchown(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
t.Run("-1 parameters means leave alone", func(t *testing.T) { t.Run("-1 parameters means leave alone", func(t *testing.T) {
require.Zero(t, Lchown(link, -1, -1)) require.EqualErrno(t, 0, Lchown(link, -1, -1))
checkUidGid(t, link, linkSys.Uid, linkSys.Gid) checkUidGid(t, link, linkSt.Uid, linkSt.Gid)
}) })
t.Run("change gid, but not uid", func(t *testing.T) { t.Run("change gid, but not uid", func(t *testing.T) {
require.Zero(t, Chown(dir, -1, gid)) require.EqualErrno(t, 0, Chown(dir, -1, gid))
checkUidGid(t, link, linkSys.Uid, uint32(gid)) checkUidGid(t, link, linkSt.Uid, uint32(gid))
// Make sure the target didn't change. // Make sure the target didn't change.
checkUidGid(t, dir, dirSys.Uid, dirSys.Gid) checkUidGid(t, dir, dirSt.Uid, dirSt.Gid)
}) })
// Now, try any other groups of the current user. // Now, try any other groups of the current user.
@@ -158,14 +151,14 @@ func TestLchown(t *testing.T) {
g := g g := g
t.Run(fmt.Sprintf("change to gid %d", g), func(t *testing.T) { t.Run(fmt.Sprintf("change to gid %d", g), func(t *testing.T) {
// Test using our Lchown // Test using our Lchown
require.Zero(t, Lchown(link, -1, g)) require.EqualErrno(t, 0, Lchown(link, -1, g))
checkUidGid(t, link, linkSys.Uid, uint32(g)) checkUidGid(t, link, linkSt.Uid, uint32(g))
// Make sure the target didn't change. // Make sure the target didn't change.
checkUidGid(t, dir, dirSys.Uid, dirSys.Gid) checkUidGid(t, dir, dirSt.Uid, dirSt.Gid)
// Revert back with syscall.Lchown // Revert back
require.NoError(t, syscall.Lchown(link, -1, gid)) require.EqualErrno(t, 0, Lchown(link, -1, gid))
checkUidGid(t, link, linkSys.Uid, uint32(gid)) checkUidGid(t, link, linkSt.Uid, uint32(gid))
}) })
} }

View File

@@ -58,6 +58,19 @@ type File interface {
// - Windows allows you to stat a closed directory. // - Windows allows you to stat a closed directory.
Stat() (Stat_t, syscall.Errno) Stat() (Stat_t, syscall.Errno)
// IsDir returns true if this file is a directory or an error there was an
// error retrieving this information.
//
// # Errors
//
// A zero syscall.Errno is success. The below are expected otherwise:
// - syscall.ENOSYS: the implementation does not support this function.
//
// # Notes
//
// - Some implementations implement this with a cached call to Stat.
IsDir() (bool, syscall.Errno)
// Read attempts to read all bytes in the file into `p`, and returns the // Read attempts to read all bytes in the file into `p`, and returns the
// count read even on error. // count read even on error.
// //
@@ -95,6 +108,33 @@ type File interface {
// read the file completely, the caller must repeat until `n` is zero. // read the file completely, the caller must repeat until `n` is zero.
Pread(p []byte, off int64) (n int, errno syscall.Errno) Pread(p []byte, off int64) (n int, errno syscall.Errno)
// Seek attempts to set the next offset for Read or Write and returns the
// resulting absolute offset or an error.
//
// # Parameters
//
// The `offset` parameters is interpreted in terms of `whence`:
// - io.SeekStart: relative to the start of the file, e.g. offset=0 sets
// the next Read or Write to the beginning of the file.
// - io.SeekCurrent: relative to the current offset, e.g. offset=16 sets
// the next Read or Write 16 bytes past the prior.
// - io.SeekEnd: relative to the end of the file, e.g. offset=-1 sets the
// next Read or Write to the last byte in the file.
//
// # Errors
//
// A zero syscall.Errno is success. The below are expected otherwise:
// - syscall.ENOSYS: the implementation does not support this function.
// - syscall.EBADF: the file or directory was closed or not readable.
// - syscall.EINVAL: the offset was negative.
// - syscall.EISDIR: the file was a directory.
//
// # Notes
//
// - This is like io.Seeker and `fseek` in POSIX, preferring semantics
// of io.Seeker. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/fseek.html
Seek(offset int64, whence int) (newOffset int64, errno syscall.Errno)
// Write attempts to write all bytes in `p` to the file, and returns the // Write attempts to write all bytes in `p` to the file, and returns the
// count written even on error. // count written even on error.
// //
@@ -103,6 +143,7 @@ type File interface {
// A zero syscall.Errno is success. The below are expected otherwise: // A zero syscall.Errno is success. The below are expected otherwise:
// - syscall.ENOSYS: the implementation does not support this function. // - syscall.ENOSYS: the implementation does not support this function.
// - syscall.EBADF: the file or directory was closed or not writeable. // - syscall.EBADF: the file or directory was closed or not writeable.
// - syscall.EISDIR: the file was a directory.
// //
// # Notes // # Notes
// //
@@ -119,6 +160,7 @@ type File interface {
// - syscall.ENOSYS: the implementation does not support this function. // - syscall.ENOSYS: the implementation does not support this function.
// - syscall.EBADF: the file or directory was closed or not writeable. // - syscall.EBADF: the file or directory was closed or not writeable.
// - syscall.EINVAL: the offset was negative. // - syscall.EINVAL: the offset was negative.
// - syscall.EISDIR: the file was a directory.
// //
// # Notes // # Notes
// //
@@ -255,6 +297,11 @@ func (UnimplementedFile) Stat() (Stat_t, syscall.Errno) {
return Stat_t{}, syscall.ENOSYS return Stat_t{}, syscall.ENOSYS
} }
// IsDir implements File.IsDir
func (UnimplementedFile) IsDir() (bool, syscall.Errno) {
return false, syscall.ENOSYS
}
// Read implements File.Read // Read implements File.Read
func (UnimplementedFile) Read([]byte) (int, syscall.Errno) { func (UnimplementedFile) Read([]byte) (int, syscall.Errno) {
return 0, syscall.ENOSYS return 0, syscall.ENOSYS
@@ -265,6 +312,11 @@ func (UnimplementedFile) Pread([]byte, int64) (int, syscall.Errno) {
return 0, syscall.ENOSYS return 0, syscall.ENOSYS
} }
// Seek implements File.Seek
func (UnimplementedFile) Seek(int64, int) (int64, syscall.Errno) {
return 0, syscall.ENOSYS
}
// Write implements File.Write // Write implements File.Write
func (UnimplementedFile) Write([]byte) (int, syscall.Errno) { func (UnimplementedFile) Write([]byte) (int, syscall.Errno) {
return 0, syscall.ENOSYS return 0, syscall.ENOSYS
@@ -317,6 +369,25 @@ type fsFile struct {
path string path string
accessMode int accessMode int
file fs.File file fs.File
// cachedStat includes fields that won't change while a file is open.
cachedSt *cachedStat
}
type cachedStat struct {
// fileType is the same as what's documented on Dirent.
fileType fs.FileMode
}
// cachedStat returns the cacheable parts of platform.Stat_t or an error if
// they couldn't be retrieved.
func (f *fsFile) cachedStat() (fileType fs.FileMode, errno syscall.Errno) {
if f.cachedSt == nil {
if _, errno = f.Stat(); errno != 0 {
return
}
}
return f.cachedSt.fileType, 0
} }
// Path implements File.Path // Path implements File.Path
@@ -329,10 +400,23 @@ func (f *fsFile) AccessMode() int {
return f.accessMode return f.accessMode
} }
// IsDir implements File.IsDir
func (f *fsFile) IsDir() (bool, syscall.Errno) {
if ft, errno := f.cachedStat(); errno != 0 {
return false, errno
} else if ft.Type() == fs.ModeDir {
return true, 0
}
return false, 0
}
// Stat implements File.Stat // Stat implements File.Stat
func (f *fsFile) Stat() (Stat_t, syscall.Errno) { func (f *fsFile) Stat() (Stat_t, syscall.Errno) {
st, errno := statFile(f.file) st, errno := statFile(f.file)
if errno == syscall.EIO { switch errno {
case 0:
f.cachedSt = &cachedStat{fileType: st.Mode & fs.ModeType}
case syscall.EIO:
errno = syscall.EBADF errno = syscall.EBADF
} }
return st, errno return st, errno
@@ -344,9 +428,12 @@ func (f *fsFile) Read(p []byte) (n int, errno syscall.Errno) {
return 0, 0 // less overhead on zero-length reads. return 0, 0 // less overhead on zero-length reads.
} }
if f.accessMode == syscall.O_WRONLY { if errno = f.isDirErrno(); errno != 0 {
return
} else if f.accessMode == syscall.O_WRONLY {
return 0, syscall.EBADF return 0, syscall.EBADF
} }
if w, ok := f.File().(io.Reader); ok { if w, ok := f.File().(io.Reader); ok {
n, err := w.Read(p) n, err := w.Read(p)
return n, UnwrapOSError(err) return n, UnwrapOSError(err)
@@ -360,7 +447,9 @@ func (f *fsFile) Pread(p []byte, off int64) (n int, errno syscall.Errno) {
return 0, 0 // less overhead on zero-length reads. return 0, 0 // less overhead on zero-length reads.
} }
if f.accessMode == syscall.O_WRONLY { if errno = f.isDirErrno(); errno != 0 {
return
} else if f.accessMode == syscall.O_WRONLY {
return 0, syscall.EBADF return 0, syscall.EBADF
} }
@@ -395,15 +484,32 @@ func (f *fsFile) Pread(p []byte, off int64) (n int, errno syscall.Errno) {
return 0, syscall.ENOSYS // unsupported return 0, syscall.ENOSYS // unsupported
} }
// Write implements File.Write // Seek implements File.Seek
func (f *fsFile) Write(p []byte) (n int, errno syscall.Errno) { func (f *fsFile) Seek(offset int64, whence int) (int64, syscall.Errno) {
if len(p) == 0 { if errno := f.isDirErrno(); errno != 0 {
return 0, 0 // less overhead on zero-length writes. return 0, errno
} else if uint(whence) > io.SeekEnd {
return 0, syscall.EINVAL // negative or exceeds the largest valid whence
} }
if f.accessMode == syscall.O_RDONLY { if seeker, ok := f.file.(io.Seeker); ok {
newOffset, err := seeker.Seek(offset, whence)
return newOffset, UnwrapOSError(err)
}
return 0, syscall.ENOSYS
}
// Write implements File.Write
func (f *fsFile) Write(p []byte) (n int, errno syscall.Errno) {
if errno = f.isDirErrno(); errno != 0 {
return
} else if f.accessMode == syscall.O_RDONLY {
return 0, syscall.EBADF return 0, syscall.EBADF
} }
if len(p) == 0 {
return 0, 0 // less overhead on zero-length writes.
}
if w, ok := f.File().(io.Writer); ok { if w, ok := f.File().(io.Writer); ok {
n, err := w.Write(p) n, err := w.Write(p)
return n, UnwrapOSError(err) return n, UnwrapOSError(err)
@@ -413,13 +519,16 @@ func (f *fsFile) Write(p []byte) (n int, errno syscall.Errno) {
// Pwrite implements File.Pwrite // Pwrite implements File.Pwrite
func (f *fsFile) Pwrite(p []byte, off int64) (n int, errno syscall.Errno) { func (f *fsFile) Pwrite(p []byte, off int64) (n int, errno syscall.Errno) {
if errno = f.isDirErrno(); errno != 0 {
return
} else if f.accessMode == syscall.O_RDONLY {
return 0, syscall.EBADF
}
if len(p) == 0 { if len(p) == 0 {
return 0, 0 // less overhead on zero-length writes. return 0, 0 // less overhead on zero-length writes.
} }
if f.accessMode == syscall.O_RDONLY {
return 0, syscall.EBADF
}
if w, ok := f.File().(io.WriterAt); ok { if w, ok := f.File().(io.WriterAt); ok {
n, err := w.WriteAt(p, off) n, err := w.WriteAt(p, off)
return n, UnwrapOSError(err) return n, UnwrapOSError(err)
@@ -429,22 +538,29 @@ func (f *fsFile) Pwrite(p []byte, off int64) (n int, errno syscall.Errno) {
// Truncate implements File.Truncate // Truncate implements File.Truncate
func (f *fsFile) Truncate(size int64) syscall.Errno { func (f *fsFile) Truncate(size int64) syscall.Errno {
if tf, ok := f.file.(truncateFile); ok { if errno := f.isDirErrno(); errno != 0 {
errno := UnwrapOSError(tf.Truncate(size)) return errno
if errno == 0 { } else if f.accessMode == syscall.O_RDONLY {
return 0 return syscall.EBADF
} }
// Operating systems return different syscall.Errno instead of EISDIR if tf, ok := f.file.(truncateFile); ok {
// double-check on any err until we can assure this per OS. return UnwrapOSError(tf.Truncate(size))
if isOpenDir(f) {
return syscall.EISDIR
}
return errno
} }
return syscall.ENOSYS return syscall.ENOSYS
} }
// isDirErrno returns syscall.EISDIR, if the file is a directory, or any error
// calling IsDir.
func (f *fsFile) isDirErrno() syscall.Errno {
if isDir, errno := f.IsDir(); errno != 0 {
return errno
} else if isDir {
return syscall.EISDIR
}
return 0
}
// Sync implements File.Sync // Sync implements File.Sync
func (f *fsFile) Sync() syscall.Errno { func (f *fsFile) Sync() syscall.Errno {
return sync(f.file) return sync(f.file)
@@ -533,10 +649,3 @@ type (
// truncateFile is implemented by os.File in file_posix.go // truncateFile is implemented by os.File in file_posix.go
truncateFile interface{ Truncate(size int64) error } truncateFile interface{ Truncate(size int64) error }
) )
func isOpenDir(f File) bool {
if st, statErrno := f.Stat(); statErrno == 0 && st.Mode.IsDir() {
return true
}
return false
}

View File

@@ -45,28 +45,57 @@ var embedFS embed.FS
var ( var (
//go:embed testdata //go:embed testdata
testdata embed.FS testdata embed.FS
preadFile = "wazero.txt" readFile = "wazero.txt"
emptyFile = "empty.txt" emptyFile = "empty.txt"
) )
func TestFsFileReadAndPread(t *testing.T) { func TestFsFileIsDir(t *testing.T) {
embedFS, err := fs.Sub(testdata, "testdata") dirFS, embedFS, mapFS := dirEmbedMapFS(t, t.TempDir())
require.NoError(t, err)
f, err := embedFS.Open(preadFile) tests := []struct {
name string
fs fs.FS
}{
{name: "os.DirFS", fs: dirFS},
{name: "embed.FS", fs: embedFS},
{name: "fstest.MapFS", fs: mapFS},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Run("file", func(t *testing.T) {
f, err := tc.fs.Open(readFile)
require.NoError(t, err) require.NoError(t, err)
defer f.Close() defer f.Close()
bytes, err := io.ReadAll(f) fsF := NewFsFile(readFile, syscall.O_RDONLY, f)
isDir, errno := fsF.IsDir()
require.EqualErrno(t, 0, errno)
require.False(t, isDir)
require.Equal(t, &cachedStat{fileType: 0}, fsF.(*fsFile).cachedSt)
})
t.Run("dir", func(t *testing.T) {
f, err := tc.fs.Open(".")
require.NoError(t, err) require.NoError(t, err)
defer f.Close()
mapFS := gofstest.MapFS{preadFile: &gofstest.MapFile{Data: bytes}} fsF := NewFsFile(readFile, syscall.O_RDONLY, f)
// Write a file as can't open "testdata" in scratch tests because they isDir, errno := fsF.IsDir()
// can't read the original filesystem. require.EqualErrno(t, 0, errno)
tmpDir := t.TempDir() require.True(t, isDir)
require.NoError(t, os.WriteFile(path.Join(tmpDir, preadFile), bytes, 0o600)) require.Equal(t, &cachedStat{fileType: fs.ModeDir}, fsF.(*fsFile).cachedSt)
dirFS := os.DirFS(tmpDir) })
})
}
}
func TestFsFileReadAndPread(t *testing.T) {
dirFS, embedFS, mapFS := dirEmbedMapFS(t, t.TempDir())
tests := []struct { tests := []struct {
name string name string
@@ -83,11 +112,11 @@ func TestFsFileReadAndPread(t *testing.T) {
tc := tc tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
f, err := tc.fs.Open(preadFile) f, err := tc.fs.Open(readFile)
require.NoError(t, err) require.NoError(t, err)
defer f.Close() defer f.Close()
fs := NewFsFile(preadFile, syscall.O_RDONLY, f) fs := NewFsFile(readFile, syscall.O_RDONLY, f)
// The file should be readable (base case) // The file should be readable (base case)
requireRead(t, fs, buf) requireRead(t, fs, buf)
@@ -124,20 +153,7 @@ func requirePread(t *testing.T, f File, buf []byte, off int64) {
} }
func TestFsFileRead_empty(t *testing.T) { func TestFsFileRead_empty(t *testing.T) {
embedFS, err := fs.Sub(testdata, "testdata") dirFS, embedFS, mapFS := dirEmbedMapFS(t, t.TempDir())
require.NoError(t, err)
f, err := embedFS.Open(preadFile)
require.NoError(t, err)
defer f.Close()
mapFS := gofstest.MapFS{emptyFile: &gofstest.MapFile{}}
// Write a file as can't open "testdata" in scratch tests because they
// can't read the original filesystem.
tmpDir := t.TempDir()
require.NoError(t, os.WriteFile(path.Join(tmpDir, emptyFile), []byte{}, 0o600))
dirFS := os.DirFS(tmpDir)
tests := []struct { tests := []struct {
name string name string
@@ -158,7 +174,7 @@ func TestFsFileRead_empty(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer f.Close() defer f.Close()
fs := NewFsFile(preadFile, syscall.O_RDONLY, f) fs := NewFsFile(readFile, syscall.O_RDONLY, f)
t.Run("Read", func(t *testing.T) { t.Run("Read", func(t *testing.T) {
// We should be able to read an empty file // We should be able to read an empty file
@@ -187,17 +203,190 @@ func TestFsFilePread_Unsupported(t *testing.T) {
// mask both io.ReaderAt and io.Seeker // mask both io.ReaderAt and io.Seeker
f = struct{ fs.File }{f} f = struct{ fs.File }{f}
fs := NewFsFile(preadFile, syscall.O_RDONLY, f) fs := NewFsFile(readFile, syscall.O_RDONLY, f)
buf := make([]byte, 3) buf := make([]byte, 3)
_, errno := fs.Pread(buf, 0) _, errno := fs.Pread(buf, 0)
require.EqualErrno(t, syscall.ENOSYS, errno) require.EqualErrno(t, syscall.ENOSYS, errno)
} }
func TestFsFileRead_Errors(t *testing.T) {
// Create the file
path := path.Join(t.TempDir(), emptyFile)
of, err := os.Create(path)
require.NoError(t, err)
require.NoError(t, of.Close())
// Open the file write-only
flag := syscall.O_WRONLY
f := openFsFile(t, path, flag, 0o600)
defer f.Close()
buf := make([]byte, 5)
tests := []struct {
name string
fn func(File) syscall.Errno
}{
{name: "Read", fn: func(f File) syscall.Errno {
_, errno := f.Read(buf)
return errno
}},
{name: "Pread", fn: func(f File) syscall.Errno {
_, errno := f.Pread(buf, 0)
return errno
}},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Run("EBADF when not open for reading", func(t *testing.T) {
// The descriptor exists, but not open for reading
errno := tc.fn(f)
require.EqualErrno(t, syscall.EBADF, errno)
})
testEISDIR(t, tc.fn)
})
}
}
func TestFsFileSeek(t *testing.T) {
dirFS, embedFS, mapFS := dirEmbedMapFS(t, t.TempDir())
tests := []struct {
name string
fs fs.FS
}{
{name: "os.DirFS", fs: dirFS},
{name: "embed.FS", fs: embedFS},
{name: "fstest.MapFS", fs: mapFS},
}
buf := make([]byte, 3)
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
f, err := tc.fs.Open(readFile)
require.NoError(t, err)
defer f.Close()
fs := NewFsFile(readFile, syscall.O_RDONLY, f)
// Shouldn't be able to use an invalid whence
_, errno := fs.Seek(0, io.SeekEnd+1)
require.EqualErrno(t, syscall.EINVAL, errno)
_, errno = fs.Seek(0, -1)
require.EqualErrno(t, syscall.EINVAL, errno)
// Shouldn't be able to seek before the file starts.
_, errno = fs.Seek(-1, io.SeekStart)
require.EqualErrno(t, syscall.EINVAL, errno)
requireRead(t, fs, buf) // read 3 bytes
// Seek to the start
newOffset, errno := fs.Seek(0, io.SeekStart)
require.EqualErrno(t, 0, errno)
// verify we can re-read from the beginning now.
require.Zero(t, newOffset)
requireRead(t, fs, buf) // read 3 bytes again
require.Equal(t, "waz", string(buf))
buf = buf[:]
// Seek to the start with zero allows you to read it back.
newOffset, errno = fs.Seek(0, io.SeekCurrent)
require.EqualErrno(t, 0, errno)
require.Equal(t, int64(3), newOffset)
// Seek to the last two bytes
newOffset, errno = fs.Seek(-2, io.SeekEnd)
require.EqualErrno(t, 0, errno)
// verify we can read the last two bytes
require.Equal(t, int64(5), newOffset)
n, errno := fs.Read(buf)
require.EqualErrno(t, 0, errno)
require.Equal(t, 2, n)
require.Equal(t, "o\n", string(buf[:2]))
})
}
seekToZero := func(f File) syscall.Errno {
_, errno := f.Seek(0, io.SeekStart)
return errno
}
testEBADFIfFileClosed(t, seekToZero)
testEISDIR(t, seekToZero)
}
func requireSeek(t *testing.T, f File, off int64, whence int) int64 {
n, errno := f.Seek(off, whence)
require.EqualErrno(t, 0, errno)
return n
}
func TestFsFileSeek_empty(t *testing.T) {
dirFS, embedFS, mapFS := dirEmbedMapFS(t, t.TempDir())
tests := []struct {
name string
fs fs.FS
}{
{name: "os.DirFS", fs: dirFS},
{name: "embed.FS", fs: embedFS},
{name: "fstest.MapFS", fs: mapFS},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
f, err := tc.fs.Open(emptyFile)
require.NoError(t, err)
defer f.Close()
fs := NewFsFile(readFile, syscall.O_RDONLY, f)
t.Run("Start", func(t *testing.T) {
require.Zero(t, requireSeek(t, fs, 0, io.SeekStart))
})
t.Run("Current", func(t *testing.T) {
require.Zero(t, requireSeek(t, fs, 0, io.SeekCurrent))
})
t.Run("End", func(t *testing.T) {
require.Zero(t, requireSeek(t, fs, 0, io.SeekEnd))
})
})
}
}
func TestFsFileSeek_Unsupported(t *testing.T) {
embedFS, err := fs.Sub(testdata, "testdata")
require.NoError(t, err)
f, err := embedFS.Open(emptyFile)
require.NoError(t, err)
defer f.Close()
// mask io.Seeker
f = struct{ fs.File }{f}
fs := NewFsFile(readFile, syscall.O_RDONLY, f)
_, errno := fs.Seek(0, io.SeekCurrent)
require.EqualErrno(t, syscall.ENOSYS, errno)
}
func TestFsFileWriteAndPwrite(t *testing.T) { func TestFsFileWriteAndPwrite(t *testing.T) {
// fs.FS doesn't support writes, and there is no other built-in // fs.FS doesn't support writes, and there is no other built-in
// implementation except os.File. // implementation except os.File.
path := path.Join(t.TempDir(), preadFile) path := path.Join(t.TempDir(), readFile)
f := openFsFile(t, path, syscall.O_RDWR|os.O_CREATE, 0o600) f := openFsFile(t, path, syscall.O_RDWR|os.O_CREATE, 0o600)
defer f.Close() defer f.Close()
@@ -291,7 +480,7 @@ func TestFsFileWrite_Unsupported(t *testing.T) {
embedFS, err := fs.Sub(testdata, "testdata") embedFS, err := fs.Sub(testdata, "testdata")
require.NoError(t, err) require.NoError(t, err)
f, err := embedFS.Open(preadFile) f, err := embedFS.Open(readFile)
require.NoError(t, err) require.NoError(t, err)
defer f.Close() defer f.Close()
@@ -314,14 +503,14 @@ func TestFsFileWrite_Unsupported(t *testing.T) {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
// Use syscall.O_RDWR so that it fails due to type not flags // Use syscall.O_RDWR so that it fails due to type not flags
f := NewFsFile(preadFile, syscall.O_RDWR, f) f := NewFsFile(readFile, syscall.O_RDWR, f)
_, errno := tc.fn(f, buf) _, errno := tc.fn(f, buf)
require.EqualErrno(t, syscall.ENOSYS, errno) require.EqualErrno(t, syscall.ENOSYS, errno)
}) })
} }
} }
func TestFsFileWrite_BadFile(t *testing.T) { func TestFsFileWrite_Errors(t *testing.T) {
// Create the file // Create the file
path := path.Join(t.TempDir(), emptyFile) path := path.Join(t.TempDir(), emptyFile)
of, err := os.Create(path) of, err := os.Create(path)
@@ -332,30 +521,33 @@ func TestFsFileWrite_BadFile(t *testing.T) {
flag := syscall.O_RDONLY flag := syscall.O_RDONLY
f := openFsFile(t, path, flag, 0o600) f := openFsFile(t, path, flag, 0o600)
defer f.Close() defer f.Close()
buf := []byte("wazero")
tests := []struct { tests := []struct {
name string name string
fn func(File, []byte) (int, syscall.Errno) fn func(File) syscall.Errno
}{ }{
{name: "Write", fn: func(f File, buf []byte) (int, syscall.Errno) { {name: "Write", fn: func(f File) syscall.Errno {
return f.Write(buf) _, errno := f.Write(buf)
return errno
}}, }},
{name: "Pwrite", fn: func(f File, buf []byte) (int, syscall.Errno) { {name: "Pwrite", fn: func(f File) syscall.Errno {
return f.Pwrite(buf, 0) _, errno := f.Pwrite(buf, 0)
return errno
}}, }},
} }
buf := []byte("wazero")
for _, tc := range tests { for _, tc := range tests {
tc := tc tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
_, errno := tc.fn(f, buf) t.Run("EBADF when not open for writing", func(t *testing.T) {
// The descriptor exists, but not open for writing // The descriptor exists, but not open for writing
errno := tc.fn(f)
require.EqualErrno(t, syscall.EBADF, errno) require.EqualErrno(t, syscall.EBADF, errno)
}) })
testEISDIR(t, tc.fn)
})
} }
} }
@@ -400,7 +592,7 @@ func testSync_NoError(t *testing.T, sync func(File) syscall.Errno) {
tc := tt tc := tt
t.Run(tc.name, func(b *testing.T) { t.Run(tc.name, func(b *testing.T) {
require.Zero(t, sync(tc.f)) require.EqualErrno(t, 0, sync(tc.f))
}) })
} }
} }
@@ -442,8 +634,8 @@ func testSync(t *testing.T, sync func(File) syscall.Errno) {
require.EqualErrno(t, 0, errno) require.EqualErrno(t, 0, errno)
// Rewind while the file is still open. // Rewind while the file is still open.
_, err = f.File().(io.Seeker).Seek(0, io.SeekStart) _, errno = f.Seek(0, io.SeekStart)
require.NoError(t, err) require.EqualErrno(t, 0, errno)
// Read data from the file // Read data from the file
buf := make([]byte, 50) buf := make([]byte, 50)
@@ -513,8 +705,7 @@ func TestFsFileTruncate(t *testing.T) {
} }
if runtime.GOOS != "windows" { if runtime.GOOS != "windows" {
// TODO: os.Truncate on windows can create the file even when it // TODO: os.Truncate on windows passes even when closed
// doesn't exist.
testEBADFIfFileClosed(t, truncateToZero) testEBADFIfFileClosed(t, truncateToZero)
} }
@@ -558,7 +749,7 @@ func testEBADFIfDirClosed(t *testing.T, fn func(File) syscall.Errno) bool {
d := openFsFile(t, t.TempDir(), syscall.O_RDONLY, 0o755) d := openFsFile(t, t.TempDir(), syscall.O_RDONLY, 0o755)
// close the directory underneath // close the directory underneath
require.Zero(t, d.Close()) require.EqualErrno(t, 0, d.Close())
require.EqualErrno(t, syscall.EBADF, fn(d)) require.EqualErrno(t, syscall.EBADF, fn(d))
}) })
@@ -571,7 +762,7 @@ func testEBADFIfFileClosed(t *testing.T, fn func(File) syscall.Errno) bool {
f := openForWrite(t, path.Join(tmpDir, "EBADF"), []byte{1, 2, 3, 4}) f := openForWrite(t, path.Join(tmpDir, "EBADF"), []byte{1, 2, 3, 4})
// close the file underneath // close the file underneath
require.Zero(t, f.Close()) require.EqualErrno(t, 0, f.Close())
require.EqualErrno(t, syscall.EBADF, fn(f)) require.EqualErrno(t, syscall.EBADF, fn(f))
}) })
@@ -596,3 +787,27 @@ func openFsFile(t *testing.T, path string, flag int, perm fs.FileMode) File {
require.EqualErrno(t, 0, errno) require.EqualErrno(t, 0, errno)
return NewFsFile(path, flag, f) return NewFsFile(path, flag, f)
} }
func dirEmbedMapFS(t *testing.T, tmpDir string) (fs.FS, fs.FS, fs.FS) {
embedFS, err := fs.Sub(testdata, "testdata")
require.NoError(t, err)
f, err := embedFS.Open(readFile)
require.NoError(t, err)
defer f.Close()
bytes, err := io.ReadAll(f)
require.NoError(t, err)
mapFS := gofstest.MapFS{
emptyFile: &gofstest.MapFile{},
readFile: &gofstest.MapFile{Data: bytes},
}
// Write a file as can't open "testdata" in scratch tests because they
// can't read the original filesystem.
require.NoError(t, os.WriteFile(path.Join(tmpDir, emptyFile), nil, 0o600))
require.NoError(t, os.WriteFile(path.Join(tmpDir, readFile), bytes, 0o600))
dirFS := os.DirFS(tmpDir)
return dirFS, embedFS, mapFS
}

View File

@@ -170,7 +170,7 @@ func testUtimens(t *testing.T, futimes bool) {
f := openFsFile(t, path, flag, 0) f := openFsFile(t, path, flag, 0)
errno = f.Utimens(tc.times) errno = f.Utimens(tc.times)
require.Zero(t, f.Close()) require.EqualErrno(t, 0, f.Close())
require.EqualErrno(t, 0, errno) require.EqualErrno(t, 0, errno)
} }

View File

@@ -76,7 +76,7 @@ func TestOpenFile_Errors(t *testing.T) {
require.EqualErrno(t, syscall.EBADF, errno) require.EqualErrno(t, syscall.EBADF, errno)
}) })
t.Run("writing to a directory is EBADF", func(t *testing.T) { t.Run("writing to a directory is EISDIR", func(t *testing.T) {
path := path.Join(tmpDir, "diragain") path := path.Join(tmpDir, "diragain")
require.NoError(t, os.Mkdir(path, 0o755)) require.NoError(t, os.Mkdir(path, 0o755))
@@ -84,7 +84,7 @@ func TestOpenFile_Errors(t *testing.T) {
defer f.Close() defer f.Close()
_, errno := f.Write([]byte{1, 2, 3, 4}) _, errno := f.Write([]byte{1, 2, 3, 4})
require.EqualErrno(t, syscall.EBADF, errno) require.EqualErrno(t, syscall.EISDIR, errno)
}) })
// This is similar to https://github.com/WebAssembly/wasi-testsuite/blob/dc7f8d27be1030cd4788ebdf07d9b57e5d23441e/tests/rust/src/bin/dangling_symlink.rs // This is similar to https://github.com/WebAssembly/wasi-testsuite/blob/dc7f8d27be1030cd4788ebdf07d9b57e5d23441e/tests/rust/src/bin/dangling_symlink.rs

View File

@@ -173,7 +173,7 @@ func TestStatFile(t *testing.T) {
// not by file descriptor. // not by file descriptor.
if runtime.GOOS != "windows" { if runtime.GOOS != "windows" {
t.Run("closed dir", func(t *testing.T) { t.Run("closed dir", func(t *testing.T) {
require.Zero(t, tmpDirF.Close()) require.EqualErrno(t, 0, tmpDirF.Close())
_, errno := tmpDirF.Stat() _, errno := tmpDirF.Stat()
require.EqualErrno(t, syscall.EBADF, errno) require.EqualErrno(t, syscall.EBADF, errno)
}) })
@@ -193,7 +193,7 @@ func TestStatFile(t *testing.T) {
}) })
t.Run("closed fsFile", func(t *testing.T) { t.Run("closed fsFile", func(t *testing.T) {
require.Zero(t, fileF.Close()) require.EqualErrno(t, 0, fileF.Close())
_, errno := fileF.Stat() _, errno := fileF.Stat()
require.EqualErrno(t, syscall.EBADF, errno) require.EqualErrno(t, syscall.EBADF, errno)
}) })
@@ -213,7 +213,7 @@ func TestStatFile(t *testing.T) {
if runtime.GOOS != "windows" { // windows allows you to stat a closed dir if runtime.GOOS != "windows" { // windows allows you to stat a closed dir
t.Run("closed subdir", func(t *testing.T) { t.Run("closed subdir", func(t *testing.T) {
require.Zero(t, subdirF.Close()) require.EqualErrno(t, 0, subdirF.Close())
_, errno := subdirF.Stat() _, errno := subdirF.Stat()
require.EqualErrno(t, syscall.EBADF, errno) require.EqualErrno(t, syscall.EBADF, errno)
}) })
@@ -318,12 +318,12 @@ func TestStatFile_dev_inode(t *testing.T) {
// On Windows, we cannot rename while opening. // On Windows, we cannot rename while opening.
// So we manually close here before renaming. // So we manually close here before renaming.
require.Zero(t, f1.Close()) require.EqualErrno(t, 0, f1.Close())
require.Zero(t, f2.Close()) require.EqualErrno(t, 0, f2.Close())
require.Zero(t, l2.Close()) require.EqualErrno(t, 0, l2.Close())
// Renaming a file shouldn't change its inodes. // Renaming a file shouldn't change its inodes.
require.Zero(t, Rename(path1, path2)) require.EqualErrno(t, 0, Rename(path1, path2))
f1 = openFsFile(t, path2, os.O_RDONLY, 0) f1 = openFsFile(t, path2, os.O_RDONLY, 0)
defer f1.Close() defer f1.Close()
@@ -361,7 +361,7 @@ func TestStat_uid_gid(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
dir := path.Join(tmpDir, "dir") dir := path.Join(tmpDir, "dir")
require.NoError(t, os.Mkdir(dir, 0o0700)) require.NoError(t, os.Mkdir(dir, 0o0700))
require.Zero(t, chgid(dir, gid)) require.EqualErrno(t, 0, chgid(dir, gid))
st, errno := Stat(dir) st, errno := Stat(dir)
require.EqualErrno(t, 0, errno) require.EqualErrno(t, 0, errno)
@@ -374,7 +374,7 @@ func TestStat_uid_gid(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
link := path.Join(tmpDir, "link") link := path.Join(tmpDir, "link")
require.NoError(t, os.Symlink(tmpDir, link)) require.NoError(t, os.Symlink(tmpDir, link))
require.Zero(t, chgid(link, gid)) require.EqualErrno(t, 0, chgid(link, gid))
st, errno := Lstat(link) st, errno := Lstat(link)
require.EqualErrno(t, 0, errno) require.EqualErrno(t, 0, errno)
@@ -387,7 +387,7 @@ func TestStat_uid_gid(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
file := path.Join(tmpDir, "file") file := path.Join(tmpDir, "file")
require.NoError(t, os.WriteFile(file, nil, 0o0600)) require.NoError(t, os.WriteFile(file, nil, 0o0600))
require.Zero(t, chgid(file, gid)) require.EqualErrno(t, 0, chgid(file, gid))
st, errno := Lstat(file) st, errno := Lstat(file)
require.EqualErrno(t, 0, errno) require.EqualErrno(t, 0, errno)

View File

@@ -51,7 +51,7 @@ func TestUnlink(t *testing.T) {
require.NoError(t, os.WriteFile(name, []byte{}, 0o600)) require.NoError(t, os.WriteFile(name, []byte{}, 0o600))
require.Zero(t, Unlink(name)) require.EqualErrno(t, 0, Unlink(name))
_, err := os.Stat(name) _, err := os.Stat(name)
require.Error(t, err) require.Error(t, err)
}) })

View File

@@ -214,6 +214,11 @@ func (r *lazyDir) AccessMode() int {
return syscall.O_RDONLY return syscall.O_RDONLY
} }
// IsDir implements the same method as documented on platform.File
func (r *lazyDir) IsDir() (bool, syscall.Errno) {
return true, 0
}
// Stat implements the same method as documented on platform.File // Stat implements the same method as documented on platform.File
func (r *lazyDir) Stat() (platform.Stat_t, syscall.Errno) { func (r *lazyDir) Stat() (platform.Stat_t, syscall.Errno) {
if f, ok := r.file(); !ok { if f, ok := r.file(); !ok {
@@ -233,6 +238,11 @@ func (r *lazyDir) Pread([]byte, int64) (int, syscall.Errno) {
return 0, syscall.EISDIR return 0, syscall.EISDIR
} }
// Seek implements File.Seek
func (r *lazyDir) Seek(int64, int) (int64, syscall.Errno) {
return 0, syscall.EISDIR
}
// Write implements the same method as documented on platform.File // Write implements the same method as documented on platform.File
func (r *lazyDir) Write([]byte) (int, syscall.Errno) { func (r *lazyDir) Write([]byte) (int, syscall.Errno) {
return 0, syscall.EBADF return 0, syscall.EBADF
@@ -359,20 +369,17 @@ type FileEntry struct {
type cachedStat struct { type cachedStat struct {
// Ino is the file serial number, or zero if not available. // Ino is the file serial number, or zero if not available.
Ino uint64 Ino uint64
// Type is the same as what's documented on platform.Dirent.
Type fs.FileMode
} }
// CachedStat returns the cacheable parts of platform.Stat_t or an error if // Inode returns the cached inode from of platform.Stat_t or an error if it
// they couldn't be retrieved. // couldn't be retrieved.
func (f *FileEntry) CachedStat() (ino uint64, fileType fs.FileMode, errno syscall.Errno) { func (f *FileEntry) Inode() (ino uint64, errno syscall.Errno) {
if f.cachedStat == nil { if f.cachedStat == nil {
if _, errno = f.Stat(); errno != 0 { if _, errno = f.Stat(); errno != 0 {
return return
} }
} }
return f.cachedStat.Ino, f.cachedStat.Type, 0 return f.cachedStat.Ino, 0
} }
// Stat returns the underlying stat of this file. // Stat returns the underlying stat of this file.
@@ -387,7 +394,7 @@ func (f *FileEntry) Stat() (st platform.Stat_t, errno syscall.Errno) {
} }
if errno == 0 { if errno == 0 {
f.cachedStat = &cachedStat{Ino: st.Ino, Type: st.Mode & fs.ModeType} f.cachedStat = &cachedStat{Ino: st.Ino}
} }
return return
} }
@@ -547,10 +554,10 @@ func (c *FSContext) ReOpenDir(fd int32) (*FileEntry, syscall.Errno) {
f, ok := c.openedFiles.Lookup(fd) f, ok := c.openedFiles.Lookup(fd)
if !ok { if !ok {
return nil, syscall.EBADF return nil, syscall.EBADF
} else if _, ft, errno := f.CachedStat(); errno != 0 { } else if isDir, errno := f.File.IsDir(); errno != 0 {
return nil, errno return nil, errno
} else if ft.Type() != fs.ModeDir { } else if !isDir {
return nil, syscall.EISDIR return nil, syscall.ENOTDIR
} }
if errno := c.reopen(f); errno != 0 { if errno := c.reopen(f); errno != 0 {
@@ -583,9 +590,9 @@ func (c *FSContext) ChangeOpenFlag(fd int32, flag int) syscall.Errno {
f, ok := c.LookupFile(fd) f, ok := c.LookupFile(fd)
if !ok { if !ok {
return syscall.EBADF return syscall.EBADF
} else if _, ft, errno := f.CachedStat(); errno != 0 { } else if isDir, errno := f.File.IsDir(); errno != 0 {
return errno return errno
} else if ft.Type() == fs.ModeDir { } else if isDir {
return syscall.EISDIR return syscall.EISDIR
} }

View File

@@ -87,7 +87,7 @@ func TestNewFSContext(t *testing.T) {
// test to ensure that our implementation properly reuses descriptor // test to ensure that our implementation properly reuses descriptor
// numbers but if we were to change the reuse strategy, this test // numbers but if we were to change the reuse strategy, this test
// would likely break and need to be updated. // would likely break and need to be updated.
require.Zero(t, fsc.CloseFile(f1)) require.EqualErrno(t, 0, fsc.CloseFile(f1))
f2, errno := fsc.OpenFile(preopenedDir.FS, preopenedDir.Name, 0, 0) f2, errno := fsc.OpenFile(preopenedDir.FS, preopenedDir.Name, 0, 0)
require.EqualErrno(t, 0, errno) require.EqualErrno(t, 0, errno)
require.Equal(t, f1, f2) require.Equal(t, f1, f2)
@@ -113,7 +113,7 @@ func TestFSContext_CloseFile(t *testing.T) {
require.EqualErrno(t, 0, errno) require.EqualErrno(t, 0, errno)
// Close // Close
require.Zero(t, fsc.CloseFile(fdToClose)) require.EqualErrno(t, 0, fsc.CloseFile(fdToClose))
// Verify fdToClose is closed and removed from the opened FDs. // Verify fdToClose is closed and removed from the opened FDs.
_, ok := fsc.LookupFile(fdToClose) _, ok := fsc.LookupFile(fdToClose)
@@ -127,7 +127,7 @@ func TestFSContext_CloseFile(t *testing.T) {
require.EqualErrno(t, syscall.EBADF, fsc.CloseFile(42)) // 42 is an arbitrary invalid FD require.EqualErrno(t, syscall.EBADF, fsc.CloseFile(42)) // 42 is an arbitrary invalid FD
}) })
t.Run("Can close a pre-open", func(t *testing.T) { t.Run("Can close a pre-open", func(t *testing.T) {
require.Zero(t, fsc.CloseFile(FdPreopen)) require.EqualErrno(t, 0, fsc.CloseFile(FdPreopen))
}) })
} }
@@ -275,7 +275,7 @@ func TestFSContext_ReOpenDir(t *testing.T) {
require.EqualErrno(t, 0, errno) require.EqualErrno(t, 0, errno)
_, errno = fsc.ReOpenDir(fd) _, errno = fsc.ReOpenDir(fd)
require.EqualErrno(t, syscall.EISDIR, errno) require.EqualErrno(t, syscall.ENOTDIR, errno)
}) })
} }
@@ -301,7 +301,7 @@ func TestFSContext_Renumber(t *testing.T) {
prevDirFile, ok := fsc.LookupFile(fromFd) prevDirFile, ok := fsc.LookupFile(fromFd)
require.True(t, ok) require.True(t, ok)
require.Zero(t, fsc.Renumber(fromFd, toFd)) require.EqualErrno(t, 0, fsc.Renumber(fromFd, toFd))
renumberedDirFile, ok := fsc.LookupFile(toFd) renumberedDirFile, ok := fsc.LookupFile(toFd)
require.True(t, ok) require.True(t, ok)
@@ -355,13 +355,13 @@ func TestFSContext_ChangeOpenFlag(t *testing.T) {
require.Equal(t, f0.openFlag&syscall.O_APPEND, 0) require.Equal(t, f0.openFlag&syscall.O_APPEND, 0)
// Set the APPEND flag. // Set the APPEND flag.
require.Zero(t, fsc.ChangeOpenFlag(fd, syscall.O_APPEND)) require.EqualErrno(t, 0, fsc.ChangeOpenFlag(fd, syscall.O_APPEND))
f1, ok := fsc.openedFiles.Lookup(fd) f1, ok := fsc.openedFiles.Lookup(fd)
require.True(t, ok) require.True(t, ok)
require.Equal(t, f1.openFlag&syscall.O_APPEND, syscall.O_APPEND) require.Equal(t, f1.openFlag&syscall.O_APPEND, syscall.O_APPEND)
// Remove the APPEND flag. // Remove the APPEND flag.
require.Zero(t, fsc.ChangeOpenFlag(fd, 0)) require.EqualErrno(t, 0, fsc.ChangeOpenFlag(fd, 0))
f2, ok := fsc.openedFiles.Lookup(fd) f2, ok := fsc.openedFiles.Lookup(fd)
require.True(t, ok) require.True(t, ok)
require.Equal(t, f2.openFlag&syscall.O_APPEND, 0) require.Equal(t, f2.openFlag&syscall.O_APPEND, 0)

View File

@@ -2,7 +2,6 @@ package sys
import ( import (
"bytes" "bytes"
"io/fs"
"runtime" "runtime"
"strings" "strings"
"testing" "testing"
@@ -67,7 +66,7 @@ func TestDefaultSysContext(t *testing.T) {
require.Equal(t, expectedOpenedFiles, sysCtx.FS().openedFiles) require.Equal(t, expectedOpenedFiles, sysCtx.FS().openedFiles)
} }
func TestFileEntry_cachedStat(t *testing.T) { func TestFileEntryInode(t *testing.T) {
t.Parallel() t.Parallel()
tmpDir := t.TempDir() tmpDir := t.TempDir()
@@ -98,14 +97,13 @@ func TestFileEntry_cachedStat(t *testing.T) {
f, ok := fsc.LookupFile(FdPreopen) f, ok := fsc.LookupFile(FdPreopen)
require.True(t, ok) require.True(t, ok)
ino, ft, errno := f.CachedStat() ino, errno := f.Inode()
require.EqualErrno(t, 0, errno) require.EqualErrno(t, 0, errno)
require.Equal(t, fs.ModeDir, ft)
if !canReadDirInode() { if !canReadDirInode() {
tc.expectedIno = 0 tc.expectedIno = 0
} }
require.Equal(t, tc.expectedIno, ino) require.Equal(t, tc.expectedIno, ino)
require.Equal(t, &cachedStat{Ino: tc.expectedIno, Type: fs.ModeDir}, f.cachedStat) require.Equal(t, &cachedStat{Ino: tc.expectedIno}, f.cachedStat)
}) })
} }
} }

View File

@@ -21,7 +21,7 @@ func TestNewDirFS(t *testing.T) {
// Guest can look up / // Guest can look up /
f, errno := testFS.OpenFile("/", os.O_RDONLY, 0) f, errno := testFS.OpenFile("/", os.O_RDONLY, 0)
require.EqualErrno(t, 0, errno) require.EqualErrno(t, 0, errno)
require.Zero(t, f.Close()) require.EqualErrno(t, 0, f.Close())
t.Run("host path not found", func(t *testing.T) { t.Run("host path not found", func(t *testing.T) {
testFS := NewDirFS("a") testFS := NewDirFS("a")
@@ -66,7 +66,7 @@ func TestDirFS_Lstat(t *testing.T) {
testFS := NewDirFS(tmpDir) testFS := NewDirFS(tmpDir)
for _, path := range []string{"animals.txt", "sub", "sub-link"} { for _, path := range []string{"animals.txt", "sub", "sub-link"} {
require.Zero(t, testFS.Symlink(path, path+"-link")) require.EqualErrno(t, 0, testFS.Symlink(path, path+"-link"))
} }
testLstat(t, testFS) testLstat(t, testFS)
@@ -80,7 +80,7 @@ func TestDirFS_MkDir(t *testing.T) {
realPath := path.Join(tmpDir, name) realPath := path.Join(tmpDir, name)
t.Run("doesn't exist", func(t *testing.T) { t.Run("doesn't exist", func(t *testing.T) {
require.Zero(t, testFS.Mkdir(name, fs.ModeDir)) require.EqualErrno(t, 0, testFS.Mkdir(name, fs.ModeDir))
stat, err := os.Stat(realPath) stat, err := os.Stat(realPath)
require.NoError(t, err) require.NoError(t, err)
@@ -131,12 +131,12 @@ func testChmod(t *testing.T, testFS FS, path string) {
requireMode(t, testFS, path, 0o444) requireMode(t, testFS, path, 0o444)
// Test adding write, using 0o666 not 0o600 for read-back on windows. // Test adding write, using 0o666 not 0o600 for read-back on windows.
require.Zero(t, testFS.Chmod(path, 0o666)) require.EqualErrno(t, 0, testFS.Chmod(path, 0o666))
requireMode(t, testFS, path, 0o666) requireMode(t, testFS, path, 0o666)
if runtime.GOOS != "windows" { if runtime.GOOS != "windows" {
// Test clearing group and world, setting owner read+execute. // Test clearing group and world, setting owner read+execute.
require.Zero(t, testFS.Chmod(path, 0o500)) require.EqualErrno(t, 0, testFS.Chmod(path, 0o500))
requireMode(t, testFS, path, 0o500) requireMode(t, testFS, path, 0o500)
} }
} }
@@ -195,7 +195,7 @@ func TestDirFS_Rename(t *testing.T) {
dir2 := "dir2" dir2 := "dir2"
dir2Path := path.Join(tmpDir, dir2) dir2Path := path.Join(tmpDir, dir2)
errrno := testFS.Rename(dir1, dir2) errrno := testFS.Rename(dir1, dir2)
require.Zero(t, errrno) require.EqualErrno(t, 0, errrno)
// Show the prior path no longer exists // Show the prior path no longer exists
_, err := os.Stat(dir1Path) _, err := os.Stat(dir1Path)
@@ -418,7 +418,7 @@ func TestDirFS_Rmdir(t *testing.T) {
name := "rmdir" name := "rmdir"
realPath := path.Join(tmpDir, name) realPath := path.Join(tmpDir, name)
require.NoError(t, os.Mkdir(realPath, 0o700)) require.NoError(t, os.Mkdir(realPath, 0o700))
require.Zero(t, testFS.Rmdir(name)) require.EqualErrno(t, 0, testFS.Rmdir(name))
_, err := os.Stat(realPath) _, err := os.Stat(realPath)
require.Error(t, err) require.Error(t, err)
}) })
@@ -433,11 +433,9 @@ func TestDirFS_Rmdir(t *testing.T) {
f, errno := testFS.OpenFile(name, platform.O_DIRECTORY, 0o700) f, errno := testFS.OpenFile(name, platform.O_DIRECTORY, 0o700)
require.EqualErrno(t, 0, errno) require.EqualErrno(t, 0, errno)
defer func() { defer f.Close()
require.Zero(t, f.Close())
}()
require.Zero(t, testFS.Rmdir(name)) require.EqualErrno(t, 0, testFS.Rmdir(name))
_, err := os.Stat(realPath) _, err := os.Stat(realPath)
require.Error(t, err) require.Error(t, err)
}) })
@@ -494,7 +492,7 @@ func TestDirFS_Unlink(t *testing.T) {
// Create a symlink to the subdirectory. // Create a symlink to the subdirectory.
const symlinkName = "symlink-to-dir" const symlinkName = "symlink-to-dir"
require.Zero(t, testFS.Symlink("subdir", symlinkName)) require.EqualErrno(t, 0, testFS.Symlink("subdir", symlinkName))
// Unlinking the symlink should suceed. // Unlinking the symlink should suceed.
errno := testFS.Unlink(symlinkName) errno := testFS.Unlink(symlinkName)
@@ -510,7 +508,7 @@ func TestDirFS_Unlink(t *testing.T) {
require.NoError(t, os.WriteFile(realPath, []byte{}, 0o600)) require.NoError(t, os.WriteFile(realPath, []byte{}, 0o600))
require.Zero(t, testFS.Unlink(name)) require.EqualErrno(t, 0, testFS.Unlink(name))
_, err := os.Stat(realPath) _, err := os.Stat(realPath)
require.Error(t, err) require.Error(t, err)

View File

@@ -16,7 +16,7 @@ func TestDirFS_Chown(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
testFS := NewDirFS(tmpDir) testFS := NewDirFS(tmpDir)
require.Zero(t, testFS.Mkdir("dir", 0o0777)) require.EqualErrno(t, 0, testFS.Mkdir("dir", 0o0777))
dirF, errno := testFS.OpenFile("dir", syscall.O_RDONLY, 0) dirF, errno := testFS.OpenFile("dir", syscall.O_RDONLY, 0)
require.EqualErrno(t, 0, errno) require.EqualErrno(t, 0, errno)
@@ -32,12 +32,12 @@ func TestDirFS_Chown(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
t.Run("-1 parameters means leave alone", func(t *testing.T) { t.Run("-1 parameters means leave alone", func(t *testing.T) {
require.Zero(t, testFS.Chown("dir", -1, -1)) require.EqualErrno(t, 0, testFS.Chown("dir", -1, -1))
checkUidGid(t, path.Join(tmpDir, "dir"), dirSys.Uid, dirSys.Gid) checkUidGid(t, path.Join(tmpDir, "dir"), dirSys.Uid, dirSys.Gid)
}) })
t.Run("change gid, but not uid", func(t *testing.T) { t.Run("change gid, but not uid", func(t *testing.T) {
require.Zero(t, testFS.Chown("dir", -1, gid)) require.EqualErrno(t, 0, testFS.Chown("dir", -1, gid))
checkUidGid(t, path.Join(tmpDir, "dir"), dirSys.Uid, uint32(gid)) checkUidGid(t, path.Join(tmpDir, "dir"), dirSys.Uid, uint32(gid))
}) })
@@ -46,11 +46,11 @@ func TestDirFS_Chown(t *testing.T) {
g := g g := g
t.Run(fmt.Sprintf("change to gid %d", g), func(t *testing.T) { t.Run(fmt.Sprintf("change to gid %d", g), func(t *testing.T) {
// Test using our Chown // Test using our Chown
require.Zero(t, testFS.Chown("dir", -1, g)) require.EqualErrno(t, 0, testFS.Chown("dir", -1, g))
checkUidGid(t, path.Join(tmpDir, "dir"), dirSys.Uid, uint32(g)) checkUidGid(t, path.Join(tmpDir, "dir"), dirSys.Uid, uint32(g))
// Revert back with File.Chown // Revert back
require.Zero(t, dirF.Chown(-1, gid)) require.EqualErrno(t, 0, dirF.Chown(-1, gid))
checkUidGid(t, path.Join(tmpDir, "dir"), dirSys.Uid, uint32(gid)) checkUidGid(t, path.Join(tmpDir, "dir"), dirSys.Uid, uint32(gid))
}) })
} }
@@ -64,7 +64,7 @@ func TestDirFS_Lchown(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
testFS := NewDirFS(tmpDir) testFS := NewDirFS(tmpDir)
require.Zero(t, testFS.Mkdir("dir", 0o0777)) require.EqualErrno(t, 0, testFS.Mkdir("dir", 0o0777))
dirF, errno := testFS.OpenFile("dir", syscall.O_RDONLY, 0) dirF, errno := testFS.OpenFile("dir", syscall.O_RDONLY, 0)
require.EqualErrno(t, 0, errno) require.EqualErrno(t, 0, errno)
@@ -73,7 +73,7 @@ func TestDirFS_Lchown(t *testing.T) {
dirSys := dirStat.Sys().(*syscall.Stat_t) dirSys := dirStat.Sys().(*syscall.Stat_t)
require.Zero(t, testFS.Symlink("dir", "link")) require.EqualErrno(t, 0, testFS.Symlink("dir", "link"))
linkF, errno := testFS.OpenFile("link", syscall.O_RDONLY, 0) linkF, errno := testFS.OpenFile("link", syscall.O_RDONLY, 0)
require.EqualErrno(t, 0, errno) require.EqualErrno(t, 0, errno)
@@ -89,12 +89,12 @@ func TestDirFS_Lchown(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
t.Run("-1 parameters means leave alone", func(t *testing.T) { t.Run("-1 parameters means leave alone", func(t *testing.T) {
require.Zero(t, testFS.Lchown("link", -1, -1)) require.EqualErrno(t, 0, testFS.Lchown("link", -1, -1))
checkUidGid(t, path.Join(tmpDir, "link"), linkSys.Uid, linkSys.Gid) checkUidGid(t, path.Join(tmpDir, "link"), linkSys.Uid, linkSys.Gid)
}) })
t.Run("change gid, but not uid", func(t *testing.T) { t.Run("change gid, but not uid", func(t *testing.T) {
require.Zero(t, testFS.Chown("dir", -1, gid)) require.EqualErrno(t, 0, testFS.Chown("dir", -1, gid))
checkUidGid(t, path.Join(tmpDir, "link"), linkSys.Uid, uint32(gid)) checkUidGid(t, path.Join(tmpDir, "link"), linkSys.Uid, uint32(gid))
// Make sure the target didn't change. // Make sure the target didn't change.
checkUidGid(t, path.Join(tmpDir, "dir"), dirSys.Uid, dirSys.Gid) checkUidGid(t, path.Join(tmpDir, "dir"), dirSys.Uid, dirSys.Gid)
@@ -105,13 +105,13 @@ func TestDirFS_Lchown(t *testing.T) {
g := g g := g
t.Run(fmt.Sprintf("change to gid %d", g), func(t *testing.T) { t.Run(fmt.Sprintf("change to gid %d", g), func(t *testing.T) {
// Test using our Lchown // Test using our Lchown
require.Zero(t, testFS.Lchown("link", -1, g)) require.EqualErrno(t, 0, testFS.Lchown("link", -1, g))
checkUidGid(t, path.Join(tmpDir, "link"), linkSys.Uid, uint32(g)) checkUidGid(t, path.Join(tmpDir, "link"), linkSys.Uid, uint32(g))
// Make sure the target didn't change. // Make sure the target didn't change.
checkUidGid(t, path.Join(tmpDir, "dir"), dirSys.Uid, dirSys.Gid) checkUidGid(t, path.Join(tmpDir, "dir"), dirSys.Uid, dirSys.Gid)
// Revert back with syscall.Lchown // Revert back
require.NoError(t, syscall.Lchown(path.Join(tmpDir, "link"), -1, gid)) require.EqualErrno(t, 0, testFS.Lchown("link", -1, gid))
checkUidGid(t, path.Join(tmpDir, "link"), linkSys.Uid, uint32(gid)) checkUidGid(t, path.Join(tmpDir, "link"), linkSys.Uid, uint32(gid))
}) })
} }

View File

@@ -41,7 +41,7 @@ func TestReadFS_Lstat(t *testing.T) {
writeable := NewDirFS(tmpDir) writeable := NewDirFS(tmpDir)
for _, path := range []string{"animals.txt", "sub", "sub-link"} { for _, path := range []string{"animals.txt", "sub", "sub-link"} {
require.Zero(t, writeable.Symlink(path, path+"-link")) require.EqualErrno(t, 0, writeable.Symlink(path, path+"-link"))
} }
testFS := NewReadFS(writeable) testFS := NewReadFS(writeable)

View File

@@ -50,7 +50,7 @@ func TestNewRootFS(t *testing.T) {
// Guest can look up /tmp // Guest can look up /tmp
f, errno := rootFS.OpenFile("/tmp", os.O_RDONLY, 0) f, errno := rootFS.OpenFile("/tmp", os.O_RDONLY, 0)
require.EqualErrno(t, 0, errno) require.EqualErrno(t, 0, errno)
require.Zero(t, f.Close()) require.EqualErrno(t, 0, f.Close())
// Guest can look up / and see "/tmp" in it // Guest can look up / and see "/tmp" in it
f, errno = rootFS.OpenFile("/", os.O_RDONLY, 0) f, errno = rootFS.OpenFile("/", os.O_RDONLY, 0)
@@ -283,7 +283,7 @@ func TestRootFS_examples(t *testing.T) {
for _, p := range tc.expected { for _, p := range tc.expected {
f, errno := root.OpenFile(p, os.O_RDONLY, 0) f, errno := root.OpenFile(p, os.O_RDONLY, 0)
require.Zero(t, errno, p) require.Zero(t, errno, p)
require.Zero(t, f.Close(), p) require.EqualErrno(t, 0, f.Close(), p)
} }
for _, p := range tc.unexpected { for _, p := range tc.unexpected {

View File

@@ -36,7 +36,7 @@ func testOpen_O_RDWR(t *testing.T, tmpDir string, testFS FS) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, fileContents, b) require.Equal(t, fileContents, b)
require.Zero(t, f.Close()) require.EqualErrno(t, 0, f.Close())
// re-create as read-only, using 0444 to allow read-back on windows. // re-create as read-only, using 0444 to allow read-back on windows.
require.NoError(t, os.Remove(realPath)) require.NoError(t, os.Remove(realPath))
@@ -194,25 +194,24 @@ shark
dinosaur dinosaur
human human
`) `)
// Ensure it implements io.ReaderAt // Ensure it implements Pread
r, ok := f.File().(io.ReaderAt)
require.True(t, ok)
lenToRead := len(fileContents) - 1 lenToRead := len(fileContents) - 1
buf := make([]byte, lenToRead) buf := make([]byte, lenToRead)
n, err := r.ReadAt(buf, 1) n, errno := f.Pread(buf, 1)
require.NoError(t, err) require.EqualErrno(t, 0, errno)
require.Equal(t, lenToRead, n) require.Equal(t, lenToRead, n)
require.Equal(t, fileContents[1:], buf) require.Equal(t, fileContents[1:], buf)
// Ensure it implements io.Seeker // Ensure it implements Seek
s, ok := f.File().(io.Seeker) offset, errno := f.Seek(1, io.SeekStart)
require.True(t, ok) require.EqualErrno(t, 0, errno)
offset, err := s.Seek(1, io.SeekStart)
require.NoError(t, err)
require.Equal(t, int64(1), offset) require.Equal(t, int64(1), offset)
b, err := io.ReadAll(f.File())
require.NoError(t, err) // Read should pick up from position 1
require.Equal(t, fileContents[1:], b) n, errno = f.Read(buf)
require.EqualErrno(t, 0, errno)
require.Equal(t, lenToRead, n)
require.Equal(t, fileContents[1:], buf)
}) })
// Make sure O_RDONLY isn't treated bitwise as it is usually zero. // Make sure O_RDONLY isn't treated bitwise as it is usually zero.
@@ -228,20 +227,20 @@ human
t.Run("writing to a read-only file is EBADF", func(t *testing.T) { t.Run("writing to a read-only file is EBADF", func(t *testing.T) {
f, errno := testFS.OpenFile("animals.txt", os.O_RDONLY, 0) f, errno := testFS.OpenFile("animals.txt", os.O_RDONLY, 0)
defer require.Zero(t, f.Close()) defer f.Close()
require.EqualErrno(t, 0, errno) require.EqualErrno(t, 0, errno)
_, err := f.Write([]byte{1, 2, 3, 4}) _, errno = f.Write([]byte{1, 2, 3, 4})
require.EqualErrno(t, syscall.EBADF, platform.UnwrapOSError(err)) require.EqualErrno(t, syscall.EBADF, errno)
}) })
t.Run("writing to a directory is EBADF", func(t *testing.T) { t.Run("writing to a directory is EISDIR", func(t *testing.T) {
f, errno := testFS.OpenFile("sub", os.O_RDONLY, 0) f, errno := testFS.OpenFile("sub", os.O_RDONLY, 0)
defer require.Zero(t, f.Close()) defer f.Close()
require.EqualErrno(t, 0, errno) require.EqualErrno(t, 0, errno)
_, err := f.Write([]byte{1, 2, 3, 4}) _, errno = f.Write([]byte{1, 2, 3, 4})
require.EqualErrno(t, syscall.EBADF, platform.UnwrapOSError(err)) require.EqualErrno(t, syscall.EISDIR, errno)
}) })
} }