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 "bytes"
5 "context"
6 "encoding/json"
7 "errors"
8 "io"
9 "os"
10 "path/filepath"
11 "reflect"
12 "sort"
13 "strings"
14
15 "github.com/carlosframework/platform/internal/hostbox"
16 "github.com/carlosframework/platform/internal/rootstate"
17)
18
19// A completed root retirement changes where an obligation is checked. It never
20// deletes the payload, its requirements, or a generation reservation.
21func generationRetirements(ctx context.Context, snapshot generationInventory) (map[int]string, error) {
22 transfers, err := generationTransfers(ctx, snapshot)
23 if err != nil {
24 return nil, err
25 }
26 retired := map[int]string{}
27 for _, j := range snapshot.Retirements {
28 if err := ctx.Err(); err != nil {
29 return nil, err
30 }
31 canonical, err := hostbox.NewRetirementJournal(j.ID, j.Inventory)
32 if err != nil || j.V != 1 || !j.Complete || j.Target == nil || j.ID != j.Target.FenceID || !reflect.DeepEqual(j.Inventory, canonical.Inventory) || len(j.Steps) != len(canonical.Steps) {
33 return nil, os.ErrPermission
34 }
35 for i, step := range j.Steps {
36 if !step.Done || step.Artifact != canonical.Steps[i].Artifact {
37 return nil, os.ErrPermission
38 }
39 }
40 t := *j.Target
41 if t.Incarnation == "." || t.Incarnation == ".." || strings.ContainsAny(t.Incarnation, "/\\\x00") {
42 return nil, os.ErrPermission
43 }
44 index := -1
45 for i, p := range snapshot.Payloads {
46 in := p.Inventory
47 if in.Host == t.Host && in.Incarnation == t.Incarnation && in.InstallationStamp == t.Stamp {
48 if index != -1 || in.Box != t.Box || in.Enrollment != t.Enrollment || in.DirectoryRevision != t.DirectoryRevision {
49 return nil, os.ErrPermission
50 }
51 index = i
52 }
53 }
54 if index < 0 || retired[index] != "" || j.Inventory.Host != t.Host || j.Inventory.Captured.Incarnation != t.Incarnation || j.Inventory.Captured.Stamp != t.Stamp {
55 return nil, os.ErrPermission
56 }
57 receipt := false
58 for _, view := range snapshot.Installations {
59 if view.Host != t.Host {
60 continue
61 }
62 for _, r := range view.Receipts {
63 if r.Target == t && bytes.Equal(r.Bytes, j.StopReceipt) && verifyRetirementReceipt(ctx, r) == nil {
64 receipt = true
65 }
66 }
67 }
68 if !receipt {
69 return nil, os.ErrPermission
70 }
71 expected := map[hostbox.Artifact]bool{}
72 for _, e := range snapshot.Payloads[index].Inventory.Entries {
73 if e.Ownership != j.Inventory.Ownership || (e.Materialization != "materialized" && !(e.Kind == "timer" && e.Materialization == "intended")) {
74 return nil, os.ErrPermission
75 }
76 a := hostbox.Artifact{AllowAbsent: e.Kind == "timer" && (e.TimerRetired || e.Materialization == "intended"), Kind: hostbox.ArtifactKind(e.Kind), Root: e.ManagedRoot, Name: e.RelativePath, Owner: e.Ownership, RowID: e.RowIdentity, Identity: e.Identity, Dev: e.Device, Ino: e.Inode, Type: e.Type}
77 if expected[a] {
78 return nil, os.ErrPermission
79 }
80 expected[a] = true
81 }
82 matched := map[hostbox.Artifact]bool{}
83 for _, a := range j.Inventory.Artifacts {
84 // The retirement producer also captures the timer registration. Root's
85 // inventory establishes that registration as the timer's exact row ID.
86 if a.Kind == hostbox.ArtifactTimer {
87 row, registration, ok := strings.Cut(a.RowID, "~")
88 if ok {
89 if row != registration {
90 return nil, os.ErrPermission
91 }
92 a.RowID = row
93 }
94 }
95 if !expected[a] {
96 return nil, os.ErrPermission
97 }
98 matched[a] = true
99 }
100 if len(matched) != len(expected) {
101 return nil, os.ErrPermission
102 }
103 retired[index] = j.ID
104 }
105 for prior := range transfers {
106 next := prior
107 for steps := 0; ; steps++ {
108 if steps > len(transfers) {
109 return nil, os.ErrPermission
110 }
111 successor, ok := transfers[next]
112 if !ok {
113 break
114 }
115 next = successor
116 }
117 if id := retired[next]; id != "" {
118 if retired[prior] != "" {
119 return nil, os.ErrPermission
120 }
121 retired[prior] = id
122 }
123 }
124 return retired, nil
125}
126
127func generationPhysicalPayloads(ctx context.Context, snapshot generationInventory) ([]hostbox.GenerationPayload, error) {
128 retired, err := generationRetirements(ctx, snapshot)
129 if err != nil {
130 return nil, err
131 }
132 for i, payload := range snapshot.Payloads {
133 if retired[i] != "" {
134 continue
135 }
136 for _, entry := range payload.Inventory.Entries {
137 if entry.Materialization != "materialized" {
138 return nil, os.ErrPermission
139 }
140 }
141 }
142 payloads := append([]hostbox.GenerationPayload(nil), snapshot.Payloads...)
143 transfers, err := generationTransfers(ctx, snapshot)
144 if err != nil {
145 return nil, err
146 }
147 for prior := range transfers {
148 next := transfers[prior]
149 for steps := 0; ; steps++ {
150 if steps > len(transfers) {
151 return nil, os.ErrPermission
152 }
153 successor, ok := transfers[next]
154 if !ok {
155 break
156 }
157 next = successor
158 }
159 in := &payloads[prior].Inventory
160 in.Entries = append([]hostbox.StopInventoryEntry(nil), in.Entries...)
161 for i, entry := range in.Entries {
162 if entry.Kind != "path" {
163 continue
164 }
165 for _, current := range snapshot.Payloads[next].Inventory.Entries {
166 if current.Kind == "path" && current.ManagedRoot == entry.ManagedRoot && current.RelativePath == entry.RelativePath {
167 in.Entries[i].Identity, in.Entries[i].Device = current.Identity, current.Device
168 }
169 }
170 }
171 }
172 for i, id := range retired {
173 in := &payloads[i].Inventory
174 in.Entries = append([]hostbox.StopInventoryEntry(nil), in.Entries...)
175 for n, e := range in.Entries {
176 if e.Kind == "path" {
177 if !filepath.IsLocal(e.RelativePath) || filepath.Clean(e.RelativePath) != e.RelativePath {
178 return nil, os.ErrPermission
179 }
180 roots := map[string]string{"data": dataDir()}
181 parker := hostbox.TypedParker{CapturePersistent: true, Roots: roots, ParkedRoots: roots}
182 parkRoot, err := parker.ParkedArtifactRoot(hostbox.Artifact{Root: e.ManagedRoot, Name: e.RelativePath, Identity: e.Identity})
183 if err != nil {
184 return nil, err
185 }
186 rel, err := filepath.Rel(dataDir(), parkRoot)
187 if err != nil || !filepath.IsLocal(rel) {
188 return nil, os.ErrPermission
189 }
190 in.Entries[n].RelativePath = filepath.Join(rel, ".parked", in.Incarnation, in.Host, id, e.ManagedRoot, e.RelativePath)
191 }
192 }
193 }
194 return payloads, nil
195}
196
197func generationRetirementRecords(ctx context.Context, store rootstate.Store) ([]hostbox.RetirementJournal, error) {
198 names, err := store.Names()
199 if err != nil {
200 return nil, err
201 }
202 if len(names) > 8192 {
203 return nil, os.ErrPermission
204 }
205 sort.Strings(names)
206 var out []hostbox.RetirementJournal
207 for _, name := range names {
208 if err := ctx.Err(); err != nil {
209 return nil, err
210 }
211 // Cold handover retains its own payload inventory. It is never parking
212 // authority here. Temporary writes cannot establish completed retirement.
213 if name == "cold" || name == "serving" || strings.HasPrefix(name, ".retire-") {
214 continue
215 }
216 if !strings.HasSuffix(name, ".json") {
217 return nil, os.ErrPermission
218 }
219 raw, err := store.Read(name)
220 if err != nil {
221 return nil, err
222 }
223 var j hostbox.RetirementJournal
224 dec := json.NewDecoder(bytes.NewReader(raw))
225 dec.DisallowUnknownFields()
226 if dec.Decode(&j) != nil || dec.Decode(new(any)) != io.EOF || j.ID+".json" != name {
227 return nil, os.ErrPermission
228 }
229 out = append(out, j)
230 }
231 return out, nil
232}
233
234func productionGenerationRetirements(ctx context.Context) ([]hostbox.RetirementJournal, error) {
235 return generationReadRetirements(ctx, claimRetireJournalDir("", ""), func(path string) (rootstate.Store, error) {
236 return rootstate.Open(path, false, false)
237 }, func(path string) error {
238 directory, err := os.Open(path)
239 if err != nil {
240 return err
241 }
242 defer directory.Close()
243 return directory.Sync()
244 })
245}
246
247func generationReadRetirements(ctx context.Context, path string, open func(string) (rootstate.Store, error), sync func(string) error) ([]hostbox.RetirementJournal, error) {
248 store, err := open(path)
249 if errors.Is(err, os.ErrNotExist) {
250 return nil, nil
251 }
252 if err != nil {
253 return nil, err
254 }
255 defer store.Close()
256 journals, err := generationRetirementRecords(ctx, store)
257 if err != nil {
258 return nil, err
259 }
260 // Sync the completion rename without rewriting concurrent progress.
261 if err := sync(path); err != nil {
262 return nil, err
263 }
264 return journals, nil
265}
266