github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/sentry/fsimpl/gofer/revalidate.go (about)

     1  // Copyright 2021 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  	"github.com/nicocha30/gvisor-ligolo/pkg/context"
    19  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/vfs"
    20  	"github.com/nicocha30/gvisor-ligolo/pkg/sync"
    21  )
    22  
    23  type errPartialRevalidation struct{}
    24  
    25  // Error implements error.Error.
    26  func (errPartialRevalidation) Error() string {
    27  	return "partial revalidation"
    28  }
    29  
    30  type errRevalidationStepDone struct{}
    31  
    32  // Error implements error.Error.
    33  func (errRevalidationStepDone) Error() string {
    34  	return "stop revalidation"
    35  }
    36  
    37  // revalidatePath checks cached dentries for external modification. File
    38  // attributes are refreshed and cache is invalidated in case the dentry has been
    39  // deleted, or a new file/directory created in its place.
    40  //
    41  // Revalidation stops at symlinks and mount points. The caller is responsible
    42  // for revalidating again after symlinks are resolved and after changing to
    43  // different mounts.
    44  //
    45  // Preconditions:
    46  //   - fs.renameMu must be locked.
    47  func (fs *filesystem) revalidatePath(ctx context.Context, rpOrig resolvingPath, start *dentry, ds **[]*dentry) error {
    48  	// Revalidation is done even if start is synthetic in case the path is
    49  	// something like: ../non_synthetic_file.
    50  	if fs.opts.interop != InteropModeShared {
    51  		return nil
    52  	}
    53  
    54  	// Copy resolving path to walk the path for revalidation.
    55  	rp := rpOrig.copy()
    56  	err := fs.revalidate(ctx, rp, start, ds)
    57  	rp.Release(ctx)
    58  	return err
    59  }
    60  
    61  // revalidateOne does the same as revalidatePath, but checks a single dentry.
    62  //
    63  // Preconditions:
    64  //   - fs.renameMu must be locked.
    65  //   - parent must have up to date metadata.
    66  func (fs *filesystem) revalidateOne(ctx context.Context, vfsObj *vfs.VirtualFilesystem, parent *dentry, name string, ds **[]*dentry) error {
    67  	// Skip revalidation for interop mode different than InteropModeShared or
    68  	// if the parent is synthetic (child must be synthetic too, but it cannot be
    69  	// replaced without first replacing the parent).
    70  	if parent.cachedMetadataAuthoritative() {
    71  		return nil
    72  	}
    73  
    74  	parent.childrenMu.Lock()
    75  	child, ok := parent.children[name]
    76  	parent.childrenMu.Unlock()
    77  	if !ok {
    78  		return nil
    79  	}
    80  
    81  	state := makeRevalidateState(parent, false /* refreshStart */)
    82  	defer state.release()
    83  	// Note that child can not be nil, because we don't cache negative entries
    84  	// when InteropModeShared is in effect.
    85  	state.add(child)
    86  	return state.doRevalidation(ctx, vfsObj, ds)
    87  }
    88  
    89  // revalidate revalidates path components in rp until done returns true, or
    90  // until a mount point or symlink is reached. It may send multiple MultiGetAttr
    91  // calls to the gofer to handle ".." in the path.
    92  //
    93  // Preconditions:
    94  //   - fs.renameMu must be locked.
    95  //   - InteropModeShared is in effect.
    96  func (fs *filesystem) revalidate(ctx context.Context, rp resolvingPath, start *dentry, ds **[]*dentry) error {
    97  	state := makeRevalidateState(start, true /* refreshStart */)
    98  	defer state.release()
    99  
   100  done:
   101  	for cur := start; !rp.done(); {
   102  		var err error
   103  		cur, err = fs.revalidateStep(ctx, rp, cur, state)
   104  		if err != nil {
   105  			switch err.(type) {
   106  			case errPartialRevalidation:
   107  				if err := state.doRevalidation(ctx, rp.VirtualFilesystem(), ds); err != nil {
   108  					return err
   109  				}
   110  
   111  				// Reset state to release any remaining locks and restart from where
   112  				// stepping stopped.
   113  				state.reset(cur /* start */, true /* refreshStart */)
   114  
   115  			case errRevalidationStepDone:
   116  				break done
   117  
   118  			default:
   119  				return err
   120  			}
   121  		}
   122  	}
   123  	return state.doRevalidation(ctx, rp.VirtualFilesystem(), ds)
   124  }
   125  
   126  // revalidateStep walks one element of the path and updates revalidationState
   127  // with the dentry if needed. It may also stop the stepping or ask for a
   128  // partial revalidation. Partial revalidation requires the caller to revalidate
   129  // the current revalidationState, release all locks, and resume stepping.
   130  // In case a symlink is hit, revalidation stops and the caller is responsible
   131  // for calling revalidate again after the symlink is resolved. Revalidation may
   132  // also stop for other reasons, like hitting a child not in the cache.
   133  //
   134  // Returns:
   135  //   - (dentry, nil): step worked, continue stepping.`
   136  //   - (dentry, errPartialRevalidation): revalidation should be done with the
   137  //     state gathered so far. Then continue stepping with the remainder of the
   138  //     path, starting at `dentry`.
   139  //   - (nil, errRevalidationStepDone): revalidation doesn't need to step any
   140  //     further. It hit a symlink, a mount point, or an uncached dentry.
   141  //
   142  // Preconditions:
   143  //   - fs.renameMu must be locked.
   144  //   - !rp.Done().
   145  //   - InteropModeShared is in effect (assumes no negative dentries).
   146  func (fs *filesystem) revalidateStep(ctx context.Context, rp resolvingPath, d *dentry, state *revalidateState) (*dentry, error) {
   147  	switch name := rp.Component(); name {
   148  	case ".":
   149  		// Do nothing.
   150  
   151  	case "..":
   152  		// Partial revalidation is required when ".." is hit because metadata locks
   153  		// can only be acquired from parent to child to avoid deadlocks.
   154  		if isRoot, err := rp.CheckRoot(ctx, &d.vfsd); err != nil {
   155  			return nil, errRevalidationStepDone{}
   156  		} else if isRoot || d.parent == nil {
   157  			rp.Advance()
   158  			return d, errPartialRevalidation{}
   159  		}
   160  		// We must assume that d.parent is correct, because if d has been moved
   161  		// elsewhere in the remote filesystem so that its parent has changed,
   162  		// we have no way of determining its new parent's location in the
   163  		// filesystem.
   164  		//
   165  		// Call rp.CheckMount() before updating d.parent's metadata, since if
   166  		// we traverse to another mount then d.parent's metadata is irrelevant.
   167  		if err := rp.CheckMount(ctx, &d.parent.vfsd); err != nil {
   168  			return nil, errRevalidationStepDone{}
   169  		}
   170  		rp.Advance()
   171  		return d.parent, errPartialRevalidation{}
   172  
   173  	default:
   174  		d.childrenMu.Lock()
   175  		child, ok := d.children[name]
   176  		d.childrenMu.Unlock()
   177  		if !ok {
   178  			// child is not cached, no need to validate any further.
   179  			return nil, errRevalidationStepDone{}
   180  		}
   181  
   182  		// Note that child can not be nil, because we don't cache negative entries
   183  		// when InteropModeShared is in effect.
   184  		state.add(child)
   185  
   186  		// Symlink must be resolved before continuing with revalidation.
   187  		if child.isSymlink() {
   188  			return nil, errRevalidationStepDone{}
   189  		}
   190  
   191  		d = child
   192  	}
   193  
   194  	rp.Advance()
   195  	return d, nil
   196  }
   197  
   198  // Precondition: fs.renameMu must be locked.
   199  func (d *dentry) invalidate(ctx context.Context, vfsObj *vfs.VirtualFilesystem, ds **[]*dentry) {
   200  	// If the dentry is a mountpoint, InvalidateDentry may drop the
   201  	// last reference on it, resulting in lock recursion. To avoid
   202  	// this, take a dentry reference first, then drop it while
   203  	// deferring the call to dentry.checkCachingLocked().
   204  	d.IncRef()
   205  	rcs := vfsObj.InvalidateDentry(ctx, &d.vfsd)
   206  	for _, rc := range rcs {
   207  		rc.DecRef(ctx)
   208  	}
   209  	d.decRefNoCaching()
   210  
   211  	// Re-evaluate its caching status (i.e. if it has 0 references, drop it).
   212  	// The dentry will be reloaded next time it's accessed.
   213  	*ds = appendDentry(*ds, d)
   214  
   215  	d.parent.opMu.RLock()
   216  	defer d.parent.opMu.RUnlock()
   217  	d.parent.childrenMu.Lock()
   218  	defer d.parent.childrenMu.Unlock()
   219  
   220  	if d.isSynthetic() {
   221  		// Normally we don't mark invalidated dentries as deleted since
   222  		// they may still exist (but at a different path), and also for
   223  		// consistency with Linux. However, synthetic files are guaranteed
   224  		// to become unreachable if their dentries are invalidated, so
   225  		// treat their invalidation as deletion.
   226  		d.setDeleted()
   227  		d.decRefNoCaching()
   228  		*ds = appendDentry(*ds, d)
   229  
   230  		d.parent.syntheticChildren--
   231  		d.parent.clearDirentsLocked()
   232  	}
   233  
   234  	// Since the opMu was just reacquired above, re-check that the
   235  	// parent's child with this name is still the same. Do not touch it if
   236  	// it has been replaced with a different one.
   237  	if child := d.parent.children[d.name]; child == d {
   238  		// Invalidate dentry so it gets reloaded next time it's accessed.
   239  		delete(d.parent.children, d.name)
   240  	}
   241  }
   242  
   243  // revalidateStatePool caches revalidateState instances to save array
   244  // allocations for dentries and names.
   245  var revalidateStatePool = sync.Pool{
   246  	New: func() any {
   247  		return &revalidateState{}
   248  	},
   249  }
   250  
   251  // revalidateState keeps state related to a revalidation request. It keeps track
   252  // of {name, dentry} list being revalidated, as well as metadata locks on the
   253  // dentries. The list must be in ancestry order, in other words `n` must be
   254  // `n-1` child.
   255  type revalidateState struct {
   256  	// start is the dentry where to start the revalidation of dentries.
   257  	start *dentry
   258  
   259  	// refreshStart indicates whether the attributes of the start dentry should
   260  	// be refreshed.
   261  	refreshStart bool
   262  
   263  	// names is just a slice of names which can be used while making LISAFS RPCs.
   264  	// This exists to avoid the cost of repeated string slice allocation to make
   265  	// RPCs.
   266  	names []string
   267  
   268  	// dentries is the list of dentries that need to be revalidated. The first
   269  	// dentry is a child of start and each successive dentry is a child of the
   270  	// previous.
   271  	dentries []*dentry
   272  }
   273  
   274  func makeRevalidateState(start *dentry, refreshStart bool) *revalidateState {
   275  	r := revalidateStatePool.Get().(*revalidateState)
   276  	r.start = start
   277  	r.refreshStart = refreshStart
   278  	return r
   279  }
   280  
   281  // release must be called after the caller is done with this object. It releases
   282  // all metadata locks and resources.
   283  func (r *revalidateState) release() {
   284  	r.reset(nil /* start */, false /* refreshStart */)
   285  	revalidateStatePool.Put(r)
   286  }
   287  
   288  // Preconditions:
   289  //   - d != nil.
   290  //   - d is a descendant of all dentries in r.dentries.
   291  func (r *revalidateState) add(d *dentry) {
   292  	r.dentries = append(r.dentries, d)
   293  }
   294  
   295  // reset releases all metadata locks and resets all fields to allow this
   296  // instance to be reused.
   297  // +checklocksignore
   298  func (r *revalidateState) reset(start *dentry, refreshStart bool) {
   299  	r.start = start
   300  	r.refreshStart = refreshStart
   301  	r.names = r.names[:0]
   302  	r.dentries = r.dentries[:0]
   303  }