diff --git a/internal/fs/node.go b/internal/fs/node.go index 2dfe54172..a8aa3c408 100644 --- a/internal/fs/node.go +++ b/internal/fs/node.go @@ -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{} diff --git a/internal/fs/node_aix.go b/internal/fs/node_aix.go index 123985c2d..2967c6db7 100644 --- a/internal/fs/node_aix.go +++ b/internal/fs/node_aix.go @@ -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 } diff --git a/internal/fs/node_darwin.go b/internal/fs/node_darwin.go index 1ca7ce480..f4c843498 100644 --- a/internal/fs/node_darwin.go +++ b/internal/fs/node_darwin.go @@ -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 } diff --git a/internal/fs/node_freebsd.go b/internal/fs/node_freebsd.go index 8796358b0..1b2f2fc7e 100644 --- a/internal/fs/node_freebsd.go +++ b/internal/fs/node_freebsd.go @@ -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 } diff --git a/internal/fs/node_linux.go b/internal/fs/node_linux.go index 4cb9bdeaa..91ef4f907 100644 --- a/internal/fs/node_linux.go +++ b/internal/fs/node_linux.go @@ -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 } diff --git a/internal/fs/node_netbsd.go b/internal/fs/node_netbsd.go index 996125851..00e6c92ca 100644 --- a/internal/fs/node_netbsd.go +++ b/internal/fs/node_netbsd.go @@ -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 } diff --git a/internal/fs/node_openbsd.go b/internal/fs/node_openbsd.go index 62eb78618..590c603a3 100644 --- a/internal/fs/node_openbsd.go +++ b/internal/fs/node_openbsd.go @@ -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 } diff --git a/internal/fs/node_solaris.go b/internal/fs/node_solaris.go index 3f025b334..f4c843498 100644 --- a/internal/fs/node_solaris.go +++ b/internal/fs/node_solaris.go @@ -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 } diff --git a/internal/fs/node_unix.go b/internal/fs/node_unix.go index fb247ac99..5f08f3623 100644 --- a/internal/fs/node_unix.go +++ b/internal/fs/node_unix.go @@ -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) } diff --git a/internal/fs/node_unix_test.go b/internal/fs/node_unix_test.go index 3658c8356..4d01b6cc5 100644 --- a/internal/fs/node_unix_test.go +++ b/internal/fs/node_unix_test.go @@ -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) } diff --git a/internal/fs/node_windows.go b/internal/fs/node_windows.go index d529e3503..0a9064fc5 100644 --- a/internal/fs/node_windows.go +++ b/internal/fs/node_windows.go @@ -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 - } -} diff --git a/internal/fs/node_windows_test.go b/internal/fs/node_windows_test.go index 618364879..218e729ce 100644 --- a/internal/fs/node_windows_test.go +++ b/internal/fs/node_windows_test.go @@ -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) { diff --git a/internal/fs/node_xattr.go b/internal/fs/node_xattr.go index 55376ba58..1781452f7 100644 --- a/internal/fs/node_xattr.go +++ b/internal/fs/node_xattr.go @@ -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 } diff --git a/internal/fs/stat_windows.go b/internal/fs/stat_windows.go index ee678d92a..57f330fb5 100644 --- a/internal/fs/stat_windows.go +++ b/internal/fs/stat_windows.go @@ -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