From 34d7d612dd376258248ee6cae07b839951fccf32 Mon Sep 17 00:00:00 2001 From: flu0r1ne Date: Thu, 22 Jul 2021 17:18:21 -0500 Subject: List command, diff command, and snapshot search infrastructure --- cmd/cat.go | 54 +++++++++++++++++++-- cmd/list.go | 20 ++++++-- snap/fs.go | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ snap/parsing.go | 3 +- 4 files changed, 214 insertions(+), 10 deletions(-) create mode 100644 snap/fs.go diff --git a/cmd/cat.go b/cmd/cat.go index d5a275d..186f7da 100644 --- a/cmd/cat.go +++ b/cmd/cat.go @@ -3,19 +3,63 @@ package cmd import ( "fmt" "golang.flu0r1ne.net/zfdiff/snap" + "os" + "io" ) func Cat(params []string) { - if(len(params) == 0) { + n_params := len(params) + + if(n_params == 0) { die.Fatal("Reference file is required") } - if(len(params) > 3) { + if(n_params > 3) { die.Fatal("Too many arguments provided") } - snapRef := snap.ToRelative(params[0]) - reference := params[1] + var reference string + snapish := "" + + if(n_params == 1) { + reference = params[0] + } else { + snapish, reference = params[0], params[1] + } + + snapRef := snap.ToRelative(snapish) + + oracle := snap.GetOracle(reference) + + path := oracle.ResolveRelative(snapRef) + + file, err := os.Open(path) + + if err != nil { + die.Fatal("Could not open snapshot %s\nError: %v", path, err) + } + + defer func() { + if err = file.Close(); err != nil { + die.Fatal("Could not close file %s\nError: %v", path, err) + } + }() + + buf := make([]byte, 10*1024*1024) //10Ki + + for { + n, err := file.Read(buf) + + if n > 0 { + fmt.Printf("%s", buf[:n]) + } + + if err == io.EOF { + break + } - fmt.Println(snapRef, reference); + if err != nil { + die.Fatal("Encountered error while reading file: %v", err) + } + } } \ No newline at end of file diff --git a/cmd/list.go b/cmd/list.go index 99d5f24..5849975 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -3,13 +3,14 @@ package cmd import ( "fmt" "flag" + "golang.flu0r1ne.net/zfdiff/snap" ) func List(params []string) { flags := flag.NewFlagSet("list", flag.ExitOnError) var withPaths bool - + withName := aliasedBoolVar( flags, &withPaths, @@ -32,6 +33,19 @@ func List(params []string) { reference := flags.Arg(0) - fmt.Printf(reference) - fmt.Printf("Your flag is: %t", withPaths); + snaps := snap.GetTimeseries(reference) + + for _, s := range snaps { + if s.Reference == "" { + continue + } + + fmt.Printf("%s", s.Name) + + if withPaths { + fmt.Printf(", %s", s.Reference) + } + + fmt.Println("") + } } diff --git a/snap/fs.go b/snap/fs.go new file mode 100644 index 0000000..d0dc83d --- /dev/null +++ b/snap/fs.go @@ -0,0 +1,147 @@ +package snap + +import ( + "time" + "path/filepath" + "os" + "sort" +) + +type Snapshot struct { + Name string + ModTime time.Time + Reference string +} + +func getZFSRoot(reference string) string { + cursor := filepath.Dir(reference) + zfsNotFound := false + + for !zfsNotFound { + zfsDir := filepath.Join(cursor, ".zfs") + + if _, err := os.Stat(zfsDir); !os.IsNotExist(err) { + break + } + + cursor = filepath.Join(cursor, "..") + + zfsNotFound = filepath.ToSlash(cursor) == "/" + } + + if(zfsNotFound) { + die.Fatal("Could not find a .zfs directory. Are you within a zfs dataset?") + } + + return cursor +} + +func getSnapshots(reference string) []*Snapshot { + zfsRoot := getZFSRoot(reference) + + snapshotDir := filepath.Join(zfsRoot, ".zfs", "snapshot"); + + files, err := os.ReadDir(snapshotDir) + if err != nil { + die.Fatalf("Could not find the snapshots directory inside %s", snapshotDir); + } + + relPath, err := filepath.Rel(zfsRoot, reference) + if err != nil { + die.Fatal("Could not find relative path to reference file: %v", err) + } + + snaps := make([]*Snapshot, len(files)) + + for i, file := range files { + + info, err := file.Info() + if(err != nil) { + die.Fatalf("Could not read file %v", err) + } + + name := file.Name() + snaps[i] = &Snapshot{ + Name: name, + ModTime: info.ModTime(), + } + + pathInSnap := filepath.Join(snapshotDir, name, relPath) + + if _, err := os.Stat(pathInSnap); err != nil { + if !os.IsNotExist(err) { + die.Fatalf("Error encountered while attempting to read reference file in snapshot:\n%v", err) + } + } else { + snaps[i].Reference = pathInSnap + } + + } + + return snaps +} + +func GetTimeseries(reference string) []*Snapshot { + reference, err := filepath.Abs(reference) + + if err != nil { + die.Fatal(err) + } + + snaps := getSnapshots(reference) + + sort.Slice(snaps, func(i, j int) bool { + return snaps[i].ModTime.Before(snaps[j].ModTime) + }) + + return snaps +} + +func buildNameMap(snapTs []*Snapshot) map[string] int { + namemap := make(map[string] int) + + for i, snap := range snapTs { + namemap[snap.Name] = i + } + + return namemap +} + +type SnapshotOracle struct { + timeseries []*Snapshot + namemap map[string] int +}; + +func GetOracle(reference string) *SnapshotOracle { + timeseries := GetTimeseries(reference) + namemap := buildNameMap(timeseries) + + + return &SnapshotOracle { + timeseries, + namemap, + } +} + +func (oracle *SnapshotOracle) ResolveRelative(ref *Relative) string { + n_ts := len(oracle.timeseries) + wanted := n_ts - 1 + + if ref.snapshot != "" { + id, ok := oracle.namemap[ref.snapshot] + + if !ok { + die.Fatal("Could not find the snapshot %s", ref.snapshot) + } + + wanted = id + } + + wanted += ref.offset + + if wanted < 0 || wanted >= n_ts { + die.Fatal("The snapshot you requested is out of range.") + } + + return oracle.timeseries[wanted].Reference +} \ No newline at end of file diff --git a/snap/parsing.go b/snap/parsing.go index e878077..fcacec9 100644 --- a/snap/parsing.go +++ b/snap/parsing.go @@ -78,7 +78,7 @@ func ToRelative(snapish string) * Relative { return ref; } - if ref := parseRelativeSyntax(snapish, "~"); ref != nil { + if ref := parseRelativeSyntax(snapish, "^"); ref != nil { ref.offset = -ref.offset; return ref; } @@ -88,7 +88,6 @@ func ToRelative(snapish string) * Relative { }; } -// FROM, TO func ParseDiff(snapdiff string) (fromRel, toRel *Relative) { snapishes := strings.Split(snapdiff, "..") -- cgit v1.2.3