From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id 547031FF0AA for ; Tue, 20 Oct 2026 16:04:25 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id C3E4721736; Tue, 20 Oct 2026 16:04:13 +0200 (CEST) From: Stoiko Ivanov To: pve-devel@lists.proxmox.com Subject: [PATCH zfsonlinux 1/2] fix #8178: d/patches: add patches already queued for zfs-2.4.5 Date: Tue, 20 Oct 2026 16:02:33 +0200 Message-ID: <20261020140319.1240275-2-s.ivanov@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20261020140319.1240275-1-s.ivanov@proxmox.com> References: <20261020140319.1240275-1-s.ivanov@proxmox.com> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1792505011948 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.448 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) KAM_LOTSOFHASH 0.25 Emails with lots of hash-like gibberish RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record X-MailFrom: s.ivanov@proxmox.com X-Mailman-Rule-Hits: max-size X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; news-moderation; no-subject; digests; suspicious-header Message-ID-Hash: 3D5CLW37L3CJMIAXSEKA22X2VY63KPXL X-Message-ID-Hash: 3D5CLW37L3CJMIAXSEKA22X2VY63KPXL X-Mailman-Approved-At: Tue, 20 Oct 2026 16:03:32 +0200 X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: https://bugzilla.proxmox.com/show_bug.cgi?id=8178 should be fixed in upstream master by 216de07d8 ("arc: fix race between arc_release() and arc_read_done()") This and needed prerequisites were already applied to zfs-2.4.5-staging. as the remaining patches don't look problematic (in not touching functionality commonly used in our products) - simply pull all patches since zfs-2.4.4 on zfs-2.4.5-staging. Signed-off-by: Stoiko Ivanov --- ...the-project-ID-for-every-object-type.patch | 209 +++++++++ ...thin-a-single-directory-under-projec.patch | 74 ++++ ...t-ID-inheritance-and-renames-within-.patch | 208 +++++++++ ...uf_hdr-s-anon-state-invariant-checks.patch | 113 +++++ ...-support-for-building-akmod-packages.patch | 344 +++++++++++++++ ...k-delivery-of-SIGUSR1-in-send_worker.patch | 93 ++++ ...I-baseline-naming-on-RHEL-to-prevent.patch | 417 ++++++++++++++++++ ...ader-destruction-for-in-flight-L2-wr.patch | 119 +++++ ...2HDR-in-arc_release-for-in-flight-wr.patch | 174 ++++++++ ...etween-arc_release-and-arc_read_done.patch | 179 ++++++++ debian/patches/series | 10 + 11 files changed, 1940 insertions(+) create mode 100644 debian/patches/0011-Inherit-the-project-ID-for-every-object-type.patch create mode 100644 debian/patches/0012-Allow-renames-within-a-single-directory-under-projec.patch create mode 100644 debian/patches/0013-ZTS-check-project-ID-inheritance-and-renames-within-.patch create mode 100644 debian/patches/0014-arc-harness-buf_hdr-s-anon-state-invariant-checks.patch create mode 100644 debian/patches/0015-rpm-Add-support-for-building-akmod-packages.patch create mode 100644 debian/patches/0016-libzfs_core-block-delivery-of-SIGUSR1-in-send_worker.patch create mode 100644 debian/patches/0017-kmodtool-use-kABI-baseline-naming-on-RHEL-to-prevent.patch create mode 100644 debian/patches/0018-L2ARC-Reorder-header-destruction-for-in-flight-L2-wr.patch create mode 100644 debian/patches/0019-L2ARC-Preserve-L2HDR-in-arc_release-for-in-flight-wr.patch create mode 100644 debian/patches/0020-arc-fix-race-between-arc_release-and-arc_read_done.patch diff --git a/debian/patches/0011-Inherit-the-project-ID-for-every-object-type.patch b/debian/patches/0011-Inherit-the-project-ID-for-every-object-type.patch new file mode 100644 index 000000000..aa7af3992 --- /dev/null +++ b/debian/patches/0011-Inherit-the-project-ID-for-every-object-type.patch @@ -0,0 +1,209 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Matt Turner +Date: Fri, 14 Aug 2026 14:59:08 -0400 +Subject: [PATCH] Inherit the project ID for every object type + +zfs_mknode() only assigned a project ID to regular files and +directories, so a symlink, device node, FIFO or socket created inside a +directory with ZFS_PROJINHERIT set was left at ZFS_DEFAULT_PROJID. The +cross-project checks in zfs_rename() and zfs_link() compare the object's +project ID against the directory's, so such an object is treated as +foreign to the very directory holding it, and cannot be renamed or +linked there at all -- "ln -sfn", which creates the new symlink under a +temporary name and renames it into place, fails with EXDEV. + +ext4 and XFS store a project ID on every inode type and so do not have +this problem. Do the same, and quota-check new symlinks and rename +whiteouts against the inherited ID rather than the default one, so their +space is accounted to the project that owns them. + +This changes accounting for newly created objects: symlinks, device +nodes and FIFOs now consume the project's quota where they previously +consumed none, so on a dataset already at its project quota, creating +one begins to fail with EDQUOT where it used to succeed. The space +involved is small, but the change in behavior is visible. + +The ZFS_PROJINHERIT flag itself keeps propagating to regular files and +directories only, leaving lsattr(1) output unchanged. + +Note that this only covers newly created objects; symlinks already on +disk keep the default project ID. + +Reviewed-by: Brian Behlendorf +Signed-off-by: Matt Turner +Closes #18932 +(cherry picked from commit 7f0d8d3d1fdf8a1c0711350b7ec6f9b6a571c647) +Signed-off-by: Stoiko Ivanov +--- + module/os/freebsd/zfs/zfs_vnops_os.c | 5 ++-- + module/os/freebsd/zfs/zfs_znode_os.c | 36 ++++++++++++++++------------ + module/os/linux/zfs/zfs_vnops_os.c | 15 ++++-------- + module/os/linux/zfs/zfs_znode_os.c | 36 ++++++++++++++++------------ + 4 files changed, 49 insertions(+), 43 deletions(-) + +diff --git a/module/os/freebsd/zfs/zfs_vnops_os.c b/module/os/freebsd/zfs/zfs_vnops_os.c +index 0080986787cc19481ada9a411ee7307522faa762..f1278f00bdc067f536442b4abb24c6d538f6a413 100644 +--- a/module/os/freebsd/zfs/zfs_vnops_os.c ++++ b/module/os/freebsd/zfs/zfs_vnops_os.c +@@ -1119,8 +1119,7 @@ zfs_create(znode_t *dzp, const char *name, vattr_t *vap, int excl, int mode, + cr, vsecp, &acl_ids, NULL)) != 0) + goto out; + +- if (S_ISREG(vap->va_mode) || S_ISDIR(vap->va_mode)) +- projid = zfs_inherit_projid(dzp); ++ projid = zfs_inherit_projid(dzp); + if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, projid)) { + zfs_acl_ids_free(&acl_ids); + error = SET_ERROR(EDQUOT); +@@ -3659,7 +3658,7 @@ zfs_symlink(znode_t *dzp, const char *name, vattr_t *vap, + return (error); + } + +- if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, ZFS_DEFAULT_PROJID)) { ++ if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, zfs_inherit_projid(dzp))) { + zfs_acl_ids_free(&acl_ids); + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EDQUOT)); +diff --git a/module/os/freebsd/zfs/zfs_znode_os.c b/module/os/freebsd/zfs/zfs_znode_os.c +index b9f427b397055c1e69856014206a402559438ef9..089a221b64a0b6f2fae26d9a7fd34ca3dfb2fe6e 100644 +--- a/module/os/freebsd/zfs/zfs_znode_os.c ++++ b/module/os/freebsd/zfs/zfs_znode_os.c +@@ -667,22 +667,28 @@ zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr, + if (flag & IS_XATTR) + pflags |= ZFS_XATTR; + +- if (vap->va_type == VREG || vap->va_type == VDIR) { +- /* +- * With ZFS_PROJID flag, we can easily know whether there is +- * project ID stored on disk or not. See zpl_get_file_info(). +- */ +- if (obj_type != DMU_OT_ZNODE && +- dmu_objset_projectquota_enabled(zfsvfs->z_os)) +- pflags |= ZFS_PROJID; ++ /* ++ * With ZFS_PROJID flag, we can easily know whether there is ++ * project ID stored on disk or not. See zpl_get_file_info(). ++ */ ++ if (obj_type != DMU_OT_ZNODE && ++ dmu_objset_projectquota_enabled(zfsvfs->z_os)) ++ pflags |= ZFS_PROJID; + +- /* +- * Inherit project ID from parent if required. +- */ +- projid = zfs_inherit_projid(dzp); +- if (dzp_pflags & ZFS_PROJINHERIT) +- pflags |= ZFS_PROJINHERIT; +- } ++ /* ++ * Inherit project ID from parent if required. Every object type ++ * takes part, as ext4 and XFS do: an object that carried no project ++ * ID of its own would be treated as belonging to a different project ++ * than the directory holding it, so zfs_rename() and zfs_link() ++ * would refuse it with EXDEV even within its own project. ++ * ++ * The ZFS_PROJINHERIT flag itself keeps passing to regular files and ++ * directories only, as before, so that lsattr(1) output is unchanged. ++ */ ++ projid = zfs_inherit_projid(dzp); ++ if ((vap->va_type == VREG || vap->va_type == VDIR) && ++ (dzp_pflags & ZFS_PROJINHERIT)) ++ pflags |= ZFS_PROJINHERIT; + + /* + * No execs denied will be determined when zfs_mode_compute() is called. +diff --git a/module/os/linux/zfs/zfs_vnops_os.c b/module/os/linux/zfs/zfs_vnops_os.c +index cb6321e4f454cbb9fd361f71230499eefe54faa6..4557b8916bca4f06aa9159c6c689b9b1d477d896 100644 +--- a/module/os/linux/zfs/zfs_vnops_os.c ++++ b/module/os/linux/zfs/zfs_vnops_os.c +@@ -736,8 +736,7 @@ top: + goto out; + have_acl = B_TRUE; + +- if (S_ISREG(vap->va_mode) || S_ISDIR(vap->va_mode)) +- projid = zfs_inherit_projid(dzp); ++ projid = zfs_inherit_projid(dzp); + if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, projid)) { + zfs_acl_ids_free(&acl_ids); + error = SET_ERROR(EDQUOT); +@@ -930,8 +929,7 @@ top: + goto out; + have_acl = B_TRUE; + +- if (S_ISREG(vap->va_mode) || S_ISDIR(vap->va_mode)) +- projid = zfs_inherit_projid(dzp); ++ projid = zfs_inherit_projid(dzp); + if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, projid)) { + zfs_acl_ids_free(&acl_ids); + error = SET_ERROR(EDQUOT); +@@ -3057,11 +3055,8 @@ top: + + /* Set up inode creation for RENAME_WHITEOUT. */ + if (rflags & RENAME_WHITEOUT) { +- /* +- * Whiteout files are not regular files or directories, so to +- * match zfs_create() we do not inherit the project id. +- */ +- uint64_t wo_projid = ZFS_DEFAULT_PROJID; ++ /* Match zfs_create(): the whiteout joins its directory. */ ++ uint64_t wo_projid = zfs_inherit_projid(sdzp); + + error = zfs_zaccess(sdzp, ACE_ADD_FILE, 0, B_FALSE, cr, mnt_ns); + if (error) +@@ -3389,7 +3384,7 @@ top: + return (error); + } + +- if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, ZFS_DEFAULT_PROJID)) { ++ if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, zfs_inherit_projid(dzp))) { + zfs_acl_ids_free(&acl_ids); + zfs_dirent_unlock(dl); + zfs_exit(zfsvfs, FTAG); +diff --git a/module/os/linux/zfs/zfs_znode_os.c b/module/os/linux/zfs/zfs_znode_os.c +index dd6bd21a60425b07297ef2d35c6fe90c9ff4dcbe..a106081702c4d0ae12a0004c60d0be055fe65bd5 100644 +--- a/module/os/linux/zfs/zfs_znode_os.c ++++ b/module/os/linux/zfs/zfs_znode_os.c +@@ -775,22 +775,28 @@ zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr, + if (flag & IS_XATTR) + pflags |= ZFS_XATTR; + +- if (S_ISREG(vap->va_mode) || S_ISDIR(vap->va_mode)) { +- /* +- * With ZFS_PROJID flag, we can easily know whether there is +- * project ID stored on disk or not. See zpl_get_file_info(). +- */ +- if (obj_type != DMU_OT_ZNODE && +- dmu_objset_projectquota_enabled(zfsvfs->z_os)) +- pflags |= ZFS_PROJID; ++ /* ++ * With ZFS_PROJID flag, we can easily know whether there is ++ * project ID stored on disk or not. See zpl_get_file_info(). ++ */ ++ if (obj_type != DMU_OT_ZNODE && ++ dmu_objset_projectquota_enabled(zfsvfs->z_os)) ++ pflags |= ZFS_PROJID; + +- /* +- * Inherit project ID from parent if required. +- */ +- projid = zfs_inherit_projid(dzp); +- if (dzp->z_pflags & ZFS_PROJINHERIT) +- pflags |= ZFS_PROJINHERIT; +- } ++ /* ++ * Inherit project ID from parent if required. Every object type ++ * takes part, as ext4 and XFS do: an object that carried no project ++ * ID of its own would be treated as belonging to a different project ++ * than the directory holding it, so zfs_rename() and zfs_link() ++ * would refuse it with EXDEV even within its own project. ++ * ++ * The ZFS_PROJINHERIT flag itself keeps passing to regular files and ++ * directories only, as before, so that lsattr(1) output is unchanged. ++ */ ++ projid = zfs_inherit_projid(dzp); ++ if ((S_ISREG(vap->va_mode) || S_ISDIR(vap->va_mode)) && ++ (dzp->z_pflags & ZFS_PROJINHERIT)) ++ pflags |= ZFS_PROJINHERIT; + + /* + * No execs denied will be determined when zfs_mode_compute() is called. diff --git a/debian/patches/0012-Allow-renames-within-a-single-directory-under-projec.patch b/debian/patches/0012-Allow-renames-within-a-single-directory-under-projec.patch new file mode 100644 index 000000000..98414213f --- /dev/null +++ b/debian/patches/0012-Allow-renames-within-a-single-directory-under-projec.patch @@ -0,0 +1,74 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Matt Turner +Date: Sat, 15 Aug 2026 22:05:41 -0400 +Subject: [PATCH] Allow renames within a single directory under project + inheritance + +A rename that keeps the object in the directory it already lives in +cannot move it between projects, so refusing it with EXDEV is never +right. Objects created before the previous commit carry no project ID of +their own, which makes this reachable on existing pools: a symlink in a +project directory cannot be renamed even to another name beside itself, +and "ln -sfn" over an existing path fails. + +zfs_link() keeps its unconditional check. It has no source directory to +compare against -- a hard link names an object that may live anywhere -- +so there is no equivalent "the object is already here" case to exempt. +Linking a pre-existing symlink, device node or FIFO into the project +directory that already holds it therefore still fails with EXDEV, until +the object is given a project ID of its own. + +Reviewed-by: Brian Behlendorf +Signed-off-by: Matt Turner +Closes #18932 +(cherry picked from commit 594332758f794fa9f969e9c72b6e93f0bf9f71af) +Signed-off-by: Stoiko Ivanov +--- + module/os/freebsd/zfs/zfs_vnops_os.c | 10 +++++++++- + module/os/linux/zfs/zfs_vnops_os.c | 10 +++++++++- + 2 files changed, 18 insertions(+), 2 deletions(-) + +diff --git a/module/os/freebsd/zfs/zfs_vnops_os.c b/module/os/freebsd/zfs/zfs_vnops_os.c +index f1278f00bdc067f536442b4abb24c6d538f6a413..91181ea86a450a43687c0af842b11d08ed2016be 100644 +--- a/module/os/freebsd/zfs/zfs_vnops_os.c ++++ b/module/os/freebsd/zfs/zfs_vnops_os.c +@@ -3384,8 +3384,16 @@ zfs_do_rename_impl(vnode_t *sdvp, vnode_t **svpp, struct componentname *scnp, + * not only the project ID, but also the ZFS_PROJINHERIT flag. Under + * such case, we only allow renames into our tree when the project + * IDs are the same. ++ * ++ * A rename within a single directory leaves the object exactly where ++ * it already is, so it cannot move it between projects and is always ++ * allowed. Objects created before symlinks and other non-regular ++ * files began inheriting a project ID carry none of their own, and ++ * would otherwise not be renameable within the very directory that ++ * holds them -- which breaks "ln -sfn", implemented as ++ * create-under-a-temporary-name-then-rename. + */ +- if (tdzp->z_pflags & ZFS_PROJINHERIT && ++ if (sdzp != tdzp && tdzp->z_pflags & ZFS_PROJINHERIT && + tdzp->z_projid != szp->z_projid) { + error = SET_ERROR(EXDEV); + goto out; +diff --git a/module/os/linux/zfs/zfs_vnops_os.c b/module/os/linux/zfs/zfs_vnops_os.c +index 4557b8916bca4f06aa9159c6c689b9b1d477d896..f27b877da06dd187f496102924fcc040efb38405 100644 +--- a/module/os/linux/zfs/zfs_vnops_os.c ++++ b/module/os/linux/zfs/zfs_vnops_os.c +@@ -2993,8 +2993,16 @@ top: + * not only the project ID, but also the ZFS_PROJINHERIT flag. Under + * such case, we only allow renames into our tree when the project + * IDs are the same. ++ * ++ * A rename within a single directory leaves the object exactly where ++ * it already is, so it cannot move it between projects and is always ++ * allowed. Objects created before symlinks and other non-regular ++ * files began inheriting a project ID carry none of their own, and ++ * would otherwise not be renameable within the very directory that ++ * holds them -- which breaks "ln -sfn", implemented as ++ * create-under-a-temporary-name-then-rename. + */ +- if (tdzp->z_pflags & ZFS_PROJINHERIT && ++ if (sdzp != tdzp && tdzp->z_pflags & ZFS_PROJINHERIT && + tdzp->z_projid != szp->z_projid) { + error = SET_ERROR(EXDEV); + goto out; diff --git a/debian/patches/0013-ZTS-check-project-ID-inheritance-and-renames-within-.patch b/debian/patches/0013-ZTS-check-project-ID-inheritance-and-renames-within-.patch new file mode 100644 index 000000000..aad0af67f --- /dev/null +++ b/debian/patches/0013-ZTS-check-project-ID-inheritance-and-renames-within-.patch @@ -0,0 +1,208 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Matt Turner +Date: Sat, 15 Aug 2026 22:07:58 -0400 +Subject: [PATCH] ZTS: check project ID inheritance and renames within a + project directory + +Covers both of the preceding changes. For the inheritance: a new symlink +and a new FIFO each add an object to the containing directory's project, +which only happens once non-regular files inherit a project ID, and a +symlink can then be renamed into a different directory carrying the same +project ID, which rename(2) permits only when the two project IDs match. + +For the rename exemption: a regular file and a symlink are created +before the directory is tagged, so they carry no project ID of their +own, as every symlink on an existing pool does. Renaming each of them +within that directory afterwards fails with EXDEV unless the +cross-project check exempts renames whose source and target directories +are the same. The same renames are then repeated for objects created +after the tagging, along with replacing a symlink with "ln -sfn", and +again in an inheriting subdirectory. + +A rename that does cross into a different project is still refused. + +Renames use renameat2(1) rather than mv(1), which falls back to copying +on EXDEV and would report success either way. The test is skipped where +the renameat2(2) syscall is unavailable. + +Reviewed-by: Brian Behlendorf +Signed-off-by: Matt Turner +Closes #18932 +(cherry picked from commit 2cf1bd209e43be2051dce28349bb171300be8c44) +Signed-off-by: Stoiko Ivanov +--- + tests/runfiles/linux.run | 1 + + tests/zfs-tests/tests/Makefile.am | 1 + + .../projectquota/projectid_004_pos.ksh | 139 ++++++++++++++++++ + 3 files changed, 141 insertions(+) + create mode 100755 tests/zfs-tests/tests/functional/projectquota/projectid_004_pos.ksh + +diff --git a/tests/runfiles/linux.run b/tests/runfiles/linux.run +index 2e2fb457585310db3a317bf0e8dc0862b14f860f..8615369dbaa0ae9355a2e982d6fec5266a84c541 100644 +--- a/tests/runfiles/linux.run ++++ b/tests/runfiles/linux.run +@@ -193,6 +193,7 @@ tags = ['functional', 'procfs'] + [tests/functional/projectquota:Linux] + tests = ['defaultprojectquota_001_pos', 'defaultprojectquota_005_pos', + 'projectid_001_pos', 'projectid_002_pos', 'projectid_003_pos', ++ 'projectid_004_pos', + 'projectquota_001_pos', 'projectquota_003_pos', 'projectquota_006_pos', + 'projectspace_001_pos', 'projectspace_002_pos', 'projectspace_003_pos', + 'projectspace_004_pos', 'projectspace_005_pos', 'projecttree_001_pos'] +diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am +index f29ba9660662dad2959607572ed3e106c25a3e51..dcfa1ada5ea5b3dc756887e68cdb0ef53ddd152e 100644 +--- a/tests/zfs-tests/tests/Makefile.am ++++ b/tests/zfs-tests/tests/Makefile.am +@@ -1835,6 +1835,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \ + functional/projectquota/projectid_001_pos.ksh \ + functional/projectquota/projectid_002_pos.ksh \ + functional/projectquota/projectid_003_pos.ksh \ ++ functional/projectquota/projectid_004_pos.ksh \ + functional/projectquota/projectquota_001_pos.ksh \ + functional/projectquota/projectquota_002_pos.ksh \ + functional/projectquota/projectquota_003_pos.ksh \ +diff --git a/tests/zfs-tests/tests/functional/projectquota/projectid_004_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projectid_004_pos.ksh +new file mode 100755 +index 0000000000000000000000000000000000000000..08b020a550add991f248f76576a7bd507dfae856 +--- /dev/null ++++ b/tests/zfs-tests/tests/functional/projectquota/projectid_004_pos.ksh +@@ -0,0 +1,139 @@ ++#!/bin/ksh -p ++# SPDX-License-Identifier: CDDL-1.0 ++# ++# This file and its contents are supplied under the terms of the ++# Common Development and Distribution License ("CDDL"), version 1.0. ++# You may only use this file in accordance with the terms of version ++# 1.0 of the CDDL. ++# ++# A full copy of the text of the CDDL should have accompanied this ++# source. A copy of the CDDL is also available via the Internet at ++# https://opensource.org/license/CDDL-1.0. ++# ++ ++# ++# Copyright (c) 2026 by Matt Turner. All rights reserved. ++# ++ ++. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib ++ ++# ++# DESCRIPTION: ++# Every object type inherits the project ID, and renaming within a ++# project directory is always allowed ++# ++# ++# STRATEGY: ++# 1. Create a regular file and a symlink in a directory, then set the ++# directory's project ID with the inherit flag, leaving the two ++# objects carrying no project ID of their own ++# 2. Rename each of them within that directory, which has to be allowed ++# whatever project ID they carry ++# 3. Rename a regular file and a symlink created after the directory was ++# tagged, and replace a symlink with "ln -sfn", which creates the new ++# symlink under a temporary name and renames it into place ++# 4. Check that a new symlink and a new FIFO are accounted to the ++# directory's project, which they only are if they inherited its ++# project ID ++# 5. Rename a symlink into a different directory carrying the same ++# project ID, which rename(2) only permits if the symlink carries ++# that project ID too ++# 6. Check that a rename into a different project is still refused, ++# and that a symlink carrying no project ID cannot be renamed into ++# one, which shows the check in step 5 is reached and enforced ++# ++ ++function cleanup ++{ ++ log_must rm -rf $PRJDIR1 $PRJDIR2 $PRJDIR3 $TESTDIR/tlink6 ++} ++ ++if ! lsattr -pd > /dev/null 2>&1; then ++ log_unsupported "Current e2fsprogs does not support set/show project ID" ++fi ++ ++# renameat2(1) is used throughout rather than mv(1), which falls back to ++# copying on EXDEV and would report success either way. ++if ! renameat2 -C; then ++ log_unsupported "renameat2 not supported on this (pre-3.15) linux kernel" ++fi ++ ++log_onexit cleanup ++ ++log_assert "Every object type inherits the project ID, and renaming within" \ ++ "a project directory is always allowed" ++ ++log_must mkdir $PRJDIR1 ++ ++# ++# Objects created before the directory was tagged carry no project ID of ++# their own, as every symlink on an existing pool does. This is the case ++# the same-directory exemption exists for: renaming within one directory ++# cannot move an object between projects, so it has to be allowed whatever ++# project ID the object carries -- including none at all. ++# ++log_must touch $PRJDIR1/tofile1 ++log_must ln -s target $PRJDIR1/tolink1 ++ ++log_must chattr +P -p $PRJID1 $PRJDIR1 ++ ++log_must renameat2 $PRJDIR1/tofile1 $PRJDIR1/tofile2 ++log_must renameat2 $PRJDIR1/tolink1 $PRJDIR1/tolink2 ++ ++# Objects created after the directory was tagged inherit its project ID, and ++# renaming those within it has to keep working too. ++log_must touch $PRJDIR1/tfile1 ++log_must renameat2 $PRJDIR1/tfile1 $PRJDIR1/tfile2 ++ ++log_must ln -s target $PRJDIR1/tlink1 ++log_must renameat2 $PRJDIR1/tlink1 $PRJDIR1/tlink2 ++ ++log_must ln -sfn other $PRJDIR1/tlink2 ++log_must eval '[[ $(readlink $PRJDIR1/tlink2) == other ]]' ++ ++# A subdirectory inherits both the project ID and the inherit flag, so it ++# has to behave the same way. ++log_must mkdir $PRJDIR1/subdir ++log_must ln -s target $PRJDIR1/subdir/tlink3 ++log_must renameat2 $PRJDIR1/subdir/tlink3 $PRJDIR1/subdir/tlink4 ++ ++# ++# A symlink and a FIFO are not regular files or directories, and used to be ++# left at the default project ID. Creating one now adds an object to the ++# directory's project. ++# ++sync_pool ++typeset prj_bef=$(project_obj_count $QFS $PRJID1) ++ ++log_must ln -s target $PRJDIR1/tlink5 ++log_must mkfifo $PRJDIR1/tfifo1 ++ ++sync_pool ++typeset prj_aft=$(project_obj_count $QFS $PRJID1) ++ ++[[ $prj_aft -eq $((prj_bef + 2)) ]] || ++ log_fail "new value ($prj_aft) is NOT 2 larger than old one ($prj_bef)" ++ ++# ++# rename(2) into a different directory is allowed only when the project IDs ++# match, so this passes only if the symlink really did inherit one. ++# ++log_must mkdir $PRJDIR2 ++log_must chattr +P -p $PRJID1 $PRJDIR2 ++log_must renameat2 $PRJDIR1/tlink5 $PRJDIR2/tlink5 ++ ++# Crossing into a different project is still refused. ++log_must mkdir $PRJDIR3 ++log_must chattr +P -p $PRJID2 $PRJDIR3 ++log_mustnot renameat2 $PRJDIR2/tlink5 $PRJDIR3/tlink5 ++ ++# And a symlink created outside any project carries no project ID, so it ++# cannot be renamed into one. This is what makes the check above meaningful: ++# it shows the cross-project test is reached and enforced, so the rename that ++# succeeded did so because the symlink had inherited a project ID. ++log_must ln -s target $TESTDIR/tlink6 ++log_mustnot renameat2 $TESTDIR/tlink6 $PRJDIR1/tlink6 ++log_must rm -f $TESTDIR/tlink6 ++ ++log_pass "Every object type inherits the project ID, and renaming within" \ ++ "a project directory is always allowed" diff --git a/debian/patches/0014-arc-harness-buf_hdr-s-anon-state-invariant-checks.patch b/debian/patches/0014-arc-harness-buf_hdr-s-anon-state-invariant-checks.patch new file mode 100644 index 000000000..10a3ce347 --- /dev/null +++ b/debian/patches/0014-arc-harness-buf_hdr-s-anon-state-invariant-checks.patch @@ -0,0 +1,113 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andriy Tkachuk +Date: Thu, 27 Aug 2026 17:38:22 +0100 +Subject: [PATCH] arc: harness buf_hdr's anon state invariant checks +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +There are several places where hdr is checked for arc_anon state +without the hash_lock, for example at arc_release() or at +arc_buf_destroy(), and it's supposed that the hdr must not be +in the hash table in this state and not having IO_IN_PROGRESS. +However, those invariants are not asserted in release builds. +But even if they would, there is one place in the current code +where they would pass without noticing the problem. + +This place is at arc_write_done() where we first insert hdr +into the hash table, then clear IO_IN_PROGRESS flag, then drop +the reference and only then move its state from anon to mru: + + 7048 exists = buf_hash_insert(hdr, &hash_lock); /* still anon */ + ... + 7081 arc_hdr_clear_flags(hdr, ARC_FLAG_IO_IN_PROGRESS); + 7082 VERIFY3S(remove_reference(hdr, hdr), >, 0); /* ref: 2 -> 1 */ + 7084 if (exists == NULL && hdr->b_l1hdr.b_state == arc_anon) + 7085 arc_access(hdr, 0, B_FALSE); /* anon -> MRU */ + 7086 mutex_exit(hash_lock); + +Let's imagine arc_buf_destroy() is called concurrently: + +- hdr->b_l1hdr.b_state == arc_anon → true, takes the fast path +- ASSERT3P(hdr->b_l1hdr.b_buf, ==, buf) → passes +- ASSERT(ARC_BUF_LAST(buf)) → passes +- ASSERT(!HDR_IO_IN_PROGRESS(hdr)) → passes, cleared at 7081 +- VERIFY0(remove_reference(hdr, tag)) → refcnt 1→0, returns 0 → passes +- remove_reference() sees state == arc_anon → arc_hdr_destroy(hdr) + +Every single guard is satisfied, and the header is kmem_cache_freed +while still linked in its hash bucket. Could it be the root cause +of #18782 kernel panic? We cannot prove this, but the harnessing +we suggest here would allow to prove it. + +(Actually, since commit 023d44b9e VERIFY0P(hdr->b_hash_next) at +arc_hdr_destroy() should catch it, but I think it would be better +catching it earlier by the code and more explicitly.) + +So apart from promoting a couple asserts to verifies at +arc_hdr_destroy() and arc_release(), like !HDR_IO_IN_PROGRESS(hdr) +and !HDR_IN_HASH_TABLE(hdr), which are cheap, we also close the +window at arc_write_done() which allows anon hdr to satisfy some +of the assertions, and yet be in the hash table, by changing hdr's +state from anon to MRU *before* clearing ARC_FLAG_IO_IN_PROGRESS +flag and dropping reference on it. + +Reviewed-by: Alexander Motin +Reviewed-by: Brian Behlendorf +Signed-off-by: Andriy Tkachuk +Closes #18968 +(cherry picked from commit b0fff48dc10459b781f18c905399a24db3dae529) +(cherry picked from commit 57fbddf6bbcf9e130157de825785f2f9d9ffa6bb) +Signed-off-by: Stoiko Ivanov +--- + module/zfs/arc.c | 12 ++++++------ + 1 file changed, 6 insertions(+), 6 deletions(-) + +diff --git a/module/zfs/arc.c b/module/zfs/arc.c +index 34dad8b0e27e49c42281c6878fd12c9b2ad45179..4cd2a67eb2a85398627fad5531dc1aa20f1b2f5d 100644 +--- a/module/zfs/arc.c ++++ b/module/zfs/arc.c +@@ -3654,8 +3654,8 @@ arc_hdr_destroy(arc_buf_hdr_t *hdr) + ASSERT(zfs_refcount_is_zero(&hdr->b_l1hdr.b_refcnt)); + ASSERT3P(hdr->b_l1hdr.b_state, ==, arc_anon); + } +- ASSERT(!HDR_IO_IN_PROGRESS(hdr)); +- ASSERT(!HDR_IN_HASH_TABLE(hdr)); ++ VERIFY(!HDR_IO_IN_PROGRESS(hdr)); ++ VERIFY(!HDR_IN_HASH_TABLE(hdr)); + + if (HDR_HAS_L2HDR(hdr)) { + l2arc_dev_t *dev = hdr->b_l2hdr.b_dev; +@@ -6612,7 +6612,7 @@ arc_release(arc_buf_t *buf, const void *tag) + * linked into the hash table. + */ + if (hdr->b_l1hdr.b_state == arc_anon) { +- ASSERT(!HDR_IO_IN_PROGRESS(hdr)); ++ VERIFY(!HDR_IO_IN_PROGRESS(hdr)); + ASSERT(!HDR_IN_HASH_TABLE(hdr)); + ASSERT(!HDR_HAS_L2HDR(hdr)); + +@@ -6827,8 +6827,8 @@ arc_write_ready(zio_t *zio) + if (HDR_IO_IN_PROGRESS(hdr)) { + ASSERT(zio->io_flags & ZIO_FLAG_REEXECUTED); + } else { +- arc_hdr_set_flags(hdr, ARC_FLAG_IO_IN_PROGRESS); + add_reference(hdr, hdr); /* For IO_IN_PROGRESS. */ ++ arc_hdr_set_flags(hdr, ARC_FLAG_IO_IN_PROGRESS); + } + + if (BP_IS_PROTECTED(bp)) { +@@ -7023,11 +7023,11 @@ arc_write_done(zio_t *zio) + ASSERT0(BP_GET_LEVEL(zio->io_bp)); + } + } +- arc_hdr_clear_flags(hdr, ARC_FLAG_IO_IN_PROGRESS); +- VERIFY3S(remove_reference(hdr, hdr), >, 0); + /* if it's not anon, we are doing a scrub */ + if (exists == NULL && hdr->b_l1hdr.b_state == arc_anon) + arc_access(hdr, 0, B_FALSE); ++ arc_hdr_clear_flags(hdr, ARC_FLAG_IO_IN_PROGRESS); ++ VERIFY3S(remove_reference(hdr, hdr), >, 0); + mutex_exit(hash_lock); + } else { + arc_hdr_clear_flags(hdr, ARC_FLAG_IO_IN_PROGRESS); diff --git a/debian/patches/0015-rpm-Add-support-for-building-akmod-packages.patch b/debian/patches/0015-rpm-Add-support-for-building-akmod-packages.patch new file mode 100644 index 000000000..8468d175a --- /dev/null +++ b/debian/patches/0015-rpm-Add-support-for-building-akmod-packages.patch @@ -0,0 +1,344 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Vincent S. Cojot" +Date: Mon, 21 Sep 2026 18:47:34 +0200 +Subject: [PATCH] rpm: Add support for building akmod packages + +Add akmod (Automatic Kernel Module) support to ZFS, providing a third +packaging option alongside the existing DKMS and kmod packages. + +Akmod packages automatically build binary kmod RPMs when new kernels +are installed, enabling: +- Automatic kernel module rebuilds on kernel updates +- Distribution of binary kmod RPMs without recompilation +- Seamless minor version upgrades on RHEL/Fedora and clones + +Key changes: +- rpm/generic/zfs-kmod.spec.in: Add conditional akmod support via + rpmbuild --define '_enable_akmod 1'. When enabled, sets + buildforkernels=akmod and repo=rpmfusion so kmodtool generates an + akmod-zfs package. Add -common subpackage for shared license/doc + files. Guard chmod and %files sections for empty kernel_versions. + Make kmod-devel packages opt-in via %bcond_with devel to prevent + akmods from installing unnecessary devel packages on every kernel + update. When devel is not enabled, devel header files are cleaned + from the buildroot to avoid unpackaged-files errors. +- config/rpm.am: Add 'make rpm-akmod' and 'make srpm-akmod' targets. + These targets use the generic spec with _enable_akmod and + intentionally omit kernel-specific defines (kernels, ksrc, kobj) + so kmodtool enters its akmod-only code path. The existing + rpm-kmod/srpm-kmod targets pass --with devel to preserve the + current behavior of producing devel packages. +- config/zfs-build.m4: Update comments to document the akmod vs + regular kmod distinction for RPM_DEFINE_KMOD. +- scripts/kmodtool: Fix a pre-existing bug where + print_rpmtemplate_kmoddevelpkg (the common kmod-NAME-devel package) + was called inside the per-kernel loop in print_customrpmtemplate + and print_rpmtemplate, causing duplicate %package definitions when + multiple kernel-devel packages are installed. Move it outside the + loop so it is emitted exactly once. + +The akmod infrastructure requires the 'akmods' package to be installed +on the target system. When the akmod-zfs RPM is installed, it places +a zfs-kmod src.rpm in /usr/src/akmods/. The akmods service then +automatically rebuilds it for the running kernel via rpmbuild +--define 'kernels ' --rebuild. + +Usage: + make rpm-akmod # Build akmod-zfs, kmod-zfs (meta), zfs-kmod-common + make rpm-kmod # Build kmod with devel packages (unchanged behavior) + +Tested on Fedora 43 (kernel 7.1), RHEL 9.8 (kernel 5.14), and +Alma Linux 10 (kernel 6.12). Verified akmods rebuilds produce only +kmod-zfs (no devel packages) after kernel updates. + +Reviewed-by: Tony Hutter +Reviewed-by: Brian Behlendorf +Signed-off-by: Vincent S. Cojot +Closes #19099 +(cherry picked from commit bb1c7ed97a92d77096852f063cbfdbfee177b185) +Signed-off-by: Stoiko Ivanov +--- + config/rpm.am | 20 ++++++++++---- + config/zfs-build.m4 | 6 ++--- + rpm/generic/zfs-kmod.spec.in | 41 ++++++++++++++++++++++++----- + scripts/kmodtool | 51 ++++++++++++++++++++++-------------- + 4 files changed, 85 insertions(+), 33 deletions(-) + +diff --git a/config/rpm.am b/config/rpm.am +index 4ccf549b3a7a842d4a048b97fec8e86a70e00529..5d8188309c08bfb77cc4042c67a63d52e5ff35d5 100644 +--- a/config/rpm.am ++++ b/config/rpm.am +@@ -7,15 +7,15 @@ + # Build targets for RPM packages. + ############################################################################### + +-PHONY += srpm srpms srpm-kmod srpm-dkms srpm-utils +-PHONY += rpm rpms rpm-kmod rpm-dkms rpm-utils rpm-utils-initramfs ++PHONY += srpm srpms srpm-kmod srpm-dkms srpm-utils srpm-akmod ++PHONY += rpm rpms rpm-kmod rpm-dkms rpm-utils rpm-utils-initramfs rpm-akmod + PHONY += srpm-common rpm-common rpm-local + +-srpm-kmod srpm-dkms srpm-utils: dist ++srpm-kmod srpm-dkms srpm-utils srpm-akmod: dist + + srpm-kmod: + $(MAKE) $(AM_MAKEFLAGS) pkg="${PACKAGE}-kmod" \ +- def='${SRPM_DEFINE_COMMON} ${SRPM_DEFINE_KMOD}' srpm-common ++ def='${SRPM_DEFINE_COMMON} ${SRPM_DEFINE_KMOD} --with devel' srpm-common + + srpm-dkms: + $(MAKE) $(AM_MAKEFLAGS) pkg="${PACKAGE}-dkms" \ +@@ -25,12 +25,17 @@ srpm-utils: + $(MAKE) $(AM_MAKEFLAGS) pkg="${PACKAGE}" \ + def='${SRPM_DEFINE_COMMON} ${SRPM_DEFINE_UTIL}' srpm-common + ++srpm-akmod: ++ $(MAKE) $(AM_MAKEFLAGS) pkg="${PACKAGE}-kmod" \ ++ def='${SRPM_DEFINE_COMMON} --define "_wrong_version_format_terminate_build 0" --define "_enable_akmod 1"' \ ++ RPM_SPEC_DIR="rpm/generic" srpm-common ++ + srpm: srpm-kmod srpm-dkms srpm-utils + srpms: srpm-kmod srpm-dkms srpm-utils + + rpm-kmod: srpm-kmod + $(MAKE) $(AM_MAKEFLAGS) pkg="${PACKAGE}-kmod" \ +- def='${RPM_DEFINE_COMMON} ${RPM_DEFINE_KMOD}' rpm-common ++ def='${RPM_DEFINE_COMMON} ${RPM_DEFINE_KMOD} --with devel' rpm-common + + rpm-dkms: srpm-dkms + $(MAKE) $(AM_MAKEFLAGS) pkg="${PACKAGE}-dkms" \ +@@ -52,6 +57,11 @@ rpm-utils-initramfs: srpm-utils + $(MAKE) $(AM_MAKEFLAGS) pkg="${PACKAGE}" \ + def='${RPM_DEFINE_COMMON} ${RPM_DEFINE_UTIL} ${RPM_DEFINE_INITRAMFS}' rpm-common + ++rpm-akmod: srpm-akmod ++ $(MAKE) $(AM_MAKEFLAGS) pkg="${PACKAGE}-kmod" \ ++ def='${RPM_DEFINE_COMMON} --define "_wrong_version_format_terminate_build 0" --define "_enable_akmod 1"' \ ++ RPM_SPEC_DIR="rpm/generic" rpm-common ++ + rpm: rpm-kmod rpm-dkms rpm-utils + rpms: rpm-kmod rpm-dkms rpm-utils + +diff --git a/config/zfs-build.m4 b/config/zfs-build.m4 +index 49e46dfd5632164a23eae67b4ace4ee9e6fc5e6e..5d245b638d1e097009e9fc589ff0199511567a24 100644 +--- a/config/zfs-build.m4 ++++ b/config/zfs-build.m4 +@@ -438,9 +438,9 @@ AC_DEFUN([ZFS_AC_RPM], [ + AC_SUBST(MULTIARCH_LIBDIR) + ]) + +- dnl # Make RPM_DEFINE_KMOD additions conditional on CONFIG_KERNEL, +- dnl # since the values will not be set otherwise. The spec files +- dnl # provide defaults for them. ++ dnl # For akmod builds, we don't need to define kernel build parameters ++ dnl # since akmods handles this automatically. For regular kmod builds, ++ dnl # we need all the kernel build parameters. + dnl # + RPM_DEFINE_KMOD='--define "_wrong_version_format_terminate_build 0"' + AM_COND_IF([CONFIG_KERNEL], [ +diff --git a/rpm/generic/zfs-kmod.spec.in b/rpm/generic/zfs-kmod.spec.in +index 7ed828bd0c9c4f5227f6fc93c7ea822a8f42e45a..b23c8cca27af2dc74963f40c827c7a9f7f770deb 100644 +--- a/rpm/generic/zfs-kmod.spec.in ++++ b/rpm/generic/zfs-kmod.spec.in +@@ -1,3 +1,4 @@ ++%define debug_package %{nil} + %define module @PACKAGE@ + + %if !%{defined ksrc} +@@ -28,16 +29,19 @@ + %endif + %endif + +-#define repo rpmfusion ++# Conditional akmod support - can be enabled via: rpmbuild --define "_enable_akmod 1" ++%{?_enable_akmod:%define repo rpmfusion} ++%{!?_enable_akmod:#define repo rpmfusion} + #define repo chaos + + # (un)define the next line to either build for the newest or all current kernels +-%define buildforkernels newest ++%{?_enable_akmod:%define buildforkernels akmod} ++%{!?_enable_akmod:%define buildforkernels newest} + #define buildforkernels current +-#define buildforkernels akmod + + %bcond_with debug + %bcond_with debuginfo ++%bcond_with devel + + + Name: %{module}-kmod +@@ -72,7 +76,8 @@ Conflicts: %{module}-dkms + # Building for a repository use the proper build-sysbuild package + # to determine which kernel-devel packages should be installed. + BuildRequires: %{_bindir}/kmodtool +-%{!?kernels:BuildRequires: buildsys-build-%{repo}-kerneldevpkgs-%{?buildforkernels:%{buildforkernels}}%{!?buildforkernels:current}-%{_target_cpu}} ++# Skip buildsys-build for akmod builds as akmods handles kernel dependencies ++%{!?_enable_akmod:%{!?kernels:BuildRequires: buildsys-build-%{repo}-kerneldevpkgs-%{?buildforkernels:%{buildforkernels}}%{!?buildforkernels:current}-%{_target_cpu}}} + + %else + +@@ -99,18 +104,28 @@ BuildRequires: %{_bindir}/kmodtool + # Kmodtool does its magic here. A patched version of kmodtool is shipped + # with the source rpm until kmod development packages are supported upstream. + # https://bugzilla.rpmfusion.org/show_bug.cgi?id=2714 +-%{expand:%(bash %{SOURCE10} --target %{_target_cpu} %{?repo:--repo %{?repo}} --kmodname %{name} %{?buildforkernels:--%{buildforkernels}} --devel %{?prefix:--prefix "%{?prefix}"} %{?kernels:--for-kernels "%{?kernels}"} %{?kernelbuildroot:--buildroot "%{?kernelbuildroot}"} 2>/dev/null) } ++%{expand:%(bash %{SOURCE10} --target %{_target_cpu} %{?repo:--repo %{?repo}} --kmodname %{name} %{?buildforkernels:--%{buildforkernels}} %{?with_devel:--devel} %{?prefix:--prefix "%{?prefix}"} %{?kernels:--for-kernels "%{?kernels}"} %{?kernelbuildroot:--buildroot "%{?kernelbuildroot}"} 2>/dev/null) } + + + %description + This package contains the ZFS kernel modules. + ++# akmod package requires -common subpackage ++%package common ++Summary: Common files for the ZFS kernel modules ++Requires: %{name} = %{version}-%{release} ++BuildArch: noarch ++ ++%description common ++This package contains the common files (license, documentation, etc.) for the ++ZFS kernel modules. ++ + %prep + # Error out if there was something wrong with kmodtool. + %{?kmodtool_check} + + # Print kmodtool output for debugging purposes: +-bash %{SOURCE10} --target %{_target_cpu} %{?repo:--repo %{?repo}} --kmodname %{name} %{?buildforkernels:--%{buildforkernels}} --devel %{?prefix:--prefix "%{?prefix}"} %{?kernels:--for-kernels "%{?kernels}"} %{?kernelbuildroot:--buildroot "%{?kernelbuildroot}"} 2>/dev/null ++bash %{SOURCE10} --target %{_target_cpu} %{?repo:--repo %{?repo}} --kmodname %{name} %{?buildforkernels:--%{buildforkernels}} %{?with_devel:--devel} %{?prefix:--prefix "%{?prefix}"} %{?kernels:--for-kernels "%{?kernels}"} %{?kernelbuildroot:--buildroot "%{?kernelbuildroot}"} 2>/dev/null + + %if %{with debug} + %define debug --enable-debug +@@ -129,6 +144,9 @@ bash %{SOURCE10} --target %{_target_cpu} %{?repo:--repo %{?repo}} --kmodname %{ + + %setup -q -c -T -a 0 + ++# For common package ++cp %{module}-%{version}/{COPYRIGHT,LICENSE,NOTICE,README.md} . ++ + for kernel_version in %{?kernel_versions}; do + %{__mkdir} _kmod_build_${kernel_version%%___*} + done +@@ -207,9 +225,20 @@ for kernel_version in %{?kernel_versions}; do + cd .. + done + # find-debuginfo.sh only considers executables ++%if %{defined kernel_versions} + chmod u+x ${RPM_BUILD_ROOT}%{kmodinstdir_prefix}/*/extra/*/* ++%endif + %{?akmod_install} ++# Remove devel files when devel packages are not being built, ++# to prevent rpmbuild from erroring on unpackaged files. ++%if !%{with devel} ++rm -rf ${RPM_BUILD_ROOT}/usr/src/%{module}-* ++%endif + + + %clean + rm -rf $RPM_BUILD_ROOT ++ ++%files common ++%doc README.md ++%license COPYRIGHT LICENSE NOTICE +diff --git a/scripts/kmodtool b/scripts/kmodtool +index e24f206e698a29d19d8a771f831ff700c7858f04..d8d2af04fc9c2d9e384a1c05b718fb1703812cc3 100755 +--- a/scripts/kmodtool ++++ b/scripts/kmodtool +@@ -332,44 +332,47 @@ EOF + + print_customrpmtemplate () + { ++ local first_kernel_custom="" ++ local first_kernel_redhat="" ++ local first_kernel_redhat_variant="" ++ + for kernel in ${1} + do + if [[ -e "${prefix}/lib/modules/${kernel}/build/Makefile" ]]; then +- # likely a user-build-kernel with available buildfiles +- # fixme: we should check if uname from Makefile is the same as ${kernel} +- + kernel_versions="${kernel_versions}${kernel}___${prefix}/lib/modules/${kernel}/build/ " + print_rpmtemplate_per_kmodpkg --custom "${kernel}" + +- # create development package + if [[ -n "${devel}" ]]; then +- # create devel package including common headers +- print_rpmtemplate_kmoddevelpkg --custom "${kernel}" +- +- # create devel package + print_rpmtemplate_per_kmoddevelpkg --custom "${kernel}" + fi ++ [[ -z "${first_kernel_custom}" ]] && first_kernel_custom="${kernel}" + elif [[ -e "${buildroot}/usr/src/kernels/${kernel}" ]]; then +- # this looks like a Fedora/RH kernel -- print a normal template (which includes the proper BR) and be happy :) + kernel_versions="${kernel_versions}${kernel}___${buildroot}%{_usrsrc}/kernels/${kernel} " + +- # parse kernel versions string and print template + local kernel_verrelarch=${kernel%%${kernels_known_variants}} + print_rpmtemplate_per_kmodpkg --redhat ${kernel} ${kernel##${kernel_verrelarch}} + +- # create development package + if [[ -n "${devel}" ]]; then +- # create devel package including common headers +- print_rpmtemplate_kmoddevelpkg --redhat ${kernel} ${kernel##${kernel_verrelarch}} +- +- # create devel package + print_rpmtemplate_per_kmoddevelpkg --redhat ${kernel} ${kernel##${kernel_verrelarch}} + fi ++ if [[ -z "${first_kernel_redhat}" ]]; then ++ first_kernel_redhat="${kernel}" ++ first_kernel_redhat_variant="${kernel##${kernel_verrelarch}}" ++ fi + else + error_out 2 "Don't know how to handle ${kernel} -- ${prefix}/lib/modules/${kernel}/build/Makefile not found" + fi + done + ++ # Common devel package (only once, outside the per-kernel loop) ++ if [[ -n "${devel}" ]]; then ++ if [[ -n "${first_kernel_custom}" ]]; then ++ print_rpmtemplate_kmoddevelpkg --custom "${first_kernel_custom}" ++ elif [[ -n "${first_kernel_redhat}" ]]; then ++ print_rpmtemplate_kmoddevelpkg --redhat ${first_kernel_redhat} ${first_kernel_redhat_variant} ++ fi ++ fi ++ + # well, it's no header anymore, but who cares ;-) + print_rpmtemplate_header + } +@@ -386,6 +389,9 @@ print_rpmtemplate () + # and print it and some other required stuff as macro + print_rpmtemplate_header + ++ local first_kernel="" ++ local first_kernel_variant="" ++ + # now print the packages + for kernel in ${kernel_versions_to_build_for} ; do + +@@ -398,13 +404,20 @@ print_rpmtemplate () + print_rpmtemplate_per_kmodpkg "${kernel}" "${kernel##${kernel_verrelarch}}" + + if [[ -n "${devel}" ]]; then +- # create devel package including common headers +- print_rpmtemplate_kmoddevelpkg "${kernel}" "${kernel##${kernel_verrelarch}}" +- +- # create devel package ++ # create per-kernel devel package + print_rpmtemplate_per_kmoddevelpkg "${kernel}" "${kernel##${kernel_verrelarch}}" + fi ++ ++ if [[ -z "${first_kernel}" ]]; then ++ first_kernel="${kernel}" ++ first_kernel_variant="${kernel##${kernel_verrelarch}}" ++ fi + done ++ ++ # Common devel package (only once, outside the per-kernel loop) ++ if [[ -n "${devel}" && -n "${first_kernel}" ]]; then ++ print_rpmtemplate_kmoddevelpkg "${first_kernel}" "${first_kernel_variant}" ++ fi + } + + myprog_help () diff --git a/debian/patches/0016-libzfs_core-block-delivery-of-SIGUSR1-in-send_worker.patch b/debian/patches/0016-libzfs_core-block-delivery-of-SIGUSR1-in-send_worker.patch new file mode 100644 index 000000000..ae5926810 --- /dev/null +++ b/debian/patches/0016-libzfs_core-block-delivery-of-SIGUSR1-in-send_worker.patch @@ -0,0 +1,93 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nico Sonack +Date: Mon, 14 Sep 2026 18:36:11 +0000 +Subject: [PATCH] libzfs_core: block delivery of SIGUSR1 in send_worker thread + +This fixes a Linux-specific bug. + +3a909fe33 (libzfs, libzfs_core: send: always write to pipe, 2022-02-21) +introduced a subtle bug where zfs send -RPv would not print status +information periodically anymore when the stdout is redirected to +something that isn't a pipe (e.g. >/dev/null). + +This is because the send_worker thread introduced by this commit +is created with an unmodified signal mask from libzfs. When +zfs_send_space is called it creates this thread. When we request +verbose status information another thread is created which, in +theory, should periodically receive a SIGUSR1 via a POSIX timer. + +The main thread blocks USR1 delivery *after* the creation of the +progress thread, leaving the send_worker thread's signal mask +unmodified. The delivery of SIGUSR1 is now random and for at least +Debian Trixie with kernel 6.12.107+deb13-amd64 this causes the +send_worker to be favoured. + +Interestingly, when stracing the zfs send, sometimes delivery flaps +over to the correct thread. Without tracing this doesn't happen and +the output remains silent instead. + +Fix this by blocking delivery of SIGUSR1 in the send_worker thread. +We don't need to take care of SIGINFO since this thread is only +created on Linux and SIGINFO is a BSD-specific signal. + +Reviewed-by: Brian Behlendorf +Signed-off-by: Nico Sonack +Closes #19098 +(cherry picked from commit 47906ec304216b91277a149d4a574ea7ce196575) +Signed-off-by: Stoiko Ivanov +--- + lib/libzfs_core/libzfs_core.c | 27 +++++++++++++++++++++++++-- + 1 file changed, 25 insertions(+), 2 deletions(-) + +diff --git a/lib/libzfs_core/libzfs_core.c b/lib/libzfs_core/libzfs_core.c +index 9347aa7c6a28dd3da5bd905689b21336b9972cb5..b0f59d04f8e1a0c192e9e088f1e4fcc355c63066 100644 +--- a/lib/libzfs_core/libzfs_core.c ++++ b/lib/libzfs_core/libzfs_core.c +@@ -78,6 +78,7 @@ + #include + #include + #include ++#include + #include + #include + #ifdef ZFS_DEBUG +@@ -699,7 +700,28 @@ send_worker(void *arg) + { + struct send_worker_ctx *ctx = arg; + unsigned int bufsiz = max_pipe_buffer(ctx->from); +- ssize_t rd; ++ sigset_t block_mask; ++ ssize_t rd = -1; ++ ++ /* ++ * When users request verbose status information about the ++ * send progress SIGUSR1 is delivered periodically in libzfs ++ * to a dedicated thread. ++ * ++ * However, we get created before this progress thread is ++ * created and the signal mask of the parent is only modified to ++ * block SIGUSR1 afterwards. ++ * ++ * Block delivery of USR1 to us such that we don't get ++ * interrupted and the right thread gets the signal. ++ */ ++ sigemptyset(&block_mask); ++ sigaddset(&block_mask, SIGUSR1); ++ int err = pthread_sigmask(SIG_BLOCK, &block_mask, NULL); ++ if (err != 0) { ++ close(ctx->from); ++ return ((void *)(uintptr_t)err); ++ } + + for (;;) { + rd = splice(ctx->from, NULL, ctx->to, NULL, bufsiz, +@@ -707,7 +729,8 @@ send_worker(void *arg) + if ((rd == -1 && errno != EINTR) || rd == 0) + break; + } +- int err = (rd == -1) ? errno : 0; ++ ++ err = (rd == -1) ? errno : 0; + close(ctx->from); + return ((void *)(uintptr_t)err); + } diff --git a/debian/patches/0017-kmodtool-use-kABI-baseline-naming-on-RHEL-to-prevent.patch b/debian/patches/0017-kmodtool-use-kABI-baseline-naming-on-RHEL-to-prevent.patch new file mode 100644 index 000000000..16acc6a98 --- /dev/null +++ b/debian/patches/0017-kmodtool-use-kABI-baseline-naming-on-RHEL-to-prevent.patch @@ -0,0 +1,417 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Vincent S. Cojot" +Date: Fri, 2 Oct 2026 19:11:17 +0200 +Subject: [PATCH] kmodtool: use kABI-baseline naming on RHEL to prevent kmod + accumulation + +On RHEL, errata kernels within a minor release share a stable kABI. +The existing kmodtool generates a unique kmod package name per exact +kernel version (e.g. kmod-zfs-5.14.0-687.52.1.el9_8), which causes +unbounded kmod package accumulation as new errata kernels are installed +and akmods rebuilds for each one. + +Port the kABI-aware naming logic from RPM Fusion's kmodtool: + +- Add init_kernel_uname_r_vars() to parse kernel uname -r into + components including the kABI baseline (kernel_uname_r_short). + Example: 5.14.0-687.52.1.el9_8.x86_64 -> 5.14.0-687.el9_8 + +- On RHEL (%{?rhel}), use kernel_uname_r_short in package names + so all errata kernels within a minor release produce the same + package name (e.g. kmod-zfs-5.14.0-687.el9_8). + +- On Fedora (no kABI guarantee), keep the full kernel_uname_r in + package names, preserving current behavior. + +- Add RHEL kABI-specific features: + - Provides: installonlypkg(kernel-module) + - Requires: kernel >= short, kernel < short+1 (range deps) + - weak-modules integration in %post/%preun/%postun scriptlets + +- File paths always use the full kernel_uname_r regardless of + the naming scheme. + +This matches the approach used by RPM Fusion's kmodtool (kmodtool +package v1.1) and negativo17's nvidia-kmod packaging, both of which +have been using kABI-baseline naming successfully on RHEL for years. + +Signed-off-by: Vincent S. Cojot +(cherry picked from commit 65ea88f071abf32622912c69ccfefde3709e2ac7) +Signed-off-by: Stoiko Ivanov +--- + scripts/kmodtool | 239 ++++++++++++++++++++++++++++++++++++++--------- + 1 file changed, 197 insertions(+), 42 deletions(-) + +diff --git a/scripts/kmodtool b/scripts/kmodtool +index d8d2af04fc9c2d9e384a1c05b718fb1703812cc3..7fa34408e2954e651e6fe04441faf1529abf16b3 100755 +--- a/scripts/kmodtool ++++ b/scripts/kmodtool +@@ -1,6 +1,6 @@ + #!/usr/bin/env bash + # SPDX-License-Identifier: MIT +-# shellcheck disable=SC2086,SC2295 ++# shellcheck disable=SC2001,SC2034,SC2086,SC2249,SC2295 + + # kmodtool - Helper script for building kernel module RPMs + # Copyright (c) 2003-2012 Ville Skyttä , +@@ -29,7 +29,7 @@ + shopt -s extglob + + myprog="kmodtool-${repo}" +-myver="0.12.1" ++myver="0.13.0" + + kmodname= + build_kernels="current" +@@ -42,6 +42,14 @@ target= + buildroot= + dashvariant= + ++kernel_uname_r="" ++kernel_uname_r_short="" ++kernel_uname_r_wo_arch="" ++kernel_uname_r_dist="" ++kernel_uname_r_rel="" ++kernel_uname_r_rel_plus_one="" ++kernel_uname_r_short_plus_one="" ++ + error_out() + { + local errorlevel=${1} +@@ -61,6 +69,69 @@ print_rpmtemplate_header() + echo + } + ++init_kernel_uname_r_vars() ++{ ++ ## Parse kernel uname -r into useful substrings for kABI-aware naming. ++ ## On RHEL, errata kernels share a kABI baseline (e.g. 5.14.0-687.el9) ++ ## so modules built for one errata kernel are compatible with all ++ ## kernels in the same minor release. Using the short form in package ++ ## names avoids unbounded kmod accumulation. ++ ## ++ ## Examples (RHEL 9.8, x86_64): ++ ## kernel_uname_r 5.14.0-687.52.1.el9_8.x86_64 ++ ## kernel_uname_r_short 5.14.0-687.el9_8 ++ ## kernel_uname_r_wo_arch 5.14.0-687.52.1.el9_8 ++ ## kernel_uname_r_dist el9_8 ++ ## kernel_uname_r_rel 687 ++ ## kernel_uname_r_rel_plus_one 688 ++ ## kernel_uname_r_short_plus_one 5.14.0-688.el9_8 ++ ## ++ kernel_uname_r=${1} ++ kernel_uname_r_short=$(echo "${1}" | sed -n \ ++ -e "s/\.${target}//" \ ++ -e "s/\(.*\)-\([[:alnum:]]\+\)\(\.[[:alnum:]\.]\+\|\)\(\.el.*\|\.fc.*\)/\1-\2\4/p") ++ kernel_uname_r_wo_arch=$(echo "${1}" | sed -e "s/\.${target}//") ++ kernel_uname_r_dist=$(echo "${1}" | sed -n \ ++ -e "s/\.${target}//" \ ++ -e "s/\(.*\)-\([[:alnum:]]\+\.[[:alnum:]]\+\-\|[[:alnum:]]\+\)\([[:alnum:]\.]\+\|\)\(\.el.*\|\.fc.*\)/\4/" \ ++ -e "s/^\.//p") ++ kernel_uname_r_rel=$(echo "${1}" | sed -n \ ++ -e "s/\.${target}//" \ ++ -e "s/\(.*\)-\([[:alnum:]]\+\.[[:alnum:]]\+\-\|[[:alnum:]]\+\.\)\([[:alnum:]\.]\+\|\)\(.*\)/\2/" \ ++ -e "s/^\.//" -e "s/[\.\-]$//p") ++ if echo "${kernel_uname_r_rel}" | grep -qE '^[0-9]+$' ; then ++ kernel_uname_r_rel_plus_one=$(( kernel_uname_r_rel+1 )) ++ else ++ kernel_uname_r_rel_plus_one="" ++ fi ++ kernel_uname_r_short_plus_one=$(echo "${kernel_uname_r_short}" | \ ++ sed -e "s/-${kernel_uname_r_rel}\./-${kernel_uname_r_rel_plus_one}\./") ++} ++ ++print_rpmtemplate_macros() ++{ ++ cat <= %{?epoch:%{epoch}:}%{version} + Provides: ${kmodname}-kmod = %{?epoch:%{epoch}:}%{version}-%{release} + EOF + +@@ -145,65 +215,122 @@ print_rpmtemplate_per_kmodpkg () + fi + + # first part ++ # ++ # On RHEL, use the kABI-baseline version (kernel_uname_r_short) in ++ # the package name so that errata kernel rebuilds produce the same ++ # package name and avoid unbounded kmod accumulation. The file ++ # paths still use the full kernel_uname_r. ++ # ++ # On Fedora (no kABI guarantee), use the full kernel_uname_r in ++ # the package name as before. + cat <= %{?epoch:%{epoch}:}%{version} +- +-%if 0%{?rhel} == 6 || 0%{?centos} == 6 +-Requires(post): module-init-tools +-Requires(postun): module-init-tools ++%if %{with rhel_kabi} ++Provides: installonlypkg(kernel-module) ++Requires(post): kmod ++Requires(postun): kmod ++Requires(post): %{_prefix}/sbin/weak-modules ++Requires(postun): %{_prefix}/sbin/weak-modules + %else + Requires(post): kmod + Requires(postun): kmod + %endif + EOF + +- # second part ++ # second part — depmod helper macro ++ cat </dev/null ; \\ ++elif [ -f /lib/modules/${kernel_uname_r}/System.map ] ; then \\ ++ ${prefix}${depmod_path} -aeF /lib/modules/${kernel_uname_r}/System.map ${kernel_uname_r} >/dev/null ; \\ ++else \\ ++ ${prefix}${depmod_path} -a >/dev/null ; \\ ++fi || : ++EOF ++ + if [[ -z "${customkernel}" ]]; then + cat <= ${kernel_uname_r_short} ++Requires: kernel < ${kernel_uname_r_short_plus_one} ++BuildRequires: kernel-devel = ${kernel_uname_r_wo_arch} redhat-rpm-config ++%else + Requires: kernel-uname-r = ${kernel_uname_r} +-BuildRequires: kernel-devel-uname-r = ${kernel_uname_r} ++BuildRequires: kernel-devel-uname-r = ${kernel_uname_r} ++%endif + %{?KmodsRequires:Requires: %{KmodsRequires}-uname-r = ${kernel_uname_r}} + %{?KmodsRequires:BuildRequires: %{KmodsRequires}-uname-r = ${kernel_uname_r}} +-%post -n kmod-${kmodname}-${kernel_uname_r} +-if [[ -f "/boot/System.map-${kernel_uname_r}" ]]; then +- ${prefix}${depmod_path} -aeF /boot/System.map-${kernel_uname_r} ${kernel_uname_r} > /dev/null || : +-elif [[ -f "/lib/modules/${kernel_uname_r}/System.map" ]]; then +- ${prefix}${depmod_path} -aeF /lib/modules/${kernel_uname_r}/System.map ${kernel_uname_r} > /dev/null || : +-else +- ${prefix}${depmod_path} -ae ${kernel_uname_r} &> /dev/null || : +-fi +-%postun -n kmod-${kmodname}-${kernel_uname_r} +-if [[ -f "/boot/System.map-${kernel_uname_r}" ]]; then +- ${prefix}${depmod_path} -aF /boot/System.map-${kernel_uname_r} ${kernel_uname_r} &> /dev/null || : +-elif [[ -f "/lib/modules/${kernel_uname_r}/System.map" ]]; then +- ${prefix}${depmod_path} -aF /lib/modules/${kernel_uname_r}/System.map ${kernel_uname_r} &> /dev/null || : +-else +- ${prefix}${depmod_path} -a ${kernel_uname_r} &> /dev/null || : +-fi ++ ++%post -n %{package_kmod_name} ++%{_kmodtool_depmod_post} ++%if %{with rhel_kabi} ++modules=( \$(find ${prefix}/lib/modules/${kernel_uname_r}/extra/${kmodname}/ \ ++ -name "*.ko" -o -name "*.ko.xz" -o -name "*.ko.gz" -o -name "*.ko.zst") ) ++printf '%s\\n' "\${modules[@]}" | %{_prefix}/sbin/weak-modules --add-modules ++%endif ++ ++%if %{with rhel_kabi} ++%preun -n %{package_kmod_name} ++rpm -ql %{package_kmod_name}-%{version}-%{release}.$(arch) \ ++ | grep -E '\.ko(\.xz|\.gz|\.zst)?$' \ ++ > /var/run/rpm-%{package_kmod_name}-modules ++%endif ++ ++%postun -n %{package_kmod_name} ++%{_kmodtool_depmod_post} ++%if %{with rhel_kabi} ++modules=( \$(cat /var/run/rpm-%{package_kmod_name}-modules) ) ++rm -f /var/run/rpm-%{package_kmod_name}-modules ++printf '%s\\n' "\${modules[@]}" | %{_prefix}/sbin/weak-modules --remove-modules ++%endif + + EOF + else + cat < /dev/null || : +-%postun -n kmod-${kmodname}-${kernel_uname_r} ++%if %{with rhel_kabi} ++if [[ "\$(uname -r)" == "${kernel_uname_r}" ]] ; then ++ modules=( \$(find ${prefix}/lib/modules/${kernel_uname_r}/extra/${kmodname}/ \ ++ -name "*.ko" -o -name "*.ko.xz" -o -name "*.ko.gz" -o -name "*.ko.zst") ) ++ printf '%s\\n' "\${modules[@]}" | %{_prefix}/sbin/weak-modules --add-modules ++fi ++%endif ++ ++%if %{with rhel_kabi} ++%preun -n %{package_kmod_name} ++if [[ "\$(uname -r)" == "${kernel_uname_r}" ]] ; then ++ rpm -ql %{package_kmod_name}-%{version}-%{release}.$(arch) \ ++ | grep -E '\.ko(\.xz|\.gz|\.zst)?$' \ ++ > /var/run/rpm-%{package_kmod_name}-modules ++fi ++%endif ++ ++%postun -n %{package_kmod_name} + [ "\$(uname -r)" = "${kernel_uname_r}" ] && ${prefix}${depmod_path} -a > /dev/null || : ++%if %{with rhel_kabi} ++if [[ "\$(uname -r)" == "${kernel_uname_r}" ]] ; then ++ modules=( \$(cat /var/run/rpm-%{package_kmod_name}-modules) ) ++ rm -f /var/run/rpm-%{package_kmod_name}-modules ++ printf '%s\\n' "\${modules[@]}" | %{_prefix}/sbin/weak-modules --remove-modules ++fi ++%endif + + EOF + fi + +- # third part ++ # third part — file paths always use the full kernel_uname_r + cat <= %{?epoch:%{epoch}:}%{version}-%{release} ++Requires: %{package_kmod_name} >= %{?epoch:%{epoch}:}%{version}-%{release} + %{?KmodsMetaRequires:Requires: %{?KmodsMetaRequires}} + EOF + + cat < +Date: Wed, 10 Sep 2025 20:20:13 +0500 +Subject: [PATCH] L2ARC: Reorder header destruction for in-flight L2 writes + +With multiple L2ARC devices, headers can be destroyed asynchronously +(e.g., during zpool sync) while L2_WRITING is set. The original code +destroyed L2HDR before L1HDR, causing ABDs to lose their device +association (b_l2hdr.b_dev) when arc_hdr_free_abd() is called. + +This caused ABDs to be added to the global free-on-write list without +device information. When any L2ARC device completed its write and +attempted to free these orphaned ABDs, it would panic on +ASSERT(!list_link_active(&abd->abd_gang_link)) because the ABD was +still part of another device's vdev_queue I/O aggregation gang. + +Fix by extending l2ad_mtx lock scope to cover L1HDR destruction and +reordering to destroy L1HDR before L2HDR when L2_WRITING is set. This +ensures arc_hdr_free_abd() can access b_l2hdr.b_dev to properly tag +ABDs with their device for deferred cleanup. + +Reviewed-by: Alexander Motin +Reviewed-by: Brian Behlendorf +Signed-off-by: Ameer Hamza +Closes #18093 +(cherry picked from commit b8610c3d93fec2297a9f69fcab3f5b35f01e941f) +Signed-off-by: Andriy Tkachuk +(cherry picked from commit cece1476c6a19c19c9ac355c923774788cee11bb) +Signed-off-by: Stoiko Ivanov +--- + module/zfs/arc.c | 55 ++++++++++++++++++++++++++++++++---------------- + 1 file changed, 37 insertions(+), 18 deletions(-) + +diff --git a/module/zfs/arc.c b/module/zfs/arc.c +index 4cd2a67eb2a85398627fad5531dc1aa20f1b2f5d..8556b55c2bf235bdb80bd451c7489f276f6322f5 100644 +--- a/module/zfs/arc.c ++++ b/module/zfs/arc.c +@@ -3656,7 +3656,13 @@ arc_hdr_destroy(arc_buf_hdr_t *hdr) + } + VERIFY(!HDR_IO_IN_PROGRESS(hdr)); + VERIFY(!HDR_IN_HASH_TABLE(hdr)); ++ boolean_t l1hdr_destroyed = B_FALSE; + ++ /* ++ * If L2_WRITING, destroy L1HDR before L2HDR (under mutex) so ++ * arc_hdr_free_abd() can properly defer ABDs. Otherwise, destroy ++ * L1HDR outside mutex to minimize contention. ++ */ + if (HDR_HAS_L2HDR(hdr)) { + l2arc_dev_t *dev = hdr->b_l2hdr.b_dev; + boolean_t buflist_held = MUTEX_HELD(&dev->l2ad_mtx); +@@ -3674,9 +3680,26 @@ arc_hdr_destroy(arc_buf_hdr_t *hdr) + * want to re-destroy the header's L2 portion. + */ + if (HDR_HAS_L2HDR(hdr)) { ++ if (HDR_L2_WRITING(hdr)) { ++ l1hdr_destroyed = B_TRUE; + +- if (!HDR_EMPTY(hdr)) +- buf_discard_identity(hdr); ++ if (!HDR_EMPTY(hdr)) ++ buf_discard_identity(hdr); ++ ++ if (HDR_HAS_L1HDR(hdr)) { ++ arc_cksum_free(hdr); ++ ++ while (hdr->b_l1hdr.b_buf != NULL) ++ arc_buf_destroy_impl( ++ hdr->b_l1hdr.b_buf); ++ ++ if (hdr->b_l1hdr.b_pabd != NULL) ++ arc_hdr_free_abd(hdr, B_FALSE); ++ ++ if (HDR_HAS_RABD(hdr)) ++ arc_hdr_free_abd(hdr, B_TRUE); ++ } ++ } + + arc_hdr_l2hdr_destroy(hdr); + } +@@ -3685,26 +3708,22 @@ arc_hdr_destroy(arc_buf_hdr_t *hdr) + mutex_exit(&dev->l2ad_mtx); + } + +- /* +- * The header's identify can only be safely discarded once it is no +- * longer discoverable. This requires removing it from the hash table +- * and the l2arc header list. After this point the hash lock can not +- * be used to protect the header. +- */ +- if (!HDR_EMPTY(hdr)) +- buf_discard_identity(hdr); ++ if (!l1hdr_destroyed) { ++ if (!HDR_EMPTY(hdr)) ++ buf_discard_identity(hdr); + +- if (HDR_HAS_L1HDR(hdr)) { +- arc_cksum_free(hdr); ++ if (HDR_HAS_L1HDR(hdr)) { ++ arc_cksum_free(hdr); + +- while (hdr->b_l1hdr.b_buf != NULL) +- arc_buf_destroy_impl(hdr->b_l1hdr.b_buf); ++ while (hdr->b_l1hdr.b_buf != NULL) ++ arc_buf_destroy_impl(hdr->b_l1hdr.b_buf); + +- if (hdr->b_l1hdr.b_pabd != NULL) +- arc_hdr_free_abd(hdr, B_FALSE); ++ if (hdr->b_l1hdr.b_pabd != NULL) ++ arc_hdr_free_abd(hdr, B_FALSE); + +- if (HDR_HAS_RABD(hdr)) +- arc_hdr_free_abd(hdr, B_TRUE); ++ if (HDR_HAS_RABD(hdr)) ++ arc_hdr_free_abd(hdr, B_TRUE); ++ } + } + + VERIFY0P(hdr->b_hash_next); diff --git a/debian/patches/0019-L2ARC-Preserve-L2HDR-in-arc_release-for-in-flight-wr.patch b/debian/patches/0019-L2ARC-Preserve-L2HDR-in-arc_release-for-in-flight-wr.patch new file mode 100644 index 000000000..245ee4165 --- /dev/null +++ b/debian/patches/0019-L2ARC-Preserve-L2HDR-in-arc_release-for-in-flight-wr.patch @@ -0,0 +1,174 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ameer Hamza +Date: Sun, 4 Jan 2026 21:10:02 +0500 +Subject: [PATCH] L2ARC: Preserve L2HDR in arc_release() for in-flight writes + +When arc_release() is called on a header with a single buffer and +L2_WRITING set, the L2HDR must be preserved for ABD cleanup (similar +to the arc_hdr_destroy() case). If we destroy the L2HDR here, later +arc_write() will allocate a new ABD and call arc_hdr_free_abd(), +which needs b_l2hdr.b_dev to properly defer ABD cleanup, causing +VERIFY(HDR_HAS_L2HDR(hdr)) to fail. + +Allocate a new header for the buffer in the single_buf_l2writing +case (single buffer + L2_WRITING), leaving the original header with +L2HDR intact. The original header becomes an "orphan" (no buffers, no +b_pabd) but retains device association for ABD cleanup when +l2arc_write_done() completes. + +The shared buffer case (HDR_SHARED_DATA) is excluded because L2ARC +makes its own transformed copy via l2arc_apply_transforms(), so the +original ABD is not used by the L2 write. The header can be safely +reused without allocating a new one. + +For proper evictable space accounting, arc_buf_remove() must be +called before remove_reference() in the single_buf_l2writing path. +This ensures arc_evictable_space_increment() (during remove_reference) +and arc_evictable_space_decrement() (during destruction) see the +same state (b_buf=NULL), preventing accounting leaks that cause +module unload to hang with non-zero esize. + +Reviewed-by: Alexander Motin +Reviewed-by: Brian Behlendorf +Signed-off-by: Ameer Hamza +Closes #18093 +(cherry picked from commit 825dc41ad4bf262e3d8e6ccb5d549709bf1e53c5) +Signed-off-by: Andriy Tkachuk +(cherry picked from commit acf6f13ff574eef41b1e5078f1fad98c9d1d8036) +Signed-off-by: Stoiko Ivanov +--- + module/zfs/arc.c | 88 ++++++++++++++++++++++++++++-------------------- + 1 file changed, 51 insertions(+), 37 deletions(-) + +diff --git a/module/zfs/arc.c b/module/zfs/arc.c +index 8556b55c2bf235bdb80bd451c7489f276f6322f5..5393c018ee82ba73eb53414683350abc7ba45c74 100644 +--- a/module/zfs/arc.c ++++ b/module/zfs/arc.c +@@ -6669,16 +6669,22 @@ arc_release(arc_buf_t *buf, const void *tag) + ASSERT3S(zfs_refcount_count(&hdr->b_l1hdr.b_refcnt), >, 0); + + /* +- * Do we have more than one buf? ++ * Do we have more than one buf? Or L2_WRITING with unshared data? ++ * Single-buf L2_WRITING with shared data can reuse the header since ++ * L2ARC uses its own transformed copy. + */ +- if (hdr->b_l1hdr.b_buf != buf || !ARC_BUF_LAST(buf)) { ++ if (hdr->b_l1hdr.b_buf != buf || !ARC_BUF_LAST(buf) || ++ (HDR_L2_WRITING(hdr) && !ARC_BUF_SHARED(buf))) { + arc_buf_hdr_t *nhdr; + uint64_t spa = hdr->b_spa; + uint64_t psize = HDR_GET_PSIZE(hdr); + uint64_t lsize = HDR_GET_LSIZE(hdr); + boolean_t protected = HDR_PROTECTED(hdr); + enum zio_compress compress = arc_hdr_get_compress(hdr); ++ uint8_t complevel = hdr->b_complevel; + arc_buf_contents_t type = arc_buf_type(hdr); ++ boolean_t single_buf_l2writing = (hdr->b_l1hdr.b_buf == buf && ++ ARC_BUF_LAST(buf) && HDR_L2_WRITING(hdr)); + + if (ARC_BUF_SHARED(buf) && !ARC_BUF_COMPRESSED(buf)) { + ASSERT3P(hdr->b_l1hdr.b_buf, !=, buf); +@@ -6689,48 +6695,51 @@ arc_release(arc_buf_t *buf, const void *tag) + * Pull the buffer off of this hdr and find the last buffer + * in the hdr's buffer list. + */ +- VERIFY3S(remove_reference(hdr, tag), >, 0); + arc_buf_t *lastbuf = arc_buf_remove(hdr, buf); +- ASSERT3P(lastbuf, !=, NULL); ++ EQUIV(single_buf_l2writing, lastbuf == NULL); + + /* + * If the current arc_buf_t and the hdr are sharing their data + * buffer, then we must stop sharing that block. + */ +- if (ARC_BUF_SHARED(buf)) { +- ASSERT(!arc_buf_is_shared(lastbuf)); ++ if (!single_buf_l2writing) { ++ if (ARC_BUF_SHARED(buf)) { ++ ASSERT(!arc_buf_is_shared(lastbuf)); + +- /* +- * First, sever the block sharing relationship between +- * buf and the arc_buf_hdr_t. +- */ +- arc_unshare_buf(hdr, buf); ++ /* ++ * First, sever the block sharing relationship ++ * between buf and the arc_buf_hdr_t. ++ */ ++ arc_unshare_buf(hdr, buf); + +- /* +- * Now we need to recreate the hdr's b_pabd. Since we +- * have lastbuf handy, we try to share with it, but if +- * we can't then we allocate a new b_pabd and copy the +- * data from buf into it. +- */ +- if (arc_can_share(hdr, lastbuf)) { +- arc_share_buf(hdr, lastbuf); +- } else { +- arc_hdr_alloc_abd(hdr, 0); +- abd_copy_from_buf(hdr->b_l1hdr.b_pabd, +- buf->b_data, psize); ++ /* ++ * Now we need to recreate the hdr's b_pabd. ++ * Since we have lastbuf handy, we try to share ++ * with it, but if we can't then we allocate a ++ * new b_pabd and copy the data from buf into it ++ */ ++ if (arc_can_share(hdr, lastbuf)) { ++ arc_share_buf(hdr, lastbuf); ++ } else { ++ arc_hdr_alloc_abd(hdr, 0); ++ abd_copy_from_buf(hdr->b_l1hdr.b_pabd, ++ buf->b_data, psize); ++ } ++ } else if (HDR_SHARED_DATA(hdr)) { ++ /* ++ * Uncompressed shared buffers are always at the ++ * end of the list. Compressed buffers don't ++ * have the same requirements. This makes it ++ * hard to simply assert that the lastbuf is ++ * shared so we rely on the hdr's compression ++ * flags to determine if we have a compressed, ++ * shared buffer. ++ */ ++ ASSERT(arc_buf_is_shared(lastbuf) || ++ arc_hdr_get_compress(hdr) != ++ ZIO_COMPRESS_OFF); ++ ASSERT(!arc_buf_is_shared(buf)); + } +- } else if (HDR_SHARED_DATA(hdr)) { +- /* +- * Uncompressed shared buffers are always at the end +- * of the list. Compressed buffers don't have the +- * same requirements. This makes it hard to +- * simply assert that the lastbuf is shared so +- * we rely on the hdr's compression flags to determine +- * if we have a compressed, shared buffer. +- */ +- ASSERT(arc_buf_is_shared(lastbuf) || +- arc_hdr_get_compress(hdr) != ZIO_COMPRESS_OFF); +- ASSERT(!arc_buf_is_shared(buf)); + } + + ASSERT(hdr->b_l1hdr.b_pabd != NULL || HDR_HAS_RABD(hdr)); +@@ -6745,10 +6754,15 @@ arc_release(arc_buf_t *buf, const void *tag) + if (!arc_hdr_has_uncompressed_buf(hdr)) + arc_cksum_free(hdr); + ++ if (single_buf_l2writing) ++ VERIFY3S(remove_reference(hdr, tag), ==, 0); ++ else ++ VERIFY3S(remove_reference(hdr, tag), >, 0); ++ + mutex_exit(hash_lock); + +- nhdr = arc_hdr_alloc(spa, psize, lsize, protected, +- compress, hdr->b_complevel, type); ++ nhdr = arc_hdr_alloc(spa, psize, lsize, protected, compress, ++ complevel, type); + ASSERT0P(nhdr->b_l1hdr.b_buf); + ASSERT0(zfs_refcount_count(&nhdr->b_l1hdr.b_refcnt)); + VERIFY3U(nhdr->b_type, ==, type); diff --git a/debian/patches/0020-arc-fix-race-between-arc_release-and-arc_read_done.patch b/debian/patches/0020-arc-fix-race-between-arc_release-and-arc_read_done.patch new file mode 100644 index 000000000..02e9361f0 --- /dev/null +++ b/debian/patches/0020-arc-fix-race-between-arc_release-and-arc_read_done.patch @@ -0,0 +1,179 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andriy Tkachuk +Date: Thu, 6 Aug 2026 22:13:46 +0100 +Subject: [PATCH] arc: fix race between arc_release() and arc_read_done() + +Consider the following scenario: + +1. arc_release() is called on hdr with one buf, but which has + IO_IN_PROGRESS (reading more raw data in encypted pool while + keeping decrypted data in buf). +2. arc_release() moves hdr to anon state and discards its identity. +3. arc_read_done() is called, adds the 2nd buf to hdr, increasing + b_refcnt to 2. + +Now we have hdr in anon state with two bufs and without identity. + +Or here's a racing scenario: + +1. arc_release() checked that hdr is not in anon state, but before + taking hash_lock +2. arc_read_done() takes hash_lock, moves hdr to anon state, in + case of an error. +3. arc_release() now takes hash_lock and tries to move hdr to anon + state again. + +Solution: 1) in arc_read_done(), don't put hdr to anon state on an +error immediately but allow remove_reference() do that when b_ref +becomes 0; 2) in arc_release(), handle IO_IN_PROGRESS case similarly +to case with several buffers without putting the hdr to anon state; +3) in remove_reference(), don't put hdr with IO_ERROR flag to the +eviction list, free it immediately, similar to uncached state. + +Note: hdr with IO_ERROR flag can now linger longer around before +it gets released at remove_reference(), so we make some additional +changes to facilitate that: 1) free b_radb or else b_pabd on error +at arc_read_done(); 2) set IO_ERROR only if no valid data remains +at hdr worth to cache. This allows next arc_read() calls on the +hdr to avoid hit branch (HDR_HAS_RABD), returning bad data, but +try to re-read it instead; as well as for arc_write() to not trip +on ASSERT(!HDR_IO_ERROR(hdr)). + +Reviewed-by: Alexander Motin +Reviewed-by: Brian Behlendorf +Signed-off-by: Andriy Tkachuk +Co-authored-by: Alexander Motin +Illumos-issue: https://www.illumos.org/issues/14003 +Related-issue: #13479 +Closes #18897 +(cherry picked from commit 216de07d89ffe0b6faadc6c090b0527a198a7ccb) +Signed-off-by: Andriy Tkachuk +(cherry picked from commit eb011f2aff3a37d69118cd5f3db184b80344940d) +Signed-off-by: Stoiko Ivanov +--- + module/zfs/arc.c | 54 ++++++++++++++++++++++++++++++++++++------------ + 1 file changed, 41 insertions(+), 13 deletions(-) + +diff --git a/module/zfs/arc.c b/module/zfs/arc.c +index 5393c018ee82ba73eb53414683350abc7ba45c74..24b0501d1eeba10b25cc2b61395468e12895b05a 100644 +--- a/module/zfs/arc.c ++++ b/module/zfs/arc.c +@@ -2361,7 +2361,8 @@ remove_reference(arc_buf_hdr_t *hdr, const void *tag) + arc_hdr_destroy(hdr); + return (0); + } +- if (state == arc_uncached && !HDR_PREFETCH(hdr)) { ++ if ((state == arc_uncached && !HDR_PREFETCH(hdr)) || ++ HDR_IO_ERROR(hdr)) { + arc_change_state(arc_anon, hdr); + arc_hdr_destroy(hdr); + return (0); +@@ -3728,7 +3729,7 @@ arc_hdr_destroy(arc_buf_hdr_t *hdr) + + VERIFY0P(hdr->b_hash_next); + if (HDR_HAS_L1HDR(hdr)) { +- ASSERT(!multilist_link_active(&hdr->b_l1hdr.b_arc_node)); ++ VERIFY(!multilist_link_active(&hdr->b_l1hdr.b_arc_node)); + ASSERT0P(hdr->b_l1hdr.b_acb); + #ifdef ZFS_DEBUG + ASSERT0P(hdr->b_l1hdr.b_freeze_cksum); +@@ -5607,6 +5608,7 @@ arc_read_done(zio_t *zio) + kmutex_t *hash_lock = NULL; + arc_callback_t *callback_list; + arc_callback_t *acb; ++ boolean_t read_error = (zio->io_error != 0); + + /* + * The hdr was inserted into hash-table and removed from lists +@@ -5760,11 +5762,29 @@ arc_read_done(zio_t *zio) + if (zio->io_error == 0) { + arc_hdr_verify(hdr, zio->io_bp); + } else { +- arc_hdr_set_flags(hdr, ARC_FLAG_IO_ERROR); +- if (hdr->b_l1hdr.b_state != arc_anon) +- arc_change_state(arc_anon, hdr); +- if (HDR_IN_HASH_TABLE(hdr)) +- buf_hash_remove(hdr); ++ /* ++ * A failed *physical* read leaves the raw/encrypted buffer it ++ * was filling full of garbage. If a valid decrypted b_pabd ++ * survives (the raw re-read case) the header stays cached, and ++ * a later raw read would be served this garbage as a hit — ++ * arc_read()'s hit test honors HDR_HAS_RABD, not IO_ERROR, and ++ * arc_cksum_verify() skips IO_ERROR headers. We should free it ++ * so that representation misses and re-fetches from disk. ++ * ++ * Gate on read_error: a *valid* b_rabd whose consumer merely ++ * failed to decrypt it (keys not loaded) also reaches here, ++ * but the read succeeded and the data should be kept. ++ */ ++ if (read_error) { ++ if (HDR_HAS_RABD(hdr)) ++ arc_hdr_free_abd(hdr, B_TRUE); ++ else if (hdr->b_l1hdr.b_pabd != NULL) ++ arc_hdr_free_abd(hdr, B_FALSE); ++ } ++ /* Flag for teardown only if nothing valid remains. */ ++ if (hdr->b_l1hdr.b_pabd == NULL && !HDR_HAS_RABD(hdr) && ++ hdr->b_l1hdr.b_buf == NULL) ++ arc_hdr_set_flags(hdr, ARC_FLAG_IO_ERROR); + } + + arc_hdr_clear_flags(hdr, ARC_FLAG_IO_IN_PROGRESS); +@@ -6672,9 +6692,13 @@ arc_release(arc_buf_t *buf, const void *tag) + * Do we have more than one buf? Or L2_WRITING with unshared data? + * Single-buf L2_WRITING with shared data can reuse the header since + * L2ARC uses its own transformed copy. ++ * Or I/O is in progress (a raw/encrypted read filling b_rabd while ++ * our decrypted buf backs b_pabd) and arc_read_done() is yet to add ++ * more bufs? + */ + if (hdr->b_l1hdr.b_buf != buf || !ARC_BUF_LAST(buf) || +- (HDR_L2_WRITING(hdr) && !ARC_BUF_SHARED(buf))) { ++ (HDR_L2_WRITING(hdr) && !ARC_BUF_SHARED(buf)) || ++ HDR_IO_IN_PROGRESS(hdr)) { + arc_buf_hdr_t *nhdr; + uint64_t spa = hdr->b_spa; + uint64_t psize = HDR_GET_PSIZE(hdr); +@@ -6683,8 +6707,10 @@ arc_release(arc_buf_t *buf, const void *tag) + enum zio_compress compress = arc_hdr_get_compress(hdr); + uint8_t complevel = hdr->b_complevel; + arc_buf_contents_t type = arc_buf_type(hdr); +- boolean_t single_buf_l2writing = (hdr->b_l1hdr.b_buf == buf && +- ARC_BUF_LAST(buf) && HDR_L2_WRITING(hdr)); ++ boolean_t single_buf = (hdr->b_l1hdr.b_buf == buf && ++ ARC_BUF_LAST(buf)); ++ boolean_t single_buf_l2writing = (single_buf && ++ HDR_L2_WRITING(hdr) && !HDR_IO_IN_PROGRESS(hdr)); + + if (ARC_BUF_SHARED(buf) && !ARC_BUF_COMPRESSED(buf)) { + ASSERT3P(hdr->b_l1hdr.b_buf, !=, buf); +@@ -6696,7 +6722,7 @@ arc_release(arc_buf_t *buf, const void *tag) + * in the hdr's buffer list. + */ + arc_buf_t *lastbuf = arc_buf_remove(hdr, buf); +- EQUIV(single_buf_l2writing, lastbuf == NULL); ++ EQUIV(single_buf, lastbuf == NULL); + + /* + * If the current arc_buf_t and the hdr are sharing their data +@@ -6704,7 +6730,8 @@ arc_release(arc_buf_t *buf, const void *tag) + */ + if (!single_buf_l2writing) { + if (ARC_BUF_SHARED(buf)) { +- ASSERT(!arc_buf_is_shared(lastbuf)); ++ ASSERT(single_buf || ++ !arc_buf_is_shared(lastbuf)); + + /* + * First, sever the block sharing relationship +@@ -6718,7 +6745,8 @@ arc_release(arc_buf_t *buf, const void *tag) + * with it, but if we can't then we allocate a + * new b_pabd and copy the data from buf into it + */ +- if (arc_can_share(hdr, lastbuf)) { ++ if (!single_buf && ++ arc_can_share(hdr, lastbuf)) { + arc_share_buf(hdr, lastbuf); + } else { + arc_hdr_alloc_abd(hdr, 0); diff --git a/debian/patches/series b/debian/patches/series index 0ef82e7bb..c594842fc 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -8,3 +8,13 @@ 0008-zpool-status-tighten-bounds-for-noalloc-stat-availab.patch 0009-libzfs-scrub-only-include-start-and-end-nv-pairs-if-.patch 0010-Add-no-preserve-encryption-flag.patch +0011-Inherit-the-project-ID-for-every-object-type.patch +0012-Allow-renames-within-a-single-directory-under-projec.patch +0013-ZTS-check-project-ID-inheritance-and-renames-within-.patch +0014-arc-harness-buf_hdr-s-anon-state-invariant-checks.patch +0015-rpm-Add-support-for-building-akmod-packages.patch +0016-libzfs_core-block-delivery-of-SIGUSR1-in-send_worker.patch +0017-kmodtool-use-kABI-baseline-naming-on-RHEL-to-prevent.patch +0018-L2ARC-Reorder-header-destruction-for-in-flight-L2-wr.patch +0019-L2ARC-Preserve-L2HDR-in-arc_release-for-in-flight-wr.patch +0020-arc-fix-race-between-arc_release-and-arc_read_done.patch -- 2.47.3