diff options
| author | Martin Matuska <mm@FreeBSD.org> | 2026-06-27 06:15:42 +0000 |
|---|---|---|
| committer | Martin Matuska <mm@FreeBSD.org> | 2026-06-27 08:51:33 +0000 |
| commit | d0b3ecdc274930e190ea233b6b69ff03782eaf8d (patch) | |
| tree | 8f7bc49621e339a1a9baa14a3221e2d90c7a0c5d /sys/contrib/openzfs/module/zfs | |
| parent | 8e61d8707f8a10acc143210089fea2502a3f2922 (diff) | |
| parent | 37af899488652c55c456d3f160fb8b295db2ec70 (diff) | |
zfs: merge openzfs/zfs@37af89948
Notable upstream pull request merges:
#18509 f16b3744d zstream: refactor common functions
#18573 -multiple Persist z_seq across znode eviction
s18611 eb0c674c2 zfs_ioctl: fix EBUSY race between quota queries and mount
#18637 77e64d86e Fix self-deadlock when setting the "allocating"/"path"
vdev property
#18645 e3082b923 freebsd: set mnt_time on the rootfs at mountroot time
#18652 50d012b2a zbookmark_compare: handle "marker" bookmarks with negative
levels
#18664 520eeeaa6 Improve performance of "zpool offline" for log devices
#18668 6b8f79877 Avoid more abd_t allocations in RAIDZ/dRAID
#18669 99ab859c3 Optimize metaslab_set_selected_txg()
#18673 97b9ba7a9 delegate: add 'send:encrypted' permission
#18687 2ea519c2a Avoid lookup overhead for nonexistent xattr directories
#18688 87593ea2b Fix handling of _PC_HAS_HIDDENSYSTEM for FreeBSD
#18693 0483a8e0c Clean up embedded slog metaslab across txgs
#18695 41311c665 RAIDZ: Optimize single data column writes
#18706 37af89948 ddt_log: Fix refcount tagging for begin/commit
Obtained from: OpenZFS
OpenZFS commit: 37af899488652c55c456d3f160fb8b295db2ec70
Diffstat (limited to 'sys/contrib/openzfs/module/zfs')
| -rw-r--r-- | sys/contrib/openzfs/module/zfs/abd.c | 45 | ||||
| -rw-r--r-- | sys/contrib/openzfs/module/zfs/ddt_log.c | 4 | ||||
| -rw-r--r-- | sys/contrib/openzfs/module/zfs/metaslab.c | 3 | ||||
| -rw-r--r-- | sys/contrib/openzfs/module/zfs/spa.c | 4 | ||||
| -rw-r--r-- | sys/contrib/openzfs/module/zfs/vdev.c | 102 | ||||
| -rw-r--r-- | sys/contrib/openzfs/module/zfs/vdev_draid.c | 10 | ||||
| -rw-r--r-- | sys/contrib/openzfs/module/zfs/vdev_raidz.c | 55 | ||||
| -rw-r--r-- | sys/contrib/openzfs/module/zfs/zap.c | 2 | ||||
| -rw-r--r-- | sys/contrib/openzfs/module/zfs/zfs_crrd.c | 22 | ||||
| -rw-r--r-- | sys/contrib/openzfs/module/zfs/zfs_ioctl.c | 134 | ||||
| -rw-r--r-- | sys/contrib/openzfs/module/zfs/zfs_sa.c | 5 | ||||
| -rw-r--r-- | sys/contrib/openzfs/module/zfs/zfs_vnops.c | 39 | ||||
| -rw-r--r-- | sys/contrib/openzfs/module/zfs/zio.c | 61 |
13 files changed, 377 insertions, 109 deletions
diff --git a/sys/contrib/openzfs/module/zfs/abd.c b/sys/contrib/openzfs/module/zfs/abd.c index 7ea07c418300..cb575e21acd7 100644 --- a/sys/contrib/openzfs/module/zfs/abd.c +++ b/sys/contrib/openzfs/module/zfs/abd.c @@ -212,11 +212,9 @@ abd_alloc(size_t size, boolean_t is_metadata) * buffer. Only use this when it would be very annoying to write your ABD * consumer with a scattered ABD. */ -abd_t * -abd_alloc_linear(size_t size, boolean_t is_metadata) +static abd_t * +abd_alloc_linear_impl(abd_t *abd, size_t size, boolean_t is_metadata) { - abd_t *abd = abd_alloc_struct(0); - VERIFY3U(size, <=, SPA_MAXBLOCKSIZE); abd->abd_flags |= ABD_FLAG_LINEAR | ABD_FLAG_OWNER; @@ -236,6 +234,19 @@ abd_alloc_linear(size_t size, boolean_t is_metadata) return (abd); } +abd_t * +abd_alloc_linear(size_t size, boolean_t is_metadata) +{ + return (abd_alloc_linear_impl(abd_alloc_struct(0), size, is_metadata)); +} + +abd_t * +abd_alloc_linear_struct(abd_t *abd, size_t size, boolean_t is_metadata) +{ + abd_init_struct(abd); + return (abd_alloc_linear_impl(abd, size, is_metadata)); +} + static void abd_free_linear(abd_t *abd) { @@ -349,16 +360,28 @@ abd_alloc_sametype(abd_t *sabd, size_t size) * to "chain" scatter/gather lists together when constructing aggregated * IO's. To free this abd, abd_free() must be called. */ -abd_t * -abd_alloc_gang(void) +static abd_t * +abd_alloc_gang_impl(abd_t *abd) { - abd_t *abd = abd_alloc_struct(0); abd->abd_flags |= ABD_FLAG_GANG | ABD_FLAG_OWNER; list_create(&ABD_GANG(abd).abd_gang_chain, sizeof (abd_t), offsetof(abd_t, abd_gang_link)); return (abd); } +abd_t * +abd_alloc_gang(void) +{ + return (abd_alloc_gang_impl(abd_alloc_struct(0))); +} + +abd_t * +abd_alloc_gang_struct(abd_t *abd) +{ + abd_init_struct(abd); + return (abd_alloc_gang_impl(abd)); +} + /* * Add a child gang ABD to a parent gang ABDs chained list. */ @@ -624,6 +647,14 @@ abd_get_zeros(size_t size) return (abd_get_offset_size(abd_zero_scatter, 0, size)); } +abd_t * +abd_get_zeros_struct(abd_t *abd, size_t size) +{ + ASSERT3P(abd_zero_scatter, !=, NULL); + ASSERT3U(size, <=, SPA_MAXBLOCKSIZE); + return (abd_get_offset_struct(abd, abd_zero_scatter, 0, size)); +} + /* * Create a linear ABD for an existing buf. */ diff --git a/sys/contrib/openzfs/module/zfs/ddt_log.c b/sys/contrib/openzfs/module/zfs/ddt_log.c index 7e699a9b4252..f16731136c67 100644 --- a/sys/contrib/openzfs/module/zfs/ddt_log.c +++ b/sys/contrib/openzfs/module/zfs/ddt_log.c @@ -211,7 +211,7 @@ ddt_log_begin(ddt_t *ddt, size_t nentries, dmu_tx_t *tx, ddt_log_update_t *dlu) ASSERT3U(reclen, <=, UINT16_MAX); dlu->dlu_reclen = reclen; - VERIFY0(dnode_hold(ddt->ddt_os, ddt->ddt_log_active->ddl_object, FTAG, + VERIFY0(dnode_hold(ddt->ddt_os, ddt->ddt_log_active->ddl_object, dlu, &dlu->dlu_dn)); dnode_set_storage_type(dlu->dlu_dn, DMU_OT_DDT_ZAP); @@ -342,7 +342,7 @@ ddt_log_commit(ddt_t *ddt, ddt_log_update_t *dlu) ddt->ddt_log_active->ddl_length += dlu->dlu_ndbp * (uint64_t)dlu->dlu_dn->dn_datablksz; - dnode_rele(dlu->dlu_dn, FTAG); + dnode_rele(dlu->dlu_dn, dlu); ddt_log_update_header(ddt, ddt->ddt_log_active, dlu->dlu_tx); diff --git a/sys/contrib/openzfs/module/zfs/metaslab.c b/sys/contrib/openzfs/module/zfs/metaslab.c index 2be1f2812681..aff0940b978c 100644 --- a/sys/contrib/openzfs/module/zfs/metaslab.c +++ b/sys/contrib/openzfs/module/zfs/metaslab.c @@ -2854,6 +2854,9 @@ metaslab_set_selected_txg(metaslab_t *msp, uint64_t txg) { ASSERT(MUTEX_HELD(&msp->ms_lock)); metaslab_class_t *mc = msp->ms_group->mg_class; + if (msp->ms_selected_txg == txg && + multilist_link_active(&msp->ms_class_txg_node)) + return; multilist_sublist_t *mls = multilist_sublist_lock_obj(&mc->mc_metaslab_txg_list, msp); if (multilist_link_active(&msp->ms_class_txg_node)) diff --git a/sys/contrib/openzfs/module/zfs/spa.c b/sys/contrib/openzfs/module/zfs/spa.c index c6ae91b8d9e9..810589b42f45 100644 --- a/sys/contrib/openzfs/module/zfs/spa.c +++ b/sys/contrib/openzfs/module/zfs/spa.c @@ -11866,3 +11866,7 @@ ZFS_MODULE_VIRTUAL_PARAM_CALL(zfs_zio, zio_, taskq_free, ZFS_MODULE_PARAM(zfs_zio, zio_, taskq_write_tpq, UINT, ZMOD_RW, "Number of CPUs per write issue taskq"); + +ZFS_MODULE_PARAM(zfs, zfs_, ccw_retry_interval, INT, ZMOD_RW, + "Configuration cache file write, retry after failure, interval " + "(seconds)"); diff --git a/sys/contrib/openzfs/module/zfs/vdev.c b/sys/contrib/openzfs/module/zfs/vdev.c index 821dfd6faffe..4a96d39eac1e 100644 --- a/sys/contrib/openzfs/module/zfs/vdev.c +++ b/sys/contrib/openzfs/module/zfs/vdev.c @@ -1739,11 +1739,12 @@ vdev_metaslab_init(vdev_t *vd, uint64_t txg) /* * The metaslab was marked as dirty at the end of * metaslab_init(). Remove it from the dirty list so that we - * can uninitialize and reinitialize it to the new class. + * can uninitialize and reinitialize it to the new class. It + * may be dirty in any txg slot, so clear them all. */ - if (txg != 0) { + for (int t = 0; t < TXG_SIZE; t++) { (void) txg_list_remove_this(&vd->vdev_ms_list, - slog_ms, txg); + slog_ms, t); } uint64_t sm_obj = space_map_object(slog_ms->ms_sm); metaslab_fini(slog_ms); @@ -4622,6 +4623,7 @@ vdev_offline_locked(spa_t *spa, uint64_t guid, uint64_t flags) int error = 0; uint64_t generation; metaslab_group_t *mg; + boolean_t dtl_required; top: spa_vdev_state_enter(spa, SCL_ALLOC); @@ -4643,13 +4645,14 @@ top: * If the device isn't already offline, try to offline it. */ if (!vd->vdev_offline) { + dtl_required = vdev_dtl_required(vd); + /* * If this device has the only valid copy of some data, * don't allow it to be offlined. Log devices are always * expendable. */ - if (!tvd->vdev_islog && vd->vdev_aux == NULL && - vdev_dtl_required(vd)) + if (!tvd->vdev_islog && vd->vdev_aux == NULL && dtl_required) return (spa_vdev_state_exit(spa, NULL, SET_ERROR(EBUSY))); @@ -4659,9 +4662,10 @@ top: * is not NULL since it's possible that we may have just * added this vdev but not yet initialized its metaslabs. */ - if (tvd->vdev_islog && mg != NULL) { + if (tvd->vdev_islog && mg != NULL && dtl_required) { /* - * Prevent any future allocations. + * Prevent future allocations unless the log device is + * redundant. */ ASSERT0P(tvd->vdev_log_mg); metaslab_group_passivate(mg); @@ -4717,7 +4721,7 @@ top: * Add the device back into the metaslab rotor so that * once we online the device it's open for business. */ - if (tvd->vdev_islog && mg != NULL) + if (tvd->vdev_islog && mg != NULL && dtl_required) metaslab_group_activate(mg); } @@ -6200,22 +6204,14 @@ vdev_props_set_sync(void *arg, dmu_tx_t *tx) } int -vdev_prop_set(vdev_t *vd, nvlist_t *innvl, nvlist_t *outnvl) +vdev_prop_set(spa_t *spa, nvlist_t *innvl, nvlist_t *outnvl) { - spa_t *spa = vd->vdev_spa; + vdev_t *vd; nvpair_t *elem = NULL; uint64_t vdev_guid; nvlist_t *nvprops; int error = 0; - ASSERT(vd != NULL); - - /* Check that vdev has a zap we can use */ - if (vd->vdev_root_zap == 0 && - vd->vdev_top_zap == 0 && - vd->vdev_leaf_zap == 0) - return (SET_ERROR(EINVAL)); - if (nvlist_lookup_uint64(innvl, ZPOOL_VDEV_PROPS_SET_VDEV, &vdev_guid) != 0) return (SET_ERROR(EINVAL)); @@ -6224,8 +6220,31 @@ vdev_prop_set(vdev_t *vd, nvlist_t *innvl, nvlist_t *outnvl) &nvprops) != 0) return (SET_ERROR(EINVAL)); - if ((vd = spa_lookup_by_guid(spa, vdev_guid, B_TRUE)) == NULL) + /* + * Resolve the vdev by guid and hold SCL_CONFIG as a reader so the + * vdev tree can't change beneath us while we touch vd. The lock is + * dropped around the "path" and "allocating" handlers below: those + * descend into spa_vdev_enter() -> spa_config_enter(SCL_ALL, + * RW_WRITER), and taking SCL_CONFIG as a writer while this same + * thread already holds it as a reader is a self-deadlock (the writer + * waits for scl_count to drain to 0, but scl_count is this thread's + * own reader, which is never released). Those handlers re-resolve + * the vdev by guid under their own locking, so we re-resolve here + * after each one in case the tree changed. + */ + spa_config_enter(spa, SCL_CONFIG, FTAG, RW_READER); + if ((vd = spa_lookup_by_guid(spa, vdev_guid, B_TRUE)) == NULL) { + spa_config_exit(spa, SCL_CONFIG, FTAG); + return (SET_ERROR(ENOENT)); + } + + /* Check that vdev has a zap we can use */ + if (vd->vdev_root_zap == 0 && + vd->vdev_top_zap == 0 && + vd->vdev_leaf_zap == 0) { + spa_config_exit(spa, SCL_CONFIG, FTAG); return (SET_ERROR(EINVAL)); + } while ((elem = nvlist_next_nvpair(nvprops, elem)) != NULL) { const char *propname = nvpair_name(elem); @@ -6259,7 +6278,17 @@ vdev_prop_set(vdev_t *vd, nvlist_t *innvl, nvlist_t *outnvl) error = EINVAL; break; } + /* + * spa_vdev_setpath() takes SCL_ALL as a writer, so we + * must not hold SCL_CONFIG across it (see above). Drop + * it, then re-resolve vd in case the tree changed. + */ + spa_config_exit(spa, SCL_CONFIG, FTAG); error = spa_vdev_setpath(spa, vdev_guid, strval); + spa_config_enter(spa, SCL_CONFIG, FTAG, RW_READER); + vd = spa_lookup_by_guid(spa, vdev_guid, B_TRUE); + if (vd == NULL && error == 0) + error = SET_ERROR(ENOENT); break; case VDEV_PROP_ALLOCATING: if (nvpair_value_uint64(elem, &intval) != 0) { @@ -6268,10 +6297,19 @@ vdev_prop_set(vdev_t *vd, nvlist_t *innvl, nvlist_t *outnvl) } if (intval != vd->vdev_noalloc) break; + /* + * spa_vdev_noalloc()/spa_vdev_alloc() take SCL_ALL as a + * writer; same locking dance as VDEV_PROP_PATH above. + */ + spa_config_exit(spa, SCL_CONFIG, FTAG); if (intval == 0) error = spa_vdev_noalloc(spa, vdev_guid); else error = spa_vdev_alloc(spa, vdev_guid); + spa_config_enter(spa, SCL_CONFIG, FTAG, RW_READER); + vd = spa_lookup_by_guid(spa, vdev_guid, B_TRUE); + if (vd == NULL && error == 0) + error = SET_ERROR(ENOENT); break; case VDEV_PROP_FAILFAST: if (nvpair_value_uint64(elem, &intval) != 0 || @@ -6444,10 +6482,15 @@ end: if (error != 0) { intval = error; vdev_prop_add_list(outnvl, propname, strval, intval, 0); - return (error); + break; } } + spa_config_exit(spa, SCL_CONFIG, FTAG); + + if (error != 0) + return (error); + return (dsl_sync_task(spa->spa_name, NULL, vdev_props_set_sync, innvl, 6, ZFS_SPACE_CHECK_EXTRA_RESERVED)); } @@ -6462,10 +6505,10 @@ vdev_get_child_idx(vdev_t *vd, uint64_t c_guid) } int -vdev_prop_get(vdev_t *vd, nvlist_t *innvl, nvlist_t *outnvl) +vdev_prop_get(spa_t *spa, nvlist_t *innvl, nvlist_t *outnvl) { - spa_t *spa = vd->vdev_spa; objset_t *mos = spa->spa_meta_objset; + vdev_t *vd; int err = 0; uint64_t objid = 0; uint64_t vdev_guid; @@ -6477,7 +6520,6 @@ vdev_prop_get(vdev_t *vd, nvlist_t *innvl, nvlist_t *outnvl) const char *propname = NULL; vdev_prop_t prop; - ASSERT(vd != NULL); ASSERT(mos != NULL); if (nvlist_lookup_uint64(innvl, ZPOOL_VDEV_PROPS_GET_VDEV, @@ -6487,6 +6529,18 @@ vdev_prop_get(vdev_t *vd, nvlist_t *innvl, nvlist_t *outnvl) nvlist_lookup_nvlist(innvl, ZPOOL_VDEV_PROPS_GET_PROPS, &nvprops); /* + * Resolve the vdev by guid and hold SCL_CONFIG as a reader across the + * property fetch so the vdev tree can't change beneath us. This path + * is read-only and never takes SCL_CONFIG as a writer, so holding the + * reader throughout is safe. + */ + spa_config_enter(spa, SCL_CONFIG, FTAG, RW_READER); + if ((vd = spa_lookup_by_guid(spa, vdev_guid, B_TRUE)) == NULL) { + spa_config_exit(spa, SCL_CONFIG, FTAG); + return (SET_ERROR(ENOENT)); + } + + /* * A missing ZAP is normal for spare and L2ARC vdevs, which are * not part of the main vdev tree and never get ZAPs allocated. * Many properties are sourced directly from vdev_t fields and @@ -6997,6 +7051,8 @@ vdev_prop_get(vdev_t *vd, nvlist_t *innvl, nvlist_t *outnvl) } mutex_exit(&spa->spa_props_lock); + spa_config_exit(spa, SCL_CONFIG, FTAG); + if (err && err != ENOENT) { return (err); } diff --git a/sys/contrib/openzfs/module/zfs/vdev_draid.c b/sys/contrib/openzfs/module/zfs/vdev_draid.c index 8f556b868784..019c20f3f99d 100644 --- a/sys/contrib/openzfs/module/zfs/vdev_draid.c +++ b/sys/contrib/openzfs/module/zfs/vdev_draid.c @@ -798,7 +798,8 @@ vdev_draid_map_alloc_write(zio_t *zio, uint64_t abd_offset, raidz_row_t *rr) if (rc->rc_size == 0) { /* empty data column (small write), add a skip sector */ ASSERT3U(skip_size, ==, parity_size); - rc->rc_abd = abd_get_zeros(skip_size); + rc->rc_abd = abd_get_zeros_struct(&rc->rc_abdstruct, + skip_size); } else if (rc->rc_size == parity_size) { /* this is a "big column" */ rc->rc_abd = abd_get_offset_struct(&rc->rc_abdstruct, @@ -806,7 +807,7 @@ vdev_draid_map_alloc_write(zio_t *zio, uint64_t abd_offset, raidz_row_t *rr) } else { /* short data column, add a skip sector */ ASSERT3U(rc->rc_size + skip_size, ==, parity_size); - rc->rc_abd = abd_alloc_gang(); + rc->rc_abd = abd_alloc_gang_struct(&rc->rc_abdstruct); abd_gang_add(rc->rc_abd, abd_get_offset_size( zio->io_abd, abd_off, rc->rc_size), B_TRUE); abd_gang_add(rc->rc_abd, abd_get_zeros(skip_size), @@ -863,7 +864,7 @@ vdev_draid_map_alloc_scrub(zio_t *zio, uint64_t abd_offset, raidz_row_t *rr) /* short data column, add a skip sector */ ASSERT3U(rc->rc_size + skip_size, ==, parity_size); ASSERT3U(rr->rr_nempty, !=, 0); - rc->rc_abd = abd_alloc_gang(); + rc->rc_abd = abd_alloc_gang_struct(&rc->rc_abdstruct); abd_gang_add(rc->rc_abd, abd_get_offset_size( zio->io_abd, abd_off, rc->rc_size), B_TRUE); abd_gang_add(rc->rc_abd, abd_get_offset_size( @@ -1240,7 +1241,8 @@ vdev_draid_map_alloc_row(zio_t *zio, raidz_row_t **rrp, uint64_t io_offset, /* Allocate buffers for the parity columns */ for (uint64_t c = 0; c < rr->rr_firstdatacol; c++) { raidz_col_t *rc = &rr->rr_col[c]; - rc->rc_abd = abd_alloc_linear(rc->rc_size, B_FALSE); + rc->rc_abd = abd_alloc_linear_struct(&rc->rc_abdstruct, + rc->rc_size, B_FALSE); } /* diff --git a/sys/contrib/openzfs/module/zfs/vdev_raidz.c b/sys/contrib/openzfs/module/zfs/vdev_raidz.c index 2db7422e772e..ab9f62c3c2d0 100644 --- a/sys/contrib/openzfs/module/zfs/vdev_raidz.c +++ b/sys/contrib/openzfs/module/zfs/vdev_raidz.c @@ -419,7 +419,16 @@ static int zfs_scrub_partial_writes = 1; static void vdev_raidz_row_free(raidz_row_t *rr) { - for (int c = 0; c < rr->rr_cols; c++) { + abd_t *dabd = rr->rr_col[rr->rr_firstdatacol].rc_abd; + for (int c = 0; c < rr->rr_firstdatacol; c++) { + raidz_col_t *rc = &rr->rr_col[c]; + + if (rc->rc_size != 0 && rc->rc_abd != dabd) + abd_free(rc->rc_abd); + if (rc->rc_orig_data != NULL) + abd_free(rc->rc_orig_data); + } + for (int c = rr->rr_firstdatacol; c < rr->rr_cols; c++) { raidz_col_t *rc = &rr->rr_col[c]; if (rc->rc_size != 0) @@ -532,6 +541,22 @@ vdev_raidz_map_alloc_write(zio_t *zio, raidz_map_t *rm, uint64_t ashift) */ int skipped = rr->rr_scols - rr->rr_cols; + /* + * When there is only a single data column the parity is a copy of + * it, so point all parity columns at the data ABD directly to avoid + * allocating buffers and computing parity. + */ + if (rr->rr_cols == rr->rr_firstdatacol + 1) { + ASSERT0(nwrapped); + ASSERT0(rm->rm_nskip); + raidz_col_t *dc = &rr->rr_col[rr->rr_firstdatacol]; + dc->rc_abd = abd_get_offset_struct(&dc->rc_abdstruct, + zio->io_abd, 0, dc->rc_size); + for (c = 0; c < rr->rr_firstdatacol; c++) + rr->rr_col[c].rc_abd = dc->rc_abd; + return; + } + /* Allocate buffers for the parity columns */ for (c = 0; c < rr->rr_firstdatacol; c++) { raidz_col_t *rc = &rr->rr_col[c]; @@ -546,12 +571,13 @@ vdev_raidz_map_alloc_write(zio_t *zio, raidz_map_t *rm, uint64_t ashift) * VDEV queue locks (vq_lock). */ if (c < nwrapped) { - rc->rc_abd = abd_alloc_linear( + rc->rc_abd = abd_alloc_linear_struct(&rc->rc_abdstruct, rc->rc_size + (1ULL << ashift), B_FALSE); abd_zero_off(rc->rc_abd, rc->rc_size, 1ULL << ashift); skipped++; } else { - rc->rc_abd = abd_alloc_linear(rc->rc_size, B_FALSE); + rc->rc_abd = abd_alloc_linear_struct(&rc->rc_abdstruct, + rc->rc_size, B_FALSE); } } @@ -599,9 +625,11 @@ vdev_raidz_map_alloc_read(zio_t *zio, raidz_map_t *rm) ASSERT3U(rm->rm_nrows, ==, 1); /* Allocate buffers for the parity columns */ - for (c = 0; c < rr->rr_firstdatacol; c++) - rr->rr_col[c].rc_abd = - abd_alloc_linear(rr->rr_col[c].rc_size, B_FALSE); + for (c = 0; c < rr->rr_firstdatacol; c++) { + raidz_col_t *rc = &rr->rr_col[c]; + rc->rc_abd = abd_alloc_linear_struct(&rc->rc_abdstruct, + rc->rc_size, B_FALSE); + } for (uint64_t off = 0; c < rr->rr_cols; c++) { raidz_col_t *rc = &rr->rr_col[c]; @@ -1046,8 +1074,8 @@ vdev_raidz_map_alloc_expanded(zio_t *zio, continue; prc->rc_abd = - abd_alloc_linear(rm->rm_phys_col[i].rc_size, - B_FALSE); + abd_alloc_linear_struct(&prc->rc_abdstruct, + prc->rc_size, B_FALSE); } /* @@ -1075,8 +1103,8 @@ vdev_raidz_map_alloc_expanded(zio_t *zio, for (int c = 0; c < rr->rr_firstdatacol; c++) { raidz_col_t *rc = &rr->rr_col[c]; rc->rc_abd = - abd_alloc_linear(rc->rc_size, - B_TRUE); + abd_alloc_linear_struct(&rc->rc_abdstruct, + rc->rc_size, B_TRUE); } } } @@ -1272,6 +1300,13 @@ vdev_raidz_generate_parity_row(raidz_map_t *rm, raidz_row_t *rr) return; } + /* + * Single data column: parity is the data itself. + */ + if (rr->rr_col[VDEV_RAIDZ_P].rc_abd == + rr->rr_col[rr->rr_firstdatacol].rc_abd) + return; + /* Generate using the new math implementation */ if (vdev_raidz_math_generate(rm, rr) != RAIDZ_ORIGINAL_IMPL) return; diff --git a/sys/contrib/openzfs/module/zfs/zap.c b/sys/contrib/openzfs/module/zfs/zap.c index ca7598f489b0..9aeb18037a87 100644 --- a/sys/contrib/openzfs/module/zfs/zap.c +++ b/sys/contrib/openzfs/module/zfs/zap.c @@ -744,7 +744,7 @@ zap_length_uint64(objset_t *os, uint64_t zapobj, const uint64_t *key, /* zap_remove */ -static int +int zap_remove_norm_by_dnode(dnode_t *dn, const char *name, matchtype_t mt, dmu_tx_t *tx) { diff --git a/sys/contrib/openzfs/module/zfs/zfs_crrd.c b/sys/contrib/openzfs/module/zfs/zfs_crrd.c index 30d4c7c36897..8aee0727161f 100644 --- a/sys/contrib/openzfs/module/zfs/zfs_crrd.c +++ b/sys/contrib/openzfs/module/zfs/zfs_crrd.c @@ -99,14 +99,14 @@ rrd_tail(rrd_t *rrd) * rrd_get works from 0..rrd_len()-1. */ size_t -rrd_len(rrd_t *rrd) +rrd_len(const rrd_t *rrd) { return (rrd->rrd_length); } const rrd_data_t * -rrd_entry(rrd_t *rrd, size_t i) +rrd_entry(const rrd_t *rrd, size_t i) { size_t n; @@ -119,7 +119,7 @@ rrd_entry(rrd_t *rrd, size_t i) } uint64_t -rrd_get(rrd_t *rrd, size_t i) +rrd_get(const rrd_t *rrd, size_t i) { const rrd_data_t *data = rrd_entry(rrd, i); @@ -226,3 +226,19 @@ dbrrd_query(dbrrd_t *r, hrtime_t tv, dbrrd_rounding_t rounding) return (data == NULL ? 0 : data->rrdd_txg); } + +hrtime_t +dbrrd_latest_time(dbrrd_t *r) +{ + const rrd_data_t *head; + const rrd_t *curdb; + size_t dblen; + + curdb = &r->dbr_minutes; + dblen = rrd_len(curdb); + if (dblen == 0) + return (0); + + head = rrd_entry(curdb, dblen - 1); + return (head->rrdd_time); +} diff --git a/sys/contrib/openzfs/module/zfs/zfs_ioctl.c b/sys/contrib/openzfs/module/zfs/zfs_ioctl.c index a23f397e698e..3eb141d6e5aa 100644 --- a/sys/contrib/openzfs/module/zfs/zfs_ioctl.c +++ b/sys/contrib/openzfs/module/zfs/zfs_ioctl.c @@ -801,6 +801,47 @@ zfs_secpolicy_rollback(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr) } static int +zfs_secpolicy_send_impl(const char *name, dsl_dataset_t *ds, cred_t *cr, + boolean_t rawok) +{ + /* Can't send from within a zone that can't see the dataset */ + int err = zfs_dozonecheck_ds(name, ds, cr); + if (err != 0) + return (err); + + /* ZFS global admin (root) can do anything. */ + err = secpolicy_zfs(cr); + if (err == 0) + return (0); + + /* 'send' permission on this dataset is allowed to send. */ + err = dsl_deleg_access_impl(ds, ZFS_DELEG_PERM_SEND, cr); + if (err == 0) + return (0); + + /* Raw sends have extra perms that might work. */ + if (rawok) { + /* 'send:raw' permission on this dataset can do raw sends. */ + err = dsl_deleg_access_impl(ds, ZFS_DELEG_PERM_SEND_RAW, cr); + if (err == 0) + return (0); + + if (ds->ds_dir->dd_crypto_obj != 0) { + /* + * Dataset is encrypted; 'send:encrypted' permission + * will allow a raw send. + */ + err = dsl_deleg_access_impl(ds, + ZFS_DELEG_PERM_SEND_ENCRYPTED, cr); + if (err == 0) + return (0); + } + } + + return (err); +} + +static int zfs_secpolicy_send(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr) { (void) innvl; @@ -829,12 +870,8 @@ zfs_secpolicy_send(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr) dsl_dataset_name(ds, zc->zc_name); - error = zfs_secpolicy_write_perms_ds(zc->zc_name, ds, - ZFS_DELEG_PERM_SEND, cr); - if (error != 0 && rawok) { - error = zfs_secpolicy_write_perms_ds(zc->zc_name, ds, - ZFS_DELEG_PERM_SEND_RAW, cr); - } + error = zfs_secpolicy_send_impl(zc->zc_name, ds, cr, rawok); + dsl_dataset_rele(ds, FTAG); dsl_pool_rele(dp, FTAG); @@ -844,16 +881,29 @@ zfs_secpolicy_send(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr) static int zfs_secpolicy_send_new(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr) { - boolean_t rawok = nvlist_exists(innvl, "rawok"); + dsl_pool_t *dp; + dsl_dataset_t *ds; int error; + boolean_t rawok = nvlist_exists(innvl, "rawok"); - (void) innvl; - error = zfs_secpolicy_write_perms(zc->zc_name, - ZFS_DELEG_PERM_SEND, cr); - if (error != 0 && rawok) { - error = zfs_secpolicy_write_perms(zc->zc_name, - ZFS_DELEG_PERM_SEND_RAW, cr); + if (INGLOBALZONE(curproc) && secpolicy_zfs(cr) == 0) + return (0); + + error = dsl_pool_hold(zc->zc_name, FTAG, &dp); + if (error != 0) + return (error); + + error = dsl_dataset_hold(dp, zc->zc_name, FTAG, &ds); + if (error != 0) { + dsl_pool_rele(dp, FTAG); + return (error); } + + error = zfs_secpolicy_send_impl(zc->zc_name, ds, cr, rawok); + + dsl_dataset_rele(ds, FTAG); + dsl_pool_rele(dp, FTAG); + return (error); } @@ -1635,8 +1685,17 @@ zfsvfs_hold(const char *name, const void *tag, zfsvfs_t **zfvp, int error = 0; if (getzfsvfs(name, zfvp) != 0) - error = zfsvfs_create(name, B_FALSE, zfvp); + error = zfsvfs_create_hold(name, zfvp); if (error == 0) { + /* + * dmu_objset_hold() keeps the pool config read lock held. + * Drop it before acquiring the teardown lock to avoid ABBA + * deadlock with zfs_resume_fs(), which holds teardown write + * then acquires the config lock. + */ + if ((*zfvp)->z_use_hold) + dsl_pool_config_exit( + dmu_objset_pool((*zfvp)->z_os), *zfvp); if (writer) ZFS_TEARDOWN_ENTER_WRITE(*zfvp, tag); else @@ -1663,7 +1722,18 @@ zfsvfs_rele(zfsvfs_t *zfsvfs, const void *tag) if (zfs_vfs_held(zfsvfs)) { zfs_vfs_rele(zfsvfs); } else { - dmu_objset_disown(zfsvfs->z_os, B_TRUE, zfsvfs); + objset_t *os = zfsvfs->z_os; + if (zfsvfs->z_use_hold) { + /* + * Opened via dmu_objset_hold(): re-acquire the pool + * config lock (released in zfsvfs_hold() before the + * teardown lock) so that dmu_objset_rele() can exit it. + */ + dsl_pool_config_enter(dmu_objset_pool(os), zfsvfs); + dmu_objset_rele(os, zfsvfs); + } else { + dmu_objset_disown(os, B_TRUE, zfsvfs); + } zfsvfs_free(zfsvfs); } } @@ -3457,13 +3527,6 @@ zfs_ioc_vdev_set_props(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl) { spa_t *spa; int error; - vdev_t *vd; - uint64_t vdev_guid; - - /* Early validation */ - if (nvlist_lookup_uint64(innvl, ZPOOL_VDEV_PROPS_SET_VDEV, - &vdev_guid) != 0) - return (SET_ERROR(EINVAL)); if (outnvl == NULL) return (SET_ERROR(EINVAL)); @@ -3473,15 +3536,7 @@ zfs_ioc_vdev_set_props(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl) ASSERT(spa_writeable(spa)); - spa_config_enter(spa, SCL_CONFIG, FTAG, RW_READER); - if ((vd = spa_lookup_by_guid(spa, vdev_guid, B_TRUE)) == NULL) { - spa_config_exit(spa, SCL_CONFIG, FTAG); - spa_close(spa, FTAG); - return (SET_ERROR(ENOENT)); - } - - error = vdev_prop_set(vd, innvl, outnvl); - spa_config_exit(spa, SCL_CONFIG, FTAG); + error = vdev_prop_set(spa, innvl, outnvl); spa_close(spa, FTAG); @@ -3506,13 +3561,6 @@ zfs_ioc_vdev_get_props(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl) { spa_t *spa; int error; - vdev_t *vd; - uint64_t vdev_guid; - - /* Early validation */ - if (nvlist_lookup_uint64(innvl, ZPOOL_VDEV_PROPS_GET_VDEV, - &vdev_guid) != 0) - return (SET_ERROR(EINVAL)); if (outnvl == NULL) return (SET_ERROR(EINVAL)); @@ -3520,15 +3568,7 @@ zfs_ioc_vdev_get_props(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl) if ((error = spa_open(poolname, &spa, FTAG)) != 0) return (error); - spa_config_enter(spa, SCL_CONFIG, FTAG, RW_READER); - if ((vd = spa_lookup_by_guid(spa, vdev_guid, B_TRUE)) == NULL) { - spa_config_exit(spa, SCL_CONFIG, FTAG); - spa_close(spa, FTAG); - return (SET_ERROR(ENOENT)); - } - - error = vdev_prop_get(vd, innvl, outnvl); - spa_config_exit(spa, SCL_CONFIG, FTAG); + error = vdev_prop_get(spa, innvl, outnvl); spa_close(spa, FTAG); diff --git a/sys/contrib/openzfs/module/zfs/zfs_sa.c b/sys/contrib/openzfs/module/zfs/zfs_sa.c index 8b4fc6fd7fbd..ba0eeccac1d9 100644 --- a/sys/contrib/openzfs/module/zfs/zfs_sa.c +++ b/sys/contrib/openzfs/module/zfs/zfs_sa.c @@ -68,6 +68,7 @@ const sa_attr_reg_t zfs_attr_table[ZPL_END+1] = { {"ZPL_DACL_ACES", 0, SA_ACL, 0}, {"ZPL_DXATTR", 0, SA_UINT8_ARRAY, 0}, {"ZPL_PROJID", sizeof (uint64_t), SA_UINT64_ARRAY, 0}, + {"ZPL_SEQ", sizeof (uint64_t), SA_UINT64_ARRAY, 0}, {NULL, 0, 0, 0} }; @@ -270,7 +271,7 @@ zfs_sa_set_xattr(znode_t *zp, const char *name, const void *value, size_t vsize) dmu_tx_abort(tx); } else { int count = 0; - sa_bulk_attr_t bulk[2]; + sa_bulk_attr_t bulk[3]; uint64_t ctime[2]; if (logsaxattr) @@ -282,6 +283,8 @@ zfs_sa_set_xattr(znode_t *zp, const char *name, const void *value, size_t vsize) NULL, obj, size); SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CTIME(zfsvfs), NULL, &ctime, 16); + ZFS_PERSIST_SEQ(zp, bulk, count); + ASSERT3S(count, <=, ARRAY_SIZE(bulk)); VERIFY0(sa_bulk_update(zp->z_sa_hdl, bulk, count, tx)); dmu_tx_commit(tx); diff --git a/sys/contrib/openzfs/module/zfs/zfs_vnops.c b/sys/contrib/openzfs/module/zfs/zfs_vnops.c index 1ceedf28ed21..59e69dc389a2 100644 --- a/sys/contrib/openzfs/module/zfs/zfs_vnops.c +++ b/sys/contrib/openzfs/module/zfs/zfs_vnops.c @@ -647,7 +647,7 @@ zfs_write(znode_t *zp, zfs_uio_t *uio, int ioflag, cred_t *cr) if ((error = zfs_enter_verify_zp(zfsvfs, zp, FTAG)) != 0) return (error); - sa_bulk_attr_t bulk[4]; + sa_bulk_attr_t bulk[5]; int count = 0; uint64_t mtime[2], ctime[2]; SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MTIME(zfsvfs), NULL, &mtime, 16); @@ -656,6 +656,9 @@ zfs_write(znode_t *zp, zfs_uio_t *uio, int ioflag, cred_t *cr) &zp->z_size, 8); SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_FLAGS(zfsvfs), NULL, &zp->z_pflags, 8); + if (zp->z_is_sa) + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_SEQ(zfsvfs), NULL, + &zp->z_seq, 8); /* * Callers might not be able to detect properly that we are read-only, @@ -870,7 +873,7 @@ zfs_write(znode_t *zp, zfs_uio_t *uio, int ioflag, cred_t *cr) * Start a transaction. */ dmu_tx_t *tx = dmu_tx_create(zfsvfs->z_os); - dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_FALSE); + dmu_tx_hold_sa(tx, zp->z_sa_hdl, ZFS_SEQ_MAY_GROW(zp)); dmu_buf_impl_t *db = (dmu_buf_impl_t *)sa_get_db(zp->z_sa_hdl); DB_DNODE_ENTER(db); dmu_tx_hold_write_by_dnode(tx, DB_DNODE(db), woff, nbytes); @@ -1010,6 +1013,8 @@ zfs_write(znode_t *zp, zfs_uio_t *uio, int ioflag, cred_t *cr) &clear_setid_bits_txg, tx); zfs_tstamp_update_setup(zp, CONTENT_MODIFIED, mtime, ctime); + if (zp->z_is_sa) + zp->z_has_seq = B_TRUE; /* * Update the file size (zp_size) if it has changed; @@ -1028,6 +1033,7 @@ zfs_write(znode_t *zp, zfs_uio_t *uio, int ioflag, cred_t *cr) if (zfsvfs->z_replay && zfsvfs->z_replay_eof != 0) zp->z_size = zfsvfs->z_replay_eof; + ASSERT3S(count, <=, ARRAY_SIZE(bulk)); error1 = sa_bulk_update(zp->z_sa_hdl, bulk, count, tx); if (error1 != 0) /* Avoid clobbering EFAULT. */ @@ -1616,7 +1622,7 @@ zfs_clone_range(znode_t *inzp, uint64_t *inoffp, znode_t *outzp, uint64_t outsize, size; int error; int count = 0; - sa_bulk_attr_t bulk[3]; + sa_bulk_attr_t bulk[5]; uint64_t mtime[2], ctime[2]; uint64_t uid, gid, projid; blkptr_t *bps; @@ -1870,6 +1876,11 @@ zfs_clone_range(znode_t *inzp, uint64_t *inoffp, znode_t *outzp, &ctime, 16); SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_SIZE(outzfsvfs), NULL, &outzp->z_size, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_FLAGS(outzfsvfs), NULL, + &outzp->z_pflags, 8); + if (outzp->z_is_sa) + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_SEQ(outzfsvfs), NULL, + &outzp->z_seq, 8); zilog = outzfsvfs->z_log; maxblocks = zil_max_log_data(zilog, sizeof (lr_clone_range_t)) / @@ -1933,7 +1944,7 @@ zfs_clone_range(znode_t *inzp, uint64_t *inoffp, znode_t *outzp, * Start a transaction. */ tx = dmu_tx_create(outos); - dmu_tx_hold_sa(tx, outzp->z_sa_hdl, B_FALSE); + dmu_tx_hold_sa(tx, outzp->z_sa_hdl, ZFS_SEQ_MAY_GROW(outzp)); db = (dmu_buf_impl_t *)sa_get_db(outzp->z_sa_hdl); DB_DNODE_ENTER(db); dmu_tx_hold_clone_by_dnode(tx, DB_DNODE(db), outoff, size, @@ -1988,6 +1999,8 @@ zfs_clone_range(znode_t *inzp, uint64_t *inoffp, znode_t *outzp, &clear_setid_bits_txg, tx); zfs_tstamp_update_setup(outzp, CONTENT_MODIFIED, mtime, ctime); + if (outzp->z_is_sa) + outzp->z_has_seq = B_TRUE; /* * Update the file size (zp_size) if it has changed; @@ -1998,6 +2011,7 @@ zfs_clone_range(znode_t *inzp, uint64_t *inoffp, znode_t *outzp, outoff + size); } + ASSERT3S(count, <=, ARRAY_SIZE(bulk)); error = sa_bulk_update(outzp->z_sa_hdl, bulk, count, tx); zfs_log_clone_range(zilog, tx, TX_CLONE_RANGE, outzp, outoff, @@ -2068,7 +2082,7 @@ zfs_clone_range_replay(znode_t *zp, uint64_t off, uint64_t len, uint64_t blksz, dmu_tx_t *tx; int error; int count = 0; - sa_bulk_attr_t bulk[3]; + sa_bulk_attr_t bulk[5]; uint64_t mtime[2], ctime[2]; ASSERT3U(off, <, MAXOFFSET_T); @@ -2091,17 +2105,12 @@ zfs_clone_range_replay(znode_t *zp, uint64_t off, uint64_t len, uint64_t blksz, return (SET_ERROR(EINVAL)); } - 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_SIZE(zfsvfs), NULL, - &zp->z_size, 8); - /* * Start a transaction. */ tx = dmu_tx_create(zfsvfs->z_os); - dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_FALSE); + dmu_tx_hold_sa(tx, zp->z_sa_hdl, ZFS_SEQ_MAY_GROW(zp)); db = (dmu_buf_impl_t *)sa_get_db(zp->z_sa_hdl); DB_DNODE_ENTER(db); dmu_tx_hold_clone_by_dnode(tx, DB_DNODE(db), off, len, blksz); @@ -2119,11 +2128,19 @@ zfs_clone_range_replay(znode_t *zp, uint64_t off, uint64_t len, uint64_t blksz, dmu_brt_clone(zfsvfs->z_os, zp->z_id, off, len, tx, bps, nbps); + 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_SIZE(zfsvfs), NULL, + &zp->z_size, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_FLAGS(zfsvfs), NULL, + &zp->z_pflags, 8); zfs_tstamp_update_setup(zp, CONTENT_MODIFIED, mtime, ctime); + ZFS_PERSIST_SEQ(zp, bulk, count); if (zp->z_size < off + len) zp->z_size = off + len; + ASSERT3S(count, <=, ARRAY_SIZE(bulk)); error = sa_bulk_update(zp->z_sa_hdl, bulk, count, tx); /* diff --git a/sys/contrib/openzfs/module/zfs/zio.c b/sys/contrib/openzfs/module/zfs/zio.c index 4b7c13dd1e94..a45221d6544e 100644 --- a/sys/contrib/openzfs/module/zfs/zio.c +++ b/sys/contrib/openzfs/module/zfs/zio.c @@ -6002,6 +6002,67 @@ zbookmark_compare(uint16_t dbss1, uint8_t ibs1, uint16_t dbss2, uint8_t ibs2, zb1->zb_blkid == zb2->zb_blkid) return (0); + if (zb1->zb_level < 0 || zb2->zb_level < 0) { + /* + * "Negative" levels are ZB_ROOT_LEVEL, ZB_ZIL_LEVEL or + * ZB_DNODE_LEVEL, and represent some sort of auxiliary dataset + * block or object. In this case, we're usually being called + * from dsl_scan or dmu_traverse. + * + * These "levels" are more like a "type" signal, not directly + * comparable, but we have to do something. So we order them in + * the order we would see them during a typical scan or + * traverse: + * + * - ZB_ROOT_LEVEL: the "top" block carrying the dataset head + * - ZB_ZIL_LEVEL: the head ZIL block attached to the dataset + * - ZB_DNODE_LEVEL: "virtual" position representing an + * entire object. Sorts ahead of the true + * data blocks for the object. + * - level >= 0: data blocks + * + * We work through these cases from top to bottom, with + * appropriate tiebreaks for each kind. + */ + + /* + * Root level wins. It shouldn't be possible for both to be the + * root level in this per-dataset tree, and there's no obvious + * tiebreaker, but we handle it as a defensive measure. + */ + if (zb1->zb_level == ZB_ROOT_LEVEL && + zb2->zb_level == ZB_ROOT_LEVEL) + return (TREE_PCMP(zb1, zb2)); + if (zb1->zb_level == ZB_ROOT_LEVEL) + return (-1); + if (zb2->zb_level == ZB_ROOT_LEVEL) + return (1); + + /* ZIL bookmarks have valid blkid, so the earlier one wins. */ + if (zb1->zb_level == ZB_ZIL_LEVEL && + zb2->zb_level == ZB_ZIL_LEVEL) + return (TREE_CMP(zb1->zb_blkid, zb2->zb_blkid)); + if (zb1->zb_level == ZB_ZIL_LEVEL) + return (-1); + if (zb2->zb_level == ZB_ZIL_LEVEL) + return (1); + + /* + * If we get this far, then at least one is ZB_DNODE_LEVEL, and + * the other is either ZB_DNODE_LEVEL or a data block. + * Regardless, the one with the lower-numbered object wins - + * earler ZB_DNODE_LEVEL beats later, but data block on earlier + * objects beats the virtual marker on later objects. + */ + int cmp = TREE_CMP(zb1->zb_object, zb2->zb_object); + if (cmp != 0) + return (cmp); + + if (zb1->zb_level == ZB_DNODE_LEVEL) + return (-1); + return (1); + } + IMPLY(zb1->zb_level > 0, ibs1 >= SPA_MINBLOCKSHIFT); IMPLY(zb2->zb_level > 0, ibs2 >= SPA_MINBLOCKSHIFT); |
