From 0ee7a49d312a9af0a1c47e544cdf550502b84532 Mon Sep 17 00:00:00 2001 From: Christian Schwarz Date: Sun, 26 Jul 2020 19:22:03 +0200 Subject: [PATCH] [#289] zfs: workaround for OpenZFS 0.7 dry send info with zero estimated size fixes #289 --- zfs/zfs.go | 30 ++++++++++++++++++++++-------- zfs/zfs_test.go | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/zfs/zfs.go b/zfs/zfs.go index 46e0359..f7c3193 100644 --- a/zfs/zfs.go +++ b/zfs/zfs.go @@ -860,9 +860,9 @@ type DrySendInfo struct { var ( // keep same number of capture groups for unmarshalInfoLine homogeneity - sendDryRunInfoLineRegexFull = regexp.MustCompile(`^(full)\t()([^\t]+@[^\t]+)\t([0-9]+)$`) + sendDryRunInfoLineRegexFull = regexp.MustCompile(`^(?Pfull)\t()(?P[^\t]+@[^\t]+)(\t(?P[0-9]+))?$`) // cannot enforce '[#@]' in incremental source, see test cases - sendDryRunInfoLineRegexIncremental = regexp.MustCompile(`^(incremental)\t([^\t]+)\t([^\t]+@[^\t]+)\t([0-9]+)$`) + sendDryRunInfoLineRegexIncremental = regexp.MustCompile(`^(?Pincremental)\t(?P[^\t]+)\t(?P[^\t]+@[^\t]+)(\t(?P[0-9]+))?$`) ) // see test cases for example output @@ -890,30 +890,44 @@ func (s *DrySendInfo) unmarshalInfoLine(l string) (regexMatched bool, err error) mFull := sendDryRunInfoLineRegexFull.FindStringSubmatch(l) mInc := sendDryRunInfoLineRegexIncremental.FindStringSubmatch(l) + var matchingExpr *regexp.Regexp 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 + matchingExpr, m = sendDryRunInfoLineRegexFull, mFull } else if mInc != nil { - m = mInc + matchingExpr, m = sendDryRunInfoLineRegexIncremental, mInc } - s.Type, err = DrySendTypeFromString(m[1]) + + fields := make(map[string]string, matchingExpr.NumSubexp()) + for i, name := range matchingExpr.SubexpNames() { + if i != 0 { + fields[name] = m[i] + } + } + + s.Type, err = DrySendTypeFromString(fields["type"]) if err != nil { return true, err } - s.From = m[2] - s.To = m[3] + s.From = fields["from"] + s.To = fields["to"] 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[4], 10, 64) + if fields["size"] == "" { + // workaround for OpenZFS 0.7 prior to https://github.com/openzfs/zfs/commit/835db58592d7d947e5818eb7281882e2a46073e0#diff-66bd524398bcd2ac70d90925ab6d8073L1245 + // see https://github.com/zrepl/zrepl/issues/289 + fields["size"] = "0" + } + s.SizeEstimate, err = strconv.ParseInt(fields["size"], 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 d59e3b9..2f781de 100644 --- a/zfs/zfs_test.go +++ b/zfs/zfs_test.go @@ -136,6 +136,18 @@ size 10511856 fullNoToken := ` full zroot/test/a@3 10518512 size 10518512 +` + + // zero-length incremental send on ZoL 0.7.12 + // (it omits the size field as well as the size line if size is 0) + // see https://github.com/zrepl/zrepl/issues/289 + // fixed in https://github.com/openzfs/zfs/commit/835db58592d7d947e5818eb7281882e2a46073e0#diff-66bd524398bcd2ac70d90925ab6d8073L1245 + incZeroSized_0_7_12 := ` +incremental p1 with/ spaces d1@1 with space p1 with/ spaces d1@2 with space +` + + fullZeroSized_0_7_12 := ` +full p1 with/ spaces d1@2 with space ` fullWithSpaces := "\nfull\tpool1/otherjob/ds with spaces@blaffoo\t12912\nsize\t12912\n" @@ -243,6 +255,25 @@ size 10518512 SizeEstimate: 624, }, }, + { + name: "incrementalZeroSizedOpenZFS_pre0.7.12", in: incZeroSized_0_7_12, + exp: &DrySendInfo{ + Type: DrySendTypeIncremental, + Filesystem: "p1 with/ spaces d1", + From: "p1 with/ spaces d1@1 with space", + To: "p1 with/ spaces d1@2 with space", + SizeEstimate: 0, + }, + }, + { + name: "fullZeroSizedOpenZFS_pre0.7.12", in: fullZeroSized_0_7_12, + exp: &DrySendInfo{ + Type: DrySendTypeFull, + Filesystem: "p1 with/ spaces d1", + To: "p1 with/ spaces d1@2 with space", + SizeEstimate: 0, + }, + }, } for _, tc := range tcs { @@ -251,10 +282,12 @@ size 10518512 in := tc.in[1:] // strip first newline var si DrySendInfo err := si.unmarshalZFSOutput([]byte(in)) + t.Logf("%#v", &si) + t.Logf("err=%T %s", err, err) + if tc.expErr { assert.Error(t, err) } - t.Logf("%#v", &si) if tc.exp != nil { assert.Equal(t, tc.exp, &si) }