go exec 源码

2022-07-15 浏览 (902)

golang exec 代码

文件路径:/src/os/exec/exec.go

/ 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 exec runs external commands. It wraps os.StartProcess to make it
/ easier to remap stdin and stdout, connect I/O with pipes, and do other
/ adjustments.
/
/ Unlike the "system" library call from C and other languages, the
/ os/exec package intentionally does not invoke the system shell and
/ does not expand any glob patterns or handle other expansions,
/ pipelines, or redirections typically done by shells. The package
/ behaves more like C's "exec" family of functions. To expand glob
/ patterns, either call the shell directly, taking care to escape any
/ dangerous input, or use the path/filepath package's Glob function.
/ To expand environment variables, use package os's ExpandEnv.
/
/ Note that the examples in this package assume a Unix system.
/ They may not run on Windows, and they do not run in the Go Playground
/ used by golang.org and godoc.org.
/
/ # Executables in the current directory
/
/ The functions Command and LookPath look for a program
/ in the directories listed in the current path, following the
/ conventions of the host operating system.
/ Operating systems have for decades included the current
/ directory in this search, sometimes implicitly and sometimes
/ configured explicitly that way by default.
/ Modern practice is that including the current directory
/ is usually unexpected and often leads to security problems.
/
/ To avoid those security problems, as of Go 1.19, this package will not resolve a program
/ using an implicit or explicit path entry relative to the current directory.
/ That is, if you run exec.LookPath("go"), it will not successfully return
/ ./go on Unix nor .\go.exe on Windows, no matter how the path is configured.
/ Instead, if the usual path algorithms would result in that answer,
/ these functions return an error err satisfying errors.Is(err, ErrDot).
/
/ For example, consider these two program snippets:
/
/	path, err := exec.LookPath("prog")
/	if err != nil {
/		log.Fatal(err)
/	}
/	use(path)
/
/ and
/
/	cmd := exec.Command("prog")
/	if err := cmd.Run(); err != nil {
/		log.Fatal(err)
/	}
/
/ These will not find and run ./prog or .\prog.exe,
/ no matter how the current path is configured.
/
/ Code that always wants to run a program from the current directory
/ can be rewritten to say "./prog" instead of "prog".
/
/ Code that insists on including results from relative path entries
/ can instead override the error using an errors.Is check:
/
/	path, err := exec.LookPath("prog")
/	if errors.Is(err, exec.ErrDot) {
/		err = nil
/	}
/	if err != nil {
/		log.Fatal(err)
/	}
/	use(path)
/
/ and
/
/	cmd := exec.Command("prog")
/	if errors.Is(cmd.Err, exec.ErrDot) {
/		cmd.Err = nil
/	}
/	if err := cmd.Run(); err != nil {
/		log.Fatal(err)
/	}
/
/ Setting the environment variable GODEBUG=execerrdot=0
/ disables generation of ErrDot entirely, temporarily restoring the pre-Go 1.19
/ behavior for programs that are unable to apply more targeted fixes.
/ A future version of Go may remove support for this variable.
/
/ Before adding such overrides, make sure you understand the
/ security implications of doing so.
/ See https://go.dev/blog/path-security for more information.
package exec

import (
	"bytes"
	"context"
	"errors"
	"internal/syscall/execenv"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"syscall"
)

/ Error is returned by LookPath when it fails to classify a file as an
/ executable.
type Error struct {
	/ Name is the file name for which the error occurred.
	Name string
	/ Err is the underlying error.
	Err error
}

func (e *Error) Error() string {
	return "exec: " + strconv.Quote(e.Name) + ": " + e.Err.Error()
}

func (e *Error) Unwrap() error { return e.Err }

/ wrappedError wraps an error without relying on fmt.Errorf.
type wrappedError struct {
	prefix string
	err    error
}

func (w wrappedError) Error() string {
	return w.prefix + ": " + w.err.Error()
}

func (w wrappedError) Unwrap() error {
	return w.err
}

/ Cmd represents an external command being prepared or run.
/
/ A Cmd cannot be reused after calling its Run, Output or CombinedOutput
/ methods.
type Cmd struct {
	/ Path is the path of the command to run.
	/
	/ This is the only field that must be set to a non-zero
	/ value. If Path is relative, it is evaluated relative
	/ to Dir.
	Path string

	/ Args holds command line arguments, including the command as Args[0].
	/ If the Args field is empty or nil, Run uses {Path}.
	/
	/ In typical use, both Path and Args are set by calling Command.
	Args []string

	/ Env specifies the environment of the process.
	/ Each entry is of the form "key=value".
	/ If Env is nil, the new process uses the current process's
	/ environment.
	/ If Env contains duplicate environment keys, only the last
	/ value in the slice for each duplicate key is used.
	/ As a special case on Windows, SYSTEMROOT is always added if
	/ missing and not explicitly set to the empty string.
	Env []string

	/ Dir specifies the working directory of the command.
	/ If Dir is the empty string, Run runs the command in the
	/ calling process's current directory.
	Dir string

	/ Stdin specifies the process's standard input.
	/
	/ If Stdin is nil, the process reads from the null device (os.DevNull).
	/
	/ If Stdin is an *os.File, the process's standard input is connected
	/ directly to that file.
	/
	/ Otherwise, during the execution of the command a separate
	/ goroutine reads from Stdin and delivers that data to the command
	/ over a pipe. In this case, Wait does not complete until the goroutine
	/ stops copying, either because it has reached the end of Stdin
	/ (EOF or a read error) or because writing to the pipe returned an error.
	Stdin io.Reader

	/ Stdout and Stderr specify the process's standard output and error.
	/
	/ If either is nil, Run connects the corresponding file descriptor
	/ to the null device (os.DevNull).
	/
	/ If either is an *os.File, the corresponding output from the process
	/ is connected directly to that file.
	/
	/ Otherwise, during the execution of the command a separate goroutine
	/ reads from the process over a pipe and delivers that data to the
	/ corresponding Writer. In this case, Wait does not complete until the
	/ goroutine reaches EOF or encounters an error.
	/
	/ If Stdout and Stderr are the same writer, and have a type that can
	/ be compared with ==, at most one goroutine at a time will call Write.
	Stdout io.Writer
	Stderr io.Writer

	/ ExtraFiles specifies additional open files to be inherited by the
	/ new process. It does not include standard input, standard output, or
	/ standard error. If non-nil, entry i becomes file descriptor 3+i.
	/
	/ ExtraFiles is not supported on Windows.
	ExtraFiles []*os.File

	/ SysProcAttr holds optional, operating system-specific attributes.
	/ Run passes it to os.StartProcess as the os.ProcAttr's Sys field.
	SysProcAttr *syscall.SysProcAttr

	/ Process is the underlying process, once started.
	Process *os.Process

	/ ProcessState contains information about an exited process,
	/ available after a call to Wait or Run.
	ProcessState *os.ProcessState

	ctx             context.Context / nil means none
	Err             error           / LookPath error, if any.
	childFiles      []*os.File
	closeAfterStart []io.Closer
	closeAfterWait  []io.Closer
	goroutine       []func() error
	goroutineErrs   <-chan error / one receive per goroutine
	ctxErr          <-chan error / if non nil, receives the error from watchCtx exactly once

	/ For a security release long ago, we created x/sys/execabs,
	/ which manipulated the unexported lookPathErr error field
	/ in this struct. For Go 1.19 we exported the field as Err error,
	/ above, but we have to keep lookPathErr around for use by
	/ old programs building against new toolchains.
	/ The String and Start methods look for an error in lookPathErr
	/ in preference to Err, to preserve the errors that execabs sets.
	/
	/ In general we don't guarantee misuse of reflect like this,
	/ but the misuse of reflect was by us, the best of various bad
	/ options to fix the security problem, and people depend on
	/ those old copies of execabs continuing to work.
	/ The result is that we have to leave this variable around for the
	/ rest of time, a compatibility scar.
	/
	/ See https://go.dev/blog/path-security
	/ and https://go.dev/issue/43724 for more context.
	lookPathErr error
}

/ Command returns the Cmd struct to execute the named program with
/ the given arguments.
/
/ It sets only the Path and Args in the returned structure.
/
/ If name contains no path separators, Command uses LookPath to
/ resolve name to a complete path if possible. Otherwise it uses name
/ directly as Path.
/
/ The returned Cmd's Args field is constructed from the command name
/ followed by the elements of arg, so arg should not include the
/ command name itself. For example, Command("echo", "hello").
/ Args[0] is always name, not the possibly resolved Path.
/
/ On Windows, processes receive the whole command line as a single string
/ and do their own parsing. Command combines and quotes Args into a command
/ line string with an algorithm compatible with applications using
/ CommandLineToArgvW (which is the most common way). Notable exceptions are
/ msiexec.exe and cmd.exe (and thus, all batch files), which have a different
/ unquoting algorithm. In these or other similar cases, you can do the
/ quoting yourself and provide the full command line in SysProcAttr.CmdLine,
/ leaving Args empty.
func Command(name string, arg ...string) *Cmd {
	cmd := &Cmd{
		Path: name,
		Args: append([]string{name}, arg...),
	}
	if filepath.Base(name) == name {
		lp, err := LookPath(name)
		if lp != "" {
			/ Update cmd.Path even if err is non-nil.
			/ If err is ErrDot (especially on Windows), lp may include a resolved
			/ extension (like .exe or .bat) that should be preserved.
			cmd.Path = lp
		}
		if err != nil {
			cmd.Err = err
		}
	}
	return cmd
}

/ CommandContext is like Command but includes a context.
/
/ The provided context is used to kill the process (by calling
/ os.Process.Kill) if the context becomes done before the command
/ completes on its own.
func CommandContext(ctx context.Context, name string, arg ...string) *Cmd {
	if ctx == nil {
		panic("nil Context")
	}
	cmd := Command(name, arg...)
	cmd.ctx = ctx
	return cmd
}

/ String returns a human-readable description of c.
/ It is intended only for debugging.
/ In particular, it is not suitable for use as input to a shell.
/ The output of String may vary across Go releases.
func (c *Cmd) String() string {
	if c.Err != nil || c.lookPathErr != nil {
		/ failed to resolve path; report the original requested path (plus args)
		return strings.Join(c.Args, " ")
	}
	/ report the exact executable path (plus args)
	b := new(strings.Builder)
	b.WriteString(c.Path)
	for _, a := range c.Args[1:] {
		b.WriteByte(' ')
		b.WriteString(a)
	}
	return b.String()
}

/ interfaceEqual protects against panics from doing equality tests on
/ two interfaces with non-comparable underlying types.
func interfaceEqual(a, b any) bool {
	defer func() {
		recover()
	}()
	return a == b
}

func (c *Cmd) argv() []string {
	if len(c.Args) > 0 {
		return c.Args
	}
	return []string{c.Path}
}

func (c *Cmd) stdin() (f *os.File, err error) {
	if c.Stdin == nil {
		f, err = os.Open(os.DevNull)
		if err != nil {
			return
		}
		c.closeAfterStart = append(c.closeAfterStart, f)
		return
	}

	if f, ok := c.Stdin.(*os.File); ok {
		return f, nil
	}

	pr, pw, err := os.Pipe()
	if err != nil {
		return
	}

	c.closeAfterStart = append(c.closeAfterStart, pr)
	c.closeAfterWait = append(c.closeAfterWait, pw)
	c.goroutine = append(c.goroutine, func() error {
		_, err := io.Copy(pw, c.Stdin)
		if skipStdinCopyError(err) {
			err = nil
		}
		if err1 := pw.Close(); err == nil {
			err = err1
		}
		return err
	})
	return pr, nil
}

func (c *Cmd) stdout() (f *os.File, err error) {
	return c.writerDescriptor(c.Stdout)
}

func (c *Cmd) stderr() (f *os.File, err error) {
	if c.Stderr != nil && interfaceEqual(c.Stderr, c.Stdout) {
		return c.childFiles[1], nil
	}
	return c.writerDescriptor(c.Stderr)
}

func (c *Cmd) writerDescriptor(w io.Writer) (f *os.File, err error) {
	if w == nil {
		f, err = os.OpenFile(os.DevNull, os.O_WRONLY, 0)
		if err != nil {
			return
		}
		c.closeAfterStart = append(c.closeAfterStart, f)
		return
	}

	if f, ok := w.(*os.File); ok {
		return f, nil
	}

	pr, pw, err := os.Pipe()
	if err != nil {
		return
	}

	c.closeAfterStart = append(c.closeAfterStart, pw)
	c.closeAfterWait = append(c.closeAfterWait, pr)
	c.goroutine = append(c.goroutine, func() error {
		_, err := io.Copy(w, pr)
		pr.Close() / in case io.Copy stopped due to write error
		return err
	})
	return pw, nil
}

func (c *Cmd) closeDescriptors(closers []io.Closer) {
	for _, fd := range closers {
		fd.Close()
	}
}

/ Run starts the specified command and waits for it to complete.
/
/ The returned error is nil if the command runs, has no problems
/ copying stdin, stdout, and stderr, and exits with a zero exit
/ status.
/
/ If the command starts but does not complete successfully, the error is of
/ type *ExitError. Other error types may be returned for other situations.
/
/ 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.
func (c *Cmd) Run() error {
	if err := c.Start(); err != nil {
		return err
	}
	return c.Wait()
}

/ lookExtensions finds windows executable by its dir and path.
/ It uses LookPath to try appropriate extensions.
/ lookExtensions does not search PATH, instead it converts `prog` into `.\prog`.
func lookExtensions(path, dir string) (string, error) {
	if filepath.Base(path) == path {
		path = "." + string(filepath.Separator) + path
	}
	if dir == "" {
		return LookPath(path)
	}
	if filepath.VolumeName(path) != "" {
		return LookPath(path)
	}
	if len(path) > 1 && os.IsPathSeparator(path[0]) {
		return LookPath(path)
	}
	dirandpath := filepath.Join(dir, path)
	/ We assume that LookPath will only add file extension.
	lp, err := LookPath(dirandpath)
	if err != nil {
		return "", err
	}
	ext := strings.TrimPrefix(lp, dirandpath)
	return path + ext, nil
}

/ Start starts the specified command but does not wait for it to complete.
/
/ If Start returns successfully, the c.Process field will be set.
/
/ After a successful call to Start the Wait method must be called in
/ order to release associated system resources.
func (c *Cmd) Start() error {
	if c.Path == "" && c.Err == nil && c.lookPathErr == nil {
		c.Err = errors.New("exec: no command")
	}
	if c.Err != nil || c.lookPathErr != nil {
		c.closeDescriptors(c.closeAfterStart)
		c.closeDescriptors(c.closeAfterWait)
		if c.lookPathErr != nil {
			return c.lookPathErr
		}
		return c.Err
	}
	if runtime.GOOS == "windows" {
		lp, err := lookExtensions(c.Path, c.Dir)
		if err != nil {
			c.closeDescriptors(c.closeAfterStart)
			c.closeDescriptors(c.closeAfterWait)
			return err
		}
		c.Path = lp
	}
	if c.Process != nil {
		return errors.New("exec: already started")
	}
	if c.ctx != nil {
		select {
		case <-c.ctx.Done():
			c.closeDescriptors(c.closeAfterStart)
			c.closeDescriptors(c.closeAfterWait)
			return c.ctx.Err()
		default:
		}
	}

	c.childFiles = make([]*os.File, 0, 3+len(c.ExtraFiles))
	type F func(*Cmd) (*os.File, error)
	for _, setupFd := range []F{(*Cmd).stdin, (*Cmd).stdout, (*Cmd).stderr} {
		fd, err := setupFd(c)
		if err != nil {
			c.closeDescriptors(c.closeAfterStart)
			c.closeDescriptors(c.closeAfterWait)
			return err
		}
		c.childFiles = append(c.childFiles, fd)
	}
	c.childFiles = append(c.childFiles, c.ExtraFiles...)

	env, err := c.environ()
	if err != nil {
		return err
	}

	c.Process, err = os.StartProcess(c.Path, c.argv(), &os.ProcAttr{
		Dir:   c.Dir,
		Files: c.childFiles,
		Env:   env,
		Sys:   c.SysProcAttr,
	})
	if err != nil {
		c.closeDescriptors(c.closeAfterStart)
		c.closeDescriptors(c.closeAfterWait)
		return err
	}

	c.closeDescriptors(c.closeAfterStart)

	/ Don't allocate the goroutineErrs channel unless there are goroutines to fire.
	if len(c.goroutine) > 0 {
		errc := make(chan error, len(c.goroutine))
		c.goroutineErrs = errc
		for _, fn := range c.goroutine {
			go func(fn func() error) {
				errc <- fn()
			}(fn)
		}
	}

	c.ctxErr = c.watchCtx()

	return nil
}

/ An ExitError reports an unsuccessful exit by a command.
type ExitError struct {
	*os.ProcessState

	/ Stderr holds a subset of the standard error output from the
	/ Cmd.Output method if standard error was not otherwise being
	/ collected.
	/
	/ If the error output is long, Stderr may contain only a prefix
	/ and suffix of the output, with the middle replaced with
	/ text about the number of omitted bytes.
	/
	/ Stderr is provided for debugging, for inclusion in error messages.
	/ Users with other needs should redirect Cmd.Stderr as needed.
	Stderr []byte
}

func (e *ExitError) Error() string {
	return e.ProcessState.String()
}

/ Wait waits for the command to exit and waits for any copying to
/ stdin or copying from stdout or stderr to complete.
/
/ The command must have been started by Start.
/
/ The returned error is nil if the command runs, has no problems
/ copying stdin, stdout, and stderr, and exits with a zero exit
/ status.
/
/ If the command fails to run or doesn't complete successfully, the
/ error is of type *ExitError. Other error types may be
/ returned for I/O problems.
/
/ If any of c.Stdin, c.Stdout or c.Stderr are not an *os.File, Wait also waits
/ for the respective I/O loop copying to or from the process to complete.
/
/ Wait releases any resources associated with the Cmd.
func (c *Cmd) Wait() error {
	if c.Process == nil {
		return errors.New("exec: not started")
	}
	if c.ProcessState != nil {
		return errors.New("exec: Wait was already called")
	}
	state, err := c.Process.Wait()
	if err == nil && !state.Success() {
		err = &ExitError{ProcessState: state}
	}
	c.ProcessState = state

	/ Wait for the pipe-copying goroutines to complete.
	var copyError error
	for range c.goroutine {
		if err := <-c.goroutineErrs; err != nil && copyError == nil {
			copyError = err
		}
	}
	c.goroutine = nil / Allow the goroutines' closures to be GC'd.

	if c.ctxErr != nil {
		interruptErr := <-c.ctxErr
		/ If c.Process.Wait returned an error, prefer that.
		/ Otherwise, report any error from the interrupt goroutine.
		if interruptErr != nil && err == nil {
			err = interruptErr
		}
	}
	/ Report errors from the copying goroutines only if the program otherwise
	/ exited normally on its own. Otherwise, the copying error may be due to the
	/ abnormal termination.
	if err == nil {
		err = copyError
	}

	c.closeDescriptors(c.closeAfterWait)
	c.closeAfterWait = nil

	return err
}

/ watchCtx conditionally starts a goroutine that waits until either c.ctx is
/ done or c.Process.Wait has completed (called from Wait).
/ If c.ctx is done first, the goroutine terminates c.Process.
/
/ If a goroutine was started, watchCtx returns a channel on which its result
/ must be received.
func (c *Cmd) watchCtx() <-chan error {
	if c.ctx == nil {
		return nil
	}

	errc := make(chan error)
	go func() {
		select {
		case errc <- nil:
			return
		case <-c.ctx.Done():
		}

		var err error
		if killErr := c.Process.Kill(); killErr == nil {
			/ We appear to have successfully delivered a kill signal, so any
			/ program behavior from this point may be due to ctx.
			err = c.ctx.Err()
		} else if !errors.Is(killErr, os.ErrProcessDone) {
			err = wrappedError{
				prefix: "exec: error sending signal to Cmd",
				err:    killErr,
			}
		}
		errc <- err
	}()

	return errc
}

/ Output runs the command and returns its standard output.
/ Any returned error will usually be of type *ExitError.
/ If c.Stderr was nil, Output populates ExitError.Stderr.
func (c *Cmd) Output() ([]byte, error) {
	if c.Stdout != nil {
		return nil, errors.New("exec: Stdout already set")
	}
	var stdout bytes.Buffer
	c.Stdout = &stdout

	captureErr := c.Stderr == nil
	if captureErr {
		c.Stderr = &prefixSuffixSaver{N: 32 << 10}
	}

	err := c.Run()
	if err != nil && captureErr {
		if ee, ok := err.(*ExitError); ok {
			ee.Stderr = c.Stderr.(*prefixSuffixSaver).Bytes()
		}
	}
	return stdout.Bytes(), err
}

/ CombinedOutput runs the command and returns its combined standard
/ output and standard error.
func (c *Cmd) CombinedOutput() ([]byte, error) {
	if c.Stdout != nil {
		return nil, errors.New("exec: Stdout already set")
	}
	if c.Stderr != nil {
		return nil, errors.New("exec: Stderr already set")
	}
	var b bytes.Buffer
	c.Stdout = &b
	c.Stderr = &b
	err := c.Run()
	return b.Bytes(), err
}

/ StdinPipe returns a pipe that will be connected to the command's
/ standard input when the command starts.
/ The pipe will be closed automatically after Wait sees the command exit.
/ A caller need only call Close to force the pipe to close sooner.
/ For example, if the command being run will not exit until standard input
/ is closed, the caller must close the pipe.
func (c *Cmd) StdinPipe() (io.WriteCloser, error) {
	if c.Stdin != nil {
		return nil, errors.New("exec: Stdin already set")
	}
	if c.Process != nil {
		return nil, errors.New("exec: StdinPipe after process started")
	}
	pr, pw, err := os.Pipe()
	if err != nil {
		return nil, err
	}
	c.Stdin = pr
	c.closeAfterStart = append(c.closeAfterStart, pr)
	wc := &closeOnce{File: pw}
	c.closeAfterWait = append(c.closeAfterWait, wc)
	return wc, nil
}

type closeOnce struct {
	*os.File

	once sync.Once
	err  error
}

func (c *closeOnce) Close() error {
	c.once.Do(c.close)
	return c.err
}

func (c *closeOnce) close() {
	c.err = c.File.Close()
}

/ StdoutPipe returns a pipe that will be connected to the command's
/ standard output when the command starts.
/
/ Wait will close the pipe after seeing the command exit, so most callers
/ need not close the pipe themselves. It is thus incorrect to call Wait
/ before all reads from the pipe have completed.
/ For the same reason, it is incorrect to call Run when using StdoutPipe.
/ See the example for idiomatic usage.
func (c *Cmd) StdoutPipe() (io.ReadCloser, error) {
	if c.Stdout != nil {
		return nil, errors.New("exec: Stdout already set")
	}
	if c.Process != nil {
		return nil, errors.New("exec: StdoutPipe after process started")
	}
	pr, pw, err := os.Pipe()
	if err != nil {
		return nil, err
	}
	c.Stdout = pw
	c.closeAfterStart = append(c.closeAfterStart, pw)
	c.closeAfterWait = append(c.closeAfterWait, pr)
	return pr, nil
}

/ StderrPipe returns a pipe that will be connected to the command's
/ standard error when the command starts.
/
/ Wait will close the pipe after seeing the command exit, so most callers
/ need not close the pipe themselves. It is thus incorrect to call Wait
/ before all reads from the pipe have completed.
/ For the same reason, it is incorrect to use Run when using StderrPipe.
/ See the StdoutPipe example for idiomatic usage.
func (c *Cmd) StderrPipe() (io.ReadCloser, error) {
	if c.Stderr != nil {
		return nil, errors.New("exec: Stderr already set")
	}
	if c.Process != nil {
		return nil, errors.New("exec: StderrPipe after process started")
	}
	pr, pw, err := os.Pipe()
	if err != nil {
		return nil, err
	}
	c.Stderr = pw
	c.closeAfterStart = append(c.closeAfterStart, pw)
	c.closeAfterWait = append(c.closeAfterWait, pr)
	return pr, nil
}

/ prefixSuffixSaver is an io.Writer which retains the first N bytes
/ and the last N bytes written to it. The Bytes() methods reconstructs
/ it with a pretty error message.
type prefixSuffixSaver struct {
	N         int / max size of prefix or suffix
	prefix    []byte
	suffix    []byte / ring buffer once len(suffix) == N
	suffixOff int    / offset to write into suffix
	skipped   int64

	/ TODO(bradfitz): we could keep one large []byte and use part of it for
	/ the prefix, reserve space for the '... Omitting N bytes ...' message,
	/ then the ring buffer suffix, and just rearrange the ring buffer
	/ suffix when Bytes() is called, but it doesn't seem worth it for
	/ now just for error messages. It's only ~64KB anyway.
}

func (w *prefixSuffixSaver) Write(p []byte) (n int, err error) {
	lenp := len(p)
	p = w.fill(&w.prefix, p)

	/ Only keep the last w.N bytes of suffix data.
	if overage := len(p) - w.N; overage > 0 {
		p = p[overage:]
		w.skipped += int64(overage)
	}
	p = w.fill(&w.suffix, p)

	/ w.suffix is full now if p is non-empty. Overwrite it in a circle.
	for len(p) > 0 { / 0, 1, or 2 iterations.
		n := copy(w.suffix[w.suffixOff:], p)
		p = p[n:]
		w.skipped += int64(n)
		w.suffixOff += n
		if w.suffixOff == w.N {
			w.suffixOff = 0
		}
	}
	return lenp, nil
}

/ fill appends up to len(p) bytes of p to *dst, such that *dst does not
/ grow larger than w.N. It returns the un-appended suffix of p.
func (w *prefixSuffixSaver) fill(dst *[]byte, p []byte) (pRemain []byte) {
	if remain := w.N - len(*dst); remain > 0 {
		add := minInt(len(p), remain)
		*dst = append(*dst, p[:add]...)
		p = p[add:]
	}
	return p
}

func (w *prefixSuffixSaver) Bytes() []byte {
	if w.suffix == nil {
		return w.prefix
	}
	if w.skipped == 0 {
		return append(w.prefix, w.suffix...)
	}
	var buf bytes.Buffer
	buf.Grow(len(w.prefix) + len(w.suffix) + 50)
	buf.Write(w.prefix)
	buf.WriteString("\n... omitting ")
	buf.WriteString(strconv.FormatInt(w.skipped, 10))
	buf.WriteString(" bytes ...\n")
	buf.Write(w.suffix[w.suffixOff:])
	buf.Write(w.suffix[:w.suffixOff])
	return buf.Bytes()
}

func minInt(a, b int) int {
	if a < b {
		return a
	}
	return b
}

/ environ returns a best-effort copy of the environment in which the command
/ would be run as it is currently configured. If an error occurs in computing
/ the environment, it is returned alongside the best-effort copy.
func (c *Cmd) environ() ([]string, error) {
	var err error

	env := c.Env
	if env == nil {
		env, err = execenv.Default(c.SysProcAttr)
		if err != nil {
			env = os.Environ()
			/ Note that the non-nil err is preserved despite env being overridden.
		}

		if c.Dir != "" {
			switch runtime.GOOS {
			case "windows", "plan9":
				/ Windows and Plan 9 do not use the PWD variable, so we don't need to
				/ keep it accurate.
			default:
				/ On POSIX platforms, PWD represents “an absolute pathname of the
				/ current working directory.” Since we are changing the working
				/ directory for the command, we should also update PWD to reflect that.
				/
				/ Unfortunately, we didn't always do that, so (as proposed in
				/ https://go.dev/issue/50599) to avoid unintended collateral damage we
				/ only implicitly update PWD when Env is nil. That way, we're much
				/ less likely to override an intentional change to the variable.
				if pwd, absErr := filepath.Abs(c.Dir); absErr == nil {
					env = append(env, "PWD="+pwd)
				} else if err == nil {
					err = absErr
				}
			}
		}
	}

	return addCriticalEnv(dedupEnv(env)), err
}

/ Environ returns a copy of the environment in which the command would be run
/ as it is currently configured.
func (c *Cmd) Environ() []string {
	/  Intentionally ignore errors: environ returns a best-effort environment no matter what.
	env, _ := c.environ()
	return env
}

/ dedupEnv returns a copy of env with any duplicates removed, in favor of
/ later values.
/ Items not of the normal environment "key=value" form are preserved unchanged.
func dedupEnv(env []string) []string {
	return dedupEnvCase(runtime.GOOS == "windows", env)
}

/ dedupEnvCase is dedupEnv with a case option for testing.
/ If caseInsensitive is true, the case of keys is ignored.
func dedupEnvCase(caseInsensitive bool, env []string) []string {
	/ Construct the output in reverse order, to preserve the
	/ last occurrence of each key.
	out := make([]string, 0, len(env))
	saw := make(map[string]bool, len(env))
	for n := len(env); n > 0; n-- {
		kv := env[n-1]

		i := strings.Index(kv, "=")
		if i == 0 {
			/ We observe in practice keys with a single leading "=" on Windows.
			/ TODO(#49886): Should we consume only the first leading "=" as part
			/ of the key, or parse through arbitrarily many of them until a non-"="?
			i = strings.Index(kv[1:], "=") + 1
		}
		if i < 0 {
			if kv != "" {
				/ The entry is not of the form "key=value" (as it is required to be).
				/ Leave it as-is for now.
				/ TODO(#52436): should we strip or reject these bogus entries?
				out = append(out, kv)
			}
			continue
		}
		k := kv[:i]
		if caseInsensitive {
			k = strings.ToLower(k)
		}
		if saw[k] {
			continue
		}

		saw[k] = true
		out = append(out, kv)
	}

	/ Now reverse the slice to restore the original order.
	for i := 0; i < len(out)/2; i++ {
		j := len(out) - i - 1
		out[i], out[j] = out[j], out[i]
	}

	return out
}

/ addCriticalEnv adds any critical environment variables that are required
/ (or at least almost always required) on the operating system.
/ Currently this is only used for Windows.
func addCriticalEnv(env []string) []string {
	if runtime.GOOS != "windows" {
		return env
	}
	for _, kv := range env {
		k, _, ok := strings.Cut(kv, "=")
		if !ok {
			continue
		}
		if strings.EqualFold(k, "SYSTEMROOT") {
			/ We already have it.
			return env
		}
	}
	return append(env, "SYSTEMROOT="+os.Getenv("SYSTEMROOT"))
}

/ ErrDot indicates that a path lookup resolved to an executable
/ in the current directory due to ‘.’ being in the path, either
/ implicitly or explicitly. See the package documentation for details.
/
/ Note that functions in this package do not return ErrDot directly.
/ Code should use errors.Is(err, ErrDot), not err == ErrDot,
/ to test whether a returned error err is due to this condition.
var ErrDot = errors.New("cannot run executable found relative to current directory")

相关信息

go 源码目录

相关文章

go bench_test 源码

go dot_test 源码

go env_test 源码

go example_test 源码

go exec_linux_test 源码

go exec_plan9 源码

go exec_posix_test 源码

go exec_test 源码

go exec_unix 源码

go exec_windows 源码

^