os: emulate plan 9 libc in stat

This change is a recreation of the CL written
by Nick Owens on http://golang.org/cl/150730043.

If the stat buffer is too short, the kernel
informs us by putting the 2-byte size in the
buffer, so we read that and try again.

This follows the same algorithm as /sys/src/libc/9sys/dirfstat.c.

Fixes #8781.

Change-Id: I01b4ad3a5e705dd4cab6673c7a119f8bef9bbd7c
Reviewed-on: https://go-review.googlesource.com/3281
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/src/os/stat_plan9.go b/src/os/stat_plan9.go
index 25c9a8c..5722787 100644
--- a/src/os/stat_plan9.go
+++ b/src/os/stat_plan9.go
@@ -9,6 +9,8 @@
 	"time"
 )
 
+const _BIT16SZ = 2
+
 func sameFile(fs1, fs2 *fileStat) bool {
 	a := fs1.sys.(*syscall.Dir)
 	b := fs2.sys.(*syscall.Dir)
@@ -41,16 +43,14 @@
 // arg is an open *File or a path string.
 func dirstat(arg interface{}) (*syscall.Dir, error) {
 	var name string
+	var err error
 
-	// This is big enough for most stat messages
-	// and rounded to a multiple of 128 bytes.
-	size := (syscall.STATFIXLEN + 16*4 + 128) &^ 128
+	size := syscall.STATFIXLEN + 16*4
 
 	for i := 0; i < 2; i++ {
-		buf := make([]byte, size)
+		buf := make([]byte, _BIT16SZ+size)
 
 		var n int
-		var err error
 		switch a := arg.(type) {
 		case *File:
 			name = a.name
@@ -61,10 +61,8 @@
 		default:
 			panic("phase error in dirstat")
 		}
-		if err != nil {
-			return nil, &PathError{"stat", name, err}
-		}
-		if n < syscall.STATFIXLEN {
+
+		if n < _BIT16SZ {
 			return nil, &PathError{"stat", name, syscall.ErrShortStat}
 		}
 
@@ -73,17 +71,21 @@
 
 		// If the stat message is larger than our buffer we will
 		// go around the loop and allocate one that is big enough.
-		if size > n {
-			continue
+		if size <= n {
+			d, err := syscall.UnmarshalDir(buf[:n])
+			if err != nil {
+				return nil, &PathError{"stat", name, err}
+			}
+			return d, nil
 		}
 
-		d, err := syscall.UnmarshalDir(buf[:n])
-		if err != nil {
-			return nil, &PathError{"stat", name, err}
-		}
-		return d, nil
 	}
-	return nil, &PathError{"stat", name, syscall.ErrBadStat}
+
+	if err == nil {
+		err = syscall.ErrBadStat
+	}
+
+	return nil, &PathError{"stat", name, err}
 }
 
 // Stat returns a FileInfo describing the named file.