diff --git a/node.go b/node.go index e607bc8b1..974f83834 100644 --- a/node.go +++ b/node.go @@ -15,6 +15,7 @@ import ( "github.com/restic/restic/debug" "github.com/restic/restic/pack" "github.com/restic/restic/repository" + "runtime" ) // Node is a file, directory or other item in a backup. @@ -148,7 +149,7 @@ func (node *Node) CreateAt(path string, repo *repository.Repository) error { func (node Node) restoreMetadata(path string) error { var err error - err = os.Lchown(path, int(node.UID), int(node.GID)) + err = lchown(path, int(node.UID), int(node.GID)) if err != nil { return errors.Annotate(err, "Lchown") } @@ -236,6 +237,10 @@ func (node Node) createFileAt(path string, repo *repository.Repository) error { } func (node Node) createSymlinkAt(path string) error { + // Windows does not allow non-admins to create soft links. + if runtime.GOOS == "windows" { + return nil + } err := os.Symlink(node.LinkTarget, path) if err != nil { return errors.Annotate(err, "Symlink") @@ -245,15 +250,15 @@ func (node Node) createSymlinkAt(path string) error { } func (node *Node) createDevAt(path string) error { - return syscall.Mknod(path, syscall.S_IFBLK|0600, int(node.Device)) + return mknod(path, syscall.S_IFBLK|0600, int(node.Device)) } func (node *Node) createCharDevAt(path string) error { - return syscall.Mknod(path, syscall.S_IFCHR|0600, int(node.Device)) + return mknod(path, syscall.S_IFCHR|0600, int(node.Device)) } func (node *Node) createFifoAt(path string) error { - return syscall.Mkfifo(path, 0600) + return mkfifo(path, 0600) } func (node Node) MarshalJSON() ([]byte, error) { @@ -381,9 +386,19 @@ func (node *Node) isNewer(path string, fi os.FileInfo) bool { return true } - extendedStat := fi.Sys().(*syscall.Stat_t) - inode := extendedStat.Ino - size := uint64(extendedStat.Size) + size := uint64(fi.Size()) + + extendedStat, ok := toStatT(fi.Sys()) + if !ok { + if node.ModTime != fi.ModTime() || + node.Size != size { + debug.Log("node.isNewer", "node %v is newer: timestamp, size or inode changed", path) + return true + } + return false + } + + inode := extendedStat.ino() if node.ModTime != fi.ModTime() || node.ChangeTime != changeTime(extendedStat) || @@ -397,11 +412,11 @@ func (node *Node) isNewer(path string, fi os.FileInfo) bool { return false } -func (node *Node) fillUser(stat *syscall.Stat_t) error { - node.UID = stat.Uid - node.GID = stat.Gid +func (node *Node) fillUser(stat statT) error { + node.UID = stat.uid() + node.GID = stat.gid() - username, err := lookupUsername(strconv.Itoa(int(stat.Uid))) + username, err := lookupUsername(strconv.Itoa(int(stat.uid()))) if err != nil { return errors.Annotate(err, "fillUser") } @@ -439,12 +454,12 @@ func lookupUsername(uid string) (string, error) { } func (node *Node) fillExtra(path string, fi os.FileInfo) error { - stat, ok := fi.Sys().(*syscall.Stat_t) + stat, ok := toStatT(fi.Sys()) if !ok { return nil } - node.Inode = uint64(stat.Ino) + node.Inode = uint64(stat.ino()) node.fillTimes(stat) @@ -456,15 +471,15 @@ func (node *Node) fillExtra(path string, fi os.FileInfo) error { switch node.Type { case "file": - node.Size = uint64(stat.Size) - node.Links = uint64(stat.Nlink) + node.Size = uint64(stat.size()) + node.Links = uint64(stat.nlink()) case "dir": case "symlink": node.LinkTarget, err = os.Readlink(path) case "dev": - node.Device = uint64(stat.Rdev) + node.Device = uint64(stat.rdev()) case "chardev": - node.Device = uint64(stat.Rdev) + node.Device = uint64(stat.rdev()) case "fifo": case "socket": default: @@ -473,3 +488,32 @@ func (node *Node) fillExtra(path string, fi os.FileInfo) error { return err } + +type statT interface { + dev() uint64 + ino() uint64 + nlink() uint64 + uid() uint32 + gid() uint32 + rdev() uint64 + size() int64 + atim() syscall.Timespec + mtim() syscall.Timespec + ctim() syscall.Timespec +} + +func mkfifo(path string, mode uint32) (err error) { + return mknod(path, mode|syscall.S_IFIFO, 0) +} + +func (node *Node) fillTimes(stat statT) { + ctim := stat.ctim() + atim := stat.atim() + node.ChangeTime = time.Unix(ctim.Unix()) + node.AccessTime = time.Unix(atim.Unix()) +} + +func changeTime(stat statT) time.Time { + ctim := stat.ctim() + return time.Unix(ctim.Unix()) +} diff --git a/node_darwin.go b/node_darwin.go index f63b41f13..adc980295 100644 --- a/node_darwin.go +++ b/node_darwin.go @@ -3,22 +3,16 @@ package restic import ( "os" "syscall" - "time" ) func (node *Node) OpenForReading() (*os.File, error) { return os.Open(node.path) } -func changeTime(stat *syscall.Stat_t) time.Time { - return time.Unix(stat.Ctimespec.Unix()) -} - -func (node *Node) fillTimes(stat *syscall.Stat_t) { - node.ChangeTime = time.Unix(stat.Ctimespec.Unix()) - node.AccessTime = time.Unix(stat.Atimespec.Unix()) -} - func (node Node) restoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error { return nil } + +func (s statUnix) atim() syscall.Timespec { return s.Atimespec } +func (s statUnix) mtim() syscall.Timespec { return s.Mtimespec } +func (s statUnix) ctim() syscall.Timespec { return s.Ctimespec } diff --git a/node_freebsd.go b/node_freebsd.go index 231cf8db7..67bcdf3e9 100644 --- a/node_freebsd.go +++ b/node_freebsd.go @@ -3,22 +3,16 @@ package restic import ( "os" "syscall" - "time" ) func (node *Node) OpenForReading() (*os.File, error) { return os.OpenFile(node.path, os.O_RDONLY, 0) } -func (node *Node) fillTimes(stat *syscall.Stat_t) { - node.ChangeTime = time.Unix(stat.Ctimespec.Unix()) - node.AccessTime = time.Unix(stat.Atimespec.Unix()) -} - -func changeTime(stat *syscall.Stat_t) time.Time { - return time.Unix(stat.Ctimespec.Unix()) -} - func (node Node) restoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error { return nil } + +func (s statUnix) atim() syscall.Timespec { return s.Atimespec } +func (s statUnix) mtim() syscall.Timespec { return s.Mtimespec } +func (s statUnix) ctim() syscall.Timespec { return s.Ctimespec } diff --git a/node_linux.go b/node_linux.go index 1043397a8..2304c13d5 100644 --- a/node_linux.go +++ b/node_linux.go @@ -4,7 +4,6 @@ import ( "os" "path/filepath" "syscall" - "time" "unsafe" "github.com/juju/errors" @@ -18,15 +17,6 @@ func (node *Node) OpenForReading() (*os.File, error) { return file, err } -func (node *Node) fillTimes(stat *syscall.Stat_t) { - node.ChangeTime = time.Unix(stat.Ctim.Unix()) - node.AccessTime = time.Unix(stat.Atim.Unix()) -} - -func changeTime(stat *syscall.Stat_t) time.Time { - return time.Unix(stat.Ctim.Unix()) -} - func (node Node) restoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error { dir, err := os.Open(filepath.Dir(path)) defer dir.Close() @@ -65,3 +55,7 @@ func utimensat(dirfd int, path string, times *[2]syscall.Timespec, flags int) (e func utimesNanoAt(dirfd int, path string, ts [2]syscall.Timespec, flags int) (err error) { return utimensat(dirfd, path, (*[2]syscall.Timespec)(unsafe.Pointer(&ts[0])), flags) } + +func (s statUnix) atim() syscall.Timespec { return s.Atim } +func (s statUnix) mtim() syscall.Timespec { return s.Mtim } +func (s statUnix) ctim() syscall.Timespec { return s.Ctim } diff --git a/node_openbsd.go b/node_openbsd.go index 38f1d8441..feafe307e 100644 --- a/node_openbsd.go +++ b/node_openbsd.go @@ -3,7 +3,6 @@ package restic import ( "os" "syscall" - "time" ) func (node *Node) OpenForReading() (*os.File, error) { @@ -14,15 +13,10 @@ func (node *Node) OpenForReading() (*os.File, error) { return file, err } -func (node *Node) fillTimes(stat *syscall.Stat_t) { - node.ChangeTime = time.Unix(stat.Ctim.Unix()) - node.AccessTime = time.Unix(stat.Atim.Unix()) -} - -func changeTime(stat *syscall.Stat_t) time.Time { - return time.Unix(stat.Ctim.Unix()) -} - func (node Node) restoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error { return nil } + +func (s statUnix) atim() syscall.Timespec { return s.Atim } +func (s statUnix) mtim() syscall.Timespec { return s.Mtim } +func (s statUnix) ctim() syscall.Timespec { return s.Ctim } diff --git a/node_test.go b/node_test.go index f6104b49b..5bdf284bb 100644 --- a/node_test.go +++ b/node_test.go @@ -119,6 +119,9 @@ func TestNodeRestoreAt(t *testing.T) { nodePath := filepath.Join(tempdir, test.Name) OK(t, test.CreateAt(nodePath, nil)) + if test.Type == "symlink" && runtime.GOOS == "windows" { + continue + } if test.Type == "dir" { OK(t, test.RestoreTimestamps(nodePath)) } @@ -135,14 +138,16 @@ func TestNodeRestoreAt(t *testing.T) { "%v: type doesn't match (%v != %v)", test.Type, test.Type, n2.Type) Assert(t, test.Size == n2.Size, "%v: size doesn't match (%v != %v)", test.Size, test.Size, n2.Size) - Assert(t, test.UID == n2.UID, - "%v: UID doesn't match (%v != %v)", test.Type, test.UID, n2.UID) - Assert(t, test.GID == n2.GID, - "%v: GID doesn't match (%v != %v)", test.Type, test.GID, n2.GID) - if test.Type != "symlink" { - Assert(t, test.Mode == n2.Mode, - "%v: mode doesn't match (%v != %v)", test.Type, test.Mode, n2.Mode) + if runtime.GOOS != "windows" { + Assert(t, test.UID == n2.UID, + "%v: UID doesn't match (%v != %v)", test.Type, test.UID, n2.UID) + Assert(t, test.GID == n2.GID, + "%v: GID doesn't match (%v != %v)", test.Type, test.GID, n2.GID) + if test.Type != "symlink" { + Assert(t, test.Mode == n2.Mode, + "%v: mode doesn't match (%v != %v)", test.Type, test.Mode, n2.Mode) + } } AssertFsTimeEqual(t, "AccessTime", test.Type, test.AccessTime, n2.AccessTime) diff --git a/node_unix.go b/node_unix.go new file mode 100644 index 000000000..5c0c4d63c --- /dev/null +++ b/node_unix.go @@ -0,0 +1,34 @@ +// + +// +build dragonfly linux netbsd openbsd freebsd solaris darwin + +package restic + +import ( + "os" + "syscall" +) + +var mknod = syscall.Mknod +var lchown = os.Lchown + +type statUnix syscall.Stat_t + +func toStatT(i interface{}) (statT, bool) { + if i == nil { + return nil, false + } + s, ok := i.(*syscall.Stat_t) + if ok && s != nil { + return statUnix(*s), true + } + return nil, false +} + +func (s statUnix) dev() uint64 { return uint64(s.Dev) } +func (s statUnix) ino() uint64 { return uint64(s.Ino) } +func (s statUnix) nlink() uint64 { return uint64(s.Nlink) } +func (s statUnix) uid() uint32 { return uint32(s.Uid) } +func (s statUnix) gid() uint32 { return uint32(s.Gid) } +func (s statUnix) rdev() uint64 { return uint64(s.Rdev) } +func (s statUnix) size() int64 { return int64(s.Size) } diff --git a/node_windows.go b/node_windows.go new file mode 100644 index 000000000..7d5854b80 --- /dev/null +++ b/node_windows.go @@ -0,0 +1,66 @@ +package restic + +import ( + "os" + "syscall" +) + +func (node *Node) OpenForReading() (*os.File, error) { + file, err := os.OpenFile(node.path, os.O_RDONLY, 0) + if os.IsPermission(err) { + return os.OpenFile(node.path, os.O_RDONLY, 0) + } + return file, err +} + +// mknod() creates a filesystem node (file, device +// special file, or named pipe) named pathname, with attributes +// specified by mode and dev. +var mknod = func(path string, mode uint32, dev int) (err error) { + panic("mknod not implemented") +} + +// Windows doesn't need lchown +var lchown = func(path string, uid int, gid int) (err error) { + return nil +} + +func (node Node) restoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error { + return nil +} + +type statWin syscall.Win32FileAttributeData + +func toStatT(i interface{}) (statT, bool) { + if i == nil { + return nil, false + } + s, ok := i.(*syscall.Win32FileAttributeData) + if ok && s != nil { + return statWin(*s), true + } + return nil, false +} + +func (s statWin) dev() uint64 { return 0 } +func (s statWin) ino() uint64 { return 0 } +func (s statWin) nlink() uint64 { return 0 } +func (s statWin) uid() uint32 { return 0 } +func (s statWin) gid() uint32 { return 0 } +func (s statWin) rdev() uint64 { return 0 } + +func (s statWin) size() int64 { + return int64(s.FileSizeLow) | (int64(s.FileSizeHigh) << 32) +} + +func (s statWin) atim() syscall.Timespec { + return syscall.NsecToTimespec(s.LastAccessTime.Nanoseconds()) +} + +func (s statWin) mtim() syscall.Timespec { + return syscall.NsecToTimespec(s.LastWriteTime.Nanoseconds()) +} + +func (s statWin) ctim() syscall.Timespec { + return syscall.NsecToTimespec(s.CreationTime.Nanoseconds()) +}