aboutsummaryrefslogtreecommitdiffhomepage
path: root/container/initdaemon.go
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-12-08 22:58:42 +0900
committerOphestra <cat@gensokyo.uk>2025-12-08 22:58:42 +0900
commite9fb1d7be540794ebe5027a08fbf97df189c6787 (patch)
tree6447dcdd7165fb2cb61dd1b46e47238d93bb54a7 /container/initdaemon.go
parentdafe9f8efc46bbd0fb391d1f7a93f05ee9751250 (diff)
container/initdaemon: copy wstatus from wait4 loop
Due to the special nature of the init process, direct use of wait outside the wait4 loop is racy. This change copies the wstatus from wait4 loop state instead. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'container/initdaemon.go')
-rw-r--r--container/initdaemon.go55
1 files changed, 42 insertions, 13 deletions
diff --git a/container/initdaemon.go b/container/initdaemon.go
index ee360dc9..0e631b25 100644
--- a/container/initdaemon.go
+++ b/container/initdaemon.go
@@ -8,7 +8,7 @@ import (
"os"
"os/exec"
"slices"
- "sync/atomic"
+ "strconv"
"syscall"
"time"
@@ -41,6 +41,38 @@ type DaemonOp struct {
Args []string
}
+// earlyTerminationError is returned by [DaemonOp] when a daemon terminates
+// before [DaemonOp.Target] appears.
+type earlyTerminationError struct {
+ // Returned by [DaemonOp.String].
+ op string
+ // Copied from wait4 loop.
+ wstatus syscall.WaitStatus
+}
+
+func (e *earlyTerminationError) Error() string {
+ res := ""
+ switch {
+ case e.wstatus.Exited():
+ res = "exit status " + strconv.Itoa(e.wstatus.ExitStatus())
+ case e.wstatus.Signaled():
+ res = "signal: " + e.wstatus.Signal().String()
+ case e.wstatus.Stopped():
+ res = "stop signal: " + e.wstatus.StopSignal().String()
+ if e.wstatus.StopSignal() == syscall.SIGTRAP && e.wstatus.TrapCause() != 0 {
+ res += " (trap " + strconv.Itoa(e.wstatus.TrapCause()) + ")"
+ }
+ case e.wstatus.Continued():
+ res = "continued"
+ }
+ if e.wstatus.CoreDump() {
+ res += " (core dumped)"
+ }
+ return res
+}
+
+func (e *earlyTerminationError) Message() string { return e.op + " " + e.Error() }
+
func (d *DaemonOp) Valid() bool { return d != nil && d.Target != nil && d.Path != nil }
func (d *DaemonOp) early(*setupState, syscallDispatcher) error { return nil }
func (d *DaemonOp) apply(*setupState, syscallDispatcher) error { return nil }
@@ -59,17 +91,9 @@ func (d *DaemonOp) late(state *setupState, k syscallDispatcher) error {
return err
}
- var done atomic.Pointer[error]
- k.new(func(k syscallDispatcher) {
- err := k.wait(cmd)
- done.Store(&err)
-
- if err != nil {
- state.Verbosef("%s %v", d.String(), err)
- }
- })
-
deadline := time.Now().Add(daemonTimeout)
+ var wstatusErr error
+
for {
if _, err := k.stat(d.Target.String()); err != nil {
if !errors.Is(err, os.ErrNotExist) {
@@ -82,8 +106,13 @@ func (d *DaemonOp) late(state *setupState, k syscallDispatcher) error {
return context.DeadlineExceeded
}
- if errP := done.Load(); errP != nil {
- return *errP
+ if wstatusErr != nil {
+ return wstatusErr
+ }
+ if wstatus, ok := state.terminated(cmd.Process.Pid); ok {
+ // check once again: process could have satisfied Target between stat and the lookup
+ wstatusErr = &earlyTerminationError{d.String(), wstatus}
+ continue
}
time.Sleep(500 * time.Microsecond)