2019-04-29 21:25:05 +00:00
|
|
|
// Copyright 2018 The gVisor Authors.
|
2018-06-19 18:09:20 +00:00
|
|
|
//
|
|
|
|
// 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 gofer
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
2019-06-13 23:49:09 +00:00
|
|
|
"gvisor.dev/gvisor/pkg/sentry/context"
|
|
|
|
"gvisor.dev/gvisor/pkg/sentry/fs"
|
2018-06-19 18:09:20 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// cachePolicy is a 9p cache policy. It has methods that determine what to
|
|
|
|
// cache (if anything) for a given inode.
|
|
|
|
type cachePolicy int
|
|
|
|
|
|
|
|
const (
|
|
|
|
// Cache nothing.
|
|
|
|
cacheNone cachePolicy = iota
|
|
|
|
|
|
|
|
// Use virtual file system cache for everything.
|
|
|
|
cacheAll
|
|
|
|
|
|
|
|
// Use virtual file system cache for everything, but send writes to the
|
|
|
|
// fs agent immediately.
|
|
|
|
cacheAllWritethrough
|
2018-08-07 18:42:29 +00:00
|
|
|
|
runsc: Change cache policy for root fs and volume mounts.
Previously, gofer filesystems were configured with the default "fscache"
policy, which caches filesystem metadata and contents aggressively. While this
setting is best for performance, it means that changes from inside the sandbox
may not be immediately propagated outside the sandbox, and vice-versa.
This CL changes volumes and the root fs configuration to use a new
"remote-revalidate" cache policy which tries to retain as much caching as
possible while still making fs changes visible across the sandbox boundary.
This cache policy is enabled by default for the root filesystem. The default
value for the "--file-access" flag is still "proxy", but the behavior is
changed to use the new cache policy.
A new value for the "--file-access" flag is added, called "proxy-exclusive",
which turns on the previous aggressive caching behavior. As the name implies,
this flag should be used when the sandbox has "exclusive" access to the
filesystem.
All volume mounts are configured to use the new cache policy, since it is
safest and most likely to be correct. There is not currently a way to change
this behavior, but it's possible to add such a mechanism in the future. The
configurability is a smaller issue for volumes, since most of the expensive
application fs operations (walking + stating files) will likely served by the
root fs.
PiperOrigin-RevId: 208735037
Change-Id: Ife048fab1948205f6665df8563434dbc6ca8cfc9
2018-08-14 23:24:46 +00:00
|
|
|
// Use the (host) page cache for reads/writes, but don't cache anything
|
|
|
|
// else. This allows the sandbox filesystem to stay in sync with any
|
|
|
|
// changes to the remote filesystem.
|
2018-08-07 18:42:29 +00:00
|
|
|
//
|
|
|
|
// This policy should *only* be used with remote filesystems that
|
|
|
|
// donate their host FDs to the sandbox and thus use the host page
|
|
|
|
// cache, otherwise the dirent state will be inconsistent.
|
|
|
|
cacheRemoteRevalidating
|
2018-06-19 18:09:20 +00:00
|
|
|
)
|
|
|
|
|
2018-08-07 18:42:29 +00:00
|
|
|
// String returns the string name of the cache policy.
|
|
|
|
func (cp cachePolicy) String() string {
|
|
|
|
switch cp {
|
|
|
|
case cacheNone:
|
|
|
|
return "cacheNone"
|
|
|
|
case cacheAll:
|
|
|
|
return "cacheAll"
|
|
|
|
case cacheAllWritethrough:
|
|
|
|
return "cacheAllWritethrough"
|
|
|
|
case cacheRemoteRevalidating:
|
|
|
|
return "cacheRemoteRevalidating"
|
|
|
|
default:
|
|
|
|
return "unknown"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-19 18:09:20 +00:00
|
|
|
func parseCachePolicy(policy string) (cachePolicy, error) {
|
|
|
|
switch policy {
|
|
|
|
case "fscache":
|
|
|
|
return cacheAll, nil
|
|
|
|
case "none":
|
|
|
|
return cacheNone, nil
|
|
|
|
case "fscache_writethrough":
|
|
|
|
return cacheAllWritethrough, nil
|
2018-08-07 18:42:29 +00:00
|
|
|
case "remote_revalidating":
|
|
|
|
return cacheRemoteRevalidating, nil
|
2018-06-19 18:09:20 +00:00
|
|
|
}
|
|
|
|
return cacheNone, fmt.Errorf("unsupported cache mode: %s", policy)
|
|
|
|
}
|
|
|
|
|
|
|
|
// cacheUAtters determines whether unstable attributes should be cached for the
|
|
|
|
// given inode.
|
|
|
|
func (cp cachePolicy) cacheUAttrs(inode *fs.Inode) bool {
|
|
|
|
if !fs.IsFile(inode.StableAttr) && !fs.IsDir(inode.StableAttr) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return cp == cacheAll || cp == cacheAllWritethrough
|
|
|
|
}
|
|
|
|
|
|
|
|
// cacheReaddir determines whether readdir results should be cached.
|
|
|
|
func (cp cachePolicy) cacheReaddir() bool {
|
|
|
|
return cp == cacheAll || cp == cacheAllWritethrough
|
|
|
|
}
|
|
|
|
|
2019-01-26 01:22:04 +00:00
|
|
|
// useCachingInodeOps determines whether the page cache should be used for the
|
|
|
|
// given inode. If the remote filesystem donates host FDs to the sentry, then
|
|
|
|
// the host kernel's page cache will be used, otherwise we will use a
|
2018-08-07 18:42:29 +00:00
|
|
|
// sentry-internal page cache.
|
2019-01-26 01:22:04 +00:00
|
|
|
func (cp cachePolicy) useCachingInodeOps(inode *fs.Inode) bool {
|
2018-06-19 18:09:20 +00:00
|
|
|
// Do cached IO for regular files only. Some "character devices" expect
|
|
|
|
// no caching.
|
|
|
|
if !fs.IsFile(inode.StableAttr) {
|
|
|
|
return false
|
|
|
|
}
|
2019-01-26 01:22:04 +00:00
|
|
|
return cp == cacheAll || cp == cacheAllWritethrough
|
|
|
|
}
|
|
|
|
|
2018-06-19 18:09:20 +00:00
|
|
|
// writeThough indicates whether writes to the file should be synced to the
|
|
|
|
// gofer immediately.
|
|
|
|
func (cp cachePolicy) writeThrough(inode *fs.Inode) bool {
|
|
|
|
return cp == cacheNone || cp == cacheAllWritethrough
|
|
|
|
}
|
|
|
|
|
2018-08-27 21:25:21 +00:00
|
|
|
// revalidate revalidates the child Inode if the cache policy allows it.
|
|
|
|
//
|
|
|
|
// Depending on the cache policy, revalidate will walk from the parent to the
|
|
|
|
// child inode, and if any unstable attributes have changed, will update the
|
|
|
|
// cached attributes on the child inode. If the walk fails, or the returned
|
|
|
|
// inode id is different from the one being revalidated, then the entire Dirent
|
|
|
|
// must be reloaded.
|
|
|
|
func (cp cachePolicy) revalidate(ctx context.Context, name string, parent, child *fs.Inode) bool {
|
2018-08-07 18:42:29 +00:00
|
|
|
if cp == cacheAll || cp == cacheAllWritethrough {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2018-08-27 21:25:21 +00:00
|
|
|
if cp == cacheNone {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
childIops, ok := child.InodeOperations.(*inodeOperations)
|
|
|
|
if !ok {
|
|
|
|
panic(fmt.Sprintf("revalidating inode operations of unknown type %T", child.InodeOperations))
|
|
|
|
}
|
|
|
|
parentIops, ok := parent.InodeOperations.(*inodeOperations)
|
|
|
|
if !ok {
|
|
|
|
panic(fmt.Sprintf("revalidating inode operations with parent of unknown type %T", parent.InodeOperations))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Walk from parent to child again.
|
|
|
|
//
|
2019-04-29 21:03:04 +00:00
|
|
|
// TODO(b/112031682): If we have a directory FD in the parent
|
2018-08-27 21:25:21 +00:00
|
|
|
// inodeOperations, then we can use fstatat(2) to get the inode
|
|
|
|
// attributes instead of making this RPC.
|
2019-05-03 21:00:31 +00:00
|
|
|
qids, f, mask, attr, err := parentIops.fileState.file.walkGetAttr(ctx, []string{name})
|
2018-08-27 21:25:21 +00:00
|
|
|
if err != nil {
|
|
|
|
// Can't look up the name. Trigger reload.
|
|
|
|
return true
|
|
|
|
}
|
2019-05-03 21:00:31 +00:00
|
|
|
f.close(ctx)
|
2018-08-27 21:25:21 +00:00
|
|
|
|
|
|
|
// If the Path has changed, then we are not looking at the file file.
|
|
|
|
// We must reload.
|
|
|
|
if qids[0].Path != childIops.fileState.key.Inode {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we are not caching unstable attrs, then there is nothing to
|
|
|
|
// update on this inode.
|
|
|
|
if !cp.cacheUAttrs(child) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the inode's cached unstable attrs.
|
|
|
|
s := childIops.session()
|
|
|
|
childIops.cachingInodeOps.UpdateUnstable(unstable(ctx, mask, attr, s.mounter, s.client))
|
|
|
|
|
|
|
|
return false
|
2018-06-19 18:09:20 +00:00
|
|
|
}
|
|
|
|
|
2018-08-27 21:25:21 +00:00
|
|
|
// keep indicates that dirents should be kept pinned in the dirent tree even if
|
|
|
|
// there are no application references on the file.
|
|
|
|
func (cp cachePolicy) keep(d *fs.Dirent) bool {
|
2018-06-19 18:09:20 +00:00
|
|
|
if cp == cacheNone {
|
|
|
|
return false
|
|
|
|
}
|
2018-08-27 21:25:21 +00:00
|
|
|
sattr := d.Inode.StableAttr
|
2019-04-29 21:03:04 +00:00
|
|
|
// NOTE(b/31979197): Only cache files, directories, and symlinks.
|
2018-06-19 18:09:20 +00:00
|
|
|
return fs.IsFile(sattr) || fs.IsDir(sattr) || fs.IsSymlink(sattr)
|
|
|
|
}
|
|
|
|
|
|
|
|
// cacheNegativeDirents indicates that negative dirents should be held in the
|
|
|
|
// dirent tree.
|
|
|
|
func (cp cachePolicy) cacheNegativeDirents() bool {
|
|
|
|
return cp == cacheAll || cp == cacheAllWritethrough
|
|
|
|
}
|