2
2
mirror of https://github.com/octoleo/restic.git synced 2024-11-22 21:05:10 +00:00

Add Windows node support.

The syscall.Stat_t doesn't exist on Windows, so it is replaced by an interface,
which Windows can fill out, and field access is replaced by function calls.

Common Unix functionality is put into "node_unix.go", so there is less boilerplate.

Symlinks are skipped on Windows, since they require admin privileges.
This commit is contained in:
Klaus Post 2015-08-14 15:57:47 +02:00
parent 2e7b40baca
commit dfe232cf46
8 changed files with 189 additions and 64 deletions

78
node.go
View File

@ -15,6 +15,7 @@ import (
"github.com/restic/restic/debug" "github.com/restic/restic/debug"
"github.com/restic/restic/pack" "github.com/restic/restic/pack"
"github.com/restic/restic/repository" "github.com/restic/restic/repository"
"runtime"
) )
// Node is a file, directory or other item in a backup. // 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 { func (node Node) restoreMetadata(path string) error {
var err 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 { if err != nil {
return errors.Annotate(err, "Lchown") 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 { 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) err := os.Symlink(node.LinkTarget, path)
if err != nil { if err != nil {
return errors.Annotate(err, "Symlink") return errors.Annotate(err, "Symlink")
@ -245,15 +250,15 @@ func (node Node) createSymlinkAt(path string) error {
} }
func (node *Node) createDevAt(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 { 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 { func (node *Node) createFifoAt(path string) error {
return syscall.Mkfifo(path, 0600) return mkfifo(path, 0600)
} }
func (node Node) MarshalJSON() ([]byte, error) { func (node Node) MarshalJSON() ([]byte, error) {
@ -381,9 +386,19 @@ func (node *Node) isNewer(path string, fi os.FileInfo) bool {
return true return true
} }
extendedStat := fi.Sys().(*syscall.Stat_t) size := uint64(fi.Size())
inode := extendedStat.Ino
size := uint64(extendedStat.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() || if node.ModTime != fi.ModTime() ||
node.ChangeTime != changeTime(extendedStat) || node.ChangeTime != changeTime(extendedStat) ||
@ -397,11 +412,11 @@ func (node *Node) isNewer(path string, fi os.FileInfo) bool {
return false return false
} }
func (node *Node) fillUser(stat *syscall.Stat_t) error { func (node *Node) fillUser(stat statT) error {
node.UID = stat.Uid node.UID = stat.uid()
node.GID = stat.Gid node.GID = stat.gid()
username, err := lookupUsername(strconv.Itoa(int(stat.Uid))) username, err := lookupUsername(strconv.Itoa(int(stat.uid())))
if err != nil { if err != nil {
return errors.Annotate(err, "fillUser") 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 { func (node *Node) fillExtra(path string, fi os.FileInfo) error {
stat, ok := fi.Sys().(*syscall.Stat_t) stat, ok := toStatT(fi.Sys())
if !ok { if !ok {
return nil return nil
} }
node.Inode = uint64(stat.Ino) node.Inode = uint64(stat.ino())
node.fillTimes(stat) node.fillTimes(stat)
@ -456,15 +471,15 @@ func (node *Node) fillExtra(path string, fi os.FileInfo) error {
switch node.Type { switch node.Type {
case "file": case "file":
node.Size = uint64(stat.Size) node.Size = uint64(stat.size())
node.Links = uint64(stat.Nlink) node.Links = uint64(stat.nlink())
case "dir": case "dir":
case "symlink": case "symlink":
node.LinkTarget, err = os.Readlink(path) node.LinkTarget, err = os.Readlink(path)
case "dev": case "dev":
node.Device = uint64(stat.Rdev) node.Device = uint64(stat.rdev())
case "chardev": case "chardev":
node.Device = uint64(stat.Rdev) node.Device = uint64(stat.rdev())
case "fifo": case "fifo":
case "socket": case "socket":
default: default:
@ -473,3 +488,32 @@ func (node *Node) fillExtra(path string, fi os.FileInfo) error {
return err 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())
}

View File

@ -3,22 +3,16 @@ package restic
import ( import (
"os" "os"
"syscall" "syscall"
"time"
) )
func (node *Node) OpenForReading() (*os.File, error) { func (node *Node) OpenForReading() (*os.File, error) {
return os.Open(node.path) 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 { func (node Node) restoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
return nil 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 }

View File

@ -3,22 +3,16 @@ package restic
import ( import (
"os" "os"
"syscall" "syscall"
"time"
) )
func (node *Node) OpenForReading() (*os.File, error) { func (node *Node) OpenForReading() (*os.File, error) {
return os.OpenFile(node.path, os.O_RDONLY, 0) 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 { func (node Node) restoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
return nil 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 }

View File

@ -4,7 +4,6 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"syscall" "syscall"
"time"
"unsafe" "unsafe"
"github.com/juju/errors" "github.com/juju/errors"
@ -18,15 +17,6 @@ func (node *Node) OpenForReading() (*os.File, error) {
return file, err 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 { func (node Node) restoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
dir, err := os.Open(filepath.Dir(path)) dir, err := os.Open(filepath.Dir(path))
defer dir.Close() 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) { 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) 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 }

View File

@ -3,7 +3,6 @@ package restic
import ( import (
"os" "os"
"syscall" "syscall"
"time"
) )
func (node *Node) OpenForReading() (*os.File, error) { func (node *Node) OpenForReading() (*os.File, error) {
@ -14,15 +13,10 @@ func (node *Node) OpenForReading() (*os.File, error) {
return file, err 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 { func (node Node) restoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
return nil 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 }

View File

@ -119,6 +119,9 @@ func TestNodeRestoreAt(t *testing.T) {
nodePath := filepath.Join(tempdir, test.Name) nodePath := filepath.Join(tempdir, test.Name)
OK(t, test.CreateAt(nodePath, nil)) OK(t, test.CreateAt(nodePath, nil))
if test.Type == "symlink" && runtime.GOOS == "windows" {
continue
}
if test.Type == "dir" { if test.Type == "dir" {
OK(t, test.RestoreTimestamps(nodePath)) 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) "%v: type doesn't match (%v != %v)", test.Type, test.Type, n2.Type)
Assert(t, test.Size == n2.Size, Assert(t, test.Size == n2.Size,
"%v: size doesn't match (%v != %v)", test.Size, 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" { if runtime.GOOS != "windows" {
Assert(t, test.Mode == n2.Mode, Assert(t, test.UID == n2.UID,
"%v: mode doesn't match (%v != %v)", test.Type, test.Mode, n2.Mode) "%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) AssertFsTimeEqual(t, "AccessTime", test.Type, test.AccessTime, n2.AccessTime)

34
node_unix.go Normal file
View File

@ -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) }

66
node_windows.go Normal file
View File

@ -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())
}