github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/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/SagerNet/gvisor/pkg/context"
    19  	"github.com/SagerNet/gvisor/pkg/sentry/vfs"
    20  	"github.com/SagerNet/gvisor/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 *vfs.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, rp.Done, ds)
    57  	rp.Release(ctx)
    58  	return err
    59  }
    60  
    61  // revalidateParentDir does the same as revalidatePath, but stops at the parent.
    62  //
    63  // Preconditions:
    64  // * fs.renameMu must be locked.
    65  func (fs *filesystem) revalidateParentDir(ctx context.Context, rpOrig *vfs.ResolvingPath, start *dentry, ds **[]*dentry) error {
    66  	// Revalidation is done even if start is synthetic in case the path is
    67  	// something like: ../non_synthetic_file and parent is non synthetic.
    68  	if fs.opts.interop != InteropModeShared {
    69  		return nil
    70  	}
    71  
    72  	// Copy resolving path to walk the path for revalidation.
    73  	rp := rpOrig.Copy()
    74  	err := fs.revalidate(ctx, rp, start, rp.Final, ds)
    75  	rp.Release(ctx)
    76  	return err
    77  }
    78  
    79  // revalidateOne does the same as revalidatePath, but checks a single dentry.
    80  //
    81  // Preconditions:
    82  // * fs.renameMu must be locked.
    83  func (fs *filesystem) revalidateOne(ctx context.Context, vfsObj *vfs.VirtualFilesystem, parent *dentry, name string, ds **[]*dentry) error {
    84  	// Skip revalidation for interop mode different than InteropModeShared or
    85  	// if the parent is synthetic (child must be synthetic too, but it cannot be
    86  	// replaced without first replacing the parent).
    87  	if parent.cachedMetadataAuthoritative() {
    88  		return nil
    89  	}
    90  
    91  	parent.dirMu.Lock()
    92  	child, ok := parent.children[name]
    93  	parent.dirMu.Unlock()
    94  	if !ok {
    95  		return nil
    96  	}
    97  
    98  	state := makeRevalidateState(parent)
    99  	defer state.release()
   100  
   101  	state.add(name, child)
   102  	return fs.revalidateHelper(ctx, vfsObj, state, ds)
   103  }
   104  
   105  // revalidate revalidates path components in rp until done returns true, or
   106  // until a mount point or symlink is reached. It may send multiple MultiGetAttr
   107  // calls to the gofer to handle ".." in the path.
   108  //
   109  // Preconditions:
   110  // * fs.renameMu must be locked.
   111  // * InteropModeShared is in effect.
   112  func (fs *filesystem) revalidate(ctx context.Context, rp *vfs.ResolvingPath, start *dentry, done func() bool, ds **[]*dentry) error {
   113  	state := makeRevalidateState(start)
   114  	defer state.release()
   115  
   116  	// Skip synthetic dentries because the start dentry cannot be replaced in case
   117  	// it has been created in the remote file system.
   118  	if !start.isSynthetic() {
   119  		state.add("", start)
   120  	}
   121  
   122  done:
   123  	for cur := start; !done(); {
   124  		var err error
   125  		cur, err = fs.revalidateStep(ctx, rp, cur, state)
   126  		if err != nil {
   127  			switch err.(type) {
   128  			case errPartialRevalidation:
   129  				if err := fs.revalidateHelper(ctx, rp.VirtualFilesystem(), state, ds); err != nil {
   130  					return err
   131  				}
   132  
   133  				// Reset state to release any remaining locks and restart from where
   134  				// stepping stopped.
   135  				state.reset()
   136  				state.start = cur
   137  
   138  				// Skip synthetic dentries because the start dentry cannot be replaced in
   139  				// case it has been created in the remote file system.
   140  				if !cur.isSynthetic() {
   141  					state.add("", cur)
   142  				}
   143  
   144  			case errRevalidationStepDone:
   145  				break done
   146  
   147  			default:
   148  				return err
   149  			}
   150  		}
   151  	}
   152  	return fs.revalidateHelper(ctx, rp.VirtualFilesystem(), state, ds)
   153  }
   154  
   155  // revalidateStep walks one element of the path and updates revalidationState
   156  // with the dentry if needed. It may also stop the stepping or ask for a
   157  // partial revalidation. Partial revalidation requires the caller to revalidate
   158  // the current revalidationState, release all locks, and resume stepping.
   159  // In case a symlink is hit, revalidation stops and the caller is responsible
   160  // for calling revalidate again after the symlink is resolved. Revalidation may
   161  // also stop for other reasons, like hitting a child not in the cache.
   162  //
   163  // Returns:
   164  // * (dentry, nil): step worked, continue stepping.`
   165  // * (dentry, errPartialRevalidation): revalidation should be done with the
   166  //     state gathered so far. Then continue stepping with the remainder of the
   167  //     path, starting at `dentry`.
   168  // * (nil, errRevalidationStepDone): revalidation doesn't need to step any
   169  //     further. It hit a symlink, a mount point, or an uncached dentry.
   170  //
   171  // Preconditions:
   172  // * fs.renameMu must be locked.
   173  // * !rp.Done().
   174  // * InteropModeShared is in effect (assumes no negative dentries).
   175  func (fs *filesystem) revalidateStep(ctx context.Context, rp *vfs.ResolvingPath, d *dentry, state *revalidateState) (*dentry, error) {
   176  	switch name := rp.Component(); name {
   177  	case ".":
   178  		// Do nothing.
   179  
   180  	case "..":
   181  		// Partial revalidation is required when ".." is hit because metadata locks
   182  		// can only be acquired from parent to child to avoid deadlocks.
   183  		if isRoot, err := rp.CheckRoot(ctx, &d.vfsd); err != nil {
   184  			return nil, errRevalidationStepDone{}
   185  		} else if isRoot || d.parent == nil {
   186  			rp.Advance()
   187  			return d, errPartialRevalidation{}
   188  		}
   189  		// We must assume that d.parent is correct, because if d has been moved
   190  		// elsewhere in the remote filesystem so that its parent has changed,
   191  		// we have no way of determining its new parent's location in the
   192  		// filesystem.
   193  		//
   194  		// Call rp.CheckMount() before updating d.parent's metadata, since if
   195  		// we traverse to another mount then d.parent's metadata is irrelevant.
   196  		if err := rp.CheckMount(ctx, &d.parent.vfsd); err != nil {
   197  			return nil, errRevalidationStepDone{}
   198  		}
   199  		rp.Advance()
   200  		return d.parent, errPartialRevalidation{}
   201  
   202  	default:
   203  		d.dirMu.Lock()
   204  		child, ok := d.children[name]
   205  		d.dirMu.Unlock()
   206  		if !ok {
   207  			// child is not cached, no need to validate any further.
   208  			return nil, errRevalidationStepDone{}
   209  		}
   210  
   211  		state.add(name, child)
   212  
   213  		// Symlink must be resolved before continuing with revalidation.
   214  		if child.isSymlink() {
   215  			return nil, errRevalidationStepDone{}
   216  		}
   217  
   218  		d = child
   219  	}
   220  
   221  	rp.Advance()
   222  	return d, nil
   223  }
   224  
   225  // revalidateHelper calls the gofer to stat all dentries in `state`. It will
   226  // update or invalidate dentries in the cache based on the result.
   227  //
   228  // Preconditions:
   229  // * fs.renameMu must be locked.
   230  // * InteropModeShared is in effect.
   231  func (fs *filesystem) revalidateHelper(ctx context.Context, vfsObj *vfs.VirtualFilesystem, state *revalidateState, ds **[]*dentry) error {
   232  	if len(state.names) == 0 {
   233  		return nil
   234  	}
   235  	// Lock metadata on all dentries *before* getting attributes for them.
   236  	state.lockAllMetadata()
   237  	stats, err := state.start.file.multiGetAttr(ctx, state.names)
   238  	if err != nil {
   239  		return err
   240  	}
   241  
   242  	i := -1
   243  	for d := state.popFront(); d != nil; d = state.popFront() {
   244  		i++
   245  		found := i < len(stats)
   246  		if i == 0 && len(state.names[0]) == 0 {
   247  			if found && !d.isSynthetic() {
   248  				// First dentry is where the search is starting, just update attributes
   249  				// since it cannot be replaced.
   250  				d.updateFromP9AttrsLocked(stats[i].Valid, &stats[i].Attr) // +checklocksforce: acquired by lockAllMetadata.
   251  			}
   252  			d.metadataMu.Unlock() // +checklocksforce: see above.
   253  			continue
   254  		}
   255  
   256  		// Note that synthetic dentries will always fails the comparison check
   257  		// below.
   258  		if !found || d.qidPath != stats[i].QID.Path {
   259  			d.metadataMu.Unlock() // +checklocksforce: see above.
   260  			if !found && d.isSynthetic() {
   261  				// We have a synthetic file, and no remote file has arisen to replace
   262  				// it.
   263  				return nil
   264  			}
   265  			// The file at this path has changed or no longer exists. Mark the
   266  			// dentry invalidated, and re-evaluate its caching status (i.e. if it
   267  			// has 0 references, drop it). The dentry will be reloaded next time it's
   268  			// accessed.
   269  			vfsObj.InvalidateDentry(ctx, &d.vfsd)
   270  
   271  			name := state.names[i]
   272  			d.parent.dirMu.Lock()
   273  
   274  			if d.isSynthetic() {
   275  				// Normally we don't mark invalidated dentries as deleted since
   276  				// they may still exist (but at a different path), and also for
   277  				// consistency with Linux. However, synthetic files are guaranteed
   278  				// to become unreachable if their dentries are invalidated, so
   279  				// treat their invalidation as deletion.
   280  				d.setDeleted()
   281  				d.decRefNoCaching()
   282  				*ds = appendDentry(*ds, d)
   283  
   284  				d.parent.syntheticChildren--
   285  				d.parent.dirents = nil
   286  			}
   287  
   288  			// Since the dirMu was released and reacquired, re-check that the
   289  			// parent's child with this name is still the same. Do not touch it if
   290  			// it has been replaced with a different one.
   291  			if child := d.parent.children[name]; child == d {
   292  				// Invalidate dentry so it gets reloaded next time it's accessed.
   293  				delete(d.parent.children, name)
   294  			}
   295  			d.parent.dirMu.Unlock()
   296  
   297  			return nil
   298  		}
   299  
   300  		// The file at this path hasn't changed. Just update cached metadata.
   301  		d.updateFromP9AttrsLocked(stats[i].Valid, &stats[i].Attr) // +checklocksforce: see above.
   302  		d.metadataMu.Unlock()
   303  	}
   304  
   305  	return nil
   306  }
   307  
   308  // revalidateStatePool caches revalidateState instances to save array
   309  // allocations for dentries and names.
   310  var revalidateStatePool = sync.Pool{
   311  	New: func() interface{} {
   312  		return &revalidateState{}
   313  	},
   314  }
   315  
   316  // revalidateState keeps state related to a revalidation request. It keeps track
   317  // of {name, dentry} list being revalidated, as well as metadata locks on the
   318  // dentries. The list must be in ancestry order, in other words `n` must be
   319  // `n-1` child.
   320  type revalidateState struct {
   321  	// start is the dentry where to start the attributes search.
   322  	start *dentry
   323  
   324  	// List of names of entries to refresh attributes. Names length must be the
   325  	// same as detries length. They are kept in separate slices because names is
   326  	// used to call File.MultiGetAttr().
   327  	names []string
   328  
   329  	// dentries is the list of dentries that correspond to the names above.
   330  	// dentry.metadataMu is acquired as each dentry is added to this list.
   331  	dentries []*dentry
   332  
   333  	// locked indicates if metadata lock has been acquired on dentries.
   334  	locked bool
   335  }
   336  
   337  func makeRevalidateState(start *dentry) *revalidateState {
   338  	r := revalidateStatePool.Get().(*revalidateState)
   339  	r.start = start
   340  	return r
   341  }
   342  
   343  // release must be called after the caller is done with this object. It releases
   344  // all metadata locks and resources.
   345  func (r *revalidateState) release() {
   346  	r.reset()
   347  	revalidateStatePool.Put(r)
   348  }
   349  
   350  // Preconditions:
   351  // * d is a descendant of all dentries in r.dentries.
   352  func (r *revalidateState) add(name string, d *dentry) {
   353  	r.names = append(r.names, name)
   354  	r.dentries = append(r.dentries, d)
   355  }
   356  
   357  // +checklocksignore
   358  func (r *revalidateState) lockAllMetadata() {
   359  	for _, d := range r.dentries {
   360  		d.metadataMu.Lock()
   361  	}
   362  	r.locked = true
   363  }
   364  
   365  func (r *revalidateState) popFront() *dentry {
   366  	if len(r.dentries) == 0 {
   367  		return nil
   368  	}
   369  	d := r.dentries[0]
   370  	r.dentries = r.dentries[1:]
   371  	return d
   372  }
   373  
   374  // reset releases all metadata locks and resets all fields to allow this
   375  // instance to be reused.
   376  // +checklocksignore
   377  func (r *revalidateState) reset() {
   378  	if r.locked {
   379  		// Unlock any remaining dentries.
   380  		for _, d := range r.dentries {
   381  			d.metadataMu.Unlock()
   382  		}
   383  		r.locked = false
   384  	}
   385  	r.start = nil
   386  	r.names = r.names[:0]
   387  	r.dentries = r.dentries[:0]
   388  }