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 "context"
5 "errors"
6 "io"
7 "os"
8 "path/filepath"
9 "strings"
10
11 "github.com/carlosframework/platform/internal/moves"
12 "github.com/carlosframework/platform/internal/release"
13 "github.com/carlosframework/platform/internal/rootparticipation"
14 "github.com/carlosframework/platform/internal/rootstate"
15)
16
17func rootMovesFloorIdentity() (rootFloorIdentity, bool, error) {
18 l, required, err := rootparticipation.OpenForUpdate()
19 if l.State != nil {
20 defer l.State.Close()
21 }
22 if err != nil || !required {
23 return rootFloorIdentity{}, required, err
24 }
25 c, err := l.RecoveryCensus()
26 id := rootFloorIdentity{Deployment: os.Getenv("CARLOS_DEPLOYMENT_BUCKET"), Box: c.Box, Enrollment: c.Enrollment}
27 if err == nil && id.Deployment == "" {
28 err = errRootFloorHistory
29 }
30 return id, true, err
31}
32
33type rootFloorIdentitySource func() (rootFloorIdentity, bool, error)
34
35func (source rootFloorIdentitySource) read() (rootFloorIdentity, bool, error) {
36 if source == nil {
37 return rootFloorIdentity{}, false, nil
38 }
39 return source()
40}
41
42// Root callers retain main's floor implementation until a census exists.
43func readEffectiveMovesFloor(source rootFloorIdentitySource, opt, dd string) (*moves.Floor, error) {
44 id, required, err := source.read()
45 if err != nil {
46 return nil, err
47 }
48 if !required {
49 return readLocalFloor(dd)
50 }
51 return readRootProtocolBound(rootFloorDir(opt), id)
52}
53
54func readRootProtocolBound(dir string, id rootFloorIdentity) (*moves.Floor, error) {
55 f, err := openRootProtocolFloors(dir, id, false)
56 if errors.Is(err, os.ErrNotExist) {
57 public, e := rootstate.Open(dir, false, true)
58 if errors.Is(e, os.ErrNotExist) {
59 return nil, nil
60 }
61 if e != nil {
62 return nil, e
63 }
64 defer public.Close()
65 return (rootMovesFloors{public: public, identity: id}).localBound()
66 }
67 if err != nil {
68 return nil, err
69 }
70 defer f.close()
71 return f.localBound()
72}
73
74func withEffectiveMovesFloorLock(source rootFloorIdentitySource, opt, dd string, create bool, fn func() error) error {
75 id, required, err := source.read()
76 if err != nil {
77 return err
78 }
79 if !required {
80 return withFloorLock(dd, create, func(*movesDir) error { return fn() })
81 }
82 f, err := openRootMovesFloors(opt, id, true)
83 if err != nil {
84 return err
85 }
86 defer f.close()
87 unlock, err := f.private.Lock(context.Background(), "floor.lock")
88 if err != nil {
89 return err
90 }
91 defer unlock()
92 seeds, err := openRootProtocolFloors(rootSeedsFloorDir(opt), id, true)
93 if err != nil {
94 return err
95 }
96 defer seeds.close()
97 unlockSeeds, err := seeds.private.Lock(context.Background(), "floor.lock")
98 if err != nil {
99 return err
100 }
101 defer unlockSeeds()
102 return fn()
103}
104
105// Read both mirrors independently. Missing files add nothing; a malformed
106// bounded file quarantines, while any usable sibling still contributes a deny.
107func rootFloorLegacy(dd string) ([]*moves.Floor, error) {
108 return rootProtocolLegacy(dd, moves.FloorFile, moves.FloorMinReleaseFile)
109}
110func rootProtocolLegacy(dd, jsonFile, lineFile string) ([]*moves.Floor, error) {
111 var legacy []*moves.Floor
112 fl, jerr := readRootLegacyProtocolJSON(dd, jsonFile)
113 if fl != nil {
114 if validRootFloor(fl) {
115 legacy = append(legacy, fl)
116 } else {
117 jerr = errors.Join(jerr, errRootFloorHistory)
118 }
119 }
120 file, err := openBeneath(dd, strings.Split(filepath.ToSlash(lineFile), "/")...)
121 if errors.Is(err, os.ErrNotExist) {
122 return legacy, jerr
123 }
124 if err != nil {
125 return legacy, errors.Join(jerr, err)
126 }
127 defer file.Close()
128 st, err := file.Stat()
129 if err != nil {
130 return legacy, errors.Join(jerr, err)
131 }
132 if !st.Mode().IsRegular() || st.Size() > 256 {
133 return legacy, errors.Join(jerr, errRootFloorHistory)
134 }
135 raw, err := io.ReadAll(io.LimitReader(file, 257))
136 line := strings.TrimSpace(string(raw))
137 if err != nil || !validFloorRelease(line) {
138 return legacy, errors.Join(jerr, err, errRootFloorHistory)
139 }
140 legacy = append(legacy, &moves.Floor{Min: 1, MinRelease: line})
141 return legacy, jerr
142}
143
144func refreshRootMovesFloor(ctx context.Context, store release.Store, opt, dd string, id rootFloorIdentity) error {
145 f, err := openRootMovesFloors(opt, id, true)
146 if err != nil {
147 return err
148 }
149 defer f.close()
150 remote, rerr := moves.ReadFloor(ctx, store)
151 legacy, lerr := rootFloorLegacy(dd)
152 return f.refresh(ctx, remote, legacy, errors.Join(rerr, lerr))
153}
154
155// Reading the final projection can race the writer's last directory sync.
156// Sync after reading makes those already-fsynced file bytes durable before the
157// honest agent advertises them, even if root died just after the rename.
158func readRootFloorProjection(opt string) (rootFloorProjection, error) {
159 return readRootProtocolProjection(rootFloorDir(opt))
160}
161func readRootProtocolProjection(dir string) (rootFloorProjection, error) {
162 s, err := rootstate.Open(dir, false, true)
163 if err != nil {
164 return rootFloorProjection{}, err
165 }
166 defer s.Close()
167 var p rootFloorProjection
168 raw, err := s.Read(rootFloorProjectionFile)
169 if err != nil {
170 return p, err
171 }
172 if err = rootstate.Sync(s); err != nil {
173 return p, err
174 }
175 if len(raw) > movesFloorMaxBytes || rootparticipation.Decode(raw, &p) != nil || !p.Accepted || !p.rootFloorRecord.valid(p.Identity) {
176 return p, errRootFloorHistory
177 }
178 f := rootMovesFloors{public: s, identity: p.Identity}
179 bound, err := f.selector()
180 if err != nil || string(rootFloorLine(bound)) != string(rootFloorLine(p.Floor)) {
181 return p, errRootFloorHistory
182 }
183 return p, nil
184}
185
186func readRootLegacyProtocolJSON(dd, name string) (*moves.Floor, error) {
187 file, err := openBeneath(dd, strings.Split(filepath.ToSlash(name), "/")...)
188 if errors.Is(err, os.ErrNotExist) {
189 return nil, nil
190 }
191 if err != nil {
192 return nil, err
193 }
194 defer file.Close()
195 st, err := file.Stat()
196 if err != nil {
197 return nil, err
198 }
199 if !st.Mode().IsRegular() || st.Size() > movesFloorMaxBytes {
200 return nil, errRootFloorHistory
201 }
202 raw, err := io.ReadAll(io.LimitReader(file, movesFloorMaxBytes+1))
203 if err != nil {
204 return nil, err
205 }
206 var floor moves.Floor
207 err = rootparticipation.Decode(raw, &floor)
208 if !validRootFloor(&floor) {
209 return nil, errors.Join(err, errRootFloorHistory)
210 }
211 return &floor, err
212}
213