package app import ( "bytes" "context" "encoding/gob" "errors" "fmt" "io" "os" "os/user" "sync/atomic" "hakurei.app/container" "hakurei.app/hst" "hakurei.app/internal/app/state" "hakurei.app/system" ) func newWithMessage(msg string) error { return newWithMessageError(msg, os.ErrInvalid) } func newWithMessageError(msg string, err error) error { return &hst.AppError{Step: "finalise", Err: err, Msg: msg} } // An outcome is the runnable state of a hakurei container via [hst.Config]. type outcome struct { // initial [hst.Config] gob stream for state data; // this is prepared ahead of time as config is clobbered during seal creation ct io.WriterTo // Supplementary group ids. Populated during finalise. supp []string // Resolved priv side operating system interactions. Populated during finalise. sys *system.I // Transmitted to shim. Populated during finalise. state *outcomeState // Whether the current process is in outcome.main. active atomic.Bool ctx context.Context syscallDispatcher } func (k *outcome) finalise(ctx context.Context, msg container.Msg, id *state.ID, config *hst.Config) error { if ctx == nil || id == nil { // unreachable panic("invalid call to finalise") } if k.ctx != nil || k.sys != nil || k.state != nil { // unreachable panic("attempting to finalise twice") } k.ctx = ctx if err := config.Validate(); err != nil { return err } // TODO(ophestra): do not clobber during finalise { // encode initial configuration for state tracking ct := new(bytes.Buffer) if err := gob.NewEncoder(ct).Encode(config); err != nil { return &hst.AppError{Step: "encode initial config", Err: err} } k.ct = ct } // hsu expects numerical group ids supp := make([]string, len(config.Groups)) for i, name := range config.Groups { if gid, err := k.lookupGroupId(name); err != nil { var unknownGroupError user.UnknownGroupError if errors.As(err, &unknownGroupError) { return newWithMessageError(fmt.Sprintf("unknown group %q", name), unknownGroupError) } else { return &hst.AppError{Step: "look up group by name", Err: err} } } else { supp[i] = gid } } // early validation complete at this point s := outcomeState{ ID: id, Identity: config.Identity, UserID: (&Hsu{k: k}).MustIDMsg(msg), EnvPaths: copyPaths(k.syscallDispatcher), Container: config.Container, } s.populateEarly(k.syscallDispatcher, msg, config) if err := s.populateLocal(k.syscallDispatcher, msg); err != nil { return err } sys := system.New(k.ctx, msg, s.uid.unwrap()) stateSys := outcomeStateSys{sys: sys, outcomeState: &s} for _, op := range s.Shim.Ops { if err := op.toSystem(&stateSys, config); err != nil { return err } } k.sys = sys k.supp = supp k.state = &s return nil }