gvisor/pkg/sentry/vfs/pathname.go

196 lines
5.2 KiB
Go
Raw Normal View History

// 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 (
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/fspath"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/syserror"
)
var fspathBuilderPool = sync.Pool{
New: func() interface{} {
return &fspath.Builder{}
},
}
func getFSPathBuilder() *fspath.Builder {
return fspathBuilderPool.Get().(*fspath.Builder)
}
func putFSPathBuilder(b *fspath.Builder) {
// No methods can be called on b after b.String(), so reset it to its zero
// value (as returned by fspathBuilderPool.New) instead.
*b = fspath.Builder{}
fspathBuilderPool.Put(b)
}
// PathnameWithDeleted returns an absolute pathname to vd, consistent with
// Linux's d_path(). In particular, if vd.Dentry() has been disowned,
// PathnameWithDeleted appends " (deleted)" to the returned pathname.
func (vfs *VirtualFilesystem) PathnameWithDeleted(ctx context.Context, vfsroot, vd VirtualDentry) (string, error) {
b := getFSPathBuilder()
defer putFSPathBuilder(b)
haveRef := false
defer func() {
if haveRef {
vd.DecRef(ctx)
}
}()
origD := vd.dentry
loop:
for {
err := vd.mount.fs.impl.PrependPath(ctx, vfsroot, vd, b)
switch err.(type) {
case nil:
if vd.mount == vfsroot.mount && vd.mount.root == vfsroot.dentry {
Remove filesystem structure from vfs.Dentry. This change: - Drastically simplifies the synchronization model: filesystem structure is both implementation-defined and implementation-synchronized. - Allows implementations of vfs.DentryImpl to use implementation-specific dentry types, reducing casts during path traversal. - Doesn't require dentries representing non-directory files to waste space on a map of children. - Allows dentry revalidation and mount lookup to be correctly ordered (fixed FIXME in fsimpl/gofer/filesystem.go). - Removes the need to have two separate maps in gofer.dentry (dentry.vfsd.children and dentry.negativeChildren) for positive and negative lookups respectively. //pkg/sentry/fsimpl/tmpfs/benchmark_test.go: name old time/op new time/op delta VFS2TmpfsStat/1-112 172ns ± 4% 165ns ± 3% -4.08% (p=0.002 n=9+9) VFS2TmpfsStat/2-112 199ns ± 3% 195ns ±10% ~ (p=0.132 n=8+9) VFS2TmpfsStat/3-112 230ns ± 2% 216ns ± 2% -6.15% (p=0.000 n=8+8) VFS2TmpfsStat/8-112 390ns ± 2% 358ns ± 4% -8.33% (p=0.000 n=9+8) VFS2TmpfsStat/64-112 2.20µs ± 3% 2.01µs ± 3% -8.48% (p=0.000 n=10+8) VFS2TmpfsStat/100-112 3.42µs ± 9% 3.08µs ± 2% -9.82% (p=0.000 n=9+8) VFS2TmpfsMountStat/1-112 278ns ± 1% 286ns ±15% ~ (p=0.712 n=8+10) VFS2TmpfsMountStat/2-112 311ns ± 4% 298ns ± 2% -4.27% (p=0.000 n=9+8) VFS2TmpfsMountStat/3-112 339ns ± 3% 330ns ± 9% ~ (p=0.070 n=8+9) VFS2TmpfsMountStat/8-112 503ns ± 3% 466ns ± 3% -7.38% (p=0.000 n=8+8) VFS2TmpfsMountStat/64-112 2.53µs ±16% 2.17µs ± 7% -14.19% (p=0.000 n=10+9) VFS2TmpfsMountStat/100-112 3.60µs ± 4% 3.30µs ± 8% -8.33% (p=0.001 n=8+9) Updates #1035 PiperOrigin-RevId: 307655892
2020-04-21 19:16:42 +00:00
// genericfstree.PrependPath() will have returned
// PrependPathAtVFSRootError in this case since it checks
// against vfsroot before mnt.root, but other implementations
// of FilesystemImpl.PrependPath() may return nil instead.
break loop
}
nextVD := vfs.getMountpointAt(ctx, vd.mount, vfsroot)
if !nextVD.Ok() {
break loop
}
if haveRef {
vd.DecRef(ctx)
}
vd = nextVD
haveRef = true
// continue loop
case PrependPathSyntheticError:
// Skip prepending "/" and appending " (deleted)".
return b.String(), nil
case PrependPathAtVFSRootError, PrependPathAtNonMountRootError:
break loop
default:
return "", err
}
}
b.PrependByte('/')
Remove filesystem structure from vfs.Dentry. This change: - Drastically simplifies the synchronization model: filesystem structure is both implementation-defined and implementation-synchronized. - Allows implementations of vfs.DentryImpl to use implementation-specific dentry types, reducing casts during path traversal. - Doesn't require dentries representing non-directory files to waste space on a map of children. - Allows dentry revalidation and mount lookup to be correctly ordered (fixed FIXME in fsimpl/gofer/filesystem.go). - Removes the need to have two separate maps in gofer.dentry (dentry.vfsd.children and dentry.negativeChildren) for positive and negative lookups respectively. //pkg/sentry/fsimpl/tmpfs/benchmark_test.go: name old time/op new time/op delta VFS2TmpfsStat/1-112 172ns ± 4% 165ns ± 3% -4.08% (p=0.002 n=9+9) VFS2TmpfsStat/2-112 199ns ± 3% 195ns ±10% ~ (p=0.132 n=8+9) VFS2TmpfsStat/3-112 230ns ± 2% 216ns ± 2% -6.15% (p=0.000 n=8+8) VFS2TmpfsStat/8-112 390ns ± 2% 358ns ± 4% -8.33% (p=0.000 n=9+8) VFS2TmpfsStat/64-112 2.20µs ± 3% 2.01µs ± 3% -8.48% (p=0.000 n=10+8) VFS2TmpfsStat/100-112 3.42µs ± 9% 3.08µs ± 2% -9.82% (p=0.000 n=9+8) VFS2TmpfsMountStat/1-112 278ns ± 1% 286ns ±15% ~ (p=0.712 n=8+10) VFS2TmpfsMountStat/2-112 311ns ± 4% 298ns ± 2% -4.27% (p=0.000 n=9+8) VFS2TmpfsMountStat/3-112 339ns ± 3% 330ns ± 9% ~ (p=0.070 n=8+9) VFS2TmpfsMountStat/8-112 503ns ± 3% 466ns ± 3% -7.38% (p=0.000 n=8+8) VFS2TmpfsMountStat/64-112 2.53µs ±16% 2.17µs ± 7% -14.19% (p=0.000 n=10+9) VFS2TmpfsMountStat/100-112 3.60µs ± 4% 3.30µs ± 8% -8.33% (p=0.001 n=8+9) Updates #1035 PiperOrigin-RevId: 307655892
2020-04-21 19:16:42 +00:00
if origD.IsDead() {
b.AppendString(" (deleted)")
}
return b.String(), nil
}
// PathnameReachable returns an absolute pathname to vd, consistent with
// Linux's __d_path() (as used by seq_path_root()). If vfsroot.Ok() and vd is
// not reachable from vfsroot, such that seq_path_root() would return SEQ_SKIP
// (causing the entire containing entry to be skipped), PathnameReachable
// returns ("", nil).
func (vfs *VirtualFilesystem) PathnameReachable(ctx context.Context, vfsroot, vd VirtualDentry) (string, error) {
b := getFSPathBuilder()
defer putFSPathBuilder(b)
haveRef := false
defer func() {
if haveRef {
vd.DecRef(ctx)
}
}()
loop:
for {
err := vd.mount.fs.impl.PrependPath(ctx, vfsroot, vd, b)
switch err.(type) {
case nil:
if vd.mount == vfsroot.mount && vd.mount.root == vfsroot.dentry {
break loop
}
nextVD := vfs.getMountpointAt(ctx, vd.mount, vfsroot)
if !nextVD.Ok() {
return "", nil
}
if haveRef {
vd.DecRef(ctx)
}
vd = nextVD
haveRef = true
case PrependPathAtVFSRootError:
break loop
case PrependPathAtNonMountRootError, PrependPathSyntheticError:
return "", nil
default:
return "", err
}
}
b.PrependByte('/')
return b.String(), nil
}
// PathnameForGetcwd returns an absolute pathname to vd, consistent with
// Linux's sys_getcwd().
func (vfs *VirtualFilesystem) PathnameForGetcwd(ctx context.Context, vfsroot, vd VirtualDentry) (string, error) {
Remove filesystem structure from vfs.Dentry. This change: - Drastically simplifies the synchronization model: filesystem structure is both implementation-defined and implementation-synchronized. - Allows implementations of vfs.DentryImpl to use implementation-specific dentry types, reducing casts during path traversal. - Doesn't require dentries representing non-directory files to waste space on a map of children. - Allows dentry revalidation and mount lookup to be correctly ordered (fixed FIXME in fsimpl/gofer/filesystem.go). - Removes the need to have two separate maps in gofer.dentry (dentry.vfsd.children and dentry.negativeChildren) for positive and negative lookups respectively. //pkg/sentry/fsimpl/tmpfs/benchmark_test.go: name old time/op new time/op delta VFS2TmpfsStat/1-112 172ns ± 4% 165ns ± 3% -4.08% (p=0.002 n=9+9) VFS2TmpfsStat/2-112 199ns ± 3% 195ns ±10% ~ (p=0.132 n=8+9) VFS2TmpfsStat/3-112 230ns ± 2% 216ns ± 2% -6.15% (p=0.000 n=8+8) VFS2TmpfsStat/8-112 390ns ± 2% 358ns ± 4% -8.33% (p=0.000 n=9+8) VFS2TmpfsStat/64-112 2.20µs ± 3% 2.01µs ± 3% -8.48% (p=0.000 n=10+8) VFS2TmpfsStat/100-112 3.42µs ± 9% 3.08µs ± 2% -9.82% (p=0.000 n=9+8) VFS2TmpfsMountStat/1-112 278ns ± 1% 286ns ±15% ~ (p=0.712 n=8+10) VFS2TmpfsMountStat/2-112 311ns ± 4% 298ns ± 2% -4.27% (p=0.000 n=9+8) VFS2TmpfsMountStat/3-112 339ns ± 3% 330ns ± 9% ~ (p=0.070 n=8+9) VFS2TmpfsMountStat/8-112 503ns ± 3% 466ns ± 3% -7.38% (p=0.000 n=8+8) VFS2TmpfsMountStat/64-112 2.53µs ±16% 2.17µs ± 7% -14.19% (p=0.000 n=10+9) VFS2TmpfsMountStat/100-112 3.60µs ± 4% 3.30µs ± 8% -8.33% (p=0.001 n=8+9) Updates #1035 PiperOrigin-RevId: 307655892
2020-04-21 19:16:42 +00:00
if vd.dentry.IsDead() {
return "", syserror.ENOENT
}
b := getFSPathBuilder()
defer putFSPathBuilder(b)
haveRef := false
defer func() {
if haveRef {
vd.DecRef(ctx)
}
}()
unreachable := false
loop:
for {
err := vd.mount.fs.impl.PrependPath(ctx, vfsroot, vd, b)
switch err.(type) {
case nil:
if vd.mount == vfsroot.mount && vd.mount.root == vfsroot.dentry {
break loop
}
nextVD := vfs.getMountpointAt(ctx, vd.mount, vfsroot)
if !nextVD.Ok() {
unreachable = true
break loop
}
if haveRef {
vd.DecRef(ctx)
}
vd = nextVD
haveRef = true
case PrependPathAtVFSRootError:
break loop
case PrependPathAtNonMountRootError, PrependPathSyntheticError:
unreachable = true
break loop
default:
return "", err
}
}
b.PrependByte('/')
if unreachable {
b.PrependString("(unreachable)")
}
return b.String(), nil
}
// As of this writing, we do not have equivalents to:
//
// - d_absolute_path(), which returns EINVAL if (effectively) any call to
// FilesystemImpl.PrependPath() would return PrependPathAtNonMountRootError.
//
// - dentry_path(), which does not walk up mounts (and only returns the path
// relative to Filesystem root), but also appends "//deleted" for disowned
// Dentries.
//
// These should be added as necessary.