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 "crypto/sha256"
7 "encoding/hex"
8 "encoding/json"
9 "errors"
10 "io"
11 "os"
12
13 "github.com/carlosframework/platform/internal/rootstate"
14)
15
16type claimUnitMaterialization struct {
17 Migration *claimUnitMigration
18 Prior claimUnitBinding
19 PriorIdentity rootstate.FileIdentity
20 Binding claimUnitBinding
21 Digest string
22 Identity rootstate.FileIdentity
23 Retiring, Retired bool
24}
25
26type claimUnitMaterializer struct {
27 admit func() error
28 socketCapture func(context.Context, claimUnitShape, claimUnitPredecessor) (claimUnitSocketIdentity, error)
29 socketPark func(claimUnitBinding, claimUnitSocketIdentity) error
30 predecessor func(context.Context, claimUnitBinding) (claimUnitPredecessor, error)
31 stopPredecessor func(context.Context, claimUnitPredecessor) error
32 idle func(string) error
33 state rootstate.Store
34 read func(string) ([]byte, rootstate.FileIdentity, error)
35 create func(string, []byte) error
36 replace func(string, rootstate.FileIdentity, []byte) error
37}
38
39func claimUnitDigest(b []byte) string { sum := sha256.Sum256(b); return hex.EncodeToString(sum[:]) }
40
41func (m claimUnitMaterializer) load(host string) (claimUnitMaterialization, error) {
42 var r claimUnitMaterialization
43 raw, err := m.state.Read(host + ".unit.json")
44 if err != nil {
45 return r, err
46 }
47 dec := json.NewDecoder(bytes.NewReader(raw))
48 dec.DisallowUnknownFields()
49 if dec.Decode(&r) != nil || dec.Decode(new(any)) != io.EOF || !r.Binding.valid() || r.Binding.Owner.Host != host || !claimHexDigest(r.Digest) || (r.Retired && !r.Retiring) {
50 return r, os.ErrPermission
51 }
52 if r.Migration != nil && (!r.Migration.valid(r.Binding) || r.Prior != (claimUnitBinding{})) {
53 return r, os.ErrPermission
54 }
55 if r.Prior != (claimUnitBinding{}) && (!r.Prior.valid() || !r.Prior.Retired || r.Prior.Shape != r.Binding.Shape || r.Prior.Owner == r.Binding.Owner || r.PriorIdentity == (rootstate.FileIdentity{})) {
56 return r, os.ErrPermission
57 }
58 return r, nil
59}
60func (m claimUnitMaterializer) save(r claimUnitMaterialization) error {
61 raw, err := json.Marshal(r)
62 if err != nil {
63 return err
64 }
65 return m.state.Write(r.Binding.Owner.Host+".unit.json", raw)
66}
67func claimOwnedUnitDefinition(b claimUnitBinding) ([]byte, error) {
68 raw, err := claimBoundUnitDefinition(b.Shape)
69 if err != nil {
70 return nil, err
71 }
72 owner, _ := json.Marshal(b.Owner)
73 return append([]byte("# claim-owner="+claimUnitDigest(owner)+"\n"), raw...), nil
74}
75func claimRetiredUnitDefinition(b claimUnitBinding) []byte {
76 owner, _ := json.Marshal(b.Owner)
77 return []byte("# claim-retired=" + claimUnitDigest(owner) + "\n[Unit]\nRefuseManualStart=yes\n[Service]\nType=oneshot\nExecStart=/usr/bin/false\nRestart=no\n")
78}
79
80func (m claimUnitMaterializer) install(ctx context.Context, b claimUnitBinding) error {
81 if m.admit != nil {
82 if err := m.admit(); err != nil {
83 return err
84 }
85 }
86 if m.state == nil || m.read == nil || m.create == nil || m.idle == nil || !b.valid() || b.Retiring || b.Retired {
87 return os.ErrPermission
88 }
89 unlock, err := m.state.Lock(ctx, "materialize")
90 if err != nil {
91 return err
92 }
93 defer unlock()
94 want, err := claimOwnedUnitDefinition(b)
95 if err != nil {
96 return err
97 }
98 r, err := m.load(b.Owner.Host)
99 if errors.Is(err, os.ErrNotExist) {
100 raw, id, e := m.read(b.Shape.Unit)
101 r = claimUnitMaterialization{Binding: b, Digest: claimUnitDigest(want)}
102 if e == nil {
103 r.Migration, err = m.migration(ctx, b, raw, id)
104 if err != nil {
105 return err
106 }
107 } else if !errors.Is(e, os.ErrNotExist) {
108 return e
109 }
110
111 } else if err != nil {
112 return err
113 }
114 if r.Binding != b || r.Digest != claimUnitDigest(want) || r.Retiring || r.Retired {
115 return os.ErrPermission
116 }
117 if r.Identity == (rootstate.FileIdentity{}) && r.Migration == nil {
118 if err := m.idle(b.Shape.Host); err != nil {
119 return err
120 }
121 }
122 if err = m.save(r); err != nil {
123 return err
124 }
125 if r.Identity == (rootstate.FileIdentity{}) && r.Migration != nil {
126 return m.finishMigration(ctx, r, want)
127 }
128 raw, id, err := m.read(b.Shape.Unit)
129 if errors.Is(err, os.ErrNotExist) && r.Identity == (rootstate.FileIdentity{}) {
130 if err = m.create(b.Shape.Unit, want); err != nil {
131 return err
132 }
133 raw, id, err = m.read(b.Shape.Unit)
134 }
135 if err != nil {
136 return err
137 }
138 if !bytes.Equal(raw, want) || id == (rootstate.FileIdentity{}) || (r.Identity != (rootstate.FileIdentity{}) && !r.Identity.Same(id)) {
139 return os.ErrPermission
140 }
141 r.Identity = id
142 return m.save(r)
143}
144
145func (m claimUnitMaterializer) verify(b claimUnitBinding) error {
146 r, err := m.load(b.Owner.Host)
147 if err != nil {
148 return err
149 }
150 if r.Binding != b || r.Retiring || r.Retired || r.Identity == (rootstate.FileIdentity{}) {
151 return os.ErrPermission
152 }
153 raw, id, err := m.read(b.Shape.Unit)
154 if err != nil {
155 return err
156 }
157 want, err := claimOwnedUnitDefinition(b)
158 if err != nil {
159 return err
160 }
161 if !id.Same(r.Identity) || !bytes.Equal(raw, want) || claimUnitDigest(raw) != r.Digest {
162 return os.ErrPermission
163 }
164 return nil
165}
166
167func (m claimUnitMaterializer) retire(ctx context.Context, b claimUnitBinding, gone func() error) error {
168 if m.state == nil || m.read == nil || m.replace == nil || gone == nil || !b.valid() {
169 return os.ErrPermission
170 }
171 unlock, err := m.state.Lock(ctx, "materialize")
172 if err != nil {
173 return err
174 }
175 defer unlock()
176 r, err := m.load(b.Owner.Host)
177 if err != nil {
178 return err
179 }
180 if r.Binding != b || r.Identity == (rootstate.FileIdentity{}) {
181 return os.ErrPermission
182 }
183 raw, id, err := m.read(b.Shape.Unit)
184 if err != nil {
185 return err
186 }
187 tombstone := claimRetiredUnitDefinition(b)
188 if r.Retiring && bytes.Equal(raw, tombstone) {
189 if r.Retired && !r.Identity.Same(id) {
190 return os.ErrPermission
191 }
192 r.Identity = id
193 r.Retired = true
194 return m.save(r)
195 }
196 if r.Retired || !id.Same(r.Identity) || claimUnitDigest(raw) != r.Digest {
197 return os.ErrPermission
198 }
199 r.Retiring = true
200 if err = m.save(r); err != nil {
201 return err
202 }
203 if err = gone(); err != nil {
204 return err
205 }
206 if err = m.replace(b.Shape.Unit, r.Identity, tombstone); err != nil {
207 return err
208 }
209 raw, id, err = m.read(b.Shape.Unit)
210 if err != nil {
211 return err
212 }
213 if !bytes.Equal(raw, tombstone) {
214 return os.ErrPermission
215 }
216 r.Identity = id
217 r.Retired = true
218 return m.save(r)
219}
220