os: delete os.EINVAL and so on
The set of errors forwarded by the os package varied with system and
was therefore non-portable.
Three helpers added for portable error checking: IsExist, IsNotExist, and IsPermission.
One or two more may need to come, but let's keep the set very small to discourage
thinking about errors that way.
R=mikioh.mikioh, gustavo, r, rsc
CC=golang-dev
https://golang.org/cl/5672047
diff --git a/src/pkg/os/dir_plan9.go b/src/pkg/os/dir_plan9.go
index f2dc154..7fa4c7f 100644
--- a/src/pkg/os/dir_plan9.go
+++ b/src/pkg/os/dir_plan9.go
@@ -10,6 +10,9 @@
"syscall"
)
+var errShortStat = errors.New("short stat message")
+var errBadStat = errors.New("bad stat message format")
+
func (file *File) readdir(n int) (fi []FileInfo, err error) {
// If this file has no dirinfo, create one.
if file.dirinfo == nil {
@@ -35,7 +38,7 @@
break
}
if d.nbuf < syscall.STATFIXLEN {
- return result, &PathError{"readdir", file.name, Eshortstat}
+ return result, &PathError{"readdir", file.name, errShortStat}
}
}
@@ -43,7 +46,7 @@
m, _ := gbit16(d.buf[d.bufp:])
m += 2
if m < syscall.STATFIXLEN {
- return result, &PathError{"readdir", file.name, Eshortstat}
+ return result, &PathError{"readdir", file.name, errShortStat}
}
dir, e := UnmarshalDir(d.buf[d.bufp : d.bufp+int(m)])
if e != nil {
@@ -138,7 +141,7 @@
n, b = gbit16(b)
if int(n) != len(b) {
- return nil, Ebadstat
+ return nil, errBadStat
}
d = new(Dir)
@@ -155,7 +158,7 @@
d.Muid, b = gstring(b)
if len(b) != 0 {
- return nil, Ebadstat
+ return nil, errBadStat
}
return d, nil
diff --git a/src/pkg/os/env.go b/src/pkg/os/env.go
index 5935051..207e0a0 100644
--- a/src/pkg/os/env.go
+++ b/src/pkg/os/env.go
@@ -84,7 +84,7 @@
// It returns the value and an error, if any.
func Getenverror(key string) (value string, err error) {
if len(key) == 0 {
- return "", EINVAL
+ return "", ErrInvalid
}
val, found := syscall.Getenv(key)
if !found {
diff --git a/src/pkg/os/error.go b/src/pkg/os/error.go
index 135cdae..5baeba4 100644
--- a/src/pkg/os/error.go
+++ b/src/pkg/os/error.go
@@ -4,6 +4,18 @@
package os
+import (
+ "errors"
+)
+
+// Portable analogs of some common system call errors.
+var (
+ ErrInvalid = errors.New("invalid argument")
+ ErrPermission = errors.New("permission denied")
+ ErrExist = errors.New("file already exists")
+ ErrNotExist = errors.New("file does not exit")
+)
+
// PathError records an error and the operation and file path that caused it.
type PathError struct {
Op string
diff --git a/src/pkg/os/error_plan9.go b/src/pkg/os/error_plan9.go
index cc847e0..159d685 100644
--- a/src/pkg/os/error_plan9.go
+++ b/src/pkg/os/error_plan9.go
@@ -4,34 +4,38 @@
package os
-import (
- "errors"
- "syscall"
-)
+// IsExist returns whether the error is known to report that a file already exists.
+func IsExist(err error) bool {
+ if pe, ok := err.(*PathError); ok {
+ err = pe.Err
+ }
+ return contains(err.Error(), " exists")
+}
-var (
- Eshortstat = errors.New("stat buffer too small")
- Ebadstat = errors.New("malformed stat buffer")
- Ebadfd = errors.New("fd out of range or not open")
- Ebadarg = errors.New("bad arg in system call")
- Enotdir = errors.New("not a directory")
- Enonexist = errors.New("file does not exist")
- Eexist = errors.New("file already exists")
- Eio = errors.New("i/o error")
- Eperm = errors.New("permission denied")
+// IsNotExist returns whether the error is known to report that a file does not exist.
+func IsNotExist(err error) bool {
+ if pe, ok := err.(*PathError); ok {
+ err = pe.Err
+ }
+ return contains(err.Error(), "does not exist")
+}
- EINVAL = Ebadarg
- ENOTDIR = Enotdir
- ENOENT = Enonexist
- EEXIST = Eexist
- EIO = Eio
- EACCES = Eperm
- EPERM = Eperm
- EISDIR = syscall.EISDIR
+// IsPermission returns whether the error is known to report that permission is denied.
+func IsPermission(err error) bool {
+ if pe, ok := err.(*PathError); ok {
+ err = pe.Err
+ }
+ return contains(err.Error(), "permission denied")
+}
- EBADF = errors.New("bad file descriptor")
- ENAMETOOLONG = errors.New("file name too long")
- ERANGE = errors.New("math result not representable")
- EPIPE = errors.New("Broken Pipe")
- EPLAN9 = errors.New("not supported by plan 9")
-)
+// contains is a local version of strings.Contains. It knows len(sep) > 1.
+func contains(s, sep string) bool {
+ n := len(sep)
+ c := sep[0]
+ for i := 0; i+n <= len(s); i++ {
+ if s[i] == c && s[i:i+n] == sep {
+ return true
+ }
+ }
+ return false
+}
diff --git a/src/pkg/os/error_posix.go b/src/pkg/os/error_posix.go
index 57c9b6f..74b75d1 100644
--- a/src/pkg/os/error_posix.go
+++ b/src/pkg/os/error_posix.go
@@ -8,44 +8,29 @@
import "syscall"
-// Commonly known Unix errors.
-var (
- EPERM error = syscall.EPERM
- ENOENT error = syscall.ENOENT
- ESRCH error = syscall.ESRCH
- EINTR error = syscall.EINTR
- EIO error = syscall.EIO
- E2BIG error = syscall.E2BIG
- ENOEXEC error = syscall.ENOEXEC
- EBADF error = syscall.EBADF
- ECHILD error = syscall.ECHILD
- EDEADLK error = syscall.EDEADLK
- ENOMEM error = syscall.ENOMEM
- EACCES error = syscall.EACCES
- EFAULT error = syscall.EFAULT
- EBUSY error = syscall.EBUSY
- EEXIST error = syscall.EEXIST
- EXDEV error = syscall.EXDEV
- ENODEV error = syscall.ENODEV
- ENOTDIR error = syscall.ENOTDIR
- EISDIR error = syscall.EISDIR
- EINVAL error = syscall.EINVAL
- ENFILE error = syscall.ENFILE
- EMFILE error = syscall.EMFILE
- ENOTTY error = syscall.ENOTTY
- EFBIG error = syscall.EFBIG
- ENOSPC error = syscall.ENOSPC
- ESPIPE error = syscall.ESPIPE
- EROFS error = syscall.EROFS
- EMLINK error = syscall.EMLINK
- EPIPE error = syscall.EPIPE
- EAGAIN error = syscall.EAGAIN
- EDOM error = syscall.EDOM
- ERANGE error = syscall.ERANGE
- EADDRINUSE error = syscall.EADDRINUSE
- ECONNREFUSED error = syscall.ECONNREFUSED
- ENAMETOOLONG error = syscall.ENAMETOOLONG
- EAFNOSUPPORT error = syscall.EAFNOSUPPORT
- ETIMEDOUT error = syscall.ETIMEDOUT
- ENOTCONN error = syscall.ENOTCONN
-)
+// IsExist returns whether the error is known to report that a file already exists.
+// It is satisfied by ErrExist as well as some syscall errors.
+func IsExist(err error) bool {
+ if pe, ok := err.(*PathError); ok {
+ err = pe.Err
+ }
+ return err == syscall.EEXIST || err == ErrExist
+}
+
+// IsNotExist returns whether the error is known to report that a file does not exist.
+// It is satisfied by ErrNotExist as well as some syscall errors.
+func IsNotExist(err error) bool {
+ if pe, ok := err.(*PathError); ok {
+ err = pe.Err
+ }
+ return err == syscall.ENOENT || err == ErrNotExist
+}
+
+// IsPermission returns whether the error is known to report that permission is denied.
+// It is satisfied by ErrPermission as well as some syscall errors.
+func IsPermission(err error) bool {
+ if pe, ok := err.(*PathError); ok {
+ err = pe.Err
+ }
+ return err == syscall.EACCES || err == syscall.EPERM || err == ErrPermission
+}
diff --git a/src/pkg/os/exec/lp_unix.go b/src/pkg/os/exec/lp_unix.go
index 2d3a919..2163221 100644
--- a/src/pkg/os/exec/lp_unix.go
+++ b/src/pkg/os/exec/lp_unix.go
@@ -23,7 +23,7 @@
if m := d.Mode(); !m.IsDir() && m&0111 != 0 {
return nil
}
- return os.EPERM
+ return os.ErrPermission
}
// LookPath searches for an executable binary named file
diff --git a/src/pkg/os/exec/lp_windows.go b/src/pkg/os/exec/lp_windows.go
index b7efcd6..d8351d7 100644
--- a/src/pkg/os/exec/lp_windows.go
+++ b/src/pkg/os/exec/lp_windows.go
@@ -19,7 +19,7 @@
return err
}
if d.IsDir() {
- return os.EPERM
+ return os.ErrPermission
}
return nil
}
@@ -39,7 +39,7 @@
return f, nil
}
}
- return ``, os.ENOENT
+ return ``, os.ErrNotExist
}
// LookPath searches for an executable binary named file
diff --git a/src/pkg/os/exec_plan9.go b/src/pkg/os/exec_plan9.go
index b725aeb..c57c4dc 100644
--- a/src/pkg/os/exec_plan9.go
+++ b/src/pkg/os/exec_plan9.go
@@ -76,7 +76,7 @@
var waitmsg syscall.Waitmsg
if p.Pid == -1 {
- return nil, EINVAL
+ return nil, ErrInvalid
}
for true {
diff --git a/src/pkg/os/exec_unix.go b/src/pkg/os/exec_unix.go
index 7fe7c2f..a5c2281 100644
--- a/src/pkg/os/exec_unix.go
+++ b/src/pkg/os/exec_unix.go
@@ -29,7 +29,7 @@
// (WNOHANG etc.) affect the behavior of the Wait call.
func (p *Process) Wait(options int) (w *Waitmsg, err error) {
if p.Pid == -1 {
- return nil, EINVAL
+ return nil, syscall.EINVAL
}
var status syscall.WaitStatus
var rusage *syscall.Rusage
diff --git a/src/pkg/os/exec_windows.go b/src/pkg/os/exec_windows.go
index f357a30..2a7affa 100644
--- a/src/pkg/os/exec_windows.go
+++ b/src/pkg/os/exec_windows.go
@@ -48,7 +48,7 @@
// Release releases any resources associated with the Process.
func (p *Process) Release() error {
if p.handle == uintptr(syscall.InvalidHandle) {
- return EINVAL
+ return syscall.EINVAL
}
e := syscall.CloseHandle(syscall.Handle(p.handle))
if e != nil {
diff --git a/src/pkg/os/file.go b/src/pkg/os/file.go
index 85f151e..4391642 100644
--- a/src/pkg/os/file.go
+++ b/src/pkg/os/file.go
@@ -55,7 +55,7 @@
// EOF is signaled by a zero count with err set to io.EOF.
func (f *File) Read(b []byte) (n int, err error) {
if f == nil {
- return 0, EINVAL
+ return 0, ErrInvalid
}
n, e := f.read(b)
if n < 0 {
@@ -76,7 +76,7 @@
// At end of file, that error is io.EOF.
func (f *File) ReadAt(b []byte, off int64) (n int, err error) {
if f == nil {
- return 0, EINVAL
+ return 0, ErrInvalid
}
for len(b) > 0 {
m, e := f.pread(b, off)
@@ -99,7 +99,7 @@
// Write returns a non-nil error when n != len(b).
func (f *File) Write(b []byte) (n int, err error) {
if f == nil {
- return 0, EINVAL
+ return 0, ErrInvalid
}
n, e := f.write(b)
if n < 0 {
@@ -119,7 +119,7 @@
// WriteAt returns a non-nil error when n != len(b).
func (f *File) WriteAt(b []byte, off int64) (n int, err error) {
if f == nil {
- return 0, EINVAL
+ return 0, ErrInvalid
}
for len(b) > 0 {
m, e := f.pwrite(b, off)
@@ -153,7 +153,7 @@
// an array of bytes.
func (f *File) WriteString(s string) (ret int, err error) {
if f == nil {
- return 0, EINVAL
+ return 0, ErrInvalid
}
return f.Write([]byte(s))
}
diff --git a/src/pkg/os/file_plan9.go b/src/pkg/os/file_plan9.go
index c28ea34..70041f2 100644
--- a/src/pkg/os/file_plan9.go
+++ b/src/pkg/os/file_plan9.go
@@ -5,11 +5,14 @@
package os
import (
+ "errors"
"runtime"
"syscall"
"time"
)
+var ErrPlan9 = errors.New("unimplemented on Plan 9")
+
// File represents an open file descriptor.
type File struct {
*file
@@ -140,7 +143,7 @@
func (file *file) close() error {
if file == nil || file.fd < 0 {
- return Ebadfd
+ return ErrInvalid
}
var err error
syscall.ForkLock.RLock()
@@ -203,7 +206,7 @@
// of recently written data to disk.
func (f *File) Sync() (err error) {
if f == nil {
- return EINVAL
+ return ErrInvalid
}
var d Dir
@@ -338,27 +341,27 @@
// Link creates a hard link.
func Link(oldname, newname string) error {
- return EPLAN9
+ return ErrPlan9
}
func Symlink(oldname, newname string) error {
- return EPLAN9
+ return ErrPlan9
}
func Readlink(name string) (string, error) {
- return "", EPLAN9
+ return "", ErrPlan9
}
func Chown(name string, uid, gid int) error {
- return EPLAN9
+ return ErrPlan9
}
func Lchown(name string, uid, gid int) error {
- return EPLAN9
+ return ErrPlan9
}
func (f *File) Chown(uid, gid int) error {
- return EPLAN9
+ return ErrPlan9
}
// TempDir returns the default directory to use for temporary files.
diff --git a/src/pkg/os/file_posix.go b/src/pkg/os/file_posix.go
index 8d3a00b..8861af1 100644
--- a/src/pkg/os/file_posix.go
+++ b/src/pkg/os/file_posix.go
@@ -160,7 +160,7 @@
// of recently written data to disk.
func (f *File) Sync() (err error) {
if f == nil {
- return EINVAL
+ return syscall.EINVAL
}
if e := syscall.Fsync(f.fd); e != nil {
return NewSyscallError("fsync", e)
diff --git a/src/pkg/os/file_unix.go b/src/pkg/os/file_unix.go
index 0a422f4..6aa0280 100644
--- a/src/pkg/os/file_unix.go
+++ b/src/pkg/os/file_unix.go
@@ -90,7 +90,7 @@
func (file *file) close() error {
if file == nil || file.fd < 0 {
- return EINVAL
+ return syscall.EINVAL
}
var err error
if e := syscall.Close(file.fd); e != nil {
diff --git a/src/pkg/os/file_windows.go b/src/pkg/os/file_windows.go
index 350d2a7..82c7429 100644
--- a/src/pkg/os/file_windows.go
+++ b/src/pkg/os/file_windows.go
@@ -98,7 +98,7 @@
if e == nil {
if flag&O_WRONLY != 0 || flag&O_RDWR != 0 {
r.Close()
- return nil, &PathError{"open", name, EISDIR}
+ return nil, &PathError{"open", name, syscall.EISDIR}
}
return r, nil
}
@@ -117,7 +117,7 @@
func (file *file) close() error {
if file == nil || file.fd == syscall.InvalidHandle {
- return EINVAL
+ return syscall.EINVAL
}
var e error
if file.isdir() {
@@ -138,10 +138,10 @@
func (file *File) readdir(n int) (fi []FileInfo, err error) {
if file == nil || file.fd == syscall.InvalidHandle {
- return nil, EINVAL
+ return nil, syscall.EINVAL
}
if !file.isdir() {
- return nil, &PathError{"Readdir", file.name, ENOTDIR}
+ return nil, &PathError{"Readdir", file.name, syscall.ENOTDIR}
}
wantAll := n <= 0
size := n
diff --git a/src/pkg/os/getwd.go b/src/pkg/os/getwd.go
index 5683643..81d8fed 100644
--- a/src/pkg/os/getwd.go
+++ b/src/pkg/os/getwd.go
@@ -52,7 +52,7 @@
pwd = ""
for parent := ".."; ; parent = "../" + parent {
if len(parent) >= 1024 { // Sanity check
- return "", ENAMETOOLONG
+ return "", syscall.ENAMETOOLONG
}
fd, err := Open(parent)
if err != nil {
@@ -74,7 +74,7 @@
}
}
fd.Close()
- return "", ENOENT
+ return "", ErrNotExist
Found:
pd, err := fd.Stat()
diff --git a/src/pkg/os/os_test.go b/src/pkg/os/os_test.go
index 9a95407..e02d7a4 100644
--- a/src/pkg/os/os_test.go
+++ b/src/pkg/os/os_test.go
@@ -13,6 +13,7 @@
"path/filepath"
"runtime"
"strings"
+ "syscall"
"testing"
"time"
)
@@ -769,7 +770,7 @@
for i, tt := range tests {
off, err := f.Seek(tt.in, tt.whence)
if off != tt.out || err != nil {
- if e, ok := err.(*PathError); ok && e.Err == EINVAL && tt.out > 1<<32 {
+ if e, ok := err.(*PathError); ok && e.Err == syscall.EINVAL && tt.out > 1<<32 {
// Reiserfs rejects the big seeks.
// http://code.google.com/p/go/issues/detail?id=91
break
@@ -789,17 +790,17 @@
{
sfdir + "/no-such-file",
O_RDONLY,
- ENOENT,
+ syscall.ENOENT,
},
{
sfdir,
O_WRONLY,
- EISDIR,
+ syscall.EISDIR,
},
{
sfdir + "/" + sfname + "/no-such-file",
O_WRONLY,
- ENOTDIR,
+ syscall.ENOTDIR,
},
}
diff --git a/src/pkg/os/path.go b/src/pkg/os/path.go
index e962f3e..02a77ec 100644
--- a/src/pkg/os/path.go
+++ b/src/pkg/os/path.go
@@ -4,7 +4,10 @@
package os
-import "io"
+import (
+ "io"
+ "syscall"
+)
// MkdirAll creates a directory named path,
// along with any necessary parents, and returns nil,
@@ -20,7 +23,7 @@
if dir.IsDir() {
return nil
}
- return &PathError{"mkdir", path, ENOTDIR}
+ return &PathError{"mkdir", path, syscall.ENOTDIR}
}
// Doesn't already exist; make sure parent does.
@@ -70,7 +73,7 @@
// Otherwise, is this a directory we need to recurse into?
dir, serr := Lstat(path)
if serr != nil {
- if serr, ok := serr.(*PathError); ok && (serr.Err == ENOENT || serr.Err == ENOTDIR) {
+ if serr, ok := serr.(*PathError); ok && (IsNotExist(serr.Err) || serr.Err == syscall.ENOTDIR) {
return nil
}
return serr
diff --git a/src/pkg/os/path_test.go b/src/pkg/os/path_test.go
index 8a78600..c1e3fb3 100644
--- a/src/pkg/os/path_test.go
+++ b/src/pkg/os/path_test.go
@@ -8,6 +8,7 @@
. "os"
"path/filepath"
"runtime"
+ "syscall"
"testing"
)
@@ -201,7 +202,7 @@
if err != nil {
pathErr, ok := err.(*PathError)
// common for users not to be able to write to /
- if ok && pathErr.Err == EACCES {
+ if ok && pathErr.Err == syscall.EACCES {
return
}
t.Fatalf(`MkdirAll "/_go_os_test/dir": %v`, err)
diff --git a/src/pkg/os/stat_plan9.go b/src/pkg/os/stat_plan9.go
index 0062258..a7990a3 100644
--- a/src/pkg/os/stat_plan9.go
+++ b/src/pkg/os/stat_plan9.go
@@ -62,7 +62,7 @@
return nil, &PathError{"stat", name, err}
}
if n < syscall.STATFIXLEN {
- return nil, &PathError{"stat", name, Eshortstat}
+ return nil, &PathError{"stat", name, errShortStat}
}
// Pull the real size out of the stat message.
@@ -79,7 +79,7 @@
return
}
}
- return nil, &PathError{"stat", name, Ebadstat}
+ return nil, &PathError{"stat", name, errBadStat}
}
// Stat returns a FileInfo structure describing the named file.
diff --git a/src/pkg/os/stat_windows.go b/src/pkg/os/stat_windows.go
index c8bfc3f..ffb679f 100644
--- a/src/pkg/os/stat_windows.go
+++ b/src/pkg/os/stat_windows.go
@@ -14,7 +14,7 @@
// If there is an error, it will be of type *PathError.
func (file *File) Stat() (fi FileInfo, err error) {
if file == nil || file.fd < 0 {
- return nil, EINVAL
+ return nil, syscall.EINVAL
}
if file.isdir() {
// I don't know any better way to do that for directory