github.com/scaleoutsean/fusego@v0.0.0-20220224074057-4a6429e46bb8/samples/cachingfs/caching_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 cachingfs
    16  
    17  import (
    18  	"context"
    19  	"crypto/rand"
    20  	"fmt"
    21  	"io"
    22  	"os"
    23  	"time"
    24  
    25  	"github.com/scaleoutsean/fusego"
    26  	"github.com/scaleoutsean/fusego/fuseops"
    27  	"github.com/scaleoutsean/fusego/fuseutil"
    28  	"github.com/jacobsa/syncutil"
    29  )
    30  
    31  const (
    32  	// Sizes of the files according to the file system.
    33  	FooSize = 123
    34  	BarSize = 456
    35  )
    36  
    37  // A file system with a fixed structure that looks like this:
    38  //
    39  //     foo
    40  //     dir/
    41  //         bar
    42  //
    43  // The file system is configured with durations that specify how long to allow
    44  // inode entries and attributes to be cached, used when responding to fuse
    45  // requests. It also exposes methods for renumbering inodes and updating mtimes
    46  // that are useful in testing that these durations are honored.
    47  //
    48  // Each file responds to reads with random contents. SetKeepCache can be used
    49  // to control whether the response to OpenFileOp tells the kernel to keep the
    50  // file's data in the page cache or not.
    51  type CachingFS interface {
    52  	fuseutil.FileSystem
    53  
    54  	// Return the current inode ID of the file/directory with the given name.
    55  	FooID() fuseops.InodeID
    56  	DirID() fuseops.InodeID
    57  	BarID() fuseops.InodeID
    58  
    59  	// Cause the inode IDs to change to values that have never before been used.
    60  	RenumberInodes()
    61  
    62  	// Cause further queries for the attributes of inodes to use the supplied
    63  	// time as the inode's mtime.
    64  	SetMtime(mtime time.Time)
    65  
    66  	// Instruct the file system whether or not to reply to OpenFileOp with
    67  	// FOPEN_KEEP_CACHE set.
    68  	SetKeepCache(keep bool)
    69  }
    70  
    71  // Create a file system that issues cacheable responses according to the
    72  // following rules:
    73  //
    74  //  *  LookUpInodeResponse.Entry.EntryExpiration is set according to
    75  //     lookupEntryTimeout.
    76  //
    77  //  *  GetInodeAttributesResponse.AttributesExpiration is set according to
    78  //     getattrTimeout.
    79  //
    80  //  *  Nothing else is marked cacheable. (In particular, the attributes
    81  //     returned by LookUpInode are not cacheable.)
    82  //
    83  func NewCachingFS(
    84  	lookupEntryTimeout time.Duration,
    85  	getattrTimeout time.Duration) (CachingFS, error) {
    86  	roundUp := func(n fuseops.InodeID) fuseops.InodeID {
    87  		return numInodes * ((n + numInodes - 1) / numInodes)
    88  	}
    89  
    90  	cfs := &cachingFS{
    91  		lookupEntryTimeout: lookupEntryTimeout,
    92  		getattrTimeout:     getattrTimeout,
    93  		baseID:             roundUp(fuseops.RootInodeID + 1),
    94  		mtime:              time.Now(),
    95  	}
    96  
    97  	cfs.mu = syncutil.NewInvariantMutex(cfs.checkInvariants)
    98  
    99  	return cfs, nil
   100  }
   101  
   102  const (
   103  	// Inode IDs are issued such that "foo" always receives an ID that is
   104  	// congruent to fooOffset modulo numInodes, etc.
   105  	fooOffset = iota
   106  	dirOffset
   107  	barOffset
   108  	numInodes
   109  )
   110  
   111  type cachingFS struct {
   112  	fuseutil.NotImplementedFileSystem
   113  
   114  	/////////////////////////
   115  	// Constant data
   116  	/////////////////////////
   117  
   118  	lookupEntryTimeout time.Duration
   119  	getattrTimeout     time.Duration
   120  
   121  	/////////////////////////
   122  	// Mutable state
   123  	/////////////////////////
   124  
   125  	mu syncutil.InvariantMutex
   126  
   127  	// GUARDED_BY(mu)
   128  	keepPageCache bool
   129  
   130  	// The current ID of the lowest numbered non-root inode.
   131  	//
   132  	// INVARIANT: baseID > fuseops.RootInodeID
   133  	// INVARIANT: baseID % numInodes == 0
   134  	//
   135  	// GUARDED_BY(mu)
   136  	baseID fuseops.InodeID
   137  
   138  	// GUARDED_BY(mu)
   139  	mtime time.Time
   140  }
   141  
   142  ////////////////////////////////////////////////////////////////////////
   143  // Helpers
   144  ////////////////////////////////////////////////////////////////////////
   145  
   146  func (fs *cachingFS) checkInvariants() {
   147  	// INVARIANT: baseID > fuseops.RootInodeID
   148  	// INVARIANT: baseID % numInodes == 0
   149  	if fs.baseID <= fuseops.RootInodeID || fs.baseID%numInodes != 0 {
   150  		panic(fmt.Sprintf("Bad baseID: %v", fs.baseID))
   151  	}
   152  }
   153  
   154  // LOCKS_REQUIRED(fs.mu)
   155  func (fs *cachingFS) fooID() fuseops.InodeID {
   156  	return fs.baseID + fooOffset
   157  }
   158  
   159  // LOCKS_REQUIRED(fs.mu)
   160  func (fs *cachingFS) dirID() fuseops.InodeID {
   161  	return fs.baseID + dirOffset
   162  }
   163  
   164  // LOCKS_REQUIRED(fs.mu)
   165  func (fs *cachingFS) barID() fuseops.InodeID {
   166  	return fs.baseID + barOffset
   167  }
   168  
   169  // LOCKS_REQUIRED(fs.mu)
   170  func (fs *cachingFS) rootAttrs() fuseops.InodeAttributes {
   171  	return fuseops.InodeAttributes{
   172  		Mode:  os.ModeDir | 0777,
   173  		Mtime: fs.mtime,
   174  	}
   175  }
   176  
   177  // LOCKS_REQUIRED(fs.mu)
   178  func (fs *cachingFS) fooAttrs() fuseops.InodeAttributes {
   179  	return fuseops.InodeAttributes{
   180  		Nlink: 1,
   181  		Size:  FooSize,
   182  		Mode:  0777,
   183  		Mtime: fs.mtime,
   184  	}
   185  }
   186  
   187  // LOCKS_REQUIRED(fs.mu)
   188  func (fs *cachingFS) dirAttrs() fuseops.InodeAttributes {
   189  	return fuseops.InodeAttributes{
   190  		Nlink: 1,
   191  		Mode:  os.ModeDir | 0777,
   192  		Mtime: fs.mtime,
   193  	}
   194  }
   195  
   196  // LOCKS_REQUIRED(fs.mu)
   197  func (fs *cachingFS) barAttrs() fuseops.InodeAttributes {
   198  	return fuseops.InodeAttributes{
   199  		Nlink: 1,
   200  		Size:  BarSize,
   201  		Mode:  0777,
   202  		Mtime: fs.mtime,
   203  	}
   204  }
   205  
   206  ////////////////////////////////////////////////////////////////////////
   207  // Public interface
   208  ////////////////////////////////////////////////////////////////////////
   209  
   210  // LOCKS_EXCLUDED(fs.mu)
   211  func (fs *cachingFS) FooID() fuseops.InodeID {
   212  	fs.mu.Lock()
   213  	defer fs.mu.Unlock()
   214  
   215  	return fs.fooID()
   216  }
   217  
   218  // LOCKS_EXCLUDED(fs.mu)
   219  func (fs *cachingFS) DirID() fuseops.InodeID {
   220  	fs.mu.Lock()
   221  	defer fs.mu.Unlock()
   222  
   223  	return fs.dirID()
   224  }
   225  
   226  // LOCKS_EXCLUDED(fs.mu)
   227  func (fs *cachingFS) BarID() fuseops.InodeID {
   228  	fs.mu.Lock()
   229  	defer fs.mu.Unlock()
   230  
   231  	return fs.barID()
   232  }
   233  
   234  // LOCKS_EXCLUDED(fs.mu)
   235  func (fs *cachingFS) RenumberInodes() {
   236  	fs.mu.Lock()
   237  	defer fs.mu.Unlock()
   238  
   239  	fs.baseID += numInodes
   240  }
   241  
   242  // LOCKS_EXCLUDED(fs.mu)
   243  func (fs *cachingFS) SetMtime(mtime time.Time) {
   244  	fs.mu.Lock()
   245  	defer fs.mu.Unlock()
   246  
   247  	fs.mtime = mtime
   248  }
   249  
   250  // LOCKS_EXCLUDED(fs.mu)
   251  func (fs *cachingFS) SetKeepCache(keep bool) {
   252  	fs.mu.Lock()
   253  	defer fs.mu.Unlock()
   254  
   255  	fs.keepPageCache = keep
   256  }
   257  
   258  ////////////////////////////////////////////////////////////////////////
   259  // FileSystem methods
   260  ////////////////////////////////////////////////////////////////////////
   261  
   262  func (fs *cachingFS) StatFS(
   263  	ctx context.Context,
   264  	op *fuseops.StatFSOp) error {
   265  	return nil
   266  }
   267  
   268  // LOCKS_EXCLUDED(fs.mu)
   269  func (fs *cachingFS) LookUpInode(
   270  	ctx context.Context,
   271  	op *fuseops.LookUpInodeOp) error {
   272  	fs.mu.Lock()
   273  	defer fs.mu.Unlock()
   274  
   275  	// Find the ID and attributes.
   276  	var id fuseops.InodeID
   277  	var attrs fuseops.InodeAttributes
   278  
   279  	switch op.Name {
   280  	case "foo":
   281  		// Parent must be the root.
   282  		if op.Parent != fuseops.RootInodeID {
   283  			return fuse.ENOENT
   284  		}
   285  
   286  		id = fs.fooID()
   287  		attrs = fs.fooAttrs()
   288  
   289  	case "dir":
   290  		// Parent must be the root.
   291  		if op.Parent != fuseops.RootInodeID {
   292  			return fuse.ENOENT
   293  		}
   294  
   295  		id = fs.dirID()
   296  		attrs = fs.dirAttrs()
   297  
   298  	case "bar":
   299  		// Parent must be dir.
   300  		if op.Parent == fuseops.RootInodeID || op.Parent%numInodes != dirOffset {
   301  			return fuse.ENOENT
   302  		}
   303  
   304  		id = fs.barID()
   305  		attrs = fs.barAttrs()
   306  
   307  	default:
   308  		return fuse.ENOENT
   309  	}
   310  
   311  	// Fill in the response.
   312  	op.Entry.Child = id
   313  	op.Entry.Attributes = attrs
   314  	op.Entry.EntryExpiration = time.Now().Add(fs.lookupEntryTimeout)
   315  
   316  	return nil
   317  }
   318  
   319  // LOCKS_EXCLUDED(fs.mu)
   320  func (fs *cachingFS) GetInodeAttributes(
   321  	ctx context.Context,
   322  	op *fuseops.GetInodeAttributesOp) error {
   323  	fs.mu.Lock()
   324  	defer fs.mu.Unlock()
   325  
   326  	// Figure out which inode the request is for.
   327  	var attrs fuseops.InodeAttributes
   328  
   329  	switch {
   330  	case op.Inode == fuseops.RootInodeID:
   331  		attrs = fs.rootAttrs()
   332  
   333  	case op.Inode%numInodes == fooOffset:
   334  		attrs = fs.fooAttrs()
   335  
   336  	case op.Inode%numInodes == dirOffset:
   337  		attrs = fs.dirAttrs()
   338  
   339  	case op.Inode%numInodes == barOffset:
   340  		attrs = fs.barAttrs()
   341  	}
   342  
   343  	// Fill in the response.
   344  	op.Attributes = attrs
   345  	op.AttributesExpiration = time.Now().Add(fs.getattrTimeout)
   346  
   347  	return nil
   348  }
   349  
   350  func (fs *cachingFS) OpenDir(
   351  	ctx context.Context,
   352  	op *fuseops.OpenDirOp) error {
   353  	return nil
   354  }
   355  
   356  func (fs *cachingFS) OpenFile(
   357  	ctx context.Context,
   358  	op *fuseops.OpenFileOp) error {
   359  	fs.mu.Lock()
   360  	defer fs.mu.Unlock()
   361  
   362  	op.KeepPageCache = fs.keepPageCache
   363  
   364  	return nil
   365  }
   366  
   367  func (fs *cachingFS) ReadFile(
   368  	ctx context.Context,
   369  	op *fuseops.ReadFileOp) error {
   370  	var err error
   371  	op.BytesRead, err = io.ReadFull(rand.Reader, op.Dst)
   372  	return err
   373  }