github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fs/gofer/cache_policy.go (about)

     1  // Copyright 2018 The gVisor Authors.
     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 gofer
    16  
    17  import (
    18  	"fmt"
    19  
    20  	"github.com/SagerNet/gvisor/pkg/context"
    21  	"github.com/SagerNet/gvisor/pkg/sentry/fs"
    22  )
    23  
    24  // cachePolicy is a 9p cache policy. It has methods that determine what to
    25  // cache (if anything) for a given inode.
    26  type cachePolicy int
    27  
    28  const (
    29  	// Cache nothing.
    30  	cacheNone cachePolicy = iota
    31  
    32  	// Use virtual file system cache for everything.
    33  	cacheAll
    34  
    35  	// Use virtual file system cache for everything, but send writes to the
    36  	// fs agent immediately.
    37  	cacheAllWritethrough
    38  
    39  	// Use the (host) page cache for reads/writes, but don't cache anything
    40  	// else. This allows the sandbox filesystem to stay in sync with any
    41  	// changes to the remote filesystem.
    42  	//
    43  	// This policy should *only* be used with remote filesystems that
    44  	// donate their host FDs to the sandbox and thus use the host page
    45  	// cache, otherwise the dirent state will be inconsistent.
    46  	cacheRemoteRevalidating
    47  )
    48  
    49  // String returns the string name of the cache policy.
    50  func (cp cachePolicy) String() string {
    51  	switch cp {
    52  	case cacheNone:
    53  		return "cacheNone"
    54  	case cacheAll:
    55  		return "cacheAll"
    56  	case cacheAllWritethrough:
    57  		return "cacheAllWritethrough"
    58  	case cacheRemoteRevalidating:
    59  		return "cacheRemoteRevalidating"
    60  	default:
    61  		return "unknown"
    62  	}
    63  }
    64  
    65  func parseCachePolicy(policy string) (cachePolicy, error) {
    66  	switch policy {
    67  	case "fscache":
    68  		return cacheAll, nil
    69  	case "none":
    70  		return cacheNone, nil
    71  	case "fscache_writethrough":
    72  		return cacheAllWritethrough, nil
    73  	case "remote_revalidating":
    74  		return cacheRemoteRevalidating, nil
    75  	}
    76  	return cacheNone, fmt.Errorf("unsupported cache mode: %s", policy)
    77  }
    78  
    79  // cacheUAtters determines whether unstable attributes should be cached for the
    80  // given inode.
    81  func (cp cachePolicy) cacheUAttrs(inode *fs.Inode) bool {
    82  	if !fs.IsFile(inode.StableAttr) && !fs.IsDir(inode.StableAttr) {
    83  		return false
    84  	}
    85  	return cp == cacheAll || cp == cacheAllWritethrough
    86  }
    87  
    88  // cacheReaddir determines whether readdir results should be cached.
    89  func (cp cachePolicy) cacheReaddir() bool {
    90  	return cp == cacheAll || cp == cacheAllWritethrough
    91  }
    92  
    93  // useCachingInodeOps determines whether the page cache should be used for the
    94  // given inode. If the remote filesystem donates host FDs to the sentry, then
    95  // the host kernel's page cache will be used, otherwise we will use a
    96  // sentry-internal page cache.
    97  func (cp cachePolicy) useCachingInodeOps(inode *fs.Inode) bool {
    98  	// Do cached IO for regular files only. Some "character devices" expect
    99  	// no caching.
   100  	if !fs.IsFile(inode.StableAttr) {
   101  		return false
   102  	}
   103  	return cp == cacheAll || cp == cacheAllWritethrough
   104  }
   105  
   106  // writeThough indicates whether writes to the file should be synced to the
   107  // gofer immediately.
   108  func (cp cachePolicy) writeThrough(inode *fs.Inode) bool {
   109  	return cp == cacheNone || cp == cacheAllWritethrough
   110  }
   111  
   112  // revalidate revalidates the child Inode if the cache policy allows it.
   113  //
   114  // Depending on the cache policy, revalidate will walk from the parent to the
   115  // child inode, and if any unstable attributes have changed, will update the
   116  // cached attributes on the child inode. If the walk fails, or the returned
   117  // inode id is different from the one being revalidated, then the entire Dirent
   118  // must be reloaded.
   119  func (cp cachePolicy) revalidate(ctx context.Context, name string, parent, child *fs.Inode) bool {
   120  	if cp == cacheAll || cp == cacheAllWritethrough {
   121  		return false
   122  	}
   123  
   124  	if cp == cacheNone {
   125  		return true
   126  	}
   127  
   128  	childIops, ok := child.InodeOperations.(*inodeOperations)
   129  	if !ok {
   130  		if _, ok := child.InodeOperations.(*fifo); ok {
   131  			return false
   132  		}
   133  		panic(fmt.Sprintf("revalidating inode operations of unknown type %T", child.InodeOperations))
   134  	}
   135  	parentIops, ok := parent.InodeOperations.(*inodeOperations)
   136  	if !ok {
   137  		panic(fmt.Sprintf("revalidating inode operations with parent of unknown type %T", parent.InodeOperations))
   138  	}
   139  
   140  	// Walk from parent to child again.
   141  	//
   142  	// NOTE(b/112031682): If we have a directory FD in the parent
   143  	// inodeOperations, then we can use fstatat(2) to get the inode
   144  	// attributes instead of making this RPC.
   145  	qids, f, mask, attr, err := parentIops.fileState.file.walkGetAttr(ctx, []string{name})
   146  	if err != nil {
   147  		// Can't look up the name. Trigger reload.
   148  		return true
   149  	}
   150  	f.close(ctx)
   151  
   152  	// If the Path has changed, then we are not looking at the file file.
   153  	// We must reload.
   154  	if qids[0].Path != childIops.fileState.key.Inode {
   155  		return true
   156  	}
   157  
   158  	// If we are not caching unstable attrs, then there is nothing to
   159  	// update on this inode.
   160  	if !cp.cacheUAttrs(child) {
   161  		return false
   162  	}
   163  
   164  	// Update the inode's cached unstable attrs.
   165  	s := childIops.session()
   166  	childIops.cachingInodeOps.UpdateUnstable(unstable(ctx, mask, attr, s.mounter, s.client))
   167  
   168  	return false
   169  }
   170  
   171  // keep indicates that dirents should be kept pinned in the dirent tree even if
   172  // there are no application references on the file.
   173  func (cp cachePolicy) keep(d *fs.Dirent) bool {
   174  	if cp == cacheNone {
   175  		return false
   176  	}
   177  	sattr := d.Inode.StableAttr
   178  	// NOTE(b/31979197): Only cache files, directories, and symlinks.
   179  	return fs.IsFile(sattr) || fs.IsDir(sattr) || fs.IsSymlink(sattr)
   180  }
   181  
   182  // cacheNegativeDirents indicates that negative dirents should be held in the
   183  // dirent tree.
   184  func (cp cachePolicy) cacheNegativeDirents() bool {
   185  	return cp == cacheAll || cp == cacheAllWritethrough
   186  }