diff --git a/sys/fs/tmpfs/tmpfs.h b/sys/fs/tmpfs/tmpfs.h --- a/sys/fs/tmpfs/tmpfs.h +++ b/sys/fs/tmpfs/tmpfs.h @@ -292,6 +292,15 @@ */ off_t tn_readdir_lastn; struct tmpfs_dirent * tn_readdir_lastp; + + /* + * Total size of whiteout directory entries. This + * must be a multiple of sizeof(struct tmpfs_dirent) + * and is used to determine whether a directory is + * empty (excluding whiteout entries) during rename/ + * rmdir operations. + */ + off_t tn_wht_size; /* (v) */ } tn_dir; /* Valid when tn_type == VLNK. */ @@ -484,6 +493,7 @@ struct uio *, int, uint64_t *, int *); int tmpfs_dir_whiteout_add(struct vnode *, struct componentname *); void tmpfs_dir_whiteout_remove(struct vnode *, struct componentname *); +void tmpfs_dir_clear(struct vnode *); int tmpfs_reg_resize(struct vnode *, off_t, boolean_t); int tmpfs_reg_punch_hole(struct vnode *vp, off_t *, off_t *); int tmpfs_chflags(struct vnode *, u_long, struct ucred *, struct thread *); @@ -533,6 +543,8 @@ #define TMPFS_VALIDATE_DIR(node) do { \ MPASS((node)->tn_type == VDIR); \ MPASS((node)->tn_size % sizeof(struct tmpfs_dirent) == 0); \ + MPASS((node)->tn_dir.tn_wht_size % sizeof(struct tmpfs_dirent) == 0); \ + MPASS((node)->tn_dir.tn_wht_size <= (node)->tn_size); \ } while (0) /* diff --git a/sys/fs/tmpfs/tmpfs_subr.c b/sys/fs/tmpfs/tmpfs_subr.c --- a/sys/fs/tmpfs/tmpfs_subr.c +++ b/sys/fs/tmpfs/tmpfs_subr.c @@ -646,6 +646,7 @@ nnode->tn_dir.tn_parent = (parent == NULL) ? nnode : parent; nnode->tn_dir.tn_readdir_lastn = 0; nnode->tn_dir.tn_readdir_lastp = NULL; + nnode->tn_dir.tn_wht_size = 0; nnode->tn_links++; TMPFS_NODE_LOCK(nnode->tn_dir.tn_parent); nnode->tn_dir.tn_parent->tn_links++; @@ -1831,13 +1832,16 @@ tmpfs_dir_whiteout_add(struct vnode *dvp, struct componentname *cnp) { struct tmpfs_dirent *de; + struct tmpfs_node *dnode; int error; error = tmpfs_alloc_dirent(VFS_TO_TMPFS(dvp->v_mount), NULL, cnp->cn_nameptr, cnp->cn_namelen, &de); if (error != 0) return (error); + dnode = VP_TO_TMPFS_DIR(dvp); tmpfs_dir_attach(dvp, de); + dnode->tn_dir.tn_wht_size += sizeof(*de); return (0); } @@ -1845,13 +1849,42 @@ tmpfs_dir_whiteout_remove(struct vnode *dvp, struct componentname *cnp) { struct tmpfs_dirent *de; + struct tmpfs_node *dnode; - de = tmpfs_dir_lookup(VP_TO_TMPFS_DIR(dvp), NULL, cnp); + dnode = VP_TO_TMPFS_DIR(dvp); + de = tmpfs_dir_lookup(dnode, NULL, cnp); MPASS(de != NULL && de->td_node == NULL); + MPASS(dnode->tn_dir.tn_wht_size >= sizeof(*de)); + dnode->tn_dir.tn_wht_size -= sizeof(*de); tmpfs_dir_detach(dvp, de); tmpfs_free_dirent(VFS_TO_TMPFS(dvp->v_mount), de); } +/* + * Frees any dirents still associated with the directory represented + * by dvp in preparation for the removal of the directory. This is + * required when removing a directory which contains only whiteout + * entries. + */ +void +tmpfs_dir_clear(struct vnode *dvp) +{ + struct tmpfs_dir_cursor dc; + struct tmpfs_dirent *de; + struct tmpfs_node *dnode; + + dnode = VP_TO_TMPFS_DIR(dvp); + + while ((de = tmpfs_dir_first(dnode, &dc)) != NULL) { + if (de->td_node == NULL) + dnode->tn_dir.tn_wht_size -= sizeof(*de); + tmpfs_dir_detach(dvp, de); + tmpfs_free_dirent(VFS_TO_TMPFS(dvp->v_mount), de); + } + MPASS(dnode->tn_size == 0); + MPASS(dnode->tn_dir.tn_wht_size == 0); +} + /* * Resizes the aobj associated with the regular file pointed to by 'vp' to the * size 'newsize'. 'vp' must point to a vnode that represents a regular file. diff --git a/sys/fs/tmpfs/tmpfs_vnops.c b/sys/fs/tmpfs/tmpfs_vnops.c --- a/sys/fs/tmpfs/tmpfs_vnops.c +++ b/sys/fs/tmpfs/tmpfs_vnops.c @@ -1078,7 +1078,9 @@ } if (fnode->tn_type == VDIR && tnode->tn_type == VDIR) { - if (tnode->tn_size > 0) { + if ((tnode->tn_size != 0 && + (tcnp->cn_flags & IGNOREWHITEOUT) == 0) || + tnode->tn_size > tnode->tn_dir.tn_wht_size) { error = ENOTEMPTY; goto out_locked; } @@ -1239,6 +1241,9 @@ tde = tmpfs_dir_lookup(tdnode, tnode, tcnp); tmpfs_dir_detach(tdvp, tde); + if (tnode->tn_type == VDIR && tnode->tn_size > 0) + tmpfs_dir_clear(tvp); + /* Update node's ctime because of possible hardlinks. */ tnode->tn_status |= TMPFS_NODE_CHANGED; tmpfs_update(tvp); @@ -1309,6 +1314,7 @@ { struct vnode *dvp = v->a_dvp; struct vnode *vp = v->a_vp; + struct componentname *cnp = v->a_cnp; int error; struct tmpfs_dirent *de; @@ -1320,12 +1326,16 @@ dnode = VP_TO_TMPFS_DIR(dvp); node = VP_TO_TMPFS_DIR(vp); - /* Directories with more than two entries ('.' and '..') cannot be - * removed. */ - if (node->tn_size > 0) { - error = ENOTEMPTY; - goto out; - } + /* + * Directories with more than two non-whiteout entries ('.' and '..') + * cannot be removed. + */ + if ((node->tn_size != 0 && + (cnp->cn_flags & IGNOREWHITEOUT) == 0) || + node->tn_size > node->tn_dir.tn_wht_size) { + error = ENOTEMPTY; + goto out; + } if ((dnode->tn_flags & APPEND) || (node->tn_flags & (NOUNLINK | IMMUTABLE | APPEND))) { @@ -1334,15 +1344,15 @@ } /* This invariant holds only if we are not trying to remove "..". - * We checked for that above so this is safe now. */ + * We checked for that above so this is safe now. */ MPASS(node->tn_dir.tn_parent == dnode); /* Get the directory entry associated with node (vp). This was * filled by tmpfs_lookup while looking up the entry. */ - de = tmpfs_dir_lookup(dnode, node, v->a_cnp); + de = tmpfs_dir_lookup(dnode, node, cnp); MPASS(TMPFS_DIRENT_MATCHES(de, - v->a_cnp->cn_nameptr, - v->a_cnp->cn_namelen)); + cnp->cn_nameptr, + cnp->cn_namelen)); /* Check flags to see if we are allowed to remove the directory. */ if ((dnode->tn_flags & APPEND) != 0 || @@ -1353,8 +1363,12 @@ /* Detach the directory entry from the directory (dnode). */ tmpfs_dir_detach(dvp, de); - if (v->a_cnp->cn_flags & DOWHITEOUT) - tmpfs_dir_whiteout_add(dvp, v->a_cnp); + + if (node->tn_size > 0) + tmpfs_dir_clear(vp); + + if (cnp->cn_flags & DOWHITEOUT) + tmpfs_dir_whiteout_add(dvp, cnp); /* No vnode should be allocated for this entry from this point */ TMPFS_NODE_LOCK(node); diff --git a/sys/fs/unionfs/union_vnops.c b/sys/fs/unionfs/union_vnops.c --- a/sys/fs/unionfs/union_vnops.c +++ b/sys/fs/unionfs/union_vnops.c @@ -1732,7 +1732,7 @@ } ump = MOUNTTOUNIONFSMOUNT(ap->a_vp->v_mount); if (ump->um_whitemode == UNIONFS_WHITE_ALWAYS || lvp != NULLVP) - cnp->cn_flags |= DOWHITEOUT; + cnp->cn_flags |= (DOWHITEOUT | IGNOREWHITEOUT); int udvp_lkflags, uvp_lkflags; unionfs_forward_vop_start_pair(udvp, &udvp_lkflags, uvp, &uvp_lkflags); diff --git a/sys/sys/namei.h b/sys/sys/namei.h --- a/sys/sys/namei.h +++ b/sys/sys/namei.h @@ -159,7 +159,7 @@ */ #define RDONLY 0x00000200 /* lookup with read-only semantics */ #define ISRESTARTED 0x00000400 /* restarted namei */ -/* UNUSED 0x00000800 */ +#define IGNOREWHITEOUT 0x00000800 /* ignore whiteouts, e.g. when checking if a dir is empty */ #define ISWHITEOUT 0x00001000 /* found whiteout */ #define DOWHITEOUT 0x00002000 /* do whiteouts */ #define WILLBEDIR 0x00004000 /* new files will be dirs; allow trailing / */ diff --git a/sys/ufs/ufs/ufs_extern.h b/sys/ufs/ufs/ufs_extern.h --- a/sys/ufs/ufs/ufs_extern.h +++ b/sys/ufs/ufs/ufs_extern.h @@ -59,7 +59,7 @@ int ufs_checkpath(ino_t, ino_t, struct inode *, struct ucred *, ino_t *); void ufs_dirbad(struct inode *, doff_t, char *); int ufs_dirbadentry(struct vnode *, struct direct *, int); -int ufs_dirempty(struct inode *, ino_t, struct ucred *); +int ufs_dirempty(struct inode *, ino_t, struct ucred *, int); int ufs_extread(struct vop_read_args *); int ufs_extwrite(struct vop_write_args *); void ufs_makedirentry(struct inode *, struct componentname *, diff --git a/sys/ufs/ufs/ufs_lookup.c b/sys/ufs/ufs/ufs_lookup.c --- a/sys/ufs/ufs/ufs_lookup.c +++ b/sys/ufs/ufs/ufs_lookup.c @@ -1298,7 +1298,8 @@ * NB: does not handle corrupted directories. */ int -ufs_dirempty(struct inode *ip, ino_t parentino, struct ucred *cred) +ufs_dirempty(struct inode *ip, ino_t parentino, struct ucred *cred, + int skipwhiteout) { doff_t off; struct dirtemplate dbuf; @@ -1321,7 +1322,8 @@ if (dp->d_reclen == 0) return (0); /* skip empty entries */ - if (dp->d_ino == 0 || dp->d_ino == UFS_WINO) + if (dp->d_ino == 0 || + (skipwhiteout != 0 && dp->d_ino == UFS_WINO)) continue; /* accept only "." and ".." */ # if (BYTE_ORDER == LITTLE_ENDIAN) diff --git a/sys/ufs/ufs/ufs_vnops.c b/sys/ufs/ufs/ufs_vnops.c --- a/sys/ufs/ufs/ufs_vnops.c +++ b/sys/ufs/ufs/ufs_vnops.c @@ -1625,7 +1625,8 @@ */ if ((tip->i_mode & IFMT) == IFDIR) { if ((tip->i_effnlink > 2) || - !ufs_dirempty(tip, tdp->i_number, tcnp->cn_cred)) { + !ufs_dirempty(tip, tdp->i_number, tcnp->cn_cred, + (tcnp->cn_flags & IGNOREWHITEOUT) != 0)) { error = ENOTEMPTY; goto bad; } @@ -2281,7 +2282,8 @@ error = EINVAL; goto out; } - if (!ufs_dirempty(ip, dp->i_number, cnp->cn_cred)) { + if (!ufs_dirempty(ip, dp->i_number, cnp->cn_cred, + (cnp->cn_flags & IGNOREWHITEOUT) != 0)) { error = ENOTEMPTY; goto out; }