diff --git a/internal/endpoint/endpoint.go b/internal/endpoint/endpoint.go index 70c2c06..ba2e4dc 100644 --- a/internal/endpoint/endpoint.go +++ b/internal/endpoint/endpoint.go @@ -636,6 +636,24 @@ func (f subroot) UserSpecifiedDatasets() zfs.UserSpecifiedDatasetsSet { } +// senderPrefixDatasetPath returns the configured literal sender-side prefix +// (e.g. "hdd_9XG1J9J1") to strip from incoming filesystem paths, or nil if +// ZREPL_STRIP_SENDER_PREFIX is unset/empty. Using a literal string (rather +// than a component count) lets us losslessly reconstruct the sender's +// original path later in ListFilesystems, which is required for the +// sender/receiver filesystem-matching in the replication planner to work. +func senderPrefixDatasetPath() *zfs.DatasetPath { + s := envconst.String("ZREPL_STRIP_SENDER_PREFIX", "") + if s == "" { + return nil + } + dp, err := zfs.NewDatasetPath(s) + if err != nil || dp.Length() == 0 { + return nil + } + return dp +} + func (f subroot) MapToLocal(fs string) (*zfs.DatasetPath, error) { p, err := zfs.NewDatasetPath(fs) if err != nil { @@ -644,9 +662,10 @@ func (f subroot) MapToLocal(fs string) (*zfs.DatasetPath, error) { if p.Length() == 0 { return nil, errors.Errorf("cannot map empty filesystem") } - // drop N leading components of the sender's path (e.g. pool name) - if n := envconst.Int("ZREPL_STRIP_SENDER_PREFIX_COMPS", 0); n > 0 { - p.TrimNPrefixComps(n) + // PATCH: strip a configured literal prefix (e.g. the source zpool name) + // from the *sender's* path before appending it under root_fs. + if prefix := senderPrefixDatasetPath(); prefix != nil && p.HasPrefix(prefix) { + p.TrimPrefix(prefix) } c := f.localRoot.Copy() c.Extend(p) @@ -654,8 +673,6 @@ func (f subroot) MapToLocal(fs string) (*zfs.DatasetPath, error) { } - - func (s *Receiver) ListFilesystems(ctx context.Context, req *pdu.ListFilesystemReq) (*pdu.ListFilesystemRes, error) { defer trace.WithSpanFromStackUpdateCtx(&ctx)() @@ -696,12 +713,28 @@ func (s *Receiver) ListFilesystems(ctx context.Context, req *pdu.ListFilesystemR a.TrimPrefix(root) + // PATCH: re-prepend the literal sender prefix that MapToLocal strips, + // so that the Path we report here matches what the sender reports + // for the same filesystem (see replication_logic.go's Path-based + // sender/receiver matching). Without this, the planner can never + // find a matching receiverFS and will treat every cycle as if the + // filesystem doesn't exist yet on the receiver. + reportPath := a.ToString() + if prefix := senderPrefixDatasetPath(); prefix != nil { + if reportPath == "" { + reportPath = prefix.ToString() + } else { + reportPath = prefix.ToString() + "/" + reportPath + } + } + fs := &pdu.Filesystem{ - Path: a.ToString(), + Path: reportPath, IsPlaceholder: ph.IsPlaceholder, ResumeToken: token, } fss = append(fss, fs) + } if len(fss) == 0 { getLogger(ctx).Debug("no filesystems found")