454 lines
14 KiB
Go
454 lines
14 KiB
Go
// Copyright 2019 The gVisor Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package vfs
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
|
|
"gvisor.dev/gvisor/pkg/abi/linux"
|
|
"gvisor.dev/gvisor/pkg/fspath"
|
|
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
|
"gvisor.dev/gvisor/pkg/syserror"
|
|
)
|
|
|
|
// ResolvingPath represents the state of an in-progress path resolution, shared
|
|
// between VFS and FilesystemImpl methods that take a path.
|
|
//
|
|
// From the perspective of FilesystemImpl methods, a ResolvingPath represents a
|
|
// starting Dentry on the associated Filesystem (on which a reference is
|
|
// already held) and a stream of path components relative to that Dentry.
|
|
//
|
|
// ResolvingPath is loosely analogous to Linux's struct nameidata.
|
|
type ResolvingPath struct {
|
|
vfs *VirtualFilesystem
|
|
root VirtualDentry // refs borrowed from PathOperation
|
|
mount *Mount
|
|
start *Dentry
|
|
pit fspath.Iterator
|
|
|
|
flags uint16
|
|
mustBeDir bool // final file must be a directory?
|
|
mustBeDirOrig bool
|
|
symlinks uint8 // number of symlinks traversed
|
|
symlinksOrig uint8
|
|
curPart uint8 // index into parts
|
|
numOrigParts uint8
|
|
|
|
creds *auth.Credentials
|
|
|
|
// Data associated with resolve*Errors, stored in ResolvingPath so that
|
|
// those errors don't need to allocate.
|
|
nextMount *Mount // ref held if not nil
|
|
nextStart *Dentry // ref held if not nil
|
|
absSymlinkTarget fspath.Path
|
|
|
|
// ResolvingPath must track up to two relative paths: the "current"
|
|
// relative path, which is updated whenever a relative symlink is
|
|
// encountered, and the "original" relative path, which is updated from the
|
|
// current relative path by handleError() when resolution must change
|
|
// filesystems (due to reaching a mount boundary or absolute symlink) and
|
|
// overwrites the current relative path when Restart() is called.
|
|
parts [1 + linux.MaxSymlinkTraversals]fspath.Iterator
|
|
origParts [1 + linux.MaxSymlinkTraversals]fspath.Iterator
|
|
}
|
|
|
|
const (
|
|
rpflagsHaveMountRef = 1 << iota // do we hold a reference on mount?
|
|
rpflagsHaveStartRef // do we hold a reference on start?
|
|
rpflagsFollowFinalSymlink // same as PathOperation.FollowFinalSymlink
|
|
)
|
|
|
|
func init() {
|
|
if maxParts := len(ResolvingPath{}.parts); maxParts > 255 {
|
|
panic(fmt.Sprintf("uint8 is insufficient to accommodate len(ResolvingPath.parts) (%d)", maxParts))
|
|
}
|
|
}
|
|
|
|
// Error types that communicate state from the FilesystemImpl-caller,
|
|
// VFS-callee side of path resolution (i.e. errors returned by
|
|
// ResolvingPath.Resolve*()) to the VFS-caller, FilesystemImpl-callee side
|
|
// (i.e. VFS methods => ResolvingPath.handleError()). These are empty structs
|
|
// rather than error values because Go doesn't support non-primitive constants,
|
|
// so error "constants" are really mutable vars, necessitating somewhat
|
|
// expensive interface object comparisons.
|
|
|
|
type resolveMountRootError struct{}
|
|
|
|
// Error implements error.Error.
|
|
func (resolveMountRootError) Error() string {
|
|
return "resolving mount root"
|
|
}
|
|
|
|
type resolveMountPointError struct{}
|
|
|
|
// Error implements error.Error.
|
|
func (resolveMountPointError) Error() string {
|
|
return "resolving mount point"
|
|
}
|
|
|
|
type resolveAbsSymlinkError struct{}
|
|
|
|
// Error implements error.Error.
|
|
func (resolveAbsSymlinkError) Error() string {
|
|
return "resolving absolute symlink"
|
|
}
|
|
|
|
var resolvingPathPool = sync.Pool{
|
|
New: func() interface{} {
|
|
return &ResolvingPath{}
|
|
},
|
|
}
|
|
|
|
func (vfs *VirtualFilesystem) getResolvingPath(creds *auth.Credentials, pop *PathOperation) (*ResolvingPath, error) {
|
|
path, err := fspath.Parse(pop.Pathname)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rp := resolvingPathPool.Get().(*ResolvingPath)
|
|
rp.vfs = vfs
|
|
rp.root = pop.Root
|
|
rp.mount = pop.Start.mount
|
|
rp.start = pop.Start.dentry
|
|
rp.pit = path.Begin
|
|
rp.flags = 0
|
|
if pop.FollowFinalSymlink {
|
|
rp.flags |= rpflagsFollowFinalSymlink
|
|
}
|
|
rp.mustBeDir = path.Dir
|
|
rp.mustBeDirOrig = path.Dir
|
|
rp.symlinks = 0
|
|
rp.curPart = 0
|
|
rp.numOrigParts = 1
|
|
rp.creds = creds
|
|
rp.parts[0] = path.Begin
|
|
rp.origParts[0] = path.Begin
|
|
return rp, nil
|
|
}
|
|
|
|
func (vfs *VirtualFilesystem) putResolvingPath(rp *ResolvingPath) {
|
|
rp.root = VirtualDentry{}
|
|
rp.decRefStartAndMount()
|
|
rp.mount = nil
|
|
rp.start = nil
|
|
rp.releaseErrorState()
|
|
resolvingPathPool.Put(rp)
|
|
}
|
|
|
|
func (rp *ResolvingPath) decRefStartAndMount() {
|
|
if rp.flags&rpflagsHaveStartRef != 0 {
|
|
rp.start.decRef(rp.mount.fs)
|
|
}
|
|
if rp.flags&rpflagsHaveMountRef != 0 {
|
|
rp.mount.decRef()
|
|
}
|
|
}
|
|
|
|
func (rp *ResolvingPath) releaseErrorState() {
|
|
if rp.nextStart != nil {
|
|
rp.nextStart.decRef(rp.nextMount.fs)
|
|
rp.nextStart = nil
|
|
}
|
|
if rp.nextMount != nil {
|
|
rp.nextMount.decRef()
|
|
rp.nextMount = nil
|
|
}
|
|
}
|
|
|
|
// VirtualFilesystem returns the containing VirtualFilesystem.
|
|
func (rp *ResolvingPath) VirtualFilesystem() *VirtualFilesystem {
|
|
return rp.vfs
|
|
}
|
|
|
|
// Credentials returns the credentials of rp's provider.
|
|
func (rp *ResolvingPath) Credentials() *auth.Credentials {
|
|
return rp.creds
|
|
}
|
|
|
|
// Mount returns the Mount on which path resolution is currently occurring. It
|
|
// does not take a reference on the returned Mount.
|
|
func (rp *ResolvingPath) Mount() *Mount {
|
|
return rp.mount
|
|
}
|
|
|
|
// Start returns the starting Dentry represented by rp. It does not take a
|
|
// reference on the returned Dentry.
|
|
func (rp *ResolvingPath) Start() *Dentry {
|
|
return rp.start
|
|
}
|
|
|
|
// Done returns true if there are no remaining path components in the stream
|
|
// represented by rp.
|
|
func (rp *ResolvingPath) Done() bool {
|
|
// We don't need to check for rp.curPart == 0 because rp.Advance() won't
|
|
// set rp.pit to a terminal iterator otherwise.
|
|
return !rp.pit.Ok()
|
|
}
|
|
|
|
// Final returns true if there is exactly one remaining path component in the
|
|
// stream represented by rp.
|
|
//
|
|
// Preconditions: !rp.Done().
|
|
func (rp *ResolvingPath) Final() bool {
|
|
return rp.curPart == 0 && !rp.pit.NextOk()
|
|
}
|
|
|
|
// Component returns the current path component in the stream represented by
|
|
// rp.
|
|
//
|
|
// Preconditions: !rp.Done().
|
|
func (rp *ResolvingPath) Component() string {
|
|
if checkInvariants {
|
|
if !rp.pit.Ok() {
|
|
panic("ResolvingPath.Component() called at end of relative path")
|
|
}
|
|
}
|
|
return rp.pit.String()
|
|
}
|
|
|
|
// Advance advances the stream of path components represented by rp.
|
|
//
|
|
// Preconditions: !rp.Done().
|
|
func (rp *ResolvingPath) Advance() {
|
|
if checkInvariants {
|
|
if !rp.pit.Ok() {
|
|
panic("ResolvingPath.Advance() called at end of relative path")
|
|
}
|
|
}
|
|
next := rp.pit.Next()
|
|
if next.Ok() || rp.curPart == 0 { // have next component, or at end of path
|
|
rp.pit = next
|
|
} else { // at end of path segment, continue with next one
|
|
rp.curPart--
|
|
rp.pit = rp.parts[rp.curPart-1]
|
|
}
|
|
}
|
|
|
|
// Restart resets the stream of path components represented by rp to its state
|
|
// on entry to the current FilesystemImpl method.
|
|
func (rp *ResolvingPath) Restart() {
|
|
rp.pit = rp.origParts[rp.numOrigParts-1]
|
|
rp.mustBeDir = rp.mustBeDirOrig
|
|
rp.symlinks = rp.symlinksOrig
|
|
rp.curPart = rp.numOrigParts - 1
|
|
copy(rp.parts[:], rp.origParts[:rp.numOrigParts])
|
|
rp.releaseErrorState()
|
|
}
|
|
|
|
func (rp *ResolvingPath) relpathCommit() {
|
|
rp.mustBeDirOrig = rp.mustBeDir
|
|
rp.symlinksOrig = rp.symlinks
|
|
rp.numOrigParts = rp.curPart + 1
|
|
copy(rp.origParts[:rp.curPart], rp.parts[:])
|
|
rp.origParts[rp.curPart] = rp.pit
|
|
}
|
|
|
|
// ResolveParent returns the VFS parent of d. It does not take a reference on
|
|
// the returned Dentry.
|
|
//
|
|
// Preconditions: There are no concurrent mutators of d.
|
|
//
|
|
// Postconditions: If the returned error is nil, then the returned Dentry is
|
|
// not nil.
|
|
func (rp *ResolvingPath) ResolveParent(d *Dentry) (*Dentry, error) {
|
|
var parent *Dentry
|
|
if d == rp.root.dentry && rp.mount == rp.root.mount {
|
|
// At contextual VFS root.
|
|
parent = d
|
|
} else if d == rp.mount.root {
|
|
// At mount root ...
|
|
mnt, mntpt := rp.vfs.getMountpointAt(rp.mount, rp.root)
|
|
if mnt != nil {
|
|
// ... of non-root mount.
|
|
rp.nextMount = mnt
|
|
rp.nextStart = mntpt
|
|
return nil, resolveMountRootError{}
|
|
}
|
|
// ... of root mount.
|
|
parent = d
|
|
} else if d.parent == nil {
|
|
// At filesystem root.
|
|
parent = d
|
|
} else {
|
|
parent = d.parent
|
|
}
|
|
if parent.isMounted() {
|
|
if mnt := rp.vfs.getMountAt(rp.mount, parent); mnt != nil {
|
|
rp.nextMount = mnt
|
|
return nil, resolveMountPointError{}
|
|
}
|
|
}
|
|
return parent, nil
|
|
}
|
|
|
|
// ResolveChild returns the VFS child of d with the given name. It does not
|
|
// take a reference on the returned Dentry. If no such child exists,
|
|
// ResolveChild returns (nil, nil).
|
|
//
|
|
// Preconditions: There are no concurrent mutators of d.
|
|
func (rp *ResolvingPath) ResolveChild(d *Dentry, name string) (*Dentry, error) {
|
|
child := d.children[name]
|
|
if child == nil {
|
|
return nil, nil
|
|
}
|
|
if child.isMounted() {
|
|
if mnt := rp.vfs.getMountAt(rp.mount, child); mnt != nil {
|
|
rp.nextMount = mnt
|
|
return nil, resolveMountPointError{}
|
|
}
|
|
}
|
|
return child, nil
|
|
}
|
|
|
|
// ResolveComponent returns the Dentry reached by starting at d and resolving
|
|
// the current path component in the stream represented by rp. It does not
|
|
// advance the stream. It does not take a reference on the returned Dentry. If
|
|
// no such Dentry exists, ResolveComponent returns (nil, nil).
|
|
//
|
|
// Preconditions: !rp.Done(). There are no concurrent mutators of d.
|
|
func (rp *ResolvingPath) ResolveComponent(d *Dentry) (*Dentry, error) {
|
|
switch pc := rp.Component(); pc {
|
|
case ".":
|
|
return d, nil
|
|
case "..":
|
|
return rp.ResolveParent(d)
|
|
default:
|
|
return rp.ResolveChild(d, pc)
|
|
}
|
|
}
|
|
|
|
// ShouldFollowSymlink returns true if, supposing that the current path
|
|
// component in pcs represents a symbolic link, the symbolic link should be
|
|
// followed.
|
|
//
|
|
// Preconditions: !rp.Done().
|
|
func (rp *ResolvingPath) ShouldFollowSymlink() bool {
|
|
// Non-final symlinks are always followed.
|
|
return rp.flags&rpflagsFollowFinalSymlink != 0 || !rp.Final()
|
|
}
|
|
|
|
// HandleSymlink is called when the current path component is a symbolic link
|
|
// to the given target. If the calling Filesystem method should continue path
|
|
// traversal, HandleSymlink updates the path component stream to reflect the
|
|
// symlink target and returns nil. Otherwise it returns a non-nil error.
|
|
//
|
|
// Preconditions: !rp.Done().
|
|
func (rp *ResolvingPath) HandleSymlink(target string) error {
|
|
if rp.symlinks >= linux.MaxSymlinkTraversals {
|
|
return syserror.ELOOP
|
|
}
|
|
targetPath, err := fspath.Parse(target)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rp.symlinks++
|
|
if targetPath.Absolute {
|
|
rp.absSymlinkTarget = targetPath
|
|
return resolveAbsSymlinkError{}
|
|
}
|
|
if !targetPath.Begin.Ok() {
|
|
panic(fmt.Sprintf("symbolic link has non-empty target %q that is both relative and has no path components?", target))
|
|
}
|
|
// Consume the path component that represented the symlink.
|
|
rp.Advance()
|
|
// Prepend the symlink target to the relative path.
|
|
rp.relpathPrepend(targetPath)
|
|
return nil
|
|
}
|
|
|
|
func (rp *ResolvingPath) relpathPrepend(path fspath.Path) {
|
|
if rp.pit.Ok() {
|
|
rp.parts[rp.curPart] = rp.pit
|
|
rp.pit = path.Begin
|
|
rp.curPart++
|
|
} else {
|
|
// The symlink was the final path component, so now the symlink target
|
|
// is the whole path.
|
|
rp.pit = path.Begin
|
|
// Symlink targets can set rp.mustBeDir (if they end in a trailing /),
|
|
// but can't unset it.
|
|
if path.Dir {
|
|
rp.mustBeDir = true
|
|
}
|
|
}
|
|
}
|
|
|
|
func (rp *ResolvingPath) handleError(err error) bool {
|
|
switch err.(type) {
|
|
case resolveMountRootError:
|
|
// Switch to the new Mount. We hold references on the Mount and Dentry
|
|
// (from VFS.getMountpointAt()).
|
|
rp.decRefStartAndMount()
|
|
rp.mount = rp.nextMount
|
|
rp.start = rp.nextStart
|
|
rp.flags |= rpflagsHaveMountRef | rpflagsHaveStartRef
|
|
rp.nextMount = nil
|
|
rp.nextStart = nil
|
|
// Commit the previous FileystemImpl's progress through the relative
|
|
// path. (Don't consume the path component that caused us to traverse
|
|
// through the mount root - i.e. the ".." - because we still need to
|
|
// resolve the mount point's parent in the new FilesystemImpl.)
|
|
rp.relpathCommit()
|
|
// Restart path resolution on the new Mount. Don't bother calling
|
|
// rp.releaseErrorState() since we already set nextMount and nextStart
|
|
// to nil above.
|
|
return true
|
|
|
|
case resolveMountPointError:
|
|
// Switch to the new Mount. We hold a reference on the Mount (from
|
|
// VFS.getMountAt()), but borrow the reference on the mount root from
|
|
// the Mount.
|
|
rp.decRefStartAndMount()
|
|
rp.mount = rp.nextMount
|
|
rp.start = rp.nextMount.root
|
|
rp.flags = rp.flags&^rpflagsHaveStartRef | rpflagsHaveMountRef
|
|
rp.nextMount = nil
|
|
// Consume the path component that represented the mount point.
|
|
rp.Advance()
|
|
// Commit the previous FilesystemImpl's progress through the relative
|
|
// path.
|
|
rp.relpathCommit()
|
|
// Restart path resolution on the new Mount.
|
|
rp.releaseErrorState()
|
|
return true
|
|
|
|
case resolveAbsSymlinkError:
|
|
// Switch to the new Mount. References are borrowed from rp.root.
|
|
rp.decRefStartAndMount()
|
|
rp.mount = rp.root.mount
|
|
rp.start = rp.root.dentry
|
|
rp.flags &^= rpflagsHaveMountRef | rpflagsHaveStartRef
|
|
// Consume the path component that represented the symlink.
|
|
rp.Advance()
|
|
// Prepend the symlink target to the relative path.
|
|
rp.relpathPrepend(rp.absSymlinkTarget)
|
|
// Commit the previous FilesystemImpl's progress through the relative
|
|
// path, including the symlink target we just prepended.
|
|
rp.relpathCommit()
|
|
// Restart path resolution on the new Mount.
|
|
rp.releaseErrorState()
|
|
return true
|
|
|
|
default:
|
|
// Not an error we can handle.
|
|
return false
|
|
}
|
|
}
|
|
|
|
// MustBeDir returns true if the file traversed by rp must be a directory.
|
|
func (rp *ResolvingPath) MustBeDir() bool {
|
|
return rp.mustBeDir
|
|
}
|