diff --git a/Dockerfile.in b/Dockerfile.in index 5d49aeb..7ebdcf5 100644 --- a/Dockerfile.in +++ b/Dockerfile.in @@ -14,8 +14,6 @@ FROM {ARG_FROM} -MAINTAINER Tim Hockin - RUN apt-get update \ && apt-get -y install \ ca-certificates \ diff --git a/cmd/git-sync/main.go b/cmd/git-sync/main.go index cb2292e..2b62846 100644 --- a/cmd/git-sync/main.go +++ b/cmd/git-sync/main.go @@ -40,6 +40,7 @@ import ( "github.com/go-logr/glogr" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" + "k8s.io/git-sync/pkg/pid1" "k8s.io/git-sync/pkg/version" ) @@ -184,6 +185,20 @@ func envDuration(key string, def time.Duration) time.Duration { } 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() flag.Parse() @@ -198,15 +213,18 @@ func main() { flag.Usage() os.Exit(1) } + if *flDest == "" { parts := strings.Split(strings.Trim(*flRepo, "/"), "/") *flDest = parts[len(parts)-1] } + if strings.Contains(*flDest, "/") { fmt.Fprintf(os.Stderr, "ERROR: --dest must be a bare name\n") flag.Usage() os.Exit(1) } + if _, err := exec.LookPath(*flGitCmd); err != nil { fmt.Fprintf(os.Stderr, "ERROR: git executable %q not found: %v\n", *flGitCmd, err) os.Exit(1) diff --git a/pkg/pid1/pid1.go b/pkg/pid1/pid1.go new file mode 100644 index 0000000..19cc84f --- /dev/null +++ b/pkg/pid1/pid1.go @@ -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. + } +}