diff options
| author | Ophestra <cat@gensokyo.uk> | 2025-10-29 04:32:43 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2025-10-29 04:33:13 +0900 |
| commit | a0b4e47acc228e48165966d8e139277b8f6f450e (patch) | |
| tree | d363a6781536ce946aa445271ca1d3df587e6031 /internal/app/process.go | |
| parent | a52f7038e5a607dd0901244abb791c96deaf9c2d (diff) | |
internal/outcome: rename from app
This is less ambiguous, and more accurately describes the purpose of the package.
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/app/process.go')
| -rw-r--r-- | internal/app/process.go | 321 |
1 files changed, 0 insertions, 321 deletions
diff --git a/internal/app/process.go b/internal/app/process.go deleted file mode 100644 index 7873109a..00000000 --- a/internal/app/process.go +++ /dev/null @@ -1,321 +0,0 @@ -package app - -import ( - "context" - "encoding/gob" - "errors" - "log" - "os" - "os/exec" - "strconv" - "strings" - "syscall" - "time" - - "hakurei.app/container" - "hakurei.app/container/fhs" - "hakurei.app/hst" - "hakurei.app/internal" - "hakurei.app/internal/app/state" - "hakurei.app/message" - "hakurei.app/system" -) - -// Duration to wait for shim to exit on top of container WaitDelay. -const shimWaitTimeout = 5 * time.Second - -// mainState holds persistent state bound to outcome.main. -type mainState struct { - // done is whether beforeExit has been called already. - done bool - - // 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 - - store state.Store - cancel context.CancelFunc - cmd *exec.Cmd - cmdWait chan error - - k *outcome - message.Msg - uintptr -} - -const ( - // mainNeedsRevert indicates the call to Commit has succeeded. - mainNeedsRevert uintptr = 1 << iota - // mainNeedsDestroy indicates the instance state entry is present in the store. - mainNeedsDestroy -) - -// beforeExit must be called immediately before a call to [os.Exit]. -func (ms mainState) beforeExit(isFault bool) { - if ms.done { - panic("attempting to call beforeExit twice") - } - ms.done = true - defer ms.BeforeExit() - - if isFault && ms.cancel != nil { - ms.cancel() - } - - var hasErr bool - // updates hasErr but does not terminate - perror := func(err error, message string) { - hasErr = true - printMessageError("cannot "+message+":", err) - } - exitCode := 1 - defer func() { - if hasErr { - os.Exit(exitCode) - } - }() - - // this also handles wait for a non-fault termination - if ms.cmd != nil && ms.cmdWait != nil { - waitDone := make(chan struct{}) - - // this ties waitDone to ctx with the additional compensated timeout duration - go func() { <-ms.k.ctx.Done(); time.Sleep(ms.k.state.Shim.WaitDelay + shimWaitTimeout); close(waitDone) }() - - select { - case err := <-ms.cmdWait: - wstatus, ok := ms.cmd.ProcessState.Sys().(syscall.WaitStatus) - if ok { - if v := wstatus.ExitStatus(); v != 0 { - hasErr = true - exitCode = v - } - } - - if ms.IsVerbose() { - if !ok { - if err != nil { - ms.Verbosef("wait: %v", err) - } - } else { - switch { - case wstatus.Exited(): - ms.Verbosef("process %d exited with code %d", ms.cmd.Process.Pid, wstatus.ExitStatus()) - - case wstatus.CoreDump(): - ms.Verbosef("process %d dumped core", ms.cmd.Process.Pid) - - case wstatus.Signaled(): - ms.Verbosef("process %d got %s", ms.cmd.Process.Pid, wstatus.Signal()) - - default: - ms.Verbosef("process %d exited with status %#x", ms.cmd.Process.Pid, wstatus) - } - } - } - - case <-waitDone: - ms.Resume() - // this is only reachable when shim did not exit within shimWaitTimeout, after its WaitDelay has elapsed. - // This is different from the container failing to terminate within its timeout period, as that is enforced - // by the shim. This path is instead reached when there is a lockup in shim preventing it from completing. - log.Printf("process %d did not terminate", ms.cmd.Process.Pid) - } - - ms.Resume() - } - - if ms.uintptr&mainNeedsRevert != 0 { - if ok, err := ms.store.Do(ms.k.state.identity.unwrap(), func(c state.Cursor) { - if ms.uintptr&mainNeedsDestroy != 0 { - if err := c.Destroy(ms.k.state.id.unwrap()); err != nil { - perror(err, "destroy state entry") - } - } - - var rt hst.Enablement - if states, err := c.Load(); err != nil { - // it is impossible to continue from this point; - // revert per-process state here to limit damage - ec := system.Process - if revertErr := ms.k.sys.Revert((*system.Criteria)(&ec)); revertErr != nil { - var joinError interface { - Unwrap() []error - error - } - if !errors.As(revertErr, &joinError) || joinError == nil { - perror(revertErr, "revert system setup") - } else { - for _, v := range joinError.Unwrap() { - perror(v, "revert system setup step") - } - } - } - perror(err, "load instance states") - } else { - ec := system.Process - if l := len(states); l == 0 { - ec |= system.User - } else { - ms.Verbosef("found %d instances, cleaning up without user-scoped operations", l) - } - - // accumulate enablements of remaining launchers - for i, s := range states { - if s.Config != nil { - rt |= s.Config.Enablements.Unwrap() - } else { - log.Printf("state entry %d does not contain config", i) - } - } - - ec |= rt ^ (hst.EWayland | hst.EX11 | hst.EDBus | hst.EPulse) - if ms.IsVerbose() { - if ec > 0 { - ms.Verbose("reverting operations scope", system.TypeString(ec)) - } - } - - if err = ms.k.sys.Revert((*system.Criteria)(&ec)); err != nil { - perror(err, "revert system setup") - } - } - }); err != nil { - if ok { - perror(err, "unlock state store") - } else { - perror(err, "open state store") - } - } - } else if ms.uintptr&mainNeedsDestroy != 0 { - panic("unreachable") - } -} - -// fatal calls printMessageError, performs necessary cleanup, followed by a call to [os.Exit](1). -func (ms mainState) fatal(fallback string, ferr error) { - printMessageError(fallback, ferr) - ms.beforeExit(true) - os.Exit(1) -} - -// main carries out outcome and terminates. main does not return. -func (k *outcome) main(msg message.Msg) { - if !k.active.CompareAndSwap(false, true) { - panic("outcome: attempted to run twice") - } - - if k.ctx == nil || k.sys == nil || k.state == nil { - panic("outcome: did not finalise") - } - - // read comp value early for early failure - hsuPath := internal.MustHsuPath() - - // ms.beforeExit required beyond this point - ms := &mainState{Msg: msg, k: k} - - if err := k.sys.Commit(); err != nil { - ms.fatal("cannot commit system setup:", err) - } - ms.uintptr |= mainNeedsRevert - ms.store = state.NewMulti(msg, k.state.sc.RunDirPath) - - ctx, cancel := context.WithCancel(k.ctx) - defer cancel() - ms.cancel = cancel - - ms.cmd = exec.CommandContext(ctx, hsuPath.String()) - ms.cmd.Stdin, ms.cmd.Stdout, ms.cmd.Stderr = os.Stdin, os.Stdout, os.Stderr - ms.cmd.Dir = fhs.Root // container init enters final working directory - // shim runs in the same session as monitor; see shim.go for behaviour - ms.cmd.Cancel = func() error { return ms.cmd.Process.Signal(syscall.SIGCONT) } - - var e *gob.Encoder - if fd, encoder, err := container.Setup(&ms.cmd.ExtraFiles); err != nil { - ms.fatal("cannot create shim setup pipe:", err) - } else { - e = encoder - ms.cmd.Env = []string{ - // passed through to shim by hsu - shimEnv + "=" + strconv.Itoa(fd), - // interpreted by hsu - "HAKUREI_IDENTITY=" + k.state.identity.String(), - } - } - - if len(k.supp) > 0 { - msg.Verbosef("attaching supplementary group ids %s", k.supp) - // interpreted by hsu - ms.cmd.Env = append(ms.cmd.Env, "HAKUREI_GROUPS="+strings.Join(k.supp, " ")) - } - - msg.Verbosef("setuid helper at %s", hsuPath) - msg.Suspend() - if err := ms.cmd.Start(); err != nil { - ms.fatal("cannot start setuid wrapper:", err) - } - startTime := time.Now().UTC() - ms.cmdWait = make(chan error, 1) - // this ties context back to the life of the process - go func() { ms.cmdWait <- ms.cmd.Wait(); cancel() }() - ms.Time = &startTime - - // unfortunately the I/O here cannot be directly canceled; - // the cancellation path leads to fatal in this case so that is fine - select { - case err := <-func() (setupErr chan error) { - setupErr = make(chan error, 1) - go func() { setupErr <- e.Encode(k.state) }() - return - }(): - if err != nil { - msg.Resume() - ms.fatal("cannot transmit shim config:", err) - } - - case <-ctx.Done(): - msg.Resume() - ms.fatal("shim context canceled:", newWithMessageError("shim setup canceled", ctx.Err())) - } - - // shim accepted setup payload, create process state - if ok, err := ms.store.Do(k.state.identity.unwrap(), func(c state.Cursor) { - if err := c.Save(&hst.State{ - ID: k.state.id.unwrap(), - PID: os.Getpid(), - ShimPID: ms.cmd.Process.Pid, - Config: k.config, - Time: *ms.Time, - }); err != nil { - ms.fatal("cannot save state entry:", err) - } - }); err != nil { - if ok { - ms.uintptr |= mainNeedsDestroy - ms.fatal("cannot unlock state store:", err) - } else { - ms.fatal("cannot open state store:", err) - } - } - // state in store at this point, destroy defunct state entry on termination - ms.uintptr |= mainNeedsDestroy - - // beforeExit ties shim process to context - ms.beforeExit(false) - os.Exit(0) -} - -// printMessageError prints the error message according to [message.GetMessage], -// or fallback prepended to err if an error message is not available. -func printMessageError(fallback string, err error) { - m, ok := message.GetMessage(err) - if !ok { - log.Println(fallback, err) - return - } - - log.Print(m) -} |
