Source file
src/os/exec/exec.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package exec
22
23 import (
24 "bytes"
25 "context"
26 "errors"
27 "internal/syscall/execenv"
28 "io"
29 "os"
30 "path/filepath"
31 "runtime"
32 "strconv"
33 "strings"
34 "sync"
35 "syscall"
36 )
37
38
39
40 type Error struct {
41
42 Name string
43
44 Err error
45 }
46
47 func (e *Error) Error() string {
48 return "exec: " + strconv.Quote(e.Name) + ": " + e.Err.Error()
49 }
50
51 func (e *Error) Unwrap() error { return e.Err }
52
53
54
55
56
57 type Cmd struct {
58
59
60
61
62
63 Path string
64
65
66
67
68
69 Args []string
70
71
72
73
74
75
76
77
78
79 Env []string
80
81
82
83
84 Dir string
85
86
87
88
89
90
91
92
93
94
95
96
97
98 Stdin io.Reader
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115 Stdout io.Writer
116 Stderr io.Writer
117
118
119
120
121
122
123 ExtraFiles []*os.File
124
125
126
127 SysProcAttr *syscall.SysProcAttr
128
129
130 Process *os.Process
131
132
133
134 ProcessState *os.ProcessState
135
136 ctx context.Context
137 lookPathErr error
138 finished bool
139 childFiles []*os.File
140 closeAfterStart []io.Closer
141 closeAfterWait []io.Closer
142 goroutine []func() error
143 errch chan error
144 waitDone chan struct{}
145 }
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169 func Command(name string, arg ...string) *Cmd {
170 cmd := &Cmd{
171 Path: name,
172 Args: append([]string{name}, arg...),
173 }
174 if filepath.Base(name) == name {
175 if lp, err := LookPath(name); err != nil {
176 cmd.lookPathErr = err
177 } else {
178 cmd.Path = lp
179 }
180 }
181 return cmd
182 }
183
184
185
186
187
188
189 func CommandContext(ctx context.Context, name string, arg ...string) *Cmd {
190 if ctx == nil {
191 panic("nil Context")
192 }
193 cmd := Command(name, arg...)
194 cmd.ctx = ctx
195 return cmd
196 }
197
198
199
200
201
202 func (c *Cmd) String() string {
203 if c.lookPathErr != nil {
204
205 return strings.Join(c.Args, " ")
206 }
207
208 b := new(strings.Builder)
209 b.WriteString(c.Path)
210 for _, a := range c.Args[1:] {
211 b.WriteByte(' ')
212 b.WriteString(a)
213 }
214 return b.String()
215 }
216
217
218
219 func interfaceEqual(a, b any) bool {
220 defer func() {
221 recover()
222 }()
223 return a == b
224 }
225
226 func (c *Cmd) envv() ([]string, error) {
227 if c.Env != nil {
228 return c.Env, nil
229 }
230 return execenv.Default(c.SysProcAttr)
231 }
232
233 func (c *Cmd) argv() []string {
234 if len(c.Args) > 0 {
235 return c.Args
236 }
237 return []string{c.Path}
238 }
239
240
241
242 var skipStdinCopyError func(error) bool
243
244 func (c *Cmd) stdin() (f *os.File, err error) {
245 if c.Stdin == nil {
246 f, err = os.Open(os.DevNull)
247 if err != nil {
248 return
249 }
250 c.closeAfterStart = append(c.closeAfterStart, f)
251 return
252 }
253
254 if f, ok := c.Stdin.(*os.File); ok {
255 return f, nil
256 }
257
258 pr, pw, err := os.Pipe()
259 if err != nil {
260 return
261 }
262
263 c.closeAfterStart = append(c.closeAfterStart, pr)
264 c.closeAfterWait = append(c.closeAfterWait, pw)
265 c.goroutine = append(c.goroutine, func() error {
266 _, err := io.Copy(pw, c.Stdin)
267 if skip := skipStdinCopyError; skip != nil && skip(err) {
268 err = nil
269 }
270 if err1 := pw.Close(); err == nil {
271 err = err1
272 }
273 return err
274 })
275 return pr, nil
276 }
277
278 func (c *Cmd) stdout() (f *os.File, err error) {
279 return c.writerDescriptor(c.Stdout)
280 }
281
282 func (c *Cmd) stderr() (f *os.File, err error) {
283 if c.Stderr != nil && interfaceEqual(c.Stderr, c.Stdout) {
284 return c.childFiles[1], nil
285 }
286 return c.writerDescriptor(c.Stderr)
287 }
288
289 func (c *Cmd) writerDescriptor(w io.Writer) (f *os.File, err error) {
290 if w == nil {
291 f, err = os.OpenFile(os.DevNull, os.O_WRONLY, 0)
292 if err != nil {
293 return
294 }
295 c.closeAfterStart = append(c.closeAfterStart, f)
296 return
297 }
298
299 if f, ok := w.(*os.File); ok {
300 return f, nil
301 }
302
303 pr, pw, err := os.Pipe()
304 if err != nil {
305 return
306 }
307
308 c.closeAfterStart = append(c.closeAfterStart, pw)
309 c.closeAfterWait = append(c.closeAfterWait, pr)
310 c.goroutine = append(c.goroutine, func() error {
311 _, err := io.Copy(w, pr)
312 pr.Close()
313 return err
314 })
315 return pw, nil
316 }
317
318 func (c *Cmd) closeDescriptors(closers []io.Closer) {
319 for _, fd := range closers {
320 fd.Close()
321 }
322 }
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337 func (c *Cmd) Run() error {
338 if err := c.Start(); err != nil {
339 return err
340 }
341 return c.Wait()
342 }
343
344
345
346
347 func lookExtensions(path, dir string) (string, error) {
348 if filepath.Base(path) == path {
349 path = filepath.Join(".", path)
350 }
351 if dir == "" {
352 return LookPath(path)
353 }
354 if filepath.VolumeName(path) != "" {
355 return LookPath(path)
356 }
357 if len(path) > 1 && os.IsPathSeparator(path[0]) {
358 return LookPath(path)
359 }
360 dirandpath := filepath.Join(dir, path)
361
362 lp, err := LookPath(dirandpath)
363 if err != nil {
364 return "", err
365 }
366 ext := strings.TrimPrefix(lp, dirandpath)
367 return path + ext, nil
368 }
369
370
371
372
373
374
375
376 func (c *Cmd) Start() error {
377 if c.Path == "" && c.lookPathErr == nil {
378 c.lookPathErr = errors.New("exec: no command")
379 }
380 if c.lookPathErr != nil {
381 c.closeDescriptors(c.closeAfterStart)
382 c.closeDescriptors(c.closeAfterWait)
383 return c.lookPathErr
384 }
385 if runtime.GOOS == "windows" {
386 lp, err := lookExtensions(c.Path, c.Dir)
387 if err != nil {
388 c.closeDescriptors(c.closeAfterStart)
389 c.closeDescriptors(c.closeAfterWait)
390 return err
391 }
392 c.Path = lp
393 }
394 if c.Process != nil {
395 return errors.New("exec: already started")
396 }
397 if c.ctx != nil {
398 select {
399 case <-c.ctx.Done():
400 c.closeDescriptors(c.closeAfterStart)
401 c.closeDescriptors(c.closeAfterWait)
402 return c.ctx.Err()
403 default:
404 }
405 }
406
407 c.childFiles = make([]*os.File, 0, 3+len(c.ExtraFiles))
408 type F func(*Cmd) (*os.File, error)
409 for _, setupFd := range []F{(*Cmd).stdin, (*Cmd).stdout, (*Cmd).stderr} {
410 fd, err := setupFd(c)
411 if err != nil {
412 c.closeDescriptors(c.closeAfterStart)
413 c.closeDescriptors(c.closeAfterWait)
414 return err
415 }
416 c.childFiles = append(c.childFiles, fd)
417 }
418 c.childFiles = append(c.childFiles, c.ExtraFiles...)
419
420 envv, err := c.envv()
421 if err != nil {
422 return err
423 }
424
425 c.Process, err = os.StartProcess(c.Path, c.argv(), &os.ProcAttr{
426 Dir: c.Dir,
427 Files: c.childFiles,
428 Env: addCriticalEnv(dedupEnv(envv)),
429 Sys: c.SysProcAttr,
430 })
431 if err != nil {
432 c.closeDescriptors(c.closeAfterStart)
433 c.closeDescriptors(c.closeAfterWait)
434 return err
435 }
436
437 c.closeDescriptors(c.closeAfterStart)
438
439
440 if len(c.goroutine) > 0 {
441 c.errch = make(chan error, len(c.goroutine))
442 for _, fn := range c.goroutine {
443 go func(fn func() error) {
444 c.errch <- fn()
445 }(fn)
446 }
447 }
448
449 if c.ctx != nil {
450 c.waitDone = make(chan struct{})
451 go func() {
452 select {
453 case <-c.ctx.Done():
454 c.Process.Kill()
455 case <-c.waitDone:
456 }
457 }()
458 }
459
460 return nil
461 }
462
463
464 type ExitError struct {
465 *os.ProcessState
466
467
468
469
470
471
472
473
474
475
476
477 Stderr []byte
478 }
479
480 func (e *ExitError) Error() string {
481 return e.ProcessState.String()
482 }
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501 func (c *Cmd) Wait() error {
502 if c.Process == nil {
503 return errors.New("exec: not started")
504 }
505 if c.finished {
506 return errors.New("exec: Wait was already called")
507 }
508 c.finished = true
509
510 state, err := c.Process.Wait()
511 if c.waitDone != nil {
512 close(c.waitDone)
513 }
514 c.ProcessState = state
515
516 var copyError error
517 for range c.goroutine {
518 if err := <-c.errch; err != nil && copyError == nil {
519 copyError = err
520 }
521 }
522
523 c.closeDescriptors(c.closeAfterWait)
524
525 if err != nil {
526 return err
527 } else if !state.Success() {
528 return &ExitError{ProcessState: state}
529 }
530
531 return copyError
532 }
533
534
535
536
537 func (c *Cmd) Output() ([]byte, error) {
538 if c.Stdout != nil {
539 return nil, errors.New("exec: Stdout already set")
540 }
541 var stdout bytes.Buffer
542 c.Stdout = &stdout
543
544 captureErr := c.Stderr == nil
545 if captureErr {
546 c.Stderr = &prefixSuffixSaver{N: 32 << 10}
547 }
548
549 err := c.Run()
550 if err != nil && captureErr {
551 if ee, ok := err.(*ExitError); ok {
552 ee.Stderr = c.Stderr.(*prefixSuffixSaver).Bytes()
553 }
554 }
555 return stdout.Bytes(), err
556 }
557
558
559
560 func (c *Cmd) CombinedOutput() ([]byte, error) {
561 if c.Stdout != nil {
562 return nil, errors.New("exec: Stdout already set")
563 }
564 if c.Stderr != nil {
565 return nil, errors.New("exec: Stderr already set")
566 }
567 var b bytes.Buffer
568 c.Stdout = &b
569 c.Stderr = &b
570 err := c.Run()
571 return b.Bytes(), err
572 }
573
574
575
576
577
578
579
580 func (c *Cmd) StdinPipe() (io.WriteCloser, error) {
581 if c.Stdin != nil {
582 return nil, errors.New("exec: Stdin already set")
583 }
584 if c.Process != nil {
585 return nil, errors.New("exec: StdinPipe after process started")
586 }
587 pr, pw, err := os.Pipe()
588 if err != nil {
589 return nil, err
590 }
591 c.Stdin = pr
592 c.closeAfterStart = append(c.closeAfterStart, pr)
593 wc := &closeOnce{File: pw}
594 c.closeAfterWait = append(c.closeAfterWait, wc)
595 return wc, nil
596 }
597
598 type closeOnce struct {
599 *os.File
600
601 once sync.Once
602 err error
603 }
604
605 func (c *closeOnce) Close() error {
606 c.once.Do(c.close)
607 return c.err
608 }
609
610 func (c *closeOnce) close() {
611 c.err = c.File.Close()
612 }
613
614
615
616
617
618
619
620
621
622 func (c *Cmd) StdoutPipe() (io.ReadCloser, error) {
623 if c.Stdout != nil {
624 return nil, errors.New("exec: Stdout already set")
625 }
626 if c.Process != nil {
627 return nil, errors.New("exec: StdoutPipe after process started")
628 }
629 pr, pw, err := os.Pipe()
630 if err != nil {
631 return nil, err
632 }
633 c.Stdout = pw
634 c.closeAfterStart = append(c.closeAfterStart, pw)
635 c.closeAfterWait = append(c.closeAfterWait, pr)
636 return pr, nil
637 }
638
639
640
641
642
643
644
645
646
647 func (c *Cmd) StderrPipe() (io.ReadCloser, error) {
648 if c.Stderr != nil {
649 return nil, errors.New("exec: Stderr already set")
650 }
651 if c.Process != nil {
652 return nil, errors.New("exec: StderrPipe after process started")
653 }
654 pr, pw, err := os.Pipe()
655 if err != nil {
656 return nil, err
657 }
658 c.Stderr = pw
659 c.closeAfterStart = append(c.closeAfterStart, pw)
660 c.closeAfterWait = append(c.closeAfterWait, pr)
661 return pr, nil
662 }
663
664
665
666
667 type prefixSuffixSaver struct {
668 N int
669 prefix []byte
670 suffix []byte
671 suffixOff int
672 skipped int64
673
674
675
676
677
678
679 }
680
681 func (w *prefixSuffixSaver) Write(p []byte) (n int, err error) {
682 lenp := len(p)
683 p = w.fill(&w.prefix, p)
684
685
686 if overage := len(p) - w.N; overage > 0 {
687 p = p[overage:]
688 w.skipped += int64(overage)
689 }
690 p = w.fill(&w.suffix, p)
691
692
693 for len(p) > 0 {
694 n := copy(w.suffix[w.suffixOff:], p)
695 p = p[n:]
696 w.skipped += int64(n)
697 w.suffixOff += n
698 if w.suffixOff == w.N {
699 w.suffixOff = 0
700 }
701 }
702 return lenp, nil
703 }
704
705
706
707 func (w *prefixSuffixSaver) fill(dst *[]byte, p []byte) (pRemain []byte) {
708 if remain := w.N - len(*dst); remain > 0 {
709 add := minInt(len(p), remain)
710 *dst = append(*dst, p[:add]...)
711 p = p[add:]
712 }
713 return p
714 }
715
716 func (w *prefixSuffixSaver) Bytes() []byte {
717 if w.suffix == nil {
718 return w.prefix
719 }
720 if w.skipped == 0 {
721 return append(w.prefix, w.suffix...)
722 }
723 var buf bytes.Buffer
724 buf.Grow(len(w.prefix) + len(w.suffix) + 50)
725 buf.Write(w.prefix)
726 buf.WriteString("\n... omitting ")
727 buf.WriteString(strconv.FormatInt(w.skipped, 10))
728 buf.WriteString(" bytes ...\n")
729 buf.Write(w.suffix[w.suffixOff:])
730 buf.Write(w.suffix[:w.suffixOff])
731 return buf.Bytes()
732 }
733
734 func minInt(a, b int) int {
735 if a < b {
736 return a
737 }
738 return b
739 }
740
741
742
743
744 func dedupEnv(env []string) []string {
745 return dedupEnvCase(runtime.GOOS == "windows", env)
746 }
747
748
749
750 func dedupEnvCase(caseInsensitive bool, env []string) []string {
751 out := make([]string, 0, len(env))
752 saw := make(map[string]int, len(env))
753 for _, kv := range env {
754 k, _, ok := strings.Cut(kv, "=")
755 if !ok {
756 out = append(out, kv)
757 continue
758 }
759 if caseInsensitive {
760 k = strings.ToLower(k)
761 }
762 if dupIdx, isDup := saw[k]; isDup {
763 out[dupIdx] = kv
764 continue
765 }
766 saw[k] = len(out)
767 out = append(out, kv)
768 }
769 return out
770 }
771
772
773
774
775 func addCriticalEnv(env []string) []string {
776 if runtime.GOOS != "windows" {
777 return env
778 }
779 for _, kv := range env {
780 k, _, ok := strings.Cut(kv, "=")
781 if !ok {
782 continue
783 }
784 if strings.EqualFold(k, "SYSTEMROOT") {
785
786 return env
787 }
788 }
789 return append(env, "SYSTEMROOT="+os.Getenv("SYSTEMROOT"))
790 }
791
View as plain text