diff options
Diffstat (limited to 'sys/contrib/openzfs/module')
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, ¶m, &result); + if (opt >= 0) { + /* + * We already know this one of our options, so a + * failure here would be nonsensical. + */ + VERIFY0(zpl_parse_param(fc, ¶m)); + } else { + /* + * Not one of our option, send it through the kernel's + * standard parameter handling. + */ + err = vfs_parse_fs_param(fc, ¶m); + 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 |
