mirror of
https://github.com/restic/restic.git
synced 2025-08-25 23:47:29 +00:00
fs: inline ExtendedStat
This commit is contained in:
@@ -1,31 +0,0 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
)
|
||||
|
||||
// deviceID extracts the device ID from an os.FileInfo object by casting it
|
||||
// to syscall.Stat_t
|
||||
func deviceID(fi os.FileInfo) (deviceID uint64, err error) {
|
||||
if fi == nil {
|
||||
return 0, errors.New("unable to determine device: fi is nil")
|
||||
}
|
||||
|
||||
if fi.Sys() == nil {
|
||||
return 0, errors.New("unable to determine device: fi.Sys() is nil")
|
||||
}
|
||||
|
||||
if st, ok := fi.Sys().(*syscall.Stat_t); ok {
|
||||
// st.Dev is uint32 on Darwin and uint64 on Linux. Just cast
|
||||
// everything to uint64.
|
||||
return uint64(st.Dev), nil
|
||||
}
|
||||
|
||||
return 0, errors.New("Could not cast to syscall.Stat_t")
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
)
|
||||
|
||||
// deviceID extracts the device ID from an os.FileInfo object by casting it
|
||||
// to syscall.Stat_t
|
||||
func deviceID(_ os.FileInfo) (deviceID uint64, err error) {
|
||||
return 0, errors.New("Device IDs are not supported on Windows")
|
||||
}
|
@@ -36,19 +36,12 @@ func (fs Local) OpenFile(name string, flag int, metadataOnly bool) (File, error)
|
||||
// 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) {
|
||||
return deviceID(fi)
|
||||
}
|
||||
|
||||
// ExtendedStat converts the give FileInfo into ExtendedFileInfo.
|
||||
func (fs Local) ExtendedStat(fi os.FileInfo) ExtendedFileInfo {
|
||||
return ExtendedStat(fi)
|
||||
func (fs Local) Lstat(name string) (*ExtendedFileInfo, error) {
|
||||
fi, err := os.Lstat(fixpath(name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return extendedStat(fi), nil
|
||||
}
|
||||
|
||||
// Join joins any number of path elements into a single path, adding a
|
||||
@@ -96,7 +89,7 @@ type localFile struct {
|
||||
name string
|
||||
flag int
|
||||
f *os.File
|
||||
fi os.FileInfo
|
||||
fi *ExtendedFileInfo
|
||||
}
|
||||
|
||||
// See the File interface for a description of each method
|
||||
@@ -137,18 +130,23 @@ func (f *localFile) cacheFI() error {
|
||||
if f.fi != nil {
|
||||
return nil
|
||||
}
|
||||
var fi os.FileInfo
|
||||
var err error
|
||||
if f.f != nil {
|
||||
f.fi, err = f.f.Stat()
|
||||
fi, err = f.f.Stat()
|
||||
} else if f.flag&O_NOFOLLOW != 0 {
|
||||
f.fi, err = os.Lstat(f.name)
|
||||
fi, err = os.Lstat(f.name)
|
||||
} else {
|
||||
f.fi, err = os.Stat(f.name)
|
||||
fi, err = os.Stat(f.name)
|
||||
}
|
||||
return err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.fi = extendedStat(fi)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *localFile) Stat() (os.FileInfo, error) {
|
||||
func (f *localFile) Stat() (*ExtendedFileInfo, error) {
|
||||
err := f.cacheFI()
|
||||
// the call to cacheFI MUST happen before reading from f.fi
|
||||
return f.fi, err
|
||||
|
@@ -84,13 +84,13 @@ func checkMetadata(t *testing.T, f File, path string, follow bool, nodeType rest
|
||||
fi2, err = os.Lstat(path)
|
||||
}
|
||||
rtest.OK(t, err)
|
||||
assertFIEqual(t, fi2, fi)
|
||||
assertFIEqual(t, fi2, fi.FileInfo)
|
||||
|
||||
node, err := f.ToNode(false)
|
||||
rtest.OK(t, err)
|
||||
|
||||
// ModTime is likely unique per file, thus it provides a good indication that it is from the correct file
|
||||
rtest.Equals(t, fi.ModTime(), node.ModTime, "node ModTime")
|
||||
rtest.Equals(t, fi.ModTime, node.ModTime, "node ModTime")
|
||||
rtest.Equals(t, nodeType, node.Type, "node Type")
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,6 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -131,7 +130,7 @@ func (fs *LocalVss) OpenFile(name string, flag int, metadataOnly bool) (File, er
|
||||
}
|
||||
|
||||
// Lstat wraps the Lstat method of the underlying file system.
|
||||
func (fs *LocalVss) Lstat(name string) (os.FileInfo, error) {
|
||||
func (fs *LocalVss) Lstat(name string) (*ExtendedFileInfo, error) {
|
||||
return fs.FS.Lstat(fs.snapshotPath(name))
|
||||
}
|
||||
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"slices"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
@@ -40,12 +41,14 @@ func (fs *Reader) VolumeName(_ string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (fs *Reader) fi() os.FileInfo {
|
||||
return fakeFileInfo{
|
||||
name: fs.Name,
|
||||
size: fs.Size,
|
||||
mode: fs.Mode,
|
||||
modtime: fs.ModTime,
|
||||
func (fs *Reader) fi() *ExtendedFileInfo {
|
||||
return &ExtendedFileInfo{
|
||||
FileInfo: fakeFileInfo{
|
||||
name: fs.Name,
|
||||
size: fs.Size,
|
||||
mode: fs.Mode,
|
||||
modtime: fs.ModTime,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +71,7 @@ func (fs *Reader) OpenFile(name string, flag int, _ bool) (f File, err error) {
|
||||
return f, nil
|
||||
case "/", ".":
|
||||
f = fakeDir{
|
||||
entries: []os.FileInfo{fs.fi()},
|
||||
entries: []string{fs.fi().Name()},
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
@@ -80,15 +83,15 @@ func (fs *Reader) OpenFile(name string, flag int, _ bool) (f File, err error) {
|
||||
// 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 *os.PathError.
|
||||
func (fs *Reader) Lstat(name string) (os.FileInfo, error) {
|
||||
getDirInfo := func(name string) os.FileInfo {
|
||||
func (fs *Reader) Lstat(name string) (*ExtendedFileInfo, error) {
|
||||
getDirInfo := func(name string) *ExtendedFileInfo {
|
||||
fi := fakeFileInfo{
|
||||
name: fs.Base(name),
|
||||
size: 0,
|
||||
mode: os.ModeDir | 0755,
|
||||
modtime: time.Now(),
|
||||
}
|
||||
return fi
|
||||
return &ExtendedFileInfo{FileInfo: fi}
|
||||
}
|
||||
|
||||
switch name {
|
||||
@@ -112,16 +115,6 @@ func (fs *Reader) Lstat(name string) (os.FileInfo, error) {
|
||||
return nil, pathError("lstat", name, os.ErrNotExist)
|
||||
}
|
||||
|
||||
func (fs *Reader) DeviceID(_ os.FileInfo) (deviceID uint64, err error) {
|
||||
return 0, errors.New("Device IDs are not supported")
|
||||
}
|
||||
|
||||
func (fs *Reader) ExtendedStat(fi os.FileInfo) ExtendedFileInfo {
|
||||
return ExtendedFileInfo{
|
||||
FileInfo: fi,
|
||||
}
|
||||
}
|
||||
|
||||
// Join joins any number of path elements into a single path, adding a
|
||||
// Separator if necessary. Join calls Clean on the result; in particular, all
|
||||
// empty strings are ignored. On Windows, the result is a UNC path if and only
|
||||
@@ -165,13 +158,13 @@ func (fs *Reader) Dir(p string) string {
|
||||
return path.Dir(p)
|
||||
}
|
||||
|
||||
func newReaderFile(rd io.ReadCloser, fi os.FileInfo, allowEmptyFile bool) *readerFile {
|
||||
func newReaderFile(rd io.ReadCloser, fi *ExtendedFileInfo, allowEmptyFile bool) *readerFile {
|
||||
return &readerFile{
|
||||
ReadCloser: rd,
|
||||
AllowEmptyFile: allowEmptyFile,
|
||||
fakeFile: fakeFile{
|
||||
FileInfo: fi,
|
||||
name: fi.Name(),
|
||||
fi: fi,
|
||||
name: fi.Name(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -213,7 +206,7 @@ var _ File = &readerFile{}
|
||||
// except Stat()
|
||||
type fakeFile struct {
|
||||
name string
|
||||
os.FileInfo
|
||||
fi *ExtendedFileInfo
|
||||
}
|
||||
|
||||
// ensure that fakeFile implements File
|
||||
@@ -235,12 +228,12 @@ func (f fakeFile) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f fakeFile) Stat() (os.FileInfo, error) {
|
||||
return f.FileInfo, nil
|
||||
func (f fakeFile) Stat() (*ExtendedFileInfo, error) {
|
||||
return f.fi, nil
|
||||
}
|
||||
|
||||
func (f fakeFile) ToNode(_ bool) (*restic.Node, error) {
|
||||
node := buildBasicNode(f.name, f.FileInfo)
|
||||
node := buildBasicNode(f.name, f.fi.FileInfo)
|
||||
|
||||
// fill minimal info with current values for uid, gid
|
||||
node.UID = uint32(os.Getuid())
|
||||
@@ -252,7 +245,7 @@ func (f fakeFile) ToNode(_ bool) (*restic.Node, error) {
|
||||
|
||||
// fakeDir implements Readdirnames and Readdir, everything else is delegated to fakeFile.
|
||||
type fakeDir struct {
|
||||
entries []os.FileInfo
|
||||
entries []string
|
||||
fakeFile
|
||||
}
|
||||
|
||||
@@ -260,12 +253,7 @@ func (d fakeDir) Readdirnames(n int) ([]string, error) {
|
||||
if n > 0 {
|
||||
return nil, pathError("readdirnames", d.name, errors.New("not implemented"))
|
||||
}
|
||||
names := make([]string, 0, len(d.entries))
|
||||
for _, entry := range d.entries {
|
||||
names = append(names, entry.Name())
|
||||
}
|
||||
|
||||
return names, nil
|
||||
return slices.Clone(d.entries), nil
|
||||
}
|
||||
|
||||
// fakeFileInfo implements the bare minimum of os.FileInfo.
|
||||
|
@@ -60,7 +60,7 @@ func verifyDirectoryContents(t testing.TB, fs FS, dir string, want []string) {
|
||||
}
|
||||
}
|
||||
|
||||
func checkFileInfo(t testing.TB, fi os.FileInfo, filename string, modtime time.Time, mode os.FileMode, isdir bool) {
|
||||
func checkFileInfo(t testing.TB, fi *ExtendedFileInfo, filename string, modtime time.Time, mode os.FileMode, isdir bool) {
|
||||
if fi.IsDir() != isdir {
|
||||
t.Errorf("IsDir returned %t, want %t", fi.IsDir(), isdir)
|
||||
}
|
||||
@@ -69,8 +69,8 @@ func checkFileInfo(t testing.TB, fi os.FileInfo, filename string, modtime time.T
|
||||
t.Errorf("Mode() returned wrong value, want 0%o, got 0%o", mode, fi.Mode())
|
||||
}
|
||||
|
||||
if !modtime.Equal(time.Time{}) && !fi.ModTime().Equal(modtime) {
|
||||
t.Errorf("ModTime() returned wrong value, want %v, got %v", modtime, fi.ModTime())
|
||||
if !modtime.Equal(time.Time{}) && !fi.FileInfo.ModTime().Equal(modtime) {
|
||||
t.Errorf("ModTime() returned wrong value, want %v, got %v", modtime, fi.FileInfo.ModTime())
|
||||
}
|
||||
|
||||
if path.Base(fi.Name()) != fi.Name() {
|
||||
|
@@ -2,7 +2,6 @@ package fs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/restic/restic/internal/restic"
|
||||
)
|
||||
@@ -18,9 +17,7 @@ type FS interface {
|
||||
//
|
||||
// Only the O_NOFOLLOW and O_DIRECTORY flags are supported.
|
||||
OpenFile(name string, flag int, metadataOnly bool) (File, error)
|
||||
Lstat(name string) (os.FileInfo, error)
|
||||
DeviceID(fi os.FileInfo) (deviceID uint64, err error)
|
||||
ExtendedStat(fi os.FileInfo) ExtendedFileInfo
|
||||
Lstat(name string) (*ExtendedFileInfo, error)
|
||||
|
||||
Join(elem ...string) string
|
||||
Separator() string
|
||||
@@ -47,7 +44,7 @@ type File interface {
|
||||
io.Closer
|
||||
|
||||
Readdirnames(n int) ([]string, error)
|
||||
Stat() (os.FileInfo, error)
|
||||
Stat() (*ExtendedFileInfo, error)
|
||||
// ToNode returns a restic.Node for the File. The internally used os.FileInfo
|
||||
// must be consistent with that returned by Stat(). In particular, the metadata
|
||||
// returned by consecutive calls to Stat() and ToNode() must match.
|
||||
|
@@ -15,15 +15,14 @@ import (
|
||||
|
||||
// nodeFromFileInfo returns a new node from the given path and FileInfo. It
|
||||
// returns the first error that is encountered, together with a node.
|
||||
func nodeFromFileInfo(path string, fi os.FileInfo, ignoreXattrListError bool) (*restic.Node, error) {
|
||||
node := buildBasicNode(path, fi)
|
||||
func nodeFromFileInfo(path string, fi *ExtendedFileInfo, ignoreXattrListError bool) (*restic.Node, error) {
|
||||
node := buildBasicNode(path, fi.FileInfo)
|
||||
|
||||
stat := ExtendedStat(fi)
|
||||
if err := nodeFillExtendedStat(node, path, &stat); err != nil {
|
||||
if err := nodeFillExtendedStat(node, path, fi); err != nil {
|
||||
return node, err
|
||||
}
|
||||
|
||||
err := nodeFillGenericAttributes(node, path, &stat)
|
||||
err := nodeFillGenericAttributes(node, path, fi)
|
||||
err = errors.Join(err, nodeFillExtendedAttributes(node, path, ignoreXattrListError))
|
||||
return node, err
|
||||
}
|
||||
@@ -37,15 +36,15 @@ func buildBasicNode(path string, fi os.FileInfo) *restic.Node {
|
||||
ModTime: fi.ModTime(),
|
||||
}
|
||||
|
||||
node.Type = nodeTypeFromFileInfo(fi)
|
||||
node.Type = nodeTypeFromFileInfo(fi.Mode())
|
||||
if node.Type == restic.NodeTypeFile {
|
||||
node.Size = uint64(fi.Size())
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
func nodeTypeFromFileInfo(fi os.FileInfo) restic.NodeType {
|
||||
switch fi.Mode() & os.ModeType {
|
||||
func nodeTypeFromFileInfo(mode os.FileMode) restic.NodeType {
|
||||
switch mode & os.ModeType {
|
||||
case 0:
|
||||
return restic.NodeTypeFile
|
||||
case os.ModeDir:
|
||||
|
@@ -26,7 +26,7 @@ type ExtendedFileInfo struct {
|
||||
}
|
||||
|
||||
// ExtendedStat returns an ExtendedFileInfo constructed from the os.FileInfo.
|
||||
func ExtendedStat(fi os.FileInfo) ExtendedFileInfo {
|
||||
func ExtendedStat(fi os.FileInfo) *ExtendedFileInfo {
|
||||
if fi == nil {
|
||||
panic("os.FileInfo is nil")
|
||||
}
|
||||
|
@@ -10,10 +10,10 @@ import (
|
||||
)
|
||||
|
||||
// extendedStat extracts info into an ExtendedFileInfo for unix based operating systems.
|
||||
func extendedStat(fi os.FileInfo) ExtendedFileInfo {
|
||||
func extendedStat(fi os.FileInfo) *ExtendedFileInfo {
|
||||
s := fi.Sys().(*syscall.Stat_t)
|
||||
|
||||
extFI := ExtendedFileInfo{
|
||||
return &ExtendedFileInfo{
|
||||
FileInfo: fi,
|
||||
DeviceID: uint64(s.Dev),
|
||||
Inode: uint64(s.Ino),
|
||||
@@ -29,6 +29,4 @@ func extendedStat(fi os.FileInfo) ExtendedFileInfo {
|
||||
ModTime: time.Unix(s.Mtimespec.Unix()),
|
||||
ChangeTime: time.Unix(s.Ctimespec.Unix()),
|
||||
}
|
||||
|
||||
return extFI
|
||||
}
|
||||
|
@@ -10,10 +10,10 @@ import (
|
||||
)
|
||||
|
||||
// extendedStat extracts info into an ExtendedFileInfo for unix based operating systems.
|
||||
func extendedStat(fi os.FileInfo) ExtendedFileInfo {
|
||||
func extendedStat(fi os.FileInfo) *ExtendedFileInfo {
|
||||
s := fi.Sys().(*syscall.Stat_t)
|
||||
|
||||
extFI := ExtendedFileInfo{
|
||||
return &ExtendedFileInfo{
|
||||
FileInfo: fi,
|
||||
DeviceID: uint64(s.Dev),
|
||||
Inode: s.Ino,
|
||||
@@ -29,6 +29,4 @@ func extendedStat(fi os.FileInfo) ExtendedFileInfo {
|
||||
ModTime: time.Unix(s.Mtim.Unix()),
|
||||
ChangeTime: time.Unix(s.Ctim.Unix()),
|
||||
}
|
||||
|
||||
return extFI
|
||||
}
|
||||
|
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
// extendedStat extracts info into an ExtendedFileInfo for Windows.
|
||||
func extendedStat(fi os.FileInfo) ExtendedFileInfo {
|
||||
func extendedStat(fi os.FileInfo) *ExtendedFileInfo {
|
||||
s, ok := fi.Sys().(*syscall.Win32FileAttributeData)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("conversion to syscall.Win32FileAttributeData failed, type is %T", fi.Sys()))
|
||||
@@ -31,5 +31,5 @@ func extendedStat(fi os.FileInfo) ExtendedFileInfo {
|
||||
// Windows does not have the concept of a "change time" in the sense Unix uses it, so we're using the LastWriteTime here.
|
||||
extFI.ChangeTime = extFI.ModTime
|
||||
|
||||
return extFI
|
||||
return &extFI
|
||||
}
|
||||
|
Reference in New Issue
Block a user