diff --git a/node.go b/node.go index d6252f572..fe62bd273 100644 --- a/node.go +++ b/node.go @@ -66,7 +66,7 @@ func NodeFromFileInfo(path string, fi os.FileInfo) (*Node, error) { ModTime: fi.ModTime(), } - node.Type = nodeTypeFromFileInfo(path, fi) + node.Type = nodeTypeFromFileInfo(fi) if node.Type == "file" { node.Size = uint64(fi.Size()) } @@ -75,7 +75,7 @@ func NodeFromFileInfo(path string, fi os.FileInfo) (*Node, error) { return node, err } -func nodeTypeFromFileInfo(path string, fi os.FileInfo) string { +func nodeTypeFromFileInfo(fi os.FileInfo) string { switch fi.Mode() & (os.ModeType | os.ModeCharDevice) { case 0: return "file" @@ -96,155 +96,114 @@ func nodeTypeFromFileInfo(path string, fi os.FileInfo) string { return "" } -func CreateNodeAt(node *Node, m *Map, s *server.Server, path string) error { +func (node *Node) CreateAt(path string, m *Map, s *server.Server) error { switch node.Type { case "dir": - err := os.Mkdir(path, node.Mode) - if err != nil { - return arrar.Annotate(err, "Mkdir") - } - - err = os.Lchown(path, int(node.UID), int(node.GID)) - if err != nil { - return arrar.Annotate(err, "Lchown") - } - - var utimes = []syscall.Timespec{ - syscall.NsecToTimespec(node.AccessTime.UnixNano()), - syscall.NsecToTimespec(node.ModTime.UnixNano()), - } - err = syscall.UtimesNano(path, utimes) - if err != nil { - return arrar.Annotate(err, "Utimesnano") + if err := node.createDirAt(path); err != nil { + return err } case "file": - // TODO: handle hard links - f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0600) - defer f.Close() - if err != nil { - return arrar.Annotate(err, "OpenFile") - } - - for _, blobid := range node.Content { - blob, err := m.FindID(blobid) - if err != nil { - return arrar.Annotate(err, "Find Blob") - } - - buf, err := s.Load(backend.Data, blob) - if err != nil { - return arrar.Annotate(err, "Load") - } - - _, err = f.Write(buf) - if err != nil { - return arrar.Annotate(err, "Write") - } - } - - f.Close() - - err = os.Lchown(path, int(node.UID), int(node.GID)) - if err != nil { - return arrar.Annotate(err, "Lchown") - } - - var utimes = []syscall.Timespec{ - syscall.NsecToTimespec(node.AccessTime.UnixNano()), - syscall.NsecToTimespec(node.ModTime.UnixNano()), - } - err = syscall.UtimesNano(path, utimes) - if err != nil { - return arrar.Annotate(err, "Utimesnano") + if err := node.createFileAt(path, m, s); err != nil { + return err } case "symlink": - err := os.Symlink(node.LinkTarget, path) - if err != nil { - return arrar.Annotate(err, "Symlink") + if err := node.createSymlinkAt(path); err != nil { + return err } - - err = os.Lchown(path, int(node.UID), int(node.GID)) - if err != nil { - return arrar.Annotate(err, "Lchown") - } - - // f, err := os.OpenFile(path, O_PATH|syscall.O_NOFOLLOW, 0600) - // defer f.Close() - // if err != nil { - // return arrar.Annotate(err, "OpenFile") - // } - - // TODO: Get Futimes() working on older Linux kernels (fails with 3.2.0) - // var utimes = []syscall.Timeval{ - // syscall.NsecToTimeval(node.AccessTime.UnixNano()), - // syscall.NsecToTimeval(node.ModTime.UnixNano()), - // } - // err = syscall.Futimes(int(f.Fd()), utimes) - // if err != nil { - // return arrar.Annotate(err, "Futimes") - // } - - return nil case "dev": - err := node.createDevAt(path) - if err != nil { + if err := node.createDevAt(path); err != nil { return arrar.Annotate(err, "Mknod") } case "chardev": - err := node.createCharDevAt(path) - if err != nil { + if err := node.createCharDevAt(path); err != nil { return arrar.Annotate(err, "Mknod") } case "fifo": - err := node.createFifoAt(path) - if err != nil { + if err := node.createFifoAt(path); err != nil { return arrar.Annotate(err, "Mkfifo") } case "socket": - // nothing to do, we do not restore sockets return nil default: return fmt.Errorf("filetype %q not implemented!\n", node.Type) } - err := os.Chmod(path, node.Mode) + return node.restoreMetadata(path) +} + +func (node Node) restoreMetadata(path string) error { + var err error + + err = os.Lchown(path, int(node.UID), int(node.GID)) + if err != nil { + return arrar.Annotate(err, "Lchown") + } + + if node.Type == "symlink" { + return nil + } + + err = os.Chmod(path, node.Mode) if err != nil { return arrar.Annotate(err, "Chmod") } - err = os.Chown(path, int(node.UID), int(node.GID)) - if err != nil { - return arrar.Annotate(err, "Chown") + var utimes = []syscall.Timespec{ + syscall.NsecToTimespec(node.AccessTime.UnixNano()), + syscall.NsecToTimespec(node.ModTime.UnixNano()), } - - err = os.Chtimes(path, node.AccessTime, node.ModTime) + err = syscall.UtimesNano(path, utimes) if err != nil { - return arrar.Annotate(err, "Chtimes") + return arrar.Annotate(err, "Utimesnano") } return nil } -func (node Node) SameContent(olderNode *Node) bool { - // if this node has a type other than "file", treat as if content has changed - if node.Type != "file" { - return false +func (node Node) createDirAt(path string) error { + err := os.Mkdir(path, node.Mode) + if err != nil { + return arrar.Annotate(err, "Mkdir") } - // if the name or type has changed, this is surely something different - if node.Name != olderNode.Name || node.Type != olderNode.Type { - return false + return nil +} + +func (node Node) createFileAt(path string, m *Map, s *server.Server) error { + f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0600) + defer f.Close() + + if err != nil { + return arrar.Annotate(err, "OpenFile") } - // if timestamps or inodes differ, content has changed - if node.ModTime != olderNode.ModTime || - node.ChangeTime != olderNode.ChangeTime || - node.Inode != olderNode.Inode { - return false + for _, blobid := range node.Content { + blob, err := m.FindID(blobid) + if err != nil { + return arrar.Annotate(err, "Find Blob") + } + + buf, err := s.Load(backend.Data, blob) + if err != nil { + return arrar.Annotate(err, "Load") + } + + _, err = f.Write(buf) + if err != nil { + return arrar.Annotate(err, "Write") + } } - // otherwise the node is assumed to have the same content - return true + return nil +} + +func (node Node) createSymlinkAt(path string) error { + err := os.Symlink(node.LinkTarget, path) + if err != nil { + return arrar.Annotate(err, "Symlink") + } + + return nil } func (node Node) MarshalJSON() ([]byte, error) { @@ -270,7 +229,6 @@ func (node *Node) UnmarshalJSON(data []byte) error { } func (node Node) Equals(other Node) bool { - // TODO: add generatored code for this if node.Name != other.Name { return false } @@ -316,29 +274,37 @@ func (node Node) Equals(other Node) bool { if node.Device != other.Device { return false } - if node.Content != nil && other.Content == nil { + if !node.sameContent(other) { return false - } else if node.Content == nil && other.Content != nil { - return false - } else if node.Content != nil && other.Content != nil { - if len(node.Content) != len(other.Content) { - return false - } - - for i := 0; i < len(node.Content); i++ { - if !node.Content[i].Equal(other.Content[i]) { - return false - } - } } - if !node.Subtree.Equal(other.Subtree) { return false } - if node.Error != other.Error { return false } return true } + +func (node Node) sameContent(other Node) bool { + if node.Content == nil { + return other.Content == nil + } + + if other.Content == nil { + return false + } + + if len(node.Content) != len(other.Content) { + return false + } + + for i := 0; i < len(node.Content); i++ { + if !node.Content[i].Equal(other.Content[i]) { + return false + } + } + + return true +} diff --git a/node_darwin.go b/node_darwin.go index 4d3297e2a..ae4b0060a 100644 --- a/node_darwin.go +++ b/node_darwin.go @@ -15,10 +15,10 @@ func (node *Node) OpenForReading() (*os.File, error) { return os.Open(node.path) } -func (node *Node) fillExtra(path string, fi os.FileInfo) (err error) { +func (node *Node) fillExtra(path string, fi os.FileInfo) error { stat, ok := fi.Sys().(*syscall.Stat_t) if !ok { - return + return nil } node.ChangeTime = time.Unix(stat.Ctimespec.Unix()) @@ -26,24 +26,19 @@ func (node *Node) fillExtra(path string, fi os.FileInfo) (err error) { node.UID = stat.Uid node.GID = stat.Gid - // TODO: cache uid lookup - if u, nil := user.LookupId(strconv.Itoa(int(stat.Uid))); err == nil { + if u, err := user.LookupId(strconv.Itoa(int(stat.Uid))); err == nil { node.User = u.Username } - // TODO: implement getgrnam() or use https://github.com/kless/osutil - // if g, nil := user.LookupId(strconv.Itoa(int(stat.Uid))); err == nil { - // node.User = u.Username - // } - node.Inode = stat.Ino + var err error + switch node.Type { case "file": node.Size = uint64(stat.Size) node.Links = uint64(stat.Nlink) case "dir": - // nothing to do case "symlink": node.LinkTarget, err = os.Readlink(path) case "dev": @@ -51,11 +46,9 @@ func (node *Node) fillExtra(path string, fi os.FileInfo) (err error) { case "chardev": node.Device = uint64(stat.Rdev) case "fifo": - // nothing to do case "socket": - // nothing to do default: - panic(fmt.Sprintf("invalid node type %q", node.Type)) + err = fmt.Errorf("invalid node type %q", node.Type) } return err @@ -74,27 +67,22 @@ func (node *Node) createFifoAt(path string) error { } func (node *Node) isNewer(path string, fi os.FileInfo) bool { - // if this node has a type other than "file", treat as if content has changed if node.Type != "file" { debug.Log("node.isNewer", "node %v is newer: not file", path) return true } - // if the name or type has changed, this is surely something different - tpe := nodeTypeFromFileInfo(path, fi) + tpe := nodeTypeFromFileInfo(fi) if node.Name != fi.Name() || node.Type != tpe { debug.Log("node.isNewer", "node %v is newer: name or type changed", path) return false } - // collect extended stat - stat := fi.Sys().(*syscall.Stat_t) + extendedStat := fi.Sys().(*syscall.Stat_t) + changeTime := time.Unix(extendedStat.Ctimespec.Unix()) + inode := extendedStat.Ino + size := uint64(extendedStat.Size) - changeTime := time.Unix(stat.Ctimespec.Unix()) - inode := stat.Ino - size := uint64(stat.Size) - - // if timestamps or inodes differ, content has changed if node.ModTime != fi.ModTime() || node.ChangeTime != changeTime || node.Inode != inode || @@ -103,7 +91,6 @@ func (node *Node) isNewer(path string, fi os.FileInfo) bool { return false } - // otherwise the node is assumed to have the same content debug.Log("node.isNewer", "node %v is not newer", path) return false } diff --git a/node_linux.go b/node_linux.go index aef5c1e3a..812621d5c 100644 --- a/node_linux.go +++ b/node_linux.go @@ -30,16 +30,10 @@ func (node *Node) fillExtra(path string, fi os.FileInfo) error { node.UID = stat.Uid node.GID = stat.Gid - // TODO: cache uid lookup if u, err := user.LookupId(strconv.Itoa(int(stat.Uid))); err == nil { node.User = u.Username } - // TODO: implement getgrnam() or use https://github.com/kless/osutil - // if g, nil := user.LookupId(strconv.Itoa(int(stat.Uid))); err == nil { - // node.User = u.Username - // } - node.Inode = stat.Ino var err error @@ -49,7 +43,6 @@ func (node *Node) fillExtra(path string, fi os.FileInfo) error { node.Size = uint64(stat.Size) node.Links = uint64(stat.Nlink) case "dir": - // nothing to do case "symlink": node.LinkTarget, err = os.Readlink(path) case "dev": @@ -57,11 +50,9 @@ func (node *Node) fillExtra(path string, fi os.FileInfo) error { case "chardev": node.Device = stat.Rdev case "fifo": - // nothing to do case "socket": - // nothing to do default: - panic(fmt.Sprintf("invalid node type %q", node.Type)) + err = fmt.Errorf("invalid node type %q", node.Type) } return err @@ -80,27 +71,22 @@ func (node *Node) createFifoAt(path string) error { } func (node *Node) isNewer(path string, fi os.FileInfo) bool { - // if this node has a type other than "file", treat as if content has changed if node.Type != "file" { debug.Log("node.isNewer", "node %v is newer: not file", path) return true } - // if the name or type has changed, this is surely something different - tpe := nodeTypeFromFileInfo(path, fi) + tpe := nodeTypeFromFileInfo(fi) if node.Name != fi.Name() || node.Type != tpe { debug.Log("node.isNewer", "node %v is newer: name or type changed", path) return true } - // collect extended stat - stat := fi.Sys().(*syscall.Stat_t) + extendedStat := fi.Sys().(*syscall.Stat_t) + changeTime := time.Unix(extendedStat.Ctim.Unix()) + inode := extendedStat.Ino + size := uint64(extendedStat.Size) - changeTime := time.Unix(stat.Ctim.Unix()) - inode := stat.Ino - size := uint64(stat.Size) - - // if timestamps or inodes differ, content has changed if node.ModTime != fi.ModTime() || node.ChangeTime != changeTime || node.Inode != inode || @@ -109,7 +95,6 @@ func (node *Node) isNewer(path string, fi os.FileInfo) bool { return true } - // otherwise the node is assumed to have the same content debug.Log("node.isNewer", "node %v is not newer", path) return false } diff --git a/restorer.go b/restorer.go index 39ac01379..e7a8d18ca 100644 --- a/restorer.go +++ b/restorer.go @@ -47,7 +47,7 @@ func (res *Restorer) to(dst string, dir string, treeBlob server.Blob) error { if res.Filter == nil || res.Filter(filepath.Join(dir, node.Name), dstpath, node) { - err := CreateNodeAt(node, tree.Map, res.s, dstpath) + err := node.CreateAt(dstpath, tree.Map, res.s) // Did it fail because of ENOENT? if arrar.Check(err, func(err error) bool { @@ -60,7 +60,7 @@ func (res *Restorer) to(dst string, dir string, treeBlob server.Blob) error { // Create parent directories and retry err = os.MkdirAll(filepath.Dir(dstpath), 0700) if err == nil || err == os.ErrExist { - err = CreateNodeAt(node, tree.Map, res.s, dstpath) + err = node.CreateAt(dstpath, tree.Map, res.s) } }