diff options
author | Ayush Ranjan <ayushranjan@google.com> | 2020-10-12 16:27:33 -0700 |
---|---|---|
committer | gVisor bot <gvisor-bot@google.com> | 2020-10-12 16:29:33 -0700 |
commit | e7bbe70f79aa9308c2eb54b057ee5779b22f478e (patch) | |
tree | eaead5c4e56fd0440fb40b02d056b8022a53ace2 /pkg/sentry/fsimpl/devpts/devpts.go | |
parent | 93bc0777be45994c4fcd7611a743f99fe73b36fa (diff) |
[vfs] kernfs: Fix inode memory leak issue.
This change aims to fix the memory leak issue reported inĀ #3933.
Background:
VFS2 kernfs kept accumulating invalid dentries if those dentries were not
walked on. After substantial consideration of the problem by our team, we
decided to have an LRU cache solution. This change is the first part to that
solution, where we don't cache anything. The LRU cache can be added on top of
this.
What has changed:
- Introduced the concept of an inode tree in kernfs.OrderedChildren.
This is helpful is cases where the lifecycle of an inode is different from
that of a dentry.
- OrderedChildren now deals with initialized inodes instead of initialized
dentries. It now implements Lookup() where it constructs a new dentry
using the inode.
- OrderedChildren holds a ref on all its children inodes. With this change,
now an inode can "outlive" a dentry pointing to it. See comments in
kernfs.OrderedChildren.
- The kernfs dentry tree is solely maintained by kernfs only. Inode
implementations can not modify the dentry tree.
- Dentries that reach ref count 0 are removed from the dentry tree.
- revalidateChildLocked now defer-DecRefs the newly created dentry from
Inode.Lookup(), limiting its life to the current filesystem operation. If
refs are picked on the dentry during the FS op (via an FD or something),
then it will stick around and will be removed when the FD is closed. So there
is essentially _no caching_ for Look()ed up dentries.
- kernfs.DecRef does not have the precondition that fs.mu must be locked.
Fixes #3933
PiperOrigin-RevId: 336768576
Diffstat (limited to 'pkg/sentry/fsimpl/devpts/devpts.go')
-rw-r--r-- | pkg/sentry/fsimpl/devpts/devpts.go | 47 |
1 files changed, 26 insertions, 21 deletions
diff --git a/pkg/sentry/fsimpl/devpts/devpts.go b/pkg/sentry/fsimpl/devpts/devpts.go index 903135fae..8e34e26df 100644 --- a/pkg/sentry/fsimpl/devpts/devpts.go +++ b/pkg/sentry/fsimpl/devpts/devpts.go @@ -87,7 +87,9 @@ func (fstype FilesystemType) newFilesystem(vfsObj *vfs.VirtualFilesystem, creds root.InodeAttrs.Init(creds, linux.UNNAMED_MAJOR, devMinor, 1, linux.ModeDirectory|0555) root.OrderedChildren.Init(kernfs.OrderedChildrenOptions{}) root.EnableLeakCheck() - root.dentry.Init(root) + + var rootD kernfs.Dentry + rootD.Init(&fs.Filesystem, root) // Construct the pts master inode and dentry. Linux always uses inode // id 2 for ptmx. See fs/devpts/inode.c:mknod_ptmx. @@ -95,15 +97,14 @@ func (fstype FilesystemType) newFilesystem(vfsObj *vfs.VirtualFilesystem, creds root: root, } master.InodeAttrs.Init(creds, linux.UNNAMED_MAJOR, devMinor, 2, linux.ModeCharacterDevice|0666) - master.dentry.Init(master) // Add the master as a child of the root. - links := root.OrderedChildren.Populate(&root.dentry, map[string]*kernfs.Dentry{ - "ptmx": &master.dentry, + links := root.OrderedChildren.Populate(map[string]kernfs.Inode{ + "ptmx": master, }) root.IncLinks(links) - return fs, &root.dentry, nil + return fs, &rootD, nil } // Release implements vfs.FilesystemImpl.Release. @@ -117,24 +118,19 @@ func (fs *filesystem) Release(ctx context.Context) { // +stateify savable type rootInode struct { implStatFS - kernfs.AlwaysValid + kernfs.InodeAlwaysValid kernfs.InodeAttrs kernfs.InodeDirectoryNoNewChildren kernfs.InodeNotSymlink + kernfs.InodeTemporary // This holds no meaning as this inode can't be Looked up and is always valid. kernfs.OrderedChildren rootInodeRefs locks vfs.FileLocks - // Keep a reference to this inode's dentry. - dentry kernfs.Dentry - // master is the master pty inode. Immutable. master *masterInode - // root is the root directory inode for this filesystem. Immutable. - root *rootInode - // mu protects the fields below. mu sync.Mutex `state:"nosave"` @@ -173,21 +169,24 @@ func (i *rootInode) allocateTerminal(creds *auth.Credentials) (*Terminal, error) // Linux always uses pty index + 3 as the inode id. See // fs/devpts/inode.c:devpts_pty_new(). replica.InodeAttrs.Init(creds, i.InodeAttrs.DevMajor(), i.InodeAttrs.DevMinor(), uint64(idx+3), linux.ModeCharacterDevice|0600) - replica.dentry.Init(replica) i.replicas[idx] = replica return t, nil } // masterClose is called when the master end of t is closed. -func (i *rootInode) masterClose(t *Terminal) { +func (i *rootInode) masterClose(ctx context.Context, t *Terminal) { i.mu.Lock() defer i.mu.Unlock() // Sanity check that replica with idx exists. - if _, ok := i.replicas[t.n]; !ok { + ri, ok := i.replicas[t.n] + if !ok { panic(fmt.Sprintf("pty with index %d does not exist", t.n)) } + + // Drop the ref on replica inode taken during rootInode.allocateTerminal. + ri.DecRef(ctx) delete(i.replicas, t.n) } @@ -203,16 +202,22 @@ func (i *rootInode) Open(ctx context.Context, rp *vfs.ResolvingPath, d *kernfs.D } // Lookup implements kernfs.Inode.Lookup. -func (i *rootInode) Lookup(ctx context.Context, name string) (*kernfs.Dentry, error) { +func (i *rootInode) Lookup(ctx context.Context, name string) (kernfs.Inode, error) { + // Check if a static entry was looked up. + if d, err := i.OrderedChildren.Lookup(ctx, name); err == nil { + return d, nil + } + + // Not a static entry. idx, err := strconv.ParseUint(name, 10, 32) if err != nil { return nil, syserror.ENOENT } i.mu.Lock() defer i.mu.Unlock() - if si, ok := i.replicas[uint32(idx)]; ok { - si.dentry.IncRef() - return &si.dentry, nil + if ri, ok := i.replicas[uint32(idx)]; ok { + ri.IncRef() // This ref is passed to the dentry upon creation via Init. + return ri, nil } return nil, syserror.ENOENT @@ -243,8 +248,8 @@ func (i *rootInode) IterDirents(ctx context.Context, cb vfs.IterDirentsCallback, } // DecRef implements kernfs.Inode.DecRef. -func (i *rootInode) DecRef(context.Context) { - i.rootInodeRefs.DecRef(i.Destroy) +func (i *rootInode) DecRef(ctx context.Context) { + i.rootInodeRefs.DecRef(func() { i.Destroy(ctx) }) } // +stateify savable |