diff options
author | kib <kib@FreeBSD.org> | 2006-09-18 13:23:08 +0000 |
---|---|---|
committer | kib <kib@FreeBSD.org> | 2006-09-18 13:23:08 +0000 |
commit | ecf34f450410ca3aac345b52bece9c0fb4ae459c (patch) | |
tree | a22547e48d6aa3545a66b54d100fbcab374bd630 | |
parent | e84508ee5e22076fddd4af5d0ca3092768e7e584 (diff) | |
download | FreeBSD-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
-rw-r--r-- | sys/fs/devfs/devfs.h | 11 | ||||
-rw-r--r-- | sys/fs/devfs/devfs_devs.c | 13 | ||||
-rw-r--r-- | sys/fs/devfs/devfs_vfsops.c | 24 | ||||
-rw-r--r-- | sys/fs/devfs/devfs_vnops.c | 81 |
4 files changed, 113 insertions, 16 deletions
diff --git a/sys/fs/devfs/devfs.h b/sys/fs/devfs/devfs.h index 109b9cd..b01f586 100644 --- a/sys/fs/devfs/devfs.h +++ b/sys/fs/devfs/devfs.h @@ -129,6 +129,8 @@ struct devfs_dirent { #define DE_WHITEOUT 0x1 #define DE_DOT 0x2 #define DE_DOTDOT 0x4 +#define DE_DOOMED 0x8 + int de_holdcnt; struct dirent *de_dirent; TAILQ_ENTRY(devfs_dirent) de_list; TAILQ_HEAD(, devfs_dirent) de_dlist; @@ -150,6 +152,7 @@ struct devfs_mount { struct mount *dm_mount; struct devfs_dirent *dm_rootdir; unsigned dm_generation; + int dm_holdcnt; struct sx dm_lock; devfs_rsnum dm_ruleset; }; @@ -160,13 +163,21 @@ extern unsigned devfs_rule_depth; #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_cleanup (struct devfs_mount *dm); 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); 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_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_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); diff --git a/sys/fs/devfs/devfs_devs.c b/sys/fs/devfs/devfs_devs.c index d933f9f..c8408ce 100644 --- a/sys/fs/devfs/devfs_devs.c +++ b/sys/fs/devfs/devfs_devs.c @@ -179,6 +179,7 @@ devfs_newdirent(char *name, int namelen) vfs_timestamp(&de->de_ctime); de->de_mtime = de->de_atime = de->de_ctime; de->de_links = 1; + de->de_holdcnt = 1; #ifdef MAC mac_init_devfsdirent(de); #endif @@ -230,9 +231,18 @@ devfs_vmkdir(struct devfs_mount *dmp, char *name, int namelen, struct devfs_dire } void +devfs_dirent_free(struct devfs_dirent *de) +{ + free(de, M_DEVFS3); +} + +void 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) { free(de->de_symlink, M_DEVFS); 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); de->de_inode = 0; } - free(de, M_DEVFS3); + if (DEVFS_DE_DROP(de)) + devfs_dirent_free(de); } /* diff --git a/sys/fs/devfs/devfs_vfsops.c b/sys/fs/devfs/devfs_vfsops.c index 17db07a..8c1069a 100644 --- a/sys/fs/devfs/devfs_vfsops.c +++ b/sys/fs/devfs/devfs_vfsops.c @@ -77,6 +77,7 @@ devfs_mount(struct mount *mp, struct thread *td) fmp = malloc(sizeof *fmp, M_DEVFS, M_WAITOK | M_ZERO); fmp->dm_idx = alloc_unr(devfs_unr); sx_init(&fmp->dm_lock, "devfsmount"); + fmp->dm_holdcnt = 1; mp->mnt_flag |= MNT_LOCAL; mp->mnt_kern_flag |= MNTK_MPSAFE; @@ -104,14 +105,25 @@ devfs_mount(struct mount *mp, struct thread *td) return (0); } +void +devfs_unmount_final(struct devfs_mount *fmp) +{ + sx_destroy(&fmp->dm_lock); + free(fmp, M_DEVFS); +} + static int devfs_unmount(struct mount *mp, int mntflags, struct thread *td) { int error; int flags = 0; struct devfs_mount *fmp; + int hold; + u_int idx; fmp = VFSTODEVFS(mp); + KASSERT(fmp->dm_mount != NULL, + ("devfs_unmount unmounted devfs_mount")); /* There is 1 extra root vnode reference from devfs_mount(). */ error = vflush(mp, 1, flags, td); if (error) @@ -119,11 +131,14 @@ devfs_unmount(struct mount *mp, int mntflags, struct thread *td) sx_xlock(&fmp->dm_lock); devfs_cleanup(fmp); devfs_rules_cleanup(fmp); - sx_xunlock(&fmp->dm_lock); + fmp->dm_mount = NULL; + hold = --fmp->dm_holdcnt; mp->mnt_data = NULL; - sx_destroy(&fmp->dm_lock); - free_unr(devfs_unr, fmp->dm_idx); - free(fmp, M_DEVFS); + idx = fmp->dm_idx; + sx_xunlock(&fmp->dm_lock); + free_unr(devfs_unr, idx); + if (hold == 0) + devfs_unmount_final(fmp); return 0; } @@ -137,6 +152,7 @@ devfs_root(struct mount *mp, int flags, struct vnode **vpp, struct thread *td) struct devfs_mount *dmp; dmp = VFSTODEVFS(mp); + sx_xlock(&dmp->dm_lock); error = devfs_allocv(dmp->dm_rootdir, mp, &vp, td); if (error) return (error); 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 */ |