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,10 +1078,12 @@ } if (fnode->tn_type == VDIR && tnode->tn_type == VDIR) { - if (tnode->tn_size > 0) { + if (tnode->tn_size > tnode->tn_dir.tn_wht_size) { error = ENOTEMPTY; goto out_locked; } + if (tnode->tn_size > 0) + tmpfs_dir_clear(tvp); } else if (fnode->tn_type == VDIR && tnode->tn_type != VDIR) { error = ENOTDIR; goto out_locked; @@ -1320,12 +1322,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 > node->tn_dir.tn_wht_size) { + error = ENOTEMPTY; + goto out; + } + if (node->tn_size > 0) + tmpfs_dir_clear(vp); if ((dnode->tn_flags & APPEND) || (node->tn_flags & (NOUNLINK | IMMUTABLE | APPEND))) { @@ -1334,7 +1340,7 @@ } /* 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