package host import ( "bytes" "context" "os" "path" "strings" "github.com/carlosframework/platform/internal/instancectl" "github.com/carlosframework/platform/internal/rootstate" ) type claimUnitPredecessor struct { Unit, Invocation, Group string Boot string `json:",omitempty"` GroupIdentity rootstate.FileIdentity } type claimUnitMigration struct { Definition rootstate.FileIdentity Digest string Predecessor claimUnitPredecessor Socket claimUnitSocketIdentity `json:",omitempty"` } type claimUnitSocketIdentity struct { Boot string Device, Inode uint64 UID, GID, Mode uint32 } func (p claimUnitPredecessor) valid(unit string) bool { return p.Unit == unit && instancectl.ValidBoundClaimUnit(unit) && p.Invocation != "" && claimPredecessorPlacement(unit, p.Group) && p.GroupIdentity != (rootstate.FileIdentity{}) } func (r claimUnitMigration) valid(b claimUnitBinding) bool { return r.Definition != (rootstate.FileIdentity{}) && claimHexDigest(r.Digest) && r.Digest == b.Shape.PredecessorDigest && r.Predecessor.valid(b.Shape.Unit) } // The materialization lock and root host lock cover capture through replacement. // Only the root configuration's exact digest admits an operator definition. func (m claimUnitMaterializer) migration(ctx context.Context, b claimUnitBinding, raw []byte, id rootstate.FileIdentity) (*claimUnitMigration, error) { if !claimHexDigest(b.Shape.PredecessorDigest) || claimUnitDigest(raw) != b.Shape.PredecessorDigest || id == (rootstate.FileIdentity{}) || m.predecessor == nil || m.stopPredecessor == nil || m.replace == nil { return nil, os.ErrPermission } p, err := m.predecessor(ctx, b) if err != nil { return nil, err } r := &claimUnitMigration{Definition: id, Digest: claimUnitDigest(raw), Predecessor: p} if claimFlatSocket(b.Shape) { if m.socketCapture == nil || m.socketPark == nil { return nil, os.ErrPermission } r.Socket, err = m.socketCapture(ctx, b.Shape, p) if err != nil { return nil, err } } if !r.valid(b) { return nil, os.ErrPermission } return r, nil } func (m claimUnitMaterializer) finishMigration(ctx context.Context, r claimUnitMaterialization, want []byte) error { if r.Migration == nil || !r.Migration.valid(r.Binding) || m.stopPredecessor == nil || m.replace == nil { return os.ErrPermission } raw, id, err := m.read(r.Binding.Shape.Unit) if err != nil { return err } if id.Same(r.Migration.Definition) && claimUnitDigest(raw) == r.Migration.Digest { if err = m.stopPredecessor(ctx, r.Migration.Predecessor); err != nil { return err } if err = m.idle(r.Binding.Shape.Host); err != nil { return err } if err = m.finishSocketMigration(r); err != nil { return err } if err = m.replace(r.Binding.Shape.Unit, id, want); err != nil { return err } raw, id, err = m.read(r.Binding.Shape.Unit) if err != nil { return err } } else if !bytes.Equal(raw, want) { return os.ErrPermission } if !bytes.Equal(raw, want) || id == (rootstate.FileIdentity{}) { return os.ErrPermission } // A crash after rename still has to prove the predecessor gone. The durable // definition refuses execution until a later root reservation opens its gate. if err = m.stopPredecessor(ctx, r.Migration.Predecessor); err != nil { return err } if err = m.idle(r.Binding.Shape.Host); err != nil { return err } if err = m.finishSocketMigration(r); err != nil { return err } r.Identity = id return m.save(r) } func (m claimUnitMaterializer) finishSocketMigration(r claimUnitMaterialization) error { if !claimFlatSocket(r.Binding.Shape) { return nil } if r.Migration == nil || r.Migration.Socket.Boot == "" || m.socketPark == nil { return os.ErrPermission } return m.socketPark(r.Binding, r.Migration.Socket) } func claimPredecessorPlacement(unit, group string) bool { if !instancectl.ValidBoundClaimUnit(unit) { return false } if group == "system.slice/"+unit { return true } prefix, _, template := strings.Cut(unit, "@") return template && prefix != "" && group == path.Join("system.slice", "system-"+instancectl.Escape(prefix)+".slice", unit) }