1
0
Fork 0
mirror of https://github.com/restic/restic.git synced 2025-03-09 00:00:02 +01:00

fs: Reimplement Stat/Lstat on Linux using statx

This gives us access to additional information, notably the birth time
of files on btrfs.
This commit is contained in:
greatroar 2024-10-27 10:40:12 +01:00
parent 60cba55647
commit e2886ba495
9 changed files with 130 additions and 23 deletions

View file

@ -32,20 +32,6 @@ func (fs Local) OpenFile(name string, flag int, perm os.FileMode) (File, error)
return f, nil
}
// Stat returns a FileInfo describing the named file. If there is an error, it
// will be of type *PathError.
func (fs Local) Stat(name string) (os.FileInfo, error) {
return os.Stat(fixpath(name))
}
// Lstat returns the FileInfo structure describing the named file.
// If the file is a symbolic link, the returned FileInfo
// describes the symbolic link. Lstat makes no attempt to follow the link.
// If there is an error, it will be of type *PathError.
func (fs Local) Lstat(name string) (os.FileInfo, error) {
return os.Lstat(fixpath(name))
}
// DeviceID extracts the DeviceID from the given FileInfo. If the fs does
// not support a DeviceID, it returns an error instead
func (fs Local) DeviceID(fi os.FileInfo) (id uint64, err error) {

View file

@ -247,7 +247,7 @@ func TestNodeRestoreAt(t *testing.T) {
rtest.OK(t, NodeCreateAt(&test, nodePath))
rtest.OK(t, NodeRestoreMetadata(&test, nodePath, func(msg string) { rtest.OK(t, fmt.Errorf("Warning triggered for path: %s: %s", nodePath, msg)) }))
fi, err := os.Lstat(nodePath)
fi, err := Local{}.Lstat(nodePath)
rtest.OK(t, err)
n2, err := NodeFromFileInfo(nodePath, fi, false)

View file

@ -1,5 +1,4 @@
//go:build !windows
// +build !windows
//go:build unix && !linux
package fs

View file

@ -30,9 +30,7 @@ func TestPreallocate(t *testing.T) {
fi, err := wr.Stat()
test.OK(t, err)
efi := ExtendedStat(fi)
test.Assert(t, efi.Size == i || efi.Blocks > 0, "Preallocated size of %v, got size %v block %v", i, efi.Size, efi.Blocks)
test.Assert(t, fi.Size() == i, "Preallocated size of %v, got size %v", i, fi.Size())
})
}
}

View file

@ -38,7 +38,7 @@ func TestNoatime(t *testing.T) {
rtest.OK(t, err)
getAtime := func() time.Time {
info, err := f.Stat()
info, err := Local{}.Stat(f.Name())
rtest.OK(t, err)
return ExtendedStat(info).AccessTime
}

104
internal/fs/stat_linux.go Normal file
View file

@ -0,0 +1,104 @@
package fs
import (
"os"
"path/filepath"
"time"
"golang.org/x/sys/unix"
)
// On Linux, we reimplement Stat and Lstat in terms of unix.Statx,
// which gives access to some interesting information that Stat doesn't provide.
func (fs Local) Stat(name string) (os.FileInfo, error) {
return statx(name, 0)
}
func (fs Local) Lstat(name string) (os.FileInfo, error) {
return statx(name, unix.AT_SYMLINK_NOFOLLOW)
}
func statx(name string, flags int) (*statxFileInfo, error) {
const mask = unix.STATX_BASIC_STATS | unix.STATX_BTIME
fi := &statxFileInfo{}
// XXX We could pick FORCE_SYNC or DONT_SYNC instead of SYNC_AS_STAT,
// to influence the behavior when dealing with remote filesystems.
err := unix.Statx(unix.AT_FDCWD, name, flags|unix.AT_STATX_SYNC_AS_STAT, mask, &fi.st)
if err != nil {
return nil, &os.PathError{Path: name, Op: "statx", Err: err}
}
fi.name = filepath.Base(name)
return fi, nil
}
type statxFileInfo struct {
name string
st unix.Statx_t
}
func (fi *statxFileInfo) Name() string { return fi.name }
func (fi *statxFileInfo) Size() int64 { return int64(fi.st.Size) }
func (fi *statxFileInfo) ModTime() time.Time { return timeFromStatx(fi.st.Mtime) }
func (fi *statxFileInfo) IsDir() bool { return fi.Mode().IsDir() }
func (fi *statxFileInfo) Sys() any { return &fi.st }
// Adapted from os/stat_linux.go in the Go stdlib.
func (fi *statxFileInfo) Mode() os.FileMode {
mode := os.FileMode(fi.st.Mode & 0o777)
switch fi.st.Mode & unix.S_IFMT {
case unix.S_IFBLK:
mode |= os.ModeDevice
case unix.S_IFCHR:
mode |= os.ModeDevice | os.ModeCharDevice
case unix.S_IFDIR:
mode |= os.ModeDir
case unix.S_IFIFO:
mode |= os.ModeNamedPipe
case unix.S_IFLNK:
mode |= os.ModeSymlink
case unix.S_IFREG:
// nothing to do
case unix.S_IFSOCK:
mode |= os.ModeSocket
}
if fi.st.Mode&unix.S_ISGID != 0 {
mode |= os.ModeSetgid
}
if fi.st.Mode&unix.S_ISUID != 0 {
mode |= os.ModeSetuid
}
if fi.st.Mode&unix.S_ISVTX != 0 {
mode |= os.ModeSticky
}
return mode
}
func extendedStat(fi os.FileInfo) ExtendedFileInfo {
s := fi.Sys().(*unix.Statx_t)
return ExtendedFileInfo{
FileInfo: fi,
DeviceID: uint64(s.Rdev_major)<<8 | uint64(s.Rdev_minor),
Inode: s.Ino,
Links: uint64(s.Nlink),
UID: s.Uid,
GID: s.Gid,
Device: uint64(s.Dev_major)<<8 | uint64(s.Dev_minor),
BlockSize: int64(s.Blksize),
Blocks: int64(s.Blocks),
Size: int64(s.Size),
AccessTime: timeFromStatx(s.Atime),
ModTime: timeFromStatx(s.Mtime),
ChangeTime: timeFromStatx(s.Ctime),
}
}
func timeFromStatx(ts unix.StatxTimestamp) time.Time {
return time.Unix(ts.Sec, int64(ts.Nsec))
}

View file

@ -0,0 +1,19 @@
//go:build !linux
package fs
import "os"
// Stat returns a FileInfo describing the named file. If there is an error, it
// will be of type *PathError.
func (fs Local) Stat(name string) (os.FileInfo, error) {
return os.Stat(fixpath(name))
}
// Lstat returns the FileInfo structure describing the named file.
// If the file is a symbolic link, the returned FileInfo
// describes the symbolic link. Lstat makes no attempt to follow the link.
// If there is an error, it will be of type *PathError.
func (fs Local) Lstat(name string) (os.FileInfo, error) {
return os.Lstat(fixpath(name))
}

View file

@ -1,3 +1,5 @@
//go:build !linux
package fs
import (

View file

@ -1,5 +1,4 @@
//go:build !windows && !darwin && !freebsd && !netbsd
// +build !windows,!darwin,!freebsd,!netbsd
//go:build unix && !darwin && !freebsd && !linux && !netbsd
package fs