carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package host
4
5import (
6 "context"
7 "errors"
8 "io"
9 "os"
10 "reflect"
11
12 "github.com/carlosframework/platform/internal/hostbox"
13 "github.com/carlosframework/platform/internal/provision"
14 "golang.org/x/sys/unix"
15)
16
17// This observation is not filesystem or transport authority. The eventual
18// transaction must retain Record and commit separate current-boot identities.
19type servingParkRebootRecord struct {
20 Topology servingParkRebootObservation
21 Record servingParkRecord
22 Source, Event servingArtifactDirectory
23}
24
25// The caller holds namespace.lock. Repeating the whole inspection rejects a
26// source/name change during handle reads and a concurrent private-record change.
27func (f *servingParkFS) inspectRebootRecord(ctx context.Context, id string) (servingParkRebootRecord, error) {
28 var zero servingParkRebootRecord
29 first, err := f.observeRebootRecord(ctx, id)
30 if err != nil {
31 return zero, err
32 }
33 again, err := f.observeRebootRecord(ctx, id)
34 if err != nil {
35 return zero, err
36 }
37 if !reflect.DeepEqual(first, again) {
38 return zero, hostbox.ErrChanged
39 }
40 if err := ctx.Err(); err != nil {
41 return zero, err
42 }
43 return again, nil
44}
45
46func (f *servingParkFS) observeRebootRecord(ctx context.Context, id string) (servingParkRebootRecord, error) {
47 var out servingParkRebootRecord
48 topology, err := f.inspectRebootTopology(ctx)
49 if err != nil {
50 return out, err
51 }
52 r, err := (&servingParkLedger{state: f.state}).readHistorical(id)
53 if err != nil {
54 return out, err
55 }
56 n := topology.Previous
57 // A later retirement binds its parent to namespace history with root-private
58 // persistent evidence. Device/mount numbers constrain only the same boot.
59 if (r.Source.BootID != n.BootID || r.Source.Parent != n.Parent) && (!r.Source.ParentPersistent.valid() || r.Source.ParentPersistent != n.Topology.Parent) {
60 return out, hostbox.ErrChanged
61 }
62 if !validServingParkBootID(r.Source.BootID) || (r.Source.BootID == topology.BootID && r.Source.Parent.Device != topology.CurrentParent.Device) || !r.Source.Persistent.valid() || r.Source.Parent.Inode != n.Parent.Inode || r.Source.Persistent.Filesystem != n.Topology.Parent.Filesystem || r.Prepared && (r.Namespace.Inode != n.Root.Inode || r.Namespace.Mount != r.Source.Parent.Mount) {
63 return out, hostbox.ErrChanged
64 }
65 if r.Source.BootID == topology.BootID && r.Source.Parent.Mount == topology.CurrentParent.Mount {
66 prior, err := servingParkReadBinding(f.state, "retirement-"+id)
67 if err != nil || prior == nil || prior.BeforeBoot != r.Source.BootID || prior.BeforeParent != r.Source.Parent {
68 return out, hostbox.ErrChanged
69 }
70 }
71 parent, pid, err := f.parent()
72 if err != nil {
73 return out, err
74 }
75 defer parent.Close()
76 root, rid, err := f.privateDir(parent, ".serving-parked")
77 if err != nil {
78 return out, err
79 }
80 defer root.Close()
81 if pid != topology.CurrentParent || rid != topology.CurrentRoot {
82 return out, hostbox.ErrChanged
83 }
84 current := servingParkNamespace{Version: 1, Parent: pid, Root: rid, BootID: topology.BootID}
85 handles, err := f.captureTopology(parent, root, current)
86 if err != nil {
87 return out, err
88 }
89 if *handles != *n.Topology {
90 return out, hostbox.ErrChanged
91 }
92 var source *os.File
93 // Completed parks are independent of the live installation, which may have
94 // restored a successor or been removed. Incomplete parks still require it.
95 if !r.Complete {
96 source, err = servingParkOpenDir(parent, provision.InstanceID(r.Authorization.Successor.Host))
97 if err != nil {
98 return out, err
99 }
100 defer source.Close()
101 out.Source, err = f.rebootDirectory(source, r.Source.Directory, r.Source.Persistent, pid)
102 if err != nil {
103 return out, err
104 }
105 }
106 event, eid, err := f.privateDir(root, r.Authorization.ID)
107 if errors.Is(err, unix.ENOENT) {
108 if r.Prepared && !r.EventRemoveIntent {
109 return out, hostbox.ErrChanged
110 }
111 } else if err != nil {
112 return out, err
113 } else {
114 defer event.Close()
115 if !r.Prepared || r.EventRemoved {
116 return out, hostbox.ErrChanged
117 }
118 out.Event, err = f.rebootDirectory(event, r.Event, r.EventPersistent, pid)
119 if err != nil || out.Event != eid {
120 return out, hostbox.ErrChanged
121 }
122 // Only four fixed artifact names can be evidence. Unknown children remain
123 // untouched and block this event, including after an interrupted unlink.
124 names, e := event.Readdirnames(5)
125 if len(names) > 4 || e != nil && !errors.Is(e, io.EOF) {
126 return out, hostbox.ErrChanged
127 }
128 for _, name := range names {
129 known := false
130 for _, item := range r.Source.Files {
131 if item.Present && !item.Deleted && item.Name == name {
132 known = true
133 }
134 }
135 if !known {
136 return out, hostbox.ErrChanged
137 }
138 }
139 }
140 for _, item := range r.Source.Files {
141 src := servingParkFile{Name: item.Name}
142 dst := servingParkFile{Name: item.Name}
143 if source != nil {
144 src, err = servingParkStat(source, item.Name)
145 if err != nil {
146 return out, err
147 }
148 }
149 if event != nil {
150 dst, err = servingParkStat(event, item.Name)
151 if err != nil {
152 return out, err
153 }
154 }
155 if !servingParkRebootFileMatches(item, src, dst, pid, r) {
156 return out, hostbox.ErrChanged
157 }
158 }
159 if err := f.checkBoot(topology.BootID); err != nil {
160 return out, err
161 }
162 if err := ctx.Err(); err != nil {
163 return out, err
164 }
165 out.Topology, out.Record = topology, r
166 return out, nil
167}
168
169func (f *servingParkFS) rebootDirectory(d *os.File, old servingArtifactDirectory, proof servingParkPersistentDirectory, parent servingArtifactDirectory) (servingArtifactDirectory, error) {
170 var zero servingArtifactDirectory
171 id, err := servingParkDirID(d)
172 if err != nil {
173 return zero, err
174 }
175 if !proof.valid() || id.Device != parent.Device || id.Inode != old.Inode || id.Mount != parent.Mount {
176 return zero, hostbox.ErrChanged
177 }
178 actual, err := f.handle(d)
179 if err != nil {
180 return zero, err
181 }
182 if actual != proof {
183 return zero, hostbox.ErrChanged
184 }
185 return id, nil
186}
187
188func servingParkRebootFileMatches(old, src, dst servingParkFile, parent servingArtifactDirectory, r servingParkRecord) bool {
189 if !old.Present {
190 return !src.Present && !dst.Present
191 }
192 if !old.Persistent.valid() {
193 return false
194 }
195 expected := old
196 expected.Device, expected.Mount = parent.Device, parent.Mount
197 if old.Deleted || r.EventRemoved {
198 return !src.Present && !dst.Present
199 }
200 if r.Deleting {
201 return !src.Present && (sameServingParkFile(expected, dst) || !dst.Present && old.DeleteIntent)
202 }
203 if r.Parked || old.Moved {
204 return !src.Present && sameServingParkFile(expected, dst)
205 }
206 // A rename can be durable before its progress write. Accept exactly one
207 // recorded inode, with its handle; a second link or a replacement refuses.
208 return sameServingParkFile(expected, src) && !dst.Present || !src.Present && sameServingParkFile(expected, dst)
209}
210