package host import ( "context" "errors" "io" "os" "path/filepath" "strings" "github.com/carlosframework/platform/internal/moves" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/rootparticipation" "github.com/carlosframework/platform/internal/rootstate" ) func rootMovesFloorIdentity() (rootFloorIdentity, bool, error) { l, required, err := rootparticipation.OpenForUpdate() if l.State != nil { defer l.State.Close() } if err != nil || !required { return rootFloorIdentity{}, required, err } c, err := l.RecoveryCensus() id := rootFloorIdentity{Deployment: os.Getenv("CARLOS_DEPLOYMENT_BUCKET"), Box: c.Box, Enrollment: c.Enrollment} if err == nil && id.Deployment == "" { err = errRootFloorHistory } return id, true, err } type rootFloorIdentitySource func() (rootFloorIdentity, bool, error) func (source rootFloorIdentitySource) read() (rootFloorIdentity, bool, error) { if source == nil { return rootFloorIdentity{}, false, nil } return source() } // Root callers retain main's floor implementation until a census exists. func readEffectiveMovesFloor(source rootFloorIdentitySource, opt, dd string) (*moves.Floor, error) { id, required, err := source.read() if err != nil { return nil, err } if !required { return readLocalFloor(dd) } return readRootProtocolBound(rootFloorDir(opt), id) } func readRootProtocolBound(dir string, id rootFloorIdentity) (*moves.Floor, error) { f, err := openRootProtocolFloors(dir, id, false) if errors.Is(err, os.ErrNotExist) { public, e := rootstate.Open(dir, false, true) if errors.Is(e, os.ErrNotExist) { return nil, nil } if e != nil { return nil, e } defer public.Close() return (rootMovesFloors{public: public, identity: id}).localBound() } if err != nil { return nil, err } defer f.close() return f.localBound() } func withEffectiveMovesFloorLock(source rootFloorIdentitySource, opt, dd string, create bool, fn func() error) error { id, required, err := source.read() if err != nil { return err } if !required { return withFloorLock(dd, create, func(*movesDir) error { return fn() }) } f, err := openRootMovesFloors(opt, id, true) if err != nil { return err } defer f.close() unlock, err := f.private.Lock(context.Background(), "floor.lock") if err != nil { return err } defer unlock() seeds, err := openRootProtocolFloors(rootSeedsFloorDir(opt), id, true) if err != nil { return err } defer seeds.close() unlockSeeds, err := seeds.private.Lock(context.Background(), "floor.lock") if err != nil { return err } defer unlockSeeds() return fn() } // Read both mirrors independently. Missing files add nothing; a malformed // bounded file quarantines, while any usable sibling still contributes a deny. func rootFloorLegacy(dd string) ([]*moves.Floor, error) { return rootProtocolLegacy(dd, moves.FloorFile, moves.FloorMinReleaseFile) } func rootProtocolLegacy(dd, jsonFile, lineFile string) ([]*moves.Floor, error) { var legacy []*moves.Floor fl, jerr := readRootLegacyProtocolJSON(dd, jsonFile) if fl != nil { if validRootFloor(fl) { legacy = append(legacy, fl) } else { jerr = errors.Join(jerr, errRootFloorHistory) } } file, err := openBeneath(dd, strings.Split(filepath.ToSlash(lineFile), "/")...) if errors.Is(err, os.ErrNotExist) { return legacy, jerr } if err != nil { return legacy, errors.Join(jerr, err) } defer file.Close() st, err := file.Stat() if err != nil { return legacy, errors.Join(jerr, err) } if !st.Mode().IsRegular() || st.Size() > 256 { return legacy, errors.Join(jerr, errRootFloorHistory) } raw, err := io.ReadAll(io.LimitReader(file, 257)) line := strings.TrimSpace(string(raw)) if err != nil || !validFloorRelease(line) { return legacy, errors.Join(jerr, err, errRootFloorHistory) } legacy = append(legacy, &moves.Floor{Min: 1, MinRelease: line}) return legacy, jerr } func refreshRootMovesFloor(ctx context.Context, store release.Store, opt, dd string, id rootFloorIdentity) error { f, err := openRootMovesFloors(opt, id, true) if err != nil { return err } defer f.close() remote, rerr := moves.ReadFloor(ctx, store) legacy, lerr := rootFloorLegacy(dd) return f.refresh(ctx, remote, legacy, errors.Join(rerr, lerr)) } // Reading the final projection can race the writer's last directory sync. // Sync after reading makes those already-fsynced file bytes durable before the // honest agent advertises them, even if root died just after the rename. func readRootFloorProjection(opt string) (rootFloorProjection, error) { return readRootProtocolProjection(rootFloorDir(opt)) } func readRootProtocolProjection(dir string) (rootFloorProjection, error) { s, err := rootstate.Open(dir, false, true) if err != nil { return rootFloorProjection{}, err } defer s.Close() var p rootFloorProjection raw, err := s.Read(rootFloorProjectionFile) if err != nil { return p, err } if err = rootstate.Sync(s); err != nil { return p, err } if len(raw) > movesFloorMaxBytes || rootparticipation.Decode(raw, &p) != nil || !p.Accepted || !p.rootFloorRecord.valid(p.Identity) { return p, errRootFloorHistory } f := rootMovesFloors{public: s, identity: p.Identity} bound, err := f.selector() if err != nil || string(rootFloorLine(bound)) != string(rootFloorLine(p.Floor)) { return p, errRootFloorHistory } return p, nil } func readRootLegacyProtocolJSON(dd, name string) (*moves.Floor, error) { file, err := openBeneath(dd, strings.Split(filepath.ToSlash(name), "/")...) if errors.Is(err, os.ErrNotExist) { return nil, nil } if err != nil { return nil, err } defer file.Close() st, err := file.Stat() if err != nil { return nil, err } if !st.Mode().IsRegular() || st.Size() > movesFloorMaxBytes { return nil, errRootFloorHistory } raw, err := io.ReadAll(io.LimitReader(file, movesFloorMaxBytes+1)) if err != nil { return nil, err } var floor moves.Floor err = rootparticipation.Decode(raw, &floor) if !validRootFloor(&floor) { return nil, errors.Join(err, errRootFloorHistory) } return &floor, err }