diff options
| author | Brad Fitzpatrick <bradfitz@golang.org> | 2014-02-03 16:32:13 -0500 |
|---|---|---|
| committer | Brad Fitzpatrick <bradfitz@golang.org> | 2014-02-03 16:32:13 -0500 |
| commit | e6d8bfe218a5f387d7aceddcaee5067a59181838 (patch) | |
| tree | 965fc1516ab382fb6d92c90bb8161d6c78df0f04 /src/pkg/os/exec/exec.go | |
| parent | 4723308ff5ab437d2b0892e7cdac86d490495a9b (diff) | |
| download | go-e6d8bfe218a5f387d7aceddcaee5067a59181838.tar.xz | |
os/exec: fix Command with relative paths
Command was (and is) documented like:
"If name contains no path separators, Command uses LookPath to
resolve the path to a complete name if possible. Otherwise it
uses name directly."
But that wasn't true. It always did LookPath, and then
set a sticky error that the user couldn't unset.
And then if cmd.Dir was changed, Start would still fail
due to the earlier sticky error being set.
This keeps LookPath in the same place as before (so no user
visible changes in cmd.Path after Command), but only does
it when the documentation says it will happen.
Also, clarify the docs about a relative Dir path.
No change in any existing behavior, except using Command
is now possible with relative paths. Previously it only
worked if you built the *Cmd by hand.
Fixes #7228
LGTM=iant
R=iant
CC=adg, golang-codereviews
https://golang.org/cl/59580044
Diffstat (limited to 'src/pkg/os/exec/exec.go')
| -rw-r--r-- | src/pkg/os/exec/exec.go | 40 |
1 files changed, 26 insertions, 14 deletions
diff --git a/src/pkg/os/exec/exec.go b/src/pkg/os/exec/exec.go index 491cc242bb..ea4f692a31 100644 --- a/src/pkg/os/exec/exec.go +++ b/src/pkg/os/exec/exec.go @@ -33,7 +33,8 @@ type Cmd struct { // Path is the path of the command to run. // // This is the only field that must be set to a non-zero - // value. + // value. If Path is relative, it is evaluated relative + // to Dir. Path string // Args holds command line arguments, including the command as Args[0]. @@ -84,7 +85,7 @@ type Cmd struct { // available after a call to Wait or Run. ProcessState *os.ProcessState - err error // last error (from LookPath, stdin, stdout, stderr) + lookPathErr error // LookPath error, if any. finished bool // when Wait was called childFiles []*os.File closeAfterStart []io.Closer @@ -96,8 +97,7 @@ type Cmd struct { // Command returns the Cmd struct to execute the named program with // the given arguments. // -// It sets Path and Args in the returned structure and zeroes the -// other fields. +// It sets only the Path and Args in the returned structure. // // If name contains no path separators, Command uses LookPath to // resolve the path to a complete name if possible. Otherwise it uses @@ -107,19 +107,31 @@ type Cmd struct { // followed by the elements of arg, so arg should not include the // command name itself. For example, Command("echo", "hello") func Command(name string, arg ...string) *Cmd { - aname, err := LookPath(name) - if err != nil { - aname = name - } - return &Cmd{ - Path: aname, + cmd := &Cmd{ + Path: name, Args: append([]string{name}, arg...), - err: err, } + if !containsPathSeparator(name) { + if lp, err := LookPath(name); err != nil { + cmd.lookPathErr = err + } else { + cmd.Path = lp + } + } + return cmd +} + +func containsPathSeparator(s string) bool { + for i := 0; i < len(s); i++ { + if os.IsPathSeparator(s[i]) { + return true + } + } + return false } // interfaceEqual protects against panics from doing equality tests on -// two interfaces with non-comparable underlying types +// two interfaces with non-comparable underlying types. func interfaceEqual(a, b interface{}) bool { defer func() { recover() @@ -235,10 +247,10 @@ func (c *Cmd) Run() error { // Start starts the specified command but does not wait for it to complete. func (c *Cmd) Start() error { - if c.err != nil { + if c.lookPathErr != nil { c.closeDescriptors(c.closeAfterStart) c.closeDescriptors(c.closeAfterWait) - return c.err + return c.lookPathErr } if c.Process != nil { return errors.New("exec: already started") |
