| index ec5eadfc3..c5f103503 100644 |
| --- a/internal/host/movesrecoveryowners.go |
| +++ b/internal/host/movesrecoveryowners.go |
| @@ -585,8 +585,9 @@ func (r movesRecoveryOwners) PublishFenced(ctx context.Context, o hostclaim.Obse |
| return ClaimPublicationReceipt{}, fmt.Errorf("%w: no admitted writer's fence for session %d", hostbox.ErrChanged, h.Session) |
| } |
| // The files on disk must be the tree the recovery placed before anything |
| - // is published, and their content is recorded as this generation's (the |
| - // landing's fix round 3 rule): the digest the relabel compares. |
| + // is published, and a tree whose label the publication will move must |
| + // still hold the content journalled when it was placed (fix round 2): |
| + // the relabel is vouched for by that digest, never one sampled now. |
| placed, ok, err := r.placement(o.Host, v) |
| if err != nil { |
| return ClaimPublicationReceipt{}, err |
| @@ -594,45 +595,52 @@ func (r movesRecoveryOwners) PublishFenced(ctx context.Context, o hostclaim.Obse |
| if !ok { |
| return ClaimPublicationReceipt{}, fmt.Errorf("%w: the recovery recorded no placed tree", hostbox.ErrChanged) |
| } |
| - var digest string |
| if err := work.With(ctx, in.Identity, func() error { |
| - if err := r.verifyPlaced(o.Host, placed); err != nil { |
| - return err |
| - } |
| - var err error |
| - digest, err = m.filesDigest(o.Host) |
| + _, err := r.vouchFiles(o.Host, h, placed) |
| return err |
| }); err != nil { |
| return ClaimPublicationReceipt{}, err |
| } |
| - if err := m.recordFilesDigest(o.Host, v.journalOp(), h.Generation, digest); err != nil { |
| - return ClaimPublicationReceipt{}, err |
| + return m.publishFresh(ctx, o.Host, h, pub, func() error { return r.refreshFilesGen(ctx, o.Host, h, placed, work, in.Identity) }) |
| +} |
| + |
| +// vouchFiles requires the files on disk to be the placed tree (identity) |
| +// and reports whether .files-gen will move to holder.generation: it names |
| +// that generation's cold predecessor for this incarnation. When it will, |
| +// the tree must also hold the journalled placement digest. A label that |
| +// will not move needs no content evidence: nothing is relabelled. |
| +func (r movesRecoveryOwners) vouchFiles(host string, h *hostclaim.Holder, placed recoveryPlacement) (bool, error) { |
| + m := r.m |
| + if err := r.verifyPlaced(host, placed); err != nil { |
| + return false, err |
| } |
| - return m.publishFresh(ctx, o.Host, h, pub, func() error { return r.refreshFilesGen(ctx, o.Host, h, placed, digest, work, in.Identity) }) |
| + dir, _ := m.FilesDir(host) |
| + files, _, err := m.localMarkers(host, filepath.Join(dir, ".none")) |
| + if err != nil || files == nil || files.Incarnation != h.Incarnation || files.Op == h.Generation { |
| + return false, err |
| + } |
| + if pred, ok := coldPredecessor(h, h.Generation); !ok || pred == "" || files.Op != pred { |
| + return false, nil |
| + } |
| + return true, r.verifyContent(host, placed, placed.Plain) |
| } |
| |
| // refreshFilesGen points .files-gen at the published generation, inside the |
| // admitted writer's fence, after the files on disk are proved the tree the |
| -// recovery placed (its identity) with the content just recorded for the |
| -// generation (digest): never by identity alone, and a mismatch fails the |
| +// recovery placed, by identity and by the journalled placement digest |
| +// (vouchFiles): never by identity alone, and a mismatch fails the |
| // publication and relabels nothing. It relabels only a .files-gen naming the |
| // generation's cold predecessor for this incarnation (the files the start |
| // check accepted for this session); absent, or anything else, is left for |
| // the start check to decide. |
| -func (r movesRecoveryOwners) refreshFilesGen(ctx context.Context, host string, h *hostclaim.Holder, placed recoveryPlacement, digest string, work hostwrite.Work, id hostwrite.Identity) error { |
| +func (r movesRecoveryOwners) refreshFilesGen(ctx context.Context, host string, h *hostclaim.Holder, placed recoveryPlacement, work hostwrite.Work, id hostwrite.Identity) error { |
| m := r.m |
| dir, _ := m.FilesDir(host) |
| return work.With(ctx, id, func() error { |
| - if err := r.verifyContent(host, placed, digest); err != nil { |
| + relabel, err := r.vouchFiles(host, h, placed) |
| + if err != nil || !relabel { |
| return err |
| } |
| - files, _, err := m.localMarkers(host, filepath.Join(dir, ".none")) |
| - if err != nil || files == nil || files.Incarnation != h.Incarnation || files.Op == h.Generation { |
| - return err |
| - } |
| - if pred, ok := coldPredecessor(h, h.Generation); !ok || pred == "" || files.Op != pred { |
| - return nil |
| - } |
| body, err := json.Marshal(moves.FilesGen{Op: h.Generation, Incarnation: h.Incarnation}) |
| if err != nil { |
| return err |