zfs: ability to specify sources for zfsGet

fix use for Placeholder, leave rest as previous behavior
This commit is contained in:
Christian Schwarz 2018-09-05 19:44:35 -07:00
parent 975fdee217
commit 1323a30a0c
4 changed files with 78 additions and 10 deletions

View File

@ -248,7 +248,7 @@ func IsPlaceholder(p *DatasetPath, placeholderPropertyValue string) (isPlacehold
// for nonexistent FS, isPlaceholder == false && err == nil // for nonexistent FS, isPlaceholder == false && err == nil
func ZFSIsPlaceholderFilesystem(p *DatasetPath) (isPlaceholder bool, err error) { 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 { if err == io.ErrUnexpectedEOF {
// interpret this as an early exit of the zfs binary due to the fs not existing // interpret this as an early exit of the zfs binary due to the fs not existing
return false, nil return false, nil

View File

@ -24,13 +24,13 @@ func ZFSGetReplicationCursor(fs *DatasetPath) (*FilesystemVersion, error) {
func ZFSSetReplicationCursor(fs *DatasetPath, snapname string) (guid uint64, err error) { func ZFSSetReplicationCursor(fs *DatasetPath, snapname string) (guid uint64, err error) {
snapPath := fmt.Sprintf("%s@%s", fs.ToString(), snapname) 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 { if err != nil {
return 0, err return 0, err
} }
snapGuid, err := strconv.ParseUint(propsSnap.Get("guid"), 10, 64) snapGuid, err := strconv.ParseUint(propsSnap.Get("guid"), 10, 64)
bookmarkPath := fmt.Sprintf("%s#%s", fs.ToString(), ReplicationCursorBookmarkName) bookmarkPath := fmt.Sprintf("%s#%s", fs.ToString(), ReplicationCursorBookmarkName)
propsBookmark, err := zfsGet(bookmarkPath, []string{"createtxg"}) propsBookmark, err := zfsGet(bookmarkPath, []string{"createtxg"}, sourceAny)
_, bookmarkNotExistErr := err.(*DatasetDoesNotExist) _, bookmarkNotExistErr := err.(*DatasetDoesNotExist)
if err != nil && !bookmarkNotExistErr { if err != nil && !bookmarkNotExistErr {
return 0, err return 0, err

View File

@ -487,7 +487,7 @@ func zfsSet(path string, props *ZFSProperties) (err error) {
} }
func ZFSGet(fs *DatasetPath, props []string) (*ZFSProperties, 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)`) 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 (d *DatasetDoesNotExist) Error() string { return fmt.Sprintf("dataset %q does not exist", d.Path) }
func zfsGet(path string, props []string) (*ZFSProperties, error) { type zfsPropertySource uint
args := []string{"get", "-Hp", "-o", "property,value", strings.Join(props, ","), path}
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...) cmd := exec.Command(ZFS_BINARY, args...)
stdout, err := cmd.Output() stdout, err := cmd.Output()
if err != nil { if err != nil {
@ -524,12 +546,20 @@ func zfsGet(path string, props []string) (*ZFSProperties, error) {
res := &ZFSProperties{ res := &ZFSProperties{
make(map[string]string, len(lines)), make(map[string]string, len(lines)),
} }
allowedPrefixes := allowedSources.zfsGetSourceFieldPrefixes()
for _, line := range lines[:len(lines)-1] { for _, line := range lines[:len(lines)-1] {
fields := strings.Fields(line) fields := strings.FieldsFunc(line, func(r rune) bool {
if len(fields) != 2 { return r == '\t'
return nil, fmt.Errorf("zfs get did not return property value pairs") })
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] res.m[fields[0]] = fields[1]
break
}
}
} }
return res, nil return res, nil
} }

View File

@ -30,3 +30,41 @@ func TestDatasetPathTrimNPrefixComps(t *testing.T) {
p.TrimNPrefixComps((1)) p.TrimNPrefixComps((1))
assert.True(t, p.Empty(), "empty trimming shouldn't do harm") 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)
}
}