fs: replace statT with ExtendedFileInfo

This commit is contained in:
Michael Eischer 2024-08-24 23:43:45 +02:00
parent 6d3a5260d3
commit f0329bb4e6
14 changed files with 50 additions and 176 deletions

View file

@ -6,7 +6,6 @@ import (
"strconv"
"sync"
"syscall"
"time"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/errors"
@ -57,8 +56,7 @@ func nodeTypeFromFileInfo(fi os.FileInfo) restic.NodeType {
}
func nodeFillExtra(node *restic.Node, path string, fi os.FileInfo, ignoreXattrListError bool) error {
stat, ok := toStatT(fi.Sys())
if !ok {
if fi.Sys() == nil {
// fill minimal info with current values for uid, gid
node.UID = uint32(os.Getuid())
node.GID = uint32(os.Getgid())
@ -66,38 +64,43 @@ func nodeFillExtra(node *restic.Node, path string, fi os.FileInfo, ignoreXattrLi
return nil
}
node.Inode = uint64(stat.ino())
node.DeviceID = uint64(stat.dev())
stat := ExtendedStat(fi)
nodeFillTimes(node, stat)
node.Inode = stat.Inode
node.DeviceID = stat.DeviceID
node.ChangeTime = stat.ChangeTime
node.AccessTime = stat.AccessTime
nodeFillUser(node, stat)
node.UID = stat.UID
node.GID = stat.GID
node.User = lookupUsername(stat.UID)
node.Group = lookupGroup(stat.GID)
switch node.Type {
case restic.NodeTypeFile:
node.Size = uint64(stat.size())
node.Links = uint64(stat.nlink())
node.Size = uint64(stat.Size)
node.Links = stat.Links
case restic.NodeTypeDir:
case restic.NodeTypeSymlink:
var err error
node.LinkTarget, err = os.Readlink(fixpath(path))
node.Links = uint64(stat.nlink())
node.Links = stat.Links
if err != nil {
return errors.WithStack(err)
}
case restic.NodeTypeDev:
node.Device = uint64(stat.rdev())
node.Links = uint64(stat.nlink())
node.Device = stat.Device
node.Links = stat.Links
case restic.NodeTypeCharDev:
node.Device = uint64(stat.rdev())
node.Links = uint64(stat.nlink())
node.Device = stat.Device
node.Links = stat.Links
case restic.NodeTypeFifo:
case restic.NodeTypeSocket:
default:
return errors.Errorf("unsupported file type %q", node.Type)
}
allowExtended, err := nodeFillGenericAttributes(node, path, fi, stat)
allowExtended, err := nodeFillGenericAttributes(node, path, &stat)
if allowExtended {
// Skip processing ExtendedAttributes if allowExtended is false.
err = errors.CombineErrors(err, nodeFillExtendedAttributes(node, path, ignoreXattrListError))
@ -105,20 +108,6 @@ func nodeFillExtra(node *restic.Node, path string, fi os.FileInfo, ignoreXattrLi
return err
}
func nodeFillTimes(node *restic.Node, stat *statT) {
ctim := stat.ctim()
atim := stat.atim()
node.ChangeTime = time.Unix(ctim.Unix())
node.AccessTime = time.Unix(atim.Unix())
}
func nodeFillUser(node *restic.Node, stat *statT) {
uid, gid := stat.uid(), stat.gid()
node.UID, node.GID = uid, gid
node.User = lookupUsername(uid)
node.Group = lookupGroup(gid)
}
var (
uidLookupCache = make(map[uint32]string)
uidLookupCacheMutex = sync.RWMutex{}

View file

@ -4,7 +4,6 @@
package fs
import (
"os"
"syscall"
"github.com/restic/restic/internal/restic"
@ -14,17 +13,6 @@ func nodeRestoreSymlinkTimestamps(_ string, _ [2]syscall.Timespec) error {
return nil
}
// AIX has a funny timespec type in syscall, with 32-bit nanoseconds.
// golang.org/x/sys/unix handles this cleanly, but we're stuck with syscall
// because os.Stat returns a syscall type in its os.FileInfo.Sys().
func toTimespec(t syscall.StTimespec_t) syscall.Timespec {
return syscall.Timespec{Sec: t.Sec, Nsec: int64(t.Nsec)}
}
func (s statT) atim() syscall.Timespec { return toTimespec(s.Atim) }
func (s statT) mtim() syscall.Timespec { return toTimespec(s.Mtim) }
func (s statT) ctim() syscall.Timespec { return toTimespec(s.Ctim) }
// nodeRestoreExtendedAttributes is a no-op on AIX.
func nodeRestoreExtendedAttributes(_ *restic.Node, _ string) error {
return nil
@ -46,6 +34,6 @@ func nodeRestoreGenericAttributes(node *restic.Node, _ string, warn func(msg str
}
// nodeFillGenericAttributes is a no-op on AIX.
func nodeFillGenericAttributes(_ *restic.Node, _ string, _ os.FileInfo, _ *statT) (allowExtended bool, err error) {
func nodeFillGenericAttributes(_ *restic.Node, _ string, _ *ExtendedFileInfo) (allowExtended bool, err error) {
return true, nil
}

View file

@ -5,7 +5,3 @@ import "syscall"
func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
return nil
}
func (s statT) atim() syscall.Timespec { return s.Atimespec }
func (s statT) mtim() syscall.Timespec { return s.Mtimespec }
func (s statT) ctim() syscall.Timespec { return s.Ctimespec }

View file

@ -12,7 +12,3 @@ func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error
func mknod(path string, mode uint32, dev uint64) (err error) {
return syscall.Mknod(path, mode, dev)
}
func (s statT) atim() syscall.Timespec { return s.Atimespec }
func (s statT) mtim() syscall.Timespec { return s.Mtimespec }
func (s statT) ctim() syscall.Timespec { return s.Ctimespec }

View file

@ -31,7 +31,3 @@ func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error
return dir.Close()
}
func (s statT) atim() syscall.Timespec { return s.Atim }
func (s statT) mtim() syscall.Timespec { return s.Mtim }
func (s statT) ctim() syscall.Timespec { return s.Ctim }

View file

@ -1,7 +1,6 @@
package fs
import (
"os"
"syscall"
"github.com/restic/restic/internal/restic"
@ -11,10 +10,6 @@ func nodeRestoreSymlinkTimestamps(_ string, _ [2]syscall.Timespec) error {
return nil
}
func (s statT) atim() syscall.Timespec { return s.Atimespec }
func (s statT) mtim() syscall.Timespec { return s.Mtimespec }
func (s statT) ctim() syscall.Timespec { return s.Ctimespec }
// nodeRestoreExtendedAttributes is a no-op on netbsd.
func nodeRestoreExtendedAttributes(_ *restic.Node, _ string) error {
return nil
@ -36,6 +31,6 @@ func nodeRestoreGenericAttributes(node *restic.Node, _ string, warn func(msg str
}
// nodeFillGenericAttributes is a no-op on netbsd.
func nodeFillGenericAttributes(_ *restic.Node, _ string, _ os.FileInfo, _ *statT) (allowExtended bool, err error) {
func nodeFillGenericAttributes(_ *restic.Node, _ string, _ *ExtendedFileInfo) (allowExtended bool, err error) {
return true, nil
}

View file

@ -1,7 +1,6 @@
package fs
import (
"os"
"syscall"
"github.com/restic/restic/internal/restic"
@ -11,10 +10,6 @@ func nodeRestoreSymlinkTimestamps(_ string, _ [2]syscall.Timespec) error {
return nil
}
func (s statT) atim() syscall.Timespec { return s.Atim }
func (s statT) mtim() syscall.Timespec { return s.Mtim }
func (s statT) ctim() syscall.Timespec { return s.Ctim }
// nodeRestoreExtendedAttributes is a no-op on openbsd.
func nodeRestoreExtendedAttributes(_ *restic.Node, _ string) error {
return nil
@ -36,6 +31,6 @@ func nodeRestoreGenericAttributes(node *restic.Node, _ string, warn func(msg str
}
// fillGenericAttributes is a no-op on openbsd.
func nodeFillGenericAttributes(_ *restic.Node, _ string, _ os.FileInfo, _ *statT) (allowExtended bool, err error) {
func nodeFillGenericAttributes(_ *restic.Node, _ string, _ *ExtendedFileInfo) (allowExtended bool, err error) {
return true, nil
}

View file

@ -5,7 +5,3 @@ import "syscall"
func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
return nil
}
func (s statT) atim() syscall.Timespec { return s.Atim }
func (s statT) mtim() syscall.Timespec { return s.Mtim }
func (s statT) ctim() syscall.Timespec { return s.Ctim }

View file

@ -5,27 +5,8 @@ package fs
import (
"os"
"syscall"
)
func lchown(name string, uid, gid int) error {
return os.Lchown(name, uid, gid)
}
type statT syscall.Stat_t
func toStatT(i interface{}) (*statT, bool) {
s, ok := i.(*syscall.Stat_t)
if ok && s != nil {
return (*statT)(s), true
}
return nil, false
}
func (s statT) dev() uint64 { return uint64(s.Dev) }
func (s statT) ino() uint64 { return uint64(s.Ino) }
func (s statT) nlink() uint64 { return uint64(s.Nlink) }
func (s statT) uid() uint32 { return uint32(s.Uid) }
func (s statT) gid() uint32 { return uint32(s.Gid) }
func (s statT) rdev() uint64 { return uint64(s.Rdev) }
func (s statT) size() int64 { return int64(s.Size) }

View file

@ -4,12 +4,12 @@
package fs
import (
"io/fs"
"os"
"path/filepath"
"runtime"
"syscall"
"testing"
"time"
"github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test"
@ -28,8 +28,11 @@ func stat(t testing.TB, filename string) (fi os.FileInfo, ok bool) {
return fi, true
}
func checkFile(t testing.TB, stat *syscall.Stat_t, node *restic.Node) {
func checkFile(t testing.TB, fi fs.FileInfo, node *restic.Node) {
t.Helper()
stat := fi.Sys().(*syscall.Stat_t)
if uint32(node.Mode.Perm()) != uint32(stat.Mode&0777) {
t.Errorf("Mode does not match, want %v, got %v", stat.Mode&0777, node.Mode)
}
@ -59,29 +62,20 @@ func checkFile(t testing.TB, stat *syscall.Stat_t, node *restic.Node) {
}
// use the os dependent function to compare the timestamps
s, ok := toStatT(stat)
if !ok {
return
s := ExtendedStat(fi)
if node.ModTime != s.ModTime {
t.Errorf("ModTime does not match, want %v, got %v", s.ModTime, node.ModTime)
}
mtime := s.mtim()
if node.ModTime != time.Unix(mtime.Unix()) {
t.Errorf("ModTime does not match, want %v, got %v", time.Unix(mtime.Unix()), node.ModTime)
if node.ChangeTime != s.ChangeTime {
t.Errorf("ChangeTime does not match, want %v, got %v", s.ChangeTime, node.ChangeTime)
}
ctime := s.ctim()
if node.ChangeTime != time.Unix(ctime.Unix()) {
t.Errorf("ChangeTime does not match, want %v, got %v", time.Unix(ctime.Unix()), node.ChangeTime)
if node.AccessTime != s.AccessTime {
t.Errorf("AccessTime does not match, want %v, got %v", s.AccessTime, node.AccessTime)
}
atime := s.atim()
if node.AccessTime != time.Unix(atime.Unix()) {
t.Errorf("AccessTime does not match, want %v, got %v", time.Unix(atime.Unix()), node.AccessTime)
}
}
func checkDevice(t testing.TB, stat *syscall.Stat_t, node *restic.Node) {
func checkDevice(t testing.TB, fi fs.FileInfo, node *restic.Node) {
stat := fi.Sys().(*syscall.Stat_t)
if node.Device != uint64(stat.Rdev) {
t.Errorf("Rdev does not match, want %v, got %v", stat.Rdev, node.Device)
}
@ -123,12 +117,6 @@ func TestNodeFromFileInfo(t *testing.T) {
return
}
s, ok := fi.Sys().(*syscall.Stat_t)
if !ok {
t.Skipf("fi type is %T, not stat_t", fi.Sys())
return
}
node, err := NodeFromFileInfo(test.filename, fi, false)
if err != nil {
t.Fatal(err)
@ -136,10 +124,10 @@ func TestNodeFromFileInfo(t *testing.T) {
switch node.Type {
case restic.NodeTypeFile, restic.NodeTypeSymlink:
checkFile(t, s, node)
checkFile(t, fi, node)
case restic.NodeTypeDev, restic.NodeTypeCharDev:
checkFile(t, s, node)
checkDevice(t, s, node)
checkFile(t, fi, node)
checkDevice(t, fi, node)
default:
t.Fatalf("invalid node type %q", node.Type)
}

View file

@ -3,7 +3,6 @@ package fs
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"reflect"
"runtime"
@ -175,40 +174,6 @@ func restoreExtendedAttributes(nodeType restic.NodeType, path string, eas []exte
return nil
}
type statT syscall.Win32FileAttributeData
func toStatT(i interface{}) (*statT, bool) {
s, ok := i.(*syscall.Win32FileAttributeData)
if ok && s != nil {
return (*statT)(s), true
}
return nil, false
}
func (s statT) dev() uint64 { return 0 }
func (s statT) ino() uint64 { return 0 }
func (s statT) nlink() uint64 { return 0 }
func (s statT) uid() uint32 { return 0 }
func (s statT) gid() uint32 { return 0 }
func (s statT) rdev() uint64 { return 0 }
func (s statT) size() int64 {
return int64(s.FileSizeLow) | (int64(s.FileSizeHigh) << 32)
}
func (s statT) atim() syscall.Timespec {
return syscall.NsecToTimespec(s.LastAccessTime.Nanoseconds())
}
func (s statT) mtim() syscall.Timespec {
return syscall.NsecToTimespec(s.LastWriteTime.Nanoseconds())
}
func (s statT) ctim() syscall.Timespec {
// Windows does not have the concept of a "change time" in the sense Unix uses it, so we're using the LastWriteTime here.
return s.mtim()
}
// restoreGenericAttributes restores generic attributes for Windows
func nodeRestoreGenericAttributes(node *restic.Node, path string, warn func(msg string)) (err error) {
if len(node.GenericAttributes) == 0 {
@ -365,7 +330,7 @@ func decryptFile(pathPointer *uint16) error {
// Created time and Security Descriptors.
// It also checks if the volume supports extended attributes and stores the result in a map
// so that it does not have to be checked again for subsequent calls for paths in the same volume.
func nodeFillGenericAttributes(node *restic.Node, path string, fi os.FileInfo, stat *statT) (allowExtended bool, err error) {
func nodeFillGenericAttributes(node *restic.Node, path string, stat *ExtendedFileInfo) (allowExtended bool, err error) {
if strings.Contains(filepath.Base(path), ":") {
// Do not process for Alternate Data Streams in Windows
// Also do not allow processing of extended attributes for ADS.
@ -396,10 +361,13 @@ func nodeFillGenericAttributes(node *restic.Node, path string, fi os.FileInfo, s
return allowExtended, err
}
}
winFI := stat.Sys().(*syscall.Win32FileAttributeData)
// Add Windows attributes
node.GenericAttributes, err = WindowsAttrsToGenericAttributes(WindowsAttributes{
CreationTime: getCreationTime(fi, path),
FileAttributes: &stat.FileAttributes,
CreationTime: &winFI.CreationTime,
FileAttributes: &winFI.FileAttributes,
SecurityDescriptor: sd,
})
return allowExtended, err
@ -501,18 +469,3 @@ func WindowsAttrsToGenericAttributes(windowsAttributes WindowsAttributes) (attrs
windowsAttributesValue := reflect.ValueOf(windowsAttributes)
return restic.OSAttrsToGenericAttributes(reflect.TypeOf(windowsAttributes), &windowsAttributesValue, runtime.GOOS)
}
// getCreationTime gets the value for the WindowsAttribute CreationTime in a windows specific time format.
// The value is a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC)
// split into two 32-bit parts: the low-order DWORD and the high-order DWORD for efficiency and interoperability.
// The low-order DWORD represents the number of 100-nanosecond intervals elapsed since January 1, 1601, modulo
// 2^32. The high-order DWORD represents the number of times the low-order DWORD has overflowed.
func getCreationTime(fi os.FileInfo, path string) (creationTimeAttribute *syscall.Filetime) {
attrib, success := fi.Sys().(*syscall.Win32FileAttributeData)
if success && attrib != nil {
return &attrib.CreationTime
} else {
debug.Log("Could not get create time for path: %s", path)
return nil
}
}

View file

@ -80,10 +80,10 @@ func TestRestoreCreationTime(t *testing.T) {
path := t.TempDir()
fi, err := os.Lstat(path)
test.OK(t, errors.Wrapf(err, "Could not Lstat for path: %s", path))
creationTimeAttribute := getCreationTime(fi, path)
test.OK(t, errors.Wrapf(err, "Could not get creation time for path: %s", path))
attr := fi.Sys().(*syscall.Win32FileAttributeData)
creationTimeAttribute := attr.CreationTime
//Using the temp dir creation time as the test creation time for the test file and folder
runGenericAttributesTest(t, path, restic.TypeCreationTime, WindowsAttributes{CreationTime: creationTimeAttribute}, false)
runGenericAttributesTest(t, path, restic.TypeCreationTime, WindowsAttributes{CreationTime: &creationTimeAttribute}, false)
}
func TestRestoreFileAttributes(t *testing.T) {

View file

@ -71,7 +71,7 @@ func nodeRestoreGenericAttributes(node *restic.Node, _ string, warn func(msg str
}
// nodeFillGenericAttributes is a no-op.
func nodeFillGenericAttributes(_ *restic.Node, _ string, _ os.FileInfo, _ *statT) (allowExtended bool, err error) {
func nodeFillGenericAttributes(_ *restic.Node, _ string, _ *ExtendedFileInfo) (allowExtended bool, err error) {
return true, nil
}

View file

@ -19,7 +19,7 @@ func extendedStat(fi os.FileInfo) ExtendedFileInfo {
extFI := ExtendedFileInfo{
FileInfo: fi,
Size: int64(s.FileSizeLow) + int64(s.FileSizeHigh)<<32,
Size: int64(s.FileSizeLow) | (int64(s.FileSizeHigh) << 32),
}
atime := syscall.NsecToTimespec(s.LastAccessTime.Nanoseconds())
@ -28,6 +28,7 @@ func extendedStat(fi os.FileInfo) ExtendedFileInfo {
mtime := syscall.NsecToTimespec(s.LastWriteTime.Nanoseconds())
extFI.ModTime = time.Unix(mtime.Unix())
// 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