diff --git a/cmd/handler.go b/cmd/handler.go index 103935d..1f2ec1b 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -41,7 +41,7 @@ func (h Handler) HandleFilesystemVersionsRequest(r rpc.FilesystemVersionsRequest h.Logger.Printf("allowed: %#v\n", r.Filesystem) // find our versions - if versions, err = zfs.ZFSListFilesystemVersions(r.Filesystem); err != nil { + if versions, err = zfs.ZFSListFilesystemVersions(r.Filesystem, nil); err != nil { h.Logger.Printf("our versions error: %#v\n", err) return } diff --git a/cmd/replication.go b/cmd/replication.go index f76d3f2..fde1216 100644 --- a/cmd/replication.go +++ b/cmd/replication.go @@ -114,7 +114,7 @@ func doPull(pull PullContext) (err error) { var versions []zfs.FilesystemVersion if m.LocalExists { - if versions, err = zfs.ZFSListFilesystemVersions(m.Local); err != nil { + if versions, err = zfs.ZFSListFilesystemVersions(m.Local, nil); err != nil { log("cannot get filesystem versions, stopping...: %v\n", m.Local.ToString(), m, err) return false } diff --git a/zfs/diff.go b/zfs/diff.go index dc2e58f..e0387ce 100644 --- a/zfs/diff.go +++ b/zfs/diff.go @@ -1,55 +1,9 @@ package zfs import ( - "bytes" - "errors" - "fmt" "sort" - "strconv" - "strings" ) -type VersionType string - -const ( - Bookmark VersionType = "bookmark" - Snapshot = "snapshot" -) - -func (t VersionType) DelimiterChar() string { - switch t { - case Bookmark: - return "#" - case Snapshot: - return "@" - default: - panic(fmt.Sprintf("unexpected VersionType %#v", t)) - } - return "" -} - -type FilesystemVersion struct { - Type VersionType - - // Display name. Should not be used for identification, only for user output - Name string - - // GUID as exported by ZFS. Uniquely identifies a snapshot across pools - Guid uint64 - - // The TXG in which the snapshot was created. For bookmarks, - // this is the GUID of the snapshot it was initially tied to. - CreateTXG uint64 -} - -func (v FilesystemVersion) ToAbsPath(p DatasetPath) string { - var b bytes.Buffer - b.WriteString(p.ToString()) - b.WriteString(v.Type.DelimiterChar()) - b.WriteString(v.Name) - return b.String() -} - type fsbyCreateTXG []FilesystemVersion func (l fsbyCreateTXG) Len() int { return len(l) } @@ -105,56 +59,6 @@ type FilesystemDiff struct { MRCAPathRight []FilesystemVersion } -func ZFSListFilesystemVersions(fs DatasetPath) (res []FilesystemVersion, err error) { - var fieldLines [][]string - fieldLines, err = ZFSList( - []string{"name", "guid", "createtxg"}, - "-r", "-d", "1", - "-t", "bookmark,snapshot", - "-s", "createtxg", fs.ToString()) - if err != nil { - return - } - res = make([]FilesystemVersion, len(fieldLines)) - for i, line := range fieldLines { - - if len(line[0]) < 3 { - err = errors.New(fmt.Sprintf("snapshot or bookmark name implausibly short: %s", line[0])) - return - } - - snapSplit := strings.SplitN(line[0], "@", 2) - bookmarkSplit := strings.SplitN(line[0], "#", 2) - if len(snapSplit)*len(bookmarkSplit) != 2 { - err = errors.New(fmt.Sprintf("dataset cannot be snapshot and bookmark at the same time: %s", line[0])) - return - } - - var v FilesystemVersion - if len(snapSplit) == 2 { - v.Name = snapSplit[1] - v.Type = Snapshot - } else { - v.Name = bookmarkSplit[1] - v.Type = Bookmark - } - - if v.Guid, err = strconv.ParseUint(line[1], 10, 64); err != nil { - err = errors.New(fmt.Sprintf("cannot parse GUID: %s", err.Error())) - return - } - - if v.CreateTXG, err = strconv.ParseUint(line[2], 10, 64); err != nil { - err = errors.New(fmt.Sprintf("cannot parse CreateTXG: %s", err.Error())) - return - } - - res[i] = v - - } - return -} - // we must assume left and right are ordered ascendingly by ZFS_PROP_CREATETXG and that // names are unique (bas ZFS_PROP_GUID replacement) func MakeFilesystemDiff(left, right []FilesystemVersion) (diff FilesystemDiff) { diff --git a/zfs/versions.go b/zfs/versions.go new file mode 100644 index 0000000..cf28b34 --- /dev/null +++ b/zfs/versions.go @@ -0,0 +1,114 @@ +package zfs + +import ( + "bytes" + "errors" + "fmt" + "strconv" + "strings" +) + +type VersionType string + +const ( + Bookmark VersionType = "bookmark" + Snapshot = "snapshot" +) + +func (t VersionType) DelimiterChar() string { + switch t { + case Bookmark: + return "#" + case Snapshot: + return "@" + default: + panic(fmt.Sprintf("unexpected VersionType %#v", t)) + } + return "" +} + +type FilesystemVersion struct { + Type VersionType + + // Display name. Should not be used for identification, only for user output + Name string + + // GUID as exported by ZFS. Uniquely identifies a snapshot across pools + Guid uint64 + + // The TXG in which the snapshot was created. For bookmarks, + // this is the GUID of the snapshot it was initially tied to. + CreateTXG uint64 +} + +func (v FilesystemVersion) ToAbsPath(p DatasetPath) string { + var b bytes.Buffer + b.WriteString(p.ToString()) + b.WriteString(v.Type.DelimiterChar()) + b.WriteString(v.Name) + return b.String() +} + +type FilesystemVersionFilter interface { + Filter(fsv FilesystemVersion) (accept bool, err error) +} + +func ZFSListFilesystemVersions(fs DatasetPath, filter FilesystemVersionFilter) (res []FilesystemVersion, err error) { + var fieldLines [][]string + fieldLines, err = ZFSList( + []string{"name", "guid", "createtxg"}, + "-r", "-d", "1", + "-t", "bookmark,snapshot", + "-s", "createtxg", fs.ToString()) + if err != nil { + return + } + res = make([]FilesystemVersion, 0, len(fieldLines)) + for _, line := range fieldLines { + + if len(line[0]) < 3 { + err = errors.New(fmt.Sprintf("snapshot or bookmark name implausibly short: %s", line[0])) + return + } + + snapSplit := strings.SplitN(line[0], "@", 2) + bookmarkSplit := strings.SplitN(line[0], "#", 2) + if len(snapSplit)*len(bookmarkSplit) != 2 { + err = errors.New(fmt.Sprintf("dataset cannot be snapshot and bookmark at the same time: %s", line[0])) + return + } + + var v FilesystemVersion + if len(snapSplit) == 2 { + v.Name = snapSplit[1] + v.Type = Snapshot + } else { + v.Name = bookmarkSplit[1] + v.Type = Bookmark + } + + if v.Guid, err = strconv.ParseUint(line[1], 10, 64); err != nil { + err = errors.New(fmt.Sprintf("cannot parse GUID: %s", err.Error())) + return + } + + if v.CreateTXG, err = strconv.ParseUint(line[2], 10, 64); err != nil { + err = errors.New(fmt.Sprintf("cannot parse CreateTXG: %s", err.Error())) + return + } + + accept := true + if filter != nil { + accept, err = filter.Filter(v) + if err != nil { + err = fmt.Errorf("error executing filter: %s", err) + return nil, err + } + } + if accept { + res = append(res, v) + } + + } + return +}