ForkReap spawns a goroutine that reaps children. In order to avoid complications with spawning `exec.Commands` in the same process that is reaping, we forkexec a child process. This prevents a race between the reaper and an exec.Command waiting for its process to complete. The provided 'pids' channe
(opt ...Option)
| 61 | // Returns the child's exit code (using 128+signal for signal termination) |
| 62 | // and any error from Wait4. |
| 63 | func ForkReap(opt ...Option) (int, error) { |
| 64 | opts := &options{ |
| 65 | ExecArgs: os.Args, |
| 66 | } |
| 67 | |
| 68 | for _, o := range opt { |
| 69 | o(opts) |
| 70 | } |
| 71 | |
| 72 | go func() { |
| 73 | reap.ReapChildren(opts.PIDs, nil, opts.ReaperStop, opts.ReapLock) |
| 74 | if opts.ReaperStopped != nil { |
| 75 | close(opts.ReaperStopped) |
| 76 | } |
| 77 | }() |
| 78 | |
| 79 | pwd, err := os.Getwd() |
| 80 | if err != nil { |
| 81 | return 1, xerrors.Errorf("get wd: %w", err) |
| 82 | } |
| 83 | |
| 84 | pattrs := &syscall.ProcAttr{ |
| 85 | Dir: pwd, |
| 86 | Env: os.Environ(), |
| 87 | Sys: &syscall.SysProcAttr{ |
| 88 | Setsid: true, |
| 89 | }, |
| 90 | Files: []uintptr{ |
| 91 | uintptr(syscall.Stdin), |
| 92 | uintptr(syscall.Stdout), |
| 93 | uintptr(syscall.Stderr), |
| 94 | }, |
| 95 | } |
| 96 | |
| 97 | //#nosec G204 |
| 98 | pid, err := syscall.ForkExec(opts.ExecArgs[0], opts.ExecArgs, pattrs) |
| 99 | if err != nil { |
| 100 | return 1, xerrors.Errorf("fork exec: %w", err) |
| 101 | } |
| 102 | |
| 103 | startSignalForwarding(opts.Logger, pid, opts.CatchSignals) |
| 104 | |
| 105 | var wstatus syscall.WaitStatus |
| 106 | _, err = syscall.Wait4(pid, &wstatus, 0, nil) |
| 107 | for xerrors.Is(err, syscall.EINTR) { |
| 108 | _, err = syscall.Wait4(pid, &wstatus, 0, nil) |
| 109 | } |
| 110 | |
| 111 | // Convert wait status to exit code using standard Unix conventions: |
| 112 | // - Normal exit: use the exit code |
| 113 | // - Signal termination: use 128 + signal number |
| 114 | var exitCode int |
| 115 | switch { |
| 116 | case wstatus.Exited(): |
| 117 | exitCode = wstatus.ExitStatus() |
| 118 | case wstatus.Signaled(): |
| 119 | exitCode = 128 + int(wstatus.Signal()) |
| 120 | default: |