Index: sys/contrib/openzfs/include/sys/dmu.h =================================================================== --- sys/contrib/openzfs/include/sys/dmu.h +++ sys/contrib/openzfs/include/sys/dmu.h @@ -820,6 +820,8 @@ */ int dmu_free_range(objset_t *os, uint64_t object, uint64_t offset, uint64_t size, dmu_tx_t *tx); +int dmu_free_long_range_ofs(objset_t *os, uint64_t object, uint64_t *offset, + uint64_t *length); int dmu_free_long_range(objset_t *os, uint64_t object, uint64_t offset, uint64_t size); int dmu_free_long_object(objset_t *os, uint64_t object); Index: sys/contrib/openzfs/module/os/freebsd/zfs/zfs_vnops_os.c =================================================================== --- sys/contrib/openzfs/module/os/freebsd/zfs/zfs_vnops_os.c +++ sys/contrib/openzfs/module/os/freebsd/zfs/zfs_vnops_os.c @@ -5217,6 +5217,9 @@ case _PC_NAME_MAX: *ap->a_retval = NAME_MAX; return (0); + case _PC_FDEALLOC_PRESENT: + *ap->a_retval = 1; + return (0); case _PC_PIPE_BUF: if (ap->a_vp->v_type == VDIR || ap->a_vp->v_type == VFIFO) { *ap->a_retval = PIPE_BUF; @@ -5813,6 +5816,80 @@ return (error); } +static int +zfs_deallocate(struct vop_deallocate_args *ap) +{ + dmu_tx_t *tx; + vnode_t *vp; + znode_t *zp; + zfsvfs_t *zfsvfs; + zilog_t *zilog; + zfs_locked_range_t *lr; + uint64_t mtime[2], ctime[2]; + sa_bulk_attr_t bulk[3]; + off_t off, len; + int count = 0; + int error; + + vp = ap->a_vp; + zp = VTOZ(vp); + zfsvfs = ZTOZSB(zp); + zilog = zfsvfs->z_log; + off = *ap->a_offset; + len = *ap->a_len; + + ZFS_ENTER(zfsvfs); + ZFS_VERIFY_ZP(zp); + + lr = zfs_rangelock_enter(&zp->z_rangelock, off, len, RL_WRITER); + + if (off >= zp->z_size) { + *ap->a_len = 0; + error = 0; + goto out; + } + + if (len > zp->z_size - off) + len = *ap->a_len = zp->z_size - off; + error = dmu_free_long_range_ofs(zfsvfs->z_os, zp->z_id, ap->a_offset, + ap->a_len); + if (error != 0) + goto out; + + vnode_pager_purge_range(vp, off, off + len); + + tx = dmu_tx_create(zfsvfs->z_os); + dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_FALSE); + zfs_sa_upgrade_txholds(tx, zp); + error = dmu_tx_assign(tx, TXG_WAIT); + if (error) { + dmu_tx_abort(tx); + goto out; + } + + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MTIME(zfsvfs), NULL, mtime, 16); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CTIME(zfsvfs), NULL, ctime, 16); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_FLAGS(zfsvfs), + NULL, &zp->z_pflags, 8); + zfs_tstamp_update_setup(zp, CONTENT_MODIFIED, mtime, ctime); + error = sa_bulk_update(zp->z_sa_hdl, bulk, count, tx); + ASSERT(error == 0); + + /* + * The log record is TX_TRUNCATE during replay. off + len has been + * truncated to fit within znode size to make sure the file size is not + * expanded. + */ + zfs_log_truncate(zilog, tx, TX_TRUNCATE, zp, off, len); + + dmu_tx_commit(tx); +out: + zfs_rangelock_exit(lr); + + ZFS_EXIT(zfsvfs); + return (error); +} + struct vop_vector zfs_vnodeops; struct vop_vector zfs_fifoops; struct vop_vector zfs_shareops; @@ -5830,6 +5907,7 @@ .vop_fplookup_symlink = zfs_freebsd_fplookup_symlink, .vop_access = zfs_freebsd_access, .vop_allocate = VOP_EINVAL, + .vop_deallocate = zfs_deallocate, .vop_lookup = zfs_cache_lookup, .vop_cachedlookup = zfs_freebsd_cachedlookup, .vop_getattr = zfs_freebsd_getattr, Index: sys/contrib/openzfs/module/os/freebsd/zfs/zfs_znode.c =================================================================== --- sys/contrib/openzfs/module/os/freebsd/zfs/zfs_znode.c +++ sys/contrib/openzfs/module/os/freebsd/zfs/zfs_znode.c @@ -1449,14 +1449,8 @@ error = dmu_free_long_range(zfsvfs->z_os, zp->z_id, off, len); - if (error == 0) { - /* - * In FreeBSD we cannot free block in the middle of a file, - * but only at the end of a file, so this code path should - * never happen. - */ - vnode_pager_setsize(ZTOV(zp), off); - } + if (error == 0) + vnode_pager_purge_range(ZTOV(zp), off, off + len); zfs_rangelock_exit(lr); Index: sys/contrib/openzfs/module/zfs/dmu.c =================================================================== --- sys/contrib/openzfs/module/zfs/dmu.c +++ sys/contrib/openzfs/module/zfs/dmu.c @@ -824,8 +824,8 @@ } static int -dmu_free_long_range_impl(objset_t *os, dnode_t *dn, uint64_t offset, - uint64_t length) +dmu_free_long_range_impl(objset_t *os, dnode_t *dn, uint64_t * const offset, + uint64_t * const length) { uint64_t object_size; int err; @@ -836,7 +836,7 @@ return (SET_ERROR(EINVAL)); object_size = (dn->dn_maxblkid + 1) * dn->dn_datablksz; - if (offset >= object_size) + if (*offset >= object_size) return (0); if (zfs_per_txg_dirty_frees_percent <= 100) @@ -845,10 +845,10 @@ else dirty_frees_threshold = zfs_dirty_data_max / 20; - if (length == DMU_OBJECT_END || offset + length > object_size) - length = object_size - offset; + if (*length == DMU_OBJECT_END || *offset + *length > object_size) + *length = object_size - *offset; - while (length != 0) { + while (*length != 0) { uint64_t chunk_end, chunk_begin, chunk_len; uint64_t l1blks; dmu_tx_t *tx; @@ -856,13 +856,13 @@ if (dmu_objset_zfs_unmounting(dn->dn_objset)) return (SET_ERROR(EINTR)); - chunk_end = chunk_begin = offset + length; + chunk_end = chunk_begin = *offset + *length; /* move chunk_begin backwards to the beginning of this chunk */ - err = get_next_chunk(dn, &chunk_begin, offset, &l1blks); + err = get_next_chunk(dn, &chunk_begin, *offset, &l1blks); if (err) return (err); - ASSERT3U(chunk_begin, >=, offset); + ASSERT3U(chunk_begin, >=, *offset); ASSERT3U(chunk_begin, <=, chunk_end); chunk_len = chunk_end - chunk_begin; @@ -922,22 +922,25 @@ dmu_tx_commit(tx); - length -= chunk_len; + *length -= chunk_len; } return (0); } int -dmu_free_long_range(objset_t *os, uint64_t object, - uint64_t offset, uint64_t length) +dmu_free_long_range_ofs(objset_t *os, uint64_t object, + uint64_t *offset, uint64_t *length) { dnode_t *dn; + uint64_t off, len; int err; err = dnode_hold(os, object, FTAG, &dn); if (err != 0) return (err); - err = dmu_free_long_range_impl(os, dn, offset, length); + off = *offset; + len = *length; + err = dmu_free_long_range_impl(os, dn, &off, &len); /* * It is important to zero out the maxblkid when freeing the entire @@ -945,13 +948,22 @@ * will take the fast path, and (b) dnode_reallocate() can verify * that the entire file has been freed. */ - if (err == 0 && offset == 0 && length == DMU_OBJECT_END) + if (err == 0 && *offset == 0 && *length == DMU_OBJECT_END) dn->dn_maxblkid = 0; dnode_rele(dn, FTAG); + *offset = off; + *length = len; return (err); } +int +dmu_free_long_range(objset_t *os, uint64_t object, + uint64_t offset, uint64_t length) +{ + return (dmu_free_long_range_ofs(os, object, &offset, &length)); +} + int dmu_free_long_object(objset_t *os, uint64_t object) {