From 987e4f40870ae07390c2468782c5dfca5e30f2c3 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Mon, 30 Mar 2015 16:09:25 +1100 Subject: [PATCH 01/19] Sketched forgetfs. --- samples/forgetfs/forget_fs.go | 45 +++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 samples/forgetfs/forget_fs.go diff --git a/samples/forgetfs/forget_fs.go b/samples/forgetfs/forget_fs.go new file mode 100644 index 0000000..d49fafd --- /dev/null +++ b/samples/forgetfs/forget_fs.go @@ -0,0 +1,45 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package forgetfs + +import "github.com/jacobsa/fuse" + +// Create a file system whose sole contents are a file named "foo" and a +// directory named "bar". +// +// The file "foo" may be opened for reading and/or writing, but reads and +// writes aren't supported. Additionally, a file named "bar" may be created +// anew an arbitrary number of times in any directory, but it will never exist +// in lookups by name. +// +// The file system maintains reference counts for the inodes involved. It will +// panic if a reference count becomes negative or if an inode ID is re-used +// after we expect it to be dead. Its Check method may be used to check that +// there are no inodes with non-zero reference counts remaining, after +// unmounting. +func NewFileSystem() (fs *ForgetFS, err error) { + fs = &ForgetFS{} + return +} + +type ForgetFS struct { + fuse.Server +} + +// Panic if there are any inodes that have a non-zero reference count. For use +// after unmounting. +func (fs *ForgetFS) Check() { + panic("TODO") +} From d4e690f2f855cb72a94ffeaa28dc21ce270f6549 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Mon, 30 Mar 2015 16:12:52 +1100 Subject: [PATCH 02/19] Added a test stub. --- samples/forgetfs/forget_fs_test.go | 52 ++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 samples/forgetfs/forget_fs_test.go diff --git a/samples/forgetfs/forget_fs_test.go b/samples/forgetfs/forget_fs_test.go new file mode 100644 index 0000000..224a880 --- /dev/null +++ b/samples/forgetfs/forget_fs_test.go @@ -0,0 +1,52 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package forgetfs_test + +import ( + "testing" + + "github.com/jacobsa/fuse/samples" + "github.com/jacobsa/fuse/samples/forgetfs" + . "github.com/jacobsa/ogletest" +) + +func TestForgetFS(t *testing.T) { RunTests(t) } + +//////////////////////////////////////////////////////////////////////// +// Boilerplate +//////////////////////////////////////////////////////////////////////// + +type ForgetFSTest struct { + samples.SampleTest + fs *forgetfs.ForgetFS +} + +func init() { RegisterTestSuite(&ForgetFSTest{}) } + +func (t *ForgetFSTest) SetUp(ti *TestInfo) { + panic("TODO") +} + +func (t *ForgetFSTest) TearDown() { + panic("TODO: Unmount then call Check") +} + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +func (t *ForgetFSTest) DoesFoo() { + AssertTrue(false, "TODO") +} From 36643c8dafcd4637d55a9d6559ad45942079a1f3 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Mon, 30 Mar 2015 16:15:13 +1100 Subject: [PATCH 03/19] Added test names. --- samples/forgetfs/forget_fs.go | 6 +++--- samples/forgetfs/forget_fs_test.go | 22 +++++++++++++++++++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/samples/forgetfs/forget_fs.go b/samples/forgetfs/forget_fs.go index d49fafd..4d3b820 100644 --- a/samples/forgetfs/forget_fs.go +++ b/samples/forgetfs/forget_fs.go @@ -20,9 +20,9 @@ import "github.com/jacobsa/fuse" // directory named "bar". // // The file "foo" may be opened for reading and/or writing, but reads and -// writes aren't supported. Additionally, a file named "bar" may be created -// anew an arbitrary number of times in any directory, but it will never exist -// in lookups by name. +// writes aren't supported. Additionally, any non-existent file or directory +// name may be created within any directory, but the resulting inode will +// appear to have been unlinked immediately. // // The file system maintains reference counts for the inodes involved. It will // panic if a reference count becomes negative or if an inode ID is re-used diff --git a/samples/forgetfs/forget_fs_test.go b/samples/forgetfs/forget_fs_test.go index 224a880..6032131 100644 --- a/samples/forgetfs/forget_fs_test.go +++ b/samples/forgetfs/forget_fs_test.go @@ -47,6 +47,26 @@ func (t *ForgetFSTest) TearDown() { // Tests //////////////////////////////////////////////////////////////////////// -func (t *ForgetFSTest) DoesFoo() { +func (t *ForgetFSTest) Open_Foo() { + AssertTrue(false, "TODO") +} + +func (t *ForgetFSTest) Open_Dir() { + AssertTrue(false, "TODO") +} + +func (t *ForgetFSTest) Stat_Foo() { + AssertTrue(false, "TODO") +} + +func (t *ForgetFSTest) Stat_Dir() { + AssertTrue(false, "TODO") +} + +func (t *ForgetFSTest) CreateFile() { + AssertTrue(false, "TODO") +} + +func (t *ForgetFSTest) MkDir() { AssertTrue(false, "TODO") } From faba594f7f37ec449d6b12dcc5f51a8926e10c1c Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Mon, 30 Mar 2015 16:16:25 +1100 Subject: [PATCH 04/19] ForgetFSTest.SetUp --- samples/forgetfs/forget_fs.go | 2 +- samples/forgetfs/forget_fs_test.go | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/samples/forgetfs/forget_fs.go b/samples/forgetfs/forget_fs.go index 4d3b820..74f39ef 100644 --- a/samples/forgetfs/forget_fs.go +++ b/samples/forgetfs/forget_fs.go @@ -29,7 +29,7 @@ import "github.com/jacobsa/fuse" // after we expect it to be dead. Its Check method may be used to check that // there are no inodes with non-zero reference counts remaining, after // unmounting. -func NewFileSystem() (fs *ForgetFS, err error) { +func NewFileSystem() (fs *ForgetFS) { fs = &ForgetFS{} return } diff --git a/samples/forgetfs/forget_fs_test.go b/samples/forgetfs/forget_fs_test.go index 6032131..410bd03 100644 --- a/samples/forgetfs/forget_fs_test.go +++ b/samples/forgetfs/forget_fs_test.go @@ -36,7 +36,9 @@ type ForgetFSTest struct { func init() { RegisterTestSuite(&ForgetFSTest{}) } func (t *ForgetFSTest) SetUp(ti *TestInfo) { - panic("TODO") + t.fs = forgetfs.NewFileSystem() + t.Server = t.fs + t.SampleTest.SetUp(ti) } func (t *ForgetFSTest) TearDown() { From 8e8c391a21f6e6e471bf3f205f7617aedbae09d5 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Mon, 30 Mar 2015 16:17:53 +1100 Subject: [PATCH 05/19] Made a panic clearer. --- samples/forgetfs/forget_fs.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/samples/forgetfs/forget_fs.go b/samples/forgetfs/forget_fs.go index 74f39ef..fe28174 100644 --- a/samples/forgetfs/forget_fs.go +++ b/samples/forgetfs/forget_fs.go @@ -35,7 +35,10 @@ func NewFileSystem() (fs *ForgetFS) { } type ForgetFS struct { - fuse.Server +} + +func (fs *ForgetFS) ServeOps(c *fuse.Connection) { + panic("TODO: Export dispatch function from fuseutil and use it here.") } // Panic if there are any inodes that have a non-zero reference count. For use From 42cefe94f7ba94a81610cdf332cf9b448872b1f9 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Mon, 30 Mar 2015 16:20:02 +1100 Subject: [PATCH 06/19] Revised a plan. --- samples/forgetfs/forget_fs.go | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/samples/forgetfs/forget_fs.go b/samples/forgetfs/forget_fs.go index fe28174..76b4af8 100644 --- a/samples/forgetfs/forget_fs.go +++ b/samples/forgetfs/forget_fs.go @@ -14,7 +14,10 @@ package forgetfs -import "github.com/jacobsa/fuse" +import ( + "github.com/jacobsa/fuse" + "github.com/jacobsa/fuse/fuseutil" +) // Create a file system whose sole contents are a file named "foo" and a // directory named "bar". @@ -30,15 +33,27 @@ import "github.com/jacobsa/fuse" // there are no inodes with non-zero reference counts remaining, after // unmounting. func NewFileSystem() (fs *ForgetFS) { - fs = &ForgetFS{} + impl := &fsImpl{} + + fs = &ForgetFS{ + impl: impl, + server: fuseutil.NewFileSystemServer(impl), + } + return } +//////////////////////////////////////////////////////////////////////// +// ForgetFS +//////////////////////////////////////////////////////////////////////// + type ForgetFS struct { + impl *fsImpl + server fuse.Server } func (fs *ForgetFS) ServeOps(c *fuse.Connection) { - panic("TODO: Export dispatch function from fuseutil and use it here.") + fs.server.ServeOps(c) } // Panic if there are any inodes that have a non-zero reference count. For use @@ -46,3 +61,11 @@ func (fs *ForgetFS) ServeOps(c *fuse.Connection) { func (fs *ForgetFS) Check() { panic("TODO") } + +//////////////////////////////////////////////////////////////////////// +// Actual implementation +//////////////////////////////////////////////////////////////////////// + +type fsImpl struct { + fuseutil.NotImplementedFileSystem +} From a37b22d786e1ea7d5be31f78539a1ac4a6121d26 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Mon, 30 Mar 2015 16:20:42 +1100 Subject: [PATCH 07/19] fsImpl.Init --- samples/forgetfs/forget_fs.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/samples/forgetfs/forget_fs.go b/samples/forgetfs/forget_fs.go index 76b4af8..11306e0 100644 --- a/samples/forgetfs/forget_fs.go +++ b/samples/forgetfs/forget_fs.go @@ -16,6 +16,7 @@ package forgetfs import ( "github.com/jacobsa/fuse" + "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseutil" ) @@ -69,3 +70,11 @@ func (fs *ForgetFS) Check() { type fsImpl struct { fuseutil.NotImplementedFileSystem } + +func (fs *fsImpl) Init( + op *fuseops.InitOp) { + var err error + defer fuseutil.RespondToOp(op, &err) + + return +} From b2b1b6a4ef7d57edb79903d808db006eb2c7ec5b Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Mon, 30 Mar 2015 16:26:52 +1100 Subject: [PATCH 08/19] Added fsImpl members. --- samples/forgetfs/forget_fs.go | 49 +++++++++++++++++++++++++++++- samples/forgetfs/forget_fs_test.go | 6 +++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/samples/forgetfs/forget_fs.go b/samples/forgetfs/forget_fs.go index 11306e0..6af2ccc 100644 --- a/samples/forgetfs/forget_fs.go +++ b/samples/forgetfs/forget_fs.go @@ -18,6 +18,7 @@ import ( "github.com/jacobsa/fuse" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseutil" + "github.com/jacobsa/gcloud/syncutil" ) // Create a file system whose sole contents are a file named "foo" and a @@ -34,8 +35,19 @@ import ( // there are no inodes with non-zero reference counts remaining, after // unmounting. func NewFileSystem() (fs *ForgetFS) { - impl := &fsImpl{} + // Set up the actual file system. + impl := &fsImpl{ + inodes: map[fuseops.InodeID]*inode{ + cannedID_Root: &inode{}, + cannedID_Foo: &inode{}, + cannedID_Bar: &inode{}, + }, + nextInodeID: cannedID_Next, + } + impl.mu = syncutil.NewInvariantMutex(impl.checkInvariants) + + // Set up a wrapper that exposes only certain methods. fs = &ForgetFS{ impl: impl, server: fuseutil.NewFileSystemServer(impl), @@ -67,10 +79,45 @@ func (fs *ForgetFS) Check() { // Actual implementation //////////////////////////////////////////////////////////////////////// +const ( + cannedID_Root = fuseops.RootInodeID + iota + cannedID_Foo + cannedID_Bar + cannedID_Next +) + type fsImpl struct { fuseutil.NotImplementedFileSystem + + ///////////////////////// + // Mutable state + ///////////////////////// + + mu syncutil.InvariantMutex + + // An index of inode by ID, for all IDs we have issued. + // + // INVARIANT: For each v, v.lookupCount >= 0 + // + // GUARDED_BY(mu) + inodes map[fuseops.InodeID]*inode + + // The next ID to issue. + // + // INVARIANT: For each k in inodes, k < nextInodeID + // + // GUARDED_BY(mu) + nextInodeID fuseops.InodeID } +type inode struct { + // The current lookup count. + lookupCount int +} + +// LOCKS_REQUIRED(fs.mu) +func (fs *fsImpl) checkInvariants() + func (fs *fsImpl) Init( op *fuseops.InitOp) { var err error diff --git a/samples/forgetfs/forget_fs_test.go b/samples/forgetfs/forget_fs_test.go index 410bd03..05c1082 100644 --- a/samples/forgetfs/forget_fs_test.go +++ b/samples/forgetfs/forget_fs_test.go @@ -42,7 +42,11 @@ func (t *ForgetFSTest) SetUp(ti *TestInfo) { } func (t *ForgetFSTest) TearDown() { - panic("TODO: Unmount then call Check") + // Unmount. + t.SampleTest.TearDown() + + // Crash if anything is left. + t.fs.Check() } //////////////////////////////////////////////////////////////////////// From a787be87bb543f377e3398307062ac6ad044830f Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Mon, 30 Mar 2015 16:28:02 +1100 Subject: [PATCH 09/19] fsImpl.checkInvariants --- samples/forgetfs/forget_fs.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/samples/forgetfs/forget_fs.go b/samples/forgetfs/forget_fs.go index 6af2ccc..582a115 100644 --- a/samples/forgetfs/forget_fs.go +++ b/samples/forgetfs/forget_fs.go @@ -97,7 +97,7 @@ type fsImpl struct { // An index of inode by ID, for all IDs we have issued. // - // INVARIANT: For each v, v.lookupCount >= 0 + // INVARIANT: For each v in inodes, v.lookupCount >= 0 // // GUARDED_BY(mu) inodes map[fuseops.InodeID]*inode @@ -116,7 +116,21 @@ type inode struct { } // LOCKS_REQUIRED(fs.mu) -func (fs *fsImpl) checkInvariants() +func (fs *fsImpl) checkInvariants() { + // INVARIANT: For each v in inodes, v.lookupCount >= 0 + for _, v := range fs.inodes { + if !(v.lookupCount >= 0) { + panic("Negative lookup count") + } + } + + // INVARIANT: For each k in inodes, k < nextInodeID + for k, _ := range fs.inodes { + if !(k < fs.nextInodeID) { + panic("Unexpectedly large inode ID") + } + } +} func (fs *fsImpl) Init( op *fuseops.InitOp) { From f76439120cf845151d280391cb70f691991a2307 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Mon, 30 Mar 2015 16:29:21 +1100 Subject: [PATCH 10/19] Check --- samples/forgetfs/forget_fs.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/samples/forgetfs/forget_fs.go b/samples/forgetfs/forget_fs.go index 582a115..eb23dd2 100644 --- a/samples/forgetfs/forget_fs.go +++ b/samples/forgetfs/forget_fs.go @@ -15,6 +15,8 @@ package forgetfs import ( + "fmt" + "github.com/jacobsa/fuse" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseutil" @@ -72,7 +74,7 @@ func (fs *ForgetFS) ServeOps(c *fuse.Connection) { // Panic if there are any inodes that have a non-zero reference count. For use // after unmounting. func (fs *ForgetFS) Check() { - panic("TODO") + fs.impl.Check() } //////////////////////////////////////////////////////////////////////// @@ -132,6 +134,18 @@ func (fs *fsImpl) checkInvariants() { } } +// LOCKS_EXCLUDED(fs.mu) +func (fs *fsImpl) Check() { + fs.mu.Lock() + defer fs.mu.Unlock() + + for k, v := range fs.inodes { + if v.lookupCount != 0 { + panic(fmt.Sprintf("Inode %v has lookup count %v", k, v.lookupCount)) + } + } +} + func (fs *fsImpl) Init( op *fuseops.InitOp) { var err error From ded5091c2e117160dad35edcf970549c50f4be88 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Mon, 30 Mar 2015 16:30:06 +1100 Subject: [PATCH 11/19] ForgetFSTest.Open_Foo --- samples/forgetfs/forget_fs_test.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/samples/forgetfs/forget_fs_test.go b/samples/forgetfs/forget_fs_test.go index 05c1082..4245122 100644 --- a/samples/forgetfs/forget_fs_test.go +++ b/samples/forgetfs/forget_fs_test.go @@ -15,6 +15,8 @@ package forgetfs_test import ( + "os" + "path" "testing" "github.com/jacobsa/fuse/samples" @@ -54,7 +56,13 @@ func (t *ForgetFSTest) TearDown() { //////////////////////////////////////////////////////////////////////// func (t *ForgetFSTest) Open_Foo() { - AssertTrue(false, "TODO") + var err error + + f, err := os.Open(path.Join(t.Dir, "foo")) + AssertEq(nil, err) + + err = f.Close() + AssertEq(nil, err) } func (t *ForgetFSTest) Open_Dir() { From a573f6e5755225bb2867613f2c35740884836a80 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Mon, 30 Mar 2015 16:36:28 +1100 Subject: [PATCH 12/19] The root inode begins with a lookup count of 1. Cf. jacobsa/fuse@36d7193638ab5086b80cf87fc16f3926a47135aa --- samples/forgetfs/forget_fs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/forgetfs/forget_fs.go b/samples/forgetfs/forget_fs.go index eb23dd2..8dfaa8e 100644 --- a/samples/forgetfs/forget_fs.go +++ b/samples/forgetfs/forget_fs.go @@ -40,7 +40,7 @@ func NewFileSystem() (fs *ForgetFS) { // Set up the actual file system. impl := &fsImpl{ inodes: map[fuseops.InodeID]*inode{ - cannedID_Root: &inode{}, + cannedID_Root: &inode{lookupCount: 1}, cannedID_Foo: &inode{}, cannedID_Bar: &inode{}, }, From 541223bb0785a7cd34f14ac3ade93e3ced64fbff Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Tue, 31 Mar 2015 09:39:27 +1100 Subject: [PATCH 13/19] fsImpl.GetInodeAttributes --- samples/forgetfs/forget_fs.go | 49 ++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/samples/forgetfs/forget_fs.go b/samples/forgetfs/forget_fs.go index 8dfaa8e..cccafa6 100644 --- a/samples/forgetfs/forget_fs.go +++ b/samples/forgetfs/forget_fs.go @@ -16,6 +16,7 @@ package forgetfs import ( "fmt" + "os" "github.com/jacobsa/fuse" "github.com/jacobsa/fuse/fuseops" @@ -40,9 +41,25 @@ func NewFileSystem() (fs *ForgetFS) { // Set up the actual file system. impl := &fsImpl{ inodes: map[fuseops.InodeID]*inode{ - cannedID_Root: &inode{lookupCount: 1}, - cannedID_Foo: &inode{}, - cannedID_Bar: &inode{}, + cannedID_Root: &inode{ + lookupCount: 1, + attributes: fuseops.InodeAttributes{ + Nlink: 1, + Mode: 0777 | os.ModeDir, + }, + }, + cannedID_Foo: &inode{ + attributes: fuseops.InodeAttributes{ + Nlink: 1, + Mode: 0777, + }, + }, + cannedID_Bar: &inode{ + attributes: fuseops.InodeAttributes{ + Nlink: 1, + Mode: 0777 | os.ModeDir, + }, + }, }, nextInodeID: cannedID_Next, } @@ -113,6 +130,8 @@ type fsImpl struct { } type inode struct { + attributes fuseops.InodeAttributes + // The current lookup count. lookupCount int } @@ -153,3 +172,27 @@ func (fs *fsImpl) Init( return } + +func (fs *fsImpl) GetInodeAttributes( + op *fuseops.GetInodeAttributesOp) { + var err error + defer fuseutil.RespondToOp(op, &err) + + fs.mu.Lock() + defer fs.mu.Unlock() + + // Find the inode, verifying that it has not been forgotten. + in := fs.inodes[op.Inode] + if in == nil { + panic(fmt.Sprintf("Unknown inode: %v", op.Inode)) + } + + if in.lookupCount <= 0 { + panic(fmt.Sprintf("Forgotten inode: %v", op.Inode)) + } + + // Return appropriate attributes. + op.Attributes = in.attributes + + return +} From d0a8174ec03aacae56ccceb4f88a71b6fba2ab7d Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Tue, 31 Mar 2015 09:46:01 +1100 Subject: [PATCH 14/19] fsImpl.LookUpInode. --- samples/forgetfs/forget_fs.go | 73 +++++++++++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 8 deletions(-) diff --git a/samples/forgetfs/forget_fs.go b/samples/forgetfs/forget_fs.go index cccafa6..4245231 100644 --- a/samples/forgetfs/forget_fs.go +++ b/samples/forgetfs/forget_fs.go @@ -136,6 +136,10 @@ type inode struct { lookupCount int } +//////////////////////////////////////////////////////////////////////// +// Helpers +//////////////////////////////////////////////////////////////////////// + // LOCKS_REQUIRED(fs.mu) func (fs *fsImpl) checkInvariants() { // INVARIANT: For each v in inodes, v.lookupCount >= 0 @@ -165,6 +169,26 @@ func (fs *fsImpl) Check() { } } +// Look up the inode and verify it hasn't been forgotten. +// +// LOCKS_REQUIRED(fs.mu) +func (fs *fsImpl) findInodeByID(id fuseops.InodeID) (in *inode) { + in = fs.inodes[id] + if in == nil { + panic(fmt.Sprintf("Unknown inode: %v", id)) + } + + if in.lookupCount <= 0 { + panic(fmt.Sprintf("Forgotten inode: %v", id)) + } + + return +} + +//////////////////////////////////////////////////////////////////////// +// FileSystem methods +//////////////////////////////////////////////////////////////////////// + func (fs *fsImpl) Init( op *fuseops.InitOp) { var err error @@ -173,6 +197,46 @@ func (fs *fsImpl) Init( return } +func (fs *fsImpl) LookUpInode( + op *fuseops.LookUpInodeOp) { + var err error + defer fuseutil.RespondToOp(op, &err) + + fs.mu.Lock() + defer fs.mu.Unlock() + + // Make sure the parent exists and has not been forgotten. + _ = fs.findInodeByID(op.Parent) + + // Handle the names we support. + var childID fuseops.InodeID + switch { + case op.Parent == cannedID_Root && op.Name == "foo": + childID = cannedID_Foo + + case op.Parent == cannedID_Root && op.Name == "bar": + childID = cannedID_Bar + + default: + err = fuse.ENOENT + return + } + + // Find the child. + child := fs.findInodeByID(childID) + + // Increment the child's lookup count. + child.lookupCount++ + + // Return an appropriate entry. + op.Entry = fuseops.ChildInodeEntry{ + Child: childID, + Attributes: child.attributes, + } + + return +} + func (fs *fsImpl) GetInodeAttributes( op *fuseops.GetInodeAttributesOp) { var err error @@ -182,14 +246,7 @@ func (fs *fsImpl) GetInodeAttributes( defer fs.mu.Unlock() // Find the inode, verifying that it has not been forgotten. - in := fs.inodes[op.Inode] - if in == nil { - panic(fmt.Sprintf("Unknown inode: %v", op.Inode)) - } - - if in.lookupCount <= 0 { - panic(fmt.Sprintf("Forgotten inode: %v", op.Inode)) - } + in := fs.findInodeByID(op.Inode) // Return appropriate attributes. op.Attributes = in.attributes From 50cda173554ea2530a9b915ad1546e4cd11b5332 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Tue, 31 Mar 2015 09:48:50 +1100 Subject: [PATCH 15/19] Fixed a panic. --- samples/forgetfs/forget_fs.go | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/samples/forgetfs/forget_fs.go b/samples/forgetfs/forget_fs.go index 4245231..bdd8461 100644 --- a/samples/forgetfs/forget_fs.go +++ b/samples/forgetfs/forget_fs.go @@ -129,11 +129,27 @@ type fsImpl struct { nextInodeID fuseops.InodeID } +//////////////////////////////////////////////////////////////////////// +// inode +//////////////////////////////////////////////////////////////////////// + type inode struct { attributes fuseops.InodeAttributes // The current lookup count. lookupCount int + + // true if lookupCount has ever been positive. + lookedUp bool +} + +func (in *inode) Forgotten() bool { + return in.lookedUp && in.lookupCount <= 0 +} + +func (in *inode) IncrementLookupCount() { + in.lookupCount++ + in.lookedUp = true } //////////////////////////////////////////////////////////////////////// @@ -178,7 +194,7 @@ func (fs *fsImpl) findInodeByID(id fuseops.InodeID) (in *inode) { panic(fmt.Sprintf("Unknown inode: %v", id)) } - if in.lookupCount <= 0 { + if in.Forgotten() { panic(fmt.Sprintf("Forgotten inode: %v", id)) } @@ -222,11 +238,9 @@ func (fs *fsImpl) LookUpInode( return } - // Find the child. + // Look up the child. child := fs.findInodeByID(childID) - - // Increment the child's lookup count. - child.lookupCount++ + child.IncrementLookupCount() // Return an appropriate entry. op.Entry = fuseops.ChildInodeEntry{ From 8e04b7d848c11a5ddb23d0c444ab07ac86fca46f Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Tue, 31 Mar 2015 09:50:01 +1100 Subject: [PATCH 16/19] fsImpl.OpenFile --- samples/forgetfs/forget_fs.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/samples/forgetfs/forget_fs.go b/samples/forgetfs/forget_fs.go index bdd8461..4256881 100644 --- a/samples/forgetfs/forget_fs.go +++ b/samples/forgetfs/forget_fs.go @@ -267,3 +267,17 @@ func (fs *fsImpl) GetInodeAttributes( return } + +func (fs *fsImpl) OpenFile( + op *fuseops.OpenFileOp) { + var err error + defer fuseutil.RespondToOp(op, &err) + + fs.mu.Lock() + defer fs.mu.Unlock() + + // Verify that the inode has not been forgotten. + _ = fs.findInodeByID(op.Inode) + + return +} From ee2040958cbea36023c05a0ffbb18757638573be Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Tue, 31 Mar 2015 09:53:39 +1100 Subject: [PATCH 17/19] The root inode ends with a lookup count of one, too. --- fuseops/ops.go | 3 ++- samples/forgetfs/forget_fs.go | 12 +++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/fuseops/ops.go b/fuseops/ops.go index 20664f5..7d43b73 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -241,7 +241,8 @@ func (o *SetInodeAttributesOp) Respond(err error) { // revalidating. // // In contrast to all other inodes, RootInodeID begins with an implicit -// reference count of one, without a corresponding op to increase it: +// reference count of one, without a corresponding op to increase it. It also +// is never decremented to zero. Code walk: // // * (http://goo.gl/gWAheU) fuse_fill_super calls fuse_get_root_inode. // diff --git a/samples/forgetfs/forget_fs.go b/samples/forgetfs/forget_fs.go index 4256881..ab9692f 100644 --- a/samples/forgetfs/forget_fs.go +++ b/samples/forgetfs/forget_fs.go @@ -35,7 +35,7 @@ import ( // The file system maintains reference counts for the inodes involved. It will // panic if a reference count becomes negative or if an inode ID is re-used // after we expect it to be dead. Its Check method may be used to check that -// there are no inodes with non-zero reference counts remaining, after +// there are no inodes with unexpected reference counts remaining, after // unmounting. func NewFileSystem() (fs *ForgetFS) { // Set up the actual file system. @@ -179,6 +179,16 @@ func (fs *fsImpl) Check() { defer fs.mu.Unlock() for k, v := range fs.inodes { + // Special case: the root inode should have an implicit lookup count of 1. + if k == fuseops.RootInodeID { + if v.lookupCount != 1 { + panic(fmt.Sprintf("Root has lookup count %v", v.lookupCount)) + } + + continue + } + + // Check other inodes. if v.lookupCount != 0 { panic(fmt.Sprintf("Inode %v has lookup count %v", k, v.lookupCount)) } From 9719d57fb6e7046f6ba984834c029876caa892f7 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Tue, 31 Mar 2015 09:58:43 +1100 Subject: [PATCH 18/19] Added connection support for ForgetInodeOp. --- fuseops/convert.go | 8 ++++++++ fuseops/ops.go | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index a77a975..738321d 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -82,6 +82,14 @@ func Convert(r bazilfuse.Request, logger *log.Logger) (o Op) { o = to co = &to.commonOp + case *bazilfuse.ForgetRequest: + to := &ForgetInodeOp{ + Inode: InodeID(typed.Header.Node), + N: typed.N, + } + o = to + co = &to.commonOp + case *bazilfuse.MkdirRequest: to := &MkDirOp{ Parent: InodeID(typed.Header.Node), diff --git a/fuseops/ops.go b/fuseops/ops.go index 7d43b73..af75d53 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -255,10 +255,10 @@ type ForgetInodeOp struct { commonOp // The inode whose reference count should be decremented. - ID InodeID + Inode InodeID // The amount to decrement the reference count. - N int + N uint64 } func (o *ForgetInodeOp) Respond(err error) { From fef47a3a328c7657862ad5613da7355fec4810c9 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Tue, 31 Mar 2015 10:01:09 +1100 Subject: [PATCH 19/19] fsImpl.ForgetInode --- samples/forgetfs/forget_fs.go | 39 +++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/samples/forgetfs/forget_fs.go b/samples/forgetfs/forget_fs.go index ab9692f..1a0f6de 100644 --- a/samples/forgetfs/forget_fs.go +++ b/samples/forgetfs/forget_fs.go @@ -116,8 +116,6 @@ type fsImpl struct { // An index of inode by ID, for all IDs we have issued. // - // INVARIANT: For each v in inodes, v.lookupCount >= 0 - // // GUARDED_BY(mu) inodes map[fuseops.InodeID]*inode @@ -137,14 +135,14 @@ type inode struct { attributes fuseops.InodeAttributes // The current lookup count. - lookupCount int + lookupCount uint64 // true if lookupCount has ever been positive. lookedUp bool } func (in *inode) Forgotten() bool { - return in.lookedUp && in.lookupCount <= 0 + return in.lookedUp && in.lookupCount == 0 } func (in *inode) IncrementLookupCount() { @@ -152,19 +150,23 @@ func (in *inode) IncrementLookupCount() { in.lookedUp = true } +func (in *inode) DecrementLookupCount(n uint64) { + if in.lookupCount < n { + panic(fmt.Sprintf( + "Overly large decrement: %v, %v", + in.lookupCount, + n)) + } + + in.lookupCount -= n +} + //////////////////////////////////////////////////////////////////////// // Helpers //////////////////////////////////////////////////////////////////////// // LOCKS_REQUIRED(fs.mu) func (fs *fsImpl) checkInvariants() { - // INVARIANT: For each v in inodes, v.lookupCount >= 0 - for _, v := range fs.inodes { - if !(v.lookupCount >= 0) { - panic("Negative lookup count") - } - } - // INVARIANT: For each k in inodes, k < nextInodeID for k, _ := range fs.inodes { if !(k < fs.nextInodeID) { @@ -278,6 +280,21 @@ func (fs *fsImpl) GetInodeAttributes( return } +func (fs *fsImpl) ForgetInode( + op *fuseops.ForgetInodeOp) { + var err error + defer fuseutil.RespondToOp(op, &err) + + fs.mu.Lock() + defer fs.mu.Unlock() + + // Find the inode and decrement its count. + in := fs.findInodeByID(op.Inode) + in.DecrementLookupCount(op.N) + + return +} + func (fs *fsImpl) OpenFile( op *fuseops.OpenFileOp) { var err error