| 1 | //go:build linux |
| 2 | |
| 3 | package host |
| 4 | |
| 5 | import ( |
| 6 | "context" |
| 7 | "errors" |
| 8 | "net" |
| 9 | "os" |
| 10 | "reflect" |
| 11 | |
| 12 | "github.com/carlosframework/platform/internal/hostbox" |
| 13 | "github.com/carlosframework/platform/internal/hostclaim" |
| 14 | "github.com/carlosframework/platform/internal/hostguard" |
| 15 | ) |
| 16 | |
| 17 | // Called only by the root stop coordinator after its kernel observation and |
| 18 | // before publishing the receipt. There is no transport operation for inserting |
| 19 | // this evidence. A claim change during the observation or lock wait refuses it. |
| 20 | func (p *servingParkRoot) retainLegacyNoProcess(ctx context.Context, o hostclaim.Observed, result StopResult) error { |
| 21 | if p == nil || p.ledger == nil || p.root.RequireNewWork == nil || p.root.InventoryActor == nil || p.root.Stop.Coordinator.Guard.Root == nil { |
| 22 | return hostbox.ErrUnsupported |
| 23 | } |
| 24 | s := servingLegacyStop{Observed: o, Result: result} |
| 25 | if validateServingLegacyStop(s) != nil { |
| 26 | return ErrClaimStopReceipt |
| 27 | } |
| 28 | return p.ledger.withNamespace(ctx, func() error { |
| 29 | return p.root.Stop.Coordinator.Guard.With(ctx, o.Host, hostguard.Captured{Incarnation: result.Target.Incarnation, Stamp: result.Target.Stamp}, func(c *hostclaim.Claim) error { |
| 30 | if !reflect.DeepEqual(c, o.Claim) { |
| 31 | return hostbox.ErrChanged |
| 32 | } |
| 33 | return nil |
| 34 | }, func() error { |
| 35 | if err := p.root.RequireNewWork(ctx); err != nil { |
| 36 | return err |
| 37 | } |
| 38 | actor, err := p.root.InventoryActor(ctx) |
| 39 | if err != nil { |
| 40 | return err |
| 41 | } |
| 42 | t := result.Target |
| 43 | if !actor.Eligible || actor.Quarantined || actor.Box != t.Box || actor.Region != t.Region || actor.Enrollment != t.Enrollment || actor.Revision == 0 || !servingPriorBinding(t.DirectoryRevision, actor) { |
| 44 | return hostbox.ErrChanged |
| 45 | } |
| 46 | fresh, err := p.root.Stop.Coordinator.Guard.Claims.Read(ctx, o.Host) |
| 47 | if err != nil { |
| 48 | return err |
| 49 | } |
| 50 | if fresh.ETag != o.ETag || fresh.Absent { |
| 51 | return hostbox.ErrChanged |
| 52 | } |
| 53 | a := servingArtifactIdentity{Host: o.Host, Box: result.Target.Box, Region: result.Target.Region, Incarnation: result.Target.Incarnation} |
| 54 | old, err := p.ledger.legacyStop(a) |
| 55 | if err == nil { |
| 56 | // Retry reservation and settlement change Gen, ETag and WakeRetry |
| 57 | // without admitting a writer. Keep the first receipt across those |
| 58 | // changes, but refuse a different holder or enrollment. |
| 59 | priorClaim, nextClaim := *old.Observed.Claim, *o.Claim |
| 60 | priorHolder, nextHolder := *priorClaim.Holder, *nextClaim.Holder |
| 61 | priorHolder.WakeRetry, nextHolder.WakeRetry = nil, nil |
| 62 | priorClaim.Holder, nextClaim.Holder = &priorHolder, &nextHolder |
| 63 | priorClaim.Gen = nextClaim.Gen |
| 64 | if !reflect.DeepEqual(priorClaim, nextClaim) || old.Result.Target.Enrollment != result.Target.Enrollment || old.Result.Target.DirectoryRevision != result.Target.DirectoryRevision { |
| 65 | return hostbox.ErrChanged |
| 66 | } |
| 67 | return nil |
| 68 | } |
| 69 | if !errors.Is(err, os.ErrNotExist) { |
| 70 | return err |
| 71 | } |
| 72 | return p.ledger.write(servingLegacyKey("legacy-stop", a), s) |
| 73 | }) |
| 74 | }) |
| 75 | } |
| 76 | |
| 77 | func (p *servingParkRoot) validateLegacyRootEvidence(ctx context.Context, o hostclaim.Observed, view hostguard.FenceView, actor hostbox.Actor, a servingParkAuthorization, e *servingLegacyEvidence, receipts []hostguard.FenceReceipt) error { |
| 78 | if validateServingLegacyEvidence(a, e) != nil { |
| 79 | return hostbox.ErrChanged |
| 80 | } |
| 81 | current := a.OriginalSuccessor |
| 82 | var entry *hostclaim.AdmissionRecord |
| 83 | for i := range o.Claim.Holder.Admissions { |
| 84 | r := &o.Claim.Holder.Admissions[i] |
| 85 | if r.Session == current.Session { |
| 86 | if entry != nil { |
| 87 | return hostbox.ErrChanged |
| 88 | } |
| 89 | entry = r |
| 90 | } |
| 91 | } |
| 92 | if entry == nil || entry.Mode != "cold" || entry.Box != current.Box || entry.Region != current.Region || entry.Generation != current.Generation || entry.Predecessor != e.Selector.Generation { |
| 93 | return hostbox.ErrChanged |
| 94 | } |
| 95 | if e.NoProcess != nil { |
| 96 | saved, err := p.ledger.legacyStop(current) |
| 97 | if err != nil || !reflect.DeepEqual(saved, *e.NoProcess) || len(receipts) != 0 { |
| 98 | return hostbox.ErrChanged |
| 99 | } |
| 100 | t, h := saved.Result.Target, saved.Observed.Claim.Holder |
| 101 | if t.Enrollment != actor.Enrollment || !servingPriorBinding(t.DirectoryRevision, actor) || h.Account != o.Claim.Holder.Account || h.App != o.Claim.Holder.App || h.Key != o.Claim.Holder.Key || !h.CreatedAt.Equal(o.Claim.Holder.CreatedAt) { |
| 102 | return hostbox.ErrChanged |
| 103 | } |
| 104 | for _, r := range o.Claim.Holder.Admissions { |
| 105 | if r.Session < current.Session { |
| 106 | return hostbox.ErrChanged |
| 107 | } |
| 108 | } |
| 109 | if e.Selector.Empty { |
| 110 | if entry.PredecessorRegion != "" || entry.WritesLost != nil { |
| 111 | return hostbox.ErrChanged |
| 112 | } |
| 113 | } else if entry.WritesLost == nil || entry.WritesLost.Box != current.Box || entry.WritesLost.Session != 0 || entry.WritesLost.Generation != "" || entry.PredecessorRegion != current.Region { |
| 114 | return hostbox.ErrChanged |
| 115 | } |
| 116 | return nil |
| 117 | } |
| 118 | prior, err := servingPredecessor(o.Claim, a.Successor, e.Selector.Session, e.Selector.Generation) |
| 119 | if err != nil || prior.Stamp >= current.Stamp { |
| 120 | return hostbox.ErrChanged |
| 121 | } |
| 122 | stops, err := servingRootStops(ctx, view, prior, actor) |
| 123 | if err != nil { |
| 124 | return err |
| 125 | } |
| 126 | if !sameServingRootStops(stops, receipts) { |
| 127 | return ErrClaimStopReceipt |
| 128 | } |
| 129 | return nil |
| 130 | } |
| 131 | |
| 132 | func (p *servingParkRoot) parkLegacyServingDatabase(ctx context.Context, conn net.Conn, current servingArtifactIdentity, requested servingLegacyPredecessor) (servingParkAuthorization, error) { |
| 133 | var out servingParkAuthorization |
| 134 | err := p.withAuthority(ctx, conn, current, func(o hostclaim.Observed, view hostguard.FenceView, actor hostbox.Actor, dir servingArtifactDirectory, tags uint64) error { |
| 135 | var err error |
| 136 | consumed, err := p.ledger.legacyConsumption(current) |
| 137 | if err != nil { |
| 138 | return err |
| 139 | } |
| 140 | a := servingParkAuthorization{Legacy: true, OriginalSuccessor: current, Successor: current, Directory: dir} |
| 141 | a.ID = servingRetirementID(a.Predecessor, current, true) |
| 142 | e := &servingLegacyEvidence{Selector: requested} |
| 143 | var receipts []hostguard.FenceReceipt |
| 144 | var saved *servingParkRecord |
| 145 | if consumed != nil { |
| 146 | if consumed.Collected { |
| 147 | return hostbox.ErrChanged |
| 148 | } |
| 149 | a, e, receipts = consumed.Authorization, consumed.LegacyEvidence, consumed.Receipts |
| 150 | if a.Directory != dir || e == nil { |
| 151 | return hostbox.ErrChanged |
| 152 | } |
| 153 | r, readErr := p.ledger.read(a.ID) |
| 154 | if readErr == nil { |
| 155 | saved = &r |
| 156 | } else if !errors.Is(readErr, os.ErrNotExist) { |
| 157 | return readErr |
| 158 | } |
| 159 | } else { |
| 160 | if tags != 0 { |
| 161 | return hostbox.ErrChanged |
| 162 | } |
| 163 | if requested.Session == 0 { |
| 164 | stop, readErr := p.ledger.legacyStop(current) |
| 165 | if readErr != nil { |
| 166 | return readErr |
| 167 | } |
| 168 | e.NoProcess = &stop |
| 169 | } else { |
| 170 | prior, readErr := servingPredecessor(o.Claim, current, requested.Session, requested.Generation) |
| 171 | if readErr != nil { |
| 172 | return readErr |
| 173 | } |
| 174 | receipts, err = servingRootStops(ctx, view, prior, actor) |
| 175 | if err != nil { |
| 176 | return err |
| 177 | } |
| 178 | } |
| 179 | } |
| 180 | if err = p.validateLegacyRootEvidence(ctx, o, view, actor, a, e, receipts); err != nil { |
| 181 | return err |
| 182 | } |
| 183 | active := a |
| 184 | if saved != nil { |
| 185 | if !sameServingLegacySource(saved.Source, consumed.Source) || saved.Authorization != a || !sameServingLegacyEvidence(saved.LegacyEvidence, e) || !sameServingRootStops(saved.Receipts, receipts) { |
| 186 | return hostbox.ErrChanged |
| 187 | } |
| 188 | if err = validateServingRootHistory(ctx, o, view, actor, *saved); err != nil { |
| 189 | return err |
| 190 | } |
| 191 | active = saved.activeAuthorization() |
| 192 | } |
| 193 | if tags != active.TagsFrom && tags != active.Successor.Stamp && tags != current.Stamp { |
| 194 | return hostbox.ErrChanged |
| 195 | } |
| 196 | var next servingParkAuthorization |
| 197 | var stops []hostguard.FenceReceipt |
| 198 | if active.Successor != current { |
| 199 | if saved != nil && saved.Complete || active.Successor.Stamp >= current.Stamp { |
| 200 | return hostbox.ErrChanged |
| 201 | } |
| 202 | prior, e := servingPredecessor(o.Claim, current, active.Successor.Stamp, active.Successor.Generation) |
| 203 | if e != nil || prior != active.Successor { |
| 204 | return hostbox.ErrChanged |
| 205 | } |
| 206 | stops, err = servingRootStops(ctx, view, prior, actor) |
| 207 | if err != nil { |
| 208 | return err |
| 209 | } |
| 210 | next = active |
| 211 | next.Successor, next.TagsFrom = current, tags |
| 212 | } |
| 213 | if err = p.ledger.legacyUnlaunched(ctx, current, actor.Enrollment); err != nil { |
| 214 | return err |
| 215 | } |
| 216 | if consumed == nil && e.Selector.Empty { |
| 217 | if p.empty == nil { |
| 218 | return hostbox.ErrUnsupported |
| 219 | } |
| 220 | if a.Successor != current { |
| 221 | return hostbox.ErrChanged |
| 222 | } |
| 223 | in := ClaimLaunchInputs{Empty: true, Admission: hostclaim.Admission{NewGeneration: current.Generation}} |
| 224 | if err = p.empty.EmptyAuthority(ctx, o, in); err != nil { |
| 225 | return err |
| 226 | } |
| 227 | } |
| 228 | // Remote first-creator proof can wait on I/O; it cannot carry a stale claim |
| 229 | // into the first source capture or any replay mutation. |
| 230 | fresh, err := p.root.Stop.Coordinator.Guard.Claims.Read(ctx, current.Host) |
| 231 | if err != nil { |
| 232 | return err |
| 233 | } |
| 234 | if fresh.Absent || fresh.ETag != o.ETag { |
| 235 | return hostbox.ErrChanged |
| 236 | } |
| 237 | if err = p.prepareServingNamespace(ctx, a, receipts, e); err != nil { |
| 238 | return err |
| 239 | } |
| 240 | r, err := p.ledger.parkWithEvidenceLocked(ctx, a, receipts, e) |
| 241 | if err != nil { |
| 242 | return err |
| 243 | } |
| 244 | if err = p.ledger.fs.CheckParked(&r); err != nil { |
| 245 | return err |
| 246 | } |
| 247 | if next.ID != "" { |
| 248 | r, err = p.ledger.continueLocked(ctx, a.ID, next, stops) |
| 249 | if err != nil { |
| 250 | return err |
| 251 | } |
| 252 | } |
| 253 | out = r.activeAuthorization() |
| 254 | return nil |
| 255 | }) |
| 256 | return out, err |
| 257 | } |
| 258 | |
| 259 | func sameServingLegacySource(a, b servingParkSource) bool { |
| 260 | if a.Directory != b.Directory || a.Parent != b.Parent || a.Persistent != b.Persistent || a.BootID != b.BootID { |
| 261 | return false |
| 262 | } |
| 263 | for i := range a.Files { |
| 264 | if a.Files[i].Persistent != b.Files[i].Persistent || !sameServingParkFile(a.Files[i], b.Files[i]) { |
| 265 | return false |
| 266 | } |
| 267 | } |
| 268 | return true |
| 269 | } |
| 270 | |