2019-07-18 22:09:14 +00:00
|
|
|
// 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"
|
|
|
|
|
|
|
|
"gvisor.dev/gvisor/pkg/abi/linux"
|
|
|
|
"gvisor.dev/gvisor/pkg/fspath"
|
|
|
|
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
2020-01-10 06:00:42 +00:00
|
|
|
"gvisor.dev/gvisor/pkg/sync"
|
2019-07-18 22:09:14 +00:00
|
|
|
"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
|
2020-04-21 19:16:42 +00:00
|
|
|
// already held), a stream of path components relative to that Dentry, and
|
|
|
|
// elements of the invoking Context that are commonly required by
|
|
|
|
// FilesystemImpl methods.
|
2019-07-18 22:09:14 +00:00
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
|
2019-12-21 01:40:18 +00:00
|
|
|
type resolveMountRootOrJumpError struct{}
|
2019-07-18 22:09:14 +00:00
|
|
|
|
|
|
|
// Error implements error.Error.
|
2019-12-21 01:40:18 +00:00
|
|
|
func (resolveMountRootOrJumpError) Error() string {
|
|
|
|
return "resolving mount root or jump"
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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{}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2019-12-23 21:17:29 +00:00
|
|
|
func (vfs *VirtualFilesystem) getResolvingPath(creds *auth.Credentials, pop *PathOperation) *ResolvingPath {
|
2019-07-18 22:09:14 +00:00
|
|
|
rp := resolvingPathPool.Get().(*ResolvingPath)
|
|
|
|
rp.vfs = vfs
|
|
|
|
rp.root = pop.Root
|
|
|
|
rp.mount = pop.Start.mount
|
|
|
|
rp.start = pop.Start.dentry
|
2019-12-23 21:17:29 +00:00
|
|
|
rp.pit = pop.Path.Begin
|
2019-07-18 22:09:14 +00:00
|
|
|
rp.flags = 0
|
|
|
|
if pop.FollowFinalSymlink {
|
|
|
|
rp.flags |= rpflagsFollowFinalSymlink
|
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
rp.mustBeDir = pop.Path.Dir
|
|
|
|
rp.mustBeDirOrig = pop.Path.Dir
|
2019-07-18 22:09:14 +00:00
|
|
|
rp.symlinks = 0
|
|
|
|
rp.curPart = 0
|
|
|
|
rp.numOrigParts = 1
|
|
|
|
rp.creds = creds
|
2019-12-23 21:17:29 +00:00
|
|
|
rp.parts[0] = pop.Path.Begin
|
|
|
|
rp.origParts[0] = pop.Path.Begin
|
|
|
|
return rp
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
Minor VFS2 interface changes.
- Remove the Filesystem argument from DentryImpl.*Ref(); in general DentryImpls
that need the Filesystem for reference counting will probably also need it
for other interface methods that don't plumb Filesystem, so it's easier to
just store a pointer to the filesystem in the DentryImpl.
- Add a pointer to the VirtualFilesystem to Filesystem, which is needed by the
gofer client to disown dentries for cache eviction triggered by dentry
reference count changes.
- Rename FilesystemType.NewFilesystem to GetFilesystem; in some cases (e.g.
sysfs, cgroupfs) it's much cleaner for there to be only one Filesystem that
is used by all mounts, and in at least one case (devtmpfs) it's visibly
incorrect not to do so, so NewFilesystem doesn't always actually create and
return a *new* Filesystem.
- Require callers of FileDescription.Init() to increment Mount/Dentry
references. This is because the gofer client may, in the OpenAt() path, take
a reference on a dentry with 0 references, which is safe due to
synchronization that is outside the scope of this CL, and it would be safer
to still have its implementation of DentryImpl.IncRef() check for an
increment for 0 references in other cases.
- Add FileDescription.TryIncRef. This is used by the gofer client to take
references on "special file descriptions" (FDs for files such as pipes,
sockets, and devices), which use per-FD handles (fids) instead of
dentry-shared handles, for sync() and syncfs().
PiperOrigin-RevId: 282473364
2019-11-26 02:09:15 +00:00
|
|
|
rp.start.DecRef()
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
if rp.flags&rpflagsHaveMountRef != 0 {
|
Minor VFS2 interface changes.
- Remove the Filesystem argument from DentryImpl.*Ref(); in general DentryImpls
that need the Filesystem for reference counting will probably also need it
for other interface methods that don't plumb Filesystem, so it's easier to
just store a pointer to the filesystem in the DentryImpl.
- Add a pointer to the VirtualFilesystem to Filesystem, which is needed by the
gofer client to disown dentries for cache eviction triggered by dentry
reference count changes.
- Rename FilesystemType.NewFilesystem to GetFilesystem; in some cases (e.g.
sysfs, cgroupfs) it's much cleaner for there to be only one Filesystem that
is used by all mounts, and in at least one case (devtmpfs) it's visibly
incorrect not to do so, so NewFilesystem doesn't always actually create and
return a *new* Filesystem.
- Require callers of FileDescription.Init() to increment Mount/Dentry
references. This is because the gofer client may, in the OpenAt() path, take
a reference on a dentry with 0 references, which is safe due to
synchronization that is outside the scope of this CL, and it would be safer
to still have its implementation of DentryImpl.IncRef() check for an
increment for 0 references in other cases.
- Add FileDescription.TryIncRef. This is used by the gofer client to take
references on "special file descriptions" (FDs for files such as pipes,
sockets, and devices), which use per-FD handles (fids) instead of
dentry-shared handles, for sync() and syncfs().
PiperOrigin-RevId: 282473364
2019-11-26 02:09:15 +00:00
|
|
|
rp.mount.DecRef()
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rp *ResolvingPath) releaseErrorState() {
|
|
|
|
if rp.nextStart != nil {
|
Minor VFS2 interface changes.
- Remove the Filesystem argument from DentryImpl.*Ref(); in general DentryImpls
that need the Filesystem for reference counting will probably also need it
for other interface methods that don't plumb Filesystem, so it's easier to
just store a pointer to the filesystem in the DentryImpl.
- Add a pointer to the VirtualFilesystem to Filesystem, which is needed by the
gofer client to disown dentries for cache eviction triggered by dentry
reference count changes.
- Rename FilesystemType.NewFilesystem to GetFilesystem; in some cases (e.g.
sysfs, cgroupfs) it's much cleaner for there to be only one Filesystem that
is used by all mounts, and in at least one case (devtmpfs) it's visibly
incorrect not to do so, so NewFilesystem doesn't always actually create and
return a *new* Filesystem.
- Require callers of FileDescription.Init() to increment Mount/Dentry
references. This is because the gofer client may, in the OpenAt() path, take
a reference on a dentry with 0 references, which is safe due to
synchronization that is outside the scope of this CL, and it would be safer
to still have its implementation of DentryImpl.IncRef() check for an
increment for 0 references in other cases.
- Add FileDescription.TryIncRef. This is used by the gofer client to take
references on "special file descriptions" (FDs for files such as pipes,
sockets, and devices), which use per-FD handles (fids) instead of
dentry-shared handles, for sync() and syncfs().
PiperOrigin-RevId: 282473364
2019-11-26 02:09:15 +00:00
|
|
|
rp.nextStart.DecRef()
|
2019-07-18 22:09:14 +00:00
|
|
|
rp.nextStart = nil
|
|
|
|
}
|
|
|
|
if rp.nextMount != nil {
|
Minor VFS2 interface changes.
- Remove the Filesystem argument from DentryImpl.*Ref(); in general DentryImpls
that need the Filesystem for reference counting will probably also need it
for other interface methods that don't plumb Filesystem, so it's easier to
just store a pointer to the filesystem in the DentryImpl.
- Add a pointer to the VirtualFilesystem to Filesystem, which is needed by the
gofer client to disown dentries for cache eviction triggered by dentry
reference count changes.
- Rename FilesystemType.NewFilesystem to GetFilesystem; in some cases (e.g.
sysfs, cgroupfs) it's much cleaner for there to be only one Filesystem that
is used by all mounts, and in at least one case (devtmpfs) it's visibly
incorrect not to do so, so NewFilesystem doesn't always actually create and
return a *new* Filesystem.
- Require callers of FileDescription.Init() to increment Mount/Dentry
references. This is because the gofer client may, in the OpenAt() path, take
a reference on a dentry with 0 references, which is safe due to
synchronization that is outside the scope of this CL, and it would be safer
to still have its implementation of DentryImpl.IncRef() check for an
increment for 0 references in other cases.
- Add FileDescription.TryIncRef. This is used by the gofer client to take
references on "special file descriptions" (FDs for files such as pipes,
sockets, and devices), which use per-FD handles (fids) instead of
dentry-shared handles, for sync() and syncfs().
PiperOrigin-RevId: 282473364
2019-11-26 02:09:15 +00:00
|
|
|
rp.nextMount.DecRef()
|
2019-07-18 22:09:14 +00:00
|
|
|
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--
|
2020-02-25 21:25:36 +00:00
|
|
|
rp.pit = rp.parts[rp.curPart]
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2020-04-21 19:16:42 +00:00
|
|
|
// CheckRoot is called before resolving the parent of the Dentry d. If the
|
|
|
|
// Dentry is contextually a VFS root, such that path resolution should treat
|
|
|
|
// d's parent as itself, CheckRoot returns (true, nil). If the Dentry is the
|
|
|
|
// root of a non-root mount, such that path resolution should switch to another
|
|
|
|
// Mount, CheckRoot returns (unspecified, non-nil error). Otherwise, path
|
|
|
|
// resolution should resolve d's parent normally, and CheckRoot returns (false,
|
|
|
|
// nil).
|
|
|
|
func (rp *ResolvingPath) CheckRoot(d *Dentry) (bool, error) {
|
2019-07-18 22:09:14 +00:00
|
|
|
if d == rp.root.dentry && rp.mount == rp.root.mount {
|
2020-04-21 19:16:42 +00:00
|
|
|
// At contextual VFS root (due to e.g. chroot(2)).
|
|
|
|
return true, nil
|
2019-07-18 22:09:14 +00:00
|
|
|
} else if d == rp.mount.root {
|
|
|
|
// At mount root ...
|
2019-11-25 23:20:25 +00:00
|
|
|
vd := rp.vfs.getMountpointAt(rp.mount, rp.root)
|
|
|
|
if vd.Ok() {
|
2019-07-18 22:09:14 +00:00
|
|
|
// ... of non-root mount.
|
2019-11-25 23:20:25 +00:00
|
|
|
rp.nextMount = vd.mount
|
|
|
|
rp.nextStart = vd.dentry
|
2020-04-21 19:16:42 +00:00
|
|
|
return false, resolveMountRootOrJumpError{}
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
// ... of root mount.
|
2020-04-21 19:16:42 +00:00
|
|
|
return true, nil
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
2020-04-21 19:16:42 +00:00
|
|
|
return false, nil
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
|
2020-04-21 19:16:42 +00:00
|
|
|
// CheckMount is called after resolving the parent or child of another Dentry
|
|
|
|
// to d. If d is a mount point, such that path resolution should switch to
|
|
|
|
// another Mount, CheckMount returns a non-nil error. Otherwise, CheckMount
|
|
|
|
// returns nil.
|
|
|
|
func (rp *ResolvingPath) CheckMount(d *Dentry) error {
|
|
|
|
if !d.isMounted() {
|
|
|
|
return nil
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
2020-04-21 19:16:42 +00:00
|
|
|
if mnt := rp.vfs.getMountAt(rp.mount, d); mnt != nil {
|
|
|
|
rp.nextMount = mnt
|
|
|
|
return resolveMountPointError{}
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
2020-04-21 19:16:42 +00:00
|
|
|
return nil
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ShouldFollowSymlink returns true if, supposing that the current path
|
|
|
|
// component in pcs represents a symbolic link, the symbolic link should be
|
|
|
|
// followed.
|
|
|
|
//
|
2020-03-25 21:54:10 +00:00
|
|
|
// If path is terminated with '/', the '/' is considered the last element and
|
|
|
|
// any symlink before that is followed:
|
|
|
|
// - For most non-creating walks, the last path component is handled by
|
|
|
|
// fs/namei.c:lookup_last(), which sets LOOKUP_FOLLOW if the first byte
|
|
|
|
// after the path component is non-NULL (which is only possible if it's '/')
|
|
|
|
// and the path component is of type LAST_NORM.
|
|
|
|
//
|
|
|
|
// - For open/openat/openat2 without O_CREAT, the last path component is
|
|
|
|
// handled by fs/namei.c:do_last(), which does the same, though without the
|
|
|
|
// LAST_NORM check.
|
|
|
|
//
|
2019-07-18 22:09:14 +00:00
|
|
|
// Preconditions: !rp.Done().
|
|
|
|
func (rp *ResolvingPath) ShouldFollowSymlink() bool {
|
2020-03-25 21:54:10 +00:00
|
|
|
// Non-final symlinks are always followed. Paths terminated with '/' are also
|
|
|
|
// always followed.
|
|
|
|
return rp.flags&rpflagsFollowFinalSymlink != 0 || !rp.Final() || rp.MustBeDir()
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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().
|
2019-12-23 21:17:29 +00:00
|
|
|
//
|
|
|
|
// Postconditions: If HandleSymlink returns a nil error, then !rp.Done().
|
2019-07-18 22:09:14 +00:00
|
|
|
func (rp *ResolvingPath) HandleSymlink(target string) error {
|
|
|
|
if rp.symlinks >= linux.MaxSymlinkTraversals {
|
|
|
|
return syserror.ELOOP
|
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
if len(target) == 0 {
|
|
|
|
return syserror.ENOENT
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
rp.symlinks++
|
2019-12-23 21:17:29 +00:00
|
|
|
targetPath := fspath.Parse(target)
|
2019-07-18 22:09:14 +00:00
|
|
|
if targetPath.Absolute {
|
|
|
|
rp.absSymlinkTarget = targetPath
|
|
|
|
return resolveAbsSymlinkError{}
|
|
|
|
}
|
|
|
|
// Consume the path component that represented the symlink.
|
|
|
|
rp.Advance()
|
|
|
|
// Prepend the symlink target to the relative path.
|
2019-12-23 21:17:29 +00:00
|
|
|
if checkInvariants {
|
|
|
|
if !targetPath.HasComponents() {
|
|
|
|
panic(fmt.Sprintf("non-empty pathname %q parsed to relative path with no components", target))
|
|
|
|
}
|
|
|
|
}
|
2019-07-18 22:09:14 +00:00
|
|
|
rp.relpathPrepend(targetPath)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-12-23 21:17:29 +00:00
|
|
|
// Preconditions: path.HasComponents().
|
2019-07-18 22:09:14 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-21 01:40:18 +00:00
|
|
|
// HandleJump is called when the current path component is a "magic" link to
|
|
|
|
// the given VirtualDentry, like /proc/[pid]/fd/[fd]. If the calling Filesystem
|
|
|
|
// method should continue path traversal, HandleMagicSymlink updates the path
|
|
|
|
// component stream to reflect the magic link target and returns nil. Otherwise
|
|
|
|
// it returns a non-nil error.
|
|
|
|
//
|
|
|
|
// Preconditions: !rp.Done().
|
|
|
|
func (rp *ResolvingPath) HandleJump(target VirtualDentry) error {
|
|
|
|
if rp.symlinks >= linux.MaxSymlinkTraversals {
|
|
|
|
return syserror.ELOOP
|
|
|
|
}
|
|
|
|
rp.symlinks++
|
|
|
|
// Consume the path component that represented the magic link.
|
|
|
|
rp.Advance()
|
|
|
|
// Unconditionally return a resolveMountRootOrJumpError, even if the Mount
|
|
|
|
// isn't changing, to force restarting at the new Dentry.
|
|
|
|
target.IncRef()
|
|
|
|
rp.nextMount = target.mount
|
|
|
|
rp.nextStart = target.dentry
|
|
|
|
return resolveMountRootOrJumpError{}
|
|
|
|
}
|
|
|
|
|
2019-07-18 22:09:14 +00:00
|
|
|
func (rp *ResolvingPath) handleError(err error) bool {
|
|
|
|
switch err.(type) {
|
2019-12-21 01:40:18 +00:00
|
|
|
case resolveMountRootOrJumpError:
|
|
|
|
// Switch to the new Mount. We hold references on the Mount and Dentry.
|
2019-07-18 22:09:14 +00:00
|
|
|
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:
|
2019-12-21 01:40:18 +00:00
|
|
|
// Switch to the new Mount. We hold a reference on the Mount, but
|
|
|
|
// borrow the reference on the mount root from the Mount.
|
2019-07-18 22:09:14 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-23 21:17:29 +00:00
|
|
|
// canHandleError returns true if err is an error returned by rp.Resolve*()
|
|
|
|
// that rp.handleError() may attempt to handle.
|
|
|
|
func (rp *ResolvingPath) canHandleError(err error) bool {
|
|
|
|
switch err.(type) {
|
|
|
|
case resolveMountRootOrJumpError, resolveMountPointError, resolveAbsSymlinkError:
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-18 22:09:14 +00:00
|
|
|
// MustBeDir returns true if the file traversed by rp must be a directory.
|
|
|
|
func (rp *ResolvingPath) MustBeDir() bool {
|
|
|
|
return rp.mustBeDir
|
|
|
|
}
|