package main import ( "os" "os/signal" "syscall" "github.com/sirupsen/logrus" ) func forkExec() { // run supercronic in other process pwd, err := os.Getwd() if err != nil { logrus.Fatalf("Failed to get current working directory: %s", err.Error()) return } exe, err := os.Executable() if err != nil { logrus.Fatalf("Failed to get executable %s", err.Error()) } pattrs := &syscall.ProcAttr{ Dir: pwd, Env: os.Environ(), Files: []uintptr{ uintptr(syscall.Stdin), uintptr(syscall.Stdout), uintptr(syscall.Stderr), }, } args := make([]string, 0, len(os.Args)+1) // disable reaping for supercronic, avoid no sense warning args = append(args, exe, "-no-reap") args = append(args, os.Args[1:]...) pid, err := syscall.ForkExec(args[0], args, pattrs) if err != nil { logrus.Fatalf("Failed to fork exec: %s", err.Error()) return } // forward signal to supercronic signalToFork(pid) // got supercronic exit status wstatus := reapChildren(pid) os.Exit(wstatus.ExitStatus()) } func signalToFork(pid int) { p, err := os.FindProcess(pid) if err != nil { logrus.Fatalf("Failed findProcess supercronic pid:%d,%s", pid, err.Error()) } termChan := make(chan os.Signal, 1) signal.Notify(termChan, signalList...) go func() { for { s := <-termChan if err := p.Signal(s); err != nil { logrus.Errorf("Failed to send signal to supercronic: %s", err.Error()) } } }() } // copy from https://github.com/ramr/go-reaper // modify for wait exit status of supercronic // without modify, supercronic exit status may not be obtained // Be a good parent - clean up behind the children. func reapChildren(superCrondPid int) syscall.WaitStatus { var notifications = make(chan os.Signal, 1) go sigChildHandler(notifications) // all child const rpid = -1 var wstatus syscall.WaitStatus for { var sig = <-notifications logrus.Debugf("reaper received signal %v\n", sig) for { pid, err := syscall.Wait4(rpid, &wstatus, 0, nil) for syscall.EINTR == err { pid, err = syscall.Wait4(pid, &wstatus, 0, nil) } if syscall.ECHILD == err { break } if superCrondPid == pid { logrus.Debugf("supercronic exit, pid=%d, wstatus=%+v, err=%+v\n", pid, wstatus, err) return wstatus } // note: change output need change test logrus.Warnf("reaper cleanup: pid=%d, wstatus=%+v\n", pid, wstatus) } } } // Handle death of child (SIGCHLD) messages. Pushes the signal onto the // notifications channel if there is a waiter. func sigChildHandler(notifications chan os.Signal) { var sigs = make(chan os.Signal, 3) signal.Notify(sigs, syscall.SIGCHLD) for { var sig = <-sigs select { case notifications <- sig: /* published it. */ default: /* * Notifications channel full - drop it to the * floor. This ensures we don't fill up the SIGCHLD * queue. The reaper just waits for any child * process (pid=-1), so we ain't loosing it!! ;^) */ } } } /* End of function sigChildHandler. */