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
This commit is contained in:
parent
827293df54
commit
e7f9b74438
@ -129,6 +129,8 @@ struct devfs_dirent {
|
|||||||
#define DE_WHITEOUT 0x1
|
#define DE_WHITEOUT 0x1
|
||||||
#define DE_DOT 0x2
|
#define DE_DOT 0x2
|
||||||
#define DE_DOTDOT 0x4
|
#define DE_DOTDOT 0x4
|
||||||
|
#define DE_DOOMED 0x8
|
||||||
|
int de_holdcnt;
|
||||||
struct dirent *de_dirent;
|
struct dirent *de_dirent;
|
||||||
TAILQ_ENTRY(devfs_dirent) de_list;
|
TAILQ_ENTRY(devfs_dirent) de_list;
|
||||||
TAILQ_HEAD(, devfs_dirent) de_dlist;
|
TAILQ_HEAD(, devfs_dirent) de_dlist;
|
||||||
@ -150,6 +152,7 @@ struct devfs_mount {
|
|||||||
struct mount *dm_mount;
|
struct mount *dm_mount;
|
||||||
struct devfs_dirent *dm_rootdir;
|
struct devfs_dirent *dm_rootdir;
|
||||||
unsigned dm_generation;
|
unsigned dm_generation;
|
||||||
|
int dm_holdcnt;
|
||||||
struct sx dm_lock;
|
struct sx dm_lock;
|
||||||
devfs_rsnum dm_ruleset;
|
devfs_rsnum dm_ruleset;
|
||||||
};
|
};
|
||||||
@ -160,13 +163,21 @@ extern unsigned devfs_rule_depth;
|
|||||||
|
|
||||||
#define VFSTODEVFS(mp) ((struct devfs_mount *)((mp)->mnt_data))
|
#define VFSTODEVFS(mp) ((struct devfs_mount *)((mp)->mnt_data))
|
||||||
|
|
||||||
|
#define DEVFS_DE_HOLD(de) ((de)->de_holdcnt++)
|
||||||
|
#define DEVFS_DE_DROP(de) (--(de)->de_holdcnt == 0)
|
||||||
|
|
||||||
|
#define DEVFS_DMP_HOLD(dmp) ((dmp)->dm_holdcnt++)
|
||||||
|
#define DEVFS_DMP_DROP(dmp) (--(dmp)->dm_holdcnt == 0)
|
||||||
|
|
||||||
void devfs_rules_apply(struct devfs_mount *dm, struct devfs_dirent *de);
|
void devfs_rules_apply(struct devfs_mount *dm, struct devfs_dirent *de);
|
||||||
void devfs_rules_cleanup (struct devfs_mount *dm);
|
void devfs_rules_cleanup (struct devfs_mount *dm);
|
||||||
int devfs_rules_ioctl(struct devfs_mount *dm, u_long cmd, caddr_t data, struct thread *td);
|
int devfs_rules_ioctl(struct devfs_mount *dm, u_long cmd, caddr_t data, struct thread *td);
|
||||||
int devfs_allocv (struct devfs_dirent *de, struct mount *mp, struct vnode **vpp, struct thread *td);
|
int devfs_allocv (struct devfs_dirent *de, struct mount *mp, struct vnode **vpp, struct thread *td);
|
||||||
void devfs_delete(struct devfs_mount *dm, struct devfs_dirent *de);
|
void devfs_delete(struct devfs_mount *dm, struct devfs_dirent *de);
|
||||||
|
void devfs_dirent_free(struct devfs_dirent *de);
|
||||||
void devfs_populate (struct devfs_mount *dm);
|
void devfs_populate (struct devfs_mount *dm);
|
||||||
void devfs_cleanup (struct devfs_mount *dm);
|
void devfs_cleanup (struct devfs_mount *dm);
|
||||||
|
void devfs_unmount_final(struct devfs_mount *mp);
|
||||||
struct devfs_dirent *devfs_newdirent (char *name, int namelen);
|
struct devfs_dirent *devfs_newdirent (char *name, int namelen);
|
||||||
struct devfs_dirent *devfs_vmkdir (struct devfs_mount *, char *name, int namelen, struct devfs_dirent *dotdot, u_int inode);
|
struct devfs_dirent *devfs_vmkdir (struct devfs_mount *, char *name, int namelen, struct devfs_dirent *dotdot, u_int inode);
|
||||||
struct devfs_dirent *devfs_find (struct devfs_dirent *dd, const char *name, int namelen);
|
struct devfs_dirent *devfs_find (struct devfs_dirent *dd, const char *name, int namelen);
|
||||||
|
@ -179,6 +179,7 @@ devfs_newdirent(char *name, int namelen)
|
|||||||
vfs_timestamp(&de->de_ctime);
|
vfs_timestamp(&de->de_ctime);
|
||||||
de->de_mtime = de->de_atime = de->de_ctime;
|
de->de_mtime = de->de_atime = de->de_ctime;
|
||||||
de->de_links = 1;
|
de->de_links = 1;
|
||||||
|
de->de_holdcnt = 1;
|
||||||
#ifdef MAC
|
#ifdef MAC
|
||||||
mac_init_devfsdirent(de);
|
mac_init_devfsdirent(de);
|
||||||
#endif
|
#endif
|
||||||
@ -229,10 +230,19 @@ devfs_vmkdir(struct devfs_mount *dmp, char *name, int namelen, struct devfs_dire
|
|||||||
return (dd);
|
return (dd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
devfs_dirent_free(struct devfs_dirent *de)
|
||||||
|
{
|
||||||
|
free(de, M_DEVFS3);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
devfs_delete(struct devfs_mount *dm, struct devfs_dirent *de)
|
devfs_delete(struct devfs_mount *dm, struct devfs_dirent *de)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
KASSERT((de->de_flags & DE_DOOMED) == 0,
|
||||||
|
("devfs_delete doomed dirent"));
|
||||||
|
de->de_flags |= DE_DOOMED;
|
||||||
if (de->de_symlink) {
|
if (de->de_symlink) {
|
||||||
free(de->de_symlink, M_DEVFS);
|
free(de->de_symlink, M_DEVFS);
|
||||||
de->de_symlink = NULL;
|
de->de_symlink = NULL;
|
||||||
@ -251,7 +261,8 @@ devfs_delete(struct devfs_mount *dm, struct devfs_dirent *de)
|
|||||||
free_unr(devfs_inos, de->de_inode);
|
free_unr(devfs_inos, de->de_inode);
|
||||||
de->de_inode = 0;
|
de->de_inode = 0;
|
||||||
}
|
}
|
||||||
free(de, M_DEVFS3);
|
if (DEVFS_DE_DROP(de))
|
||||||
|
devfs_dirent_free(de);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -77,6 +77,7 @@ devfs_mount(struct mount *mp, struct thread *td)
|
|||||||
fmp = malloc(sizeof *fmp, M_DEVFS, M_WAITOK | M_ZERO);
|
fmp = malloc(sizeof *fmp, M_DEVFS, M_WAITOK | M_ZERO);
|
||||||
fmp->dm_idx = alloc_unr(devfs_unr);
|
fmp->dm_idx = alloc_unr(devfs_unr);
|
||||||
sx_init(&fmp->dm_lock, "devfsmount");
|
sx_init(&fmp->dm_lock, "devfsmount");
|
||||||
|
fmp->dm_holdcnt = 1;
|
||||||
|
|
||||||
mp->mnt_flag |= MNT_LOCAL;
|
mp->mnt_flag |= MNT_LOCAL;
|
||||||
mp->mnt_kern_flag |= MNTK_MPSAFE;
|
mp->mnt_kern_flag |= MNTK_MPSAFE;
|
||||||
@ -104,14 +105,25 @@ devfs_mount(struct mount *mp, struct thread *td)
|
|||||||
return (0);
|
return (0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
devfs_unmount_final(struct devfs_mount *fmp)
|
||||||
|
{
|
||||||
|
sx_destroy(&fmp->dm_lock);
|
||||||
|
free(fmp, M_DEVFS);
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
devfs_unmount(struct mount *mp, int mntflags, struct thread *td)
|
devfs_unmount(struct mount *mp, int mntflags, struct thread *td)
|
||||||
{
|
{
|
||||||
int error;
|
int error;
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
struct devfs_mount *fmp;
|
struct devfs_mount *fmp;
|
||||||
|
int hold;
|
||||||
|
u_int idx;
|
||||||
|
|
||||||
fmp = VFSTODEVFS(mp);
|
fmp = VFSTODEVFS(mp);
|
||||||
|
KASSERT(fmp->dm_mount != NULL,
|
||||||
|
("devfs_unmount unmounted devfs_mount"));
|
||||||
/* There is 1 extra root vnode reference from devfs_mount(). */
|
/* There is 1 extra root vnode reference from devfs_mount(). */
|
||||||
error = vflush(mp, 1, flags, td);
|
error = vflush(mp, 1, flags, td);
|
||||||
if (error)
|
if (error)
|
||||||
@ -119,11 +131,14 @@ devfs_unmount(struct mount *mp, int mntflags, struct thread *td)
|
|||||||
sx_xlock(&fmp->dm_lock);
|
sx_xlock(&fmp->dm_lock);
|
||||||
devfs_cleanup(fmp);
|
devfs_cleanup(fmp);
|
||||||
devfs_rules_cleanup(fmp);
|
devfs_rules_cleanup(fmp);
|
||||||
sx_xunlock(&fmp->dm_lock);
|
fmp->dm_mount = NULL;
|
||||||
|
hold = --fmp->dm_holdcnt;
|
||||||
mp->mnt_data = NULL;
|
mp->mnt_data = NULL;
|
||||||
sx_destroy(&fmp->dm_lock);
|
idx = fmp->dm_idx;
|
||||||
free_unr(devfs_unr, fmp->dm_idx);
|
sx_xunlock(&fmp->dm_lock);
|
||||||
free(fmp, M_DEVFS);
|
free_unr(devfs_unr, idx);
|
||||||
|
if (hold == 0)
|
||||||
|
devfs_unmount_final(fmp);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,6 +152,7 @@ devfs_root(struct mount *mp, int flags, struct vnode **vpp, struct thread *td)
|
|||||||
struct devfs_mount *dmp;
|
struct devfs_mount *dmp;
|
||||||
|
|
||||||
dmp = VFSTODEVFS(mp);
|
dmp = VFSTODEVFS(mp);
|
||||||
|
sx_xlock(&dmp->dm_lock);
|
||||||
error = devfs_allocv(dmp->dm_rootdir, mp, &vp, td);
|
error = devfs_allocv(dmp->dm_rootdir, mp, &vp, td);
|
||||||
if (error)
|
if (error)
|
||||||
return (error);
|
return (error);
|
||||||
|
@ -125,36 +125,84 @@ devfs_fqpn(char *buf, struct vnode *dvp, struct componentname *cnp)
|
|||||||
return (buf + i);
|
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
|
int
|
||||||
devfs_allocv(struct devfs_dirent *de, struct mount *mp, struct vnode **vpp, struct thread *td)
|
devfs_allocv(struct devfs_dirent *de, struct mount *mp, struct vnode **vpp, struct thread *td)
|
||||||
{
|
{
|
||||||
int error;
|
int error;
|
||||||
struct vnode *vp;
|
struct vnode *vp;
|
||||||
struct cdev *dev;
|
struct cdev *dev;
|
||||||
|
struct devfs_mount *dmp;
|
||||||
|
|
||||||
KASSERT(td == curthread, ("devfs_allocv: td != curthread"));
|
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);
|
mtx_lock(&devfs_de_interlock);
|
||||||
vp = de->de_vnode;
|
vp = de->de_vnode;
|
||||||
if (vp != NULL) {
|
if (vp != NULL) {
|
||||||
VI_LOCK(vp);
|
VI_LOCK(vp);
|
||||||
mtx_unlock(&devfs_de_interlock);
|
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;
|
goto loop;
|
||||||
|
sx_xunlock(&dmp->dm_lock);
|
||||||
*vpp = vp;
|
*vpp = vp;
|
||||||
return (0);
|
return (0);
|
||||||
}
|
}
|
||||||
mtx_unlock(&devfs_de_interlock);
|
mtx_unlock(&devfs_de_interlock);
|
||||||
if (de->de_dirent->d_type == DT_CHR) {
|
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);
|
return (ENOENT);
|
||||||
|
}
|
||||||
dev = &de->de_cdp->cdp_c;
|
dev = &de->de_cdp->cdp_c;
|
||||||
} else {
|
} else {
|
||||||
dev = NULL;
|
dev = NULL;
|
||||||
}
|
}
|
||||||
error = getnewvnode("devfs", mp, &devfs_vnodeops, &vp);
|
error = getnewvnode("devfs", mp, &devfs_vnodeops, &vp);
|
||||||
if (error != 0) {
|
if (error != 0) {
|
||||||
|
devfs_allocv_drop_refs(1, dmp, de);
|
||||||
printf("devfs_allocv: failed to allocate new vnode\n");
|
printf("devfs_allocv: failed to allocate new vnode\n");
|
||||||
return (error);
|
return (error);
|
||||||
}
|
}
|
||||||
@ -182,10 +230,17 @@ loop:
|
|||||||
vp->v_data = de;
|
vp->v_data = de;
|
||||||
de->de_vnode = vp;
|
de->de_vnode = vp;
|
||||||
mtx_unlock(&devfs_de_interlock);
|
mtx_unlock(&devfs_de_interlock);
|
||||||
|
sx_xunlock(&dmp->dm_lock);
|
||||||
vn_lock(vp, LK_EXCLUSIVE | LK_RETRY, td);
|
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
|
#ifdef MAC
|
||||||
mac_associate_vnode_devfs(mp, de, vp);
|
mac_associate_vnode_devfs(mp, de, vp);
|
||||||
#endif
|
#endif
|
||||||
|
sx_xunlock(&dmp->dm_lock);
|
||||||
*vpp = vp;
|
*vpp = vp;
|
||||||
return (0);
|
return (0);
|
||||||
}
|
}
|
||||||
@ -456,7 +511,7 @@ devfs_kqfilter_f(struct file *fp, struct knote *kn)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
devfs_lookupx(struct vop_lookup_args *ap)
|
devfs_lookupx(struct vop_lookup_args *ap, int *dm_unlock)
|
||||||
{
|
{
|
||||||
struct componentname *cnp;
|
struct componentname *cnp;
|
||||||
struct vnode *dvp, **vpp;
|
struct vnode *dvp, **vpp;
|
||||||
@ -508,6 +563,7 @@ devfs_lookupx(struct vop_lookup_args *ap)
|
|||||||
de = TAILQ_NEXT(de, de_list); /* ".." */
|
de = TAILQ_NEXT(de, de_list); /* ".." */
|
||||||
de = de->de_dir;
|
de = de->de_dir;
|
||||||
error = devfs_allocv(de, dvp->v_mount, vpp, td);
|
error = devfs_allocv(de, dvp->v_mount, vpp, td);
|
||||||
|
*dm_unlock = 0;
|
||||||
vn_lock(dvp, LK_EXCLUSIVE | LK_RETRY, td);
|
vn_lock(dvp, LK_EXCLUSIVE | LK_RETRY, td);
|
||||||
return (error);
|
return (error);
|
||||||
}
|
}
|
||||||
@ -565,6 +621,7 @@ devfs_lookupx(struct vop_lookup_args *ap)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
error = devfs_allocv(de, dvp->v_mount, vpp, td);
|
error = devfs_allocv(de, dvp->v_mount, vpp, td);
|
||||||
|
*dm_unlock = 0;
|
||||||
return (error);
|
return (error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -573,11 +630,14 @@ devfs_lookup(struct vop_lookup_args *ap)
|
|||||||
{
|
{
|
||||||
int j;
|
int j;
|
||||||
struct devfs_mount *dmp;
|
struct devfs_mount *dmp;
|
||||||
|
int dm_unlock;
|
||||||
|
|
||||||
dmp = VFSTODEVFS(ap->a_dvp->v_mount);
|
dmp = VFSTODEVFS(ap->a_dvp->v_mount);
|
||||||
|
dm_unlock = 1;
|
||||||
sx_xlock(&dmp->dm_lock);
|
sx_xlock(&dmp->dm_lock);
|
||||||
j = devfs_lookupx(ap);
|
j = devfs_lookupx(ap, &dm_unlock);
|
||||||
sx_xunlock(&dmp->dm_lock);
|
if (dm_unlock == 1)
|
||||||
|
sx_xunlock(&dmp->dm_lock);
|
||||||
return (j);
|
return (j);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -599,7 +659,6 @@ devfs_mknod(struct vop_mknod_args *ap)
|
|||||||
return (EOPNOTSUPP);
|
return (EOPNOTSUPP);
|
||||||
dvp = ap->a_dvp;
|
dvp = ap->a_dvp;
|
||||||
dmp = VFSTODEVFS(dvp->v_mount);
|
dmp = VFSTODEVFS(dvp->v_mount);
|
||||||
sx_xlock(&dmp->dm_lock);
|
|
||||||
|
|
||||||
cnp = ap->a_cnp;
|
cnp = ap->a_cnp;
|
||||||
vpp = ap->a_vpp;
|
vpp = ap->a_vpp;
|
||||||
@ -607,6 +666,7 @@ devfs_mknod(struct vop_mknod_args *ap)
|
|||||||
dd = dvp->v_data;
|
dd = dvp->v_data;
|
||||||
|
|
||||||
error = ENOENT;
|
error = ENOENT;
|
||||||
|
sx_xlock(&dmp->dm_lock);
|
||||||
TAILQ_FOREACH(de, &dd->de_dlist, de_list) {
|
TAILQ_FOREACH(de, &dd->de_dlist, de_list) {
|
||||||
if (cnp->cn_namelen != de->de_dirent->d_namlen)
|
if (cnp->cn_namelen != de->de_dirent->d_namlen)
|
||||||
continue;
|
continue;
|
||||||
@ -621,6 +681,7 @@ devfs_mknod(struct vop_mknod_args *ap)
|
|||||||
goto notfound;
|
goto notfound;
|
||||||
de->de_flags &= ~DE_WHITEOUT;
|
de->de_flags &= ~DE_WHITEOUT;
|
||||||
error = devfs_allocv(de, dvp->v_mount, vpp, td);
|
error = devfs_allocv(de, dvp->v_mount, vpp, td);
|
||||||
|
return (error);
|
||||||
notfound:
|
notfound:
|
||||||
sx_xunlock(&dmp->dm_lock);
|
sx_xunlock(&dmp->dm_lock);
|
||||||
return (error);
|
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);
|
mac_create_devfs_symlink(ap->a_cnp->cn_cred, dmp->dm_mount, dd, de);
|
||||||
#endif
|
#endif
|
||||||
TAILQ_INSERT_TAIL(&dd->de_dlist, de, de_list);
|
TAILQ_INSERT_TAIL(&dd->de_dlist, de, de_list);
|
||||||
devfs_allocv(de, ap->a_dvp->v_mount, ap->a_vpp, td);
|
return (devfs_allocv(de, ap->a_dvp->v_mount, ap->a_vpp, td));
|
||||||
sx_xunlock(&dmp->dm_lock);
|
|
||||||
return (0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ARGSUSED */
|
/* ARGSUSED */
|
||||||
|
Loading…
x
Reference in New Issue
Block a user