carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "crypto/sha256"
6 "encoding/hex"
7 "encoding/json"
8 "errors"
9 "fmt"
10 "os"
11 "reflect"
12 "strings"
13
14 "github.com/carlosframework/platform/internal/hostbox"
15 "github.com/carlosframework/platform/internal/hostguard"
16 "github.com/carlosframework/platform/internal/registry"
17 "github.com/carlosframework/platform/internal/rootparticipation"
18 "github.com/carlosframework/platform/internal/rootstate"
19)
20
21type rootMigrationTimerRecord struct {
22 Schema int
23 Target hostguard.FenceTarget
24 Before, Next hostbox.StopInventory
25 Revision uint64
26 Rows []registry.Timer
27 Registrations []string
28 Artifacts hostguard.InstallationArtifacts
29}
30type rootMigrationTimers struct {
31 state rootstate.Store
32 registry *registry.Registry
33 inventory *hostbox.StopInventoryDB
34 guard hostguard.Guard
35}
36
37func (m rootMigrationTimers) key(item rootMigrationItem) string {
38 in := item.Installation
39 return "timers-" + generationInventoryDigest([]any{in.Host, in.Incarnation, in.Stamp, item.Session}) + ".json"
40}
41func (m rootMigrationTimers) owner(item rootMigrationItem, registration string) registry.ClaimOwnership {
42 in := item.Installation
43 sum := sha256.Sum256([]byte(fmt.Sprintf("%s\x00%s\x00%d\x00%s", in.Host, in.Incarnation, in.Stamp, in.Box)))
44 return registry.ClaimOwnership{ClaimIncarnation: in.Incarnation, ClaimStamp: in.Stamp, ClaimOwner: hex.EncodeToString(sum[:]), ClaimRowID: registration}
45}
46func (m rootMigrationTimers) load(item rootMigrationItem) (rootMigrationTimerRecord, bool, error) {
47 var r rootMigrationTimerRecord
48 raw, err := m.state.Read(m.key(item))
49 if errors.Is(err, os.ErrNotExist) {
50 return r, false, nil
51 }
52 if err != nil {
53 return r, false, err
54 }
55 in := item.Installation
56 if rootparticipation.Decode(raw, &r) != nil || r.Schema != 1 || r.Revision == 0 || r.Revision == ^uint64(0) || r.Target.Host != in.Host || r.Target.Incarnation != in.Incarnation || r.Target.Stamp != in.Stamp || r.Target.Session != item.Session || r.Target.Box != in.Box || r.Target.Enrollment != in.Enrollment {
57 return r, false, errRootMigrationBusy
58 }
59 if len(r.Rows) != len(r.Registrations) {
60 return r, false, errRootMigrationBusy
61 }
62 for i, id := range r.Registrations {
63 if id == "" {
64 return r, false, errRootMigrationBusy
65 }
66 r.Rows[i].Registration = id
67 }
68 return r, true, nil
69}
70func (m rootMigrationTimers) adopt(ctx context.Context, item rootMigrationItem, target hostguard.FenceTarget, canonical registry.Route) error {
71 if m.state == nil || m.registry == nil || m.inventory == nil || m.guard.Root == nil {
72 return errRootMigrationBusy
73 }
74 r, exists, err := m.load(item)
75 if err != nil {
76 return err
77 }
78 if exists {
79 if r.Target != target {
80 return errRootMigrationBusy
81 }
82 } else {
83 in, rev, err := m.inventory.Snapshot(ctx, target.Host, hostguard.Captured{Incarnation: target.Incarnation, Stamp: target.Stamp})
84 if err != nil {
85 return err
86 }
87 if !in.Complete || rev == 0 || rev == ^uint64(0) || in.Host != item.Installation.Host || in.Incarnation != item.Installation.Incarnation || in.InstallationStamp != item.Installation.Stamp || in.Box != item.Installation.Box || in.Enrollment != item.Installation.Enrollment {
88 return errRootMigrationBusy
89 }
90 rows, err := m.registry.TimersFor(in.Host)
91 if err != nil {
92 return err
93 }
94 r = rootMigrationTimerRecord{Schema: 1, Target: target, Before: in, Next: in, Revision: rev, Rows: rows}
95 for _, row := range rows {
96 r.Registrations = append(r.Registrations, row.Registration)
97 }
98 r.Next.Entries = append([]hostbox.StopInventoryEntry(nil), in.Entries...)
99 matched := map[string]bool{}
100 for i, e := range r.Next.Entries {
101 if e.Kind != "timer" {
102 continue
103 }
104 name := strings.TrimPrefix(e.RelativePath, in.Host+"/")
105 if name == e.RelativePath || name == "" || strings.ContainsAny(name, "/\\\r\n\x00") || matched[name] || e.TimerRetired || e.Materialization != "materialized" {
106 return errRootMigrationBusy
107 }
108 found := false
109 for _, row := range rows {
110 if row.Name != name {
111 continue
112 }
113 found = true
114 matched[name] = true
115 owner := m.owner(item, row.Registration)
116 if row.Registration == "" || row.Host != in.Host || (row.ClaimOwnership != (registry.ClaimOwnership{}) && (row.ClaimOwnership != owner || e.RowIdentity != row.Registration || e.Ownership != owner.ClaimOwner)) {
117 return errRootMigrationBusy
118 }
119 e.ManagedRoot, e.RowIdentity, e.Ownership = "registry", row.Registration, owner.ClaimOwner
120 e.Device, e.Inode, e.Type = 0, 0, 0
121 r.Next.Entries[i] = e
122 }
123 if !found {
124 return errRootMigrationBusy
125 }
126 }
127 if len(matched) != len(rows) {
128 return errRootMigrationBusy
129 }
130 prior := item.Installation
131 prior.Artifacts.Schema = 1
132 r.Artifacts, err = rootInventoryArtifacts(prior, canonical, r.Next, rev)
133 if err != nil {
134 return err
135 }
136 }
137 // The original inventory remains bound to root's completed receipt. Registry
138 // observations cannot replace the stop proof or the retained session denial.
139 receipt, err := m.guard.FenceReceiptFor(ctx, r.Target)
140 if err != nil {
141 return err
142 }
143 var proof StopReceipt
144 if json.Unmarshal(receipt.Bytes, &proof) != nil || proof.InventorySHA256 != digestStop(r.Before) {
145 return errRootMigrationBusy
146 }
147 view, err := m.guard.Root.View(ctx, target.Host, hostguard.Captured{Incarnation: target.Incarnation, Stamp: target.Stamp})
148 if err != nil {
149 return err
150 }
151 denied := false
152 for _, d := range view.MigrationDenies {
153 if d.Incarnation == target.Incarnation && d.Stamp >= target.Stamp && d.Session >= target.Session {
154 denied = true
155 }
156 }
157 if !denied {
158 return errRootMigrationBusy
159 }
160 raw, err := json.Marshal(r)
161 if err != nil {
162 return err
163 }
164 if err := m.state.Write(m.key(item), raw); err != nil {
165 return err
166 }
167 current, rev, err := m.inventory.Snapshot(ctx, target.Host, hostguard.Captured{Incarnation: target.Incarnation, Stamp: target.Stamp})
168 if err != nil {
169 return err
170 }
171 before := rev == r.Revision && reflect.DeepEqual(current, r.Before)
172 after := rev == r.Revision+1 && reflect.DeepEqual(current, r.Next)
173 if !before && !after {
174 return errRootMigrationBusy
175 }
176 rows, err := m.registry.TimersFor(target.Host)
177 if err != nil {
178 return err
179 }
180 if len(rows) != len(r.Rows) {
181 return errRootMigrationBusy
182 }
183 for _, observed := range r.Rows {
184 owned := observed
185 owned.ClaimOwnership = m.owner(item, observed.Registration)
186 current, err := m.registry.GetTimer(observed.Host, observed.Name)
187 if err != nil {
188 return err
189 }
190 if *current == observed && observed.ClaimOwnership == (registry.ClaimOwnership{}) {
191 if err := m.registry.AdoptClaimTimer(ctx, observed, owned.ClaimOwnership); err != nil {
192 return err
193 }
194 } else if *current != owned {
195 return errRootMigrationBusy
196 }
197 }
198 if before {
199 if err := m.inventory.WriteStopInventory(ctx, r.Next); err != nil {
200 return err
201 }
202 }
203 return m.guard.Root.CompleteMigrationInventory(ctx, target, r.Revision+1, r.Artifacts, r.Revision)
204}
205