Skip to content

Commit f7f266c

Browse files
Bryan C. Millsgopherbot
authored andcommitted
os/exec: avoid calling LookPath in cmd.Start for resolved paths
This reapplies CL 512155, which was previously reverted in CL 527337. The race that prompted the revert should be fixed by CL 527820, which will be submitted before this one. For #36768. Updates #62596. Change-Id: I3c3cd92470254072901b6ef91c0ac52c8071e0a2 Cq-Include-Trybots: luci.golang.try:gotip-linux-amd64-race,gotip-windows-amd64-race,gotip-windows-amd64-longtest Reviewed-on: https://go-review.googlesource.com/c/go/+/528038 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Ian Lance Taylor <iant@google.com> Auto-Submit: Bryan Mills <bcmills@google.com>
1 parent 07d4de9 commit f7f266c

6 files changed

Lines changed: 83 additions & 40 deletions

File tree

src/os/exec/exec.go

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -429,17 +429,14 @@ func Command(name string, arg ...string) *Cmd {
429429
} else if runtime.GOOS == "windows" && filepath.IsAbs(name) {
430430
// We may need to add a filename extension from PATHEXT
431431
// 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-
//
435432
// Since the path is absolute, its extension should be unambiguous
436433
// and independent of cmd.Dir, and we can go ahead and update cmd.Path to
437434
// reflect it.
438435
//
439436
// Note that we cannot add an extension here for relative paths, because
440437
// cmd.Dir may be set after we return from this function and that may cause
441438
// the command to resolve to a different extension.
442-
lp, err := LookPath(name)
439+
lp, err := lookExtensions(name, "")
443440
if lp != "" {
444441
cmd.Path = lp
445442
}
@@ -610,32 +607,6 @@ func (c *Cmd) Run() error {
610607
return c.Wait()
611608
}
612609

613-
// lookExtensions finds windows executable by its dir and path.
614-
// It uses LookPath to try appropriate extensions.
615-
// lookExtensions does not search PATH, instead it converts `prog` into `.\prog`.
616-
func lookExtensions(path, dir string) (string, error) {
617-
if filepath.Base(path) == path {
618-
path = "." + string(filepath.Separator) + path
619-
}
620-
if dir == "" {
621-
return LookPath(path)
622-
}
623-
if filepath.VolumeName(path) != "" {
624-
return LookPath(path)
625-
}
626-
if len(path) > 1 && os.IsPathSeparator(path[0]) {
627-
return LookPath(path)
628-
}
629-
dirandpath := filepath.Join(dir, path)
630-
// We assume that LookPath will only add file extension.
631-
lp, err := LookPath(dirandpath)
632-
if err != nil {
633-
return "", err
634-
}
635-
ext := strings.TrimPrefix(lp, dirandpath)
636-
return path + ext, nil
637-
}
638-
639610
// Start starts the specified command but does not wait for it to complete.
640611
//
641612
// If Start returns successfully, the c.Process field will be set.

src/os/exec/lp_plan9.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,9 @@ func LookPath(file string) (string, error) {
6464
}
6565
return "", &Error{file, ErrNotFound}
6666
}
67+
68+
// lookExtensions is a no-op on non-Windows platforms, since
69+
// they do not restrict executables to specific extensions.
70+
func lookExtensions(path, dir string) (string, error) {
71+
return path, nil
72+
}

src/os/exec/lp_unix.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,9 @@ func LookPath(file string) (string, error) {
8080
}
8181
return "", &Error{file, ErrNotFound}
8282
}
83+
84+
// lookExtensions is a no-op on non-Windows platforms, since
85+
// they do not restrict executables to specific extensions.
86+
func lookExtensions(path, dir string) (string, error) {
87+
return path, nil
88+
}

src/os/exec/lp_wasm.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,9 @@ func LookPath(file string) (string, error) {
2121
// Wasm can not execute processes, so act as if there are no executables at all.
2222
return "", &Error{file, ErrNotFound}
2323
}
24+
25+
// lookExtensions is a no-op on non-Windows platforms, since
26+
// they do not restrict executables to specific extensions.
27+
func lookExtensions(path, dir string) (string, error) {
28+
return path, nil
29+
}

src/os/exec/lp_windows.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,51 @@ func findExecutable(file string, exts []string) (string, error) {
6868
// As of Go 1.19, LookPath will instead return that path along with an error satisfying
6969
// errors.Is(err, ErrDot). See the package documentation for more details.
7070
func LookPath(file string) (string, error) {
71+
return lookPath(file, pathExt())
72+
}
73+
74+
// lookExtensions finds windows executable by its dir and path.
75+
// It uses LookPath to try appropriate extensions.
76+
// lookExtensions does not search PATH, instead it converts `prog` into `.\prog`.
77+
//
78+
// If the path already has an extension found in PATHEXT,
79+
// lookExtensions returns it directly without searching
80+
// for additional extensions. For example,
81+
// "C:\foo\example.com" would be returned as-is even if the
82+
// program is actually "C:\foo\example.com.exe".
83+
func lookExtensions(path, dir string) (string, error) {
84+
if filepath.Base(path) == path {
85+
path = "." + string(filepath.Separator) + path
86+
}
87+
exts := pathExt()
88+
if ext := filepath.Ext(path); ext != "" {
89+
for _, e := range exts {
90+
if strings.EqualFold(ext, e) {
91+
// Assume that path has already been resolved.
92+
return path, nil
93+
}
94+
}
95+
}
96+
if dir == "" {
97+
return lookPath(path, exts)
98+
}
99+
if filepath.VolumeName(path) != "" {
100+
return lookPath(path, exts)
101+
}
102+
if len(path) > 1 && os.IsPathSeparator(path[0]) {
103+
return lookPath(path, exts)
104+
}
105+
dirandpath := filepath.Join(dir, path)
106+
// We assume that LookPath will only add file extension.
107+
lp, err := lookPath(dirandpath, exts)
108+
if err != nil {
109+
return "", err
110+
}
111+
ext := strings.TrimPrefix(lp, dirandpath)
112+
return path + ext, nil
113+
}
114+
115+
func pathExt() []string {
71116
var exts []string
72117
x := os.Getenv(`PATHEXT`)
73118
if x != "" {
@@ -83,7 +128,11 @@ func LookPath(file string) (string, error) {
83128
} else {
84129
exts = []string{".com", ".exe", ".bat", ".cmd"}
85130
}
131+
return exts
132+
}
86133

134+
// lookPath implements LookPath for the given PATHEXT list.
135+
func lookPath(file string, exts []string) (string, error) {
87136
if strings.ContainsAny(file, `:\/`) {
88137
f, err := findExecutable(file, exts)
89138
if err == nil {

src/os/exec/lp_windows_test.go

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -611,22 +611,27 @@ func TestCommand(t *testing.T) {
611611
func TestAbsCommandWithDoubledExtension(t *testing.T) {
612612
t.Parallel()
613613

614+
// We expect that ".com" is always included in PATHEXT, but it may also be
615+
// found in the import path of a Go package. If it is at the root of the
616+
// import path, the resulting executable may be named like "example.com.exe".
617+
//
618+
// Since "example.com" looks like a proper executable name, it is probably ok
619+
// for exec.Command to try to run it directly without re-resolving it.
620+
// However, exec.LookPath should try a little harder to figure it out.
621+
614622
comPath := filepath.Join(t.TempDir(), "example.com")
615623
batPath := comPath + ".bat"
616624
installBat(t, batPath)
617625

618626
cmd := exec.Command(comPath)
619627
out, err := cmd.CombinedOutput()
620-
t.Logf("%v:\n%s", cmd, out)
621-
if err == nil {
622-
got := strings.TrimSpace(string(out))
623-
if got != batPath {
624-
t.Errorf("wanted output %#q", batPath)
625-
}
626-
} else {
627-
t.Errorf("%v: %v", cmd, err)
628+
t.Logf("%v: %v\n%s", cmd, err, out)
629+
if !errors.Is(err, fs.ErrNotExist) {
630+
t.Errorf("Command(%#q).Run: %v\nwant fs.ErrNotExist", comPath, err)
628631
}
629-
if cmd.Path != batPath {
630-
t.Errorf("exec.Command(%#q).Path =\n %#q\nwant %#q", comPath, cmd.Path, batPath)
632+
633+
resolved, err := exec.LookPath(comPath)
634+
if err != nil || resolved != batPath {
635+
t.Fatalf("LookPath(%#q) = %v, %v; want %#q, <nil>", comPath, resolved, err, batPath)
631636
}
632637
}

0 commit comments

Comments
 (0)