aboutsummaryrefslogtreecommitdiff
path: root/sys/contrib/openzfs/module/zfs
diff options
context:
space:
mode:
authorMartin Matuska <mm@FreeBSD.org>2024-08-10 09:15:30 +0000
committerMartin Matuska <mm@FreeBSD.org>2024-08-10 09:43:43 +0000
commitce4dcb97ca433b2a2f03fbae957dae0ff16f6f51 (patch)
treed839178ddbd33504db2c2a3b106c3c6af71fb1c7 /sys/contrib/openzfs/module/zfs
parent54a543d5ea3a58aee2f001498376127efea24bd2 (diff)
parent9c56b8ec7855119c8948c551ee28086a11465a56 (diff)
zfs: merge openzfs/zfs@9c56b8ec7
Notable upstream pull request merges: #15817 5536c0dee Sync AUX label during pool import #15889 c7ada64bb ddt: dedup table quota enforcement #15890 62e7d3c89 ddt: add support for prefetching tables into the ARC #15894 e26b3771e spa_preferred_class: pass the entire zio #15894 d54d0fff3 dnode: allow storage class to be overridden by object type #16197 55427add3 Several improvements to ARC shrinking #16217 -multiple JSON output for various zfs and zpool subcommands #16248 24e6585e7 libzfs.h: Set ZFS_MAXPROPLEN and ZPOOL_MAXPROPLEN to ZAP_MAXVALUELEN #16264 9dfc5c4a0 Fix long_free_dirty accounting for small files #16268 ed0db1cc8 Make txg_wait_synced conditional in zfsvfs_teardown, for FreeBSD #16288 d60debbf5 Fix sa_add_projid to lookup and update SA_ZPL_DXATTR #16308 ec580bc52 zfs: add bounds checking to zil_parse #16310 c21dc56ea Fix zdb_dump_block for little endian #16315 7ddc1f737 zil: add stats for commit failure/fallback #16326 b0bf14cdb abd: lift ABD zero scan from zio_compress_data() to abd_cmp_zero() #16337 c8184d714 Block cloning conditionally destroy ARC buffer #16338 dbe07928b Add support for multiple lines to the sharenfs property for FreeBSD #16374 1a3e32e6a Cleanup DB_DNODE() macros usage #16374 ed87d456e Skip dnode handles use when not needed #16346 fb6d8cf22 Add some missing vdev properties #16364 670147be5 zvol: ensure device minors are properly cleaned up #16382 dea8fabf7 FreeBSD: Fix RLIMIT_FSIZE handling for block cloning #16387 aef452f10 Improve zfs_blkptr_verify() #16395 cbcb52243 Fix the names of some FreeBSD sysctls in include/tunables.cfg #16401 5b9f3b766 Soften pruning threshold on not evictable metadata #16404 cdd53fea1 FreeBSD: Add missing memory reclamation accounting #16404 1fdcb653b Once more refactor arc_summary output #16419 1f5bf91a8 Fix memory corruption during parallel zpool import with -o cachefile #16426 cf6e8b218 zstream: remove duplicate highbit64 definition Obtained from: OpenZFS OpenZFS commit: 9c56b8ec7855119c8948c551ee28086a11465a56
Diffstat (limited to 'sys/contrib/openzfs/module/zfs')
-rw-r--r--sys/contrib/openzfs/module/zfs/abd.c25
-rw-r--r--sys/contrib/openzfs/module/zfs/arc.c230
-rw-r--r--sys/contrib/openzfs/module/zfs/dbuf.c35
-rw-r--r--sys/contrib/openzfs/module/zfs/ddt.c160
-rw-r--r--sys/contrib/openzfs/module/zfs/ddt_stats.c79
-rw-r--r--sys/contrib/openzfs/module/zfs/ddt_zap.c7
-rw-r--r--sys/contrib/openzfs/module/zfs/dmu.c281
-rw-r--r--sys/contrib/openzfs/module/zfs/dmu_tx.c5
-rw-r--r--sys/contrib/openzfs/module/zfs/dnode.c34
-rw-r--r--sys/contrib/openzfs/module/zfs/lz4_zfs.c3
-rw-r--r--sys/contrib/openzfs/module/zfs/sa.c88
-rw-r--r--sys/contrib/openzfs/module/zfs/spa.c75
-rw-r--r--sys/contrib/openzfs/module/zfs/spa_misc.c29
-rw-r--r--sys/contrib/openzfs/module/zfs/vdev.c18
-rw-r--r--sys/contrib/openzfs/module/zfs/vdev_label.c123
-rw-r--r--sys/contrib/openzfs/module/zfs/zap_micro.c16
-rw-r--r--sys/contrib/openzfs/module/zfs/zfs_ioctl.c98
-rw-r--r--sys/contrib/openzfs/module/zfs/zfs_log.c2
-rw-r--r--sys/contrib/openzfs/module/zfs/zfs_replay.c4
-rw-r--r--sys/contrib/openzfs/module/zfs/zil.c40
-rw-r--r--sys/contrib/openzfs/module/zfs/zio.c104
-rw-r--r--sys/contrib/openzfs/module/zfs/zio_compress.c28
-rw-r--r--sys/contrib/openzfs/module/zfs/zvol.c116
23 files changed, 1287 insertions, 313 deletions
diff --git a/sys/contrib/openzfs/module/zfs/abd.c b/sys/contrib/openzfs/module/zfs/abd.c
index 2c0cda25dbc6..94f492522f0d 100644
--- a/sys/contrib/openzfs/module/zfs/abd.c
+++ b/sys/contrib/openzfs/module/zfs/abd.c
@@ -1051,6 +1051,31 @@ abd_cmp(abd_t *dabd, abd_t *sabd)
}
/*
+ * Check if ABD content is all-zeroes.
+ */
+static int
+abd_cmp_zero_off_cb(void *data, size_t len, void *private)
+{
+ (void) private;
+
+ /* This function can only check whole uint64s. Enforce that. */
+ ASSERT0(P2PHASE(len, 8));
+
+ uint64_t *end = (uint64_t *)((char *)data + len);
+ for (uint64_t *word = (uint64_t *)data; word < end; word++)
+ if (*word != 0)
+ return (1);
+
+ return (0);
+}
+
+int
+abd_cmp_zero_off(abd_t *abd, size_t off, size_t size)
+{
+ return (abd_iterate_func(abd, off, size, abd_cmp_zero_off_cb, NULL));
+}
+
+/*
* Iterate over code ABDs and a data ABD and call @func_raidz_gen.
*
* @cabds parity ABDs, must have equal size
diff --git a/sys/contrib/openzfs/module/zfs/arc.c b/sys/contrib/openzfs/module/zfs/arc.c
index 30d30b98a6c6..78c2cf8ec5c3 100644
--- a/sys/contrib/openzfs/module/zfs/arc.c
+++ b/sys/contrib/openzfs/module/zfs/arc.c
@@ -26,7 +26,7 @@
* Copyright (c) 2017, Nexenta Systems, Inc. All rights reserved.
* Copyright (c) 2019, loli10K <ezomori.nozomu@gmail.com>. All rights reserved.
* Copyright (c) 2020, George Amanakis. All rights reserved.
- * Copyright (c) 2019, Klara Inc.
+ * Copyright (c) 2019, 2023, Klara Inc.
* Copyright (c) 2019, Allan Jude
* Copyright (c) 2020, The FreeBSD Foundation [1]
*
@@ -1258,7 +1258,7 @@ retry:
}
hdr_full_cache = kmem_cache_create("arc_buf_hdr_t_full", HDR_FULL_SIZE,
- 0, hdr_full_cons, hdr_full_dest, NULL, NULL, NULL, 0);
+ 0, hdr_full_cons, hdr_full_dest, NULL, NULL, NULL, KMC_RECLAIMABLE);
hdr_l2only_cache = kmem_cache_create("arc_buf_hdr_t_l2only",
HDR_L2ONLY_SIZE, 0, hdr_l2only_cons, hdr_l2only_dest, NULL,
NULL, NULL, 0);
@@ -4236,6 +4236,18 @@ arc_evict_adj(uint64_t frac, uint64_t total, uint64_t up, uint64_t down,
}
/*
+ * Calculate (x * multiplier / divisor) without unnecesary overflows.
+ */
+static uint64_t
+arc_mf(uint64_t x, uint64_t multiplier, uint64_t divisor)
+{
+ uint64_t q = (x / divisor);
+ uint64_t r = (x % divisor);
+
+ return ((q * multiplier) + ((r * multiplier) / divisor));
+}
+
+/*
* Evict buffers from the cache, such that arcstat_size is capped by arc_c.
*/
static uint64_t
@@ -4287,17 +4299,20 @@ arc_evict(void)
*/
int64_t prune = 0;
int64_t dn = wmsum_value(&arc_sums.arcstat_dnode_size);
+ int64_t nem = zfs_refcount_count(&arc_mru->arcs_size[ARC_BUFC_METADATA])
+ + zfs_refcount_count(&arc_mfu->arcs_size[ARC_BUFC_METADATA])
+ - zfs_refcount_count(&arc_mru->arcs_esize[ARC_BUFC_METADATA])
+ - zfs_refcount_count(&arc_mfu->arcs_esize[ARC_BUFC_METADATA]);
w = wt * (int64_t)(arc_meta >> 16) >> 16;
- if (zfs_refcount_count(&arc_mru->arcs_size[ARC_BUFC_METADATA]) +
- zfs_refcount_count(&arc_mfu->arcs_size[ARC_BUFC_METADATA]) -
- zfs_refcount_count(&arc_mru->arcs_esize[ARC_BUFC_METADATA]) -
- zfs_refcount_count(&arc_mfu->arcs_esize[ARC_BUFC_METADATA]) >
- w * 3 / 4) {
+ if (nem > w * 3 / 4) {
prune = dn / sizeof (dnode_t) *
zfs_arc_dnode_reduce_percent / 100;
- } else if (dn > arc_dnode_limit) {
- prune = (dn - arc_dnode_limit) / sizeof (dnode_t) *
- zfs_arc_dnode_reduce_percent / 100;
+ if (nem < w && w > 4)
+ prune = arc_mf(prune, nem - w * 3 / 4, w / 4);
+ }
+ if (dn > arc_dnode_limit) {
+ prune = MAX(prune, (dn - arc_dnode_limit) / sizeof (dnode_t) *
+ zfs_arc_dnode_reduce_percent / 100);
}
if (prune > 0)
arc_prune_async(prune);
@@ -4398,13 +4413,14 @@ arc_flush(spa_t *spa, boolean_t retry)
(void) arc_flush_state(arc_uncached, guid, ARC_BUFC_METADATA, retry);
}
-void
-arc_reduce_target_size(int64_t to_free)
+uint64_t
+arc_reduce_target_size(uint64_t to_free)
{
- uint64_t c = arc_c;
-
- if (c <= arc_c_min)
- return;
+ /*
+ * Get the actual arc size. Even if we don't need it, this updates
+ * the aggsum lower bound estimate for arc_is_overflowing().
+ */
+ uint64_t asize = aggsum_value(&arc_sums.arcstat_size);
/*
* All callers want the ARC to actually evict (at least) this much
@@ -4414,16 +4430,28 @@ arc_reduce_target_size(int64_t to_free)
* immediately have arc_c < arc_size and therefore the arc_evict_zthr
* will evict.
*/
- uint64_t asize = aggsum_value(&arc_sums.arcstat_size);
- if (asize < c)
- to_free += c - asize;
- arc_c = MAX((int64_t)c - to_free, (int64_t)arc_c_min);
+ uint64_t c = arc_c;
+ if (c > arc_c_min) {
+ c = MIN(c, MAX(asize, arc_c_min));
+ to_free = MIN(to_free, c - arc_c_min);
+ arc_c = c - to_free;
+ } else {
+ to_free = 0;
+ }
- /* See comment in arc_evict_cb_check() on why lock+flag */
- mutex_enter(&arc_evict_lock);
- arc_evict_needed = B_TRUE;
- mutex_exit(&arc_evict_lock);
- zthr_wakeup(arc_evict_zthr);
+ /*
+ * Whether or not we reduced the target size, request eviction if the
+ * current size is over it now, since caller obviously wants some RAM.
+ */
+ if (asize > arc_c) {
+ /* See comment in arc_evict_cb_check() on why lock+flag */
+ mutex_enter(&arc_evict_lock);
+ arc_evict_needed = B_TRUE;
+ mutex_exit(&arc_evict_lock);
+ zthr_wakeup(arc_evict_zthr);
+ }
+
+ return (to_free);
}
/*
@@ -4630,9 +4658,9 @@ arc_reap_cb_check(void *arg, zthr_t *zthr)
static void
arc_reap_cb(void *arg, zthr_t *zthr)
{
- (void) arg, (void) zthr;
+ int64_t can_free, free_memory, to_free;
- int64_t free_memory;
+ (void) arg, (void) zthr;
fstrans_cookie_t cookie = spl_fstrans_mark();
/*
@@ -4660,13 +4688,10 @@ arc_reap_cb(void *arg, zthr_t *zthr)
* amount, reduce by what is needed to hit the fractional amount.
*/
free_memory = arc_available_memory();
-
- int64_t can_free = arc_c - arc_c_min;
- if (can_free > 0) {
- int64_t to_free = (can_free >> arc_shrink_shift) - free_memory;
- if (to_free > 0)
- arc_reduce_target_size(to_free);
- }
+ can_free = arc_c - arc_c_min;
+ to_free = (MAX(can_free, 0) >> arc_shrink_shift) - free_memory;
+ if (to_free > 0)
+ arc_reduce_target_size(to_free);
spl_fstrans_unmark(cookie);
}
@@ -4754,16 +4779,11 @@ arc_adapt(uint64_t bytes)
}
/*
- * Check if arc_size has grown past our upper threshold, determined by
- * zfs_arc_overflow_shift.
+ * Check if ARC current size has grown past our upper thresholds.
*/
static arc_ovf_level_t
-arc_is_overflowing(boolean_t use_reserve)
+arc_is_overflowing(boolean_t lax, boolean_t use_reserve)
{
- /* Always allow at least one block of overflow */
- int64_t overflow = MAX(SPA_MAXBLOCKSIZE,
- arc_c >> zfs_arc_overflow_shift);
-
/*
* We just compare the lower bound here for performance reasons. Our
* primary goals are to make sure that the arc never grows without
@@ -4773,12 +4793,22 @@ arc_is_overflowing(boolean_t use_reserve)
* in the ARC. In practice, that's in the tens of MB, which is low
* enough to be safe.
*/
- int64_t over = aggsum_lower_bound(&arc_sums.arcstat_size) -
- arc_c - overflow / 2;
- if (!use_reserve)
- overflow /= 2;
- return (over < 0 ? ARC_OVF_NONE :
- over < overflow ? ARC_OVF_SOME : ARC_OVF_SEVERE);
+ int64_t over = aggsum_lower_bound(&arc_sums.arcstat_size) - arc_c -
+ zfs_max_recordsize;
+
+ /* Always allow at least one block of overflow. */
+ if (over < 0)
+ return (ARC_OVF_NONE);
+
+ /* If we are under memory pressure, report severe overflow. */
+ if (!lax)
+ return (ARC_OVF_SEVERE);
+
+ /* We are not under pressure, so be more or less relaxed. */
+ int64_t overflow = (arc_c >> zfs_arc_overflow_shift) / 2;
+ if (use_reserve)
+ overflow *= 3;
+ return (over < overflow ? ARC_OVF_SOME : ARC_OVF_SEVERE);
}
static abd_t *
@@ -4810,15 +4840,17 @@ arc_get_data_buf(arc_buf_hdr_t *hdr, uint64_t size, const void *tag)
/*
* Wait for the specified amount of data (in bytes) to be evicted from the
- * ARC, and for there to be sufficient free memory in the system. Waiting for
- * eviction ensures that the memory used by the ARC decreases. Waiting for
- * free memory ensures that the system won't run out of free pages, regardless
- * of ARC behavior and settings. See arc_lowmem_init().
+ * ARC, and for there to be sufficient free memory in the system.
+ * The lax argument specifies that caller does not have a specific reason
+ * to wait, not aware of any memory pressure. Low memory handlers though
+ * should set it to B_FALSE to wait for all required evictions to complete.
+ * The use_reserve argument allows some callers to wait less than others
+ * to not block critical code paths, possibly blocking other resources.
*/
void
-arc_wait_for_eviction(uint64_t amount, boolean_t use_reserve)
+arc_wait_for_eviction(uint64_t amount, boolean_t lax, boolean_t use_reserve)
{
- switch (arc_is_overflowing(use_reserve)) {
+ switch (arc_is_overflowing(lax, use_reserve)) {
case ARC_OVF_NONE:
return;
case ARC_OVF_SOME:
@@ -4913,7 +4945,7 @@ arc_get_data_impl(arc_buf_hdr_t *hdr, uint64_t size, const void *tag,
* under arc_c. See the comment above zfs_arc_eviction_pct.
*/
arc_wait_for_eviction(size * zfs_arc_eviction_pct / 100,
- alloc_flags & ARC_HDR_USE_RESERVE);
+ B_TRUE, alloc_flags & ARC_HDR_USE_RESERVE);
arc_buf_contents_t type = arc_buf_type(hdr);
if (type == ARC_BUFC_METADATA) {
@@ -5455,6 +5487,57 @@ arc_read_done(zio_t *zio)
}
/*
+ * Lookup the block at the specified DVA (in bp), and return the manner in
+ * which the block is cached. A zero return indicates not cached.
+ */
+int
+arc_cached(spa_t *spa, const blkptr_t *bp)
+{
+ arc_buf_hdr_t *hdr = NULL;
+ kmutex_t *hash_lock = NULL;
+ uint64_t guid = spa_load_guid(spa);
+ int flags = 0;
+
+ if (BP_IS_EMBEDDED(bp))
+ return (ARC_CACHED_EMBEDDED);
+
+ hdr = buf_hash_find(guid, bp, &hash_lock);
+ if (hdr == NULL)
+ return (0);
+
+ if (HDR_HAS_L1HDR(hdr)) {
+ arc_state_t *state = hdr->b_l1hdr.b_state;
+ /*
+ * We switch to ensure that any future arc_state_type_t
+ * changes are handled. This is just a shift to promote
+ * more compile-time checking.
+ */
+ switch (state->arcs_state) {
+ case ARC_STATE_ANON:
+ break;
+ case ARC_STATE_MRU:
+ flags |= ARC_CACHED_IN_MRU | ARC_CACHED_IN_L1;
+ break;
+ case ARC_STATE_MFU:
+ flags |= ARC_CACHED_IN_MFU | ARC_CACHED_IN_L1;
+ break;
+ case ARC_STATE_UNCACHED:
+ /* The header is still in L1, probably not for long */
+ flags |= ARC_CACHED_IN_L1;
+ break;
+ default:
+ break;
+ }
+ }
+ if (HDR_HAS_L2HDR(hdr))
+ flags |= ARC_CACHED_IN_L2;
+
+ mutex_exit(hash_lock);
+
+ return (flags);
+}
+
+/*
* "Read" the block at the specified DVA (in bp) via the
* cache. If the block is found in the cache, invoke the provided
* callback immediately and return. Note that the `zio' parameter
@@ -5508,19 +5591,6 @@ arc_read(zio_t *pio, spa_t *spa, const blkptr_t *bp,
*/
fstrans_cookie_t cookie = spl_fstrans_mark();
top:
- /*
- * Verify the block pointer contents are reasonable. This should
- * always be the case since the blkptr is protected by a checksum.
- * However, if there is damage it's desirable to detect this early
- * and treat it as a checksum error. This allows an alternate blkptr
- * to be tried when one is available (e.g. ditto blocks).
- */
- if (!zfs_blkptr_verify(spa, bp, (zio_flags & ZIO_FLAG_CONFIG_WRITER) ?
- BLK_CONFIG_HELD : BLK_CONFIG_NEEDED, BLK_VERIFY_LOG)) {
- rc = SET_ERROR(ECKSUM);
- goto done;
- }
-
if (!embedded_bp) {
/*
* Embedded BP's have no DVA and require no I/O to "read".
@@ -5540,6 +5610,18 @@ top:
(hdr->b_l1hdr.b_pabd != NULL && !encrypted_read))) {
boolean_t is_data = !HDR_ISTYPE_METADATA(hdr);
+ /*
+ * Verify the block pointer contents are reasonable. This
+ * should always be the case since the blkptr is protected by
+ * a checksum.
+ */
+ if (!zfs_blkptr_verify(spa, bp, BLK_CONFIG_SKIP,
+ BLK_VERIFY_LOG)) {
+ mutex_exit(hash_lock);
+ rc = SET_ERROR(ECKSUM);
+ goto done;
+ }
+
if (HDR_IO_IN_PROGRESS(hdr)) {
if (*arc_flags & ARC_FLAG_CACHED_ONLY) {
mutex_exit(hash_lock);
@@ -5693,6 +5775,20 @@ top:
goto done;
}
+ /*
+ * Verify the block pointer contents are reasonable. This
+ * should always be the case since the blkptr is protected by
+ * a checksum.
+ */
+ if (!zfs_blkptr_verify(spa, bp,
+ (zio_flags & ZIO_FLAG_CONFIG_WRITER) ?
+ BLK_CONFIG_HELD : BLK_CONFIG_NEEDED, BLK_VERIFY_LOG)) {
+ if (hash_lock != NULL)
+ mutex_exit(hash_lock);
+ rc = SET_ERROR(ECKSUM);
+ goto done;
+ }
+
if (hdr == NULL) {
/*
* This block is not in the cache or it has
diff --git a/sys/contrib/openzfs/module/zfs/dbuf.c b/sys/contrib/openzfs/module/zfs/dbuf.c
index 56fe2c4dbe30..099883ba2652 100644
--- a/sys/contrib/openzfs/module/zfs/dbuf.c
+++ b/sys/contrib/openzfs/module/zfs/dbuf.c
@@ -2705,6 +2705,9 @@ void
dmu_buf_will_clone(dmu_buf_t *db_fake, dmu_tx_t *tx)
{
dmu_buf_impl_t *db = (dmu_buf_impl_t *)db_fake;
+ ASSERT0(db->db_level);
+ ASSERT(db->db_blkid != DMU_BONUS_BLKID);
+ ASSERT(db->db.db_object != DMU_META_DNODE_OBJECT);
/*
* Block cloning: We are going to clone into this block, so undirty
@@ -2716,11 +2719,22 @@ dmu_buf_will_clone(dmu_buf_t *db_fake, dmu_tx_t *tx)
VERIFY(!dbuf_undirty(db, tx));
ASSERT0P(dbuf_find_dirty_eq(db, tx->tx_txg));
if (db->db_buf != NULL) {
- arc_buf_destroy(db->db_buf, db);
+ /*
+ * If there is an associated ARC buffer with this dbuf we can
+ * only destroy it if the previous dirty record does not
+ * reference it.
+ */
+ dbuf_dirty_record_t *dr = list_head(&db->db_dirty_records);
+ if (dr == NULL || dr->dt.dl.dr_data != db->db_buf)
+ arc_buf_destroy(db->db_buf, db);
+
db->db_buf = NULL;
dbuf_clear_data(db);
}
+ ASSERT3P(db->db_buf, ==, NULL);
+ ASSERT3P(db->db.db_data, ==, NULL);
+
db->db_state = DB_NOFILL;
DTRACE_SET_STATE(db, "allocating NOFILL buffer for clone");
@@ -3103,7 +3117,11 @@ dbuf_destroy(dmu_buf_impl_t *db)
*/
mutex_enter(&dn->dn_mtx);
dnode_rele_and_unlock(dn, db, B_TRUE);
+#ifdef USE_DNODE_HANDLE
db->db_dnode_handle = NULL;
+#else
+ db->db_dnode = NULL;
+#endif
dbuf_hash_remove(db);
} else {
@@ -3252,7 +3270,11 @@ dbuf_create(dnode_t *dn, uint8_t level, uint64_t blkid,
db->db_level = level;
db->db_blkid = blkid;
db->db_dirtycnt = 0;
+#ifdef USE_DNODE_HANDLE
db->db_dnode_handle = dn->dn_handle;
+#else
+ db->db_dnode = dn;
+#endif
db->db_parent = parent;
db->db_blkptr = blkptr;
db->db_hash = hash;
@@ -4390,7 +4412,7 @@ dbuf_lightweight_bp(dbuf_dirty_record_t *dr)
dmu_buf_impl_t *parent_db = dr->dr_parent->dr_dbuf;
int epbs = dn->dn_indblkshift - SPA_BLKPTRSHIFT;
VERIFY3U(parent_db->db_level, ==, 1);
- VERIFY3P(parent_db->db_dnode_handle->dnh_dnode, ==, dn);
+ VERIFY3P(DB_DNODE(parent_db), ==, dn);
VERIFY3U(dr->dt.dll.dr_blkid >> epbs, ==, parent_db->db_blkid);
blkptr_t *bp = parent_db->db.db_data;
return (&bp[dr->dt.dll.dr_blkid & ((1 << epbs) - 1)]);
@@ -4813,14 +4835,13 @@ dbuf_write_children_ready(zio_t *zio, arc_buf_t *buf, void *vdb)
{
(void) zio, (void) buf;
dmu_buf_impl_t *db = vdb;
- dnode_t *dn;
blkptr_t *bp;
unsigned int epbs, i;
ASSERT3U(db->db_level, >, 0);
DB_DNODE_ENTER(db);
- dn = DB_DNODE(db);
- epbs = dn->dn_phys->dn_indblkshift - SPA_BLKPTRSHIFT;
+ epbs = DB_DNODE(db)->dn_phys->dn_indblkshift - SPA_BLKPTRSHIFT;
+ DB_DNODE_EXIT(db);
ASSERT3U(epbs, <, 31);
/* Determine if all our children are holes */
@@ -4843,7 +4864,6 @@ dbuf_write_children_ready(zio_t *zio, arc_buf_t *buf, void *vdb)
memset(db->db.db_data, 0, db->db.db_size);
rw_exit(&db->db_rwlock);
}
- DB_DNODE_EXIT(db);
}
static void
@@ -5062,8 +5082,7 @@ dbuf_remap(dnode_t *dn, dmu_buf_impl_t *db, dmu_tx_t *tx)
}
} else if (db->db.db_object == DMU_META_DNODE_OBJECT) {
dnode_phys_t *dnp = db->db.db_data;
- ASSERT3U(db->db_dnode_handle->dnh_dnode->dn_type, ==,
- DMU_OT_DNODE);
+ ASSERT3U(dn->dn_type, ==, DMU_OT_DNODE);
for (int i = 0; i < db->db.db_size >> DNODE_SHIFT;
i += dnp[i].dn_extra_slots + 1) {
for (int j = 0; j < dnp[i].dn_nblkptr; j++) {
diff --git a/sys/contrib/openzfs/module/zfs/ddt.c b/sys/contrib/openzfs/module/zfs/ddt.c
index 4c53cb0a2f9b..d70ae1a031d5 100644
--- a/sys/contrib/openzfs/module/zfs/ddt.c
+++ b/sys/contrib/openzfs/module/zfs/ddt.c
@@ -23,7 +23,7 @@
* Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2016 by Delphix. All rights reserved.
* Copyright (c) 2022 by Pawel Jakub Dawidek
- * Copyright (c) 2023, Klara Inc.
+ * Copyright (c) 2019, 2023, Klara Inc.
*/
#include <sys/zfs_context.h>
@@ -101,6 +101,22 @@
* object and (if necessary), removed from an old one. ddt_tree is cleared and
* the next txg can start.
*
+ * ## Dedup quota
+ *
+ * A maximum size for all DDTs on the pool can be set with the
+ * dedup_table_quota property. This is determined in ddt_over_quota() and
+ * enforced during ddt_lookup(). If the pool is at or over its quota limit,
+ * ddt_lookup() will only return entries for existing blocks, as updates are
+ * still possible. New entries will not be created; instead, ddt_lookup() will
+ * return NULL. In response, the DDT write stage (zio_ddt_write()) will remove
+ * the D bit on the block and reissue the IO as a regular write. The block will
+ * not be deduplicated.
+ *
+ * Note that this is based on the on-disk size of the dedup store. Reclaiming
+ * this space after deleting entries relies on the ZAP "shrinking" behaviour,
+ * without which, no space would be recovered and the DDT would continue to be
+ * considered "over quota". See zap_shrink_enabled.
+ *
* ## Repair IO
*
* If a read on a dedup block fails, but there are other copies of the block in
@@ -152,6 +168,13 @@ static kmem_cache_t *ddt_entry_cache;
*/
int zfs_dedup_prefetch = 0;
+/*
+ * If the dedup class cannot satisfy a DDT allocation, treat as over quota
+ * for this many TXGs.
+ */
+uint_t dedup_class_wait_txgs = 5;
+
+
static const ddt_ops_t *const ddt_ops[DDT_TYPES] = {
&ddt_zap_ops,
};
@@ -317,6 +340,16 @@ ddt_object_prefetch(ddt_t *ddt, ddt_type_t type, ddt_class_t class,
ddt->ddt_object[type][class], ddk);
}
+static void
+ddt_object_prefetch_all(ddt_t *ddt, ddt_type_t type, ddt_class_t class)
+{
+ if (!ddt_object_exists(ddt, type, class))
+ return;
+
+ ddt_ops[type]->ddt_op_prefetch_all(ddt->ddt_os,
+ ddt->ddt_object[type][class]);
+}
+
static int
ddt_object_update(ddt_t *ddt, ddt_type_t type, ddt_class_t class,
ddt_entry_t *dde, dmu_tx_t *tx)
@@ -554,8 +587,6 @@ ddt_alloc(const ddt_key_t *ddk)
static void
ddt_free(ddt_entry_t *dde)
{
- ASSERT(dde->dde_flags & DDE_FLAG_LOADED);
-
for (int p = 0; p < DDT_PHYS_TYPES; p++)
ASSERT3P(dde->dde_lead_zio[p], ==, NULL);
@@ -575,9 +606,88 @@ ddt_remove(ddt_t *ddt, ddt_entry_t *dde)
ddt_free(dde);
}
+static boolean_t
+ddt_special_over_quota(spa_t *spa, metaslab_class_t *mc)
+{
+ if (mc != NULL && metaslab_class_get_space(mc) > 0) {
+ /* Over quota if allocating outside of this special class */
+ if (spa_syncing_txg(spa) <= spa->spa_dedup_class_full_txg +
+ dedup_class_wait_txgs) {
+ /* Waiting for some deferred frees to be processed */
+ return (B_TRUE);
+ }
+
+ /*
+ * We're considered over quota when we hit 85% full, or for
+ * larger drives, when there is less than 8GB free.
+ */
+ uint64_t allocated = metaslab_class_get_alloc(mc);
+ uint64_t capacity = metaslab_class_get_space(mc);
+ uint64_t limit = MAX(capacity * 85 / 100,
+ (capacity > (1LL<<33)) ? capacity - (1LL<<33) : 0);
+
+ return (allocated >= limit);
+ }
+ return (B_FALSE);
+}
+
+/*
+ * Check if the DDT is over its quota. This can be due to a few conditions:
+ * 1. 'dedup_table_quota' property is not 0 (none) and the dedup dsize
+ * exceeds this limit
+ *
+ * 2. 'dedup_table_quota' property is set to automatic and
+ * a. the dedup or special allocation class could not satisfy a DDT
+ * allocation in a recent transaction
+ * b. the dedup or special allocation class has exceeded its 85% limit
+ */
+static boolean_t
+ddt_over_quota(spa_t *spa)
+{
+ if (spa->spa_dedup_table_quota == 0)
+ return (B_FALSE);
+
+ if (spa->spa_dedup_table_quota != UINT64_MAX)
+ return (ddt_get_ddt_dsize(spa) > spa->spa_dedup_table_quota);
+
+ /*
+ * For automatic quota, table size is limited by dedup or special class
+ */
+ if (ddt_special_over_quota(spa, spa_dedup_class(spa)))
+ return (B_TRUE);
+ else if (spa_special_has_ddt(spa) &&
+ ddt_special_over_quota(spa, spa_special_class(spa)))
+ return (B_TRUE);
+
+ return (B_FALSE);
+}
+
+void
+ddt_prefetch_all(spa_t *spa)
+{
+ /*
+ * Load all DDT entries for each type/class combination. This is
+ * indended to perform a prefetch on all such blocks. For the same
+ * reason that ddt_prefetch isn't locked, this is also not locked.
+ */
+ for (enum zio_checksum c = 0; c < ZIO_CHECKSUM_FUNCTIONS; c++) {
+ ddt_t *ddt = spa->spa_ddt[c];
+ if (!ddt)
+ continue;
+
+ for (ddt_type_t type = 0; type < DDT_TYPES; type++) {
+ for (ddt_class_t class = 0; class < DDT_CLASSES;
+ class++) {
+ ddt_object_prefetch_all(ddt, type, class);
+ }
+ }
+ }
+}
+
ddt_entry_t *
ddt_lookup(ddt_t *ddt, const blkptr_t *bp, boolean_t add)
{
+ spa_t *spa = ddt->ddt_spa;
ddt_key_t search;
ddt_entry_t *dde;
ddt_type_t type;
@@ -592,13 +702,28 @@ ddt_lookup(ddt_t *ddt, const blkptr_t *bp, boolean_t add)
/* Find an existing live entry */
dde = avl_find(&ddt->ddt_tree, &search, &where);
if (dde != NULL) {
- /* Found it. If it's already loaded, we can just return it. */
+ /* If we went over quota, act like we didn't find it */
+ if (dde->dde_flags & DDE_FLAG_OVERQUOTA)
+ return (NULL);
+
+ /* If it's already loaded, we can just return it. */
if (dde->dde_flags & DDE_FLAG_LOADED)
return (dde);
/* Someone else is loading it, wait for it. */
+ dde->dde_waiters++;
while (!(dde->dde_flags & DDE_FLAG_LOADED))
cv_wait(&dde->dde_cv, &ddt->ddt_lock);
+ dde->dde_waiters--;
+
+ /* Loaded but over quota, forget we were ever here */
+ if (dde->dde_flags & DDE_FLAG_OVERQUOTA) {
+ if (dde->dde_waiters == 0) {
+ avl_remove(&ddt->ddt_tree, dde);
+ ddt_free(dde);
+ }
+ return (NULL);
+ }
return (dde);
}
@@ -639,14 +764,27 @@ ddt_lookup(ddt_t *ddt, const blkptr_t *bp, boolean_t add)
dde->dde_type = type; /* will be DDT_TYPES if no entry found */
dde->dde_class = class; /* will be DDT_CLASSES if no entry found */
- if (error == 0)
+ if (dde->dde_type == DDT_TYPES &&
+ dde->dde_class == DDT_CLASSES &&
+ ddt_over_quota(spa)) {
+ /* Over quota. If no one is waiting, clean up right now. */
+ if (dde->dde_waiters == 0) {
+ avl_remove(&ddt->ddt_tree, dde);
+ ddt_free(dde);
+ return (NULL);
+ }
+
+ /* Flag cleanup required */
+ dde->dde_flags |= DDE_FLAG_OVERQUOTA;
+ } else if (error == 0) {
ddt_stat_update(ddt, dde, -1ULL);
+ }
/* Entry loaded, everyone can proceed now */
dde->dde_flags |= DDE_FLAG_LOADED;
cv_broadcast(&dde->dde_cv);
- return (dde);
+ return (dde->dde_flags & DDE_FLAG_OVERQUOTA ? NULL : dde);
}
void
@@ -775,6 +913,7 @@ ddt_load(spa_t *spa)
memcpy(&ddt->ddt_histogram_cache, ddt->ddt_histogram,
sizeof (ddt->ddt_histogram));
spa->spa_dedup_dspace = ~0ULL;
+ spa->spa_dedup_dsize = ~0ULL;
}
return (0);
@@ -1032,6 +1171,7 @@ ddt_sync_table(ddt_t *ddt, dmu_tx_t *tx, uint64_t txg)
memcpy(&ddt->ddt_histogram_cache, ddt->ddt_histogram,
sizeof (ddt->ddt_histogram));
spa->spa_dedup_dspace = ~0ULL;
+ spa->spa_dedup_dsize = ~0ULL;
}
void
@@ -1123,7 +1263,13 @@ ddt_addref(spa_t *spa, const blkptr_t *bp)
ddt_enter(ddt);
dde = ddt_lookup(ddt, bp, B_TRUE);
- ASSERT3P(dde, !=, NULL);
+
+ /* Can be NULL if the entry for this block was pruned. */
+ if (dde == NULL) {
+ ddt_exit(ddt);
+ spa_config_exit(spa, SCL_ZIO, FTAG);
+ return (B_FALSE);
+ }
if (dde->dde_type < DDT_TYPES) {
ddt_phys_t *ddp;
diff --git a/sys/contrib/openzfs/module/zfs/ddt_stats.c b/sys/contrib/openzfs/module/zfs/ddt_stats.c
index af5365a1d114..82b682019ae9 100644
--- a/sys/contrib/openzfs/module/zfs/ddt_stats.c
+++ b/sys/contrib/openzfs/module/zfs/ddt_stats.c
@@ -129,7 +129,8 @@ ddt_histogram_empty(const ddt_histogram_t *ddh)
void
ddt_get_dedup_object_stats(spa_t *spa, ddt_object_t *ddo_total)
{
- /* Sum the statistics we cached in ddt_object_sync(). */
+ memset(ddo_total, 0, sizeof (*ddo_total));
+
for (enum zio_checksum c = 0; c < ZIO_CHECKSUM_FUNCTIONS; c++) {
ddt_t *ddt = spa->spa_ddt[c];
if (!ddt)
@@ -138,8 +139,32 @@ ddt_get_dedup_object_stats(spa_t *spa, ddt_object_t *ddo_total)
for (ddt_type_t type = 0; type < DDT_TYPES; type++) {
for (ddt_class_t class = 0; class < DDT_CLASSES;
class++) {
+ dmu_object_info_t doi;
+ uint64_t cnt;
+ int err;
+
+ /*
+ * These stats were originally calculated
+ * during ddt_object_load().
+ */
+
+ err = ddt_object_info(ddt, type, class, &doi);
+ if (err != 0)
+ continue;
+
+ err = ddt_object_count(ddt, type, class, &cnt);
+ if (err != 0)
+ continue;
+
ddt_object_t *ddo =
&ddt->ddt_object_stats[type][class];
+
+ ddo->ddo_count = cnt;
+ ddo->ddo_dspace =
+ doi.doi_physical_blocks_512 << 9;
+ ddo->ddo_mspace = doi.doi_fill_count *
+ doi.doi_data_block_size;
+
ddo_total->ddo_count += ddo->ddo_count;
ddo_total->ddo_dspace += ddo->ddo_dspace;
ddo_total->ddo_mspace += ddo->ddo_mspace;
@@ -147,11 +172,24 @@ ddt_get_dedup_object_stats(spa_t *spa, ddt_object_t *ddo_total)
}
}
- /* ... and compute the averages. */
- if (ddo_total->ddo_count != 0) {
- ddo_total->ddo_dspace /= ddo_total->ddo_count;
- ddo_total->ddo_mspace /= ddo_total->ddo_count;
- }
+ /*
+ * This returns raw counts (not averages). One of the consumers,
+ * print_dedup_stats(), historically has expected raw counts.
+ */
+
+ spa->spa_dedup_dsize = ddo_total->ddo_dspace;
+}
+
+uint64_t
+ddt_get_ddt_dsize(spa_t *spa)
+{
+ ddt_object_t ddo_total;
+
+ /* recalculate after each txg sync */
+ if (spa->spa_dedup_dsize == ~0ULL)
+ ddt_get_dedup_object_stats(spa, &ddo_total);
+
+ return (spa->spa_dedup_dsize);
}
void
@@ -210,3 +248,32 @@ ddt_get_pool_dedup_ratio(spa_t *spa)
return (dds_total.dds_ref_dsize * 100 / dds_total.dds_dsize);
}
+
+int
+ddt_get_pool_dedup_cached(spa_t *spa, uint64_t *psize)
+{
+ uint64_t l1sz, l1tot, l2sz, l2tot;
+ int err = 0;
+
+ l1tot = l2tot = 0;
+ *psize = 0;
+ for (enum zio_checksum c = 0; c < ZIO_CHECKSUM_FUNCTIONS; c++) {
+ ddt_t *ddt = spa->spa_ddt[c];
+ if (ddt == NULL)
+ continue;
+ for (ddt_type_t type = 0; type < DDT_TYPES; type++) {
+ for (ddt_class_t class = 0; class < DDT_CLASSES;
+ class++) {
+ err = dmu_object_cached_size(ddt->ddt_os,
+ ddt->ddt_object[type][class], &l1sz, &l2sz);
+ if (err != 0)
+ return (err);
+ l1tot += l1sz;
+ l2tot += l2sz;
+ }
+ }
+ }
+
+ *psize = l1tot + l2tot;
+ return (err);
+}
diff --git a/sys/contrib/openzfs/module/zfs/ddt_zap.c b/sys/contrib/openzfs/module/zfs/ddt_zap.c
index 741554de3c60..7ce7461a2b25 100644
--- a/sys/contrib/openzfs/module/zfs/ddt_zap.c
+++ b/sys/contrib/openzfs/module/zfs/ddt_zap.c
@@ -147,6 +147,12 @@ ddt_zap_prefetch(objset_t *os, uint64_t object, const ddt_key_t *ddk)
(void) zap_prefetch_uint64(os, object, (uint64_t *)ddk, DDT_KEY_WORDS);
}
+static void
+ddt_zap_prefetch_all(objset_t *os, uint64_t object)
+{
+ (void) zap_prefetch_object(os, object);
+}
+
static int
ddt_zap_update(objset_t *os, uint64_t object, const ddt_key_t *ddk,
const ddt_phys_t *phys, size_t psize, dmu_tx_t *tx)
@@ -231,6 +237,7 @@ const ddt_ops_t ddt_zap_ops = {
ddt_zap_lookup,
ddt_zap_contains,
ddt_zap_prefetch,
+ ddt_zap_prefetch_all,
ddt_zap_update,
ddt_zap_remove,
ddt_zap_walk,
diff --git a/sys/contrib/openzfs/module/zfs/dmu.c b/sys/contrib/openzfs/module/zfs/dmu.c
index 8b440aafba43..3dcf49ceb64e 100644
--- a/sys/contrib/openzfs/module/zfs/dmu.c
+++ b/sys/contrib/openzfs/module/zfs/dmu.c
@@ -26,7 +26,7 @@
* Copyright (c) 2016, Nexenta Systems, Inc. All rights reserved.
* Copyright (c) 2015 by Chunwei Chen. All rights reserved.
* Copyright (c) 2019 Datto Inc.
- * Copyright (c) 2019, Klara Inc.
+ * Copyright (c) 2019, 2023, Klara Inc.
* Copyright (c) 2019, Allan Jude
* Copyright (c) 2022 Hewlett Packard Enterprise Development LP.
* Copyright (c) 2021, 2022 by Pawel Jakub Dawidek
@@ -276,13 +276,14 @@ dmu_set_bonus(dmu_buf_t *db_fake, int newsize, dmu_tx_t *tx)
dnode_t *dn;
int error;
+ if (newsize < 0 || newsize > db_fake->db_size)
+ return (SET_ERROR(EINVAL));
+
DB_DNODE_ENTER(db);
dn = DB_DNODE(db);
if (dn->dn_bonus != db) {
error = SET_ERROR(EINVAL);
- } else if (newsize < 0 || newsize > db_fake->db_size) {
- error = SET_ERROR(EINVAL);
} else {
dnode_setbonuslen(dn, newsize, tx);
error = 0;
@@ -299,12 +300,13 @@ dmu_set_bonustype(dmu_buf_t *db_fake, dmu_object_type_t type, dmu_tx_t *tx)
dnode_t *dn;
int error;
+ if (!DMU_OT_IS_VALID(type))
+ return (SET_ERROR(EINVAL));
+
DB_DNODE_ENTER(db);
dn = DB_DNODE(db);
- if (!DMU_OT_IS_VALID(type)) {
- error = SET_ERROR(EINVAL);
- } else if (dn->dn_bonus != db) {
+ if (dn->dn_bonus != db) {
error = SET_ERROR(EINVAL);
} else {
dnode_setbonus_type(dn, type, tx);
@@ -319,12 +321,10 @@ dmu_object_type_t
dmu_get_bonustype(dmu_buf_t *db_fake)
{
dmu_buf_impl_t *db = (dmu_buf_impl_t *)db_fake;
- dnode_t *dn;
dmu_object_type_t type;
DB_DNODE_ENTER(db);
- dn = DB_DNODE(db);
- type = dn->dn_bonustype;
+ type = DB_DNODE(db)->dn_bonustype;
DB_DNODE_EXIT(db);
return (type);
@@ -486,7 +486,6 @@ dmu_spill_hold_by_bonus(dmu_buf_t *bonus, uint32_t flags, const void *tag,
dmu_buf_t **dbp)
{
dmu_buf_impl_t *db = (dmu_buf_impl_t *)bonus;
- dnode_t *dn;
int err;
uint32_t db_flags = DB_RF_CANFAIL;
@@ -494,8 +493,7 @@ dmu_spill_hold_by_bonus(dmu_buf_t *bonus, uint32_t flags, const void *tag,
db_flags |= DB_RF_NO_DECRYPT;
DB_DNODE_ENTER(db);
- dn = DB_DNODE(db);
- err = dmu_spill_hold_by_dnode(dn, db_flags, tag, dbp);
+ err = dmu_spill_hold_by_dnode(DB_DNODE(db), db_flags, tag, dbp);
DB_DNODE_EXIT(db);
return (err);
@@ -668,13 +666,11 @@ dmu_buf_hold_array_by_bonus(dmu_buf_t *db_fake, uint64_t offset,
dmu_buf_t ***dbpp)
{
dmu_buf_impl_t *db = (dmu_buf_impl_t *)db_fake;
- dnode_t *dn;
int err;
DB_DNODE_ENTER(db);
- dn = DB_DNODE(db);
- err = dmu_buf_hold_array_by_dnode(dn, offset, length, read, tag,
- numbufsp, dbpp, DMU_READ_PREFETCH);
+ err = dmu_buf_hold_array_by_dnode(DB_DNODE(db), offset, length, read,
+ tag, numbufsp, dbpp, DMU_READ_PREFETCH);
DB_DNODE_EXIT(db);
return (err);
@@ -701,7 +697,7 @@ dmu_buf_rele_array(dmu_buf_t **dbp_fake, int numbufs, const void *tag)
* Issue prefetch I/Os for the given blocks. If level is greater than 0, the
* indirect blocks prefetched will be those that point to the blocks containing
* the data starting at offset, and continuing to offset + len. If the range
- * it too long, prefetch the first dmu_prefetch_max bytes as requested, while
+ * is too long, prefetch the first dmu_prefetch_max bytes as requested, while
* for the rest only a higher level, also fitting within dmu_prefetch_max. It
* should primarily help random reads, since for long sequential reads there is
* a speculative prefetcher.
@@ -777,6 +773,106 @@ dmu_prefetch_by_dnode(dnode_t *dn, int64_t level, uint64_t offset,
rw_exit(&dn->dn_struct_rwlock);
}
+typedef struct {
+ kmutex_t dpa_lock;
+ kcondvar_t dpa_cv;
+ uint64_t dpa_pending_io;
+} dmu_prefetch_arg_t;
+
+static void
+dmu_prefetch_done(void *arg, uint64_t level, uint64_t blkid, boolean_t issued)
+{
+ (void) level; (void) blkid; (void)issued;
+ dmu_prefetch_arg_t *dpa = arg;
+
+ ASSERT0(level);
+
+ mutex_enter(&dpa->dpa_lock);
+ ASSERT3U(dpa->dpa_pending_io, >, 0);
+ if (--dpa->dpa_pending_io == 0)
+ cv_broadcast(&dpa->dpa_cv);
+ mutex_exit(&dpa->dpa_lock);
+}
+
+static void
+dmu_prefetch_wait_by_dnode(dnode_t *dn, uint64_t offset, uint64_t len)
+{
+ dmu_prefetch_arg_t dpa;
+
+ mutex_init(&dpa.dpa_lock, NULL, MUTEX_DEFAULT, NULL);
+ cv_init(&dpa.dpa_cv, NULL, CV_DEFAULT, NULL);
+
+ rw_enter(&dn->dn_struct_rwlock, RW_READER);
+
+ uint64_t start = dbuf_whichblock(dn, 0, offset);
+ uint64_t end = dbuf_whichblock(dn, 0, offset + len - 1) + 1;
+ dpa.dpa_pending_io = end - start;
+
+ for (uint64_t blk = start; blk < end; blk++) {
+ (void) dbuf_prefetch_impl(dn, 0, blk, ZIO_PRIORITY_ASYNC_READ,
+ 0, dmu_prefetch_done, &dpa);
+ }
+
+ rw_exit(&dn->dn_struct_rwlock);
+
+ /* wait for prefetch L0 reads to finish */
+ mutex_enter(&dpa.dpa_lock);
+ while (dpa.dpa_pending_io > 0) {
+ cv_wait(&dpa.dpa_cv, &dpa.dpa_lock);
+
+ }
+ mutex_exit(&dpa.dpa_lock);
+
+ mutex_destroy(&dpa.dpa_lock);
+ cv_destroy(&dpa.dpa_cv);
+}
+
+/*
+ * Issue prefetch I/Os for the given L0 block range and wait for the I/O
+ * to complete. This does not enforce dmu_prefetch_max and will prefetch
+ * the entire range. The blocks are read from disk into the ARC but no
+ * decompression occurs (i.e., the dbuf cache is not required).
+ */
+int
+dmu_prefetch_wait(objset_t *os, uint64_t object, uint64_t offset, uint64_t size)
+{
+ dnode_t *dn;
+ int err = 0;
+
+ err = dnode_hold(os, object, FTAG, &dn);
+ if (err != 0)
+ return (err);
+
+ /*
+ * Chunk the requests (16 indirects worth) so that we can be interrupted
+ */
+ uint64_t chunksize;
+ if (dn->dn_indblkshift) {
+ uint64_t nbps = bp_span_in_blocks(dn->dn_indblkshift, 1);
+ chunksize = (nbps * 16) << dn->dn_datablkshift;
+ } else {
+ chunksize = dn->dn_datablksz;
+ }
+
+ while (size > 0) {
+ uint64_t mylen = MIN(size, chunksize);
+
+ dmu_prefetch_wait_by_dnode(dn, offset, mylen);
+
+ offset += mylen;
+ size -= mylen;
+
+ if (issig()) {
+ err = SET_ERROR(EINTR);
+ break;
+ }
+ }
+
+ dnode_rele(dn, FTAG);
+
+ return (err);
+}
+
/*
* Issue prefetch I/Os for the given object's dnode.
*/
@@ -815,6 +911,13 @@ get_next_chunk(dnode_t *dn, uint64_t *start, uint64_t minimum, uint64_t *l1blks)
ASSERT3U(minimum, <=, *start);
+ /* dn_nlevels == 1 means we don't have any L1 blocks */
+ if (dn->dn_nlevels <= 1) {
+ *l1blks = 0;
+ *start = minimum;
+ return (0);
+ }
+
/*
* Check if we can free the entire range assuming that all of the
* L1 blocks in this range have data. If we can, we use this
@@ -1301,15 +1404,13 @@ int
dmu_read_uio_dbuf(dmu_buf_t *zdb, zfs_uio_t *uio, uint64_t size)
{
dmu_buf_impl_t *db = (dmu_buf_impl_t *)zdb;
- dnode_t *dn;
int err;
if (size == 0)
return (0);
DB_DNODE_ENTER(db);
- dn = DB_DNODE(db);
- err = dmu_read_uio_dnode(dn, uio, size);
+ err = dmu_read_uio_dnode(DB_DNODE(db), uio, size);
DB_DNODE_EXIT(db);
return (err);
@@ -1403,15 +1504,13 @@ dmu_write_uio_dbuf(dmu_buf_t *zdb, zfs_uio_t *uio, uint64_t size,
dmu_tx_t *tx)
{
dmu_buf_impl_t *db = (dmu_buf_impl_t *)zdb;
- dnode_t *dn;
int err;
if (size == 0)
return (0);
DB_DNODE_ENTER(db);
- dn = DB_DNODE(db);
- err = dmu_write_uio_dnode(dn, uio, size, tx);
+ err = dmu_write_uio_dnode(DB_DNODE(db), uio, size, tx);
DB_DNODE_EXIT(db);
return (err);
@@ -1444,6 +1543,114 @@ dmu_write_uio(objset_t *os, uint64_t object, zfs_uio_t *uio, uint64_t size,
}
#endif /* _KERNEL */
+static void
+dmu_cached_bps(spa_t *spa, blkptr_t *bps, uint_t nbps,
+ uint64_t *l1sz, uint64_t *l2sz)
+{
+ int cached_flags;
+
+ if (bps == NULL)
+ return;
+
+ for (size_t blk_off = 0; blk_off < nbps; blk_off++) {
+ blkptr_t *bp = &bps[blk_off];
+
+ if (BP_IS_HOLE(bp))
+ continue;
+
+ cached_flags = arc_cached(spa, bp);
+ if (cached_flags == 0)
+ continue;
+
+ if ((cached_flags & (ARC_CACHED_IN_L1 | ARC_CACHED_IN_L2)) ==
+ ARC_CACHED_IN_L2)
+ *l2sz += BP_GET_LSIZE(bp);
+ else
+ *l1sz += BP_GET_LSIZE(bp);
+ }
+}
+
+/*
+ * Estimate DMU object cached size.
+ */
+int
+dmu_object_cached_size(objset_t *os, uint64_t object,
+ uint64_t *l1sz, uint64_t *l2sz)
+{
+ dnode_t *dn;
+ dmu_object_info_t doi;
+ int err = 0;
+
+ *l1sz = *l2sz = 0;
+
+ if (dnode_hold(os, object, FTAG, &dn) != 0)
+ return (0);
+
+ if (dn->dn_nlevels < 2) {
+ dnode_rele(dn, FTAG);
+ return (0);
+ }
+
+ dmu_object_info_from_dnode(dn, &doi);
+
+ for (uint64_t off = 0; off < doi.doi_max_offset;
+ off += dmu_prefetch_max) {
+ /* dbuf_read doesn't prefetch L1 blocks. */
+ dmu_prefetch_by_dnode(dn, 1, off,
+ dmu_prefetch_max, ZIO_PRIORITY_SYNC_READ);
+ }
+
+ /*
+ * Hold all valid L1 blocks, asking ARC the status of each BP
+ * contained in each such L1 block.
+ */
+ uint_t nbps = bp_span_in_blocks(dn->dn_indblkshift, 1);
+ uint64_t l1blks = 1 + (dn->dn_maxblkid / nbps);
+
+ rw_enter(&dn->dn_struct_rwlock, RW_READER);
+ for (uint64_t blk = 0; blk < l1blks; blk++) {
+ dmu_buf_impl_t *db = NULL;
+
+ if (issig()) {
+ /*
+ * On interrupt, get out, and bubble up EINTR
+ */
+ err = EINTR;
+ break;
+ }
+
+ /*
+ * If we get an i/o error here, the L1 can't be read,
+ * and nothing under it could be cached, so we just
+ * continue. Ignoring the error from dbuf_hold_impl
+ * or from dbuf_read is then a reasonable choice.
+ */
+ err = dbuf_hold_impl(dn, 1, blk, B_TRUE, B_FALSE, FTAG, &db);
+ if (err != 0) {
+ /*
+ * ignore error and continue
+ */
+ err = 0;
+ continue;
+ }
+
+ err = dbuf_read(db, NULL, DB_RF_CANFAIL);
+ if (err == 0) {
+ dmu_cached_bps(dmu_objset_spa(os), db->db.db_data,
+ nbps, l1sz, l2sz);
+ }
+ /*
+ * error may be ignored, and we continue
+ */
+ err = 0;
+ dbuf_rele(db, FTAG);
+ }
+ rw_exit(&dn->dn_struct_rwlock);
+
+ dnode_rele(dn, FTAG);
+ return (err);
+}
+
/*
* Allocate a loaned anonymous arc buffer.
*/
@@ -1539,11 +1746,11 @@ dmu_assign_arcbuf_by_dbuf(dmu_buf_t *handle, uint64_t offset, arc_buf_t *buf,
dmu_tx_t *tx)
{
int err;
- dmu_buf_impl_t *dbuf = (dmu_buf_impl_t *)handle;
+ dmu_buf_impl_t *db = (dmu_buf_impl_t *)handle;
- DB_DNODE_ENTER(dbuf);
- err = dmu_assign_arcbuf_by_dnode(DB_DNODE(dbuf), offset, buf, tx);
- DB_DNODE_EXIT(dbuf);
+ DB_DNODE_ENTER(db);
+ err = dmu_assign_arcbuf_by_dnode(DB_DNODE(db), offset, buf, tx);
+ DB_DNODE_EXIT(db);
return (err);
}
@@ -1782,7 +1989,6 @@ dmu_sync(zio_t *pio, uint64_t txg, dmu_sync_cb_t *done, zgd_t *zgd)
dmu_sync_arg_t *dsa;
zbookmark_phys_t zb;
zio_prop_t zp;
- dnode_t *dn;
ASSERT(pio != NULL);
ASSERT(txg != 0);
@@ -1791,8 +1997,7 @@ dmu_sync(zio_t *pio, uint64_t txg, dmu_sync_cb_t *done, zgd_t *zgd)
db->db.db_object, db->db_level, db->db_blkid);
DB_DNODE_ENTER(db);
- dn = DB_DNODE(db);
- dmu_write_policy(os, dn, db->db_level, WP_DMU_SYNC, &zp);
+ dmu_write_policy(os, DB_DNODE(db), db->db_level, WP_DMU_SYNC, &zp);
DB_DNODE_EXIT(db);
/*
@@ -1877,11 +2082,14 @@ dmu_sync(zio_t *pio, uint64_t txg, dmu_sync_cb_t *done, zgd_t *zgd)
* zio_done(), which VERIFYs that the override BP is identical
* to the on-disk BP.
*/
- DB_DNODE_ENTER(db);
- dn = DB_DNODE(db);
- if (dr_next != NULL || dnode_block_freed(dn, db->db_blkid))
+ if (dr_next != NULL) {
zp.zp_nopwrite = B_FALSE;
- DB_DNODE_EXIT(db);
+ } else {
+ DB_DNODE_ENTER(db);
+ if (dnode_block_freed(DB_DNODE(db), db->db_blkid))
+ zp.zp_nopwrite = B_FALSE;
+ DB_DNODE_EXIT(db);
+ }
ASSERT(dr->dr_txg == txg);
if (dr->dt.dl.dr_override_state == DR_IN_DMU_SYNC ||
@@ -2154,6 +2362,7 @@ dmu_write_policy(objset_t *os, dnode_t *dn, int level, int wp, zio_prop_t *zp)
memset(zp->zp_mac, 0, ZIO_DATA_MAC_LEN);
zp->zp_zpl_smallblk = DMU_OT_IS_FILE(zp->zp_type) ?
os->os_zpl_special_smallblock : 0;
+ zp->zp_storage_type = dn ? dn->dn_storage_type : DMU_OT_NONE;
ASSERT3U(zp->zp_compress, !=, ZIO_COMPRESS_INHERIT);
}
@@ -2487,11 +2696,9 @@ void
dmu_object_dnsize_from_db(dmu_buf_t *db_fake, int *dnsize)
{
dmu_buf_impl_t *db = (dmu_buf_impl_t *)db_fake;
- dnode_t *dn;
DB_DNODE_ENTER(db);
- dn = DB_DNODE(db);
- *dnsize = dn->dn_num_slots << DNODE_SHIFT;
+ *dnsize = DB_DNODE(db)->dn_num_slots << DNODE_SHIFT;
DB_DNODE_EXIT(db);
}
diff --git a/sys/contrib/openzfs/module/zfs/dmu_tx.c b/sys/contrib/openzfs/module/zfs/dmu_tx.c
index 8451b5082e86..2c2a6c7642a5 100644
--- a/sys/contrib/openzfs/module/zfs/dmu_tx.c
+++ b/sys/contrib/openzfs/module/zfs/dmu_tx.c
@@ -1520,11 +1520,8 @@ dmu_tx_hold_sa(dmu_tx_t *tx, sa_handle_t *hdl, boolean_t may_grow)
ASSERT(tx->tx_txg == 0);
dmu_tx_hold_spill(tx, object);
} else {
- dnode_t *dn;
-
DB_DNODE_ENTER(db);
- dn = DB_DNODE(db);
- if (dn->dn_have_spill) {
+ if (DB_DNODE(db)->dn_have_spill) {
ASSERT(tx->tx_txg == 0);
dmu_tx_hold_spill(tx, object);
}
diff --git a/sys/contrib/openzfs/module/zfs/dnode.c b/sys/contrib/openzfs/module/zfs/dnode.c
index a703fd414f87..ecc6761f8fa4 100644
--- a/sys/contrib/openzfs/module/zfs/dnode.c
+++ b/sys/contrib/openzfs/module/zfs/dnode.c
@@ -306,7 +306,7 @@ dnode_init(void)
{
ASSERT(dnode_cache == NULL);
dnode_cache = kmem_cache_create("dnode_t", sizeof (dnode_t),
- 0, dnode_cons, dnode_dest, NULL, NULL, NULL, 0);
+ 0, dnode_cons, dnode_dest, NULL, NULL, NULL, KMC_RECLAIMABLE);
kmem_cache_set_move(dnode_cache, dnode_move);
wmsum_init(&dnode_sums.dnode_hold_dbuf_hold, 0);
@@ -544,6 +544,17 @@ dnode_setbonus_type(dnode_t *dn, dmu_object_type_t newtype, dmu_tx_t *tx)
}
void
+dnode_set_storage_type(dnode_t *dn, dmu_object_type_t newtype)
+{
+ /*
+ * This is not in the dnode_phys, but it should be, and perhaps one day
+ * will. For now we require it be set after taking a hold.
+ */
+ ASSERT3U(zfs_refcount_count(&dn->dn_holds), >=, 1);
+ dn->dn_storage_type = newtype;
+}
+
+void
dnode_rm_spill(dnode_t *dn, dmu_tx_t *tx)
{
ASSERT3U(zfs_refcount_count(&dn->dn_holds), >=, 1);
@@ -604,6 +615,8 @@ dnode_create(objset_t *os, dnode_phys_t *dnp, dmu_buf_impl_t *db,
dn->dn_have_spill = ((dnp->dn_flags & DNODE_FLAG_SPILL_BLKPTR) != 0);
dn->dn_id_flags = 0;
+ dn->dn_storage_type = DMU_OT_NONE;
+
dmu_zfetch_init(&dn->dn_zfetch, dn);
ASSERT(DMU_OT_IS_VALID(dn->dn_phys->dn_type));
@@ -687,6 +700,8 @@ dnode_destroy(dnode_t *dn)
dn->dn_newprojid = ZFS_DEFAULT_PROJID;
dn->dn_id_flags = 0;
+ dn->dn_storage_type = DMU_OT_NONE;
+
dmu_zfetch_fini(&dn->dn_zfetch);
kmem_cache_free(dnode_cache, dn);
arc_space_return(sizeof (dnode_t), ARC_SPACE_DNODE);
@@ -946,6 +961,7 @@ dnode_move_impl(dnode_t *odn, dnode_t *ndn)
ndn->dn_newgid = odn->dn_newgid;
ndn->dn_newprojid = odn->dn_newprojid;
ndn->dn_id_flags = odn->dn_id_flags;
+ ndn->dn_storage_type = odn->dn_storage_type;
dmu_zfetch_init(&ndn->dn_zfetch, ndn);
/*
@@ -1004,6 +1020,7 @@ dnode_move_impl(dnode_t *odn, dnode_t *ndn)
odn->dn_newgid = 0;
odn->dn_newprojid = ZFS_DEFAULT_PROJID;
odn->dn_id_flags = 0;
+ odn->dn_storage_type = DMU_OT_NONE;
/*
* Mark the dnode.
@@ -1020,6 +1037,19 @@ dnode_move(void *buf, void *newbuf, size_t size, void *arg)
int64_t refcount;
uint32_t dbufs;
+#ifndef USE_DNODE_HANDLE
+ /*
+ * We can't move dnodes if dbufs reference them directly without
+ * using handles and respecitve locking. Unless USE_DNODE_HANDLE
+ * is defined the code below is only to make sure it still builds,
+ * but it should never be used, since it is unsafe.
+ */
+#ifdef ZFS_DEBUG
+ PANIC("dnode_move() called without USE_DNODE_HANDLE");
+#endif
+ return (KMEM_CBRC_NO);
+#endif
+
/*
* The dnode is on the objset's list of known dnodes if the objset
* pointer is valid. We set the low bit of the objset pointer when
@@ -1757,7 +1787,7 @@ dnode_rele_and_unlock(dnode_t *dn, const void *tag, boolean_t evicting)
* handle.
*/
#ifdef ZFS_DEBUG
- ASSERT(refs > 0 || dnh->dnh_zrlock.zr_owner != curthread);
+ ASSERT(refs > 0 || zrl_owner(&dnh->dnh_zrlock) != curthread);
#endif
/* NOTE: the DNODE_DNODE does not have a dn_dbuf */
diff --git a/sys/contrib/openzfs/module/zfs/lz4_zfs.c b/sys/contrib/openzfs/module/zfs/lz4_zfs.c
index 820556effb8b..de90c45f2f07 100644
--- a/sys/contrib/openzfs/module/zfs/lz4_zfs.c
+++ b/sys/contrib/openzfs/module/zfs/lz4_zfs.c
@@ -886,7 +886,8 @@ void
lz4_init(void)
{
lz4_cache = kmem_cache_create("lz4_cache",
- sizeof (struct refTables), 0, NULL, NULL, NULL, NULL, NULL, 0);
+ sizeof (struct refTables), 0, NULL, NULL, NULL, NULL, NULL,
+ KMC_RECLAIMABLE);
}
void
diff --git a/sys/contrib/openzfs/module/zfs/sa.c b/sys/contrib/openzfs/module/zfs/sa.c
index 0ae4c331dd36..bc4c9dff31e7 100644
--- a/sys/contrib/openzfs/module/zfs/sa.c
+++ b/sys/contrib/openzfs/module/zfs/sa.c
@@ -236,7 +236,7 @@ sa_cache_init(void)
{
sa_cache = kmem_cache_create("sa_cache",
sizeof (sa_handle_t), 0, sa_cache_constructor,
- sa_cache_destructor, NULL, NULL, NULL, 0);
+ sa_cache_destructor, NULL, NULL, NULL, KMC_RECLAIMABLE);
}
void
@@ -1501,6 +1501,42 @@ sa_lookup(sa_handle_t *hdl, sa_attr_type_t attr, void *buf, uint32_t buflen)
return (error);
}
+/*
+ * Return size of an attribute
+ */
+
+static int
+sa_size_locked(sa_handle_t *hdl, sa_attr_type_t attr, int *size)
+{
+ sa_bulk_attr_t bulk;
+ int error;
+
+ bulk.sa_data = NULL;
+ bulk.sa_attr = attr;
+ bulk.sa_data_func = NULL;
+
+ ASSERT(hdl);
+ ASSERT(MUTEX_HELD(&hdl->sa_lock));
+ if ((error = sa_attr_op(hdl, &bulk, 1, SA_LOOKUP, NULL)) != 0) {
+ return (error);
+ }
+ *size = bulk.sa_size;
+
+ return (0);
+}
+
+int
+sa_size(sa_handle_t *hdl, sa_attr_type_t attr, int *size)
+{
+ int error;
+
+ mutex_enter(&hdl->sa_lock);
+ error = sa_size_locked(hdl, attr, size);
+ mutex_exit(&hdl->sa_lock);
+
+ return (error);
+}
+
#ifdef _KERNEL
int
sa_lookup_uio(sa_handle_t *hdl, sa_attr_type_t attr, zfs_uio_t *uio)
@@ -1542,6 +1578,8 @@ sa_add_projid(sa_handle_t *hdl, dmu_tx_t *tx, uint64_t projid)
uint64_t crtime[2], mtime[2], ctime[2], atime[2];
zfs_acl_phys_t znode_acl = { 0 };
char scanstamp[AV_SCANSTAMP_SZ];
+ char *dxattr_obj = NULL;
+ int dxattr_size = 0;
if (zp->z_acl_cached == NULL) {
zfs_acl_t *aclp;
@@ -1623,6 +1661,17 @@ sa_add_projid(sa_handle_t *hdl, dmu_tx_t *tx, uint64_t projid)
if (err != 0 && err != ENOENT)
goto out;
+ err = sa_size_locked(hdl, SA_ZPL_DXATTR(zfsvfs), &dxattr_size);
+ if (err != 0 && err != ENOENT)
+ goto out;
+ if (dxattr_size != 0) {
+ dxattr_obj = vmem_alloc(dxattr_size, KM_SLEEP);
+ err = sa_lookup_locked(hdl, SA_ZPL_DXATTR(zfsvfs), dxattr_obj,
+ dxattr_size);
+ if (err != 0 && err != ENOENT)
+ goto out;
+ }
+
zp->z_projid = projid;
zp->z_pflags |= ZFS_PROJID;
links = ZTONLNK(zp);
@@ -1674,6 +1723,11 @@ sa_add_projid(sa_handle_t *hdl, dmu_tx_t *tx, uint64_t projid)
zp->z_pflags &= ~ZFS_BONUS_SCANSTAMP;
}
+ if (dxattr_obj) {
+ SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_DXATTR(zfsvfs),
+ NULL, dxattr_obj, dxattr_size);
+ }
+
VERIFY(dmu_set_bonustype(db, DMU_OT_SA, tx) == 0);
VERIFY(sa_replace_all_by_template_locked(hdl, attrs, count, tx) == 0);
if (znode_acl.z_acl_extern_obj) {
@@ -1688,6 +1742,8 @@ out:
mutex_exit(&hdl->sa_lock);
kmem_free(attrs, sizeof (sa_bulk_attr_t) * ZPL_END);
kmem_free(bulk, sizeof (sa_bulk_attr_t) * ZPL_END);
+ if (dxattr_obj)
+ vmem_free(dxattr_obj, dxattr_size);
return (err);
}
#endif
@@ -1852,7 +1908,6 @@ sa_modify_attrs(sa_handle_t *hdl, sa_attr_type_t newattr,
{
sa_os_t *sa = hdl->sa_os->os_sa;
dmu_buf_impl_t *db = (dmu_buf_impl_t *)hdl->sa_bonus;
- dnode_t *dn;
sa_bulk_attr_t *attr_desc;
void *old_data[2];
int bonus_attr_count = 0;
@@ -1872,8 +1927,7 @@ sa_modify_attrs(sa_handle_t *hdl, sa_attr_type_t newattr,
/* First make of copy of the old data */
DB_DNODE_ENTER(db);
- dn = DB_DNODE(db);
- if (dn->dn_bonuslen != 0) {
+ if (DB_DNODE(db)->dn_bonuslen != 0) {
bonus_data_size = hdl->sa_bonus->db_size;
old_data[0] = kmem_alloc(bonus_data_size, KM_SLEEP);
memcpy(old_data[0], hdl->sa_bonus->db_data,
@@ -2059,32 +2113,6 @@ sa_update(sa_handle_t *hdl, sa_attr_type_t type,
return (error);
}
-/*
- * Return size of an attribute
- */
-
-int
-sa_size(sa_handle_t *hdl, sa_attr_type_t attr, int *size)
-{
- sa_bulk_attr_t bulk;
- int error;
-
- bulk.sa_data = NULL;
- bulk.sa_attr = attr;
- bulk.sa_data_func = NULL;
-
- ASSERT(hdl);
- mutex_enter(&hdl->sa_lock);
- if ((error = sa_attr_op(hdl, &bulk, 1, SA_LOOKUP, NULL)) != 0) {
- mutex_exit(&hdl->sa_lock);
- return (error);
- }
- *size = bulk.sa_size;
-
- mutex_exit(&hdl->sa_lock);
- return (0);
-}
-
int
sa_bulk_lookup_locked(sa_handle_t *hdl, sa_bulk_attr_t *attrs, int count)
{
diff --git a/sys/contrib/openzfs/module/zfs/spa.c b/sys/contrib/openzfs/module/zfs/spa.c
index 638572996c3a..cafc7196c354 100644
--- a/sys/contrib/openzfs/module/zfs/spa.c
+++ b/sys/contrib/openzfs/module/zfs/spa.c
@@ -34,7 +34,7 @@
* Copyright (c) 2017, Intel Corporation.
* Copyright (c) 2021, Colm Buckley <colm@tuatha.org>
* Copyright (c) 2023 Hewlett Packard Enterprise Development LP.
- * Copyright (c) 2024, Klara Inc.
+ * Copyright (c) 2023, 2024, Klara Inc.
*/
/*
@@ -337,6 +337,55 @@ spa_prop_add_list(nvlist_t *nvl, zpool_prop_t prop, const char *strval,
nvlist_free(propval);
}
+static int
+spa_prop_add(spa_t *spa, const char *propname, nvlist_t *outnvl)
+{
+ zpool_prop_t prop = zpool_name_to_prop(propname);
+ zprop_source_t src = ZPROP_SRC_NONE;
+ uint64_t intval;
+ int err;
+
+ /*
+ * NB: Not all properties lookups via this API require
+ * the spa props lock, so they must explicitly grab it here.
+ */
+ switch (prop) {
+ case ZPOOL_PROP_DEDUPCACHED:
+ err = ddt_get_pool_dedup_cached(spa, &intval);
+ if (err != 0)
+ return (SET_ERROR(err));
+ break;
+ default:
+ return (SET_ERROR(EINVAL));
+ }
+
+ spa_prop_add_list(outnvl, prop, NULL, intval, src);
+
+ return (0);
+}
+
+int
+spa_prop_get_nvlist(spa_t *spa, char **props, unsigned int n_props,
+ nvlist_t **outnvl)
+{
+ int err = 0;
+
+ if (props == NULL)
+ return (0);
+
+ if (*outnvl == NULL) {
+ err = nvlist_alloc(outnvl, NV_UNIQUE_NAME, KM_SLEEP);
+ if (err)
+ return (err);
+ }
+
+ for (unsigned int i = 0; i < n_props && err == 0; i++) {
+ err = spa_prop_add(spa, props[i], *outnvl);
+ }
+
+ return (err);
+}
+
/*
* Add a user property (source=src, propname=propval) to an nvlist.
*/
@@ -406,6 +455,9 @@ spa_prop_get_config(spa_t *spa, nvlist_t **nvp)
spa_prop_add_list(*nvp, ZPOOL_PROP_BCLONERATIO, NULL,
brt_get_ratio(spa), src);
+ spa_prop_add_list(*nvp, ZPOOL_PROP_DEDUP_TABLE_SIZE, NULL,
+ ddt_get_ddt_dsize(spa), src);
+
spa_prop_add_list(*nvp, ZPOOL_PROP_HEALTH, NULL,
rvd->vdev_state, src);
@@ -500,9 +552,11 @@ spa_prop_get(spa_t *spa, nvlist_t **nvp)
dsl_pool_t *dp;
int err;
- err = nvlist_alloc(nvp, NV_UNIQUE_NAME, KM_SLEEP);
- if (err)
- return (err);
+ if (*nvp == NULL) {
+ err = nvlist_alloc(nvp, NV_UNIQUE_NAME, KM_SLEEP);
+ if (err)
+ return (err);
+ }
dp = spa_get_dsl(spa);
dsl_pool_config_enter(dp, FTAG);
@@ -672,6 +726,10 @@ spa_prop_validate(spa_t *spa, nvlist_t *props)
error = SET_ERROR(EINVAL);
break;
+ case ZPOOL_PROP_DEDUP_TABLE_QUOTA:
+ error = nvpair_value_uint64(elem, &intval);
+ break;
+
case ZPOOL_PROP_DELEGATION:
case ZPOOL_PROP_AUTOREPLACE:
case ZPOOL_PROP_LISTSNAPS:
@@ -4732,6 +4790,8 @@ spa_ld_get_props(spa_t *spa)
spa_prop_find(spa, ZPOOL_PROP_DELEGATION, &spa->spa_delegation);
spa_prop_find(spa, ZPOOL_PROP_FAILUREMODE, &spa->spa_failmode);
spa_prop_find(spa, ZPOOL_PROP_AUTOEXPAND, &spa->spa_autoexpand);
+ spa_prop_find(spa, ZPOOL_PROP_DEDUP_TABLE_QUOTA,
+ &spa->spa_dedup_table_quota);
spa_prop_find(spa, ZPOOL_PROP_MULTIHOST, &spa->spa_multihost);
spa_prop_find(spa, ZPOOL_PROP_AUTOTRIM, &spa->spa_autotrim);
spa->spa_autoreplace = (autoreplace != 0);
@@ -6588,6 +6648,8 @@ spa_create(const char *pool, nvlist_t *nvroot, nvlist_t *props,
spa->spa_autoexpand = zpool_prop_default_numeric(ZPOOL_PROP_AUTOEXPAND);
spa->spa_multihost = zpool_prop_default_numeric(ZPOOL_PROP_MULTIHOST);
spa->spa_autotrim = zpool_prop_default_numeric(ZPOOL_PROP_AUTOTRIM);
+ spa->spa_dedup_table_quota =
+ zpool_prop_default_numeric(ZPOOL_PROP_DEDUP_TABLE_QUOTA);
if (props != NULL) {
spa_configfile_set(spa, props, B_FALSE);
@@ -6755,6 +6817,7 @@ spa_import(char *pool, nvlist_t *config, nvlist_t *props, uint64_t flags)
spa_load_spares(spa);
spa_config_exit(spa, SCL_ALL, FTAG);
spa->spa_spares.sav_sync = B_TRUE;
+ spa->spa_spares.sav_label_sync = B_TRUE;
}
if (nvlist_lookup_nvlist_array(nvroot, ZPOOL_CONFIG_L2CACHE,
&l2cache, &nl2cache) == 0) {
@@ -6770,6 +6833,7 @@ spa_import(char *pool, nvlist_t *config, nvlist_t *props, uint64_t flags)
spa_load_l2cache(spa);
spa_config_exit(spa, SCL_ALL, FTAG);
spa->spa_l2cache.sav_sync = B_TRUE;
+ spa->spa_l2cache.sav_label_sync = B_TRUE;
}
/*
@@ -9631,6 +9695,9 @@ spa_sync_props(void *arg, dmu_tx_t *tx)
case ZPOOL_PROP_MULTIHOST:
spa->spa_multihost = intval;
break;
+ case ZPOOL_PROP_DEDUP_TABLE_QUOTA:
+ spa->spa_dedup_table_quota = intval;
+ break;
default:
break;
}
diff --git a/sys/contrib/openzfs/module/zfs/spa_misc.c b/sys/contrib/openzfs/module/zfs/spa_misc.c
index d1d41bbe7214..97191e768549 100644
--- a/sys/contrib/openzfs/module/zfs/spa_misc.c
+++ b/sys/contrib/openzfs/module/zfs/spa_misc.c
@@ -1996,13 +1996,31 @@ spa_dedup_class(spa_t *spa)
return (spa->spa_dedup_class);
}
+boolean_t
+spa_special_has_ddt(spa_t *spa)
+{
+ return (zfs_ddt_data_is_special &&
+ spa->spa_special_class->mc_groups != 0);
+}
+
/*
* Locate an appropriate allocation class
*/
metaslab_class_t *
-spa_preferred_class(spa_t *spa, uint64_t size, dmu_object_type_t objtype,
- uint_t level, uint_t special_smallblk)
+spa_preferred_class(spa_t *spa, const zio_t *zio)
{
+ const zio_prop_t *zp = &zio->io_prop;
+
+ /*
+ * Override object type for the purposes of selecting a storage class.
+ * Primarily for DMU_OTN_ types where we can't explicitly control their
+ * storage class; instead, choose a static type most closely matches
+ * what we want.
+ */
+ dmu_object_type_t objtype =
+ zp->zp_storage_type == DMU_OT_NONE ?
+ zp->zp_type : zp->zp_storage_type;
+
/*
* ZIL allocations determine their class in zio_alloc_zil().
*/
@@ -2020,14 +2038,15 @@ spa_preferred_class(spa_t *spa, uint64_t size, dmu_object_type_t objtype,
}
/* Indirect blocks for user data can land in special if allowed */
- if (level > 0 && (DMU_OT_IS_FILE(objtype) || objtype == DMU_OT_ZVOL)) {
+ if (zp->zp_level > 0 &&
+ (DMU_OT_IS_FILE(objtype) || objtype == DMU_OT_ZVOL)) {
if (has_special_class && zfs_user_indirect_is_special)
return (spa_special_class(spa));
else
return (spa_normal_class(spa));
}
- if (DMU_OT_IS_METADATA(objtype) || level > 0) {
+ if (DMU_OT_IS_METADATA(objtype) || zp->zp_level > 0) {
if (has_special_class)
return (spa_special_class(spa));
else
@@ -2040,7 +2059,7 @@ spa_preferred_class(spa_t *spa, uint64_t size, dmu_object_type_t objtype,
* zfs_special_class_metadata_reserve_pct exclusively for metadata.
*/
if (DMU_OT_IS_FILE(objtype) &&
- has_special_class && size <= special_smallblk) {
+ has_special_class && zio->io_size <= zp->zp_zpl_smallblk) {
metaslab_class_t *special = spa_special_class(spa);
uint64_t alloc = metaslab_class_get_alloc(special);
uint64_t space = metaslab_class_get_space(special);
diff --git a/sys/contrib/openzfs/module/zfs/vdev.c b/sys/contrib/openzfs/module/zfs/vdev.c
index 11cc39ba3527..6ae0a14127bf 100644
--- a/sys/contrib/openzfs/module/zfs/vdev.c
+++ b/sys/contrib/openzfs/module/zfs/vdev.c
@@ -6222,6 +6222,16 @@ vdev_prop_get(vdev_t *vd, nvlist_t *innvl, nvlist_t *outnvl)
vd->vdev_stat.vs_initialize_errors,
ZPROP_SRC_NONE);
continue;
+ case VDEV_PROP_TRIM_ERRORS:
+ vdev_prop_add_list(outnvl, propname, NULL,
+ vd->vdev_stat.vs_trim_errors,
+ ZPROP_SRC_NONE);
+ continue;
+ case VDEV_PROP_SLOW_IOS:
+ vdev_prop_add_list(outnvl, propname, NULL,
+ vd->vdev_stat.vs_slow_ios,
+ ZPROP_SRC_NONE);
+ continue;
case VDEV_PROP_OPS_NULL:
vdev_prop_add_list(outnvl, propname, NULL,
vd->vdev_stat.vs_ops[ZIO_TYPE_NULL],
@@ -6306,6 +6316,14 @@ vdev_prop_get(vdev_t *vd, nvlist_t *innvl, nvlist_t *outnvl)
ZPROP_SRC_NONE);
}
continue;
+ case VDEV_PROP_TRIM_SUPPORT:
+ /* only valid for leaf vdevs */
+ if (vd->vdev_ops->vdev_op_leaf) {
+ vdev_prop_add_list(outnvl, propname,
+ NULL, vd->vdev_has_trim,
+ ZPROP_SRC_NONE);
+ }
+ continue;
/* Numeric Properites */
case VDEV_PROP_ALLOCATING:
/* Leaf vdevs cannot have this property */
diff --git a/sys/contrib/openzfs/module/zfs/vdev_label.c b/sys/contrib/openzfs/module/zfs/vdev_label.c
index ed592514fded..47346dd5acff 100644
--- a/sys/contrib/openzfs/module/zfs/vdev_label.c
+++ b/sys/contrib/openzfs/module/zfs/vdev_label.c
@@ -1007,6 +1007,47 @@ vdev_inuse(vdev_t *vd, uint64_t crtxg, vdev_labeltype_t reason,
return (state == POOL_STATE_ACTIVE);
}
+static nvlist_t *
+vdev_aux_label_generate(vdev_t *vd, boolean_t reason_spare)
+{
+ /*
+ * For inactive hot spares and level 2 ARC devices, we generate
+ * a special label that identifies as a mutually shared hot
+ * spare or l2cache device. We write the label in case of
+ * addition or removal of hot spare or l2cache vdev (in which
+ * case we want to revert the labels).
+ */
+ nvlist_t *label = fnvlist_alloc();
+ fnvlist_add_uint64(label, ZPOOL_CONFIG_VERSION,
+ spa_version(vd->vdev_spa));
+ fnvlist_add_uint64(label, ZPOOL_CONFIG_POOL_STATE, reason_spare ?
+ POOL_STATE_SPARE : POOL_STATE_L2CACHE);
+ fnvlist_add_uint64(label, ZPOOL_CONFIG_GUID, vd->vdev_guid);
+
+ /*
+ * This is merely to facilitate reporting the ashift of the
+ * cache device through zdb. The actual retrieval of the
+ * ashift (in vdev_alloc()) uses the nvlist
+ * spa->spa_l2cache->sav_config (populated in
+ * spa_ld_open_aux_vdevs()).
+ */
+ if (!reason_spare)
+ fnvlist_add_uint64(label, ZPOOL_CONFIG_ASHIFT, vd->vdev_ashift);
+
+ /*
+ * Add path information to help find it during pool import
+ */
+ if (vd->vdev_path != NULL)
+ fnvlist_add_string(label, ZPOOL_CONFIG_PATH, vd->vdev_path);
+ if (vd->vdev_devid != NULL)
+ fnvlist_add_string(label, ZPOOL_CONFIG_DEVID, vd->vdev_devid);
+ if (vd->vdev_physpath != NULL) {
+ fnvlist_add_string(label, ZPOOL_CONFIG_PHYS_PATH,
+ vd->vdev_physpath);
+ }
+ return (label);
+}
+
/*
* Initialize a vdev label. We check to make sure each leaf device is not in
* use, and writable. We put down an initial label which we will later
@@ -1121,49 +1162,7 @@ vdev_label_init(vdev_t *vd, uint64_t crtxg, vdev_labeltype_t reason)
* be written again with a meaningful txg by spa_sync().
*/
if (reason_spare || reason_l2cache) {
- /*
- * For inactive hot spares and level 2 ARC devices, we generate
- * a special label that identifies as a mutually shared hot
- * spare or l2cache device. We write the label in case of
- * addition or removal of hot spare or l2cache vdev (in which
- * case we want to revert the labels).
- */
- VERIFY(nvlist_alloc(&label, NV_UNIQUE_NAME, KM_SLEEP) == 0);
-
- VERIFY(nvlist_add_uint64(label, ZPOOL_CONFIG_VERSION,
- spa_version(spa)) == 0);
- VERIFY(nvlist_add_uint64(label, ZPOOL_CONFIG_POOL_STATE,
- reason_spare ? POOL_STATE_SPARE : POOL_STATE_L2CACHE) == 0);
- VERIFY(nvlist_add_uint64(label, ZPOOL_CONFIG_GUID,
- vd->vdev_guid) == 0);
-
- /*
- * This is merely to facilitate reporting the ashift of the
- * cache device through zdb. The actual retrieval of the
- * ashift (in vdev_alloc()) uses the nvlist
- * spa->spa_l2cache->sav_config (populated in
- * spa_ld_open_aux_vdevs()).
- */
- if (reason_l2cache) {
- VERIFY(nvlist_add_uint64(label, ZPOOL_CONFIG_ASHIFT,
- vd->vdev_ashift) == 0);
- }
-
- /*
- * Add path information to help find it during pool import
- */
- if (vd->vdev_path != NULL) {
- VERIFY(nvlist_add_string(label, ZPOOL_CONFIG_PATH,
- vd->vdev_path) == 0);
- }
- if (vd->vdev_devid != NULL) {
- VERIFY(nvlist_add_string(label, ZPOOL_CONFIG_DEVID,
- vd->vdev_devid) == 0);
- }
- if (vd->vdev_physpath != NULL) {
- VERIFY(nvlist_add_string(label, ZPOOL_CONFIG_PHYS_PATH,
- vd->vdev_physpath) == 0);
- }
+ label = vdev_aux_label_generate(vd, reason_spare);
/*
* When spare or l2cache (aux) vdev is added during pool
@@ -1900,6 +1899,8 @@ vdev_label_sync(zio_t *zio, uint64_t *good_writes,
abd_t *vp_abd;
char *buf;
size_t buflen;
+ vdev_t *pvd = vd->vdev_parent;
+ boolean_t spare_in_use = B_FALSE;
for (int c = 0; c < vd->vdev_children; c++) {
vdev_label_sync(zio, good_writes,
@@ -1920,10 +1921,17 @@ vdev_label_sync(zio_t *zio, uint64_t *good_writes,
if (vd->vdev_ops == &vdev_draid_spare_ops)
return;
+ if (pvd && pvd->vdev_ops == &vdev_spare_ops)
+ spare_in_use = B_TRUE;
+
/*
* Generate a label describing the top-level config to which we belong.
*/
- label = spa_config_generate(vd->vdev_spa, vd, txg, B_FALSE);
+ if ((vd->vdev_isspare && !spare_in_use) || vd->vdev_isl2cache) {
+ label = vdev_aux_label_generate(vd, vd->vdev_isspare);
+ } else {
+ label = spa_config_generate(vd->vdev_spa, vd, txg, B_FALSE);
+ }
vp_abd = abd_alloc_linear(sizeof (vdev_phys_t), B_TRUE);
abd_zero(vp_abd, sizeof (vdev_phys_t));
@@ -1973,6 +1981,24 @@ vdev_label_sync_list(spa_t *spa, int l, uint64_t txg, int flags)
zio_nowait(vio);
}
+ /*
+ * AUX path may have changed during import
+ */
+ spa_aux_vdev_t *sav[2] = {&spa->spa_spares, &spa->spa_l2cache};
+ for (int i = 0; i < 2; i++) {
+ for (int v = 0; v < sav[i]->sav_count; v++) {
+ uint64_t *good_writes;
+ if (!sav[i]->sav_label_sync)
+ continue;
+ good_writes = kmem_zalloc(sizeof (uint64_t), KM_SLEEP);
+ zio_t *vio = zio_null(zio, spa, NULL,
+ vdev_label_sync_ignore_done, good_writes, flags);
+ vdev_label_sync(vio, good_writes, sav[i]->sav_vdevs[v],
+ l, txg, flags);
+ zio_nowait(vio);
+ }
+ }
+
error = zio_wait(zio);
/*
@@ -1983,6 +2009,15 @@ vdev_label_sync_list(spa_t *spa, int l, uint64_t txg, int flags)
for (vd = list_head(dl); vd != NULL; vd = list_next(dl, vd))
zio_flush(zio, vd);
+ for (int i = 0; i < 2; i++) {
+ if (!sav[i]->sav_label_sync)
+ continue;
+ for (int v = 0; v < sav[i]->sav_count; v++)
+ zio_flush(zio, sav[i]->sav_vdevs[v]);
+ if (l == 1)
+ sav[i]->sav_label_sync = B_FALSE;
+ }
+
(void) zio_wait(zio);
return (error);
diff --git a/sys/contrib/openzfs/module/zfs/zap_micro.c b/sys/contrib/openzfs/module/zfs/zap_micro.c
index d806988af96d..0d6533b0e131 100644
--- a/sys/contrib/openzfs/module/zfs/zap_micro.c
+++ b/sys/contrib/openzfs/module/zfs/zap_micro.c
@@ -1073,6 +1073,21 @@ zap_prefetch(objset_t *os, uint64_t zapobj, const char *name)
}
int
+zap_prefetch_object(objset_t *os, uint64_t zapobj)
+{
+ int error;
+ dmu_object_info_t doi;
+
+ error = dmu_object_info(os, zapobj, &doi);
+ if (error == 0 && DMU_OT_BYTESWAP(doi.doi_type) != DMU_BSWAP_ZAP)
+ error = SET_ERROR(EINVAL);
+ if (error == 0)
+ dmu_prefetch_wait(os, zapobj, 0, doi.doi_max_offset);
+
+ return (error);
+}
+
+int
zap_lookup_by_dnode(dnode_t *dn, const char *name,
uint64_t integer_size, uint64_t num_integers, void *buf)
{
@@ -1784,6 +1799,7 @@ EXPORT_SYMBOL(zap_lookup_uint64);
EXPORT_SYMBOL(zap_contains);
EXPORT_SYMBOL(zap_prefetch);
EXPORT_SYMBOL(zap_prefetch_uint64);
+EXPORT_SYMBOL(zap_prefetch_object);
EXPORT_SYMBOL(zap_add);
EXPORT_SYMBOL(zap_add_by_dnode);
EXPORT_SYMBOL(zap_add_uint64);
diff --git a/sys/contrib/openzfs/module/zfs/zfs_ioctl.c b/sys/contrib/openzfs/module/zfs/zfs_ioctl.c
index 7b527eb75e83..897335dd4e4f 100644
--- a/sys/contrib/openzfs/module/zfs/zfs_ioctl.c
+++ b/sys/contrib/openzfs/module/zfs/zfs_ioctl.c
@@ -38,7 +38,7 @@
* Copyright (c) 2017 Open-E, Inc. All Rights Reserved.
* Copyright (c) 2019 Datto Inc.
* Copyright (c) 2019, 2020 by Christian Schwarz. All rights reserved.
- * Copyright (c) 2019, 2021, 2024, Klara Inc.
+ * Copyright (c) 2019, 2021, 2023, 2024, Klara Inc.
* Copyright (c) 2019, Allan Jude
* Copyright 2024 Oxide Computer Company
*/
@@ -3009,34 +3009,51 @@ zfs_ioc_pool_set_props(zfs_cmd_t *zc)
return (error);
}
+/*
+ * innvl: {
+ * "get_props_names": [ "prop1", "prop2", ..., "propN" ]
+ * }
+ */
+
+static const zfs_ioc_key_t zfs_keys_get_props[] = {
+ { ZPOOL_GET_PROPS_NAMES, DATA_TYPE_STRING_ARRAY, ZK_OPTIONAL },
+};
+
static int
-zfs_ioc_pool_get_props(zfs_cmd_t *zc)
+zfs_ioc_pool_get_props(const char *pool, nvlist_t *innvl, nvlist_t *outnvl)
{
+ nvlist_t *nvp = outnvl;
spa_t *spa;
+ char **props = NULL;
+ unsigned int n_props = 0;
int error;
- nvlist_t *nvp = NULL;
- if ((error = spa_open(zc->zc_name, &spa, FTAG)) != 0) {
+ if (nvlist_lookup_string_array(innvl, ZPOOL_GET_PROPS_NAMES,
+ &props, &n_props) != 0) {
+ props = NULL;
+ }
+
+ if ((error = spa_open(pool, &spa, FTAG)) != 0) {
/*
* If the pool is faulted, there may be properties we can still
* get (such as altroot and cachefile), so attempt to get them
* anyway.
*/
mutex_enter(&spa_namespace_lock);
- if ((spa = spa_lookup(zc->zc_name)) != NULL)
+ if ((spa = spa_lookup(pool)) != NULL) {
error = spa_prop_get(spa, &nvp);
+ if (error == 0 && props != NULL)
+ error = spa_prop_get_nvlist(spa, props, n_props,
+ &nvp);
+ }
mutex_exit(&spa_namespace_lock);
} else {
error = spa_prop_get(spa, &nvp);
+ if (error == 0 && props != NULL)
+ error = spa_prop_get_nvlist(spa, props, n_props, &nvp);
spa_close(spa, FTAG);
}
- if (error == 0 && zc->zc_nvlist_dst != 0)
- error = put_nvlist(zc, nvp);
- else
- error = SET_ERROR(EFAULT);
-
- nvlist_free(nvp);
return (error);
}
@@ -4032,6 +4049,52 @@ zfs_ioc_pool_discard_checkpoint(const char *poolname, nvlist_t *innvl,
}
/*
+ * Loads specific types of data for the given pool
+ *
+ * innvl: {
+ * "prefetch_type" -> int32_t
+ * }
+ *
+ * outnvl: empty
+ */
+static const zfs_ioc_key_t zfs_keys_pool_prefetch[] = {
+ {ZPOOL_PREFETCH_TYPE, DATA_TYPE_INT32, 0},
+};
+
+static int
+zfs_ioc_pool_prefetch(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl)
+{
+ (void) outnvl;
+
+ int error;
+ spa_t *spa;
+ int32_t type;
+
+ /*
+ * Currently, only ZPOOL_PREFETCH_DDT is supported
+ */
+ if (nvlist_lookup_int32(innvl, ZPOOL_PREFETCH_TYPE, &type) != 0 ||
+ type != ZPOOL_PREFETCH_DDT) {
+ return (EINVAL);
+ }
+
+ error = spa_open(poolname, &spa, FTAG);
+ if (error != 0)
+ return (error);
+
+ hrtime_t start_time = gethrtime();
+
+ ddt_prefetch_all(spa);
+
+ zfs_dbgmsg("pool '%s': loaded ddt into ARC in %llu ms", spa->spa_name,
+ (u_longlong_t)NSEC2MSEC(gethrtime() - start_time));
+
+ spa_close(spa, FTAG);
+
+ return (error);
+}
+
+/*
* inputs:
* zc_name name of dataset to destroy
* zc_defer_destroy mark for deferred destroy
@@ -7283,6 +7346,12 @@ zfs_ioctl_init(void)
zfs_keys_pool_discard_checkpoint,
ARRAY_SIZE(zfs_keys_pool_discard_checkpoint));
+ zfs_ioctl_register("zpool_prefetch",
+ ZFS_IOC_POOL_PREFETCH, zfs_ioc_pool_prefetch,
+ zfs_secpolicy_config, POOL_NAME,
+ POOL_CHECK_SUSPENDED, B_TRUE, B_TRUE,
+ zfs_keys_pool_prefetch, ARRAY_SIZE(zfs_keys_pool_prefetch));
+
zfs_ioctl_register("initialize", ZFS_IOC_POOL_INITIALIZE,
zfs_ioc_pool_initialize, zfs_secpolicy_config, POOL_NAME,
POOL_CHECK_SUSPENDED | POOL_CHECK_READONLY, B_TRUE, B_TRUE,
@@ -7328,6 +7397,11 @@ zfs_ioctl_init(void)
POOL_CHECK_NONE, B_TRUE, B_TRUE,
zfs_keys_pool_scrub, ARRAY_SIZE(zfs_keys_pool_scrub));
+ zfs_ioctl_register("get_props", ZFS_IOC_POOL_GET_PROPS,
+ zfs_ioc_pool_get_props, zfs_secpolicy_read, POOL_NAME,
+ POOL_CHECK_NONE, B_FALSE, B_FALSE,
+ zfs_keys_get_props, ARRAY_SIZE(zfs_keys_get_props));
+
/* IOCTLS that use the legacy function signature */
zfs_ioctl_register_legacy(ZFS_IOC_POOL_FREEZE, zfs_ioc_pool_freeze,
@@ -7383,8 +7457,6 @@ zfs_ioctl_init(void)
zfs_ioctl_register_pool(ZFS_IOC_POOL_STATS, zfs_ioc_pool_stats,
zfs_secpolicy_read, B_FALSE, POOL_CHECK_NONE);
- zfs_ioctl_register_pool(ZFS_IOC_POOL_GET_PROPS, zfs_ioc_pool_get_props,
- zfs_secpolicy_read, B_FALSE, POOL_CHECK_NONE);
zfs_ioctl_register_pool(ZFS_IOC_ERROR_LOG, zfs_ioc_error_log,
zfs_secpolicy_inject, B_FALSE, POOL_CHECK_SUSPENDED);
diff --git a/sys/contrib/openzfs/module/zfs/zfs_log.c b/sys/contrib/openzfs/module/zfs/zfs_log.c
index fa4e7093ca46..399f5a0117bb 100644
--- a/sys/contrib/openzfs/module/zfs/zfs_log.c
+++ b/sys/contrib/openzfs/module/zfs/zfs_log.c
@@ -665,13 +665,13 @@ zfs_log_write(zilog_t *zilog, dmu_tx_t *tx, int txtype,
DB_DNODE_ENTER(db);
err = dmu_read_by_dnode(DB_DNODE(db), off, len, lr + 1,
DMU_READ_NO_PREFETCH);
+ DB_DNODE_EXIT(db);
if (err != 0) {
zil_itx_destroy(itx);
itx = zil_itx_create(txtype, sizeof (*lr));
lr = (lr_write_t *)&itx->itx_lr;
wr_state = WR_NEED_COPY;
}
- DB_DNODE_EXIT(db);
}
itx->itx_wr_state = wr_state;
diff --git a/sys/contrib/openzfs/module/zfs/zfs_replay.c b/sys/contrib/openzfs/module/zfs/zfs_replay.c
index 2e0af60f6db4..810550161f8b 100644
--- a/sys/contrib/openzfs/module/zfs/zfs_replay.c
+++ b/sys/contrib/openzfs/module/zfs/zfs_replay.c
@@ -439,7 +439,7 @@ zfs_replay_create_acl(void *arg1, void *arg2, boolean_t byteswap)
bail:
if (error == 0 && zp != NULL) {
#ifdef __FreeBSD__
- VOP_UNLOCK1(ZTOV(zp));
+ VOP_UNLOCK(ZTOV(zp));
#endif
zrele(zp);
}
@@ -595,7 +595,7 @@ zfs_replay_create(void *arg1, void *arg2, boolean_t byteswap)
out:
if (error == 0 && zp != NULL) {
#ifdef __FreeBSD__
- VOP_UNLOCK1(ZTOV(zp));
+ VOP_UNLOCK(ZTOV(zp));
#endif
zrele(zp);
}
diff --git a/sys/contrib/openzfs/module/zfs/zil.c b/sys/contrib/openzfs/module/zfs/zil.c
index 34be54b337fd..3983da6aa424 100644
--- a/sys/contrib/openzfs/module/zfs/zil.c
+++ b/sys/contrib/openzfs/module/zfs/zil.c
@@ -99,6 +99,9 @@ static uint_t zfs_commit_timeout_pct = 10;
static zil_kstat_values_t zil_stats = {
{ "zil_commit_count", KSTAT_DATA_UINT64 },
{ "zil_commit_writer_count", KSTAT_DATA_UINT64 },
+ { "zil_commit_error_count", KSTAT_DATA_UINT64 },
+ { "zil_commit_stall_count", KSTAT_DATA_UINT64 },
+ { "zil_commit_suspend_count", KSTAT_DATA_UINT64 },
{ "zil_itx_count", KSTAT_DATA_UINT64 },
{ "zil_itx_indirect_count", KSTAT_DATA_UINT64 },
{ "zil_itx_indirect_bytes", KSTAT_DATA_UINT64 },
@@ -360,6 +363,9 @@ zil_sums_init(zil_sums_t *zs)
{
wmsum_init(&zs->zil_commit_count, 0);
wmsum_init(&zs->zil_commit_writer_count, 0);
+ wmsum_init(&zs->zil_commit_error_count, 0);
+ wmsum_init(&zs->zil_commit_stall_count, 0);
+ wmsum_init(&zs->zil_commit_suspend_count, 0);
wmsum_init(&zs->zil_itx_count, 0);
wmsum_init(&zs->zil_itx_indirect_count, 0);
wmsum_init(&zs->zil_itx_indirect_bytes, 0);
@@ -382,6 +388,9 @@ zil_sums_fini(zil_sums_t *zs)
{
wmsum_fini(&zs->zil_commit_count);
wmsum_fini(&zs->zil_commit_writer_count);
+ wmsum_fini(&zs->zil_commit_error_count);
+ wmsum_fini(&zs->zil_commit_stall_count);
+ wmsum_fini(&zs->zil_commit_suspend_count);
wmsum_fini(&zs->zil_itx_count);
wmsum_fini(&zs->zil_itx_indirect_count);
wmsum_fini(&zs->zil_itx_indirect_bytes);
@@ -406,6 +415,12 @@ zil_kstat_values_update(zil_kstat_values_t *zs, zil_sums_t *zil_sums)
wmsum_value(&zil_sums->zil_commit_count);
zs->zil_commit_writer_count.value.ui64 =
wmsum_value(&zil_sums->zil_commit_writer_count);
+ zs->zil_commit_error_count.value.ui64 =
+ wmsum_value(&zil_sums->zil_commit_error_count);
+ zs->zil_commit_stall_count.value.ui64 =
+ wmsum_value(&zil_sums->zil_commit_stall_count);
+ zs->zil_commit_suspend_count.value.ui64 =
+ wmsum_value(&zil_sums->zil_commit_suspend_count);
zs->zil_itx_count.value.ui64 =
wmsum_value(&zil_sums->zil_itx_count);
zs->zil_itx_indirect_count.value.ui64 =
@@ -512,9 +527,26 @@ zil_parse(zilog_t *zilog, zil_parse_blk_func_t *parse_blk_func,
for (; lrp < end; lrp += reclen) {
lr_t *lr = (lr_t *)lrp;
+
+ /*
+ * Are the remaining bytes large enough to hold an
+ * log record?
+ */
+ if ((char *)(lr + 1) > end) {
+ cmn_err(CE_WARN, "zil_parse: lr_t overrun");
+ error = SET_ERROR(ECKSUM);
+ arc_buf_destroy(abuf, &abuf);
+ goto done;
+ }
reclen = lr->lrc_reclen;
- ASSERT3U(reclen, >=, sizeof (lr_t));
- ASSERT3U(reclen, <=, end - lrp);
+ if (reclen < sizeof (lr_t) || reclen > end - lrp) {
+ cmn_err(CE_WARN,
+ "zil_parse: lr_t has an invalid reclen");
+ error = SET_ERROR(ECKSUM);
+ arc_buf_destroy(abuf, &abuf);
+ goto done;
+ }
+
if (lr->lrc_seq > claim_lr_seq) {
arc_buf_destroy(abuf, &abuf);
goto done;
@@ -2823,6 +2855,7 @@ zil_commit_writer_stall(zilog_t *zilog)
* (which is achieved via the txg_wait_synced() call).
*/
ASSERT(MUTEX_HELD(&zilog->zl_issuer_lock));
+ ZIL_STAT_BUMP(zilog, zil_commit_stall_count);
txg_wait_synced(zilog->zl_dmu_pool, 0);
ASSERT(list_is_empty(&zilog->zl_lwb_list));
}
@@ -3592,6 +3625,7 @@ zil_commit(zilog_t *zilog, uint64_t foid)
* semantics, and avoid calling those functions altogether.
*/
if (zilog->zl_suspend > 0) {
+ ZIL_STAT_BUMP(zilog, zil_commit_suspend_count);
txg_wait_synced(zilog->zl_dmu_pool, 0);
return;
}
@@ -3645,10 +3679,12 @@ zil_commit_impl(zilog_t *zilog, uint64_t foid)
* implications, but the expectation is for this to be
* an exceptional case, and shouldn't occur often.
*/
+ ZIL_STAT_BUMP(zilog, zil_commit_error_count);
DTRACE_PROBE2(zil__commit__io__error,
zilog_t *, zilog, zil_commit_waiter_t *, zcw);
txg_wait_synced(zilog->zl_dmu_pool, 0);
} else if (wtxg != 0) {
+ ZIL_STAT_BUMP(zilog, zil_commit_suspend_count);
txg_wait_synced(zilog->zl_dmu_pool, wtxg);
}
diff --git a/sys/contrib/openzfs/module/zfs/zio.c b/sys/contrib/openzfs/module/zfs/zio.c
index d68d5ababe79..6d08d4bd1633 100644
--- a/sys/contrib/openzfs/module/zfs/zio.c
+++ b/sys/contrib/openzfs/module/zfs/zio.c
@@ -194,6 +194,10 @@ zio_init(void)
cflags = (zio_exclude_metadata || size > zio_buf_debug_limit) ?
KMC_NODEBUG : 0;
data_cflags = KMC_NODEBUG;
+ if (abd_size_alloc_linear(size)) {
+ cflags |= KMC_RECLAIMABLE;
+ data_cflags |= KMC_RECLAIMABLE;
+ }
if (cflags == data_cflags) {
/*
* Resulting kmem caches would be identical.
@@ -1101,45 +1105,50 @@ zfs_blkptr_verify(spa_t *spa, const blkptr_t *bp,
{
int errors = 0;
- if (!DMU_OT_IS_VALID(BP_GET_TYPE(bp))) {
+ if (unlikely(!DMU_OT_IS_VALID(BP_GET_TYPE(bp)))) {
errors += zfs_blkptr_verify_log(spa, bp, blk_verify,
"blkptr at %px has invalid TYPE %llu",
bp, (longlong_t)BP_GET_TYPE(bp));
}
- if (BP_GET_CHECKSUM(bp) >= ZIO_CHECKSUM_FUNCTIONS) {
- errors += zfs_blkptr_verify_log(spa, bp, blk_verify,
- "blkptr at %px has invalid CHECKSUM %llu",
- bp, (longlong_t)BP_GET_CHECKSUM(bp));
- }
- if (BP_GET_COMPRESS(bp) >= ZIO_COMPRESS_FUNCTIONS) {
+ if (unlikely(BP_GET_COMPRESS(bp) >= ZIO_COMPRESS_FUNCTIONS)) {
errors += zfs_blkptr_verify_log(spa, bp, blk_verify,
"blkptr at %px has invalid COMPRESS %llu",
bp, (longlong_t)BP_GET_COMPRESS(bp));
}
- if (BP_GET_LSIZE(bp) > SPA_MAXBLOCKSIZE) {
+ if (unlikely(BP_GET_LSIZE(bp) > SPA_MAXBLOCKSIZE)) {
errors += zfs_blkptr_verify_log(spa, bp, blk_verify,
"blkptr at %px has invalid LSIZE %llu",
bp, (longlong_t)BP_GET_LSIZE(bp));
}
- if (BP_GET_PSIZE(bp) > SPA_MAXBLOCKSIZE) {
- errors += zfs_blkptr_verify_log(spa, bp, blk_verify,
- "blkptr at %px has invalid PSIZE %llu",
- bp, (longlong_t)BP_GET_PSIZE(bp));
- }
-
if (BP_IS_EMBEDDED(bp)) {
- if (BPE_GET_ETYPE(bp) >= NUM_BP_EMBEDDED_TYPES) {
+ if (unlikely(BPE_GET_ETYPE(bp) >= NUM_BP_EMBEDDED_TYPES)) {
errors += zfs_blkptr_verify_log(spa, bp, blk_verify,
"blkptr at %px has invalid ETYPE %llu",
bp, (longlong_t)BPE_GET_ETYPE(bp));
}
+ if (unlikely(BPE_GET_PSIZE(bp) > BPE_PAYLOAD_SIZE)) {
+ errors += zfs_blkptr_verify_log(spa, bp, blk_verify,
+ "blkptr at %px has invalid PSIZE %llu",
+ bp, (longlong_t)BPE_GET_PSIZE(bp));
+ }
+ return (errors == 0);
+ }
+ if (unlikely(BP_GET_CHECKSUM(bp) >= ZIO_CHECKSUM_FUNCTIONS)) {
+ errors += zfs_blkptr_verify_log(spa, bp, blk_verify,
+ "blkptr at %px has invalid CHECKSUM %llu",
+ bp, (longlong_t)BP_GET_CHECKSUM(bp));
+ }
+ if (unlikely(BP_GET_PSIZE(bp) > SPA_MAXBLOCKSIZE)) {
+ errors += zfs_blkptr_verify_log(spa, bp, blk_verify,
+ "blkptr at %px has invalid PSIZE %llu",
+ bp, (longlong_t)BP_GET_PSIZE(bp));
}
/*
* Do not verify individual DVAs if the config is not trusted. This
* will be done once the zio is executed in vdev_mirror_map_alloc.
*/
- if (!spa->spa_trust_config)
+ if (unlikely(!spa->spa_trust_config))
return (errors == 0);
switch (blk_config) {
@@ -1168,20 +1177,20 @@ zfs_blkptr_verify(spa_t *spa, const blkptr_t *bp,
const dva_t *dva = &bp->blk_dva[i];
uint64_t vdevid = DVA_GET_VDEV(dva);
- if (vdevid >= spa->spa_root_vdev->vdev_children) {
+ if (unlikely(vdevid >= spa->spa_root_vdev->vdev_children)) {
errors += zfs_blkptr_verify_log(spa, bp, blk_verify,
"blkptr at %px DVA %u has invalid VDEV %llu",
bp, i, (longlong_t)vdevid);
continue;
}
vdev_t *vd = spa->spa_root_vdev->vdev_child[vdevid];
- if (vd == NULL) {
+ if (unlikely(vd == NULL)) {
errors += zfs_blkptr_verify_log(spa, bp, blk_verify,
"blkptr at %px DVA %u has invalid VDEV %llu",
bp, i, (longlong_t)vdevid);
continue;
}
- if (vd->vdev_ops == &vdev_hole_ops) {
+ if (unlikely(vd->vdev_ops == &vdev_hole_ops)) {
errors += zfs_blkptr_verify_log(spa, bp, blk_verify,
"blkptr at %px DVA %u has hole VDEV %llu",
bp, i, (longlong_t)vdevid);
@@ -1199,7 +1208,7 @@ zfs_blkptr_verify(spa_t *spa, const blkptr_t *bp,
uint64_t asize = DVA_GET_ASIZE(dva);
if (DVA_GET_GANG(dva))
asize = vdev_gang_header_asize(vd);
- if (offset + asize > vd->vdev_asize) {
+ if (unlikely(offset + asize > vd->vdev_asize)) {
errors += zfs_blkptr_verify_log(spa, bp, blk_verify,
"blkptr at %px DVA %u has invalid OFFSET %llu",
bp, i, (longlong_t)offset);
@@ -1850,8 +1859,13 @@ zio_write_compress(zio_t *zio)
if (compress != ZIO_COMPRESS_OFF &&
!(zio->io_flags & ZIO_FLAG_RAW_COMPRESS)) {
void *cbuf = NULL;
- psize = zio_compress_data(compress, zio->io_abd, &cbuf, lsize,
- zp->zp_complevel);
+ if (abd_cmp_zero(zio->io_abd, lsize) == 0)
+ psize = 0;
+ else if (compress == ZIO_COMPRESS_EMPTY)
+ psize = lsize;
+ else
+ psize = zio_compress_data(compress, zio->io_abd, &cbuf,
+ lsize, zp->zp_complevel);
if (psize == 0) {
compress = ZIO_COMPRESS_OFF;
} else if (psize >= lsize) {
@@ -1915,10 +1929,12 @@ zio_write_compress(zio_t *zio)
* receive, we must check whether the block can be compressed
* to a hole.
*/
- psize = zio_compress_data(ZIO_COMPRESS_EMPTY,
- zio->io_abd, NULL, lsize, zp->zp_complevel);
- if (psize == 0 || psize >= lsize)
+ if (abd_cmp_zero(zio->io_abd, lsize) == 0) {
+ psize = 0;
compress = ZIO_COMPRESS_OFF;
+ } else {
+ psize = lsize;
+ }
} else if (zio->io_flags & ZIO_FLAG_RAW_COMPRESS &&
!(zio->io_flags & ZIO_FLAG_RAW_ENCRYPT)) {
/*
@@ -3069,7 +3085,7 @@ zio_write_gang_block(zio_t *pio, metaslab_class_t *mc)
zp.zp_checksum = gio->io_prop.zp_checksum;
zp.zp_compress = ZIO_COMPRESS_OFF;
zp.zp_complevel = gio->io_prop.zp_complevel;
- zp.zp_type = DMU_OT_NONE;
+ zp.zp_type = zp.zp_storage_type = DMU_OT_NONE;
zp.zp_level = 0;
zp.zp_copies = gio->io_prop.zp_copies;
zp.zp_dedup = B_FALSE;
@@ -3503,6 +3519,15 @@ zio_ddt_write(zio_t *zio)
ddt_enter(ddt);
dde = ddt_lookup(ddt, bp, B_TRUE);
+ if (dde == NULL) {
+ /* DDT size is over its quota so no new entries */
+ zp->zp_dedup = B_FALSE;
+ BP_SET_DEDUP(bp, B_FALSE);
+ if (zio->io_bp_override == NULL)
+ zio->io_pipeline = ZIO_WRITE_PIPELINE;
+ ddt_exit(ddt);
+ return (zio);
+ }
ddp = &dde->dde_phys[p];
if (zp->zp_dedup_verify && zio_ddt_collision(zio, ddt, dde)) {
@@ -3628,8 +3653,7 @@ zio_dva_throttle(zio_t *zio)
metaslab_class_t *mc;
/* locate an appropriate allocation class */
- mc = spa_preferred_class(spa, zio->io_size, zio->io_prop.zp_type,
- zio->io_prop.zp_level, zio->io_prop.zp_zpl_smallblk);
+ mc = spa_preferred_class(spa, zio);
if (zio->io_priority == ZIO_PRIORITY_SYNC_WRITE ||
!mc->mc_alloc_throttle_enabled ||
@@ -3701,9 +3725,7 @@ zio_dva_allocate(zio_t *zio)
*/
mc = zio->io_metaslab_class;
if (mc == NULL) {
- mc = spa_preferred_class(spa, zio->io_size,
- zio->io_prop.zp_type, zio->io_prop.zp_level,
- zio->io_prop.zp_zpl_smallblk);
+ mc = spa_preferred_class(spa, zio);
zio->io_metaslab_class = mc;
}
@@ -3728,6 +3750,26 @@ zio_dva_allocate(zio_t *zio)
*/
if (error == ENOSPC && mc != spa_normal_class(spa)) {
/*
+ * When the dedup or special class is spilling into the normal
+ * class, there can still be significant space available due
+ * to deferred frees that are in-flight. We track the txg when
+ * this occurred and back off adding new DDT entries for a few
+ * txgs to allow the free blocks to be processed.
+ */
+ if ((mc == spa_dedup_class(spa) || (spa_special_has_ddt(spa) &&
+ mc == spa_special_class(spa))) &&
+ spa->spa_dedup_class_full_txg != zio->io_txg) {
+ spa->spa_dedup_class_full_txg = zio->io_txg;
+ zfs_dbgmsg("%s[%d]: %s class spilling, req size %d, "
+ "%llu allocated of %llu",
+ spa_name(spa), (int)zio->io_txg,
+ mc == spa_dedup_class(spa) ? "dedup" : "special",
+ (int)zio->io_size,
+ (u_longlong_t)metaslab_class_get_alloc(mc),
+ (u_longlong_t)metaslab_class_get_space(mc));
+ }
+
+ /*
* If throttling, transfer reservation over to normal class.
* The io_allocator slot can remain the same even though we
* are switching classes.
diff --git a/sys/contrib/openzfs/module/zfs/zio_compress.c b/sys/contrib/openzfs/module/zfs/zio_compress.c
index c8a10db7483b..e12d5498ccda 100644
--- a/sys/contrib/openzfs/module/zfs/zio_compress.c
+++ b/sys/contrib/openzfs/module/zfs/zio_compress.c
@@ -111,19 +111,6 @@ zio_compress_select(spa_t *spa, enum zio_compress child,
return (result);
}
-static int
-zio_compress_zeroed_cb(void *data, size_t len, void *private)
-{
- (void) private;
-
- uint64_t *end = (uint64_t *)((char *)data + len);
- for (uint64_t *word = (uint64_t *)data; word < end; word++)
- if (*word != 0)
- return (1);
-
- return (0);
-}
-
size_t
zio_compress_data(enum zio_compress c, abd_t *src, void **dst, size_t s_len,
uint8_t level)
@@ -132,18 +119,9 @@ zio_compress_data(enum zio_compress c, abd_t *src, void **dst, size_t s_len,
uint8_t complevel;
zio_compress_info_t *ci = &zio_compress_table[c];
- ASSERT((uint_t)c < ZIO_COMPRESS_FUNCTIONS);
- ASSERT((uint_t)c == ZIO_COMPRESS_EMPTY || ci->ci_compress != NULL);
-
- /*
- * If the data is all zeroes, we don't even need to allocate
- * a block for it. We indicate this by returning zero size.
- */
- if (abd_iterate_func(src, 0, s_len, zio_compress_zeroed_cb, NULL) == 0)
- return (0);
-
- if (c == ZIO_COMPRESS_EMPTY)
- return (s_len);
+ ASSERT3U(c, <, ZIO_COMPRESS_FUNCTIONS);
+ ASSERT3U(ci->ci_compress, !=, NULL);
+ ASSERT3U(s_len, >, 0);
/* Compress at least 12.5% */
d_len = s_len - (s_len >> 3);
diff --git a/sys/contrib/openzfs/module/zfs/zvol.c b/sys/contrib/openzfs/module/zfs/zvol.c
index 5b6a3f5cb410..001f774a6d16 100644
--- a/sys/contrib/openzfs/module/zfs/zvol.c
+++ b/sys/contrib/openzfs/module/zfs/zvol.c
@@ -37,6 +37,7 @@
* Copyright 2014 Nexenta Systems, Inc. All rights reserved.
* Copyright (c) 2016 Actifio, Inc. All rights reserved.
* Copyright (c) 2012, 2019 by Delphix. All rights reserved.
+ * Copyright (c) 2024, Klara, Inc.
*/
/*
@@ -894,6 +895,9 @@ zvol_resume(zvol_state_t *zv)
*/
atomic_dec(&zv->zv_suspend_ref);
+ if (zv->zv_flags & ZVOL_REMOVING)
+ cv_broadcast(&zv->zv_removing_cv);
+
return (SET_ERROR(error));
}
@@ -929,6 +933,9 @@ zvol_last_close(zvol_state_t *zv)
ASSERT(RW_READ_HELD(&zv->zv_suspend_lock));
ASSERT(MUTEX_HELD(&zv->zv_state_lock));
+ if (zv->zv_flags & ZVOL_REMOVING)
+ cv_broadcast(&zv->zv_removing_cv);
+
zvol_shutdown_zv(zv);
dmu_objset_disown(zv->zv_objset, 1, zv);
@@ -1221,6 +1228,41 @@ zvol_create_minor(const char *name)
* Remove minors for specified dataset including children and snapshots.
*/
+/*
+ * Remove the minor for a given zvol. This will do it all:
+ * - flag the zvol for removal, so new requests are rejected
+ * - wait until outstanding requests are completed
+ * - remove it from lists
+ * - free it
+ * It's also usable as a taskq task, and smells nice too.
+ */
+static void
+zvol_remove_minor_task(void *arg)
+{
+ zvol_state_t *zv = (zvol_state_t *)arg;
+
+ ASSERT(!RW_LOCK_HELD(&zvol_state_lock));
+ ASSERT(!MUTEX_HELD(&zv->zv_state_lock));
+
+ mutex_enter(&zv->zv_state_lock);
+ while (zv->zv_open_count > 0 || atomic_read(&zv->zv_suspend_ref)) {
+ zv->zv_flags |= ZVOL_REMOVING;
+ cv_wait(&zv->zv_removing_cv, &zv->zv_state_lock);
+ }
+ mutex_exit(&zv->zv_state_lock);
+
+ rw_enter(&zvol_state_lock, RW_WRITER);
+ mutex_enter(&zv->zv_state_lock);
+
+ zvol_remove(zv);
+ zvol_os_clear_private(zv);
+
+ mutex_exit(&zv->zv_state_lock);
+ rw_exit(&zvol_state_lock);
+
+ zvol_os_free(zv);
+}
+
static void
zvol_free_task(void *arg)
{
@@ -1233,11 +1275,13 @@ zvol_remove_minors_impl(const char *name)
zvol_state_t *zv, *zv_next;
int namelen = ((name) ? strlen(name) : 0);
taskqid_t t;
- list_t free_list;
+ list_t delay_list, free_list;
if (zvol_inhibit_dev)
return;
+ list_create(&delay_list, sizeof (zvol_state_t),
+ offsetof(zvol_state_t, zv_next));
list_create(&free_list, sizeof (zvol_state_t),
offsetof(zvol_state_t, zv_next));
@@ -1256,9 +1300,24 @@ zvol_remove_minors_impl(const char *name)
* one is currently using this zv
*/
- /* If in use, leave alone */
+ /*
+ * If in use, try to throw everyone off and try again
+ * later.
+ */
if (zv->zv_open_count > 0 ||
atomic_read(&zv->zv_suspend_ref)) {
+ zv->zv_flags |= ZVOL_REMOVING;
+ t = taskq_dispatch(
+ zv->zv_objset->os_spa->spa_zvol_taskq,
+ zvol_remove_minor_task, zv, TQ_SLEEP);
+ if (t == TASKQID_INVALID) {
+ /*
+ * Couldn't create the task, so we'll
+ * do it in place once the loop is
+ * finished.
+ */
+ list_insert_head(&delay_list, zv);
+ }
mutex_exit(&zv->zv_state_lock);
continue;
}
@@ -1285,7 +1344,11 @@ zvol_remove_minors_impl(const char *name)
}
rw_exit(&zvol_state_lock);
- /* Drop zvol_state_lock before calling zvol_free() */
+ /* Wait for zvols that we couldn't create a remove task for */
+ while ((zv = list_remove_head(&delay_list)) != NULL)
+ zvol_remove_minor_task(zv);
+
+ /* Free any that we couldn't free in parallel earlier */
while ((zv = list_remove_head(&free_list)) != NULL)
zvol_os_free(zv);
}
@@ -1305,33 +1368,38 @@ zvol_remove_minor_impl(const char *name)
zv_next = list_next(&zvol_state_list, zv);
mutex_enter(&zv->zv_state_lock);
- if (strcmp(zv->zv_name, name) == 0) {
- /*
- * By holding zv_state_lock here, we guarantee that no
- * one is currently using this zv
- */
+ if (strcmp(zv->zv_name, name) == 0)
+ /* Found, leave the the loop with zv_lock held */
+ break;
+ mutex_exit(&zv->zv_state_lock);
+ }
- /* If in use, leave alone */
- if (zv->zv_open_count > 0 ||
- atomic_read(&zv->zv_suspend_ref)) {
- mutex_exit(&zv->zv_state_lock);
- continue;
- }
- zvol_remove(zv);
+ if (zv == NULL) {
+ rw_exit(&zvol_state_lock);
+ return;
+ }
- zvol_os_clear_private(zv);
- mutex_exit(&zv->zv_state_lock);
- break;
- } else {
- mutex_exit(&zv->zv_state_lock);
- }
+ ASSERT(MUTEX_HELD(&zv->zv_state_lock));
+
+ if (zv->zv_open_count > 0 || atomic_read(&zv->zv_suspend_ref)) {
+ /*
+ * In use, so try to throw everyone off, then wait
+ * until finished.
+ */
+ zv->zv_flags |= ZVOL_REMOVING;
+ mutex_exit(&zv->zv_state_lock);
+ rw_exit(&zvol_state_lock);
+ zvol_remove_minor_task(zv);
+ return;
}
- /* Drop zvol_state_lock before calling zvol_free() */
+ zvol_remove(zv);
+ zvol_os_clear_private(zv);
+
+ mutex_exit(&zv->zv_state_lock);
rw_exit(&zvol_state_lock);
- if (zv != NULL)
- zvol_os_free(zv);
+ zvol_os_free(zv);
}
/*