commit
706617d03b
|
@ -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),
|
||||
|
|
|
@ -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.
|
||||
//
|
||||
|
@ -254,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) {
|
||||
|
|
|
@ -0,0 +1,310 @@
|
|||
// 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 (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"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
|
||||
// directory named "bar".
|
||||
//
|
||||
// The file "foo" may be opened for reading and/or writing, but reads and
|
||||
// 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
|
||||
// after we expect it to be dead. Its Check method may be used to check that
|
||||
// there are no inodes with unexpected reference counts remaining, after
|
||||
// unmounting.
|
||||
func NewFileSystem() (fs *ForgetFS) {
|
||||
// Set up the actual file system.
|
||||
impl := &fsImpl{
|
||||
inodes: map[fuseops.InodeID]*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,
|
||||
}
|
||||
|
||||
impl.mu = syncutil.NewInvariantMutex(impl.checkInvariants)
|
||||
|
||||
// Set up a wrapper that exposes only certain methods.
|
||||
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) {
|
||||
fs.server.ServeOps(c)
|
||||
}
|
||||
|
||||
// Panic if there are any inodes that have a non-zero reference count. For use
|
||||
// after unmounting.
|
||||
func (fs *ForgetFS) Check() {
|
||||
fs.impl.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.
|
||||
//
|
||||
// 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
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// inode
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type inode struct {
|
||||
attributes fuseops.InodeAttributes
|
||||
|
||||
// The current lookup count.
|
||||
lookupCount uint64
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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 k in inodes, k < nextInodeID
|
||||
for k, _ := range fs.inodes {
|
||||
if !(k < fs.nextInodeID) {
|
||||
panic("Unexpectedly large inode ID")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LOCKS_EXCLUDED(fs.mu)
|
||||
func (fs *fsImpl) Check() {
|
||||
fs.mu.Lock()
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.Forgotten() {
|
||||
panic(fmt.Sprintf("Forgotten inode: %v", id))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// FileSystem methods
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func (fs *fsImpl) Init(
|
||||
op *fuseops.InitOp) {
|
||||
var err error
|
||||
defer fuseutil.RespondToOp(op, &err)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Look up the child.
|
||||
child := fs.findInodeByID(childID)
|
||||
child.IncrementLookupCount()
|
||||
|
||||
// Return an appropriate entry.
|
||||
op.Entry = fuseops.ChildInodeEntry{
|
||||
Child: childID,
|
||||
Attributes: child.attributes,
|
||||
}
|
||||
|
||||
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.findInodeByID(op.Inode)
|
||||
|
||||
// Return appropriate attributes.
|
||||
op.Attributes = in.attributes
|
||||
|
||||
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
|
||||
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
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
// 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 (
|
||||
"os"
|
||||
"path"
|
||||
"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) {
|
||||
t.fs = forgetfs.NewFileSystem()
|
||||
t.Server = t.fs
|
||||
t.SampleTest.SetUp(ti)
|
||||
}
|
||||
|
||||
func (t *ForgetFSTest) TearDown() {
|
||||
// Unmount.
|
||||
t.SampleTest.TearDown()
|
||||
|
||||
// Crash if anything is left.
|
||||
t.fs.Check()
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Tests
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func (t *ForgetFSTest) Open_Foo() {
|
||||
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() {
|
||||
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")
|
||||
}
|
Loading…
Reference in New Issue