diff options
| -rw-r--r-- | cmd/cat.go | 54 | ||||
| -rw-r--r-- | cmd/list.go | 20 | ||||
| -rw-r--r-- | snap/fs.go | 147 | ||||
| -rw-r--r-- | snap/parsing.go | 3 | 
4 files changed, 214 insertions, 10 deletions
| @@ -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, "..") | 
