package snap import ( "time" "path/filepath" "os" "sort" "io" ) 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 (snap * Snapshot) tryReference(reference string) { if _, err := os.Stat(reference); err != nil { if !os.IsNotExist(err) { die.Fatalf("Error encountered while attempting to read reference file in snapshot:\n%v", err) } snap.Reference = "" } else { snap.Reference = reference } } 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.Fatalf("Could not find relative path to reference file: %v", err) } n_snaps := len(files) + 1 snaps := make([]*Snapshot, n_snaps) 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) snaps[i].tryReference(pathInSnap) } snaps[n_snaps - 1] = &Snapshot { ModTime: time.Now(), Name: "@", } snaps[n_snaps - 1].tryReference(reference) 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 referenceMap(snapTs []*Snapshot) []int { var trimmed []int for i, snap := range snapTs { if snap.Reference != "" { trimmed = append(trimmed, i) } } return trimmed } 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) PathTo(ref *Relative) string { n_ts := len(oracle.timeseries) nthRef := referenceMap(oracle.timeseries) if len(nthRef) == 0 { die.Fatalf("The reference file is not contained in any snapshot.") } var wanted int switch (ref.snapshot) { case "@": wanted = nthRef[len(nthRef) - 1] case "%": wanted = nthRef[0] default: id, ok := oracle.namemap[ref.snapshot] if !ok { die.Fatalf("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 } func (oracle *SnapshotOracle) ReadAll(ref *Relative) string { path := oracle.PathTo(ref) file, err := os.Open(path) data, err := io.ReadAll(file) if err != nil { die.Fatalf("Can't read snapshot.\n%v", err) } return string(data) }