1// Copyright 2009 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5// Package exec runs external commands. It wraps os.StartProcess to make it 6// easier to remap stdin and stdout, connect I/O with pipes, and do other 7// adjustments. 8// 9// Unlike the "system" library call from C and other languages, the 10// os/exec package intentionally does not invoke the system shell and 11// does not expand any glob patterns or handle other expansions, 12// pipelines, or redirections typically done by shells. The package 13// behaves more like C's "exec" family of functions. To expand glob 14// patterns, either call the shell directly, taking care to escape any 15// dangerous input, or use the path/filepath package's Glob function. 16// To expand environment variables, use package os's ExpandEnv. 17// 18// Note that the examples in this package assume a Unix system. 19// They may not run on Windows, and they do not run in the Go Playground 20// used by golang.org and godoc.org. 21package exec 22 23import ( 24 "bytes" 25 "context" 26 "errors" 27 "io" 28 "os" 29 "path/filepath" 30 "runtime" 31 "strconv" 32 "strings" 33 "sync" 34 "syscall" 35) 36 37// Error is returned by LookPath when it fails to classify a file as an 38// executable. 39type Error struct { 40 // Name is the file name for which the error occurred. 41 Name string 42 // Err is the underlying error. 43 Err error 44} 45 46func (e *Error) Error() string { 47 return "exec: " + strconv.Quote(e.Name) + ": " + e.Err.Error() 48} 49 50func (e *Error) Unwrap() error { return e.Err } 51 52// Cmd represents an external command being prepared or run. 53// 54// A Cmd cannot be reused after calling its Run, Output or CombinedOutput 55// methods. 56type Cmd struct { 57 // Path is the path of the command to run. 58 // 59 // This is the only field that must be set to a non-zero 60 // value. If Path is relative, it is evaluated relative 61 // to Dir. 62 Path string 63 64 // Args holds command line arguments, including the command as Args[0]. 65 // If the Args field is empty or nil, Run uses {Path}. 66 // 67 // In typical use, both Path and Args are set by calling Command. 68 Args []string 69 70 // Env specifies the environment of the process. 71 // Each entry is of the form "key=value". 72 // If Env is nil, the new process uses the current process's 73 // environment. 74 // If Env contains duplicate environment keys, only the last 75 // value in the slice for each duplicate key is used. 76 // As a special case on Windows, SYSTEMROOT is always added if 77 // missing and not explicitly set to the empty string. 78 Env []string 79 80 // Dir specifies the working directory of the command. 81 // If Dir is the empty string, Run runs the command in the 82 // calling process's current directory. 83 Dir string 84 85 // Stdin specifies the process's standard input. 86 // 87 // If Stdin is nil, the process reads from the null device (os.DevNull). 88 // 89 // If Stdin is an *os.File, the process's standard input is connected 90 // directly to that file. 91 // 92 // Otherwise, during the execution of the command a separate 93 // goroutine reads from Stdin and delivers that data to the command 94 // over a pipe. In this case, Wait does not complete until the goroutine 95 // stops copying, either because it has reached the end of Stdin 96 // (EOF or a read error) or because writing to the pipe returned an error. 97 Stdin io.Reader 98 99 // Stdout and Stderr specify the process's standard output and error. 100 // 101 // If either is nil, Run connects the corresponding file descriptor 102 // to the null device (os.DevNull). 103 // 104 // If either is an *os.File, the corresponding output from the process 105 // is connected directly to that file. 106 // 107 // Otherwise, during the execution of the command a separate goroutine 108 // reads from the process over a pipe and delivers that data to the 109 // corresponding Writer. In this case, Wait does not complete until the 110 // goroutine reaches EOF or encounters an error. 111 // 112 // If Stdout and Stderr are the same writer, and have a type that can 113 // be compared with ==, at most one goroutine at a time will call Write. 114 Stdout io.Writer 115 Stderr io.Writer 116 117 // ExtraFiles specifies additional open files to be inherited by the 118 // new process. It does not include standard input, standard output, or 119 // standard error. If non-nil, entry i becomes file descriptor 3+i. 120 // 121 // ExtraFiles is not supported on Windows. 122 ExtraFiles []*os.File 123 124 // SysProcAttr holds optional, operating system-specific attributes. 125 // Run passes it to os.StartProcess as the os.ProcAttr's Sys field. 126 SysProcAttr *syscall.SysProcAttr 127 128 // Process is the underlying process, once started. 129 Process *os.Process 130 131 // ProcessState contains information about an exited process, 132 // available after a call to Wait or Run. 133 ProcessState *os.ProcessState 134 135 ctx context.Context // nil means none 136 lookPathErr error // LookPath error, if any. 137 finished bool // when Wait was called 138 childFiles []*os.File 139 closeAfterStart []io.Closer 140 closeAfterWait []io.Closer 141 goroutine []func() error 142 errch chan error // one send per goroutine 143 waitDone chan struct{} 144} 145 146// Command returns the Cmd struct to execute the named program with 147// the given arguments. 148// 149// It sets only the Path and Args in the returned structure. 150// 151// If name contains no path separators, Command uses LookPath to 152// resolve name to a complete path if possible. Otherwise it uses name 153// directly as Path. 154// 155// The returned Cmd's Args field is constructed from the command name 156// followed by the elements of arg, so arg should not include the 157// command name itself. For example, Command("echo", "hello"). 158// Args[0] is always name, not the possibly resolved Path. 159// 160// On Windows, processes receive the whole command line as a single string 161// and do their own parsing. Command combines and quotes Args into a command 162// line string with an algorithm compatible with applications using 163// CommandLineToArgvW (which is the most common way). Notable exceptions are 164// msiexec.exe and cmd.exe (and thus, all batch files), which have a different 165// unquoting algorithm. In these or other similar cases, you can do the 166// quoting yourself and provide the full command line in SysProcAttr.CmdLine, 167// leaving Args empty. 168func Command(name string, arg ...string) *Cmd { 169 cmd := &Cmd{ 170 Path: name, 171 Args: append([]string{name}, arg...), 172 } 173 if filepath.Base(name) == name { 174 if lp, err := LookPath(name); err != nil { 175 cmd.lookPathErr = err 176 } else { 177 cmd.Path = lp 178 } 179 } 180 return cmd 181} 182 183// CommandContext is like Command but includes a context. 184// 185// The provided context is used to kill the process (by calling 186// os.Process.Kill) if the context becomes done before the command 187// completes on its own. 188func CommandContext(ctx context.Context, name string, arg ...string) *Cmd { 189 if ctx == nil { 190 panic("nil Context") 191 } 192 cmd := Command(name, arg...) 193 cmd.ctx = ctx 194 return cmd 195} 196 197// String returns a human-readable description of c. 198// It is intended only for debugging. 199// In particular, it is not suitable for use as input to a shell. 200// The output of String may vary across Go releases. 201func (c *Cmd) String() string { 202 if c.lookPathErr != nil { 203 // failed to resolve path; report the original requested path (plus args) 204 return strings.Join(c.Args, " ") 205 } 206 // report the exact executable path (plus args) 207 b := new(strings.Builder) 208 b.WriteString(c.Path) 209 for _, a := range c.Args[1:] { 210 b.WriteByte(' ') 211 b.WriteString(a) 212 } 213 return b.String() 214} 215 216// interfaceEqual protects against panics from doing equality tests on 217// two interfaces with non-comparable underlying types. 218func interfaceEqual(a, b interface{}) bool { 219 defer func() { 220 recover() 221 }() 222 return a == b 223} 224 225func (c *Cmd) envv() ([]string, error) { 226 if c.Env != nil { 227 return c.Env, nil 228 } 229 return execenvDefault(c.SysProcAttr) 230} 231 232func (c *Cmd) argv() []string { 233 if len(c.Args) > 0 { 234 return c.Args 235 } 236 return []string{c.Path} 237} 238 239// skipStdinCopyError optionally specifies a function which reports 240// whether the provided stdin copy error should be ignored. 241var skipStdinCopyError func(error) bool 242 243func (c *Cmd) stdin() (f *os.File, err error) { 244 if c.Stdin == nil { 245 f, err = os.Open(os.DevNull) 246 if err != nil { 247 return 248 } 249 c.closeAfterStart = append(c.closeAfterStart, f) 250 return 251 } 252 253 if f, ok := c.Stdin.(*os.File); ok { 254 return f, nil 255 } 256 257 pr, pw, err := os.Pipe() 258 if err != nil { 259 return 260 } 261 262 c.closeAfterStart = append(c.closeAfterStart, pr) 263 c.closeAfterWait = append(c.closeAfterWait, pw) 264 c.goroutine = append(c.goroutine, func() error { 265 _, err := io.Copy(pw, c.Stdin) 266 if skip := skipStdinCopyError; skip != nil && skip(err) { 267 err = nil 268 } 269 if err1 := pw.Close(); err == nil { 270 err = err1 271 } 272 return err 273 }) 274 return pr, nil 275} 276 277func (c *Cmd) stdout() (f *os.File, err error) { 278 return c.writerDescriptor(c.Stdout) 279} 280 281func (c *Cmd) stderr() (f *os.File, err error) { 282 if c.Stderr != nil && interfaceEqual(c.Stderr, c.Stdout) { 283 return c.childFiles[1], nil 284 } 285 return c.writerDescriptor(c.Stderr) 286} 287 288func (c *Cmd) writerDescriptor(w io.Writer) (f *os.File, err error) { 289 if w == nil { 290 f, err = os.OpenFile(os.DevNull, os.O_WRONLY, 0) 291 if err != nil { 292 return 293 } 294 c.closeAfterStart = append(c.closeAfterStart, f) 295 return 296 } 297 298 if f, ok := w.(*os.File); ok { 299 return f, nil 300 } 301 302 pr, pw, err := os.Pipe() 303 if err != nil { 304 return 305 } 306 307 c.closeAfterStart = append(c.closeAfterStart, pw) 308 c.closeAfterWait = append(c.closeAfterWait, pr) 309 c.goroutine = append(c.goroutine, func() error { 310 _, err := io.Copy(w, pr) 311 pr.Close() // in case io.Copy stopped due to write error 312 return err 313 }) 314 return pw, nil 315} 316 317func (c *Cmd) closeDescriptors(closers []io.Closer) { 318 for _, fd := range closers { 319 fd.Close() 320 } 321} 322 323// Run starts the specified command and waits for it to complete. 324// 325// The returned error is nil if the command runs, has no problems 326// copying stdin, stdout, and stderr, and exits with a zero exit 327// status. 328// 329// If the command starts but does not complete successfully, the error is of 330// type *ExitError. Other error types may be returned for other situations. 331// 332// If the calling goroutine has locked the operating system thread 333// with runtime.LockOSThread and modified any inheritable OS-level 334// thread state (for example, Linux or Plan 9 name spaces), the new 335// process will inherit the caller's thread state. 336func (c *Cmd) Run() error { 337 if err := c.Start(); err != nil { 338 return err 339 } 340 return c.Wait() 341} 342 343// lookExtensions finds windows executable by its dir and path. 344// It uses LookPath to try appropriate extensions. 345// lookExtensions does not search PATH, instead it converts `prog` into `.\prog`. 346func lookExtensions(path, dir string) (string, error) { 347 if filepath.Base(path) == path { 348 path = filepath.Join(".", path) 349 } 350 if dir == "" { 351 return LookPath(path) 352 } 353 if filepath.VolumeName(path) != "" { 354 return LookPath(path) 355 } 356 if len(path) > 1 && os.IsPathSeparator(path[0]) { 357 return LookPath(path) 358 } 359 dirandpath := filepath.Join(dir, path) 360 // We assume that LookPath will only add file extension. 361 lp, err := LookPath(dirandpath) 362 if err != nil { 363 return "", err 364 } 365 ext := strings.TrimPrefix(lp, dirandpath) 366 return path + ext, nil 367} 368 369// Start starts the specified command but does not wait for it to complete. 370// 371// If Start returns successfully, the c.Process field will be set. 372// 373// The Wait method will return the exit code and release associated resources 374// once the command exits. 375func (c *Cmd) Start() error { 376 if c.lookPathErr != nil { 377 c.closeDescriptors(c.closeAfterStart) 378 c.closeDescriptors(c.closeAfterWait) 379 return c.lookPathErr 380 } 381 if runtime.GOOS == "windows" { 382 lp, err := lookExtensions(c.Path, c.Dir) 383 if err != nil { 384 c.closeDescriptors(c.closeAfterStart) 385 c.closeDescriptors(c.closeAfterWait) 386 return err 387 } 388 c.Path = lp 389 } 390 if c.Process != nil { 391 return errors.New("exec: already started") 392 } 393 if c.ctx != nil { 394 select { 395 case <-c.ctx.Done(): 396 c.closeDescriptors(c.closeAfterStart) 397 c.closeDescriptors(c.closeAfterWait) 398 return c.ctx.Err() 399 default: 400 } 401 } 402 403 c.childFiles = make([]*os.File, 0, 3+len(c.ExtraFiles)) 404 type F func(*Cmd) (*os.File, error) 405 for _, setupFd := range []F{(*Cmd).stdin, (*Cmd).stdout, (*Cmd).stderr} { 406 fd, err := setupFd(c) 407 if err != nil { 408 c.closeDescriptors(c.closeAfterStart) 409 c.closeDescriptors(c.closeAfterWait) 410 return err 411 } 412 c.childFiles = append(c.childFiles, fd) 413 } 414 c.childFiles = append(c.childFiles, c.ExtraFiles...) 415 416 envv, err := c.envv() 417 if err != nil { 418 return err 419 } 420 421 c.Process, err = os.StartProcess(c.Path, c.argv(), &os.ProcAttr{ 422 Dir: c.Dir, 423 Files: c.childFiles, 424 Env: addCriticalEnv(dedupEnv(envv)), 425 Sys: c.SysProcAttr, 426 }) 427 if err != nil { 428 c.closeDescriptors(c.closeAfterStart) 429 c.closeDescriptors(c.closeAfterWait) 430 return err 431 } 432 433 c.closeDescriptors(c.closeAfterStart) 434 435 // Don't allocate the channel unless there are goroutines to fire. 436 if len(c.goroutine) > 0 { 437 c.errch = make(chan error, len(c.goroutine)) 438 for _, fn := range c.goroutine { 439 go func(fn func() error) { 440 c.errch <- fn() 441 }(fn) 442 } 443 } 444 445 if c.ctx != nil { 446 c.waitDone = make(chan struct{}) 447 go func() { 448 select { 449 case <-c.ctx.Done(): 450 c.Process.Kill() 451 case <-c.waitDone: 452 } 453 }() 454 } 455 456 return nil 457} 458 459// An ExitError reports an unsuccessful exit by a command. 460type ExitError struct { 461 *os.ProcessState 462 463 // Stderr holds a subset of the standard error output from the 464 // Cmd.Output method if standard error was not otherwise being 465 // collected. 466 // 467 // If the error output is long, Stderr may contain only a prefix 468 // and suffix of the output, with the middle replaced with 469 // text about the number of omitted bytes. 470 // 471 // Stderr is provided for debugging, for inclusion in error messages. 472 // Users with other needs should redirect Cmd.Stderr as needed. 473 Stderr []byte 474} 475 476func (e *ExitError) Error() string { 477 return e.ProcessState.String() 478} 479 480// Wait waits for the command to exit and waits for any copying to 481// stdin or copying from stdout or stderr to complete. 482// 483// The command must have been started by Start. 484// 485// The returned error is nil if the command runs, has no problems 486// copying stdin, stdout, and stderr, and exits with a zero exit 487// status. 488// 489// If the command fails to run or doesn't complete successfully, the 490// error is of type *ExitError. Other error types may be 491// returned for I/O problems. 492// 493// If any of c.Stdin, c.Stdout or c.Stderr are not an *os.File, Wait also waits 494// for the respective I/O loop copying to or from the process to complete. 495// 496// Wait releases any resources associated with the Cmd. 497func (c *Cmd) Wait() error { 498 if c.Process == nil { 499 return errors.New("exec: not started") 500 } 501 if c.finished { 502 return errors.New("exec: Wait was already called") 503 } 504 c.finished = true 505 506 state, err := c.Process.Wait() 507 if c.waitDone != nil { 508 close(c.waitDone) 509 } 510 c.ProcessState = state 511 512 var copyError error 513 for range c.goroutine { 514 if err := <-c.errch; err != nil && copyError == nil { 515 copyError = err 516 } 517 } 518 519 c.closeDescriptors(c.closeAfterWait) 520 521 if err != nil { 522 return err 523 } else if !state.Success() { 524 return &ExitError{ProcessState: state} 525 } 526 527 return copyError 528} 529 530// Output runs the command and returns its standard output. 531// Any returned error will usually be of type *ExitError. 532// If c.Stderr was nil, Output populates ExitError.Stderr. 533func (c *Cmd) Output() ([]byte, error) { 534 if c.Stdout != nil { 535 return nil, errors.New("exec: Stdout already set") 536 } 537 var stdout bytes.Buffer 538 c.Stdout = &stdout 539 540 captureErr := c.Stderr == nil 541 if captureErr { 542 c.Stderr = &prefixSuffixSaver{N: 32 << 10} 543 } 544 545 err := c.Run() 546 if err != nil && captureErr { 547 if ee, ok := err.(*ExitError); ok { 548 ee.Stderr = c.Stderr.(*prefixSuffixSaver).Bytes() 549 } 550 } 551 return stdout.Bytes(), err 552} 553 554// CombinedOutput runs the command and returns its combined standard 555// output and standard error. 556func (c *Cmd) CombinedOutput() ([]byte, error) { 557 if c.Stdout != nil { 558 return nil, errors.New("exec: Stdout already set") 559 } 560 if c.Stderr != nil { 561 return nil, errors.New("exec: Stderr already set") 562 } 563 var b bytes.Buffer 564 c.Stdout = &b 565 c.Stderr = &b 566 err := c.Run() 567 return b.Bytes(), err 568} 569 570// StdinPipe returns a pipe that will be connected to the command's 571// standard input when the command starts. 572// The pipe will be closed automatically after Wait sees the command exit. 573// A caller need only call Close to force the pipe to close sooner. 574// For example, if the command being run will not exit until standard input 575// is closed, the caller must close the pipe. 576func (c *Cmd) StdinPipe() (io.WriteCloser, error) { 577 if c.Stdin != nil { 578 return nil, errors.New("exec: Stdin already set") 579 } 580 if c.Process != nil { 581 return nil, errors.New("exec: StdinPipe after process started") 582 } 583 pr, pw, err := os.Pipe() 584 if err != nil { 585 return nil, err 586 } 587 c.Stdin = pr 588 c.closeAfterStart = append(c.closeAfterStart, pr) 589 wc := &closeOnce{File: pw} 590 c.closeAfterWait = append(c.closeAfterWait, wc) 591 return wc, nil 592} 593 594type closeOnce struct { 595 *os.File 596 597 once sync.Once 598 err error 599} 600 601func (c *closeOnce) Close() error { 602 c.once.Do(c.close) 603 return c.err 604} 605 606func (c *closeOnce) close() { 607 c.err = c.File.Close() 608} 609 610// StdoutPipe returns a pipe that will be connected to the command's 611// standard output when the command starts. 612// 613// Wait will close the pipe after seeing the command exit, so most callers 614// need not close the pipe themselves. It is thus incorrect to call Wait 615// before all reads from the pipe have completed. 616// For the same reason, it is incorrect to call Run when using StdoutPipe. 617// See the example for idiomatic usage. 618func (c *Cmd) StdoutPipe() (io.ReadCloser, error) { 619 if c.Stdout != nil { 620 return nil, errors.New("exec: Stdout already set") 621 } 622 if c.Process != nil { 623 return nil, errors.New("exec: StdoutPipe after process started") 624 } 625 pr, pw, err := os.Pipe() 626 if err != nil { 627 return nil, err 628 } 629 c.Stdout = pw 630 c.closeAfterStart = append(c.closeAfterStart, pw) 631 c.closeAfterWait = append(c.closeAfterWait, pr) 632 return pr, nil 633} 634 635// StderrPipe returns a pipe that will be connected to the command's 636// standard error when the command starts. 637// 638// Wait will close the pipe after seeing the command exit, so most callers 639// need not close the pipe themselves. It is thus incorrect to call Wait 640// before all reads from the pipe have completed. 641// For the same reason, it is incorrect to use Run when using StderrPipe. 642// See the StdoutPipe example for idiomatic usage. 643func (c *Cmd) StderrPipe() (io.ReadCloser, error) { 644 if c.Stderr != nil { 645 return nil, errors.New("exec: Stderr already set") 646 } 647 if c.Process != nil { 648 return nil, errors.New("exec: StderrPipe after process started") 649 } 650 pr, pw, err := os.Pipe() 651 if err != nil { 652 return nil, err 653 } 654 c.Stderr = pw 655 c.closeAfterStart = append(c.closeAfterStart, pw) 656 c.closeAfterWait = append(c.closeAfterWait, pr) 657 return pr, nil 658} 659 660// prefixSuffixSaver is an io.Writer which retains the first N bytes 661// and the last N bytes written to it. The Bytes() methods reconstructs 662// it with a pretty error message. 663type prefixSuffixSaver struct { 664 N int // max size of prefix or suffix 665 prefix []byte 666 suffix []byte // ring buffer once len(suffix) == N 667 suffixOff int // offset to write into suffix 668 skipped int64 669 670 // TODO(bradfitz): we could keep one large []byte and use part of it for 671 // the prefix, reserve space for the '... Omitting N bytes ...' message, 672 // then the ring buffer suffix, and just rearrange the ring buffer 673 // suffix when Bytes() is called, but it doesn't seem worth it for 674 // now just for error messages. It's only ~64KB anyway. 675} 676 677func (w *prefixSuffixSaver) Write(p []byte) (n int, err error) { 678 lenp := len(p) 679 p = w.fill(&w.prefix, p) 680 681 // Only keep the last w.N bytes of suffix data. 682 if overage := len(p) - w.N; overage > 0 { 683 p = p[overage:] 684 w.skipped += int64(overage) 685 } 686 p = w.fill(&w.suffix, p) 687 688 // w.suffix is full now if p is non-empty. Overwrite it in a circle. 689 for len(p) > 0 { // 0, 1, or 2 iterations. 690 n := copy(w.suffix[w.suffixOff:], p) 691 p = p[n:] 692 w.skipped += int64(n) 693 w.suffixOff += n 694 if w.suffixOff == w.N { 695 w.suffixOff = 0 696 } 697 } 698 return lenp, nil 699} 700 701// fill appends up to len(p) bytes of p to *dst, such that *dst does not 702// grow larger than w.N. It returns the un-appended suffix of p. 703func (w *prefixSuffixSaver) fill(dst *[]byte, p []byte) (pRemain []byte) { 704 if remain := w.N - len(*dst); remain > 0 { 705 add := minInt(len(p), remain) 706 *dst = append(*dst, p[:add]...) 707 p = p[add:] 708 } 709 return p 710} 711 712func (w *prefixSuffixSaver) Bytes() []byte { 713 if w.suffix == nil { 714 return w.prefix 715 } 716 if w.skipped == 0 { 717 return append(w.prefix, w.suffix...) 718 } 719 var buf bytes.Buffer 720 buf.Grow(len(w.prefix) + len(w.suffix) + 50) 721 buf.Write(w.prefix) 722 buf.WriteString("\n... omitting ") 723 buf.WriteString(strconv.FormatInt(w.skipped, 10)) 724 buf.WriteString(" bytes ...\n") 725 buf.Write(w.suffix[w.suffixOff:]) 726 buf.Write(w.suffix[:w.suffixOff]) 727 return buf.Bytes() 728} 729 730func minInt(a, b int) int { 731 if a < b { 732 return a 733 } 734 return b 735} 736 737// dedupEnv returns a copy of env with any duplicates removed, in favor of 738// later values. 739// Items not of the normal environment "key=value" form are preserved unchanged. 740func dedupEnv(env []string) []string { 741 return dedupEnvCase(runtime.GOOS == "windows", env) 742} 743 744// dedupEnvCase is dedupEnv with a case option for testing. 745// If caseInsensitive is true, the case of keys is ignored. 746func dedupEnvCase(caseInsensitive bool, env []string) []string { 747 out := make([]string, 0, len(env)) 748 saw := make(map[string]int, len(env)) // key => index into out 749 for _, kv := range env { 750 eq := strings.Index(kv, "=") 751 if eq < 0 { 752 out = append(out, kv) 753 continue 754 } 755 k := kv[:eq] 756 if caseInsensitive { 757 k = strings.ToLower(k) 758 } 759 if dupIdx, isDup := saw[k]; isDup { 760 out[dupIdx] = kv 761 continue 762 } 763 saw[k] = len(out) 764 out = append(out, kv) 765 } 766 return out 767} 768 769// addCriticalEnv adds any critical environment variables that are required 770// (or at least almost always required) on the operating system. 771// Currently this is only used for Windows. 772func addCriticalEnv(env []string) []string { 773 if runtime.GOOS != "windows" { 774 return env 775 } 776 for _, kv := range env { 777 eq := strings.Index(kv, "=") 778 if eq < 0 { 779 continue 780 } 781 k := kv[:eq] 782 if strings.EqualFold(k, "SYSTEMROOT") { 783 // We already have it. 784 return env 785 } 786 } 787 return append(env, "SYSTEMROOT="+os.Getenv("SYSTEMROOT")) 788} 789