webdav: allow opening the memFS root file "/".

Also fix memFile.Readdir(n) to return nil error when n <= 0, to match
package os. Fix memFile.Write to fail when writing to directories. Avoid
taking the node lock twice for memFile methods.

Change-Id: I72b0753c9376c3889972662e0454efe67d73479a
Reviewed-on: https://go-review.googlesource.com/2711
Reviewed-by: Dave Cheney <dave@cheney.net>
Reviewed-by: Nick Cooper <nmvc@google.com>
diff --git a/webdav/file.go b/webdav/file.go
index 4a0c7ff..782f03d 100644
--- a/webdav/file.go
+++ b/webdav/file.go
@@ -126,6 +126,8 @@
 //   - "/", "foo", false
 //   - "/foo/", "bar", false
 //   - "/foo/bar/", "x", true
+// The frag argument will be empty only if dir is the root node and the walk
+// ends at that root node.
 func (fs *memFS) walk(op, fullname string, f func(dir *memFSNode, frag string, final bool) error) error {
 	fs.mu.Lock()
 	defer fs.mu.Unlock()
@@ -147,6 +149,9 @@
 		if !final {
 			frag, remaining = fullname[:i], fullname[i+1:]
 		}
+		if frag == "" && dir != &fs.root {
+			panic("webdav: empty path fragment for a clean path")
+		}
 		if err := f(dir, frag, final); err != nil {
 			return &os.PathError{
 				Op:   op,
@@ -204,32 +209,38 @@
 		if !final {
 			return nil
 		}
+		var n *memFSNode
 		if frag == "" {
-			return os.ErrInvalid
-		}
-		if flag&(os.O_SYNC|os.O_APPEND) != 0 {
-			return os.ErrInvalid
-		}
-		n := dir.children[frag]
-		if flag&os.O_CREATE != 0 {
-			if flag&os.O_EXCL != 0 && n != nil {
-				return os.ErrExist
+			if flag&(os.O_WRONLY|os.O_RDWR) != 0 {
+				return os.ErrPermission
+			}
+			n = &fs.root
+
+		} else {
+			n = dir.children[frag]
+			if flag&(os.O_SYNC|os.O_APPEND) != 0 {
+				return os.ErrInvalid
+			}
+			if flag&os.O_CREATE != 0 {
+				if flag&os.O_EXCL != 0 && n != nil {
+					return os.ErrExist
+				}
+				if n == nil {
+					n = &memFSNode{
+						name: frag,
+						mode: perm.Perm(),
+					}
+					dir.children[frag] = n
+				}
 			}
 			if n == nil {
-				n = &memFSNode{
-					name: frag,
-					mode: perm.Perm(),
-				}
-				dir.children[frag] = n
+				return os.ErrNotExist
 			}
-		}
-		if n == nil {
-			return os.ErrNotExist
-		}
-		if flag&(os.O_WRONLY|os.O_RDWR) != 0 && flag&os.O_TRUNC != 0 {
-			n.mu.Lock()
-			n.data = nil
-			n.mu.Unlock()
+			if flag&(os.O_WRONLY|os.O_RDWR) != 0 && flag&os.O_TRUNC != 0 {
+				n.mu.Lock()
+				n.data = nil
+				n.mu.Unlock()
+			}
 		}
 
 		children := make([]os.FileInfo, 0, len(n.children))
@@ -271,7 +282,8 @@
 			return nil
 		}
 		if frag == "" {
-			return os.ErrInvalid
+			n = &fs.root
+			return nil
 		}
 		n = dir.children[frag]
 		if n == nil {
@@ -342,11 +354,11 @@
 }
 
 func (f *memFile) Read(p []byte) (int, error) {
-	if f.n.IsDir() {
-		return 0, os.ErrInvalid
-	}
 	f.n.mu.Lock()
 	defer f.n.mu.Unlock()
+	if f.n.mode.IsDir() {
+		return 0, os.ErrInvalid
+	}
 	if f.pos >= len(f.n.data) {
 		return 0, io.EOF
 	}
@@ -356,14 +368,19 @@
 }
 
 func (f *memFile) Readdir(count int) ([]os.FileInfo, error) {
-	if !f.n.IsDir() {
-		return nil, os.ErrInvalid
-	}
 	f.n.mu.Lock()
 	defer f.n.mu.Unlock()
+	if !f.n.mode.IsDir() {
+		return nil, os.ErrInvalid
+	}
 	old := f.pos
 	if old >= len(f.children) {
-		return nil, io.EOF
+		// The os.File Readdir docs say that at the end of a directory,
+		// the error is io.EOF if count > 0 and nil if count <= 0.
+		if count > 0 {
+			return nil, io.EOF
+		}
+		return nil, nil
 	}
 	if count > 0 {
 		f.pos += count
@@ -408,6 +425,9 @@
 	f.n.mu.Lock()
 	defer f.n.mu.Unlock()
 
+	if f.n.mode.IsDir() {
+		return 0, os.ErrInvalid
+	}
 	if f.pos < len(f.n.data) {
 		n := copy(f.n.data[f.pos:], p)
 		f.pos += n
diff --git a/webdav/file_test.go b/webdav/file_test.go
index d95d5a8..babc2bf 100644
--- a/webdav/file_test.go
+++ b/webdav/file_test.go
@@ -198,6 +198,61 @@
 	}
 }
 
+func TestMemFSRoot(t *testing.T) {
+	fs := NewMemFS()
+	for i := 0; i < 5; i++ {
+		stat, err := fs.Stat("/")
+		if err != nil {
+			t.Fatalf("i=%d: Stat: %v", i, err)
+		}
+		if !stat.IsDir() {
+			t.Fatalf("i=%d: Stat.IsDir is false, want true", i)
+		}
+
+		f, err := fs.OpenFile("/", os.O_RDONLY, 0)
+		if err != nil {
+			t.Fatalf("i=%d: OpenFile: %v", i, err)
+		}
+		defer f.Close()
+		children, err := f.Readdir(-1)
+		if err != nil {
+			t.Fatalf("i=%d: Readdir: %v", i, err)
+		}
+		if len(children) != i {
+			t.Fatalf("i=%d: got %d children, want %d", i, len(children), i)
+		}
+
+		if _, err := f.Write(make([]byte, 1)); err == nil {
+			t.Fatalf("i=%d: Write: got nil error, want non-nil", i)
+		}
+
+		if err := fs.Mkdir(fmt.Sprintf("/dir%d", i), 0777); err != nil {
+			t.Fatalf("i=%d: Mkdir: %v", i, err)
+		}
+	}
+}
+
+func TestMemFileReaddir(t *testing.T) {
+	fs := NewMemFS()
+	if err := fs.Mkdir("/foo", 0777); err != nil {
+		t.Fatalf("Mkdir: %v", err)
+	}
+	readdir := func(count int) ([]os.FileInfo, error) {
+		f, err := fs.OpenFile("/foo", os.O_RDONLY, 0)
+		if err != nil {
+			t.Fatalf("OpenFile: %v", err)
+		}
+		defer f.Close()
+		return f.Readdir(count)
+	}
+	if got, err := readdir(-1); len(got) != 0 || err != nil {
+		t.Fatalf("readdir(-1): got %d fileInfos with err=%v, want 0, <nil>", len(got), err)
+	}
+	if got, err := readdir(+1); len(got) != 0 || err != io.EOF {
+		t.Fatalf("readdir(+1): got %d fileInfos with err=%v, want 0, EOF", len(got), err)
+	}
+}
+
 func TestMemFile(t *testing.T) {
 	testCases := []string{
 		"wantData ",