From 173dfbd051483e6c692377eef0e845c6a5fdbe97 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood <nick@craig-wood.com>
Date: Wed, 27 Feb 2019 16:38:32 +0000
Subject: [PATCH] vfs: read directory and check for a file before mkdir

Before this change when doing Mkdir the VFS layer could add the new
item to an unread directory which caused confusion.

It could also do mkdir on a file when run on a bucket based remote
which would temporarily overwrite the file with a directory.

Fixes #2993
---
 vfs/dir.go      | 19 +++++++++++++++++--
 vfs/dir_test.go | 27 +++++++++++++++++++++++++++
 2 files changed, 44 insertions(+), 2 deletions(-)

diff --git a/vfs/dir.go b/vfs/dir.go
index da857c70e..aa7567cc8 100644
--- a/vfs/dir.go
+++ b/vfs/dir.go
@@ -458,8 +458,23 @@ func (d *Dir) Mkdir(name string) (*Dir, error) {
 		return nil, EROFS
 	}
 	path := path.Join(d.path, name)
+	node, err := d.stat(name)
+	switch err {
+	case ENOENT:
+		// not found, carry on
+	case nil:
+		// found so check what it is
+		if node.IsDir() {
+			return node.(*Dir), err
+		}
+		return nil, EEXIST
+	default:
+		// a different error - report
+		fs.Errorf(d, "Dir.Mkdir failed to read directory: %v", err)
+		return nil, err
+	}
 	// fs.Debugf(path, "Dir.Mkdir")
-	err := d.f.Mkdir(path)
+	err = d.f.Mkdir(path)
 	if err != nil {
 		fs.Errorf(d, "Dir.Mkdir failed to create directory: %v", err)
 		return nil, err
@@ -600,7 +615,7 @@ func (d *Dir) Rename(oldName, newName string, destDir *Dir) error {
 		}
 	default:
 		err = errors.Errorf("unknown type %T", oldNode)
-		fs.Errorf(d.path, "Dir.ReadDirAll error: %v", err)
+		fs.Errorf(d.path, "Dir.Rename error: %v", err)
 		return err
 	}
 
diff --git a/vfs/dir_test.go b/vfs/dir_test.go
index 958160f2a..43efa2a9a 100644
--- a/vfs/dir_test.go
+++ b/vfs/dir_test.go
@@ -353,6 +353,33 @@ func TestDirMkdir(t *testing.T) {
 	assert.Equal(t, EROFS, err)
 }
 
+func TestDirMkdirSub(t *testing.T) {
+	r := fstest.NewRun(t)
+	defer r.Finalise()
+	vfs, dir, file1 := dirCreate(t, r)
+
+	_, err := dir.Mkdir("file1")
+	assert.Error(t, err)
+
+	sub, err := dir.Mkdir("sub")
+	assert.NoError(t, err)
+
+	subsub, err := sub.Mkdir("subsub")
+	assert.NoError(t, err)
+
+	// check the vfs
+	checkListing(t, dir, []string{"file1,14,false", "sub,0,true"})
+	checkListing(t, sub, []string{"subsub,0,true"})
+	checkListing(t, subsub, []string(nil))
+
+	// check the underlying r.Fremote
+	fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1}, []string{"dir", "dir/sub", "dir/sub/subsub"}, r.Fremote.Precision())
+
+	vfs.Opt.ReadOnly = true
+	_, err = dir.Mkdir("sausage")
+	assert.Equal(t, EROFS, err)
+}
+
 func TestDirRemove(t *testing.T) {
 	r := fstest.NewRun(t)
 	defer r.Finalise()