github.com/metacubex/gvisor@v0.0.0-20240320004321-933faba989ec/pkg/sentry/vfs/propagation.go (about) 1 // Copyright 2022 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 vfs 16 17 import ( 18 "github.com/metacubex/gvisor/pkg/abi/linux" 19 "github.com/metacubex/gvisor/pkg/bits" 20 "github.com/metacubex/gvisor/pkg/context" 21 "github.com/metacubex/gvisor/pkg/errors/linuxerr" 22 "github.com/metacubex/gvisor/pkg/sentry/kernel/auth" 23 ) 24 25 const ( 26 // The following constants are possible bits for the cloneType argument to 27 // VirtualFilesystem.cloneMount() and related functions. 28 // Analogous to CL_MAKE_SHARED in Linux. 29 makeSharedClone = 1 << iota 30 // Analogous to CL_SLAVE in Linux. 31 makeFollowerClone 32 // Analogous to CL_PRIVATE in Linux. 33 makePrivateClone 34 // Analogous to CL_SHARED_TO_SLAVE in Linux. 35 sharedToFollowerClone 36 37 propagationFlags = linux.MS_SHARED | linux.MS_PRIVATE | linux.MS_SLAVE | linux.MS_UNBINDABLE 38 ) 39 40 // +checklocks:vfs.mountMu 41 func (vfs *VirtualFilesystem) commitChildren(ctx context.Context, mnt *Mount) { 42 for c := range mnt.children { 43 if c.neverConnected() { 44 vfs.commitMount(ctx, c) 45 } 46 } 47 } 48 49 // commitMount attaches mnt to the parent and mountpoint specified by its 50 // mountKey and recursively does the same for all of mnt's descendants. 51 // 52 // +checklocks:vfs.mountMu 53 func (vfs *VirtualFilesystem) commitMount(ctx context.Context, mnt *Mount) { 54 mp := mnt.getKey() 55 56 // If there is already a mount at this (parent, point), disconnect it from its 57 // parent and reconnect it to mnt once mnt has been connected. 58 child := vfs.mounts.Lookup(mp.mount, mp.dentry) 59 vfs.mounts.seq.BeginWrite() 60 if child != nil { 61 vfs.delayDecRef(vfs.disconnectLocked(child)) 62 } 63 mp.dentry.mu.Lock() 64 vfs.connectLocked(mnt, mp, mp.mount.ns) 65 mp.dentry.mu.Unlock() 66 vfs.delayDecRef(mnt) 67 68 if child != nil { 69 newmp := VirtualDentry{mnt, mnt.root} 70 newmp.IncRef() 71 newmp.dentry.mu.Lock() 72 vfs.connectLocked(child, newmp, newmp.mount.ns) 73 newmp.dentry.mu.Unlock() 74 vfs.delayDecRef(child) 75 } 76 vfs.mounts.seq.EndWrite() 77 vfs.commitChildren(ctx, mnt) 78 } 79 80 // +checklocks:vfs.mountMu 81 func (vfs *VirtualFilesystem) abortUncomittedChildren(ctx context.Context, mnt *Mount) { 82 for c := range mnt.children { 83 if c.neverConnected() { 84 vfs.abortUncommitedMount(ctx, c) 85 delete(mnt.children, c) 86 } 87 } 88 } 89 90 // abortUncommitedMount releases references on mnt and all its descendants. 91 // 92 // Prerequisite: mnt is not connected, i.e. mnt.ns == nil. 93 // +checklocks:vfs.mountMu 94 func (vfs *VirtualFilesystem) abortUncommitedMount(ctx context.Context, mnt *Mount) { 95 vfs.delayDecRef(mnt) 96 vfs.delayDecRef(mnt.getKey()) 97 mnt.setKey(VirtualDentry{}) 98 vfs.setPropagation(mnt, linux.MS_PRIVATE) 99 vfs.abortUncomittedChildren(ctx, mnt) 100 } 101 102 // SetMountPropagationAt changes the propagation type of the mount pointed to by 103 // pop. 104 func (vfs *VirtualFilesystem) SetMountPropagationAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, propFlag uint32) error { 105 recursive := propFlag&linux.MS_REC != 0 106 propFlag &= propagationFlags 107 // Check if flags is a power of 2. If not then more than one flag is set. 108 if !bits.IsPowerOfTwo32(propFlag) { 109 return linuxerr.EINVAL 110 } 111 vd, err := vfs.getMountpoint(ctx, creds, pop) 112 if err != nil { 113 return err 114 } 115 defer vd.DecRef(ctx) 116 vfs.SetMountPropagation(vd.mount, propFlag, recursive) 117 return nil 118 } 119 120 // SetMountPropagation changes the propagation type of the mount. 121 func (vfs *VirtualFilesystem) SetMountPropagation(mnt *Mount, propFlag uint32, recursive bool) error { 122 vfs.lockMounts() 123 defer vfs.unlockMounts(context.Background()) 124 if propFlag == linux.MS_SHARED { 125 if err := vfs.allocMountGroupIDs(mnt, recursive); err != nil { 126 return err 127 } 128 } 129 130 if !recursive { 131 vfs.setPropagation(mnt, propFlag) 132 return nil 133 } 134 for _, m := range mnt.submountsLocked() { 135 vfs.setPropagation(m, propFlag) 136 } 137 return nil 138 } 139 140 // setPropagation sets the propagation on mnt for a propagation type. This 141 // method is analogous to fs/pnode.c:change_mnt_propagation() in Linux. 142 // 143 // +checklocks:vfs.mountMu 144 func (vfs *VirtualFilesystem) setPropagation(mnt *Mount, propFlags uint32) { 145 if propFlags == linux.MS_SHARED { 146 mnt.isShared = true 147 return 148 } 149 // pflag is MS_PRIVATE, MS_SLAVE, or MS_UNBINDABLE. The algorithm is the same 150 // for MS_PRIVATE/MS_SLAVE/MS_UNBINDABLE, except that in the 151 // private/unbindable case we clear the leader and followerEntry after the 152 // procedure is finished. 153 var leader *Mount 154 if mnt.sharedEntry.Empty() { 155 // If mnt is shared and in a peer group with only itself, just make it 156 // private. 157 if mnt.isShared { 158 vfs.freeGroupID(mnt) 159 mnt.isShared = false 160 } 161 // If mnt is not a follower to any other mount, make all of its followers 162 // also private. 163 leader = mnt.leader 164 if leader == nil { 165 for !mnt.followerList.Empty() { 166 f := mnt.followerList.Front() 167 mnt.followerList.Remove(f) 168 f.leader = nil 169 } 170 } 171 } else { 172 // Pick a suitable new leader. Linux chooses the first peer that shares a 173 // root dentry, or any peer if none matches that criteria. 174 leader = mnt.sharedEntry.Next() 175 for m := mnt.sharedEntry.Next(); m != mnt; m = m.sharedEntry.Next() { 176 if m.root == mnt.root { 177 leader = m 178 break 179 } 180 } 181 // Clear out mnt's shared attributes. 182 mnt.sharedEntry.Remove() 183 mnt.groupID = 0 184 mnt.isShared = false 185 } 186 // Transfer all of mnt's followers to the new leader. 187 for f := mnt.followerList.Front(); f != nil; f = f.followerEntry.Next() { 188 f.leader = leader 189 } 190 // Remove mnt from its current follower list and add it to the new leader. 191 if mnt.leader != nil { 192 mnt.leader.followerList.Remove(mnt) 193 } 194 if leader != nil && propFlags == linux.MS_SLAVE { 195 leader.followerList.PushFront(mnt) 196 mnt.leader = leader 197 } else { 198 mnt.leader = nil 199 } 200 201 // Add mnts followers to leader's follower list. This also links all their 202 // followerEntry together. 203 if !mnt.followerList.Empty() && leader != nil { 204 leader.followerList.PushBackList(&mnt.followerList) 205 } 206 } 207 208 type propState struct { 209 origSrc *Mount 210 prevSrc *Mount 211 prevDst *Mount 212 dstLeader *Mount 213 propList map[*Mount]struct{} 214 visitedLeaders map[*Mount]struct{} 215 } 216 217 // doPropagation returns a list of propagated mounts with their mount points 218 // set. The mounts are clones of src and have an extra reference taken. If 219 // propagation fails at any point, the method returns all the mounts propagated 220 // up until that point so they can be properly released. This method is 221 // analogous to fs/pnode.c:propagate_mnt() in Linux. 222 // 223 // +checklocks:vfs.mountMu 224 func (vfs *VirtualFilesystem) doPropagation(ctx context.Context, src *Mount, dst VirtualDentry) (map[*Mount]struct{}, error) { 225 if !dst.mount.isShared { 226 return nil, nil 227 } 228 s := propState{ 229 origSrc: src, 230 prevSrc: src, 231 prevDst: dst.mount, 232 dstLeader: dst.mount.leader, 233 propList: map[*Mount]struct{}{}, 234 visitedLeaders: map[*Mount]struct{}{}, 235 } 236 for peer := dst.mount.sharedEntry.Next(); peer != dst.mount; peer = peer.sharedEntry.Next() { 237 if err := vfs.propagateMount(ctx, peer, dst.dentry, &s); err != nil { 238 return s.propList, err 239 } 240 } 241 for follower := nextFollowerPeerGroup(dst.mount, dst.mount); follower != nil; follower = nextFollowerPeerGroup(follower, dst.mount) { 242 peer := follower 243 for { 244 if err := vfs.propagateMount(ctx, peer, dst.dentry, &s); err != nil { 245 return s.propList, err 246 } 247 peer = peer.sharedEntry.Next() 248 if peer == follower { 249 break 250 } 251 } 252 } 253 return s.propList, nil 254 } 255 256 // peers returns if two mounts are in the same peer group. 257 // 258 // +checklocks:vfs.mountMu 259 func (vfs *VirtualFilesystem) peers(m1, m2 *Mount) bool { 260 return m1.groupID == m2.groupID && m1.groupID != 0 261 } 262 263 // propagateMount propagates state.srcMount to dstMount at dstPoint. 264 // This method is analogous to fs/pnode.c:propagate_one() in Linux. 265 // 266 // +checklocks:vfs.mountMu 267 func (vfs *VirtualFilesystem) propagateMount(ctx context.Context, dstMnt *Mount, dstPoint *Dentry, state *propState) error { 268 // Skip newly added mounts. 269 if dstMnt.neverConnected() || dstMnt.umounted { 270 return nil 271 } 272 mp := VirtualDentry{mount: dstMnt, dentry: dstPoint} 273 if !mp.mount.fs.Impl().IsDescendant(VirtualDentry{dstMnt, dstMnt.root}, mp) { 274 return nil 275 } 276 cloneType := 0 277 if vfs.peers(dstMnt, state.prevDst) { 278 cloneType = makeSharedClone 279 } else { 280 done := false 281 // Get the most recent leader that we've propagated from in the tree. 282 var leader, underLeader *Mount 283 for underLeader = dstMnt; ; underLeader = leader { 284 leader = underLeader.leader 285 if _, ok := state.visitedLeaders[leader]; ok { 286 break 287 } 288 if leader == state.dstLeader { 289 break 290 } 291 } 292 for { 293 parent := state.prevSrc.parent() 294 // Check that prevSrc is a follower, not a peer of the original. 295 if vfs.peers(state.prevSrc, state.origSrc) { 296 break 297 } 298 // Check if the mount prvSrc attached to (aka parent) has the same leader 299 // as the most recently visited leader in the mount tree. 300 done = parent.leader == leader 301 // If the leader under the most recently visited leader is not peers with 302 // the mount prevSrc attached to, then it's not part of this propagation 303 // tree and we need to traverse up the tree to get to the real src. 304 if done && vfs.peers(underLeader, parent) { 305 break 306 } 307 // Traverse back up the propagation tree to get the proper src. We only 308 // want to propagate from this mount's leader or peers of that leader. 309 state.prevSrc = state.prevSrc.leader 310 if done { 311 break 312 } 313 } 314 cloneType = makeFollowerClone 315 if dstMnt.isShared { 316 cloneType |= makeSharedClone 317 } 318 } 319 clone, err := vfs.cloneMountTree(ctx, state.prevSrc, state.prevSrc.root, cloneType, nil) 320 if err != nil { 321 return err 322 } 323 mp.IncRef() 324 clone.setKey(mp) 325 state.propList[clone] = struct{}{} 326 if dstMnt.leader != state.dstLeader { 327 state.visitedLeaders[dstMnt.leader] = struct{}{} 328 } 329 state.prevDst = dstMnt 330 state.prevSrc = clone 331 return dstMnt.ns.checkMountCount(ctx, clone) 332 } 333 334 // nextFollowerPeerGroup iterates through the propagation tree and returns the 335 // first mount in each follower peer group under mnt. Once all the groups 336 // have been iterated through the method returns nil. This method is analogous 337 // to fs/pnode.c:next_group() in Linux. 338 func nextFollowerPeerGroup(mnt *Mount, start *Mount) *Mount { 339 for { 340 // If mnt has any followers, this loop returns that follower. Otherwise mnt 341 // is updated until it is the last peer in its peer group. This has the 342 // effect of moving down the propagation tree until the bottommost follower. 343 // After that the loop moves across peers (if possible) to the last peer 344 // in the group. 345 for { 346 if !mnt.neverConnected() && !mnt.followerList.Empty() { 347 return mnt.followerList.Front() 348 } 349 next := mnt.sharedEntry.Next() 350 if mnt.groupID == start.groupID { 351 if next == start { 352 return nil 353 } 354 // If mnt is shared+slave, its next follower will be the same as its 355 // next peer. 356 } else if mnt.isFollower() && mnt.followerEntry.Next() != next { 357 break 358 } 359 mnt = next 360 } 361 // At this point mnt is the last peer in its shared+slave peer group. 362 // This loop returns the next follower in mnt's leader's follower list. Once 363 // the list of followers is exhausted it sets mnt to be the leader and 364 // breaks out of the loop. This has the effect of moving across the tree 365 // branches until all branches are exhausted. Then it moves up the tree to 366 // the parent. 367 for { 368 leader := mnt.leader 369 if mnt.followerEntry.Next() != nil { 370 return mnt.followerEntry.Next() 371 } 372 mnt = leader.sharedEntry.Next() 373 if leader.groupID == start.groupID { 374 break 375 } 376 if leader.followerEntry.Next() == mnt { 377 break 378 } 379 mnt = leader 380 } 381 if mnt == start { 382 return nil 383 } 384 } 385 } 386 387 // nextPropMount iterates through the propagation tree rooted at start. It 388 // returns nil when there are no more mounts in the tree. Otherwise, it returns 389 // the next mount in the tree. It is analogous to fs/pnode.c:propagation_next() 390 // in Linux. 391 func nextPropMount(mnt, start *Mount) *Mount { 392 m := mnt 393 if !m.neverConnected() && !m.followerList.Empty() { 394 return m.followerList.Front() 395 } 396 for { 397 leader := m.leader 398 if leader == start.leader { 399 next := m.sharedEntry.Next() 400 if next == start { 401 return nil 402 } 403 return next 404 } else if m.followerEntry.Next() != nil { 405 return m.followerEntry.Next() 406 } 407 m = leader 408 } 409 } 410 411 // arePropMountsBusy checks if all the mounts that mnt's parents propagate to 412 // have the correct number of references before a call to umount. It is 413 // analogous to fs/pnode.c:propagate_mount_busy() in Linux. 414 // 415 // +checklocks:vfs.mountMu 416 func (vfs *VirtualFilesystem) arePropMountsBusy(mnt *Mount) bool { 417 parent := mnt.parent() 418 if parent == nil { 419 return !vfs.mountHasExpectedRefs(mnt) 420 } 421 if len(mnt.children) != 0 || !vfs.mountHasExpectedRefs(mnt) { 422 return true 423 } 424 for m := nextPropMount(parent, parent); m != nil; m = nextPropMount(m, parent) { 425 child := vfs.mounts.Lookup(m, mnt.point()) 426 if child == nil { 427 continue 428 } 429 if len(child.children) != 0 && child.coveringMount() == nil { 430 continue 431 } 432 if !vfs.mountHasExpectedRefs(child) { 433 return true 434 } 435 } 436 return false 437 } 438 439 // allocateGroupID populates mnt.groupID with a new group id if one is 440 // available, and returns an error otherwise. If the group ID bitmap is full, 441 // double the size of the bitmap before allocating the new group id. It is 442 // analogous to fs/namespace.c:mnt_alloc_group_id() in Linux. 443 // 444 // +checklocks:vfs.mountMu 445 func (vfs *VirtualFilesystem) allocateGroupID(mnt *Mount) error { 446 groupID, err := vfs.groupIDBitmap.FirstZero(1) 447 if err != nil { 448 if err := vfs.groupIDBitmap.Grow(uint32(vfs.groupIDBitmap.Size())); err != nil { 449 return linuxerr.ENOSPC 450 } 451 groupID, err = vfs.groupIDBitmap.FirstZero(1) 452 if err != nil { 453 return err 454 } 455 } 456 vfs.groupIDBitmap.Add(groupID) 457 mnt.groupID = groupID 458 return nil 459 } 460 461 // freeGroupID marks a groupID as available for reuse. It is analogous to 462 // fs/namespace.c:mnt_release_group_id() in Linux. 463 // 464 // +checklocks:vfs.mountMu 465 func (vfs *VirtualFilesystem) freeGroupID(mnt *Mount) { 466 vfs.groupIDBitmap.Remove(mnt.groupID) 467 mnt.groupID = 0 468 } 469 470 // cleanupGroupIDs zeroes out all of the mounts' groupIDs and returns them 471 // to the pool of available ids. It is analogous to 472 // fs/namespace.c:cleanup_group_ids() in Linux. 473 // 474 // +checklocks:vfs.mountMu 475 func (vfs *VirtualFilesystem) cleanupGroupIDs(mnts []*Mount) { 476 for _, m := range mnts { 477 if m.groupID != 0 && !m.isShared { 478 vfs.freeGroupID(m) 479 } 480 } 481 } 482 483 // allocMountGroupIDs allocates a new group id for mnt. If recursive is true, it 484 // also allocates a new group id for all mounts children. It is analogous to 485 // fs/namespace.c:invent_group_ids() in Linux. 486 // 487 // +checklocks:vfs.mountMu 488 func (vfs *VirtualFilesystem) allocMountGroupIDs(mnt *Mount, recursive bool) error { 489 var mnts []*Mount 490 if recursive { 491 mnts = mnt.submountsLocked() 492 } else { 493 mnts = []*Mount{mnt} 494 } 495 for _, m := range mnts { 496 if m.groupID == 0 && !m.isShared { 497 if err := vfs.allocateGroupID(m); err != nil { 498 vfs.cleanupGroupIDs(mnts) 499 return err 500 } 501 } 502 } 503 return nil 504 } 505 506 // propagateUmount returns a list of mounts that the umount of mnts propagates 507 // to. 508 // 509 // Prerequisites: all the mounts in mnts have had vfs.umount() called on them. 510 // 511 // +checklocks:vfs.mountMu 512 func (vfs *VirtualFilesystem) propagateUmount(mnts []*Mount) []*Mount { 513 const ( 514 umountVisited = iota 515 umountRestore 516 ) 517 var toUmount []*Mount 518 noChildren := make(map[*Mount]struct{}) 519 // Processed contains all the mounts that the algorithm has processed so far. 520 // If the mount maps to umountRestore, it should be restored after processing 521 // all the mounts. This happens in cases where a mount was speculatively 522 // unmounted that had children or is a cover mount. 523 processed := make(map[*Mount]int) 524 525 // Iterate through the mounts from the leafs back to the root. 526 for i := len(mnts) - 1; i >= 0; i-- { 527 mnt := mnts[i] 528 529 // If a mount has already been visited we know all its peers and followers 530 // have been visited so there's no need to visit them again. 531 if _, ok := processed[mnt]; ok { 532 continue 533 } 534 processed[mnt] = umountVisited 535 536 parent := mnt.parent() 537 if parent == nil { 538 continue 539 } 540 for m := nextPropMount(parent, parent); m != nil; m = nextPropMount(m, parent) { 541 child := vfs.mounts.Lookup(m, mnt.point()) 542 if child == nil { 543 continue 544 } 545 if _, ok := processed[child]; ok { 546 // If the child has been visited we know its peer group and followers 547 // have all been visited so there's no need to visit them again. We can 548 // skip this propagation subtree by setting the iterator to be the last 549 // mount in the follower group. 550 if !child.followerList.Empty() { 551 m = child.followerList.Back() 552 } 553 continue 554 } else if child.umounted { 555 // If this child has already been marked for unmounting, just mark it 556 // as visited and move on. This means it was either part of the original 557 // mount list passed to this method or was umounted from another mount's 558 // propagation. In either case we can consider all its peers and 559 // followers as visited. 560 processed[child] = umountVisited 561 continue 562 } 563 564 // This loop starts at the child we are propagating the umount to and 565 // iterates through the child's parents. It continues as until it 566 // encounters a parent that's been visited. 567 loop: 568 for { 569 if _, ok := noChildren[child]; ok || child.umounted { 570 break 571 } 572 // If there are any children that have mountpoint != parent's root then 573 // the current mount cannot be unmounted. 574 for gchild := range child.children { 575 if gchild.point() == child.root { 576 continue 577 } 578 _, isProcessed := processed[gchild] 579 _, hasNoChildren := noChildren[gchild] 580 if isProcessed && hasNoChildren { 581 continue 582 } 583 processed[child] = umountRestore 584 break loop 585 } 586 if child.locked { 587 processed[child] = umountRestore 588 noChildren[child] = struct{}{} 589 } else { 590 vfs.umount(child) 591 toUmount = append(toUmount, child) 592 } 593 // If this parent was a mount that had to be restored because it had 594 // children, it might be safe to umount now that its child is gone. If 595 // it has been visited then it's already being umounted. 596 child = child.parent() 597 if _, ok := processed[child]; !ok { 598 break 599 } 600 } 601 } 602 } 603 604 // Add all the children of mounts marked for umount to the umount list. This 605 // excludes "cover" mounts (mounts whose mount point is equal to their 606 // parent's root) which will be reparented in the next step. 607 for i := 0; i < len(toUmount); i++ { 608 umount := toUmount[i] 609 for child := range umount.children { 610 if child.point() == umount.root { 611 processed[child] = umountRestore 612 } else { 613 vfs.umount(child) 614 toUmount = append(toUmount, child) 615 } 616 } 617 } 618 619 vfs.mounts.seq.BeginWrite() 620 for m, status := range processed { 621 if status == umountVisited { 622 continue 623 } 624 mp := m.getKey() 625 for mp.mount.umounted { 626 mp = mp.mount.getKey() 627 } 628 if mp != m.getKey() { 629 vfs.changeMountpoint(m, mp) 630 } 631 } 632 vfs.mounts.seq.EndWrite() 633 634 return toUmount 635 } 636 637 // unlockPropagationMounts sets locked to false for every mount that a umount 638 // of mnt propagates to. It is analogous to fs/pnode.c:propagate_mount_unlock() 639 // in Linux. 640 // 641 // +checklocks:vfs.mountMu 642 func (vfs *VirtualFilesystem) unlockPropagationMounts(mnt *Mount) { 643 parent := mnt.parent() 644 if parent == nil { 645 return 646 } 647 for m := nextPropMount(parent, parent); m != nil; m = nextPropMount(m, parent) { 648 child := vfs.mounts.Lookup(m, mnt.point()) 649 if child == nil { 650 continue 651 } 652 child.locked = false 653 } 654 } 655 656 // peerUnderRoot iterates through mnt's peers until it finds a mount that is in 657 // ns and is reachable from root. This method is analogous to 658 // fs/pnode.c:get_peer_under_root() in Linux. 659 // 660 // +checklocks:vfs.mountMu 661 func (vfs *VirtualFilesystem) peerUnderRoot(ctx context.Context, mnt *Mount, ns *MountNamespace, root VirtualDentry) *Mount { 662 m := mnt 663 for { 664 if m.ns == ns { 665 if vfs.isPathReachable(ctx, root, VirtualDentry{mnt, mnt.root}) { 666 return m 667 } 668 } 669 m = m.sharedEntry.Next() 670 if m == mnt { 671 break 672 } 673 } 674 return nil 675 } 676 677 // isPathReachable returns true if vd is reachable from vfsroot. It is analogous 678 // to fs/namespace.c:is_path_reachable() in Linux. 679 // 680 // +checklocks:vfs.mountMu 681 func (vfs *VirtualFilesystem) isPathReachable(ctx context.Context, vfsroot VirtualDentry, vd VirtualDentry) bool { 682 for vd.mount != vfsroot.mount && vd.mount.parent() != nil { 683 vd = vd.mount.getKey() 684 } 685 return vd.mount == vfsroot.mount && vd.mount.fs.Impl().IsDescendant(vfsroot, vd) 686 }