summaryrefslogtreecommitdiffhomepage
path: root/pkg/sentry/fsimpl/gofer
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/sentry/fsimpl/gofer')
-rw-r--r--pkg/sentry/fsimpl/gofer/BUILD1
-rw-r--r--pkg/sentry/fsimpl/gofer/filesystem.go280
-rw-r--r--pkg/sentry/fsimpl/gofer/gofer.go6
-rw-r--r--pkg/sentry/fsimpl/gofer/p9file.go7
-rw-r--r--pkg/sentry/fsimpl/gofer/revalidate.go386
5 files changed, 187 insertions, 493 deletions
diff --git a/pkg/sentry/fsimpl/gofer/BUILD b/pkg/sentry/fsimpl/gofer/BUILD
index 52879f871..6d5258a9b 100644
--- a/pkg/sentry/fsimpl/gofer/BUILD
+++ b/pkg/sentry/fsimpl/gofer/BUILD
@@ -38,7 +38,6 @@ go_library(
"host_named_pipe.go",
"p9file.go",
"regular_file.go",
- "revalidate.go",
"save_restore.go",
"socket.go",
"special_file.go",
diff --git a/pkg/sentry/fsimpl/gofer/filesystem.go b/pkg/sentry/fsimpl/gofer/filesystem.go
index 40c9243f0..4b5621043 100644
--- a/pkg/sentry/fsimpl/gofer/filesystem.go
+++ b/pkg/sentry/fsimpl/gofer/filesystem.go
@@ -169,89 +169,156 @@ func (fs *filesystem) renameMuUnlockAndCheckCaching(ctx context.Context, ds **[]
// * fs.renameMu must be locked.
// * d.dirMu must be locked.
// * !rp.Done().
-// * If !d.cachedMetadataAuthoritative(), then d and all children that are
-// part of rp must have been revalidated.
+// * If !d.cachedMetadataAuthoritative(), then d's cached metadata must be up
+// to date.
//
// Postconditions: The returned dentry's cached metadata is up to date.
-func (fs *filesystem) stepLocked(ctx context.Context, rp *vfs.ResolvingPath, d *dentry, mayFollowSymlinks bool, ds **[]*dentry) (*dentry, bool, error) {
+func (fs *filesystem) stepLocked(ctx context.Context, rp *vfs.ResolvingPath, d *dentry, mayFollowSymlinks bool, ds **[]*dentry) (*dentry, error) {
if !d.isDir() {
- return nil, false, syserror.ENOTDIR
+ return nil, syserror.ENOTDIR
}
if err := d.checkPermissions(rp.Credentials(), vfs.MayExec); err != nil {
- return nil, false, err
+ return nil, err
}
- followedSymlink := false
afterSymlink:
name := rp.Component()
if name == "." {
rp.Advance()
- return d, followedSymlink, nil
+ return d, nil
}
if name == ".." {
if isRoot, err := rp.CheckRoot(ctx, &d.vfsd); err != nil {
- return nil, false, err
+ return nil, err
} else if isRoot || d.parent == nil {
rp.Advance()
- return d, followedSymlink, nil
- }
+ return d, nil
+ }
+ // We must assume that d.parent is correct, because if d has been moved
+ // elsewhere in the remote filesystem so that its parent has changed,
+ // we have no way of determining its new parent's location in the
+ // filesystem.
+ //
+ // Call rp.CheckMount() before updating d.parent's metadata, since if
+ // we traverse to another mount then d.parent's metadata is irrelevant.
if err := rp.CheckMount(ctx, &d.parent.vfsd); err != nil {
- return nil, false, err
+ return nil, err
+ }
+ if d != d.parent && !d.cachedMetadataAuthoritative() {
+ if err := d.parent.updateFromGetattr(ctx); err != nil {
+ return nil, err
+ }
}
rp.Advance()
- return d.parent, followedSymlink, nil
+ return d.parent, nil
}
- child, err := fs.getChildLocked(ctx, d, name, ds)
+ child, err := fs.getChildLocked(ctx, rp.VirtualFilesystem(), d, name, ds)
if err != nil {
- return nil, false, err
+ return nil, err
+ }
+ if child == nil {
+ return nil, syserror.ENOENT
}
if err := rp.CheckMount(ctx, &child.vfsd); err != nil {
- return nil, false, err
+ return nil, err
}
if child.isSymlink() && mayFollowSymlinks && rp.ShouldFollowSymlink() {
target, err := child.readlink(ctx, rp.Mount())
if err != nil {
- return nil, false, err
+ return nil, err
}
if err := rp.HandleSymlink(target); err != nil {
- return nil, false, err
+ return nil, err
}
- followedSymlink = true
goto afterSymlink // don't check the current directory again
}
rp.Advance()
- return child, followedSymlink, nil
+ return child, nil
}
// getChildLocked returns a dentry representing the child of parent with the
-// given name. Returns ENOENT if the child doesn't exist.
+// given name. If no such child exists, getChildLocked returns (nil, nil).
//
// Preconditions:
// * fs.renameMu must be locked.
// * parent.dirMu must be locked.
// * parent.isDir().
// * name is not "." or "..".
-// * dentry at name has been revalidated
-func (fs *filesystem) getChildLocked(ctx context.Context, parent *dentry, name string, ds **[]*dentry) (*dentry, error) {
+//
+// Postconditions: If getChildLocked returns a non-nil dentry, its cached
+// metadata is up to date.
+func (fs *filesystem) getChildLocked(ctx context.Context, vfsObj *vfs.VirtualFilesystem, parent *dentry, name string, ds **[]*dentry) (*dentry, error) {
if len(name) > maxFilenameLen {
return nil, syserror.ENAMETOOLONG
}
- if child, ok := parent.children[name]; ok || parent.isSynthetic() {
- if child == nil {
- return nil, syserror.ENOENT
- }
+ child, ok := parent.children[name]
+ if (ok && fs.opts.interop != InteropModeShared) || parent.isSynthetic() {
+ // Whether child is nil or not, it is cached information that is
+ // assumed to be correct.
return child, nil
}
+ // We either don't have cached information or need to verify that it's
+ // still correct, either of which requires a remote lookup. Check if this
+ // name is valid before performing the lookup.
+ return fs.revalidateChildLocked(ctx, vfsObj, parent, name, child, ds)
+}
+// Preconditions: Same as getChildLocked, plus:
+// * !parent.isSynthetic().
+func (fs *filesystem) revalidateChildLocked(ctx context.Context, vfsObj *vfs.VirtualFilesystem, parent *dentry, name string, child *dentry, ds **[]*dentry) (*dentry, error) {
+ if child != nil {
+ // Need to lock child.metadataMu because we might be updating child
+ // metadata. We need to hold the lock *before* getting metadata from the
+ // server and release it after updating local metadata.
+ child.metadataMu.Lock()
+ }
qid, file, attrMask, attr, err := parent.file.walkGetAttrOne(ctx, name)
- if err != nil {
- if err == syserror.ENOENT {
- parent.cacheNegativeLookupLocked(name)
+ if err != nil && err != syserror.ENOENT {
+ if child != nil {
+ child.metadataMu.Unlock()
}
return nil, err
}
-
+ if child != nil {
+ if !file.isNil() && qid.Path == child.qidPath {
+ // The file at this path hasn't changed. Just update cached metadata.
+ file.close(ctx)
+ child.updateFromP9AttrsLocked(attrMask, &attr)
+ child.metadataMu.Unlock()
+ return child, nil
+ }
+ child.metadataMu.Unlock()
+ if file.isNil() && child.isSynthetic() {
+ // We have a synthetic file, and no remote file has arisen to
+ // replace it.
+ return child, nil
+ }
+ // The file at this path has changed or no longer exists. Mark the
+ // dentry invalidated, and re-evaluate its caching status (i.e. if it
+ // has 0 references, drop it). Wait to update parent.children until we
+ // know what to replace the existing dentry with (i.e. one of the
+ // returns below), to avoid a redundant map access.
+ vfsObj.InvalidateDentry(ctx, &child.vfsd)
+ if child.isSynthetic() {
+ // Normally we don't mark invalidated dentries as deleted since
+ // they may still exist (but at a different path), and also for
+ // consistency with Linux. However, synthetic files are guaranteed
+ // to become unreachable if their dentries are invalidated, so
+ // treat their invalidation as deletion.
+ child.setDeleted()
+ parent.syntheticChildren--
+ child.decRefNoCaching()
+ parent.dirents = nil
+ }
+ *ds = appendDentry(*ds, child)
+ }
+ if file.isNil() {
+ // No file exists at this path now. Cache the negative lookup if
+ // allowed.
+ parent.cacheNegativeLookupLocked(name)
+ return nil, nil
+ }
// Create a new dentry representing the file.
- child, err := fs.newDentry(ctx, file, qid, attrMask, &attr)
+ child, err = fs.newDentry(ctx, file, qid, attrMask, &attr)
if err != nil {
file.close(ctx)
delete(parent.children, name)
@@ -277,22 +344,14 @@ func (fs *filesystem) getChildLocked(ctx context.Context, parent *dentry, name s
// * If !d.cachedMetadataAuthoritative(), then d's cached metadata must be up
// to date.
func (fs *filesystem) walkParentDirLocked(ctx context.Context, rp *vfs.ResolvingPath, d *dentry, ds **[]*dentry) (*dentry, error) {
- if err := fs.revalidateParentDir(ctx, rp, d, ds); err != nil {
- return nil, err
- }
for !rp.Final() {
d.dirMu.Lock()
- next, followedSymlink, err := fs.stepLocked(ctx, rp, d, true /* mayFollowSymlinks */, ds)
+ next, err := fs.stepLocked(ctx, rp, d, true /* mayFollowSymlinks */, ds)
d.dirMu.Unlock()
if err != nil {
return nil, err
}
d = next
- if followedSymlink {
- if err := fs.revalidateParentDir(ctx, rp, d, ds); err != nil {
- return nil, err
- }
- }
}
if !d.isDir() {
return nil, syserror.ENOTDIR
@@ -305,22 +364,20 @@ func (fs *filesystem) walkParentDirLocked(ctx context.Context, rp *vfs.Resolving
// Preconditions: fs.renameMu must be locked.
func (fs *filesystem) resolveLocked(ctx context.Context, rp *vfs.ResolvingPath, ds **[]*dentry) (*dentry, error) {
d := rp.Start().Impl().(*dentry)
- if err := fs.revalidatePath(ctx, rp, d, ds); err != nil {
- return nil, err
+ if !d.cachedMetadataAuthoritative() {
+ // Get updated metadata for rp.Start() as required by fs.stepLocked().
+ if err := d.updateFromGetattr(ctx); err != nil {
+ return nil, err
+ }
}
for !rp.Done() {
d.dirMu.Lock()
- next, followedSymlink, err := fs.stepLocked(ctx, rp, d, true /* mayFollowSymlinks */, ds)
+ next, err := fs.stepLocked(ctx, rp, d, true /* mayFollowSymlinks */, ds)
d.dirMu.Unlock()
if err != nil {
return nil, err
}
d = next
- if followedSymlink {
- if err := fs.revalidatePath(ctx, rp, d, ds); err != nil {
- return nil, err
- }
- }
}
if rp.MustBeDir() && !d.isDir() {
return nil, syserror.ENOTDIR
@@ -340,6 +397,13 @@ func (fs *filesystem) doCreateAt(ctx context.Context, rp *vfs.ResolvingPath, dir
fs.renameMu.RLock()
defer fs.renameMuRUnlockAndCheckCaching(ctx, &ds)
start := rp.Start().Impl().(*dentry)
+ if !start.cachedMetadataAuthoritative() {
+ // Get updated metadata for start as required by
+ // fs.walkParentDirLocked().
+ if err := start.updateFromGetattr(ctx); err != nil {
+ return err
+ }
+ }
parent, err := fs.walkParentDirLocked(ctx, rp, start, &ds)
if err != nil {
return err
@@ -357,14 +421,11 @@ func (fs *filesystem) doCreateAt(ctx context.Context, rp *vfs.ResolvingPath, dir
if parent.isDeleted() {
return syserror.ENOENT
}
- if err := fs.revalidateOne(ctx, rp.VirtualFilesystem(), parent, name, &ds); err != nil {
- return err
- }
parent.dirMu.Lock()
defer parent.dirMu.Unlock()
- child, err := fs.getChildLocked(ctx, parent, name, &ds)
+ child, err := fs.getChildLocked(ctx, rp.VirtualFilesystem(), parent, name, &ds)
switch {
case err != nil && err != syserror.ENOENT:
return err
@@ -428,6 +489,13 @@ func (fs *filesystem) unlinkAt(ctx context.Context, rp *vfs.ResolvingPath, dir b
fs.renameMu.RLock()
defer fs.renameMuRUnlockAndCheckCaching(ctx, &ds)
start := rp.Start().Impl().(*dentry)
+ if !start.cachedMetadataAuthoritative() {
+ // Get updated metadata for start as required by
+ // fs.walkParentDirLocked().
+ if err := start.updateFromGetattr(ctx); err != nil {
+ return err
+ }
+ }
parent, err := fs.walkParentDirLocked(ctx, rp, start, &ds)
if err != nil {
return err
@@ -453,32 +521,33 @@ func (fs *filesystem) unlinkAt(ctx context.Context, rp *vfs.ResolvingPath, dir b
return syserror.EISDIR
}
}
-
vfsObj := rp.VirtualFilesystem()
- if err := fs.revalidateOne(ctx, vfsObj, parent, rp.Component(), &ds); err != nil {
- return err
- }
-
mntns := vfs.MountNamespaceFromContext(ctx)
defer mntns.DecRef(ctx)
-
parent.dirMu.Lock()
defer parent.dirMu.Unlock()
- // Load child if sticky bit is set because we need to determine whether
- // deletion is allowed.
- var child *dentry
- if atomic.LoadUint32(&parent.mode)&linux.ModeSticky == 0 {
- var ok bool
- child, ok = parent.children[name]
- if ok && child == nil {
- // Hit a negative cached entry, child doesn't exist.
- return syserror.ENOENT
- }
- } else {
- child, _, err = fs.stepLocked(ctx, rp, parent, false /* mayFollowSymlinks */, &ds)
- if err != nil {
- return err
+ child, ok := parent.children[name]
+ if ok && child == nil {
+ return syserror.ENOENT
+ }
+
+ sticky := atomic.LoadUint32(&parent.mode)&linux.ModeSticky != 0
+ if sticky {
+ if !ok {
+ // If the sticky bit is set, we need to retrieve the child to determine
+ // whether removing it is allowed.
+ child, err = fs.stepLocked(ctx, rp, parent, false /* mayFollowSymlinks */, &ds)
+ if err != nil {
+ return err
+ }
+ } else if child != nil && !child.cachedMetadataAuthoritative() {
+ // Make sure the dentry representing the file at name is up to date
+ // before examining its metadata.
+ child, err = fs.revalidateChildLocked(ctx, vfsObj, parent, name, child, &ds)
+ if err != nil {
+ return err
+ }
}
if err := parent.mayDelete(rp.Credentials(), child); err != nil {
return err
@@ -487,7 +556,11 @@ func (fs *filesystem) unlinkAt(ctx context.Context, rp *vfs.ResolvingPath, dir b
// If a child dentry exists, prepare to delete it. This should fail if it is
// a mount point. We detect mount points by speculatively calling
- // PrepareDeleteDentry, which fails if child is a mount point.
+ // PrepareDeleteDentry, which fails if child is a mount point. However, we
+ // may need to revalidate the file in this case to make sure that it has not
+ // been deleted or replaced on the remote fs, in which case the mount point
+ // will have disappeared. If calling PrepareDeleteDentry fails again on the
+ // up-to-date dentry, we can be sure that it is a mount point.
//
// Also note that if child is nil, then it can't be a mount point.
if child != nil {
@@ -502,7 +575,23 @@ func (fs *filesystem) unlinkAt(ctx context.Context, rp *vfs.ResolvingPath, dir b
child.dirMu.Lock()
defer child.dirMu.Unlock()
if err := vfsObj.PrepareDeleteDentry(mntns, &child.vfsd); err != nil {
- return err
+ // We can skip revalidation in several cases:
+ // - We are not in InteropModeShared
+ // - The parent directory is synthetic, in which case the child must also
+ // be synthetic
+ // - We already updated the child during the sticky bit check above
+ if parent.cachedMetadataAuthoritative() || sticky {
+ return err
+ }
+ child, err = fs.revalidateChildLocked(ctx, vfsObj, parent, name, child, &ds)
+ if err != nil {
+ return err
+ }
+ if child != nil {
+ if err := vfsObj.PrepareDeleteDentry(mntns, &child.vfsd); err != nil {
+ return err
+ }
+ }
}
}
flags := uint32(0)
@@ -634,6 +723,13 @@ func (fs *filesystem) GetParentDentryAt(ctx context.Context, rp *vfs.ResolvingPa
fs.renameMu.RLock()
defer fs.renameMuRUnlockAndCheckCaching(ctx, &ds)
start := rp.Start().Impl().(*dentry)
+ if !start.cachedMetadataAuthoritative() {
+ // Get updated metadata for start as required by
+ // fs.walkParentDirLocked().
+ if err := start.updateFromGetattr(ctx); err != nil {
+ return nil, err
+ }
+ }
d, err := fs.walkParentDirLocked(ctx, rp, start, &ds)
if err != nil {
return nil, err
@@ -734,7 +830,7 @@ func (fs *filesystem) MknodAt(ctx context.Context, rp *vfs.ResolvingPath, opts v
// to creating a synthetic one, i.e. one that is kept entirely in memory.
// Check that we're not overriding an existing file with a synthetic one.
- _, _, err = fs.stepLocked(ctx, rp, parent, true, ds)
+ _, err = fs.stepLocked(ctx, rp, parent, true, ds)
switch {
case err == nil:
// Step succeeded, another file exists.
@@ -795,6 +891,12 @@ func (fs *filesystem) OpenAt(ctx context.Context, rp *vfs.ResolvingPath, opts vf
defer unlock()
start := rp.Start().Impl().(*dentry)
+ if !start.cachedMetadataAuthoritative() {
+ // Get updated metadata for start as required by fs.stepLocked().
+ if err := start.updateFromGetattr(ctx); err != nil {
+ return nil, err
+ }
+ }
if rp.Done() {
// Reject attempts to open mount root directory with O_CREAT.
if mayCreate && rp.MustBeDir() {
@@ -803,12 +905,6 @@ func (fs *filesystem) OpenAt(ctx context.Context, rp *vfs.ResolvingPath, opts vf
if mustCreate {
return nil, syserror.EEXIST
}
- if !start.cachedMetadataAuthoritative() {
- // Refresh dentry's attributes before opening.
- if err := start.updateFromGetattr(ctx); err != nil {
- return nil, err
- }
- }
start.IncRef()
defer start.DecRef(ctx)
unlock()
@@ -830,12 +926,9 @@ afterTrailingSymlink:
if mayCreate && rp.MustBeDir() {
return nil, syserror.EISDIR
}
- if err := fs.revalidateOne(ctx, rp.VirtualFilesystem(), parent, rp.Component(), &ds); err != nil {
- return nil, err
- }
// Determine whether or not we need to create a file.
parent.dirMu.Lock()
- child, _, err := fs.stepLocked(ctx, rp, parent, false /* mayFollowSymlinks */, &ds)
+ child, err := fs.stepLocked(ctx, rp, parent, false /* mayFollowSymlinks */, &ds)
if err == syserror.ENOENT && mayCreate {
if parent.isSynthetic() {
parent.dirMu.Unlock()
@@ -1204,23 +1297,18 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
if err := oldParent.checkPermissions(creds, vfs.MayWrite|vfs.MayExec); err != nil {
return err
}
-
vfsObj := rp.VirtualFilesystem()
- if err := fs.revalidateOne(ctx, vfsObj, newParent, newName, &ds); err != nil {
- return err
- }
- if err := fs.revalidateOne(ctx, vfsObj, oldParent, oldName, &ds); err != nil {
- return err
- }
-
// We need a dentry representing the renamed file since, if it's a
// directory, we need to check for write permission on it.
oldParent.dirMu.Lock()
defer oldParent.dirMu.Unlock()
- renamed, err := fs.getChildLocked(ctx, oldParent, oldName, &ds)
+ renamed, err := fs.getChildLocked(ctx, vfsObj, oldParent, oldName, &ds)
if err != nil {
return err
}
+ if renamed == nil {
+ return syserror.ENOENT
+ }
if err := oldParent.mayDelete(creds, renamed); err != nil {
return err
}
@@ -1249,8 +1337,8 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
if newParent.isDeleted() {
return syserror.ENOENT
}
- replaced, err := fs.getChildLocked(ctx, newParent, newName, &ds)
- if err != nil && err != syserror.ENOENT {
+ replaced, err := fs.getChildLocked(ctx, rp.VirtualFilesystem(), newParent, newName, &ds)
+ if err != nil {
return err
}
var replacedVFSD *vfs.Dentry
diff --git a/pkg/sentry/fsimpl/gofer/gofer.go b/pkg/sentry/fsimpl/gofer/gofer.go
index 21692d2ac..fb42c5f62 100644
--- a/pkg/sentry/fsimpl/gofer/gofer.go
+++ b/pkg/sentry/fsimpl/gofer/gofer.go
@@ -32,9 +32,9 @@
// specialFileFD.mu
// specialFileFD.bufMu
//
-// Locking dentry.dirMu and dentry.metadataMu in multiple dentries requires that
-// either ancestor dentries are locked before descendant dentries, or that
-// filesystem.renameMu is locked for writing.
+// Locking dentry.dirMu in multiple dentries requires that either ancestor
+// dentries are locked before descendant dentries, or that filesystem.renameMu
+// is locked for writing.
package gofer
import (
diff --git a/pkg/sentry/fsimpl/gofer/p9file.go b/pkg/sentry/fsimpl/gofer/p9file.go
index b0a429d42..21b4a96fe 100644
--- a/pkg/sentry/fsimpl/gofer/p9file.go
+++ b/pkg/sentry/fsimpl/gofer/p9file.go
@@ -238,10 +238,3 @@ func (f p9file) connect(ctx context.Context, flags p9.ConnectFlags) (*fd.FD, err
ctx.UninterruptibleSleepFinish(false)
return fdobj, err
}
-
-func (f p9file) multiGetAttr(ctx context.Context, names []string) ([]p9.FullStat, error) {
- ctx.UninterruptibleSleepStart(false)
- stats, err := f.file.MultiGetAttr(names)
- ctx.UninterruptibleSleepFinish(false)
- return stats, err
-}
diff --git a/pkg/sentry/fsimpl/gofer/revalidate.go b/pkg/sentry/fsimpl/gofer/revalidate.go
deleted file mode 100644
index 8f81f0822..000000000
--- a/pkg/sentry/fsimpl/gofer/revalidate.go
+++ /dev/null
@@ -1,386 +0,0 @@
-// Copyright 2021 The gVisor Authors.
-//
-// 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 (
- "gvisor.dev/gvisor/pkg/context"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
- "gvisor.dev/gvisor/pkg/sync"
-)
-
-type errPartialRevalidation struct{}
-
-// Error implements error.Error.
-func (errPartialRevalidation) Error() string {
- return "partial revalidation"
-}
-
-type errRevalidationStepDone struct{}
-
-// Error implements error.Error.
-func (errRevalidationStepDone) Error() string {
- return "stop revalidation"
-}
-
-// revalidatePath checks cached dentries for external modification. File
-// attributes are refreshed and cache is invalidated in case the dentry has been
-// deleted, or a new file/directory created in its place.
-//
-// Revalidation stops at symlinks and mount points. The caller is responsible
-// for revalidating again after symlinks are resolved and after changing to
-// different mounts.
-//
-// Preconditions:
-// * fs.renameMu must be locked.
-func (fs *filesystem) revalidatePath(ctx context.Context, rpOrig *vfs.ResolvingPath, start *dentry, ds **[]*dentry) error {
- // Revalidation is done even if start is synthetic in case the path is
- // something like: ../non_synthetic_file.
- if fs.opts.interop != InteropModeShared {
- return nil
- }
-
- // Copy resolving path to walk the path for revalidation.
- rp := rpOrig.Copy()
- err := fs.revalidate(ctx, rp, start, rp.Done, ds)
- rp.Release(ctx)
- return err
-}
-
-// revalidateParentDir does the same as revalidatePath, but stops at the parent.
-//
-// Preconditions:
-// * fs.renameMu must be locked.
-func (fs *filesystem) revalidateParentDir(ctx context.Context, rpOrig *vfs.ResolvingPath, start *dentry, ds **[]*dentry) error {
- // Revalidation is done even if start is synthetic in case the path is
- // something like: ../non_synthetic_file and parent is non synthetic.
- if fs.opts.interop != InteropModeShared {
- return nil
- }
-
- // Copy resolving path to walk the path for revalidation.
- rp := rpOrig.Copy()
- err := fs.revalidate(ctx, rp, start, rp.Final, ds)
- rp.Release(ctx)
- return err
-}
-
-// revalidateOne does the same as revalidatePath, but checks a single dentry.
-//
-// Preconditions:
-// * fs.renameMu must be locked.
-func (fs *filesystem) revalidateOne(ctx context.Context, vfsObj *vfs.VirtualFilesystem, parent *dentry, name string, ds **[]*dentry) error {
- // Skip revalidation for interop mode different than InteropModeShared or
- // if the parent is synthetic (child must be synthetic too, but it cannot be
- // replaced without first replacing the parent).
- if parent.cachedMetadataAuthoritative() {
- return nil
- }
-
- parent.dirMu.Lock()
- child, ok := parent.children[name]
- parent.dirMu.Unlock()
- if !ok {
- return nil
- }
-
- state := makeRevalidateState(parent)
- defer state.release()
-
- state.add(name, child)
- return fs.revalidateHelper(ctx, vfsObj, state, ds)
-}
-
-// revalidate revalidates path components in rp until done returns true, or
-// until a mount point or symlink is reached. It may send multiple MultiGetAttr
-// calls to the gofer to handle ".." in the path.
-//
-// Preconditions:
-// * fs.renameMu must be locked.
-// * InteropModeShared is in effect.
-func (fs *filesystem) revalidate(ctx context.Context, rp *vfs.ResolvingPath, start *dentry, done func() bool, ds **[]*dentry) error {
- state := makeRevalidateState(start)
- defer state.release()
-
- // Skip synthetic dentries because the start dentry cannot be replaced in case
- // it has been created in the remote file system.
- if !start.isSynthetic() {
- state.add("", start)
- }
-
-done:
- for cur := start; !done(); {
- var err error
- cur, err = fs.revalidateStep(ctx, rp, cur, state)
- if err != nil {
- switch err.(type) {
- case errPartialRevalidation:
- if err := fs.revalidateHelper(ctx, rp.VirtualFilesystem(), state, ds); err != nil {
- return err
- }
-
- // Reset state to release any remaining locks and restart from where
- // stepping stopped.
- state.reset()
- state.start = cur
-
- // Skip synthetic dentries because the start dentry cannot be replaced in
- // case it has been created in the remote file system.
- if !cur.isSynthetic() {
- state.add("", cur)
- }
-
- case errRevalidationStepDone:
- break done
-
- default:
- return err
- }
- }
- }
- return fs.revalidateHelper(ctx, rp.VirtualFilesystem(), state, ds)
-}
-
-// revalidateStep walks one element of the path and updates revalidationState
-// with the dentry if needed. It may also stop the stepping or ask for a
-// partial revalidation. Partial revalidation requires the caller to revalidate
-// the current revalidationState, release all locks, and resume stepping.
-// In case a symlink is hit, revalidation stops and the caller is responsible
-// for calling revalidate again after the symlink is resolved. Revalidation may
-// also stop for other reasons, like hitting a child not in the cache.
-//
-// Returns:
-// * (dentry, nil): step worked, continue stepping.`
-// * (dentry, errPartialRevalidation): revalidation should be done with the
-// state gathered so far. Then continue stepping with the remainder of the
-// path, starting at `dentry`.
-// * (nil, errRevalidationStepDone): revalidation doesn't need to step any
-// further. It hit a symlink, a mount point, or an uncached dentry.
-//
-// Preconditions:
-// * fs.renameMu must be locked.
-// * !rp.Done().
-// * InteropModeShared is in effect (assumes no negative dentries).
-func (fs *filesystem) revalidateStep(ctx context.Context, rp *vfs.ResolvingPath, d *dentry, state *revalidateState) (*dentry, error) {
- switch name := rp.Component(); name {
- case ".":
- // Do nothing.
-
- case "..":
- // Partial revalidation is required when ".." is hit because metadata locks
- // can only be acquired from parent to child to avoid deadlocks.
- if isRoot, err := rp.CheckRoot(ctx, &d.vfsd); err != nil {
- return nil, errRevalidationStepDone{}
- } else if isRoot || d.parent == nil {
- rp.Advance()
- return d, errPartialRevalidation{}
- }
- // We must assume that d.parent is correct, because if d has been moved
- // elsewhere in the remote filesystem so that its parent has changed,
- // we have no way of determining its new parent's location in the
- // filesystem.
- //
- // Call rp.CheckMount() before updating d.parent's metadata, since if
- // we traverse to another mount then d.parent's metadata is irrelevant.
- if err := rp.CheckMount(ctx, &d.parent.vfsd); err != nil {
- return nil, errRevalidationStepDone{}
- }
- rp.Advance()
- return d.parent, errPartialRevalidation{}
-
- default:
- d.dirMu.Lock()
- child, ok := d.children[name]
- d.dirMu.Unlock()
- if !ok {
- // child is not cached, no need to validate any further.
- return nil, errRevalidationStepDone{}
- }
-
- state.add(name, child)
-
- // Symlink must be resolved before continuing with revalidation.
- if child.isSymlink() {
- return nil, errRevalidationStepDone{}
- }
-
- d = child
- }
-
- rp.Advance()
- return d, nil
-}
-
-// revalidateHelper calls the gofer to stat all dentries in `state`. It will
-// update or invalidate dentries in the cache based on the result.
-//
-// Preconditions:
-// * fs.renameMu must be locked.
-// * InteropModeShared is in effect.
-func (fs *filesystem) revalidateHelper(ctx context.Context, vfsObj *vfs.VirtualFilesystem, state *revalidateState, ds **[]*dentry) error {
- if len(state.names) == 0 {
- return nil
- }
- // Lock metadata on all dentries *before* getting attributes for them.
- state.lockAllMetadata()
- stats, err := state.start.file.multiGetAttr(ctx, state.names)
- if err != nil {
- return err
- }
-
- i := -1
- for d := state.popFront(); d != nil; d = state.popFront() {
- i++
- found := i < len(stats)
- if i == 0 && len(state.names[0]) == 0 {
- if found && !d.isSynthetic() {
- // First dentry is where the search is starting, just update attributes
- // since it cannot be replaced.
- d.updateFromP9AttrsLocked(stats[i].Valid, &stats[i].Attr)
- }
- d.metadataMu.Unlock()
- continue
- }
-
- // Note that synthetic dentries will always fails the comparison check
- // below.
- if !found || d.qidPath != stats[i].QID.Path {
- d.metadataMu.Unlock()
- if !found && d.isSynthetic() {
- // We have a synthetic file, and no remote file has arisen to replace
- // it.
- return nil
- }
- // The file at this path has changed or no longer exists. Mark the
- // dentry invalidated, and re-evaluate its caching status (i.e. if it
- // has 0 references, drop it). The dentry will be reloaded next time it's
- // accessed.
- vfsObj.InvalidateDentry(ctx, &d.vfsd)
-
- name := state.names[i]
- d.parent.dirMu.Lock()
-
- if d.isSynthetic() {
- // Normally we don't mark invalidated dentries as deleted since
- // they may still exist (but at a different path), and also for
- // consistency with Linux. However, synthetic files are guaranteed
- // to become unreachable if their dentries are invalidated, so
- // treat their invalidation as deletion.
- d.setDeleted()
- d.decRefNoCaching()
- *ds = appendDentry(*ds, d)
-
- d.parent.syntheticChildren--
- d.parent.dirents = nil
- }
-
- // Since the dirMu was released and reacquired, re-check that the
- // parent's child with this name is still the same. Do not touch it if
- // it has been replaced with a different one.
- if child := d.parent.children[name]; child == d {
- // Invalidate dentry so it gets reloaded next time it's accessed.
- delete(d.parent.children, name)
- }
- d.parent.dirMu.Unlock()
-
- return nil
- }
-
- // The file at this path hasn't changed. Just update cached metadata.
- d.updateFromP9AttrsLocked(stats[i].Valid, &stats[i].Attr)
- d.metadataMu.Unlock()
- }
-
- return nil
-}
-
-// revalidateStatePool caches revalidateState instances to save array
-// allocations for dentries and names.
-var revalidateStatePool = sync.Pool{
- New: func() interface{} {
- return &revalidateState{}
- },
-}
-
-// revalidateState keeps state related to a revalidation request. It keeps track
-// of {name, dentry} list being revalidated, as well as metadata locks on the
-// dentries. The list must be in ancestry order, in other words `n` must be
-// `n-1` child.
-type revalidateState struct {
- // start is the dentry where to start the attributes search.
- start *dentry
-
- // List of names of entries to refresh attributes. Names length must be the
- // same as detries length. They are kept in separate slices because names is
- // used to call File.MultiGetAttr().
- names []string
-
- // dentries is the list of dentries that correspond to the names above.
- // dentry.metadataMu is acquired as each dentry is added to this list.
- dentries []*dentry
-
- // locked indicates if metadata lock has been acquired on dentries.
- locked bool
-}
-
-func makeRevalidateState(start *dentry) *revalidateState {
- r := revalidateStatePool.Get().(*revalidateState)
- r.start = start
- return r
-}
-
-// release must be called after the caller is done with this object. It releases
-// all metadata locks and resources.
-func (r *revalidateState) release() {
- r.reset()
- revalidateStatePool.Put(r)
-}
-
-// Preconditions:
-// * d is a descendant of all dentries in r.dentries.
-func (r *revalidateState) add(name string, d *dentry) {
- r.names = append(r.names, name)
- r.dentries = append(r.dentries, d)
-}
-
-func (r *revalidateState) lockAllMetadata() {
- for _, d := range r.dentries {
- d.metadataMu.Lock()
- }
- r.locked = true
-}
-
-func (r *revalidateState) popFront() *dentry {
- if len(r.dentries) == 0 {
- return nil
- }
- d := r.dentries[0]
- r.dentries = r.dentries[1:]
- return d
-}
-
-// reset releases all metadata locks and resets all fields to allow this
-// instance to be reused.
-func (r *revalidateState) reset() {
- if r.locked {
- // Unlock any remaining dentries.
- for _, d := range r.dentries {
- d.metadataMu.Unlock()
- }
- r.locked = false
- }
- r.start = nil
- r.names = r.names[:0]
- r.dentries = r.dentries[:0]
-}