diff --git a/internal/app/app.go b/internal/app/app.go index d59b145..17b4103 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -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 -} - -// SetStart stores the current time in [RunState] once. -func (rs *RunState) SetStart() { - if rs.Time != nil { - panic("attempted to store time twice") +func MustNew(ctx context.Context, os sys.State) *App { + a, err := New(ctx, os) + if err != nil { + log.Fatalf("cannot create app: %v", err) } - now := time.Now().UTC() - rs.Time = &now + return a +} + +type App struct { + outcome *Outcome + + id *stringPair[state.ID] + sys sys.State + ctx context.Context + 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) (*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 } diff --git a/internal/app/app_linux.go b/internal/app/app_linux.go deleted file mode 100644 index eebfacd..0000000 --- a/internal/app/app_linux.go +++ /dev/null @@ -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 -} diff --git a/internal/app/app_linux_test.go b/internal/app/app_test.go similarity index 100% rename from internal/app/app_linux_test.go rename to internal/app/app_test.go diff --git a/internal/app/container_linux.go b/internal/app/container.go similarity index 100% rename from internal/app/container_linux.go rename to internal/app/container.go diff --git a/internal/app/export_linux_test.go b/internal/app/export_test.go similarity index 79% rename from internal/app/export_linux_test.go rename to internal/app/export_test.go index f7af527..b7ee7f7 100644 --- a/internal/app/export_linux_test.go +++ b/internal/app/export_test.go @@ -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") } diff --git a/internal/app/process_linux.go b/internal/app/process.go similarity index 87% rename from internal/app/process_linux.go rename to internal/app/process.go index c7da474..4435569 100644 --- a/internal/app/process_linux.go +++ b/internal/app/process.go @@ -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) } diff --git a/internal/app/seal_linux.go b/internal/app/seal.go similarity index 99% rename from internal/app/seal_linux.go rename to internal/app/seal.go index be18a2a..d6a23f2 100644 --- a/internal/app/seal_linux.go +++ b/internal/app/seal.go @@ -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") } diff --git a/internal/app/shim_linux.go b/internal/app/shim.go similarity index 100% rename from internal/app/shim_linux.go rename to internal/app/shim.go