git-sync/pkg/pid1/pid1.go

71 lines
1.8 KiB
Go

package pid1
import (
"fmt"
"os"
"os/exec"
"os/signal"
"syscall"
)
// ReRun converts the current process into a bare-bones init, runs the current
// commandline as a child process, and waits for it to complete. The new child
// process shares stdin/stdout/stderr with the parent. When the child exits,
// this will return the same value as exec.Command.Run(). If there is an error
// in reaping children that this can not handle, it will panic.
func ReRun() error {
bin, err := os.Readlink("/proc/self/exe")
if err != nil {
return err
}
cmd := exec.Command(bin, os.Args[1:]...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
return err
}
runInit(cmd.Process.Pid)
return nil
}
// runInit runs a bare-bones init process. This will return when firstborn
// exits. In case of truly unknown errors it will panic.
func runInit(firstborn int) {
sigs := make(chan os.Signal, 8)
signal.Notify(sigs)
for sig := range sigs {
if sig != syscall.SIGCHLD {
// Pass it on to the real process.
syscall.Kill(firstborn, sig.(syscall.Signal))
}
// Always try to reap a child - empirically, sometimes this gets missed.
if sigchld(firstborn) {
return
}
}
}
// sigchld handles a SIGCHLD. This will return true when firstborn exits. In
// case of truly unknown errors it will panic.
func sigchld(firstborn int) bool {
// Loop to handle multiple child processes.
for {
var status syscall.WaitStatus
pid, err := syscall.Wait4(-1, &status, syscall.WNOHANG, nil)
if err != nil {
panic(fmt.Sprintf("failed to wait4(): %v\n", err))
}
if pid == firstborn {
return true
}
if pid <= 0 {
// No more children to reap.
break
}
// Must have found one, see if there are more.
}
return false
}