| // 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 || windows | |
| package os | |
| import ( | |
| "internal/strconv" | |
| "internal/syscall/execenv" | |
| "runtime" | |
| "syscall" | |
| ) | |
| // The only signal values guaranteed to be present in the os package on all | |
| // systems are os.Interrupt (send the process an interrupt) and os.Kill (force | |
| // the process to exit). On Windows, sending os.Interrupt to a process with | |
| // os.Process.Signal is not implemented; it will return an error instead of | |
| // sending a signal. | |
| var ( | |
| Interrupt Signal = syscall.SIGINT | |
| Kill Signal = syscall.SIGKILL | |
| ) | |
| func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err error) { | |
| // If there is no SysProcAttr (ie. no Chroot or changed | |
| // UID/GID), double-check existence of the directory we want | |
| // to chdir into. We can make the error clearer this way. | |
| if attr != nil && attr.Sys == nil && attr.Dir != "" { | |
| if _, err := Stat(attr.Dir); err != nil { | |
| pe := err.(*PathError) | |
| pe.Op = "chdir" | |
| return nil, pe | |
| } | |
| } | |
| attrSys, shouldDupPidfd := ensurePidfd(attr.Sys) | |
| sysattr := &syscall.ProcAttr{ | |
| Dir: attr.Dir, | |
| Env: attr.Env, | |
| Sys: attrSys, | |
| } | |
| if sysattr.Env == nil { | |
| sysattr.Env, err = execenv.Default(sysattr.Sys) | |
| if err != nil { | |
| return nil, err | |
| } | |
| } | |
| sysattr.Files = make([]uintptr, 0, len(attr.Files)) | |
| for _, f := range attr.Files { | |
| sysattr.Files = append(sysattr.Files, f.Fd()) | |
| } | |
| pid, h, e := syscall.StartProcess(name, argv, sysattr) | |
| // Make sure we don't run the finalizers of attr.Files. | |
| runtime.KeepAlive(attr) | |
| if e != nil { | |
| return nil, &PathError{Op: "fork/exec", Path: name, Err: e} | |
| } | |
| // For Windows, syscall.StartProcess above already returned a process handle. | |
| if runtime.GOOS != "windows" { | |
| var ok bool | |
| h, ok = getPidfd(sysattr.Sys, shouldDupPidfd) | |
| if !ok { | |
| return newPIDProcess(pid), nil | |
| } | |
| } | |
| return newHandleProcess(pid, h), nil | |
| } | |
| func (p *Process) kill() error { | |
| return p.Signal(Kill) | |
| } | |
| func (p *Process) withHandle(f func(handle uintptr)) error { | |
| if p.handle == nil { | |
| return ErrNoHandle | |
| } | |
| handle, status := p.handleTransientAcquire() | |
| switch status { | |
| case statusDone: | |
| return ErrProcessDone | |
| case statusReleased: | |
| return errProcessReleased | |
| } | |
| defer p.handleTransientRelease() | |
| f(handle) | |
| return nil | |
| } | |
| // 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 returns the process id of the exited process. | |
| func (p *ProcessState) Pid() int { | |
| return p.pid | |
| } | |
| func (p *ProcessState) exited() bool { | |
| return p.status.Exited() | |
| } | |
| func (p *ProcessState) success() bool { | |
| return p.status.ExitStatus() == 0 | |
| } | |
| func (p *ProcessState) sys() any { | |
| return p.status | |
| } | |
| func (p *ProcessState) sysUsage() any { | |
| return p.rusage | |
| } | |
| func (p *ProcessState) String() string { | |
| if p == nil { | |
| return "<nil>" | |
| } | |
| status := p.Sys().(syscall.WaitStatus) | |
| res := "" | |
| switch { | |
| case status.Exited(): | |
| code := status.ExitStatus() | |
| if runtime.GOOS == "windows" && uint(code) >= 1<<16 { // windows uses large hex numbers | |
| res = "exit status 0x" + strconv.FormatUint(uint64(code), 16) | |
| } else { // unix systems use small decimal integers | |
| res = "exit status " + strconv.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 " + strconv.Itoa(status.TrapCause()) + ")" | |
| } | |
| case status.Continued(): | |
| res = "continued" | |
| } | |
| if status.CoreDump() { | |
| res += " (core dumped)" | |
| } | |
| return res | |
| } | |
| // ExitCode returns the exit code of the exited process, or -1 | |
| // if the process hasn't exited or was terminated by a signal. | |
| func (p *ProcessState) ExitCode() int { | |
| // return -1 if the process hasn't started. | |
| if p == nil { | |
| return -1 | |
| } | |
| return p.status.ExitStatus() | |
| } |