2
2
mirror of https://github.com/octoleo/restic.git synced 2024-11-26 23:06:32 +00:00

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

View File

@ -4,7 +4,6 @@
package fs package fs
import ( import (
"os"
"syscall" "syscall"
"github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/restic"
@ -14,17 +13,6 @@ func nodeRestoreSymlinkTimestamps(_ string, _ [2]syscall.Timespec) error {
return nil 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. // nodeRestoreExtendedAttributes is a no-op on AIX.
func nodeRestoreExtendedAttributes(_ *restic.Node, _ string) error { func nodeRestoreExtendedAttributes(_ *restic.Node, _ string) error {
return nil return nil
@ -46,6 +34,6 @@ func nodeRestoreGenericAttributes(node *restic.Node, _ string, warn func(msg str
} }
// nodeFillGenericAttributes is a no-op on AIX. // 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 return true, nil
} }

View File

@ -5,7 +5,3 @@ import "syscall"
func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error { func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
return nil 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) { func mknod(path string, mode uint32, dev uint64) (err error) {
return syscall.Mknod(path, mode, dev) 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() 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 package fs
import ( import (
"os"
"syscall" "syscall"
"github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/restic"
@ -11,10 +10,6 @@ func nodeRestoreSymlinkTimestamps(_ string, _ [2]syscall.Timespec) error {
return nil 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. // nodeRestoreExtendedAttributes is a no-op on netbsd.
func nodeRestoreExtendedAttributes(_ *restic.Node, _ string) error { func nodeRestoreExtendedAttributes(_ *restic.Node, _ string) error {
return nil return nil
@ -36,6 +31,6 @@ func nodeRestoreGenericAttributes(node *restic.Node, _ string, warn func(msg str
} }
// nodeFillGenericAttributes is a no-op on netbsd. // 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 return true, nil
} }

View File

@ -1,7 +1,6 @@
package fs package fs
import ( import (
"os"
"syscall" "syscall"
"github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/restic"
@ -11,10 +10,6 @@ func nodeRestoreSymlinkTimestamps(_ string, _ [2]syscall.Timespec) error {
return nil 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. // nodeRestoreExtendedAttributes is a no-op on openbsd.
func nodeRestoreExtendedAttributes(_ *restic.Node, _ string) error { func nodeRestoreExtendedAttributes(_ *restic.Node, _ string) error {
return nil return nil
@ -36,6 +31,6 @@ func nodeRestoreGenericAttributes(node *restic.Node, _ string, warn func(msg str
} }
// fillGenericAttributes is a no-op on openbsd. // 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 return true, nil
} }

View File

@ -5,7 +5,3 @@ import "syscall"
func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error { func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
return nil 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 ( import (
"os" "os"
"syscall"
) )
func lchown(name string, uid, gid int) error { func lchown(name string, uid, gid int) error {
return os.Lchown(name, uid, gid) 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 package fs
import ( import (
"io/fs"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"syscall" "syscall"
"testing" "testing"
"time"
"github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test" 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 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() t.Helper()
stat := fi.Sys().(*syscall.Stat_t)
if uint32(node.Mode.Perm()) != uint32(stat.Mode&0777) { if uint32(node.Mode.Perm()) != uint32(stat.Mode&0777) {
t.Errorf("Mode does not match, want %v, got %v", stat.Mode&0777, node.Mode) 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 // use the os dependent function to compare the timestamps
s, ok := toStatT(stat) s := ExtendedStat(fi)
if !ok { if node.ModTime != s.ModTime {
return t.Errorf("ModTime does not match, want %v, got %v", s.ModTime, node.ModTime)
} }
if node.ChangeTime != s.ChangeTime {
mtime := s.mtim() t.Errorf("ChangeTime does not match, want %v, got %v", s.ChangeTime, node.ChangeTime)
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.AccessTime != s.AccessTime {
ctime := s.ctim() t.Errorf("AccessTime does not match, want %v, got %v", s.AccessTime, node.AccessTime)
if node.ChangeTime != time.Unix(ctime.Unix()) {
t.Errorf("ChangeTime does not match, want %v, got %v", time.Unix(ctime.Unix()), node.ChangeTime)
} }
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) { if node.Device != uint64(stat.Rdev) {
t.Errorf("Rdev does not match, want %v, got %v", stat.Rdev, node.Device) t.Errorf("Rdev does not match, want %v, got %v", stat.Rdev, node.Device)
} }
@ -123,12 +117,6 @@ func TestNodeFromFileInfo(t *testing.T) {
return 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) node, err := NodeFromFileInfo(test.filename, fi, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -136,10 +124,10 @@ func TestNodeFromFileInfo(t *testing.T) {
switch node.Type { switch node.Type {
case restic.NodeTypeFile, restic.NodeTypeSymlink: case restic.NodeTypeFile, restic.NodeTypeSymlink:
checkFile(t, s, node) checkFile(t, fi, node)
case restic.NodeTypeDev, restic.NodeTypeCharDev: case restic.NodeTypeDev, restic.NodeTypeCharDev:
checkFile(t, s, node) checkFile(t, fi, node)
checkDevice(t, s, node) checkDevice(t, fi, node)
default: default:
t.Fatalf("invalid node type %q", node.Type) t.Fatalf("invalid node type %q", node.Type)
} }

View File

@ -3,7 +3,6 @@ package fs
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"runtime" "runtime"
@ -175,40 +174,6 @@ func restoreExtendedAttributes(nodeType restic.NodeType, path string, eas []exte
return nil 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 // restoreGenericAttributes restores generic attributes for Windows
func nodeRestoreGenericAttributes(node *restic.Node, path string, warn func(msg string)) (err error) { func nodeRestoreGenericAttributes(node *restic.Node, path string, warn func(msg string)) (err error) {
if len(node.GenericAttributes) == 0 { if len(node.GenericAttributes) == 0 {
@ -365,7 +330,7 @@ func decryptFile(pathPointer *uint16) error {
// Created time and Security Descriptors. // Created time and Security Descriptors.
// It also checks if the volume supports extended attributes and stores the result in a map // 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. // 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), ":") { if strings.Contains(filepath.Base(path), ":") {
// Do not process for Alternate Data Streams in Windows // Do not process for Alternate Data Streams in Windows
// Also do not allow processing of extended attributes for ADS. // 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 return allowExtended, err
} }
} }
winFI := stat.Sys().(*syscall.Win32FileAttributeData)
// Add Windows attributes // Add Windows attributes
node.GenericAttributes, err = WindowsAttrsToGenericAttributes(WindowsAttributes{ node.GenericAttributes, err = WindowsAttrsToGenericAttributes(WindowsAttributes{
CreationTime: getCreationTime(fi, path), CreationTime: &winFI.CreationTime,
FileAttributes: &stat.FileAttributes, FileAttributes: &winFI.FileAttributes,
SecurityDescriptor: sd, SecurityDescriptor: sd,
}) })
return allowExtended, err return allowExtended, err
@ -501,18 +469,3 @@ func WindowsAttrsToGenericAttributes(windowsAttributes WindowsAttributes) (attrs
windowsAttributesValue := reflect.ValueOf(windowsAttributes) windowsAttributesValue := reflect.ValueOf(windowsAttributes)
return restic.OSAttrsToGenericAttributes(reflect.TypeOf(windowsAttributes), &windowsAttributesValue, runtime.GOOS) 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() path := t.TempDir()
fi, err := os.Lstat(path) fi, err := os.Lstat(path)
test.OK(t, errors.Wrapf(err, "Could not Lstat for path: %s", path)) test.OK(t, errors.Wrapf(err, "Could not Lstat for path: %s", path))
creationTimeAttribute := getCreationTime(fi, path) attr := fi.Sys().(*syscall.Win32FileAttributeData)
test.OK(t, errors.Wrapf(err, "Could not get creation time for path: %s", path)) creationTimeAttribute := attr.CreationTime
//Using the temp dir creation time as the test creation time for the test file and folder //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) { 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. // 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 return true, nil
} }

View File

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