From f709fc930cb88c696fd043dd28cbfede896872e6 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Sat, 27 Jul 2013 22:54:55 -0700 Subject: [PATCH] fs: implement Rename. all tests pass now. Change-Id: I3876aeb6dafd7e4cc5254a741e6938a579373a64 --- pkg/fs/fs_test.go | 63 +++++++++++++++++++++++++++++++++-- pkg/fs/mut.go | 84 +++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 138 insertions(+), 9 deletions(-) diff --git a/pkg/fs/fs_test.go b/pkg/fs/fs_test.go index 26aecfcbc..d5415fd93 100644 --- a/pkg/fs/fs_test.go +++ b/pkg/fs/fs_test.go @@ -339,6 +339,62 @@ func TestDifferentWriteTypes(t *testing.T) { }) } +func statStr(name string) string { + fi, err := os.Stat(name) + if os.IsNotExist(err) { + return "ENOENT" + } + if err != nil { + return "err=" + err.Error() + } + return fmt.Sprintf("file %v, size %d", fi.Mode(), fi.Size()) +} + +func TestRename(t *testing.T) { + condSkip(t) + inEmptyMutDir(t, func(env *mountEnv, rootDir string) { + name1 := filepath.Join(rootDir, "1") + name2 := filepath.Join(rootDir, "2") + subdir := filepath.Join(rootDir, "dir") + name3 := filepath.Join(subdir, "3") + + contents := []byte("Some file contents") + const gone = "ENOENT" + const reg = "file -rw-------, size 18" + + if err := ioutil.WriteFile(name1, contents, 0644); err != nil { + t.Fatal(err) + } + if err := os.Mkdir(subdir, 0755); err != nil { + t.Fatal(err) + } + + if got, want := statStr(name1), reg; got != want { + t.Errorf("name1 = %q; want %q", got, want) + } + if err := os.Rename(name1, name2); err != nil { + t.Fatal(err) + } + if got, want := statStr(name1), gone; got != want { + t.Errorf("name1 = %q; want %q", got, want) + } + if got, want := statStr(name2), reg; got != want { + t.Errorf("name2 = %q; want %q", got, want) + } + + // Moving to a different directory. + if err := os.Rename(name2, name3); err != nil { + t.Fatal(err) + } + if got, want := statStr(name2), gone; got != want { + t.Errorf("name2 = %q; want %q", got, want) + } + if got, want := statStr(name3), reg; got != want { + t.Errorf("name3 = %q; want %q", got, want) + } + }) +} + func brokenTest(t *testing.T) { if v, _ := strconv.ParseBool(os.Getenv("RUN_BROKEN_TESTS")); !v { t.Skipf("Skipping broken tests without RUN_BROKEN_TESTS=1") @@ -397,11 +453,13 @@ end tell } func TestTextEdit(t *testing.T) { + if testing.Short() { + t.Skipf("Skipping in short mode") + } if runtime.GOOS != "darwin" { t.Skipf("Skipping Darwin-specific test.") } condSkip(t) - brokenTest(t) inEmptyMutDir(t, func(env *mountEnv, testDir string) { var ( testFile = filepath.Join(testDir, "some-text-file.txt") @@ -435,8 +493,7 @@ end tell fi, err := os.Stat(testFile) if err != nil { t.Errorf("Stat = %v, %v", fi, err) - } - if fi.Size() != int64(len(content2)) { + } else if fi.Size() != int64(len(content2)) { t.Errorf("Stat size = %d; want %d", fi.Size(), len(content2)) } slurp, err := ioutil.ReadFile(testFile) diff --git a/pkg/fs/mut.go b/pkg/fs/mut.go index 011aba59b..22df2ee0a 100644 --- a/pkg/fs/mut.go +++ b/pkg/fs/mut.go @@ -50,7 +50,7 @@ type mutDir struct { mu sync.Mutex lastPop time.Time - children map[string]fuse.Node + children map[string]mutFileOrDir } // for debugging @@ -97,7 +97,7 @@ func (n *mutDir) populate() error { // Find all child permanodes and stick them in n.children if n.children == nil { - n.children = make(map[string]fuse.Node) + n.children = make(map[string]mutFileOrDir) } for k, v := range db.Permanode.Attr { const p = "camliPath:" @@ -244,7 +244,7 @@ func (n *mutDir) creat(name string, isDir bool) (fuse.Node, error) { } // Add a child node to this node. - var child fuse.Node + var child mutFileOrDir if isDir { child = &mutDir{ fs: n.fs, @@ -262,7 +262,7 @@ func (n *mutDir) creat(name string, isDir bool) (fuse.Node, error) { } n.mu.Lock() if n.children == nil { - n.children = make(map[string]fuse.Node) + n.children = make(map[string]mutFileOrDir) } n.children[name] = child n.mu.Unlock() @@ -287,9 +287,67 @@ func (n *mutDir) Remove(req *fuse.RemoveRequest, intr fuse.Intr) fuse.Error { return nil } +// &RenameRequest{Header:fuse.Header{Conn:(*fuse.Conn)(0xc210048180), ID:0x2, Node:0x8, Uid:0xf0d4, Gid:0x1388, Pid:0x5edb}, NewDir:0x8, OldName:"1", NewName:"2"} func (n *mutDir) Rename(req *fuse.RenameRequest, newDir fuse.Node, intr fuse.Intr) fuse.Error { - log.Printf("UNIMPLEMENTED %T.Rename %+v; newDir=%#v", req, newDir) - return fuse.EIO + n2, ok := newDir.(*mutDir) + if !ok { + log.Printf("*mutDir newDir node isn't a *mutDir; is a %T; can't handle. returning EIO.", newDir) + return fuse.EIO + } + + // TODO: do these populates in parallel: + if err := n.populate(); err != nil { + log.Printf("*mutDir.Rename src dir populate = %v", err) + return fuse.EIO + } + if err := n2.populate(); err != nil { + log.Printf("*mutDir.Rename dst dir populate = %v", err) + return fuse.EIO + } + + n.mu.Lock() + target, ok := n.children[req.OldName] + n.mu.Unlock() + if !ok { + log.Printf("*mutDir.Rename src name %q isn't known", req.OldName) + return fuse.ENOENT + } + + now := time.Now() + + // Add a camliPath:name attribute to the dest permanode before unlinking it from + // the source. + claim := schema.NewSetAttributeClaim(n2.permanode, "camliPath:"+req.NewName, target.permanodeString()) + claim.SetClaimDate(now) + _, err := n.fs.client.UploadAndSignBlob(claim) + if err != nil { + log.Printf("Upload rename link error: %v", err) + return fuse.EIO + } + + delClaim := schema.NewDelAttributeClaim(n.permanode, "camliPath:"+req.OldName) + delClaim.SetClaimDate(now) + _, err = n.fs.client.UploadAndSignBlob(delClaim) + if err != nil { + log.Printf("Upload rename src unlink error: %v", err) + return fuse.EIO + } + + // TODO(bradfitz): this locking would be racy, if the kernel + // doesn't do it properly. (It should) Let's just trust the + // kernel for now. Later we can verify and remove this + // comment. + n.mu.Lock() + if n.children[req.OldName] != target { + panic("Race.") + } + delete(n.children, req.OldName) + n.mu.Unlock() + n2.mu.Lock() + n2.children[req.NewName] = target + n2.mu.Unlock() + + return nil } // mutFile is a mutable file. @@ -550,3 +608,17 @@ func (h *mutFileHandle) Truncate(size uint64, intr fuse.Intr) fuse.Error { } return nil } + +// mutFileOrDir is a *mutFile or *mutDir +type mutFileOrDir interface { + fuse.Node + permanodeString() string +} + +func (n *mutFile) permanodeString() string { + return n.permanode.String() +} + +func (n *mutDir) permanodeString() string { + return n.permanode.String() +}