From 40dcb03d95bd2d17701d779718fb850acb859325 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Mon, 28 Apr 2014 00:00:15 +0200 Subject: [PATCH] Add main binary --- cmd_backup.go | 99 +++++++++++++++++++++++++++++++++++++ cmd_restore.go | 132 +++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 73 +++++++++++++++++++++++++++ 3 files changed, 304 insertions(+) create mode 100644 cmd_backup.go create mode 100644 cmd_restore.go create mode 100644 main.go diff --git a/cmd_backup.go b/cmd_backup.go new file mode 100644 index 000000000..e8813dded --- /dev/null +++ b/cmd_backup.go @@ -0,0 +1,99 @@ +package main + +import ( + "bytes" + "crypto/sha256" + "errors" + "fmt" + "io" + "log" + "os" + "path/filepath" + + "github.com/fd0/khepri/storage" +) + +func hash(filename string) (storage.ID, error) { + h := sha256.New() + f, err := os.Open(filename) + if err != nil { + return nil, err + } + + io.Copy(h, f) + return h.Sum([]byte{}), nil +} + +func archive_dir(repo *storage.DirRepository, path string) (storage.ID, error) { + log.Printf("archiving dir %q", path) + dir, err := os.Open(path) + if err != nil { + log.Printf("open(%q): %v\n", path, err) + return nil, err + } + + entries, err := dir.Readdir(-1) + if err != nil { + log.Printf("readdir(%q): %v\n", path, err) + return nil, err + } + + // use nil ID for empty directories + if len(entries) == 0 { + return nil, nil + } + + t := storage.NewTree() + for _, e := range entries { + node := storage.NodeFromFileInfo(e) + + var id storage.ID + var err error + + if e.IsDir() { + id, err = archive_dir(repo, filepath.Join(path, e.Name())) + } else { + id, err = repo.PutFile(filepath.Join(path, e.Name())) + } + + node.Content = id + + t.Nodes = append(t.Nodes, node) + + if err != nil { + log.Printf(" error storing %q: %v\n", e.Name(), err) + continue + } + } + + log.Printf(" dir %q: %v entries", path, len(t.Nodes)) + + var buf bytes.Buffer + t.Save(&buf) + id, err := repo.PutRaw(buf.Bytes()) + + if err != nil { + log.Printf("error saving tree to repo: %v", err) + } + + log.Printf("tree for %q saved at %s", path, id) + + return id, nil +} + +func commandBackup(repo *storage.DirRepository, args []string) error { + if len(args) != 1 { + return errors.New("usage: backup dir") + } + + target := args[0] + + id, err := archive_dir(repo, target) + if err != nil { + return err + } + + fmt.Printf("%q archived as %v\n", target, id) + + return nil +} diff --git a/cmd_restore.go b/cmd_restore.go new file mode 100644 index 000000000..a20f7db25 --- /dev/null +++ b/cmd_restore.go @@ -0,0 +1,132 @@ +package main + +import ( + "errors" + "fmt" + "io" + "os" + "path" + + "github.com/fd0/khepri/storage" +) + +func restore_file(repo *storage.DirRepository, node storage.Node, target string) error { + fmt.Printf(" restore file %q\n", target) + + rd, err := repo.Get(node.Content) + if err != nil { + return err + } + + f, err := os.OpenFile(target, 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 + } + + err = f.Chmod(node.Mode) + if err != nil { + return err + } + + err = f.Chown(int(node.User), int(node.Group)) + if err != nil { + return err + } + + err = os.Chtimes(target, node.AccessTime, node.ModTime) + if err != nil { + return err + } + + return nil +} + +func restore_dir(repo *storage.DirRepository, id storage.ID, target string) error { + fmt.Printf(" restore dir %q\n", target) + rd, err := repo.Get(id) + if err != nil { + return err + } + + t := storage.NewTree() + err = t.Restore(rd) + if err != nil { + return err + } + + for _, node := range t.Nodes { + name := path.Base(node.Name) + if name == "." || name == ".." { + return errors.New("invalid path") + } + + nodepath := path.Join(target, name) + if node.Mode.IsDir() { + err = os.MkdirAll(nodepath, 0700) + if err != nil { + return err + } + + err = os.Chmod(nodepath, node.Mode) + if err != nil { + return err + } + + err = os.Chown(nodepath, int(node.User), int(node.Group)) + if err != nil { + return err + } + + err = os.Chtimes(nodepath, node.AccessTime, node.ModTime) + if err != nil { + return err + } + + err = restore_dir(repo, node.Content, nodepath) + + if err != nil { + return err + } + } else { + err = restore_file(repo, node, nodepath) + if err != nil { + return err + } + } + } + + return nil +} + +func commandRestore(repo *storage.DirRepository, args []string) error { + if len(args) != 2 { + return errors.New("usage: restore ID dir") + } + + id, err := storage.ParseID(args[0]) + if err != nil { + errmsg(1, "invalid id %q: %v", args[0], err) + } + + target := args[1] + + err = os.MkdirAll(target, 0700) + if err != nil { + return err + } + + err = restore_dir(repo, id, target) + if err != nil { + return err + } + + fmt.Printf("%q restored to %q\n", id, target) + + return nil +} diff --git a/main.go b/main.go new file mode 100644 index 000000000..46f4c2467 --- /dev/null +++ b/main.go @@ -0,0 +1,73 @@ +package main + +import ( + "fmt" + "os" + "sort" + "strings" + + "github.com/fd0/khepri/storage" + "github.com/jessevdk/go-flags" +) + +var Opts struct { + Repo string `short:"r" long:"repo" description:"Repository directory to backup to/restor from"` +} + +func errmsg(code int, format string, data ...interface{}) { + if len(format) > 0 && format[len(format)-1] != '\n' { + format += "\n" + } + fmt.Fprintf(os.Stderr, format, data...) + os.Exit(code) +} + +type commandFunc func(*storage.DirRepository, []string) error + +var commands map[string]commandFunc + +func init() { + commands = make(map[string]commandFunc) + commands["backup"] = commandBackup + commands["restore"] = commandRestore +} + +func main() { + Opts.Repo = os.Getenv("KHEPRI_REPOSITORY") + if Opts.Repo == "" { + Opts.Repo = "khepri-backup" + } + args, err := flags.Parse(&Opts) + + if e, ok := err.(*flags.Error); ok && e.Type == flags.ErrHelp { + os.Exit(0) + } + + if len(args) == 0 { + cmds := []string{} + for k := range commands { + cmds = append(cmds, k) + } + sort.Strings(cmds) + fmt.Printf("nothing to do, available commands: [%v]\n", strings.Join(cmds, "|")) + os.Exit(0) + } + + cmd := args[0] + + f, ok := commands[cmd] + if !ok { + errmsg(1, "unknown command: %q\n", cmd) + } + + repo, err := storage.NewDirRepository(Opts.Repo) + + if err != nil { + errmsg(1, "unable to create/open repo: %v", err) + } + + err = f(repo, args[1:]) + if err != nil { + errmsg(1, "error executing command %q: %v", cmd, err) + } +}