aboutsummaryrefslogtreecommitdiff
path: root/sys/contrib/openzfs/module
diff options
context:
space:
mode:
Diffstat (limited to 'sys/contrib/openzfs/module')
-rw-r--r--sys/contrib/openzfs/module/os/freebsd/zfs/zfs_vfsops.c8
-rw-r--r--sys/contrib/openzfs/module/os/freebsd/zfs/zfs_vnops_os.c4
-rw-r--r--sys/contrib/openzfs/module/os/freebsd/zfs/zfs_znode_os.c43
-rw-r--r--sys/contrib/openzfs/module/os/freebsd/zfs/zvol_os.c84
-rw-r--r--sys/contrib/openzfs/module/os/linux/spl/spl-zone.c413
-rw-r--r--sys/contrib/openzfs/module/os/linux/zfs/spa_misc_os.c50
-rw-r--r--sys/contrib/openzfs/module/os/linux/zfs/zfs_ctldir.c57
-rw-r--r--sys/contrib/openzfs/module/os/linux/zfs/zfs_ioctl_os.c4
-rw-r--r--sys/contrib/openzfs/module/os/linux/zfs/zfs_vfsops.c240
-rw-r--r--sys/contrib/openzfs/module/os/linux/zfs/zpl_file.c39
-rw-r--r--sys/contrib/openzfs/module/os/linux/zfs/zpl_super.c509
-rw-r--r--sys/contrib/openzfs/module/os/linux/zfs/zvol_os.c2
-rw-r--r--sys/contrib/openzfs/module/zcommon/zfeature_common.c13
-rw-r--r--sys/contrib/openzfs/module/zcommon/zfs_prop.c15
-rw-r--r--sys/contrib/openzfs/module/zcommon/zpool_prop.c6
-rw-r--r--sys/contrib/openzfs/module/zfs/abd.c3
-rw-r--r--sys/contrib/openzfs/module/zfs/dbuf.c78
-rw-r--r--sys/contrib/openzfs/module/zfs/ddt_log.c3
-rw-r--r--sys/contrib/openzfs/module/zfs/dmu.c79
-rw-r--r--sys/contrib/openzfs/module/zfs/dmu_zfetch.c69
-rw-r--r--sys/contrib/openzfs/module/zfs/dnode.c2
-rw-r--r--sys/contrib/openzfs/module/zfs/dnode_sync.c105
-rw-r--r--sys/contrib/openzfs/module/zfs/dsl_crypt.c15
-rw-r--r--sys/contrib/openzfs/module/zfs/dsl_deleg.c13
-rw-r--r--sys/contrib/openzfs/module/zfs/spa.c58
-rw-r--r--sys/contrib/openzfs/module/zfs/spa_errlog.c2
-rw-r--r--sys/contrib/openzfs/module/zfs/spa_log_spacemap.c8
-rw-r--r--sys/contrib/openzfs/module/zfs/space_map.c3
-rw-r--r--sys/contrib/openzfs/module/zfs/vdev.c68
-rw-r--r--sys/contrib/openzfs/module/zfs/vdev_draid.c423
-rw-r--r--sys/contrib/openzfs/module/zfs/vdev_label.c23
-rw-r--r--sys/contrib/openzfs/module/zfs/vdev_mirror.c13
-rw-r--r--sys/contrib/openzfs/module/zfs/vdev_raidz.c65
-rw-r--r--sys/contrib/openzfs/module/zfs/zfs_ioctl.c318
-rw-r--r--sys/contrib/openzfs/module/zfs/zio.c6
-rw-r--r--sys/contrib/openzfs/module/zfs/zvol.c51
36 files changed, 2372 insertions, 520 deletions
diff --git a/sys/contrib/openzfs/module/os/freebsd/zfs/zfs_vfsops.c b/sys/contrib/openzfs/module/os/freebsd/zfs/zfs_vfsops.c
index 79b784288911..7f9c02678229 100644
--- a/sys/contrib/openzfs/module/os/freebsd/zfs/zfs_vfsops.c
+++ b/sys/contrib/openzfs/module/os/freebsd/zfs/zfs_vfsops.c
@@ -495,6 +495,12 @@ atime_changed_cb(void *arg, uint64_t newval)
}
static void
+relatime_changed_cb(void *arg, uint64_t newval)
+{
+ ((zfsvfs_t *)arg)->z_relatime = (newval != 0);
+}
+
+static void
xattr_changed_cb(void *arg, uint64_t newval)
{
zfsvfs_t *zfsvfs = arg;
@@ -753,6 +759,8 @@ zfs_register_callbacks(vfs_t *vfsp)
error = dsl_prop_register(ds,
zfs_prop_to_name(ZFS_PROP_ATIME), atime_changed_cb, zfsvfs);
error = error ? error : dsl_prop_register(ds,
+ zfs_prop_to_name(ZFS_PROP_RELATIME), relatime_changed_cb, zfsvfs);
+ error = error ? error : dsl_prop_register(ds,
zfs_prop_to_name(ZFS_PROP_XATTR), xattr_changed_cb, zfsvfs);
error = error ? error : dsl_prop_register(ds,
zfs_prop_to_name(ZFS_PROP_RECORDSIZE), blksz_changed_cb, zfsvfs);
diff --git a/sys/contrib/openzfs/module/os/freebsd/zfs/zfs_vnops_os.c b/sys/contrib/openzfs/module/os/freebsd/zfs/zfs_vnops_os.c
index 1b3eeb4353fe..6e52d90e0940 100644
--- a/sys/contrib/openzfs/module/os/freebsd/zfs/zfs_vnops_os.c
+++ b/sys/contrib/openzfs/module/os/freebsd/zfs/zfs_vnops_os.c
@@ -6767,10 +6767,12 @@ zfs_freebsd_advise(struct vop_advise_args *ap)
dmu_prefetch(os, zp->z_id, 0, start, len,
ZIO_PRIORITY_ASYNC_READ);
break;
+ case POSIX_FADV_DONTNEED:
+ dmu_evict_range(os, zp->z_id, start, len);
+ break;
case POSIX_FADV_NORMAL:
case POSIX_FADV_RANDOM:
case POSIX_FADV_SEQUENTIAL:
- case POSIX_FADV_DONTNEED:
case POSIX_FADV_NOREUSE:
/* ignored for now */
break;
diff --git a/sys/contrib/openzfs/module/os/freebsd/zfs/zfs_znode_os.c b/sys/contrib/openzfs/module/os/freebsd/zfs/zfs_znode_os.c
index b9f427b39705..6f74c924eb79 100644
--- a/sys/contrib/openzfs/module/os/freebsd/zfs/zfs_znode_os.c
+++ b/sys/contrib/openzfs/module/os/freebsd/zfs/zfs_znode_os.c
@@ -1325,6 +1325,49 @@ zfs_znode_free(znode_t *zp)
zfs_znode_free_kmem(zp);
}
+/*
+ * Determine whether the znode's atime must be updated. The logic mostly
+ * duplicates the Linux kernel's relatime_need_update() functionality.
+ * This function is only called if the underlying filesystem actually has
+ * atime updates enabled.
+ */
+boolean_t
+zfs_relatime_need_update(const znode_t *zp)
+{
+ uint64_t mtime[2], ctime[2];
+ sa_bulk_attr_t bulk[2];
+ zfsvfs_t *zfsvfs = zp->z_zfsvfs;
+ struct timespec now, tmp_atime, tmp_ts;
+ int count = 0;
+
+ 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);
+ if (sa_bulk_lookup(zp->z_sa_hdl, bulk, count) != 0)
+ return (B_TRUE);
+
+ ZFS_TIME_DECODE(&tmp_atime, zp->z_atime);
+ /*
+ * In relatime mode, only update the atime if the previous atime
+ * is earlier than either the ctime or mtime or if at least a day
+ * has passed since the last update of atime.
+ */
+ ZFS_TIME_DECODE(&tmp_ts, mtime);
+ /* CSTYLED */
+ if (timespeccmp(&tmp_ts, &tmp_atime, >=))
+ return (B_TRUE);
+
+ ZFS_TIME_DECODE(&tmp_ts, ctime);
+ /* CSTYLED */
+ if (timespeccmp(&tmp_ts, &tmp_atime, >=))
+ return (B_TRUE);
+
+ vfs_timestamp(&now);
+ if ((hrtime_t)now.tv_sec - (hrtime_t)tmp_atime.tv_sec >= 24*60*60)
+ return (B_TRUE);
+
+ return (B_FALSE);
+}
+
void
zfs_tstamp_update_setup_ext(znode_t *zp, uint_t flag, uint64_t mtime[2],
uint64_t ctime[2], boolean_t have_tx)
diff --git a/sys/contrib/openzfs/module/os/freebsd/zfs/zvol_os.c b/sys/contrib/openzfs/module/os/freebsd/zfs/zvol_os.c
index dc30f6dd939c..5be153a90ec5 100644
--- a/sys/contrib/openzfs/module/os/freebsd/zfs/zvol_os.c
+++ b/sys/contrib/openzfs/module/os/freebsd/zfs/zvol_os.c
@@ -128,7 +128,8 @@ struct zvol_state_os {
struct g_provider *zsg_provider;
} _zso_geom;
} _zso_state;
- int zso_dying;
+ boolean_t zso_opening;
+ boolean_t zso_dying;
};
static uint32_t zvol_minors;
@@ -226,12 +227,13 @@ zvol_geom_open(struct g_provider *pp, int flag, int count)
}
retry:
- zv = atomic_load_ptr(&pp->private);
+ zv = pp->private;
if (zv == NULL)
return (SET_ERROR(ENXIO));
mutex_enter(&zv->zv_state_lock);
- if (zv->zv_zso->zso_dying || zv->zv_flags & ZVOL_REMOVING) {
+ g_topology_unlock();
+ if (zv->zv_flags & ZVOL_REMOVING || zv->zv_zso->zso_dying) {
err = SET_ERROR(ENXIO);
goto out_locked;
}
@@ -245,18 +247,16 @@ retry:
if (zv->zv_open_count == 0) {
drop_suspend = B_TRUE;
if (!rw_tryenter(&zv->zv_suspend_lock, ZVOL_RW_READER)) {
- mutex_exit(&zv->zv_state_lock);
-
/*
- * Removal may happen while the locks are down, so
- * we can't trust zv any longer; we have to start over.
+ * Set a flag to interlock with zvol_os_remove_minor()
+ * while locks are dropped.
*/
- zv = atomic_load_ptr(&pp->private);
- if (zv == NULL)
- return (SET_ERROR(ENXIO));
-
+ zv->zv_zso->zso_opening = B_TRUE;
+ mutex_exit(&zv->zv_state_lock);
rw_enter(&zv->zv_suspend_lock, ZVOL_RW_READER);
mutex_enter(&zv->zv_state_lock);
+ zv->zv_zso->zso_opening = B_FALSE;
+ cv_broadcast(&zv->zv_removing_cv);
if (zv->zv_zso->zso_dying ||
zv->zv_flags & ZVOL_REMOVING) {
@@ -289,6 +289,7 @@ retry:
rw_exit(&zv->zv_suspend_lock);
drop_suspend = B_FALSE;
kern_yield(PRI_USER);
+ g_topology_lock();
goto retry;
} else {
drop_namespace = B_TRUE;
@@ -337,6 +338,7 @@ out_locked:
mutex_exit(&zv->zv_state_lock);
if (drop_suspend)
rw_exit(&zv->zv_suspend_lock);
+ g_topology_lock();
return (err);
}
@@ -348,11 +350,12 @@ zvol_geom_close(struct g_provider *pp, int flag, int count)
boolean_t drop_suspend = B_TRUE;
int new_open_count;
- zv = atomic_load_ptr(&pp->private);
+ zv = pp->private;
if (zv == NULL)
return (SET_ERROR(ENXIO));
mutex_enter(&zv->zv_state_lock);
+ g_topology_unlock();
if (zv->zv_flags & ZVOL_EXCL) {
ASSERT3U(zv->zv_open_count, ==, 1);
zv->zv_flags &= ~ZVOL_EXCL;
@@ -413,6 +416,7 @@ zvol_geom_close(struct g_provider *pp, int flag, int count)
if (drop_suspend)
rw_exit(&zv->zv_suspend_lock);
+ g_topology_lock();
return (0);
}
@@ -448,7 +452,7 @@ zvol_geom_access(struct g_provider *pp, int acr, int acw, int ace)
("Unsupported access request to %s (acr=%d, acw=%d, ace=%d).",
pp->name, acr, acw, ace));
- if (atomic_load_ptr(&pp->private) == NULL) {
+ if (pp->private == NULL) {
if (acr <= 0 && acw <= 0 && ace <= 0)
return (0);
return (pp->error);
@@ -473,24 +477,16 @@ zvol_geom_access(struct g_provider *pp, int acr, int acw, int ace)
if (acw != 0)
flags |= FWRITE;
- g_topology_unlock();
if (count > 0)
error = zvol_geom_open(pp, flags, count);
else
error = zvol_geom_close(pp, flags, -count);
- g_topology_lock();
return (error);
}
static void
zvol_geom_bio_start(struct bio *bp)
{
- zvol_state_t *zv = bp->bio_to->private;
-
- if (zv == NULL) {
- g_io_deliver(bp, ENXIO);
- return;
- }
if (bp->bio_cmd == BIO_GETATTR) {
if (zvol_geom_bio_getattr(bp))
g_io_deliver(bp, EOPNOTSUPP);
@@ -507,7 +503,10 @@ zvol_geom_bio_getattr(struct bio *bp)
zvol_state_t *zv;
zv = bp->bio_to->private;
- ASSERT3P(zv, !=, NULL);
+ if (zv == NULL) {
+ g_io_deliver(bp, ENXIO);
+ return (0);
+ }
spa_t *spa = dmu_objset_spa(zv->zv_objset);
uint64_t refd, avail, usedobjs, availobjs;
@@ -920,7 +919,7 @@ retry:
return (SET_ERROR(ENXIO));
mutex_enter(&zv->zv_state_lock);
- if (zv->zv_zso->zso_dying || zv->zv_flags & ZVOL_REMOVING) {
+ if (zv->zv_flags & ZVOL_REMOVING || zv->zv_zso->zso_dying) {
err = SET_ERROR(ENXIO);
goto out_locked;
}
@@ -1251,24 +1250,32 @@ zvol_os_rename_minor(zvol_state_t *zv, const char *newname)
{
int error = 0;
- ASSERT(RW_LOCK_HELD(&zvol_state_lock));
+ ASSERT(RW_WRITE_HELD(&zvol_state_lock));
ASSERT(MUTEX_HELD(&zv->zv_state_lock));
/* Move to a new hashtable entry. */
zv->zv_hash = zvol_name_hash(newname);
hlist_del(&zv->zv_hlink);
hlist_add_head(&zv->zv_hlink, ZVOL_HT_HEAD(zv->zv_hash));
+ strlcpy(zv->zv_name, newname, sizeof (zv->zv_name));
+ dataset_kstats_rename(&zv->zv_kstat, newname);
if (zv->zv_volmode == ZFS_VOLMODE_GEOM) {
struct zvol_state_geom *zsg = &zv->zv_zso->zso_geom;
- struct g_provider *pp = zsg->zsg_provider;
+ struct g_provider *pp;
struct g_geom *gp;
+ mutex_exit(&zv->zv_state_lock);
g_topology_lock();
+ pp = zsg->zsg_provider;
+ if (pp->private == NULL) {
+ g_topology_unlock();
+ mutex_enter(&zv->zv_state_lock);
+ return (SET_ERROR(ENXIO));
+ }
gp = pp->geom;
ASSERT3P(gp, !=, NULL);
- zsg->zsg_provider = NULL;
g_wither_provider(pp, ENXIO);
pp = g_new_providerf(gp, "%s/%s", ZVOL_DRIVER, newname);
@@ -1278,6 +1285,7 @@ zvol_os_rename_minor(zvol_state_t *zv, const char *newname)
pp->private = zv;
zsg->zsg_provider = pp;
g_error_provider(pp, 0);
+ mutex_enter(&zv->zv_state_lock);
g_topology_unlock();
} else if (zv->zv_volmode == ZFS_VOLMODE_DEV) {
struct zvol_state_dev *zsd = &zv->zv_zso->zso_dev;
@@ -1310,8 +1318,6 @@ zvol_os_rename_minor(zvol_state_t *zv, const char *newname)
zsd->zsd_cdev = dev;
}
}
- strlcpy(zv->zv_name, newname, sizeof (zv->zv_name));
- dataset_kstats_rename(&zv->zv_kstat, newname);
return (error);
}
@@ -1400,27 +1406,32 @@ zvol_alloc(const char *name, uint64_t volsize, uint64_t volblocksize,
void
zvol_os_remove_minor(zvol_state_t *zv)
{
+ struct zvol_state_os *zso = zv->zv_zso;
+
ASSERT(MUTEX_HELD(&zv->zv_state_lock));
ASSERT0(zv->zv_open_count);
ASSERT0(atomic_read(&zv->zv_suspend_ref));
ASSERT(zv->zv_flags & ZVOL_REMOVING);
- struct zvol_state_os *zso = zv->zv_zso;
- zv->zv_zso = NULL;
-
if (zv->zv_volmode == ZFS_VOLMODE_GEOM) {
struct zvol_state_geom *zsg = &zso->zso_geom;
- struct g_provider *pp = zsg->zsg_provider;
- atomic_store_ptr(&pp->private, NULL);
- mutex_exit(&zv->zv_state_lock);
+ struct g_provider *pp;
+ while (zso->zso_opening)
+ cv_wait(&zv->zv_removing_cv, &zv->zv_state_lock);
+ zv->zv_zso = NULL;
+ mutex_exit(&zv->zv_state_lock);
g_topology_lock();
+ pp = zsg->zsg_provider;
+ pp->private = NULL;
g_wither_geom(pp->geom, ENXIO);
g_topology_unlock();
+ g_waitidle(curthread);
} else if (zv->zv_volmode == ZFS_VOLMODE_DEV) {
struct zvol_state_dev *zsd = &zso->zso_dev;
struct cdev *dev = zsd->zsd_cdev;
+ zv->zv_zso = NULL;
if (dev != NULL)
atomic_store_ptr(&dev->si_drv2, NULL);
mutex_exit(&zv->zv_state_lock);
@@ -1545,6 +1556,7 @@ out_dmu_objset_disown:
g_error_provider(zv->zv_zso->zso_geom.zsg_provider, 0);
/* geom was locked inside zvol_alloc() function */
g_topology_unlock();
+ g_waitidle(curthread);
}
out_doi:
kmem_free(doi, sizeof (dmu_object_info_t));
@@ -1565,10 +1577,10 @@ zvol_os_update_volsize(zvol_state_t *zv, uint64_t volsize)
zv->zv_volsize = volsize;
if (zv->zv_volmode == ZFS_VOLMODE_GEOM) {
struct zvol_state_geom *zsg = &zv->zv_zso->zso_geom;
- struct g_provider *pp = zsg->zsg_provider;
+ struct g_provider *pp;
g_topology_lock();
-
+ pp = zsg->zsg_provider;
if (pp->private == NULL) {
g_topology_unlock();
return (SET_ERROR(ENXIO));
diff --git a/sys/contrib/openzfs/module/os/linux/spl/spl-zone.c b/sys/contrib/openzfs/module/os/linux/spl/spl-zone.c
index b2eae5d00b10..5992957280e4 100644
--- a/sys/contrib/openzfs/module/os/linux/spl/spl-zone.c
+++ b/sys/contrib/openzfs/module/os/linux/spl/spl-zone.c
@@ -59,6 +59,18 @@ typedef struct zone_dataset {
char zd_dsname[]; /* name of the member dataset */
} zone_dataset_t;
+/*
+ * UID-based dataset zoning: allows delegating datasets to all user
+ * namespaces owned by a specific UID, enabling rootless container support.
+ */
+typedef struct zone_uid_datasets {
+ struct list_head zuds_list; /* zone_uid_datasets linkage */
+ kuid_t zuds_owner; /* owner UID */
+ struct list_head zuds_datasets; /* datasets for this UID */
+} zone_uid_datasets_t;
+
+static struct list_head zone_uid_datasets;
+
#ifdef CONFIG_USER_NS
/*
@@ -138,6 +150,18 @@ zone_datasets_lookup(unsigned int nsinum)
}
#ifdef CONFIG_USER_NS
+static zone_uid_datasets_t *
+zone_uid_datasets_lookup(kuid_t owner)
+{
+ zone_uid_datasets_t *zuds;
+
+ list_for_each_entry(zuds, &zone_uid_datasets, zuds_list) {
+ if (uid_eq(zuds->zuds_owner, owner))
+ return (zuds);
+ }
+ return (NULL);
+}
+
static struct zone_dataset *
zone_dataset_lookup(zone_datasets_t *zds, const char *dataset, size_t dsnamelen)
{
@@ -232,6 +256,62 @@ zone_dataset_attach(cred_t *cred, const char *dataset, int userns_fd)
EXPORT_SYMBOL(zone_dataset_attach);
int
+zone_dataset_attach_uid(cred_t *cred, const char *dataset, uid_t owner_uid)
+{
+#ifdef CONFIG_USER_NS
+ zone_uid_datasets_t *zuds;
+ zone_dataset_t *zd;
+ int error;
+ size_t dsnamelen;
+ kuid_t kowner;
+
+ /* Only root can attach datasets to UIDs */
+ if ((error = zone_dataset_cred_check(cred)) != 0)
+ return (error);
+ if ((error = zone_dataset_name_check(dataset, &dsnamelen)) != 0)
+ return (error);
+
+ kowner = make_kuid(current_user_ns(), owner_uid);
+ if (!uid_valid(kowner))
+ return (EINVAL);
+
+ mutex_enter(&zone_datasets_lock);
+
+ /* Find or create UID entry */
+ zuds = zone_uid_datasets_lookup(kowner);
+ if (zuds == NULL) {
+ zuds = kmem_alloc(sizeof (zone_uid_datasets_t), KM_SLEEP);
+ INIT_LIST_HEAD(&zuds->zuds_list);
+ INIT_LIST_HEAD(&zuds->zuds_datasets);
+ zuds->zuds_owner = kowner;
+ list_add_tail(&zuds->zuds_list, &zone_uid_datasets);
+ } else {
+ /* Check if dataset already attached */
+ list_for_each_entry(zd, &zuds->zuds_datasets, zd_list) {
+ if (zd->zd_dsnamelen == dsnamelen &&
+ strncmp(zd->zd_dsname, dataset, dsnamelen) == 0) {
+ mutex_exit(&zone_datasets_lock);
+ return (EEXIST);
+ }
+ }
+ }
+
+ /* Add dataset to UID's list */
+ zd = kmem_alloc(sizeof (zone_dataset_t) + dsnamelen + 1, KM_SLEEP);
+ zd->zd_dsnamelen = dsnamelen;
+ strlcpy(zd->zd_dsname, dataset, dsnamelen + 1);
+ INIT_LIST_HEAD(&zd->zd_list);
+ list_add_tail(&zd->zd_list, &zuds->zuds_datasets);
+
+ mutex_exit(&zone_datasets_lock);
+ return (0);
+#else
+ return (ENXIO);
+#endif /* CONFIG_USER_NS */
+}
+EXPORT_SYMBOL(zone_dataset_attach_uid);
+
+int
zone_dataset_detach(cred_t *cred, const char *dataset, int userns_fd)
{
#ifdef CONFIG_USER_NS
@@ -280,6 +360,217 @@ zone_dataset_detach(cred_t *cred, const char *dataset, int userns_fd)
}
EXPORT_SYMBOL(zone_dataset_detach);
+int
+zone_dataset_detach_uid(cred_t *cred, const char *dataset, uid_t owner_uid)
+{
+#ifdef CONFIG_USER_NS
+ zone_uid_datasets_t *zuds;
+ zone_dataset_t *zd;
+ int error;
+ size_t dsnamelen;
+ kuid_t kowner;
+
+ if ((error = zone_dataset_cred_check(cred)) != 0)
+ return (error);
+ if ((error = zone_dataset_name_check(dataset, &dsnamelen)) != 0)
+ return (error);
+
+ kowner = make_kuid(current_user_ns(), owner_uid);
+ if (!uid_valid(kowner))
+ return (EINVAL);
+
+ mutex_enter(&zone_datasets_lock);
+
+ zuds = zone_uid_datasets_lookup(kowner);
+ if (zuds == NULL) {
+ mutex_exit(&zone_datasets_lock);
+ return (ENOENT);
+ }
+
+ /* Find and remove dataset */
+ list_for_each_entry(zd, &zuds->zuds_datasets, zd_list) {
+ if (zd->zd_dsnamelen == dsnamelen &&
+ strncmp(zd->zd_dsname, dataset, dsnamelen) == 0) {
+ list_del(&zd->zd_list);
+ kmem_free(zd, sizeof (*zd) + zd->zd_dsnamelen + 1);
+
+ /* Remove UID entry if no more datasets */
+ if (list_empty(&zuds->zuds_datasets)) {
+ list_del(&zuds->zuds_list);
+ kmem_free(zuds, sizeof (*zuds));
+ }
+
+ mutex_exit(&zone_datasets_lock);
+ return (0);
+ }
+ }
+
+ mutex_exit(&zone_datasets_lock);
+ return (ENOENT);
+#else
+ return (ENXIO);
+#endif /* CONFIG_USER_NS */
+}
+EXPORT_SYMBOL(zone_dataset_detach_uid);
+
+/*
+ * Callback for looking up zoned_uid property (registered by ZFS module).
+ */
+static zone_get_zoned_uid_fn_t zone_get_zoned_uid_fn = NULL;
+
+void
+zone_register_zoned_uid_callback(zone_get_zoned_uid_fn_t fn)
+{
+ zone_get_zoned_uid_fn = fn;
+}
+EXPORT_SYMBOL(zone_register_zoned_uid_callback);
+
+void
+zone_unregister_zoned_uid_callback(void)
+{
+ zone_get_zoned_uid_fn = NULL;
+}
+EXPORT_SYMBOL(zone_unregister_zoned_uid_callback);
+
+#ifdef CONFIG_USER_NS
+/*
+ * Check if a dataset is the delegation root (has zoned_uid set locally).
+ */
+static boolean_t
+zone_dataset_is_zoned_uid_root(const char *dataset, uid_t zoned_uid)
+{
+ char *root;
+ uid_t found_uid;
+ boolean_t is_root;
+
+ if (zone_get_zoned_uid_fn == NULL)
+ return (B_FALSE);
+
+ root = kmem_alloc(MAXPATHLEN, KM_SLEEP);
+ found_uid = zone_get_zoned_uid_fn(dataset, root, MAXPATHLEN);
+ is_root = (found_uid == zoned_uid && strcmp(root, dataset) == 0);
+ kmem_free(root, MAXPATHLEN);
+ return (is_root);
+}
+#endif /* CONFIG_USER_NS */
+
+/*
+ * Core authorization check for zoned_uid write delegation.
+ */
+zone_admin_result_t
+zone_dataset_admin_check(const char *dataset, zone_uid_op_t op,
+ const char *aux_dataset)
+{
+#ifdef CONFIG_USER_NS
+ struct user_namespace *user_ns;
+ char *delegation_root;
+ uid_t zoned_uid, ns_owner_uid;
+ int write_unused;
+ zone_admin_result_t result = ZONE_ADMIN_NOT_APPLICABLE;
+
+ /* Step 1: If in global zone, not applicable */
+ if (INGLOBALZONE(curproc))
+ return (ZONE_ADMIN_NOT_APPLICABLE);
+
+ /* Step 2: Need callback to be registered */
+ if (zone_get_zoned_uid_fn == NULL)
+ return (ZONE_ADMIN_NOT_APPLICABLE);
+
+ delegation_root = kmem_alloc(MAXPATHLEN, KM_SLEEP);
+
+ /* Step 3: Find delegation root */
+ zoned_uid = zone_get_zoned_uid_fn(dataset, delegation_root,
+ MAXPATHLEN);
+ if (zoned_uid == 0)
+ goto out;
+
+ /* Step 4: Verify namespace owner matches */
+ user_ns = current_user_ns();
+ ns_owner_uid = from_kuid(&init_user_ns, user_ns->owner);
+ if (ns_owner_uid != zoned_uid)
+ goto out;
+
+ /* Step 5: Tiered capability check based on operation class */
+ {
+ int required_cap;
+ switch (op) {
+ case ZONE_OP_DESTROY:
+ case ZONE_OP_RENAME:
+ case ZONE_OP_CLONE:
+ required_cap = CAP_SYS_ADMIN;
+ break;
+ case ZONE_OP_CREATE:
+ case ZONE_OP_SNAPSHOT:
+ case ZONE_OP_SETPROP:
+ required_cap = CAP_FOWNER;
+ break;
+ default:
+ required_cap = CAP_SYS_ADMIN;
+ break;
+ }
+ if (!ns_capable(user_ns, required_cap)) {
+ result = ZONE_ADMIN_DENIED;
+ goto out;
+ }
+ }
+
+ /* Step 6: Operation-specific constraints */
+ switch (op) {
+ case ZONE_OP_DESTROY:
+ /* Cannot destroy the delegation root itself */
+ if (zone_dataset_is_zoned_uid_root(dataset, zoned_uid)) {
+ result = ZONE_ADMIN_DENIED;
+ goto out;
+ }
+ break;
+
+ case ZONE_OP_RENAME:
+ /* Cannot rename outside delegation subtree */
+ if (aux_dataset != NULL) {
+ char *dst_root;
+ uid_t dst_uid;
+
+ dst_root = kmem_alloc(MAXPATHLEN, KM_SLEEP);
+ dst_uid = zone_get_zoned_uid_fn(aux_dataset,
+ dst_root, MAXPATHLEN);
+ if (dst_uid != zoned_uid ||
+ strcmp(dst_root, delegation_root) != 0) {
+ kmem_free(dst_root, MAXPATHLEN);
+ result = ZONE_ADMIN_DENIED;
+ goto out;
+ }
+ kmem_free(dst_root, MAXPATHLEN);
+ }
+ break;
+
+ case ZONE_OP_CLONE:
+ /* Clone source must be visible */
+ if (aux_dataset != NULL) {
+ if (!zone_dataset_visible(aux_dataset, &write_unused)) {
+ result = ZONE_ADMIN_DENIED;
+ goto out;
+ }
+ }
+ break;
+
+ case ZONE_OP_CREATE:
+ case ZONE_OP_SNAPSHOT:
+ case ZONE_OP_SETPROP:
+ /* No additional constraints */
+ break;
+ }
+
+ result = ZONE_ADMIN_ALLOWED;
+out:
+ kmem_free(delegation_root, MAXPATHLEN);
+ return (result);
+#else
+ (void) dataset, (void) op, (void) aux_dataset;
+ return (ZONE_ADMIN_NOT_APPLICABLE);
+#endif
+}
+EXPORT_SYMBOL(zone_dataset_admin_check);
+
/*
* A dataset is visible if:
* - It is a parent of a namespace entry.
@@ -293,34 +584,19 @@ EXPORT_SYMBOL(zone_dataset_detach);
* The parent datasets of namespace entries are visible and
* read-only to provide a path back to the root of the pool.
*/
-int
-zone_dataset_visible(const char *dataset, int *write)
+/*
+ * Helper function to check if a dataset matches against a list of
+ * delegated datasets. Returns visibility and sets write permission.
+ */
+static int
+zone_dataset_check_list(struct list_head *datasets, const char *dataset,
+ size_t dsnamelen, int *write)
{
- zone_datasets_t *zds;
zone_dataset_t *zd;
- size_t dsnamelen, zd_len;
- int visible;
+ size_t zd_len;
+ int visible = 0;
- /* Default to read-only, in case visible is returned. */
- if (write != NULL)
- *write = 0;
- if (zone_dataset_name_check(dataset, &dsnamelen) != 0)
- return (0);
- if (INGLOBALZONE(curproc)) {
- if (write != NULL)
- *write = 1;
- return (1);
- }
-
- mutex_enter(&zone_datasets_lock);
- zds = zone_datasets_lookup(crgetzoneid(curproc->cred));
- if (zds == NULL) {
- mutex_exit(&zone_datasets_lock);
- return (0);
- }
-
- visible = 0;
- list_for_each_entry(zd, &zds->zds_datasets, zd_list) {
+ list_for_each_entry(zd, datasets, zd_list) {
zd_len = strlen(zd->zd_dsname);
if (zd_len > dsnamelen) {
/*
@@ -352,7 +628,8 @@ zone_dataset_visible(const char *dataset, int *write)
* the namespace entry.
*/
visible = memcmp(zd->zd_dsname, dataset,
- zd_len) == 0 && dataset[zd_len] == '/';
+ zd_len) == 0 && (dataset[zd_len] == '/' ||
+ dataset[zd_len] == '@' || dataset[zd_len] == '#');
if (visible) {
if (write != NULL)
*write = 1;
@@ -361,9 +638,70 @@ zone_dataset_visible(const char *dataset, int *write)
}
}
- mutex_exit(&zone_datasets_lock);
return (visible);
}
+
+#if defined(CONFIG_USER_NS)
+/*
+ * Check UID-based zoning visibility for the current process.
+ * Must be called with zone_datasets_lock held.
+ */
+static int
+zone_dataset_visible_uid(const char *dataset, size_t dsnamelen, int *write)
+{
+ zone_uid_datasets_t *zuds;
+
+ zuds = zone_uid_datasets_lookup(curproc->cred->user_ns->owner);
+ if (zuds != NULL)
+ return (zone_dataset_check_list(&zuds->zuds_datasets, dataset,
+ dsnamelen, write));
+ return (0);
+}
+#endif
+
+int
+zone_dataset_visible(const char *dataset, int *write)
+{
+ zone_datasets_t *zds;
+ size_t dsnamelen;
+ int visible;
+
+ /* Default to read-only, in case visible is returned. */
+ if (write != NULL)
+ *write = 0;
+ if (zone_dataset_name_check(dataset, &dsnamelen) != 0)
+ return (0);
+ if (INGLOBALZONE(curproc)) {
+ if (write != NULL)
+ *write = 1;
+ return (1);
+ }
+
+ mutex_enter(&zone_datasets_lock);
+
+ /* First, check namespace-specific zoning (existing behavior) */
+ zds = zone_datasets_lookup(crgetzoneid(curproc->cred));
+ if (zds != NULL) {
+ visible = zone_dataset_check_list(&zds->zds_datasets, dataset,
+ dsnamelen, write);
+ if (visible) {
+ mutex_exit(&zone_datasets_lock);
+ return (visible);
+ }
+ }
+
+ /* Second, check UID-based zoning */
+#if defined(CONFIG_USER_NS)
+ visible = zone_dataset_visible_uid(dataset, dsnamelen, write);
+ if (visible) {
+ mutex_exit(&zone_datasets_lock);
+ return (visible);
+ }
+#endif
+
+ mutex_exit(&zone_datasets_lock);
+ return (0);
+}
EXPORT_SYMBOL(zone_dataset_visible);
unsigned int
@@ -395,8 +733,9 @@ EXPORT_SYMBOL(crgetzoneid);
boolean_t
inglobalzone(proc_t *proc)
{
+ (void) proc;
#if defined(CONFIG_USER_NS)
- return (proc->cred->user_ns == &init_user_ns);
+ return (current_user_ns() == &init_user_ns);
#else
return (B_TRUE);
#endif
@@ -408,6 +747,7 @@ spl_zone_init(void)
{
mutex_init(&zone_datasets_lock, NULL, MUTEX_DEFAULT, NULL);
INIT_LIST_HEAD(&zone_datasets);
+ INIT_LIST_HEAD(&zone_uid_datasets);
return (0);
}
@@ -415,6 +755,7 @@ void
spl_zone_fini(void)
{
zone_datasets_t *zds;
+ zone_uid_datasets_t *zuds;
zone_dataset_t *zd;
/*
@@ -423,6 +764,22 @@ spl_zone_fini(void)
* namespace is destroyed, just do it here, since spl is about to go
* out of context.
*/
+
+ /* Clean up UID-based delegations */
+ while (!list_empty(&zone_uid_datasets)) {
+ zuds = list_entry(zone_uid_datasets.next,
+ zone_uid_datasets_t, zuds_list);
+ while (!list_empty(&zuds->zuds_datasets)) {
+ zd = list_entry(zuds->zuds_datasets.next,
+ zone_dataset_t, zd_list);
+ list_del(&zd->zd_list);
+ kmem_free(zd, sizeof (*zd) + zd->zd_dsnamelen + 1);
+ }
+ list_del(&zuds->zuds_list);
+ kmem_free(zuds, sizeof (*zuds));
+ }
+
+ /* Clean up namespace-based delegations */
while (!list_empty(&zone_datasets)) {
zds = list_entry(zone_datasets.next, zone_datasets_t, zds_list);
while (!list_empty(&zds->zds_datasets)) {
diff --git a/sys/contrib/openzfs/module/os/linux/zfs/spa_misc_os.c b/sys/contrib/openzfs/module/os/linux/zfs/spa_misc_os.c
index d6323fd56a8f..91010bdf642a 100644
--- a/sys/contrib/openzfs/module/os/linux/zfs/spa_misc_os.c
+++ b/sys/contrib/openzfs/module/os/linux/zfs/spa_misc_os.c
@@ -39,8 +39,10 @@
#include <sys/dsl_prop.h>
#include <sys/fm/util.h>
#include <sys/dsl_scan.h>
+#include <sys/dmu.h>
#include <sys/fs/zfs.h>
#include <sys/kstat.h>
+#include <sys/zone.h>
#include "zfs_prop.h"
@@ -122,16 +124,60 @@ spa_history_zone(void)
return ("linux");
}
+static int
+spa_restore_zoned_uid_cb(const char *dsname, void *arg)
+{
+ (void) arg;
+ uint64_t zoned_uid = 0;
+
+ if (dsl_prop_get(dsname, "zoned_uid", 8, 1, &zoned_uid, NULL) != 0)
+ return (0);
+
+ if (zoned_uid != 0) {
+ int err = zone_dataset_attach_uid(kcred, dsname,
+ (uid_t)zoned_uid);
+ if (err != 0 && err != EEXIST) {
+ cmn_err(CE_WARN, "failed to restore zoned_uid for "
+ "'%s' (uid %llu): %d", dsname,
+ (unsigned long long)zoned_uid, err);
+ }
+ }
+ return (0);
+}
+
void
spa_import_os(spa_t *spa)
{
- (void) spa;
+ (void) dmu_objset_find(spa_name(spa),
+ spa_restore_zoned_uid_cb, NULL, DS_FIND_CHILDREN);
+}
+
+static int
+spa_cleanup_zoned_uid_cb(const char *dsname, void *arg)
+{
+ (void) arg;
+ uint64_t zoned_uid = 0;
+
+ if (dsl_prop_get(dsname, "zoned_uid", 8, 1, &zoned_uid, NULL) != 0)
+ return (0);
+
+ if (zoned_uid != 0) {
+ int err = zone_dataset_detach_uid(kcred, dsname,
+ (uid_t)zoned_uid);
+ if (err != 0 && err != ENOENT) {
+ cmn_err(CE_WARN, "failed to detach zoned_uid for "
+ "'%s' (uid %llu): %d", dsname,
+ (unsigned long long)zoned_uid, err);
+ }
+ }
+ return (0);
}
void
spa_export_os(spa_t *spa)
{
- (void) spa;
+ (void) dmu_objset_find(spa_name(spa),
+ spa_cleanup_zoned_uid_cb, NULL, DS_FIND_CHILDREN);
}
void
diff --git a/sys/contrib/openzfs/module/os/linux/zfs/zfs_ctldir.c b/sys/contrib/openzfs/module/os/linux/zfs/zfs_ctldir.c
index b1dc50b2d47d..c73ef86df4dc 100644
--- a/sys/contrib/openzfs/module/os/linux/zfs/zfs_ctldir.c
+++ b/sys/contrib/openzfs/module/os/linux/zfs/zfs_ctldir.c
@@ -1187,8 +1187,10 @@ zfsctl_snapshot_mount(struct path *path, int flags)
error = zfsctl_snapshot_name(zfsvfs, dname(dentry),
ZFS_MAX_DATASET_NAME_LEN, full_name);
- if (error)
+ if (error) {
+ zfs_exit(zfsvfs, FTAG);
goto error;
+ }
if (is_current_chrooted() == 0) {
/*
@@ -1206,6 +1208,7 @@ zfsctl_snapshot_mount(struct path *path, int flags)
error = get_root_path(&mnt_path, m, MAXPATHLEN);
if (error != 0) {
kmem_free(m, MAXPATHLEN);
+ zfs_exit(zfsvfs, FTAG);
goto error;
}
mutex_enter(&zfsvfs->z_vfs->vfs_mntpt_lock);
@@ -1239,6 +1242,33 @@ zfsctl_snapshot_mount(struct path *path, int flags)
zfs_snapshot_no_setuid ? "nosuid" : "suid");
/*
+ * Release z_teardown_lock before potentially blocking operations
+ * (cv_wait for concurrent mounts, call_usermodehelper for the mount
+ * helper). Holding z_teardown_lock(R) across call_usermodehelper
+ * deadlocks with namespace_sem: the mount helper needs
+ * namespace_sem(W) via move_mount, while /proc/self/mountinfo
+ * readers hold namespace_sem(R) and need z_teardown_lock(R) via
+ * zpl_show_devname. A concurrent zfs_suspend_fs queuing
+ * z_teardown_lock(W) blocks new readers, completing the cycle.
+ * See https://github.com/openzfs/zfs/issues/18409
+ *
+ * Releasing the lock allows zfs_suspend_fs to proceed during
+ * the mount, so dmu_objset_hold in zpl_get_tree can transiently
+ * fail with ENOENT during the clone swap. The mount helper
+ * fails, this function returns EISDIR, and the VFS silently
+ * falls back to the ctldir stub (empty directory). The caller
+ * gets the stub inode instead of the real snapshot root until
+ * the next access retries the automount.
+ *
+ * Safe because everything below operates on local string copies
+ * (full_name, full_path) or uses its own synchronization
+ * (zfs_snapshot_lock, se_mtx). The parent zfsvfs pointer
+ * remains valid because we hold a path reference to the
+ * automount trigger dentry.
+ */
+ zfs_exit(zfsvfs, FTAG);
+
+ /*
* Check if snapshot is already being mounted. If found, wait for
* pending mount to complete before returning success.
*/
@@ -1352,8 +1382,7 @@ zfsctl_snapshot_mount(struct path *path, int flags)
error:
kmem_free(full_name, ZFS_MAX_DATASET_NAME_LEN);
kmem_free(full_path, MAXPATHLEN);
-
- zfs_exit(zfsvfs, FTAG);
+ kmem_free(options, 7);
return (error);
}
@@ -1365,17 +1394,31 @@ int
zfsctl_snapdir_vget(struct super_block *sb, uint64_t objsetid, int gen,
struct inode **ipp)
{
+ zfsvfs_t *zfsvfs = sb->s_fs_info;
int error;
struct path path;
char *mnt;
struct dentry *dentry;
+ zfs_snapentry_t *se;
mnt = kmem_alloc(MAXPATHLEN, KM_SLEEP);
- error = zfsctl_snapshot_path_objset(sb->s_fs_info, objsetid,
- MAXPATHLEN, mnt);
- if (error)
- goto out;
+ /*
+ * Try the in-memory AVL tree first for previously mounted
+ * snapshots, falling back to the on-disk scan if not found.
+ */
+ rw_enter(&zfs_snapshot_lock, RW_READER);
+ se = zfsctl_snapshot_find_by_objsetid(zfsvfs->z_os->os_spa, objsetid);
+ rw_exit(&zfs_snapshot_lock);
+ if (se != NULL) {
+ strlcpy(mnt, se->se_path, MAXPATHLEN);
+ zfsctl_snapshot_rele(se);
+ } else {
+ error = zfsctl_snapshot_path_objset(zfsvfs, objsetid,
+ MAXPATHLEN, mnt);
+ if (error)
+ goto out;
+ }
/* Trigger automount */
error = -kern_path(mnt, LOOKUP_FOLLOW|LOOKUP_DIRECTORY, &path);
diff --git a/sys/contrib/openzfs/module/os/linux/zfs/zfs_ioctl_os.c b/sys/contrib/openzfs/module/os/linux/zfs/zfs_ioctl_os.c
index 5421a441b323..ce6092be1da7 100644
--- a/sys/contrib/openzfs/module/os/linux/zfs/zfs_ioctl_os.c
+++ b/sys/contrib/openzfs/module/os/linux/zfs/zfs_ioctl_os.c
@@ -170,6 +170,8 @@ zfs_ioc_userns_attach(zfs_cmd_t *zc)
*/
if (error == ENOTTY)
error = ZFS_ERR_NOT_USER_NAMESPACE;
+ if (error == ENXIO)
+ error = ZFS_ERR_NO_USER_NS_SUPPORT;
return (error);
}
@@ -190,6 +192,8 @@ zfs_ioc_userns_detach(zfs_cmd_t *zc)
*/
if (error == ENOTTY)
error = ZFS_ERR_NOT_USER_NAMESPACE;
+ if (error == ENXIO)
+ error = ZFS_ERR_NO_USER_NS_SUPPORT;
return (error);
}
diff --git a/sys/contrib/openzfs/module/os/linux/zfs/zfs_vfsops.c b/sys/contrib/openzfs/module/os/linux/zfs/zfs_vfsops.c
index 8a7d14ab6119..9c0d92551843 100644
--- a/sys/contrib/openzfs/module/os/linux/zfs/zfs_vfsops.c
+++ b/sys/contrib/openzfs/module/os/linux/zfs/zfs_vfsops.c
@@ -22,6 +22,7 @@
/*
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2018 by Delphix. All rights reserved.
+ * Copyright (c) 2026, TrueNAS.
*/
/* Portions Copyright 2010 Robert Milkowski */
@@ -64,53 +65,15 @@
#include <linux/fs.h>
#include "zfs_comutil.h"
-enum {
- TOKEN_RO,
- TOKEN_RW,
- TOKEN_SETUID,
- TOKEN_NOSETUID,
- TOKEN_EXEC,
- TOKEN_NOEXEC,
- TOKEN_DEVICES,
- TOKEN_NODEVICES,
- TOKEN_DIRXATTR,
- TOKEN_SAXATTR,
- TOKEN_XATTR,
- TOKEN_NOXATTR,
- TOKEN_ATIME,
- TOKEN_NOATIME,
- TOKEN_RELATIME,
- TOKEN_NORELATIME,
- TOKEN_NBMAND,
- TOKEN_NONBMAND,
- TOKEN_MNTPOINT,
- TOKEN_LAST,
-};
-
-static const match_table_t zpl_tokens = {
- { TOKEN_RO, MNTOPT_RO },
- { TOKEN_RW, MNTOPT_RW },
- { TOKEN_SETUID, MNTOPT_SETUID },
- { TOKEN_NOSETUID, MNTOPT_NOSETUID },
- { TOKEN_EXEC, MNTOPT_EXEC },
- { TOKEN_NOEXEC, MNTOPT_NOEXEC },
- { TOKEN_DEVICES, MNTOPT_DEVICES },
- { TOKEN_NODEVICES, MNTOPT_NODEVICES },
- { TOKEN_DIRXATTR, MNTOPT_DIRXATTR },
- { TOKEN_SAXATTR, MNTOPT_SAXATTR },
- { TOKEN_XATTR, MNTOPT_XATTR },
- { TOKEN_NOXATTR, MNTOPT_NOXATTR },
- { TOKEN_ATIME, MNTOPT_ATIME },
- { TOKEN_NOATIME, MNTOPT_NOATIME },
- { TOKEN_RELATIME, MNTOPT_RELATIME },
- { TOKEN_NORELATIME, MNTOPT_NORELATIME },
- { TOKEN_NBMAND, MNTOPT_NBMAND },
- { TOKEN_NONBMAND, MNTOPT_NONBMAND },
- { TOKEN_MNTPOINT, MNTOPT_MNTPOINT "=%s" },
- { TOKEN_LAST, NULL },
-};
+vfs_t *
+zfsvfs_vfs_alloc(void)
+{
+ vfs_t *vfsp = kmem_zalloc(sizeof (vfs_t), KM_SLEEP);
+ mutex_init(&vfsp->vfs_mntpt_lock, NULL, MUTEX_DEFAULT, NULL);
+ return (vfsp);
+}
-static void
+void
zfsvfs_vfs_free(vfs_t *vfsp)
{
if (vfsp != NULL) {
@@ -121,139 +84,6 @@ zfsvfs_vfs_free(vfs_t *vfsp)
}
}
-static int
-zfsvfs_parse_option(char *option, int token, substring_t *args, vfs_t *vfsp)
-{
- switch (token) {
- case TOKEN_RO:
- vfsp->vfs_readonly = B_TRUE;
- vfsp->vfs_do_readonly = B_TRUE;
- break;
- case TOKEN_RW:
- vfsp->vfs_readonly = B_FALSE;
- vfsp->vfs_do_readonly = B_TRUE;
- break;
- case TOKEN_SETUID:
- vfsp->vfs_setuid = B_TRUE;
- vfsp->vfs_do_setuid = B_TRUE;
- break;
- case TOKEN_NOSETUID:
- vfsp->vfs_setuid = B_FALSE;
- vfsp->vfs_do_setuid = B_TRUE;
- break;
- case TOKEN_EXEC:
- vfsp->vfs_exec = B_TRUE;
- vfsp->vfs_do_exec = B_TRUE;
- break;
- case TOKEN_NOEXEC:
- vfsp->vfs_exec = B_FALSE;
- vfsp->vfs_do_exec = B_TRUE;
- break;
- case TOKEN_DEVICES:
- vfsp->vfs_devices = B_TRUE;
- vfsp->vfs_do_devices = B_TRUE;
- break;
- case TOKEN_NODEVICES:
- vfsp->vfs_devices = B_FALSE;
- vfsp->vfs_do_devices = B_TRUE;
- break;
- case TOKEN_DIRXATTR:
- vfsp->vfs_xattr = ZFS_XATTR_DIR;
- vfsp->vfs_do_xattr = B_TRUE;
- break;
- case TOKEN_SAXATTR:
- vfsp->vfs_xattr = ZFS_XATTR_SA;
- vfsp->vfs_do_xattr = B_TRUE;
- break;
- case TOKEN_XATTR:
- vfsp->vfs_xattr = ZFS_XATTR_SA;
- vfsp->vfs_do_xattr = B_TRUE;
- break;
- case TOKEN_NOXATTR:
- vfsp->vfs_xattr = ZFS_XATTR_OFF;
- vfsp->vfs_do_xattr = B_TRUE;
- break;
- case TOKEN_ATIME:
- vfsp->vfs_atime = B_TRUE;
- vfsp->vfs_do_atime = B_TRUE;
- break;
- case TOKEN_NOATIME:
- vfsp->vfs_atime = B_FALSE;
- vfsp->vfs_do_atime = B_TRUE;
- break;
- case TOKEN_RELATIME:
- vfsp->vfs_relatime = B_TRUE;
- vfsp->vfs_do_relatime = B_TRUE;
- break;
- case TOKEN_NORELATIME:
- vfsp->vfs_relatime = B_FALSE;
- vfsp->vfs_do_relatime = B_TRUE;
- break;
- case TOKEN_NBMAND:
- vfsp->vfs_nbmand = B_TRUE;
- vfsp->vfs_do_nbmand = B_TRUE;
- break;
- case TOKEN_NONBMAND:
- vfsp->vfs_nbmand = B_FALSE;
- vfsp->vfs_do_nbmand = B_TRUE;
- break;
- case TOKEN_MNTPOINT:
- if (vfsp->vfs_mntpoint != NULL)
- kmem_strfree(vfsp->vfs_mntpoint);
- vfsp->vfs_mntpoint = match_strdup(&args[0]);
- if (vfsp->vfs_mntpoint == NULL)
- return (SET_ERROR(ENOMEM));
- break;
- default:
- break;
- }
-
- return (0);
-}
-
-/*
- * Parse the raw mntopts and return a vfs_t describing the options.
- */
-static int
-zfsvfs_parse_options(char *mntopts, vfs_t **vfsp)
-{
- vfs_t *tmp_vfsp;
- int error;
-
- tmp_vfsp = kmem_zalloc(sizeof (vfs_t), KM_SLEEP);
- mutex_init(&tmp_vfsp->vfs_mntpt_lock, NULL, MUTEX_DEFAULT, NULL);
-
- if (mntopts != NULL) {
- substring_t args[MAX_OPT_ARGS];
- char *tmp_mntopts, *p, *t;
- int token;
-
- tmp_mntopts = t = kmem_strdup(mntopts);
- if (tmp_mntopts == NULL)
- return (SET_ERROR(ENOMEM));
-
- while ((p = strsep(&t, ",")) != NULL) {
- if (!*p)
- continue;
-
- args[0].to = args[0].from = NULL;
- token = match_token(p, zpl_tokens, args);
- error = zfsvfs_parse_option(p, token, args, tmp_vfsp);
- if (error) {
- kmem_strfree(tmp_mntopts);
- zfsvfs_vfs_free(tmp_vfsp);
- return (error);
- }
- }
-
- kmem_strfree(tmp_mntopts);
- }
-
- *vfsp = tmp_vfsp;
-
- return (0);
-}
-
boolean_t
zfs_is_readonly(zfsvfs_t *zfsvfs)
{
@@ -1486,20 +1316,16 @@ zfsvfs_teardown(zfsvfs_t *zfsvfs, boolean_t unmounting)
static atomic_long_t zfs_bdi_seq = ATOMIC_LONG_INIT(0);
int
-zfs_domount(struct super_block *sb, zfs_mnt_t *zm, int silent)
+zfs_domount(struct super_block *sb, const char *osname,
+ vfs_t *vfs, int silent)
{
- const char *osname = zm->mnt_osname;
struct inode *root_inode = NULL;
uint64_t recordsize;
int error = 0;
zfsvfs_t *zfsvfs = NULL;
- vfs_t *vfs = NULL;
int canwrite;
int dataset_visible_zone;
- ASSERT(zm);
- ASSERT(osname);
-
dataset_visible_zone = zone_dataset_visible(osname, &canwrite);
/*
@@ -1511,10 +1337,6 @@ zfs_domount(struct super_block *sb, zfs_mnt_t *zm, int silent)
return (SET_ERROR(EPERM));
}
- error = zfsvfs_parse_options(zm->mnt_data, &vfs);
- if (error)
- return (error);
-
/*
* If a non-writable filesystem is being mounted without the
* read-only flag, pretend it was set, as done for snapshots.
@@ -1523,16 +1345,12 @@ zfs_domount(struct super_block *sb, zfs_mnt_t *zm, int silent)
vfs->vfs_readonly = B_TRUE;
error = zfsvfs_create(osname, vfs->vfs_readonly, &zfsvfs);
- if (error) {
- zfsvfs_vfs_free(vfs);
+ if (error)
goto out;
- }
if ((error = dsl_prop_get_integer(osname, "recordsize",
- &recordsize, NULL))) {
- zfsvfs_vfs_free(vfs);
+ &recordsize, NULL)))
goto out;
- }
vfs->vfs_data = zfsvfs;
zfsvfs->z_vfs = vfs;
@@ -1614,6 +1432,13 @@ zfs_domount(struct super_block *sb, zfs_mnt_t *zm, int silent)
out:
if (error) {
if (zfsvfs != NULL) {
+ /*
+ * We're returning error, so the caller still owns
+ * the mount options vfs_t. Remove them from zfsvfs
+ * so we don't try to free them.
+ */
+ zfsvfs->z_vfs = NULL;
+
dmu_objset_disown(zfsvfs->z_os, B_TRUE, zfsvfs);
zfsvfs_free(zfsvfs);
}
@@ -1704,24 +1529,16 @@ zfs_umount(struct super_block *sb)
}
int
-zfs_remount(struct super_block *sb, int *flags, zfs_mnt_t *zm)
+zfs_remount(struct super_block *sb, vfs_t *vfsp, int flags)
{
zfsvfs_t *zfsvfs = sb->s_fs_info;
- vfs_t *vfsp;
boolean_t issnap = dmu_objset_is_snapshot(zfsvfs->z_os);
- int error;
if ((issnap || !spa_writeable(dmu_objset_spa(zfsvfs->z_os))) &&
- !(*flags & SB_RDONLY)) {
- *flags |= SB_RDONLY;
+ !(flags & SB_RDONLY))
return (EROFS);
- }
- error = zfsvfs_parse_options(zm->mnt_data, &vfsp);
- if (error)
- return (error);
-
- if (!zfs_is_readonly(zfsvfs) && (*flags & SB_RDONLY))
+ if (!zfs_is_readonly(zfsvfs) && (flags & SB_RDONLY))
txg_wait_synced(dmu_objset_pool(zfsvfs->z_os), 0);
zfs_unregister_callbacks(zfsvfs);
@@ -1732,7 +1549,7 @@ zfs_remount(struct super_block *sb, int *flags, zfs_mnt_t *zm)
if (!issnap)
(void) zfs_register_callbacks(vfsp);
- return (error);
+ return (0);
}
int
@@ -1963,15 +1780,6 @@ bail:
/* release the VFS ops */
rw_exit(&zfsvfs->z_teardown_inactive_lock);
ZFS_TEARDOWN_EXIT(zfsvfs, FTAG);
-
- if (err != 0) {
- /*
- * Since we couldn't setup the sa framework, try to force
- * unmount this file system.
- */
- if (zfsvfs->z_os)
- (void) zfs_umount(zfsvfs->z_sb);
- }
return (err);
}
diff --git a/sys/contrib/openzfs/module/os/linux/zfs/zpl_file.c b/sys/contrib/openzfs/module/os/linux/zfs/zpl_file.c
index efcb400f196e..ffe227796f0a 100644
--- a/sys/contrib/openzfs/module/os/linux/zfs/zpl_file.c
+++ b/sys/contrib/openzfs/module/os/linux/zfs/zpl_file.c
@@ -779,34 +779,23 @@ zpl_fadvise(struct file *filp, loff_t offset, loff_t len, int advice)
if ((error = zpl_enter_verify_zp(zfsvfs, zp, FTAG)) != 0)
return (error);
- switch (advice) {
- case POSIX_FADV_SEQUENTIAL:
- case POSIX_FADV_WILLNEED:
+ if (advice == POSIX_FADV_WILLNEED) {
+ loff_t rlen = len ? len : i_size_read(ip) - offset;
+ dmu_prefetch(os, zp->z_id, 0, offset, rlen,
+ ZIO_PRIORITY_ASYNC_READ);
+ if (!zn_has_cached_data(zp, offset, offset + rlen - 1)) {
+ zfs_exit(zfsvfs, FTAG);
+ return (error);
+ }
+ }
+
#ifdef HAVE_GENERIC_FADVISE
- if (zn_has_cached_data(zp, offset, offset + len - 1))
- error = generic_fadvise(filp, offset, len, advice);
+ error = generic_fadvise(filp, offset, len, advice);
#endif
- /*
- * Pass on the caller's size directly, but note that
- * dmu_prefetch_max will effectively cap it. If there
- * really is a larger sequential access pattern, perhaps
- * dmu_zfetch will detect it.
- */
- if (len == 0)
- len = i_size_read(ip) - offset;
- dmu_prefetch(os, zp->z_id, 0, offset, len,
- ZIO_PRIORITY_ASYNC_READ);
- break;
- case POSIX_FADV_NORMAL:
- case POSIX_FADV_RANDOM:
- case POSIX_FADV_DONTNEED:
- case POSIX_FADV_NOREUSE:
- /* ignored for now */
- break;
- default:
- error = -EINVAL;
- break;
+ if (error == 0 && advice == POSIX_FADV_DONTNEED) {
+ loff_t rlen = len ? len : i_size_read(ip) - offset;
+ dmu_evict_range(os, zp->z_id, offset, rlen);
}
zfs_exit(zfsvfs, FTAG);
diff --git a/sys/contrib/openzfs/module/os/linux/zfs/zpl_super.c b/sys/contrib/openzfs/module/os/linux/zfs/zpl_super.c
index a970959531a3..2cd0f17c860f 100644
--- a/sys/contrib/openzfs/module/os/linux/zfs/zpl_super.c
+++ b/sys/contrib/openzfs/module/os/linux/zfs/zpl_super.c
@@ -37,6 +37,7 @@
#include <linux/version.h>
#include <linux/vfs_compat.h>
#include <linux/fs_context.h>
+#include <linux/fs_parser.h>
/*
* What to do when the last reference to an inode is released. If 0, the kernel
@@ -390,16 +391,430 @@ zpl_prune_sb(uint64_t nr_to_scan, void *arg)
#endif
}
+/*
+ * Mount option parsing.
+ *
+ * The kernel receives a set of "stringy" mount options, typically a
+ * comma-separated list through mount(2) or fsconfig(2). These are split into a
+ * set of struct fs_parameter, and then vfs_parse_fs_param() is called for
+ * each. That function will handle (and consume) some options directly, and
+ * other subsystems (mainly security modules) are given the opportunity to
+ * consume them too. Any left over are passed to zpl_parse_param(). Our job is
+ * to use them to fill in the vfs_t we've attached previously to
+ * fc->fs_private, ready for the mount or remount call when it comes.
+ *
+ * Historically, mount options have been generated, removed, modified and
+ * otherwise complicated by multiple different actors over a long time: the
+ * kernel itself, the original mount(8) utility and later libmount,
+ * mount.zfs(8), libzfs and the ZFS tools that use it, and any program using
+ * the various mount APIs that have come and gone over the years. This is
+ * further complicated by cross-pollination between OpenSolaris/illumos, Linux
+ * and FreeBSD. Long story short: we could see all sorts of things, and we need
+ * to at least try not to break old userspace programs.
+ *
+ * At time of writing, this is my best understanding of all the options we
+ * might reasonably see, and where and how they're handled.
+ *
+ *
+ * These are common options for all filesystems that are processed by the
+ * kernel directly, without zpl_parse_param() being called. They're a bit of a
+ * mixed bag, but are ultimately all available to us via either sb->s_flags or
+ * fc->sb_flags:
+ *
+ * dirsync: set SB_DIRSYNC
+ * lazytime: set SB_LAZYTIME
+ * mand: set SB_MANDLOCK
+ * ro: set SB_RDONLY
+ * sync: set SB_SYNCHRONOUS
+ *
+ * async: clear SB_SYNCHRONOUS
+ * nolazytime: clear SB_LAZYTIME
+ * nomand: clear SB_MANDLOCK
+ * rw: clear SB_RDONLY
+ *
+ * Fortunately, almost all of these are handled directly by the kernel. 'mand'
+ * and 'nomand' are swallowed by the kernel ('mand' emits a warning in the
+ * kernel log), but it and the corresponding dataset property have been a no-op
+ * in OpenZFS for years, so there's nothing for us to do there.
+ *
+ * The only tricky one is SB_RDONLY ('ro'/'rw'), which can be both a mount and
+ * a superblock option. While we won't receive the "stringy" options, the
+ * kernel will set it for us in fc->sb_flags, and we've always had special
+ * handling for it at mount and remount time (eg handling snapshot mounts), so
+ * it's not a problem to do nothing here because we will sort it out later.
+ *
+ *
+ * These are options that we may receive as "stringy" options but also as mount
+ * flags.
+ *
+ * exec: clear MS_NOEXEC
+ * noexec: set MS_NOEXEC
+ * suid: clear MS_NOSUID
+ * nosuid: set MS_NOSUID
+ * dev: clear MS_NODEV
+ * nodev: set MS_NODEV
+ * atime: clear MS_NOATIME
+ * noatime: set MS_NOATIME
+ * relatime: set MS_RELATIME
+ * norelatime: clear MS_RELATIME
+ *
+ * In testing, it appears that recent libmount will convert them, but our own
+ * mount code (libzfs_mount) may not. We will be called for the stringy
+ * versions, but not for the flags. The flags will later be available on
+ * vfsmount->mnt_flags, not set on the vfs_t. This tends not to matter in
+ * practice, as almost all mounts come through libzfs (via zfs-mount(8) or
+ * mount.zfs(8)) and so as strings, and when they do come through flags, they
+ * will still be reported correctly via mountinfo and by zfs-get(8), which has
+ * special handling for "temporary" properties. Also, we never use these
+ * internally for any decisions; 'exec', 'suid' and 'dev' are handled in the
+ * kernel, and the kernel provides helpers for 'atime' and 'relatime'. The
+ * only place the difference is observable is through zfs_get_temporary_prop(),
+ * which is only used by the zfs.get_prop() Lua call.
+ *
+ * This is fixable by getting at vfsmount->mnt_flags, but this is not readily
+ * available until after the mount operation is completed, and with some
+ * effort. This is all very low impact, so it's left for future improvement.
+ *
+ *
+ * These are true OpenZFS-specific mount options. They give the equivalent
+ * of temporarily setting the pool properties as follows:
+ *
+ * strictatime atime=on, relatime=off
+ *
+ * xattr: xattr=sa
+ * saxattr: xattr=sa
+ * dirxattr: xattr=dir
+ * noxattr: xattr=off
+ *
+ *
+ * mntpoint= provides the canonical mount point for a snapshot mount. This
+ * is an assist for the snapshot automounter call out to userspace, to
+ * understand where the snapshot is mounted even when triggered from an
+ * alternate mount namespace (eg inside a chroot).
+ *
+ * mntpoint= vfs->vfs_mntpoint=...
+ *
+ *
+ * These are used for coordination inside libzfs, and should not make it
+ * to the kernel, but it does not strip them, so we handle them and ignore
+ * them.
+ *
+ * defaults
+ * zfsutil
+ * remount
+ *
+ *
+ * These are specific to SELinux. When that security module is running, it
+ * will consume them, but if not, they will be passed through to us. libzfs
+ * adds them unconditionally, so we will always see them when SELinux is not
+ * running, and ignore them.
+ *
+ * fscontext
+ * defcontext
+ * rootcontext
+ * context
+ *
+ *
+ * When preparing a remount, libmount will read /proc/self/mountinfo and add
+ * any unrecognised flags it finds there to the options. So, we have to accept
+ * anything that __zpl_show_options() can produce.
+ *
+ * posixacl
+ * noacl
+ * casesensitive
+ * caseinsensitive
+ * casemixed
+ *
+ *
+ * mount(8) has a notion of "sloppy" options. According to the documentation,
+ * when the -s switch is provided, unrecognised mount options will be ignored.
+ * Only the Linux NFS and SMB filesystems support it, and traditionally
+ * OpenZFS has too. however, it appears massively underspecified and
+ * inconsistent. Depending on the interplay between mount(8), the mount helper
+ * (eg mount.zfs(8)) and libmount, -s may cause unknown options to be filtered
+ * in userspace, _or_ an additional option 'sloppy' to be passed to the kernel
+ * either before or after the "unknown" option, _or_ nothing at all happens
+ * and the unknown option to be passed through to the kernel as-is. The
+ * kernel NFS and SMB filesystems both expect to see an explicit option
+ * 'sloppy' and use this to either ignore or reject unknown options, but as
+ * described, it's very easy for that option to not appear, or appear too late.
+ *
+ * OpenZFS has a test for this in the test suite, and it's documented in
+ * mount.zfs(8), so to support it we accept 'sloppy' and ignore it, and all
+ * other unknown options produce a notice in the kernel log, and are also
+ * ignored. This allows the "feature" to continue to work, while avoiding
+ * the additional housekeeping for the 'sloppy' option.
+ *
+ * sloppy
+ *
+ *
+ * Finally, all filesystems get automatic handling for the 'source' option,
+ * that is, the "name" of the filesystem (the first column of df(1)'s output).
+ * However, this only happens if the handler does not otherwise handle
+ * the 'source' option. Since we handle _all_ options because of 'sloppy', we
+ * deal with this explicitly by calling into the kernel's helper for this,
+ * vfs_parse_fs_param_source(), which sets up fc->source.
+ *
+ * source
+ *
+ *
+ * Thank you for reading this far. I hope you find what you are looking for,
+ * in this life or the next.
+ *
+ * -- robn, 2026-03-26
+ */
+
+enum {
+ Opt_exec, Opt_suid, Opt_dev,
+ Opt_atime, Opt_relatime, Opt_strictatime,
+ Opt_saxattr, Opt_dirxattr, Opt_noxattr,
+ Opt_mntpoint,
+
+ Opt_ignore, Opt_warn,
+};
+
+static const struct fs_parameter_spec zpl_param_spec[] = {
+ fsparam_flag_no("exec", Opt_exec),
+ fsparam_flag_no("suid", Opt_suid),
+ fsparam_flag_no("dev", Opt_dev),
+
+ fsparam_flag_no("atime", Opt_atime),
+ fsparam_flag_no("relatime", Opt_relatime),
+ fsparam_flag("strictatime", Opt_strictatime),
+
+ fsparam_flag("xattr", Opt_saxattr),
+ fsparam_flag("saxattr", Opt_saxattr),
+ fsparam_flag("dirxattr", Opt_dirxattr),
+ fsparam_flag("noxattr", Opt_noxattr),
+
+ fsparam_string("mntpoint", Opt_mntpoint),
+
+ fsparam_flag("defaults", Opt_ignore),
+ fsparam_flag("zfsutil", Opt_ignore),
+ fsparam_flag("remount", Opt_ignore),
+
+ fsparam_string("fscontext", Opt_ignore),
+ fsparam_string("defcontext", Opt_ignore),
+ fsparam_string("rootcontext", Opt_ignore),
+ fsparam_string("context", Opt_ignore),
+
+ fsparam_flag("posixacl", Opt_ignore),
+ fsparam_flag("noacl", Opt_ignore),
+ fsparam_flag("casesensitive", Opt_ignore),
+ fsparam_flag("caseinsensitive", Opt_ignore),
+ fsparam_flag("casemixed", Opt_ignore),
+
+ fsparam_flag("sloppy", Opt_ignore),
+
+ {}
+};
+
+static int
+zpl_parse_param(struct fs_context *fc, struct fs_parameter *param)
+{
+ vfs_t *vfs = fc->fs_private;
+
+ /* Handle 'source' explicitly so we don't trip on it as an unknown. */
+ int opt = vfs_parse_fs_param_source(fc, param);
+ if (opt != -ENOPARAM)
+ return (opt);
+
+ struct fs_parse_result result;
+ opt = fs_parse(fc, zpl_param_spec, param, &result);
+ if (opt == -ENOPARAM) {
+ /*
+ * Convert unknowns to warnings, to work around the whole
+ * "sloppy option" mess.
+ */
+ opt = Opt_warn;
+ }
+ if (opt < 0)
+ return (opt);
+
+ switch (opt) {
+ case Opt_exec:
+ vfs->vfs_exec = !result.negated;
+ vfs->vfs_do_exec = B_TRUE;
+ break;
+ case Opt_suid:
+ vfs->vfs_setuid = !result.negated;
+ vfs->vfs_do_setuid = B_TRUE;
+ break;
+ case Opt_dev:
+ vfs->vfs_devices = !result.negated;
+ vfs->vfs_do_devices = B_TRUE;
+ break;
+
+ case Opt_atime:
+ vfs->vfs_atime = !result.negated;
+ vfs->vfs_do_atime = B_TRUE;
+ break;
+ case Opt_relatime:
+ vfs->vfs_relatime = !result.negated;
+ vfs->vfs_do_relatime = B_TRUE;
+ break;
+ case Opt_strictatime:
+ vfs->vfs_atime = B_TRUE;
+ vfs->vfs_do_atime = B_TRUE;
+ vfs->vfs_relatime = B_FALSE;
+ vfs->vfs_do_relatime = B_TRUE;
+ break;
+
+ case Opt_saxattr:
+ vfs->vfs_xattr = ZFS_XATTR_SA;
+ vfs->vfs_do_xattr = B_TRUE;
+ break;
+ case Opt_dirxattr:
+ vfs->vfs_xattr = ZFS_XATTR_DIR;
+ vfs->vfs_do_xattr = B_TRUE;
+ break;
+ case Opt_noxattr:
+ vfs->vfs_xattr = ZFS_XATTR_OFF;
+ vfs->vfs_do_xattr = B_TRUE;
+ break;
+
+ case Opt_mntpoint:
+ if (vfs->vfs_mntpoint != NULL)
+ kmem_strfree(vfs->vfs_mntpoint);
+ vfs->vfs_mntpoint = kmem_strdup(param->string);
+ break;
+
+ case Opt_ignore:
+ break;
+
+ case Opt_warn:
+ cmn_err(CE_NOTE,
+ "ZFS: ignoring unknown mount option: %s", param->key);
+ break;
+
+ default:
+ return (-SET_ERROR(EINVAL));
+ }
+
+ return (0);
+}
+
+/*
+ * Before Linux 5.8, the kernel's individual parameter parsing had a list of
+ * "forbidden" options that would always be rejected early. These were options
+ * that should be specified by MS_* flags, to be set on the superblock
+ * directly. However, it was inconsistently applied (eg it had various "*atime"
+ * options but not "atime", and also caused problems when it was not in sync
+ * with the version of libmount in use. It was deemed needlessly restrictive
+ * and was dropped in torvalds/linux@9193ae87a8af.
+ *
+ * Unfortunately, some of the options on this list are used by OpenZFS, so
+ * we need to see them. These include the aforementioned "*atime", "dev",
+ * "exec" and "suid".
+ *
+ * There is no easy compile-time check available to detect this, so we use
+ * a simple version check that should make it available everywhere needed,
+ * most notably RHEL8's 4.18+extras, which has backported fs_context support
+ * but does not include the 5.8 commit.
+ */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 8, 0)
+#define HAVE_FORBIDDEN_SB_FLAGS 1
+#endif
+
+#ifdef HAVE_FORBIDDEN_SB_FLAGS
+/*
+ * The typical path for options parsing through mount(2) is:
+ *
+ * ksys_mount
+ * do_mount
+ * generic_parse_monolithic
+ * vfs_parse_fs_string
+ * vfs_parse_fs_param
+ * zpl_parse_param
+ *
+ * vfs_parse_fs_param() calls the internal vfs_parse_sb_flag(), which is
+ * where the "forbidden" flags are applied. If it makes it through there,
+ * it will later call fc->parse_param() ie zpl_parse_param(). We can't
+ * intercept this chain in the middle anywhere; the earliest thing we can
+ * override is generic_parse_monolithic(), substituting our own by setting
+ * fc->parse_monolithic and doing the parsing work ourselves.
+ *
+ * Fortunately, generic_parse_monolithic() is almost entirely splitting the
+ * incoming parameter string on comma and handing off to the rest of the
+ * pipeline. This is easily replaced (almost entirely by reviving a few bits
+ * of our old options parser).
+ *
+ * To keep the change as narrow as possible, we reuse zpl_param_spec and
+ * zpl_parse_param() as much as possible. Once we've parsed the option, we call
+ * fs_parse(zpl_param_spec) to find out if the option is actually one we
+ * explicitly care about. If it is, we call zpl_parse_param() directly,
+ * avoiding vfs_parse_fs_param() and so the risk of being rejected. If it is
+ * not one we explicitly care about, we call zpl_parse_param() as normal,
+ * letting the kernel reject it if it wishes. If it doesn't, it will end up
+ * back in zpl_parse_param() via fc->parse_param, and we can ignore or warn
+ * about it we normally would.
+ */
static int
zpl_parse_monolithic(struct fs_context *fc, void *data)
{
+ char *mntopts = data;
+
+ if (mntopts == NULL)
+ return (0);
+
/*
- * We do options parsing in zfs_domount(); just stash the options blob
- * in the fs_context so we can pass it down later.
+ * Because we supply a .parse_monolithic callback, the kernel does
+ * no consideration of the options blob at all. Because of this, we
+ * have to give LSMs a first look at it. They will remove any options
+ * of interest to them (eg the SELinux *context= options).
*/
- fc->fs_private = data;
+ int err = security_sb_eat_lsm_opts(mntopts, &fc->security);
+ if (err)
+ return (err);
+
+ char *key;
+ while ((key = strsep(&mntopts, ",")) != NULL) {
+ if (!*key)
+ continue;
+
+ struct fs_parameter param = {
+ .key = key,
+ };
+
+ char *value = strchr(key, '=');
+ if (value != NULL) {
+ /* Key starts with '='. Kernel ignores, we will too. */
+ if (value == key)
+ continue;
+ *value++ = '\0';
+
+ /* key=value is a "string" type, set up for that */
+ param.string = value;
+ param.type = fs_value_is_string;
+ param.size = strlen(value);
+ } else {
+ /* unadorned key is a "flag" type */
+ param.type = fs_value_is_flag;
+ }
+
+ /* Check if this is one of our options. */
+ struct fs_parse_result result;
+ int opt = fs_parse(fc, zpl_param_spec, &param, &result);
+ if (opt >= 0) {
+ /*
+ * We already know this one of our options, so a
+ * failure here would be nonsensical.
+ */
+ VERIFY0(zpl_parse_param(fc, &param));
+ } else {
+ /*
+ * Not one of our option, send it through the kernel's
+ * standard parameter handling.
+ */
+ err = vfs_parse_fs_param(fc, &param);
+ if (err < 0)
+ return (err);
+ }
+ }
+
return (0);
}
+#endif /* HAVE_FORBIDDEN_SB_FLAGS */
static int
zpl_get_tree(struct fs_context *fc)
@@ -457,13 +872,17 @@ zpl_get_tree(struct fs_context *fc)
}
if (sb->s_root == NULL) {
- zfs_mnt_t zm = {
- .mnt_osname = fc->source,
- .mnt_data = fc->fs_private,
- };
+ vfs_t *vfs = fc->fs_private;
+
+ /* Apply readonly flag as mount option */
+ if (fc->sb_flags & SB_RDONLY) {
+ vfs->vfs_readonly = B_TRUE;
+ vfs->vfs_do_readonly = B_TRUE;
+ }
fstrans_cookie_t cookie = spl_fstrans_mark();
- err = zfs_domount(sb, &zm, fc->sb_flags & SB_SILENT ? 1 : 0);
+ err = zfs_domount(sb, fc->source, vfs,
+ fc->sb_flags & SB_SILENT ? 1 : 0);
spl_fstrans_unmark(cookie);
if (err) {
@@ -471,6 +890,12 @@ zpl_get_tree(struct fs_context *fc)
return (-err);
}
+ /*
+ * zfsvfs has taken ownership of the mount options, so we
+ * need to ensure we don't free them.
+ */
+ fc->fs_private = NULL;
+
sb->s_flags |= SB_ACTIVE;
} else if (!issnap && ((fc->sb_flags ^ sb->s_flags) & SB_RDONLY)) {
/*
@@ -492,28 +917,92 @@ zpl_get_tree(struct fs_context *fc)
static int
zpl_reconfigure(struct fs_context *fc)
{
- zfs_mnt_t zm = { .mnt_osname = NULL, .mnt_data = fc->fs_private };
fstrans_cookie_t cookie;
int error;
cookie = spl_fstrans_mark();
- error = -zfs_remount(fc->root->d_sb, &fc->sb_flags, &zm);
+ error = -zfs_remount(fc->root->d_sb, fc->fs_private, fc->sb_flags);
spl_fstrans_unmark(cookie);
ASSERT3S(error, <=, 0);
+ if (error == 0) {
+ /*
+ * zfsvfs has taken ownership of the mount options, so we
+ * need to ensure we don't free them.
+ */
+ fc->fs_private = NULL;
+ }
+
return (error);
}
+static int
+zpl_dup_fc(struct fs_context *fc, struct fs_context *src_fc)
+{
+ vfs_t *src_vfs = src_fc->fs_private;
+ if (src_vfs == NULL)
+ return (0);
+
+ vfs_t *vfs = zfsvfs_vfs_alloc();
+ if (vfs == NULL)
+ return (-SET_ERROR(ENOMEM));
+
+ /*
+ * This is annoying, but a straight memcpy() would require us to
+ * reinitialise the lock.
+ */
+ vfs->vfs_xattr = src_vfs->vfs_xattr;
+ vfs->vfs_readonly = src_vfs->vfs_readonly;
+ vfs->vfs_do_readonly = src_vfs->vfs_do_readonly;
+ vfs->vfs_setuid = src_vfs->vfs_setuid;
+ vfs->vfs_do_setuid = src_vfs->vfs_do_setuid;
+ vfs->vfs_exec = src_vfs->vfs_exec;
+ vfs->vfs_do_exec = src_vfs->vfs_do_exec;
+ vfs->vfs_devices = src_vfs->vfs_devices;
+ vfs->vfs_do_devices = src_vfs->vfs_do_devices;
+ vfs->vfs_do_xattr = src_vfs->vfs_do_xattr;
+ vfs->vfs_atime = src_vfs->vfs_atime;
+ vfs->vfs_do_atime = src_vfs->vfs_do_atime;
+ vfs->vfs_relatime = src_vfs->vfs_relatime;
+ vfs->vfs_do_relatime = src_vfs->vfs_do_relatime;
+ vfs->vfs_nbmand = src_vfs->vfs_nbmand;
+ vfs->vfs_do_nbmand = src_vfs->vfs_do_nbmand;
+
+ mutex_enter(&src_vfs->vfs_mntpt_lock);
+ if (src_vfs->vfs_mntpoint != NULL)
+ vfs->vfs_mntpoint = kmem_strdup(src_vfs->vfs_mntpoint);
+ mutex_exit(&src_vfs->vfs_mntpt_lock);
+
+ fc->fs_private = vfs;
+ return (0);
+}
+
+static void
+zpl_free_fc(struct fs_context *fc)
+{
+ zfsvfs_vfs_free(fc->fs_private);
+}
+
const struct fs_context_operations zpl_fs_context_operations = {
+#ifdef HAVE_FORBIDDEN_SB_FLAGS
.parse_monolithic = zpl_parse_monolithic,
+#endif
+ .parse_param = zpl_parse_param,
.get_tree = zpl_get_tree,
.reconfigure = zpl_reconfigure,
+ .dup = zpl_dup_fc,
+ .free = zpl_free_fc,
};
static int
zpl_init_fs_context(struct fs_context *fc)
{
+ fc->fs_private = zfsvfs_vfs_alloc();
+ if (fc->fs_private == NULL)
+ return (-SET_ERROR(ENOMEM));
+
fc->ops = &zpl_fs_context_operations;
+
return (0);
}
diff --git a/sys/contrib/openzfs/module/os/linux/zfs/zvol_os.c b/sys/contrib/openzfs/module/os/linux/zfs/zvol_os.c
index 89f9bc555fcf..dc47ff20fd74 100644
--- a/sys/contrib/openzfs/module/os/linux/zfs/zvol_os.c
+++ b/sys/contrib/openzfs/module/os/linux/zfs/zvol_os.c
@@ -1796,7 +1796,7 @@ zvol_os_rename_minor(zvol_state_t *zv, const char *newname)
{
int readonly = get_disk_ro(zv->zv_zso->zvo_disk);
- ASSERT(RW_LOCK_HELD(&zvol_state_lock));
+ ASSERT(RW_WRITE_HELD(&zvol_state_lock));
ASSERT(MUTEX_HELD(&zv->zv_state_lock));
strlcpy(zv->zv_name, newname, sizeof (zv->zv_name));
diff --git a/sys/contrib/openzfs/module/zcommon/zfeature_common.c b/sys/contrib/openzfs/module/zcommon/zfeature_common.c
index 6ba9892eeb64..2bb19c0cf5fd 100644
--- a/sys/contrib/openzfs/module/zcommon/zfeature_common.c
+++ b/sys/contrib/openzfs/module/zcommon/zfeature_common.c
@@ -697,6 +697,19 @@ zpool_feature_init(void)
ZFEATURE_FLAG_MOS, ZFEATURE_TYPE_BOOLEAN, NULL, sfeatures);
{
+ static const spa_feature_t draid_fdomain_deps[] = {
+ SPA_FEATURE_DRAID,
+ SPA_FEATURE_NONE
+ };
+ zfeature_register(SPA_FEATURE_DRAID_FAIL_DOMAINS,
+ "com.seagate:draid_failure_domains",
+ "draid_failure_domains",
+ "Support for failure domains in dRAID",
+ ZFEATURE_FLAG_MOS, ZFEATURE_TYPE_BOOLEAN,
+ draid_fdomain_deps, sfeatures);
+ }
+
+ {
static const spa_feature_t zilsaxattr_deps[] = {
SPA_FEATURE_EXTENSIBLE_DATASET,
SPA_FEATURE_NONE
diff --git a/sys/contrib/openzfs/module/zcommon/zfs_prop.c b/sys/contrib/openzfs/module/zcommon/zfs_prop.c
index 78d4b0a05f75..0866caf8795c 100644
--- a/sys/contrib/openzfs/module/zcommon/zfs_prop.c
+++ b/sys/contrib/openzfs/module/zcommon/zfs_prop.c
@@ -497,9 +497,14 @@ zfs_prop_init(void)
/* inherit index (boolean) properties */
zprop_register_index(ZFS_PROP_ATIME, "atime", 1, PROP_INHERIT,
ZFS_TYPE_FILESYSTEM, "on | off", "ATIME", boolean_table, sfeatures);
- zprop_register_index(ZFS_PROP_RELATIME, "relatime", 1, PROP_INHERIT,
- ZFS_TYPE_FILESYSTEM, "on | off", "RELATIME", boolean_table,
- sfeatures);
+ zprop_register_index(ZFS_PROP_RELATIME, "relatime",
+#ifdef __FreeBSD__
+ 0, /* FreeBSD does not natively support relatime. */
+#else
+ 1,
+#endif
+ PROP_INHERIT, ZFS_TYPE_FILESYSTEM, "on | off", "RELATIME",
+ boolean_table, sfeatures);
zprop_register_index(ZFS_PROP_DEVICES, "devices", 1, PROP_INHERIT,
ZFS_TYPE_FILESYSTEM | ZFS_TYPE_SNAPSHOT, "on | off", "DEVICES",
boolean_table, sfeatures);
@@ -520,6 +525,10 @@ zfs_prop_init(void)
zprop_register_index(ZFS_PROP_ZONED, "zoned", 0, PROP_INHERIT,
ZFS_TYPE_FILESYSTEM, "on | off", "ZONED", boolean_table, sfeatures);
#endif
+ /* UID-based zoning for rootless containers */
+ zprop_register_number(ZFS_PROP_ZONED_UID, "zoned_uid", 0,
+ PROP_INHERIT, ZFS_TYPE_FILESYSTEM, "<uid> | none", "ZONED_UID",
+ B_FALSE, sfeatures);
zprop_register_index(ZFS_PROP_VSCAN, "vscan", 0, PROP_INHERIT,
ZFS_TYPE_FILESYSTEM, "on | off", "VSCAN", boolean_table, sfeatures);
zprop_register_index(ZFS_PROP_NBMAND, "nbmand", 0, PROP_INHERIT,
diff --git a/sys/contrib/openzfs/module/zcommon/zpool_prop.c b/sys/contrib/openzfs/module/zcommon/zpool_prop.c
index ef21f17be8ca..ee86fe0c7171 100644
--- a/sys/contrib/openzfs/module/zcommon/zpool_prop.c
+++ b/sys/contrib/openzfs/module/zcommon/zpool_prop.c
@@ -441,6 +441,12 @@ vdev_prop_init(void)
ZFS_TYPE_VDEV, "<ashift>", "ASHIFT", B_FALSE, sfeatures);
zprop_register_number(VDEV_PROP_PARITY, "parity", 0, PROP_READONLY,
ZFS_TYPE_VDEV, "<parity>", "PARITY", B_FALSE, sfeatures);
+ zprop_register_number(VDEV_PROP_FDOMAIN, "failure_domain", UINT64_MAX,
+ PROP_READONLY, ZFS_TYPE_VDEV, "<fdomain>", "FDOM", B_FALSE,
+ sfeatures);
+ zprop_register_number(VDEV_PROP_FGROUP, "failure_group", UINT64_MAX,
+ PROP_READONLY, ZFS_TYPE_VDEV, "<fgroup>", "FGRP", B_FALSE,
+ sfeatures);
zprop_register_number(VDEV_PROP_NUMCHILDREN, "numchildren", 0,
PROP_READONLY, ZFS_TYPE_VDEV, "<number-of-children>", "NUMCHILD",
B_FALSE, sfeatures);
diff --git a/sys/contrib/openzfs/module/zfs/abd.c b/sys/contrib/openzfs/module/zfs/abd.c
index 2d310276af1c..7ea07c418300 100644
--- a/sys/contrib/openzfs/module/zfs/abd.c
+++ b/sys/contrib/openzfs/module/zfs/abd.c
@@ -280,7 +280,8 @@ static void
abd_free_scatter(abd_t *abd)
{
abd_free_chunks(abd);
- abd_update_scatter_stats(abd, ABDSTAT_DECR);
+ if (!abd_is_from_pages(abd))
+ abd_update_scatter_stats(abd, ABDSTAT_DECR);
}
/*
diff --git a/sys/contrib/openzfs/module/zfs/dbuf.c b/sys/contrib/openzfs/module/zfs/dbuf.c
index df75d3fbe0b0..a4fe3e519700 100644
--- a/sys/contrib/openzfs/module/zfs/dbuf.c
+++ b/sys/contrib/openzfs/module/zfs/dbuf.c
@@ -1480,8 +1480,12 @@ dbuf_read_hole(dmu_buf_impl_t *db, dnode_t *dn, blkptr_t *bp)
* Recheck BP_IS_HOLE() after dnode_block_freed() in case dnode_sync()
* processes the delete record and clears the bp while we are waiting
* for the dn_mtx (resulting in a "no" from block_freed).
+ *
+ * If bp != db->db_blkptr, it means that it was overridden (by a block
+ * clone or direct I/O write). We cannot rely on dnode_block_freed as
+ * the range can be freed in an earlier TXG but overridden in later.
*/
- if (!is_hole && db->db_level == 0)
+ if (!is_hole && db->db_level == 0 && bp == db->db_blkptr)
is_hole = dnode_block_freed(dn, db->db_blkid) || BP_IS_HOLE(bp);
if (is_hole) {
@@ -2077,6 +2081,65 @@ dbuf_free_range(dnode_t *dn, uint64_t start_blkid, uint64_t end_blkid,
kmem_free(db_search, sizeof (dmu_buf_impl_t));
}
+/*
+ * Advisory eviction of level-0 dbufs in [start_blkid, end_blkid] for
+ * the given dnode. Dirty dbufs carry a reference, so they will be
+ * evicted once their sync is completed.
+ */
+void
+dbuf_evict_range(dnode_t *dn, uint64_t start_blkid, uint64_t end_blkid)
+{
+ dmu_buf_impl_t *db_marker;
+ dmu_buf_impl_t *db, *db_next;
+ avl_index_t where;
+
+ db_marker = kmem_alloc(sizeof (dmu_buf_impl_t), KM_SLEEP);
+ db_marker->db_level = 0;
+ db_marker->db_blkid = start_blkid;
+ db_marker->db_state = DB_SEARCH;
+
+ mutex_enter(&dn->dn_dbufs_mtx);
+ db = avl_find(&dn->dn_dbufs, db_marker, &where);
+ ASSERT0P(db);
+ db = avl_nearest(&dn->dn_dbufs, where, AVL_AFTER);
+
+ for (; db != NULL; db = db_next) {
+ if (db->db_level != 0 || db->db_blkid > end_blkid)
+ break;
+
+ mutex_enter(&db->db_mtx);
+ if (db->db_state != DB_EVICTING &&
+ zfs_refcount_is_zero(&db->db_holds)) {
+ /*
+ * Clean and unreferenced: evict immediately.
+ * Use the marker pattern from dnode_evict_dbufs()
+ * because dbuf_destroy() may recursively remove
+ * the parent indirect dbuf from dn_dbufs, which
+ * could be the node db_next would point to.
+ */
+ db_marker->db_level = db->db_level;
+ db_marker->db_blkid = db->db_blkid;
+ db_marker->db_state = DB_MARKER;
+ db_marker->db_parent =
+ (void *)((uintptr_t)db - 1);
+ avl_insert_here(&dn->dn_dbufs, db_marker,
+ db, AVL_BEFORE);
+ dbuf_destroy(db);
+ db_next = AVL_NEXT(&dn->dn_dbufs, db_marker);
+ avl_remove(&dn->dn_dbufs, db_marker);
+ } else {
+ /* Referenced (possibly dirty): evict when released. */
+ db->db_pending_evict = TRUE;
+ db->db_partial_read = FALSE;
+ mutex_exit(&db->db_mtx);
+ db_next = AVL_NEXT(&dn->dn_dbufs, db);
+ }
+ }
+ mutex_exit(&dn->dn_dbufs_mtx);
+
+ kmem_free(db_marker, sizeof (dmu_buf_impl_t));
+}
+
void
dbuf_new_size(dmu_buf_impl_t *db, int size, dmu_tx_t *tx)
{
@@ -2201,6 +2264,17 @@ dbuf_dirty_lightweight(dnode_t *dn, uint64_t blkid, dmu_tx_t *tx)
mutex_enter(&dn->dn_mtx);
int txgoff = tx->tx_txg & TXG_MASK;
+
+ /*
+ * Assert that we are not modifying the range tree for the syncing
+ * TXG from a non-syncing thread. We verify that the tx's
+ * transaction group is strictly newer than the one currently
+ * syncing (meaning we are in open context). If this triggers,
+ * it indicates a race where syncing dn_free_range tree is
+ * being modified while dnode_sync() may be iterating over it.
+ */
+ ASSERT(tx->tx_txg > spa_syncing_txg(dn->dn_objset->os_spa));
+
if (dn->dn_free_ranges[txgoff] != NULL) {
zfs_range_tree_clear(dn->dn_free_ranges[txgoff], blkid, 1);
}
@@ -2388,6 +2462,7 @@ dbuf_dirty(dmu_buf_impl_t *db, dmu_tx_t *tx)
db->db_blkid != DMU_SPILL_BLKID) {
mutex_enter(&dn->dn_mtx);
if (dn->dn_free_ranges[txgoff] != NULL) {
+ FREE_RANGE_VERIFY(tx, dn);
zfs_range_tree_clear(dn->dn_free_ranges[txgoff],
db->db_blkid, 1);
}
@@ -5434,6 +5509,7 @@ EXPORT_SYMBOL(dbuf_whichblock);
EXPORT_SYMBOL(dbuf_read);
EXPORT_SYMBOL(dbuf_unoverride);
EXPORT_SYMBOL(dbuf_free_range);
+EXPORT_SYMBOL(dbuf_evict_range);
EXPORT_SYMBOL(dbuf_new_size);
EXPORT_SYMBOL(dbuf_release_bp);
EXPORT_SYMBOL(dbuf_dirty);
diff --git a/sys/contrib/openzfs/module/zfs/ddt_log.c b/sys/contrib/openzfs/module/zfs/ddt_log.c
index e36c15085baa..51ce8b9a0842 100644
--- a/sys/contrib/openzfs/module/zfs/ddt_log.c
+++ b/sys/contrib/openzfs/module/zfs/ddt_log.c
@@ -604,8 +604,7 @@ ddt_log_load_one(ddt_t *ddt, uint_t n)
}
if (hdr.dlh_length > 0) {
- dmu_prefetch_by_dnode(dn, 0, 0, hdr.dlh_length,
- ZIO_PRIORITY_SYNC_READ);
+ dmu_prefetch_stream_by_dnode(dn, 0, hdr.dlh_length, B_FALSE);
for (uint64_t offset = 0; offset < hdr.dlh_length;
offset += dn->dn_datablksz) {
diff --git a/sys/contrib/openzfs/module/zfs/dmu.c b/sys/contrib/openzfs/module/zfs/dmu.c
index 5cb02831a251..0f40164ecc95 100644
--- a/sys/contrib/openzfs/module/zfs/dmu.c
+++ b/sys/contrib/openzfs/module/zfs/dmu.c
@@ -779,6 +779,54 @@ dmu_prefetch_by_dnode(dnode_t *dn, int64_t level, uint64_t offset,
rw_exit(&dn->dn_struct_rwlock);
}
+/*
+ * Prime a prefetch for sequential accesses from offset for at least len bytes.
+ */
+void
+dmu_prefetch_stream(objset_t *os, uint64_t object, uint64_t offset,
+ uint64_t len, boolean_t start_now)
+{
+ dnode_t *dn;
+
+ if (dnode_hold(os, object, FTAG, &dn) != 0)
+ return;
+ dmu_prefetch_stream_by_dnode(dn, offset, len, start_now);
+ dnode_rele(dn, FTAG);
+}
+
+void
+dmu_prefetch_stream_by_dnode(dnode_t *dn, uint64_t offset, uint64_t len,
+ boolean_t start_now)
+{
+ rw_enter(&dn->dn_struct_rwlock, RW_READER);
+ if (dn->dn_datablkshift != 0) {
+ uint64_t start = dbuf_whichblock(dn, 0, offset);
+ if (len == 0) {
+ if (dmu_zfetch_prime(&dn->dn_zfetch, start, start) &&
+ start_now) {
+ dmu_zfetch(&dn->dn_zfetch, start, 0, B_TRUE,
+ B_TRUE, B_TRUE, B_FALSE);
+ }
+ } else {
+ uint64_t end = dbuf_whichblock(dn, 0, offset + len - 1);
+ if (start == end) {
+ if (start_now) {
+ dbuf_prefetch(dn, 0, start,
+ ZIO_PRIORITY_ASYNC_READ, 0);
+ }
+ } else if (
+ dmu_zfetch_prime(&dn->dn_zfetch, start, end + 1) &&
+ start_now) {
+ dmu_zfetch(&dn->dn_zfetch, start, 0, B_TRUE,
+ B_TRUE, B_TRUE, B_FALSE);
+ }
+ }
+ } else if (offset < dn->dn_datablksz && start_now) {
+ dbuf_prefetch(dn, 0, 0, ZIO_PRIORITY_ASYNC_READ, 0);
+ }
+ rw_exit(&dn->dn_struct_rwlock);
+}
+
typedef struct {
kmutex_t dpa_lock;
kcondvar_t dpa_cv;
@@ -899,6 +947,35 @@ dmu_prefetch_dnode(objset_t *os, uint64_t object, zio_priority_t pri)
}
/*
+ * Advisory cache eviction for a byte range of an object.
+ */
+void
+dmu_evict_range(objset_t *os, uint64_t object, uint64_t offset, uint64_t len)
+{
+ dnode_t *dn;
+
+ if (len == 0)
+ return;
+ if (dnode_hold(os, object, FTAG, &dn) != 0)
+ return;
+
+ /*
+ * Exclude the last block if the range end is not block-aligned:
+ * a sequential access may continue into that block. The first
+ * block is included even when partially covered since backwards
+ * access patterns are rare.
+ */
+ 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);
+ if (end > start)
+ dbuf_evict_range(dn, start, end - 1);
+ rw_exit(&dn->dn_struct_rwlock);
+
+ dnode_rele(dn, FTAG);
+}
+
+/*
* Get the next "chunk" of file data to free. We traverse the file from
* the end so that the file gets shorter over time (if we crash in the
* middle, this will leave us in a better state). We find allocated file
@@ -2943,6 +3020,8 @@ EXPORT_SYMBOL(dmu_buf_rele_array);
EXPORT_SYMBOL(dmu_prefetch);
EXPORT_SYMBOL(dmu_prefetch_by_dnode);
EXPORT_SYMBOL(dmu_prefetch_dnode);
+EXPORT_SYMBOL(dmu_prefetch_stream);
+EXPORT_SYMBOL(dmu_prefetch_stream_by_dnode);
EXPORT_SYMBOL(dmu_free_range);
EXPORT_SYMBOL(dmu_free_long_range);
EXPORT_SYMBOL(dmu_free_long_object);
diff --git a/sys/contrib/openzfs/module/zfs/dmu_zfetch.c b/sys/contrib/openzfs/module/zfs/dmu_zfetch.c
index 51165d0bf723..f2101579c00f 100644
--- a/sys/contrib/openzfs/module/zfs/dmu_zfetch.c
+++ b/sys/contrib/openzfs/module/zfs/dmu_zfetch.c
@@ -455,6 +455,61 @@ dmu_zfetch_future(zstream_t *zs, uint64_t blkid, uint64_t nblks)
}
/*
+ * Prime a zfetch stream at blkid, so that the first demand access triggered
+ * enough prefetch without ramp-up to sequentially read up to end_blkid.
+ */
+boolean_t
+dmu_zfetch_prime(zfetch_t *zf, uint64_t blkid, uint64_t end_blkid)
+{
+ zstream_t *zs;
+ dnode_t *dn = zf->zf_dnode;
+ spa_t *spa = dn->dn_objset->os_spa;
+
+ ASSERT(RW_LOCK_HELD(&dn->dn_struct_rwlock));
+ if (zfs_prefetch_disable ||
+ dn->dn_objset->os_prefetch == ZFS_PREFETCH_NONE)
+ return (B_FALSE);
+
+ if (!spa_indirect_vdevs_loaded(spa))
+ return (B_FALSE);
+
+ uint64_t maxblkid = dn->dn_maxblkid;
+ unsigned int dbs = dn->dn_datablkshift;
+
+ if (blkid >= maxblkid)
+ return (B_FALSE);
+ if (end_blkid > maxblkid + 1)
+ end_blkid = maxblkid + 1;
+
+ mutex_enter(&zf->zf_lock);
+
+ /* Skip if a nearby stream already covers this range. */
+ uint_t max_near = zfetch_max_reorder >> dbs;
+ for (zs = list_head(&zf->zf_stream); zs != NULL;
+ zs = list_next(&zf->zf_stream, zs)) {
+ uint64_t diff = (blkid >= zs->zs_blkid) ?
+ (blkid - zs->zs_blkid) : (zs->zs_blkid - blkid);
+ if (diff <= max_near) {
+ mutex_exit(&zf->zf_lock);
+ return (B_FALSE);
+ }
+ }
+
+ dmu_zfetch_stream_create(zf, blkid);
+ zs = list_head(&zf->zf_stream);
+ ASSERT(zs != NULL);
+ ASSERT3U(zs->zs_blkid, ==, blkid);
+
+ /* dmu_zfetch_prepare() will double the distances, so take a half. */
+ unsigned int nbytes = ((end_blkid - blkid) << dbs) / 2;
+ zs->zs_pf_dist = MIN(nbytes, zfetch_min_distance);
+ zs->zs_ipf_dist = MIN(nbytes, zfetch_max_idistance);
+
+ mutex_exit(&zf->zf_lock);
+ return (B_TRUE);
+}
+
+/*
* This is the predictive prefetch entry point. dmu_zfetch_prepare()
* associates dnode access specified with blkid and nblks arguments with
* prefetch stream, predicts further accesses based on that stats and returns
@@ -493,20 +548,21 @@ dmu_zfetch_prepare(zfetch_t *zf, uint64_t blkid, uint64_t nblks,
/*
* As a fast path for small (single-block) files, ignore access
- * to the first block.
+ * to the first block, unless some streams exist, since a prime
+ * may be waiting.
*/
- if (!have_lock && blkid == 0)
+ if (!have_lock && blkid == 0 && zf->zf_numstreams == 0)
return (NULL);
if (!have_lock)
rw_enter(&zf->zf_dnode->dn_struct_rwlock, RW_READER);
/*
- * A fast path for small files for which no prefetch will
- * happen.
+ * A fast path for small files for which no prefetch will happen,
+ * unless streams exist, since a prime may be waiting.
*/
uint64_t maxblkid = zf->zf_dnode->dn_maxblkid;
- if (maxblkid < 2) {
+ if (maxblkid < 2 && (maxblkid == 0 || zf->zf_numstreams == 0)) {
if (!have_lock)
rw_exit(&zf->zf_dnode->dn_struct_rwlock);
return (NULL);
@@ -589,7 +645,7 @@ future:
zs->zs_atime = gethrestime_sec();
/* Exit if we already prefetched for this position before. */
- if (nblks == 0)
+ if (nblks == 0 && zs->zs_ipf_end > end_blkid)
goto out;
/* If the file is ending, remove the stream. */
@@ -643,6 +699,7 @@ out:
* Do the same for indirects, starting where we will stop reading
* data blocks (and the indirects that point to them).
*/
+ nbytes = MAX(nbytes, (1 << dbs));
if (unlikely(zs->zs_ipf_dist < nbytes))
zs->zs_ipf_dist = nbytes;
else
diff --git a/sys/contrib/openzfs/module/zfs/dnode.c b/sys/contrib/openzfs/module/zfs/dnode.c
index e0cc4a7e13e0..be0e3de9bb23 100644
--- a/sys/contrib/openzfs/module/zfs/dnode.c
+++ b/sys/contrib/openzfs/module/zfs/dnode.c
@@ -2409,6 +2409,8 @@ done:
mutex_enter(&dn->dn_mtx);
{
int txgoff = tx->tx_txg & TXG_MASK;
+
+ FREE_RANGE_VERIFY(tx, dn);
if (dn->dn_free_ranges[txgoff] == NULL) {
dn->dn_free_ranges[txgoff] =
zfs_range_tree_create_flags(
diff --git a/sys/contrib/openzfs/module/zfs/dnode_sync.c b/sys/contrib/openzfs/module/zfs/dnode_sync.c
index 046ceddb3609..0e070c69dcd6 100644
--- a/sys/contrib/openzfs/module/zfs/dnode_sync.c
+++ b/sys/contrib/openzfs/module/zfs/dnode_sync.c
@@ -440,24 +440,6 @@ dnode_sync_free_range_impl(dnode_t *dn, uint64_t blkid, uint64_t nblks,
}
}
-typedef struct dnode_sync_free_range_arg {
- dnode_t *dsfra_dnode;
- dmu_tx_t *dsfra_tx;
- boolean_t dsfra_free_indirects;
-} dnode_sync_free_range_arg_t;
-
-static void
-dnode_sync_free_range(void *arg, uint64_t blkid, uint64_t nblks)
-{
- dnode_sync_free_range_arg_t *dsfra = arg;
- dnode_t *dn = dsfra->dsfra_dnode;
-
- mutex_exit(&dn->dn_mtx);
- dnode_sync_free_range_impl(dn, blkid, nblks,
- dsfra->dsfra_free_indirects, dsfra->dsfra_tx);
- mutex_enter(&dn->dn_mtx);
-}
-
/*
* Try to kick all the dnode's dbufs out of the cache...
*/
@@ -635,6 +617,64 @@ dnode_sync_free(dnode_t *dn, dmu_tx_t *tx)
}
/*
+ * We cannot simply detach the range tree (set dn_free_ranges to NULL)
+ * before processing it because dnode_block_freed() relies on it to
+ * correctly identify blocks that have been freed in the current TXG
+ * (for dbuf_read() calls on holes). If we detached it early, a concurrent
+ * reader might see the block as valid on disk and return stale data
+ * instead of zeros.
+ *
+ * We also can't use zfs_range_tree_walk() nor zfs_range_tree_vacate()
+ * with a callback that drops dn_mtx (dnode_sync_free_range()). This is
+ * unsafe because another thread (spa_sync_deferred_frees() ->
+ * dnode_free_range()) could acquire dn_mtx and modify the tree while the
+ * walk or vacate was in progress. This leads to tree corruption or panic
+ * when we resume.
+ *
+ * To fix the race while maintaining visibility, we process the tree
+ * incrementally. We pick a segment, drop the lock to sync it, and
+ * re-acquire the lock to remove it. By always restarting from the head
+ * of the tree, we ensure we are never using an invalid iterator.
+ * We use zfs_range_tree_clear() instead of ..._remove() because the range
+ * might have already been removed while the lock was dropped (specifically
+ * in the dbuf_dirty path mentioned above). ..._clear() handles this
+ * gracefully, while ..._remove() would panic on a missing segment.
+ */
+static void
+dnode_sync_free_ranges(dnode_t *dn, dmu_tx_t *tx)
+{
+ int txgoff = tx->tx_txg & TXG_MASK;
+
+ mutex_enter(&dn->dn_mtx);
+ zfs_range_tree_t *rt = dn->dn_free_ranges[txgoff];
+ if (rt != NULL) {
+ boolean_t freeing_dnode = dn->dn_free_txg > 0 &&
+ dn->dn_free_txg <= tx->tx_txg;
+ zfs_range_seg_t *rs;
+
+ if (freeing_dnode) {
+ ASSERT(zfs_range_tree_contains(rt, 0,
+ dn->dn_maxblkid + 1));
+ }
+
+ while ((rs = zfs_range_tree_first(rt)) != NULL) {
+ uint64_t start = zfs_rs_get_start(rs, rt);
+ uint64_t size = zfs_rs_get_end(rs, rt) - start;
+
+ mutex_exit(&dn->dn_mtx);
+ dnode_sync_free_range_impl(dn, start, size,
+ freeing_dnode, tx);
+ mutex_enter(&dn->dn_mtx);
+
+ zfs_range_tree_clear(rt, start, size);
+ }
+ zfs_range_tree_destroy(rt);
+ dn->dn_free_ranges[txgoff] = NULL;
+ }
+ mutex_exit(&dn->dn_mtx);
+}
+
+/*
* Write out the dnode's dirty buffers.
* Does not wait for zio completions.
*/
@@ -781,32 +821,7 @@ dnode_sync(dnode_t *dn, dmu_tx_t *tx)
}
/* process all the "freed" ranges in the file */
- if (dn->dn_free_ranges[txgoff] != NULL) {
- dnode_sync_free_range_arg_t dsfra;
- dsfra.dsfra_dnode = dn;
- dsfra.dsfra_tx = tx;
- dsfra.dsfra_free_indirects = freeing_dnode;
- mutex_enter(&dn->dn_mtx);
- if (freeing_dnode) {
- ASSERT(zfs_range_tree_contains(
- dn->dn_free_ranges[txgoff], 0,
- dn->dn_maxblkid + 1));
- }
- /*
- * Because dnode_sync_free_range() must drop dn_mtx during its
- * processing, using it as a callback to zfs_range_tree_vacate()
- * is not safe. No other operations (besides destroy) are
- * allowed once zfs_range_tree_vacate() has begun, and dropping
- * dn_mtx would leave a window open for another thread to
- * observe that invalid (and unsafe) state.
- */
- zfs_range_tree_walk(dn->dn_free_ranges[txgoff],
- dnode_sync_free_range, &dsfra);
- zfs_range_tree_vacate(dn->dn_free_ranges[txgoff], NULL, NULL);
- zfs_range_tree_destroy(dn->dn_free_ranges[txgoff]);
- dn->dn_free_ranges[txgoff] = NULL;
- mutex_exit(&dn->dn_mtx);
- }
+ dnode_sync_free_ranges(dn, tx);
if (freeing_dnode) {
dn->dn_objset->os_freed_dnodes++;
@@ -828,7 +843,7 @@ dnode_sync(dnode_t *dn, dmu_tx_t *tx)
}
/*
- * This must be done after dnode_sync_free_range()
+ * This must be done after dnode_sync_free_ranges()
* and dnode_increase_indirection(). See dnode_new_blkid()
* for an explanation of the high bit being set.
*/
diff --git a/sys/contrib/openzfs/module/zfs/dsl_crypt.c b/sys/contrib/openzfs/module/zfs/dsl_crypt.c
index 9cb1536642d1..9207737f908b 100644
--- a/sys/contrib/openzfs/module/zfs/dsl_crypt.c
+++ b/sys/contrib/openzfs/module/zfs/dsl_crypt.c
@@ -17,6 +17,7 @@
/*
* Copyright (c) 2017, Datto, Inc. All rights reserved.
* Copyright (c) 2018 by Delphix. All rights reserved.
+ * Copyright 2026 Oxide Computer Company
*/
#include <sys/dsl_crypt.h>
@@ -1241,6 +1242,7 @@ dsl_crypto_key_sync(dsl_crypto_key_t *dck, dmu_tx_t *tx)
typedef struct spa_keystore_change_key_args {
const char *skcka_dsname;
dsl_crypto_params_t *skcka_cp;
+ nvlist_t *skcka_userprops;
} spa_keystore_change_key_args_t;
static int
@@ -1253,6 +1255,8 @@ spa_keystore_change_key_check(void *arg, dmu_tx_t *tx)
dsl_crypto_params_t *dcp = skcka->skcka_cp;
uint64_t rddobj;
+ /* we assume skcka_userprops has already been verified */
+
/* check for the encryption feature */
if (!spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_ENCRYPTION)) {
ret = SET_ERROR(ENOTSUP);
@@ -1539,6 +1543,10 @@ spa_keystore_change_key_sync(void *arg, dmu_tx_t *tx)
VERIFY0(dsl_dataset_hold(dp, skcka->skcka_dsname, FTAG, &ds));
ASSERT(!ds->ds_is_snapshot);
+ /* set user properties */
+ dsl_props_set_sync_impl(ds, ZPROP_SRC_LOCAL, skcka->skcka_userprops,
+ tx);
+
if (dcp->cp_cmd == DCP_CMD_NEW_KEY ||
dcp->cp_cmd == DCP_CMD_FORCE_NEW_KEY) {
/*
@@ -1617,14 +1625,19 @@ spa_keystore_change_key_sync(void *arg, dmu_tx_t *tx)
dsl_dataset_rele(ds, FTAG);
}
+/*
+ * Note: assumes userprops has already been checked for validity.
+ */
int
-spa_keystore_change_key(const char *dsname, dsl_crypto_params_t *dcp)
+spa_keystore_change_key(const char *dsname, dsl_crypto_params_t *dcp,
+ nvlist_t *userprops)
{
spa_keystore_change_key_args_t skcka;
/* initialize the args struct */
skcka.skcka_dsname = dsname;
skcka.skcka_cp = dcp;
+ skcka.skcka_userprops = userprops;
/*
* Perform the actual work in syncing context. The blocks modified
diff --git a/sys/contrib/openzfs/module/zfs/dsl_deleg.c b/sys/contrib/openzfs/module/zfs/dsl_deleg.c
index 200bee200d34..f3153d6901c2 100644
--- a/sys/contrib/openzfs/module/zfs/dsl_deleg.c
+++ b/sys/contrib/openzfs/module/zfs/dsl_deleg.c
@@ -591,13 +591,16 @@ dsl_deleg_access_impl(dsl_dataset_t *ds, const char *perm, cred_t *cr)
* the zoned property is set
*/
if (!INGLOBALZONE(curproc)) {
- uint64_t zoned;
+ uint64_t zoned = 0;
+ uint64_t zoned_uid_val = 0;
- if (dsl_prop_get_dd(dd,
+ (void) dsl_prop_get_dd(dd,
zfs_prop_to_name(ZFS_PROP_ZONED),
- 8, 1, &zoned, NULL, B_FALSE) != 0)
- break;
- if (!zoned)
+ 8, 1, &zoned, NULL, B_FALSE);
+ (void) dsl_prop_get_dd(dd,
+ zfs_prop_to_name(ZFS_PROP_ZONED_UID),
+ 8, 1, &zoned_uid_val, NULL, B_FALSE);
+ if (!zoned && zoned_uid_val == 0)
break;
}
zapobj = dsl_dir_phys(dd)->dd_deleg_zapobj;
diff --git a/sys/contrib/openzfs/module/zfs/spa.c b/sys/contrib/openzfs/module/zfs/spa.c
index 4397c14b5c77..eafd4b176208 100644
--- a/sys/contrib/openzfs/module/zfs/spa.c
+++ b/sys/contrib/openzfs/module/zfs/spa.c
@@ -1947,6 +1947,10 @@ spa_activate(spa_t *spa, spa_mode_t mode)
static void
spa_deactivate(spa_t *spa)
{
+ if (spa->spa_create_info != NULL) {
+ nvlist_free(spa->spa_create_info);
+ spa->spa_create_info = NULL;
+ }
ASSERT(spa->spa_sync_on == B_FALSE);
ASSERT0P(spa->spa_dsl_pool);
ASSERT0P(spa->spa_root_vdev);
@@ -7060,7 +7064,7 @@ spa_create_check_encryption_params(dsl_crypto_params_t *dcp,
*/
int
spa_create(const char *pool, nvlist_t *nvroot, nvlist_t *props,
- nvlist_t *zplprops, dsl_crypto_params_t *dcp)
+ nvlist_t *zplprops, dsl_crypto_params_t *dcp, nvlist_t **errinfo)
{
spa_t *spa;
const char *altroot = NULL;
@@ -7071,10 +7075,12 @@ spa_create(const char *pool, nvlist_t *nvroot, nvlist_t *props,
uint64_t txg = TXG_INITIAL;
nvlist_t **spares, **l2cache;
uint_t nspares, nl2cache;
- uint64_t version, obj, ndraid = 0;
+ uint64_t version, obj, ndraid = 0, draid_nfgroup = 0;
boolean_t has_features;
boolean_t has_encryption;
boolean_t has_allocclass;
+ boolean_t has_draid;
+ boolean_t has_draid_fdomains;
spa_feature_t feat;
const char *feat_name;
const char *poolname;
@@ -7121,6 +7127,8 @@ spa_create(const char *pool, nvlist_t *nvroot, nvlist_t *props,
has_features = B_FALSE;
has_encryption = B_FALSE;
has_allocclass = B_FALSE;
+ has_draid = B_FALSE;
+ has_draid_fdomains = B_FALSE;
for (nvpair_t *elem = nvlist_next_nvpair(props, NULL);
elem != NULL; elem = nvlist_next_nvpair(props, elem)) {
if (zpool_prop_feature(nvpair_name(elem))) {
@@ -7132,6 +7140,10 @@ spa_create(const char *pool, nvlist_t *nvroot, nvlist_t *props,
has_encryption = B_TRUE;
if (feat == SPA_FEATURE_ALLOCATION_CLASSES)
has_allocclass = B_TRUE;
+ if (feat == SPA_FEATURE_DRAID)
+ has_draid = B_TRUE;
+ if (feat == SPA_FEATURE_DRAID_FAIL_DOMAINS)
+ has_draid_fdomains = B_TRUE;
}
}
@@ -7195,7 +7207,11 @@ spa_create(const char *pool, nvlist_t *nvroot, nvlist_t *props,
if (error == 0 &&
(error = vdev_create(rvd, txg, B_FALSE)) == 0 &&
- (error = vdev_draid_spare_create(nvroot, rvd, &ndraid, 0)) == 0 &&
+ (error = vdev_draid_spare_create(nvroot, rvd, &ndraid,
+ &draid_nfgroup, 0)) == 0 &&
+ (ndraid == 0 || has_draid || (error = SET_ERROR(ENOTSUP))) &&
+ (draid_nfgroup == 0 || has_draid_fdomains ||
+ (error = SET_ERROR(ENOTSUP))) && error == 0 &&
(error = spa_validate_aux(spa, nvroot, txg, VDEV_ALLOC_ADD)) == 0) {
/*
* instantiate the metaslab groups (this will dirty the vdevs)
@@ -7212,6 +7228,10 @@ spa_create(const char *pool, nvlist_t *nvroot, nvlist_t *props,
spa_config_exit(spa, SCL_ALL, FTAG);
if (error != 0) {
+ if (errinfo != NULL) {
+ *errinfo = spa->spa_create_info;
+ spa->spa_create_info = NULL;
+ }
spa_unload(spa);
spa_deactivate(spa);
spa_remove(spa);
@@ -7346,6 +7366,9 @@ spa_create(const char *pool, nvlist_t *nvroot, nvlist_t *props,
for (int i = 0; i < ndraid; i++)
spa_feature_incr(spa, SPA_FEATURE_DRAID, tx);
+ for (int i = 0; i < draid_nfgroup; i++)
+ spa_feature_incr(spa, SPA_FEATURE_DRAID_FAIL_DOMAINS, tx);
+
dmu_tx_commit(tx);
spa->spa_sync_on = B_TRUE;
@@ -7943,12 +7966,25 @@ spa_draid_feature_incr(void *arg, dmu_tx_t *tx)
}
/*
+ * This is called as a synctask to increment the draid_fail_domains feature flag
+ */
+static void
+spa_draid_fdomains_feature_incr(void *arg, dmu_tx_t *tx)
+{
+ spa_t *spa = dmu_tx_pool(tx)->dp_spa;
+ int nfgrp = (int)(uintptr_t)arg;
+
+ for (int c = 0; c < nfgrp; c++)
+ spa_feature_incr(spa, SPA_FEATURE_DRAID_FAIL_DOMAINS, tx);
+}
+
+/*
* Add a device to a storage pool.
*/
int
spa_vdev_add(spa_t *spa, nvlist_t *nvroot, boolean_t check_ashift)
{
- uint64_t txg, ndraid = 0;
+ uint64_t txg, ndraid = 0, draid_nfgroup = 0;
int error;
vdev_t *rvd = spa->spa_root_vdev;
vdev_t *vd, *tvd;
@@ -7987,10 +8023,15 @@ spa_vdev_add(spa_t *spa, nvlist_t *nvroot, boolean_t check_ashift)
* dRAID is stored in the config and used when opening the spare.
*/
if ((error = vdev_draid_spare_create(nvroot, vd, &ndraid,
- rvd->vdev_children)) == 0) {
+ &draid_nfgroup, rvd->vdev_children)) == 0) {
+
if (ndraid > 0 && nvlist_lookup_nvlist_array(nvroot,
ZPOOL_CONFIG_SPARES, &spares, &nspares) != 0)
nspares = 0;
+
+ if (draid_nfgroup > 0 && !spa_feature_is_enabled(spa,
+ SPA_FEATURE_DRAID_FAIL_DOMAINS))
+ return (spa_vdev_exit(spa, vd, txg, ENOTSUP));
} else {
return (spa_vdev_exit(spa, vd, txg, error));
}
@@ -8077,8 +8118,15 @@ spa_vdev_add(spa_t *spa, nvlist_t *nvroot, boolean_t check_ashift)
dmu_tx_t *tx;
tx = dmu_tx_create_assigned(spa->spa_dsl_pool, txg);
+
dsl_sync_task_nowait(spa->spa_dsl_pool, spa_draid_feature_incr,
(void *)(uintptr_t)ndraid, tx);
+
+ if (draid_nfgroup > 0)
+ dsl_sync_task_nowait(spa->spa_dsl_pool,
+ spa_draid_fdomains_feature_incr,
+ (void *)(uintptr_t)draid_nfgroup, tx);
+
dmu_tx_commit(tx);
}
diff --git a/sys/contrib/openzfs/module/zfs/spa_errlog.c b/sys/contrib/openzfs/module/zfs/spa_errlog.c
index 7252fd534bdf..afa9e9d0efd4 100644
--- a/sys/contrib/openzfs/module/zfs/spa_errlog.c
+++ b/sys/contrib/openzfs/module/zfs/spa_errlog.c
@@ -468,7 +468,7 @@ check_filesystem(spa_t *spa, uint64_t head_ds, zbookmark_err_phys_t *zep,
kmem_free(zc, sizeof (*zc));
out:
- kmem_free(snap_obj_array, sizeof (*snap_obj_array));
+ kmem_free(snap_obj_array, snap_count * sizeof (*snap_obj_array));
return (error);
}
diff --git a/sys/contrib/openzfs/module/zfs/spa_log_spacemap.c b/sys/contrib/openzfs/module/zfs/spa_log_spacemap.c
index 32ef0aaeb858..10817c2c3df6 100644
--- a/sys/contrib/openzfs/module/zfs/spa_log_spacemap.c
+++ b/sys/contrib/openzfs/module/zfs/spa_log_spacemap.c
@@ -1160,7 +1160,7 @@ spa_ld_log_sm_data(spa_t *spa)
while (sls != NULL) {
/* Prefetch log spacemaps up to 16 TXGs or MBs ahead. */
if (psls != NULL && pn < 16 &&
- (pn < 2 || ps < 2 * dmu_prefetch_max)) {
+ (pn < 2 || ps < dmu_prefetch_max)) {
error = space_map_open(&psls->sls_sm,
spa_meta_objset(spa), psls->sls_sm_obj, 0,
UINT64_MAX, SPA_MINBLOCKSHIFT);
@@ -1171,9 +1171,9 @@ spa_ld_log_sm_data(spa_t *spa)
(u_longlong_t)sls->sls_sm_obj, error);
goto out;
}
- dmu_prefetch(spa_meta_objset(spa), psls->sls_sm_obj,
- 0, 0, space_map_length(psls->sls_sm),
- ZIO_PRIORITY_ASYNC_READ);
+ dmu_prefetch_stream(spa_meta_objset(spa),
+ psls->sls_sm_obj, 0,
+ space_map_length(psls->sls_sm), B_TRUE);
pn++;
ps += space_map_length(psls->sls_sm);
psls = AVL_NEXT(&spa->spa_sm_logs_by_txg, psls);
diff --git a/sys/contrib/openzfs/module/zfs/space_map.c b/sys/contrib/openzfs/module/zfs/space_map.c
index f20c49ebb6de..13c606e9ff34 100644
--- a/sys/contrib/openzfs/module/zfs/space_map.c
+++ b/sys/contrib/openzfs/module/zfs/space_map.c
@@ -92,8 +92,7 @@ space_map_iterate(space_map_t *sm, uint64_t end, sm_cb_t callback, void *arg)
ASSERT3U(end, <=, space_map_length(sm));
ASSERT0(P2PHASE(end, sizeof (uint64_t)));
- dmu_prefetch(sm->sm_os, space_map_object(sm), 0, 0, end,
- ZIO_PRIORITY_SYNC_READ);
+ dmu_prefetch_stream(sm->sm_os, space_map_object(sm), 0, end, B_FALSE);
int error = 0;
uint64_t txg = 0, sync_pass = 0;
diff --git a/sys/contrib/openzfs/module/zfs/vdev.c b/sys/contrib/openzfs/module/zfs/vdev.c
index 9def59b06727..30639d7f4c7f 100644
--- a/sys/contrib/openzfs/module/zfs/vdev.c
+++ b/sys/contrib/openzfs/module/zfs/vdev.c
@@ -3429,23 +3429,51 @@ vdev_dtl_reassess_impl(vdev_t *vd, uint64_t txg, uint64_t scrub_txg,
/* leaf vdevs only */
continue;
}
+ int children = vd->vdev_children;
+ int width = children;
if (t == DTL_PARTIAL) {
/* i.e. non-zero */
minref = 1;
} else if (vdev_get_nparity(vd) != 0) {
/* RAIDZ, DRAID */
minref = vdev_get_nparity(vd) + 1;
+ if (vd->vdev_ops == &vdev_draid_ops) {
+ vdev_draid_config_t *vdc = vd->vdev_tsd;
+ minref = vdc->vdc_nparity + 1;
+ children = vdc->vdc_children;
+ }
} else {
/* any kind of mirror */
minref = vd->vdev_children;
}
+ /*
+ * For dRAID with failure domains, count failures
+ * only once for any i-th child failure in each failure
+ * group, but only if the failures threshold is not
+ * reached in any of the groups.
+ */
+ boolean_t safe2skip = B_FALSE;
+ if (width > children &&
+ vdev_draid_fail_domain_allowed(vd))
+ safe2skip = B_TRUE;
+
space_reftree_create(&reftree);
- for (int c = 0; c < vd->vdev_children; c++) {
- vdev_t *cvd = vd->vdev_child[c];
- mutex_enter(&cvd->vdev_dtl_lock);
- space_reftree_add_map(&reftree,
- cvd->vdev_dtl[s], 1);
- mutex_exit(&cvd->vdev_dtl_lock);
+ for (int c = 0; c < children; c++) {
+ for (int i = c; i < width; i += children) {
+ vdev_t *cvd = vd->vdev_child[i];
+
+ mutex_enter(&cvd->vdev_dtl_lock);
+ space_reftree_add_map(&reftree,
+ cvd->vdev_dtl[s], 1);
+ boolean_t empty =
+ zfs_range_tree_is_empty(
+ cvd->vdev_dtl[s]);
+ mutex_exit(&cvd->vdev_dtl_lock);
+
+ if (s == DTL_OUTAGE && !empty &&
+ safe2skip)
+ break;
+ }
}
space_reftree_generate_map(&reftree,
vd->vdev_dtl[t], minref);
@@ -6307,6 +6335,15 @@ end:
innvl, 6, ZFS_SPACE_CHECK_EXTRA_RESERVED));
}
+static int
+vdev_get_child_idx(vdev_t *vd, uint64_t c_guid)
+{
+ for (int c = 0; c < vd->vdev_children; c++)
+ if (vd->vdev_child[c]->vdev_guid == c_guid)
+ return (c);
+ return (0);
+}
+
int
vdev_prop_get(vdev_t *vd, nvlist_t *innvl, nvlist_t *outnvl)
{
@@ -6413,6 +6450,25 @@ vdev_prop_get(vdev_t *vd, nvlist_t *innvl, nvlist_t *outnvl)
vdev_prop_add_list(outnvl, propname, NULL,
vdev_get_nparity(vd), ZPROP_SRC_NONE);
continue;
+ case VDEV_PROP_FDOMAIN:
+ case VDEV_PROP_FGROUP:
+ if (vd->vdev_ops->vdev_op_leaf &&
+ vd->vdev_top != NULL &&
+ vd->vdev_top->vdev_ops ==
+ &vdev_draid_ops) {
+ vdev_draid_config_t *vdc =
+ vd->vdev_top->vdev_tsd;
+ if (vdc->vdc_width == vdc->vdc_children)
+ continue;
+ int c_idx = vdev_get_child_idx(
+ vd->vdev_top, vd->vdev_guid);
+ vdev_prop_add_list(outnvl, propname,
+ NULL, prop == VDEV_PROP_FDOMAIN ?
+ (c_idx % vdc->vdc_children) :
+ (c_idx / vdc->vdc_children),
+ ZPROP_SRC_NONE);
+ }
+ continue;
case VDEV_PROP_PATH:
if (vd->vdev_path == NULL)
continue;
diff --git a/sys/contrib/openzfs/module/zfs/vdev_draid.c b/sys/contrib/openzfs/module/zfs/vdev_draid.c
index 9e02d868213b..c76557e80c9b 100644
--- a/sys/contrib/openzfs/module/zfs/vdev_draid.c
+++ b/sys/contrib/openzfs/module/zfs/vdev_draid.c
@@ -23,6 +23,8 @@
* Copyright (c) 2018 Intel Corporation.
* Copyright (c) 2020 by Lawrence Livermore National Security, LLC.
* Copyright (c) 2025, Klara, Inc.
+ * Copyright (c) 2026, Seagate Technology, LLC.
+ * Copyright (c) 2026, Wasabi Technologies, Inc.
*/
#include <sys/zfs_context.h>
@@ -140,6 +142,57 @@
* the same for all groups (although some of the logic around computing
* permutation numbers and drive offsets is more complicated).
*
+ * === dRAID failure domains ===
+ *
+ * If we put several slices alongside in a row and configure each disk in
+ * slice to be from different failure domain (for example an enclosure), we
+ * can then tolerate the failure of the whole domain -- only one device
+ * will be failed in every slice in this case. The column of such slices
+ * we will call failure group, and the row with such slices alongside we
+ * will call "big width row", width being multiple of children (W = C*n).
+ *
+ * Here's an example of configuration with 7 failure domains and two
+ * failure groups:
+ *
+ * 7 C disks in each slice, 2 slices in big 14 W rows
+ * +===+===+===+===+===+===+===+===+===+===+===+===+===+===+
+ * | 1 | 7 | 3 | 9 | 11| 5 | 13| 6 | 10| 4 | 8 | 0 | 12| 2 | device map 0
+ * s +===+===+===+===+===+===+===+===+===+===+===+===+===+===+
+ * l | group 0 | gr1..| S | group 3 | gr4.. | S | row 0
+ * c +-------+-------+-------+---+-------+-------+-------+---+
+ * 0,1 | ..gr1 | group 2 | S | ..gr4 | group 5 | S | row 1
+ * +===+===+===+===+===+===+===+===+===+===+===+===+===+===+
+ * | 2 | 10| 12| 7 | 8 | 13| 11| 1 | 5 | 4 | 6 | 3 | 9 | 0 | device map 1
+ * s +===+===+===+===+===+===+===+===+===+===+===+===+===+===+
+ * l | group 6 | gr7..| S | group 9 |gr10.. | S | row 2
+ * c +-------+-------+-------+---+---------------+-------+---+
+ * 2,3 | ..gr7 | group 8 | S |..gr10 | group 11 | S | row 3
+ * +-------+---------------+---+-------+---------------+---+
+ * failure group 0 failure group 1
+ *
+ * In practice, there might be much more failure groups. And in theory, the
+ * width of the big rows can be much larger than curent limit of 255 imposed
+ * for the number of children. But we kept the same limit for now for the
+ * sake of simplicity of implementation.
+ *
+ * In order to preserve fast sequential resilvering in case of a disk failure,
+ * all failure groups much share all disks between themselves, and this is
+ * achieved by shuffling the disks between the groups. But only i-th disks
+ * in each group are shuffled between themselves, i.e. the disks from the
+ * same failure domains (enclosures). After that, they are shuffled within
+ * each group. Thus, no more than one disk from any failure domain can appear
+ * in any failure group as a result of this shuffling. In the above example,
+ * you won't find any tuple of (0, 7) or (1, 8) or (2, 9) or ... (6, 13)
+ * mapped to the same slice. This is done in vdev_draid_shuffle_perms().
+ *
+ * Spare disks are evenly distributed among failure groups, and they are
+ * shared by all groups. However, to support domain failure, we cannot have
+ * more than (nparity - 1) failed disks in any group, no matter if they are
+ * rebuilt to draid spares or not (the blocks of those spares can be mapped
+ * to the disks from the failed domain (enclosure), and we cannot tolerate
+ * more than nparity failures in any failure group).
+ *
+ *
* N.B. The following array describes all valid dRAID permutation maps.
* Each row is used to generate a permutation map for a different number
* of children from a unique seed. The seeds were generated and carefully
@@ -537,6 +590,73 @@ vdev_draid_generate_perms(const draid_map_t *map, uint8_t **permsp)
return (0);
}
+static void
+vdev_draid_swap_perms(uint8_t *perms, uint64_t i, uint64_t j)
+{
+ uint8_t val = perms[i];
+
+ perms[i] = perms[j];
+ perms[j] = val;
+}
+
+/*
+ * Shuffle every i-th disk in slices that lie alongside in the big width row,
+ * increasing disk indices in each next slice in the row accordingly. The
+ * input to this function is the array of ready permutations from
+ * vdev_draid_generate_perms(), so in order to correctly shuffle i-th disks,
+ * we need to locate their position first and build a map of their locations.
+ *
+ * Note: the same Fisher-Yates shuffle algorithm is used as in
+ * vdev_draid_generate_perms().
+ */
+static void
+vdev_draid_shuffle_perms(const draid_map_t *map, uint8_t *perms, uint64_t width)
+{
+ uint64_t cn = map->dm_children;
+ uint64_t n = width / cn;
+ uint64_t nperms = map->dm_nperms / n * n;
+
+ if (width <= cn)
+ return;
+
+ VERIFY3U(width, >=, VDEV_DRAID_MIN_CHILDREN);
+ VERIFY3U(width, <=, VDEV_DRAID_MAX_CHILDREN);
+ ASSERT0(width % cn);
+
+ uint64_t draid_seed[2] = { VDEV_DRAID_SEED, map->dm_seed };
+
+ uint8_t *cmap = kmem_alloc(n, KM_SLEEP);
+
+ for (int i = 0; i < nperms; i += n) {
+ for (int j = 0; j < cn; j++) {
+
+ /* locate position of the same child in other slices */
+ for (int k = n - 1; k > 0; k--)
+ for (int l = 0; l < cn; l++)
+ if (perms[(i+k) * cn + l] ==
+ perms[(i+0) * cn + j])
+ cmap[k] = l;
+ cmap[0] = j;
+
+ /* increase index values for slices on the right */
+ for (int k = n - 1; k > 0; k--)
+ perms[(i+k) * cn + cmap[k]] += k * cn;
+
+ /* shuffle */
+ for (int k = n - 1; k > 0; k--) {
+ int l = vdev_draid_rand(draid_seed) % (k + 1);
+ if (k == l)
+ continue;
+ vdev_draid_swap_perms(perms,
+ (i+k) * cn + cmap[k],
+ (i+l) * cn + cmap[l]);
+ }
+ }
+ }
+
+ kmem_free(cmap, n);
+}
+
/*
* Lookup the fixed draid_map_t for the requested number of children.
*/
@@ -560,17 +680,26 @@ static void
vdev_draid_get_perm(vdev_draid_config_t *vdc, uint64_t pindex,
uint8_t **base, uint64_t *iter)
{
+ uint64_t n = vdc->vdc_width / vdc->vdc_children;
uint64_t ncols = vdc->vdc_children;
- uint64_t poff = pindex % (vdc->vdc_nperms * ncols);
+ uint64_t nperms = (vdc->vdc_nperms / n) * n;
+ uint64_t poff = pindex % (nperms * ncols);
- *base = vdc->vdc_perms + (poff / ncols) * ncols;
- *iter = poff % ncols;
+ ASSERT3P(nperms, >=, ncols * n);
+
+ *base = vdc->vdc_perms + (poff / (ncols * n)) * (ncols * n);
+ *iter = (poff % ncols) + (pindex % n) * ncols;
}
static inline uint64_t
vdev_draid_permute_id(vdev_draid_config_t *vdc,
uint8_t *base, uint64_t iter, uint64_t index)
{
+ if (vdc->vdc_width > vdc->vdc_children) {
+ uint64_t off = (iter / vdc->vdc_children) * vdc->vdc_children;
+ return (base[(index + iter) % vdc->vdc_children + off]);
+ }
+
return ((base[index] + iter) % vdc->vdc_children);
}
@@ -899,7 +1028,7 @@ vdev_draid_map_verify_empty(zio_t *zio, raidz_row_t *rr)
*/
static uint64_t
vdev_draid_logical_to_physical(vdev_t *vd, uint64_t logical_offset,
- uint64_t *perm, uint64_t *start)
+ uint64_t *perm, uint64_t *start, uint64_t *ndisks)
{
vdev_draid_config_t *vdc = vd->vdev_tsd;
@@ -925,16 +1054,31 @@ vdev_draid_logical_to_physical(vdev_t *vd, uint64_t logical_offset,
*/
uint64_t groupwidth = vdc->vdc_groupwidth;
uint64_t ngroups = vdc->vdc_ngroups;
- uint64_t ndisks = vdc->vdc_ndisks;
+
+ uint64_t group = logical_offset / vdc->vdc_groupsz;
+ uint64_t fgrps = vdc->vdc_width / vdc->vdc_children;
+
+ *perm = (group / ngroups) * fgrps;
/*
- * groupstart is where the group this IO will land in "starts" in
- * the permutation array.
+ * Failure groups starting from (vdc_nspares % fgrps) have one less
+ * spare, so they have one more ndisks.
*/
- uint64_t group = logical_offset / vdc->vdc_groupsz;
- uint64_t groupstart = (group * groupwidth) % ndisks;
- ASSERT3U(groupstart + groupwidth, <=, ndisks + groupstart);
- *start = groupstart;
+ uint64_t biggies = vdc->vdc_nspares % fgrps;
+
+ uint64_t poff = 0;
+ group %= ngroups;
+ uint64_t ngroups1 = ngroups / fgrps;
+ if (!biggies || group < biggies * ngroups1)
+ poff = group / ngroups1;
+ else
+ poff = biggies +
+ (group - (biggies * ngroups1)) / (ngroups1 + 1);
+ ASSERT3U(poff, <, fgrps);
+ *perm += poff;
+
+ *ndisks = (vdc->vdc_ndisks / fgrps) +
+ (biggies ? ((poff >= biggies) ? 1 : 0) : 0);
/* b_offset is the sector offset within a group chunk */
b_offset = b_offset % (rowheight_sectors * groupwidth);
@@ -948,9 +1092,33 @@ vdev_draid_logical_to_physical(vdev_t *vd, uint64_t logical_offset,
* - so each permutation covers rows * slice portion of the disk
* - so we need to find the row where this IO group target begins
*/
- *perm = group / ngroups;
- uint64_t row = (*perm * ((groupwidth * ngroups) / ndisks)) +
- (((group % ngroups) * groupwidth) / ndisks);
+ uint64_t perm_rows = (groupwidth * ngroups) / vdc->vdc_ndisks;
+
+ /* Adjust group for our failure group. */
+ if (!biggies || poff <= biggies)
+ group -= poff * ngroups1;
+ else
+ group -= (biggies * ngroups1) +
+ (poff - biggies) * (ngroups1 + 1);
+
+ IMPLY(poff < biggies, group < ngroups1);
+ ASSERT3U(group, <=, ngroups1);
+
+ /*
+ * groupstart is where the group this IO will land in "starts" in
+ * the permutation array.
+ */
+ uint64_t groupstart = (group * groupwidth) % *ndisks;
+ ASSERT3U(groupstart + groupwidth, <=, *ndisks + groupstart);
+ *start = groupstart;
+
+ /* Adjust ngroups for our failure group. */
+ ngroups = ngroups1 + ((biggies && poff >= biggies) ? 1 : 0);
+
+ ASSERT3U(group, <, ngroups);
+
+ uint64_t row = ((*perm / fgrps) * perm_rows) +
+ (((group % ngroups) * groupwidth) / *ndisks);
return (((rowheight_sectors * row) +
(b_offset / groupwidth)) << ashift);
@@ -989,16 +1157,15 @@ vdev_draid_map_alloc_row(zio_t *zio, raidz_row_t **rrp, uint64_t io_offset,
vdev_draid_group_to_offset(vd, group) == io_offset);
/* Lookup starting byte offset on each child vdev */
- uint64_t groupstart, perm;
+ uint64_t groupstart, perm, ndisks;
uint64_t physical_offset = vdev_draid_logical_to_physical(vd,
- io_offset, &perm, &groupstart);
+ io_offset, &perm, &groupstart, &ndisks);
/*
* If there is less than groupwidth drives available after the group
* start, the group is going to wrap onto the next row. 'wrap' is the
* group disk number that starts on the next row.
*/
- uint64_t ndisks = vdc->vdc_ndisks;
uint64_t groupwidth = vdc->vdc_groupwidth;
uint64_t wrap = groupwidth;
@@ -1161,7 +1328,7 @@ vdev_draid_get_astart(vdev_t *vd, const uint64_t start)
/*
* Allocatable space for dRAID is (children - nspares) * sizeof(smallest child)
* rounded down to the last full slice. So each child must provide at least
- * 1 / (children - nspares) of its asize.
+ * 1 / (children - nspares) of its asize rounded up to VDEV_DRAID_ROWHEIGHT.
*/
static uint64_t
vdev_draid_min_asize(vdev_t *vd)
@@ -1171,7 +1338,8 @@ vdev_draid_min_asize(vdev_t *vd)
ASSERT3P(vd->vdev_ops, ==, &vdev_draid_ops);
return (VDEV_DRAID_REFLOW_RESERVE +
- (vd->vdev_min_asize + vdc->vdc_ndisks - 1) / (vdc->vdc_ndisks));
+ DIV_ROUND_UP(DIV_ROUND_UP(vd->vdev_min_asize, vdc->vdc_ndisks),
+ VDEV_DRAID_ROWHEIGHT) * VDEV_DRAID_ROWHEIGHT);
}
/*
@@ -1189,7 +1357,7 @@ vdev_draid_min_alloc(vdev_t *vd)
}
/*
- * Returns true if the txg range does not exist on any leaf vdev.
+ * Returns false if the txg range exists on any leaf vdev, true otherwise.
*
* A dRAID spare does not fit into the DTL model. While it has child vdevs
* there is no redundancy among them, and the effective child vdev is
@@ -1247,8 +1415,7 @@ vdev_draid_missing(vdev_t *vd, uint64_t physical_offset, uint64_t txg,
if (vd == NULL)
return (B_TRUE);
- return (vdev_draid_missing(vd, physical_offset,
- txg, size));
+ return (vdev_draid_missing(vd, physical_offset, txg, size));
}
return (vdev_dtl_contains(vd, DTL_MISSING, txg, size));
@@ -1396,16 +1563,16 @@ vdev_draid_group_degraded(vdev_t *vd, uint64_t offset)
ASSERT3P(vd->vdev_ops, ==, &vdev_draid_ops);
ASSERT3U(vdev_draid_get_astart(vd, offset), ==, offset);
- uint64_t groupstart, perm;
+ uint64_t groupstart, perm, ndisks;
uint64_t physical_offset = vdev_draid_logical_to_physical(vd,
- offset, &perm, &groupstart);
+ offset, &perm, &groupstart, &ndisks);
uint8_t *base;
uint64_t iter;
vdev_draid_get_perm(vdc, perm, &base, &iter);
for (uint64_t i = 0; i < vdc->vdc_groupwidth; i++) {
- uint64_t c = (groupstart + i) % vdc->vdc_ndisks;
+ uint64_t c = (groupstart + i) % ndisks;
uint64_t cid = vdev_draid_permute_id(vdc, base, iter, c);
vdev_t *cvd = vd->vdev_child[cid];
@@ -1436,16 +1603,16 @@ vdev_draid_group_missing(vdev_t *vd, uint64_t offset, uint64_t txg,
ASSERT3P(vd->vdev_ops, ==, &vdev_draid_ops);
ASSERT3U(vdev_draid_get_astart(vd, offset), ==, offset);
- uint64_t groupstart, perm;
+ uint64_t groupstart, perm, ndisks;
uint64_t physical_offset = vdev_draid_logical_to_physical(vd,
- offset, &perm, &groupstart);
+ offset, &perm, &groupstart, &ndisks);
uint8_t *base;
uint64_t iter;
vdev_draid_get_perm(vdc, perm, &base, &iter);
for (uint64_t i = 0; i < vdc->vdc_groupwidth; i++) {
- uint64_t c = (groupstart + i) % vdc->vdc_ndisks;
+ uint64_t c = (groupstart + i) % ndisks;
uint64_t cid = vdev_draid_permute_id(vdc, base, iter, c);
vdev_t *cvd = vd->vdev_child[cid];
@@ -1528,7 +1695,7 @@ vdev_draid_open(vdev_t *vd, uint64_t *asize, uint64_t *max_asize,
int open_errors = 0;
if (nparity > VDEV_DRAID_MAXPARITY ||
- vd->vdev_children < nparity + 1) {
+ vdc->vdc_children < nparity + 1) {
vd->vdev_stat.vs_aux = VDEV_AUX_BAD_LABEL;
return (SET_ERROR(EINVAL));
}
@@ -1541,12 +1708,26 @@ vdev_draid_open(vdev_t *vd, uint64_t *asize, uint64_t *max_asize,
vdev_open_children_subset(vd, vdev_draid_open_children);
vdev_open_children_subset(vd, vdev_draid_open_spares);
- /* Verify enough of the children are available to continue. */
- for (int c = 0; c < vd->vdev_children; c++) {
- if (vd->vdev_child[c]->vdev_open_error != 0) {
- if ((++open_errors) > nparity) {
- vd->vdev_stat.vs_aux = VDEV_AUX_NO_REPLICAS;
- return (SET_ERROR(ENXIO));
+ /*
+ * Verify enough of the children are available to continue.
+ * If several disks got failed on i-th position in each slice in the
+ * big width row (failure groups) - they are counted as one failure,
+ * but only if the failures threshold is not reached in any group.
+ */
+ boolean_t safe2skip = B_FALSE;
+ if (vdc->vdc_width > vdc->vdc_children &&
+ vdev_draid_fail_domain_allowed(vd))
+ safe2skip = B_TRUE;
+ for (int c = 0; c < vdc->vdc_children; c++) {
+ for (int i = c; i < vdc->vdc_width; i += vdc->vdc_children) {
+ if (vd->vdev_child[i]->vdev_open_error != 0) {
+ if ((++open_errors) > nparity) {
+ vd->vdev_stat.vs_aux =
+ VDEV_AUX_NO_REPLICAS;
+ return (SET_ERROR(ENXIO));
+ }
+ if (safe2skip)
+ break;
}
}
}
@@ -1581,6 +1762,16 @@ vdev_draid_open(vdev_t *vd, uint64_t *asize, uint64_t *max_asize,
*max_asize = (((child_max_asize * vdc->vdc_ndisks) / vdc->vdc_groupsz) *
vdc->vdc_groupsz);
+ /*
+ * For failure groups with multiple silices in the big width row,
+ * round down to the big slice size.
+ */
+ if (vdc->vdc_width > vdc->vdc_children) {
+ uint64_t slicesz = vdc->vdc_devslicesz * vdc->vdc_ndisks;
+ *asize = (*asize / slicesz) * slicesz;
+ *max_asize = (*max_asize / slicesz) * slicesz;
+ }
+
return (0);
}
@@ -1667,10 +1858,11 @@ vdev_draid_metaslab_init(vdev_t *vd, uint64_t *ms_start, uint64_t *ms_size)
*/
int
vdev_draid_spare_create(nvlist_t *nvroot, vdev_t *vd, uint64_t *ndraidp,
- uint64_t next_vdev_id)
+ uint64_t *nfgroupp, uint64_t next_vdev_id)
{
uint64_t draid_nspares = 0;
uint64_t ndraid = 0;
+ uint64_t nfgroup = 0;
int error;
for (uint64_t i = 0; i < vd->vdev_children; i++) {
@@ -1680,11 +1872,14 @@ vdev_draid_spare_create(nvlist_t *nvroot, vdev_t *vd, uint64_t *ndraidp,
vdev_draid_config_t *vdc = cvd->vdev_tsd;
draid_nspares += vdc->vdc_nspares;
ndraid++;
+ if (vdc->vdc_width > vdc->vdc_children)
+ nfgroup++;
}
}
if (draid_nspares == 0) {
*ndraidp = ndraid;
+ *nfgroupp = nfgroup;
return (0);
}
@@ -1752,6 +1947,7 @@ vdev_draid_spare_create(nvlist_t *nvroot, vdev_t *vd, uint64_t *ndraidp,
kmem_free(new_spares, sizeof (*new_spares) * n);
*ndraidp = ndraid;
+ *nfgroupp = nfgroup;
return (0);
}
@@ -1907,12 +2103,34 @@ vdev_draid_io_start_read(zio_t *zio, raidz_row_t *rr)
}
if (vdev_draid_missing(cvd, rc->rc_offset, zio->io_txg, 1)) {
+ vdev_t *svd;
+
if (c >= rr->rr_firstdatacol)
rr->rr_missingdata++;
else
rr->rr_missingparity++;
rc->rc_error = SET_ERROR(ESTALE);
rc->rc_skipped = 1;
+
+ /*
+ * If this child has draid spare attached, and that
+ * spare by rc_offset maps to another spare, the repair
+ * would go to that spare, and we want all mirrored
+ * children on it to be updated with the repaired data,
+ * even when we cannot vouch for it during rebuilds
+ * (which don't have checksums). Otherwise, we will have
+ * a lot of checksum errors on that spares during scrub.
+ * The worst thing that can happen in this case is that
+ * we will update the reserved spare column on some
+ * device with unverified data, which is harmless.
+ */
+ if ((svd = vdev_draid_find_spare(cvd)) != NULL) {
+ svd = vdev_draid_spare_get_child(svd,
+ rc->rc_offset);
+ if (svd && (svd->vdev_ops == &vdev_spare_ops ||
+ svd->vdev_ops == &vdev_replacing_ops))
+ rc->rc_tgt_is_dspare = 1;
+ }
continue;
}
@@ -1930,34 +2148,15 @@ vdev_draid_io_start_read(zio_t *zio, raidz_row_t *rr)
vdev_t *svd;
/*
- * Sequential rebuilds need to always consider the data
- * on the child being rebuilt to be stale. This is
- * important when all columns are available to aid
- * known reconstruction in identifing which columns
- * contain incorrect data.
- *
- * Furthermore, all repairs need to be constrained to
- * the devices being rebuilt because without a checksum
- * we cannot verify the data is actually correct and
- * performing an incorrect repair could result in
- * locking in damage and making the data unrecoverable.
+ * Repairs need to be constrained to the devices being
+ * rebuilt since without a checksum we cannot verify the
+ * data is actually correct and performing an incorrect
+ * repair could result in locking in the damage and
+ * making the data unrecoverable.
*/
- if (zio->io_priority == ZIO_PRIORITY_REBUILD) {
- if (vdev_draid_rebuilding(cvd)) {
- if (c >= rr->rr_firstdatacol)
- rr->rr_missingdata++;
- else
- rr->rr_missingparity++;
- rc->rc_error = SET_ERROR(ESTALE);
- rc->rc_skipped = 1;
- rc->rc_allow_repair = 1;
- continue;
- } else {
- rc->rc_allow_repair = 0;
- }
- } else {
- rc->rc_allow_repair = 1;
- }
+ if (zio->io_priority == ZIO_PRIORITY_REBUILD &&
+ !vdev_draid_rebuilding(cvd))
+ rc->rc_allow_repair = 0;
/*
* If this child is a distributed spare then the
@@ -2093,7 +2292,7 @@ vdev_draid_state_change(vdev_t *vd, int faulted, int degraded)
vdev_draid_config_t *vdc = vd->vdev_tsd;
ASSERT(vd->vdev_ops == &vdev_draid_ops);
- if (faulted > vdc->vdc_nparity)
+ if (faulted > vdc->vdc_nparity * (vdc->vdc_width / vdc->vdc_children))
vdev_set_state(vd, B_FALSE, VDEV_STATE_CANT_OPEN,
VDEV_AUX_NO_REPLICAS);
else if (degraded + faulted != 0)
@@ -2147,9 +2346,9 @@ vdev_draid_xlate(vdev_t *cvd, const zfs_range_seg64_t *logical_rs,
logical_end = nextstart;
/* Find the starting offset for each vdev in the group */
- uint64_t perm, groupstart;
+ uint64_t perm, groupstart, ndisks;
uint64_t start = vdev_draid_logical_to_physical(raidvd,
- logical_start, &perm, &groupstart);
+ logical_start, &perm, &groupstart, &ndisks);
uint64_t end = start;
uint8_t *base;
@@ -2163,7 +2362,7 @@ vdev_draid_xlate(vdev_t *cvd, const zfs_range_seg64_t *logical_rs,
* (zero-length) physical range being returned.
*/
for (uint64_t i = 0; i < vdc->vdc_groupwidth; i++) {
- uint64_t c = (groupstart + i) % vdc->vdc_ndisks;
+ uint64_t c = (groupstart + i) % ndisks;
if (c == 0 && i != 0) {
/* the group wrapped, increment the start */
@@ -2210,6 +2409,10 @@ vdev_draid_config_generate(vdev_t *vd, nvlist_t *nv)
fnvlist_add_uint64(nv, ZPOOL_CONFIG_DRAID_NDATA, vdc->vdc_ndata);
fnvlist_add_uint64(nv, ZPOOL_CONFIG_DRAID_NSPARES, vdc->vdc_nspares);
fnvlist_add_uint64(nv, ZPOOL_CONFIG_DRAID_NGROUPS, vdc->vdc_ngroups);
+
+ if (spa_feature_is_active(vd->vdev_spa, SPA_FEATURE_DRAID_FAIL_DOMAINS))
+ fnvlist_add_uint64(nv, ZPOOL_CONFIG_DRAID_NCHILDREN,
+ vdc->vdc_children);
}
/*
@@ -2230,30 +2433,44 @@ vdev_draid_init(spa_t *spa, nvlist_t *nv, void **tsd)
return (SET_ERROR(EINVAL));
}
- uint_t children;
+ uint_t width;
+ uint64_t children;
nvlist_t **child;
if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_CHILDREN,
- &child, &children) != 0 || children == 0 ||
- children > VDEV_DRAID_MAX_CHILDREN) {
+ &child, &width) != 0 || width == 0) {
return (SET_ERROR(EINVAL));
}
- if (nvlist_lookup_uint64(nv, ZPOOL_CONFIG_DRAID_NSPARES, &nspares) ||
- nspares > 100 || nspares > (children - (ndata + nparity))) {
- return (SET_ERROR(EINVAL));
+ if (nvlist_lookup_uint64(nv, ZPOOL_CONFIG_DRAID_NCHILDREN, &children)) {
+ children = width;
+ if (children > VDEV_DRAID_MAX_CHILDREN)
+ return (SET_ERROR(EINVAL));
}
- if (nvlist_lookup_uint64(nv, ZPOOL_CONFIG_DRAID_NGROUPS, &ngroups) ||
- ngroups == 0 || ngroups > VDEV_DRAID_MAX_CHILDREN) {
+ if (children == 0 || width % children != 0)
+ return (SET_ERROR(EINVAL));
+
+ if (nvlist_lookup_uint64(nv, ZPOOL_CONFIG_DRAID_NSPARES, &nspares) ||
+ nspares > 100) {
return (SET_ERROR(EINVAL));
}
+ int fgrps = width / children;
+ int nspare = nspares / fgrps;
+ if (nspares % fgrps)
+ nspare++;
+
/*
* Validate the minimum number of children exist per group for the
* specified parity level (draid1 >= 2, draid2 >= 3, draid3 >= 4).
*/
- if (children < (ndata + nparity + nspares))
+ if (children < (ndata + nparity + nspare))
+ return (SET_ERROR(EINVAL));
+
+ if (nvlist_lookup_uint64(nv, ZPOOL_CONFIG_DRAID_NGROUPS, &ngroups) ||
+ ngroups == 0 || ngroups > VDEV_DRAID_MAX_CHILDREN) {
return (SET_ERROR(EINVAL));
+ }
/*
* Create the dRAID configuration using the pool nvlist configuration
@@ -2272,6 +2489,7 @@ vdev_draid_init(spa_t *spa, nvlist_t *nv, void **tsd)
vdc->vdc_nspares = nspares;
vdc->vdc_children = children;
vdc->vdc_ngroups = ngroups;
+ vdc->vdc_width = width;
vdc->vdc_nperms = map->dm_nperms;
error = vdev_draid_generate_perms(map, &vdc->vdc_perms);
@@ -2280,11 +2498,14 @@ vdev_draid_init(spa_t *spa, nvlist_t *nv, void **tsd)
return (SET_ERROR(EINVAL));
}
+ if (width > children)
+ vdev_draid_shuffle_perms(map, vdc->vdc_perms, width);
+
/*
* Derived constants.
*/
vdc->vdc_groupwidth = vdc->vdc_ndata + vdc->vdc_nparity;
- vdc->vdc_ndisks = vdc->vdc_children - vdc->vdc_nspares;
+ vdc->vdc_ndisks = vdc->vdc_width - vdc->vdc_nspares;
vdc->vdc_groupsz = vdc->vdc_groupwidth * VDEV_DRAID_ROWHEIGHT;
vdc->vdc_devslicesz = (vdc->vdc_groupsz * vdc->vdc_ngroups) /
vdc->vdc_ndisks;
@@ -2317,7 +2538,7 @@ vdev_draid_nparity(vdev_t *vd)
{
vdev_draid_config_t *vdc = vd->vdev_tsd;
- return (vdc->vdc_nparity);
+ return (vdc->vdc_nparity * (vdc->vdc_width / vdc->vdc_children));
}
static uint64_t
@@ -2429,17 +2650,25 @@ vdev_draid_spare_get_child(vdev_t *vd, uint64_t physical_offset)
vdev_t *tvd = vds->vds_draid_vdev;
vdev_draid_config_t *vdc = tvd->vdev_tsd;
+ uint64_t fgrps = vdc->vdc_width / vdc->vdc_children;
+
ASSERT3P(tvd->vdev_ops, ==, &vdev_draid_ops);
ASSERT3U(vds->vds_spare_id, <, vdc->vdc_nspares);
uint8_t *base;
uint64_t iter;
- uint64_t perm = physical_offset / vdc->vdc_devslicesz;
+ uint64_t perm = (physical_offset / vdc->vdc_devslicesz) * fgrps;
+
+ /*
+ * Adjust permutation so that it points to the correct slice in the
+ * big width row.
+ */
+ perm += vds->vds_spare_id % fgrps;
vdev_draid_get_perm(vdc, perm, &base, &iter);
uint64_t cid = vdev_draid_permute_id(vdc, base, iter,
- (tvd->vdev_children - 1) - vds->vds_spare_id);
+ (vdc->vdc_children - 1) - (vds->vds_spare_id / fgrps));
vdev_t *cvd = tvd->vdev_child[cid];
if (cvd->vdev_ops == &vdev_draid_spare_ops)
@@ -2448,6 +2677,40 @@ vdev_draid_spare_get_child(vdev_t *vd, uint64_t physical_offset)
return (cvd);
}
+/*
+ * Returns true if no failure group reached failures threshold so that
+ * enclosure failure cannot be tolerated anymore. Used spares are counted
+ * as failures because in case of enclosure failure their blocks can belong
+ * to the disks from that enclosure and can be lost.
+ */
+boolean_t
+vdev_draid_fail_domain_allowed(vdev_t *vd)
+{
+ vdev_draid_config_t *vdc = vd->vdev_tsd;
+
+ ASSERT3P(vd->vdev_ops, ==, &vdev_draid_ops);
+ ASSERT3P(vdc->vdc_width, >, vdc->vdc_children);
+
+ int counter = 0;
+
+ for (int c = 0; c < vdc->vdc_width; c++) {
+ vdev_t *cvd = vd->vdev_child[c];
+
+ if ((c % vdc->vdc_children) == 0)
+ counter = 0;
+
+ if (cvd->vdev_ops == &vdev_spare_ops ||
+ cvd->vdev_ops == &vdev_draid_spare_ops ||
+ !vdev_readable(cvd))
+ counter++;
+
+ if (counter > vdc->vdc_nparity)
+ return (B_FALSE);
+ }
+
+ return (B_TRUE);
+}
+
static void
vdev_draid_spare_close(vdev_t *vd)
{
diff --git a/sys/contrib/openzfs/module/zfs/vdev_label.c b/sys/contrib/openzfs/module/zfs/vdev_label.c
index 16ba09c6f295..b1371b0349c6 100644
--- a/sys/contrib/openzfs/module/zfs/vdev_label.c
+++ b/sys/contrib/openzfs/module/zfs/vdev_label.c
@@ -1109,8 +1109,29 @@ vdev_label_init(vdev_t *vd, uint64_t crtxg, vdev_labeltype_t reason)
* Determine if the vdev is in use.
*/
if (reason != VDEV_LABEL_REMOVE && reason != VDEV_LABEL_SPLIT &&
- vdev_inuse(vd, crtxg, reason, &spare_guid, &l2cache_guid))
+ vdev_inuse(vd, crtxg, reason, &spare_guid, &l2cache_guid)) {
+ if (spa->spa_create_info == NULL) {
+ nvlist_t *nv = fnvlist_alloc();
+ nvlist_t *cfg;
+
+ if (vd->vdev_path != NULL)
+ fnvlist_add_string(nv,
+ ZPOOL_CREATE_INFO_VDEV, vd->vdev_path);
+
+ cfg = vdev_label_read_config(vd, -1ULL);
+ if (cfg != NULL) {
+ const char *pname;
+ if (nvlist_lookup_string(cfg,
+ ZPOOL_CONFIG_POOL_NAME, &pname) == 0)
+ fnvlist_add_string(nv,
+ ZPOOL_CREATE_INFO_POOL, pname);
+ nvlist_free(cfg);
+ }
+
+ spa->spa_create_info = nv;
+ }
return (SET_ERROR(EBUSY));
+ }
/*
* If this is a request to add or replace a spare or l2cache device
diff --git a/sys/contrib/openzfs/module/zfs/vdev_mirror.c b/sys/contrib/openzfs/module/zfs/vdev_mirror.c
index 18efdaac006f..35a4a5bebecf 100644
--- a/sys/contrib/openzfs/module/zfs/vdev_mirror.c
+++ b/sys/contrib/openzfs/module/zfs/vdev_mirror.c
@@ -669,18 +669,19 @@ vdev_mirror_io_start(zio_t *zio)
}
while (children--) {
- mc = &mm->mm_child[c];
- c++;
+ mc = &mm->mm_child[c++];
/*
- * When sequentially resilvering only issue write repair
- * IOs to the vdev which is being rebuilt since performance
- * is limited by the slowest child. This is an issue for
- * faster replacement devices such as distributed spares.
+ * When sequentially resilvering and the integrity of the data
+ * is speculative (ZIO_FLAG_SPECULATIVE), issue write repair IOs
+ * only to the vdev which is being rebuilt. Existing data on
+ * other children must never be overwritten with unconfirmed
+ * data to avoid unrecoverable damage to the pool.
*/
if ((zio->io_priority == ZIO_PRIORITY_REBUILD) &&
(zio->io_flags & ZIO_FLAG_IO_REPAIR) &&
!(zio->io_flags & ZIO_FLAG_SCRUB) &&
+ (zio->io_flags & ZIO_FLAG_SPECULATIVE) &&
mm->mm_rebuilding && !mc->mc_rebuilding) {
continue;
}
diff --git a/sys/contrib/openzfs/module/zfs/vdev_raidz.c b/sys/contrib/openzfs/module/zfs/vdev_raidz.c
index 520ddd692bda..aa44acbf39cb 100644
--- a/sys/contrib/openzfs/module/zfs/vdev_raidz.c
+++ b/sys/contrib/openzfs/module/zfs/vdev_raidz.c
@@ -25,6 +25,7 @@
* Copyright (c) 2012, 2020 by Delphix. All rights reserved.
* Copyright (c) 2016 Gvozden Nešković. All rights reserved.
* Copyright (c) 2025, Klara, Inc.
+ * Copyright (c) 2026, Wasabi Technologies, Inc.
*/
#include <sys/zfs_context.h>
@@ -3104,6 +3105,7 @@ vdev_raidz_io_done_verified(zio_t *zio, raidz_row_t *rr)
int parity_errors = 0;
int parity_untried = 0;
int data_errors = 0;
+ zio_flag_t add_flags = 0;
ASSERT3U(zio->io_type, ==, ZIO_TYPE_READ);
@@ -3134,10 +3136,30 @@ vdev_raidz_io_done_verified(zio_t *zio, raidz_row_t *rr)
* Note that we also regenerate parity when resilvering so we
* can write it out to failed devices later.
*/
- if (parity_errors + parity_untried <
- rr->rr_firstdatacol - data_errors ||
- (zio->io_flags & ZIO_FLAG_RESILVER)) {
+ boolean_t parity_verify = (parity_errors + parity_untried) <
+ (rr->rr_firstdatacol - data_errors);
+ if (parity_verify || (zio->io_flags & ZIO_FLAG_RESILVER)) {
int n = raidz_parity_verify(zio, rr);
+ /*
+ * In, Reed-Solomon encoding, if we have ndata+1 columns and
+ * the parity doesn't match, it means the data integrity is
+ * compromised. We shouldn't try to repair anything in this
+ * case.
+ */
+ if (parity_verify && n > 0 &&
+ zio->io_priority == ZIO_PRIORITY_REBUILD)
+ return;
+ /*
+ * If we have only ndata columns, the data integrity will
+ * be checked by the checksums normally, but not in case
+ * of rebuild when we don't have checksums. In this case,
+ * we add ZIO_FLAG_SPECULATIVE and try to not spread
+ * unverified data. For example, when the target vdev happens
+ * to be the mirroring spare vdev, we would repair only that
+ * child in it which is being rebuilt.
+ */
+ if (!parity_verify && zio->io_priority == ZIO_PRIORITY_REBUILD)
+ add_flags |= ZIO_FLAG_SPECULATIVE;
unexpected_errors += n;
}
@@ -3163,13 +3185,27 @@ vdev_raidz_io_done_verified(zio_t *zio, raidz_row_t *rr)
*/
ASSERT0(zio->io_flags & ZIO_FLAG_DIO_READ);
+ /*
+ * When the target vdev is draid spare, we should clear
+ * ZIO_FLAG_SPECULATIVE. First, if that draid spare maps
+ * to another spare having an online/degraded disk, that
+ * disk must be repaired also. Otherwise, the scrub will
+ * detect a lot of cksum errors later. Second, since it
+ * is draid spare, there is no harm in updating its
+ * content on any vdev it maps to because the space is
+ * reserved as a spare anyway.
+ */
+ zio_flag_t aflags = add_flags;
+ if (rc->rc_tgt_is_dspare)
+ aflags &= ~ZIO_FLAG_SPECULATIVE;
+
zio_nowait(zio_vdev_child_io(zio, NULL, cvd,
rc->rc_offset, rc->rc_abd, rc->rc_size,
ZIO_TYPE_WRITE,
zio->io_priority == ZIO_PRIORITY_REBUILD ?
ZIO_PRIORITY_REBUILD : ZIO_PRIORITY_ASYNC_WRITE,
ZIO_FLAG_IO_REPAIR | (unexpected_errors ?
- ZIO_FLAG_SELF_HEAL : 0), NULL, NULL));
+ ZIO_FLAG_SELF_HEAL : 0) | aflags, NULL, NULL));
}
}
@@ -3271,11 +3307,18 @@ raidz_simulate_failure(int physical_width, int original_width, int ashift,
static int
raidz_reconstruct(zio_t *zio, int *ltgts, int ntgts, int nparity)
{
+ vdev_t *vd = zio->io_vd;
raidz_map_t *rm = zio->io_vsd;
- int physical_width = zio->io_vd->vdev_children;
+ int physical_width = vd->vdev_children;
+ int dbgmsg = zfs_flags & ZFS_DEBUG_RAIDZ_RECONSTRUCT;
+
+ if (vd->vdev_ops == &vdev_draid_ops) {
+ vdev_draid_config_t *vdc = vd->vdev_tsd;
+ physical_width = vdc->vdc_children;
+ }
+
int original_width = (rm->rm_original_width != 0) ?
rm->rm_original_width : physical_width;
- int dbgmsg = zfs_flags & ZFS_DEBUG_RAIDZ_RECONSTRUCT;
if (dbgmsg) {
zfs_dbgmsg("raidz_reconstruct_expanded(zio=%px ltgts=%u,%u,%u "
@@ -3465,9 +3508,17 @@ raidz_reconstruct(zio_t *zio, int *ltgts, int ntgts, int nparity)
static int
vdev_raidz_combrec(zio_t *zio)
{
- int nparity = vdev_get_nparity(zio->io_vd);
+ vdev_t *vd = zio->io_vd;
+ int nparity = vdev_get_nparity(vd);
raidz_map_t *rm = zio->io_vsd;
int physical_width = zio->io_vd->vdev_children;
+
+ if (vd->vdev_ops == &vdev_draid_ops) {
+ vdev_draid_config_t *vdc = vd->vdev_tsd;
+ nparity = vdc->vdc_nparity;
+ physical_width = vdc->vdc_children;
+ }
+
int original_width = (rm->rm_original_width != 0) ?
rm->rm_original_width : physical_width;
diff --git a/sys/contrib/openzfs/module/zfs/zfs_ioctl.c b/sys/contrib/openzfs/module/zfs/zfs_ioctl.c
index 3bbc9107ae2e..fe98e7db073e 100644
--- a/sys/contrib/openzfs/module/zfs/zfs_ioctl.c
+++ b/sys/contrib/openzfs/module/zfs/zfs_ioctl.c
@@ -41,7 +41,7 @@
* Copyright (c) 2019, 2020 by Christian Schwarz. All rights reserved.
* Copyright (c) 2019, 2021, 2023, 2024, Klara Inc.
* Copyright (c) 2019, Allan Jude
- * Copyright 2024 Oxide Computer Company
+ * Copyright 2026 Oxide Computer Company
*/
/*
@@ -286,6 +286,59 @@ static int zfs_fill_zplprops_root(uint64_t, nvlist_t *, nvlist_t *,
int zfs_set_prop_nvlist(const char *, zprop_source_t, nvlist_t *, nvlist_t *);
static int get_nvlist(uint64_t nvl, uint64_t size, int iflag, nvlist_t **nvp);
+/*
+ * Callback for SPL to look up zoned_uid property.
+ * Walks ancestors to find the delegation root with zoned_uid set.
+ * Returns the zoned_uid value if found, or 0 if not set.
+ */
+static uid_t
+zfs_get_zoned_uid(const char *dataset, char *root_out, size_t root_size)
+{
+ char path[ZFS_MAX_DATASET_NAME_LEN];
+ char setpoint[ZFS_MAX_DATASET_NAME_LEN];
+ char *slash, *at;
+ uint64_t zoned_uid_val = 0;
+ int error;
+
+ (void) strlcpy(path, dataset, sizeof (path));
+
+ /*
+ * Strip snapshot suffix if present — snapshots inherit properties
+ * from their parent filesystem.
+ */
+ at = strchr(path, '@');
+ if (at != NULL)
+ *at = '\0';
+
+ /*
+ * Walk up the hierarchy until we find a dataset with zoned_uid set.
+ * This handles the case where the dataset doesn't exist yet (e.g.,
+ * rename destination) — dsl_prop_get fails on non-existent datasets,
+ * so we walk up to find an existing ancestor.
+ *
+ * When the property is found (possibly via inheritance), setpoint
+ * tells us the actual delegation root where zoned_uid is locally
+ * set, rather than the dataset where we happened to query it.
+ */
+ while (path[0] != '\0') {
+ error = dsl_prop_get(path, "zoned_uid", 8, 1,
+ &zoned_uid_val, setpoint);
+
+ if (error == 0 && zoned_uid_val != 0) {
+ if (root_out != NULL)
+ (void) strlcpy(root_out, setpoint, root_size);
+ return ((uid_t)zoned_uid_val);
+ }
+
+ slash = strrchr(path, '/');
+ if (slash == NULL)
+ break;
+ *slash = '\0';
+ }
+
+ return (0);
+}
+
static void
history_str_free(char *buf)
{
@@ -502,6 +555,42 @@ zfs_secpolicy_write_perms(const char *name, const char *perm, cred_t *cr)
}
/*
+ * Check dsl_deleg permission for zoned_uid datasets.
+ *
+ * This bypasses zfs_dozonecheck_ds() (which requires the 'zoned' property)
+ * because zoned_uid datasets use a different authentication model. The zone
+ * check was already performed by zone_dataset_admin_check().
+ *
+ * Returns 0 if permission is granted, error otherwise.
+ * ECANCELED from dsl_deleg_access_impl() means delegation is disabled on the
+ * pool — in that case we deny access (POLP: no delegation = no access).
+ */
+static int
+zfs_secpolicy_zoned_uid_deleg(const char *name, const char *perm, cred_t *cr)
+{
+ dsl_pool_t *dp;
+ dsl_dataset_t *ds;
+ int error;
+
+ error = dsl_pool_hold(name, FTAG, &dp);
+ if (error != 0)
+ return (error);
+ error = dsl_dataset_hold(dp, name, FTAG, &ds);
+ if (error != 0) {
+ dsl_pool_rele(dp, FTAG);
+ return (error);
+ }
+ error = dsl_deleg_access_impl(ds, perm, cr);
+ dsl_dataset_rele(ds, FTAG);
+ dsl_pool_rele(dp, FTAG);
+
+ /* ECANCELED = delegation disabled on pool; deny access (POLP) */
+ if (error == ECANCELED)
+ return (SET_ERROR(EPERM));
+ return (error);
+}
+
+/*
* Policy for setting the security label property.
*
* Returns 0 for success, non-zero for access and other errors.
@@ -607,6 +696,31 @@ zfs_secpolicy_setprop(const char *dsname, zfs_prop_t prop, nvpair_t *propval,
cred_t *cr)
{
const char *strval;
+ zone_admin_result_t zone_result;
+
+ /*
+ * Check zoned_uid delegation first. However, even delegated
+ * namespace users must not be allowed to modify zoned_uid itself.
+ */
+ zone_result = zone_dataset_admin_check(dsname, ZONE_OP_SETPROP, NULL);
+ if (zone_result == ZONE_ADMIN_ALLOWED) {
+ if (prop == ZFS_PROP_ZONED_UID)
+ return (SET_ERROR(EPERM));
+ if (prop == ZFS_PROP_FILESYSTEM_LIMIT ||
+ prop == ZFS_PROP_SNAPSHOT_LIMIT) {
+ char setpoint[ZFS_MAX_DATASET_NAME_LEN];
+ uint64_t zoned_uid_val = 0;
+ if (dsl_prop_get(dsname, "zoned_uid", 8, 1,
+ &zoned_uid_val, setpoint) == 0 &&
+ zoned_uid_val != 0 &&
+ strcmp(dsname, setpoint) == 0)
+ return (SET_ERROR(EPERM));
+ }
+ return (zfs_secpolicy_zoned_uid_deleg(dsname,
+ zfs_prop_to_name(prop), cr));
+ }
+ if (zone_result == ZONE_ADMIN_DENIED)
+ return (SET_ERROR(EPERM));
/*
* Check permissions for special properties.
@@ -621,6 +735,15 @@ zfs_secpolicy_setprop(const char *dsname, zfs_prop_t prop, nvpair_t *propval,
if (!INGLOBALZONE(curproc))
return (SET_ERROR(EPERM));
break;
+ case ZFS_PROP_ZONED_UID:
+ /*
+ * Disallow setting of 'zoned_uid' from within a
+ * delegated namespace -- only global zone can manage
+ * delegation assignments.
+ */
+ if (!INGLOBALZONE(curproc))
+ return (SET_ERROR(EPERM));
+ break;
case ZFS_PROP_QUOTA:
case ZFS_PROP_FILESYSTEM_LIMIT:
@@ -774,7 +897,21 @@ int
zfs_secpolicy_destroy_perms(const char *name, cred_t *cr)
{
int error;
+ zone_admin_result_t result;
+
+ /* Check zoned_uid delegation first */
+ result = zone_dataset_admin_check(name, ZONE_OP_DESTROY, NULL);
+ if (result == ZONE_ADMIN_ALLOWED) {
+ if ((error = zfs_secpolicy_zoned_uid_deleg(name,
+ ZFS_DELEG_PERM_DESTROY, cr)) != 0)
+ return (error);
+ return (zfs_secpolicy_zoned_uid_deleg(name,
+ ZFS_DELEG_PERM_MOUNT, cr));
+ }
+ if (result == ZONE_ADMIN_DENIED)
+ return (SET_ERROR(EPERM));
+ /* NOT_APPLICABLE: continue with existing checks */
if ((error = zfs_secpolicy_write_perms(name,
ZFS_DELEG_PERM_MOUNT, cr)) != 0)
return (error);
@@ -831,7 +968,21 @@ zfs_secpolicy_rename_perms(const char *from, const char *to, cred_t *cr)
{
char parentname[ZFS_MAX_DATASET_NAME_LEN];
int error;
+ zone_admin_result_t result;
+ /* Check zoned_uid delegation first */
+ result = zone_dataset_admin_check(from, ZONE_OP_RENAME, to);
+ if (result == ZONE_ADMIN_ALLOWED) {
+ if ((error = zfs_secpolicy_zoned_uid_deleg(from,
+ ZFS_DELEG_PERM_RENAME, cr)) != 0)
+ return (error);
+ return (zfs_secpolicy_zoned_uid_deleg(from,
+ ZFS_DELEG_PERM_MOUNT, cr));
+ }
+ if (result == ZONE_ADMIN_DENIED)
+ return (SET_ERROR(EPERM));
+
+ /* NOT_APPLICABLE: continue with existing checks */
if ((error = zfs_secpolicy_write_perms(from,
ZFS_DELEG_PERM_RENAME, cr)) != 0)
return (error);
@@ -940,6 +1091,17 @@ zfs_secpolicy_recv(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr)
int
zfs_secpolicy_snapshot_perms(const char *name, cred_t *cr)
{
+ zone_admin_result_t result;
+
+ /* Check zoned_uid delegation first */
+ result = zone_dataset_admin_check(name, ZONE_OP_SNAPSHOT, NULL);
+ if (result == ZONE_ADMIN_ALLOWED)
+ return (zfs_secpolicy_zoned_uid_deleg(name,
+ ZFS_DELEG_PERM_SNAPSHOT, cr));
+ if (result == ZONE_ADMIN_DENIED)
+ return (SET_ERROR(EPERM));
+
+ /* NOT_APPLICABLE: continue with existing checks */
return (zfs_secpolicy_write_perms(name,
ZFS_DELEG_PERM_SNAPSHOT, cr));
}
@@ -1062,13 +1224,35 @@ zfs_secpolicy_create_clone(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr)
{
char parentname[ZFS_MAX_DATASET_NAME_LEN];
int error;
- const char *origin;
+ const char *origin = NULL;
+ zone_admin_result_t result;
if ((error = zfs_get_parent(zc->zc_name, parentname,
sizeof (parentname))) != 0)
return (error);
- if (nvlist_lookup_string(innvl, "origin", &origin) == 0 &&
+ (void) nvlist_lookup_string(innvl, "origin", &origin);
+
+ /* Check zoned_uid delegation first */
+ result = zone_dataset_admin_check(parentname,
+ origin != NULL ? ZONE_OP_CLONE : ZONE_OP_CREATE, origin);
+ if (result == ZONE_ADMIN_ALLOWED) {
+ if (origin != NULL) {
+ if ((error = zfs_secpolicy_zoned_uid_deleg(origin,
+ ZFS_DELEG_PERM_CLONE, cr)) != 0)
+ return (error);
+ }
+ if ((error = zfs_secpolicy_zoned_uid_deleg(parentname,
+ ZFS_DELEG_PERM_CREATE, cr)) != 0)
+ return (error);
+ return (zfs_secpolicy_zoned_uid_deleg(parentname,
+ ZFS_DELEG_PERM_MOUNT, cr));
+ }
+ if (result == ZONE_ADMIN_DENIED)
+ return (SET_ERROR(EPERM));
+
+ /* NOT_APPLICABLE: continue with existing checks */
+ if (origin != NULL &&
(error = zfs_secpolicy_write_perms(origin,
ZFS_DELEG_PERM_CLONE, cr)) != 0)
return (error);
@@ -1131,6 +1315,14 @@ zfs_secpolicy_inherit_prop(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr)
if (prop == ZPROP_USERPROP) {
if (!zfs_prop_user(zc->zc_value))
return (SET_ERROR(EINVAL));
+ zone_admin_result_t zone_result;
+ zone_result = zone_dataset_admin_check(zc->zc_name,
+ ZONE_OP_SETPROP, NULL);
+ if (zone_result == ZONE_ADMIN_ALLOWED)
+ return (zfs_secpolicy_zoned_uid_deleg(zc->zc_name,
+ ZFS_DELEG_PERM_USERPROP, cr));
+ if (zone_result == ZONE_ADMIN_DENIED)
+ return (SET_ERROR(EPERM));
return (zfs_secpolicy_write_perms(zc->zc_name,
ZFS_DELEG_PERM_USERPROP, cr));
} else {
@@ -1439,6 +1631,7 @@ zfsvfs_hold(const char *name, const void *tag, zfsvfs_t **zfvp,
* objset from the zfsvfs.
*/
ZFS_TEARDOWN_EXIT(*zfvp, tag);
+ zfs_vfs_rele(*zfvp);
return (SET_ERROR(EBUSY));
}
}
@@ -1468,6 +1661,7 @@ zfs_ioc_pool_create(zfs_cmd_t *zc)
dsl_crypto_params_t *dcp = NULL;
const char *spa_name = zc->zc_name;
boolean_t unload_wkey = B_TRUE;
+ nvlist_t *errinfo = NULL;
if ((error = get_nvlist(zc->zc_nvlist_conf, zc->zc_nvlist_conf_size,
zc->zc_iflags, &config)))
@@ -1519,7 +1713,16 @@ zfs_ioc_pool_create(zfs_cmd_t *zc)
spa_name = tname;
}
- error = spa_create(zc->zc_name, config, props, zplprops, dcp);
+ error = spa_create(zc->zc_name, config, props, zplprops, dcp,
+ &errinfo);
+ if (errinfo != NULL) {
+ nvlist_t *outnv = fnvlist_alloc();
+ fnvlist_add_nvlist(outnv,
+ ZPOOL_CONFIG_CREATE_INFO, errinfo);
+ (void) put_nvlist(zc, outnv);
+ nvlist_free(outnv);
+ nvlist_free(errinfo);
+ }
/*
* Set the remaining root properties
@@ -2707,6 +2910,28 @@ zfs_prop_set_special(const char *dsname, zprop_source_t source,
zfsvfs_rele(zfsvfs, FTAG);
break;
}
+ case ZFS_PROP_ZONED_UID:
+ {
+ uint64_t old_uid = 0;
+ (void) dsl_prop_get(dsname, "zoned_uid", 8, 1, &old_uid, NULL);
+ if (old_uid != 0)
+ (void) zone_dataset_detach_uid(CRED(), dsname,
+ (uid_t)old_uid);
+ if (intval != 0) {
+ err = zone_dataset_attach_uid(CRED(), dsname,
+ (uid_t)intval);
+ if (err == ENXIO)
+ err = ZFS_ERR_NO_USER_NS_SUPPORT;
+ if (err != 0)
+ break;
+ }
+ /*
+ * Set err to -1 to force the zfs_set_prop_nvlist code down the
+ * default path to set the value in the nvlist.
+ */
+ err = -1;
+ break;
+ }
default:
err = -1;
}
@@ -3850,8 +4075,20 @@ zfs_ioc_snapshot(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl)
*/
if (!nvlist_empty(props)) {
*cp = '\0';
- error = zfs_secpolicy_write_perms(name,
- ZFS_DELEG_PERM_USERPROP, CRED());
+ zone_admin_result_t zone_result;
+ zone_result = zone_dataset_admin_check(name,
+ ZONE_OP_SETPROP, NULL);
+ if (zone_result == ZONE_ADMIN_DENIED) {
+ *cp = '@';
+ return (SET_ERROR(EPERM));
+ }
+ if (zone_result == ZONE_ADMIN_ALLOWED) {
+ error = zfs_secpolicy_zoned_uid_deleg(name,
+ ZFS_DELEG_PERM_USERPROP, CRED());
+ } else {
+ error = zfs_secpolicy_write_perms(name,
+ ZFS_DELEG_PERM_USERPROP, CRED());
+ }
*cp = '@';
if (error != 0)
return (error);
@@ -4333,6 +4570,14 @@ zfs_ioc_destroy(zfs_cmd_t *zc)
if (strchr(zc->zc_name, '@')) {
err = dsl_destroy_snapshot(zc->zc_name, zc->zc_defer_destroy);
} else {
+ /*
+ * Save zoned_uid before destroying so we can clean up
+ * kernel-side zone tracking after a successful destroy.
+ */
+ uint64_t zoned_uid = 0;
+ (void) dsl_prop_get(zc->zc_name, "zoned_uid",
+ 8, 1, &zoned_uid, NULL);
+
err = dsl_destroy_head(zc->zc_name);
if (err == EEXIST) {
/*
@@ -4362,6 +4607,11 @@ zfs_ioc_destroy(zfs_cmd_t *zc)
else if (err == ENOENT)
err = SET_ERROR(EEXIST);
}
+
+ if (err == 0 && zoned_uid != 0) {
+ (void) zone_dataset_detach_uid(kcred,
+ zc->zc_name, (uid_t)zoned_uid);
+ }
}
return (err);
@@ -4859,7 +5109,24 @@ zfs_ioc_rename(zfs_cmd_t *zc)
return (error);
} else {
- return (dsl_dir_rename(zc->zc_name, zc->zc_value));
+ /*
+ * For dataset renames, update kernel-side zone tracking
+ * if the dataset has a zoned_uid delegation. Read the
+ * property before rename, then detach old / attach new.
+ */
+ uint64_t zoned_uid = 0;
+ (void) dsl_prop_get(zc->zc_name, "zoned_uid",
+ 8, 1, &zoned_uid, NULL);
+
+ err = dsl_dir_rename(zc->zc_name, zc->zc_value);
+
+ if (err == 0 && zoned_uid != 0) {
+ (void) zone_dataset_detach_uid(kcred,
+ zc->zc_name, (uid_t)zoned_uid);
+ (void) zone_dataset_attach_uid(kcred,
+ zc->zc_value, (uid_t)zoned_uid);
+ }
+ return (err);
}
}
@@ -4874,6 +5141,14 @@ zfs_check_settable(const char *dsname, nvpair_t *pair, cred_t *cr)
if (prop == ZPROP_USERPROP) {
if (zfs_prop_user(propname)) {
+ zone_admin_result_t zone_result;
+ zone_result = zone_dataset_admin_check(dsname,
+ ZONE_OP_SETPROP, NULL);
+ if (zone_result == ZONE_ADMIN_ALLOWED)
+ return (zfs_secpolicy_zoned_uid_deleg(dsname,
+ ZFS_DELEG_PERM_USERPROP, cr));
+ if (zone_result == ZONE_ADMIN_DENIED)
+ return (SET_ERROR(EPERM));
if ((err = zfs_secpolicy_write_perms(dsname,
ZFS_DELEG_PERM_USERPROP, cr)))
return (err);
@@ -4918,6 +5193,14 @@ zfs_check_settable(const char *dsname, nvpair_t *pair, cred_t *cr)
return (SET_ERROR(EINVAL));
}
+ zone_admin_result_t zone_result;
+ zone_result = zone_dataset_admin_check(dsname,
+ ZONE_OP_SETPROP, NULL);
+ if (zone_result == ZONE_ADMIN_ALLOWED)
+ return (zfs_secpolicy_zoned_uid_deleg(dsname,
+ perm, cr));
+ if (zone_result == ZONE_ADMIN_DENIED)
+ return (SET_ERROR(EPERM));
if ((err = zfs_secpolicy_write_perms(dsname, perm, cr)))
return (err);
return (0);
@@ -7318,7 +7601,7 @@ zfs_ioc_change_key(const char *dsname, nvlist_t *innvl, nvlist_t *outnvl)
int ret;
uint64_t cmd = DCP_CMD_NONE;
dsl_crypto_params_t *dcp = NULL;
- nvlist_t *args = NULL, *hidden_args = NULL;
+ nvlist_t *props = NULL, *hidden_args = NULL;
if (strchr(dsname, '@') != NULL || strchr(dsname, '%') != NULL) {
ret = (SET_ERROR(EINVAL));
@@ -7326,14 +7609,20 @@ zfs_ioc_change_key(const char *dsname, nvlist_t *innvl, nvlist_t *outnvl)
}
(void) nvlist_lookup_uint64(innvl, "crypt_cmd", &cmd);
- (void) nvlist_lookup_nvlist(innvl, "props", &args);
+ (void) nvlist_lookup_nvlist(innvl, "props", &props);
(void) nvlist_lookup_nvlist(innvl, ZPOOL_HIDDEN_ARGS, &hidden_args);
- ret = dsl_crypto_params_create_nvlist(cmd, args, hidden_args, &dcp);
+ ret = dsl_crypto_params_create_nvlist(cmd, props, hidden_args, &dcp);
if (ret != 0)
goto error;
- ret = spa_keystore_change_key(dsname, dcp);
+ /* The keylocation property is set from dcp->cp_keylocation. */
+ (void) nvlist_remove_all(props, zfs_prop_to_name(ZFS_PROP_KEYLOCATION));
+
+ if ((ret = zfs_check_userprops(props)) != 0)
+ goto error;
+
+ ret = spa_keystore_change_key(dsname, dcp, props);
if (ret != 0)
goto error;
@@ -8267,6 +8556,9 @@ zfs_kmod_init(void)
zfs_ioctl_init();
+ /* Register zoned_uid property lookup callback with SPL */
+ zone_register_zoned_uid_callback(zfs_get_zoned_uid);
+
mutex_init(&zfsdev_state_lock, NULL, MUTEX_DEFAULT, NULL);
zfsdev_state_listhead.zs_minor = -1;
@@ -8305,6 +8597,10 @@ zfs_kmod_fini(void)
}
zfs_ereport_taskq_fini(); /* run before zfs_fini() on Linux */
+
+ /* Unregister zoned_uid callback before ZFS layer is torn down */
+ zone_unregister_zoned_uid_callback();
+
zfs_fini();
spa_fini();
zvol_fini();
diff --git a/sys/contrib/openzfs/module/zfs/zio.c b/sys/contrib/openzfs/module/zfs/zio.c
index 08cea9156688..5c2c984c34b6 100644
--- a/sys/contrib/openzfs/module/zfs/zio.c
+++ b/sys/contrib/openzfs/module/zfs/zio.c
@@ -1662,9 +1662,11 @@ zio_vdev_child_io(zio_t *pio, blkptr_t *bp, vdev_t *vd, uint64_t offset,
/*
* If we've decided to do a repair, the write is not speculative --
- * even if the original read was.
+ * even if the original read was. Rebuild is an exception since we
+ * cannot always ensure its data integrity.
*/
- if (flags & ZIO_FLAG_IO_REPAIR)
+ if ((flags & ZIO_FLAG_IO_REPAIR) &&
+ pio->io_priority != ZIO_PRIORITY_REBUILD)
flags &= ~ZIO_FLAG_SPECULATIVE;
/*
diff --git a/sys/contrib/openzfs/module/zfs/zvol.c b/sys/contrib/openzfs/module/zfs/zvol.c
index 285b194a6969..21f41c38c980 100644
--- a/sys/contrib/openzfs/module/zfs/zvol.c
+++ b/sys/contrib/openzfs/module/zfs/zvol.c
@@ -1825,9 +1825,10 @@ zvol_rename_minors_impl(zvol_task_t *task)
if (zvol_inhibit_dev)
return;
+ last_error = 0;
oldnamelen = strlen(oldname);
- rw_enter(&zvol_state_lock, RW_READER);
+ rw_enter(&zvol_state_lock, RW_WRITER);
for (zv = list_head(&zvol_state_list); zv != NULL; zv = zv_next) {
zv_next = list_next(&zvol_state_list, zv);
@@ -1844,6 +1845,8 @@ zvol_rename_minors_impl(zvol_task_t *task)
zv->zv_name + oldnamelen + 1);
error = zvol_os_rename_minor(zv, name);
kmem_strfree(name);
+ } else {
+ error = 0;
}
if (error) {
last_error = error;
@@ -1999,6 +2002,10 @@ typedef struct zvol_set_prop_int_arg {
uint64_t zsda_value;
zprop_source_t zsda_source;
zfs_prop_t zsda_prop;
+ taskqid_t zsda_taskqid;
+ boolean_t zsda_dispatched;
+ kmutex_t zsda_lock;
+ kcondvar_t zsda_cv;
} zvol_set_prop_int_arg_t;
/*
@@ -2029,6 +2036,7 @@ zvol_set_common_sync_cb(dsl_pool_t *dp, dsl_dataset_t *ds, void *arg)
char dsname[ZFS_MAX_DATASET_NAME_LEN];
zvol_task_t *task;
uint64_t prop;
+ taskqid_t id;
const char *prop_name = zfs_prop_to_name(zsda->zsda_prop);
dsl_dataset_name(ds, dsname);
@@ -2047,8 +2055,12 @@ zvol_set_common_sync_cb(dsl_pool_t *dp, dsl_dataset_t *ds, void *arg)
}
task->zt_value = prop;
strlcpy(task->zt_name1, dsname, sizeof (task->zt_name1));
- (void) taskq_dispatch(dp->dp_spa->spa_zvol_taskq, zvol_task_cb,
- task, TQ_SLEEP);
+ id = taskq_dispatch(dp->dp_spa->spa_zvol_taskq, zvol_task_cb, task,
+ TQ_SLEEP);
+ mutex_enter(&zsda->zsda_lock);
+ if (id != TASKQID_INVALID && id > zsda->zsda_taskqid)
+ zsda->zsda_taskqid = id;
+ mutex_exit(&zsda->zsda_lock);
return (0);
}
@@ -2081,6 +2093,11 @@ zvol_set_common_sync(void *arg, dmu_tx_t *tx)
dmu_objset_find_dp(dp, dd->dd_object, zvol_set_common_sync_cb,
zsda, DS_FIND_CHILDREN);
+ mutex_enter(&zsda->zsda_lock);
+ zsda->zsda_dispatched = TRUE;
+ cv_broadcast(&zsda->zsda_cv);
+ mutex_exit(&zsda->zsda_lock);
+
dsl_dir_rele(dd, FTAG);
}
@@ -2089,14 +2106,38 @@ zvol_set_common(const char *ddname, zfs_prop_t prop, zprop_source_t source,
uint64_t val)
{
zvol_set_prop_int_arg_t zsda;
+ spa_t *spa;
+ int error;
zsda.zsda_name = ddname;
zsda.zsda_source = source;
zsda.zsda_value = val;
zsda.zsda_prop = prop;
+ zsda.zsda_taskqid = TASKQID_INVALID;
+ zsda.zsda_dispatched = FALSE;
+ mutex_init(&zsda.zsda_lock, NULL, MUTEX_DEFAULT, NULL);
+ cv_init(&zsda.zsda_cv, NULL, CV_DEFAULT, NULL);
+
+ error = spa_open(ddname, &spa, FTAG);
+ if (error != 0)
+ goto out;
+ error = dsl_sync_task(ddname, zvol_set_common_check,
+ zvol_set_common_sync, &zsda, 0, ZFS_SPACE_CHECK_NONE);
+ if (error == 0) {
+ mutex_enter(&zsda.zsda_lock);
+ while (!zsda.zsda_dispatched)
+ cv_wait(&zsda.zsda_cv, &zsda.zsda_lock);
+ mutex_exit(&zsda.zsda_lock);
- return (dsl_sync_task(ddname, zvol_set_common_check,
- zvol_set_common_sync, &zsda, 0, ZFS_SPACE_CHECK_NONE));
+ if (zsda.zsda_taskqid != TASKQID_INVALID)
+ taskq_wait_outstanding(spa->spa_zvol_taskq,
+ zsda.zsda_taskqid);
+ }
+ spa_close(spa, FTAG);
+out:
+ cv_destroy(&zsda.zsda_cv);
+ mutex_destroy(&zsda.zsda_lock);
+ return (error);
}
void