github.com/scaleoutsean/fusego@v0.0.0-20220224074057-4a6429e46bb8/samples/forgetfs/forget_fs.go (about)

     1  // Copyright 2015 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package forgetfs
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"os"
    21  
    22  	"github.com/scaleoutsean/fusego"
    23  	"github.com/scaleoutsean/fusego/fuseops"
    24  	"github.com/scaleoutsean/fusego/fuseutil"
    25  	"github.com/jacobsa/syncutil"
    26  )
    27  
    28  // Create a file system whose sole contents are a file named "foo" and a
    29  // directory named "bar".
    30  //
    31  // The file "foo" may be opened for reading and/or writing, but reads and
    32  // writes aren't supported. Additionally, any non-existent file or directory
    33  // name may be created within any directory, but the resulting inode will
    34  // appear to have been unlinked immediately.
    35  //
    36  // The file system maintains reference counts for the inodes involved. It will
    37  // panic if a reference count becomes negative or if an inode ID is re-used
    38  // after we expect it to be dead. Its Check method may be used to check that
    39  // there are no inodes with unexpected reference counts remaining, after
    40  // unmounting.
    41  func NewFileSystem() *ForgetFS {
    42  	// Set up the actual file system.
    43  	impl := &fsImpl{
    44  		inodes: map[fuseops.InodeID]*inode{
    45  			cannedID_Root: &inode{
    46  				attributes: fuseops.InodeAttributes{
    47  					Nlink: 1,
    48  					Mode:  0777 | os.ModeDir,
    49  				},
    50  			},
    51  			cannedID_Foo: &inode{
    52  				attributes: fuseops.InodeAttributes{
    53  					Nlink: 1,
    54  					Mode:  0777,
    55  				},
    56  			},
    57  			cannedID_Bar: &inode{
    58  				attributes: fuseops.InodeAttributes{
    59  					Nlink: 1,
    60  					Mode:  0777 | os.ModeDir,
    61  				},
    62  			},
    63  		},
    64  		nextInodeID: cannedID_Next,
    65  	}
    66  
    67  	// The root inode starts with a lookup count of one.
    68  	impl.inodes[cannedID_Root].IncrementLookupCount()
    69  
    70  	// The canned inodes are supposed to be stable from the user's point of view,
    71  	// so we should allow them to be looked up at any point even if the kernel
    72  	// has balanced its lookups with its forgets. Ensure that they never go to
    73  	// zero until the file system is destroyed.
    74  	impl.inodes[cannedID_Foo].IncrementLookupCount()
    75  	impl.inodes[cannedID_Bar].IncrementLookupCount()
    76  
    77  	// Set up the mutex.
    78  	impl.mu = syncutil.NewInvariantMutex(impl.checkInvariants)
    79  
    80  	// Set up a wrapper that exposes only certain methods.
    81  	return &ForgetFS{
    82  		impl:   impl,
    83  		server: fuseutil.NewFileSystemServer(impl),
    84  	}
    85  }
    86  
    87  ////////////////////////////////////////////////////////////////////////
    88  // ForgetFS
    89  ////////////////////////////////////////////////////////////////////////
    90  
    91  type ForgetFS struct {
    92  	impl   *fsImpl
    93  	server fuse.Server
    94  }
    95  
    96  func (fs *ForgetFS) ServeOps(c *fuse.Connection) {
    97  	fs.server.ServeOps(c)
    98  }
    99  
   100  // Panic if there are any inodes that have a non-zero reference count. For use
   101  // after unmounting.
   102  func (fs *ForgetFS) Check() {
   103  	fs.impl.Check()
   104  }
   105  
   106  ////////////////////////////////////////////////////////////////////////
   107  // Actual implementation
   108  ////////////////////////////////////////////////////////////////////////
   109  
   110  const (
   111  	cannedID_Root = fuseops.RootInodeID + iota
   112  	cannedID_Foo
   113  	cannedID_Bar
   114  	cannedID_Next
   115  )
   116  
   117  type fsImpl struct {
   118  	fuseutil.NotImplementedFileSystem
   119  
   120  	/////////////////////////
   121  	// Mutable state
   122  	/////////////////////////
   123  
   124  	mu syncutil.InvariantMutex
   125  
   126  	// An index of inode by ID, for all IDs we have issued.
   127  	//
   128  	// GUARDED_BY(mu)
   129  	inodes map[fuseops.InodeID]*inode
   130  
   131  	// The next ID to issue.
   132  	//
   133  	// INVARIANT: For each k in inodes, k < nextInodeID
   134  	//
   135  	// GUARDED_BY(mu)
   136  	nextInodeID fuseops.InodeID
   137  }
   138  
   139  ////////////////////////////////////////////////////////////////////////
   140  // inode
   141  ////////////////////////////////////////////////////////////////////////
   142  
   143  type inode struct {
   144  	attributes fuseops.InodeAttributes
   145  
   146  	// The current lookup count.
   147  	lookupCount uint64
   148  
   149  	// true if lookupCount has ever been positive.
   150  	lookedUp bool
   151  }
   152  
   153  func (in *inode) Forgotten() bool {
   154  	return in.lookedUp && in.lookupCount == 0
   155  }
   156  
   157  func (in *inode) IncrementLookupCount() {
   158  	in.lookupCount++
   159  	in.lookedUp = true
   160  }
   161  
   162  func (in *inode) DecrementLookupCount(n uint64) {
   163  	if in.lookupCount < n {
   164  		panic(fmt.Sprintf(
   165  			"Overly large decrement: %v, %v",
   166  			in.lookupCount,
   167  			n))
   168  	}
   169  
   170  	in.lookupCount -= n
   171  }
   172  
   173  // Decrement the lookup count to zero.
   174  func (in *inode) Destroy() {
   175  	in.DecrementLookupCount(in.lookupCount)
   176  }
   177  
   178  ////////////////////////////////////////////////////////////////////////
   179  // Helpers
   180  ////////////////////////////////////////////////////////////////////////
   181  
   182  // LOCKS_REQUIRED(fs.mu)
   183  func (fs *fsImpl) checkInvariants() {
   184  	// INVARIANT: For each k in inodes, k < nextInodeID
   185  	for k, _ := range fs.inodes {
   186  		if !(k < fs.nextInodeID) {
   187  			panic("Unexpectedly large inode ID")
   188  		}
   189  	}
   190  }
   191  
   192  // LOCKS_EXCLUDED(fs.mu)
   193  func (fs *fsImpl) Check() {
   194  	fs.mu.Lock()
   195  	defer fs.mu.Unlock()
   196  
   197  	for k, v := range fs.inodes {
   198  		if v.lookupCount != 0 {
   199  			panic(fmt.Sprintf("Inode %v has lookup count %v", k, v.lookupCount))
   200  		}
   201  	}
   202  }
   203  
   204  // Look up the inode and verify it hasn't been forgotten.
   205  //
   206  // LOCKS_REQUIRED(fs.mu)
   207  func (fs *fsImpl) findInodeByID(id fuseops.InodeID) *inode {
   208  	in := fs.inodes[id]
   209  	if in == nil {
   210  		panic(fmt.Sprintf("Unknown inode: %v", id))
   211  	}
   212  
   213  	if in.Forgotten() {
   214  		panic(fmt.Sprintf("Forgotten inode: %v", id))
   215  	}
   216  
   217  	return in
   218  }
   219  
   220  ////////////////////////////////////////////////////////////////////////
   221  // FileSystem methods
   222  ////////////////////////////////////////////////////////////////////////
   223  
   224  func (fs *fsImpl) StatFS(
   225  	ctx context.Context,
   226  	op *fuseops.StatFSOp) error {
   227  	return nil
   228  }
   229  
   230  func (fs *fsImpl) LookUpInode(
   231  	ctx context.Context,
   232  	op *fuseops.LookUpInodeOp) error {
   233  	fs.mu.Lock()
   234  	defer fs.mu.Unlock()
   235  
   236  	// Make sure the parent exists and has not been forgotten.
   237  	_ = fs.findInodeByID(op.Parent)
   238  
   239  	// Handle the names we support.
   240  	var childID fuseops.InodeID
   241  	switch {
   242  	case op.Parent == cannedID_Root && op.Name == "foo":
   243  		childID = cannedID_Foo
   244  
   245  	case op.Parent == cannedID_Root && op.Name == "bar":
   246  		childID = cannedID_Bar
   247  
   248  	default:
   249  		return fuse.ENOENT
   250  	}
   251  
   252  	// Look up the child.
   253  	child := fs.findInodeByID(childID)
   254  	child.IncrementLookupCount()
   255  
   256  	// Return an appropriate entry.
   257  	op.Entry = fuseops.ChildInodeEntry{
   258  		Child:      childID,
   259  		Attributes: child.attributes,
   260  	}
   261  
   262  	return nil
   263  }
   264  
   265  func (fs *fsImpl) GetInodeAttributes(
   266  	ctx context.Context,
   267  	op *fuseops.GetInodeAttributesOp) error {
   268  	fs.mu.Lock()
   269  	defer fs.mu.Unlock()
   270  
   271  	// Find the inode, verifying that it has not been forgotten.
   272  	in := fs.findInodeByID(op.Inode)
   273  
   274  	// Return appropriate attributes.
   275  	op.Attributes = in.attributes
   276  
   277  	return nil
   278  }
   279  
   280  func (fs *fsImpl) ForgetInode(
   281  	ctx context.Context,
   282  	op *fuseops.ForgetInodeOp) error {
   283  	fs.mu.Lock()
   284  	defer fs.mu.Unlock()
   285  
   286  	// Find the inode and decrement its count.
   287  	in := fs.findInodeByID(op.Inode)
   288  	in.DecrementLookupCount(op.N)
   289  
   290  	return nil
   291  }
   292  
   293  func (fs *fsImpl) MkDir(
   294  	ctx context.Context,
   295  	op *fuseops.MkDirOp) error {
   296  	fs.mu.Lock()
   297  	defer fs.mu.Unlock()
   298  
   299  	// Make sure the parent exists and has not been forgotten.
   300  	_ = fs.findInodeByID(op.Parent)
   301  
   302  	// Mint a child inode.
   303  	childID := fs.nextInodeID
   304  	fs.nextInodeID++
   305  
   306  	child := &inode{
   307  		attributes: fuseops.InodeAttributes{
   308  			Nlink: 0,
   309  			Mode:  0777 | os.ModeDir,
   310  		},
   311  	}
   312  
   313  	fs.inodes[childID] = child
   314  	child.IncrementLookupCount()
   315  
   316  	// Return an appropriate entry.
   317  	op.Entry = fuseops.ChildInodeEntry{
   318  		Child:      childID,
   319  		Attributes: child.attributes,
   320  	}
   321  
   322  	return nil
   323  }
   324  
   325  func (fs *fsImpl) CreateFile(
   326  	ctx context.Context,
   327  	op *fuseops.CreateFileOp) error {
   328  	fs.mu.Lock()
   329  	defer fs.mu.Unlock()
   330  
   331  	// Make sure the parent exists and has not been forgotten.
   332  	_ = fs.findInodeByID(op.Parent)
   333  
   334  	// Mint a child inode.
   335  	childID := fs.nextInodeID
   336  	fs.nextInodeID++
   337  
   338  	child := &inode{
   339  		attributes: fuseops.InodeAttributes{
   340  			Nlink: 0,
   341  			Mode:  0777,
   342  		},
   343  	}
   344  
   345  	fs.inodes[childID] = child
   346  	child.IncrementLookupCount()
   347  
   348  	// Return an appropriate entry.
   349  	op.Entry = fuseops.ChildInodeEntry{
   350  		Child:      childID,
   351  		Attributes: child.attributes,
   352  	}
   353  
   354  	return nil
   355  }
   356  
   357  func (fs *fsImpl) OpenFile(
   358  	ctx context.Context,
   359  	op *fuseops.OpenFileOp) error {
   360  	fs.mu.Lock()
   361  	defer fs.mu.Unlock()
   362  
   363  	// Verify that the inode has not been forgotten.
   364  	_ = fs.findInodeByID(op.Inode)
   365  
   366  	return nil
   367  }
   368  
   369  func (fs *fsImpl) OpenDir(
   370  	ctx context.Context,
   371  	op *fuseops.OpenDirOp) error {
   372  	fs.mu.Lock()
   373  	defer fs.mu.Unlock()
   374  
   375  	// Verify that the inode has not been forgotten.
   376  	_ = fs.findInodeByID(op.Inode)
   377  
   378  	return nil
   379  }
   380  
   381  func (fs *fsImpl) Destroy() {
   382  	for _, in := range fs.inodes {
   383  		in.Destroy()
   384  	}
   385  }