patch library: syscall, os/exec
This commit is contained in:
172
internal/lib/os/exec.go
Normal file
172
internal/lib/os/exec.go
Normal file
@@ -0,0 +1,172 @@
|
||||
// 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.
|
||||
|
||||
package os
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ErrProcessDone indicates a Process has finished.
|
||||
var ErrProcessDone = errors.New("os: process already finished")
|
||||
|
||||
// Process stores the information about a process created by StartProcess.
|
||||
type Process struct {
|
||||
Pid int
|
||||
handle uintptr // handle is accessed atomically on Windows
|
||||
isdone atomic.Bool // process has been successfully waited on
|
||||
sigMu sync.RWMutex // avoid race between wait and signal
|
||||
}
|
||||
|
||||
func newProcess(pid int, handle uintptr) *Process {
|
||||
p := &Process{Pid: pid, handle: handle}
|
||||
runtime.SetFinalizer(p, (*Process).Release)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Process) setDone() {
|
||||
p.isdone.Store(true)
|
||||
}
|
||||
|
||||
func (p *Process) done() bool {
|
||||
return p.isdone.Load()
|
||||
}
|
||||
|
||||
// ProcAttr holds the attributes that will be applied to a new process
|
||||
// started by StartProcess.
|
||||
type ProcAttr struct {
|
||||
// If Dir is non-empty, the child changes into the directory before
|
||||
// creating the process.
|
||||
Dir string
|
||||
// If Env is non-nil, it gives the environment variables for the
|
||||
// new process in the form returned by Environ.
|
||||
// If it is nil, the result of Environ will be used.
|
||||
Env []string
|
||||
// Files specifies the open files inherited by the new process. The
|
||||
// first three entries correspond to standard input, standard output, and
|
||||
// standard error. An implementation may support additional entries,
|
||||
// depending on the underlying operating system. A nil entry corresponds
|
||||
// to that file being closed when the process starts.
|
||||
// On Unix systems, StartProcess will change these File values
|
||||
// to blocking mode, which means that SetDeadline will stop working
|
||||
// and calling Close will not interrupt a Read or Write.
|
||||
Files []*File
|
||||
|
||||
// Operating system-specific process creation attributes.
|
||||
// Note that setting this field means that your program
|
||||
// may not execute properly or even compile on some
|
||||
// operating systems.
|
||||
Sys *syscall.SysProcAttr
|
||||
}
|
||||
|
||||
// A Signal represents an operating system signal.
|
||||
// The usual underlying implementation is operating system-dependent:
|
||||
// on Unix it is syscall.Signal.
|
||||
type Signal interface {
|
||||
String() string
|
||||
Signal() // to distinguish from other Stringers
|
||||
}
|
||||
|
||||
// FindProcess looks for a running process by its pid.
|
||||
//
|
||||
// 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
|
||||
// for the given pid, regardless of whether the process exists. To test whether
|
||||
// the process actually exists, see whether p.Signal(syscall.Signal(0)) reports
|
||||
// an error.
|
||||
func FindProcess(pid int) (*Process, error) {
|
||||
return findProcess(pid)
|
||||
}
|
||||
|
||||
// StartProcess starts a new process with the program, arguments and attributes
|
||||
// specified by name, argv and attr. The argv slice will become os.Args in the
|
||||
// new process, so it normally starts with the program name.
|
||||
//
|
||||
// If the calling goroutine has locked the operating system thread
|
||||
// with runtime.LockOSThread and modified any inheritable OS-level
|
||||
// thread state (for example, Linux or Plan 9 name spaces), the new
|
||||
// process will inherit the caller's thread state.
|
||||
//
|
||||
// StartProcess is a low-level interface. The os/exec package provides
|
||||
// higher-level interfaces.
|
||||
//
|
||||
// If there is an error, it will be of type *PathError.
|
||||
func StartProcess(name string, argv []string, attr *ProcAttr) (*Process, error) {
|
||||
return startProcess(name, argv, attr)
|
||||
}
|
||||
|
||||
// Release releases any resources associated with the Process p,
|
||||
// rendering it unusable in the future.
|
||||
// Release only needs to be called if Wait is not.
|
||||
func (p *Process) Release() error {
|
||||
return p.release()
|
||||
}
|
||||
|
||||
// Kill causes the Process to exit immediately. Kill does not wait until
|
||||
// the Process has actually exited. This only kills the Process itself,
|
||||
// not any other processes it may have started.
|
||||
func (p *Process) Kill() error {
|
||||
return p.kill()
|
||||
}
|
||||
|
||||
// Wait waits for the Process to exit, and then returns a
|
||||
// ProcessState describing its status and an error, if any.
|
||||
// Wait releases any resources associated with the Process.
|
||||
// On most operating systems, the Process must be a child
|
||||
// of the current process or an error will be returned.
|
||||
func (p *Process) Wait() (*ProcessState, error) {
|
||||
return p.wait()
|
||||
}
|
||||
|
||||
// Signal sends a signal to the Process.
|
||||
// Sending Interrupt on Windows is not implemented.
|
||||
func (p *Process) Signal(sig Signal) error {
|
||||
return p.signal(sig)
|
||||
}
|
||||
|
||||
// UserTime returns the user CPU time of the exited process and its children.
|
||||
func (p *ProcessState) UserTime() time.Duration {
|
||||
return p.userTime()
|
||||
}
|
||||
|
||||
// SystemTime returns the system CPU time of the exited process and its children.
|
||||
func (p *ProcessState) SystemTime() time.Duration {
|
||||
return p.systemTime()
|
||||
}
|
||||
|
||||
// Exited reports whether the program has exited.
|
||||
// On Unix systems this reports true if the program exited due to calling exit,
|
||||
// but false if the program terminated due to a signal.
|
||||
func (p *ProcessState) Exited() bool {
|
||||
return p.exited()
|
||||
}
|
||||
|
||||
// Success reports whether the program exited successfully,
|
||||
// such as with exit status 0 on Unix.
|
||||
func (p *ProcessState) Success() bool {
|
||||
return p.success()
|
||||
}
|
||||
|
||||
// Sys returns system-dependent exit information about
|
||||
// the process. Convert it to the appropriate underlying
|
||||
// type, such as syscall.WaitStatus on Unix, to access its contents.
|
||||
func (p *ProcessState) Sys() any {
|
||||
return p.sys()
|
||||
}
|
||||
|
||||
// SysUsage returns system-dependent resource usage information about
|
||||
// the exited process. Convert it to the appropriate underlying
|
||||
// type, such as *syscall.Rusage on Unix, to access its contents.
|
||||
// (On Unix, *syscall.Rusage matches struct rusage as defined in the
|
||||
// getrusage(2) manual page.)
|
||||
func (p *ProcessState) SysUsage() any {
|
||||
return p.sysUsage()
|
||||
}
|
||||
22
internal/lib/os/exec/exec.go
Normal file
22
internal/lib/os/exec/exec.go
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package exec
|
||||
|
||||
// llgo:skipall
|
||||
import (
|
||||
_ "unsafe"
|
||||
)
|
||||
66
internal/lib/os/exec/lp_plan9.go
Normal file
66
internal/lib/os/exec/lp_plan9.go
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright 2011 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.
|
||||
|
||||
package exec
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ErrNotFound is the error resulting if a path search failed to find an executable file.
|
||||
var ErrNotFound = errors.New("executable file not found in $path")
|
||||
|
||||
func findExecutable(file string) error {
|
||||
d, err := os.Stat(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if m := d.Mode(); !m.IsDir() && m&0111 != 0 {
|
||||
return nil
|
||||
}
|
||||
return fs.ErrPermission
|
||||
}
|
||||
|
||||
// LookPath searches for an executable named file in the
|
||||
// directories named by the path environment variable.
|
||||
// If file begins with "/", "#", "./", or "../", it is tried
|
||||
// directly and the path is not consulted.
|
||||
// On success, the result is an absolute path.
|
||||
//
|
||||
// In older versions of Go, LookPath could return a path relative to the current directory.
|
||||
// As of Go 1.19, LookPath will instead return that path along with an error satisfying
|
||||
// errors.Is(err, ErrDot). See the package documentation for more details.
|
||||
func LookPath(file string) (string, error) {
|
||||
// skip the path lookup for these prefixes
|
||||
skip := []string{"/", "#", "./", "../"}
|
||||
|
||||
for _, p := range skip {
|
||||
if strings.HasPrefix(file, p) {
|
||||
err := findExecutable(file)
|
||||
if err == nil {
|
||||
return file, nil
|
||||
}
|
||||
return "", &Error{file, err}
|
||||
}
|
||||
}
|
||||
|
||||
path := os.Getenv("path")
|
||||
for _, dir := range filepath.SplitList(path) {
|
||||
path := filepath.Join(dir, file)
|
||||
if err := findExecutable(path); err == nil {
|
||||
if !filepath.IsAbs(path) {
|
||||
if execerrdot.Value() != "0" {
|
||||
return path, &Error{file, ErrDot}
|
||||
}
|
||||
execerrdot.IncNonDefault()
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
}
|
||||
return "", &Error{file, ErrNotFound}
|
||||
}
|
||||
82
internal/lib/os/exec/lp_unix.go
Normal file
82
internal/lib/os/exec/lp_unix.go
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright 2010 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
|
||||
|
||||
package exec
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// ErrNotFound is the error resulting if a path search failed to find an executable file.
|
||||
var ErrNotFound = errors.New("executable file not found in $PATH")
|
||||
|
||||
func findExecutable(file string) error {
|
||||
/* TODO(xsw):
|
||||
d, err := os.Stat(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m := d.Mode()
|
||||
if m.IsDir() {
|
||||
return syscall.EISDIR
|
||||
}
|
||||
err = unix.Eaccess(file, unix.X_OK)
|
||||
// ENOSYS means Eaccess is not available or not implemented.
|
||||
// EPERM can be returned by Linux containers employing seccomp.
|
||||
// In both cases, fall back to checking the permission bits.
|
||||
if err == nil || (err != syscall.ENOSYS && err != syscall.EPERM) {
|
||||
return err
|
||||
}
|
||||
if m&0111 != 0 {
|
||||
return nil
|
||||
}
|
||||
return fs.ErrPermission
|
||||
*/
|
||||
panic("todo: exec.findExecutable")
|
||||
}
|
||||
|
||||
// LookPath searches for an executable named file in the
|
||||
// directories named by the PATH environment variable.
|
||||
// If file contains a slash, it is tried directly and the PATH is not consulted.
|
||||
// Otherwise, on success, the result is an absolute path.
|
||||
//
|
||||
// In older versions of Go, LookPath could return a path relative to the current directory.
|
||||
// As of Go 1.19, LookPath will instead return that path along with an error satisfying
|
||||
// errors.Is(err, ErrDot). See the package documentation for more details.
|
||||
func LookPath(file string) (string, error) {
|
||||
/* TODO(xsw):
|
||||
// NOTE(rsc): I wish we could use the Plan 9 behavior here
|
||||
// (only bypass the path if file begins with / or ./ or ../)
|
||||
// but that would not match all the Unix shells.
|
||||
|
||||
if strings.Contains(file, "/") {
|
||||
err := findExecutable(file)
|
||||
if err == nil {
|
||||
return file, nil
|
||||
}
|
||||
return "", &Error{file, err}
|
||||
}
|
||||
path := os.Getenv("PATH")
|
||||
for _, dir := range filepath.SplitList(path) {
|
||||
if dir == "" {
|
||||
// Unix shell semantics: path element "" means "."
|
||||
dir = "."
|
||||
}
|
||||
path := filepath.Join(dir, file)
|
||||
if err := findExecutable(path); err == nil {
|
||||
if !filepath.IsAbs(path) {
|
||||
if execerrdot.Value() != "0" {
|
||||
return path, &Error{file, ErrDot}
|
||||
}
|
||||
execerrdot.IncNonDefault()
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
}
|
||||
return "", &Error{file, ErrNotFound}
|
||||
*/
|
||||
panic("todo: exec.LookPath")
|
||||
}
|
||||
23
internal/lib/os/exec/lp_wasm.go
Normal file
23
internal/lib/os/exec/lp_wasm.go
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright 2018 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 wasm
|
||||
|
||||
package exec
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// ErrNotFound is the error resulting if a path search failed to find an executable file.
|
||||
var ErrNotFound = errors.New("executable file not found in $PATH")
|
||||
|
||||
// LookPath searches for an executable named file in the
|
||||
// directories named by the PATH environment variable.
|
||||
// If file contains a slash, it is tried directly and the PATH is not consulted.
|
||||
// The result may be an absolute path or a path relative to the current directory.
|
||||
func LookPath(file string) (string, error) {
|
||||
// Wasm can not execute processes, so act as if there are no executables at all.
|
||||
return "", &Error{file, ErrNotFound}
|
||||
}
|
||||
145
internal/lib/os/exec/lp_windows.go
Normal file
145
internal/lib/os/exec/lp_windows.go
Normal file
@@ -0,0 +1,145 @@
|
||||
// Copyright 2010 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.
|
||||
|
||||
package exec
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// ErrNotFound is the error resulting if a path search failed to find an executable file.
|
||||
var ErrNotFound = errors.New("executable file not found in %PATH%")
|
||||
|
||||
func chkStat(file string) error {
|
||||
d, err := os.Stat(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.IsDir() {
|
||||
return fs.ErrPermission
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasExt(file string) bool {
|
||||
i := strings.LastIndex(file, ".")
|
||||
if i < 0 {
|
||||
return false
|
||||
}
|
||||
return strings.LastIndexAny(file, `:\/`) < i
|
||||
}
|
||||
|
||||
func findExecutable(file string, exts []string) (string, error) {
|
||||
if len(exts) == 0 {
|
||||
return file, chkStat(file)
|
||||
}
|
||||
if hasExt(file) {
|
||||
if chkStat(file) == nil {
|
||||
return file, nil
|
||||
}
|
||||
}
|
||||
for _, e := range exts {
|
||||
if f := file + e; chkStat(f) == nil {
|
||||
return f, nil
|
||||
}
|
||||
}
|
||||
return "", fs.ErrNotExist
|
||||
}
|
||||
|
||||
// LookPath searches for an executable named file in the
|
||||
// directories named by the PATH environment variable.
|
||||
// LookPath also uses PATHEXT environment variable to match
|
||||
// a suitable candidate.
|
||||
// If file contains a slash, it is tried directly and the PATH is not consulted.
|
||||
// Otherwise, on success, the result is an absolute path.
|
||||
//
|
||||
// In older versions of Go, LookPath could return a path relative to the current directory.
|
||||
// As of Go 1.19, LookPath will instead return that path along with an error satisfying
|
||||
// errors.Is(err, ErrDot). See the package documentation for more details.
|
||||
func LookPath(file string) (string, error) {
|
||||
var exts []string
|
||||
x := os.Getenv(`PATHEXT`)
|
||||
if x != "" {
|
||||
for _, e := range strings.Split(strings.ToLower(x), `;`) {
|
||||
if e == "" {
|
||||
continue
|
||||
}
|
||||
if e[0] != '.' {
|
||||
e = "." + e
|
||||
}
|
||||
exts = append(exts, e)
|
||||
}
|
||||
} else {
|
||||
exts = []string{".com", ".exe", ".bat", ".cmd"}
|
||||
}
|
||||
|
||||
if strings.ContainsAny(file, `:\/`) {
|
||||
f, err := findExecutable(file, exts)
|
||||
if err == nil {
|
||||
return f, nil
|
||||
}
|
||||
return "", &Error{file, err}
|
||||
}
|
||||
|
||||
// On Windows, creating the NoDefaultCurrentDirectoryInExePath
|
||||
// environment variable (with any value or no value!) signals that
|
||||
// path lookups should skip the current directory.
|
||||
// In theory we are supposed to call NeedCurrentDirectoryForExePathW
|
||||
// "as the registry location of this environment variable can change"
|
||||
// but that seems exceedingly unlikely: it would break all users who
|
||||
// have configured their environment this way!
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-needcurrentdirectoryforexepathw
|
||||
// See also go.dev/issue/43947.
|
||||
var (
|
||||
dotf string
|
||||
dotErr error
|
||||
)
|
||||
if _, found := syscall.Getenv("NoDefaultCurrentDirectoryInExePath"); !found {
|
||||
if f, err := findExecutable(filepath.Join(".", file), exts); err == nil {
|
||||
if execerrdot.Value() == "0" {
|
||||
execerrdot.IncNonDefault()
|
||||
return f, nil
|
||||
}
|
||||
dotf, dotErr = f, &Error{file, ErrDot}
|
||||
}
|
||||
}
|
||||
|
||||
path := os.Getenv("path")
|
||||
for _, dir := range filepath.SplitList(path) {
|
||||
if f, err := findExecutable(filepath.Join(dir, file), exts); err == nil {
|
||||
if dotErr != nil {
|
||||
// https://go.dev/issue/53536: if we resolved a relative path implicitly,
|
||||
// and it is the same executable that would be resolved from the explicit %PATH%,
|
||||
// prefer the explicit name for the executable (and, likely, no error) instead
|
||||
// of the equivalent implicit name with ErrDot.
|
||||
//
|
||||
// Otherwise, return the ErrDot for the implicit path as soon as we find
|
||||
// out that the explicit one doesn't match.
|
||||
dotfi, dotfiErr := os.Lstat(dotf)
|
||||
fi, fiErr := os.Lstat(f)
|
||||
if dotfiErr != nil || fiErr != nil || !os.SameFile(dotfi, fi) {
|
||||
return dotf, dotErr
|
||||
}
|
||||
}
|
||||
|
||||
if !filepath.IsAbs(f) {
|
||||
if execerrdot.Value() != "0" {
|
||||
return f, &Error{file, ErrDot}
|
||||
}
|
||||
execerrdot.IncNonDefault()
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
}
|
||||
|
||||
if dotErr != nil {
|
||||
return dotf, dotErr
|
||||
}
|
||||
return "", &Error{file, ErrNotFound}
|
||||
}
|
||||
149
internal/lib/os/exec_plan9.go
Normal file
149
internal/lib/os/exec_plan9.go
Normal file
@@ -0,0 +1,149 @@
|
||||
// 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.
|
||||
|
||||
package os
|
||||
|
||||
import (
|
||||
"internal/itoa"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The only signal values guaranteed to be present in the os package
|
||||
// on all systems are Interrupt (send the process an interrupt) and
|
||||
// Kill (force the process to exit). Interrupt is not implemented on
|
||||
// Windows; using it with os.Process.Signal will return an error.
|
||||
var (
|
||||
Interrupt Signal = syscall.Note("interrupt")
|
||||
Kill Signal = syscall.Note("kill")
|
||||
)
|
||||
|
||||
func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err error) {
|
||||
sysattr := &syscall.ProcAttr{
|
||||
Dir: attr.Dir,
|
||||
Env: attr.Env,
|
||||
Sys: attr.Sys,
|
||||
}
|
||||
|
||||
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)
|
||||
if e != nil {
|
||||
return nil, &PathError{Op: "fork/exec", Path: name, Err: e}
|
||||
}
|
||||
|
||||
return newProcess(pid, h), nil
|
||||
}
|
||||
|
||||
func (p *Process) writeProcFile(file string, data string) error {
|
||||
f, e := OpenFile("/proc/"+itoa.Itoa(p.Pid)+"/"+file, O_WRONLY, 0)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
defer f.Close()
|
||||
_, e = f.Write([]byte(data))
|
||||
return e
|
||||
}
|
||||
|
||||
func (p *Process) signal(sig Signal) error {
|
||||
if p.done() {
|
||||
return ErrProcessDone
|
||||
}
|
||||
if e := p.writeProcFile("note", sig.String()); e != nil {
|
||||
return NewSyscallError("signal", e)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Process) kill() error {
|
||||
return p.signal(Kill)
|
||||
}
|
||||
|
||||
func (p *Process) wait() (ps *ProcessState, err error) {
|
||||
var waitmsg syscall.Waitmsg
|
||||
|
||||
if p.Pid == -1 {
|
||||
return nil, ErrInvalid
|
||||
}
|
||||
err = syscall.WaitProcess(p.Pid, &waitmsg)
|
||||
if err != nil {
|
||||
return nil, NewSyscallError("wait", err)
|
||||
}
|
||||
|
||||
p.setDone()
|
||||
ps = &ProcessState{
|
||||
pid: waitmsg.Pid,
|
||||
status: &waitmsg,
|
||||
}
|
||||
return ps, nil
|
||||
}
|
||||
|
||||
func (p *Process) release() error {
|
||||
// NOOP for Plan 9.
|
||||
p.Pid = -1
|
||||
// no need for a finalizer anymore
|
||||
runtime.SetFinalizer(p, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func findProcess(pid int) (p *Process, err error) {
|
||||
// NOOP for Plan 9.
|
||||
return newProcess(pid, 0), nil
|
||||
}
|
||||
|
||||
// ProcessState stores information about a process, as reported by Wait.
|
||||
type ProcessState struct {
|
||||
pid int // The process's id.
|
||||
status *syscall.Waitmsg // System-dependent status info.
|
||||
}
|
||||
|
||||
// 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.status
|
||||
}
|
||||
|
||||
func (p *ProcessState) userTime() time.Duration {
|
||||
return time.Duration(p.status.Time[0]) * time.Millisecond
|
||||
}
|
||||
|
||||
func (p *ProcessState) systemTime() time.Duration {
|
||||
return time.Duration(p.status.Time[1]) * time.Millisecond
|
||||
}
|
||||
|
||||
func (p *ProcessState) String() string {
|
||||
if p == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
return "exit status: " + p.status.Msg
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
139
internal/lib/os/exec_posix.go
Normal file
139
internal/lib/os/exec_posix.go
Normal file
@@ -0,0 +1,139 @@
|
||||
// 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 (
|
||||
"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) {
|
||||
/* TODO(xsw):
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
sysattr := &syscall.ProcAttr{
|
||||
Dir: attr.Dir,
|
||||
Env: attr.Env,
|
||||
Sys: attr.Sys,
|
||||
}
|
||||
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}
|
||||
}
|
||||
|
||||
return newProcess(pid, h), nil
|
||||
*/
|
||||
panic("todo: os.startProcess")
|
||||
}
|
||||
|
||||
func (p *Process) kill() error {
|
||||
return p.Signal(Kill)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
/* TODO(xsw):
|
||||
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 " + 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 status.Continued():
|
||||
res = "continued"
|
||||
}
|
||||
if status.CoreDump() {
|
||||
res += " (core dumped)"
|
||||
}
|
||||
return res
|
||||
*/
|
||||
panic("todo: os.ProcessState.String")
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
109
internal/lib/os/exec_unix.go
Normal file
109
internal/lib/os/exec_unix.go
Normal file
@@ -0,0 +1,109 @@
|
||||
// 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"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (p *Process) wait() (ps *ProcessState, err error) {
|
||||
/* TODO(xsw):
|
||||
if p.Pid == -1 {
|
||||
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.signal will not send a signal.
|
||||
p.setDone()
|
||||
// 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 int
|
||||
e error
|
||||
)
|
||||
for {
|
||||
pid1, e = syscall.Wait4(p.Pid, &status, 0, &rusage)
|
||||
if e != syscall.EINTR {
|
||||
break
|
||||
}
|
||||
}
|
||||
if e != nil {
|
||||
return nil, NewSyscallError("wait", e)
|
||||
}
|
||||
if pid1 != 0 {
|
||||
p.setDone()
|
||||
}
|
||||
ps = &ProcessState{
|
||||
pid: pid1,
|
||||
status: status,
|
||||
rusage: &rusage,
|
||||
}
|
||||
return ps, nil
|
||||
*/
|
||||
panic("todo: os.Process.wait")
|
||||
}
|
||||
|
||||
func (p *Process) signal(sig Signal) error {
|
||||
if p.Pid == -1 {
|
||||
return errors.New("os: process already released")
|
||||
}
|
||||
if p.Pid == 0 {
|
||||
return errors.New("os: process not initialized")
|
||||
}
|
||||
p.sigMu.RLock()
|
||||
defer p.sigMu.RUnlock()
|
||||
if p.done() {
|
||||
return ErrProcessDone
|
||||
}
|
||||
s, ok := sig.(syscall.Signal)
|
||||
if !ok {
|
||||
return errors.New("os: unsupported signal type")
|
||||
}
|
||||
if e := syscall.Kill(p.Pid, s); e != nil {
|
||||
if e == syscall.ESRCH {
|
||||
return ErrProcessDone
|
||||
}
|
||||
return e
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Process) release() error {
|
||||
// NOOP for unix.
|
||||
p.Pid = -1
|
||||
// no need for a finalizer anymore
|
||||
runtime.SetFinalizer(p, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func findProcess(pid int) (p *Process, err error) {
|
||||
// NOOP for unix.
|
||||
return newProcess(pid, 0), 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
|
||||
}
|
||||
183
internal/lib/os/exec_windows.go
Normal file
183
internal/lib/os/exec_windows.go
Normal file
@@ -0,0 +1,183 @@
|
||||
// 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.
|
||||
|
||||
package os
|
||||
|
||||
import (
|
||||
"internal/syscall/windows"
|
||||
"runtime"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (p *Process) wait() (ps *ProcessState, err error) {
|
||||
/* TODO(xsw):
|
||||
handle := atomic.LoadUintptr(&p.handle)
|
||||
s, e := syscall.WaitForSingleObject(syscall.Handle(handle), syscall.INFINITE)
|
||||
switch s {
|
||||
case syscall.WAIT_OBJECT_0:
|
||||
break
|
||||
case syscall.WAIT_FAILED:
|
||||
return nil, NewSyscallError("WaitForSingleObject", e)
|
||||
default:
|
||||
return nil, errors.New("os: unexpected result from WaitForSingleObject")
|
||||
}
|
||||
var ec uint32
|
||||
e = syscall.GetExitCodeProcess(syscall.Handle(handle), &ec)
|
||||
if e != nil {
|
||||
return nil, NewSyscallError("GetExitCodeProcess", e)
|
||||
}
|
||||
var u syscall.Rusage
|
||||
e = syscall.GetProcessTimes(syscall.Handle(handle), &u.CreationTime, &u.ExitTime, &u.KernelTime, &u.UserTime)
|
||||
if e != nil {
|
||||
return nil, NewSyscallError("GetProcessTimes", e)
|
||||
}
|
||||
p.setDone()
|
||||
// NOTE(brainman): It seems that sometimes process is not dead
|
||||
// when WaitForSingleObject returns. But we do not know any
|
||||
// other way to wait for it. Sleeping for a while seems to do
|
||||
// the trick sometimes.
|
||||
// See https://golang.org/issue/25965 for details.
|
||||
defer time.Sleep(5 * time.Millisecond)
|
||||
defer p.Release()
|
||||
return &ProcessState{p.Pid, syscall.WaitStatus{ExitCode: ec}, &u}, nil
|
||||
*/
|
||||
panic("todo: os.Process.wait")
|
||||
}
|
||||
|
||||
func (p *Process) signal(sig Signal) error {
|
||||
handle := atomic.LoadUintptr(&p.handle)
|
||||
if handle == uintptr(syscall.InvalidHandle) {
|
||||
return syscall.EINVAL
|
||||
}
|
||||
if p.done() {
|
||||
return ErrProcessDone
|
||||
}
|
||||
if sig == Kill {
|
||||
var terminationHandle syscall.Handle
|
||||
e := syscall.DuplicateHandle(^syscall.Handle(0), syscall.Handle(handle), ^syscall.Handle(0), &terminationHandle, syscall.PROCESS_TERMINATE, false, 0)
|
||||
if e != nil {
|
||||
return NewSyscallError("DuplicateHandle", e)
|
||||
}
|
||||
runtime.KeepAlive(p)
|
||||
defer syscall.CloseHandle(terminationHandle)
|
||||
e = syscall.TerminateProcess(syscall.Handle(terminationHandle), 1)
|
||||
return NewSyscallError("TerminateProcess", e)
|
||||
}
|
||||
// TODO(rsc): Handle Interrupt too?
|
||||
return syscall.Errno(syscall.EWINDOWS)
|
||||
}
|
||||
|
||||
func (p *Process) release() error {
|
||||
handle := atomic.SwapUintptr(&p.handle, uintptr(syscall.InvalidHandle))
|
||||
if handle == uintptr(syscall.InvalidHandle) {
|
||||
return syscall.EINVAL
|
||||
}
|
||||
e := syscall.CloseHandle(syscall.Handle(handle))
|
||||
if e != nil {
|
||||
return NewSyscallError("CloseHandle", e)
|
||||
}
|
||||
// no need for a finalizer anymore
|
||||
runtime.SetFinalizer(p, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func findProcess(pid int) (p *Process, err error) {
|
||||
const da = syscall.STANDARD_RIGHTS_READ |
|
||||
syscall.PROCESS_QUERY_INFORMATION | syscall.SYNCHRONIZE
|
||||
h, e := syscall.OpenProcess(da, false, uint32(pid))
|
||||
if e != nil {
|
||||
return nil, NewSyscallError("OpenProcess", e)
|
||||
}
|
||||
return newProcess(pid, uintptr(h)), nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmd := windows.UTF16PtrToString(syscall.GetCommandLine())
|
||||
if len(cmd) == 0 {
|
||||
arg0, _ := Executable()
|
||||
Args = []string{arg0}
|
||||
} else {
|
||||
Args = commandLineToArgv(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
// appendBSBytes appends n '\\' bytes to b and returns the resulting slice.
|
||||
func appendBSBytes(b []byte, n int) []byte {
|
||||
for ; n > 0; n-- {
|
||||
b = append(b, '\\')
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// readNextArg splits command line string cmd into next
|
||||
// argument and command line remainder.
|
||||
func readNextArg(cmd string) (arg []byte, rest string) {
|
||||
var b []byte
|
||||
var inquote bool
|
||||
var nslash int
|
||||
for ; len(cmd) > 0; cmd = cmd[1:] {
|
||||
c := cmd[0]
|
||||
switch c {
|
||||
case ' ', '\t':
|
||||
if !inquote {
|
||||
return appendBSBytes(b, nslash), cmd[1:]
|
||||
}
|
||||
case '"':
|
||||
b = appendBSBytes(b, nslash/2)
|
||||
if nslash%2 == 0 {
|
||||
// use "Prior to 2008" rule from
|
||||
// http://daviddeley.com/autohotkey/parameters/parameters.htm
|
||||
// section 5.2 to deal with double double quotes
|
||||
if inquote && len(cmd) > 1 && cmd[1] == '"' {
|
||||
b = append(b, c)
|
||||
cmd = cmd[1:]
|
||||
}
|
||||
inquote = !inquote
|
||||
} else {
|
||||
b = append(b, c)
|
||||
}
|
||||
nslash = 0
|
||||
continue
|
||||
case '\\':
|
||||
nslash++
|
||||
continue
|
||||
}
|
||||
b = appendBSBytes(b, nslash)
|
||||
nslash = 0
|
||||
b = append(b, c)
|
||||
}
|
||||
return appendBSBytes(b, nslash), ""
|
||||
}
|
||||
|
||||
// commandLineToArgv splits a command line into individual argument
|
||||
// strings, following the Windows conventions documented
|
||||
// at http://daviddeley.com/autohotkey/parameters/parameters.htm#WINARGV
|
||||
func commandLineToArgv(cmd string) []string {
|
||||
var args []string
|
||||
for len(cmd) > 0 {
|
||||
if cmd[0] == ' ' || cmd[0] == '\t' {
|
||||
cmd = cmd[1:]
|
||||
continue
|
||||
}
|
||||
var arg []byte
|
||||
arg, cmd = readNextArg(cmd)
|
||||
args = append(args, string(arg))
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func ftToDuration(ft *syscall.Filetime) time.Duration {
|
||||
n := int64(ft.HighDateTime)<<32 + int64(ft.LowDateTime) // in 100-nanosecond intervals
|
||||
return time.Duration(n*100) * time.Nanosecond
|
||||
}
|
||||
|
||||
func (p *ProcessState) userTime() time.Duration {
|
||||
return ftToDuration(&p.rusage.UserTime)
|
||||
}
|
||||
|
||||
func (p *ProcessState) systemTime() time.Duration {
|
||||
return ftToDuration(&p.rusage.KernelTime)
|
||||
}
|
||||
@@ -147,7 +147,11 @@ func Clearenv()
|
||||
|
||||
// TODO(xsw):
|
||||
// func DirFS(dir string) fs.FS
|
||||
// func Environ() []string
|
||||
|
||||
func Environ() []string {
|
||||
panic("todo: os.Environ")
|
||||
}
|
||||
|
||||
// func Executable() (string, error)
|
||||
|
||||
func Exit(code int) {
|
||||
@@ -282,7 +286,10 @@ func Mkdir(name string, perm FileMode) error {
|
||||
// func MkdirAll(path string, perm FileMode) error
|
||||
// func MkdirTemp(dir, pattern string) (string, error)
|
||||
// func NewSyscallError(syscall string, err error) error
|
||||
// func Pipe() (r *File, w *File, err error)
|
||||
func Pipe() (r *File, w *File, err error) {
|
||||
panic("todo: os.Pipe")
|
||||
}
|
||||
|
||||
// func ReadFile(name string) ([]byte, error)
|
||||
|
||||
func Readlink(name string) (string, error) {
|
||||
|
||||
Reference in New Issue
Block a user