mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
354 lines
12 KiB
Go
354 lines
12 KiB
Go
// 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 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, ds)
|
|
rp.Release(ctx)
|
|
return err
|
|
}
|
|
|
|
// revalidateOne does the same as revalidatePath, but checks a single dentry.
|
|
//
|
|
// Preconditions:
|
|
// - fs.renameMu must be locked.
|
|
// - parent must have up to date metadata.
|
|
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.inode.cachedMetadataAuthoritative() {
|
|
return nil
|
|
}
|
|
|
|
parent.childrenMu.Lock()
|
|
child, ok := parent.children[name]
|
|
parent.childrenMu.Unlock()
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
state := makeRevalidateState(parent, false /* refreshStart */)
|
|
defer state.release()
|
|
// Note that child can not be nil, because we don't cache negative entries
|
|
// when InteropModeShared is in effect.
|
|
state.add(child)
|
|
return state.doRevalidation(ctx, vfsObj, 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 resolvingPath, start *dentry, ds **[]*dentry) error {
|
|
state := makeRevalidateState(start, true /* refreshStart */)
|
|
defer state.release()
|
|
|
|
done:
|
|
for cur := start; !rp.done(); {
|
|
var err error
|
|
cur, err = fs.revalidateStep(ctx, rp, cur, state)
|
|
if err != nil {
|
|
switch err.(type) {
|
|
case errPartialRevalidation:
|
|
if err := state.doRevalidation(ctx, rp.VirtualFilesystem(), ds); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Reset state to release any remaining locks and restart from where
|
|
// stepping stopped.
|
|
state.reset(cur /* start */, true /* refreshStart */)
|
|
|
|
case errRevalidationStepDone:
|
|
break done
|
|
|
|
default:
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return state.doRevalidation(ctx, rp.VirtualFilesystem(), 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 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.
|
|
parent := d.parent.Load()
|
|
if isRoot, err := rp.CheckRoot(ctx, &d.vfsd); err != nil {
|
|
return nil, errRevalidationStepDone{}
|
|
} else if isRoot || 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, &parent.vfsd); err != nil {
|
|
return nil, errRevalidationStepDone{}
|
|
}
|
|
rp.Advance()
|
|
return parent, errPartialRevalidation{}
|
|
|
|
default:
|
|
d.childrenMu.Lock()
|
|
child, ok := d.children[name]
|
|
d.childrenMu.Unlock()
|
|
if !ok {
|
|
// child is not cached, no need to validate any further.
|
|
return nil, errRevalidationStepDone{}
|
|
}
|
|
|
|
// Note that child can not be nil, because we don't cache negative entries
|
|
// when InteropModeShared is in effect.
|
|
state.add(child)
|
|
|
|
// Symlink must be resolved before continuing with revalidation.
|
|
if child.inode.isSymlink() {
|
|
return nil, errRevalidationStepDone{}
|
|
}
|
|
|
|
d = child
|
|
}
|
|
|
|
rp.Advance()
|
|
return d, nil
|
|
}
|
|
|
|
// Precondition: fs.renameMu must be locked.
|
|
func (d *dentry) invalidate(ctx context.Context, vfsObj *vfs.VirtualFilesystem, ds **[]*dentry) {
|
|
// Remove d from its parent.
|
|
removed := func() bool {
|
|
parent := d.parent.Load()
|
|
parent.opMu.RLock()
|
|
defer parent.opMu.RUnlock()
|
|
parent.childrenMu.Lock()
|
|
defer parent.childrenMu.Unlock()
|
|
|
|
// Since the opMu was just reacquired above, 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 := parent.children[d.name]; child == d {
|
|
// Invalidate dentry so it gets reloaded next time it's accessed.
|
|
delete(parent.children, d.name)
|
|
if d.inode.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.deleteSynthetic(parent, ds)
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}()
|
|
|
|
// Invalidate d and its descendants.
|
|
toInvalidate := []*dentry{d}
|
|
for len(toInvalidate) != 0 {
|
|
d := toInvalidate[len(toInvalidate)-1]
|
|
toInvalidate = toInvalidate[:len(toInvalidate)-1]
|
|
|
|
// If the dentry is a mountpoint, InvalidateDentry may drop the
|
|
// last reference on it, resulting in lock recursion. To avoid
|
|
// this, take a dentry reference first, then drop it while
|
|
// deferring the call to dentry.checkCachingLocked().
|
|
d.IncRef()
|
|
rcs := vfsObj.InvalidateDentry(ctx, &d.vfsd)
|
|
for _, rc := range rcs {
|
|
rc.DecRef(ctx)
|
|
}
|
|
d.decRefNoCaching()
|
|
|
|
// If an extra reference is held on d as described by the comment for
|
|
// dentry.refs, and d hasn't been racily removed by
|
|
// filesystem.unlinkAt() or another revalidation, drop that reference
|
|
// now. (The same would apply to racy replacement by
|
|
// filesystem.RenameAt(), but we can't race with rename since renameMu
|
|
// has been locked since entering filesystem.revalidatePath().)
|
|
if removed && (d.inode.isSynthetic() || d.inode.endpoint != nil) {
|
|
d.decRefNoCaching()
|
|
}
|
|
|
|
// Re-evaluate its caching status (i.e. if it has 0 references, drop it).
|
|
// The dentry will be reloaded next time it's accessed.
|
|
*ds = appendDentry(*ds, d)
|
|
|
|
if d.isDir() {
|
|
toInvalidate = d.disownAllChildrenForInvalidation(ctx, vfsObj, toInvalidate, ds)
|
|
}
|
|
}
|
|
}
|
|
|
|
// +checklocks:parent.childrenMu
|
|
func (d *dentry) deleteSynthetic(parent *dentry, ds **[]*dentry) {
|
|
d.setDeleted()
|
|
*ds = appendDentry(*ds, d)
|
|
parent.syntheticChildren--
|
|
parent.clearDirentsLocked()
|
|
}
|
|
|
|
// disownAllChildrenForInvalidation removes all child dentries from d, appends
|
|
// them to children, and returns an updated slice. Consistent with
|
|
// dentry.invalidate(), removed synthetic dentries are marked deleted.
|
|
//
|
|
// Precondition: fs.renameMu must be locked.
|
|
func (d *dentry) disownAllChildrenForInvalidation(ctx context.Context, vfsObj *vfs.VirtualFilesystem, children []*dentry, ds **[]*dentry) []*dentry {
|
|
d.opMu.RLock()
|
|
defer d.opMu.RUnlock()
|
|
d.childrenMu.Lock()
|
|
defer d.childrenMu.Unlock()
|
|
for name, child := range d.children {
|
|
children = append(children, child)
|
|
delete(d.children, name)
|
|
if child.inode.isSynthetic() {
|
|
child.deleteSynthetic(d, ds)
|
|
}
|
|
}
|
|
return children
|
|
}
|
|
|
|
// revalidateStatePool caches revalidateState instances to save array
|
|
// allocations for dentries and names.
|
|
var revalidateStatePool = sync.Pool{
|
|
New: func() any {
|
|
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 revalidation of dentries.
|
|
start *dentry
|
|
|
|
// refreshStart indicates whether the attributes of the start dentry should
|
|
// be refreshed.
|
|
refreshStart bool
|
|
|
|
// names is just a slice of names which can be used while making LISAFS RPCs.
|
|
// This exists to avoid the cost of repeated string slice allocation to make
|
|
// RPCs.
|
|
names []string
|
|
|
|
// dentries is the list of dentries that need to be revalidated. The first
|
|
// dentry is a child of start and each successive dentry is a child of the
|
|
// previous.
|
|
dentries []*dentry
|
|
}
|
|
|
|
func makeRevalidateState(start *dentry, refreshStart bool) *revalidateState {
|
|
r := revalidateStatePool.Get().(*revalidateState)
|
|
r.start = start
|
|
r.refreshStart = refreshStart
|
|
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(nil /* start */, false /* refreshStart */)
|
|
revalidateStatePool.Put(r)
|
|
}
|
|
|
|
// Preconditions:
|
|
// - d != nil.
|
|
// - d is a descendant of all dentries in r.dentries.
|
|
func (r *revalidateState) add(d *dentry) {
|
|
r.dentries = append(r.dentries, d)
|
|
}
|
|
|
|
// reset releases all metadata locks and resets all fields to allow this
|
|
// instance to be reused.
|
|
// +checklocksignore
|
|
func (r *revalidateState) reset(start *dentry, refreshStart bool) {
|
|
r.start = start
|
|
r.refreshStart = refreshStart
|
|
r.names = r.names[:0]
|
|
r.dentries = r.dentries[:0]
|
|
}
|