| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "errors" |
| 6 | "os" |
| 7 | "path/filepath" |
| 8 | "time" |
| 9 | |
| 10 | "github.com/carlosframework/platform/internal/moves" |
| 11 | "github.com/carlosframework/platform/internal/release" |
| 12 | "github.com/carlosframework/platform/internal/rootstate" |
| 13 | "github.com/carlosframework/platform/internal/seeds" |
| 14 | ) |
| 15 | |
| 16 | func rootSeedsFloorDir(opt string) string { return filepath.Join(opt, "system", "guard", "seeds") } |
| 17 | func (b rootFloorBinary) forSeeds() rootFloorBinary { b.Protocol = b.SeedsProtocol; return b } |
| 18 | func readRootSeedsBinary(opt, path string) (rootFloorBinary, error) { |
| 19 | b, err := readRootFloorBinary(opt, path) |
| 20 | return b.forSeeds(), err |
| 21 | } |
| 22 | func readEffectiveSeedsFloor(source rootFloorIdentitySource, opt, dd string) (*moves.Floor, error) { |
| 23 | id, required, err := source.read() |
| 24 | if err != nil { |
| 25 | return nil, err |
| 26 | } |
| 27 | if required { |
| 28 | return readRootSeedsBound(opt, id) |
| 29 | } |
| 30 | f, err := readLocalSeedsFloor(dd) |
| 31 | if f == nil { |
| 32 | return nil, err |
| 33 | } |
| 34 | return &moves.Floor{Min: f.Min, MinRelease: f.MinRelease}, err |
| 35 | } |
| 36 | func refreshRootSeedsFloor(ctx context.Context, store release.Store, opt, dd string, id rootFloorIdentity) error { |
| 37 | f, err := openRootProtocolFloors(rootSeedsFloorDir(opt), id, true) |
| 38 | if err != nil { |
| 39 | return err |
| 40 | } |
| 41 | defer f.close() |
| 42 | raw, _, rerr := store.Get(ctx, seeds.FloorKey) |
| 43 | var bound *moves.Floor |
| 44 | if rerr == nil { |
| 45 | // Unsigned legacy publication cannot arm a root seeds floor. |
| 46 | if verified, err := verifyRootSeedsPublication(raw, id); err == nil { |
| 47 | if err := acceptRootSeedsPublication(ctx, f, raw); err != nil { |
| 48 | return err |
| 49 | } |
| 50 | bound = verified |
| 51 | } |
| 52 | } |
| 53 | published, err := readRootSeedsBound(opt, id) |
| 54 | if err != nil { |
| 55 | return err |
| 56 | } |
| 57 | if published == nil { |
| 58 | return nil |
| 59 | } |
| 60 | legacy, lerr := rootProtocolLegacy(dd, seeds.FloorFile, seeds.FloorMinReleaseFile) |
| 61 | if errors.Is(rerr, os.ErrNotExist) { |
| 62 | rerr = nil |
| 63 | } |
| 64 | unlock, err := f.private.Lock(ctx, "floor.lock") |
| 65 | if err != nil { |
| 66 | return err |
| 67 | } |
| 68 | defer unlock() |
| 69 | capped, err := f.capLegacy(legacy) |
| 70 | if err != nil { |
| 71 | return err |
| 72 | } |
| 73 | local := floorMaximum(published, bound) |
| 74 | for _, fl := range capped { |
| 75 | local = floorMaximum(local, fl) |
| 76 | } |
| 77 | generation := uint64(1) |
| 78 | if r, e := f.record(rootFloorAccepted); e == nil { |
| 79 | generation = r.Generation + 1 |
| 80 | } |
| 81 | r := rootFloorRecord{Schema: 1, Identity: id, Generation: generation, Floor: local} |
| 82 | failure := errors.Join(rerr, lerr) |
| 83 | return errors.Join(failure, f.persist(r, local, failure == nil)) |
| 84 | } |
| 85 | func judgeRootSeedsPrev(ctx context.Context, opt string, id rootFloorIdentity) error { |
| 86 | bound, err := readRootSeedsBound(opt, id) |
| 87 | if err != nil || bound == nil { |
| 88 | return err |
| 89 | } |
| 90 | f, err := openRootProtocolFloors(rootSeedsFloorDir(opt), id, true) |
| 91 | if err != nil { |
| 92 | return err |
| 93 | } |
| 94 | defer f.close() |
| 95 | return (rootFloorJudge{floors: f, current: systemBinaryPath(opt), prev: systemPrevPath(opt), binary: func(path string) (rootFloorBinary, error) { return readRootSeedsBinary(opt, path) }, idle: rootFloorDowngradeIdle}).judge(ctx) |
| 96 | } |
| 97 | func rootRefreshAndJudgeSeedsPrev(id rootFloorIdentity) error { |
| 98 | ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) |
| 99 | defer cancel() |
| 100 | opt, dd := release.OptDir(), dataDir() |
| 101 | return withPrevLock(opt, func() error { |
| 102 | store, err := openDeployStore(ctx) |
| 103 | if err != nil { |
| 104 | return err |
| 105 | } |
| 106 | if err := refreshRootSeedsFloor(ctx, store, opt, dd, id); err != nil { |
| 107 | return err |
| 108 | } |
| 109 | return judgeRootSeedsPrev(ctx, opt, id) |
| 110 | }) |
| 111 | } |
| 112 | func (a *systemApplier) seedsActivationRefusal(version string) error { |
| 113 | floor, err := readEffectiveSeedsFloor(a.rootMoves, a.optDir, a.dataDir) |
| 114 | if err != nil { |
| 115 | return refuse("seeds protocol floor: the floor on this box cannot be read: %v", err) |
| 116 | } |
| 117 | floor = a.activationFloor(floor, true) |
| 118 | if floor == nil { |
| 119 | return nil |
| 120 | } |
| 121 | if ok, _ := seeds.ReleaseAtOrAbove(version, floor.MinRelease); !ok { |
| 122 | return refuse("seeds protocol floor: %s is below %s", version, floor.MinRelease) |
| 123 | } |
| 124 | return nil |
| 125 | } |
| 126 | func rootSeedsAllowsBinary(opt string, id rootFloorIdentity, path string) error { |
| 127 | floor, err := readRootSeedsBound(opt, id) |
| 128 | if err != nil { |
| 129 | return err |
| 130 | } |
| 131 | if floor == nil { |
| 132 | return nil |
| 133 | } |
| 134 | b, err := readRootSeedsBinary(opt, path) |
| 135 | if err != nil { |
| 136 | return err |
| 137 | } |
| 138 | if !b.supports(floor) && rootFloorAvailable(opt, floor, true, path) { |
| 139 | return errRootFloorHistory |
| 140 | } |
| 141 | return nil |
| 142 | } |
| 143 | func withRootSeedsFields(f edgesFile, opt string) edgesFile { |
| 144 | f.SeedsProtocol = seeds.ProtocolVersion |
| 145 | f.SeedsFloorState = "quarantined" |
| 146 | f.SeedsFloorMinRelease, f.PrevSeedsAllowedFor = "", "" |
| 147 | p, err := readRootProtocolProjection(rootSeedsFloorDir(opt)) |
| 148 | if err != nil { |
| 149 | return f |
| 150 | } |
| 151 | if p.Identity.Deployment != os.Getenv("CARLOS_DEPLOYMENT_BUCKET") || p.Identity.Box != f.Box { |
| 152 | return f |
| 153 | } |
| 154 | public, err := rootstate.Open(rootSeedsFloorDir(opt), false, true) |
| 155 | if err != nil { |
| 156 | return f |
| 157 | } |
| 158 | defer public.Close() |
| 159 | raw, err := public.Read("publication.json") |
| 160 | if err != nil { |
| 161 | return f |
| 162 | } |
| 163 | if _, err := verifyRootSeedsPublication(raw, p.Identity); err != nil { |
| 164 | return f |
| 165 | } |
| 166 | ad, err := rootFloorAdvertisementFor(public, p) |
| 167 | if err != nil || rootstate.Sync(public) != nil { |
| 168 | return f |
| 169 | } |
| 170 | f.SeedsFloorState, f.SeedsFloorGeneration = ad.State, ad.Generation |
| 171 | f.SeedsFloorMinRelease, f.PrevSeedsAllowedFor = ad.MinRelease, ad.PrevAllowedFor |
| 172 | return f |
| 173 | } |
| 174 | |
| 175 | func (a *systemApplier) rootProtocolAllowsBinary(path string) error { |
| 176 | id, required, err := a.rootMoves.read() |
| 177 | if err != nil || !required { |
| 178 | return err |
| 179 | } |
| 180 | b, err := readRootFloorBinary(a.optDir, path) |
| 181 | if err != nil { |
| 182 | return err |
| 183 | } |
| 184 | floor, err := readRootProtocolBound(rootFloorDir(a.optDir), id) |
| 185 | if err != nil { |
| 186 | return err |
| 187 | } |
| 188 | if !b.supports(floor) && rootFloorAvailable(a.optDir, floor, false, path) { |
| 189 | return errRootFloorHistory |
| 190 | } |
| 191 | return rootSeedsAllowsBinary(a.optDir, id, path) |
| 192 | } |
| 193 | |