os, net, internal/poll: return consistent error for closed socket

In the past we returned "use of closed network connection" when using
a closed network descriptor in some way. In CL 36799 that was changed
to return "use of closed file or network connection". Because programs
have no access to a value of this error type (see issue #4373) they
resort to doing direct string comparisons (see issue #19252). This CL
restores the old error string so that we don't break programs
unnecessarily with the 1.9 release.

This adds a test to the net package for the expected string.

For symmetry check that the os package returns the expected error,
which for os already exists as os.ErrClosed.

Updates #4373.
Fixed #19252.

Change-Id: I5b83fd12cfa03501a077cad9336499b819f4a38b
Reviewed-on: https://go-review.googlesource.com/39997
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
diff --git a/src/internal/poll/fd.go b/src/internal/poll/fd.go
index 3d3f36e..f1454db 100644
--- a/src/internal/poll/fd.go
+++ b/src/internal/poll/fd.go
@@ -11,8 +11,23 @@
 
 import "errors"
 
-// ErrClosing is returned when a descriptor is used after it has been closed.
-var ErrClosing = errors.New("use of closed file or network connection")
+// ErrNetClosing is returned when a network descriptor is used after
+// it has been closed. Keep this string consistent because of issue
+// #4373: since historically programs have not been able to detect
+// this error, they look for the string.
+var ErrNetClosing = errors.New("use of closed network connection")
+
+// ErrFileClosing is returned when a file descriptor is used after it
+// has been closed.
+var ErrFileClosing = errors.New("use of closed file")
+
+// Return the appropriate closing error based on isFile.
+func errClosing(isFile bool) error {
+	if isFile {
+		return ErrFileClosing
+	}
+	return ErrNetClosing
+}
 
 // ErrTimeout is returned for an expired deadline.
 var ErrTimeout error = &TimeoutError{}
diff --git a/src/internal/poll/fd_mutex.go b/src/internal/poll/fd_mutex.go
index 38d4be2..76174e5 100644
--- a/src/internal/poll/fd_mutex.go
+++ b/src/internal/poll/fd_mutex.go
@@ -198,7 +198,7 @@
 // It returns an error when fd cannot be used.
 func (fd *FD) incref() error {
 	if !fd.fdmu.incref() {
-		return ErrClosing
+		return errClosing(fd.isFile)
 	}
 	return nil
 }
@@ -217,7 +217,7 @@
 // It returns an error when fd cannot be used for reading.
 func (fd *FD) readLock() error {
 	if !fd.fdmu.rwlock(true) {
-		return ErrClosing
+		return errClosing(fd.isFile)
 	}
 	return nil
 }
@@ -235,7 +235,7 @@
 // It returns an error when fd cannot be used for writing.
 func (fd *FD) writeLock() error {
 	if !fd.fdmu.rwlock(false) {
-		return ErrClosing
+		return errClosing(fd.isFile)
 	}
 	return nil
 }
diff --git a/src/internal/poll/fd_plan9.go b/src/internal/poll/fd_plan9.go
index 574036e..49590ab 100644
--- a/src/internal/poll/fd_plan9.go
+++ b/src/internal/poll/fd_plan9.go
@@ -29,6 +29,12 @@
 	wtimer    *time.Timer
 	rtimedout atomicBool // set true when read deadline has been reached
 	wtimedout atomicBool // set true when write deadline has been reached
+
+	// Whether this is a normal file.
+	// On Plan 9 we do not use this package for ordinary files,
+	// so this is always false, but the field is present because
+	// shared code in fd_mutex.go checks it.
+	isFile bool
 }
 
 // We need this to close out a file descriptor when it is unlocked,
@@ -45,7 +51,7 @@
 // is in the net package.
 func (fd *FD) Close() error {
 	if !fd.fdmu.increfAndClose() {
-		return ErrClosing
+		return errClosing(fd.isFile)
 	}
 	return nil
 }
diff --git a/src/internal/poll/fd_poll_nacl.go b/src/internal/poll/fd_poll_nacl.go
index 45256a4..8fa75c5 100644
--- a/src/internal/poll/fd_poll_nacl.go
+++ b/src/internal/poll/fd_poll_nacl.go
@@ -25,27 +25,27 @@
 	}
 }
 
-func (pd *pollDesc) prepare(mode int) error {
+func (pd *pollDesc) prepare(mode int, isFile bool) error {
 	if pd.closing {
-		return ErrClosing
+		return errClosing(isFile)
 	}
 	return nil
 }
 
-func (pd *pollDesc) prepareRead() error { return pd.prepare('r') }
+func (pd *pollDesc) prepareRead(isFile bool) error { return pd.prepare('r', isFile) }
 
-func (pd *pollDesc) prepareWrite() error { return pd.prepare('w') }
+func (pd *pollDesc) prepareWrite(isFile bool) error { return pd.prepare('w', isFile) }
 
-func (pd *pollDesc) wait(mode int) error {
+func (pd *pollDesc) wait(mode int, isFile bool) error {
 	if pd.closing {
-		return ErrClosing
+		return errClosing(isFile)
 	}
 	return ErrTimeout
 }
 
-func (pd *pollDesc) waitRead() error { return pd.wait('r') }
+func (pd *pollDesc) waitRead(isFile bool) error { return pd.wait('r', isFile) }
 
-func (pd *pollDesc) waitWrite() error { return pd.wait('w') }
+func (pd *pollDesc) waitWrite(isFile bool) error { return pd.wait('w', isFile) }
 
 func (pd *pollDesc) waitCanceled(mode int) {}
 
diff --git a/src/internal/poll/fd_poll_runtime.go b/src/internal/poll/fd_poll_runtime.go
index b1e3a84..08b40c2 100644
--- a/src/internal/poll/fd_poll_runtime.go
+++ b/src/internal/poll/fd_poll_runtime.go
@@ -62,36 +62,36 @@
 	runtime_pollUnblock(pd.runtimeCtx)
 }
 
-func (pd *pollDesc) prepare(mode int) error {
+func (pd *pollDesc) prepare(mode int, isFile bool) error {
 	if pd.runtimeCtx == 0 {
 		return nil
 	}
 	res := runtime_pollReset(pd.runtimeCtx, mode)
-	return convertErr(res)
+	return convertErr(res, isFile)
 }
 
-func (pd *pollDesc) prepareRead() error {
-	return pd.prepare('r')
+func (pd *pollDesc) prepareRead(isFile bool) error {
+	return pd.prepare('r', isFile)
 }
 
-func (pd *pollDesc) prepareWrite() error {
-	return pd.prepare('w')
+func (pd *pollDesc) prepareWrite(isFile bool) error {
+	return pd.prepare('w', isFile)
 }
 
-func (pd *pollDesc) wait(mode int) error {
+func (pd *pollDesc) wait(mode int, isFile bool) error {
 	if pd.runtimeCtx == 0 {
 		return errors.New("waiting for unsupported file type")
 	}
 	res := runtime_pollWait(pd.runtimeCtx, mode)
-	return convertErr(res)
+	return convertErr(res, isFile)
 }
 
-func (pd *pollDesc) waitRead() error {
-	return pd.wait('r')
+func (pd *pollDesc) waitRead(isFile bool) error {
+	return pd.wait('r', isFile)
 }
 
-func (pd *pollDesc) waitWrite() error {
-	return pd.wait('w')
+func (pd *pollDesc) waitWrite(isFile bool) error {
+	return pd.wait('w', isFile)
 }
 
 func (pd *pollDesc) waitCanceled(mode int) {
@@ -101,12 +101,12 @@
 	runtime_pollWaitCanceled(pd.runtimeCtx, mode)
 }
 
-func convertErr(res int) error {
+func convertErr(res int, isFile bool) error {
 	switch res {
 	case 0:
 		return nil
 	case 1:
-		return ErrClosing
+		return errClosing(isFile)
 	case 2:
 		return ErrTimeout
 	}
diff --git a/src/internal/poll/fd_posix_test.go b/src/internal/poll/fd_posix_test.go
index edc2dcb..cbe015e 100644
--- a/src/internal/poll/fd_posix_test.go
+++ b/src/internal/poll/fd_posix_test.go
@@ -20,17 +20,17 @@
 }{
 	{100, nil, &FD{ZeroReadIsEOF: true}, nil},
 	{100, io.EOF, &FD{ZeroReadIsEOF: true}, io.EOF},
-	{100, ErrClosing, &FD{ZeroReadIsEOF: true}, ErrClosing},
+	{100, ErrNetClosing, &FD{ZeroReadIsEOF: true}, ErrNetClosing},
 	{0, nil, &FD{ZeroReadIsEOF: true}, io.EOF},
 	{0, io.EOF, &FD{ZeroReadIsEOF: true}, io.EOF},
-	{0, ErrClosing, &FD{ZeroReadIsEOF: true}, ErrClosing},
+	{0, ErrNetClosing, &FD{ZeroReadIsEOF: true}, ErrNetClosing},
 
 	{100, nil, &FD{ZeroReadIsEOF: false}, nil},
 	{100, io.EOF, &FD{ZeroReadIsEOF: false}, io.EOF},
-	{100, ErrClosing, &FD{ZeroReadIsEOF: false}, ErrClosing},
+	{100, ErrNetClosing, &FD{ZeroReadIsEOF: false}, ErrNetClosing},
 	{0, nil, &FD{ZeroReadIsEOF: false}, nil},
 	{0, io.EOF, &FD{ZeroReadIsEOF: false}, io.EOF},
-	{0, ErrClosing, &FD{ZeroReadIsEOF: false}, ErrClosing},
+	{0, ErrNetClosing, &FD{ZeroReadIsEOF: false}, ErrNetClosing},
 }
 
 func TestEOFError(t *testing.T) {
diff --git a/src/internal/poll/fd_unix.go b/src/internal/poll/fd_unix.go
index c461f04..782ecd5 100644
--- a/src/internal/poll/fd_unix.go
+++ b/src/internal/poll/fd_unix.go
@@ -33,11 +33,23 @@
 	// Whether a zero byte read indicates EOF. This is false for a
 	// message based socket connection.
 	ZeroReadIsEOF bool
+
+	// Whether this is a file rather than a network socket.
+	isFile bool
 }
 
 // Init initializes the FD. The Sysfd field should already be set.
 // This can be called multiple times on a single FD.
-func (fd *FD) Init() error {
+// The net argument is a network name from the net package (e.g., "tcp"),
+// or "file".
+func (fd *FD) Init(net string, pollable bool) error {
+	// We don't actually care about the various network types.
+	if net == "file" {
+		fd.isFile = true
+	}
+	if !pollable {
+		return nil
+	}
 	return fd.pd.init(fd)
 }
 
@@ -56,13 +68,13 @@
 // destroy method when there are no remaining references.
 func (fd *FD) Close() error {
 	if !fd.fdmu.increfAndClose() {
-		return ErrClosing
+		return errClosing(fd.isFile)
 	}
 	// Unblock any I/O.  Once it all unblocks and returns,
 	// so that it cannot be referring to fd.sysfd anymore,
 	// the final decref will close fd.sysfd. This should happen
 	// fairly quickly, since all the I/O is non-blocking, and any
-	// attempts to block in the pollDesc will return ErrClosing.
+	// attempts to block in the pollDesc will return errClosing(fd.isFile).
 	fd.pd.evict()
 	// The call to decref will call destroy if there are no other
 	// references.
@@ -99,7 +111,7 @@
 		// TODO(bradfitz): make it wait for readability? (Issue 15735)
 		return 0, nil
 	}
-	if err := fd.pd.prepareRead(); err != nil {
+	if err := fd.pd.prepareRead(fd.isFile); err != nil {
 		return 0, err
 	}
 	if fd.IsStream && len(p) > maxRW {
@@ -110,7 +122,7 @@
 		if err != nil {
 			n = 0
 			if err == syscall.EAGAIN {
-				if err = fd.pd.waitRead(); err == nil {
+				if err = fd.pd.waitRead(fd.isFile); err == nil {
 					continue
 				}
 			}
@@ -146,7 +158,7 @@
 		return 0, nil, err
 	}
 	defer fd.readUnlock()
-	if err := fd.pd.prepareRead(); err != nil {
+	if err := fd.pd.prepareRead(fd.isFile); err != nil {
 		return 0, nil, err
 	}
 	for {
@@ -154,7 +166,7 @@
 		if err != nil {
 			n = 0
 			if err == syscall.EAGAIN {
-				if err = fd.pd.waitRead(); err == nil {
+				if err = fd.pd.waitRead(fd.isFile); err == nil {
 					continue
 				}
 			}
@@ -170,7 +182,7 @@
 		return 0, 0, 0, nil, err
 	}
 	defer fd.readUnlock()
-	if err := fd.pd.prepareRead(); err != nil {
+	if err := fd.pd.prepareRead(fd.isFile); err != nil {
 		return 0, 0, 0, nil, err
 	}
 	for {
@@ -178,7 +190,7 @@
 		if err != nil {
 			// TODO(dfc) should n and oobn be set to 0
 			if err == syscall.EAGAIN {
-				if err = fd.pd.waitRead(); err == nil {
+				if err = fd.pd.waitRead(fd.isFile); err == nil {
 					continue
 				}
 			}
@@ -194,7 +206,7 @@
 		return 0, err
 	}
 	defer fd.writeUnlock()
-	if err := fd.pd.prepareWrite(); err != nil {
+	if err := fd.pd.prepareWrite(fd.isFile); err != nil {
 		return 0, err
 	}
 	var nn int
@@ -211,7 +223,7 @@
 			return nn, err
 		}
 		if err == syscall.EAGAIN {
-			if err = fd.pd.waitWrite(); err == nil {
+			if err = fd.pd.waitWrite(fd.isFile); err == nil {
 				continue
 			}
 		}
@@ -261,13 +273,13 @@
 		return 0, err
 	}
 	defer fd.writeUnlock()
-	if err := fd.pd.prepareWrite(); err != nil {
+	if err := fd.pd.prepareWrite(fd.isFile); err != nil {
 		return 0, err
 	}
 	for {
 		err := syscall.Sendto(fd.Sysfd, p, 0, sa)
 		if err == syscall.EAGAIN {
-			if err = fd.pd.waitWrite(); err == nil {
+			if err = fd.pd.waitWrite(fd.isFile); err == nil {
 				continue
 			}
 		}
@@ -284,13 +296,13 @@
 		return 0, 0, err
 	}
 	defer fd.writeUnlock()
-	if err := fd.pd.prepareWrite(); err != nil {
+	if err := fd.pd.prepareWrite(fd.isFile); err != nil {
 		return 0, 0, err
 	}
 	for {
 		n, err := syscall.SendmsgN(fd.Sysfd, p, oob, sa, 0)
 		if err == syscall.EAGAIN {
-			if err = fd.pd.waitWrite(); err == nil {
+			if err = fd.pd.waitWrite(fd.isFile); err == nil {
 				continue
 			}
 		}
@@ -308,7 +320,7 @@
 	}
 	defer fd.readUnlock()
 
-	if err := fd.pd.prepareRead(); err != nil {
+	if err := fd.pd.prepareRead(fd.isFile); err != nil {
 		return -1, nil, "", err
 	}
 	for {
@@ -318,7 +330,7 @@
 		}
 		switch err {
 		case syscall.EAGAIN:
-			if err = fd.pd.waitRead(); err == nil {
+			if err = fd.pd.waitRead(fd.isFile); err == nil {
 				continue
 			}
 		case syscall.ECONNABORTED:
@@ -353,7 +365,7 @@
 		if err != nil {
 			n = 0
 			if err == syscall.EAGAIN {
-				if err = fd.pd.waitRead(); err == nil {
+				if err = fd.pd.waitRead(fd.isFile); err == nil {
 					continue
 				}
 			}
@@ -385,5 +397,5 @@
 
 // WaitWrite waits until data can be read from fd.
 func (fd *FD) WaitWrite() error {
-	return fd.pd.waitWrite()
+	return fd.pd.waitWrite(fd.isFile)
 }
diff --git a/src/internal/poll/fd_windows.go b/src/internal/poll/fd_windows.go
index 4d7ec68..d312cfe 100644
--- a/src/internal/poll/fd_windows.go
+++ b/src/internal/poll/fd_windows.go
@@ -160,7 +160,7 @@
 
 	fd := o.fd
 	// Notify runtime netpoll about starting IO.
-	err := fd.pd.prepare(int(o.mode))
+	err := fd.pd.prepare(int(o.mode), fd.isFile)
 	if err != nil {
 		return 0, err
 	}
@@ -188,7 +188,7 @@
 		return 0, err
 	}
 	// Wait for our request to complete.
-	err = fd.pd.wait(int(o.mode))
+	err = fd.pd.wait(int(o.mode), fd.isFile)
 	if err == nil {
 		// All is good. Extract our IO results and return.
 		if o.errno != 0 {
@@ -200,7 +200,7 @@
 	// IO is interrupted by "close" or "timeout"
 	netpollErr := err
 	switch netpollErr {
-	case ErrClosing, ErrTimeout:
+	case ErrNetClosing, ErrFileClosing, ErrTimeout:
 		// will deal with those.
 	default:
 		panic("unexpected runtime.netpoll error: " + netpollErr.Error())
@@ -380,7 +380,7 @@
 // the destroy method when there are no remaining references.
 func (fd *FD) Close() error {
 	if !fd.fdmu.increfAndClose() {
-		return ErrClosing
+		return errClosing(fd.isFile)
 	}
 	// unblock pending reader and writer
 	fd.pd.evict()
diff --git a/src/internal/poll/sendfile_bsd.go b/src/internal/poll/sendfile_bsd.go
index 13ef205..980a75a 100644
--- a/src/internal/poll/sendfile_bsd.go
+++ b/src/internal/poll/sendfile_bsd.go
@@ -37,7 +37,7 @@
 			break
 		}
 		if err1 == syscall.EAGAIN {
-			if err1 = dstFD.pd.waitWrite(); err1 == nil {
+			if err1 = dstFD.pd.waitWrite(dstFD.isFile); err1 == nil {
 				continue
 			}
 		}
diff --git a/src/internal/poll/sendfile_linux.go b/src/internal/poll/sendfile_linux.go
index 4014e05..52955a1 100644
--- a/src/internal/poll/sendfile_linux.go
+++ b/src/internal/poll/sendfile_linux.go
@@ -34,7 +34,7 @@
 			break
 		}
 		if err1 == syscall.EAGAIN {
-			if err1 = dstFD.pd.waitWrite(); err1 == nil {
+			if err1 = dstFD.pd.waitWrite(dstFD.isFile); err1 == nil {
 				continue
 			}
 		}
diff --git a/src/internal/poll/sendfile_solaris.go b/src/internal/poll/sendfile_solaris.go
index 816c17c..2ce5323 100644
--- a/src/internal/poll/sendfile_solaris.go
+++ b/src/internal/poll/sendfile_solaris.go
@@ -47,7 +47,7 @@
 			break
 		}
 		if err1 == syscall.EAGAIN {
-			if err1 = dstFD.pd.waitWrite(); err1 == nil {
+			if err1 = dstFD.pd.waitWrite(dstFD.isFile); err1 == nil {
 				continue
 			}
 		}
diff --git a/src/internal/poll/writev.go b/src/internal/poll/writev.go
index 574e0de..4bf8804 100644
--- a/src/internal/poll/writev.go
+++ b/src/internal/poll/writev.go
@@ -18,7 +18,7 @@
 		return 0, err
 	}
 	defer fd.writeUnlock()
-	if err := fd.pd.prepareWrite(); err != nil {
+	if err := fd.pd.prepareWrite(fd.isFile); err != nil {
 		return 0, err
 	}
 
@@ -65,7 +65,7 @@
 		n += int64(wrote)
 		consume(v, int64(wrote))
 		if e0 == syscall.EAGAIN {
-			if err = fd.pd.waitWrite(); err == nil {
+			if err = fd.pd.waitWrite(fd.isFile); err == nil {
 				continue
 			}
 		} else if e0 != 0 {
diff --git a/src/net/error_test.go b/src/net/error_test.go
index 021968b..9791e6f 100644
--- a/src/net/error_test.go
+++ b/src/net/error_test.go
@@ -13,6 +13,7 @@
 	"net/internal/socktest"
 	"os"
 	"runtime"
+	"strings"
 	"testing"
 	"time"
 )
@@ -98,7 +99,7 @@
 		goto third
 	}
 	switch nestedErr {
-	case errCanceled, poll.ErrClosing, errMissingAddress, errNoSuitableAddress,
+	case errCanceled, poll.ErrNetClosing, errMissingAddress, errNoSuitableAddress,
 		context.DeadlineExceeded, context.Canceled:
 		return nil
 	}
@@ -433,7 +434,7 @@
 		goto third
 	}
 	switch nestedErr {
-	case poll.ErrClosing, poll.ErrTimeout:
+	case poll.ErrNetClosing, poll.ErrTimeout:
 		return nil
 	}
 	return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr)
@@ -475,7 +476,7 @@
 		goto third
 	}
 	switch nestedErr {
-	case errCanceled, poll.ErrClosing, errMissingAddress, poll.ErrTimeout, ErrWriteToConnected, io.ErrUnexpectedEOF:
+	case errCanceled, poll.ErrNetClosing, errMissingAddress, poll.ErrTimeout, ErrWriteToConnected, io.ErrUnexpectedEOF:
 		return nil
 	}
 	return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr)
@@ -490,11 +491,21 @@
 // parseCloseError parses nestedErr and reports whether it is a valid
 // error value from Close functions.
 // It returns nil when nestedErr is valid.
-func parseCloseError(nestedErr error) error {
+func parseCloseError(nestedErr error, isShutdown bool) error {
 	if nestedErr == nil {
 		return nil
 	}
 
+	// Because historically we have not exported the error that we
+	// return for an operation on a closed network connection,
+	// there are programs that test for the exact error string.
+	// Verify that string here so that we don't break those
+	// programs unexpectedly. See issues #4373 and #19252.
+	want := "use of closed network connection"
+	if !isShutdown && !strings.Contains(nestedErr.Error(), want) {
+		return fmt.Errorf("error string %q does not contain expected string %q", nestedErr, want)
+	}
+
 	switch err := nestedErr.(type) {
 	case *OpError:
 		if err := err.isValid(); err != nil {
@@ -518,7 +529,7 @@
 		goto third
 	}
 	switch nestedErr {
-	case poll.ErrClosing:
+	case poll.ErrNetClosing:
 		return nil
 	}
 	return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr)
@@ -548,23 +559,23 @@
 
 	for i := 0; i < 3; i++ {
 		err = c.(*TCPConn).CloseRead()
-		if perr := parseCloseError(err); perr != nil {
+		if perr := parseCloseError(err, true); perr != nil {
 			t.Errorf("#%d: %v", i, perr)
 		}
 	}
 	for i := 0; i < 3; i++ {
 		err = c.(*TCPConn).CloseWrite()
-		if perr := parseCloseError(err); perr != nil {
+		if perr := parseCloseError(err, true); perr != nil {
 			t.Errorf("#%d: %v", i, perr)
 		}
 	}
 	for i := 0; i < 3; i++ {
 		err = c.Close()
-		if perr := parseCloseError(err); perr != nil {
+		if perr := parseCloseError(err, false); perr != nil {
 			t.Errorf("#%d: %v", i, perr)
 		}
 		err = ln.Close()
-		if perr := parseCloseError(err); perr != nil {
+		if perr := parseCloseError(err, false); perr != nil {
 			t.Errorf("#%d: %v", i, perr)
 		}
 	}
@@ -577,7 +588,7 @@
 
 	for i := 0; i < 3; i++ {
 		err = pc.Close()
-		if perr := parseCloseError(err); perr != nil {
+		if perr := parseCloseError(err, false); perr != nil {
 			t.Errorf("#%d: %v", i, perr)
 		}
 	}
@@ -614,7 +625,7 @@
 		goto third
 	}
 	switch nestedErr {
-	case poll.ErrClosing, poll.ErrTimeout:
+	case poll.ErrNetClosing, poll.ErrTimeout:
 		return nil
 	}
 	return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr)
@@ -693,7 +704,7 @@
 		goto third
 	}
 	switch nestedErr {
-	case poll.ErrClosing:
+	case poll.ErrNetClosing:
 		return nil
 	}
 	return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr)
diff --git a/src/net/fd_unix.go b/src/net/fd_unix.go
index 505a1f1..1122ee4 100644
--- a/src/net/fd_unix.go
+++ b/src/net/fd_unix.go
@@ -43,7 +43,7 @@
 }
 
 func (fd *netFD) init() error {
-	return fd.pfd.Init()
+	return fd.pfd.Init(fd.net, true)
 }
 
 func (fd *netFD) setAddr(laddr, raddr Addr) {
@@ -75,7 +75,7 @@
 			return mapErr(ctx.Err())
 		default:
 		}
-		if err := fd.pfd.Init(); err != nil {
+		if err := fd.pfd.Init(fd.net, true); err != nil {
 			return err
 		}
 		runtime.KeepAlive(fd)
@@ -93,7 +93,7 @@
 	default:
 		return os.NewSyscallError("connect", err)
 	}
-	if err := fd.pfd.Init(); err != nil {
+	if err := fd.pfd.Init(fd.net, true); err != nil {
 		return err
 	}
 	if deadline, _ := ctx.Deadline(); !deadline.IsZero() {
diff --git a/src/net/file_test.go b/src/net/file_test.go
index 6566ce2..abf8b3a 100644
--- a/src/net/file_test.go
+++ b/src/net/file_test.go
@@ -90,7 +90,7 @@
 			f, err = c1.File()
 		}
 		if err := c1.Close(); err != nil {
-			if perr := parseCloseError(err); perr != nil {
+			if perr := parseCloseError(err, false); perr != nil {
 				t.Error(perr)
 			}
 			t.Error(err)
@@ -256,7 +256,7 @@
 			f, err = c1.File()
 		}
 		if err := c1.Close(); err != nil {
-			if perr := parseCloseError(err); perr != nil {
+			if perr := parseCloseError(err, false); perr != nil {
 				t.Error(perr)
 			}
 			t.Error(err)
diff --git a/src/net/net_test.go b/src/net/net_test.go
index 9a9a7e5..024505e 100644
--- a/src/net/net_test.go
+++ b/src/net/net_test.go
@@ -54,7 +54,7 @@
 			err = c.CloseRead()
 		}
 		if err != nil {
-			if perr := parseCloseError(err); perr != nil {
+			if perr := parseCloseError(err, true); perr != nil {
 				t.Error(perr)
 			}
 			t.Fatal(err)
@@ -94,7 +94,7 @@
 			err = c.CloseWrite()
 		}
 		if err != nil {
-			if perr := parseCloseError(err); perr != nil {
+			if perr := parseCloseError(err, true); perr != nil {
 				t.Error(perr)
 			}
 			t.Error(err)
@@ -139,7 +139,7 @@
 			err = c.CloseWrite()
 		}
 		if err != nil {
-			if perr := parseCloseError(err); perr != nil {
+			if perr := parseCloseError(err, true); perr != nil {
 				t.Error(perr)
 			}
 			t.Fatal(err)
@@ -184,7 +184,7 @@
 		defer c.Close()
 
 		if err := c.Close(); err != nil {
-			if perr := parseCloseError(err); perr != nil {
+			if perr := parseCloseError(err, false); perr != nil {
 				t.Error(perr)
 			}
 			t.Fatal(err)
@@ -215,7 +215,7 @@
 
 		dst := ln.Addr().String()
 		if err := ln.Close(); err != nil {
-			if perr := parseCloseError(err); perr != nil {
+			if perr := parseCloseError(err, false); perr != nil {
 				t.Error(perr)
 			}
 			t.Fatal(err)
@@ -269,7 +269,7 @@
 		defer c.Close()
 
 		if err := c.Close(); err != nil {
-			if perr := parseCloseError(err); perr != nil {
+			if perr := parseCloseError(err, false); perr != nil {
 				t.Error(perr)
 			}
 			t.Fatal(err)
@@ -292,7 +292,7 @@
 		}
 		addr := ln.Addr().String()
 		if err := ln.Close(); err != nil {
-			if perr := parseCloseError(err); perr != nil {
+			if perr := parseCloseError(err, false); perr != nil {
 				t.Error(perr)
 			}
 			t.Fatal(err)
diff --git a/src/os/file.go b/src/os/file.go
index e5a3efa..271197a 100644
--- a/src/os/file.go
+++ b/src/os/file.go
@@ -102,7 +102,7 @@
 	}
 	n, e := f.read(b)
 	if e != nil {
-		if e == poll.ErrClosing {
+		if e == poll.ErrFileClosing {
 			e = ErrClosed
 		}
 		if e == io.EOF {
diff --git a/src/os/file_unix.go b/src/os/file_unix.go
index 6850ff7..8473164 100644
--- a/src/os/file_unix.go
+++ b/src/os/file_unix.go
@@ -87,20 +87,18 @@
 		pollable = false
 	}
 
-	if pollable {
-		if err := f.pfd.Init(); err != nil {
-			// An error here indicates a failure to register
-			// with the netpoll system. That can happen for
-			// a file descriptor that is not supported by
-			// epoll/kqueue; for example, disk files on
-			// GNU/Linux systems. We assume that any real error
-			// will show up in later I/O.
-		} else {
-			// We successfully registered with netpoll, so put
-			// the file into nonblocking mode.
-			if err := syscall.SetNonblock(fdi, true); err == nil {
-				f.nonblock = true
-			}
+	if err := f.pfd.Init("file", pollable); err != nil {
+		// An error here indicates a failure to register
+		// with the netpoll system. That can happen for
+		// a file descriptor that is not supported by
+		// epoll/kqueue; for example, disk files on
+		// GNU/Linux systems. We assume that any real error
+		// will show up in later I/O.
+	} else if pollable {
+		// We successfully registered with netpoll, so put
+		// the file into nonblocking mode.
+		if err := syscall.SetNonblock(fdi, true); err == nil {
+			f.nonblock = true
 		}
 	}
 
diff --git a/src/os/pipe_test.go b/src/os/pipe_test.go
index 032173b..a7bd41f 100644
--- a/src/os/pipe_test.go
+++ b/src/os/pipe_test.go
@@ -144,6 +144,8 @@
 		t.Error("Read of closed pipe unexpectedly succeeded")
 	} else if pe, ok := err.(*os.PathError); !ok {
 		t.Errorf("Read of closed pipe returned unexpected error type %T; expected os.PathError", pe)
+	} else if pe.Err != os.ErrClosed {
+		t.Errorf("got error %q but expected %q", pe.Err, os.ErrClosed)
 	} else {
 		t.Logf("Read returned expected error %q", err)
 	}