aboutsummaryrefslogtreecommitdiff
path: root/sys/contrib/openzfs/module/zfs
diff options
context:
space:
mode:
authorMartin Matuska <mm@FreeBSD.org>2026-06-27 06:15:42 +0000
committerMartin Matuska <mm@FreeBSD.org>2026-06-27 08:51:33 +0000
commitd0b3ecdc274930e190ea233b6b69ff03782eaf8d (patch)
tree8f7bc49621e339a1a9baa14a3221e2d90c7a0c5d /sys/contrib/openzfs/module/zfs
parent8e61d8707f8a10acc143210089fea2502a3f2922 (diff)
parent37af899488652c55c456d3f160fb8b295db2ec70 (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.c45
-rw-r--r--sys/contrib/openzfs/module/zfs/ddt_log.c4
-rw-r--r--sys/contrib/openzfs/module/zfs/metaslab.c3
-rw-r--r--sys/contrib/openzfs/module/zfs/spa.c4
-rw-r--r--sys/contrib/openzfs/module/zfs/vdev.c102
-rw-r--r--sys/contrib/openzfs/module/zfs/vdev_draid.c10
-rw-r--r--sys/contrib/openzfs/module/zfs/vdev_raidz.c55
-rw-r--r--sys/contrib/openzfs/module/zfs/zap.c2
-rw-r--r--sys/contrib/openzfs/module/zfs/zfs_crrd.c22
-rw-r--r--sys/contrib/openzfs/module/zfs/zfs_ioctl.c134
-rw-r--r--sys/contrib/openzfs/module/zfs/zfs_sa.c5
-rw-r--r--sys/contrib/openzfs/module/zfs/zfs_vnops.c39
-rw-r--r--sys/contrib/openzfs/module/zfs/zio.c61
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);