sysfs: return st instead of accepting it (#1261)

This returns stat as a value instead of a pointer param. This is both
more efficient and faster. It is also more efficient than returning a
pointer to a stat.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
Crypt Keeper
2023-03-21 10:57:19 +08:00
committed by GitHub
parent 57c887113e
commit c46a6eb4ae
24 changed files with 333 additions and 308 deletions

View File

@@ -96,8 +96,8 @@ func fdAllocateFn(_ context.Context, mod api.Module, params []uint64) Errno {
return ErrnoInval return ErrnoInval
} }
var st platform.Stat_t st, err := f.Stat()
if err := f.Stat(&st); err != nil { if err != nil {
return ToErrno(err) return ToErrno(err)
} }
@@ -350,12 +350,12 @@ func fdFilestatGetFunc(mod api.Module, fd, resultBuf uint32) Errno {
return ErrnoBadf return ErrnoBadf
} }
var st platform.Stat_t st, err := f.Stat()
if err := f.Stat(&st); err != nil { if err != nil {
return ToErrno(err) return ToErrno(err)
} }
if err := writeFilestat(buf, &st); err != nil { if err = writeFilestat(buf, &st); err != nil {
return ToErrno(err) return ToErrno(err)
} }
@@ -896,11 +896,11 @@ func dotDirents(f *sys.FileEntry) ([]*platform.Dirent, error) {
} }
dotDotIno := uint64(0) dotDotIno := uint64(0)
if !f.IsPreopen && f.Name != "." { if !f.IsPreopen && f.Name != "." {
var st platform.Stat_t if st, err := f.FS.Stat(path.Dir(f.Name)); err != nil {
if err = f.FS.Stat(path.Dir(f.Name), &st); err != nil {
return nil, err return nil, err
} else {
dotDotIno = st.Ino
} }
dotDotIno = st.Ino
} }
return []*platform.Dirent{ return []*platform.Dirent{
{Name: ".", Ino: dotIno, Type: fs.ModeDir}, {Name: ".", Ino: dotIno, Type: fs.ModeDir},
@@ -1436,9 +1436,9 @@ func pathFilestatGetFn(_ context.Context, mod api.Module, params []uint64) Errno
var err error var err error
if (flags & LOOKUP_SYMLINK_FOLLOW) == 0 { if (flags & LOOKUP_SYMLINK_FOLLOW) == 0 {
err = preopen.Lstat(pathName, &st) st, err = preopen.Lstat(pathName)
} else { } else {
err = preopen.Stat(pathName, &st) st, err = preopen.Stat(pathName)
} }
if err != nil { if err != nil {
return ToErrno(err) return ToErrno(err)
@@ -1451,7 +1451,7 @@ func pathFilestatGetFn(_ context.Context, mod api.Module, params []uint64) Errno
return ErrnoFault return ErrnoFault
} }
if err := writeFilestat(buf, &st); err != nil { if err = writeFilestat(buf, &st); err != nil {
return ToErrno(err) return ToErrno(err)
} }
return ErrnoSuccess return ErrnoSuccess

View File

@@ -63,8 +63,8 @@ func Test_fdAllocate(t *testing.T) {
require.True(t, ok) require.True(t, ok)
requireSizeEqual := func(exp int64) { requireSizeEqual := func(exp int64) {
var st platform.Stat_t st, err := f.Stat()
require.NoError(t, f.Stat(&st)) require.NoError(t, err)
require.Equal(t, exp, st.Size) require.Equal(t, exp, st.Size)
} }
@@ -849,8 +849,8 @@ func Test_fdFilestatSetTimes(t *testing.T) {
f, ok := fsc.LookupFile(fd) f, ok := fsc.LookupFile(fd)
require.True(t, ok) require.True(t, ok)
var st platform.Stat_t st, err := f.Stat()
require.NoError(t, f.Stat(&st)) require.NoError(t, err)
prevAtime, prevMtime := st.Atim, st.Mtim prevAtime, prevMtime := st.Atim, st.Mtim
requireErrnoResult(t, tc.expectedErrno, mod, FdFilestatSetTimesName, requireErrnoResult(t, tc.expectedErrno, mod, FdFilestatSetTimesName,
@@ -862,8 +862,8 @@ func Test_fdFilestatSetTimes(t *testing.T) {
f, ok := fsc.LookupFile(fd) f, ok := fsc.LookupFile(fd)
require.True(t, ok) require.True(t, ok)
var st platform.Stat_t st, err = f.Stat()
require.NoError(t, f.Stat(&st)) require.NoError(t, err)
if tc.flags&FstflagsAtim != 0 { if tc.flags&FstflagsAtim != 0 {
require.Equal(t, tc.atime, st.Atim) require.Equal(t, tc.atime, st.Atim)
} else if tc.flags&FstflagsAtimNow != 0 { } else if tc.flags&FstflagsAtimNow != 0 {
@@ -3551,8 +3551,10 @@ func Test_pathFilestatSetTimes(t *testing.T) {
fsc := sys.FS() fsc := sys.FS()
var oldSt platform.Stat_t var oldSt platform.Stat_t
var err error
if tc.expectedErrno == ErrnoSuccess { if tc.expectedErrno == ErrnoSuccess {
require.NoError(t, fsc.RootFS().Stat(pathName, &oldSt)) oldSt, err = fsc.RootFS().Stat(pathName)
require.NoError(t, err)
} }
requireErrnoResult(t, tc.expectedErrno, mod, PathFilestatSetTimesName, uint64(fd), uint64(tc.flags), requireErrnoResult(t, tc.expectedErrno, mod, PathFilestatSetTimesName, uint64(fd), uint64(tc.flags),
@@ -3563,8 +3565,8 @@ func Test_pathFilestatSetTimes(t *testing.T) {
return return
} }
var newSt platform.Stat_t newSt, err := fsc.RootFS().Stat(pathName)
require.NoError(t, fsc.RootFS().Stat(pathName, &newSt)) require.NoError(t, err)
if platform.CompilerSupported() { if platform.CompilerSupported() {
if tc.fstFlags&FstflagsAtim != 0 { if tc.fstFlags&FstflagsAtim != 0 {
@@ -3639,8 +3641,7 @@ func Test_pathLink(t *testing.T) {
require.NoError(t, f.Close()) require.NoError(t, f.Close())
}() }()
var st platform.Stat_t st, err := platform.StatFile(f)
require.NoError(t, platform.StatFile(f, &st))
require.NoError(t, err) require.NoError(t, err)
require.False(t, st.Mode&os.ModeSymlink == os.ModeSymlink) require.False(t, st.Mode&os.ModeSymlink == os.ModeSymlink)
require.Equal(t, uint64(2), st.Nlink) require.Equal(t, uint64(2), st.Nlink)
@@ -4870,8 +4871,8 @@ func Test_fdReaddir_dotEntriesHaveRealInodes(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
// get the real inode of the current directory // get the real inode of the current directory
var st platform.Stat_t st, err := preopen.Stat(readDirTarget)
require.NoError(t, preopen.Stat(readDirTarget, &st)) require.NoError(t, err)
dirents := []byte{1, 0, 0, 0, 0, 0, 0, 0} // d_next = 1 dirents := []byte{1, 0, 0, 0, 0, 0, 0, 0} // d_next = 1
dirents = append(dirents, u64.LeBytes(st.Ino)...) // d_ino dirents = append(dirents, u64.LeBytes(st.Ino)...) // d_ino
dirents = append(dirents, 1, 0, 0, 0) // d_namlen = 1 character dirents = append(dirents, 1, 0, 0, 0) // d_namlen = 1 character
@@ -4879,7 +4880,8 @@ func Test_fdReaddir_dotEntriesHaveRealInodes(t *testing.T) {
dirents = append(dirents, '.') // name dirents = append(dirents, '.') // name
// get the real inode of the parent directory // get the real inode of the parent directory
require.NoError(t, preopen.Stat(".", &st)) st, err = preopen.Stat(".")
require.NoError(t, err)
dirents = append(dirents, 2, 0, 0, 0, 0, 0, 0, 0) // d_next = 2 dirents = append(dirents, 2, 0, 0, 0, 0, 0, 0, 0) // d_next = 2
dirents = append(dirents, u64.LeBytes(st.Ino)...) // d_ino dirents = append(dirents, u64.LeBytes(st.Ino)...) // d_ino
dirents = append(dirents, 2, 0, 0, 0) // d_namlen = 2 characters dirents = append(dirents, 2, 0, 0, 0) // d_namlen = 2 characters
@@ -4933,8 +4935,8 @@ func Test_fdReaddir_opened_file_written(t *testing.T) {
defer f.Close() defer f.Close()
// get the real inode of the current directory // get the real inode of the current directory
var st platform.Stat_t st, err := preopen.Stat(dirName)
require.NoError(t, preopen.Stat(dirName, &st)) require.NoError(t, err)
dirents := []byte{1, 0, 0, 0, 0, 0, 0, 0} // d_next = 1 dirents := []byte{1, 0, 0, 0, 0, 0, 0, 0} // d_next = 1
dirents = append(dirents, u64.LeBytes(st.Ino)...) // d_ino dirents = append(dirents, u64.LeBytes(st.Ino)...) // d_ino
dirents = append(dirents, 1, 0, 0, 0) // d_namlen = 1 character dirents = append(dirents, 1, 0, 0, 0) // d_namlen = 1 character
@@ -4942,7 +4944,8 @@ func Test_fdReaddir_opened_file_written(t *testing.T) {
dirents = append(dirents, '.') // name dirents = append(dirents, '.') // name
// get the real inode of the parent directory // get the real inode of the parent directory
require.NoError(t, preopen.Stat(".", &st)) st, err = preopen.Stat(".")
require.NoError(t, err)
dirents = append(dirents, 2, 0, 0, 0, 0, 0, 0, 0) // d_next = 2 dirents = append(dirents, 2, 0, 0, 0, 0, 0, 0, 0) // d_next = 2
dirents = append(dirents, u64.LeBytes(st.Ino)...) // d_ino dirents = append(dirents, u64.LeBytes(st.Ino)...) // d_ino
dirents = append(dirents, 2, 0, 0, 0) // d_namlen = 2 characters dirents = append(dirents, 2, 0, 0, 0) // d_namlen = 2 characters
@@ -4950,7 +4953,8 @@ func Test_fdReaddir_opened_file_written(t *testing.T) {
dirents = append(dirents, '.', '.') // name dirents = append(dirents, '.', '.') // name
// get the real inode of the file // get the real inode of the file
require.NoError(t, platform.StatFile(f, &st)) st, err = platform.StatFile(f)
require.NoError(t, err)
dirents = append(dirents, 3, 0, 0, 0, 0, 0, 0, 0) // d_next = 3 dirents = append(dirents, 3, 0, 0, 0, 0, 0, 0, 0) // d_next = 3
dirents = append(dirents, u64.LeBytes(st.Ino)...) // d_ino dirents = append(dirents, u64.LeBytes(st.Ino)...) // d_ino
dirents = append(dirents, 4, 0, 0, 0) // d_namlen = 4 characters dirents = append(dirents, 4, 0, 0, 0) // d_namlen = 4 characters

View File

@@ -100,16 +100,16 @@ func WriteTestFiles(tmpDir string) (err error) {
} }
// os.Stat uses GetFileInformationByHandle internally. // os.Stat uses GetFileInformationByHandle internally.
var stat platform.Stat_t var st platform.Stat_t
if err = platform.Stat(path, &stat); err != nil { if st, err = platform.Stat(path); err != nil {
return err return err
} }
if stat.Mtim == info.ModTime().UnixNano() { if st.Mtim == info.ModTime().UnixNano() {
return nil // synced! return nil // synced!
} }
// Otherwise, we need to sync the timestamps. // Otherwise, we need to sync the timestamps.
return os.Chtimes(path, time.Unix(0, stat.Atim), time.Unix(0, stat.Mtim)) return os.Chtimes(path, time.Unix(0, st.Atim), time.Unix(0, st.Mtim))
}) })
} }
return return

View File

@@ -134,11 +134,11 @@ func (s *jsfsStat) invoke(ctx context.Context, mod api.Module, args ...interface
func syscallStat(mod api.Module, path string) (*jsSt, error) { func syscallStat(mod api.Module, path string) (*jsSt, error) {
fsc := mod.(*wasm.CallContext).Sys.FS() fsc := mod.(*wasm.CallContext).Sys.FS()
var stat platform.Stat_t if st, err := fsc.RootFS().Stat(path); err != nil {
if err := fsc.RootFS().Stat(path, &stat); err != nil {
return nil, err return nil, err
} else {
return newJsSt(st), nil
} }
return newJsSt(&stat), nil
} }
// jsfsLstat implements jsFn for syscall.Lstat // jsfsLstat implements jsFn for syscall.Lstat
@@ -161,11 +161,11 @@ func (l *jsfsLstat) invoke(ctx context.Context, mod api.Module, args ...interfac
func syscallLstat(mod api.Module, path string) (*jsSt, error) { func syscallLstat(mod api.Module, path string) (*jsSt, error) {
fsc := mod.(*wasm.CallContext).Sys.FS() fsc := mod.(*wasm.CallContext).Sys.FS()
var stat platform.Stat_t if st, err := fsc.RootFS().Lstat(path); err != nil {
if err := fsc.RootFS().Lstat(path, &stat); err != nil {
return nil, err return nil, err
} else {
return newJsSt(st), nil
} }
return newJsSt(&stat), nil
} }
// jsfsFstat implements jsFn for syscall.Open // jsfsFstat implements jsFn for syscall.Open
@@ -190,14 +190,14 @@ func syscallFstat(fsc *internalsys.FSContext, fd uint32) (*jsSt, error) {
return nil, syscall.EBADF return nil, syscall.EBADF
} }
var st platform.Stat_t if st, err := f.Stat(); err != nil {
if err := f.Stat(&st); err != nil {
return nil, err return nil, err
} else {
return newJsSt(st), nil
} }
return newJsSt(&st), nil
} }
func newJsSt(st *platform.Stat_t) *jsSt { func newJsSt(st platform.Stat_t) *jsSt {
ret := &jsSt{} ret := &jsSt{}
ret.isDir = st.Mode.IsDir() ret.isDir = st.Mode.IsDir()
ret.dev = st.Dev ret.dev = st.Dev

View File

@@ -122,7 +122,7 @@ func normalizeTimespec(path string, times *[2]syscall.Timespec, i int) (ts sysca
// - https://github.com/golang/go/issues/32558. // - https://github.com/golang/go/issues/32558.
// - https://go-review.googlesource.com/c/go/+/219638 (unmerged) // - https://go-review.googlesource.com/c/go/+/219638 (unmerged)
var st Stat_t var st Stat_t
if err = stat(path, &st); err != nil { if st, err = stat(path); err != nil {
return return
} }
switch i { switch i {

View File

@@ -146,8 +146,8 @@ func testFutimens(t *testing.T, usePath bool) {
panic(tc) panic(tc)
} }
var oldSt Stat_t oldSt, err := Lstat(statPath)
require.NoError(t, Lstat(statPath, &oldSt)) require.NoError(t, err)
if usePath { if usePath {
err = Utimens(path, tc.times, !symlinkNoFollow) err = Utimens(path, tc.times, !symlinkNoFollow)
@@ -172,8 +172,8 @@ func testFutimens(t *testing.T, usePath bool) {
require.NoError(t, err) require.NoError(t, err)
} }
var newSt Stat_t newSt, err := Lstat(statPath)
require.NoError(t, Lstat(statPath, &newSt)) require.NoError(t, err)
if CompilerSupported() { if CompilerSupported() {
if tc.times != nil && tc.times[0].Nsec == UTIME_OMIT { if tc.times != nil && tc.times[0].Nsec == UTIME_OMIT {

View File

@@ -58,38 +58,39 @@ type Stat_t struct {
// The primary difference between this and Stat is, when the path is a // The primary difference between this and Stat is, when the path is a
// symbolic link, the stat is about the link, not its target, such as directory // symbolic link, the stat is about the link, not its target, such as directory
// listings. // listings.
func Lstat(path string, st *Stat_t) error { func Lstat(path string) (Stat_t, error) {
err := lstat(path, st) // extracted to override more expensively in windows st, err := lstat(path) // extracted to override more expensively in windows
return UnwrapOSError(err) return st, UnwrapOSError(err)
} }
// Stat is like syscall.Stat. This returns syscall.ENOENT if the path doesn't // Stat is like syscall.Stat. This returns syscall.ENOENT if the path doesn't
// exist. // exist.
func Stat(path string, st *Stat_t) error { func Stat(path string) (Stat_t, error) {
err := stat(path, st) // extracted to override more expensively in windows st, err := stat(path) // extracted to override more expensively in windows
return UnwrapOSError(err) return st, UnwrapOSError(err)
} }
// StatFile is like syscall.Fstat, but for fs.File instead of a file // StatFile is like syscall.Fstat, but for fs.File instead of a file
// descriptor. This returns syscall.EBADF if the file or directory was closed. // descriptor. This returns syscall.EBADF if the file or directory was closed.
// Note: windows allows you to stat a closed directory. // Note: windows allows you to stat a closed directory.
func StatFile(f fs.File, st *Stat_t) (err error) { func StatFile(f fs.File) (Stat_t, error) {
err = statFile(f, st) st, err := statFile(f)
if err = UnwrapOSError(err); err == syscall.EIO { if err = UnwrapOSError(err); err == syscall.EIO {
err = syscall.EBADF err = syscall.EBADF
} }
return return st, err
} }
func defaultStatFile(f fs.File, st *Stat_t) (err error) { func defaultStatFile(f fs.File) (Stat_t, error) {
var t fs.FileInfo if t, err := f.Stat(); err != nil {
if t, err = f.Stat(); err == nil { return Stat_t{}, err
fillStatFromFileInfo(st, t) } else {
return statFromFileInfo(t), nil
} }
return
} }
func fillStatFromDefaultFileInfo(st *Stat_t, t fs.FileInfo) { func statFromDefaultFileInfo(t fs.FileInfo) Stat_t {
st := Stat_t{}
st.Ino = 0 st.Ino = 0
st.Dev = 0 st.Dev = 0
st.Mode = t.Mode() st.Mode = t.Mode()
@@ -99,4 +100,5 @@ func fillStatFromDefaultFileInfo(st *Stat_t, t fs.FileInfo) {
st.Atim = mtim st.Atim = mtim
st.Mtim = mtim st.Mtim = mtim
st.Ctim = mtim st.Ctim = mtim
return st
} }

View File

@@ -8,24 +8,24 @@ import (
"syscall" "syscall"
) )
func lstat(path string, st *Stat_t) (err error) { func lstat(path string) (Stat_t, error) {
var t fs.FileInfo if t, err := os.Lstat(path); err != nil {
if t, err = os.Lstat(path); err == nil { return Stat_t{}, err
fillStatFromFileInfo(st, t) } else {
return statFromFileInfo(t), nil
} }
return
} }
func stat(path string, st *Stat_t) (err error) { func stat(path string) (Stat_t, error) {
var t fs.FileInfo if t, err := os.Stat(path); err != nil {
if t, err = os.Stat(path); err == nil { return Stat_t{}, err
fillStatFromFileInfo(st, t) } else {
return statFromFileInfo(t), nil
} }
return
} }
func statFile(f fs.File, st *Stat_t) error { func statFile(f fs.File) (Stat_t, error) {
return defaultStatFile(f, st) return defaultStatFile(f)
} }
func inoFromFileInfo(_ readdirFile, t fs.FileInfo) (ino uint64, err error) { func inoFromFileInfo(_ readdirFile, t fs.FileInfo) (ino uint64, err error) {
@@ -35,8 +35,9 @@ func inoFromFileInfo(_ readdirFile, t fs.FileInfo) (ino uint64, err error) {
return return
} }
func fillStatFromFileInfo(st *Stat_t, t fs.FileInfo) { func statFromFileInfo(t fs.FileInfo) Stat_t {
if d, ok := t.Sys().(*syscall.Stat_t); ok { if d, ok := t.Sys().(*syscall.Stat_t); ok {
st := Stat_t{}
st.Dev = uint64(d.Dev) st.Dev = uint64(d.Dev)
st.Ino = d.Ino st.Ino = d.Ino
st.Uid = d.Uid st.Uid = d.Uid
@@ -50,7 +51,7 @@ func fillStatFromFileInfo(st *Stat_t, t fs.FileInfo) {
st.Mtim = mtime.Sec*1e9 + mtime.Nsec st.Mtim = mtime.Sec*1e9 + mtime.Nsec
ctime := d.Ctimespec ctime := d.Ctimespec
st.Ctim = ctime.Sec*1e9 + ctime.Nsec st.Ctim = ctime.Sec*1e9 + ctime.Nsec
} else { return st
fillStatFromDefaultFileInfo(st, t)
} }
return statFromDefaultFileInfo(t)
} }

View File

@@ -11,35 +11,36 @@ import (
"syscall" "syscall"
) )
func lstat(path string, st *Stat_t) (err error) { func lstat(path string) (Stat_t, error) {
var t fs.FileInfo if t, err := os.Lstat(path); err != nil {
if t, err = os.Lstat(path); err == nil { return Stat_t{}, err
fillStatFromFileInfo(st, t) } else {
return statFromFileInfo(t), nil
} }
return
} }
func stat(path string, st *Stat_t) (err error) { func stat(path string) (Stat_t, error) {
var t fs.FileInfo if t, err := os.Stat(path); err != nil {
if t, err = os.Stat(path); err == nil { return Stat_t{}, err
fillStatFromFileInfo(st, t) } else {
return statFromFileInfo(t), nil
} }
return
} }
func statFile(f fs.File, st *Stat_t) error { func statFile(f fs.File) (Stat_t, error) {
return defaultStatFile(f, st) return defaultStatFile(f)
} }
func inoFromFileInfo(_ readdirFile, t fs.FileInfo) (ino uint64, err error) { func inoFromFileInfo(_ readdirFile, t fs.FileInfo) (ino uint64, err error) {
if d, ok := t.Sys().(*syscall.Stat_t); ok { if d, ok := t.Sys().(*syscall.Stat_t); ok {
ino = (d.Ino) ino = d.Ino
} }
return return
} }
func fillStatFromFileInfo(st *Stat_t, t fs.FileInfo) { func statFromFileInfo(t fs.FileInfo) Stat_t {
if d, ok := t.Sys().(*syscall.Stat_t); ok { if d, ok := t.Sys().(*syscall.Stat_t); ok {
st := Stat_t{}
st.Dev = uint64(d.Dev) st.Dev = uint64(d.Dev)
st.Ino = uint64(d.Ino) st.Ino = uint64(d.Ino)
st.Uid = d.Uid st.Uid = d.Uid
@@ -53,7 +54,7 @@ func fillStatFromFileInfo(st *Stat_t, t fs.FileInfo) {
st.Mtim = mtime.Sec*1e9 + mtime.Nsec st.Mtim = mtime.Sec*1e9 + mtime.Nsec
ctime := d.Ctim ctime := d.Ctim
st.Ctim = ctime.Sec*1e9 + ctime.Nsec st.Ctim = ctime.Sec*1e9 + ctime.Nsec
} else { return st
fillStatFromDefaultFileInfo(st, t)
} }
return statFromDefaultFileInfo(t)
} }

View File

@@ -15,139 +15,152 @@ import (
func TestLstat(t *testing.T) { func TestLstat(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
var stat Stat_t _, err := Lstat(path.Join(tmpDir, "cat"))
require.EqualErrno(t, syscall.ENOENT, Lstat(path.Join(tmpDir, "cat"), &stat)) require.EqualErrno(t, syscall.ENOENT, err)
require.EqualErrno(t, syscall.ENOENT, Lstat(path.Join(tmpDir, "sub/cat"), &stat)) _, err = Lstat(path.Join(tmpDir, "sub/cat"))
require.EqualErrno(t, syscall.ENOENT, err)
var st Stat_t
t.Run("dir", func(t *testing.T) { t.Run("dir", func(t *testing.T) {
err := Lstat(tmpDir, &stat) st, err = Lstat(tmpDir)
require.NoError(t, err) require.NoError(t, err)
require.True(t, stat.Mode.IsDir()) require.True(t, st.Mode.IsDir())
require.NotEqual(t, uint64(0), stat.Ino) require.NotEqual(t, uint64(0), st.Ino)
}) })
file := path.Join(tmpDir, "file") file := path.Join(tmpDir, "file")
var statFile Stat_t var stFile Stat_t
t.Run("file", func(t *testing.T) { t.Run("file", func(t *testing.T) {
require.NoError(t, os.WriteFile(file, []byte{1, 2}, 0o400)) require.NoError(t, os.WriteFile(file, []byte{1, 2}, 0o400))
require.NoError(t, Lstat(file, &statFile)) stFile, err = Lstat(file)
require.Zero(t, statFile.Mode.Type()) require.NoError(t, err)
require.Equal(t, int64(2), statFile.Size) require.Zero(t, stFile.Mode.Type())
require.NotEqual(t, uint64(0), statFile.Ino) require.Equal(t, int64(2), stFile.Size)
require.NotEqual(t, uint64(0), stFile.Ino)
}) })
t.Run("link to file", func(t *testing.T) { t.Run("link to file", func(t *testing.T) {
requireLinkStat(t, file, &statFile) requireLinkStat(t, file, stFile)
}) })
subdir := path.Join(tmpDir, "sub") subdir := path.Join(tmpDir, "sub")
var statSubdir Stat_t var stSubdir Stat_t
t.Run("subdir", func(t *testing.T) { t.Run("subdir", func(t *testing.T) {
require.NoError(t, os.Mkdir(subdir, 0o500)) require.NoError(t, os.Mkdir(subdir, 0o500))
require.NoError(t, Lstat(subdir, &statSubdir)) stSubdir, err = Lstat(subdir)
require.True(t, statSubdir.Mode.IsDir()) require.NoError(t, err)
require.NotEqual(t, uint64(0), statSubdir.Ino) require.True(t, stSubdir.Mode.IsDir())
require.NotEqual(t, uint64(0), stSubdir.Ino)
}) })
t.Run("link to dir", func(t *testing.T) { t.Run("link to dir", func(t *testing.T) {
requireLinkStat(t, subdir, &statSubdir) requireLinkStat(t, subdir, stSubdir)
}) })
t.Run("link to dir link", func(t *testing.T) { t.Run("link to dir link", func(t *testing.T) {
pathLink := subdir + "-link" pathLink := subdir + "-link"
var statLink Stat_t stLink, err := Lstat(pathLink)
require.NoError(t, Lstat(pathLink, &statLink)) require.NoError(t, err)
requireLinkStat(t, pathLink, &statLink) requireLinkStat(t, pathLink, stLink)
}) })
} }
func requireLinkStat(t *testing.T, path string, stat *Stat_t) { func requireLinkStat(t *testing.T, path string, stat Stat_t) {
link := path + "-link" link := path + "-link"
var linkStat Stat_t
require.NoError(t, os.Symlink(path, link)) require.NoError(t, os.Symlink(path, link))
require.NoError(t, Lstat(link, &linkStat)) stLink, err := Lstat(link)
require.NotEqual(t, uint64(0), linkStat.Ino) require.NoError(t, err)
require.NotEqual(t, stat.Ino, linkStat.Ino) // inodes are not equal
require.Equal(t, fs.ModeSymlink, linkStat.Mode.Type()) require.NotEqual(t, uint64(0), stLink.Ino)
require.NotEqual(t, stat.Ino, stLink.Ino) // inodes are not equal
require.Equal(t, fs.ModeSymlink, stLink.Mode.Type())
// From https://linux.die.net/man/2/lstat: // From https://linux.die.net/man/2/lstat:
// The size of a symbolic link is the length of the pathname it // The size of a symbolic link is the length of the pathname it
// contains, without a terminating null byte. // contains, without a terminating null byte.
if runtime.GOOS == "windows" { // size is zero, not the path length if runtime.GOOS == "windows" { // size is zero, not the path length
require.Zero(t, linkStat.Size) require.Zero(t, stLink.Size)
} else { } else {
require.Equal(t, int64(len(path)), linkStat.Size) require.Equal(t, int64(len(path)), stLink.Size)
} }
} }
func TestStat(t *testing.T) { func TestStat(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
var stat Stat_t _, err := Stat(path.Join(tmpDir, "cat"))
require.EqualErrno(t, syscall.ENOENT, Stat(path.Join(tmpDir, "cat"), &stat)) require.EqualErrno(t, syscall.ENOENT, err)
require.EqualErrno(t, syscall.ENOENT, Stat(path.Join(tmpDir, "sub/cat"), &stat)) _, err = Stat(path.Join(tmpDir, "sub/cat"))
require.EqualErrno(t, syscall.ENOENT, err)
var st Stat_t
t.Run("dir", func(t *testing.T) { t.Run("dir", func(t *testing.T) {
err := Stat(tmpDir, &stat) st, err = Stat(tmpDir)
require.NoError(t, err) require.NoError(t, err)
require.True(t, stat.Mode.IsDir()) require.True(t, st.Mode.IsDir())
require.NotEqual(t, uint64(0), stat.Ino) require.NotEqual(t, uint64(0), st.Ino)
}) })
file := path.Join(tmpDir, "file") file := path.Join(tmpDir, "file")
var statFile Stat_t var stFile Stat_t
t.Run("file", func(t *testing.T) { t.Run("file", func(t *testing.T) {
require.NoError(t, os.WriteFile(file, nil, 0o400)) require.NoError(t, os.WriteFile(file, nil, 0o400))
require.NoError(t, Stat(file, &statFile))
require.False(t, statFile.Mode.IsDir()) stFile, err = Stat(file)
require.NotEqual(t, uint64(0), stat.Ino) require.NoError(t, err)
require.False(t, stFile.Mode.IsDir())
require.NotEqual(t, uint64(0), st.Ino)
}) })
t.Run("link to file", func(t *testing.T) { t.Run("link to file", func(t *testing.T) {
link := path.Join(tmpDir, "file-link") link := path.Join(tmpDir, "file-link")
require.NoError(t, os.Symlink(file, link)) require.NoError(t, os.Symlink(file, link))
require.NoError(t, Stat(link, &stat)) stLink, err := Stat(link)
require.Equal(t, statFile, stat) // resolves to the file require.NoError(t, err)
require.Equal(t, stFile, stLink) // resolves to the file
}) })
subdir := path.Join(tmpDir, "sub") subdir := path.Join(tmpDir, "sub")
var statSubdir Stat_t var stSubdir Stat_t
t.Run("subdir", func(t *testing.T) { t.Run("subdir", func(t *testing.T) {
require.NoError(t, os.Mkdir(subdir, 0o500)) require.NoError(t, os.Mkdir(subdir, 0o500))
require.NoError(t, Stat(subdir, &statSubdir)) stSubdir, err = Stat(subdir)
require.True(t, statSubdir.Mode.IsDir()) require.NoError(t, err)
require.NotEqual(t, uint64(0), stat.Ino) require.True(t, stSubdir.Mode.IsDir())
require.NotEqual(t, uint64(0), st.Ino)
}) })
t.Run("link to dir", func(t *testing.T) { t.Run("link to dir", func(t *testing.T) {
link := path.Join(tmpDir, "dir-link") link := path.Join(tmpDir, "dir-link")
require.NoError(t, os.Symlink(subdir, link)) require.NoError(t, os.Symlink(subdir, link))
require.NoError(t, Stat(link, &stat)) stLink, err := Stat(link)
require.Equal(t, statSubdir, stat) // resolves to the dir require.NoError(t, err)
require.Equal(t, stSubdir, stLink) // resolves to the dir
}) })
} }
func TestStatFile(t *testing.T) { func TestStatFile(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
var stat Stat_t var st Stat_t
tmpDirF, err := OpenFile(tmpDir, syscall.O_RDONLY, 0) tmpDirF, err := OpenFile(tmpDir, syscall.O_RDONLY, 0)
require.NoError(t, err) require.NoError(t, err)
defer tmpDirF.Close() defer tmpDirF.Close()
t.Run("dir", func(t *testing.T) { t.Run("dir", func(t *testing.T) {
err = StatFile(tmpDirF, &stat) st, err = StatFile(tmpDirF)
require.NoError(t, err) require.NoError(t, err)
require.True(t, stat.Mode.IsDir()) require.True(t, st.Mode.IsDir())
requireDirectoryDevIno(t, stat) requireDirectoryDevIno(t, st)
}) })
// Windows allows you to stat a closed dir because it is accessed by path, // Windows allows you to stat a closed dir because it is accessed by path,
@@ -155,7 +168,8 @@ func TestStatFile(t *testing.T) {
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.NoError(t, tmpDirF.Close()) require.NoError(t, tmpDirF.Close())
require.EqualErrno(t, syscall.EBADF, StatFile(tmpDirF, &stat)) st, err = StatFile(tmpDirF)
require.EqualErrno(t, syscall.EBADF, err)
}) })
} }
@@ -166,16 +180,16 @@ func TestStatFile(t *testing.T) {
defer fileF.Close() defer fileF.Close()
t.Run("file", func(t *testing.T) { t.Run("file", func(t *testing.T) {
err = StatFile(fileF, &stat) st, err = StatFile(fileF)
require.NoError(t, err) require.NoError(t, err)
require.False(t, stat.Mode.IsDir()) require.False(t, st.Mode.IsDir())
require.NotEqual(t, uint64(0), stat.Ino) require.NotEqual(t, uint64(0), st.Ino)
}) })
t.Run("closed file", func(t *testing.T) { t.Run("closed file", func(t *testing.T) {
require.NoError(t, fileF.Close()) require.NoError(t, fileF.Close())
require.EqualErrno(t, syscall.EBADF, StatFile(fileF, &stat)) _, err = StatFile(fileF)
require.NotEqual(t, uint64(0), stat.Ino) require.EqualErrno(t, syscall.EBADF, err)
}) })
subdir := path.Join(tmpDir, "sub") subdir := path.Join(tmpDir, "sub")
@@ -185,16 +199,17 @@ func TestStatFile(t *testing.T) {
defer subdirF.Close() defer subdirF.Close()
t.Run("subdir", func(t *testing.T) { t.Run("subdir", func(t *testing.T) {
err = StatFile(subdirF, &stat) st, err = StatFile(subdirF)
require.NoError(t, err) require.NoError(t, err)
require.True(t, stat.Mode.IsDir()) require.True(t, st.Mode.IsDir())
requireDirectoryDevIno(t, stat) requireDirectoryDevIno(t, st)
}) })
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.NoError(t, subdirF.Close()) require.NoError(t, subdirF.Close())
require.EqualErrno(t, syscall.EBADF, StatFile(subdirF, &stat)) st, err = StatFile(subdirF)
require.EqualErrno(t, syscall.EBADF, err)
}) })
} }
} }
@@ -238,14 +253,14 @@ func Test_StatFile_times(t *testing.T) {
err := os.Chtimes(file, time.UnixMicro(tc.atimeNsec/1e3), time.UnixMicro(tc.mtimeNsec/1e3)) err := os.Chtimes(file, time.UnixMicro(tc.atimeNsec/1e3), time.UnixMicro(tc.mtimeNsec/1e3))
require.NoError(t, err) require.NoError(t, err)
file, err := os.Open(file) f, err := os.Open(file)
require.NoError(t, err) require.NoError(t, err)
defer file.Close() defer f.Close()
var stat Stat_t st, err := StatFile(f)
require.NoError(t, StatFile(file, &stat)) require.NoError(t, err)
require.Equal(t, stat.Atim, tc.atimeNsec) require.Equal(t, st.Atim, tc.atimeNsec)
require.Equal(t, stat.Mtim, tc.mtimeNsec) require.Equal(t, st.Mtim, tc.mtimeNsec)
}) })
} }
} }
@@ -274,28 +289,29 @@ func TestStatFile_dev_inode(t *testing.T) {
defer l2.Close() defer l2.Close()
// First, stat the directory // First, stat the directory
var stat1 Stat_t st1, err := StatFile(d)
require.NoError(t, StatFile(d, &stat1)) require.NoError(t, err)
requireDirectoryDevIno(t, stat1) requireDirectoryDevIno(t, st1)
// Now, stat the files in it // Now, stat the files in it
require.NoError(t, StatFile(f1, &stat1)) st1, err = StatFile(f1)
require.NoError(t, err)
var stat2 Stat_t st2, err := StatFile(f2)
require.NoError(t, StatFile(f2, &stat2)) require.NoError(t, err)
var stat3 Stat_t st3, err := StatFile(l2)
require.NoError(t, StatFile(l2, &stat3)) require.NoError(t, err)
// The files should be on the same device, but different inodes // The files should be on the same device, but different inodes
require.Equal(t, stat1.Dev, stat2.Dev) require.Equal(t, st1.Dev, st2.Dev)
require.NotEqual(t, stat1.Ino, stat2.Ino) require.NotEqual(t, st1.Ino, st2.Ino)
require.Equal(t, stat2, stat3) // stat on a link is for its target require.Equal(t, st2, st3) // stat on a link is for its target
// Redoing stat should result in the same inodes // Redoing stat should result in the same inodes
var stat1Again Stat_t st1Again, err := StatFile(f1)
require.NoError(t, StatFile(f1, &stat1Again)) require.NoError(t, err)
require.Equal(t, stat1.Dev, stat1Again.Dev) require.Equal(t, st1.Dev, st1Again.Dev)
// 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.
@@ -309,9 +325,10 @@ func TestStatFile_dev_inode(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer f1.Close() defer f1.Close()
require.NoError(t, StatFile(f1, &stat1Again)) st1Again, err = StatFile(f1)
require.Equal(t, stat1.Dev, stat1Again.Dev) require.NoError(t, err)
require.Equal(t, stat1.Ino, stat1Again.Ino) require.Equal(t, st1.Dev, st1Again.Dev)
require.Equal(t, st1.Ino, st1Again.Ino)
} }
func requireDirectoryDevIno(t *testing.T, st Stat_t) { func requireDirectoryDevIno(t *testing.T, st Stat_t) {
@@ -344,8 +361,8 @@ func TestStat_uid_gid(t *testing.T) {
require.NoError(t, os.Mkdir(dir, 0o0700)) require.NoError(t, os.Mkdir(dir, 0o0700))
require.NoError(t, chgid(dir, gid)) require.NoError(t, chgid(dir, gid))
var st Stat_t st, err := Stat(dir)
require.NoError(t, Stat(dir, &st)) require.NoError(t, err)
require.Equal(t, uid, st.Uid) require.Equal(t, uid, st.Uid)
require.Equal(t, gid, st.Gid) require.Equal(t, gid, st.Gid)
}) })
@@ -356,8 +373,8 @@ func TestStat_uid_gid(t *testing.T) {
require.NoError(t, os.Symlink(tmpDir, link)) require.NoError(t, os.Symlink(tmpDir, link))
require.NoError(t, chgid(link, gid)) require.NoError(t, chgid(link, gid))
var st Stat_t st, err := Lstat(link)
require.NoError(t, Lstat(link, &st)) require.NoError(t, err)
require.Equal(t, uid, st.Uid) require.Equal(t, uid, st.Uid)
require.Equal(t, gid, st.Gid) require.Equal(t, gid, st.Gid)
}) })
@@ -368,8 +385,8 @@ func TestStat_uid_gid(t *testing.T) {
require.NoError(t, os.WriteFile(file, nil, 0o0600)) require.NoError(t, os.WriteFile(file, nil, 0o0600))
require.NoError(t, chgid(file, gid)) require.NoError(t, chgid(file, gid))
var st Stat_t st, err := Lstat(file)
require.NoError(t, Lstat(file, &st)) require.NoError(t, err)
require.Equal(t, uid, st.Uid) require.Equal(t, uid, st.Uid)
require.Equal(t, gid, st.Gid) require.Equal(t, gid, st.Gid)
}) })

View File

@@ -7,35 +7,30 @@ import (
"os" "os"
) )
func lstat(path string, st *Stat_t) (err error) { func lstat(path string) (Stat_t, error) {
t, err := os.Lstat(path) t, err := os.Lstat(path)
if err = UnwrapOSError(err); err == nil { if err = UnwrapOSError(err); err != nil {
fillStatFromFileInfo(st, t) return statFromFileInfo(t), nil
} }
return return Stat_t{}, err
} }
func stat(path string, st *Stat_t) (err error) { func stat(path string) (Stat_t, error) {
t, err := os.Stat(path) t, err := os.Stat(path)
if err = UnwrapOSError(err); err == nil { if err = UnwrapOSError(err); err == nil {
fillStatFromFileInfo(st, t) return statFromFileInfo(t), nil
} }
return return Stat_t{}, err
} }
func statFile(f fs.File, st *Stat_t) error { func statFile(f fs.File) (Stat_t, error) {
return defaultStatFile(f, st) return defaultStatFile(f)
} }
func inoFromFileInfo(readdirFile, fs.FileInfo) (ino uint64, err error) { func inoFromFileInfo(readdirFile, fs.FileInfo) (ino uint64, err error) {
return return
} }
func fillStatFromFileInfo(st *Stat_t, t fs.FileInfo) { func statFromFileInfo(t fs.FileInfo) Stat_t {
fillStatFromDefaultFileInfo(st, t) return statFromDefaultFileInfo(t)
}
func fillStatFromOpenFile(st *Stat_t, fd uintptr, t os.FileInfo) (err error) {
fillStatFromFileInfo(st, t)
return
} }

View File

@@ -8,26 +8,26 @@ import (
"syscall" "syscall"
) )
func lstat(path string, st *Stat_t) error { func lstat(path string) (Stat_t, error) {
attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS) attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS)
// Use FILE_FLAG_OPEN_REPARSE_POINT, otherwise CreateFile will follow symlink. // Use FILE_FLAG_OPEN_REPARSE_POINT, otherwise CreateFile will follow symlink.
// See https://docs.microsoft.com/en-us/windows/desktop/FileIO/symbolic-link-effects-on-file-systems-functions#createfile-and-createfiletransacted // See https://docs.microsoft.com/en-us/windows/desktop/FileIO/symbolic-link-effects-on-file-systems-functions#createfile-and-createfiletransacted
attrs |= syscall.FILE_FLAG_OPEN_REPARSE_POINT attrs |= syscall.FILE_FLAG_OPEN_REPARSE_POINT
return statPath(attrs, path, st) return statPath(attrs, path)
} }
func stat(path string, st *Stat_t) error { func stat(path string) (Stat_t, error) {
attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS) attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS)
return statPath(attrs, path, st) return statPath(attrs, path)
} }
func statPath(createFileAttrs uint32, path string, st *Stat_t) (err error) { func statPath(createFileAttrs uint32, path string) (Stat_t, error) {
if len(path) == 0 { if len(path) == 0 {
return syscall.ENOENT return Stat_t{}, syscall.ENOENT
} }
pathp, err := syscall.UTF16PtrFromString(path) pathp, err := syscall.UTF16PtrFromString(path)
if err != nil { if err != nil {
return syscall.EINVAL return Stat_t{}, syscall.EINVAL
} }
// open the file handle // open the file handle
@@ -39,41 +39,41 @@ func statPath(createFileAttrs uint32, path string, st *Stat_t) (err error) {
if err == syscall.ENOTDIR { if err == syscall.ENOTDIR {
err = syscall.ENOENT err = syscall.ENOENT
} }
return err return Stat_t{}, err
} }
defer syscall.CloseHandle(h) defer syscall.CloseHandle(h)
return statHandle(h, st) return statHandle(h)
} }
func statFile(f fs.File, st *Stat_t) (err error) { func statFile(f fs.File) (Stat_t, error) {
if of, ok := f.(fdFile); ok { if of, ok := f.(fdFile); ok {
// Attempt to get the stat by handle, which works for normal files // Attempt to get the stat by handle, which works for normal files
err = statHandle(syscall.Handle(of.Fd()), st) st, err := statHandle(syscall.Handle(of.Fd()))
// ERROR_INVALID_HANDLE happens before Go 1.20. Don't fail as we only // ERROR_INVALID_HANDLE happens before Go 1.20. Don't fail as we only
// use that approach to fill in inode data, which is not critical. // use that approach to fill in inode data, which is not critical.
if err != ERROR_INVALID_HANDLE { if err != ERROR_INVALID_HANDLE {
return return st, err
} }
} }
return defaultStatFile(f, st) return defaultStatFile(f)
} }
// inoFromFileInfo uses stat to get the inode information of the file. // inoFromFileInfo uses stat to get the inode information of the file.
func inoFromFileInfo(f readdirFile, t fs.FileInfo) (ino uint64, err error) { func inoFromFileInfo(f readdirFile, t fs.FileInfo) (ino uint64, err error) {
if pf, ok := f.(PathFile); ok { if pf, ok := f.(PathFile); ok {
var st Stat_t
inoPath := path.Clean(path.Join(pf.Path(), t.Name())) inoPath := path.Clean(path.Join(pf.Path(), t.Name()))
if err = lstat(inoPath, &st); err == nil { if st, err := lstat(inoPath); err == nil {
ino = st.Ino ino = st.Ino
} }
} }
return // not in Win32FileAttributeData return // not in Win32FileAttributeData
} }
func fillStatFromFileInfo(st *Stat_t, t fs.FileInfo) { func statFromFileInfo(t fs.FileInfo) Stat_t {
if d, ok := t.Sys().(*syscall.Win32FileAttributeData); ok { if d, ok := t.Sys().(*syscall.Win32FileAttributeData); ok {
st := Stat_t{}
st.Ino = 0 // not in Win32FileAttributeData st.Ino = 0 // not in Win32FileAttributeData
st.Dev = 0 // not in Win32FileAttributeData st.Dev = 0 // not in Win32FileAttributeData
st.Mode = t.Mode() st.Mode = t.Mode()
@@ -82,20 +82,21 @@ func fillStatFromFileInfo(st *Stat_t, t fs.FileInfo) {
st.Atim = d.LastAccessTime.Nanoseconds() st.Atim = d.LastAccessTime.Nanoseconds()
st.Mtim = d.LastWriteTime.Nanoseconds() st.Mtim = d.LastWriteTime.Nanoseconds()
st.Ctim = d.CreationTime.Nanoseconds() st.Ctim = d.CreationTime.Nanoseconds()
return st
} else { } else {
fillStatFromDefaultFileInfo(st, t) return statFromDefaultFileInfo(t)
} }
} }
func statHandle(h syscall.Handle, st *Stat_t) (err error) { func statHandle(h syscall.Handle) (Stat_t, error) {
winFt, err := syscall.GetFileType(h) winFt, err := syscall.GetFileType(h)
if err != nil { if err != nil {
return err return Stat_t{}, err
} }
var fi syscall.ByHandleFileInformation var fi syscall.ByHandleFileInformation
if err = syscall.GetFileInformationByHandle(h, &fi); err != nil { if err = syscall.GetFileInformationByHandle(h, &fi); err != nil {
return err return Stat_t{}, err
} }
var m fs.FileMode var m fs.FileMode
@@ -116,6 +117,7 @@ func statHandle(h syscall.Handle, st *Stat_t) (err error) {
m |= fs.ModeDir | 0o111 // e.g. 0o444 -> 0o555 m |= fs.ModeDir | 0o111 // e.g. 0o444 -> 0o555
} }
st := Stat_t{}
// FileIndex{High,Low} can be combined and used as a unique identifier like inode. // FileIndex{High,Low} can be combined and used as a unique identifier like inode.
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/ns-fileapi-by_handle_file_information // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/ns-fileapi-by_handle_file_information
st.Dev = uint64(fi.VolumeSerialNumber) st.Dev = uint64(fi.VolumeSerialNumber)
@@ -126,5 +128,5 @@ func statHandle(h syscall.Handle, st *Stat_t) (err error) {
st.Atim = fi.LastAccessTime.Nanoseconds() st.Atim = fi.LastAccessTime.Nanoseconds()
st.Mtim = fi.LastWriteTime.Nanoseconds() st.Mtim = fi.LastWriteTime.Nanoseconds()
st.Ctim = fi.CreationTime.Nanoseconds() st.Ctim = fi.CreationTime.Nanoseconds()
return return st, nil
} }

View File

@@ -120,8 +120,8 @@ func (w *windowsWrappedFile) requireFile(op string, readOnly, isDir bool) error
// getFileType caches the file type as this cannot change on an open file. // getFileType caches the file type as this cannot change on an open file.
func (w *windowsWrappedFile) getFileType() (fs.FileMode, error) { func (w *windowsWrappedFile) getFileType() (fs.FileMode, error) {
if w.fileType == nil { if w.fileType == nil {
var st Stat_t st, err := StatFile(w.File)
if err := StatFile(w.File, &st); err != nil { if err != nil {
return 0, nil return 0, nil
} }
ft := st.Mode & fs.ModeType ft := st.Mode & fs.ModeType

View File

@@ -190,8 +190,7 @@ type cachedStat struct {
// they couldn't be retrieved. // they couldn't be retrieved.
func (f *FileEntry) CachedStat() (ino uint64, fileType fs.FileMode, err error) { func (f *FileEntry) CachedStat() (ino uint64, fileType fs.FileMode, err error) {
if f.cachedStat == nil { if f.cachedStat == nil {
var st platform.Stat_t if _, err = f.Stat(); err != nil {
if err = f.Stat(&st); err != nil {
return return
} }
} }
@@ -199,14 +198,14 @@ func (f *FileEntry) CachedStat() (ino uint64, fileType fs.FileMode, err error) {
} }
// Stat returns the underlying stat of this file. // Stat returns the underlying stat of this file.
func (f *FileEntry) Stat(st *platform.Stat_t) (err error) { func (f *FileEntry) Stat() (st platform.Stat_t, err error) {
if ld, ok := f.File.(*lazyDir); ok { if ld, ok := f.File.(*lazyDir); ok {
var sf fs.File var sf fs.File
if sf, err = ld.file(); err == nil { if sf, err = ld.file(); err == nil {
err = platform.StatFile(sf, st) st, err = platform.StatFile(sf)
} }
} else { } else {
err = platform.StatFile(f.File, st) st, err = platform.StatFile(f.File)
} }
if err == nil { if err == nil {

View File

@@ -88,8 +88,8 @@ func TestFileEntry_cachedStat(t *testing.T) {
dirFS := sysfs.NewDirFS(tmpDir) dirFS := sysfs.NewDirFS(tmpDir)
// get the expected inode // get the expected inode
var st platform.Stat_t st, err := platform.Stat(tmpDir)
require.NoError(t, platform.Stat(tmpDir, &st)) require.NoError(t, err)
tests := []struct { tests := []struct {
name string name string

View File

@@ -51,18 +51,18 @@ func (a *adapter) OpenFile(path string, flag int, perm fs.FileMode) (fs.File, er
} }
// Stat implements FS.Stat // Stat implements FS.Stat
func (a *adapter) Stat(path string, stat *platform.Stat_t) error { func (a *adapter) Stat(path string) (platform.Stat_t, error) {
name := cleanPath(path) name := cleanPath(path)
f, err := a.fs.Open(name) f, err := a.fs.Open(name)
if err != nil { if err != nil {
return platform.UnwrapOSError(err) return platform.Stat_t{}, platform.UnwrapOSError(err)
} }
defer f.Close() defer f.Close()
return platform.StatFile(f, stat) return platform.StatFile(f)
} }
// Lstat implements FS.Lstat // Lstat implements FS.Lstat
func (a *adapter) Lstat(path string, stat *platform.Stat_t) error { func (a *adapter) Lstat(path string) (platform.Stat_t, error) {
// At this time, we make the assumption that fs.FS instances do not support // At this time, we make the assumption that fs.FS instances do not support
// symbolic links, therefore Lstat is the same as Stat. This is obviously // symbolic links, therefore Lstat is the same as Stat. This is obviously
// not true but until fs.FS has a solid story for how to handle symlinks we // not true but until fs.FS has a solid story for how to handle symlinks we
@@ -71,7 +71,7 @@ func (a *adapter) Lstat(path string, stat *platform.Stat_t) error {
// //
// For further discussions on the topic, see: // For further discussions on the topic, see:
// https://github.com/golang/go/issues/49580 // https://github.com/golang/go/issues/49580
return a.Stat(path, stat) return a.Stat(path)
} }
func cleanPath(name string) string { func cleanPath(name string) string {

View File

@@ -10,7 +10,6 @@ import (
"testing" "testing"
"github.com/tetratelabs/wazero/internal/fstest" "github.com/tetratelabs/wazero/internal/fstest"
"github.com/tetratelabs/wazero/internal/platform"
"github.com/tetratelabs/wazero/internal/testing/require" "github.com/tetratelabs/wazero/internal/testing/require"
) )
@@ -126,8 +125,8 @@ func TestAdapt_Lstat(t *testing.T) {
fullPath := joinPath(tmpDir, path) fullPath := joinPath(tmpDir, path)
linkPath := joinPath(tmpDir, path+"-link") linkPath := joinPath(tmpDir, path+"-link")
require.NoError(t, os.Symlink(fullPath, linkPath)) require.NoError(t, os.Symlink(fullPath, linkPath))
var stat platform.Stat_t _, err := testFS.Lstat(filepath.Base(linkPath))
require.NoError(t, testFS.Lstat(filepath.Base(linkPath), &stat)) require.NoError(t, err)
} }
} }

View File

@@ -46,13 +46,13 @@ func (d *dirFS) OpenFile(path string, flag int, perm fs.FileMode) (fs.File, erro
} }
// Lstat implements FS.Lstat // Lstat implements FS.Lstat
func (d *dirFS) Lstat(path string, stat *platform.Stat_t) error { func (d *dirFS) Lstat(path string) (platform.Stat_t, error) {
return platform.Lstat(d.join(path), stat) return platform.Lstat(d.join(path))
} }
// Stat implements FS.Stat // Stat implements FS.Stat
func (d *dirFS) Stat(path string, stat *platform.Stat_t) error { func (d *dirFS) Stat(path string) (platform.Stat_t, error) {
return platform.Stat(d.join(path), stat) return platform.Stat(d.join(path))
} }
// Mkdir implements FS.Mkdir // Mkdir implements FS.Mkdir

View File

@@ -140,9 +140,9 @@ func testChmod(t *testing.T, testFS FS, path string) {
} }
func requireMode(t *testing.T, testFS FS, path string, mode fs.FileMode) { func requireMode(t *testing.T, testFS FS, path string, mode fs.FileMode) {
var stat platform.Stat_t st, err := testFS.Stat(path)
require.NoError(t, testFS.Stat(path, &stat)) require.NoError(t, err)
require.Equal(t, mode, stat.Mode.Perm()) require.Equal(t, mode, st.Mode.Perm())
} }
func TestDirFS_Rename(t *testing.T) { func TestDirFS_Rename(t *testing.T) {
@@ -638,8 +638,8 @@ func TestDirFS_Utimesns(t *testing.T) {
panic(tc) panic(tc)
} }
var oldSt platform.Stat_t oldSt, err := testFS.Lstat(statPath)
require.NoError(t, testFS.Lstat(statPath, &oldSt)) require.NoError(t, err)
err = testFS.Utimens(path, tc.times, !symlinkNoFollow) err = testFS.Utimens(path, tc.times, !symlinkNoFollow)
if symlinkNoFollow && !platform.SupportsSymlinkNoFollow { if symlinkNoFollow && !platform.SupportsSymlinkNoFollow {
@@ -648,8 +648,8 @@ func TestDirFS_Utimesns(t *testing.T) {
} }
require.NoError(t, err) require.NoError(t, err)
var newSt platform.Stat_t newSt, err := testFS.Lstat(statPath)
require.NoError(t, testFS.Lstat(statPath, &newSt)) require.NoError(t, err)
if platform.CompilerSupported() { if platform.CompilerSupported() {
if tc.times != nil && tc.times[0].Nsec == platform.UTIME_OMIT { if tc.times != nil && tc.times[0].Nsec == platform.UTIME_OMIT {
@@ -711,8 +711,8 @@ func TestDirFS_Stat(t *testing.T) {
name := `e:xperi\ment.txt` name := `e:xperi\ment.txt`
require.NoError(t, os.WriteFile(path.Join(tmpDir, name), nil, 0o600)) require.NoError(t, os.WriteFile(path.Join(tmpDir, name), nil, 0o600))
var st platform.Stat_t _, err := testFS.Stat(name)
require.NoError(t, testFS.Stat(name, &st)) require.NoError(t, err)
}) })
} }
} }

View File

@@ -141,13 +141,13 @@ func maskForReads(f fs.File) fs.File {
} }
// Lstat implements FS.Lstat // Lstat implements FS.Lstat
func (r *readFS) Lstat(path string, lstat *platform.Stat_t) error { func (r *readFS) Lstat(path string) (platform.Stat_t, error) {
return r.fs.Lstat(path, lstat) return r.fs.Lstat(path)
} }
// Stat implements FS.Stat // Stat implements FS.Stat
func (r *readFS) Stat(path string, stat *platform.Stat_t) error { func (r *readFS) Stat(path string) (platform.Stat_t, error) {
return r.fs.Stat(path, stat) return r.fs.Stat(path)
} }
// Readlink implements FS.Readlink // Readlink implements FS.Readlink

View File

@@ -192,11 +192,10 @@ func (d *openRootDir) readDir() (err error) {
} }
func (d *openRootDir) rootEntry(name string, fsI int) (fs.DirEntry, error) { func (d *openRootDir) rootEntry(name string, fsI int) (fs.DirEntry, error) {
var stat platform.Stat_t if st, err := d.c.fs[fsI].Stat("."); err != nil {
if err := d.c.fs[fsI].Stat(".", &stat); err != nil {
return nil, err return nil, err
} else { } else {
return &dirInfo{name, &stat}, nil return &dirInfo{name, st}, nil
} }
} }
@@ -206,7 +205,7 @@ type dirInfo struct {
// directory is masked. For example, we don't want to leak the underlying // directory is masked. For example, we don't want to leak the underlying
// host directory name. // host directory name.
name string name string
stat *platform.Stat_t stat platform.Stat_t
} }
func (i *dirInfo) Name() string { return i.name } func (i *dirInfo) Name() string { return i.name }
@@ -247,15 +246,15 @@ func (d *openRootDir) ReadDir(count int) ([]fs.DirEntry, error) {
} }
// Lstat implements FS.Lstat // Lstat implements FS.Lstat
func (c *CompositeFS) Lstat(path string, stat *platform.Stat_t) error { func (c *CompositeFS) Lstat(path string) (platform.Stat_t, error) {
matchIndex, relativePath := c.chooseFS(path) matchIndex, relativePath := c.chooseFS(path)
return c.fs[matchIndex].Lstat(relativePath, stat) return c.fs[matchIndex].Lstat(relativePath)
} }
// Stat implements FS.Stat // Stat implements FS.Stat
func (c *CompositeFS) Stat(path string, stat *platform.Stat_t) error { func (c *CompositeFS) Stat(path string) (platform.Stat_t, error) {
matchIndex, relativePath := c.chooseFS(path) matchIndex, relativePath := c.chooseFS(path)
return c.fs[matchIndex].Stat(relativePath, stat) return c.fs[matchIndex].Stat(relativePath)
} }
// Mkdir implements FS.Mkdir // Mkdir implements FS.Mkdir

View File

@@ -75,7 +75,7 @@ type FS interface {
// same value. // same value.
// - When the path is a symbolic link, the stat returned is for the link, // - When the path is a symbolic link, the stat returned is for the link,
// not the file it refers to. // not the file it refers to.
Lstat(path string, stat *platform.Stat_t) error Lstat(path string) (platform.Stat_t, error)
// Stat is similar to syscall.Stat, except the path is relative to this // Stat is similar to syscall.Stat, except the path is relative to this
// file system. // file system.
@@ -91,7 +91,7 @@ type FS interface {
// same value. // same value.
// - When the path is a symbolic link, the stat returned is for the file // - When the path is a symbolic link, the stat returned is for the file
// it refers to. // it refers to.
Stat(path string, stat *platform.Stat_t) error Stat(path string) (platform.Stat_t, error)
// Mkdir is similar to os.Mkdir, except the path is relative to this file // Mkdir is similar to os.Mkdir, except the path is relative to this file
// system, and syscall.Errno are returned instead of a os.PathError. // system, and syscall.Errno are returned instead of a os.PathError.

View File

@@ -71,8 +71,8 @@ func testOpen_O_RDWR(t *testing.T, tmpDir string, testFS FS) {
require.NoError(t, err) require.NoError(t, err)
defer f.Close() defer f.Close()
var st platform.Stat_t _, err = platform.StatFile(f)
require.NoError(t, platform.StatFile(f, &st)) require.NoError(t, err)
}) })
} }
} }
@@ -263,84 +263,90 @@ human
} }
func testLstat(t *testing.T, testFS FS) { func testLstat(t *testing.T, testFS FS) {
var stat platform.Stat_t _, err := testFS.Lstat("cat")
require.EqualErrno(t, syscall.ENOENT, testFS.Lstat("cat", &stat)) require.EqualErrno(t, syscall.ENOENT, err)
require.EqualErrno(t, syscall.ENOENT, testFS.Lstat("sub/cat", &stat)) _, err = testFS.Lstat("sub/cat")
require.EqualErrno(t, syscall.ENOENT, err)
var st platform.Stat_t
t.Run("dir", func(t *testing.T) { t.Run("dir", func(t *testing.T) {
err := testFS.Lstat(".", &stat) st, err = testFS.Lstat(".")
require.NoError(t, err) require.NoError(t, err)
require.True(t, stat.Mode.IsDir()) require.True(t, st.Mode.IsDir())
require.NotEqual(t, uint64(0), stat.Ino) require.NotEqual(t, uint64(0), st.Ino)
}) })
var statFile platform.Stat_t var stFile platform.Stat_t
t.Run("file", func(t *testing.T) { t.Run("file", func(t *testing.T) {
require.NoError(t, testFS.Lstat("animals.txt", &statFile)) stFile, err = testFS.Lstat("animals.txt")
require.Zero(t, statFile.Mode.Type()) require.NoError(t, err)
require.Equal(t, int64(30), statFile.Size) require.Zero(t, stFile.Mode.Type())
require.NotEqual(t, uint64(0), stat.Ino) require.Equal(t, int64(30), stFile.Size)
require.NotEqual(t, uint64(0), st.Ino)
}) })
t.Run("link to file", func(t *testing.T) { t.Run("link to file", func(t *testing.T) {
requireLinkStat(t, testFS, "animals.txt", &statFile) requireLinkStat(t, testFS, "animals.txt", stFile)
}) })
var statSubdir platform.Stat_t var stSubdir platform.Stat_t
t.Run("subdir", func(t *testing.T) { t.Run("subdir", func(t *testing.T) {
require.NoError(t, testFS.Lstat("sub", &statSubdir)) stSubdir, err = testFS.Lstat("sub")
require.True(t, statSubdir.Mode.IsDir()) require.NoError(t, err)
require.NotEqual(t, uint64(0), stat.Ino) require.True(t, stSubdir.Mode.IsDir())
require.NotEqual(t, uint64(0), st.Ino)
}) })
t.Run("link to dir", func(t *testing.T) { t.Run("link to dir", func(t *testing.T) {
requireLinkStat(t, testFS, "sub", &statSubdir) requireLinkStat(t, testFS, "sub", stSubdir)
}) })
t.Run("link to dir link", func(t *testing.T) { t.Run("link to dir link", func(t *testing.T) {
pathLink := "sub-link" pathLink := "sub-link"
var statLink platform.Stat_t stLink, err := testFS.Lstat(pathLink)
require.NoError(t, testFS.Lstat(pathLink, &statLink)) require.NoError(t, err)
requireLinkStat(t, testFS, pathLink, &statLink) requireLinkStat(t, testFS, pathLink, stLink)
}) })
} }
func requireLinkStat(t *testing.T, testFS FS, path string, stat *platform.Stat_t) { func requireLinkStat(t *testing.T, testFS FS, path string, stat platform.Stat_t) {
link := path + "-link" link := path + "-link"
var linkStat platform.Stat_t stLink, err := testFS.Lstat(link)
require.NoError(t, testFS.Lstat(link, &linkStat)) require.NoError(t, err)
require.NotEqual(t, stat.Ino, linkStat.Ino) // inodes are not equal require.NotEqual(t, stat.Ino, stLink.Ino) // inodes are not equal
require.Equal(t, fs.ModeSymlink, linkStat.Mode.Type()) require.Equal(t, fs.ModeSymlink, stLink.Mode.Type())
// From https://linux.die.net/man/2/lstat: // From https://linux.die.net/man/2/lstat:
// The size of a symbolic link is the length of the pathname it // The size of a symbolic link is the length of the pathname it
// contains, without a terminating null byte. // contains, without a terminating null byte.
if runtime.GOOS == "windows" { // size is zero, not the path length if runtime.GOOS == "windows" { // size is zero, not the path length
require.Zero(t, linkStat.Size) require.Zero(t, stLink.Size)
} else { } else {
require.Equal(t, int64(len(path)), linkStat.Size) require.Equal(t, int64(len(path)), stLink.Size)
} }
} }
func testStat(t *testing.T, testFS FS) { func testStat(t *testing.T, testFS FS) {
var stat platform.Stat_t _, err := testFS.Stat("cat")
require.EqualErrno(t, syscall.ENOENT, testFS.Stat("cat", &stat)) require.EqualErrno(t, syscall.ENOENT, err)
require.EqualErrno(t, syscall.ENOENT, testFS.Stat("sub/cat", &stat)) _, err = testFS.Stat("sub/cat")
require.EqualErrno(t, syscall.ENOENT, err)
err := testFS.Stat("sub/test.txt", &stat) st, err := testFS.Stat("sub/test.txt")
require.NoError(t, err) require.NoError(t, err)
require.False(t, stat.Mode.IsDir()) require.False(t, st.Mode.IsDir())
require.NotEqual(t, uint64(0), stat.Dev) require.NotEqual(t, uint64(0), st.Dev)
require.NotEqual(t, uint64(0), stat.Ino) require.NotEqual(t, uint64(0), st.Ino)
err = testFS.Stat("sub", &stat) st, err = testFS.Stat("sub")
require.NoError(t, err) require.NoError(t, err)
require.True(t, stat.Mode.IsDir()) require.True(t, st.Mode.IsDir())
// windows before go 1.20 has trouble reading the inode information on directories. // windows before go 1.20 has trouble reading the inode information on directories.
if runtime.GOOS != "windows" || platform.IsGo120 { if runtime.GOOS != "windows" || platform.IsGo120 {
require.NotEqual(t, uint64(0), stat.Dev) require.NotEqual(t, uint64(0), st.Dev)
require.NotEqual(t, uint64(0), stat.Ino) require.NotEqual(t, uint64(0), st.Ino)
} }
} }

View File

@@ -27,13 +27,13 @@ func (UnimplementedFS) OpenFile(path string, flag int, perm fs.FileMode) (fs.Fil
} }
// Lstat implements FS.Lstat // Lstat implements FS.Lstat
func (UnimplementedFS) Lstat(path string, stat *platform.Stat_t) error { func (UnimplementedFS) Lstat(path string) (platform.Stat_t, error) {
return syscall.ENOSYS return platform.Stat_t{}, syscall.ENOSYS
} }
// Stat implements FS.Stat // Stat implements FS.Stat
func (UnimplementedFS) Stat(path string, stat *platform.Stat_t) error { func (UnimplementedFS) Stat(path string) (platform.Stat_t, error) {
return syscall.ENOSYS return platform.Stat_t{}, syscall.ENOSYS
} }
// Readlink implements FS.Readlink // Readlink implements FS.Readlink