From 1323a30a0c4b96b4e7235b871abb127ba32d6e67 Mon Sep 17 00:00:00 2001 From: Christian Schwarz Date: Wed, 5 Sep 2018 19:44:35 -0700 Subject: [PATCH] zfs: ability to specify sources for zfsGet fix use for Placeholder, leave rest as previous behavior --- zfs/diff.go | 2 +- zfs/replication_history.go | 4 ++-- zfs/zfs.go | 44 ++++++++++++++++++++++++++++++++------ zfs/zfs_test.go | 38 ++++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 10 deletions(-) diff --git a/zfs/diff.go b/zfs/diff.go index e7a7e0c..31886e3 100644 --- a/zfs/diff.go +++ b/zfs/diff.go @@ -248,7 +248,7 @@ func IsPlaceholder(p *DatasetPath, placeholderPropertyValue string) (isPlacehold // for nonexistent FS, isPlaceholder == false && err == nil func ZFSIsPlaceholderFilesystem(p *DatasetPath) (isPlaceholder bool, err error) { - props, err := ZFSGet(p, []string{ZREPL_PLACEHOLDER_PROPERTY_NAME}) + props, err := zfsGet(p.ToString(), []string{ZREPL_PLACEHOLDER_PROPERTY_NAME}, sourceLocal) if err == io.ErrUnexpectedEOF { // interpret this as an early exit of the zfs binary due to the fs not existing return false, nil diff --git a/zfs/replication_history.go b/zfs/replication_history.go index c5b46c3..2ad5abf 100644 --- a/zfs/replication_history.go +++ b/zfs/replication_history.go @@ -24,13 +24,13 @@ func ZFSGetReplicationCursor(fs *DatasetPath) (*FilesystemVersion, error) { func ZFSSetReplicationCursor(fs *DatasetPath, snapname string) (guid uint64, err error) { snapPath := fmt.Sprintf("%s@%s", fs.ToString(), snapname) - propsSnap, err := zfsGet(snapPath, []string{"createtxg", "guid"}) + propsSnap, err := zfsGet(snapPath, []string{"createtxg", "guid"}, sourceAny) if err != nil { return 0, err } snapGuid, err := strconv.ParseUint(propsSnap.Get("guid"), 10, 64) bookmarkPath := fmt.Sprintf("%s#%s", fs.ToString(), ReplicationCursorBookmarkName) - propsBookmark, err := zfsGet(bookmarkPath, []string{"createtxg"}) + propsBookmark, err := zfsGet(bookmarkPath, []string{"createtxg"}, sourceAny) _, bookmarkNotExistErr := err.(*DatasetDoesNotExist) if err != nil && !bookmarkNotExistErr { return 0, err diff --git a/zfs/zfs.go b/zfs/zfs.go index 22a4d49..8cd4fd2 100644 --- a/zfs/zfs.go +++ b/zfs/zfs.go @@ -487,7 +487,7 @@ func zfsSet(path string, props *ZFSProperties) (err error) { } func ZFSGet(fs *DatasetPath, props []string) (*ZFSProperties, error) { - return zfsGet(fs.ToString(), props) + return zfsGet(fs.ToString(), props, sourceAny) } var zfsGetDatasetDoesNotExistRegexp = regexp.MustCompile(`^cannot open '(\S+)': (dataset does not exist|no such pool or dataset)`) @@ -498,8 +498,30 @@ type DatasetDoesNotExist struct { func (d *DatasetDoesNotExist) Error() string { return fmt.Sprintf("dataset %q does not exist", d.Path) } -func zfsGet(path string, props []string) (*ZFSProperties, error) { - args := []string{"get", "-Hp", "-o", "property,value", strings.Join(props, ","), path} +type zfsPropertySource uint + +const ( + sourceLocal zfsPropertySource = 1 << iota + sourceDefault + sourceInherited + sourceNone + sourceTemporary + + sourceAny zfsPropertySource = ^zfsPropertySource(0) +) + +func (s zfsPropertySource) zfsGetSourceFieldPrefixes() []string { + prefixes := make([]string, 0, 5) + if s&sourceLocal != 0 {prefixes = append(prefixes, "local")} + if s&sourceDefault != 0 {prefixes = append(prefixes, "default")} + if s&sourceInherited != 0 {prefixes = append(prefixes, "inherited")} + if s&sourceNone != 0 {prefixes = append(prefixes, "-")} + if s&sourceTemporary != 0 { prefixes = append(prefixes, "temporary")} + return prefixes +} + +func zfsGet(path string, props []string, allowedSources zfsPropertySource) (*ZFSProperties, error) { + args := []string{"get", "-Hp", "-o", "property,value,source", strings.Join(props, ","), path} cmd := exec.Command(ZFS_BINARY, args...) stdout, err := cmd.Output() if err != nil { @@ -524,12 +546,20 @@ func zfsGet(path string, props []string) (*ZFSProperties, error) { res := &ZFSProperties{ make(map[string]string, len(lines)), } + allowedPrefixes := allowedSources.zfsGetSourceFieldPrefixes() for _, line := range lines[:len(lines)-1] { - fields := strings.Fields(line) - if len(fields) != 2 { - return nil, fmt.Errorf("zfs get did not return property value pairs") + fields := strings.FieldsFunc(line, func(r rune) bool { + return r == '\t' + }) + if len(fields) != 3 { + return nil, fmt.Errorf("zfs get did not return property,value,source tuples") + } + for _, p := range allowedPrefixes { + if strings.HasPrefix(fields[2],p) { + res.m[fields[0]] = fields[1] + break + } } - res.m[fields[0]] = fields[1] } return res, nil } diff --git a/zfs/zfs_test.go b/zfs/zfs_test.go index dcfe0f9..b46a36a 100644 --- a/zfs/zfs_test.go +++ b/zfs/zfs_test.go @@ -30,3 +30,41 @@ func TestDatasetPathTrimNPrefixComps(t *testing.T) { p.TrimNPrefixComps((1)) assert.True(t, p.Empty(), "empty trimming shouldn't do harm") } + +func TestZFSPropertySource(t *testing.T) { + + tcs := []struct{ + in zfsPropertySource + exp []string + }{ + { + in: sourceAny, + exp: []string{"local", "default", "inherited", "-", "temporary"}, + }, + { + in: sourceTemporary, + exp: []string{"temporary"}, + }, + { + in: sourceLocal|sourceInherited, + exp: []string{"local", "inherited"}, + }, + } + + toSet := func(in []string) map[string]struct{} { + m := make(map[string]struct{}, len(in)) + for _, s := range in { + m[s] = struct{}{} + } + return m + } + + for _, tc := range tcs { + + res := tc.in.zfsGetSourceFieldPrefixes() + resSet := toSet(res) + expSet := toSet(tc.exp) + assert.Equal(t, expSet, resSet) + } + +}