git-sync/pkg/pid1/pid1.go

84 lines
2.2 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 exit code. If this returns an error, the child process
// may not be terminated.
func ReRun() (int, error) {
bin, err := os.Readlink("/proc/self/exe")
if err != nil {
return 0, 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 0, err
}
return runInit(cmd.Process.Pid)
}
// runInit runs a bare-bones init process. When firstborn exits, this will
// return the exit code. If this returns an error, the child process may not
// be terminated.
func runInit(firstborn int) (int, error) {
sigs := make(chan os.Signal, 8)
signal.Notify(sigs)
for sig := range sigs {
if sig != syscall.SIGCHLD {
// Pass it on to the real process.
if err := syscall.Kill(firstborn, sig.(syscall.Signal)); err != nil {
return 0, err
}
}
// Always try to reap a child - empirically, sometimes this gets missed.
die, status, err := sigchld(firstborn)
if err != nil {
return 0, err
}
if die {
if status.Signaled() {
return 128 + int(status.Signal()), nil
}
if status.Exited() {
return status.ExitStatus(), nil
}
return 0, fmt.Errorf("unhandled exit status: 0x%x\n", status)
}
}
return 0, fmt.Errorf("signal handler terminated unexpectedly")
}
// sigchld handles a SIGCHLD. This will return true only when firstborn exits.
// For any other process this will return false and a 0 status.
func sigchld(firstborn int) (bool, syscall.WaitStatus, error) {
// Loop to handle multiple child processes.
for {
var status syscall.WaitStatus
pid, err := syscall.Wait4(-1, &status, syscall.WNOHANG, nil)
if err != nil {
return false, 0, fmt.Errorf("wait4(): %v\n", err)
}
if pid == firstborn {
return true, status, nil
}
if pid <= 0 {
// No more children to reap.
break
}
// Must have found one, see if there are more.
}
return false, 0, nil
}