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