blob: 9a05d971dadb9e49f632ba7622f88371e1b3a27d [file] [log] [blame]
Roland Shoemaker953d1fe2021-01-15 12:14:06 -08001// Copyright 2021 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 execabs is a drop-in replacement for os/exec
6// that requires PATH lookups to find absolute paths.
7// That is, execabs.Command("cmd") runs the same PATH lookup
8// as exec.Command("cmd"), but if the result is a path
9// which is relative, the Run and Start methods will report
10// an error instead of running the executable.
11package execabs
12
13import (
14 "context"
15 "fmt"
16 "os/exec"
17 "path/filepath"
18 "reflect"
19 "unsafe"
20)
21
22var ErrNotFound = exec.ErrNotFound
23
24type (
25 Cmd = exec.Cmd
26 Error = exec.Error
27 ExitError = exec.ExitError
28)
29
30func relError(file, path string) error {
31 return fmt.Errorf("%s resolves to executable relative to current directory (.%c%s)", file, filepath.Separator, path)
32}
33
34func LookPath(file string) (string, error) {
35 path, err := exec.LookPath(file)
36 if err != nil {
37 return "", err
38 }
39 if filepath.Base(file) == file && !filepath.IsAbs(path) {
40 return "", relError(file, path)
41 }
42 return path, nil
43}
44
45func fixCmd(name string, cmd *exec.Cmd) {
46 if filepath.Base(name) == name && !filepath.IsAbs(cmd.Path) {
47 // exec.Command was called with a bare binary name and
48 // exec.LookPath returned a path which is not absolute.
49 // Set cmd.lookPathErr and clear cmd.Path so that it
50 // cannot be run.
51 lookPathErr := (*error)(unsafe.Pointer(reflect.ValueOf(cmd).Elem().FieldByName("lookPathErr").Addr().Pointer()))
52 if *lookPathErr == nil {
53 *lookPathErr = relError(name, cmd.Path)
54 }
55 cmd.Path = ""
56 }
57}
58
59func CommandContext(ctx context.Context, name string, arg ...string) *exec.Cmd {
60 cmd := exec.CommandContext(ctx, name, arg...)
61 fixCmd(name, cmd)
62 return cmd
63
64}
65
66func Command(name string, arg ...string) *exec.Cmd {
67 cmd := exec.Command(name, arg...)
68 fixCmd(name, cmd)
69 return cmd
70}