github.com/metacubex/gvisor@v0.0.0-20240320004321-933faba989ec/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/metacubex/gvisor/pkg/context" 19 "github.com/metacubex/gvisor/pkg/sentry/vfs" 20 "github.com/metacubex/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 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.Load() == 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.Load().vfsd); err != nil { 168 return nil, errRevalidationStepDone{} 169 } 170 rp.Advance() 171 return d.parent.Load(), 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 parent := d.parent.Load() 216 parent.opMu.RLock() 217 defer parent.opMu.RUnlock() 218 parent.childrenMu.Lock() 219 defer parent.childrenMu.Unlock() 220 221 if d.isSynthetic() { 222 // Normally we don't mark invalidated dentries as deleted since 223 // they may still exist (but at a different path), and also for 224 // consistency with Linux. However, synthetic files are guaranteed 225 // to become unreachable if their dentries are invalidated, so 226 // treat their invalidation as deletion. 227 d.setDeleted() 228 d.decRefNoCaching() 229 *ds = appendDentry(*ds, d) 230 231 parent.syntheticChildren-- 232 parent.clearDirentsLocked() 233 } 234 235 // Since the opMu was just reacquired above, re-check that the 236 // parent's child with this name is still the same. Do not touch it if 237 // it has been replaced with a different one. 238 if child := parent.children[d.name]; child == d { 239 // Invalidate dentry so it gets reloaded next time it's accessed. 240 delete(parent.children, d.name) 241 } 242 } 243 244 // revalidateStatePool caches revalidateState instances to save array 245 // allocations for dentries and names. 246 var revalidateStatePool = sync.Pool{ 247 New: func() any { 248 return &revalidateState{} 249 }, 250 } 251 252 // revalidateState keeps state related to a revalidation request. It keeps track 253 // of {name, dentry} list being revalidated, as well as metadata locks on the 254 // dentries. The list must be in ancestry order, in other words `n` must be 255 // `n-1` child. 256 type revalidateState struct { 257 // start is the dentry where to start the revalidation of dentries. 258 start *dentry 259 260 // refreshStart indicates whether the attributes of the start dentry should 261 // be refreshed. 262 refreshStart bool 263 264 // names is just a slice of names which can be used while making LISAFS RPCs. 265 // This exists to avoid the cost of repeated string slice allocation to make 266 // RPCs. 267 names []string 268 269 // dentries is the list of dentries that need to be revalidated. The first 270 // dentry is a child of start and each successive dentry is a child of the 271 // previous. 272 dentries []*dentry 273 } 274 275 func makeRevalidateState(start *dentry, refreshStart bool) *revalidateState { 276 r := revalidateStatePool.Get().(*revalidateState) 277 r.start = start 278 r.refreshStart = refreshStart 279 return r 280 } 281 282 // release must be called after the caller is done with this object. It releases 283 // all metadata locks and resources. 284 func (r *revalidateState) release() { 285 r.reset(nil /* start */, false /* refreshStart */) 286 revalidateStatePool.Put(r) 287 } 288 289 // Preconditions: 290 // - d != nil. 291 // - d is a descendant of all dentries in r.dentries. 292 func (r *revalidateState) add(d *dentry) { 293 r.dentries = append(r.dentries, d) 294 } 295 296 // reset releases all metadata locks and resets all fields to allow this 297 // instance to be reused. 298 // +checklocksignore 299 func (r *revalidateState) reset(start *dentry, refreshStart bool) { 300 r.start = start 301 r.refreshStart = refreshStart 302 r.names = r.names[:0] 303 r.dentries = r.dentries[:0] 304 }