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 }