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 ",