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
This commit is contained in:
Jamie Liu
2020-04-21 12:18:07 -07:00
committed by gVisor bot
parent 639c8dd808
commit 9b5e305e05
31 changed files with 836 additions and 754 deletions
+12
View File
@@ -15,6 +15,17 @@ go_template_instance(
},
)
go_template_instance(
name = "fstree",
out = "fstree.go",
package = "ext",
prefix = "generic",
template = "//pkg/sentry/vfs/genericfstree:generic_fstree",
types = {
"Dentry": "dentry",
},
)
go_library(
name = "ext",
srcs = [
@@ -26,6 +37,7 @@ go_library(
"extent_file.go",
"file_description.go",
"filesystem.go",
"fstree.go",
"inode.go",
"regular_file.go",
"symlink.go",
+4
View File
@@ -22,6 +22,10 @@ import (
type dentry struct {
vfsd vfs.Dentry
// Protected by filesystem.mu.
parent *dentry
name string
// inode is the inode represented by this dentry. Multiple Dentries may
// share a single non-directory Inode (with hard links). inode is
// immutable.
+11 -10
View File
@@ -21,7 +21,6 @@ import (
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/fs"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/ext/disklayout"
"gvisor.dev/gvisor/pkg/sentry/memmap"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/syserror"
@@ -31,6 +30,10 @@ import (
type directory struct {
inode inode
// childCache maps filenames to dentries for children for which dentries
// have been instantiated. childCache is protected by filesystem.mu.
childCache map[string]*dentry
// mu serializes the changes to childList.
// Lock Order (outermost locks must be taken first):
// directory.mu
@@ -50,9 +53,13 @@ type directory struct {
childMap map[string]*dirent
}
// newDirectroy is the directory constructor.
func newDirectroy(inode inode, newDirent bool) (*directory, error) {
file := &directory{inode: inode, childMap: make(map[string]*dirent)}
// newDirectory is the directory constructor.
func newDirectory(inode inode, newDirent bool) (*directory, error) {
file := &directory{
inode: inode,
childCache: make(map[string]*dentry),
childMap: make(map[string]*dirent),
}
file.inode.impl = file
// Initialize childList by reading dirents from the underlying file.
@@ -299,9 +306,3 @@ func (fd *directoryFD) Seek(ctx context.Context, offset int64, whence int32) (in
fd.off = offset
return offset, nil
}
// ConfigureMMap implements vfs.FileDescriptionImpl.ConfigureMMap.
func (fd *directoryFD) ConfigureMMap(ctx context.Context, opts *memmap.MMapOpts) error {
// mmap(2) specifies that EACCESS should be returned for non-regular file fds.
return syserror.EACCES
}
+37 -17
View File
@@ -89,14 +89,33 @@ func stepLocked(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, inode *inode, write boo
}
for {
nextVFSD, err := rp.ResolveComponent(vfsd)
if err != nil {
return nil, nil, err
name := rp.Component()
if name == "." {
rp.Advance()
return vfsd, inode, nil
}
if nextVFSD == nil {
// Since the Dentry tree is not the sole source of truth for extfs, if it's
// not in the Dentry tree, it might need to be pulled from disk.
childDirent, ok := inode.impl.(*directory).childMap[rp.Component()]
d := vfsd.Impl().(*dentry)
if name == ".." {
isRoot, err := rp.CheckRoot(vfsd)
if err != nil {
return nil, nil, err
}
if isRoot || d.parent == nil {
rp.Advance()
return vfsd, inode, nil
}
if err := rp.CheckMount(&d.parent.vfsd); err != nil {
return nil, nil, err
}
rp.Advance()
return &d.parent.vfsd, d.parent.inode, nil
}
dir := inode.impl.(*directory)
child, ok := dir.childCache[name]
if !ok {
// We may need to instantiate a new dentry for this child.
childDirent, ok := dir.childMap[name]
if !ok {
// The underlying inode does not exist on disk.
return nil, nil, syserror.ENOENT
@@ -115,21 +134,22 @@ func stepLocked(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, inode *inode, write boo
}
// incRef because this is being added to the dentry tree.
childInode.incRef()
child := newDentry(childInode)
vfsd.InsertChild(&child.vfsd, rp.Component())
// Continue as usual now that nextVFSD is not nil.
nextVFSD = &child.vfsd
child = newDentry(childInode)
child.parent = d
child.name = name
dir.childCache[name] = child
}
nextInode := nextVFSD.Impl().(*dentry).inode
if nextInode.isSymlink() && rp.ShouldFollowSymlink() {
if err := rp.HandleSymlink(inode.impl.(*symlink).target); err != nil {
if err := rp.CheckMount(&child.vfsd); err != nil {
return nil, nil, err
}
if child.inode.isSymlink() && rp.ShouldFollowSymlink() {
if err := rp.HandleSymlink(child.inode.impl.(*symlink).target); err != nil {
return nil, nil, err
}
continue
}
rp.Advance()
return nextVFSD, nextInode, nil
return &child.vfsd, child.inode, nil
}
}
@@ -515,5 +535,5 @@ func (fs *filesystem) RemovexattrAt(ctx context.Context, rp *vfs.ResolvingPath,
func (fs *filesystem) PrependPath(ctx context.Context, vfsroot, vd vfs.VirtualDentry, b *fspath.Builder) error {
fs.mu.RLock()
defer fs.mu.RUnlock()
return vfs.GenericPrependPath(vfsroot, vd, b)
return genericPrependPath(vfsroot, vd.Mount(), vd.Dentry().Impl().(*dentry), b)
}
+1 -1
View File
@@ -136,7 +136,7 @@ func newInode(fs *filesystem, inodeNum uint32) (*inode, error) {
}
return &f.inode, nil
case linux.ModeDirectory:
f, err := newDirectroy(inode, fs.sb.IncompatibleFeatures().DirentFileType)
f, err := newDirectory(inode, fs.sb.IncompatibleFeatures().DirentFileType)
if err != nil {
return nil, err
}
+12
View File
@@ -15,12 +15,24 @@ go_template_instance(
},
)
go_template_instance(
name = "fstree",
out = "fstree.go",
package = "gofer",
prefix = "generic",
template = "//pkg/sentry/vfs/genericfstree:generic_fstree",
types = {
"Dentry": "dentry",
},
)
go_library(
name = "gofer",
srcs = [
"dentry_list.go",
"directory.go",
"filesystem.go",
"fstree.go",
"gofer.go",
"handle.go",
"handle_unsafe.go",
+32 -21
View File
@@ -29,13 +29,25 @@ func (d *dentry) isDir() bool {
return d.fileType() == linux.S_IFDIR
}
// Preconditions: filesystem.renameMu must be locked. d.dirMu must be locked.
// d.isDir(). child must be a newly-created dentry that has never had a parent.
func (d *dentry) cacheNewChildLocked(child *dentry, name string) {
d.IncRef() // reference held by child on its parent
child.parent = d
child.name = name
if d.children == nil {
d.children = make(map[string]*dentry)
}
d.children[name] = child
}
// Preconditions: d.dirMu must be locked. d.isDir(). fs.opts.interop !=
// InteropModeShared.
func (d *dentry) cacheNegativeChildLocked(name string) {
if d.negativeChildren == nil {
d.negativeChildren = make(map[string]struct{})
if d.children == nil {
d.children = make(map[string]*dentry)
}
d.negativeChildren[name] = struct{}{}
d.children[name] = nil
}
type directoryFD struct {
@@ -80,34 +92,32 @@ func (fd *directoryFD) IterDirents(ctx context.Context, cb vfs.IterDirentsCallba
// Preconditions: d.isDir(). There exists at least one directoryFD representing d.
func (d *dentry) getDirents(ctx context.Context) ([]vfs.Dirent, error) {
// 9P2000.L's readdir does not specify behavior in the presence of
// concurrent mutation of an iterated directory, so implementations may
// duplicate or omit entries in this case, which violates POSIX semantics.
// Thus we read all directory entries while holding d.dirMu to exclude
// directory mutations. (Note that it is impossible for the client to
// exclude concurrent mutation from other remote filesystem users. Since
// there is no way to detect if the server has incorrectly omitted
// directory entries, we simply assume that the server is well-behaved
// under InteropModeShared.) This is inconsistent with Linux (which appears
// to assume that directory fids have the correct semantics, and translates
// struct file_operations::readdir calls directly to readdir RPCs), but is
// consistent with VFS1.
//
// NOTE(b/135560623): In particular, some gofer implementations may not
// retain state between calls to Readdir, so may not provide a coherent
// directory stream across in the presence of mutation.
// NOTE(b/135560623): 9P2000.L's readdir does not specify behavior in the
// presence of concurrent mutation of an iterated directory, so
// implementations may duplicate or omit entries in this case, which
// violates POSIX semantics. Thus we read all directory entries while
// holding d.dirMu to exclude directory mutations. (Note that it is
// impossible for the client to exclude concurrent mutation from other
// remote filesystem users. Since there is no way to detect if the server
// has incorrectly omitted directory entries, we simply assume that the
// server is well-behaved under InteropModeShared.) This is inconsistent
// with Linux (which appears to assume that directory fids have the correct
// semantics, and translates struct file_operations::readdir calls directly
// to readdir RPCs), but is consistent with VFS1.
// filesystem.renameMu is needed for d.parent, and must be locked before
// dentry.dirMu.
d.fs.renameMu.RLock()
defer d.fs.renameMu.RUnlock()
d.dirMu.Lock()
defer d.dirMu.Unlock()
if d.dirents != nil {
d.fs.renameMu.RUnlock()
return d.dirents, nil
}
// It's not clear if 9P2000.L's readdir is expected to return "." and "..",
// so we generate them here.
parent := d.vfsd.ParentOrSelf().Impl().(*dentry)
parent := genericParentOrSelf(d)
dirents := []vfs.Dirent{
{
Name: ".",
@@ -122,6 +132,7 @@ func (d *dentry) getDirents(ctx context.Context) ([]vfs.Dirent, error) {
NextOff: 2,
},
}
d.fs.renameMu.RUnlock()
off := uint64(0)
const count = 64 * 1024 // for consistency with the vfs1 client
d.handleMu.RLock()
+109 -93
View File
@@ -116,6 +116,8 @@ func putDentrySlice(ds *[]*dentry) {
// Preconditions: fs.renameMu must be locked. d.dirMu must be locked.
// !rp.Done(). If fs.opts.interop == InteropModeShared, then d's cached
// metadata must be up to date.
//
// Postconditions: The returned dentry's cached metadata is up to date.
func (fs *filesystem) stepLocked(ctx context.Context, rp *vfs.ResolvingPath, d *dentry, ds **[]*dentry) (*dentry, error) {
if !d.isDir() {
return nil, syserror.ENOTDIR
@@ -130,39 +132,42 @@ afterSymlink:
return d, nil
}
if name == ".." {
parentVFSD, err := rp.ResolveParent(&d.vfsd)
if err != nil {
if isRoot, err := rp.CheckRoot(&d.vfsd); err != nil {
return nil, err
} else if isRoot || d.parent == nil {
rp.Advance()
return d, nil
}
// We must assume that d.parent is correct, because if d has been moved
// elsewhere in the remote filesystem so that its parent has changed,
// we have no way of determining its new parent's location in the
// filesystem.
//
// Call rp.CheckMount() before updating d.parent's metadata, since if
// we traverse to another mount then d.parent's metadata is irrelevant.
if err := rp.CheckMount(&d.parent.vfsd); err != nil {
return nil, err
}
parent := parentVFSD.Impl().(*dentry)
if fs.opts.interop == InteropModeShared {
// We must assume that parentVFSD is correct, because if d has been
// moved elsewhere in the remote filesystem so that its parent has
// changed, we have no way of determining its new parent's location
// in the filesystem. Get updated metadata for parentVFSD.
_, attrMask, attr, err := parent.file.getAttr(ctx, dentryAttrMask())
if fs.opts.interop == InteropModeShared && d != d.parent {
_, attrMask, attr, err := d.parent.file.getAttr(ctx, dentryAttrMask())
if err != nil {
return nil, err
}
parent.updateFromP9Attrs(attrMask, &attr)
d.parent.updateFromP9Attrs(attrMask, &attr)
}
rp.Advance()
return parent, nil
return d.parent, nil
}
childVFSD, err := rp.ResolveChild(&d.vfsd, name)
if err != nil {
return nil, err
}
// FIXME(jamieliu): Linux performs revalidation before mount lookup
// (fs/namei.c:lookup_fast() => __d_lookup_rcu(), d_revalidate(),
// __follow_mount_rcu()).
child, err := fs.revalidateChildLocked(ctx, rp.VirtualFilesystem(), d, name, childVFSD, ds)
child, err := fs.getChildLocked(ctx, rp.VirtualFilesystem(), d, name, ds)
if err != nil {
return nil, err
}
if child == nil {
return nil, syserror.ENOENT
}
if err := rp.CheckMount(&child.vfsd); err != nil {
return nil, err
}
if child.isSymlink() && rp.ShouldFollowSymlink() {
target, err := child.readlink(ctx, rp.Mount())
if err != nil {
@@ -177,38 +182,37 @@ afterSymlink:
return child, nil
}
// revalidateChildLocked must be called after a call to parent.vfsd.Child(name)
// or vfs.ResolvingPath.ResolveChild(name) returns childVFSD (which may be
// nil) to verify that the returned child (or lack thereof) is correct. If no file
// exists at name, revalidateChildLocked returns (nil, nil).
// getChildLocked returns a dentry representing the child of parent with the
// given name. If no such child exists, getChildLocked returns (nil, nil).
//
// Preconditions: fs.renameMu must be locked. parent.dirMu must be locked.
// parent.isDir(). name is not "." or "..".
//
// Postconditions: If revalidateChildLocked returns a non-nil dentry, its
// cached metadata is up to date.
func (fs *filesystem) revalidateChildLocked(ctx context.Context, vfsObj *vfs.VirtualFilesystem, parent *dentry, name string, childVFSD *vfs.Dentry, ds **[]*dentry) (*dentry, error) {
if childVFSD != nil && fs.opts.interop != InteropModeShared {
// We have a cached dentry that is assumed to be correct.
return childVFSD.Impl().(*dentry), nil
}
// We either don't have a cached dentry or need to verify that it's still
// correct, either of which requires a remote lookup. Check if this name is
// valid before performing the lookup.
// Postconditions: If getChildLocked returns a non-nil dentry, its cached
// metadata is up to date.
func (fs *filesystem) getChildLocked(ctx context.Context, vfsObj *vfs.VirtualFilesystem, parent *dentry, name string, ds **[]*dentry) (*dentry, error) {
if len(name) > maxFilenameLen {
return nil, syserror.ENAMETOOLONG
}
// Check if we've already cached this lookup with a negative result.
if _, ok := parent.negativeChildren[name]; ok {
return nil, nil
child, ok := parent.children[name]
if ok && fs.opts.interop != InteropModeShared {
// Whether child is nil or not, it is cached information that is
// assumed to be correct.
return child, nil
}
// Perform the remote lookup.
// We either don't have cached information or need to verify that it's
// still correct, either of which requires a remote lookup. Check if this
// name is valid before performing the lookup.
return fs.revalidateChildLocked(ctx, vfsObj, parent, name, child, ds)
}
// Preconditions: As for getChildLocked.
func (fs *filesystem) revalidateChildLocked(ctx context.Context, vfsObj *vfs.VirtualFilesystem, parent *dentry, name string, child *dentry, ds **[]*dentry) (*dentry, error) {
qid, file, attrMask, attr, err := parent.file.walkGetAttrOne(ctx, name)
if err != nil && err != syserror.ENOENT {
return nil, err
}
if childVFSD != nil {
child := childVFSD.Impl().(*dentry)
if child != nil {
if !file.isNil() && qid.Path == child.ino {
// The file at this path hasn't changed. Just update cached
// metadata.
@@ -219,9 +223,8 @@ func (fs *filesystem) revalidateChildLocked(ctx context.Context, vfsObj *vfs.Vir
// The file at this path has changed or no longer exists. Remove
// the stale dentry from the tree, and re-evaluate its caching
// status (i.e. if it has 0 references, drop it).
vfsObj.ForceDeleteDentry(childVFSD)
vfsObj.InvalidateDentry(&child.vfsd)
*ds = appendDentry(*ds, child)
childVFSD = nil
}
if file.isNil() {
// No file exists at this path now. Cache the negative lookup if
@@ -232,13 +235,12 @@ func (fs *filesystem) revalidateChildLocked(ctx context.Context, vfsObj *vfs.Vir
return nil, nil
}
// Create a new dentry representing the file.
child, err := fs.newDentry(ctx, file, qid, attrMask, &attr)
child, err = fs.newDentry(ctx, file, qid, attrMask, &attr)
if err != nil {
file.close(ctx)
return nil, err
}
parent.IncRef() // reference held by child on its parent
parent.vfsd.InsertChild(&child.vfsd, name)
parent.cacheNewChildLocked(child, name)
// For now, child has 0 references, so our caller should call
// child.checkCachingLocked().
*ds = appendDentry(*ds, child)
@@ -318,9 +320,6 @@ func (fs *filesystem) doCreateAt(ctx context.Context, rp *vfs.ResolvingPath, dir
if err := parent.checkPermissions(rp.Credentials(), vfs.MayWrite|vfs.MayExec); err != nil {
return err
}
if parent.isDeleted() {
return syserror.ENOENT
}
name := rp.Component()
if name == "." || name == ".." {
return syserror.EEXIST
@@ -331,6 +330,9 @@ func (fs *filesystem) doCreateAt(ctx context.Context, rp *vfs.ResolvingPath, dir
if !dir && rp.MustBeDir() {
return syserror.ENOENT
}
if parent.isDeleted() {
return syserror.ENOENT
}
mnt := rp.Mount()
if err := mnt.CheckBeginWrite(); err != nil {
return err
@@ -348,7 +350,7 @@ func (fs *filesystem) doCreateAt(ctx context.Context, rp *vfs.ResolvingPath, dir
// it's used.
return create(parent, name)
}
if parent.vfsd.Child(name) != nil {
if child := parent.children[name]; child != nil {
return syserror.EEXIST
}
// No cached dentry exists; however, there might still be an existing file
@@ -356,10 +358,11 @@ func (fs *filesystem) doCreateAt(ctx context.Context, rp *vfs.ResolvingPath, dir
if err := create(parent, name); err != nil {
return err
}
if fs.opts.interop != InteropModeShared {
parent.touchCMtime()
}
delete(parent.negativeChildren, name)
parent.touchCMtime()
// Either parent.children[name] doesn't exist (in which case this is a
// no-op) or is nil (in which case this erases the now-stale information
// that the file doesn't exist).
delete(parent.children, name)
parent.dirents = nil
return nil
}
@@ -407,56 +410,55 @@ func (fs *filesystem) unlinkAt(ctx context.Context, rp *vfs.ResolvingPath, dir b
defer mntns.DecRef()
parent.dirMu.Lock()
defer parent.dirMu.Unlock()
childVFSD := parent.vfsd.Child(name)
var child *dentry
child, ok := parent.children[name]
if ok && child == nil {
return syserror.ENOENT
}
// We only need a dentry representing the file at name if it can be a mount
// point. If childVFSD is nil, then it can't be a mount point. If childVFSD
// is non-nil but stale, the actual file can't be a mount point either; we
// point. If child is nil, then it can't be a mount point. If child is
// non-nil but stale, the actual file can't be a mount point either; we
// detect this case by just speculatively calling PrepareDeleteDentry and
// only revalidating the dentry if that fails (indicating that the existing
// dentry is a mount point).
if childVFSD != nil {
child = childVFSD.Impl().(*dentry)
if err := vfsObj.PrepareDeleteDentry(mntns, childVFSD); err != nil {
child, err = fs.revalidateChildLocked(ctx, vfsObj, parent, name, childVFSD, &ds)
if child != nil {
if err := vfsObj.PrepareDeleteDentry(mntns, &child.vfsd); err != nil {
if fs.opts.interop != InteropModeShared {
return err
}
child, err = fs.revalidateChildLocked(ctx, vfsObj, parent, name, child, &ds)
if err != nil {
return err
}
if child != nil {
childVFSD = &child.vfsd
if err := vfsObj.PrepareDeleteDentry(mntns, childVFSD); err != nil {
if err := vfsObj.PrepareDeleteDentry(mntns, &child.vfsd); err != nil {
return err
}
} else {
childVFSD = nil
}
}
} else if _, ok := parent.negativeChildren[name]; ok {
return syserror.ENOENT
}
flags := uint32(0)
if dir {
if child != nil && !child.isDir() {
vfsObj.AbortDeleteDentry(childVFSD)
vfsObj.AbortDeleteDentry(&child.vfsd)
return syserror.ENOTDIR
}
flags = linux.AT_REMOVEDIR
} else {
if child != nil && child.isDir() {
vfsObj.AbortDeleteDentry(childVFSD)
vfsObj.AbortDeleteDentry(&child.vfsd)
return syserror.EISDIR
}
if rp.MustBeDir() {
if childVFSD != nil {
vfsObj.AbortDeleteDentry(childVFSD)
if child != nil {
vfsObj.AbortDeleteDentry(&child.vfsd)
}
return syserror.ENOTDIR
}
}
err = parent.file.unlinkAt(ctx, name, flags)
if err != nil {
if childVFSD != nil {
vfsObj.AbortDeleteDentry(childVFSD)
if child != nil {
vfsObj.AbortDeleteDentry(&child.vfsd)
}
return err
}
@@ -467,10 +469,12 @@ func (fs *filesystem) unlinkAt(ctx context.Context, rp *vfs.ResolvingPath, dir b
}
parent.cacheNegativeChildLocked(name)
parent.dirents = nil
} else {
delete(parent.children, name)
}
if child != nil {
child.setDeleted()
vfsObj.CommitDeleteDentry(childVFSD)
vfsObj.CommitDeleteDentry(&child.vfsd)
ds = appendDentry(ds, child)
}
return nil
@@ -806,16 +810,14 @@ func (d *dentry) createAndOpenChildLocked(ctx context.Context, rp *vfs.Resolving
// eligible for caching yet, so we don't need to append to a dentry slice.)
child.refs = 1
// Insert the dentry into the tree.
d.IncRef() // reference held by child on its parent d
d.vfsd.InsertChild(&child.vfsd, name)
d.cacheNewChildLocked(child, name)
if d.fs.opts.interop != InteropModeShared {
delete(d.negativeChildren, name)
d.touchCMtime()
d.dirents = nil
}
// Finally, construct a file description representing the created file.
var childVFSFD *vfs.FileDescription
mnt.IncRef()
if useRegularFileFD {
fd := &regularFileFD{}
if err := fd.vfsfd.Init(fd, opts.Flags, mnt, &child.vfsd, &vfs.FileDescriptionOptions{
@@ -840,9 +842,6 @@ func (d *dentry) createAndOpenChildLocked(ctx context.Context, rp *vfs.Resolving
}
childVFSFD = &fd.vfsfd
}
if d.fs.opts.interop != InteropModeShared {
d.touchCMtime()
}
return childVFSFD, nil
}
@@ -902,7 +901,7 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
// directory, we need to check for write permission on it.
oldParent.dirMu.Lock()
defer oldParent.dirMu.Unlock()
renamed, err := fs.revalidateChildLocked(ctx, vfsObj, oldParent, oldName, oldParent.vfsd.Child(oldName), &ds)
renamed, err := fs.getChildLocked(ctx, vfsObj, oldParent, oldName, &ds)
if err != nil {
return err
}
@@ -910,7 +909,7 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
return syserror.ENOENT
}
if renamed.isDir() {
if renamed == newParent || renamed.vfsd.IsAncestorOf(&newParent.vfsd) {
if renamed == newParent || genericIsAncestorDentry(renamed, newParent) {
return syserror.EINVAL
}
if oldParent != newParent {
@@ -934,16 +933,17 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
if newParent.isDeleted() {
return syserror.ENOENT
}
replacedVFSD := newParent.vfsd.Child(newName)
var replaced *dentry
replaced := newParent.children[newName]
// This is similar to unlinkAt, except:
//
// - We revalidate the replaced dentry unconditionally for simplicity.
// - If a dentry exists for the file to be replaced, we revalidate it
// unconditionally (instead of only if PrepareRenameDentry fails) for
// simplicity.
//
// - If rp.MustBeDir(), then we need a dentry representing the replaced
// file regardless to confirm that it's a directory.
if replacedVFSD != nil || rp.MustBeDir() {
replaced, err = fs.revalidateChildLocked(ctx, vfsObj, newParent, newName, replacedVFSD, &ds)
if replaced != nil || rp.MustBeDir() {
replaced, err = fs.getChildLocked(ctx, rp.VirtualFilesystem(), newParent, newName, &ds)
if err != nil {
return err
}
@@ -957,11 +957,12 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
return syserror.ENOTDIR
}
}
replacedVFSD = &replaced.vfsd
} else {
replacedVFSD = nil
}
}
var replacedVFSD *vfs.Dentry
if replaced != nil {
replacedVFSD = &replaced.vfsd
}
if oldParent == newParent && oldName == newName {
return nil
@@ -978,7 +979,6 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
if fs.opts.interop != InteropModeShared {
oldParent.cacheNegativeChildLocked(oldName)
oldParent.dirents = nil
delete(newParent.negativeChildren, newName)
newParent.dirents = nil
if renamed.isDir() {
oldParent.decLinks()
@@ -987,8 +987,24 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
oldParent.touchCMtime()
newParent.touchCMtime()
renamed.touchCtime()
} else {
delete(oldParent.children, oldName)
}
vfsObj.CommitRenameReplaceDentry(&renamed.vfsd, &newParent.vfsd, newName, replacedVFSD)
if oldParent != newParent {
appendDentry(ds, oldParent)
newParent.IncRef()
}
renamed.parent = newParent
renamed.name = newName
if newParent.children == nil {
newParent.children = make(map[string]*dentry)
}
newParent.children[newName] = renamed
if replaced != nil {
replaced.setDeleted()
appendDentry(ds, replaced)
}
vfsObj.CommitRenameReplaceDentry(&renamed.vfsd, replacedVFSD)
return nil
}
@@ -1131,5 +1147,5 @@ func (fs *filesystem) RemovexattrAt(ctx context.Context, rp *vfs.ResolvingPath,
func (fs *filesystem) PrependPath(ctx context.Context, vfsroot, vd vfs.VirtualDentry, b *fspath.Builder) error {
fs.renameMu.RLock()
defer fs.renameMu.RUnlock()
return vfs.GenericPrependPath(vfsroot, vd, b)
return genericPrependPath(vfsroot, vd.Mount(), vd.Dentry().Impl().(*dentry), b)
}
+39 -27
View File
@@ -452,6 +452,16 @@ type dentry struct {
// fs is the owning filesystem. fs is immutable.
fs *filesystem
// parent is this dentry's parent directory. Each dentry holds a reference
// on its parent. If this dentry is a filesystem root, parent is nil.
// parent is protected by filesystem.renameMu.
parent *dentry
// name is the name of this dentry in its parent. If this dentry is a
// filesystem root, name is the empty string. name is protected by
// filesystem.renameMu.
name string
// We don't support hard links, so each dentry maps 1:1 to an inode.
// file is the unopened p9.File that backs this dentry. file is immutable.
@@ -469,10 +479,15 @@ type dentry struct {
dirMu sync.Mutex
// If this dentry represents a directory, and InteropModeShared is not in
// effect, negativeChildren is a set of child names in this directory that
// are known not to exist. negativeChildren is protected by dirMu.
negativeChildren map[string]struct{}
// If this dentry represents a directory, children contains:
//
// - Mappings of child filenames to dentries representing those children.
//
// - Mappings of child filenames that are known not to exist to nil
// dentries (only if InteropModeShared is not in effect).
//
// children is protected by dirMu.
children map[string]*dentry
// If this dentry represents a directory, InteropModeShared is not in
// effect, and dirents is not nil, it is a cache of all entries in the
@@ -910,9 +925,9 @@ func (d *dentry) checkCachingLocked() {
// Dentry has already been destroyed.
return
}
// Non-child dentries with zero references are no longer reachable by path
// resolution and should be dropped immediately.
if d.vfsd.Parent() == nil || d.vfsd.IsDisowned() {
// Deleted and invalidated dentries with zero references are no longer
// reachable by path resolution and should be dropped immediately.
if d.vfsd.IsDead() {
if d.cached {
d.fs.cachedDentries.Remove(d)
d.fs.cachedDentriesLen--
@@ -937,28 +952,26 @@ func (d *dentry) checkCachingLocked() {
d.fs.cachedDentries.Remove(victim)
d.fs.cachedDentriesLen--
victim.cached = false
// victim.refs may have become non-zero from an earlier path
// resolution since it was inserted into fs.cachedDentries; see
// dentry.incRefLocked(). Either way, we brought
// fs.cachedDentriesLen back down to fs.opts.maxCachedDentries, so
// we don't loop.
// victim.refs may have become non-zero from an earlier path resolution
// since it was inserted into fs.cachedDentries.
if atomic.LoadInt64(&victim.refs) == 0 {
if victimParentVFSD := victim.vfsd.Parent(); victimParentVFSD != nil {
victimParent := victimParentVFSD.Impl().(*dentry)
victimParent.dirMu.Lock()
if !victim.vfsd.IsDisowned() {
// victim can't be a mount point (in any mount
// namespace), since VFS holds references on mount
// points.
d.fs.vfsfs.VirtualFilesystem().ForceDeleteDentry(&victim.vfsd)
if victim.parent != nil {
victim.parent.dirMu.Lock()
if !victim.vfsd.IsDead() {
// Note that victim can't be a mount point (in any mount
// namespace), since VFS holds references on mount points.
d.fs.vfsfs.VirtualFilesystem().InvalidateDentry(&victim.vfsd)
delete(victim.parent.children, victim.name)
// We're only deleting the dentry, not the file it
// represents, so we don't need to update
// victimParent.dirents etc.
}
victimParent.dirMu.Unlock()
victim.parent.dirMu.Unlock()
}
victim.destroyLocked()
}
// Whether or not victim was destroyed, we brought fs.cachedDentriesLen
// back down to fs.opts.maxCachedDentries, so we don't loop.
}
}
@@ -1005,12 +1018,11 @@ func (d *dentry) destroyLocked() {
d.fs.syncMu.Lock()
delete(d.fs.dentries, d)
d.fs.syncMu.Unlock()
// Drop the reference held by d on its parent.
if parentVFSD := d.vfsd.Parent(); parentVFSD != nil {
parent := parentVFSD.Impl().(*dentry)
// This is parent.DecRef() without recursive locking of d.fs.renameMu.
if refs := atomic.AddInt64(&parent.refs, -1); refs == 0 {
parent.checkCachingLocked()
// Drop the reference held by d on its parent without recursively locking
// d.fs.renameMu.
if d.parent != nil {
if refs := atomic.AddInt64(&d.parent.refs, -1); refs == 0 {
d.parent.checkCachingLocked()
} else if refs < 0 {
panic("gofer.dentry.DecRef() called without holding a reference")
}
+1 -2
View File
@@ -48,8 +48,7 @@ func TestDestroyIdempotent(t *testing.T) {
if err != nil {
t.Fatalf("fs.newDentry(): %v", err)
}
parent.IncRef() // reference held by child on its parent.
parent.vfsd.InsertChild(&child.vfsd, "child")
parent.cacheNewChildLocked(child, "child")
child.checkCachingLocked()
if got := atomic.LoadInt64(&child.refs); got != -1 {
+12
View File
@@ -3,6 +3,17 @@ load("//tools/go_generics:defs.bzl", "go_template_instance")
licenses(["notice"])
go_template_instance(
name = "fstree",
out = "fstree.go",
package = "kernfs",
prefix = "generic",
template = "//pkg/sentry/vfs/genericfstree:generic_fstree",
types = {
"Dentry": "Dentry",
},
)
go_template_instance(
name = "slot_list",
out = "slot_list.go",
@@ -21,6 +32,7 @@ go_library(
"dynamic_bytes_file.go",
"fd_impl_util.go",
"filesystem.go",
"fstree.go",
"inode_impl_util.go",
"kernfs.go",
"slot_list.go",
+1 -1
View File
@@ -129,7 +129,7 @@ func (fd *GenericDirectoryFD) IterDirents(ctx context.Context, cb vfs.IterDirent
// Handle "..".
if fd.off == 1 {
parentInode := vfsd.ParentOrSelf().Impl().(*Dentry).inode
parentInode := genericParentOrSelf(vfsd.Impl().(*Dentry)).inode
stat, err := parentInode.Stat(vfsFS, opts)
if err != nil {
return err
+88 -73
View File
@@ -56,27 +56,30 @@ afterSymlink:
return vfsd, nil
}
if name == ".." {
nextVFSD, err := rp.ResolveParent(vfsd)
if err != nil {
if isRoot, err := rp.CheckRoot(vfsd); err != nil {
return nil, err
} else if isRoot || d.parent == nil {
rp.Advance()
return vfsd, nil
}
if err := rp.CheckMount(&d.parent.vfsd); err != nil {
return nil, err
}
rp.Advance()
return nextVFSD, nil
return &d.parent.vfsd, nil
}
if len(name) > linux.NAME_MAX {
return nil, syserror.ENAMETOOLONG
}
d.dirMu.Lock()
nextVFSD, err := rp.ResolveChild(vfsd, name)
if err != nil {
d.dirMu.Unlock()
return nil, err
}
next, err := fs.revalidateChildLocked(ctx, rp.VirtualFilesystem(), d, name, nextVFSD)
next, err := fs.revalidateChildLocked(ctx, rp.VirtualFilesystem(), d, name, d.children[name])
d.dirMu.Unlock()
if err != nil {
return nil, err
}
if err := rp.CheckMount(&next.vfsd); err != nil {
return nil, err
}
// Resolve any symlink at current path component.
if rp.ShouldFollowSymlink() && next.isSymlink() {
targetVD, targetPathname, err := next.inode.Getlink(ctx)
@@ -108,17 +111,17 @@ afterSymlink:
// parent.dirMu must be locked. parent.isDir(). name is not "." or "..".
//
// Postconditions: Caller must call fs.processDeferredDecRefs*.
func (fs *Filesystem) revalidateChildLocked(ctx context.Context, vfsObj *vfs.VirtualFilesystem, parent *Dentry, name string, childVFSD *vfs.Dentry) (*Dentry, error) {
if childVFSD != nil {
func (fs *Filesystem) revalidateChildLocked(ctx context.Context, vfsObj *vfs.VirtualFilesystem, parent *Dentry, name string, child *Dentry) (*Dentry, error) {
if child != nil {
// Cached dentry exists, revalidate.
child := childVFSD.Impl().(*Dentry)
if !child.inode.Valid(ctx) {
vfsObj.ForceDeleteDentry(childVFSD)
fs.deferDecRef(childVFSD) // Reference from Lookup.
childVFSD = nil
delete(parent.children, name)
vfsObj.InvalidateDentry(&child.vfsd)
fs.deferDecRef(&child.vfsd) // Reference from Lookup.
child = nil
}
}
if childVFSD == nil {
if child == nil {
// Dentry isn't cached; it either doesn't exist or failed
// revalidation. Attempt to resolve it via Lookup.
//
@@ -126,15 +129,15 @@ func (fs *Filesystem) revalidateChildLocked(ctx context.Context, vfsObj *vfs.Vir
// *(kernfs.)Dentry, not *vfs.Dentry, since (kernfs.)Filesystem assumes
// that all dentries in the filesystem are (kernfs.)Dentry and performs
// vfs.DentryImpl casts accordingly.
var err error
childVFSD, err = parent.inode.Lookup(ctx, name)
childVFSD, err := parent.inode.Lookup(ctx, name)
if err != nil {
return nil, err
}
// Reference on childVFSD dropped by a corresponding Valid.
parent.insertChildLocked(name, childVFSD)
child = childVFSD.Impl().(*Dentry)
parent.insertChildLocked(name, child)
}
return childVFSD.Impl().(*Dentry), nil
return child, nil
}
// walkExistingLocked resolves rp to an existing file.
@@ -203,14 +206,11 @@ func checkCreateLocked(ctx context.Context, rp *vfs.ResolvingPath, parentVFSD *v
if len(pc) > linux.NAME_MAX {
return "", syserror.ENAMETOOLONG
}
childVFSD, err := rp.ResolveChild(parentVFSD, pc)
if err != nil {
return "", err
}
if childVFSD != nil {
// FIXME(gvisor.dev/issue/1193): Data race due to not holding dirMu.
if _, ok := parentVFSD.Impl().(*Dentry).children[pc]; ok {
return "", syserror.EEXIST
}
if parentVFSD.IsDisowned() {
if parentVFSD.IsDead() {
return "", syserror.ENOENT
}
return pc, nil
@@ -220,14 +220,14 @@ func checkCreateLocked(ctx context.Context, rp *vfs.ResolvingPath, parentVFSD *v
//
// Preconditions: Filesystem.mu must be locked for at least reading.
func checkDeleteLocked(ctx context.Context, rp *vfs.ResolvingPath, vfsd *vfs.Dentry) error {
parentVFSD := vfsd.Parent()
if parentVFSD == nil {
parent := vfsd.Impl().(*Dentry).parent
if parent == nil {
return syserror.EBUSY
}
if parentVFSD.IsDisowned() {
if parent.vfsd.IsDead() {
return syserror.ENOENT
}
if err := parentVFSD.Impl().(*Dentry).inode.CheckPermissions(ctx, rp.Credentials(), vfs.MayWrite|vfs.MayExec); err != nil {
if err := parent.inode.CheckPermissions(ctx, rp.Credentials(), vfs.MayWrite|vfs.MayExec); err != nil {
return err
}
return nil
@@ -321,11 +321,11 @@ func (fs *Filesystem) LinkAt(ctx context.Context, rp *vfs.ResolvingPath, vd vfs.
return syserror.EPERM
}
child, err := parentInode.NewLink(ctx, pc, d.inode)
childVFSD, err := parentInode.NewLink(ctx, pc, d.inode)
if err != nil {
return err
}
parentVFSD.Impl().(*Dentry).InsertChild(pc, child)
parentVFSD.Impl().(*Dentry).InsertChild(pc, childVFSD.Impl().(*Dentry))
return nil
}
@@ -349,11 +349,11 @@ func (fs *Filesystem) MkdirAt(ctx context.Context, rp *vfs.ResolvingPath, opts v
return err
}
defer rp.Mount().EndWrite()
child, err := parentInode.NewDir(ctx, pc, opts)
childVFSD, err := parentInode.NewDir(ctx, pc, opts)
if err != nil {
return err
}
parentVFSD.Impl().(*Dentry).InsertChild(pc, child)
parentVFSD.Impl().(*Dentry).InsertChild(pc, childVFSD.Impl().(*Dentry))
return nil
}
@@ -377,11 +377,11 @@ func (fs *Filesystem) MknodAt(ctx context.Context, rp *vfs.ResolvingPath, opts v
return err
}
defer rp.Mount().EndWrite()
new, err := parentInode.NewNode(ctx, pc, opts)
newVFSD, err := parentInode.NewNode(ctx, pc, opts)
if err != nil {
return err
}
parentVFSD.Impl().(*Dentry).InsertChild(pc, new)
parentVFSD.Impl().(*Dentry).InsertChild(pc, newVFSD.Impl().(*Dentry))
return nil
}
@@ -449,11 +449,8 @@ afterTrailingSymlink:
return nil, syserror.ENAMETOOLONG
}
// Determine whether or not we need to create a file.
childVFSD, err := rp.ResolveChild(parentVFSD, pc)
if err != nil {
return nil, err
}
if childVFSD == nil {
childVFSD, err := fs.stepExistingLocked(ctx, rp, parentVFSD)
if err == syserror.ENOENT {
// Already checked for searchability above; now check for writability.
if err := parentInode.CheckPermissions(ctx, rp.Credentials(), vfs.MayWrite); err != nil {
return nil, err
@@ -463,21 +460,24 @@ afterTrailingSymlink:
}
defer rp.Mount().EndWrite()
// Create and open the child.
child, err := parentInode.NewFile(ctx, pc, opts)
childVFSD, err = parentInode.NewFile(ctx, pc, opts)
if err != nil {
return nil, err
}
child := childVFSD.Impl().(*Dentry)
parentVFSD.Impl().(*Dentry).InsertChild(pc, child)
return child.Impl().(*Dentry).inode.Open(rp, child, opts)
return child.inode.Open(rp, childVFSD, opts)
}
if err != nil {
return nil, err
}
// Open existing file or follow symlink.
if mustCreate {
return nil, syserror.EEXIST
}
childDentry := childVFSD.Impl().(*Dentry)
childInode := childDentry.inode
if rp.ShouldFollowSymlink() && childDentry.isSymlink() {
targetVD, targetPathname, err := childInode.Getlink(ctx)
child := childVFSD.Impl().(*Dentry)
if rp.ShouldFollowSymlink() && child.isSymlink() {
targetVD, targetPathname, err := child.inode.Getlink(ctx)
if err != nil {
return nil, err
}
@@ -496,10 +496,10 @@ afterTrailingSymlink:
// symlink target.
goto afterTrailingSymlink
}
if err := childInode.CheckPermissions(ctx, rp.Credentials(), ats); err != nil {
if err := child.inode.CheckPermissions(ctx, rp.Credentials(), ats); err != nil {
return nil, err
}
return childInode.Open(rp, childVFSD, opts)
return child.inode.Open(rp, &child.vfsd, opts)
}
// ReadlinkAt implements vfs.FilesystemImpl.ReadlinkAt.
@@ -526,15 +526,16 @@ func (fs *Filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
noReplace := opts.Flags&linux.RENAME_NOREPLACE != 0
fs.mu.Lock()
defer fs.mu.Lock()
defer fs.processDeferredDecRefsLocked()
defer fs.mu.Unlock()
// Resolve the destination directory first to verify that it's on this
// Mount.
dstDirVFSD, dstDirInode, err := fs.walkParentDirLocked(ctx, rp)
fs.processDeferredDecRefsLocked()
if err != nil {
return err
}
dstDir := dstDirVFSD.Impl().(*Dentry)
mnt := rp.Mount()
if mnt != oldParentVD.Mount() {
return syserror.EXDEV
@@ -547,9 +548,8 @@ func (fs *Filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
srcDirVFSD := oldParentVD.Dentry()
srcDir := srcDirVFSD.Impl().(*Dentry)
srcDir.dirMu.Lock()
src, err := fs.revalidateChildLocked(ctx, rp.VirtualFilesystem(), srcDir, oldName, srcDirVFSD.Child(oldName))
src, err := fs.revalidateChildLocked(ctx, rp.VirtualFilesystem(), srcDir, oldName, srcDir.children[oldName])
srcDir.dirMu.Unlock()
fs.processDeferredDecRefsLocked()
if err != nil {
return err
}
@@ -561,7 +561,7 @@ func (fs *Filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
}
// Can we create the dst dentry?
var dstVFSD *vfs.Dentry
var dst *Dentry
pc, err := checkCreateLocked(ctx, rp, dstDirVFSD, dstDirInode)
switch err {
case nil:
@@ -571,38 +571,51 @@ func (fs *Filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
// Won't overwrite existing node since RENAME_NOREPLACE was requested.
return syserror.EEXIST
}
dstVFSD, err = rp.ResolveChild(dstDirVFSD, pc)
if err != nil {
dst = dstDir.children[pc]
if dst == nil {
panic(fmt.Sprintf("Child %q for parent Dentry %+v disappeared inside atomic section?", pc, dstDirVFSD))
}
default:
return err
}
var dstVFSD *vfs.Dentry
if dst != nil {
dstVFSD = &dst.vfsd
}
mntns := vfs.MountNamespaceFromContext(ctx)
defer mntns.DecRef()
virtfs := rp.VirtualFilesystem()
srcDirDentry := srcDirVFSD.Impl().(*Dentry)
dstDirDentry := dstDirVFSD.Impl().(*Dentry)
// We can't deadlock here due to lock ordering because we're protected from
// concurrent renames by fs.mu held for writing.
srcDirDentry.dirMu.Lock()
defer srcDirDentry.dirMu.Unlock()
dstDirDentry.dirMu.Lock()
defer dstDirDentry.dirMu.Unlock()
srcDir.dirMu.Lock()
defer srcDir.dirMu.Unlock()
if srcDir != dstDir {
dstDir.dirMu.Lock()
defer dstDir.dirMu.Unlock()
}
if err := virtfs.PrepareRenameDentry(mntns, srcVFSD, dstVFSD); err != nil {
return err
}
srcDirInode := srcDirDentry.inode
replaced, err := srcDirInode.Rename(ctx, srcVFSD.Name(), pc, srcVFSD, dstDirVFSD)
replaced, err := srcDir.inode.Rename(ctx, src.name, pc, srcVFSD, dstDirVFSD)
if err != nil {
virtfs.AbortRenameDentry(srcVFSD, dstVFSD)
return err
}
virtfs.CommitRenameReplaceDentry(srcVFSD, dstDirVFSD, pc, replaced)
delete(srcDir.children, src.name)
if srcDir != dstDir {
fs.deferDecRef(srcDirVFSD)
dstDir.IncRef()
}
src.parent = dstDir
src.name = pc
if dstDir.children == nil {
dstDir.children = make(map[string]*Dentry)
}
dstDir.children[pc] = src
virtfs.CommitRenameReplaceDentry(srcVFSD, replaced)
return nil
}
@@ -622,14 +635,15 @@ func (fs *Filesystem) RmdirAt(ctx context.Context, rp *vfs.ResolvingPath) error
if err := checkDeleteLocked(ctx, rp, vfsd); err != nil {
return err
}
if !vfsd.Impl().(*Dentry).isDir() {
d := vfsd.Impl().(*Dentry)
if !d.isDir() {
return syserror.ENOTDIR
}
if inode.HasChildren() {
return syserror.ENOTEMPTY
}
virtfs := rp.VirtualFilesystem()
parentDentry := vfsd.Parent().Impl().(*Dentry)
parentDentry := d.parent
parentDentry.dirMu.Lock()
defer parentDentry.dirMu.Unlock()
@@ -706,11 +720,11 @@ func (fs *Filesystem) SymlinkAt(ctx context.Context, rp *vfs.ResolvingPath, targ
return err
}
defer rp.Mount().EndWrite()
child, err := parentInode.NewSymlink(ctx, pc, target)
childVFSD, err := parentInode.NewSymlink(ctx, pc, target)
if err != nil {
return err
}
parentVFSD.Impl().(*Dentry).InsertChild(pc, child)
parentVFSD.Impl().(*Dentry).InsertChild(pc, childVFSD.Impl().(*Dentry))
return nil
}
@@ -730,11 +744,12 @@ func (fs *Filesystem) UnlinkAt(ctx context.Context, rp *vfs.ResolvingPath) error
if err := checkDeleteLocked(ctx, rp, vfsd); err != nil {
return err
}
if vfsd.Impl().(*Dentry).isDir() {
d := vfsd.Impl().(*Dentry)
if d.isDir() {
return syserror.EISDIR
}
virtfs := rp.VirtualFilesystem()
parentDentry := vfsd.Parent().Impl().(*Dentry)
parentDentry := d.parent
parentDentry.dirMu.Lock()
defer parentDentry.dirMu.Unlock()
mntns := vfs.MountNamespaceFromContext(ctx)
@@ -818,5 +833,5 @@ func (fs *Filesystem) RemovexattrAt(ctx context.Context, rp *vfs.ResolvingPath,
func (fs *Filesystem) PrependPath(ctx context.Context, vfsroot, vd vfs.VirtualDentry, b *fspath.Builder) error {
fs.mu.RLock()
defer fs.mu.RUnlock()
return vfs.GenericPrependPath(vfsroot, vd, b)
return genericPrependPath(vfsroot, vd.Mount(), vd.Dentry().Impl().(*Dentry), b)
}
+1 -1
View File
@@ -370,7 +370,7 @@ func (o *OrderedChildren) Populate(d *Dentry, children map[string]*Dentry) uint3
if err := o.Insert(name, child.VFSDentry()); err != nil {
panic(fmt.Sprintf("Collision when attempting to insert child %q (%+v) into %+v", name, child, d))
}
d.InsertChild(name, child.VFSDentry())
d.InsertChild(name, child)
}
return links
}
+21 -12
View File
@@ -168,17 +168,22 @@ const (
//
// Must be initialized by Init prior to first use.
type Dentry struct {
refs.AtomicRefCount
vfsd vfs.Dentry
vfsd vfs.Dentry
inode Inode
refs.AtomicRefCount
// flags caches useful information about the dentry from the inode. See the
// dflags* consts above. Must be accessed by atomic ops.
flags uint32
// dirMu protects vfsd.children for directory dentries.
dirMu sync.Mutex
parent *Dentry
name string
// dirMu protects children and the names of child Dentries.
dirMu sync.Mutex
children map[string]*Dentry
inode Inode
}
// Init initializes this dentry.
@@ -222,8 +227,8 @@ func (d *Dentry) DecRef() {
func (d *Dentry) destroy() {
d.inode.DecRef() // IncRef from Init.
d.inode = nil
if parent := d.vfsd.Parent(); parent != nil {
parent.DecRef() // IncRef from Dentry.InsertChild.
if d.parent != nil {
d.parent.DecRef() // IncRef from Dentry.InsertChild.
}
}
@@ -233,7 +238,7 @@ func (d *Dentry) destroy() {
// updates the link count on d if required.
//
// Precondition: d must represent a directory inode.
func (d *Dentry) InsertChild(name string, child *vfs.Dentry) {
func (d *Dentry) InsertChild(name string, child *Dentry) {
d.dirMu.Lock()
d.insertChildLocked(name, child)
d.dirMu.Unlock()
@@ -243,13 +248,17 @@ func (d *Dentry) InsertChild(name string, child *vfs.Dentry) {
// preconditions.
//
// Precondition: d.dirMu must be locked.
func (d *Dentry) insertChildLocked(name string, child *vfs.Dentry) {
func (d *Dentry) insertChildLocked(name string, child *Dentry) {
if !d.isDir() {
panic(fmt.Sprintf("InsertChild called on non-directory Dentry: %+v.", d))
}
vfsDentry := d.VFSDentry()
vfsDentry.IncRef() // DecRef in child's Dentry.destroy.
vfsDentry.InsertChild(child, name)
d.IncRef() // DecRef in child's Dentry.destroy.
child.parent = d
child.name = name
if d.children == nil {
d.children = make(map[string]*Dentry)
}
d.children[name] = child
}
// The Inode interface maps filesystem-level operations that operate on paths to
+8 -8
View File
@@ -415,36 +415,36 @@ func iterateDir(ctx context.Context, t *testing.T, s *testutil.System, fd *vfs.F
if d.Name == "." || d.Name == ".." {
continue
}
childPath := path.Join(fd.MappedName(ctx), d.Name)
absPath := path.Join(fd.MappedName(ctx), d.Name)
if d.Type == linux.DT_LNK {
link, err := s.VFS.ReadlinkAt(
ctx,
auth.CredentialsFromContext(ctx),
&vfs.PathOperation{Root: s.Root, Start: s.Root, Path: fspath.Parse(childPath)},
&vfs.PathOperation{Root: s.Root, Start: s.Root, Path: fspath.Parse(absPath)},
)
if err != nil {
t.Errorf("vfsfs.ReadlinkAt(%v) failed: %v", childPath, err)
t.Errorf("vfsfs.ReadlinkAt(%v) failed: %v", absPath, err)
} else {
t.Logf("Skipping symlink: /proc%s => %s", childPath, link)
t.Logf("Skipping symlink: %s => %s", absPath, link)
}
continue
}
t.Logf("Opening: /proc%s", childPath)
t.Logf("Opening: %s", absPath)
child, err := s.VFS.OpenAt(
ctx,
auth.CredentialsFromContext(ctx),
&vfs.PathOperation{Root: s.Root, Start: s.Root, Path: fspath.Parse(childPath)},
&vfs.PathOperation{Root: s.Root, Start: s.Root, Path: fspath.Parse(absPath)},
&vfs.OpenOptions{},
)
if err != nil {
t.Errorf("vfsfs.OpenAt(%v) failed: %v", childPath, err)
t.Errorf("vfsfs.OpenAt(%v) failed: %v", absPath, err)
continue
}
defer child.DecRef()
stat, err := child.Stat(ctx, vfs.StatOptions{})
if err != nil {
t.Errorf("Stat(%v) failed: %v", childPath, err)
t.Errorf("Stat(%v) failed: %v", absPath, err)
}
if got := linux.FileMode(stat.Mode).DirentType(); got != d.Type {
t.Errorf("wrong file mode, stat: %v, dirent: %v", got, d.Type)
+12
View File
@@ -15,6 +15,17 @@ go_template_instance(
},
)
go_template_instance(
name = "fstree",
out = "fstree.go",
package = "tmpfs",
prefix = "generic",
template = "//pkg/sentry/vfs/genericfstree:generic_fstree",
types = {
"Dentry": "dentry",
},
)
go_library(
name = "tmpfs",
srcs = [
@@ -22,6 +33,7 @@ go_library(
"device_file.go",
"directory.go",
"filesystem.go",
"fstree.go",
"named_pipe.go",
"regular_file.go",
"socket_file.go",
@@ -438,13 +438,6 @@ func BenchmarkVFS2TmpfsMountStat(b *testing.B) {
filePathBuilder.WriteByte('/')
}
// Verify that we didn't create any directories under the mount
// point (i.e. they were all created on the submount).
firstDirName := fmt.Sprintf("%d", depth)
if child := mountPoint.Dentry().Child(firstDirName); child != nil {
b.Fatalf("created directory %q under root mount, not submount", firstDirName)
}
// Create the file that will be stat'd.
fd, err := vfsObj.OpenAt(ctx, creds, &vfs.PathOperation{
Root: root,
+63 -21
View File
@@ -15,35 +15,77 @@
package tmpfs
import (
"sync/atomic"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/syserror"
)
type directory struct {
inode inode
// Since directories can't be hard-linked, each directory can only be
// associated with a single dentry, which we can store in the directory
// struct.
dentry dentry
inode inode
// childList is a list containing (1) child Dentries and (2) fake Dentries
// childMap maps the names of the directory's children to their dentries.
// childMap is protected by filesystem.mu.
childMap map[string]*dentry
// numChildren is len(childMap), but accessed using atomic memory
// operations to avoid locking in inode.statTo().
numChildren int64
// childList is a list containing (1) child dentries and (2) fake dentries
// (with inode == nil) that represent the iteration position of
// directoryFDs. childList is used to support directoryFD.IterDirents()
// efficiently. childList is protected by filesystem.mu.
// efficiently. childList is protected by iterMu.
iterMu sync.Mutex
childList dentryList
}
func (fs *filesystem) newDirectory(creds *auth.Credentials, mode linux.FileMode) *inode {
func (fs *filesystem) newDirectory(creds *auth.Credentials, mode linux.FileMode) *directory {
dir := &directory{}
dir.inode.init(dir, fs, creds, linux.S_IFDIR|mode)
dir.inode.nlink = 2 // from "." and parent directory or ".." for root
return &dir.inode
dir.dentry.inode = &dir.inode
dir.dentry.vfsd.Init(&dir.dentry)
return dir
}
// Preconditions: filesystem.mu must be locked for writing. dir must not
// already contain a child with the given name.
func (dir *directory) insertChildLocked(child *dentry, name string) {
child.parent = &dir.dentry
child.name = name
if dir.childMap == nil {
dir.childMap = make(map[string]*dentry)
}
dir.childMap[name] = child
atomic.AddInt64(&dir.numChildren, 1)
dir.iterMu.Lock()
dir.childList.PushBack(child)
dir.iterMu.Unlock()
}
// Preconditions: filesystem.mu must be locked for writing.
func (dir *directory) removeChildLocked(child *dentry) {
delete(dir.childMap, child.name)
atomic.AddInt64(&dir.numChildren, -1)
dir.iterMu.Lock()
dir.childList.Remove(child)
dir.iterMu.Unlock()
}
type directoryFD struct {
fileDescription
vfs.DirectoryFileDescriptionDefaultImpl
// Protected by filesystem.mu.
// Protected by directory.iterMu.
iter *dentry
off int64
}
@@ -51,11 +93,10 @@ type directoryFD struct {
// Release implements vfs.FileDescriptionImpl.Release.
func (fd *directoryFD) Release() {
if fd.iter != nil {
fs := fd.filesystem()
dir := fd.inode().impl.(*directory)
fs.mu.Lock()
dir.iterMu.Lock()
dir.childList.Remove(fd.iter)
fs.mu.Unlock()
dir.iterMu.Unlock()
fd.iter = nil
}
}
@@ -63,10 +104,13 @@ func (fd *directoryFD) Release() {
// IterDirents implements vfs.FileDescriptionImpl.IterDirents.
func (fd *directoryFD) IterDirents(ctx context.Context, cb vfs.IterDirentsCallback) error {
fs := fd.filesystem()
vfsd := fd.vfsfd.VirtualDentry().Dentry()
dir := fd.inode().impl.(*directory)
fs.mu.Lock()
defer fs.mu.Unlock()
// fs.mu is required to read d.parent and dentry.name.
fs.mu.RLock()
defer fs.mu.RUnlock()
dir.iterMu.Lock()
defer dir.iterMu.Unlock()
fd.inode().touchAtime(fd.vfsfd.Mount())
@@ -74,15 +118,16 @@ func (fd *directoryFD) IterDirents(ctx context.Context, cb vfs.IterDirentsCallba
if err := cb.Handle(vfs.Dirent{
Name: ".",
Type: linux.DT_DIR,
Ino: vfsd.Impl().(*dentry).inode.ino,
Ino: dir.inode.ino,
NextOff: 1,
}); err != nil {
return err
}
fd.off++
}
if fd.off == 1 {
parentInode := vfsd.ParentOrSelf().Impl().(*dentry).inode
parentInode := genericParentOrSelf(&dir.dentry).inode
if err := cb.Handle(vfs.Dirent{
Name: "..",
Type: parentInode.direntType(),
@@ -94,7 +139,6 @@ func (fd *directoryFD) IterDirents(ctx context.Context, cb vfs.IterDirentsCallba
fd.off++
}
dir := vfsd.Impl().(*dentry).inode.impl.(*directory)
var child *dentry
if fd.iter == nil {
// Start iteration at the beginning of dir.
@@ -109,7 +153,7 @@ func (fd *directoryFD) IterDirents(ctx context.Context, cb vfs.IterDirentsCallba
// Skip other directoryFD iterators.
if child.inode != nil {
if err := cb.Handle(vfs.Dirent{
Name: child.vfsd.Name(),
Name: child.name,
Type: child.inode.direntType(),
Ino: child.inode.ino,
NextOff: fd.off + 1,
@@ -127,9 +171,9 @@ func (fd *directoryFD) IterDirents(ctx context.Context, cb vfs.IterDirentsCallba
// Seek implements vfs.FileDescriptionImpl.Seek.
func (fd *directoryFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
fs := fd.filesystem()
fs.mu.Lock()
defer fs.mu.Unlock()
dir := fd.inode().impl.(*directory)
dir.iterMu.Lock()
defer dir.iterMu.Unlock()
switch whence {
case linux.SEEK_SET:
@@ -157,8 +201,6 @@ func (fd *directoryFD) Seek(ctx context.Context, offset int64, whence int32) (in
remChildren = offset - 2
}
dir := fd.inode().impl.(*directory)
// Ensure that fd.iter exists and is not linked into dir.childList.
if fd.iter == nil {
fd.iter = &dentry{}
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More