From 868dc2c475725683cde1c4bd0d16014c040a632c Mon Sep 17 00:00:00 2001
From: Han-Wen Nienhuys <hanwen@google.com>
Date: Mon, 20 Jun 2011 20:10:07 -0300
Subject: [PATCH] Deal with deletion directories which are removed from the
 backing store.

Add test.
---
 unionfs/unionfs.go      | 67 +++++++++++++++++++++++++++--------------
 unionfs/unionfs_test.go | 26 ++++++++++++----
 2 files changed, 65 insertions(+), 28 deletions(-)

diff --git a/unionfs/unionfs.go b/unionfs/unionfs.go
index aa19c95..077547d 100644
--- a/unionfs/unionfs.go
+++ b/unionfs/unionfs.go
@@ -101,14 +101,10 @@ func NewUnionFs(name string, fileSystems []fuse.FileSystem, options UnionFsOptio
 	}
 
 	writable := g.fileSystems[0]
-	fi, code := writable.GetAttr(options.DeletionDirName)
-	if code == fuse.ENOENT {
-		code = writable.Mkdir(options.DeletionDirName, 0755)
-		fi, code = writable.GetAttr(options.DeletionDirName)
-	}
-	if !code.Ok() || !fi.IsDirectory() {
-		panic(fmt.Sprintf("could not create deletion path %v: %v",
-			options.DeletionDirName, code))
+	code := g.createDeletionStore()
+	if !code.Ok() {
+		log.Printf("could not create deletion path %v: %v", options.DeletionDirName, code)
+		return nil
 	}
 
 	g.deletionCache = NewDirCache(writable, options.DeletionDirName, int64(options.DeletionCacheTTLSecs*1e9))
@@ -122,24 +118,41 @@ func NewUnionFs(name string, fileSystems []fuse.FileSystem, options UnionFsOptio
 ////////////////
 // Deal with all the caches.
 
-func (me *UnionFs) isDeleted(name string) (deleted bool, accessError os.Error) {
+func (me *UnionFs) isDeleted(name string) (deleted bool, code fuse.Status) {
 	marker := me.deletionPath(name)
 	haveCache, found := me.deletionCache.HasEntry(filepath.Base(marker))
 	if haveCache {
-		return found, nil
+		return found, fuse.OK
 	}
 
-	_, code := me.fileSystems[0].GetAttr(marker)
+	_, code = me.fileSystems[0].GetAttr(marker)
 
 	if code == fuse.OK {
-		return true, nil
+		return true, code
+	}
+	if code == fuse.ENOENT {
+		return false, fuse.OK
 	}
+
+	log.Println("error accessing deletion marker:", marker)
+	return false, syscall.EROFS
+}
+
+func (me *UnionFs) createDeletionStore() (code fuse.Status) {
+	writable := me.fileSystems[0]
+	fi, code := writable.GetAttr(me.options.DeletionDirName)
 	if code == fuse.ENOENT {
-		return false, nil
+		code = writable.Mkdir(me.options.DeletionDirName, 0755)
+		if code.Ok() {
+			fi, code = writable.GetAttr(me.options.DeletionDirName)
+		}
+	}
+
+	if !code.Ok() || !fi.IsDirectory() {
+		code = syscall.EROFS
 	}
 
-	log.Println("error accessing deletion marker:", marker) 
-	return false, os.EROFS
+	return code
 }
 
 func (me *UnionFs) getBranch(name string) branchResult {
@@ -209,6 +222,11 @@ func (me *UnionFs) removeDeletion(name string) {
 }
 
 func (me *UnionFs) putDeletion(name string) (code fuse.Status) {
+	code = me.createDeletionStore()
+	if !code.Ok() {
+		return code
+	}
+
 	marker := me.deletionPath(name)
 	me.deletionCache.AddEntry(path.Base(marker))
 
@@ -218,7 +236,7 @@ func (me *UnionFs) putDeletion(name string) (code fuse.Status) {
 	if code.Ok() && fi.Size == int64(len(name)) {
 		return fuse.OK
 	}
-	
+
 	var f fuse.File
 	if code == fuse.ENOENT {
 		f, code = writable.Create(marker, uint32(os.O_TRUNC|os.O_WRONLY), 0644)
@@ -248,7 +266,7 @@ func (me *UnionFs) Promote(name string, srcResult branchResult) fuse.Status {
 		// TODO - implement rename for dirs, links, etc.
 		return fuse.ENOSYS
 	}
-	
+
 	writable := me.fileSystems[0]
 	sourceFs := me.fileSystems[srcResult.branch]
 
@@ -557,11 +575,11 @@ func (me *UnionFs) GetAttr(name string) (a *os.FileInfo, s fuse.Status) {
 	if name == me.options.DeletionDirName {
 		return nil, fuse.ENOENT
 	}
-	isDel, err := me.isDeleted(name)
-	if err != nil {
-		return nil, fuse.OsErrorToErrno(err)
+	isDel, s := me.isDeleted(name)
+	if !s.Ok() {
+		return nil, s
 	}
-	
+
 	if isDel {
 		return nil, fuse.ENOENT
 	}
@@ -627,7 +645,12 @@ func (me *UnionFs) OpenDir(directory string) (stream chan fuse.DirEntry, status
 
 	wg.Wait()
 	if deletions == nil {
-		return nil, syscall.EROFS
+		_, code := me.fileSystems[0].GetAttr(me.options.DeletionDirName)
+		if code == fuse.ENOENT {
+			deletions = map[string]bool{}
+		} else {
+			return nil, syscall.EROFS
+		}
 	}
 
 	results := entries[0]
diff --git a/unionfs/unionfs_test.go b/unionfs/unionfs_test.go
index 63ded70..2fab605 100644
--- a/unionfs/unionfs_test.go
+++ b/unionfs/unionfs_test.go
@@ -119,6 +119,20 @@ func remove(path string) {
 	fuse.CheckSuccess(err)
 }
 
+func TestAutocreateDeletionDir(t *testing.T) {
+	wd, clean := setupUfs(t)
+	defer clean()
+
+	err := os.Remove(wd+"/rw/DELETIONS")
+	CheckSuccess(err)
+
+	err = os.Mkdir(wd+"/mount/dir", 0755)
+	CheckSuccess(err)
+
+	_, err = ioutil.ReadDir(wd+"/mount/dir")
+	CheckSuccess(err)
+}
+
 func TestSymlink(t *testing.T) {
 	wd, clean := setupUfs(t)
 	defer clean()
@@ -203,7 +217,7 @@ func TestDelete(t *testing.T) {
 		c, err := ioutil.ReadFile(delPath + "/" + k)
 		CheckSuccess(err)
 		if string(c) != "file" {
-			t.Fatal("content mismatch", string(c)) 
+			t.Fatal("content mismatch", string(c))
 		}
 	}
 }
@@ -587,9 +601,9 @@ func TestDisappearing(t *testing.T) {
 	_, err = ioutil.ReadDir(wd+"/mount")
 	if err == nil {
 	       t.Fatal("Readdir should have failed")
-	} 
+	}
 	log.Println("expected readdir failure:", err)
-	
+
 	err = ioutil.WriteFile(wd + "/mount/file2", []byte("blabla"), 0644)
 	if err == nil {
 		t.Fatal("write should have failed")
@@ -599,14 +613,14 @@ func TestDisappearing(t *testing.T) {
 	// Restore, and wait for caches to catch up.
 	wrFs.Root = oldRoot
 	time.Sleep(1.5*entryTtl*1e9)
-	
+
 	_, err = ioutil.ReadDir(wd+"/mount")
 	if err != nil {
 	       t.Fatal("Readdir should succeed", err)
-	} 
+	}
 	err = ioutil.WriteFile(wd + "/mount/file2", []byte("blabla"), 0644)
 	if err != nil {
 		t.Fatal("write should succeed", err)
-	}	
+	}
 }
 
-- 
2.30.9