Commit 4e9e9250 authored by Rob Pike's avatar Rob Pike

exec: change exec.PathError to exec.Error

There were two issues:
1) It might not be a path error, it might be 'permission denied'.
2) The concept of $PATH is Unix-specific.

R=alex.brainman, rsc, r, mattn.jp
CC=golang-dev
https://golang.org/cl/4530096
parent 31c79c4e
...@@ -14,14 +14,15 @@ import ( ...@@ -14,14 +14,15 @@ import (
"strconv" "strconv"
) )
// PathError records the name of a binary that was not // Error records the name of a binary that failed to be be executed
// found on the current $PATH. // and the reason it failed.
type PathError struct { type Error struct {
Name string Name string
Error os.Error
} }
func (e *PathError) String() string { func (e *Error) String() string {
return "command " + strconv.Quote(e.Name) + " not found in $PATH" return "exec: " + strconv.Quote(e.Name) + ": " + e.Error.String()
} }
// Cmd represents an external command being prepared or run. // Cmd represents an external command being prepared or run.
...@@ -32,8 +33,8 @@ type Cmd struct { ...@@ -32,8 +33,8 @@ type Cmd struct {
// value. // value.
Path string Path string
// Args is the command line arguments, including the command as Args[0]. // Args holds command line arguments, including the command as Args[0].
// If Args is empty, Run uses {Path}. // If the Args field is empty or nil, Run uses {Path}.
// //
// In typical use, both Path and Args are set by calling Command. // In typical use, both Path and Args are set by calling Command.
Args []string Args []string
...@@ -44,7 +45,7 @@ type Cmd struct { ...@@ -44,7 +45,7 @@ type Cmd struct {
// Dir specifies the working directory of the command. // Dir specifies the working directory of the command.
// If Dir is the empty string, Run runs the command in the // If Dir is the empty string, Run runs the command in the
// process's current directory. // calling process's current directory.
Dir string Dir string
// Stdin specifies the process's standard input. // Stdin specifies the process's standard input.
...@@ -81,7 +82,7 @@ type Cmd struct { ...@@ -81,7 +82,7 @@ type Cmd struct {
// resolve the path to a complete name if possible. Otherwise it uses // resolve the path to a complete name if possible. Otherwise it uses
// name directly. // name directly.
// //
// The returned Cmd's Args is constructed from the command name // The returned Cmd's Args field is constructed from the command name
// followed by the elements of arg, so arg should not include the // followed by the elements of arg, so arg should not include the
// command name itself. For example, Command("echo", "hello") // command name itself. For example, Command("echo", "hello")
func Command(name string, arg ...string) *Cmd { func Command(name string, arg ...string) *Cmd {
...@@ -97,7 +98,7 @@ func Command(name string, arg ...string) *Cmd { ...@@ -97,7 +98,7 @@ func Command(name string, arg ...string) *Cmd {
} }
// interfaceEqual protects against panics from doing equality tests on // interfaceEqual protects against panics from doing equality tests on
// two interface with non-comparable underlying types // two interfaces with non-comparable underlying types
func interfaceEqual(a, b interface{}) bool { func interfaceEqual(a, b interface{}) bool {
defer func() { defer func() {
recover() recover()
......
...@@ -22,12 +22,12 @@ func TestLookPathNotFound(t *testing.T) { ...@@ -22,12 +22,12 @@ func TestLookPathNotFound(t *testing.T) {
if path != "" { if path != "" {
t.Fatalf("LookPath path == %q when err != nil", path) t.Fatalf("LookPath path == %q when err != nil", path)
} }
perr, ok := err.(*PathError) perr, ok := err.(*Error)
if !ok { if !ok {
t.Fatal("LookPath error is not a PathError") t.Fatal("LookPath error is not an exec.Error")
} }
if perr.Name != name { if perr.Name != name {
t.Fatalf("want PathError name %q, got %q", name, perr.Name) t.Fatalf("want Error name %q, got %q", name, perr.Name)
} }
} }
} }
...@@ -9,12 +9,18 @@ import ( ...@@ -9,12 +9,18 @@ import (
"strings" "strings"
) )
func canExec(file string) bool { // ErrNotFound is the error resulting if a path search failed to find an executable file.
var ErrNotFound = os.ErrorString("executable file not found in $PATH")
func findExecutable(file string) os.Error {
d, err := os.Stat(file) d, err := os.Stat(file)
if err != nil { if err != nil {
return false return err
}
if d.IsRegular() && d.Permission()&0111 != 0 {
return nil
} }
return d.IsRegular() && d.Permission()&0111 != 0 return os.EPERM
} }
// LookPath searches for an executable binary named file // LookPath searches for an executable binary named file
...@@ -26,10 +32,11 @@ func LookPath(file string) (string, os.Error) { ...@@ -26,10 +32,11 @@ func LookPath(file string) (string, os.Error) {
// but that would not match all the Unix shells. // but that would not match all the Unix shells.
if strings.Contains(file, "/") { if strings.Contains(file, "/") {
if canExec(file) { err := findExecutable(file)
if err == nil {
return file, nil return file, nil
} }
return "", &PathError{file} return "", &Error{file, err}
} }
pathenv := os.Getenv("PATH") pathenv := os.Getenv("PATH")
for _, dir := range strings.Split(pathenv, ":", -1) { for _, dir := range strings.Split(pathenv, ":", -1) {
...@@ -37,9 +44,9 @@ func LookPath(file string) (string, os.Error) { ...@@ -37,9 +44,9 @@ func LookPath(file string) (string, os.Error) {
// Unix shell semantics: path element "" means "." // Unix shell semantics: path element "" means "."
dir = "." dir = "."
} }
if canExec(dir + "/" + file) { if err := findExecutable(dir + "/" + file); err == nil {
return dir + "/" + file, nil return dir + "/" + file, nil
} }
} }
return "", &PathError{file} return "", &Error{file, ErrNotFound}
} }
...@@ -9,15 +9,21 @@ import ( ...@@ -9,15 +9,21 @@ import (
"strings" "strings"
) )
func chkStat(file string) bool { // ErrNotFound is the error resulting if a path search failed to find an executable file.
var ErrNotFound = os.ErrorString("executable file not found in %PATH%")
func chkStat(file string) os.Error {
d, err := os.Stat(file) d, err := os.Stat(file)
if err != nil { if err != nil {
return false return err
}
if d.IsRegular() {
return nil
} }
return d.IsRegular() return os.EPERM
} }
func canExec(file string, exts []string) (string, bool) { func findExecutable(file string, exts []string) (string, os.Error) {
if len(exts) == 0 { if len(exts) == 0 {
return file, chkStat(file) return file, chkStat(file)
} }
...@@ -28,14 +34,14 @@ func canExec(file string, exts []string) (string, bool) { ...@@ -28,14 +34,14 @@ func canExec(file string, exts []string) (string, bool) {
} }
} }
for _, e := range exts { for _, e := range exts {
if f := file + e; chkStat(f) { if f := file + e; chkStat(f) == nil {
return f, true return f, nil
} }
} }
return ``, false return ``, ErrNotFound
} }
func LookPath(file string) (string, os.Error) { func LookPath(file string) (f string, err os.Error) {
exts := []string{} exts := []string{}
if x := os.Getenv(`PATHEXT`); x != `` { if x := os.Getenv(`PATHEXT`); x != `` {
exts = strings.Split(strings.ToLower(x), `;`, -1) exts = strings.Split(strings.ToLower(x), `;`, -1)
...@@ -46,21 +52,21 @@ func LookPath(file string) (string, os.Error) { ...@@ -46,21 +52,21 @@ func LookPath(file string) (string, os.Error) {
} }
} }
if strings.Contains(file, `\`) || strings.Contains(file, `/`) { if strings.Contains(file, `\`) || strings.Contains(file, `/`) {
if f, ok := canExec(file, exts); ok { if f, err = findExecutable(file, exts); err == nil {
return f, nil return
} }
return ``, &PathError{file} return ``, &Error{file, err}
} }
if pathenv := os.Getenv(`PATH`); pathenv == `` { if pathenv := os.Getenv(`PATH`); pathenv == `` {
if f, ok := canExec(`.\`+file, exts); ok { if f, err = findExecutable(`.\`+file, exts); err == nil {
return f, nil return
} }
} else { } else {
for _, dir := range strings.Split(pathenv, `;`, -1) { for _, dir := range strings.Split(pathenv, `;`, -1) {
if f, ok := canExec(dir+`\`+file, exts); ok { if f, err = findExecutable(dir+`\`+file, exts); err == nil {
return f, nil return
} }
} }
} }
return ``, &PathError{file} return ``, &Error{file, ErrNotFound}
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment