diff options
author | Nicolas Lacasse <nlacasse@google.com> | 2018-08-27 14:25:21 -0700 |
---|---|---|
committer | Shentubot <shentubot@google.com> | 2018-08-27 14:26:29 -0700 |
commit | 0b3bfe2ea30d491a6533f8ee74eb6e3cea707f06 (patch) | |
tree | 1be36b37d8d3f6d47fed0f17af901311eb51bb90 /pkg/sentry/fs/gofer | |
parent | 5999767d53d6c00d7e0f1966700e2876879f490e (diff) |
fs: Fix remote-revalidate cache policy.
When revalidating a Dirent, if the inode id is the same, then we don't need to
throw away the entire Dirent. We can just update the unstable attributes in
place.
If the inode id has changed, then the remote file has been deleted or moved,
and we have no choice but to throw away the dirent we have a look up another.
In this case, we may still end up losing a mounted dirent that is a child of
the revalidated dirent. However, that seems appropriate here because the entire
mount point has been pulled out from underneath us.
Because gVisor's overlay is at the Inode level rather than the Dirent level, we
must pass the parent Inode and name along with the Inode that is being
revalidated.
PiperOrigin-RevId: 210431270
Change-Id: I705caef9c68900234972d5aac4ae3a78c61c7d42
Diffstat (limited to 'pkg/sentry/fs/gofer')
-rw-r--r-- | pkg/sentry/fs/gofer/cache_policy.go | 64 | ||||
-rw-r--r-- | pkg/sentry/fs/gofer/gofer_test.go | 95 | ||||
-rw-r--r-- | pkg/sentry/fs/gofer/session.go | 6 |
3 files changed, 127 insertions, 38 deletions
diff --git a/pkg/sentry/fs/gofer/cache_policy.go b/pkg/sentry/fs/gofer/cache_policy.go index fa8abf51c..98f43c578 100644 --- a/pkg/sentry/fs/gofer/cache_policy.go +++ b/pkg/sentry/fs/gofer/cache_policy.go @@ -17,6 +17,7 @@ package gofer import ( "fmt" + "gvisor.googlesource.com/gvisor/pkg/sentry/context" "gvisor.googlesource.com/gvisor/pkg/sentry/fs" ) @@ -108,25 +109,68 @@ func (cp cachePolicy) writeThrough(inode *fs.Inode) bool { return cp == cacheNone || cp == cacheAllWritethrough } -// revalidateDirent indicates that a dirent should be revalidated after a -// lookup, because the looked up version may be stale. -func (cp cachePolicy) revalidateDirent() bool { +// 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 { if cp == cacheAll || cp == cacheAllWritethrough { return false } - // TODO: The cacheRemoteRevalidating policy should only - // return true if the remote file's attributes have changed. - return true + 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. + // + // TODO: If we have a directory FD in the parent + // inodeOperations, then we can use fstatat(2) to get the inode + // attributes instead of making this RPC. + qids, _, mask, attr, err := parentIops.fileState.file.walkGetAttr(ctx, []string{name}) + if err != nil { + // Can't look up the name. Trigger reload. + return true + } + + // 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 } -// keepDirent indicates that dirents should be kept pinned in the dirent tree -// even if there are no application references on the file. -func (cp cachePolicy) keepDirent(inode *fs.Inode) bool { +// 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 { if cp == cacheNone { return false } - sattr := inode.StableAttr + sattr := d.Inode.StableAttr // NOTE: Only cache files, directories, and symlinks. return fs.IsFile(sattr) || fs.IsDir(sattr) || fs.IsSymlink(sattr) } diff --git a/pkg/sentry/fs/gofer/gofer_test.go b/pkg/sentry/fs/gofer/gofer_test.go index 45fdaacfd..c8d7bd773 100644 --- a/pkg/sentry/fs/gofer/gofer_test.go +++ b/pkg/sentry/fs/gofer/gofer_test.go @@ -151,41 +151,60 @@ func TestLookup(t *testing.T) { func TestRevalidation(t *testing.T) { tests := []struct { - cachePolicy cachePolicy - preModificationWantReval bool - postModificationWantReval bool + cachePolicy cachePolicy + + // Whether dirent should be reloaded before any modifications. + preModificationWantReload bool + + // Whether dirent should be reloaded after updating an unstable + // attribute on the remote fs. + postModificationWantReload bool + + // Whether dirent unstable attributes should be updated after + // updating an attribute on the remote fs. + postModificationWantUpdatedAttrs bool + + // Whether dirent should be reloaded after the remote has + // removed the file. + postRemovalWantReload bool }{ { // Policy cacheNone causes Revalidate to always return // true. - cachePolicy: cacheNone, - preModificationWantReval: true, - postModificationWantReval: true, + cachePolicy: cacheNone, + preModificationWantReload: true, + postModificationWantReload: true, + postModificationWantUpdatedAttrs: true, + postRemovalWantReload: true, }, { // Policy cacheAll causes Revalidate to always return // false. - cachePolicy: cacheAll, - preModificationWantReval: false, - postModificationWantReval: false, + cachePolicy: cacheAll, + preModificationWantReload: false, + postModificationWantReload: false, + postModificationWantUpdatedAttrs: false, + postRemovalWantReload: false, }, { // Policy cacheAllWritethrough causes Revalidate to // always return false. - cachePolicy: cacheAllWritethrough, - preModificationWantReval: false, - postModificationWantReval: false, + cachePolicy: cacheAllWritethrough, + preModificationWantReload: false, + postModificationWantReload: false, + postModificationWantUpdatedAttrs: false, + postRemovalWantReload: false, }, { // Policy cacheRemoteRevalidating causes Revalidate to - // always return true. - // - // TODO: The cacheRemoteRevalidating - // policy should only return true if the remote file's - // attributes have changed. - cachePolicy: cacheRemoteRevalidating, - preModificationWantReval: true, - postModificationWantReval: true, + // return update cached unstable attrs, and returns + // true only when the remote inode itself has been + // removed or replaced. + cachePolicy: cacheRemoteRevalidating, + preModificationWantReload: false, + postModificationWantReload: false, + postModificationWantUpdatedAttrs: true, + postRemovalWantReload: true, }, } @@ -227,15 +246,17 @@ func TestRevalidation(t *testing.T) { if err != nil { t.Fatalf("Lookup(%q) failed: %v", name, err) } - if test.preModificationWantReval && dirent == newDirent { + if test.preModificationWantReload && dirent == newDirent { t.Errorf("Lookup(%q) with cachePolicy=%s got old dirent %v, wanted a new dirent", name, test.cachePolicy, dirent) } - if !test.preModificationWantReval && dirent != newDirent { + if !test.preModificationWantReload && dirent != newDirent { t.Errorf("Lookup(%q) with cachePolicy=%s got new dirent %v, wanted old dirent %v", name, test.cachePolicy, newDirent, dirent) } // Modify the underlying mocked file's modification time. - file.GetAttrMock.Attr.MTimeSeconds = uint64(time.Now().Unix()) + nowSeconds := time.Now().Unix() + rootFile.WalkGetAttrMock.Attr.MTimeSeconds = uint64(nowSeconds) + file.GetAttrMock.Attr.MTimeSeconds = uint64(nowSeconds) // Walk again. Depending on the cache policy, we may get a new // dirent. @@ -243,12 +264,36 @@ func TestRevalidation(t *testing.T) { if err != nil { t.Fatalf("Lookup(%q) failed: %v", name, err) } - if test.postModificationWantReval && dirent == newDirent { + if test.postModificationWantReload && dirent == newDirent { t.Errorf("Lookup(%q) with cachePolicy=%s got old dirent %v, wanted a new dirent", name, test.cachePolicy, dirent) } - if !test.postModificationWantReval && dirent != newDirent { + if !test.postModificationWantReload && dirent != newDirent { t.Errorf("Lookup(%q) with cachePolicy=%s got new dirent %v, wanted old dirent %v", name, test.cachePolicy, newDirent, dirent) } + uattrs, err := newDirent.Inode.UnstableAttr(ctx) + if err != nil { + t.Fatalf("Error getting unstable attrs: %v", err) + } + gotModTimeSeconds := uattrs.ModificationTime.Seconds() + if test.postModificationWantUpdatedAttrs && gotModTimeSeconds != nowSeconds { + t.Fatalf("Lookup(%q) with cachePolicy=%s got new modification time %v, wanted %v", name, test.cachePolicy, gotModTimeSeconds, nowSeconds) + } + + // Make WalkGetAttr return ENOENT. This simulates + // removing the file from the remote fs. + rootFile.WalkGetAttrMock = p9test.WalkGetAttrMock{ + Err: syscall.ENOENT, + } + + // Walk again. Depending on the cache policy, we may + // get ENOENT. + newDirent, err = rootDir.Walk(ctx, rootDir, name) + if test.postRemovalWantReload && err == nil { + t.Errorf("Lookup(%q) with cachePolicy=%s got nil error, wanted ENOENT", name, test.cachePolicy) + } + if !test.postRemovalWantReload && (err != nil || dirent != newDirent) { + t.Errorf("Lookup(%q) with cachePolicy=%s got new dirent %v and error %v, wanted old dirent %v and nil error", name, test.cachePolicy, newDirent, err, dirent) + } }) } } diff --git a/pkg/sentry/fs/gofer/session.go b/pkg/sentry/fs/gofer/session.go index eeb9087e9..49d27ee88 100644 --- a/pkg/sentry/fs/gofer/session.go +++ b/pkg/sentry/fs/gofer/session.go @@ -146,13 +146,13 @@ func (s *session) Destroy() { } // Revalidate implements MountSource.Revalidate. -func (s *session) Revalidate(ctx context.Context, i *fs.Inode) bool { - return s.cachePolicy.revalidateDirent() +func (s *session) Revalidate(ctx context.Context, name string, parent, child *fs.Inode) bool { + return s.cachePolicy.revalidate(ctx, name, parent, child) } // Keep implements MountSource.Keep. func (s *session) Keep(d *fs.Dirent) bool { - return s.cachePolicy.keepDirent(d.Inode) + return s.cachePolicy.keep(d) } // ResetInodeMappings implements fs.MountSourceOperations.ResetInodeMappings. |