// 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) // } // // 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")