github.com/ttpreport/gvisor-ligolo@v0.0.0-20240123134145-a858404967ba/pkg/sentry/vfs/vfs.go (about) 1 // Copyright 2019 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 implements a virtual filesystem layer. 16 // 17 // Lock order: 18 // 19 // EpollInstance.interestMu 20 // FileDescription.epollMu 21 // Locks acquired by FilesystemImpl/FileDescriptionImpl methods 22 // VirtualFilesystem.mountMu 23 // Dentry.mu 24 // Locks acquired by FilesystemImpls between Prepare{Delete,Rename}Dentry and Commit{Delete,Rename*}Dentry 25 // VirtualFilesystem.filesystemsMu 26 // fdnotifier.notifier.mu 27 // EpollInstance.readyMu 28 // Inotify.mu 29 // Watches.mu 30 // Inotify.evMu 31 // VirtualFilesystem.fsTypesMu 32 // 33 // Locking Dentry.mu in multiple Dentries requires holding 34 // VirtualFilesystem.mountMu. Locking EpollInstance.interestMu in multiple 35 // EpollInstances requires holding epollCycleMu. 36 package vfs 37 38 import ( 39 "fmt" 40 "path" 41 "time" 42 43 "github.com/ttpreport/gvisor-ligolo/pkg/abi/linux" 44 "github.com/ttpreport/gvisor-ligolo/pkg/atomicbitops" 45 "github.com/ttpreport/gvisor-ligolo/pkg/bitmap" 46 "github.com/ttpreport/gvisor-ligolo/pkg/context" 47 "github.com/ttpreport/gvisor-ligolo/pkg/errors/linuxerr" 48 "github.com/ttpreport/gvisor-ligolo/pkg/eventchannel" 49 "github.com/ttpreport/gvisor-ligolo/pkg/fspath" 50 "github.com/ttpreport/gvisor-ligolo/pkg/log" 51 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/fsmetric" 52 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/kernel/auth" 53 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/socket/unix/transport" 54 epb "github.com/ttpreport/gvisor-ligolo/pkg/sentry/vfs/events_go_proto" 55 "github.com/ttpreport/gvisor-ligolo/pkg/sync" 56 "github.com/ttpreport/gvisor-ligolo/pkg/waiter" 57 ) 58 59 // How long to wait for a mount promise before proceeding with the VFS 60 // operation. This should be configurable by the user eventually. 61 const mountPromiseTimeout = 10 * time.Second 62 63 // A VirtualFilesystem (VFS for short) combines Filesystems in trees of Mounts. 64 // 65 // There is no analogue to the VirtualFilesystem type in Linux, as the 66 // equivalent state in Linux is global. 67 // 68 // +stateify savable 69 type VirtualFilesystem struct { 70 // mountMu serializes mount mutations. 71 // 72 // mountMu is analogous to Linux's namespace_sem. 73 mountMu virtualFilesystemMutex `state:"nosave"` 74 75 // mounts maps (mount parent, mount point) pairs to mounts. (Since mounts 76 // are uniquely namespaced, including mount parent in the key correctly 77 // handles both bind mounts and mount namespaces; Linux does the same.) 78 // Synchronization between mutators and readers is provided by mounts.seq; 79 // synchronization between mutators is provided by mountMu. 80 // 81 // mounts is used to follow mount points during path traversal. We use a 82 // single table rather than per-Dentry tables to reduce size (and therefore 83 // cache footprint) for the vast majority of Dentries that are not mount 84 // points. 85 // 86 // mounts is analogous to Linux's mount_hashtable. 87 mounts mountTable `state:".([]*Mount)"` 88 89 // mountpoints maps mount points to mounts at those points in all 90 // namespaces. mountpoints is protected by mountMu. 91 // 92 // mountpoints is used to find mounts that must be umounted due to 93 // removal of a mount point Dentry from another mount namespace. ("A file 94 // or directory that is a mount point in one namespace that is not a mount 95 // point in another namespace, may be renamed, unlinked, or removed 96 // (rmdir(2)) in the mount namespace in which it is not a mount point 97 // (subject to the usual permission checks)." - mount_namespaces(7)) 98 // 99 // mountpoints is analogous to Linux's mountpoint_hashtable. 100 mountpoints map[*Dentry]map[*Mount]struct{} 101 102 // lastMountID is the last allocated mount ID. lastMountID is accessed 103 // using atomic memory operations. 104 lastMountID atomicbitops.Uint64 105 106 // anonMount is a Mount, not included in mounts or mountpoints, 107 // representing an anonFilesystem. anonMount is used to back 108 // VirtualDentries returned by VirtualFilesystem.NewAnonVirtualDentry(). 109 // anonMount is immutable. 110 // 111 // anonMount is analogous to Linux's anon_inode_mnt. 112 anonMount *Mount 113 114 // devices contains all registered Devices. devices is protected by 115 // devicesMu. 116 devicesMu sync.RWMutex `state:"nosave"` 117 devices map[devTuple]*registeredDevice 118 119 // dynCharDevMajorUsed contains all allocated dynamic character device 120 // major numbers. dynCharDevMajor is protected by dynCharDevMajorMu. 121 dynCharDevMajorMu sync.Mutex `state:"nosave"` 122 dynCharDevMajorUsed map[uint32]struct{} 123 124 // anonBlockDevMinor contains all allocated anonymous block device minor 125 // numbers. anonBlockDevMinorNext is a lower bound for the smallest 126 // unallocated anonymous block device number. anonBlockDevMinorNext and 127 // anonBlockDevMinor are protected by anonBlockDevMinorMu. 128 anonBlockDevMinorMu sync.Mutex `state:"nosave"` 129 anonBlockDevMinorNext uint32 130 anonBlockDevMinor map[uint32]struct{} 131 132 // fsTypes contains all registered FilesystemTypes. fsTypes is protected by 133 // fsTypesMu. 134 fsTypesMu sync.RWMutex `state:"nosave"` 135 fsTypes map[string]*registeredFilesystemType 136 137 // filesystems contains all Filesystems. filesystems is protected by 138 // filesystemsMu. 139 filesystemsMu sync.Mutex `state:"nosave"` 140 filesystems map[*Filesystem]struct{} 141 142 // groupIDBitmap tracks which mount group IDs are available for allocation. 143 groupIDBitmap bitmap.Bitmap 144 145 // mountPromises contains all unresolved mount promises. 146 mountPromisesMu sync.RWMutex `state:"nosave"` 147 mountPromises map[VirtualDentry]*waiter.Queue 148 } 149 150 // Init initializes a new VirtualFilesystem with no mounts or FilesystemTypes. 151 func (vfs *VirtualFilesystem) Init(ctx context.Context) error { 152 if vfs.mountpoints != nil { 153 panic("VFS already initialized") 154 } 155 vfs.mountpoints = make(map[*Dentry]map[*Mount]struct{}) 156 vfs.devices = make(map[devTuple]*registeredDevice) 157 vfs.dynCharDevMajorUsed = make(map[uint32]struct{}) 158 vfs.anonBlockDevMinorNext = 1 159 vfs.anonBlockDevMinor = make(map[uint32]struct{}) 160 vfs.fsTypes = make(map[string]*registeredFilesystemType) 161 vfs.filesystems = make(map[*Filesystem]struct{}) 162 vfs.mounts.Init() 163 vfs.groupIDBitmap = bitmap.New(1024) 164 vfs.mountPromises = make(map[VirtualDentry]*waiter.Queue) 165 166 // Construct vfs.anonMount. 167 anonfsDevMinor, err := vfs.GetAnonBlockDevMinor() 168 if err != nil { 169 // This shouldn't be possible since anonBlockDevMinorNext was 170 // initialized to 1 above (no device numbers have been allocated yet). 171 panic(fmt.Sprintf("VirtualFilesystem.Init: device number allocation for anonfs failed: %v", err)) 172 } 173 anonfs := anonFilesystem{ 174 devMinor: anonfsDevMinor, 175 } 176 anonfs.vfsfs.Init(vfs, &anonFilesystemType{}, &anonfs) 177 defer anonfs.vfsfs.DecRef(ctx) 178 anonMount := vfs.NewDisconnectedMount(&anonfs.vfsfs, nil, &MountOptions{}) 179 vfs.anonMount = anonMount 180 181 return nil 182 } 183 184 // Release drops references on filesystem objects held by vfs. 185 // 186 // Precondition: This must be called after VFS.Init() has succeeded. 187 func (vfs *VirtualFilesystem) Release(ctx context.Context) { 188 vfs.anonMount.DecRef(ctx) 189 for _, fst := range vfs.fsTypes { 190 fst.fsType.Release(ctx) 191 } 192 } 193 194 // PathOperation specifies the path operated on by a VFS method. 195 // 196 // PathOperation is passed to VFS methods by pointer to reduce memory copying: 197 // it's somewhat large and should never escape. (Options structs are passed by 198 // pointer to VFS and FileDescription methods for the same reason.) 199 // 200 // +stateify savable 201 type PathOperation struct { 202 // Root is the VFS root. References on Root are borrowed from the provider 203 // of the PathOperation. 204 // 205 // Invariants: Root.Ok(). 206 Root VirtualDentry 207 208 // Start is the starting point for the path traversal. References on Start 209 // are borrowed from the provider of the PathOperation (i.e. the caller of 210 // the VFS method to which the PathOperation was passed). 211 // 212 // Invariants: Start.Ok(). If Path.Absolute, then Start == Root. 213 Start VirtualDentry 214 215 // Path is the pathname traversed by this operation. 216 Path fspath.Path 217 218 // If FollowFinalSymlink is true, and the Dentry traversed by the final 219 // path component represents a symbolic link, the symbolic link should be 220 // followed. 221 FollowFinalSymlink bool 222 } 223 224 // AccessAt checks whether a user with creds has access to the file at 225 // the given path. 226 func (vfs *VirtualFilesystem) AccessAt(ctx context.Context, creds *auth.Credentials, ats AccessTypes, pop *PathOperation) error { 227 rp := vfs.getResolvingPath(creds, pop) 228 for { 229 vfs.maybeBlockOnMountPromise(ctx, rp) 230 err := rp.mount.fs.impl.AccessAt(ctx, rp, creds, ats) 231 if err == nil { 232 rp.Release(ctx) 233 return nil 234 } 235 if !rp.handleError(ctx, err) { 236 rp.Release(ctx) 237 return err 238 } 239 } 240 } 241 242 // GetDentryAt returns a VirtualDentry representing the given path, at which a 243 // file must exist. A reference is taken on the returned VirtualDentry. 244 func (vfs *VirtualFilesystem) GetDentryAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *GetDentryOptions) (VirtualDentry, error) { 245 rp := vfs.getResolvingPath(creds, pop) 246 for { 247 vfs.maybeBlockOnMountPromise(ctx, rp) 248 d, err := rp.mount.fs.impl.GetDentryAt(ctx, rp, *opts) 249 if err == nil { 250 vd := VirtualDentry{ 251 mount: rp.mount, 252 dentry: d, 253 } 254 rp.mount.IncRef() 255 rp.Release(ctx) 256 return vd, nil 257 } 258 if !rp.handleError(ctx, err) { 259 rp.Release(ctx) 260 return VirtualDentry{}, err 261 } 262 } 263 } 264 265 // Preconditions: pop.Path.Begin.Ok(). 266 func (vfs *VirtualFilesystem) getParentDirAndName(ctx context.Context, creds *auth.Credentials, pop *PathOperation) (VirtualDentry, string, error) { 267 rp := vfs.getResolvingPath(creds, pop) 268 for { 269 vfs.maybeBlockOnMountPromise(ctx, rp) 270 parent, err := rp.mount.fs.impl.GetParentDentryAt(ctx, rp) 271 if err == nil { 272 parentVD := VirtualDentry{ 273 mount: rp.mount, 274 dentry: parent, 275 } 276 rp.mount.IncRef() 277 name := rp.Component() 278 rp.Release(ctx) 279 return parentVD, name, nil 280 } 281 if checkInvariants { 282 if rp.canHandleError(err) && rp.Done() { 283 panic(fmt.Sprintf("%T.GetParentDentryAt() consumed all path components and returned %v", rp.mount.fs.impl, err)) 284 } 285 } 286 if !rp.handleError(ctx, err) { 287 rp.Release(ctx) 288 return VirtualDentry{}, "", err 289 } 290 } 291 } 292 293 // LinkAt creates a hard link at newpop representing the existing file at 294 // oldpop. 295 func (vfs *VirtualFilesystem) LinkAt(ctx context.Context, creds *auth.Credentials, oldpop, newpop *PathOperation) error { 296 oldVD, err := vfs.GetDentryAt(ctx, creds, oldpop, &GetDentryOptions{}) 297 if err != nil { 298 return err 299 } 300 301 if !newpop.Path.Begin.Ok() { 302 oldVD.DecRef(ctx) 303 if newpop.Path.Absolute { 304 return linuxerr.EEXIST 305 } 306 return linuxerr.ENOENT 307 } 308 if newpop.FollowFinalSymlink { 309 oldVD.DecRef(ctx) 310 ctx.Warningf("VirtualFilesystem.LinkAt: file creation paths can't follow final symlink") 311 return linuxerr.EINVAL 312 } 313 314 rp := vfs.getResolvingPath(creds, newpop) 315 for { 316 vfs.maybeBlockOnMountPromise(ctx, rp) 317 err := rp.mount.fs.impl.LinkAt(ctx, rp, oldVD) 318 if err == nil { 319 rp.Release(ctx) 320 oldVD.DecRef(ctx) 321 return nil 322 } 323 if checkInvariants { 324 if rp.canHandleError(err) && rp.Done() { 325 panic(fmt.Sprintf("%T.LinkAt() consumed all path components and returned %v", rp.mount.fs.impl, err)) 326 } 327 } 328 if !rp.handleError(ctx, err) { 329 rp.Release(ctx) 330 oldVD.DecRef(ctx) 331 return err 332 } 333 } 334 } 335 336 // MkdirAt creates a directory at the given path. 337 func (vfs *VirtualFilesystem) MkdirAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *MkdirOptions) error { 338 if !pop.Path.Begin.Ok() { 339 // pop.Path should not be empty in operations that create/delete files. 340 // This is consistent with mkdirat(dirfd, "", mode). 341 if pop.Path.Absolute { 342 return linuxerr.EEXIST 343 } 344 return linuxerr.ENOENT 345 } 346 if pop.FollowFinalSymlink { 347 ctx.Warningf("VirtualFilesystem.MkdirAt: file creation paths can't follow final symlink") 348 return linuxerr.EINVAL 349 } 350 // "Under Linux, apart from the permission bits, the S_ISVTX mode bit is 351 // also honored." - mkdir(2) 352 opts.Mode &= 0777 | linux.S_ISVTX 353 354 rp := vfs.getResolvingPath(creds, pop) 355 for { 356 vfs.maybeBlockOnMountPromise(ctx, rp) 357 err := rp.mount.fs.impl.MkdirAt(ctx, rp, *opts) 358 if err == nil { 359 rp.Release(ctx) 360 return nil 361 } 362 if checkInvariants { 363 if rp.canHandleError(err) && rp.Done() { 364 panic(fmt.Sprintf("%T.MkdirAt() consumed all path components and returned %v", rp.mount.fs.impl, err)) 365 } 366 } 367 if !rp.handleError(ctx, err) { 368 rp.Release(ctx) 369 return err 370 } 371 } 372 } 373 374 // MknodAt creates a file of the given mode at the given path. It returns an 375 // error from the linuxerr package. 376 func (vfs *VirtualFilesystem) MknodAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *MknodOptions) error { 377 if !pop.Path.Begin.Ok() { 378 // pop.Path should not be empty in operations that create/delete files. 379 // This is consistent with mknodat(dirfd, "", mode, dev). 380 if pop.Path.Absolute { 381 return linuxerr.EEXIST 382 } 383 return linuxerr.ENOENT 384 } 385 if pop.FollowFinalSymlink { 386 ctx.Warningf("VirtualFilesystem.MknodAt: file creation paths can't follow final symlink") 387 return linuxerr.EINVAL 388 } 389 390 rp := vfs.getResolvingPath(creds, pop) 391 for { 392 vfs.maybeBlockOnMountPromise(ctx, rp) 393 err := rp.mount.fs.impl.MknodAt(ctx, rp, *opts) 394 if err == nil { 395 rp.Release(ctx) 396 return nil 397 } 398 if checkInvariants { 399 if rp.canHandleError(err) && rp.Done() { 400 panic(fmt.Sprintf("%T.MknodAt() consumed all path components and returned %v", rp.mount.fs.impl, err)) 401 } 402 } 403 if !rp.handleError(ctx, err) { 404 rp.Release(ctx) 405 return err 406 } 407 } 408 } 409 410 // OpenAt returns a FileDescription providing access to the file at the given 411 // path. A reference is taken on the returned FileDescription. 412 func (vfs *VirtualFilesystem) OpenAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *OpenOptions) (*FileDescription, error) { 413 fsmetric.Opens.Increment() 414 415 // Remove: 416 // 417 // - O_CLOEXEC, which affects file descriptors and therefore must be 418 // handled outside of VFS. 419 // 420 // - Unknown flags. 421 opts.Flags &= linux.O_ACCMODE | linux.O_CREAT | linux.O_EXCL | linux.O_NOCTTY | linux.O_TRUNC | linux.O_APPEND | linux.O_NONBLOCK | linux.O_DSYNC | linux.O_ASYNC | linux.O_DIRECT | linux.O_LARGEFILE | linux.O_DIRECTORY | linux.O_NOFOLLOW | linux.O_NOATIME | linux.O_SYNC | linux.O_PATH | linux.O_TMPFILE 422 // Linux's __O_SYNC (which we call linux.O_SYNC) implies O_DSYNC. 423 if opts.Flags&linux.O_SYNC != 0 { 424 opts.Flags |= linux.O_DSYNC 425 } 426 // Linux's __O_TMPFILE (which we call linux.O_TMPFILE) must be specified 427 // with O_DIRECTORY and a writable access mode (to ensure that it fails on 428 // filesystem implementations that do not support it). 429 if opts.Flags&linux.O_TMPFILE != 0 { 430 if opts.Flags&linux.O_DIRECTORY == 0 { 431 return nil, linuxerr.EINVAL 432 } 433 if opts.Flags&linux.O_CREAT != 0 { 434 return nil, linuxerr.EINVAL 435 } 436 if opts.Flags&linux.O_ACCMODE == linux.O_RDONLY { 437 return nil, linuxerr.EINVAL 438 } 439 } 440 // O_PATH causes most other flags to be ignored. 441 if opts.Flags&linux.O_PATH != 0 { 442 opts.Flags &= linux.O_DIRECTORY | linux.O_NOFOLLOW | linux.O_PATH 443 } 444 // "On Linux, the following bits are also honored in mode: [S_ISUID, 445 // S_ISGID, S_ISVTX]" - open(2) 446 opts.Mode &= 0777 | linux.S_ISUID | linux.S_ISGID | linux.S_ISVTX 447 448 if opts.Flags&linux.O_NOFOLLOW != 0 { 449 pop.FollowFinalSymlink = false 450 } 451 if opts.Flags&linux.O_PATH != 0 { 452 return vfs.openOPathFD(ctx, creds, pop, opts.Flags) 453 } 454 rp := vfs.getResolvingPath(creds, pop) 455 if opts.Flags&linux.O_DIRECTORY != 0 { 456 rp.mustBeDir = true 457 } 458 for { 459 vfs.maybeBlockOnMountPromise(ctx, rp) 460 fd, err := rp.mount.fs.impl.OpenAt(ctx, rp, *opts) 461 if err == nil { 462 rp.Release(ctx) 463 464 if opts.FileExec { 465 if fd.Mount().Flags.NoExec { 466 fd.DecRef(ctx) 467 return nil, linuxerr.EACCES 468 } 469 470 // Only a regular file can be executed. 471 stat, err := fd.Stat(ctx, StatOptions{Mask: linux.STATX_TYPE}) 472 if err != nil { 473 fd.DecRef(ctx) 474 return nil, err 475 } 476 if stat.Mask&linux.STATX_TYPE == 0 || stat.Mode&linux.S_IFMT != linux.S_IFREG { 477 fd.DecRef(ctx) 478 return nil, linuxerr.EACCES 479 } 480 } 481 482 fd.Dentry().InotifyWithParent(ctx, linux.IN_OPEN, 0, PathEvent) 483 return fd, nil 484 } 485 if !rp.handleError(ctx, err) { 486 rp.Release(ctx) 487 return nil, err 488 } 489 } 490 } 491 492 // ReadlinkAt returns the target of the symbolic link at the given path. 493 func (vfs *VirtualFilesystem) ReadlinkAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation) (string, error) { 494 rp := vfs.getResolvingPath(creds, pop) 495 for { 496 vfs.maybeBlockOnMountPromise(ctx, rp) 497 target, err := rp.mount.fs.impl.ReadlinkAt(ctx, rp) 498 if err == nil { 499 rp.Release(ctx) 500 return target, nil 501 } 502 if !rp.handleError(ctx, err) { 503 rp.Release(ctx) 504 return "", err 505 } 506 } 507 } 508 509 // RenameAt renames the file at oldpop to newpop. 510 func (vfs *VirtualFilesystem) RenameAt(ctx context.Context, creds *auth.Credentials, oldpop, newpop *PathOperation, opts *RenameOptions) error { 511 if !oldpop.Path.Begin.Ok() { 512 if oldpop.Path.Absolute { 513 return linuxerr.EBUSY 514 } 515 return linuxerr.ENOENT 516 } 517 if oldpop.FollowFinalSymlink { 518 ctx.Warningf("VirtualFilesystem.RenameAt: source path can't follow final symlink") 519 return linuxerr.EINVAL 520 } 521 522 oldParentVD, oldName, err := vfs.getParentDirAndName(ctx, creds, oldpop) 523 if err != nil { 524 return err 525 } 526 if oldName == "." || oldName == ".." { 527 oldParentVD.DecRef(ctx) 528 return linuxerr.EBUSY 529 } 530 if len(oldName) > linux.NAME_MAX { 531 oldParentVD.DecRef(ctx) 532 return linuxerr.ENAMETOOLONG 533 } 534 535 if !newpop.Path.Begin.Ok() { 536 oldParentVD.DecRef(ctx) 537 if newpop.Path.Absolute { 538 return linuxerr.EBUSY 539 } 540 return linuxerr.ENOENT 541 } 542 if newpop.FollowFinalSymlink { 543 oldParentVD.DecRef(ctx) 544 ctx.Warningf("VirtualFilesystem.RenameAt: destination path can't follow final symlink") 545 return linuxerr.EINVAL 546 } 547 548 rp := vfs.getResolvingPath(creds, newpop) 549 renameOpts := *opts 550 if oldpop.Path.Dir { 551 renameOpts.MustBeDir = true 552 } 553 for { 554 vfs.maybeBlockOnMountPromise(ctx, rp) 555 err := rp.mount.fs.impl.RenameAt(ctx, rp, oldParentVD, oldName, renameOpts) 556 if err == nil { 557 rp.Release(ctx) 558 oldParentVD.DecRef(ctx) 559 return nil 560 } 561 if checkInvariants { 562 if rp.canHandleError(err) && rp.Done() { 563 panic(fmt.Sprintf("%T.RenameAt() consumed all path components and returned %v", rp.mount.fs.impl, err)) 564 } 565 } 566 if !rp.handleError(ctx, err) { 567 rp.Release(ctx) 568 oldParentVD.DecRef(ctx) 569 return err 570 } 571 } 572 } 573 574 // RmdirAt removes the directory at the given path. 575 func (vfs *VirtualFilesystem) RmdirAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation) error { 576 if !pop.Path.Begin.Ok() { 577 // pop.Path should not be empty in operations that create/delete files. 578 // This is consistent with unlinkat(dirfd, "", AT_REMOVEDIR). 579 if pop.Path.Absolute { 580 return linuxerr.EBUSY 581 } 582 return linuxerr.ENOENT 583 } 584 if pop.FollowFinalSymlink { 585 ctx.Warningf("VirtualFilesystem.RmdirAt: file deletion paths can't follow final symlink") 586 return linuxerr.EINVAL 587 } 588 589 rp := vfs.getResolvingPath(creds, pop) 590 for { 591 vfs.maybeBlockOnMountPromise(ctx, rp) 592 err := rp.mount.fs.impl.RmdirAt(ctx, rp) 593 if err == nil { 594 rp.Release(ctx) 595 return nil 596 } 597 if checkInvariants { 598 if rp.canHandleError(err) && rp.Done() { 599 panic(fmt.Sprintf("%T.RmdirAt() consumed all path components and returned %v", rp.mount.fs.impl, err)) 600 } 601 } 602 if !rp.handleError(ctx, err) { 603 rp.Release(ctx) 604 return err 605 } 606 } 607 } 608 609 // SetStatAt changes metadata for the file at the given path. 610 func (vfs *VirtualFilesystem) SetStatAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *SetStatOptions) error { 611 rp := vfs.getResolvingPath(creds, pop) 612 for { 613 vfs.maybeBlockOnMountPromise(ctx, rp) 614 err := rp.mount.fs.impl.SetStatAt(ctx, rp, *opts) 615 if err == nil { 616 rp.Release(ctx) 617 return nil 618 } 619 if !rp.handleError(ctx, err) { 620 rp.Release(ctx) 621 return err 622 } 623 } 624 } 625 626 // StatAt returns metadata for the file at the given path. 627 func (vfs *VirtualFilesystem) StatAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *StatOptions) (linux.Statx, error) { 628 rp := vfs.getResolvingPath(creds, pop) 629 for { 630 vfs.maybeBlockOnMountPromise(ctx, rp) 631 stat, err := rp.mount.fs.impl.StatAt(ctx, rp, *opts) 632 if err == nil { 633 rp.Release(ctx) 634 return stat, nil 635 } 636 if !rp.handleError(ctx, err) { 637 rp.Release(ctx) 638 return linux.Statx{}, err 639 } 640 } 641 } 642 643 // StatFSAt returns metadata for the filesystem containing the file at the 644 // given path. 645 func (vfs *VirtualFilesystem) StatFSAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation) (linux.Statfs, error) { 646 rp := vfs.getResolvingPath(creds, pop) 647 for { 648 vfs.maybeBlockOnMountPromise(ctx, rp) 649 statfs, err := rp.mount.fs.impl.StatFSAt(ctx, rp) 650 if err == nil { 651 rp.Release(ctx) 652 return statfs, nil 653 } 654 if !rp.handleError(ctx, err) { 655 rp.Release(ctx) 656 return linux.Statfs{}, err 657 } 658 } 659 } 660 661 // SymlinkAt creates a symbolic link at the given path with the given target. 662 func (vfs *VirtualFilesystem) SymlinkAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, target string) error { 663 if !pop.Path.Begin.Ok() { 664 // pop.Path should not be empty in operations that create/delete files. 665 // This is consistent with symlinkat(oldpath, newdirfd, ""). 666 if pop.Path.Absolute { 667 return linuxerr.EEXIST 668 } 669 return linuxerr.ENOENT 670 } 671 if pop.FollowFinalSymlink { 672 ctx.Warningf("VirtualFilesystem.SymlinkAt: file creation paths can't follow final symlink") 673 return linuxerr.EINVAL 674 } 675 676 rp := vfs.getResolvingPath(creds, pop) 677 for { 678 vfs.maybeBlockOnMountPromise(ctx, rp) 679 err := rp.mount.fs.impl.SymlinkAt(ctx, rp, target) 680 if err == nil { 681 rp.Release(ctx) 682 return nil 683 } 684 if checkInvariants { 685 if rp.canHandleError(err) && rp.Done() { 686 panic(fmt.Sprintf("%T.SymlinkAt() consumed all path components and returned %v", rp.mount.fs.impl, err)) 687 } 688 } 689 if !rp.handleError(ctx, err) { 690 rp.Release(ctx) 691 return err 692 } 693 } 694 } 695 696 // UnlinkAt deletes the non-directory file at the given path. 697 func (vfs *VirtualFilesystem) UnlinkAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation) error { 698 if !pop.Path.Begin.Ok() { 699 // pop.Path should not be empty in operations that create/delete files. 700 // This is consistent with unlinkat(dirfd, "", 0). 701 if pop.Path.Absolute { 702 return linuxerr.EBUSY 703 } 704 return linuxerr.ENOENT 705 } 706 if pop.FollowFinalSymlink { 707 ctx.Warningf("VirtualFilesystem.UnlinkAt: file deletion paths can't follow final symlink") 708 return linuxerr.EINVAL 709 } 710 711 rp := vfs.getResolvingPath(creds, pop) 712 for { 713 vfs.maybeBlockOnMountPromise(ctx, rp) 714 err := rp.mount.fs.impl.UnlinkAt(ctx, rp) 715 if err == nil { 716 rp.Release(ctx) 717 return nil 718 } 719 if checkInvariants { 720 if rp.canHandleError(err) && rp.Done() { 721 panic(fmt.Sprintf("%T.UnlinkAt() consumed all path components and returned %v", rp.mount.fs.impl, err)) 722 } 723 } 724 if !rp.handleError(ctx, err) { 725 rp.Release(ctx) 726 return err 727 } 728 } 729 } 730 731 // BoundEndpointAt gets the bound endpoint at the given path, if one exists. 732 func (vfs *VirtualFilesystem) BoundEndpointAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *BoundEndpointOptions) (transport.BoundEndpoint, error) { 733 rp := vfs.getResolvingPath(creds, pop) 734 for { 735 vfs.maybeBlockOnMountPromise(ctx, rp) 736 bep, err := rp.mount.fs.impl.BoundEndpointAt(ctx, rp, *opts) 737 if err == nil { 738 rp.Release(ctx) 739 return bep, nil 740 } 741 if checkInvariants { 742 if rp.canHandleError(err) && rp.Done() { 743 panic(fmt.Sprintf("%T.BoundEndpointAt() consumed all path components and returned %v", rp.mount.fs.impl, err)) 744 } 745 } 746 if !rp.handleError(ctx, err) { 747 rp.Release(ctx) 748 return nil, err 749 } 750 } 751 } 752 753 // ListXattrAt returns all extended attribute names for the file at the given 754 // path. 755 func (vfs *VirtualFilesystem) ListXattrAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, size uint64) ([]string, error) { 756 rp := vfs.getResolvingPath(creds, pop) 757 for { 758 vfs.maybeBlockOnMountPromise(ctx, rp) 759 names, err := rp.mount.fs.impl.ListXattrAt(ctx, rp, size) 760 if err == nil { 761 rp.Release(ctx) 762 return names, nil 763 } 764 if linuxerr.Equals(linuxerr.EOPNOTSUPP, err) { 765 // Linux doesn't actually return EOPNOTSUPP in this case; instead, 766 // fs/xattr.c:vfs_listxattr() falls back to allowing the security 767 // subsystem to return security extended attributes, which by 768 // default don't exist. 769 rp.Release(ctx) 770 return nil, nil 771 } 772 if !rp.handleError(ctx, err) { 773 rp.Release(ctx) 774 return nil, err 775 } 776 } 777 } 778 779 // GetXattrAt returns the value associated with the given extended attribute 780 // for the file at the given path. 781 func (vfs *VirtualFilesystem) GetXattrAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *GetXattrOptions) (string, error) { 782 rp := vfs.getResolvingPath(creds, pop) 783 for { 784 vfs.maybeBlockOnMountPromise(ctx, rp) 785 val, err := rp.mount.fs.impl.GetXattrAt(ctx, rp, *opts) 786 if err == nil { 787 rp.Release(ctx) 788 return val, nil 789 } 790 if !rp.handleError(ctx, err) { 791 rp.Release(ctx) 792 return "", err 793 } 794 } 795 } 796 797 // SetXattrAt changes the value associated with the given extended attribute 798 // for the file at the given path. 799 func (vfs *VirtualFilesystem) SetXattrAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *SetXattrOptions) error { 800 rp := vfs.getResolvingPath(creds, pop) 801 for { 802 vfs.maybeBlockOnMountPromise(ctx, rp) 803 err := rp.mount.fs.impl.SetXattrAt(ctx, rp, *opts) 804 if err == nil { 805 rp.Release(ctx) 806 return nil 807 } 808 if !rp.handleError(ctx, err) { 809 rp.Release(ctx) 810 return err 811 } 812 } 813 } 814 815 // RemoveXattrAt removes the given extended attribute from the file at rp. 816 func (vfs *VirtualFilesystem) RemoveXattrAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, name string) error { 817 rp := vfs.getResolvingPath(creds, pop) 818 for { 819 vfs.maybeBlockOnMountPromise(ctx, rp) 820 err := rp.mount.fs.impl.RemoveXattrAt(ctx, rp, name) 821 if err == nil { 822 rp.Release(ctx) 823 return nil 824 } 825 if !rp.handleError(ctx, err) { 826 rp.Release(ctx) 827 return err 828 } 829 } 830 } 831 832 // SyncAllFilesystems has the semantics of Linux's sync(2). 833 func (vfs *VirtualFilesystem) SyncAllFilesystems(ctx context.Context) error { 834 var retErr error 835 for fs := range vfs.getFilesystems() { 836 if err := fs.impl.Sync(ctx); err != nil && retErr == nil { 837 retErr = err 838 } 839 fs.DecRef(ctx) 840 } 841 return retErr 842 } 843 844 func (vfs *VirtualFilesystem) getFilesystems() map[*Filesystem]struct{} { 845 fss := make(map[*Filesystem]struct{}) 846 vfs.filesystemsMu.Lock() 847 defer vfs.filesystemsMu.Unlock() 848 for fs := range vfs.filesystems { 849 if !fs.TryIncRef() { 850 continue 851 } 852 fss[fs] = struct{}{} 853 } 854 return fss 855 } 856 857 // MkdirAllAt recursively creates non-existent directories on the given path 858 // (including the last component). 859 func (vfs *VirtualFilesystem) MkdirAllAt(ctx context.Context, currentPath string, root VirtualDentry, creds *auth.Credentials, mkdirOpts *MkdirOptions, mustBeDir bool) error { 860 pop := &PathOperation{ 861 Root: root, 862 Start: root, 863 Path: fspath.Parse(currentPath), 864 } 865 stat, err := vfs.StatAt(ctx, creds, pop, &StatOptions{Mask: linux.STATX_TYPE}) 866 switch { 867 case err == nil: 868 if mustBeDir && (stat.Mask&linux.STATX_TYPE == 0 || stat.Mode&linux.FileTypeMask != linux.ModeDirectory) { 869 return linuxerr.ENOTDIR 870 } 871 // Directory already exists. 872 return nil 873 case linuxerr.Equals(linuxerr.ENOENT, err): 874 // Expected, we will create the dir. 875 default: 876 return fmt.Errorf("stat failed for %q during directory creation: %w", currentPath, err) 877 } 878 879 // Recurse to ensure parent is created and then create the final directory. 880 if err := vfs.MkdirAllAt(ctx, path.Dir(currentPath), root, creds, mkdirOpts, true /* mustBeDir */); err != nil { 881 return err 882 } 883 if err := vfs.MkdirAt(ctx, creds, pop, mkdirOpts); err != nil { 884 return fmt.Errorf("failed to create directory %q: %w", currentPath, err) 885 } 886 return nil 887 } 888 889 // MakeSyntheticMountpoint creates parent directories of target if they do not 890 // exist and attempts to create a directory for the mountpoint. If a 891 // non-directory file already exists there then we allow it. 892 func (vfs *VirtualFilesystem) MakeSyntheticMountpoint(ctx context.Context, target string, root VirtualDentry, creds *auth.Credentials) error { 893 mkdirOpts := &MkdirOptions{Mode: 0777, ForSyntheticMountpoint: true} 894 895 // Make sure the parent directory of target exists. 896 if err := vfs.MkdirAllAt(ctx, path.Dir(target), root, creds, mkdirOpts, true /* mustBeDir */); err != nil { 897 return fmt.Errorf("failed to create parent directory of mountpoint %q: %w", target, err) 898 } 899 900 // Attempt to mkdir the final component. If a file (of any type) exists 901 // then we let allow mounting on top of that because we do not require the 902 // target to be an existing directory, unlike Linux mount(2). 903 if err := vfs.MkdirAllAt(ctx, target, root, creds, mkdirOpts, false /* mustBeDir */); err != nil { 904 return fmt.Errorf("failed to create mountpoint %q: %w", target, err) 905 } 906 return nil 907 } 908 909 // RegisterMountPromise marks vd as a mount promise. This means any VFS 910 // operation on vd will be blocked until another process mounts over it or the 911 // mount promise times out. 912 func (vfs *VirtualFilesystem) RegisterMountPromise(vd VirtualDentry) error { 913 vfs.mountPromisesMu.Lock() 914 defer vfs.mountPromisesMu.Unlock() 915 if _, ok := vfs.mountPromises[vd]; ok { 916 return fmt.Errorf("mount promise for %v already exists", vd) 917 } 918 wq := &waiter.Queue{} 919 vfs.mountPromises[vd] = wq 920 return nil 921 } 922 923 // Emit a SentryMountPromiseBlockEvent and wait for the mount promise to be 924 // resolved or time out. 925 func (vfs *VirtualFilesystem) maybeBlockOnMountPromise(ctx context.Context, rp *ResolvingPath) { 926 vd := VirtualDentry{rp.mount, rp.start} 927 vfs.mountPromisesMu.RLock() 928 wq, ok := vfs.mountPromises[vd] 929 vfs.mountPromisesMu.RUnlock() 930 if !ok { 931 return 932 } 933 934 path, err := vfs.PathnameReachable(ctx, rp.root, vd) 935 if err != nil { 936 panic(fmt.Sprintf("could not reach %v from root", rp.Component())) 937 } 938 e, ch := waiter.NewChannelEntry(waiter.EventOut) 939 wq.EventRegister(&e) 940 eventchannel.Emit(&epb.SentryMountPromiseBlockEvent{Path: path}) 941 942 select { 943 case <-ch: 944 // Update rp to point to the promised mount. 945 newMnt := vfs.getMountAt(ctx, rp.mount, rp.start) 946 rp.mount = newMnt 947 rp.start = newMnt.root 948 rp.flags = rp.flags&^rpflagsHaveStartRef | rpflagsHaveMountRef 949 case <-time.After(mountPromiseTimeout): 950 log.Warningf("mount promise for %s timed out, proceeding with VFS operation", path) 951 } 952 } 953 954 func (vfs *VirtualFilesystem) maybeResolveMountPromise(vd VirtualDentry) { 955 vfs.mountPromisesMu.Lock() 956 defer vfs.mountPromisesMu.Unlock() 957 wq, ok := vfs.mountPromises[vd] 958 if !ok { 959 return 960 } 961 wq.Notify(waiter.EventOut) 962 delete(vfs.mountPromises, vd) 963 } 964 965 // A VirtualDentry represents a node in a VFS tree, by combining a Dentry 966 // (which represents a node in a Filesystem's tree) and a Mount (which 967 // represents the Filesystem's position in a VFS mount tree). 968 // 969 // VirtualDentry's semantics are similar to that of a Go interface object 970 // representing a pointer: it is a copyable value type that represents 971 // references to another entity. The zero value of VirtualDentry is an "empty 972 // VirtualDentry", directly analogous to a nil interface object. 973 // VirtualDentry.Ok() checks that a VirtualDentry is not zero-valued; unless 974 // otherwise specified, all other VirtualDentry methods require 975 // VirtualDentry.Ok() == true. 976 // 977 // Mounts and Dentries are reference-counted, requiring that users call 978 // VirtualDentry.{Inc,Dec}Ref() as appropriate. We often colloquially refer to 979 // references on the Mount and Dentry referred to by a VirtualDentry as 980 // references on the VirtualDentry itself. Unless otherwise specified, all 981 // VirtualDentry methods require that a reference is held on the VirtualDentry. 982 // 983 // VirtualDentry is analogous to Linux's struct path. 984 // 985 // +stateify savable 986 type VirtualDentry struct { 987 mount *Mount 988 dentry *Dentry 989 } 990 991 // MakeVirtualDentry creates a VirtualDentry. 992 func MakeVirtualDentry(mount *Mount, dentry *Dentry) VirtualDentry { 993 return VirtualDentry{ 994 mount: mount, 995 dentry: dentry, 996 } 997 } 998 999 // Ok returns true if vd is not empty. It does not require that a reference is 1000 // held. 1001 func (vd VirtualDentry) Ok() bool { 1002 return vd.mount != nil 1003 } 1004 1005 // IncRef increments the reference counts on the Mount and Dentry represented 1006 // by vd. 1007 func (vd VirtualDentry) IncRef() { 1008 vd.mount.IncRef() 1009 vd.dentry.IncRef() 1010 } 1011 1012 // DecRef decrements the reference counts on the Mount and Dentry represented 1013 // by vd. 1014 func (vd VirtualDentry) DecRef(ctx context.Context) { 1015 vd.dentry.DecRef(ctx) 1016 vd.mount.DecRef(ctx) 1017 } 1018 1019 // Mount returns the Mount associated with vd. It does not take a reference on 1020 // the returned Mount. 1021 func (vd VirtualDentry) Mount() *Mount { 1022 return vd.mount 1023 } 1024 1025 // Dentry returns the Dentry associated with vd. It does not take a reference 1026 // on the returned Dentry. 1027 func (vd VirtualDentry) Dentry() *Dentry { 1028 return vd.dentry 1029 }