diff options
Diffstat (limited to 'sys/fs/tmpfs')
-rw-r--r-- | sys/fs/tmpfs/tmpfs.h | 17 | ||||
-rw-r--r-- | sys/fs/tmpfs/tmpfs_subr.c | 106 | ||||
-rw-r--r-- | sys/fs/tmpfs/tmpfs_vfsops.c | 18 | ||||
-rw-r--r-- | sys/fs/tmpfs/tmpfs_vnops.c | 136 |
4 files changed, 133 insertions, 144 deletions
diff --git a/sys/fs/tmpfs/tmpfs.h b/sys/fs/tmpfs/tmpfs.h index c28f3a02a7bf..52307cc7c7b2 100644 --- a/sys/fs/tmpfs/tmpfs.h +++ b/sys/fs/tmpfs/tmpfs.h @@ -292,6 +292,15 @@ struct tmpfs_node { */ 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. */ @@ -439,11 +448,10 @@ struct tmpfs_mount { * NFS code. */ struct tmpfs_fid_data { + unsigned short tfd_len; ino_t tfd_id; unsigned long tfd_gen; -}; -_Static_assert(sizeof(struct tmpfs_fid_data) <= MAXFIDSZ, - "(struct tmpfs_fid_data) is larger than (struct fid).fid_data"); +} __packed; struct tmpfs_dir_cursor { struct tmpfs_dirent *tdc_current; @@ -484,6 +492,7 @@ int tmpfs_dir_getdents(struct tmpfs_mount *, struct tmpfs_node *, 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_whiteouts(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 +542,8 @@ tmpfs_update(struct vnode *vp) #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 index 9bdcc4575511..1237f6b92cdb 100644 --- a/sys/fs/tmpfs/tmpfs_subr.c +++ b/sys/fs/tmpfs/tmpfs_subr.c @@ -120,7 +120,7 @@ tmpfs_pager_writecount_recalc(vm_object_t object, vm_offset_t old, /* * Forced unmount? */ - if (vp == NULL) { + if (vp == NULL || vp->v_object == NULL) { KASSERT((object->flags & OBJ_TMPFS_VREF) == 0, ("object %p with OBJ_TMPFS_VREF but without vnode", object)); @@ -183,6 +183,9 @@ tmpfs_pager_release_writecount(vm_object_t object, vm_offset_t start, KASSERT((object->flags & OBJ_ANON) == 0, ("%s: object %p with OBJ_ANON", __func__, object)); old = object->un_pager.swp.writemappings; + KASSERT(old >= (vm_ooffset_t)end - start, + ("tmpfs obj %p writecount %jx dec %jx", object, (uintmax_t)old, + (uintmax_t)((vm_ooffset_t)end - start))); object->un_pager.swp.writemappings -= (vm_ooffset_t)end - start; new = object->un_pager.swp.writemappings; tmpfs_pager_writecount_recalc(object, old, new); @@ -346,7 +349,7 @@ tmpfs_node_init(void *mem, int size, int flags) node = mem; node->tn_id = 0; - mtx_init(&node->tn_interlock, "tmpfsni", NULL, MTX_DEF); + mtx_init(&node->tn_interlock, "tmpfsni", NULL, MTX_DEF | MTX_NEW); node->tn_gen = arc4random(); return (0); } @@ -425,7 +428,7 @@ sysctl_mem_percent(SYSCTL_HANDLER_ARGS) if ((unsigned) percent > 100) return (EINVAL); - *(long *)arg1 = percent; + *(int *)arg1 = percent; tmpfs_set_reserve_from_percent(); return (0); } @@ -440,7 +443,7 @@ tmpfs_set_reserve_from_percent(void) } SYSCTL_PROC(_vfs_tmpfs, OID_AUTO, memory_percent, - CTLTYPE_INT | CTLFLAG_MPSAFE | CTLFLAG_RW, &tmpfs_mem_percent, 0, + CTLTYPE_INT | CTLFLAG_MPSAFE | CTLFLAG_RWTUN, &tmpfs_mem_percent, 0, sysctl_mem_percent, "I", "Percent of available memory that can be used if no size limit"); @@ -490,50 +493,11 @@ static int tmpfs_partial_page_invalidate(vm_object_t object, vm_pindex_t idx, int base, int end, boolean_t ignerr) { - vm_page_t m; - int rv, error; - - VM_OBJECT_ASSERT_WLOCKED(object); - KASSERT(base >= 0, ("%s: base %d", __func__, base)); - KASSERT(end - base <= PAGE_SIZE, ("%s: base %d end %d", __func__, base, - end)); - error = 0; - -retry: - m = vm_page_grab(object, idx, VM_ALLOC_NOCREAT); - if (m != NULL) { - MPASS(vm_page_all_valid(m)); - } else if (vm_pager_has_page(object, idx, NULL, NULL)) { - m = vm_page_alloc(object, idx, VM_ALLOC_NORMAL | - VM_ALLOC_WAITFAIL); - if (m == NULL) - goto retry; - vm_object_pip_add(object, 1); - VM_OBJECT_WUNLOCK(object); - rv = vm_pager_get_pages(object, &m, 1, NULL, NULL); - VM_OBJECT_WLOCK(object); - vm_object_pip_wakeup(object); - if (rv == VM_PAGER_OK) { - /* - * Since the page was not resident, and therefore not - * recently accessed, immediately enqueue it for - * asynchronous laundering. The current operation is - * not regarded as an access. - */ - vm_page_launder(m); - } else { - vm_page_free(m); - m = NULL; - if (!ignerr) - error = EIO; - } - } - if (m != NULL) { - pmap_zero_page_area(m, base, end - base); - vm_page_set_dirty(m); - vm_page_xunbusy(m); - } + int error; + error = vm_page_grab_zero_partial(object, idx, base, end); + if (ignerr) + error = 0; return (error); } @@ -643,6 +607,7 @@ tmpfs_alloc_node(struct mount *mp, struct tmpfs_mount *tmp, __enum_uint8(vtype) 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++; @@ -954,6 +919,8 @@ tmpfs_destroy_vobject(struct vnode *vp, vm_object_t obj) VM_OBJECT_WLOCK(obj); VI_LOCK(vp); + vp->v_object = NULL; + /* * May be going through forced unmount. */ @@ -1094,15 +1061,19 @@ loop: KASSERT((object->flags & OBJ_TMPFS_VREF) == 0, ("%s: object %p with OBJ_TMPFS_VREF but without vnode", __func__, object)); - KASSERT(object->un_pager.swp.writemappings == 0, - ("%s: object %p has writemappings", - __func__, object)); VI_LOCK(vp); KASSERT(vp->v_object == NULL, ("Not NULL v_object in tmpfs")); vp->v_object = object; vn_irflag_set_locked(vp, (tm->tm_pgread ? VIRF_PGREAD : 0) | VIRF_TEXT_REF); VI_UNLOCK(vp); + VNASSERT((object->flags & OBJ_TMPFS_VREF) == 0, vp, + ("leaked OBJ_TMPFS_VREF")); + if (object->un_pager.swp.writemappings > 0) { + vrefact(vp); + vlazy(vp); + vm_object_set_flag(object, OBJ_TMPFS_VREF); + } VM_OBJECT_WUNLOCK(object); break; case VDIR: @@ -1822,13 +1793,16 @@ int 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); } @@ -1836,14 +1810,44 @@ void 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_whiteouts(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) { + KASSERT(de->td_node == NULL, ("%s: non-whiteout dirent %p", + __func__, de)); + 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. * 'newsize' must be positive. diff --git a/sys/fs/tmpfs/tmpfs_vfsops.c b/sys/fs/tmpfs/tmpfs_vfsops.c index 32eb9c958df1..431893b77bb9 100644 --- a/sys/fs/tmpfs/tmpfs_vfsops.c +++ b/sys/fs/tmpfs/tmpfs_vfsops.c @@ -208,7 +208,7 @@ again: continue; } vm = vmspace_acquire_ref(p); - _PHOLD_LITE(p); + _PHOLD(p); PROC_UNLOCK(p); if (vm == NULL) { PRELE(p); @@ -585,29 +585,25 @@ static int tmpfs_fhtovp(struct mount *mp, struct fid *fhp, int flags, struct vnode **vpp) { - struct tmpfs_fid_data tfd; + struct tmpfs_fid_data *tfd; struct tmpfs_mount *tmp; struct tmpfs_node *node; int error; - if (fhp->fid_len != sizeof(tfd)) + if (fhp->fid_len != sizeof(*tfd)) return (EINVAL); - /* - * Copy from fid_data onto the stack to avoid unaligned pointer use. - * See the comment in sys/mount.h on struct fid for details. - */ - memcpy(&tfd, fhp->fid_data, fhp->fid_len); + tfd = (struct tmpfs_fid_data *)fhp; tmp = VFS_TO_TMPFS(mp); - if (tfd.tfd_id >= tmp->tm_nodes_max) + if (tfd->tfd_id >= tmp->tm_nodes_max) return (EINVAL); TMPFS_LOCK(tmp); LIST_FOREACH(node, &tmp->tm_nodes_used, tn_entries) { - if (node->tn_id == tfd.tfd_id && - node->tn_gen == tfd.tfd_gen) { + if (node->tn_id == tfd->tfd_id && + node->tn_gen == tfd->tfd_gen) { tmpfs_ref_node(node); break; } diff --git a/sys/fs/tmpfs/tmpfs_vnops.c b/sys/fs/tmpfs/tmpfs_vnops.c index 718cfef6bfa3..9d2a587b177a 100644 --- a/sys/fs/tmpfs/tmpfs_vnops.c +++ b/sys/fs/tmpfs/tmpfs_vnops.c @@ -476,6 +476,7 @@ tmpfs_stat(struct vop_stat_args *v) sb->st_blksize = PAGE_SIZE; sb->st_flags = node->tn_flags; sb->st_gen = node->tn_gen; + sb->st_filerev = 0; if (vp->v_type == VREG) { #ifdef __ILP32__ vm_object_t obj = node->tn_reg.tn_aobj; @@ -1078,7 +1079,9 @@ tmpfs_rename(struct vop_rename_args *v) } 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 +1242,16 @@ tmpfs_rename(struct vop_rename_args *v) tde = tmpfs_dir_lookup(tdnode, tnode, tcnp); tmpfs_dir_detach(tdvp, tde); + /* + * If we are overwriting a directory, per the ENOTEMPTY check + * above it must either be empty or contain only whiteout + * entries. In the latter case (which can only happen if + * IGNOREWHITEOUT was passed in tcnp->cn_flags), clear the + * whiteout entries to avoid leaking memory. + */ + if (tnode->tn_type == VDIR && tnode->tn_size > 0) + tmpfs_dir_clear_whiteouts(tvp); + /* Update node's ctime because of possible hardlinks. */ tnode->tn_status |= TMPFS_NODE_CHANGED; tmpfs_update(tvp); @@ -1309,6 +1322,7 @@ tmpfs_rmdir(struct vop_rmdir_args *v) { 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,13 +1334,18 @@ tmpfs_rmdir(struct vop_rmdir_args *v) 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; + } + /* Check flags to see if we are allowed to remove the directory. */ if ((dnode->tn_flags & APPEND) || (node->tn_flags & (NOUNLINK | IMMUTABLE | APPEND))) { error = EPERM; @@ -1334,27 +1353,31 @@ tmpfs_rmdir(struct vop_rmdir_args *v) } /* 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)); - - /* Check flags to see if we are allowed to remove the directory. */ - if ((dnode->tn_flags & APPEND) != 0 || - (node->tn_flags & (NOUNLINK | IMMUTABLE | APPEND)) != 0) { - error = EPERM; - goto out; - } + cnp->cn_nameptr, + cnp->cn_namelen)); /* 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 we are removing a directory, per the ENOTEMPTY check above it + * must either be empty or contain only whiteout entries. In the + * latter case (which can only happen if IGNOREWHITEOUT was passed + * in cnp->cn_flags), clear the whiteout entries to avoid leaking + * memory. + */ + if (node->tn_size > 0) + tmpfs_dir_clear_whiteouts(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); @@ -1668,6 +1691,10 @@ tmpfs_pathconf(struct vop_pathconf_args *v) *retval = PAGE_SIZE; break; + case _PC_HAS_HIDDENSYSTEM: + *retval = 1; + break; + default: error = vop_stdpathconf(v); } @@ -1684,21 +1711,15 @@ vop_vptofh { }; */ { - struct tmpfs_fid_data tfd; + struct tmpfs_fid_data *const tfd = (struct tmpfs_fid_data *)ap->a_fhp; struct tmpfs_node *node; - struct fid *fhp; + _Static_assert(sizeof(struct tmpfs_fid_data) <= sizeof(struct fid), + "struct tmpfs_fid_data cannot be larger than struct fid"); node = VP_TO_TMPFS_NODE(ap->a_vp); - fhp = ap->a_fhp; - fhp->fid_len = sizeof(tfd); - - /* - * Copy into fid_data from the stack to avoid unaligned pointer use. - * See the comment in sys/mount.h on struct fid for details. - */ - tfd.tfd_id = node->tn_id; - tfd.tfd_gen = node->tn_gen; - memcpy(fhp->fid_data, &tfd, fhp->fid_len); + tfd->tfd_len = sizeof(*tfd); + tfd->tfd_gen = node->tn_gen; + tfd->tfd_id = node->tn_id; return (0); } @@ -2070,31 +2091,10 @@ tmpfs_setextattr(struct vop_setextattr_args *ap) static off_t tmpfs_seek_data_locked(vm_object_t obj, off_t noff) { - vm_page_t m; - vm_pindex_t p, p_m, p_swp; - - p = OFF_TO_IDX(noff); - m = vm_page_find_least(obj, p); - - /* - * Microoptimize the most common case for SEEK_DATA, where - * there is no hole and the page is resident. - */ - if (m != NULL && vm_page_any_valid(m) && m->pindex == p) - return (noff); - - p_swp = swap_pager_find_least(obj, p); - if (p_swp == p) - return (noff); - - p_m = m == NULL ? obj->size : m->pindex; - return (IDX_TO_OFF(MIN(p_m, p_swp))); -} + vm_pindex_t p; -static off_t -tmpfs_seek_next(off_t noff) -{ - return (noff + PAGE_SIZE - (noff & PAGE_MASK)); + p = swap_pager_seek_data(obj, OFF_TO_IDX(noff)); + return (p == OFF_TO_IDX(noff) ? noff : IDX_TO_OFF(p)); } static int @@ -2111,30 +2111,8 @@ tmpfs_seek_clamp(struct tmpfs_node *tn, off_t *noff, bool seekdata) static off_t tmpfs_seek_hole_locked(vm_object_t obj, off_t noff) { - vm_page_t m; - vm_pindex_t p, p_swp; - - for (;; noff = tmpfs_seek_next(noff)) { - /* - * Walk over the largest sequential run of the valid pages. - */ - for (m = vm_page_lookup(obj, OFF_TO_IDX(noff)); - m != NULL && vm_page_any_valid(m); - m = vm_page_next(m), noff = tmpfs_seek_next(noff)) - ; - /* - * Found a hole in the object's page queue. Check if - * there is a hole in the swap at the same place. - */ - p = OFF_TO_IDX(noff); - p_swp = swap_pager_find_least(obj, p); - if (p_swp != p) { - noff = IDX_TO_OFF(p); - break; - } - } - return (noff); + return (IDX_TO_OFF(swap_pager_seek_hole(obj, OFF_TO_IDX(noff)))); } static int |