diff --git a/src/os/exec.go b/src/os/exec.go index d01ca592baefed..b60f1ec3380aab 100644 --- a/src/os/exec.go +++ b/src/os/exec.go @@ -85,7 +85,7 @@ func Getppid() int { return syscall.Getppid() } // The Process it returns can be used to obtain information // about the underlying operating system process. // -// On Unix systems, FindProcess always succeeds and returns a Process +// On non-Linux Unix systems, FindProcess always succeeds and returns a Process // for the given pid, regardless of whether the process exists. func FindProcess(pid int) (*Process, error) { return findProcess(pid) diff --git a/src/os/exec_posix.go b/src/os/exec_posix.go index a512d5199a7d10..a36dfe30469315 100644 --- a/src/os/exec_posix.go +++ b/src/os/exec_posix.go @@ -69,9 +69,9 @@ func (p *Process) kill() error { // ProcessState stores information about a process, as reported by Wait. type ProcessState struct { - pid int // The process's id. - status syscall.WaitStatus // System-dependent status info. - rusage *syscall.Rusage + pid int // The process's id. + siginfo Siginfo // System-dependent status info. + rusage *syscall.Rusage } // Pid returns the process id of the exited process. @@ -80,15 +80,15 @@ func (p *ProcessState) Pid() int { } func (p *ProcessState) exited() bool { - return p.status.Exited() + return p.siginfo.Exited() } func (p *ProcessState) success() bool { - return p.status.ExitStatus() == 0 + return p.siginfo.ExitStatus() == 0 } func (p *ProcessState) sys() any { - return p.status + return p.siginfo } func (p *ProcessState) sysUsage() any { @@ -99,27 +99,27 @@ func (p *ProcessState) String() string { if p == nil { return "" } - status := p.Sys().(syscall.WaitStatus) + siginfo := p.Sys().(Siginfo) res := "" switch { - case status.Exited(): - code := status.ExitStatus() + case siginfo.Exited(): + code := siginfo.ExitStatus() if runtime.GOOS == "windows" && uint(code) >= 1<<16 { // windows uses large hex numbers res = "exit status " + uitox(uint(code)) } else { // unix systems use small decimal integers res = "exit status " + itoa.Itoa(code) // unix } - case status.Signaled(): - res = "signal: " + status.Signal().String() - case status.Stopped(): - res = "stop signal: " + status.StopSignal().String() - if status.StopSignal() == syscall.SIGTRAP && status.TrapCause() != 0 { - res += " (trap " + itoa.Itoa(status.TrapCause()) + ")" + case siginfo.Signaled(): + res = "signal: " + siginfo.Signal().String() + case siginfo.Stopped(): + res = "stop signal: " + siginfo.StopSignal().String() + if siginfo.Trapped() && siginfo.TrapCause() != 0 { + res += " (trap " + itoa.Itoa(siginfo.TrapCause()) + ")" } - case status.Continued(): + case siginfo.Continued(): res = "continued" } - if status.CoreDump() { + if siginfo.CoreDump() { res += " (core dumped)" } return res @@ -132,5 +132,5 @@ func (p *ProcessState) ExitCode() int { if p == nil { return -1 } - return p.status.ExitStatus() + return p.siginfo.ExitStatus() } diff --git a/src/os/exec_unix.go b/src/os/exec_unix.go index f9063b4db45578..f3b8999f15bc2d 100644 --- a/src/os/exec_unix.go +++ b/src/os/exec_unix.go @@ -11,8 +11,64 @@ import ( "runtime" "syscall" "time" + "unsafe" ) +const CLD_EXITED = 1 +const CLD_KILLED = 2 +const CLD_DUMPED = 3 +const CLD_TRAPPED = 4 +const CLD_STOPPED = 5 +const CLD_CONTINUED = 6 + +type Siginfo struct { + Signo int32 + Errno int32 + Code int32 + _ int32 + _ [112]byte +} + +func (s Siginfo) Exited() bool { return s.Code == CLD_EXITED } + +func (s Siginfo) Signaled() bool { return s.Code == CLD_KILLED } + +func (s Siginfo) Stopped() bool { return s.Code == CLD_STOPPED } + +func (s Siginfo) Trapped() bool { return s.Code == CLD_TRAPPED } + +func (s Siginfo) Continued() bool { return s.Code == CLD_CONTINUED } + +func (s Siginfo) CoreDump() bool { return s.Code == CLD_DUMPED } + +func (s Siginfo) ExitStatus() int { + if !s.Exited() { + return -1 + } + return int(s.Errno) +} + +func (s Siginfo) Signal() syscall.Signal { + if !s.Signaled() { + return -1 + } + return syscall.Signal(s.Errno) +} + +func (s Siginfo) StopSignal() syscall.Signal { + if !s.Stopped() { + return -1 + } + return syscall.Signal(s.Errno) +} + +func (s Siginfo) TrapCause() int { + if !s.Trapped() { + return -1 + } + return int(s.Errno) +} + func (p *Process) wait() (ps *ProcessState, err error) { if p.Pid == -1 { return nil, syscall.EINVAL @@ -34,27 +90,37 @@ func (p *Process) wait() (ps *ProcessState, err error) { } var ( - status syscall.WaitStatus - rusage syscall.Rusage - pid1 int - e error + siginfo Siginfo + rusage syscall.Rusage + e syscall.Errno ) for { - pid1, e = syscall.Wait4(p.Pid, &status, 0, &rusage) - if e != syscall.EINTR { + _, _, e = syscall.Syscall6(syscall.SYS_WAITID, _P_PIDFD, p.handle, uintptr(unsafe.Pointer(&siginfo)), syscall.WEXITED, uintptr(unsafe.Pointer(&rusage)), 0) + if e == syscall.EINTR { + continue + } else if e == syscall.ENOSYS { + // waitid has been available since Linux 2.6.9, but + // reportedly is not available in Ubuntu on Windows. + // See issue 16610. + panic("TODO: Implement fallback") + } else if e != 0 { + break + } + // During ptrace the wait might return also for non-exit reasons. In that case we retry. + // See: https://lwn.net/Articles/688624/ + if siginfo.Exited() || siginfo.Signaled() || siginfo.CoreDump() { break } } - if e != nil { - return nil, NewSyscallError("wait", e) - } - if pid1 != 0 { - p.setDone() + runtime.KeepAlive(p) + if e != 0 { + return nil, NewSyscallError("waitid", e) } + p.setDone() ps = &ProcessState{ - pid: pid1, - status: status, - rusage: &rusage, + pid: p.Pid, + siginfo: siginfo, + rusage: &rusage, } return ps, nil } @@ -75,17 +141,22 @@ func (p *Process) signal(sig Signal) error { if !ok { return errors.New("os: unsupported signal type") } - if e := syscall.Kill(p.Pid, s); e != nil { + if _, _, e := syscall.RawSyscall6(syscall.SYS_PIDFD_SEND_SIGNAL, p.handle, uintptr(s), 0, 0, 0, 0); e != 0 { if e == syscall.ESRCH { return ErrProcessDone } - return e + return NewSyscallError("pidfd_send_signal", e) } + runtime.KeepAlive(p) return nil } func (p *Process) release() error { - // NOOP for unix. + e := syscall.Close(int(p.handle)) + if e != nil { + return NewSyscallError("close", e) + + } p.Pid = -1 // no need for a finalizer anymore runtime.SetFinalizer(p, nil) @@ -93,8 +164,12 @@ func (p *Process) release() error { } func findProcess(pid int) (p *Process, err error) { - // NOOP for unix. - return newProcess(pid, 0), nil + fd, _, e := syscall.Syscall(syscall.SYS_PIDFD_OPEN, uintptr(pid), 0, 0) + runtime.KeepAlive(p) + if e != 0 { + return nil, NewSyscallError("pidfd_open", e) + } + return newProcess(pid, fd), nil } func (p *ProcessState) userTime() time.Duration { diff --git a/src/os/wait_waitid.go b/src/os/wait_waitid.go index c0503b209c082c..5966e560f79f3a 100644 --- a/src/os/wait_waitid.go +++ b/src/os/wait_waitid.go @@ -10,39 +10,32 @@ package os import ( - "runtime" + "internal/poll" "syscall" - "unsafe" ) -const _P_PID = 1 +const _P_PIDFD = 3 // blockUntilWaitable attempts to block until a call to p.Wait will // succeed immediately, and reports whether it has done so. // It does not actually call p.Wait. func (p *Process) blockUntilWaitable() (bool, error) { - // The waitid system call expects a pointer to a siginfo_t, - // which is 128 bytes on all Linux systems. - // On darwin/amd64, it requires 104 bytes. - // We don't care about the values it returns. - var siginfo [16]uint64 - psig := &siginfo[0] - var e syscall.Errno - for { - _, _, e = syscall.Syscall6(syscall.SYS_WAITID, _P_PID, uintptr(p.Pid), uintptr(unsafe.Pointer(psig)), syscall.WEXITED|syscall.WNOWAIT, 0, 0) - if e != syscall.EINTR { - break - } + fd := poll.FD{ + Sysfd: int(p.handle), } - runtime.KeepAlive(p) - if e != 0 { - // waitid has been available since Linux 2.6.9, but - // reportedly is not available in Ubuntu on Windows. - // See issue 16610. - if e == syscall.ENOSYS { - return false, nil - } - return false, NewSyscallError("waitid", e) + err := fd.Init("pidfd", false) + if err != nil { + return false, err + } + // We just want to make sure fd is ready for reading, but that is not yet available. + // See: https://github.com/golang/go/issues/15735 + buf := make([]byte, 1) + _, err = fd.Read(buf) + if err == syscall.EINVAL { + // fd is ready for reading, but reading failed, as expected on pidfd. + return true, nil + } else if err != nil { + return false, err } return true, nil } diff --git a/src/syscall/exec_unix.go b/src/syscall/exec_unix.go index 4b9c04db83ee2c..c2f0f28e07e3fa 100644 --- a/src/syscall/exec_unix.go +++ b/src/syscall/exec_unix.go @@ -16,6 +16,9 @@ import ( "unsafe" ) +const SYS_PIDFD_OPEN = 434 +const SYS_PIDFD_SEND_SIGNAL = 424 + // ForkLock is used to synchronize creation of new file descriptors // with fork. // @@ -332,7 +335,15 @@ func ForkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error) // StartProcess wraps ForkExec for package os. func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle uintptr, err error) { pid, err = forkExec(argv0, argv, attr) - return pid, 0, err + if err != nil { + return + } + fd, _, e := Syscall(SYS_PIDFD_OPEN, uintptr(pid), 0, 0) + if e != 0 { + err = errnoErr(e) + return + } + return pid, fd, err } // Implemented in runtime package.