diff --git a/zfs/zfs.go b/zfs/zfs.go index 652a494..d693f2c 100644 --- a/zfs/zfs.go +++ b/zfs/zfs.go @@ -577,10 +577,17 @@ type DrySendInfo struct { SizeEstimate int64 // -1 if size estimate is not possible } -var sendDryRunInfoLineRegex = regexp.MustCompile(`^(full|incremental)(\t(\S+))?\t(\S+)\t([0-9]+)$`) +var ( + // keep same number of capture groups for unmarshalInfoLine homogenity + + sendDryRunInfoLineRegexFull = regexp.MustCompile(`^(full)\t()([^\t]+@[^\t]+)\t([0-9]+)$`) + // cannot enforce '[#@]' in incremental source, see test cases + sendDryRunInfoLineRegexIncremental = regexp.MustCompile(`^(incremental)\t([^\t]+)\t([^\t]+@[^\t]+)\t([0-9]+)$`) +) // see test cases for example output func (s *DrySendInfo) unmarshalZFSOutput(output []byte) (err error) { + debug("DrySendInfo.unmarshalZFSOutput: output=%q", output) lines := strings.Split(string(output), "\n") for _, l := range lines { regexMatched, err := s.unmarshalInfoLine(l) @@ -592,7 +599,7 @@ func (s *DrySendInfo) unmarshalZFSOutput(output []byte) (err error) { } return nil } - return fmt.Errorf("no match for info line (regex %s)", sendDryRunInfoLineRegex) + return fmt.Errorf("no match for info line (regex1 %s) (regex2 %s)", sendDryRunInfoLineRegexFull, sendDryRunInfoLineRegexIncremental) } @@ -602,24 +609,32 @@ func (s *DrySendInfo) unmarshalZFSOutput(output []byte) (err error) { // => see test cases func (s *DrySendInfo) unmarshalInfoLine(l string) (regexMatched bool, err error) { - m := sendDryRunInfoLineRegex.FindStringSubmatch(l) - if m == nil { + mFull := sendDryRunInfoLineRegexFull.FindStringSubmatch(l) + mInc := sendDryRunInfoLineRegexIncremental.FindStringSubmatch(l) + var m []string + if mFull == nil && mInc == nil { return false, nil + } else if mFull != nil && mInc != nil { + panic(fmt.Sprintf("ambiguous ZFS dry send output: %q", l)) + } else if mFull != nil { + m = mFull + } else if mInc != nil { + m = mInc } s.Type, err = DrySendTypeFromString(m[1]) if err != nil { return true, err } - s.From = m[3] - s.To = m[4] - toFS, _, _ , err := DecomposeVersionString(s.To) + s.From = m[2] + s.To = m[3] + toFS, _, _, err := DecomposeVersionString(s.To) if err != nil { return true, fmt.Errorf("'to' is not a valid filesystem version: %s", err) } s.Filesystem = toFS - s.SizeEstimate, err = strconv.ParseInt(m[5], 10, 64) + s.SizeEstimate, err = strconv.ParseInt(m[4], 10, 64) if err != nil { return true, fmt.Errorf("cannot not parse size: %s", err) } diff --git a/zfs/zfs_test.go b/zfs/zfs_test.go index 9126bb0..217d765 100644 --- a/zfs/zfs_test.go +++ b/zfs/zfs_test.go @@ -70,6 +70,10 @@ func TestZFSPropertySource(t *testing.T) { } +func TestDrySendRegexesHaveSameCaptureGroupCount(t *testing.T) { + assert.Equal(t, sendDryRunInfoLineRegexFull.NumSubexp(), sendDryRunInfoLineRegexIncremental.NumSubexp()) +} + func TestDrySendInfo(t *testing.T) { // # full send @@ -127,6 +131,11 @@ full zroot/test/a@3 10518512 size 10518512 ` + fullWithSpaces := "\nfull\tpool1/otherjob/ds with spaces@blaffoo\t12912\nsize\t12912\n" + fullWithSpacesInIntermediateComponent := "\nfull\tpool1/otherjob/another ds with spaces/childfs@blaffoo\t12912\nsize\t12912\n" + incrementalWithSpaces := "\nincremental\tblaffoo\tpool1/otherjob/another ds with spaces@blaffoo2\t624\nsize\t624\n" + incrementalWithSpacesInIntermediateComponent := "\nincremental\tblaffoo\tpool1/otherjob/another ds with spaces/childfs@blaffoo2\t624\nsize\t624\n" + type tc struct { name string in string @@ -165,7 +174,7 @@ size 10518512 SizeEstimate: 5383312, }, }, - { + { name: "incNoToken", in: incNoToken, exp: &DrySendInfo{ Type: DrySendTypeIncremental, @@ -187,6 +196,46 @@ size 10518512 SizeEstimate: 10518512, }, }, + { + name: "fullWithSpaces", in: fullWithSpaces, + exp: &DrySendInfo{ + Type: DrySendTypeFull, + Filesystem: "pool1/otherjob/ds with spaces", + From: "", + To: "pool1/otherjob/ds with spaces@blaffoo", + SizeEstimate: 12912, + }, + }, + { + name: "fullWithSpacesInIntermediateComponent", in: fullWithSpacesInIntermediateComponent, + exp: &DrySendInfo{ + Type: DrySendTypeFull, + Filesystem: "pool1/otherjob/another ds with spaces/childfs", + From: "", + To: "pool1/otherjob/another ds with spaces/childfs@blaffoo", + SizeEstimate: 12912, + }, + }, + { + name: "incrementalWithSpaces", in: incrementalWithSpaces, + exp: &DrySendInfo{ + Type: DrySendTypeIncremental, + Filesystem: "pool1/otherjob/another ds with spaces", + From: "blaffoo", + To: "pool1/otherjob/another ds with spaces@blaffoo2", + SizeEstimate: 624, + }, + }, + { + name: "incrementalWithSpacesInIntermediateComponent", in: incrementalWithSpacesInIntermediateComponent, + exp: &DrySendInfo{ + Type: DrySendTypeIncremental, + Filesystem: "pool1/otherjob/another ds with spaces/childfs", + From: "blaffoo", + To: "pool1/otherjob/another ds with spaces/childfs@blaffoo2", + SizeEstimate: 624, + }, + }, } for _, tc := range tcs {