internal/app: remove seal interface
All checks were successful
Test / Create distribution (push) Successful in 34s
Test / Sandbox (push) Successful in 2m1s
Test / Hakurei (push) Successful in 3m13s
Test / Hpkg (push) Successful in 3m55s
Test / Sandbox (race detector) (push) Successful in 4m33s
Test / Hakurei (race detector) (push) Successful in 5m19s
Test / Flake checks (push) Successful in 1m36s

This further cleans up the package for the restructure.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
Ophestra 2025-08-28 01:07:51 +09:00
parent d0b6852cd7
commit b14690aa77
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
8 changed files with 108 additions and 116 deletions

View File

@ -2,35 +2,81 @@
package app
import (
"syscall"
"time"
"context"
"fmt"
"log"
"sync"
"hakurei.app/hst"
"hakurei.app/internal/app/state"
"hakurei.app/internal/sys"
)
type SealedApp interface {
// Run commits sealed system setup and starts the app process.
Run(rs *RunState) error
func New(ctx context.Context, os sys.State) (*App, error) {
a := new(App)
a.sys = os
a.ctx = ctx
id := new(state.ID)
err := state.NewAppID(id)
a.id = newID(id)
return a, err
}
// RunState stores the outcome of a call to [SealedApp.Run].
type RunState struct {
// Time is the exact point in time where the process was created.
// Location must be set to UTC.
//
// Time is nil if no process was ever created.
Time *time.Time
// RevertErr is stored by the deferred revert call.
RevertErr error
// WaitErr is the generic error value created by the standard library.
WaitErr error
syscall.WaitStatus
func MustNew(ctx context.Context, os sys.State) *App {
a, err := New(ctx, os)
if err != nil {
log.Fatalf("cannot create app: %v", err)
}
return a
}
// SetStart stores the current time in [RunState] once.
func (rs *RunState) SetStart() {
if rs.Time != nil {
panic("attempted to store time twice")
type App struct {
outcome *Outcome
id *stringPair[state.ID]
sys sys.State
ctx context.Context
mu sync.RWMutex
}
now := time.Now().UTC()
rs.Time = &now
// ID returns a copy of [state.ID] held by App.
func (a *App) ID() state.ID { a.mu.RLock(); defer a.mu.RUnlock(); return a.id.unwrap() }
func (a *App) String() string {
if a == nil {
return "(invalid app)"
}
a.mu.RLock()
defer a.mu.RUnlock()
if a.outcome != nil {
if a.outcome.user.uid == nil {
return fmt.Sprintf("(sealed app %s with invalid uid)", a.id)
}
return fmt.Sprintf("(sealed app %s as uid %s)", a.id, a.outcome.user.uid)
}
return fmt.Sprintf("(unsealed app %s)", a.id)
}
// Seal determines the outcome of [hst.Config] as a [SealedApp].
// Values stored in and referred to by [hst.Config] might be overwritten and must not be used again.
func (a *App) Seal(config *hst.Config) (*Outcome, error) {
a.mu.Lock()
defer a.mu.Unlock()
if a.outcome != nil {
panic("app sealed twice")
}
seal := new(Outcome)
seal.id = a.id
err := seal.finalise(a.ctx, a.sys, config)
if err == nil {
a.outcome = seal
}
return seal, err
}

View File

@ -1,81 +0,0 @@
package app
import (
"context"
"fmt"
"log"
"sync"
"hakurei.app/hst"
"hakurei.app/internal/app/state"
"hakurei.app/internal/sys"
)
func New(ctx context.Context, os sys.State) (*App, error) {
a := new(App)
a.sys = os
a.ctx = ctx
id := new(state.ID)
err := state.NewAppID(id)
a.id = newID(id)
return a, err
}
func MustNew(ctx context.Context, os sys.State) *App {
a, err := New(ctx, os)
if err != nil {
log.Fatalf("cannot create app: %v", err)
}
return a
}
type App struct {
id *stringPair[state.ID]
sys sys.State
ctx context.Context
*outcome
mu sync.RWMutex
}
// ID returns a copy of [state.ID] held by App.
func (a *App) ID() state.ID { a.mu.RLock(); defer a.mu.RUnlock(); return a.id.unwrap() }
func (a *App) String() string {
if a == nil {
return "(invalid app)"
}
a.mu.RLock()
defer a.mu.RUnlock()
if a.outcome != nil {
if a.outcome.user.uid == nil {
return fmt.Sprintf("(sealed app %s with invalid uid)", a.id)
}
return fmt.Sprintf("(sealed app %s as uid %s)", a.id, a.outcome.user.uid)
}
return fmt.Sprintf("(unsealed app %s)", a.id)
}
// Seal determines the outcome of [hst.Config] as a [SealedApp].
// Values stored in and referred to by [hst.Config] might be overwritten and must not be used again.
func (a *App) Seal(config *hst.Config) (SealedApp, error) {
a.mu.Lock()
defer a.mu.Unlock()
if a.outcome != nil {
panic("app sealed twice")
}
seal := new(outcome)
seal.id = a.id
err := seal.finalise(a.ctx, a.sys, config)
if err == nil {
a.outcome = seal
}
return seal, err
}

View File

@ -14,8 +14,7 @@ func NewWithID(id state.ID, os sys.State) *App {
return a
}
func AppIParams(a *App, sa SealedApp) (*system.I, *container.Params) {
seal := sa.(*outcome)
func AppIParams(a *App, seal *Outcome) (*system.I, *container.Params) {
if a.outcome != seal || a.id != seal.id {
panic("broken app/outcome link")
}

View File

@ -21,9 +21,34 @@ import (
const shimWaitTimeout = 5 * time.Second
func (seal *outcome) Run(rs *RunState) error {
// RunState stores the outcome of a call to [Outcome.Run].
type RunState struct {
// Time is the exact point in time where the process was created.
// Location must be set to UTC.
//
// Time is nil if no process was ever created.
Time *time.Time
// RevertErr is stored by the deferred revert call.
RevertErr error
// WaitErr is the generic error value created by the standard library.
WaitErr error
syscall.WaitStatus
}
// setStart stores the current time in [RunState] once.
func (rs *RunState) setStart() {
if rs.Time != nil {
panic("attempted to store time twice")
}
now := time.Now().UTC()
rs.Time = &now
}
// Run commits deferred system setup and starts the container.
func (seal *Outcome) Run(rs *RunState) error {
if !seal.f.CompareAndSwap(false, true) {
// run does much more than just starting a process; calling it twice, even if the first call fails, will result
// Run does much more than just starting a process; calling it twice, even if the first call fails, will result
// in inconsistent state that is impossible to clean up; return here to limit damage and hopefully give the
// other Run a chance to return
return errors.New("outcome: attempted to run twice")
@ -118,7 +143,7 @@ func (seal *outcome) Run(rs *RunState) error {
return hlog.WrapErrSuffix(err,
"cannot start setuid wrapper:")
}
rs.SetStart()
rs.setStart()
// this prevents blocking forever on an early failure
waitErr, setupErr := make(chan error, 1), make(chan error, 1)
@ -173,10 +198,13 @@ func (seal *outcome) Run(rs *RunState) error {
switch {
case rs.Exited():
hlog.Verbosef("process %d exited with code %d", cmd.Process.Pid, rs.ExitStatus())
case rs.CoreDump():
hlog.Verbosef("process %d dumped core", cmd.Process.Pid)
case rs.Signaled():
hlog.Verbosef("process %d got %s", cmd.Process.Pid, rs.Signal())
default:
hlog.Verbosef("process %d exited with status %#x", cmd.Process.Pid, rs.WaitStatus)
}

View File

@ -58,8 +58,8 @@ var (
ErrPulseMode = errors.New("unexpected pulse socket mode")
)
// outcome stores copies of various parts of [hst.Config]
type outcome struct {
// An Outcome is the runnable state of a hakurei container via [hst.Config].
type Outcome struct {
// copied from initialising [app]
id *stringPair[state.ID]
// copied from [sys.State]
@ -92,7 +92,7 @@ type shareHost struct {
// process-specific directory in XDG_RUNTIME_DIR, empty if unused
runtimeSharePath *container.Absolute
seal *outcome
seal *Outcome
sc hst.Paths
}
@ -145,7 +145,7 @@ type hsuUser struct {
username string
}
func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Config) error {
func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Config) error {
if seal.ctx != nil {
panic("finalise called twice")
}