@@ -426,6 +426,26 @@ func Command(name string, arg ...string) *Cmd {
426
426
if err != nil {
427
427
cmd .Err = err
428
428
}
429
+ } else if runtime .GOOS == "windows" && filepath .IsAbs (name ) {
430
+ // We may need to add a filename extension from PATHEXT
431
+ // or verify an extension that is already present.
432
+ // (We need to do this even for names that already have an extension
433
+ // in case of weird names like "foo.bat.exe".)
434
+ //
435
+ // Since the path is absolute, its extension should be unambiguous
436
+ // and independent of cmd.Dir, and we can go ahead and update cmd.Path to
437
+ // reflect it.
438
+ //
439
+ // Note that we cannot add an extension here for relative paths, because
440
+ // cmd.Dir may be set after we return from this function and that may cause
441
+ // the command to resolve to a different extension.
442
+ lp , err := LookPath (name )
443
+ if lp != "" {
444
+ cmd .Path = lp
445
+ }
446
+ if err != nil {
447
+ cmd .Err = err
448
+ }
429
449
}
430
450
return cmd
431
451
}
@@ -649,12 +669,28 @@ func (c *Cmd) Start() error {
649
669
}
650
670
return c .Err
651
671
}
652
- if runtime .GOOS == "windows" {
653
- lp , err := lookExtensions (c .Path , c .Dir )
672
+ lp := c .Path
673
+ if runtime .GOOS == "windows" && ! filepath .IsAbs (c .Path ) {
674
+ // If c.Path is relative, we had to wait until now
675
+ // to resolve it in case c.Dir was changed.
676
+ // (If it is absolute, we already resolved its extension in Command
677
+ // and shouldn't need to do so again.)
678
+ //
679
+ // Unfortunately, we cannot write the result back to c.Path because programs
680
+ // may assume that they can call Start concurrently with reading the path.
681
+ // (It is safe and non-racy to do so on Unix platforms, and users might not
682
+ // test with the race detector on all platforms;
683
+ // see https://go.dev/issue/62596.)
684
+ //
685
+ // So we will pass the fully resolved path to os.StartProcess, but leave
686
+ // c.Path as is: missing a bit of logging information seems less harmful
687
+ // than triggering a surprising data race, and if the user really cares
688
+ // about that bit of logging they can always use LookPath to resolve it.
689
+ var err error
690
+ lp , err = lookExtensions (c .Path , c .Dir )
654
691
if err != nil {
655
692
return err
656
693
}
657
- c .Path = lp
658
694
}
659
695
if c .Cancel != nil && c .ctx == nil {
660
696
return errors .New ("exec: command with a non-nil Cancel was not created with CommandContext" )
@@ -690,7 +726,7 @@ func (c *Cmd) Start() error {
690
726
return err
691
727
}
692
728
693
- c .Process , err = os .StartProcess (c . Path , c .argv (), & os.ProcAttr {
729
+ c .Process , err = os .StartProcess (lp , c .argv (), & os.ProcAttr {
694
730
Dir : c .Dir ,
695
731
Files : childFiles ,
696
732
Env : env ,
0 commit comments