Index: sys/conf/files =================================================================== --- sys/conf/files (revision 210108) +++ sys/conf/files (working copy) @@ -1869,6 +1869,7 @@ fs/coda/coda_vfsops.c optional vcoda fs/coda/coda_vnops.c optional vcoda fs/deadfs/dead_vnops.c standard fs/devfs/devfs_devs.c standard +fs/devfs/devfs_dir.c standard fs/devfs/devfs_rule.c standard fs/devfs/devfs_vfsops.c standard fs/devfs/devfs_vnops.c standard Index: sys/fs/devfs/devfs_dir.c =================================================================== --- sys/fs/devfs/devfs_dir.c (revision 0) +++ sys/fs/devfs/devfs_dir.c (revision 0) @@ -0,0 +1,171 @@ +/*- + * Copyright (c) 2010 Jaakko Heinonen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +struct dirlistent { + char *dir; + u_int refcnt; + LIST_ENTRY(dirlistent) link; +}; + +static LIST_HEAD(, dirlistent) devfs_dirlist = + LIST_HEAD_INITIALIZER(devfs_dirlist); + +static MALLOC_DEFINE(M_DEVFS4, "DEVFS4", "DEVFS directory list"); + +static struct mtx dirlist_mtx; +MTX_SYSINIT(dirlist_mtx, &dirlist_mtx, "devfs dirlist lock", MTX_DEF); + +/* Returns 1 if the path is in the directory list. */ +int +devfs_dir_find(const char *path) +{ + struct dirlistent *dle; + + mtx_lock(&dirlist_mtx); + LIST_FOREACH(dle, &devfs_dirlist, link) { + if (devfs_pathpath(dle->dir, path) != 0) { + mtx_unlock(&dirlist_mtx); + return (1); + } + } + mtx_unlock(&dirlist_mtx); + + return (0); +} + +static struct dirlistent * +devfs_dir_findent_locked(const char *dir) +{ + struct dirlistent *dle; + + mtx_assert(&dirlist_mtx, MA_OWNED); + + LIST_FOREACH(dle, &devfs_dirlist, link) { + if (strcmp(dir, dle->dir) == 0) + return (dle); + } + + return (NULL); +} + +static void +devfs_dir_ref(const char *dir) +{ + struct dirlistent *dle, *dle_new; + + if (*dir == '\0') + return; + + dle_new = malloc(sizeof(*dle), M_DEVFS4, M_WAITOK); + dle_new->dir = strdup(dir, M_DEVFS4); + dle_new->refcnt = 1; + + mtx_lock(&dirlist_mtx); + dle = devfs_dir_findent_locked(dir); + if (dle != NULL) { + dle->refcnt++; + mtx_unlock(&dirlist_mtx); + free(dle_new->dir, M_DEVFS4); + free(dle_new, M_DEVFS4); + return; + } + LIST_INSERT_HEAD(&devfs_dirlist, dle_new, link); + mtx_unlock(&dirlist_mtx); +} + +void +devfs_dir_ref_de(struct devfs_mount *dm, struct devfs_dirent *de) +{ + char dirname[SPECNAMELEN + 1], *namep; + + namep = devfs_fqpn(dirname, dm, de, NULL); + KASSERT(namep != NULL, ("devfs_ref_dir_de: NULL namep")); + + devfs_dir_ref(namep); +} + +static void +devfs_dir_unref(const char *dir) +{ + struct dirlistent *dle; + + if (*dir == '\0') + return; + + mtx_lock(&dirlist_mtx); + dle = devfs_dir_findent_locked(dir); + KASSERT(dle != NULL, ("devfs_dir_unref: dir %s not referenced", dir)); + if (--dle->refcnt == 0) { + LIST_REMOVE(dle, link); + mtx_unlock(&dirlist_mtx); + free(dle->dir, M_DEVFS4); + free(dle, M_DEVFS4); + } else + mtx_unlock(&dirlist_mtx); +} + +void +devfs_dir_unref_de(struct devfs_mount *dm, struct devfs_dirent *de) +{ + char dirname[SPECNAMELEN + 1], *namep; + + namep = devfs_fqpn(dirname, dm, de, NULL); + KASSERT(namep != NULL, ("devfs_unref_dir_de: NULL namep")); + + devfs_dir_unref(namep); +} + +/* Returns 1 if the path p1 contains the path p2. */ +int +devfs_pathpath(const char *p1, const char *p2) +{ + + for (;;p1++, p2++) { + if (*p1 != *p2) { + if (*p1 == '/' && *p2 == '\0') + return (1); + else + return (0); + } else if (*p1 == '\0') + return (1); + } + /* NOTREACHED */ +} Index: sys/fs/devfs/devfs_int.h =================================================================== --- sys/fs/devfs/devfs_int.h (revision 210108) +++ sys/fs/devfs/devfs_int.h (working copy) @@ -37,7 +37,10 @@ #ifdef _KERNEL +#include + struct devfs_dirent; +struct devfs_mount; struct cdev_privdata { struct file *cdpd_fp; @@ -71,11 +74,17 @@ struct cdev_priv { #define cdev2priv(c) member2struct(cdev_priv, cdp_c, c) struct cdev *devfs_alloc(int); +int devfs_dev_exists(const char *); void devfs_free(struct cdev *); void devfs_create(struct cdev *dev); void devfs_destroy(struct cdev *dev); void devfs_destroy_cdevpriv(struct cdev_privdata *p); +int devfs_dir_find(const char *); +void devfs_dir_ref_de(struct devfs_mount *, struct devfs_dirent *); +void devfs_dir_unref_de(struct devfs_mount *, struct devfs_dirent *); +int devfs_pathpath(const char *, const char *); + extern struct unrhdr *devfs_inos; extern struct mtx devmtx; extern struct mtx devfs_de_interlock; Index: sys/fs/devfs/devfs_devs.c =================================================================== --- sys/fs/devfs/devfs_devs.c (revision 210108) +++ sys/fs/devfs/devfs_devs.c (working copy) @@ -142,6 +142,27 @@ devfs_alloc(int flags) return (cdev); } +int +devfs_dev_exists(const char *name) +{ + struct cdev_priv *cdp; + + mtx_assert(&devmtx, MA_OWNED); + + TAILQ_FOREACH(cdp, &cdevp_list, cdp_list) { + if ((cdp->cdp_flags & CDP_ACTIVE) == 0) + continue; + if (devfs_pathpath(cdp->cdp_c.si_name, name) != 0) + return (1); + if (devfs_pathpath(name, cdp->cdp_c.si_name) != 0) + return (1); + } + if (devfs_dir_find(name) != 0) + return (1); + + return (0); +} + void devfs_free(struct cdev *cdev) { @@ -158,13 +179,15 @@ devfs_free(struct cdev *cdev) } struct devfs_dirent * -devfs_find(struct devfs_dirent *dd, const char *name, int namelen) +devfs_find(struct devfs_dirent *dd, const char *name, int namelen, int type) { struct devfs_dirent *de; TAILQ_FOREACH(de, &dd->de_dlist, de_list) { if (namelen != de->de_dirent->d_namlen) continue; + if (type != 0 && type != de->de_dirent->d_type) + continue; if (bcmp(name, de->de_dirent->d_name, namelen) != 0) continue; break; @@ -235,6 +258,11 @@ devfs_vmkdir(struct devfs_mount *dmp, ch else dd->de_inode = alloc_unr(devfs_inos); + /* + * "." and ".." are always the two first entries in the + * de_dlist list. + */ + /* Create the "." entry in the new directory */ de = devfs_newdirent(".", 1); de->de_dirent->d_type = DT_DIR; @@ -267,13 +295,89 @@ devfs_dirent_free(struct devfs_dirent *d free(de, M_DEVFS3); } +static int +devfs_dir_isempty(struct devfs_dirent *de) +{ + + KASSERT(de->de_dirent->d_type == DT_DIR, + ("devfs_dir_isempty: de is not a directory")); + + de = TAILQ_FIRST(&de->de_dlist); /* "." */ + if (de == NULL) + return (1); + de = TAILQ_NEXT(de, de_list); /* ".." */ + if (de == NULL) + return (1); + de = TAILQ_NEXT(de, de_list); + + return (de == NULL ? 1 : 0); +} + +/* + * Called on unmount. + * Recursively removes the entire tree. + * The caller needs to hold the dm for the duration of the call. + */ +static void +devfs_purge(struct devfs_mount *dm, struct devfs_dirent *dd) +{ + struct devfs_dirent *de; + + sx_assert(&dm->dm_lock, SX_XLOCKED); + + for (;;) { + /* + * Use TAILQ_LAST() to remove "." and ".." last. + * We need ".." to resolve path in devfs_dir_unref_de(). + */ + de = TAILQ_LAST(&dd->de_dlist, devfs_dlist_head); + if (de == NULL) + break; + TAILQ_REMOVE(&dd->de_dlist, de, de_list); + if (de->de_flags & DE_USER) + devfs_dir_unref_de(dm, dd); + if (de->de_flags & (DE_DOT|DE_DOTDOT)) + devfs_delete(dm, de, DEVFS_DEL_NORECURSE); + else if (de->de_dirent->d_type == DT_DIR) + devfs_purge(dm, de); + else + devfs_delete(dm, de, DEVFS_DEL_NORECURSE); + } + devfs_delete(dm, dd, DEVFS_DEL_NORECURSE); +} + +/* + * Removes a directory if it is empty. Also empty parent directories are + * removed recursively. + */ +static void +devfs_rmdir_empty(struct devfs_mount *dm, struct devfs_dirent *de) +{ + struct devfs_dirent *dd; + + sx_assert(&dm->dm_lock, SX_XLOCKED); + + for (;;) { + if (devfs_dir_isempty(de) == 0 || de == dm->dm_rootdir) + return; + + dd = devfs_parent_dirent(de); + KASSERT(dd != NULL, ("devfs_rmdir_empty: NULL dd")); + TAILQ_REMOVE(&dd->de_dlist, de, de_list); + devfs_purge(dm, de); + + de = dd; + } +} + /* * The caller needs to hold the dm for the duration of the call since * dm->dm_lock may be temporary dropped. */ void -devfs_delete(struct devfs_mount *dm, struct devfs_dirent *de, int vp_locked) +devfs_delete(struct devfs_mount *dm, struct devfs_dirent *de, int flags) { + struct devfs_dirent *dd; struct vnode *vp; KASSERT((de->de_flags & DE_DOOMED) == 0, @@ -286,12 +390,12 @@ devfs_delete(struct devfs_mount *dm, str mtx_unlock(&devfs_de_interlock); vholdl(vp); sx_unlock(&dm->dm_lock); - if (!vp_locked) + if ((flags & DEVFS_DEL_VNLOCKED) == 0) vn_lock(vp, LK_EXCLUSIVE | LK_INTERLOCK | LK_RETRY); else VI_UNLOCK(vp); vgone(vp); - if (!vp_locked) + if ((flags & DEVFS_DEL_VNLOCKED) == 0) VOP_UNLOCK(vp, 0); vdrop(vp); sx_xlock(&dm->dm_lock); @@ -308,35 +412,19 @@ devfs_delete(struct devfs_mount *dm, str free_unr(devfs_inos, de->de_inode); de->de_inode = 0; } - if (DEVFS_DE_DROP(de)) - devfs_dirent_free(de); -} -/* - * Called on unmount. - * Recursively removes the entire tree. - * The caller needs to hold the dm for the duration of the call. - */ - -static void -devfs_purge(struct devfs_mount *dm, struct devfs_dirent *dd) -{ - struct devfs_dirent *de; - - sx_assert(&dm->dm_lock, SX_XLOCKED); - for (;;) { - de = TAILQ_FIRST(&dd->de_dlist); - if (de == NULL) - break; - TAILQ_REMOVE(&dd->de_dlist, de, de_list); - if (de->de_flags & (DE_DOT|DE_DOTDOT)) - devfs_delete(dm, de, 0); - else if (de->de_dirent->d_type == DT_DIR) - devfs_purge(dm, de); - else - devfs_delete(dm, de, 0); + if ((flags & DEVFS_DEL_NORECURSE) == 0) { + dd = devfs_parent_dirent(de); + if (de->de_flags & DE_USER) { + KASSERT(dd != NULL, ("devfs_delete: NULL dd")); + devfs_dir_unref_de(dm, dd); + } + if (dd != NULL) + devfs_rmdir_empty(dm, dd); } - devfs_delete(dm, dd, 0); + + if (DEVFS_DE_DROP(de)) + devfs_dirent_free(de); } /* @@ -382,7 +470,7 @@ devfs_populate_loop(struct devfs_mount * struct devfs_dirent *de; struct devfs_dirent *dd; struct cdev *pdev; - int j; + int de_flags, j; char *q, *s; sx_assert(&dm->dm_lock, SX_XLOCKED); @@ -454,12 +542,27 @@ devfs_populate_loop(struct devfs_mount * continue; if (*q != '/') break; - de = devfs_find(dd, s, q - s); + de = devfs_find(dd, s, q - s, 0); if (de == NULL) de = devfs_vmkdir(dm, s, q - s, dd, 0); + else if (de->de_dirent->d_type == DT_LNK) { + de = devfs_find(dd, s, q - s, DT_DIR); + if (de == NULL) + de = devfs_vmkdir(dm, s, q - s, dd, 0); + de->de_flags |= DE_COVERED; + } s = q + 1; dd = de; + KASSERT(dd->de_dirent->d_type == DT_DIR && + (dd->de_flags & (DE_DOT | DE_DOTDOT)) == 0, + ("%s: invalid directory (si_name=%s)", + __func__, cdp->cdp_c.si_name)); + } + de_flags = 0; + de = devfs_find(dd, s, q - s, DT_LNK); + if (de != NULL) + de_flags |= DE_COVERED; de = devfs_newdirent(s, q - s); if (cdp->cdp_c.si_flags & SI_ALIAS) { @@ -477,6 +580,7 @@ devfs_populate_loop(struct devfs_mount * de->de_mode = cdp->cdp_c.si_mode; de->de_dirent->d_type = DT_CHR; } + de->de_flags |= de_flags; de->de_inode = cdp->cdp_inode; de->de_cdp = cdp; #ifdef MAC Index: sys/fs/devfs/devfs.h =================================================================== --- sys/fs/devfs/devfs.h (revision 210108) +++ sys/fs/devfs/devfs.h (working copy) @@ -118,6 +118,9 @@ struct devfs_rule { #ifdef _KERNEL +#include +#include + #ifdef MALLOC_DECLARE MALLOC_DECLARE(M_DEVFS); #endif @@ -126,14 +129,16 @@ struct devfs_dirent { struct cdev_priv *de_cdp; int de_inode; int de_flags; -#define DE_WHITEOUT 0x1 -#define DE_DOT 0x2 -#define DE_DOTDOT 0x4 -#define DE_DOOMED 0x8 +#define DE_WHITEOUT 0x01 +#define DE_DOT 0x02 +#define DE_DOTDOT 0x04 +#define DE_DOOMED 0x08 +#define DE_COVERED 0x10 +#define DE_USER 0x20 int de_holdcnt; struct dirent *de_dirent; TAILQ_ENTRY(devfs_dirent) de_list; - TAILQ_HEAD(, devfs_dirent) de_dlist; + TAILQ_HEAD(devfs_dlist_head, devfs_dirent) de_dlist; struct devfs_dirent *de_dir; int de_links; mode_t de_mode; @@ -169,20 +174,25 @@ extern unsigned devfs_rule_depth; #define DEVFS_DMP_HOLD(dmp) ((dmp)->dm_holdcnt++) #define DEVFS_DMP_DROP(dmp) (--(dmp)->dm_holdcnt == 0) +#define DEVFS_DEL_VNLOCKED 0x01 +#define DEVFS_DEL_NORECURSE 0x02 + 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); -void devfs_delete(struct devfs_mount *dm, struct devfs_dirent *de, int vp_locked); +char *devfs_fqpn(char *, struct devfs_mount *, struct devfs_dirent *, + struct componentname *); +void devfs_delete(struct devfs_mount *dm, struct devfs_dirent *de, int flags); 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_parent_dirent(struct devfs_dirent *de); -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_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, int type); #endif /* _KERNEL */ Index: sys/fs/devfs/devfs_vnops.c =================================================================== --- sys/fs/devfs/devfs_vnops.c (revision 210108) +++ sys/fs/devfs/devfs_vnops.c (working copy) @@ -36,7 +36,6 @@ /* * TODO: - * remove empty directories * mkdir: want it ? */ @@ -185,6 +184,42 @@ devfs_clear_cdevpriv(void) } static int +devfs_populate_vp(struct vnode *vp, int lock_dm) +{ + struct devfs_mount *dmp; + int locked; + + ASSERT_VOP_LOCKED(vp, "devfs_populate_vp"); + + dmp = VFSTODEVFS(vp->v_mount); + locked = VOP_ISLOCKED(vp); + + sx_xlock(&dmp->dm_lock); + DEVFS_DMP_HOLD(dmp); + + /* Can't call devfs_populate() with the vnode lock held. */ + VOP_UNLOCK(vp, 0); + devfs_populate(dmp); + + sx_xunlock(&dmp->dm_lock); + vn_lock(vp, locked | LK_RETRY); + sx_xlock(&dmp->dm_lock); + if (DEVFS_DMP_DROP(dmp)) { + sx_xunlock(&dmp->dm_lock); + devfs_unmount_final(dmp); + return (EBADF); + } + if (vp->v_iflag & VI_DOOMED) { + sx_xunlock(&dmp->dm_lock); + return (EBADF); + } + if (lock_dm == 0) + sx_xunlock(&dmp->dm_lock); + + return (0); +} + +static int devfs_vptocnp(struct vop_vptocnp_args *ap) { struct vnode *vp = ap->a_vp; @@ -196,11 +231,13 @@ devfs_vptocnp(struct vop_vptocnp_args *a int i, error; dmp = VFSTODEVFS(vp->v_mount); + + error = devfs_populate_vp(vp, 1); + if (error != 0) + return (error); + i = *buflen; dd = vp->v_data; - error = 0; - - sx_xlock(&dmp->dm_lock); if (vp->v_type == VCHR) { i -= strlen(dd->de_cdp->cdp_c.si_name); @@ -252,29 +289,34 @@ finished: } /* - * Construct the fully qualified path name relative to the mountpoint + * Construct the fully qualified path name relative to the mountpoint. + * If NULL cnp is provided, no '/' is appended to the resulting path. */ -static char * -devfs_fqpn(char *buf, struct vnode *dvp, struct componentname *cnp) +char * +devfs_fqpn(char *buf, struct devfs_mount *dmp, struct devfs_dirent *dd, + struct componentname *cnp) { int i; - struct devfs_dirent *de, *dd; - struct devfs_mount *dmp; + struct devfs_dirent *de; + + sx_assert(&dmp->dm_lock, SA_LOCKED); - dmp = VFSTODEVFS(dvp->v_mount); - dd = dvp->v_data; i = SPECNAMELEN; buf[i] = '\0'; - i -= cnp->cn_namelen; + if (cnp != NULL) + i -= cnp->cn_namelen; if (i < 0) return (NULL); - bcopy(cnp->cn_nameptr, buf + i, cnp->cn_namelen); + if (cnp != NULL) + bcopy(cnp->cn_nameptr, buf + i, cnp->cn_namelen); de = dd; while (de != dmp->dm_rootdir) { - i--; - if (i < 0) - return (NULL); - buf[i] = '/'; + if (cnp != NULL || i < SPECNAMELEN) { + i--; + if (i < 0) + return (NULL); + buf[i] = '/'; + } i -= de->de_dirent->d_namlen; if (i < 0) return (NULL); @@ -571,10 +613,14 @@ devfs_getattr(struct vop_getattr_args *a { struct vnode *vp = ap->a_vp; struct vattr *vap = ap->a_vap; - int error = 0; + int error; struct devfs_dirent *de; struct cdev *dev; + error = devfs_populate_vp(vp, 0); + if (error != 0) + return (error); + de = vp->v_data; KASSERT(de != NULL, ("Null dirent in devfs_getattr vp=%p", vp)); if (vp->v_type == VDIR) { @@ -801,16 +847,8 @@ devfs_lookupx(struct vop_lookup_args *ap return (error); } - DEVFS_DMP_HOLD(dmp); - devfs_populate(dmp); - if (DEVFS_DMP_DROP(dmp)) { - *dm_unlock = 0; - sx_xunlock(&dmp->dm_lock); - devfs_unmount_final(dmp); - return (ENOENT); - } dd = dvp->v_data; - de = devfs_find(dd, cnp->cn_nameptr, cnp->cn_namelen); + de = devfs_find(dd, cnp->cn_nameptr, cnp->cn_namelen, 0); while (de == NULL) { /* While(...) so we can use break */ if (nameiop == DELETE) @@ -820,7 +858,7 @@ devfs_lookupx(struct vop_lookup_args *ap * OK, we didn't have an entry for the name we were asked for * so we try to see if anybody can create it on demand. */ - pname = devfs_fqpn(specname, dvp, cnp); + pname = devfs_fqpn(specname, dmp, dd, cnp); if (pname == NULL) break; @@ -831,18 +869,18 @@ devfs_lookupx(struct vop_lookup_args *ap EVENTHANDLER_INVOKE(dev_clone, td->td_ucred, pname, strlen(pname), &cdev); sx_sunlock(&clone_drain_lock); - sx_xlock(&dmp->dm_lock); - if (DEVFS_DMP_DROP(dmp)) { + + if (cdev != NULL && devfs_populate_vp(dvp, 0) != 0) { *dm_unlock = 0; - sx_xunlock(&dmp->dm_lock); - devfs_unmount_final(dmp); + sx_xlock(&dmp->dm_lock); + if (DEVFS_DMP_DROP(dmp)) { + sx_xunlock(&dmp->dm_lock); + devfs_unmount_final(dmp); + } else + sx_xunlock(&dmp->dm_lock); return (ENOENT); } - if (cdev == NULL) - break; - - DEVFS_DMP_HOLD(dmp); - devfs_populate(dmp); + sx_xlock(&dmp->dm_lock); if (DEVFS_DMP_DROP(dmp)) { *dm_unlock = 0; sx_xunlock(&dmp->dm_lock); @@ -850,6 +888,9 @@ devfs_lookupx(struct vop_lookup_args *ap return (ENOENT); } + if (cdev == NULL) + break; + dev_lock(); dde = &cdev2priv(cdev)->cdp_dirents[dmp->dm_idx]; if (dde != NULL && *dde != NULL) @@ -893,9 +934,11 @@ devfs_lookup(struct vop_lookup_args *ap) struct devfs_mount *dmp; int dm_unlock; + if (devfs_populate_vp(ap->a_dvp, 1) != 0) + return (ENOTDIR); + dmp = VFSTODEVFS(ap->a_dvp->v_mount); dm_unlock = 1; - sx_xlock(&dmp->dm_lock); j = devfs_lookupx(ap, &dm_unlock); if (dm_unlock == 1) sx_xunlock(&dmp->dm_lock); @@ -1129,12 +1172,7 @@ devfs_readdir(struct vop_readdir_args *a } dmp = VFSTODEVFS(ap->a_vp->v_mount); - sx_xlock(&dmp->dm_lock); - DEVFS_DMP_HOLD(dmp); - devfs_populate(dmp); - if (DEVFS_DMP_DROP(dmp)) { - sx_xunlock(&dmp->dm_lock); - devfs_unmount_final(dmp); + if (devfs_populate_vp(ap->a_vp, 1) != 0) { if (tmp_ncookies != NULL) ap->a_ncookies = tmp_ncookies; return (EIO); @@ -1144,7 +1182,7 @@ devfs_readdir(struct vop_readdir_args *a off = 0; TAILQ_FOREACH(dd, &de->de_dlist, de_list) { KASSERT(dd->de_cdp != (void *)0xdeadc0de, ("%s %d\n", __func__, __LINE__)); - if (dd->de_flags & DE_WHITEOUT) + if (dd->de_flags & (DE_COVERED | DE_WHITEOUT)) continue; if (devfs_prison_check(dd, uio->uio_td)) continue; @@ -1223,21 +1261,39 @@ devfs_reclaim(struct vop_reclaim_args *a static int devfs_remove(struct vop_remove_args *ap) { + struct vnode *dvp = ap->a_dvp; struct vnode *vp = ap->a_vp; struct devfs_dirent *dd; - struct devfs_dirent *de; + struct devfs_dirent *de, *de_covered; struct devfs_mount *dmp = VFSTODEVFS(vp->v_mount); + ASSERT_VOP_ELOCKED(dvp, "devfs_remove"); + ASSERT_VOP_ELOCKED(vp, "devfs_remove"); + sx_xlock(&dmp->dm_lock); dd = ap->a_dvp->v_data; de = vp->v_data; if (de->de_cdp == NULL) { TAILQ_REMOVE(&dd->de_dlist, de, de_list); - devfs_delete(dmp, de, 1); + if (de->de_dirent->d_type == DT_LNK) { + de_covered = devfs_find(dd, de->de_dirent->d_name, + de->de_dirent->d_namlen, 0); + if (de_covered != NULL) + de_covered->de_flags &= ~DE_COVERED; + } + /* We need to unlock dvp because devfs_delete() may lock it. */ + VOP_UNLOCK(vp, 0); + if (dvp != vp) + VOP_UNLOCK(dvp, 0); + devfs_delete(dmp, de, 0); + sx_xunlock(&dmp->dm_lock); + if (dvp != vp) + vn_lock(dvp, LK_EXCLUSIVE | LK_RETRY); + vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); } else { de->de_flags |= DE_WHITEOUT; + sx_xunlock(&dmp->dm_lock); } - sx_xunlock(&dmp->dm_lock); return (0); } @@ -1472,7 +1528,7 @@ devfs_symlink(struct vop_symlink_args *a { int i, error; struct devfs_dirent *dd; - struct devfs_dirent *de; + struct devfs_dirent *de, *de_dotdot; struct devfs_mount *dmp; error = priv_check(curthread, PRIV_DEVFS_SYMLINK); @@ -1481,10 +1537,12 @@ devfs_symlink(struct vop_symlink_args *a dmp = VFSTODEVFS(ap->a_dvp->v_mount); dd = ap->a_dvp->v_data; de = devfs_newdirent(ap->a_cnp->cn_nameptr, ap->a_cnp->cn_namelen); + de->de_flags = DE_USER; de->de_uid = 0; de->de_gid = 0; de->de_mode = 0755; de->de_inode = alloc_unr(devfs_inos); + de->de_dir = dd; de->de_dirent->d_type = DT_LNK; i = strlen(ap->a_target) + 1; de->de_symlink = malloc(i, M_DEVFS, M_WAITOK); @@ -1493,7 +1551,11 @@ devfs_symlink(struct vop_symlink_args *a #ifdef MAC mac_devfs_create_symlink(ap->a_cnp->cn_cred, dmp->dm_mount, dd, de); #endif - TAILQ_INSERT_TAIL(&dd->de_dlist, de, de_list); + de_dotdot = TAILQ_FIRST(&dd->de_dlist); /* "." */ + de_dotdot = TAILQ_NEXT(de_dotdot, de_list); /* ".." */ + TAILQ_INSERT_AFTER(&dd->de_dlist, de_dotdot, de, de_list); + devfs_dir_ref_de(dmp, dd); + return (devfs_allocv(de, ap->a_dvp->v_mount, ap->a_vpp)); } Index: sys/kern/kern_conf.c =================================================================== --- sys/kern/kern_conf.c (revision 210108) +++ sys/kern/kern_conf.c (working copy) @@ -654,26 +654,96 @@ prep_cdevsw(struct cdevsw *devsw, int fl } static int +prep_devname(struct cdev *dev, const char *fmt, va_list ap) +{ + int len; + char *from, *q, *s, *to; + + mtx_assert(&devmtx, MA_OWNED); + + len = vsnrprintf(dev->__si_namebuf, sizeof(dev->__si_namebuf), 32, + fmt, ap); + if (len > sizeof(dev->__si_namebuf) - 1) + return (ENAMETOOLONG); + + /* Strip leading slashes. */ + for (from = dev->__si_namebuf; *from == '/'; from++) + ; + + for (to = dev->__si_namebuf; *from != '\0'; from++, to++) { + /* Treat multiple sequential slashes as single. */ + while (from[0] == '/' && from[1] == '/') + from++; + /* Strip trailing slash. */ + if (from[0] == '/' && from[1] == '\0') + break; + *to = *from; + } + *to = '\0'; + + if (dev->__si_namebuf[0] == '\0') + return (EINVAL); + + /* Disallow "." and ".." components. */ + for (s = dev->__si_namebuf;;) { + for (q = s; *q != '/' && *q != '\0'; q++) + ; + if (q - s == 1 && s[0] == '.') + return (EINVAL); + if (q - s == 2 && s[0] == '.' && s[1] == '.') + return (EINVAL); + if (*q != '/') + break; + s = q + 1; + } + + if (devfs_dev_exists(dev->__si_namebuf) != 0) + return (EEXIST); + + return (0); +} + +static int make_dev_credv(int flags, struct cdev **dres, struct cdevsw *devsw, int unit, struct ucred *cr, uid_t uid, gid_t gid, int mode, const char *fmt, va_list ap) { - struct cdev *dev; - int i, res; + struct cdev *dev, *dev_new; + int res; KASSERT((flags & MAKEDEV_WAITOK) == 0 || (flags & MAKEDEV_NOWAIT) == 0, ("make_dev_credv: both WAITOK and NOWAIT specified")); - dev = devfs_alloc(flags); - if (dev == NULL) + dev_new = devfs_alloc(flags); + if (dev_new == NULL) return (ENOMEM); dev_lock(); res = prep_cdevsw(devsw, flags); if (res != 0) { dev_unlock(); - devfs_free(dev); + devfs_free(dev_new); + return (res); + } + dev = newdev(devsw, unit, dev_new); + res = prep_devname(dev, fmt, ap); + if (res != 0) { +#ifdef notyet + if ((flags & MAKEDEV_CHECKNAME) == 0) { + panic( + "make_dev_credv: bad si_name (error=%d, si_name=%s)", + res, dev->si_name); + } + if (dev == dev_new) { + LIST_REMOVE(dev, si_list); + dev_unlock(); + devfs_free(dev); + } return (res); +#else + printf( + "make_dev_credv: prep_devname() failed (error=%d, si_name=%s)\n", + res, dev->si_name); +#endif } - dev = newdev(devsw, unit, dev); if (flags & MAKEDEV_REF) dev_refl(dev); if (dev->si_flags & SI_CHEAPCLONE && @@ -690,13 +760,6 @@ make_dev_credv(int flags, struct cdev ** KASSERT(!(dev->si_flags & SI_NAMED), ("make_dev() by driver %s on pre-existing device (min=%x, name=%s)", devsw->d_name, dev2unit(dev), devtoname(dev))); - - i = vsnrprintf(dev->__si_namebuf, sizeof dev->__si_namebuf, 32, fmt, ap); - if (i > (sizeof dev->__si_namebuf - 1)) { - printf("WARNING: Device name truncated! (%s)\n", - dev->__si_namebuf); - } - dev->si_flags |= SI_NAMED; if (cr != NULL) dev->si_cred = crhold(cr); @@ -726,7 +789,8 @@ make_dev(struct cdevsw *devsw, int unit, res = make_dev_credv(0, &dev, devsw, unit, NULL, uid, gid, mode, fmt, ap); va_end(ap); - KASSERT(res == 0 && dev != NULL, ("make_dev: failed make_dev_credv")); + KASSERT(res == 0 && dev != NULL, + ("make_dev: failed make_dev_credv (error=%d)", res)); return (dev); } @@ -743,7 +807,7 @@ make_dev_cred(struct cdevsw *devsw, int va_end(ap); KASSERT(res == 0 && dev != NULL, - ("make_dev_cred: failed make_dev_credv")); + ("make_dev_cred: failed make_dev_credv (error=%d)", res)); return (dev); } @@ -760,8 +824,9 @@ make_dev_credf(int flags, struct cdevsw fmt, ap); va_end(ap); - KASSERT((flags & MAKEDEV_NOWAIT) != 0 || res == 0, - ("make_dev_credf: failed make_dev_credv")); + KASSERT(((flags & MAKEDEV_NOWAIT) != 0 && res == ENOMEM) || + ((flags & MAKEDEV_CHECKNAME) != 0 && res != ENOMEM) || res == 0, + ("make_dev_credf: failed make_dev_credv (error=%d)", res)); return (res == 0 ? dev : NULL); } @@ -777,8 +842,9 @@ make_dev_p(int flags, struct cdev **cdev fmt, ap); va_end(ap); - KASSERT((flags & MAKEDEV_NOWAIT) != 0 || res == 0, - ("make_dev_p: failed make_dev_credv")); + KASSERT(((flags & MAKEDEV_NOWAIT) != 0 && res == ENOMEM) || + ((flags & MAKEDEV_CHECKNAME) != 0 && res != ENOMEM) || res == 0, + ("make_dev_p: failed make_dev_credv (error=%d)", res)); return (res); } @@ -806,21 +872,18 @@ make_dev_alias(struct cdev *pdev, const { struct cdev *dev; va_list ap; - int i; + int error; KASSERT(pdev != NULL, ("NULL pdev")); dev = devfs_alloc(MAKEDEV_WAITOK); dev_lock(); dev->si_flags |= SI_ALIAS; - dev->si_flags |= SI_NAMED; va_start(ap, fmt); - i = vsnrprintf(dev->__si_namebuf, sizeof dev->__si_namebuf, 32, fmt, ap); - if (i > (sizeof dev->__si_namebuf - 1)) { - printf("WARNING: Device name truncated! (%s)\n", - dev->__si_namebuf); - } + error = prep_devname(dev, fmt, ap); va_end(ap); - + KASSERT(error == 0, ("make_dev_alias: prep_devname() failed (error=%d)", + error)); + dev->si_flags |= SI_NAMED; devfs_create(dev); dev_dependsl(pdev, dev); clean_unrhdrl(devfs_inos); Index: sys/sys/conf.h =================================================================== --- sys/sys/conf.h (revision 210108) +++ sys/sys/conf.h (working copy) @@ -262,10 +262,11 @@ struct cdev *make_dev(struct cdevsw *_de struct cdev *make_dev_cred(struct cdevsw *_devsw, int _unit, struct ucred *_cr, uid_t _uid, gid_t _gid, int _perms, const char *_fmt, ...) __printflike(7, 8); -#define MAKEDEV_REF 0x1 -#define MAKEDEV_WHTOUT 0x2 -#define MAKEDEV_NOWAIT 0x4 -#define MAKEDEV_WAITOK 0x8 +#define MAKEDEV_REF 0x01 +#define MAKEDEV_WHTOUT 0x02 +#define MAKEDEV_NOWAIT 0x04 +#define MAKEDEV_WAITOK 0x08 +#define MAKEDEV_CHECKNAME 0x10 struct cdev *make_dev_credf(int _flags, struct cdevsw *_devsw, int _unit, struct ucred *_cr, uid_t _uid, gid_t _gid, int _mode,