From d66996e648f13aad462de5d0ce66e78321af726d Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Mon, 11 Aug 2014 23:14:40 +0200 Subject: [PATCH] Move restore functionality into khepri package --- cmd/khepri/cmd_restore.go | 146 +------------------------------------- snapshot.go | 18 +++++ tree.go | 142 ++++++++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 144 deletions(-) diff --git a/cmd/khepri/cmd_restore.go b/cmd/khepri/cmd_restore.go index db7b6088b..0c7188f46 100644 --- a/cmd/khepri/cmd_restore.go +++ b/cmd/khepri/cmd_restore.go @@ -2,146 +2,11 @@ package main import ( "errors" - "fmt" - "io" "log" - "os" - "path/filepath" - "syscall" "github.com/fd0/khepri" ) -func restore_file(repo *khepri.Repository, node *khepri.Node, path string) (err error) { - switch node.Type { - case "file": - // TODO: handle hard links - rd, err := repo.Get(khepri.TYPE_BLOB, node.Content) - if err != nil { - return err - } - - f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0600) - defer f.Close() - if err != nil { - return err - } - - _, err = io.Copy(f, rd) - if err != nil { - return err - } - - case "symlink": - err = os.Symlink(node.LinkTarget, path) - if err != nil { - return err - } - - err = os.Lchown(path, int(node.UID), int(node.GID)) - if err != nil { - return err - } - - f, err := os.OpenFile(path, khepri.O_PATH|syscall.O_NOFOLLOW, 0600) - defer f.Close() - if err != nil { - return err - } - - 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 err - } - - return nil - case "dev": - err = syscall.Mknod(path, syscall.S_IFBLK|0600, int(node.Device)) - if err != nil { - return err - } - case "chardev": - err = syscall.Mknod(path, syscall.S_IFCHR|0600, int(node.Device)) - if err != nil { - return err - } - case "fifo": - err = syscall.Mkfifo(path, 0600) - if err != nil { - return err - } - case "socket": - // nothing to do, we do not restore sockets - default: - return fmt.Errorf("filetype %q not implemented!\n", node.Type) - } - - err = os.Chmod(path, node.Mode) - if err != nil { - return err - } - - err = os.Chown(path, int(node.UID), int(node.GID)) - if err != nil { - return err - } - - err = os.Chtimes(path, node.AccessTime, node.ModTime) - if err != nil { - return err - } - - return nil -} - -func restore_subtree(repo *khepri.Repository, tree *khepri.Tree, path string) { - fmt.Printf("restore_subtree(%s)\n", path) - - for _, node := range tree.Nodes { - nodepath := filepath.Join(path, node.Name) - // fmt.Printf("%s:%s\n", node.Type, nodepath) - - if node.Type == "dir" { - err := os.Mkdir(nodepath, 0700) - if err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) - continue - } - - err = os.Chmod(nodepath, node.Mode) - if err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) - continue - } - - err = os.Chown(nodepath, int(node.UID), int(node.GID)) - if err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) - continue - } - - restore_subtree(repo, node.Tree, filepath.Join(path, node.Name)) - - err = os.Chtimes(nodepath, node.AccessTime, node.ModTime) - if err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) - continue - } - - } else { - err := restore_file(repo, node, nodepath) - if err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) - continue - } - } - } -} - func commandRestore(repo *khepri.Repository, args []string) error { if len(args) != 2 { return errors.New("usage: restore ID dir") @@ -154,23 +19,16 @@ func commandRestore(repo *khepri.Repository, args []string) error { target := args[1] - err = os.MkdirAll(target, 0700) - if err != nil { - return err - } - sn, err := khepri.LoadSnapshot(repo, id) if err != nil { log.Fatalf("error loading snapshot %s", id) } - tree, err := khepri.NewTreeFromRepo(repo, sn.Content) + err = sn.RestoreAt(target) if err != nil { - log.Fatalf("error loading tree %s", sn.Content) + log.Fatalf("error restoring snapshot %s", id) } - restore_subtree(repo, tree, target) - log.Printf("%q restored to %q\n", id, target) return nil diff --git a/snapshot.go b/snapshot.go index d6da7b099..463d56915 100644 --- a/snapshot.go +++ b/snapshot.go @@ -18,6 +18,7 @@ type Snapshot struct { UID string `json:"uid,omitempty"` GID string `json:"gid,omitempty"` id ID `json:omit` + repo *Repository } func NewSnapshot(dir string) *Snapshot { @@ -84,10 +85,27 @@ func LoadSnapshot(repo *Repository, id ID) (*Snapshot, error) { } sn.id = id + sn.repo = repo return sn, nil } +func (sn *Snapshot) RestoreAt(path string) error { + err := os.MkdirAll(path, 0700) + if err != nil { + return err + } + + if sn.Tree == nil { + sn.Tree, err = NewTreeFromRepo(sn.repo, sn.Content) + if err != nil { + return err + } + } + + return sn.Tree.CreateAt(path) +} + func (sn *Snapshot) ID() ID { return sn.id } diff --git a/tree.go b/tree.go index 4e90e32c1..97ae361ad 100644 --- a/tree.go +++ b/tree.go @@ -35,6 +35,7 @@ type Node struct { Content ID `json:"content,omitempty"` Subtree ID `json:"subtree,omitempty"` Tree *Tree `json:"-"` + repo *Repository } func NewTree() *Tree { @@ -65,6 +66,7 @@ func NewTreeFromPath(repo *Repository, dir string) (*Tree, error) { if err != nil { return nil, err } + node.repo = repo tree.Nodes = append(tree.Nodes, node) @@ -152,6 +154,8 @@ func NewTreeFromRepo(repo *Repository, id ID) (*Tree, error) { } for _, node := range tree.Nodes { + node.repo = repo + if node.Subtree != nil { node.Tree, err = NewTreeFromRepo(repo, node.Subtree) if err != nil { @@ -163,6 +167,53 @@ func NewTreeFromRepo(repo *Repository, id ID) (*Tree, error) { return tree, nil } +func (tree *Tree) CreateAt(path string) error { + for _, node := range tree.Nodes { + nodepath := filepath.Join(path, node.Name) + // fmt.Printf("%s:%s\n", node.Type, nodepath) + + if node.Type == "dir" { + err := os.Mkdir(nodepath, 0700) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + continue + } + + err = os.Chmod(nodepath, node.Mode) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + continue + } + + err = os.Chown(nodepath, int(node.UID), int(node.GID)) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + continue + } + + err = node.Tree.CreateAt(filepath.Join(path, node.Name)) + if err != nil { + return err + } + + err = os.Chtimes(nodepath, node.AccessTime, node.ModTime) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + continue + } + + } else { + err := node.CreateAt(nodepath) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + continue + } + } + } + + return nil +} + // TODO: make sure that node.Type is valid func (node *Node) fill_extra(path string, fi os.FileInfo) (err error) { @@ -237,3 +288,94 @@ func NodeFromFileInfo(path string, fi os.FileInfo) (*Node, error) { err := node.fill_extra(path, fi) return node, err } + +func (node *Node) CreateAt(path string) error { + if node.repo == nil { + return fmt.Errorf("repository is nil!") + } + + switch node.Type { + case "file": + // TODO: handle hard links + rd, err := node.repo.Get(TYPE_BLOB, node.Content) + if err != nil { + return err + } + + f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0600) + defer f.Close() + if err != nil { + return err + } + + _, err = io.Copy(f, rd) + if err != nil { + return err + } + + f.Close() + case "symlink": + err := os.Symlink(node.LinkTarget, path) + if err != nil { + return err + } + + err = os.Lchown(path, int(node.UID), int(node.GID)) + if err != nil { + return err + } + + f, err := os.OpenFile(path, O_PATH|syscall.O_NOFOLLOW, 0600) + defer f.Close() + if err != nil { + return err + } + + 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 err + } + + return nil + case "dev": + err := syscall.Mknod(path, syscall.S_IFBLK|0600, int(node.Device)) + if err != nil { + return err + } + case "chardev": + err := syscall.Mknod(path, syscall.S_IFCHR|0600, int(node.Device)) + if err != nil { + return err + } + case "fifo": + err := syscall.Mkfifo(path, 0600) + if err != nil { + return err + } + case "socket": + // nothing to do, we do not restore sockets + default: + return fmt.Errorf("filetype %q not implemented!\n", node.Type) + } + + err := os.Chmod(path, node.Mode) + if err != nil { + return err + } + + err = os.Chown(path, int(node.UID), int(node.GID)) + if err != nil { + return err + } + + err = os.Chtimes(path, node.AccessTime, node.ModTime) + if err != nil { + return err + } + + return nil +}