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:
parent
60cba55647
commit
e2886ba495
9 changed files with 130 additions and 23 deletions
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !windows
|
||||
// +build !windows
|
||||
//go:build unix && !linux
|
||||
|
||||
package fs
|
||||
|
||||
|
|
|
@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
104
internal/fs/stat_linux.go
Normal 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))
|
||||
}
|
19
internal/fs/stat_notlinux.go
Normal file
19
internal/fs/stat_notlinux.go
Normal 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))
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
//go:build !linux
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !windows && !darwin && !freebsd && !netbsd
|
||||
// +build !windows,!darwin,!freebsd,!netbsd
|
||||
//go:build unix && !darwin && !freebsd && !linux && !netbsd
|
||||
|
||||
package fs
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue