carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package park
2
3import (
4 "context"
5 "errors"
6 "path"
7 "sort"
8 "strings"
9
10 "github.com/carlosframework/platform/internal/hostclaim"
11 "github.com/carlosframework/platform/internal/hostguard"
12 "github.com/carlosframework/platform/internal/release"
13)
14
15var (
16 ErrLocalIdentity = errors.New("park: local retirement identity mismatch")
17 ErrLocalInventory = errors.New("park: missing or unsafe inventory")
18 ErrLocalJournal = errors.New("park: invalid or conflicting local journal")
19 ErrLocalUnsupported = errors.New("park: local retirement unsupported")
20 errLocalComplete = errors.New("park: local obligation already completed")
21)
22
23type Local struct {
24 Authorize func(context.Context, *hostclaim.Claim, hostclaim.Obligation) error
25 Guard hostguard.Guard
26 Claims release.Store
27 DataDir, Box string
28 afterRename func(string) error
29}
30
31type localItem struct {
32 Path string
33 Stamp uint64
34 Dev, Ino uint64
35 Type uint32
36 Missing, Done bool
37}
38
39type localJournal struct {
40 V int
41 Host, Incarnation, Op, Obligation, Retirement, Mode string
42 Installation, ArtifactStamp uint64
43 Items []localItem
44}
45
46func localComponent(s string) bool {
47 return s != "" && s != "." && s != ".." && !strings.ContainsAny(s, "/\\\x00")
48}
49
50func localPath(s string) bool {
51 if s == "" || path.IsAbs(s) || path.Clean(s) != s || strings.ContainsAny(s, "\\\x00") {
52 return false
53 }
54 for _, p := range strings.Split(s, "/") {
55 if !localComponent(p) {
56 return false
57 }
58 }
59 return s != "hostguard" && !strings.HasPrefix(s, "hostguard/") && s != ".parked" && !strings.HasPrefix(s, ".parked/")
60}
61
62func (l Local) chore(c *hostclaim.Claim, captured hostguard.Captured, id, op string) (hostclaim.Obligation, error) {
63 if c == nil {
64 return hostclaim.Obligation{}, ErrLocalIdentity
65 }
66 var found *hostclaim.Obligation
67 for i := range c.Obligations {
68 if c.Obligations[i].ID == id && c.Obligations[i].Op == op {
69 found = &c.Obligations[i]
70 break
71 }
72 }
73 if found == nil {
74 return hostclaim.Obligation{}, errLocalComplete
75 }
76 if c.Holder == nil || c.Holder.Incarnation != captured.Incarnation || found.Kind != "park-local" || found.Box != l.Box || found.Owner != "box:"+l.Box || !localComponent(found.Retirement) {
77 return hostclaim.Obligation{}, ErrLocalIdentity
78 }
79 if _, ok := c.Inventory[l.Box]; !ok {
80 return hostclaim.Obligation{}, ErrLocalInventory
81 }
82 return *found, nil
83}
84
85func (l Local) RetireArtifacts(ctx context.Context, host string, captured hostguard.Captured, id, op string, stamp uint64) error {
86 if stamp == 0 {
87 return ErrLocalIdentity
88 }
89 return l.retire(ctx, host, captured, id, op, "artifacts", stamp)
90}
91
92// RetireSuperseded is retire-artifacts(S) of claim §6.4 for an admitted
93// writer's own superseded local generation (claim Plan 9 D4): a restarted
94// landing probe, or a cold wake after the open, over its predecessor's
95// database. No chore is read, required or completed. The authority is the
96// guard at the active installation captured, newer than stamp, together with
97// check, which runs on every fresh claim under the guard and again in the
98// inventory CAS. Exactly paths are parked, each of which the inventory must
99// tag stamp; the same CAS tags them captured.Stamp, the ownership the
100// successor records before it materializes them (§6.6 P6-D2), so its own
101// successor can retire them in turn. No stamp moves and every other path and
102// box is left as found. A replay of the same retirement resumes its journal.
103func (l Local) RetireSuperseded(ctx context.Context, host string, captured hostguard.Captured, op, retirement string, stamp uint64, paths []string, check func(*hostclaim.Claim) error) (before, after hostclaim.Observed, resultErr error) {
104 if !localSupported() {
105 return before, after, ErrLocalUnsupported
106 }
107 if ctx == nil || check == nil || l.Claims == nil || l.Box == "" || l.DataDir == "" || l.Guard.DataDir != l.DataDir || captured.Incarnation == "" || captured.Stamp == 0 ||
108 stamp == 0 || stamp >= captured.Stamp || !localComponent(op) || !localComponent(retirement) || len(paths) == 0 {
109 return before, after, ErrLocalIdentity
110 }
111 sorted := append([]string(nil), paths...)
112 sort.Strings(sorted)
113 for i := range sorted {
114 if !localPath(sorted[i]) {
115 return before, after, ErrLocalInventory
116 }
117 for k := i + 1; k < len(sorted); k++ {
118 if sorted[k] == sorted[i] || strings.HasPrefix(sorted[k], sorted[i]+"/") {
119 return before, after, ErrLocalInventory
120 }
121 }
122 }
123 selected := func(c *hostclaim.Claim) error {
124 if c == nil || c.Holder == nil || c.Holder.Incarnation != captured.Incarnation || c.Holder.Stamp != captured.Stamp {
125 return ErrLocalIdentity
126 }
127 return check(c)
128 }
129 var j localJournal
130 guarded := func(c *hostclaim.Claim) error {
131 if err := selected(c); err != nil {
132 return err
133 }
134 entry, ok := c.Inventory[l.Box]
135 if !ok {
136 return ErrLocalInventory
137 }
138 superseded, successor := 0, 0
139 for _, p := range sorted {
140 switch entry.Paths[p] {
141 case stamp:
142 superseded++
143 case captured.Stamp:
144 successor++
145 default:
146 return ErrLocalInventory
147 }
148 }
149 if successor == len(sorted) {
150 return errLocalComplete
151 }
152 if superseded != len(sorted) {
153 return ErrLocalInventory
154 }
155 j = localJournal{V: 1, Host: host, Incarnation: captured.Incarnation, Op: op, Retirement: retirement, Mode: "superseded", Installation: captured.Stamp, ArtifactStamp: stamp}
156 for _, p := range sorted {
157 j.Items = append(j.Items, localItem{Path: p, Stamp: stamp})
158 }
159 return nil
160 }
161 work := func() error {
162 if err := l.parkLocal(ctx, &j); err != nil {
163 return err
164 }
165 fresh, err := hostclaim.Read(ctx, l.Claims, host)
166 if err != nil {
167 return err
168 }
169 if fresh.Absent {
170 return ErrLocalIdentity
171 }
172 before = fresh
173 after, err = hostclaim.Update(ctx, l.Claims, fresh, func(c *hostclaim.Claim) error {
174 if err := selected(c); err != nil {
175 return err
176 }
177 entry, ok := c.Inventory[l.Box]
178 if !ok {
179 return ErrLocalInventory
180 }
181 for _, item := range j.Items {
182 if entry.Paths[item.Path] != item.Stamp {
183 return ErrLocalInventory
184 }
185 }
186 for _, item := range j.Items {
187 entry.Paths[item.Path] = captured.Stamp
188 }
189 c.Inventory[l.Box] = entry
190 return nil
191 })
192 return err
193 }
194 err := l.Guard.With(ctx, host, captured, guarded, work)
195 if errors.Is(err, errLocalComplete) {
196 return before, after, nil
197 }
198 return before, after, err
199}
200
201func (l Local) RetireInstallation(ctx context.Context, host string, captured hostguard.Captured, id, op string) error {
202 return l.retire(ctx, host, captured, id, op, "installation", 0)
203}
204
205func (l Local) retire(ctx context.Context, host string, captured hostguard.Captured, id, op, mode string, stamp uint64) error {
206 if !localSupported() {
207 return ErrLocalUnsupported
208 }
209 if ctx == nil || l.Authorize == nil || l.Claims == nil || l.Box == "" || l.DataDir == "" || l.Guard.DataDir != l.DataDir || (captured.Stamp == 0 && mode != "empty") || id == "" || op == "" {
210 return ErrLocalIdentity
211 }
212 obs, err := hostclaim.Read(ctx, l.Claims, host)
213 if err != nil {
214 return err
215 }
216 if obs.Absent {
217 return ErrLocalIdentity
218 }
219 if _, err := l.chore(obs.Claim, captured, id, op); err != nil {
220 if errors.Is(err, errLocalComplete) {
221 return nil
222 }
223 return err
224 }
225 var j localJournal
226 var inventory map[string]uint64
227 check := func(c *hostclaim.Claim) error {
228 o, err := l.chore(c, captured, id, op)
229 if err != nil {
230 return err
231 }
232 if err := l.Authorize(ctx, c, o); err != nil {
233 return err
234 }
235 inventory = c.Inventory[l.Box].Paths
236 if mode == "empty" && len(inventory) != 0 {
237 return ErrLocalInventory
238 }
239 paths := make([]string, 0, len(inventory))
240 for p, s := range inventory {
241 if !localPath(p) || s == 0 {
242 return ErrLocalInventory
243 }
244 paths = append(paths, p)
245 }
246 sort.Strings(paths)
247 for i := range paths {
248 for k := i + 1; k < len(paths); k++ {
249 if strings.HasPrefix(paths[k], paths[i]+"/") {
250 return ErrLocalInventory
251 }
252 }
253 }
254 j = localJournal{V: 1, Host: host, Incarnation: captured.Incarnation, Op: op, Obligation: id, Retirement: o.Retirement, Mode: mode, Installation: captured.Stamp, ArtifactStamp: stamp}
255 for _, p := range paths {
256 if mode == "installation" || inventory[p] == stamp {
257 j.Items = append(j.Items, localItem{Path: p, Stamp: inventory[p]})
258 }
259 }
260 return nil
261 }
262 work := func() error {
263 if err := l.parkLocal(ctx, &j); err != nil {
264 return err
265 }
266 fresh, err := hostclaim.Read(ctx, l.Claims, host)
267 if err != nil {
268 return err
269 }
270 if fresh.Absent {
271 return ErrLocalIdentity
272 }
273 _, err = hostclaim.Update(ctx, l.Claims, fresh, func(c *hostclaim.Claim) error {
274 o, err := l.chore(c, captured, id, op)
275 if err != nil {
276 return err
277 }
278 if err := l.Authorize(ctx, c, o); err != nil {
279 return err
280 }
281 entry := c.Inventory[l.Box]
282 for _, item := range j.Items {
283 if entry.Paths[item.Path] != item.Stamp {
284 return ErrLocalInventory
285 }
286 }
287 if (mode == "installation" || mode == "empty") && len(entry.Paths) != len(j.Items) {
288 return ErrLocalInventory
289 }
290 for _, item := range j.Items {
291 delete(entry.Paths, item.Path)
292 }
293 c.Inventory[l.Box] = entry
294 return hostclaim.CompleteObligation(op, id)(c)
295 })
296 return err
297 }
298 if mode == "installation" {
299 err = l.Guard.RetireInstallationWith(ctx, host, captured, check, work)
300 } else if mode == "empty" {
301 err = l.Guard.WithNoInstallation(ctx, host, captured.Incarnation, check, work)
302 } else {
303 err = l.Guard.With(ctx, host, captured, check, work)
304 }
305 if errors.Is(err, errLocalComplete) {
306 return nil
307 }
308 return err
309}
310
311func (l Local) RecordInventory(ctx context.Context, host string, captured hostguard.Captured, layout string, paths []string, check func(*hostclaim.Claim) error) error {
312 if !localSupported() {
313 return ErrLocalUnsupported
314 }
315 if ctx == nil || l.Claims == nil || l.Box == "" || l.Guard.DataDir != l.DataDir || l.DataDir == "" || check == nil || captured.Stamp == 0 || (layout != "flat" && layout != "nested") {
316 return ErrLocalInventory
317 }
318 for _, p := range paths {
319 if !localPath(p) {
320 return ErrLocalInventory
321 }
322 }
323 return l.Guard.With(ctx, host, captured, check, func() error {
324 obs, err := hostclaim.Read(ctx, l.Claims, host)
325 if err != nil {
326 return err
327 }
328 if obs.Absent {
329 return ErrLocalIdentity
330 }
331 _, err = hostclaim.Update(ctx, l.Claims, obs, func(c *hostclaim.Claim) error {
332 if c.Holder == nil || c.Holder.Incarnation != captured.Incarnation {
333 return ErrLocalIdentity
334 }
335 if err := check(c); err != nil {
336 return err
337 }
338 if c.Inventory == nil {
339 c.Inventory = make(map[string]hostclaim.InventoryEntry)
340 }
341 entry, exists := c.Inventory[l.Box]
342 if exists && entry.Layout != layout && len(entry.Paths) != 0 {
343 return ErrLocalInventory
344 }
345 entry.Layout = layout
346 if entry.Paths == nil {
347 entry.Paths = make(map[string]uint64)
348 }
349 for _, p := range paths {
350 if old := entry.Paths[p]; old != 0 && old != captured.Stamp {
351 return ErrLocalInventory
352 }
353 entry.Paths[p] = captured.Stamp
354 }
355 all := make([]string, 0, len(entry.Paths))
356 for p, s := range entry.Paths {
357 if !localPath(p) || s == 0 {
358 return ErrLocalInventory
359 }
360 all = append(all, p)
361 }
362 sort.Strings(all)
363 for i := range all {
364 for k := i + 1; k < len(all); k++ {
365 if strings.HasPrefix(all[k], all[i]+"/") {
366 return ErrLocalInventory
367 }
368 }
369 }
370 c.Inventory[l.Box] = entry
371 return nil
372 })
373 return err
374 })
375}
376
377func (l Local) AcknowledgeEmpty(ctx context.Context, host, incarnation, id, op string) error {
378 return l.retire(ctx, host, hostguard.Captured{Incarnation: incarnation}, id, op, "empty", 0)
379}
380