summaryrefslogtreecommitdiffstats
path: root/sys/fs
diff options
context:
space:
mode:
authorkib <kib@FreeBSD.org>2013-05-11 11:17:44 +0000
committerkib <kib@FreeBSD.org>2013-05-11 11:17:44 +0000
commitdfd7a7f46d4cb6fa7e3cfa78fcff22319891d343 (patch)
treeeec4ec06bf51508293c37264e9b64bf402c90040 /sys/fs
parentf43ee707dde8c47ed5b7e330f329246e0c77e9fe (diff)
downloadFreeBSD-src-dfd7a7f46d4cb6fa7e3cfa78fcff22319891d343.zip
FreeBSD-src-dfd7a7f46d4cb6fa7e3cfa78fcff22319891d343.tar.gz
- Fix nullfs vnode reference leak in nullfs_reclaim_lowervp(). The
null_hashget() obtains the reference on the nullfs vnode, which must be dropped. - Fix a wart which existed from the introduction of the nullfs caching, do not unlock lower vnode in the nullfs_reclaim_lowervp(). It should be innocent, but now it is also formally safe. Inform the nullfs_reclaim() about this using the NULLV_NOUNLOCK flag set on nullfs inode. - Add a callback to the upper filesystems for the lower vnode unlinking. When inactivating a nullfs vnode, check if the lower vnode was unlinked, indicated by nullfs flag NULLV_DROP or VV_NOSYNC on the lower vnode, and reclaim upper vnode if so. This allows nullfs to purge cached vnodes for the unlinked lower vnode, avoiding excessive caching. Reported by: G??ran L??wkrantz <goran.lowkrantz@ismobile.com> Tested by: pho Sponsored by: The FreeBSD Foundation MFC after: 2 weeks
Diffstat (limited to 'sys/fs')
-rw-r--r--sys/fs/nullfs/null.h4
-rw-r--r--sys/fs/nullfs/null_subr.c1
-rw-r--r--sys/fs/nullfs/null_vfsops.c33
-rw-r--r--sys/fs/nullfs/null_vnops.c19
4 files changed, 50 insertions, 7 deletions
diff --git a/sys/fs/nullfs/null.h b/sys/fs/nullfs/null.h
index 4f37020..0972dfc 100644
--- a/sys/fs/nullfs/null.h
+++ b/sys/fs/nullfs/null.h
@@ -53,8 +53,12 @@ struct null_node {
LIST_ENTRY(null_node) null_hash; /* Hash list */
struct vnode *null_lowervp; /* VREFed once */
struct vnode *null_vnode; /* Back pointer */
+ u_int null_flags;
};
+#define NULLV_NOUNLOCK 0x0001
+#define NULLV_DROP 0x0002
+
#define MOUNTTONULLMOUNT(mp) ((struct null_mount *)((mp)->mnt_data))
#define VTONULL(vp) ((struct null_node *)(vp)->v_data)
#define NULLTOV(xp) ((xp)->null_vnode)
diff --git a/sys/fs/nullfs/null_subr.c b/sys/fs/nullfs/null_subr.c
index 90e56ea..fa6c4af 100644
--- a/sys/fs/nullfs/null_subr.c
+++ b/sys/fs/nullfs/null_subr.c
@@ -247,6 +247,7 @@ null_nodeget(mp, lowervp, vpp)
xp->null_vnode = vp;
xp->null_lowervp = lowervp;
+ xp->null_flags = 0;
vp->v_type = lowervp->v_type;
vp->v_data = xp;
vp->v_vnlock = lowervp->v_vnlock;
diff --git a/sys/fs/nullfs/null_vfsops.c b/sys/fs/nullfs/null_vfsops.c
index 02932bd..ad02236 100644
--- a/sys/fs/nullfs/null_vfsops.c
+++ b/sys/fs/nullfs/null_vfsops.c
@@ -65,7 +65,6 @@ static vfs_statfs_t nullfs_statfs;
static vfs_unmount_t nullfs_unmount;
static vfs_vget_t nullfs_vget;
static vfs_extattrctl_t nullfs_extattrctl;
-static vfs_reclaim_lowervp_t nullfs_reclaim_lowervp;
/*
* Mount null layer
@@ -391,8 +390,37 @@ nullfs_reclaim_lowervp(struct mount *mp, struct vnode *lowervp)
vp = null_hashget(mp, lowervp);
if (vp == NULL)
return;
+ VTONULL(vp)->null_flags |= NULLV_NOUNLOCK;
vgone(vp);
- vn_lock(lowervp, LK_EXCLUSIVE | LK_RETRY);
+ vput(vp);
+}
+
+static void
+nullfs_unlink_lowervp(struct mount *mp, struct vnode *lowervp)
+{
+ struct vnode *vp;
+ struct null_node *xp;
+
+ vp = null_hashget(mp, lowervp);
+ if (vp == NULL)
+ return;
+ xp = VTONULL(vp);
+ xp->null_flags |= NULLV_DROP | NULLV_NOUNLOCK;
+ vhold(vp);
+ vunref(vp);
+
+ /*
+ * If vunref() dropped the last use reference on the nullfs
+ * vnode, it must be reclaimed, and its lock was split from
+ * the lower vnode lock. Need to do extra unlock before
+ * allowing the final vdrop() to free the vnode.
+ */
+ if (vp->v_usecount == 0) {
+ KASSERT((vp->v_iflag & VI_DOOMED) != 0,
+ ("not reclaimed %p", vp));
+ VOP_UNLOCK(vp, 0);
+ }
+ vdrop(vp);
}
static struct vfsops null_vfsops = {
@@ -408,6 +436,7 @@ static struct vfsops null_vfsops = {
.vfs_unmount = nullfs_unmount,
.vfs_vget = nullfs_vget,
.vfs_reclaim_lowervp = nullfs_reclaim_lowervp,
+ .vfs_unlink_lowervp = nullfs_unlink_lowervp,
};
VFS_SET(null_vfsops, nullfs, VFCF_LOOPBACK | VFCF_JAIL);
diff --git a/sys/fs/nullfs/null_vnops.c b/sys/fs/nullfs/null_vnops.c
index f59865f..6ff15ee 100644
--- a/sys/fs/nullfs/null_vnops.c
+++ b/sys/fs/nullfs/null_vnops.c
@@ -692,18 +692,24 @@ null_unlock(struct vop_unlock_args *ap)
static int
null_inactive(struct vop_inactive_args *ap __unused)
{
- struct vnode *vp;
+ struct vnode *vp, *lvp;
+ struct null_node *xp;
struct mount *mp;
struct null_mount *xmp;
vp = ap->a_vp;
+ xp = VTONULL(vp);
+ lvp = NULLVPTOLOWERVP(vp);
mp = vp->v_mount;
xmp = MOUNTTONULLMOUNT(mp);
- if ((xmp->nullm_flags & NULLM_CACHE) == 0) {
+ if ((xmp->nullm_flags & NULLM_CACHE) == 0 ||
+ (xp->null_flags & NULLV_DROP) != 0 ||
+ (lvp->v_vflag & VV_NOSYNC) != 0) {
/*
* If this is the last reference and caching of the
- * nullfs vnodes is not enabled, then free up the
- * vnode so as not to tie up the lower vnodes.
+ * nullfs vnodes is not enabled, or the lower vnode is
+ * deleted, then free up the vnode so as not to tie up
+ * the lower vnodes.
*/
vp->v_object = NULL;
vrecycle(vp);
@@ -748,7 +754,10 @@ null_reclaim(struct vop_reclaim_args *ap)
*/
if (vp->v_writecount > 0)
VOP_ADD_WRITECOUNT(lowervp, -1);
- vput(lowervp);
+ if ((xp->null_flags & NULLV_NOUNLOCK) != 0)
+ vunref(lowervp);
+ else
+ vput(lowervp);
free(xp, M_NULLFSNODE);
return (0);
OpenPOWER on IntegriCloud