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 "encoding/hex"
8 "errors"
9 "os"
10
11 "github.com/carlosframework/platform/internal/hostbox"
12 "golang.org/x/sys/unix"
13)
14
15type servingParkTopology struct {
16 Parent, Root servingParkPersistentDirectory
17}
18
19// This only obtains a handle for an already-open directory. It never opens a
20// supplied handle, follows a request path or changes mount permissions.
21func servingParkPersistentDir(d *os.File) (servingParkPersistentDirectory, error) {
22 var out servingParkPersistentDirectory
23 if d == nil {
24 return out, hostbox.ErrUnsupported
25 }
26 id, err := servingParkDirID(d)
27 if err != nil {
28 return out, err
29 }
30 var st unix.Stat_t
31 if err = unix.Fstat(int(d.Fd()), &st); err != nil {
32 return out, err
33 }
34 var fs unix.Statfs_t
35 if err = unix.Fstatfs(int(d.Fd()), &fs); err != nil {
36 return out, err
37 }
38 h, mount, err := unix.NameToHandleAt(int(d.Fd()), "", unix.AT_EMPTY_PATH)
39 if err != nil {
40 return out, err
41 }
42 if mount <= 0 || uint64(mount) != id.Mount {
43 return out, hostbox.ErrChanged
44 }
45 out = servingParkPersistentDirectory{Filesystem: fs.Fsid.Val, Type: h.Type(), Handle: hex.EncodeToString(h.Bytes()), Owner: st.Uid, Mode: st.Mode}
46 if !out.valid() {
47 return servingParkPersistentDirectory{}, hostbox.ErrChanged
48 }
49 return out, nil
50}
51
52// Capture only while the recorded boot and both descriptor identities still
53// match. Old namespaces are never upgraded by looking at their current paths.
54func (f *servingParkFS) captureTopology(parent, root *os.File, n servingParkNamespace) (*servingParkTopology, error) {
55 if f.handle == nil {
56 return nil, hostbox.ErrUnsupported
57 }
58 if err := f.checkBoot(n.BootID); err != nil {
59 return nil, err
60 }
61 p, err := servingParkDirID(parent)
62 if err != nil || p != n.Parent {
63 return nil, hostbox.ErrChanged
64 }
65 r, err := servingParkDirID(root)
66 if err != nil || r != n.Root || r.Device != p.Device || r.Mount != p.Mount {
67 return nil, hostbox.ErrChanged
68 }
69 ph, err := f.handle(parent)
70 if err != nil {
71 return nil, err
72 }
73 rh, err := f.handle(root)
74 if err != nil {
75 return nil, err
76 }
77 if !ph.valid() || !rh.valid() || ph.Filesystem != rh.Filesystem || rh.Owner != f.owner || rh.Mode&0077 != 0 {
78 return nil, hostbox.ErrChanged
79 }
80 p, err = servingParkDirID(parent)
81 if err != nil || p != n.Parent {
82 return nil, hostbox.ErrChanged
83 }
84 r, err = servingParkDirID(root)
85 if err != nil || r != n.Root || f.checkBoot(n.BootID) != nil {
86 return nil, hostbox.ErrChanged
87 }
88 return &servingParkTopology{Parent: ph, Root: rh}, nil
89}
90
91// This observation cannot be used by the park filesystem or sent to an agent.
92// A later recovery transaction must preserve retirement history and validate
93// every affected source/event before it can publish current mount authority.
94type servingParkRebootObservation struct {
95 Volume string `json:",omitempty"`
96 Previous servingParkNamespace
97 Parent servingParkParent
98 BootID string
99 CurrentParent, CurrentRoot servingArtifactDirectory
100}
101
102// Called under the namespace lock. This is a read-only prerequisite for reboot
103// recovery, not permission to rewrite mount IDs or resume deletion. Quarantines
104// require a separate transition covering the displaced root and retained data.
105func (f *servingParkFS) inspectRebootTopology(ctx context.Context) (servingParkRebootObservation, error) {
106 var zero servingParkRebootObservation
107 if ctx == nil || f == nil || f.state == nil || f.handle == nil {
108 return zero, hostbox.ErrUnsupported
109 }
110 if err := ctx.Err(); err != nil {
111 return zero, err
112 }
113 boot, err := f.currentBoot()
114 if err != nil {
115 return zero, err
116 }
117 p, err := f.historicalNamespaceParent()
118 if err != nil {
119 return zero, err
120 }
121 var n servingParkNamespace
122 body, err := f.state.Read("namespace.json")
123 if err != nil {
124 return zero, err
125 }
126 if servingParkDecode(body, &n) != nil || n.Version != 1 || !validServingParkBootID(n.BootID) || n.BootID != p.BootID || n.Parent != p.Parent || n.Topology == nil || !n.Topology.Parent.valid() || !n.Topology.Root.valid() || n.Root.Device != n.Parent.Device || n.Root.Mount != n.Parent.Mount || n.Root.Inode == 0 {
127 return zero, hostbox.ErrChanged
128 }
129 if _, err := f.state.Read("namespace-quarantine.json"); !errors.Is(err, os.ErrNotExist) {
130 if err == nil {
131 err = hostbox.ErrChanged
132 }
133 return zero, err
134 }
135 parent, pid, err := f.parent()
136 if err != nil {
137 return zero, err
138 }
139 defer parent.Close()
140 root, rid, err := f.privateDir(parent, ".serving-parked")
141 if err != nil {
142 return zero, err
143 }
144 defer root.Close()
145 // Device and mount numbers are boot-local. Across boots, require the same
146 // filesystem and persistent handles below, plus unchanged inode numbers.
147 // Both directories must still share the current device and mount.
148 if n.BootID == boot && pid.Device != n.Parent.Device {
149 return zero, hostbox.ErrChanged
150 }
151 if n.BootID == boot && pid.Mount == n.Parent.Mount {
152 prior, err := servingParkReadBinding(f.state, "namespace")
153 if err != nil || prior == nil || prior.BeforeBoot != n.BootID || prior.BeforeParent != n.Parent {
154 return zero, hostbox.ErrChanged
155 }
156 }
157 if pid.Inode != n.Parent.Inode || rid.Device != pid.Device || rid.Inode != n.Root.Inode || rid.Mount != pid.Mount {
158 return zero, hostbox.ErrChanged
159 }
160 current := servingParkNamespace{Version: 1, Parent: pid, Root: rid, BootID: boot}
161 volume := ""
162 if f.volume != nil {
163 volume = f.volume(pid.Device)
164 }
165 topology, err := f.captureTopology(parent, root, current)
166 if err != nil {
167 return zero, err
168 }
169 if *topology != *n.Topology {
170 return zero, hostbox.ErrChanged
171 }
172 // The service user owns the mount parent. Reopen both names after reading
173 // the persistent handles; a detached original descriptor proves no path.
174 again, nextParent, err := f.parent()
175 if err != nil {
176 return zero, err
177 }
178 defer again.Close()
179 next, nextRoot, err := f.privateDir(again, ".serving-parked")
180 if err != nil {
181 return zero, err
182 }
183 defer next.Close()
184 if nextParent != pid || nextRoot != rid || f.checkBoot(boot) != nil {
185 return zero, hostbox.ErrChanged
186 }
187 againTopology, err := f.captureTopology(again, next, current)
188 if err != nil {
189 return zero, err
190 }
191 if *againTopology != *topology || f.volume != nil && f.volume(nextParent.Device) != volume {
192 return zero, hostbox.ErrChanged
193 }
194 if err := ctx.Err(); err != nil {
195 return zero, err
196 }
197 return servingParkRebootObservation{Volume: volume, Previous: n, Parent: p, BootID: boot, CurrentParent: pid, CurrentRoot: rid}, nil
198}
199