aboutsummaryrefslogtreecommitdiff
path: root/snap/fs.go
diff options
context:
space:
mode:
Diffstat (limited to 'snap/fs.go')
-rw-r--r--snap/fs.go147
1 files changed, 147 insertions, 0 deletions
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