summaryrefslogtreecommitdiffstats
path: root/sys/fs/devfs/devfs_vnops.c
diff options
context:
space:
mode:
authorkib <kib@FreeBSD.org>2006-09-18 13:23:08 +0000
committerkib <kib@FreeBSD.org>2006-09-18 13:23:08 +0000
commitecf34f450410ca3aac345b52bece9c0fb4ae459c (patch)
treea22547e48d6aa3545a66b54d100fbcab374bd630 /sys/fs/devfs/devfs_vnops.c
parente84508ee5e22076fddd4af5d0ca3092768e7e584 (diff)
downloadFreeBSD-src-ecf34f450410ca3aac345b52bece9c0fb4ae459c.zip
FreeBSD-src-ecf34f450410ca3aac345b52bece9c0fb4ae459c.tar.gz
Resolve the devfs deadlock caused by LOR between devfs_mount->dm_lock and
vnode lock in devfs_allocv. Do this by temporary dropping dm_lock around vnode locking. For safe operation, add hold counters for both devfs_mount and devfs_dirent, and DE_DOOMED flag for devfs_dirent. The facilities allow to continue after dropping of the dm_lock, by making sure that referenced memory does not disappear. Reviewed by: tegge Tested by: kris Approved by: kan (mentor) PR: kern/102335
Diffstat (limited to 'sys/fs/devfs/devfs_vnops.c')
-rw-r--r--sys/fs/devfs/devfs_vnops.c81
1 files changed, 70 insertions, 11 deletions
diff --git a/sys/fs/devfs/devfs_vnops.c b/sys/fs/devfs/devfs_vnops.c
index 9d2ca1e..79f6c60 100644
--- a/sys/fs/devfs/devfs_vnops.c
+++ b/sys/fs/devfs/devfs_vnops.c
@@ -125,36 +125,84 @@ devfs_fqpn(char *buf, struct vnode *dvp, struct componentname *cnp)
return (buf + i);
}
+static int
+devfs_allocv_drop_refs(int drop_dm_lock, struct devfs_mount *dmp,
+ struct devfs_dirent *de)
+{
+ int not_found;
+
+ not_found = 0;
+ if (de->de_flags & DE_DOOMED)
+ not_found = 1;
+ if (DEVFS_DE_DROP(de)) {
+ KASSERT(not_found == 1, ("DEVFS de dropped but not doomed"));
+ devfs_dirent_free(de);
+ }
+ if (DEVFS_DMP_DROP(dmp)) {
+ KASSERT(not_found == 1,
+ ("DEVFS mount struct freed before dirent"));
+ not_found = 2;
+ sx_xunlock(&dmp->dm_lock);
+ devfs_unmount_final(dmp);
+ }
+ if (not_found == 1 || drop_dm_lock)
+ sx_unlock(&dmp->dm_lock);
+ return (not_found);
+}
+
+/*
+ * devfs_allocv shall be entered with dmp->dm_lock held, and it drops
+ * it on return.
+ */
int
devfs_allocv(struct devfs_dirent *de, struct mount *mp, struct vnode **vpp, struct thread *td)
{
int error;
struct vnode *vp;
struct cdev *dev;
+ struct devfs_mount *dmp;
KASSERT(td == curthread, ("devfs_allocv: td != curthread"));
-loop:
-
+ dmp = VFSTODEVFS(mp);
+ if (de->de_flags & DE_DOOMED) {
+ sx_xunlock(&dmp->dm_lock);
+ return (ENOENT);
+ }
+ loop:
+ DEVFS_DE_HOLD(de);
+ DEVFS_DMP_HOLD(dmp);
mtx_lock(&devfs_de_interlock);
vp = de->de_vnode;
if (vp != NULL) {
VI_LOCK(vp);
mtx_unlock(&devfs_de_interlock);
- if (vget(vp, LK_EXCLUSIVE | LK_INTERLOCK, td))
+ sx_xunlock(&dmp->dm_lock);
+ error = vget(vp, LK_EXCLUSIVE | LK_INTERLOCK, td);
+ sx_xlock(&dmp->dm_lock);
+ if (devfs_allocv_drop_refs(0, dmp, de)) {
+ if (error == 0)
+ vput(vp);
+ return (ENOENT);
+ }
+ else if (error)
goto loop;
+ sx_xunlock(&dmp->dm_lock);
*vpp = vp;
return (0);
}
mtx_unlock(&devfs_de_interlock);
if (de->de_dirent->d_type == DT_CHR) {
- if (!(de->de_cdp->cdp_flags & CDP_ACTIVE))
+ if (!(de->de_cdp->cdp_flags & CDP_ACTIVE)) {
+ devfs_allocv_drop_refs(1, dmp, de);
return (ENOENT);
+ }
dev = &de->de_cdp->cdp_c;
} else {
dev = NULL;
}
error = getnewvnode("devfs", mp, &devfs_vnodeops, &vp);
if (error != 0) {
+ devfs_allocv_drop_refs(1, dmp, de);
printf("devfs_allocv: failed to allocate new vnode\n");
return (error);
}
@@ -182,10 +230,17 @@ loop:
vp->v_data = de;
de->de_vnode = vp;
mtx_unlock(&devfs_de_interlock);
+ sx_xunlock(&dmp->dm_lock);
vn_lock(vp, LK_EXCLUSIVE | LK_RETRY, td);
+ sx_xlock(&dmp->dm_lock);
+ if (devfs_allocv_drop_refs(0, dmp, de)) {
+ vput(vp);
+ return (ENOENT);
+ }
#ifdef MAC
mac_associate_vnode_devfs(mp, de, vp);
#endif
+ sx_xunlock(&dmp->dm_lock);
*vpp = vp;
return (0);
}
@@ -456,7 +511,7 @@ devfs_kqfilter_f(struct file *fp, struct knote *kn)
}
static int
-devfs_lookupx(struct vop_lookup_args *ap)
+devfs_lookupx(struct vop_lookup_args *ap, int *dm_unlock)
{
struct componentname *cnp;
struct vnode *dvp, **vpp;
@@ -508,6 +563,7 @@ devfs_lookupx(struct vop_lookup_args *ap)
de = TAILQ_NEXT(de, de_list); /* ".." */
de = de->de_dir;
error = devfs_allocv(de, dvp->v_mount, vpp, td);
+ *dm_unlock = 0;
vn_lock(dvp, LK_EXCLUSIVE | LK_RETRY, td);
return (error);
}
@@ -565,6 +621,7 @@ devfs_lookupx(struct vop_lookup_args *ap)
}
}
error = devfs_allocv(de, dvp->v_mount, vpp, td);
+ *dm_unlock = 0;
return (error);
}
@@ -573,11 +630,14 @@ devfs_lookup(struct vop_lookup_args *ap)
{
int j;
struct devfs_mount *dmp;
+ int dm_unlock;
dmp = VFSTODEVFS(ap->a_dvp->v_mount);
+ dm_unlock = 1;
sx_xlock(&dmp->dm_lock);
- j = devfs_lookupx(ap);
- sx_xunlock(&dmp->dm_lock);
+ j = devfs_lookupx(ap, &dm_unlock);
+ if (dm_unlock == 1)
+ sx_xunlock(&dmp->dm_lock);
return (j);
}
@@ -599,7 +659,6 @@ devfs_mknod(struct vop_mknod_args *ap)
return (EOPNOTSUPP);
dvp = ap->a_dvp;
dmp = VFSTODEVFS(dvp->v_mount);
- sx_xlock(&dmp->dm_lock);
cnp = ap->a_cnp;
vpp = ap->a_vpp;
@@ -607,6 +666,7 @@ devfs_mknod(struct vop_mknod_args *ap)
dd = dvp->v_data;
error = ENOENT;
+ sx_xlock(&dmp->dm_lock);
TAILQ_FOREACH(de, &dd->de_dlist, de_list) {
if (cnp->cn_namelen != de->de_dirent->d_namlen)
continue;
@@ -621,6 +681,7 @@ devfs_mknod(struct vop_mknod_args *ap)
goto notfound;
de->de_flags &= ~DE_WHITEOUT;
error = devfs_allocv(de, dvp->v_mount, vpp, td);
+ return (error);
notfound:
sx_xunlock(&dmp->dm_lock);
return (error);
@@ -1124,9 +1185,7 @@ devfs_symlink(struct vop_symlink_args *ap)
mac_create_devfs_symlink(ap->a_cnp->cn_cred, dmp->dm_mount, dd, de);
#endif
TAILQ_INSERT_TAIL(&dd->de_dlist, de, de_list);
- devfs_allocv(de, ap->a_dvp->v_mount, ap->a_vpp, td);
- sx_xunlock(&dmp->dm_lock);
- return (0);
+ return (devfs_allocv(de, ap->a_dvp->v_mount, ap->a_vpp, td));
}
/* ARGSUSED */
OpenPOWER on IntegriCloud