Handle running as pid 1
This detects when it is running as pid 1, and becomes an init process. Specifically this means handling SIGCHLD and reaping processes (otherwise they become zombies) and forwarding signals to "real" process. We fork and re-exec ourselves so that we only get *this* SIGCHLD for orphaned processes (re-parented to 1) and not the real events from running things like git or ssh.
This commit is contained in:
parent
98022e4fe8
commit
2a42fba009
|
|
@ -14,8 +14,6 @@
|
||||||
|
|
||||||
FROM {ARG_FROM}
|
FROM {ARG_FROM}
|
||||||
|
|
||||||
MAINTAINER Tim Hockin <thockin@google.com>
|
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get -y install \
|
&& apt-get -y install \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ import (
|
||||||
"github.com/go-logr/glogr"
|
"github.com/go-logr/glogr"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
"k8s.io/git-sync/pkg/pid1"
|
||||||
"k8s.io/git-sync/pkg/version"
|
"k8s.io/git-sync/pkg/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -184,6 +185,20 @@ func envDuration(key string, def time.Duration) time.Duration {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// In case we come up as pid 1, act as init.
|
||||||
|
if os.Getpid() == 1 {
|
||||||
|
fmt.Fprintf(os.Stderr, "INFO: detected pid 1, running init handler\n")
|
||||||
|
err := pid1.ReRun()
|
||||||
|
if err == nil {
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
if exerr, ok := err.(*exec.ExitError); ok {
|
||||||
|
os.Exit(exerr.ExitCode())
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "ERROR: unhandled pid1 error: %v\n", err)
|
||||||
|
os.Exit(127)
|
||||||
|
}
|
||||||
|
|
||||||
setFlagDefaults()
|
setFlagDefaults()
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
@ -198,15 +213,18 @@ func main() {
|
||||||
flag.Usage()
|
flag.Usage()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *flDest == "" {
|
if *flDest == "" {
|
||||||
parts := strings.Split(strings.Trim(*flRepo, "/"), "/")
|
parts := strings.Split(strings.Trim(*flRepo, "/"), "/")
|
||||||
*flDest = parts[len(parts)-1]
|
*flDest = parts[len(parts)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(*flDest, "/") {
|
if strings.Contains(*flDest, "/") {
|
||||||
fmt.Fprintf(os.Stderr, "ERROR: --dest must be a bare name\n")
|
fmt.Fprintf(os.Stderr, "ERROR: --dest must be a bare name\n")
|
||||||
flag.Usage()
|
flag.Usage()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := exec.LookPath(*flGitCmd); err != nil {
|
if _, err := exec.LookPath(*flGitCmd); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "ERROR: git executable %q not found: %v\n", *flGitCmd, err)
|
fmt.Fprintf(os.Stderr, "ERROR: git executable %q not found: %v\n", *flGitCmd, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
go runInit(cmd.Process.Pid)
|
||||||
|
return cmd.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// runInit runs a bare-bones init process. This will never return. In case of
|
||||||
|
// truly unknown errors it will panic.
|
||||||
|
func runInit(pid int) {
|
||||||
|
sigs := make(chan os.Signal, 8)
|
||||||
|
signal.Notify(sigs)
|
||||||
|
for sig := range sigs {
|
||||||
|
if sig == syscall.SIGCHLD {
|
||||||
|
sigchld()
|
||||||
|
} else {
|
||||||
|
// Pass it on to the real process.
|
||||||
|
syscall.Kill(pid, sig.(syscall.Signal))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sigchld handles a SIGCHLD.
|
||||||
|
func sigchld() {
|
||||||
|
// 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 <= 0 {
|
||||||
|
// No more children to reap.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Must have found one, see if there are more.
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue