blob: d2ceb0ceb5aeebf1cd943461d879e5d8d98e1406 [file] [log] [blame] [edit]
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build unix || (js && wasm) || wasip1
package os
import (
"errors"
"syscall"
"time"
)
const (
// Special values for Process.Pid.
pidUnset = 0
pidReleased = -1
)
func (p *Process) wait() (ps *ProcessState, err error) {
// Which type of Process do we have?
if p.handle != nil {
// pidfd
return p.pidfdWait()
} else {
// Regular PID
return p.pidWait()
}
}
func (p *Process) pidWait() (*ProcessState, error) {
// TODO(go.dev/issue/67642): When there are concurrent Wait calls, one
// may wait on the wrong process if the PID is reused after the
// completes its wait.
//
// Checking for statusDone here would not be a complete fix, as the PID
// could still be waited on and reused prior to blockUntilWaitable.
switch p.pidStatus() {
case statusReleased:
return nil, syscall.EINVAL
}
// If we can block until Wait4 will succeed immediately, do so.
ready, err := p.blockUntilWaitable()
if err != nil {
return nil, err
}
if ready {
// Mark the process done now, before the call to Wait4,
// so that Process.pidSignal will not send a signal.
p.doRelease(statusDone)
// Acquire a write lock on sigMu to wait for any
// active call to the signal method to complete.
p.sigMu.Lock()
p.sigMu.Unlock()
}
var (
status syscall.WaitStatus
rusage syscall.Rusage
)
pid1, err := ignoringEINTR2(func() (int, error) {
return syscall.Wait4(p.Pid, &status, 0, &rusage)
})
if err != nil {
return nil, NewSyscallError("wait", err)
}
p.doRelease(statusDone)
return &ProcessState{
pid: pid1,
status: status,
rusage: &rusage,
}, nil
}
func (p *Process) signal(sig Signal) error {
s, ok := sig.(syscall.Signal)
if !ok {
return errors.New("os: unsupported signal type")
}
// Which type of Process do we have?
if p.handle != nil {
// pidfd
return p.pidfdSendSignal(s)
} else {
// Regular PID
return p.pidSignal(s)
}
}
func (p *Process) pidSignal(s syscall.Signal) error {
if p.Pid == pidReleased {
return errProcessReleased
}
if p.Pid == pidUnset {
return errors.New("os: process not initialized")
}
p.sigMu.RLock()
defer p.sigMu.RUnlock()
switch p.pidStatus() {
case statusDone:
return ErrProcessDone
case statusReleased:
return errProcessReleased
}
return convertESRCH(syscall.Kill(p.Pid, s))
}
func convertESRCH(err error) error {
if err == syscall.ESRCH {
return ErrProcessDone
}
return err
}
func findProcess(pid int) (p *Process, err error) {
h, err := pidfdFind(pid)
if err == ErrProcessDone {
// We can't return an error here since users are not expecting
// it. Instead, return a process with a "done" state already
// and let a subsequent Signal or Wait call catch that.
return newDoneProcess(pid), nil
} else if err != nil {
// Ignore other errors from pidfdFind, as the callers
// do not expect them. Fall back to using the PID.
return newPIDProcess(pid), nil
}
// Use the handle.
return newHandleProcess(pid, h), nil
}
func (p *ProcessState) userTime() time.Duration {
return time.Duration(p.rusage.Utime.Nano()) * time.Nanosecond
}
func (p *ProcessState) systemTime() time.Duration {
return time.Duration(p.rusage.Stime.Nano()) * time.Nanosecond
}