mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 := ®ularFileFD{}
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user