Refactor log and reaper exec to omit MAINPID

Using MAINPID breaks systemd's exit detection, as it stops watching the
original pid, but is unable to watch the new pid as it is not a child
of systemd itself. The best we can do is just notify when execing the child
process.

We also need to consolidate forking into a sigle place so that we don't
end up with multiple levels of child processes if both redirecting log
output and reaping child processes.

Signed-off-by: Brad Davidson <brad.davidson@rancher.com>
(cherry picked from commit dc18ef2e51)
This commit is contained in:
Brad Davidson
2021-10-08 12:47:20 -07:00
committed by Brad Davidson
parent 1a8bd3156f
commit bfc22ca613
7 changed files with 126 additions and 105 deletions

View File

@@ -4,83 +4,21 @@ package cmds
import (
"os"
"os/signal"
"syscall"
"github.com/containerd/containerd/sys"
"github.com/erikdubbelboer/gspt"
"github.com/pkg/errors"
"github.com/rancher/k3s/pkg/version"
"github.com/rootless-containers/rootlesskit/pkg/parent/cgrouputil"
)
// HandleInit takes care of things that need to be done when running as process 1, usually in a
// Docker container. This includes evacuating the root cgroup and reaping child pids.
func HandleInit() error {
if os.Getpid() != 1 {
return nil
}
if !sys.RunningInUserNS() {
// EvacuateCgroup2 will handle evacuating the root cgroup in order to enable subtree_control,
// if running as pid 1 without rootless support.
func EvacuateCgroup2() error {
if os.Getpid() == 1 && !sys.RunningInUserNS() {
// The root cgroup has to be empty to enable subtree_control, so evacuate it by placing
// ourselves in the init cgroup.
if err := cgrouputil.EvacuateCgroup2("init"); err != nil {
return errors.Wrap(err, "failed to evacuate root cgroup")
}
}
pwd, err := os.Getwd()
if err != nil {
return errors.Wrap(err, "failed to get working directory for init process")
}
go reapChildren()
// fork the main process to do work so that this init process can handle reaping pids
// without interfering with any other exec's that the rest of the codebase may do.
var wstatus syscall.WaitStatus
pattrs := &syscall.ProcAttr{
Dir: pwd,
Env: os.Environ(),
Sys: &syscall.SysProcAttr{Setsid: true},
Files: []uintptr{
uintptr(syscall.Stdin),
uintptr(syscall.Stdout),
uintptr(syscall.Stderr),
},
}
pid, err := syscall.ForkExec(os.Args[0], os.Args, pattrs)
if err != nil {
return errors.Wrap(err, "failed to fork/exec "+version.Program)
}
gspt.SetProcTitle(os.Args[0] + " init")
// wait for main process to exit, and return its status when it does
_, err = syscall.Wait4(pid, &wstatus, 0, nil)
for err == syscall.EINTR {
_, err = syscall.Wait4(pid, &wstatus, 0, nil)
}
os.Exit(wstatus.ExitStatus())
return nil
}
//reapChildren calls Wait4 whenever SIGCHLD is received
func reapChildren() {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGCHLD)
for {
select {
case <-sigs:
}
for {
var wstatus syscall.WaitStatus
_, err := syscall.Wait4(-1, &wstatus, 0, nil)
for err == syscall.EINTR {
_, err = syscall.Wait4(-1, &wstatus, 0, nil)
}
if err == nil || err == syscall.ECHILD {
break
}
}
}
}