diff --git a/sys/kern/vfs_mount.c b/sys/kern/vfs_mount.c index bf532df335bf..e7f9eab33158 100644 --- a/sys/kern/vfs_mount.c +++ b/sys/kern/vfs_mount.c @@ -1160,7 +1160,7 @@ vfs_domount_first( error = ENOTDIR; } if (error == 0 && (fsflags & MNT_EMPTYDIR) != 0) - error = vfs_emptydir(vp); + error = vn_dir_check_empty(vp); if (error == 0) { VI_LOCK(vp); if ((vp->v_iflag & VI_MOUNT) == 0 && vp->v_mountedhere == NULL) diff --git a/sys/kern/vfs_subr.c b/sys/kern/vfs_subr.c index 3c783f7326d0..9901a20172a4 100644 --- a/sys/kern/vfs_subr.c +++ b/sys/kern/vfs_subr.c @@ -6382,96 +6382,6 @@ filt_vfsvnode(struct knote *kn, long hint) return (res); } -/* - * Returns whether the directory is empty or not. - * If it is empty, the return value is 0; otherwise - * the return value is an error value (which may - * be ENOTEMPTY). - */ -int -vfs_emptydir(struct vnode *vp) -{ - struct thread *const td = curthread; - char *dirbuf; - size_t dirbuflen, len; - off_t off; - int eofflag, error; - struct dirent *dp; - struct vattr va; - - ASSERT_VOP_LOCKED(vp, "vfs_emptydir"); - VNPASS(vp->v_type == VDIR, vp); - - error = VOP_GETATTR(vp, &va, td->td_ucred); - if (error != 0) - return (error); - - dirbuflen = max(DEV_BSIZE, GENERIC_MAXDIRSIZ); - if (dirbuflen < va.va_blocksize) - dirbuflen = va.va_blocksize; - dirbuf = malloc(dirbuflen, M_TEMP, M_WAITOK); - - len = 0; - off = 0; - eofflag = 0; - - for (;;) { - error = vn_dir_next_dirent(vp, td, dirbuf, dirbuflen, - &dp, &len, &off, &eofflag); - if (error != 0) - goto end; - - if (len == 0) { - /* EOF */ - error = 0; - goto end; - } - - /* - * Skip whiteouts. Unionfs operates on filesystems only and not - * on hierarchies, so these whiteouts would be shadowed on the - * system hierarchy but not for a union using the filesystem of - * their directories as the upper layer. Additionally, unionfs - * currently transparently exposes union-specific metadata of - * its upper layer, meaning that whiteouts can be seen through - * the union view in empty directories. Taking into account - * these whiteouts would then prevent mounting another - * filesystem on such effectively empty directories. - */ - if (dp->d_type == DT_WHT) - continue; - - /* - * Any file in the directory which is not '.' or '..' indicates - * the directory is not empty. - */ - switch (dp->d_namlen) { - case 2: - if (dp->d_name[1] != '.') { - /* Can't be '..' (nor '.') */ - error = ENOTEMPTY; - goto end; - } - /* FALLTHROUGH */ - case 1: - if (dp->d_name[0] != '.') { - /* Can't be '..' nor '.' */ - error = ENOTEMPTY; - goto end; - } - break; - - default: - error = ENOTEMPTY; - goto end; - } - } - -end: - free(dirbuf, M_TEMP); - return (error); -} - int vfs_read_dirent(struct vop_readdir_args *ap, struct dirent *dp, off_t off) { diff --git a/sys/kern/vfs_vnops.c b/sys/kern/vfs_vnops.c index d2b1a9b0570b..33cb3ebed60f 100644 --- a/sys/kern/vfs_vnops.c +++ b/sys/kern/vfs_vnops.c @@ -3897,6 +3897,97 @@ vn_dir_next_dirent(struct vnode *vp, struct thread *td, return (error); } +/* + * Checks whether a directory is empty or not. + * + * If the directory is empty, returns 0, and if it is not, ENOTEMPTY. Other + * values are genuine errors preventing the check. + */ +int +vn_dir_check_empty(struct vnode *vp) +{ + struct thread *const td = curthread; + char *dirbuf; + size_t dirbuflen, len; + off_t off; + int eofflag, error; + struct dirent *dp; + struct vattr va; + + ASSERT_VOP_LOCKED(vp, "vfs_emptydir"); + VNPASS(vp->v_type == VDIR, vp); + + error = VOP_GETATTR(vp, &va, td->td_ucred); + if (error != 0) + return (error); + + dirbuflen = max(DEV_BSIZE, GENERIC_MAXDIRSIZ); + if (dirbuflen < va.va_blocksize) + dirbuflen = va.va_blocksize; + dirbuf = malloc(dirbuflen, M_TEMP, M_WAITOK); + + len = 0; + off = 0; + eofflag = 0; + + for (;;) { + error = vn_dir_next_dirent(vp, td, dirbuf, dirbuflen, + &dp, &len, &off, &eofflag); + if (error != 0) + goto end; + + if (len == 0) { + /* EOF */ + error = 0; + goto end; + } + + /* + * Skip whiteouts. Unionfs operates on filesystems only and + * not on hierarchies, so these whiteouts would be shadowed on + * the system hierarchy but not for a union using the + * filesystem of their directories as the upper layer. + * Additionally, unionfs currently transparently exposes + * union-specific metadata of its upper layer, meaning that + * whiteouts can be seen through the union view in empty + * directories. Taking into account these whiteouts would then + * prevent mounting another filesystem on such effectively + * empty directories. + */ + if (dp->d_type == DT_WHT) + continue; + + /* + * Any file in the directory which is not '.' or '..' indicates + * the directory is not empty. + */ + switch (dp->d_namlen) { + case 2: + if (dp->d_name[1] != '.') { + /* Can't be '..' (nor '.') */ + error = ENOTEMPTY; + goto end; + } + /* FALLTHROUGH */ + case 1: + if (dp->d_name[0] != '.') { + /* Can't be '..' nor '.' */ + error = ENOTEMPTY; + goto end; + } + break; + + default: + error = ENOTEMPTY; + goto end; + } + } + +end: + free(dirbuf, M_TEMP); + return (error); +} + static u_long vn_lock_pair_pause_cnt; SYSCTL_ULONG(_debug, OID_AUTO, vn_lock_pair_pause, CTLFLAG_RD, diff --git a/sys/sys/vnode.h b/sys/sys/vnode.h index 8403dd8c035b..7ba95de34b5d 100644 --- a/sys/sys/vnode.h +++ b/sys/sys/vnode.h @@ -1102,8 +1102,8 @@ struct dirent; int vn_dir_next_dirent(struct vnode *vp, struct thread *td, char *dirbuf, size_t dirbuflen, struct dirent **dpp, size_t *len, off_t *off, int *eofflag); +int vn_dir_check_empty(struct vnode *vp); int vfs_read_dirent(struct vop_readdir_args *ap, struct dirent *dp, off_t off); -int vfs_emptydir(struct vnode *vp); int vfs_unixify_accmode(accmode_t *accmode);