fusego/samples/forgetfs/forget_fs.go

386 lines
8.6 KiB
Go

// 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 (
"context"
"fmt"
"os"
"github.com/jacobsa/fuse"
"github.com/jacobsa/fuse/fuseops"
"github.com/jacobsa/fuse/fuseutil"
"github.com/jacobsa/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() *ForgetFS {
// Set up the actual file system.
impl := &fsImpl{
inodes: map[fuseops.InodeID]*inode{
cannedID_Root: &inode{
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,
}
// The root inode starts with a lookup count of one.
impl.inodes[cannedID_Root].IncrementLookupCount()
// The canned inodes are supposed to be stable from the user's point of view,
// so we should allow them to be looked up at any point even if the kernel
// has balanced its lookups with its forgets. Ensure that they never go to
// zero until the file system is destroyed.
impl.inodes[cannedID_Foo].IncrementLookupCount()
impl.inodes[cannedID_Bar].IncrementLookupCount()
// Set up the mutex.
impl.mu = syncutil.NewInvariantMutex(impl.checkInvariants)
// Set up a wrapper that exposes only certain methods.
return &ForgetFS{
impl: impl,
server: fuseutil.NewFileSystemServer(impl),
}
}
////////////////////////////////////////////////////////////////////////
// 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
}
// Decrement the lookup count to zero.
func (in *inode) Destroy() {
in.DecrementLookupCount(in.lookupCount)
}
////////////////////////////////////////////////////////////////////////
// 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 {
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) *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 in
}
////////////////////////////////////////////////////////////////////////
// FileSystem methods
////////////////////////////////////////////////////////////////////////
func (fs *fsImpl) StatFS(
ctx context.Context,
op *fuseops.StatFSOp) error {
return nil
}
func (fs *fsImpl) LookUpInode(
ctx context.Context,
op *fuseops.LookUpInodeOp) error {
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:
return fuse.ENOENT
}
// Look up the child.
child := fs.findInodeByID(childID)
child.IncrementLookupCount()
// Return an appropriate entry.
op.Entry = fuseops.ChildInodeEntry{
Child: childID,
Attributes: child.attributes,
}
return nil
}
func (fs *fsImpl) GetInodeAttributes(
ctx context.Context,
op *fuseops.GetInodeAttributesOp) error {
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 nil
}
func (fs *fsImpl) ForgetInode(
ctx context.Context,
op *fuseops.ForgetInodeOp) error {
fs.mu.Lock()
defer fs.mu.Unlock()
// Find the inode and decrement its count.
in := fs.findInodeByID(op.Inode)
in.DecrementLookupCount(op.N)
return nil
}
func (fs *fsImpl) MkDir(
ctx context.Context,
op *fuseops.MkDirOp) error {
fs.mu.Lock()
defer fs.mu.Unlock()
// Make sure the parent exists and has not been forgotten.
_ = fs.findInodeByID(op.Parent)
// Mint a child inode.
childID := fs.nextInodeID
fs.nextInodeID++
child := &inode{
attributes: fuseops.InodeAttributes{
Nlink: 0,
Mode: 0777 | os.ModeDir,
},
}
fs.inodes[childID] = child
child.IncrementLookupCount()
// Return an appropriate entry.
op.Entry = fuseops.ChildInodeEntry{
Child: childID,
Attributes: child.attributes,
}
return nil
}
func (fs *fsImpl) CreateFile(
ctx context.Context,
op *fuseops.CreateFileOp) error {
fs.mu.Lock()
defer fs.mu.Unlock()
// Make sure the parent exists and has not been forgotten.
_ = fs.findInodeByID(op.Parent)
// Mint a child inode.
childID := fs.nextInodeID
fs.nextInodeID++
child := &inode{
attributes: fuseops.InodeAttributes{
Nlink: 0,
Mode: 0777,
},
}
fs.inodes[childID] = child
child.IncrementLookupCount()
// Return an appropriate entry.
op.Entry = fuseops.ChildInodeEntry{
Child: childID,
Attributes: child.attributes,
}
return nil
}
func (fs *fsImpl) OpenFile(
ctx context.Context,
op *fuseops.OpenFileOp) error {
fs.mu.Lock()
defer fs.mu.Unlock()
// Verify that the inode has not been forgotten.
_ = fs.findInodeByID(op.Inode)
return nil
}
func (fs *fsImpl) OpenDir(
ctx context.Context,
op *fuseops.OpenDirOp) error {
fs.mu.Lock()
defer fs.mu.Unlock()
// Verify that the inode has not been forgotten.
_ = fs.findInodeByID(op.Inode)
return nil
}
func (fs *fsImpl) Destroy() {
for _, in := range fs.inodes {
in.Destroy()
}
}