github.com/ttpreport/gvisor-ligolo@v0.0.0-20240123134145-a858404967ba/pkg/sentry/fsimpl/gofer/lisafs_dentry.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 gofer 16 17 import ( 18 "fmt" 19 "strings" 20 21 "github.com/ttpreport/gvisor-ligolo/pkg/abi/linux" 22 "github.com/ttpreport/gvisor-ligolo/pkg/atomicbitops" 23 "github.com/ttpreport/gvisor-ligolo/pkg/context" 24 "github.com/ttpreport/gvisor-ligolo/pkg/errors/linuxerr" 25 "github.com/ttpreport/gvisor-ligolo/pkg/hostarch" 26 "github.com/ttpreport/gvisor-ligolo/pkg/lisafs" 27 "github.com/ttpreport/gvisor-ligolo/pkg/log" 28 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/kernel/auth" 29 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/socket/unix/transport" 30 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/vfs" 31 ) 32 33 func (fs *filesystem) handleAnameLisafs(ctx context.Context, rootInode lisafs.Inode) (lisafs.Inode, error) { 34 if fs.opts.aname == "/" { 35 return rootInode, nil 36 } 37 38 // Walk to the attach point from root inode. aname is always absolute. 39 rootFD := fs.client.NewFD(rootInode.ControlFD) 40 status, inodes, err := rootFD.WalkMultiple(ctx, strings.Split(fs.opts.aname, "/")[1:]) 41 if err != nil { 42 return lisafs.Inode{}, err 43 } 44 45 // Close all intermediate FDs to the attach point. 46 rootFD.Close(ctx, false /* flush */) 47 numInodes := len(inodes) 48 for i := 0; i < numInodes-1; i++ { 49 curFD := fs.client.NewFD(inodes[i].ControlFD) 50 curFD.Close(ctx, false /* flush */) 51 } 52 53 switch status { 54 case lisafs.WalkSuccess: 55 return inodes[numInodes-1], nil 56 default: 57 if numInodes > 0 { 58 last := fs.client.NewFD(inodes[numInodes-1].ControlFD) 59 last.Close(ctx, false /* flush */) 60 } 61 log.Warningf("initClient failed because walk to attach point %q failed: lisafs.WalkStatus = %v", fs.opts.aname, status) 62 return lisafs.Inode{}, linuxerr.ENOENT 63 } 64 } 65 66 // lisafsDentry is a gofer dentry implementation. It represents a dentry backed 67 // by a lisafs connection. 68 // 69 // +stateify savable 70 type lisafsDentry struct { 71 dentry 72 73 // controlFD is used by lisafs to perform path based operations on this 74 // dentry. controlFD is immutable. 75 // 76 // if !controlFD.Ok(), this dentry represents a synthetic file, i.e. a 77 // file that does not exist on the remote filesystem. As of this writing, the 78 // only files that can be synthetic are sockets, pipes, and directories. 79 controlFD lisafs.ClientFD `state:"nosave"` 80 81 // If this dentry represents a regular file or directory, readFDLisa is a 82 // LISAFS FD used for reads by all regularFileFDs/directoryFDs representing 83 // this dentry. readFDLisa is protected by dentry.handleMu. 84 readFDLisa lisafs.ClientFD `state:"nosave"` 85 86 // If this dentry represents a regular file, writeFDLisa is the LISAFS FD 87 // used for writes by all regularFileFDs representing this dentry. 88 // readFDLisa and writeFDLisa may or may not represent the same LISAFS FD. 89 // Once either transitions from closed (Ok() == false) to open 90 // (Ok() == true), it may be mutated with dentry.handleMu locked, but cannot 91 // be closed until the dentry is destroyed. writeFDLisa is protected by 92 // dentry.handleMu. 93 writeFDLisa lisafs.ClientFD `state:"nosave"` 94 } 95 96 // newLisafsDentry creates a new dentry representing the given file. The dentry 97 // initially has no references, but is not cached; it is the caller's 98 // responsibility to set the dentry's reference count and/or call 99 // dentry.checkCachingLocked() as appropriate. 100 // newLisafsDentry takes ownership of ino. 101 func (fs *filesystem) newLisafsDentry(ctx context.Context, ino *lisafs.Inode) (*dentry, error) { 102 if ino.Stat.Mask&linux.STATX_TYPE == 0 { 103 ctx.Warningf("can't create gofer.dentry without file type") 104 fs.client.CloseFD(ctx, ino.ControlFD, false /* flush */) 105 return nil, linuxerr.EIO 106 } 107 if ino.Stat.Mode&linux.FileTypeMask == linux.ModeRegular && ino.Stat.Mask&linux.STATX_SIZE == 0 { 108 ctx.Warningf("can't create regular file gofer.dentry without file size") 109 fs.client.CloseFD(ctx, ino.ControlFD, false /* flush */) 110 return nil, linuxerr.EIO 111 } 112 113 inoKey := inoKeyFromStatx(&ino.Stat) 114 d := &lisafsDentry{ 115 dentry: dentry{ 116 fs: fs, 117 inoKey: inoKey, 118 ino: fs.inoFromKey(inoKey), 119 mode: atomicbitops.FromUint32(uint32(ino.Stat.Mode)), 120 uid: atomicbitops.FromUint32(uint32(fs.opts.dfltuid)), 121 gid: atomicbitops.FromUint32(uint32(fs.opts.dfltgid)), 122 blockSize: atomicbitops.FromUint32(hostarch.PageSize), 123 readFD: atomicbitops.FromInt32(-1), 124 writeFD: atomicbitops.FromInt32(-1), 125 mmapFD: atomicbitops.FromInt32(-1), 126 }, 127 controlFD: fs.client.NewFD(ino.ControlFD), 128 } 129 if ino.Stat.Mask&linux.STATX_UID != 0 { 130 d.uid = atomicbitops.FromUint32(dentryUID(lisafs.UID(ino.Stat.UID))) 131 } 132 if ino.Stat.Mask&linux.STATX_GID != 0 { 133 d.gid = atomicbitops.FromUint32(dentryGID(lisafs.GID(ino.Stat.GID))) 134 } 135 if ino.Stat.Mask&linux.STATX_SIZE != 0 { 136 d.size = atomicbitops.FromUint64(ino.Stat.Size) 137 } 138 if ino.Stat.Blksize != 0 { 139 d.blockSize = atomicbitops.FromUint32(ino.Stat.Blksize) 140 } 141 if ino.Stat.Mask&linux.STATX_ATIME != 0 { 142 d.atime = atomicbitops.FromInt64(dentryTimestamp(ino.Stat.Atime)) 143 } else { 144 d.atime = atomicbitops.FromInt64(fs.clock.Now().Nanoseconds()) 145 } 146 if ino.Stat.Mask&linux.STATX_MTIME != 0 { 147 d.mtime = atomicbitops.FromInt64(dentryTimestamp(ino.Stat.Mtime)) 148 } else { 149 d.mtime = atomicbitops.FromInt64(fs.clock.Now().Nanoseconds()) 150 } 151 if ino.Stat.Mask&linux.STATX_CTIME != 0 { 152 d.ctime = atomicbitops.FromInt64(dentryTimestamp(ino.Stat.Ctime)) 153 } else { 154 // Approximate ctime with mtime if ctime isn't available. 155 d.ctime = atomicbitops.FromInt64(d.mtime.Load()) 156 } 157 if ino.Stat.Mask&linux.STATX_BTIME != 0 { 158 d.btime = atomicbitops.FromInt64(dentryTimestamp(ino.Stat.Btime)) 159 } 160 if ino.Stat.Mask&linux.STATX_NLINK != 0 { 161 d.nlink = atomicbitops.FromUint32(ino.Stat.Nlink) 162 } else { 163 if ino.Stat.Mode&linux.FileTypeMask == linux.ModeDirectory { 164 d.nlink = atomicbitops.FromUint32(2) 165 } else { 166 d.nlink = atomicbitops.FromUint32(1) 167 } 168 } 169 d.dentry.init(d) 170 fs.syncMu.Lock() 171 fs.syncableDentries.PushBack(&d.syncableListEntry) 172 fs.syncMu.Unlock() 173 return &d.dentry, nil 174 } 175 176 func (d *lisafsDentry) openHandle(ctx context.Context, flags uint32) (handle, error) { 177 openFD, hostFD, err := d.controlFD.OpenAt(ctx, flags) 178 if err != nil { 179 return noHandle, err 180 } 181 return handle{ 182 fdLisa: d.controlFD.Client().NewFD(openFD), 183 fd: int32(hostFD), 184 }, nil 185 } 186 187 func (d *lisafsDentry) updateHandles(ctx context.Context, h handle, readable, writable bool) { 188 // Switch to new LISAFS FDs. Note that the read, write and mmap host FDs are 189 // updated separately. 190 oldReadFD := lisafs.InvalidFDID 191 if readable { 192 oldReadFD = d.readFDLisa.ID() 193 d.readFDLisa = h.fdLisa 194 } 195 oldWriteFD := lisafs.InvalidFDID 196 if writable { 197 oldWriteFD = d.writeFDLisa.ID() 198 d.writeFDLisa = h.fdLisa 199 } 200 // NOTE(b/141991141): Close old FDs before making new fids visible (by 201 // unlocking d.handleMu). 202 if oldReadFD.Ok() { 203 d.fs.client.CloseFD(ctx, oldReadFD, false /* flush */) 204 } 205 if oldWriteFD.Ok() && oldReadFD != oldWriteFD { 206 d.fs.client.CloseFD(ctx, oldWriteFD, false /* flush */) 207 } 208 } 209 210 // Precondition: d.metadataMu must be locked. 211 // 212 // +checklocks:d.metadataMu 213 func (d *lisafsDentry) updateMetadataLocked(ctx context.Context, h handle) error { 214 handleMuRLocked := false 215 if !h.fdLisa.Ok() { 216 // Use open FDs in preferenece to the control FD. This may be significantly 217 // more efficient in some implementations. Prefer a writable FD over a 218 // readable one since some filesystem implementations may update a writable 219 // FD's metadata after writes, without making metadata updates immediately 220 // visible to read-only FDs representing the same file. 221 d.handleMu.RLock() 222 switch { 223 case d.writeFDLisa.Ok(): 224 h.fdLisa = d.writeFDLisa 225 handleMuRLocked = true 226 case d.readFDLisa.Ok(): 227 h.fdLisa = d.readFDLisa 228 handleMuRLocked = true 229 default: 230 h.fdLisa = d.controlFD 231 d.handleMu.RUnlock() 232 } 233 } 234 235 var stat linux.Statx 236 err := h.fdLisa.StatTo(ctx, &stat) 237 if handleMuRLocked { 238 // handleMu must be released before updateMetadataFromStatLocked(). 239 d.handleMu.RUnlock() // +checklocksforce: complex case. 240 } 241 if err != nil { 242 return err 243 } 244 d.updateMetadataFromStatxLocked(&stat) 245 return nil 246 } 247 248 func chmod(ctx context.Context, controlFD lisafs.ClientFD, mode uint16) error { 249 setStat := linux.Statx{ 250 Mask: linux.STATX_MODE, 251 Mode: mode, 252 } 253 _, failureErr, err := controlFD.SetStat(ctx, &setStat) 254 if err != nil { 255 return err 256 } 257 return failureErr 258 } 259 260 func (d *lisafsDentry) destroy(ctx context.Context) { 261 if d.readFDLisa.Ok() && d.readFDLisa.ID() != d.writeFDLisa.ID() { 262 d.readFDLisa.Close(ctx, false /* flush */) 263 } 264 if d.writeFDLisa.Ok() { 265 d.writeFDLisa.Close(ctx, false /* flush */) 266 } 267 if d.controlFD.Ok() { 268 // Close the control FD. Propagate the Close RPCs immediately to the server 269 // if the dentry being destroyed is a deleted regular file. This is to 270 // release the disk space on remote immediately. This will flush the above 271 // read/write lisa FDs as well. 272 flushClose := d.isDeleted() && d.isRegularFile() 273 d.controlFD.Close(ctx, flushClose) 274 } 275 } 276 277 func (d *lisafsDentry) getRemoteChild(ctx context.Context, name string) (*dentry, error) { 278 childInode, err := d.controlFD.Walk(ctx, name) 279 if err != nil { 280 return nil, err 281 } 282 return d.fs.newLisafsDentry(ctx, &childInode) 283 } 284 285 // Preconditions: 286 // - fs.renameMu must be locked. 287 // - d.opMu must be locked. 288 // - d.isDir(). 289 // - !rp.done() && rp.Component() is not "." or "..". 290 // 291 // Postcondition: The returned dentry is already cached appropriately. 292 func (d *lisafsDentry) getRemoteChildAndWalkPathLocked(ctx context.Context, rp resolvingPath, ds **[]*dentry) (*dentry, error) { 293 // Collect as many path components as possible to walk. 294 var namesArr [16]string // arbitrarily sized array to help avoid slice allocation. 295 names := namesArr[:0] 296 rp.getComponents(func(name string) bool { 297 if name == "." { 298 return true 299 } 300 if name == ".." { 301 return false 302 } 303 names = append(names, name) 304 return true 305 }) 306 // Walk as much of the path as possible in 1 RPC. 307 _, inodes, err := d.controlFD.WalkMultiple(ctx, names) 308 if err != nil { 309 return nil, err 310 } 311 if len(inodes) == 0 { 312 // d.opMu is locked. So a new child could not have appeared concurrently. 313 // It should be safe to mark this as a negative entry. 314 d.childrenMu.Lock() 315 defer d.childrenMu.Unlock() 316 d.cacheNegativeLookupLocked(names[0]) 317 return nil, linuxerr.ENOENT 318 } 319 320 // Add the walked inodes into the dentry tree. 321 startParent := &d.dentry 322 curParent := startParent 323 curParentLock := func() { 324 if curParent != startParent { 325 curParent.opMu.RLock() 326 } 327 curParent.childrenMu.Lock() 328 } 329 curParentUnlock := func() { 330 curParent.childrenMu.Unlock() 331 if curParent != startParent { 332 curParent.opMu.RUnlock() // +checklocksforce: locked via curParentLock(). 333 } 334 } 335 var ret *dentry 336 var dentryCreationErr error 337 for i := range inodes { 338 if dentryCreationErr != nil { 339 d.fs.client.CloseFD(ctx, inodes[i].ControlFD, false /* flush */) 340 continue 341 } 342 343 curParentLock() 344 // Did we race with another walk + cache operation? 345 child, ok := curParent.children[names[i]] // +checklocksforce: locked via curParentLock() 346 if ok && child != nil { 347 // We raced. Clean up the new inode and proceed with 348 // the cached child. 349 d.fs.client.CloseFD(ctx, inodes[i].ControlFD, false /* flush */) 350 } else { 351 // Create and cache the new dentry. 352 var err error 353 child, err = d.fs.newLisafsDentry(ctx, &inodes[i]) 354 if err != nil { 355 dentryCreationErr = err 356 curParentUnlock() 357 continue 358 } 359 curParent.cacheNewChildLocked(child, names[i]) // +checklocksforce: locked via curParentLock(). 360 } 361 curParentUnlock() 362 363 // For now, child has 0 references, so our caller should call 364 // child.checkCachingLocked(). curParent gained a ref so we should also 365 // call curParent.checkCachingLocked() so it can be removed from the cache 366 // if needed. We only do that for the first iteration because all 367 // subsequent parents would have already been added to ds. 368 if i == 0 { 369 *ds = appendDentry(*ds, curParent) 370 } 371 *ds = appendDentry(*ds, child) 372 curParent = child 373 if i == 0 { 374 ret = child 375 } 376 } 377 return ret, dentryCreationErr 378 } 379 380 func (d *lisafsDentry) newChildDentry(ctx context.Context, childIno *lisafs.Inode, childName string) (*dentry, error) { 381 child, err := d.fs.newLisafsDentry(ctx, childIno) 382 if err != nil { 383 if err := d.controlFD.UnlinkAt(ctx, childName, 0 /* flags */); err != nil { 384 log.Warningf("failed to clean up created child %s after newLisafsDentry() failed: %v", childName, err) 385 } 386 } 387 return child, err 388 } 389 390 func (d *lisafsDentry) mknod(ctx context.Context, name string, creds *auth.Credentials, opts *vfs.MknodOptions) (*dentry, error) { 391 if _, ok := opts.Endpoint.(transport.HostBoundEndpoint); !ok { 392 childInode, err := d.controlFD.MknodAt(ctx, name, opts.Mode, lisafs.UID(creds.EffectiveKUID), lisafs.GID(creds.EffectiveKGID), opts.DevMinor, opts.DevMajor) 393 if err != nil { 394 return nil, err 395 } 396 return d.newChildDentry(ctx, &childInode, name) 397 } 398 399 // This mknod(2) is coming from unix bind(2), as opts.Endpoint is set. 400 sockType := opts.Endpoint.(transport.Endpoint).Type() 401 childInode, boundSocketFD, err := d.controlFD.BindAt(ctx, sockType, name, opts.Mode, lisafs.UID(creds.EffectiveKUID), lisafs.GID(creds.EffectiveKGID)) 402 if err != nil { 403 return nil, err 404 } 405 hbep := opts.Endpoint.(transport.HostBoundEndpoint) 406 if err := hbep.SetBoundSocketFD(ctx, boundSocketFD); err != nil { 407 if err := d.controlFD.UnlinkAt(ctx, name, 0 /* flags */); err != nil { 408 log.Warningf("failed to clean up socket which was created by BindAt RPC: %v", err) 409 } 410 d.fs.client.CloseFD(ctx, childInode.ControlFD, false /* flush */) 411 return nil, err 412 } 413 child, err := d.newChildDentry(ctx, &childInode, name) 414 if err != nil { 415 hbep.ResetBoundSocketFD(ctx) 416 return nil, err 417 } 418 // Set the endpoint on the newly created child dentry. 419 child.endpoint = opts.Endpoint 420 return child, nil 421 } 422 423 func (d *lisafsDentry) link(ctx context.Context, target *lisafsDentry, name string) (*dentry, error) { 424 linkInode, err := d.controlFD.LinkAt(ctx, target.controlFD.ID(), name) 425 if err != nil { 426 return nil, err 427 } 428 return d.newChildDentry(ctx, &linkInode, name) 429 } 430 431 func (d *lisafsDentry) mkdir(ctx context.Context, name string, mode linux.FileMode, uid auth.KUID, gid auth.KGID) (*dentry, error) { 432 childDirInode, err := d.controlFD.MkdirAt(ctx, name, mode, lisafs.UID(uid), lisafs.GID(gid)) 433 if err != nil { 434 return nil, err 435 } 436 return d.newChildDentry(ctx, &childDirInode, name) 437 } 438 439 func (d *lisafsDentry) symlink(ctx context.Context, name, target string, creds *auth.Credentials) (*dentry, error) { 440 symlinkInode, err := d.controlFD.SymlinkAt(ctx, name, target, lisafs.UID(creds.EffectiveKUID), lisafs.GID(creds.EffectiveKGID)) 441 if err != nil { 442 return nil, err 443 } 444 return d.newChildDentry(ctx, &symlinkInode, name) 445 } 446 447 func (d *lisafsDentry) openCreate(ctx context.Context, name string, flags uint32, mode linux.FileMode, uid auth.KUID, gid auth.KGID) (*dentry, handle, error) { 448 ino, openFD, hostFD, err := d.controlFD.OpenCreateAt(ctx, name, flags, mode, lisafs.UID(uid), lisafs.GID(gid)) 449 if err != nil { 450 return nil, noHandle, err 451 } 452 453 h := handle{ 454 fdLisa: d.fs.client.NewFD(openFD), 455 fd: int32(hostFD), 456 } 457 child, err := d.fs.newLisafsDentry(ctx, &ino) 458 if err != nil { 459 h.close(ctx) 460 return nil, noHandle, err 461 } 462 return child, h, nil 463 } 464 465 // lisafsGetdentsCount is the number of bytes of dirents to read from the 466 // server in each Getdents RPC. This value is consistent with vfs1 client. 467 const lisafsGetdentsCount = int32(64 * 1024) 468 469 // Preconditions: 470 // - getDirents may not be called concurrently with another getDirents call. 471 func (d *lisafsDentry) getDirentsLocked(ctx context.Context, recordDirent func(name string, key inoKey, dType uint8)) error { 472 // shouldSeek0 indicates whether the server should SEEK to 0 before reading 473 // directory entries. 474 shouldSeek0 := true 475 for { 476 count := lisafsGetdentsCount 477 if shouldSeek0 { 478 // See lisafs.Getdents64Req.Count. 479 count = -count 480 shouldSeek0 = false 481 } 482 dirents, err := d.readFDLisa.Getdents64(ctx, count) 483 if err != nil { 484 return err 485 } 486 if len(dirents) == 0 { 487 return nil 488 } 489 for i := range dirents { 490 name := string(dirents[i].Name) 491 if name == "." || name == ".." { 492 continue 493 } 494 recordDirent(name, inoKey{ 495 ino: uint64(dirents[i].Ino), 496 devMinor: uint32(dirents[i].DevMinor), 497 devMajor: uint32(dirents[i].DevMajor), 498 }, uint8(dirents[i].Type)) 499 } 500 } 501 } 502 503 func flush(ctx context.Context, fd lisafs.ClientFD) error { 504 if fd.Ok() { 505 return fd.Flush(ctx) 506 } 507 return nil 508 } 509 510 func (d *lisafsDentry) statfs(ctx context.Context) (linux.Statfs, error) { 511 var statFS lisafs.StatFS 512 if err := d.controlFD.StatFSTo(ctx, &statFS); err != nil { 513 return linux.Statfs{}, err 514 } 515 return linux.Statfs{ 516 BlockSize: statFS.BlockSize, 517 FragmentSize: statFS.BlockSize, 518 Blocks: statFS.Blocks, 519 BlocksFree: statFS.BlocksFree, 520 BlocksAvailable: statFS.BlocksAvailable, 521 Files: statFS.Files, 522 FilesFree: statFS.FilesFree, 523 NameLength: statFS.NameLength, 524 }, nil 525 } 526 527 func (d *lisafsDentry) restoreFile(ctx context.Context, inode *lisafs.Inode, opts *vfs.CompleteRestoreOptions) error { 528 d.controlFD = d.fs.client.NewFD(inode.ControlFD) 529 530 // Gofers do not preserve inoKey across checkpoint/restore, so: 531 // 532 // - We must assume that the remote filesystem did not change in a way that 533 // would invalidate dentries, since we can't revalidate dentries by 534 // checking inoKey. 535 // 536 // - We need to associate the new inoKey with the existing d.ino. 537 d.inoKey = inoKeyFromStatx(&inode.Stat) 538 d.fs.inoMu.Lock() 539 d.fs.inoByKey[d.inoKey] = d.ino 540 d.fs.inoMu.Unlock() 541 542 // Check metadata stability before updating metadata. 543 d.metadataMu.Lock() 544 defer d.metadataMu.Unlock() 545 if d.isRegularFile() { 546 if opts.ValidateFileSizes { 547 if inode.Stat.Mask&linux.STATX_SIZE == 0 { 548 return vfs.ErrCorruption{fmt.Errorf("gofer.dentry(%q).restoreFile: file size validation failed: file size not available", genericDebugPathname(&d.dentry))} 549 } 550 if d.size.RacyLoad() != inode.Stat.Size { 551 return vfs.ErrCorruption{fmt.Errorf("gofer.dentry(%q).restoreFile: file size validation failed: size changed from %d to %d", genericDebugPathname(&d.dentry), d.size.Load(), inode.Stat.Size)} 552 } 553 } 554 if opts.ValidateFileModificationTimestamps { 555 if inode.Stat.Mask&linux.STATX_MTIME == 0 { 556 return vfs.ErrCorruption{fmt.Errorf("gofer.dentry(%q).restoreFile: mtime validation failed: mtime not available", genericDebugPathname(&d.dentry))} 557 } 558 if want := dentryTimestamp(inode.Stat.Mtime); d.mtime.RacyLoad() != want { 559 return vfs.ErrCorruption{fmt.Errorf("gofer.dentry(%q).restoreFile: mtime validation failed: mtime changed from %+v to %+v", genericDebugPathname(&d.dentry), linux.NsecToStatxTimestamp(d.mtime.RacyLoad()), linux.NsecToStatxTimestamp(want))} 560 } 561 } 562 } 563 if !d.cachedMetadataAuthoritative() { 564 d.updateMetadataFromStatxLocked(&inode.Stat) 565 } 566 567 if rw, ok := d.fs.savedDentryRW[&d.dentry]; ok { 568 if err := d.ensureSharedHandle(ctx, rw.read, rw.write, false /* trunc */); err != nil { 569 return err 570 } 571 } 572 573 return nil 574 } 575 576 // doRevalidationLisafs stats all dentries in `state`. It will update or 577 // invalidate dentries in the cache based on the result. 578 // 579 // Preconditions: 580 // - fs.renameMu must be locked. 581 // - InteropModeShared is in effect. 582 func doRevalidationLisafs(ctx context.Context, vfsObj *vfs.VirtualFilesystem, state *revalidateState, ds **[]*dentry) error { 583 start := state.start.impl.(*lisafsDentry) 584 585 // Populate state.names. 586 state.names = state.names[:0] // For sanity. 587 if state.refreshStart { 588 state.names = append(state.names, "") 589 } 590 for _, d := range state.dentries { 591 state.names = append(state.names, d.name) 592 } 593 594 // Lock metadata on all dentries *before* getting attributes for them. 595 if state.refreshStart { 596 start.metadataMu.Lock() 597 defer start.metadataMu.Unlock() 598 } 599 for _, d := range state.dentries { 600 d.metadataMu.Lock() 601 } 602 // lastUnlockedDentry keeps track of the dentries in state.dentries that have 603 // already had their metadataMu unlocked. Avoid defer unlock in the loop 604 // above to avoid heap allocation. 605 lastUnlockedDentry := -1 606 defer func() { 607 // Advance to the first unevaluated dentry and unlock the remaining 608 // dentries. 609 for lastUnlockedDentry++; lastUnlockedDentry < len(state.dentries); lastUnlockedDentry++ { 610 state.dentries[lastUnlockedDentry].metadataMu.Unlock() 611 } 612 }() 613 614 // Make WalkStat RPC. 615 stats, err := start.controlFD.WalkStat(ctx, state.names) 616 if err != nil { 617 return err 618 } 619 620 if state.refreshStart { 621 if len(stats) > 0 { 622 // First dentry is where the search is starting, just update attributes 623 // since it cannot be replaced. 624 start.updateMetadataFromStatxLocked(&stats[0]) // +checklocksforce: see above. 625 stats = stats[1:] 626 } 627 } 628 629 for i := 0; i < len(state.dentries); i++ { 630 d := state.dentries[i] 631 found := i < len(stats) 632 // Advance lastUnlockedDentry. It is the responsibility of this for loop 633 // block to unlock d.metadataMu. 634 lastUnlockedDentry = i 635 636 // Note that synthetic dentries will always fail this comparison check. 637 if !found || d.inoKey != inoKeyFromStatx(&stats[i]) { 638 d.metadataMu.Unlock() 639 if !found && d.isSynthetic() { 640 // We have a synthetic file, and no remote file has arisen to replace 641 // it. 642 return nil 643 } 644 // The file at this path has changed or no longer exists. Mark the 645 // dentry invalidated. 646 d.invalidate(ctx, vfsObj, ds) 647 return nil 648 } 649 650 // The file at this path hasn't changed. Just update cached metadata. 651 d.impl.(*lisafsDentry).updateMetadataFromStatxLocked(&stats[i]) // +checklocksforce: see above. 652 d.metadataMu.Unlock() 653 } 654 return nil 655 }