* [PATCH RFC 01/17] lib/crc: add crc32c_flip_range() for incremental CRC update
From: Baokun Li @ 2026-05-08 12:15 UTC (permalink / raw)
To: linux-ext4
Cc: linux-crypto, ebiggers, ardb, tytso, adilger.kernel, jack,
yi.zhang, ojaswin, ritesh.list, Baokun Li
In-Reply-To: <20260508121539.4174601-1-libaokun@linux.alibaba.com>
When a contiguous range of bits in a buffer is flipped, the CRC32c
checksum can be updated incrementally without re-scanning the entire
buffer, by exploiting the linearity of CRCs over GF(2):
New_CRC = Old_CRC ^ CRC(flip_mask << trailing_bits)
Introduce crc32c_flip_range() which computes this delta using
precomputed GF(2) shift matrices and nibble-indexed lookup tables.
The implementation decomposes nbits and trailing_bits into
power-of-2 components and combines them via the CRC concatenation
property:
CRC(A || B) = shift(CRC(A), len(B)) ^ CRC(B)
This gives O(log N) complexity with only ~9.8KB of static tables
(fits in L1 cache). The current maximum supported buffer size is
64KB (INCR_MAX_ORDER = 19, i.e. 2^19 bits = 524288 bits = 64KB).
This is useful for filesystems like ext4, where bitmap updates
involve flipping a contiguous range of bits, and recalculating
the full CRC after every update is wasteful.
Benchmark results on Intel Xeon (Ice Lake) with CRC32c hardware
acceleration:
bitmap: 1024 2048 4096 8192 16384 32768 65536
flip(ns): 48 53 57 63 68 73 78
full(ns): 45 88 182 357 709 1421 2853
speedup: 0.9x 1.6x 3.1x 5.6x 10.3x 19.3x 36.3x
Signed-off-by: Baokun Li <libaokun@linux.alibaba.com>
---
include/linux/crc32.h | 25 ++++++
lib/crc/.gitignore | 2 +
lib/crc/Makefile | 13 ++-
lib/crc/crc32c-incr.c | 140 +++++++++++++++++++++++++++++++
lib/crc/gen_crc32c_incr_table.c | 141 ++++++++++++++++++++++++++++++++
5 files changed, 318 insertions(+), 3 deletions(-)
create mode 100644 lib/crc/crc32c-incr.c
create mode 100644 lib/crc/gen_crc32c_incr_table.c
diff --git a/include/linux/crc32.h b/include/linux/crc32.h
index da78b215ff2e..034f73f0f5dc 100644
--- a/include/linux/crc32.h
+++ b/include/linux/crc32.h
@@ -81,6 +81,31 @@ u32 crc32_be(u32 crc, const void *p, size_t len);
*/
u32 crc32c(u32 crc, const void *p, size_t len);
+/**
+ * crc32c_flip_range - Update CRC32c after flipping a range of bits
+ * @old_crc: Existing CRC32c value of the buffer (pre-flip).
+ * @total_bits: Total size of the buffer in bits (e.g., 524288 for 64KB).
+ * @bit_off: Starting bit offset of the modified range.
+ * @nbits: Length of the flipped bit sequence.
+ *
+ * This function calculates the new CRC32c value when a contiguous range of
+ * bits is flipped (XORed with 1s) without re-scanning the entire buffer.
+ * It leverages the linearity of CRCs in Galois Field GF(2):
+ *
+ * New_CRC = Old_CRC ^ CRC(Mask_of_Ones << Trailing_Bits)
+ *
+ * The complexity is O(log nbits + log trailing_bits), making it
+ * significantly faster than recomputing the CRC for large buffers.
+ *
+ * Note: @total_bits must not exceed 524288 (2^19 bits = 64KB). Callers
+ * must ensure that @bit_off + @nbits <= @total_bits. Behavior is
+ * undefined if these constraints are violated.
+ *
+ * Return: The updated CRC32c value.
+ */
+u32 crc32c_flip_range(u32 old_crc, u32 total_bits,
+ u32 bit_off, u32 nbits);
+
/*
* crc32_optimizations() returns flags that indicate which CRC32 library
* functions are using architecture-specific optimizations. Unlike
diff --git a/lib/crc/.gitignore b/lib/crc/.gitignore
index a9e48103c9fb..4e2b9524426d 100644
--- a/lib/crc/.gitignore
+++ b/lib/crc/.gitignore
@@ -1,5 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
/crc32table.h
+/crc32c-incr-table.h
/crc64table.h
/gen_crc32table
+/gen_crc32c_incr_table
/gen_crc64table
diff --git a/lib/crc/Makefile b/lib/crc/Makefile
index ff213590e4e3..2c255ac029d0 100644
--- a/lib/crc/Makefile
+++ b/lib/crc/Makefile
@@ -21,7 +21,7 @@ crc-t10dif-$(CONFIG_X86) += x86/crc16-msb-pclmul.o
endif
obj-$(CONFIG_CRC32) += crc32.o
-crc32-y := crc32-main.o
+crc32-y := crc32-main.o crc32c-incr.o
ifeq ($(CONFIG_CRC32_ARCH),y)
CFLAGS_crc32-main.o += -I$(src)/$(SRCARCH)
crc32-$(CONFIG_ARM) += arm/crc32-core.o
@@ -49,20 +49,27 @@ endif # CONFIG_CRC64_ARCH
obj-y += tests/
-hostprogs := gen_crc32table gen_crc64table
-clean-files := crc32table.h crc64table.h
+hostprogs := gen_crc32table gen_crc32c_incr_table gen_crc64table
+clean-files := crc32table.h crc32c-incr-table.h crc64table.h
$(obj)/crc32-main.o: $(obj)/crc32table.h
+$(obj)/crc32c-incr.o: $(obj)/crc32c-incr-table.h
$(obj)/crc64-main.o: $(obj)/crc64table.h
quiet_cmd_crc32 = GEN $@
cmd_crc32 = $< > $@
+quiet_cmd_crc32c_incr = GEN $@
+ cmd_crc32c_incr = $< > $@
+
quiet_cmd_crc64 = GEN $@
cmd_crc64 = $< > $@
$(obj)/crc32table.h: $(obj)/gen_crc32table
$(call cmd,crc32)
+$(obj)/crc32c-incr-table.h: $(obj)/gen_crc32c_incr_table
+ $(call cmd,crc32c_incr)
+
$(obj)/crc64table.h: $(obj)/gen_crc64table
$(call cmd,crc64)
diff --git a/lib/crc/crc32c-incr.c b/lib/crc/crc32c-incr.c
new file mode 100644
index 000000000000..b6258231cc0d
--- /dev/null
+++ b/lib/crc/crc32c-incr.c
@@ -0,0 +1,140 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * GF(2) matrix-based CRC32c incremental update.
+ *
+ * When a contiguous range of bits is flipped, the new CRC can be
+ * derived from the old one without re-scanning the buffer:
+ * New_CRC = Old_CRC ^ CRC(flip_mask << trailing_bits)
+ *
+ * The delta CRC is computed by decomposing num_bits and trailing_bits
+ * into power-of-2 components and combining them via the CRC
+ * concatenation property, giving O(log N) complexity.
+ *
+ * Memory usage: ~9.8KB
+ * - crc32c_incr_nibble_table: 19 * 8 * 16 * 4 = 9728 bytes
+ * - crc32c_incr_ones_lookup: 20 * 4 = 80 bytes
+ *
+ * Tables are generated at compile time by gen_crc32c_incr_table.
+ * INCR_MAX_ORDER 19 supports up to 64KB buffers (2^19 bits).
+ *
+ * Copyright (C) 2026 Alibaba Inc.
+ */
+
+#include <linux/bitops.h>
+#include <linux/bug.h>
+#include <linux/export.h>
+#include <linux/crc32.h>
+
+#include "crc32c-incr-table.h"
+
+#define INCR_MAX_ORDER 19
+
+/**
+ * gf2_xform - Multiply a CRC state vector by a GF(2) shift matrix
+ * @order: Selects the precomputed matrix M^(2^order).
+ * @v: The 32-bit CRC state vector.
+ *
+ * Computes v * M^(2^order) using nibble (4-bit) indexed tables,
+ * reducing the operation from 32 bit-level iterations to 8 lookups.
+ */
+static inline u32 gf2_xform(int order, u32 v)
+{
+ const u32 (*tab)[16] = crc32c_incr_nibble_table[order];
+
+ return tab[0][v & 0xf] ^
+ tab[1][(v >> 4) & 0xf] ^
+ tab[2][(v >> 8) & 0xf] ^
+ tab[3][(v >> 12) & 0xf] ^
+ tab[4][(v >> 16) & 0xf] ^
+ tab[5][(v >> 20) & 0xf] ^
+ tab[6][(v >> 24) & 0xf] ^
+ tab[7][(v >> 28) & 0xf];
+}
+
+/**
+ * crc32c_incr_get_ones_delta - Compute CRC of an all-ones bit sequence
+ * @num_bits: Length of the all-ones sequence.
+ *
+ * Returns CRC(0, [111...1] of length num_bits). Decomposes num_bits
+ * into powers of 2 (MSB-first) and combines using:
+ * CRC(A || B) = shift(CRC(A), len(B)) ^ CRC(B)
+ *
+ * This requires only (popcount - 1) gf2_xform calls, each doing
+ * 8 table lookups.
+ *
+ * Caller must ensure num_bits <= (1UL << INCR_MAX_ORDER).
+ */
+static u32 crc32c_incr_get_ones_delta(size_t num_bits)
+{
+ u32 delta;
+ int n;
+
+ if (!num_bits)
+ return 0;
+
+ /* Initialize with the highest power-of-2 block */
+ n = __fls(num_bits);
+ delta = crc32c_incr_ones_lookup[n];
+ num_bits ^= (1UL << n);
+
+ /* Concatenate remaining blocks from high to low */
+ while (num_bits) {
+ n = __fls(num_bits);
+ delta = gf2_xform(n, delta);
+ delta ^= crc32c_incr_ones_lookup[n];
+ num_bits ^= (1UL << n);
+ }
+ return delta;
+}
+
+/**
+ * gf2_shift_crc - Shift a CRC state by @trailing_bits zero-bit positions
+ * @crc: The CRC state vector.
+ * @trailing_bits: Number of zero bits to shift through.
+ *
+ * Equivalent to appending @trailing_bits zero bits to the data stream
+ * and continuing the CRC computation. Decomposes trailing_bits into
+ * powers of 2 and applies the corresponding precomputed matrices.
+ */
+static u32 gf2_shift_crc(u32 crc, size_t trailing_bits)
+{
+ int n;
+
+ for (n = 0; trailing_bits > 0 && n < INCR_MAX_ORDER; n++) {
+ if (trailing_bits & 1)
+ crc = gf2_xform(n, crc);
+ trailing_bits >>= 1;
+ }
+ return crc;
+}
+
+/* See full kernel-doc in include/linux/crc32.h */
+u32 crc32c_flip_range(u32 old_crc, u32 total_bits,
+ u32 bit_off, u32 nbits)
+{
+ u32 delta, trailing_bits;
+
+ if (!nbits)
+ return old_crc;
+
+ /*
+ * total_bits must not exceed 2^INCR_MAX_ORDER bits (64KB).
+ * bit_off + nbits must not exceed total_bits.
+ */
+ if (WARN_ON_ONCE(total_bits > (1UL << INCR_MAX_ORDER)))
+ return old_crc;
+ if (WARN_ON_ONCE(bit_off + nbits > total_bits))
+ return old_crc;
+
+ trailing_bits = total_bits - (bit_off + nbits);
+
+ /* 1. Calculate CRC of the flip-mask (all 1s of length nbits) */
+ delta = crc32c_incr_get_ones_delta(nbits);
+
+ /* 2. Shift the mask-CRC to the correct bit position */
+ delta = gf2_shift_crc(delta, trailing_bits);
+
+ /* 3. Apply the delta to the existing CRC */
+ return old_crc ^ delta;
+}
+EXPORT_SYMBOL(crc32c_flip_range);
diff --git a/lib/crc/gen_crc32c_incr_table.c b/lib/crc/gen_crc32c_incr_table.c
new file mode 100644
index 000000000000..f906506282cc
--- /dev/null
+++ b/lib/crc/gen_crc32c_incr_table.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Generate GF(2) nibble-based lookup tables for incremental CRC32c updates.
+ * MAX_ORDER 19 supports up to 64KB buffers (2^19 bits = 524288 bits).
+ *
+ * Instead of storing raw 32x32 bit matrices (32 rows per order),
+ * we precompute nibble (4-bit) indexed tables. This reduces gf2_xform
+ * to 8 table lookups instead of 32 branchless mask-and-XOR iterations.
+ *
+ * Memory layout:
+ * - crc32c_incr_nibble_table[19][8][16]: 19 * 8 * 16 * 4 = 9728 bytes
+ * - crc32c_incr_ones_lookup[20]: 20 * 4 = 80 bytes
+ * Total: ~9.8KB (fits comfortably in L1 cache)
+ *
+ * Copyright (C) 2026 Alibaba Inc.
+ */
+
+#include <stdio.h>
+#include <inttypes.h>
+
+#include "../../include/linux/crc32poly.h"
+
+#define CRC32C_INCR_MAX_ORDER 19
+#define NIBBLES_PER_U32 8
+
+static uint32_t bit_matrix[CRC32C_INCR_MAX_ORDER][32];
+static uint32_t nibble_table[CRC32C_INCR_MAX_ORDER][NIBBLES_PER_U32][16];
+static uint32_t ones_lookup[CRC32C_INCR_MAX_ORDER + 1];
+
+static void crc32c_incr_init(void)
+{
+ int n, i, k, v;
+
+ /*
+ * Step 1: Build the order-0 matrix M, where M[i] is the CRC
+ * state after shifting basis vector e_i by one bit position.
+ */
+ for (i = 0; i < 32; i++) {
+ uint32_t r = 1U << i;
+
+ bit_matrix[0][i] = (r & 1) ?
+ (r >> 1) ^ CRC32C_POLY_LE : (r >> 1);
+ }
+
+ /* Step 2: M^(2^n) = (M^(2^(n-1)))^2 via matrix squaring */
+ for (n = 1; n < CRC32C_INCR_MAX_ORDER; n++) {
+ for (i = 0; i < 32; i++) {
+ uint32_t r = bit_matrix[n - 1][i];
+ uint32_t res = 0;
+
+ for (k = 0; k < 32; k++) {
+ if (r & (1U << k))
+ res ^= bit_matrix[n - 1][k];
+ }
+ bit_matrix[n][i] = res;
+ }
+ }
+
+ /* Step 3: Convert bit matrices to nibble-indexed lookup tables */
+ for (n = 0; n < CRC32C_INCR_MAX_ORDER; n++) {
+ for (i = 0; i < NIBBLES_PER_U32; i++) {
+ nibble_table[n][i][0] = 0;
+ for (v = 1; v < 16; v++) {
+ uint32_t res = 0;
+
+ for (k = 0; k < 4; k++) {
+ if (v & (1 << k))
+ res ^= bit_matrix[n][i * 4 + k];
+ }
+ nibble_table[n][i][v] = res;
+ }
+ }
+ }
+
+ /*
+ * Step 4: ones_lookup[n] = CRC(0, all-ones of 2^n bits).
+ * Uses CRC(A||B) = shift(CRC(A), len(B)) ^ CRC(B) to double
+ * the length at each step. ones_lookup[0] = CRC of a single
+ * 1-bit, which equals the generator polynomial.
+ */
+ ones_lookup[0] = CRC32C_POLY_LE;
+
+ for (n = 1; n <= CRC32C_INCR_MAX_ORDER; n++) {
+ uint32_t low = ones_lookup[n - 1];
+ uint32_t high = 0;
+
+ for (k = 0; k < 32; k++) {
+ if (low & (1U << k))
+ high ^= bit_matrix[n - 1][k];
+ }
+ ones_lookup[n] = low ^ high;
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int n, i, v;
+
+ crc32c_incr_init();
+
+ printf("/* this file is generated - do not edit */\n\n");
+
+ printf("static const u32 crc32c_incr_nibble_table[%d][%d][16] = {\n",
+ CRC32C_INCR_MAX_ORDER, NIBBLES_PER_U32);
+ for (n = 0; n < CRC32C_INCR_MAX_ORDER; n++) {
+ printf("\t{\n");
+ for (i = 0; i < NIBBLES_PER_U32; i++) {
+ printf("\t\t{\n");
+ for (v = 0; v < 16; v += 4) {
+ printf("\t\t\t0x%08x, 0x%08x, 0x%08x, 0x%08x,\n",
+ nibble_table[n][i][v],
+ nibble_table[n][i][v + 1],
+ nibble_table[n][i][v + 2],
+ nibble_table[n][i][v + 3]);
+ }
+ printf("\t\t},\n");
+ }
+ printf("\t},\n");
+ }
+ printf("};\n\n");
+
+ printf("static const u32 crc32c_incr_ones_lookup[%d] = {\n",
+ CRC32C_INCR_MAX_ORDER + 1);
+ for (n = 0; n <= CRC32C_INCR_MAX_ORDER; n += 4) {
+ int remaining = CRC32C_INCR_MAX_ORDER + 1 - n;
+
+ if (remaining >= 4) {
+ printf("\t0x%08x, 0x%08x, 0x%08x, 0x%08x,\n",
+ ones_lookup[n], ones_lookup[n + 1],
+ ones_lookup[n + 2], ones_lookup[n + 3]);
+ } else {
+ printf("\t");
+ for (i = 0; i < remaining; i++)
+ printf("0x%08x, ", ones_lookup[n + i]);
+ printf("\n");
+ }
+ }
+ printf("};\n");
+
+ return 0;
+}
--
2.43.7
^ permalink raw reply related
* [PATCH RFC 02/17] lib/crc: crc_kunit: add kunit test for crc32c_flip_range()
From: Baokun Li @ 2026-05-08 12:15 UTC (permalink / raw)
To: linux-ext4
Cc: linux-crypto, ebiggers, ardb, tytso, adilger.kernel, jack,
yi.zhang, ojaswin, ritesh.list, Baokun Li
In-Reply-To: <20260508121539.4174601-1-libaokun@linux.alibaba.com>
Add kunit tests for crc32c_flip_range(), validating correctness
against naive full-buffer CRC recomputation. All tests use a 64KB
buffer and a non-zero CRC seed to match real-world usage (e.g. ext4
metadata checksums):
- ones_lookup[0] single-bit verification.
- num_bits=0 no-op, first/last byte, full 64KB flip.
- Random single-bit flips (100 iterations).
- Random multi-bit contiguous ranges (100 iterations).
Signed-off-by: Baokun Li <libaokun@linux.alibaba.com>
---
lib/crc/tests/crc_kunit.c | 85 +++++++++++++++++++++++++++++++++++++++
1 file changed, 85 insertions(+)
diff --git a/lib/crc/tests/crc_kunit.c b/lib/crc/tests/crc_kunit.c
index 9428cd913625..46f9df5b58e4 100644
--- a/lib/crc/tests/crc_kunit.c
+++ b/lib/crc/tests/crc_kunit.c
@@ -470,6 +470,90 @@ static void crc64_nvme_benchmark(struct kunit *test)
}
#endif /* CONFIG_CRC64 */
+/*
+ * Test crc32c_flip_range() against naive full-buffer CRC recomputation.
+ * All tests use a 64KB buffer (2^19 bits = INCR_MAX_ORDER limit)
+ * and a non-zero seed to match real-world usage (e.g. ext4 checksums).
+ */
+static void crc32c_flip_range_test(struct kunit *test)
+{
+ size_t buflen = 65536;
+ size_t total_bits = buflen * 8;
+ u32 seed = 0x12345678;
+ u32 expected, flip_crc;
+ size_t start, num_bits, b, pos;
+ u8 *buf;
+ int i;
+
+ buf = kunit_kmalloc(test, buflen, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, buf);
+
+ /* Test 1: Single bit at bit 0 (verifies ones_lookup[0]) */
+ buf[0] = 0x00;
+ expected = crc32c(seed, buf, 1);
+ buf[0] = 0x01;
+ flip_crc = crc32c_flip_range(expected, 8, 0, 1);
+ expected = crc32c(seed, buf, 1);
+ KUNIT_ASSERT_EQ_MSG(test, expected, flip_crc, "Single bit at bit 0");
+
+ /* Test 2: num_bits=0 should be a no-op */
+ memset(buf, 0, buflen);
+ expected = crc32c(seed, buf, buflen);
+ flip_crc = crc32c_flip_range(expected, total_bits, 0, 0);
+ KUNIT_ASSERT_EQ_MSG(test, expected, flip_crc,
+ "num_bits=0: expected=0x%08x got=0x%08x",
+ expected, flip_crc);
+
+ /* Test 3: Boundary flips - first byte, last byte, all bits */
+ buf[0] = 0xFF;
+ flip_crc = crc32c_flip_range(expected, total_bits, 0, 8);
+ expected = crc32c(seed, buf, buflen);
+ KUNIT_ASSERT_EQ_MSG(test, expected, flip_crc, "Flip first byte");
+
+ buf[buflen - 1] = 0xFF;
+ flip_crc = crc32c_flip_range(expected, total_bits, (buflen - 1) * 8, 8);
+ expected = crc32c(seed, buf, buflen);
+ KUNIT_ASSERT_EQ_MSG(test, expected, flip_crc, "Flip last byte");
+
+ memset(buf, 0, buflen);
+ expected = crc32c(seed, buf, buflen);
+ memset(buf, 0xFF, buflen);
+ flip_crc = crc32c_flip_range(expected, total_bits, 0, total_bits);
+ expected = crc32c(seed, buf, buflen);
+ KUNIT_ASSERT_EQ_MSG(test, expected, flip_crc, "Flip all 64KB bits");
+
+ /* Test 4: Random single-bit flips (100 iterations) */
+ memset(buf, 0, buflen);
+ expected = crc32c(seed, buf, buflen);
+ for (i = 0; i < 100; i++) {
+ start = rand32() % total_bits;
+ buf[start / 8] ^= (1 << (start % 8));
+
+ flip_crc = crc32c_flip_range(expected, total_bits, start, 1);
+ expected = crc32c(seed, buf, buflen);
+ KUNIT_ASSERT_EQ_MSG(test, expected, flip_crc,
+ "Single bit at %zu: expected=0x%08x got=0x%08x",
+ start, expected, flip_crc);
+ }
+
+ /* Test 5: Random multi-bit ranges (100 iterations) */
+ for (i = 0; i < 100; i++) {
+ num_bits = (rand32() % (total_bits - 1)) + 1;
+ start = rand32() % (total_bits - num_bits + 1);
+ for (b = 0; b < num_bits; b++) {
+ pos = start + b;
+ buf[pos / 8] ^= (1 << (pos % 8));
+ }
+
+ flip_crc = crc32c_flip_range(expected, total_bits, start, num_bits);
+ expected = crc32c(seed, buf, buflen);
+
+ KUNIT_ASSERT_EQ_MSG(test, expected, flip_crc,
+ "Range [%zu, +%zu): expected=0x%08x got=0x%08x",
+ start, num_bits, expected, flip_crc);
+ }
+}
+
static struct kunit_case crc_test_cases[] = {
#if IS_REACHABLE(CONFIG_CRC7)
KUNIT_CASE(crc7_be_test),
@@ -490,6 +574,7 @@ static struct kunit_case crc_test_cases[] = {
KUNIT_CASE(crc32_be_benchmark),
KUNIT_CASE(crc32c_test),
KUNIT_CASE(crc32c_benchmark),
+ KUNIT_CASE(crc32c_flip_range_test),
#endif
#if IS_REACHABLE(CONFIG_CRC64)
KUNIT_CASE(crc64_be_test),
--
2.43.7
^ permalink raw reply related
* [PATCH RFC 03/17] lib/crc: crc_kunit: add benchmark for crc32c_flip_range()
From: Baokun Li @ 2026-05-08 12:15 UTC (permalink / raw)
To: linux-ext4
Cc: linux-crypto, ebiggers, ardb, tytso, adilger.kernel, jack,
yi.zhang, ojaswin, ritesh.list, Baokun Li
In-Reply-To: <20260508121539.4174601-1-libaokun@linux.alibaba.com>
Add a kunit benchmark comparing crc32c_flip_range() against full crc32c
recomputation across bitmap sizes from 1KB to 64KB. The benchmark reports
per-call latency in nanoseconds and the speedup ratio.
Sample results (x86_64, Intel(R) Xeon(R) Platinum 8331C):
bitmap=1024: flip_range=48 ns, full_crc=45 ns, speedup=0.9x
bitmap=2048: flip_range=53 ns, full_crc=88 ns, speedup=1.6x
bitmap=4096: flip_range=57 ns, full_crc=182 ns, speedup=3.1x
bitmap=8192: flip_range=63 ns, full_crc=357 ns, speedup=5.6x
bitmap=16384: flip_range=68 ns, full_crc=709 ns, speedup=10.3x
bitmap=32768: flip_range=73 ns, full_crc=1421 ns, speedup=19.3x
bitmap=65536: flip_range=78 ns, full_crc=2853 ns, speedup=36.3x
Signed-off-by: Baokun Li <libaokun@linux.alibaba.com>
---
lib/crc/tests/crc_kunit.c | 52 +++++++++++++++++++++++++++++++++++++++
1 file changed, 52 insertions(+)
diff --git a/lib/crc/tests/crc_kunit.c b/lib/crc/tests/crc_kunit.c
index 46f9df5b58e4..8e8b541b37d3 100644
--- a/lib/crc/tests/crc_kunit.c
+++ b/lib/crc/tests/crc_kunit.c
@@ -554,6 +554,57 @@ static void crc32c_flip_range_test(struct kunit *test)
}
}
+/*
+ * Benchmark crc32c_flip_range vs full crc32c recomputation
+ */
+static void crc32c_flip_range_benchmark(struct kunit *test)
+{
+ static const size_t bitmap_sizes[] = {
+ 1024, 2048, 4096, 8192, 16384, 32768, 65536,
+ };
+ size_t i, j, num_iters, buflen, total_bits;
+ volatile u32 crc;
+ u64 t_flip, t_full;
+ u8 *buf;
+
+ if (!IS_ENABLED(CONFIG_CRC_BENCHMARK))
+ kunit_skip(test, "not enabled");
+
+ buf = kunit_kzalloc(test, 65536, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, buf);
+
+ for (i = 0; i < ARRAY_SIZE(bitmap_sizes); i++) {
+ buflen = bitmap_sizes[i];
+ total_bits = buflen * 8;
+ num_iters = 10000000 / (buflen + 128);
+
+ /* Benchmark crc32c_flip_range */
+ crc = crc32c(0, buf, buflen);
+ preempt_disable();
+ t_flip = ktime_get_ns();
+ for (j = 0; j < num_iters; j++)
+ crc = crc32c_flip_range(crc, total_bits, 100, 100);
+ t_flip = ktime_get_ns() - t_flip;
+ preempt_enable();
+
+ /* Benchmark full crc32c recomputation */
+ preempt_disable();
+ t_full = ktime_get_ns();
+ for (j = 0; j < num_iters; j++)
+ crc = crc32c(0, buf, buflen);
+ t_full = ktime_get_ns() - t_full;
+ preempt_enable();
+
+ kunit_info(test,
+ "bitmap=%zu: flip_range=%llu ns, full_crc=%llu ns, speedup=%llu.%01llux\n",
+ buflen,
+ div64_u64(t_flip, num_iters),
+ div64_u64(t_full, num_iters),
+ div64_u64(t_full * 10, t_flip ? t_flip : 1) / 10,
+ div64_u64(t_full * 10, t_flip ? t_flip : 1) % 10);
+ }
+}
+
static struct kunit_case crc_test_cases[] = {
#if IS_REACHABLE(CONFIG_CRC7)
KUNIT_CASE(crc7_be_test),
@@ -575,6 +626,7 @@ static struct kunit_case crc_test_cases[] = {
KUNIT_CASE(crc32c_test),
KUNIT_CASE(crc32c_benchmark),
KUNIT_CASE(crc32c_flip_range_test),
+ KUNIT_CASE(crc32c_flip_range_benchmark),
#endif
#if IS_REACHABLE(CONFIG_CRC64)
KUNIT_CASE(crc64_be_test),
--
2.43.7
^ permalink raw reply related
* [PATCH RFC 06/17] ext4: add ext4_block_bitmap_csum_set_range() for incremental checksum update
From: Baokun Li @ 2026-05-08 12:15 UTC (permalink / raw)
To: linux-ext4
Cc: linux-crypto, ebiggers, ardb, tytso, adilger.kernel, jack,
yi.zhang, ojaswin, ritesh.list, Baokun Li
In-Reply-To: <20260508121539.4174601-1-libaokun@linux.alibaba.com>
Add a helper function ext4_block_bitmap_csum_set_range() that updates
the block bitmap checksum using crc32c_flip_range() for incremental CRC
calculation. Unlike ext4_block_bitmap_csum_set() which re-scans the
entire bitmap buffer, this function efficiently computes the CRC delta
for a range of flipped bits, avoiding the cost of a full CRC
recalculation.
Signed-off-by: Baokun Li <libaokun@linux.alibaba.com>
---
fs/ext4/bitmap.c | 24 ++++++++++++++++++++++++
fs/ext4/ext4.h | 3 +++
2 files changed, 27 insertions(+)
diff --git a/fs/ext4/bitmap.c b/fs/ext4/bitmap.c
index 46affc9e80ca..00b0a3c74859 100644
--- a/fs/ext4/bitmap.c
+++ b/fs/ext4/bitmap.c
@@ -110,3 +110,27 @@ void ext4_block_bitmap_csum_set(struct super_block *sb,
csum = ext4_chksum(sbi->s_csum_seed, (__u8 *)bh->b_data, sz);
ext4_block_bitmap_csum_store(sb, gdp, csum);
}
+
+/*
+ * Update block bitmap checksum using incremental CRC calculation.
+ *
+ * This function assumes that ALL bits in the range [offset, offset+len)
+ * have been flipped (XORed with 1). It uses crc32c_flip_range() to
+ * efficiently compute the CRC delta without re-scanning the entire bitmap.
+ * The csum_seed cancels out in the XOR delta, so it is not needed here.
+ */
+void ext4_block_bitmap_csum_set_range(struct super_block *sb,
+ struct ext4_group_desc *gdp,
+ ext4_grpblk_t offset, ext4_grpblk_t len)
+{
+ __u32 new_csum, old_csum;
+
+ if (!ext4_has_feature_metadata_csum(sb))
+ return;
+
+ old_csum = ext4_block_bitmap_csum_get(sb, gdp);
+ new_csum = crc32c_flip_range(old_csum, EXT4_CLUSTERS_PER_GROUP(sb),
+ offset, len);
+
+ ext4_block_bitmap_csum_store(sb, gdp, new_csum);
+}
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 94283a991e5c..c423a9a04047 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -2770,6 +2770,9 @@ int ext4_inode_bitmap_csum_verify(struct super_block *sb,
void ext4_block_bitmap_csum_set(struct super_block *sb,
struct ext4_group_desc *gdp,
struct buffer_head *bh);
+void ext4_block_bitmap_csum_set_range(struct super_block *sb,
+ struct ext4_group_desc *gdp,
+ ext4_grpblk_t offset, ext4_grpblk_t len);
int ext4_block_bitmap_csum_verify(struct super_block *sb,
struct ext4_group_desc *gdp,
struct buffer_head *bh);
--
2.43.7
^ permalink raw reply related
* [PATCH RFC 05/17] ext4: extract block bitmap checksum get and store helpers
From: Baokun Li @ 2026-05-08 12:15 UTC (permalink / raw)
To: linux-ext4
Cc: linux-crypto, ebiggers, ardb, tytso, adilger.kernel, jack,
yi.zhang, ojaswin, ritesh.list, Baokun Li
In-Reply-To: <20260508121539.4174601-1-libaokun@linux.alibaba.com>
Add ext4_block_bitmap_csum_get() and ext4_block_bitmap_csum_store()
helpers, and use EXT4_DESC_SIZE(sb) instead of sbi->s_desc_size for
consistency. No functional change.
Signed-off-by: Baokun Li <libaokun@linux.alibaba.com>
---
fs/ext4/bitmap.c | 31 ++++++++++++++++++++++---------
1 file changed, 22 insertions(+), 9 deletions(-)
diff --git a/fs/ext4/bitmap.c b/fs/ext4/bitmap.c
index 87760fabdd2e..46affc9e80ca 100644
--- a/fs/ext4/bitmap.c
+++ b/fs/ext4/bitmap.c
@@ -58,11 +58,29 @@ void ext4_inode_bitmap_csum_set(struct super_block *sb,
gdp->bg_inode_bitmap_csum_hi = cpu_to_le16(csum >> 16);
}
+static inline __u32 ext4_block_bitmap_csum_get(struct super_block *sb,
+ struct ext4_group_desc *gdp)
+{
+ __u32 csum = le16_to_cpu(gdp->bg_block_bitmap_csum_lo);
+
+ if (EXT4_DESC_SIZE(sb) >= EXT4_BG_BLOCK_BITMAP_CSUM_HI_END)
+ csum |= (__u32)le16_to_cpu(gdp->bg_block_bitmap_csum_hi) << 16;
+ return csum;
+}
+
+static inline void ext4_block_bitmap_csum_store(struct super_block *sb,
+ struct ext4_group_desc *gdp,
+ __u32 csum)
+{
+ gdp->bg_block_bitmap_csum_lo = cpu_to_le16(csum & 0xFFFF);
+ if (EXT4_DESC_SIZE(sb) >= EXT4_BG_BLOCK_BITMAP_CSUM_HI_END)
+ gdp->bg_block_bitmap_csum_hi = cpu_to_le16(csum >> 16);
+}
+
int ext4_block_bitmap_csum_verify(struct super_block *sb,
struct ext4_group_desc *gdp,
struct buffer_head *bh)
{
- __u32 hi;
__u32 provided, calculated;
struct ext4_sb_info *sbi = EXT4_SB(sb);
int sz = EXT4_CLUSTERS_PER_GROUP(sb) / 8;
@@ -70,12 +88,9 @@ int ext4_block_bitmap_csum_verify(struct super_block *sb,
if (!ext4_has_feature_metadata_csum(sb))
return 1;
- provided = le16_to_cpu(gdp->bg_block_bitmap_csum_lo);
+ provided = ext4_block_bitmap_csum_get(sb, gdp);
calculated = ext4_chksum(sbi->s_csum_seed, (__u8 *)bh->b_data, sz);
- if (sbi->s_desc_size >= EXT4_BG_BLOCK_BITMAP_CSUM_HI_END) {
- hi = le16_to_cpu(gdp->bg_block_bitmap_csum_hi);
- provided |= (hi << 16);
- } else
+ if (EXT4_DESC_SIZE(sb) < EXT4_BG_BLOCK_BITMAP_CSUM_HI_END)
calculated &= 0xFFFF;
return provided == calculated;
@@ -93,7 +108,5 @@ void ext4_block_bitmap_csum_set(struct super_block *sb,
return;
csum = ext4_chksum(sbi->s_csum_seed, (__u8 *)bh->b_data, sz);
- gdp->bg_block_bitmap_csum_lo = cpu_to_le16(csum & 0xFFFF);
- if (sbi->s_desc_size >= EXT4_BG_BLOCK_BITMAP_CSUM_HI_END)
- gdp->bg_block_bitmap_csum_hi = cpu_to_le16(csum >> 16);
+ ext4_block_bitmap_csum_store(sb, gdp, csum);
}
--
2.43.7
^ permalink raw reply related
* [PATCH RFC 07/17] ext4: use fast incremental CRC update in ext4_mb_mark_context()
From: Baokun Li @ 2026-05-08 12:15 UTC (permalink / raw)
To: linux-ext4
Cc: linux-crypto, ebiggers, ardb, tytso, adilger.kernel, jack,
yi.zhang, ojaswin, ritesh.list, Baokun Li
In-Reply-To: <20260508121539.4174601-1-libaokun@linux.alibaba.com>
Use ext4_block_bitmap_csum_set_range() in ext4_mb_mark_context() for
fast incremental block bitmap checksum updates. Instead of re-scanning
the entire bitmap after every allocation or free, the incremental update
computes the CRC delta for the modified bit range in O(log N) time.
Add a fast_crc flag that is set when EXT4_MB_BITMAP_MARKED_CHECK is not
used. When fast_crc is true, all bits in the range are guaranteed to flip,
so the incremental CRC via ext4_block_bitmap_csum_set_range() is correct.
Otherwise, fall back to ext4_block_bitmap_csum_set() for a full CRC
recalculation, since idempotent operations (mb_set_bits/mb_clear_bits
with EXT4_MB_BITMAP_MARKED_CHECK) may leave some bits unchanged.
For the BLOCK_UNINIT case, the bitmap was just initialized and there is
no valid old checksum, so fast_crc is forced to false to ensure a full
CRC recalculation establishes a correct baseline.
Signed-off-by: Baokun Li <libaokun@linux.alibaba.com>
---
fs/ext4/mballoc.c | 22 +++++++++++++++++++++-
1 file changed, 21 insertions(+), 1 deletion(-)
diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c
index ff2023c9f52c..77f6309916d1 100644
--- a/fs/ext4/mballoc.c
+++ b/fs/ext4/mballoc.c
@@ -4095,6 +4095,7 @@ ext4_mb_mark_context(handle_t *handle, struct super_block *sb, bool state,
struct buffer_head *gdp_bh;
int err;
unsigned int i, already, changed = len;
+ bool fast_crc;
KUNIT_STATIC_STUB_REDIRECT(ext4_mb_mark_context,
handle, sb, state, group, blkoff, len,
@@ -4127,12 +4128,28 @@ ext4_mb_mark_context(handle_t *handle, struct super_block *sb, bool state,
goto out_err;
}
+ /*
+ * fast_crc: Use incremental CRC update via crc32c_flip_range().
+ * This is only valid when all bits in [blkoff, blkoff+len) are
+ * guaranteed to be in the opposite state (i.e., every bit will
+ * actually flip). When EXT4_MB_BITMAP_MARKED_CHECK is set,
+ * mb_set_bits/mb_clear_bits are idempotent, so some bits may not
+ * change and incremental CRC would produce incorrect results.
+ */
+ fast_crc = !(flags & EXT4_MB_BITMAP_MARKED_CHECK);
+
ext4_lock_group(sb, group);
if (ext4_has_group_desc_csum(sb) &&
(gdp->bg_flags & cpu_to_le16(EXT4_BG_BLOCK_UNINIT))) {
gdp->bg_flags &= cpu_to_le16(~EXT4_BG_BLOCK_UNINIT);
ext4_free_group_clusters_set(sb, gdp,
ext4_free_clusters_after_init(sb, group, gdp));
+ /*
+ * The bitmap was just initialized, so the old checksum
+ * is invalid for incremental CRC update. Fall back to
+ * full recalculation.
+ */
+ fast_crc = false;
}
if (flags & EXT4_MB_BITMAP_MARKED_CHECK) {
@@ -4154,7 +4171,10 @@ ext4_mb_mark_context(handle_t *handle, struct super_block *sb, bool state,
ext4_free_group_clusters(sb, gdp) + changed);
}
- ext4_block_bitmap_csum_set(sb, gdp, bitmap_bh);
+ if (fast_crc)
+ ext4_block_bitmap_csum_set_range(sb, gdp, blkoff, len);
+ else
+ ext4_block_bitmap_csum_set(sb, gdp, bitmap_bh);
ext4_group_desc_csum_set(sb, group, gdp);
ext4_unlock_group(sb, group);
if (ret_changed)
--
2.43.7
^ permalink raw reply related
* [PATCH RFC 14/17] ext4: rename ino to bit in __ext4_new_inode()
From: Baokun Li @ 2026-05-08 12:15 UTC (permalink / raw)
To: linux-ext4
Cc: linux-crypto, ebiggers, ardb, tytso, adilger.kernel, jack,
yi.zhang, ojaswin, ritesh.list, Baokun Li
In-Reply-To: <20260508121539.4174601-1-libaokun@linux.alibaba.com>
In __ext4_new_inode(), the variable 'ino' actually holds a zero-based
bit position within the inode bitmap, not an absolute inode number.
Rename it to 'bit' to better reflect its semantics and avoid confusion
with inode->i_ino.
With this rename, the previous 'ino++' before calculating i_ino is no
longer needed; instead compute i_ino directly as 'bit + 1'.
Signed-off-by: Baokun Li <libaokun@linux.alibaba.com>
---
fs/ext4/ialloc.c | 26 +++++++++++++-------------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/fs/ext4/ialloc.c b/fs/ext4/ialloc.c
index e209e27f827f..8b75b331b26e 100644
--- a/fs/ext4/ialloc.c
+++ b/fs/ext4/ialloc.c
@@ -974,7 +974,7 @@ struct inode *__ext4_new_inode(struct mnt_idmap *idmap,
struct buffer_head *inode_bitmap_bh = NULL;
struct buffer_head *group_desc_bh;
ext4_group_t ngroups, group = 0;
- unsigned long ino = 0;
+ unsigned long bit = 0;
struct inode *inode;
struct ext4_group_desc *gdp = NULL;
struct ext4_inode_info *ei;
@@ -1050,7 +1050,7 @@ struct inode *__ext4_new_inode(struct mnt_idmap *idmap,
if (goal && goal <= le32_to_cpu(sbi->s_es->s_inodes_count)) {
group = (goal - 1) / EXT4_INODES_PER_GROUP(sb);
- ino = (goal - 1) % EXT4_INODES_PER_GROUP(sb);
+ bit = (goal - 1) % EXT4_INODES_PER_GROUP(sb);
ret2 = 0;
goto got_group;
}
@@ -1071,7 +1071,7 @@ struct inode *__ext4_new_inode(struct mnt_idmap *idmap,
* unless we get unlucky and it turns out the group we selected
* had its last inode grabbed by someone else.
*/
- for (i = 0; i < ngroups; i++, ino = 0) {
+ for (i = 0; i < ngroups; i++, bit = 0) {
err = -EIO;
gdp = ext4_get_group_desc(sb, group, &group_desc_bh);
@@ -1105,13 +1105,13 @@ struct inode *__ext4_new_inode(struct mnt_idmap *idmap,
EXT4_MB_GRP_IBITMAP_CORRUPT(grp))
goto next_group;
- ret2 = find_inode_bit(sb, group, inode_bitmap_bh, &ino);
+ ret2 = find_inode_bit(sb, group, inode_bitmap_bh, &bit);
if (!ret2)
goto next_group;
- if (group == 0 && (ino + 1) < EXT4_FIRST_INO(sb)) {
+ if (group == 0 && (bit + 1) < EXT4_FIRST_INO(sb)) {
ext4_error(sb, "reserved inode found cleared - "
- "inode=%lu", ino + 1);
+ "inode=%lu", bit + 1);
ext4_mark_group_bitmap_corrupted(sb, group,
EXT4_GROUP_INFO_IBITMAP_CORRUPT);
goto next_group;
@@ -1136,21 +1136,20 @@ struct inode *__ext4_new_inode(struct mnt_idmap *idmap,
goto out;
}
ext4_lock_group(sb, group);
- ret2 = ext4_test_and_set_bit(ino, inode_bitmap_bh->b_data);
+ ret2 = ext4_test_and_set_bit(bit, inode_bitmap_bh->b_data);
if (ret2) {
/* Someone already took the bit. Repeat the search
* with lock held.
*/
- ret2 = find_inode_bit(sb, group, inode_bitmap_bh, &ino);
+ ret2 = find_inode_bit(sb, group, inode_bitmap_bh, &bit);
if (ret2) {
- ext4_set_bit(ino, inode_bitmap_bh->b_data);
+ ext4_set_bit(bit, inode_bitmap_bh->b_data);
ret2 = 0;
} else {
ret2 = 1; /* we didn't grab the inode */
}
}
ext4_unlock_group(sb, group);
- ino++; /* the inode bitmap is zero-based */
if (!ret2)
goto got; /* we grabbed the inode! */
@@ -1210,9 +1209,9 @@ struct inode *__ext4_new_inode(struct mnt_idmap *idmap,
* relative inode number in this group. if it is greater
* we need to update the bg_itable_unused count
*/
- if (ino > free)
+ if (bit >= free)
ext4_itable_unused_set(sb, gdp,
- (EXT4_INODES_PER_GROUP(sb) - ino));
+ (EXT4_INODES_PER_GROUP(sb) - bit - 1));
if (!(sbi->s_mount_state & EXT4_FC_REPLAY))
up_read(&grp->alloc_sem);
} else {
@@ -1252,7 +1251,8 @@ struct inode *__ext4_new_inode(struct mnt_idmap *idmap,
flex_group)->free_inodes);
}
- inode->i_ino = ino + group * EXT4_INODES_PER_GROUP(sb);
+ /* the inode bitmap is zero-based */
+ inode->i_ino = bit + 1 + group * EXT4_INODES_PER_GROUP(sb);
/* This is the optimal IO size (for stat), not the fs block size */
inode->i_blocks = 0;
simple_inode_init_ts(inode);
--
2.43.7
^ permalink raw reply related
* [PATCH RFC 16/17] ext4: extract ext4_update_inode_group_desc() to reduce duplication
From: Baokun Li @ 2026-05-08 12:15 UTC (permalink / raw)
To: linux-ext4
Cc: linux-crypto, ebiggers, ardb, tytso, adilger.kernel, jack,
yi.zhang, ojaswin, ritesh.list, Baokun Li
In-Reply-To: <20260508121539.4174601-1-libaokun@linux.alibaba.com>
ext4_mark_inode_used() and __ext4_new_inode() contain nearly
identical code blocks for updating group descriptor fields after
inode allocation (UNINIT flag clearing, itable_unused update,
inode bitmap checksum, group desc checksum). Extract the common
logic into ext4_update_inode_group_desc() to eliminate duplication.
Signed-off-by: Baokun Li <libaokun@linux.alibaba.com>
---
fs/ext4/ialloc.c | 136 ++++++++++++++++++++---------------------------
1 file changed, 57 insertions(+), 79 deletions(-)
diff --git a/fs/ext4/ialloc.c b/fs/ext4/ialloc.c
index 9dd1cdb367ba..25430c572818 100644
--- a/fs/ext4/ialloc.c
+++ b/fs/ext4/ialloc.c
@@ -808,12 +808,63 @@ static int ext4_might_init_block_bitmap(handle_t *handle,
return err;
}
+/*
+ * Update group descriptor checksums and itable_unused after allocating
+ * inode @bit (0-based relative inode number within the group).
+ * Must be called with the group lock held.
+ */
+static void ext4_update_inode_group_desc(struct super_block *sb,
+ ext4_group_t group,
+ struct ext4_group_desc *gdp,
+ struct buffer_head *inode_bitmap_bh,
+ int bit, umode_t mode)
+{
+ int free;
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
+ bool fast_crc = true;
+
+ ext4_free_inodes_set(sb, gdp, ext4_free_inodes_count(sb, gdp) - 1);
+ if (S_ISDIR(mode)) {
+ ext4_used_dirs_set(sb, gdp, ext4_used_dirs_count(sb, gdp) + 1);
+ if (sbi->s_log_groups_per_flex) {
+ ext4_group_t f = ext4_flex_group(sbi, group);
+
+ atomic_inc(&sbi_array_rcu_deref(sbi, s_flex_groups,
+ f)->used_dirs);
+ }
+ }
+
+ if (!ext4_has_group_desc_csum(sb))
+ return;
+
+ free = EXT4_INODES_PER_GROUP(sb) - ext4_itable_unused_count(sb, gdp);
+ if (gdp->bg_flags & cpu_to_le16(EXT4_BG_INODE_UNINIT)) {
+ gdp->bg_flags &= cpu_to_le16(~EXT4_BG_INODE_UNINIT);
+ free = 0;
+ /* Incremental CRC needs a valid checksum baseline */
+ fast_crc = false;
+ }
+
+ /*
+ * Check the relative inode number against the last used
+ * relative inode number in this group. If it is greater
+ * we need to update the bg_itable_unused count.
+ */
+ if (bit >= free)
+ ext4_itable_unused_set(sb, gdp,
+ EXT4_INODES_PER_GROUP(sb) - bit - 1);
+ if (fast_crc)
+ ext4_inode_bitmap_csum_set_fast(sb, gdp, bit);
+ else
+ ext4_inode_bitmap_csum_set(sb, gdp, inode_bitmap_bh);
+ ext4_group_desc_csum_set(sb, group, gdp);
+}
+
int ext4_mark_inode_used(struct super_block *sb, int ino, umode_t mode)
{
unsigned long max_ino = le32_to_cpu(EXT4_SB(sb)->s_es->s_inodes_count);
struct buffer_head *inode_bitmap_bh = NULL, *group_desc_bh = NULL;
struct ext4_group_desc *gdp;
- struct ext4_sb_info *sbi = EXT4_SB(sb);
ext4_group_t group;
int bit;
int err;
@@ -848,44 +899,8 @@ int ext4_mark_inode_used(struct super_block *sb, int ino, umode_t mode)
ext4_set_bit(bit, inode_bitmap_bh->b_data);
/* Update the relevant bg descriptor fields */
- ext4_free_inodes_set(sb, gdp, ext4_free_inodes_count(sb, gdp) - 1);
- if (S_ISDIR(mode)) {
- ext4_used_dirs_set(sb, gdp, ext4_used_dirs_count(sb, gdp) + 1);
- if (sbi->s_log_groups_per_flex) {
- ext4_group_t f = ext4_flex_group(sbi, group);
-
- atomic_inc(&sbi_array_rcu_deref(sbi, s_flex_groups,
- f)->used_dirs);
- }
- }
-
- if (ext4_has_group_desc_csum(sb)) {
- bool fast_crc = true;
- int free = EXT4_INODES_PER_GROUP(sb) -
- ext4_itable_unused_count(sb, gdp);
-
- if (gdp->bg_flags & cpu_to_le16(EXT4_BG_INODE_UNINIT)) {
- gdp->bg_flags &= cpu_to_le16(~EXT4_BG_INODE_UNINIT);
- free = 0;
- /* Incremental CRC needs a valid checksum baseline */
- fast_crc = false;
- }
-
- /*
- * Check the relative inode number against the last used
- * relative inode number in this group. if it is greater
- * we need to update the bg_itable_unused count
- */
- if (bit >= free)
- ext4_itable_unused_set(sb, gdp,
- (EXT4_INODES_PER_GROUP(sb) - bit - 1));
- if (fast_crc)
- ext4_inode_bitmap_csum_set_fast(sb, gdp, bit);
- else
- ext4_inode_bitmap_csum_set(sb, gdp, inode_bitmap_bh);
- ext4_group_desc_csum_set(sb, group, gdp);
- }
-
+ ext4_update_inode_group_desc(sb, group, gdp,
+ inode_bitmap_bh, bit, mode);
ext4_unlock_group(sb, group);
BUFFER_TRACE(inode_bitmap_bh, "call ext4_handle_dirty_metadata");
@@ -1165,50 +1180,13 @@ struct inode *__ext4_new_inode(struct mnt_idmap *idmap,
ret2 = 0;
} else {
ret2 = 1; /* we didn't grab the inode */
- goto unlock_group;
}
}
/* Update the relevant bg descriptor fields */
- ext4_free_inodes_set(sb, gdp,
- ext4_free_inodes_count(sb, gdp) - 1);
- if (S_ISDIR(mode)) {
- ext4_used_dirs_set(sb, gdp,
- ext4_used_dirs_count(sb, gdp) + 1);
- if (sbi->s_log_groups_per_flex) {
- ext4_group_t f = ext4_flex_group(sbi, group);
- atomic_inc(&sbi_array_rcu_deref(sbi, s_flex_groups,
- f)->used_dirs);
- }
- }
-
- if (ext4_has_group_desc_csum(sb)) {
- bool fast_crc = true;
- int free = EXT4_INODES_PER_GROUP(sb) -
- ext4_itable_unused_count(sb, gdp);
-
- if (gdp->bg_flags & cpu_to_le16(EXT4_BG_INODE_UNINIT)) {
- gdp->bg_flags &= cpu_to_le16(~EXT4_BG_INODE_UNINIT);
- free = 0;
- /* Incremental CRC needs a valid csum baseline */
- fast_crc = false;
- }
- /*
- * Check the relative inode number against the
- * last used relative inode number in this group.
- * If it is greater we need to update the
- * bg_itable_unused count.
- */
- if (bit >= free)
- ext4_itable_unused_set(sb, gdp,
- EXT4_INODES_PER_GROUP(sb) - bit - 1);
- if (fast_crc)
- ext4_inode_bitmap_csum_set_fast(sb, gdp, bit);
- else
- ext4_inode_bitmap_csum_set(sb, gdp, inode_bitmap_bh);
- ext4_group_desc_csum_set(sb, group, gdp);
- }
-unlock_group:
+ if (!ret2)
+ ext4_update_inode_group_desc(sb, group, gdp,
+ inode_bitmap_bh, bit, mode);
ext4_unlock_group(sb, group);
if (ext4_has_group_desc_csum(sb) &&
!(sbi->s_mount_state & EXT4_FC_REPLAY))
--
2.43.7
^ permalink raw reply related
* [PATCH RFC 09/17] ext4: add ext4_inode_bitmap_csum_set_fast() for incremental checksum update
From: Baokun Li @ 2026-05-08 12:15 UTC (permalink / raw)
To: linux-ext4
Cc: linux-crypto, ebiggers, ardb, tytso, adilger.kernel, jack,
yi.zhang, ojaswin, ritesh.list, Baokun Li
In-Reply-To: <20260508121539.4174601-1-libaokun@linux.alibaba.com>
Add a helper function ext4_inode_bitmap_csum_set_fast() that uses
crc32c_flip_range() to incrementally update the inode bitmap checksum
after flipping a single bit at the given offset. This avoids a full
bitmap CRC rescan, computing the CRC delta in O(log N) time.
Signed-off-by: Baokun Li <libaokun@linux.alibaba.com>
---
fs/ext4/bitmap.c | 23 +++++++++++++++++++++++
fs/ext4/ext4.h | 3 +++
2 files changed, 26 insertions(+)
diff --git a/fs/ext4/bitmap.c b/fs/ext4/bitmap.c
index 008acc439301..ea47ca0d7046 100644
--- a/fs/ext4/bitmap.c
+++ b/fs/ext4/bitmap.c
@@ -71,6 +71,29 @@ void ext4_inode_bitmap_csum_set(struct super_block *sb,
ext4_inode_bitmap_csum_store(sb, gdp, csum);
}
+/*
+ * Update inode bitmap checksum for a single flipped bit.
+ *
+ * Use crc32c_flip_range() to incrementally update the checksum after
+ * flipping the bit at @offset, avoiding a full bitmap CRC rescan.
+ * The csum_seed cancels out in the XOR delta, so it is not needed here.
+ */
+void ext4_inode_bitmap_csum_set_fast(struct super_block *sb,
+ struct ext4_group_desc *gdp,
+ ext4_grpblk_t offset)
+{
+ __u32 new_csum, old_csum;
+
+ if (!ext4_has_feature_metadata_csum(sb))
+ return;
+
+ old_csum = ext4_inode_bitmap_csum_get(sb, gdp);
+ new_csum = crc32c_flip_range(old_csum, EXT4_INODES_PER_GROUP(sb),
+ offset, 1);
+
+ ext4_inode_bitmap_csum_store(sb, gdp, new_csum);
+}
+
static inline __u32 ext4_block_bitmap_csum_get(struct super_block *sb,
struct ext4_group_desc *gdp)
{
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index c423a9a04047..e6739d5af490 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -2764,6 +2764,9 @@ extern unsigned int ext4_count_free(char *bitmap, unsigned numchars);
void ext4_inode_bitmap_csum_set(struct super_block *sb,
struct ext4_group_desc *gdp,
struct buffer_head *bh);
+void ext4_inode_bitmap_csum_set_fast(struct super_block *sb,
+ struct ext4_group_desc *gdp,
+ ext4_grpblk_t offset);
int ext4_inode_bitmap_csum_verify(struct super_block *sb,
struct ext4_group_desc *gdp,
struct buffer_head *bh);
--
2.43.7
^ permalink raw reply related
* [PATCH RFC 17/17] ext4: add ext4_get_flex_group() helper to simplify flex group lookups
From: Baokun Li @ 2026-05-08 12:15 UTC (permalink / raw)
To: linux-ext4
Cc: linux-crypto, ebiggers, ardb, tytso, adilger.kernel, jack,
yi.zhang, ojaswin, ritesh.list, Baokun Li
In-Reply-To: <20260508121539.4174601-1-libaokun@linux.alibaba.com>
Introduce ext4_get_flex_group() that combines ext4_flex_group() and
sbi_array_rcu_deref() into a single call, replacing the repeated
pattern across ialloc.c, mballoc.c, resize.c, and super.c.
No functional change.
Signed-off-by: Baokun Li <libaokun@linux.alibaba.com>
---
fs/ext4/ext4.h | 7 +++++++
fs/ext4/ialloc.c | 19 +++++--------------
fs/ext4/mballoc.c | 4 +---
fs/ext4/resize.c | 4 +---
fs/ext4/super.c | 4 +---
5 files changed, 15 insertions(+), 23 deletions(-)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index f48cb9d998ab..e38ada51972a 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -3457,6 +3457,13 @@ static inline unsigned int ext4_flex_bg_size(struct ext4_sb_info *sbi)
return 1 << sbi->s_log_groups_per_flex;
}
+static inline struct flex_groups *ext4_get_flex_group(struct ext4_sb_info *sbi,
+ ext4_group_t block_group)
+{
+ return sbi_array_rcu_deref(sbi, s_flex_groups,
+ ext4_flex_group(sbi, block_group));
+}
+
static inline loff_t ext4_get_maxbytes(struct inode *inode)
{
if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))
diff --git a/fs/ext4/ialloc.c b/fs/ext4/ialloc.c
index 25430c572818..d88160afd6b6 100644
--- a/fs/ext4/ialloc.c
+++ b/fs/ext4/ialloc.c
@@ -336,8 +336,7 @@ void ext4_free_inode(handle_t *handle, struct inode *inode)
if (sbi->s_log_groups_per_flex) {
struct flex_groups *fg;
- fg = sbi_array_rcu_deref(sbi, s_flex_groups,
- ext4_flex_group(sbi, block_group));
+ fg = ext4_get_flex_group(sbi, block_group);
atomic_inc(&fg->free_inodes);
if (is_directory)
atomic_dec(&fg->used_dirs);
@@ -826,12 +825,8 @@ static void ext4_update_inode_group_desc(struct super_block *sb,
ext4_free_inodes_set(sb, gdp, ext4_free_inodes_count(sb, gdp) - 1);
if (S_ISDIR(mode)) {
ext4_used_dirs_set(sb, gdp, ext4_used_dirs_count(sb, gdp) + 1);
- if (sbi->s_log_groups_per_flex) {
- ext4_group_t f = ext4_flex_group(sbi, group);
-
- atomic_inc(&sbi_array_rcu_deref(sbi, s_flex_groups,
- f)->used_dirs);
- }
+ if (sbi->s_log_groups_per_flex)
+ atomic_inc(&ext4_get_flex_group(sbi, group)->used_dirs);
}
if (!ext4_has_group_desc_csum(sb))
@@ -997,7 +992,6 @@ struct inode *__ext4_new_inode(struct mnt_idmap *idmap,
int ret2, err;
struct inode *ret;
ext4_group_t i;
- ext4_group_t flex_group;
struct ext4_group_info *grp = NULL;
bool encrypt = false;
@@ -1220,11 +1214,8 @@ struct inode *__ext4_new_inode(struct mnt_idmap *idmap,
if (S_ISDIR(mode))
percpu_counter_inc(&sbi->s_dirs_counter);
- if (sbi->s_log_groups_per_flex) {
- flex_group = ext4_flex_group(sbi, group);
- atomic_dec(&sbi_array_rcu_deref(sbi, s_flex_groups,
- flex_group)->free_inodes);
- }
+ if (sbi->s_log_groups_per_flex)
+ atomic_dec(&ext4_get_flex_group(sbi, group)->free_inodes);
/* the inode bitmap is zero-based */
inode->i_ino = bit + 1 + group * EXT4_INODES_PER_GROUP(sb);
diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c
index 77f6309916d1..9e30c9eefd35 100644
--- a/fs/ext4/mballoc.c
+++ b/fs/ext4/mballoc.c
@@ -4181,9 +4181,7 @@ ext4_mb_mark_context(handle_t *handle, struct super_block *sb, bool state,
*ret_changed = changed;
if (sbi->s_log_groups_per_flex) {
- ext4_group_t flex_group = ext4_flex_group(sbi, group);
- struct flex_groups *fg = sbi_array_rcu_deref(sbi,
- s_flex_groups, flex_group);
+ struct flex_groups *fg = ext4_get_flex_group(sbi, group);
if (state)
atomic64_sub(changed, &fg->free_clusters);
diff --git a/fs/ext4/resize.c b/fs/ext4/resize.c
index 2c5b851c552a..8d2cd1bc17bb 100644
--- a/fs/ext4/resize.c
+++ b/fs/ext4/resize.c
@@ -1495,11 +1495,9 @@ static void ext4_update_super(struct super_block *sb,
ext4_debug("free blocks count %llu",
percpu_counter_read(&sbi->s_freeclusters_counter));
if (ext4_has_feature_flex_bg(sb) && sbi->s_log_groups_per_flex) {
- ext4_group_t flex_group;
struct flex_groups *fg;
- flex_group = ext4_flex_group(sbi, group_data[0].group);
- fg = sbi_array_rcu_deref(sbi, s_flex_groups, flex_group);
+ fg = ext4_get_flex_group(sbi, group_data[0].group);
atomic64_add(EXT4_NUM_B2C(sbi, free_blocks),
&fg->free_clusters);
atomic_add(EXT4_INODES_PER_GROUP(sb) * flex_gd->count,
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index 6a77db4d3124..064e06163716 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -3211,7 +3211,6 @@ static int ext4_fill_flex_info(struct super_block *sb)
struct ext4_sb_info *sbi = EXT4_SB(sb);
struct ext4_group_desc *gdp = NULL;
struct flex_groups *fg;
- ext4_group_t flex_group;
int i, err;
sbi->s_log_groups_per_flex = sbi->s_es->s_log_groups_per_flex;
@@ -3227,8 +3226,7 @@ static int ext4_fill_flex_info(struct super_block *sb)
for (i = 0; i < sbi->s_groups_count; i++) {
gdp = ext4_get_group_desc(sb, i, NULL);
- flex_group = ext4_flex_group(sbi, i);
- fg = sbi_array_rcu_deref(sbi, s_flex_groups, flex_group);
+ fg = ext4_get_flex_group(sbi, i);
atomic_add(ext4_free_inodes_count(sb, gdp), &fg->free_inodes);
atomic64_add(ext4_free_group_clusters(sb, gdp),
&fg->free_clusters);
--
2.43.7
^ permalink raw reply related
* [PATCH RFC 10/17] ext4: use fast incremental CRC update in ext4_free_inode()
From: Baokun Li @ 2026-05-08 12:15 UTC (permalink / raw)
To: linux-ext4
Cc: linux-crypto, ebiggers, ardb, tytso, adilger.kernel, jack,
yi.zhang, ojaswin, ritesh.list, Baokun Li
In-Reply-To: <20260508121539.4174601-1-libaokun@linux.alibaba.com>
Replace ext4_inode_bitmap_csum_set() with the newly added
ext4_inode_bitmap_csum_set_fast() in ext4_free_inode() for incremental
inode bitmap checksum update.
This is safe because:
- At inode free time, the inode bitmap checksum has already been
initialized, so the old checksum is always valid.
- The bitmap buffer modification and checksum update are protected
by the same group lock, ensuring the old checksum is consistent
with the bitmap content before the bit flip.
Signed-off-by: Baokun Li <libaokun@linux.alibaba.com>
---
fs/ext4/ialloc.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fs/ext4/ialloc.c b/fs/ext4/ialloc.c
index 3fd8f0099852..55eb69fbb4c9 100644
--- a/fs/ext4/ialloc.c
+++ b/fs/ext4/ialloc.c
@@ -327,7 +327,7 @@ void ext4_free_inode(handle_t *handle, struct inode *inode)
if (percpu_counter_initialized(&sbi->s_dirs_counter))
percpu_counter_dec(&sbi->s_dirs_counter);
}
- ext4_inode_bitmap_csum_set(sb, gdp, bitmap_bh);
+ ext4_inode_bitmap_csum_set_fast(sb, gdp, bit);
ext4_group_desc_csum_set(sb, block_group, gdp);
ext4_unlock_group(sb, block_group);
--
2.43.7
^ permalink raw reply related
* [PATCH RFC 11/17] ext4: fix missing bg_used_dirs_count update in fast commit replay
From: Baokun Li @ 2026-05-08 12:15 UTC (permalink / raw)
To: linux-ext4
Cc: linux-crypto, ebiggers, ardb, tytso, adilger.kernel, jack,
yi.zhang, ojaswin, ritesh.list, Baokun Li
In-Reply-To: <20260508121539.4174601-1-libaokun@linux.alibaba.com>
ext4_mark_inode_used() did not update bg_used_dirs_count for directory
inodes during fast commit replay because it lacked the inode mode.
Add a mode parameter and pass it from both ext4_fc_replay_inode() (from
raw_fc_inode) and ext4_fc_replay_create() (after ext4_iget).
Fixes: 8016e29f4362 ("ext4: fast commit recovery path")
Signed-off-by: Baokun Li <libaokun@linux.alibaba.com>
---
fs/ext4/ext4.h | 2 +-
fs/ext4/fast_commit.c | 13 +++++++------
fs/ext4/ialloc.c | 13 ++++++++++++-
3 files changed, 20 insertions(+), 8 deletions(-)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index e6739d5af490..f48cb9d998ab 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -2941,7 +2941,7 @@ extern int ext4fs_dirhash(const struct inode *dir, const char *name, int len,
struct dx_hash_info *hinfo);
/* ialloc.c */
-extern int ext4_mark_inode_used(struct super_block *sb, int ino);
+extern int ext4_mark_inode_used(struct super_block *sb, int ino, umode_t mode);
extern struct inode *__ext4_new_inode(struct mnt_idmap *, handle_t *,
struct inode *, umode_t,
const struct qstr *qstr, __u32 goal,
diff --git a/fs/ext4/fast_commit.c b/fs/ext4/fast_commit.c
index b3c22636251d..f68d7b2eb0db 100644
--- a/fs/ext4/fast_commit.c
+++ b/fs/ext4/fast_commit.c
@@ -1578,7 +1578,7 @@ static int ext4_fc_replay_inode(struct super_block *sb,
ret = sync_dirty_buffer(iloc.bh);
if (ret)
goto out_brelse;
- ret = ext4_mark_inode_used(sb, ino);
+ ret = ext4_mark_inode_used(sb, ino, le16_to_cpu(raw_fc_inode->i_mode));
if (ret)
goto out_brelse;
@@ -1635,11 +1635,7 @@ static int ext4_fc_replay_create(struct super_block *sb,
trace_ext4_fc_replay(sb, EXT4_FC_TAG_CREAT, darg.ino,
darg.parent_ino, darg.dname_len);
- /* This takes care of update group descriptor and other metadata */
- ret = ext4_mark_inode_used(sb, darg.ino);
- if (ret)
- goto out;
-
+ /* Inode already on disk from TAG_INODE replay; iget first for mode. */
inode = ext4_iget(sb, darg.ino, EXT4_IGET_NORMAL);
if (IS_ERR(inode)) {
ext4_debug("inode %d not found.", darg.ino);
@@ -1648,6 +1644,11 @@ static int ext4_fc_replay_create(struct super_block *sb,
goto out;
}
+ /* This takes care of update group descriptor and other metadata */
+ ret = ext4_mark_inode_used(sb, darg.ino, inode->i_mode);
+ if (ret)
+ goto out;
+
if (S_ISDIR(inode->i_mode)) {
/*
* If we are creating a directory, we need to make sure that the
diff --git a/fs/ext4/ialloc.c b/fs/ext4/ialloc.c
index 55eb69fbb4c9..5896cdfb2ccf 100644
--- a/fs/ext4/ialloc.c
+++ b/fs/ext4/ialloc.c
@@ -756,11 +756,12 @@ static int find_inode_bit(struct super_block *sb, ext4_group_t group,
return 1;
}
-int ext4_mark_inode_used(struct super_block *sb, int ino)
+int ext4_mark_inode_used(struct super_block *sb, int ino, umode_t mode)
{
unsigned long max_ino = le32_to_cpu(EXT4_SB(sb)->s_es->s_inodes_count);
struct buffer_head *inode_bitmap_bh = NULL, *group_desc_bh = NULL;
struct ext4_group_desc *gdp;
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
ext4_group_t group;
int bit;
int err;
@@ -858,6 +859,16 @@ int ext4_mark_inode_used(struct super_block *sb, int ino)
}
ext4_free_inodes_set(sb, gdp, ext4_free_inodes_count(sb, gdp) - 1);
+ if (S_ISDIR(mode)) {
+ ext4_used_dirs_set(sb, gdp, ext4_used_dirs_count(sb, gdp) + 1);
+ if (sbi->s_log_groups_per_flex) {
+ ext4_group_t f = ext4_flex_group(sbi, group);
+
+ atomic_inc(&sbi_array_rcu_deref(sbi, s_flex_groups,
+ f)->used_dirs);
+ }
+ }
+
if (ext4_has_group_desc_csum(sb)) {
ext4_inode_bitmap_csum_set(sb, gdp, inode_bitmap_bh);
ext4_group_desc_csum_set(sb, group, gdp);
--
2.43.7
^ permalink raw reply related
* [PATCH RFC 08/17] ext4: extract inode bitmap checksum get and store helpers
From: Baokun Li @ 2026-05-08 12:15 UTC (permalink / raw)
To: linux-ext4
Cc: linux-crypto, ebiggers, ardb, tytso, adilger.kernel, jack,
yi.zhang, ojaswin, ritesh.list, Baokun Li
In-Reply-To: <20260508121539.4174601-1-libaokun@linux.alibaba.com>
Add ext4_inode_bitmap_csum_get() and ext4_inode_bitmap_csum_store()
helpers, and use EXT4_DESC_SIZE(sb) instead of sbi->s_desc_size for
consistency. No functional change.
Signed-off-by: Baokun Li <libaokun@linux.alibaba.com>
---
fs/ext4/bitmap.c | 31 ++++++++++++++++++++++---------
1 file changed, 22 insertions(+), 9 deletions(-)
diff --git a/fs/ext4/bitmap.c b/fs/ext4/bitmap.c
index 00b0a3c74859..008acc439301 100644
--- a/fs/ext4/bitmap.c
+++ b/fs/ext4/bitmap.c
@@ -16,11 +16,29 @@ unsigned int ext4_count_free(char *bitmap, unsigned int numchars)
return numchars * BITS_PER_BYTE - memweight(bitmap, numchars);
}
+static inline __u32 ext4_inode_bitmap_csum_get(struct super_block *sb,
+ struct ext4_group_desc *gdp)
+{
+ __u32 csum = le16_to_cpu(gdp->bg_inode_bitmap_csum_lo);
+
+ if (EXT4_DESC_SIZE(sb) >= EXT4_BG_INODE_BITMAP_CSUM_HI_END)
+ csum |= (__u32)le16_to_cpu(gdp->bg_inode_bitmap_csum_hi) << 16;
+ return csum;
+}
+
+static inline void ext4_inode_bitmap_csum_store(struct super_block *sb,
+ struct ext4_group_desc *gdp,
+ __u32 csum)
+{
+ gdp->bg_inode_bitmap_csum_lo = cpu_to_le16(csum & 0xFFFF);
+ if (EXT4_DESC_SIZE(sb) >= EXT4_BG_INODE_BITMAP_CSUM_HI_END)
+ gdp->bg_inode_bitmap_csum_hi = cpu_to_le16(csum >> 16);
+}
+
int ext4_inode_bitmap_csum_verify(struct super_block *sb,
struct ext4_group_desc *gdp,
struct buffer_head *bh)
{
- __u32 hi;
__u32 provided, calculated;
struct ext4_sb_info *sbi = EXT4_SB(sb);
int sz;
@@ -29,12 +47,9 @@ int ext4_inode_bitmap_csum_verify(struct super_block *sb,
return 1;
sz = EXT4_INODES_PER_GROUP(sb) >> 3;
- provided = le16_to_cpu(gdp->bg_inode_bitmap_csum_lo);
+ provided = ext4_inode_bitmap_csum_get(sb, gdp);
calculated = ext4_chksum(sbi->s_csum_seed, (__u8 *)bh->b_data, sz);
- if (sbi->s_desc_size >= EXT4_BG_INODE_BITMAP_CSUM_HI_END) {
- hi = le16_to_cpu(gdp->bg_inode_bitmap_csum_hi);
- provided |= (hi << 16);
- } else
+ if (EXT4_DESC_SIZE(sb) < EXT4_BG_INODE_BITMAP_CSUM_HI_END)
calculated &= 0xFFFF;
return provided == calculated;
@@ -53,9 +68,7 @@ void ext4_inode_bitmap_csum_set(struct super_block *sb,
sz = EXT4_INODES_PER_GROUP(sb) >> 3;
csum = ext4_chksum(sbi->s_csum_seed, (__u8 *)bh->b_data, sz);
- gdp->bg_inode_bitmap_csum_lo = cpu_to_le16(csum & 0xFFFF);
- if (sbi->s_desc_size >= EXT4_BG_INODE_BITMAP_CSUM_HI_END)
- gdp->bg_inode_bitmap_csum_hi = cpu_to_le16(csum >> 16);
+ ext4_inode_bitmap_csum_store(sb, gdp, csum);
}
static inline __u32 ext4_block_bitmap_csum_get(struct super_block *sb,
--
2.43.7
^ permalink raw reply related
* [PATCH RFC 13/17] ext4: use fast incremental CRC update in ext4_mark_inode_used()
From: Baokun Li @ 2026-05-08 12:15 UTC (permalink / raw)
To: linux-ext4
Cc: linux-crypto, ebiggers, ardb, tytso, adilger.kernel, jack,
yi.zhang, ojaswin, ritesh.list, Baokun Li
In-Reply-To: <20260508121539.4174601-1-libaokun@linux.alibaba.com>
Move the bitmap modification, GDP update, and checksum update into a
single group lock acquisition in ext4_mark_inode_used(), eliminating the
race window where another thread could interleave a full recomputation
between bitmap modification and checksum update.
Add a fast_crc flag to select between incremental and full CRC update.
When EXT4_BG_INODE_UNINIT is set, the stored checksum in the group
descriptor is not a valid CRC of the bitmap -- mkfs leaves it as zero
for UNINIT groups, and ext4_read_inode_bitmap() memsets the buffer to
zero without updating the gdp checksum. So fast_crc is forced to false
to fall back to ext4_inode_bitmap_csum_set() for a full recalculation
that establishes a correct baseline.
For non-UNINIT groups, ext4_inode_bitmap_csum_set_fast() computes the
CRC delta for the single flipped bit in O(log N) time.
Signed-off-by: Baokun Li <libaokun@linux.alibaba.com>
---
fs/ext4/ialloc.c | 69 ++++++++++++++++++++++++------------------------
1 file changed, 35 insertions(+), 34 deletions(-)
diff --git a/fs/ext4/ialloc.c b/fs/ext4/ialloc.c
index 90896b7f8c73..e209e27f827f 100644
--- a/fs/ext4/ialloc.c
+++ b/fs/ext4/ialloc.c
@@ -838,35 +838,37 @@ int ext4_mark_inode_used(struct super_block *sb, int ino, umode_t mode)
goto out;
}
- ext4_set_bit(bit, inode_bitmap_bh->b_data);
-
- BUFFER_TRACE(inode_bitmap_bh, "call ext4_handle_dirty_metadata");
- err = ext4_handle_dirty_metadata(NULL, NULL, inode_bitmap_bh);
- if (err) {
- ext4_std_error(sb, err);
- goto out;
- }
- err = sync_dirty_buffer(inode_bitmap_bh);
- if (err) {
- ext4_std_error(sb, err);
- goto out;
- }
-
/* We may have to initialize the block bitmap if it isn't already */
err = ext4_might_init_block_bitmap(NULL, sb, group, gdp);
if (err)
goto out;
+ ext4_lock_group(sb, group);
+ /* Fast commit replay is single-threaded, no need for test_and_set */
+ ext4_set_bit(bit, inode_bitmap_bh->b_data);
+
/* Update the relevant bg descriptor fields */
+ ext4_free_inodes_set(sb, gdp, ext4_free_inodes_count(sb, gdp) - 1);
+ if (S_ISDIR(mode)) {
+ ext4_used_dirs_set(sb, gdp, ext4_used_dirs_count(sb, gdp) + 1);
+ if (sbi->s_log_groups_per_flex) {
+ ext4_group_t f = ext4_flex_group(sbi, group);
+
+ atomic_inc(&sbi_array_rcu_deref(sbi, s_flex_groups,
+ f)->used_dirs);
+ }
+ }
+
if (ext4_has_group_desc_csum(sb)) {
- int free;
+ bool fast_crc = true;
+ int free = EXT4_INODES_PER_GROUP(sb) -
+ ext4_itable_unused_count(sb, gdp);
- ext4_lock_group(sb, group); /* while we modify the bg desc */
- free = EXT4_INODES_PER_GROUP(sb) -
- ext4_itable_unused_count(sb, gdp);
if (gdp->bg_flags & cpu_to_le16(EXT4_BG_INODE_UNINIT)) {
gdp->bg_flags &= cpu_to_le16(~EXT4_BG_INODE_UNINIT);
free = 0;
+ /* Incremental CRC needs a valid checksum baseline */
+ fast_crc = false;
}
/*
@@ -877,27 +879,26 @@ int ext4_mark_inode_used(struct super_block *sb, int ino, umode_t mode)
if (bit >= free)
ext4_itable_unused_set(sb, gdp,
(EXT4_INODES_PER_GROUP(sb) - bit - 1));
- } else {
- ext4_lock_group(sb, group);
+ if (fast_crc)
+ ext4_inode_bitmap_csum_set_fast(sb, gdp, bit);
+ else
+ ext4_inode_bitmap_csum_set(sb, gdp, inode_bitmap_bh);
+ ext4_group_desc_csum_set(sb, group, gdp);
}
- ext4_free_inodes_set(sb, gdp, ext4_free_inodes_count(sb, gdp) - 1);
- if (S_ISDIR(mode)) {
- ext4_used_dirs_set(sb, gdp, ext4_used_dirs_count(sb, gdp) + 1);
- if (sbi->s_log_groups_per_flex) {
- ext4_group_t f = ext4_flex_group(sbi, group);
+ ext4_unlock_group(sb, group);
- atomic_inc(&sbi_array_rcu_deref(sbi, s_flex_groups,
- f)->used_dirs);
- }
+ BUFFER_TRACE(inode_bitmap_bh, "call ext4_handle_dirty_metadata");
+ err = ext4_handle_dirty_metadata(NULL, NULL, inode_bitmap_bh);
+ if (err) {
+ ext4_std_error(sb, err);
+ goto out;
}
-
- if (ext4_has_group_desc_csum(sb)) {
- ext4_inode_bitmap_csum_set(sb, gdp, inode_bitmap_bh);
- ext4_group_desc_csum_set(sb, group, gdp);
+ err = sync_dirty_buffer(inode_bitmap_bh);
+ if (err) {
+ ext4_std_error(sb, err);
+ goto out;
}
-
- ext4_unlock_group(sb, group);
err = ext4_handle_dirty_metadata(NULL, NULL, group_desc_bh);
sync_dirty_buffer(group_desc_bh);
out:
--
2.43.7
^ permalink raw reply related
* [PATCH RFC 12/17] ext4: factor out ext4_might_init_block_bitmap() helper
From: Baokun Li @ 2026-05-08 12:15 UTC (permalink / raw)
To: linux-ext4
Cc: linux-crypto, ebiggers, ardb, tytso, adilger.kernel, jack,
yi.zhang, ojaswin, ritesh.list, Baokun Li
In-Reply-To: <20260508121539.4174601-1-libaokun@linux.alibaba.com>
Extract the BLOCK_UNINIT initialization logic from ext4_mark_inode_used()
and __ext4_new_inode() into a shared ext4_might_init_block_bitmap() helper.
Both call sites perform the same sequence: check EXT4_BG_BLOCK_UNINIT,
read the block bitmap, dirty it, then clear the flag and establish the
correct block bitmap checksum under the group lock. The only difference
is whether a journal handle is available (NULL during fast commit replay
in ext4_mark_inode_used()).
No functional change.
Signed-off-by: Baokun Li <libaokun@linux.alibaba.com>
---
fs/ext4/ialloc.c | 129 +++++++++++++++++++++--------------------------
1 file changed, 58 insertions(+), 71 deletions(-)
diff --git a/fs/ext4/ialloc.c b/fs/ext4/ialloc.c
index 5896cdfb2ccf..90896b7f8c73 100644
--- a/fs/ext4/ialloc.c
+++ b/fs/ext4/ialloc.c
@@ -756,6 +756,58 @@ static int find_inode_bit(struct super_block *sb, ext4_group_t group,
return 1;
}
+/*
+ * If the block bitmap for @group is not yet initialized (EXT4_BG_BLOCK_UNINIT),
+ * read it into memory, dirty it, and clear the UNINIT flag under the group lock
+ * so that the on-disk checksum is established. @handle may be NULL during fast
+ * commit replay (no journal credits needed in that path).
+ */
+static int ext4_might_init_block_bitmap(handle_t *handle,
+ struct super_block *sb,
+ ext4_group_t group,
+ struct ext4_group_desc *gdp)
+{
+ int err;
+ struct buffer_head *block_bitmap_bh;
+
+ if (!ext4_has_group_desc_csum(sb) ||
+ !(gdp->bg_flags & cpu_to_le16(EXT4_BG_BLOCK_UNINIT)))
+ return 0;
+
+ block_bitmap_bh = ext4_read_block_bitmap(sb, group);
+ if (IS_ERR(block_bitmap_bh))
+ return PTR_ERR(block_bitmap_bh);
+
+ if (handle) {
+ BUFFER_TRACE(block_bitmap_bh, "get block bitmap access");
+ err = ext4_journal_get_write_access(handle, sb,
+ block_bitmap_bh, EXT4_JTR_NONE);
+ if (err)
+ goto out_brelse;
+ }
+
+ BUFFER_TRACE(block_bitmap_bh, "dirty block bitmap");
+ err = ext4_handle_dirty_metadata(handle, NULL, block_bitmap_bh);
+ if (!handle)
+ sync_dirty_buffer(block_bitmap_bh);
+
+ /* recheck and clear flag under lock if we still need to */
+ ext4_lock_group(sb, group);
+ if (gdp->bg_flags & cpu_to_le16(EXT4_BG_BLOCK_UNINIT)) {
+ gdp->bg_flags &= cpu_to_le16(~EXT4_BG_BLOCK_UNINIT);
+ ext4_free_group_clusters_set(sb, gdp,
+ ext4_free_clusters_after_init(sb, group, gdp));
+ ext4_block_bitmap_csum_set(sb, gdp, block_bitmap_bh);
+ ext4_group_desc_csum_set(sb, group, gdp);
+ }
+ ext4_unlock_group(sb, group);
+
+out_brelse:
+ brelse(block_bitmap_bh);
+ ext4_std_error(sb, err);
+ return err;
+}
+
int ext4_mark_inode_used(struct super_block *sb, int ino, umode_t mode)
{
unsigned long max_ino = le32_to_cpu(EXT4_SB(sb)->s_es->s_inodes_count);
@@ -801,38 +853,9 @@ int ext4_mark_inode_used(struct super_block *sb, int ino, umode_t mode)
}
/* We may have to initialize the block bitmap if it isn't already */
- if (ext4_has_group_desc_csum(sb) &&
- gdp->bg_flags & cpu_to_le16(EXT4_BG_BLOCK_UNINIT)) {
- struct buffer_head *block_bitmap_bh;
-
- block_bitmap_bh = ext4_read_block_bitmap(sb, group);
- if (IS_ERR(block_bitmap_bh)) {
- err = PTR_ERR(block_bitmap_bh);
- goto out;
- }
-
- BUFFER_TRACE(block_bitmap_bh, "dirty block bitmap");
- err = ext4_handle_dirty_metadata(NULL, NULL, block_bitmap_bh);
- sync_dirty_buffer(block_bitmap_bh);
-
- /* recheck and clear flag under lock if we still need to */
- ext4_lock_group(sb, group);
- if (ext4_has_group_desc_csum(sb) &&
- (gdp->bg_flags & cpu_to_le16(EXT4_BG_BLOCK_UNINIT))) {
- gdp->bg_flags &= cpu_to_le16(~EXT4_BG_BLOCK_UNINIT);
- ext4_free_group_clusters_set(sb, gdp,
- ext4_free_clusters_after_init(sb, group, gdp));
- ext4_block_bitmap_csum_set(sb, gdp, block_bitmap_bh);
- ext4_group_desc_csum_set(sb, group, gdp);
- }
- ext4_unlock_group(sb, group);
- brelse(block_bitmap_bh);
-
- if (err) {
- ext4_std_error(sb, err);
- goto out;
- }
- }
+ err = ext4_might_init_block_bitmap(NULL, sb, group, gdp);
+ if (err)
+ goto out;
/* Update the relevant bg descriptor fields */
if (ext4_has_group_desc_csum(sb)) {
@@ -1154,45 +1177,9 @@ struct inode *__ext4_new_inode(struct mnt_idmap *idmap,
}
/* We may have to initialize the block bitmap if it isn't already */
- if (ext4_has_group_desc_csum(sb) &&
- gdp->bg_flags & cpu_to_le16(EXT4_BG_BLOCK_UNINIT)) {
- struct buffer_head *block_bitmap_bh;
-
- block_bitmap_bh = ext4_read_block_bitmap(sb, group);
- if (IS_ERR(block_bitmap_bh)) {
- err = PTR_ERR(block_bitmap_bh);
- goto out;
- }
- BUFFER_TRACE(block_bitmap_bh, "get block bitmap access");
- err = ext4_journal_get_write_access(handle, sb, block_bitmap_bh,
- EXT4_JTR_NONE);
- if (err) {
- brelse(block_bitmap_bh);
- ext4_std_error(sb, err);
- goto out;
- }
-
- BUFFER_TRACE(block_bitmap_bh, "dirty block bitmap");
- err = ext4_handle_dirty_metadata(handle, NULL, block_bitmap_bh);
-
- /* recheck and clear flag under lock if we still need to */
- ext4_lock_group(sb, group);
- if (ext4_has_group_desc_csum(sb) &&
- (gdp->bg_flags & cpu_to_le16(EXT4_BG_BLOCK_UNINIT))) {
- gdp->bg_flags &= cpu_to_le16(~EXT4_BG_BLOCK_UNINIT);
- ext4_free_group_clusters_set(sb, gdp,
- ext4_free_clusters_after_init(sb, group, gdp));
- ext4_block_bitmap_csum_set(sb, gdp, block_bitmap_bh);
- ext4_group_desc_csum_set(sb, group, gdp);
- }
- ext4_unlock_group(sb, group);
- brelse(block_bitmap_bh);
-
- if (err) {
- ext4_std_error(sb, err);
- goto out;
- }
- }
+ err = ext4_might_init_block_bitmap(handle, sb, group, gdp);
+ if (err)
+ goto out;
/* Update the relevant bg descriptor fields */
if (ext4_has_group_desc_csum(sb)) {
--
2.43.7
^ permalink raw reply related
* [PATCH RFC 15/17] ext4: use fast incremental CRC update in __ext4_new_inode()
From: Baokun Li @ 2026-05-08 12:15 UTC (permalink / raw)
To: linux-ext4
Cc: linux-crypto, ebiggers, ardb, tytso, adilger.kernel, jack,
yi.zhang, ojaswin, ritesh.list, Baokun Li
In-Reply-To: <20260508121539.4174601-1-libaokun@linux.alibaba.com>
Merge the bitmap modification and group descriptor update into a single
group lock acquisition in __ext4_new_inode(). Previously the bitmap bit
was set under one lock/unlock pair, and the GDP fields (UNINIT,
itable_unused, free_inodes, dirs, csum) were updated under a separate
lock/unlock pair with a gap in between. Another thread could modify the
bitmap and update the checksum during that gap, making incremental CRC
incorrect.
Now the full sequence -- set bit, update free inodes, clear UNINIT,
update itable_unused, and compute checksum -- happens atomically under
the same ext4_lock_group(). The alloc_sem is acquired before the group
lock to maintain correct locking order with itable lazyinit.
Use ext4_inode_bitmap_csum_set_fast() for the normal path where the
stored checksum is valid. When EXT4_BG_INODE_UNINIT is set, fall back
to ext4_inode_bitmap_csum_set() for a full recalculation to establish
a correct baseline (mkfs leaves the checksum as zero for UNINIT groups).
Signed-off-by: Baokun Li <libaokun@linux.alibaba.com>
---
fs/ext4/ialloc.c | 129 +++++++++++++++++++++++------------------------
1 file changed, 63 insertions(+), 66 deletions(-)
diff --git a/fs/ext4/ialloc.c b/fs/ext4/ialloc.c
index 8b75b331b26e..9dd1cdb367ba 100644
--- a/fs/ext4/ialloc.c
+++ b/fs/ext4/ialloc.c
@@ -1135,7 +1135,25 @@ struct inode *__ext4_new_inode(struct mnt_idmap *idmap,
ext4_std_error(sb, err);
goto out;
}
+
+ BUFFER_TRACE(group_desc_bh, "get_write_access");
+ err = ext4_journal_get_write_access(handle, sb, group_desc_bh,
+ EXT4_JTR_NONE);
+ if (err) {
+ ext4_std_error(sb, err);
+ goto out;
+ }
+
+ /* We may have to initialize the block bitmap if it isn't already */
+ err = ext4_might_init_block_bitmap(handle, sb, group, gdp);
+ if (err)
+ goto out;
+
+ if (ext4_has_group_desc_csum(sb) &&
+ !(sbi->s_mount_state & EXT4_FC_REPLAY))
+ down_read(&grp->alloc_sem);
ext4_lock_group(sb, group);
+
ret2 = ext4_test_and_set_bit(bit, inode_bitmap_bh->b_data);
if (ret2) {
/* Someone already took the bit. Repeat the search
@@ -1147,9 +1165,54 @@ struct inode *__ext4_new_inode(struct mnt_idmap *idmap,
ret2 = 0;
} else {
ret2 = 1; /* we didn't grab the inode */
+ goto unlock_group;
+ }
+ }
+
+ /* Update the relevant bg descriptor fields */
+ ext4_free_inodes_set(sb, gdp,
+ ext4_free_inodes_count(sb, gdp) - 1);
+ if (S_ISDIR(mode)) {
+ ext4_used_dirs_set(sb, gdp,
+ ext4_used_dirs_count(sb, gdp) + 1);
+ if (sbi->s_log_groups_per_flex) {
+ ext4_group_t f = ext4_flex_group(sbi, group);
+ atomic_inc(&sbi_array_rcu_deref(sbi, s_flex_groups,
+ f)->used_dirs);
+ }
+ }
+
+ if (ext4_has_group_desc_csum(sb)) {
+ bool fast_crc = true;
+ int free = EXT4_INODES_PER_GROUP(sb) -
+ ext4_itable_unused_count(sb, gdp);
+
+ if (gdp->bg_flags & cpu_to_le16(EXT4_BG_INODE_UNINIT)) {
+ gdp->bg_flags &= cpu_to_le16(~EXT4_BG_INODE_UNINIT);
+ free = 0;
+ /* Incremental CRC needs a valid csum baseline */
+ fast_crc = false;
}
+ /*
+ * Check the relative inode number against the
+ * last used relative inode number in this group.
+ * If it is greater we need to update the
+ * bg_itable_unused count.
+ */
+ if (bit >= free)
+ ext4_itable_unused_set(sb, gdp,
+ EXT4_INODES_PER_GROUP(sb) - bit - 1);
+ if (fast_crc)
+ ext4_inode_bitmap_csum_set_fast(sb, gdp, bit);
+ else
+ ext4_inode_bitmap_csum_set(sb, gdp, inode_bitmap_bh);
+ ext4_group_desc_csum_set(sb, group, gdp);
}
+unlock_group:
ext4_unlock_group(sb, group);
+ if (ext4_has_group_desc_csum(sb) &&
+ !(sbi->s_mount_state & EXT4_FC_REPLAY))
+ up_read(&grp->alloc_sem);
if (!ret2)
goto got; /* we grabbed the inode! */
@@ -1168,72 +1231,6 @@ struct inode *__ext4_new_inode(struct mnt_idmap *idmap,
goto out;
}
- BUFFER_TRACE(group_desc_bh, "get_write_access");
- err = ext4_journal_get_write_access(handle, sb, group_desc_bh,
- EXT4_JTR_NONE);
- if (err) {
- ext4_std_error(sb, err);
- goto out;
- }
-
- /* We may have to initialize the block bitmap if it isn't already */
- err = ext4_might_init_block_bitmap(handle, sb, group, gdp);
- if (err)
- goto out;
-
- /* Update the relevant bg descriptor fields */
- if (ext4_has_group_desc_csum(sb)) {
- int free;
- struct ext4_group_info *grp = NULL;
-
- if (!(sbi->s_mount_state & EXT4_FC_REPLAY)) {
- grp = ext4_get_group_info(sb, group);
- if (!grp) {
- err = -EFSCORRUPTED;
- goto out;
- }
- down_read(&grp->alloc_sem); /*
- * protect vs itable
- * lazyinit
- */
- }
- ext4_lock_group(sb, group); /* while we modify the bg desc */
- free = EXT4_INODES_PER_GROUP(sb) -
- ext4_itable_unused_count(sb, gdp);
- if (gdp->bg_flags & cpu_to_le16(EXT4_BG_INODE_UNINIT)) {
- gdp->bg_flags &= cpu_to_le16(~EXT4_BG_INODE_UNINIT);
- free = 0;
- }
- /*
- * Check the relative inode number against the last used
- * relative inode number in this group. if it is greater
- * we need to update the bg_itable_unused count
- */
- if (bit >= free)
- ext4_itable_unused_set(sb, gdp,
- (EXT4_INODES_PER_GROUP(sb) - bit - 1));
- if (!(sbi->s_mount_state & EXT4_FC_REPLAY))
- up_read(&grp->alloc_sem);
- } else {
- ext4_lock_group(sb, group);
- }
-
- ext4_free_inodes_set(sb, gdp, ext4_free_inodes_count(sb, gdp) - 1);
- if (S_ISDIR(mode)) {
- ext4_used_dirs_set(sb, gdp, ext4_used_dirs_count(sb, gdp) + 1);
- if (sbi->s_log_groups_per_flex) {
- ext4_group_t f = ext4_flex_group(sbi, group);
-
- atomic_inc(&sbi_array_rcu_deref(sbi, s_flex_groups,
- f)->used_dirs);
- }
- }
- if (ext4_has_group_desc_csum(sb)) {
- ext4_inode_bitmap_csum_set(sb, gdp, inode_bitmap_bh);
- ext4_group_desc_csum_set(sb, group, gdp);
- }
- ext4_unlock_group(sb, group);
-
BUFFER_TRACE(group_desc_bh, "call ext4_handle_dirty_metadata");
err = ext4_handle_dirty_metadata(handle, NULL, group_desc_bh);
if (err) {
--
2.43.7
^ permalink raw reply related
* Re: [PATCH 6.6.y] ext4: validate p_idx bounds in ext4_ext_correct_indexes
From: Sasha Levin @ 2026-05-08 21:11 UTC (permalink / raw)
To: gregkh, stable, tejas.bharambe
Cc: Sasha Levin, patches, linux-kernel, tytso, adilger.kernel,
linux-ext4, Jianqiang kang
In-Reply-To: <20260508065845.3031006-1-jianqkang@sina.cn>
> Subject: [PATCH 6.6.y] ext4: validate p_idx bounds in ext4_ext_correct_indexes
>
> commit 2acb5c12ebd860f30e4faf67e6cc8c44ddfe5fe8 upstream.
Now queued for 6.6 and 6.1, thanks.
--
Thanks,
Sasha
^ permalink raw reply
* [syzbot] Monthly ext4 report (May 2026)
From: syzbot @ 2026-05-09 12:32 UTC (permalink / raw)
To: linux-ext4, linux-kernel, syzkaller-bugs
Hello ext4 maintainers/developers,
This is a 31-day syzbot report for the ext4 subsystem.
All related reports/information can be found at:
https://syzkaller.appspot.com/upstream/s/ext4
During the period, 4 new issues were detected and 0 were fixed.
In total, 50 issues are still open and 175 have already been fixed.
Some of the still happening issues:
Ref Crashes Repro Title
<1> 7887 Yes WARNING in ext4_xattr_inode_update_ref (2)
https://syzkaller.appspot.com/bug?extid=76916a45d2294b551fd9
<2> 7431 Yes KASAN: out-of-bounds Read in ext4_xattr_set_entry
https://syzkaller.appspot.com/bug?extid=f792df426ff0f5ceb8d1
<3> 6043 Yes possible deadlock in ext4_writepages (2)
https://syzkaller.appspot.com/bug?extid=eb5b4ef634a018917f3c
<4> 3089 Yes kernel BUG in ext4_do_writepages
https://syzkaller.appspot.com/bug?extid=d1da16f03614058fdc48
<5> 2985 Yes possible deadlock in ext4_destroy_inline_data (2)
https://syzkaller.appspot.com/bug?extid=bb2455d02bda0b5701e3
<6> 2947 Yes INFO: task hung in sync_inodes_sb (5)
https://syzkaller.appspot.com/bug?extid=30476ec1b6dc84471133
<7> 996 Yes WARNING in ext4_xattr_inode_lookup_create
https://syzkaller.appspot.com/bug?extid=fe42a669c87e4a980051
<8> 723 Yes possible deadlock in do_writepages (2)
https://syzkaller.appspot.com/bug?extid=756f498a88797cda9299
<9> 429 Yes possible deadlock in ext4_evict_inode (5)
https://syzkaller.appspot.com/bug?extid=212e8f62790f8e0bc63b
<10> 290 Yes kernel BUG in ext4_mb_use_inode_pa (2)
https://syzkaller.appspot.com/bug?extid=d79019213609e7056a19
---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller@googlegroups.com.
To disable reminders for individual bugs, reply with the following command:
#syz set <Ref> no-reminders
To change bug's subsystems, reply with:
#syz set <Ref> subsystems: new-subsystem
You may send multiple commands in a single email message.
^ permalink raw reply
* Re: [PATCH 1/2] ext4/064 encryption + casefold feature combination WITHOUT dirdata
From: Zorro Lang @ 2026-05-09 17:05 UTC (permalink / raw)
To: Artem Blagodarenko; +Cc: fstests, adilger.kernel, tytso, linux-ext4
In-Reply-To: <20260419185209.4526-1-ablagodarenko@ddn.com>
On Sun, Apr 19, 2026 at 02:52:08PM -0400, Artem Blagodarenko wrote:
> From: Artem Blagodarenko <artem.blagodarenko@gmail.com>
>
> This test verifies that files created in directories with both
> encryption and case-insensitive (casefold) attributes work correctly.
> See ext4/065 for the same test WITH dirdata feature enabled.
>
> Signed-off-by: Artem Blagodarenko <artem.blagodarenko@gmail.com>
> ---
> tests/ext4/064 | 154 +++++++++++++++++++++++++++++++++++++++++++++
> tests/ext4/064.out | 17 +++++
> 2 files changed, 171 insertions(+)
> create mode 100755 tests/ext4/064
> create mode 100644 tests/ext4/064.out
>
> diff --git a/tests/ext4/064 b/tests/ext4/064
> new file mode 100755
> index 00000000..6ad865a9
> --- /dev/null
> +++ b/tests/ext4/064
> @@ -0,0 +1,154 @@
> +#! /bin/bash
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# Copyright (c) 2026 The Lustre Collective. All Rights Reserved.
> +# Author: Artem Blagodarenko <ablagodarenko@thelustrecollective.com>
> +#
> +# FS QA Test ext4/064
> +#
> +# Test ext4 encryption + casefold feature combination WITHOUT dirdata.
> +# This test verifies that files created in directories with both
> +# encryption and case-insensitive (casefold) attributes work correctly.
> +# See ext4/065 for the same test WITH dirdata feature enabled.
> +#
> +. ./common/preamble
> +_begin_fstest auto quick encrypt casefold
> +
> +# get standard environment and checks
> +. ./common/filter
> +. ./common/encrypt
> +. ./common/casefold
> +. ./common/attr
> +
> +_exclude_fs ext2
> +_exclude_fs ext3
> +
> +_require_scratch_nocheck
> +_require_encrypted_casefold
Do you want to write a new helper names _require_encrypted_casefold? Or do you
mean _require_scratch_casefold at here?
CC ext4-list to get more review points.
Thanks,
Zorro
> +_require_command "$CHATTR_PROG" chattr
> +_require_command "$LSATTR_PROG" lsattr
> +_require_xfs_io_command "set_encpolicy"
> +_require_xfs_io_command "add_enckey"
> +
> +# Helper to add a v2 encryption key and set policy on a directory
> +_setup_encrypted_casefold_dir()
> +{
> + local dir=$1
> + local raw_key=$(_generate_raw_encryption_key)
> + local keyspec=$(_add_enckey $SCRATCH_MNT "$raw_key" | awk '{print $NF}')
> + _set_encpolicy $dir $keyspec
> + _casefold_set_attr $dir
> + echo $keyspec
> +}
> +
> +# Create a filesystem with both encrypt and casefold features
> +_scratch_mkfs -O encrypt,casefold &>>$seqres.full
> +_scratch_mount
> +
> +# Test 1: Create an encrypted + casefolded directory and verify lookups work
> +echo "Test 1: Basic encrypted casefold lookup"
> +mkdir $SCRATCH_MNT/test1
> +_setup_encrypted_casefold_dir $SCRATCH_MNT/test1 > /dev/null
> +
> +# Create file with lowercase, lookup with uppercase
> +echo "hello" > $SCRATCH_MNT/test1/testfile.txt
> +if [ -f "$SCRATCH_MNT/test1/TESTFILE.TXT" ]; then
> + echo "Case-insensitive lookup works in encrypted dir"
> +else
> + echo "FAIL: Case-insensitive lookup failed in encrypted dir"
> +fi
> +
> +# Verify the exact name on disk is preserved
> +if _casefold_check_exact_name "$SCRATCH_MNT/test1" "testfile.txt"; then
> + echo "Original filename preserved"
> +else
> + echo "FAIL: Original filename not preserved"
> +fi
> +
> +# Test 2: Create files with different case variations
> +echo "Test 2: Conflicting names in encrypted casefold dir"
> +mkdir $SCRATCH_MNT/test2
> +_setup_encrypted_casefold_dir $SCRATCH_MNT/test2 > /dev/null
> +
> +echo "first" > $SCRATCH_MNT/test2/MyFile.txt
> +# This should fail or overwrite since "MYFILE.TXT" is equivalent
> +echo "second" > $SCRATCH_MNT/test2/MYFILE.TXT 2>/dev/null
> +content=$(cat $SCRATCH_MNT/test2/myfile.txt)
> +echo "Content after writes: $content"
> +
> +# Test 3: Unicode normalization in encrypted casefold dir
> +echo "Test 3: Unicode in encrypted casefold dir"
> +mkdir $SCRATCH_MNT/test3
> +_setup_encrypted_casefold_dir $SCRATCH_MNT/test3 > /dev/null
> +
> +# Test with UTF-8 characters
> +fr_file1=$(echo -e "cafe\xcc\x81.txt")
> +fr_file2=$(echo -e "caf\xc3\xa9.txt")
> +echo "french" > "$SCRATCH_MNT/test3/$fr_file1"
> +if [ -f "$SCRATCH_MNT/test3/$fr_file2" ]; then
> + echo "Unicode normalization works in encrypted dir"
> +else
> + echo "FAIL: Unicode normalization failed in encrypted dir"
> +fi
> +
> +# Test 4: Directory operations in encrypted casefold dir
> +echo "Test 4: Directory operations in encrypted casefold dir"
> +mkdir $SCRATCH_MNT/test4
> +_setup_encrypted_casefold_dir $SCRATCH_MNT/test4 > /dev/null
> +
> +mkdir $SCRATCH_MNT/test4/SubDir
> +if [ -d "$SCRATCH_MNT/test4/SUBDIR" ]; then
> + echo "Directory case-insensitive lookup works"
> +else
> + echo "FAIL: Directory case-insensitive lookup failed"
> +fi
> +
> +# Test 5: Verify inheritance of casefold+encryption in subdirectories
> +echo "Test 5: Inheritance of attributes"
> +mkdir $SCRATCH_MNT/test5
> +_setup_encrypted_casefold_dir $SCRATCH_MNT/test5 > /dev/null
> +
> +mkdir $SCRATCH_MNT/test5/child
> +echo "data" > $SCRATCH_MNT/test5/child/file.txt
> +if [ -f "$SCRATCH_MNT/test5/CHILD/FILE.TXT" ]; then
> + echo "Attributes inherited correctly"
> +else
> + echo "FAIL: Attributes not inherited"
> +fi
> +
> +# Test 6: Remove and recreate with different case
> +echo "Test 6: Remove and recreate with different case"
> +mkdir $SCRATCH_MNT/test6
> +_setup_encrypted_casefold_dir $SCRATCH_MNT/test6 > /dev/null
> +
> +echo "original" > $SCRATCH_MNT/test6/RemoveMe.txt
> +rm $SCRATCH_MNT/test6/REMOVEME.TXT
> +echo "recreated" > $SCRATCH_MNT/test6/REMOVEME.TXT
> +if _casefold_check_exact_name "$SCRATCH_MNT/test6" "REMOVEME.TXT"; then
> + echo "Recreated file has new case"
> +else
> + echo "FAIL: Recreated file case incorrect"
> +fi
> +
> +# Test 7: Hard links in encrypted casefold dir
> +echo "Test 7: Hard links in encrypted casefold dir"
> +mkdir $SCRATCH_MNT/test7
> +_setup_encrypted_casefold_dir $SCRATCH_MNT/test7 > /dev/null
> +
> +echo "linkdata" > $SCRATCH_MNT/test7/original.txt
> +ln $SCRATCH_MNT/test7/original.txt $SCRATCH_MNT/test7/hardlink.txt
> +if [ -f "$SCRATCH_MNT/test7/HARDLINK.TXT" ]; then
> + echo "Hard link case-insensitive lookup works"
> +else
> + echo "FAIL: Hard link case-insensitive lookup failed"
> +fi
> +
> +# Cleanup and verify filesystem
> +_scratch_unmount
> +_check_scratch_fs
> +
> +echo "Encrypted casefold tests completed"
> +
> +# success, all done
> +status=0
> +exit
> diff --git a/tests/ext4/064.out b/tests/ext4/064.out
> new file mode 100644
> index 00000000..0197e51e
> --- /dev/null
> +++ b/tests/ext4/064.out
> @@ -0,0 +1,17 @@
> +QA output created by 064
> +Test 1: Basic encrypted casefold lookup
> +Case-insensitive lookup works in encrypted dir
> +Original filename preserved
> +Test 2: Conflicting names in encrypted casefold dir
> +Content after writes: second
> +Test 3: Unicode in encrypted casefold dir
> +Unicode normalization works in encrypted dir
> +Test 4: Directory operations in encrypted casefold dir
> +Directory case-insensitive lookup works
> +Test 5: Inheritance of attributes
> +Attributes inherited correctly
> +Test 6: Remove and recreate with different case
> +Recreated file has new case
> +Test 7: Hard links in encrypted casefold dir
> +Hard link case-insensitive lookup works
> +Encrypted casefold tests completed
> --
> 2.43.5
>
>
^ permalink raw reply
* Re: [f2fs-dev] [GIT PULL] fsverity updates for 7.0
From: patchwork-bot+f2fs @ 2026-05-11 1:41 UTC (permalink / raw)
To: Eric Biggers
Cc: torvalds, fsverity, tytso, djwong, aalbersh, linux-kernel,
linux-f2fs-devel, dsterba, linux-fsdevel, jack, linux-ext4, hch,
linux-btrfs
In-Reply-To: <20260212012652.GA8885@sol>
Hello:
This pull request was applied to jaegeuk/f2fs.git (dev)
by Linus Torvalds <torvalds@linux-foundation.org>:
On Wed, 11 Feb 2026 17:26:52 -0800 you wrote:
> The following changes since commit 63804fed149a6750ffd28610c5c1c98cce6bd377:
>
> Linux 6.19-rc7 (2026-01-25 14:11:24 -0800)
>
> are available in the Git repository at:
>
> git://git.kernel.org/pub/scm/fs/fsverity/linux.git tags/fsverity-for-linus
>
> [...]
Here is the summary with links:
- [f2fs-dev,GIT,PULL] fsverity updates for 7.0
https://git.kernel.org/jaegeuk/f2fs/c/1bfaee9d3351
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply
* [PATCH v4 02/23] ext4: factor out ext4_truncate_[up|down]()
From: Zhang Yi @ 2026-05-11 7:23 UTC (permalink / raw)
To: linux-ext4, linux-fsdevel
Cc: linux-kernel, tytso, adilger.kernel, libaokun, jack, ojaswin,
ritesh.list, djwong, hch, yi.zhang, yi.zhang, yizhang089,
yangerkun, yukuai
In-Reply-To: <20260511072344.191271-1-yi.zhang@huaweicloud.com>
From: Zhang Yi <yi.zhang@huawei.com>
Refactor ext4_setattr() by introducing two helper functions,
ext4_truncate_up() and ext4_truncate_down(), to handle size changes. The
current ATTR_SIZE processing consolidates checks for both shrinking and
non-shrinking cases, leading to cluttered code. Separating the
truncation paths improves readability.
Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
---
fs/ext4/inode.c | 199 +++++++++++++++++++++++++++---------------------
1 file changed, 112 insertions(+), 87 deletions(-)
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 0751dc55e94f..35e958f89bd5 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -5855,6 +5855,112 @@ static void ext4_wait_for_tail_page_commit(struct inode *inode)
}
}
+/*
+ * Set i_size and i_disksize to 'newsize'.
+ *
+ * Both i_rwsem and i_data_sem are required here to avoid races between
+ * generic append writeback and concurrent truncate that also modify
+ * i_size and i_disksize.
+ */
+static inline void ext4_set_inode_size(struct inode *inode, loff_t newsize)
+{
+ WARN_ON_ONCE(S_ISREG(inode->i_mode) && !inode_is_locked(inode));
+
+ down_write(&EXT4_I(inode)->i_data_sem);
+ i_size_write(inode, newsize);
+ EXT4_I(inode)->i_disksize = newsize;
+ up_write(&EXT4_I(inode)->i_data_sem);
+}
+
+static int ext4_truncate_up(struct inode *inode, loff_t oldsize, loff_t newsize)
+{
+ ext4_lblk_t old_lblk, new_lblk;
+ handle_t *handle;
+ int ret;
+
+ if (!IS_ALIGNED(oldsize | newsize, i_blocksize(inode))) {
+ ret = ext4_inode_attach_jinode(inode);
+ if (ret)
+ return ret;
+ }
+
+ inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
+ if (!IS_ALIGNED(oldsize, i_blocksize(inode))) {
+ ret = ext4_block_zero_eof(inode, oldsize, LLONG_MAX);
+ if (ret)
+ return ret;
+ }
+
+ handle = ext4_journal_start(inode, EXT4_HT_INODE, 3);
+ if (IS_ERR(handle))
+ return PTR_ERR(handle);
+
+ old_lblk = oldsize > 0 ? (oldsize - 1) >> inode->i_blkbits : 0;
+ new_lblk = newsize > 0 ? (newsize - 1) >> inode->i_blkbits : 0;
+ ext4_fc_track_range(handle, inode, old_lblk, new_lblk);
+
+ ext4_set_inode_size(inode, newsize);
+
+ ret = ext4_mark_inode_dirty(handle, inode);
+ ext4_journal_stop(handle);
+ if (ret)
+ return ret;
+ /*
+ * isize extend must be called outside an active handle due to
+ * the lock ordering of transaction start and folio lock in the
+ * iomap buffered I/O path (folio lock -> transaction start).
+ */
+ pagecache_isize_extended(inode, oldsize, newsize);
+ return 0;
+}
+
+static int ext4_truncate_down(struct inode *inode, loff_t oldsize,
+ loff_t newsize, int *orphan)
+{
+ ext4_lblk_t start_lblk;
+ handle_t *handle;
+ int ret;
+
+ /* Do not change i_size. */
+ if (newsize == oldsize)
+ goto truncate;
+
+ /* Shrink. */
+ handle = ext4_journal_start(inode, EXT4_HT_INODE, 3);
+ if (IS_ERR(handle))
+ return PTR_ERR(handle);
+
+ if (ext4_handle_valid(handle)) {
+ ret = ext4_orphan_add(handle, inode);
+ *orphan = 1;
+ if (ret) {
+ ext4_journal_stop(handle);
+ return ret;
+ }
+ }
+
+ start_lblk = newsize > 0 ? (newsize - 1) >> inode->i_blkbits : 0;
+ ext4_fc_track_range(handle, inode, start_lblk, EXT_MAX_BLOCKS - 1);
+
+ ext4_set_inode_size(inode, newsize);
+
+ ret = ext4_mark_inode_dirty(handle, inode);
+ ext4_journal_stop(handle);
+ if (ret)
+ return ret;
+
+ if (ext4_should_journal_data(inode))
+ ext4_wait_for_tail_page_commit(inode);
+truncate:
+ /*
+ * Truncate pagecache after we've waited for commit in data=journal
+ * mode to make pages freeable. Call ext4_truncate() even if
+ * i_size didn't change to truncatea possible preallocated blocks.
+ */
+ truncate_pagecache(inode, newsize);
+ return ext4_truncate(inode);
+}
+
/*
* ext4_setattr()
*
@@ -5951,7 +6057,6 @@ int ext4_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
}
if (attr->ia_valid & ATTR_SIZE) {
- handle_t *handle;
loff_t oldsize = inode->i_size;
int shrink = (attr->ia_size < inode->i_size);
@@ -6003,94 +6108,14 @@ int ext4_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
goto err_out;
}
- if (attr->ia_size != inode->i_size) {
- /* attach jbd2 jinode for EOF folio tail zeroing */
- if (attr->ia_size & (inode->i_sb->s_blocksize - 1) ||
- oldsize & (inode->i_sb->s_blocksize - 1)) {
- error = ext4_inode_attach_jinode(inode);
- if (error)
- goto out_mmap_sem;
- }
-
- /*
- * Update c/mtime and tail zero the EOF folio on
- * truncate up. ext4_truncate() handles the shrink case
- * below.
- */
- if (!shrink) {
- inode_set_mtime_to_ts(inode,
- inode_set_ctime_current(inode));
- if (oldsize & (inode->i_sb->s_blocksize - 1)) {
- error = ext4_block_zero_eof(inode,
- oldsize, LLONG_MAX);
- if (error)
- goto out_mmap_sem;
- }
- }
-
- handle = ext4_journal_start(inode, EXT4_HT_INODE, 3);
- if (IS_ERR(handle)) {
- error = PTR_ERR(handle);
- goto out_mmap_sem;
- }
- if (ext4_handle_valid(handle) && shrink) {
- error = ext4_orphan_add(handle, inode);
- orphan = 1;
- if (error)
- goto out_handle;
- }
-
- if (shrink)
- ext4_fc_track_range(handle, inode,
- (attr->ia_size > 0 ? attr->ia_size - 1 : 0) >>
- inode->i_sb->s_blocksize_bits,
- EXT_MAX_BLOCKS - 1);
- else
- ext4_fc_track_range(
- handle, inode,
- (oldsize > 0 ? oldsize - 1 : oldsize) >>
- inode->i_sb->s_blocksize_bits,
- (attr->ia_size > 0 ? attr->ia_size - 1 : 0) >>
- inode->i_sb->s_blocksize_bits);
-
- /*
- * We have to update i_size under i_data_sem together
- * with i_disksize to avoid races with writeback code
- * updating disksize in mpage_map_and_submit_extent().
- */
- down_write(&EXT4_I(inode)->i_data_sem);
- i_size_write(inode, attr->ia_size);
- EXT4_I(inode)->i_disksize = attr->ia_size;
- up_write(&EXT4_I(inode)->i_data_sem);
-
- error = ext4_mark_inode_dirty(handle, inode);
-out_handle:
- ext4_journal_stop(handle);
- if (error)
- goto out_mmap_sem;
- if (!shrink) {
- pagecache_isize_extended(inode, oldsize,
- inode->i_size);
- } else if (ext4_should_journal_data(inode)) {
- ext4_wait_for_tail_page_commit(inode);
- }
+ if (attr->ia_size > oldsize)
+ error = ext4_truncate_up(inode, oldsize, attr->ia_size);
+ else {
+ /* Shrink or do not change i_size. */
+ error = ext4_truncate_down(inode, oldsize,
+ attr->ia_size, &orphan);
}
- /*
- * Truncate pagecache after we've waited for commit
- * in data=journal mode to make pages freeable.
- */
- truncate_pagecache(inode, inode->i_size);
- /*
- * Call ext4_truncate() even if i_size didn't change to
- * truncate possible preallocated blocks.
- */
- if (attr->ia_size <= oldsize) {
- rc = ext4_truncate(inode);
- if (rc)
- error = rc;
- }
-out_mmap_sem:
filemap_invalidate_unlock(inode->i_mapping);
}
--
2.52.0
^ permalink raw reply related
* [PATCH v4 16/23] ext4: disable online defrag when inode using iomap buffered I/O path
From: Zhang Yi @ 2026-05-11 7:23 UTC (permalink / raw)
To: linux-ext4, linux-fsdevel
Cc: linux-kernel, tytso, adilger.kernel, libaokun, jack, ojaswin,
ritesh.list, djwong, hch, yi.zhang, yi.zhang, yizhang089,
yangerkun, yukuai
In-Reply-To: <20260511072344.191271-1-yi.zhang@huaweicloud.com>
From: Zhang Yi <yi.zhang@huawei.com>
Online defragmentation does not currently support inodes using the
iomap buffered I/O path. The existing implementation relies on
buffer_head for sub-folio block management and data=ordered mode for
data consistency, both of which are incompatible with the iomap path.
Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
---
fs/ext4/move_extent.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/fs/ext4/move_extent.c b/fs/ext4/move_extent.c
index 3329b7ad5dbd..f707a1096544 100644
--- a/fs/ext4/move_extent.c
+++ b/fs/ext4/move_extent.c
@@ -476,6 +476,17 @@ static int mext_check_validity(struct inode *orig_inode,
return -EOPNOTSUPP;
}
+ /*
+ * TODO: support online defrag for inodes that using the buffered
+ * I/O iomap path.
+ */
+ if (ext4_inode_buffered_iomap(orig_inode) ||
+ ext4_inode_buffered_iomap(donor_inode)) {
+ ext4_msg(sb, KERN_ERR,
+ "Online defrag not supported for inode with iomap buffered IO path");
+ return -EOPNOTSUPP;
+ }
+
if (donor_inode->i_mode & (S_ISUID|S_ISGID)) {
ext4_debug("ext4 move extent: suid or sgid is set to donor file [ino:orig %llu, donor %llu]\n",
orig_inode->i_ino, donor_inode->i_ino);
--
2.52.0
^ permalink raw reply related
* [PATCH v4 13/23] iomap: fix incorrect did_zero setting in iomap_zero_iter()
From: Zhang Yi @ 2026-05-11 7:23 UTC (permalink / raw)
To: linux-ext4, linux-fsdevel
Cc: linux-kernel, tytso, adilger.kernel, libaokun, jack, ojaswin,
ritesh.list, djwong, hch, yi.zhang, yi.zhang, yizhang089,
yangerkun, yukuai
In-Reply-To: <20260511072344.191271-1-yi.zhang@huaweicloud.com>
From: Zhang Yi <yi.zhang@huawei.com>
The did_zero output parameter was unconditionally set after the loop,
which is incorrect. It should only be set when the zeroing operation
actually completes, not when IOMAP_F_STALE is set or when
IOMAP_F_FOLIO_BATCH is set but !folio causes the loop to break early,
or when iomap_iter_advance() returns an error.
This causes did_zero to be incorrectly set when zeroing a clean
unwritten extent because the loop exits early without actually zeroing
any data.
Fix it by using a local variable to track whether any folio was actually
zeroed, and only set did_zero after the loop if zeroing happened.
Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
This is cherry picked form:
https://lore.kernel.org/linux-fsdevel/20260310082250.3535486-1-yi.zhang@huaweicloud.com/
No changes.
fs/iomap/buffered-io.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/fs/iomap/buffered-io.c b/fs/iomap/buffered-io.c
index 876c2f507f58..27ab33edbdee 100644
--- a/fs/iomap/buffered-io.c
+++ b/fs/iomap/buffered-io.c
@@ -1542,6 +1542,7 @@ static int iomap_zero_iter(struct iomap_iter *iter, bool *did_zero,
const struct iomap_write_ops *write_ops)
{
u64 bytes = iomap_length(iter);
+ bool zeroed = false;
int status;
do {
@@ -1560,6 +1561,8 @@ static int iomap_zero_iter(struct iomap_iter *iter, bool *did_zero,
/* a NULL folio means we're done with a folio batch */
if (!folio) {
status = iomap_iter_advance_full(iter);
+ if (status)
+ return status;
break;
}
@@ -1570,6 +1573,7 @@ static int iomap_zero_iter(struct iomap_iter *iter, bool *did_zero,
bytes);
folio_zero_range(folio, offset, bytes);
+ zeroed = true;
folio_mark_accessed(folio);
ret = iomap_write_end(iter, bytes, bytes, folio);
@@ -1579,10 +1583,10 @@ static int iomap_zero_iter(struct iomap_iter *iter, bool *did_zero,
status = iomap_iter_advance(iter, bytes);
if (status)
- break;
+ return status;
} while ((bytes = iomap_length(iter)) > 0);
- if (did_zero)
+ if (did_zero && zeroed)
*did_zero = true;
return status;
}
--
2.52.0
^ permalink raw reply related
* [PATCH v4 01/23] ext4: simplify size updating in ext4_setattr()
From: Zhang Yi @ 2026-05-11 7:23 UTC (permalink / raw)
To: linux-ext4, linux-fsdevel
Cc: linux-kernel, tytso, adilger.kernel, libaokun, jack, ojaswin,
ritesh.list, djwong, hch, yi.zhang, yi.zhang, yizhang089,
yangerkun, yukuai
In-Reply-To: <20260511072344.191271-1-yi.zhang@huaweicloud.com>
From: Zhang Yi <yi.zhang@huawei.com>
The logic for updating the file size in ext4_setattr() is currently
somewhat messy. By directly entering the error-handling path after
failing to add an orphan inode, the unnecessary recovery process
involving old_disksize and the file size can be avoided.
Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
Reviewed-by: Jan Kara <jack@suse.cz>
---
fs/ext4/inode.c | 22 +++++++++-------------
1 file changed, 9 insertions(+), 13 deletions(-)
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index c2c2d6ac7f3d..0751dc55e94f 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -5953,7 +5953,6 @@ int ext4_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
if (attr->ia_valid & ATTR_SIZE) {
handle_t *handle;
loff_t oldsize = inode->i_size;
- loff_t old_disksize;
int shrink = (attr->ia_size < inode->i_size);
if (!(ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))) {
@@ -6037,6 +6036,8 @@ int ext4_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
if (ext4_handle_valid(handle) && shrink) {
error = ext4_orphan_add(handle, inode);
orphan = 1;
+ if (error)
+ goto out_handle;
}
if (shrink)
@@ -6052,23 +6053,18 @@ int ext4_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
(attr->ia_size > 0 ? attr->ia_size - 1 : 0) >>
inode->i_sb->s_blocksize_bits);
- down_write(&EXT4_I(inode)->i_data_sem);
- old_disksize = EXT4_I(inode)->i_disksize;
- EXT4_I(inode)->i_disksize = attr->ia_size;
-
/*
* We have to update i_size under i_data_sem together
* with i_disksize to avoid races with writeback code
- * running ext4_wb_update_i_disksize().
+ * updating disksize in mpage_map_and_submit_extent().
*/
- if (!error)
- i_size_write(inode, attr->ia_size);
- else
- EXT4_I(inode)->i_disksize = old_disksize;
+ down_write(&EXT4_I(inode)->i_data_sem);
+ i_size_write(inode, attr->ia_size);
+ EXT4_I(inode)->i_disksize = attr->ia_size;
up_write(&EXT4_I(inode)->i_data_sem);
- rc = ext4_mark_inode_dirty(handle, inode);
- if (!error)
- error = rc;
+
+ error = ext4_mark_inode_dirty(handle, inode);
+out_handle:
ext4_journal_stop(handle);
if (error)
goto out_mmap_sem;
--
2.52.0
^ permalink raw reply related
* [PATCH v4 09/23] ext4: implement writeback path using iomap
From: Zhang Yi @ 2026-05-11 7:23 UTC (permalink / raw)
To: linux-ext4, linux-fsdevel
Cc: linux-kernel, tytso, adilger.kernel, libaokun, jack, ojaswin,
ritesh.list, djwong, hch, yi.zhang, yi.zhang, yizhang089,
yangerkun, yukuai
In-Reply-To: <20260511072344.191271-1-yi.zhang@huaweicloud.com>
From: Zhang Yi <yi.zhang@huawei.com>
Add the iomap writeback path for ext4 buffered I/O. This introduces:
- ext4_iomap_writepages(): the main writeback entry point.
- ext4_writeback_ops: a new iomap_writeback_ops instance to handle
block mapping and I/O submission.
- A new end I/O worker for converting unwritten extents, updating file
size, and handling DATA_ERR_ABORT after I/O completion.
Core implementation details:
- ->writeback_range() callback
Calls ext4_iomap_map_writeback_range() to query the longest range of
existing mapped extents. For performance, when a block range is not
yet allocated, it allocates based on the writeback length and delalloc
extent length, rather than allocating for a single folio at a time.
The folio is then added to an iomap_ioend instance.
- ->writeback_submit() callback
Registers ext4_iomap_end_bio() as the end bio callback. This callback
schedules a worker to handle:
- Unwritten extent conversion.
- i_disksize update after data is written back.
- Journal abort on writeback I/O failure.
Key changes and considerations:
- Append write and unwritten extents
Since data=ordered mode is not used to prevent stale data exposure
during append writebacks, new blocks are always allocated as unwritten
extents (i.e. always enable dioread_nolock), and i_disksize update is
postponed until I/O completion. Additionally, the deadlock that the
reserve handle was expected to resolve does not occur anymore.
Therefore, the end I/O worker can start a normal journal handle
instead of a reserve handle when converting unwritten extents.
- Lock ordering
The ->writeback_range() callback runs under the folio lock, requiring
the journal handle to be started under that same lock. This reverses
the order compared to the buffer_head writeback path. The lock ordering
documentation in super.c has been updated accordingly.
Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
---
fs/ext4/ext4.h | 4 +
fs/ext4/inode.c | 208 +++++++++++++++++++++++++++++++++++++++++-
fs/ext4/page-io.c | 126 +++++++++++++++++++++++++
fs/ext4/super.c | 7 +-
fs/iomap/ioend.c | 3 +-
include/linux/iomap.h | 1 +
6 files changed, 346 insertions(+), 3 deletions(-)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 4832e7f7db82..078feda47e36 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -1173,6 +1173,8 @@ struct ext4_inode_info {
*/
struct list_head i_rsv_conversion_list;
struct work_struct i_rsv_conversion_work;
+ struct list_head i_iomap_ioend_list;
+ struct work_struct i_iomap_ioend_work;
/*
* Transactions that contain inode's metadata needed to complete
@@ -3870,6 +3872,8 @@ int ext4_bio_write_folio(struct ext4_io_submit *io, struct folio *page,
size_t len);
extern struct ext4_io_end_vec *ext4_alloc_io_end_vec(ext4_io_end_t *io_end);
extern struct ext4_io_end_vec *ext4_last_io_end_vec(ext4_io_end_t *io_end);
+extern void ext4_iomap_end_io(struct work_struct *work);
+extern void ext4_iomap_end_bio(struct bio *bio);
/* mmp.c */
extern int ext4_multi_mount_protect(struct super_block *, ext4_fsblk_t);
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 1ae7d3f4a1c8..a80195bd6f20 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -44,6 +44,7 @@
#include <linux/iversion.h>
#include "ext4_jbd2.h"
+#include "ext4_extents.h"
#include "xattr.h"
#include "acl.h"
#include "truncate.h"
@@ -4120,10 +4121,215 @@ static void ext4_iomap_readahead(struct readahead_control *rac)
iomap_bio_readahead(rac, &ext4_iomap_buffered_read_ops);
}
+static int ext4_iomap_map_one_extent(struct inode *inode,
+ struct ext4_map_blocks *map)
+{
+ struct extent_status es;
+ handle_t *handle = NULL;
+ int credits, map_flags;
+ int retval;
+
+ credits = ext4_chunk_trans_blocks(inode, map->m_len);
+ handle = ext4_journal_start(inode, EXT4_HT_WRITE_PAGE, credits);
+ if (IS_ERR(handle))
+ return PTR_ERR(handle);
+
+ map->m_flags = 0;
+ /*
+ * It is necessary to look up extent and map blocks under i_data_sem
+ * in write mode, otherwise, the delalloc extent may become stale
+ * during concurrent truncate operations.
+ */
+ ext4_fc_track_inode(handle, inode);
+ down_write(&EXT4_I(inode)->i_data_sem);
+ if (ext4_es_lookup_extent(inode, map->m_lblk, NULL, &es, &map->m_seq)) {
+ retval = es.es_len - (map->m_lblk - es.es_lblk);
+ map->m_len = min_t(unsigned int, retval, map->m_len);
+
+ if (ext4_es_is_delayed(&es)) {
+ map->m_flags |= EXT4_MAP_DELAYED;
+ trace_ext4_da_write_pages_extent(inode, map);
+ /*
+ * Call ext4_map_create_blocks() to allocate any
+ * delayed allocation blocks. It is possible that
+ * we're going to need more metadata blocks, however
+ * we must not fail because we're in writeback and
+ * there is nothing we can do so it might result in
+ * data loss. So use reserved blocks to allocate
+ * metadata if possible.
+ */
+ map_flags = EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT |
+ EXT4_GET_BLOCKS_METADATA_NOFAIL |
+ EXT4_EX_NOCACHE;
+
+ retval = ext4_map_create_blocks(handle, inode, map,
+ map_flags);
+ if (retval > 0)
+ ext4_fc_track_range(handle, inode, map->m_lblk,
+ map->m_lblk + map->m_len - 1);
+ goto out;
+ } else if (unlikely(ext4_es_is_hole(&es)))
+ goto out;
+
+ /* Found written or unwritten extent. */
+ map->m_pblk = ext4_es_pblock(&es) + map->m_lblk - es.es_lblk;
+ map->m_flags = ext4_es_is_written(&es) ?
+ EXT4_MAP_MAPPED : EXT4_MAP_UNWRITTEN;
+ goto out;
+ }
+
+ retval = ext4_map_query_blocks(handle, inode, map, EXT4_EX_NOCACHE);
+out:
+ up_write(&EXT4_I(inode)->i_data_sem);
+ ext4_journal_stop(handle);
+ return retval < 0 ? retval : 0;
+}
+
+static int ext4_iomap_map_writeback_range(struct iomap_writepage_ctx *wpc,
+ loff_t offset, unsigned int dirty_len)
+{
+ struct inode *inode = wpc->inode;
+ struct super_block *sb = inode->i_sb;
+ struct journal_s *journal = EXT4_SB(sb)->s_journal;
+ struct ext4_map_blocks map;
+ unsigned int blkbits = inode->i_blkbits;
+ unsigned int index = offset >> blkbits;
+ unsigned int blk_end, blk_len;
+ int ret;
+
+ ret = ext4_emergency_state(sb);
+ if (unlikely(ret))
+ return ret;
+
+ /* Check validity of the cached writeback mapping. */
+ if (offset >= wpc->iomap.offset &&
+ offset < wpc->iomap.offset + wpc->iomap.length &&
+ ext4_iomap_valid(inode, &wpc->iomap))
+ return 0;
+
+ blk_len = dirty_len >> blkbits;
+ blk_end = min_t(unsigned int, (wpc->wbc->range_end >> blkbits),
+ (UINT_MAX - 1));
+ if (blk_end > index + blk_len)
+ blk_len = blk_end - index + 1;
+
+retry:
+ map.m_lblk = index;
+ map.m_len = min_t(unsigned int, MAX_WRITEPAGES_EXTENT_LEN, blk_len);
+ ret = ext4_map_blocks(NULL, inode, &map,
+ EXT4_GET_BLOCKS_IO_SUBMIT | EXT4_EX_NOCACHE);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * The map is not a delalloc extent, it must either be a hole
+ * or an extent which have already been allocated.
+ */
+ if (!(map.m_flags & EXT4_MAP_DELAYED))
+ goto out;
+
+ /* Map one delalloc extent. */
+ ret = ext4_iomap_map_one_extent(inode, &map);
+ if (ret < 0) {
+ if (ext4_emergency_state(sb))
+ return ret;
+
+ /*
+ * Retry transient ENOSPC errors, if
+ * ext4_count_free_blocks() is non-zero, a commit
+ * should free up blocks.
+ */
+ if (ret == -ENOSPC && journal && ext4_count_free_clusters(sb)) {
+ jbd2_journal_force_commit_nested(journal);
+ goto retry;
+ }
+
+ ext4_msg(sb, KERN_CRIT,
+ "Delayed block allocation failed for inode %llu at logical offset %llu with max blocks %u with error %d",
+ inode->i_ino, (unsigned long long)map.m_lblk,
+ (unsigned int)map.m_len, -ret);
+ ext4_msg(sb, KERN_CRIT,
+ "This should not happen!! Data will be lost\n");
+ if (ret == -ENOSPC)
+ ext4_print_free_blocks(inode);
+ return ret;
+ }
+out:
+ ext4_set_iomap(inode, &wpc->iomap, &map, offset, dirty_len, 0);
+ return 0;
+}
+
+static void ext4_iomap_discard_folio(struct folio *folio, loff_t pos)
+{
+ struct inode *inode = folio->mapping->host;
+ loff_t length = folio_pos(folio) + folio_size(folio) - pos;
+
+ ext4_iomap_punch_delalloc(inode, pos, length, NULL);
+}
+
+static ssize_t ext4_iomap_writeback_range(struct iomap_writepage_ctx *wpc,
+ struct folio *folio, u64 offset,
+ unsigned int len, u64 end_pos)
+{
+ ssize_t ret;
+
+ ret = ext4_iomap_map_writeback_range(wpc, offset, len);
+ if (!ret)
+ ret = iomap_add_to_ioend(wpc, folio, offset, end_pos, len);
+ if (ret < 0)
+ ext4_iomap_discard_folio(folio, offset);
+ return ret;
+}
+
+static int ext4_iomap_writeback_submit(struct iomap_writepage_ctx *wpc,
+ int error)
+{
+ struct iomap_ioend *ioend = wpc->wb_ctx;
+ struct ext4_inode_info *ei = EXT4_I(ioend->io_inode);
+
+ /*
+ * After I/O completion, a worker needs to be scheduled when:
+ * 1) Unwritten extents require conversion.
+ * 2) The file size needs to be extended.
+ * 3) The journal needs to be aborted due to an I/O error.
+ */
+ if ((ioend->io_flags & IOMAP_IOEND_UNWRITTEN) ||
+ (ioend->io_offset + ioend->io_size > READ_ONCE(ei->i_disksize)) ||
+ test_opt(ioend->io_inode->i_sb, DATA_ERR_ABORT))
+ ioend->io_bio.bi_end_io = ext4_iomap_end_bio;
+
+ return iomap_ioend_writeback_submit(wpc, error);
+}
+
+static const struct iomap_writeback_ops ext4_writeback_ops = {
+ .writeback_range = ext4_iomap_writeback_range,
+ .writeback_submit = ext4_iomap_writeback_submit,
+};
+
static int ext4_iomap_writepages(struct address_space *mapping,
struct writeback_control *wbc)
{
- return 0;
+ struct inode *inode = mapping->host;
+ struct super_block *sb = inode->i_sb;
+ long nr = wbc->nr_to_write;
+ int alloc_ctx, ret;
+ struct iomap_writepage_ctx wpc = {
+ .inode = inode,
+ .wbc = wbc,
+ .ops = &ext4_writeback_ops,
+ };
+
+ ret = ext4_emergency_state(sb);
+ if (unlikely(ret))
+ return ret;
+
+ alloc_ctx = ext4_writepages_down_read(sb);
+ trace_ext4_writepages(inode, wbc);
+ ret = iomap_writepages(&wpc);
+ trace_ext4_writepages_result(inode, wbc, ret, nr - wbc->nr_to_write);
+ ext4_writepages_up_read(sb, alloc_ctx);
+
+ return ret;
}
/*
diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c
index dc82e7b57e75..3050c887329f 100644
--- a/fs/ext4/page-io.c
+++ b/fs/ext4/page-io.c
@@ -22,6 +22,7 @@
#include <linux/bio.h>
#include <linux/workqueue.h>
#include <linux/kernel.h>
+#include <linux/iomap.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/sched/mm.h>
@@ -611,3 +612,128 @@ int ext4_bio_write_folio(struct ext4_io_submit *io, struct folio *folio,
return 0;
}
+
+static int ext4_iomap_wb_update_disksize(handle_t *handle, struct inode *inode,
+ loff_t end)
+{
+ loff_t new_disksize = end;
+ struct ext4_inode_info *ei = EXT4_I(inode);
+ int ret;
+
+ if (new_disksize <= READ_ONCE(ei->i_disksize))
+ return 0;
+
+ /*
+ * Update on-disk size after IO is completed. Races with truncate
+ * are avoided by checking i_size under i_data_sem.
+ */
+ down_write(&ei->i_data_sem);
+ new_disksize = min(new_disksize, i_size_read(inode));
+ if (new_disksize > ei->i_disksize)
+ ei->i_disksize = new_disksize;
+ up_write(&ei->i_data_sem);
+ ret = ext4_mark_inode_dirty(handle, inode);
+ if (ret)
+ EXT4_ERROR_INODE_ERR(inode, -ret, "Failed to mark inode dirty");
+
+ return ret;
+}
+
+static void ext4_iomap_finish_ioend(struct iomap_ioend *ioend)
+{
+ struct inode *inode = ioend->io_inode;
+ struct super_block *sb = inode->i_sb;
+ loff_t pos = ioend->io_offset;
+ size_t size = ioend->io_size;
+ handle_t *handle;
+ int credits;
+ int ret, err;
+
+ ret = blk_status_to_errno(ioend->io_bio.bi_status);
+ if (unlikely(ret)) {
+ if (test_opt(sb, DATA_ERR_ABORT) && !ext4_emergency_state(sb))
+ jbd2_journal_abort(EXT4_SB(sb)->s_journal, ret);
+ goto out;
+ }
+
+ /* We may need to convert one extent and dirty the inode. */
+ credits = ext4_chunk_trans_blocks(inode,
+ EXT4_MAX_BLOCKS(size, pos, inode->i_blkbits));
+ handle = ext4_journal_start(inode, EXT4_HT_EXT_CONVERT, credits);
+ if (IS_ERR(handle)) {
+ ret = PTR_ERR(handle);
+ goto out_err;
+ }
+
+ if (ioend->io_flags & IOMAP_IOEND_UNWRITTEN) {
+ ret = ext4_convert_unwritten_extents(handle, inode, pos, size);
+ if (ret)
+ goto out_journal;
+ }
+
+ ret = ext4_iomap_wb_update_disksize(handle, inode, pos + size);
+out_journal:
+ err = ext4_journal_stop(handle);
+ if (!ret)
+ ret = err;
+out_err:
+ if (ret < 0 && !ext4_emergency_state(sb)) {
+ ext4_msg(sb, KERN_EMERG,
+ "failed to convert unwritten extents to written extents or update inode size -- potential data loss! (inode %llu, error %d)",
+ inode->i_ino, ret);
+ }
+out:
+ iomap_finish_ioends(ioend, ret);
+}
+
+/*
+ * Work on buffered iomap completed IO, to convert unwritten extents to
+ * mapped extents
+ */
+void ext4_iomap_end_io(struct work_struct *work)
+{
+ struct ext4_inode_info *ei = container_of(work, struct ext4_inode_info,
+ i_iomap_ioend_work);
+ struct iomap_ioend *ioend;
+ struct list_head ioend_list;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ei->i_completed_io_lock, flags);
+ list_replace_init(&ei->i_iomap_ioend_list, &ioend_list);
+ spin_unlock_irqrestore(&ei->i_completed_io_lock, flags);
+
+ iomap_sort_ioends(&ioend_list);
+ while (!list_empty(&ioend_list)) {
+ ioend = list_entry(ioend_list.next, struct iomap_ioend, io_list);
+ list_del_init(&ioend->io_list);
+ iomap_ioend_try_merge(ioend, &ioend_list);
+ ext4_iomap_finish_ioend(ioend);
+ }
+}
+
+void ext4_iomap_end_bio(struct bio *bio)
+{
+ struct iomap_ioend *ioend = iomap_ioend_from_bio(bio);
+ struct inode *inode = ioend->io_inode;
+ struct ext4_inode_info *ei = EXT4_I(inode);
+ struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
+ unsigned long flags;
+
+ /* Needs to convert unwritten extents or update the i_disksize. */
+ if ((ioend->io_flags & IOMAP_IOEND_UNWRITTEN) ||
+ ioend->io_offset + ioend->io_size > READ_ONCE(ei->i_disksize))
+ goto defer;
+
+ /* Needs to abort the journal on data_err=abort. */
+ if (unlikely(ioend->io_bio.bi_status))
+ goto defer;
+
+ iomap_finish_ioend(ioend, 0);
+ return;
+defer:
+ spin_lock_irqsave(&ei->i_completed_io_lock, flags);
+ if (list_empty(&ei->i_iomap_ioend_list))
+ queue_work(sbi->rsv_conversion_wq, &ei->i_iomap_ioend_work);
+ list_add_tail(&ioend->io_list, &ei->i_iomap_ioend_list);
+ spin_unlock_irqrestore(&ei->i_completed_io_lock, flags);
+}
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index 9bc294b769db..51d87db53543 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -123,7 +123,10 @@ static const struct fs_parameter_spec ext4_param_specs[];
* sb_start_write -> i_mutex -> transaction start -> i_data_sem (rw)
*
* writepages:
- * transaction start -> page lock(s) -> i_data_sem (rw)
+ * - buffer_head path:
+ * transaction start -> folio lock(s) -> i_data_sem (rw)
+ * - iomap path:
+ * folio lock -> transaction start -> i_data_sem (rw)
*/
static const struct fs_context_operations ext4_context_ops = {
@@ -1428,10 +1431,12 @@ static struct inode *ext4_alloc_inode(struct super_block *sb)
#endif
ei->jinode = NULL;
INIT_LIST_HEAD(&ei->i_rsv_conversion_list);
+ INIT_LIST_HEAD(&ei->i_iomap_ioend_list);
spin_lock_init(&ei->i_completed_io_lock);
ei->i_sync_tid = 0;
ei->i_datasync_tid = 0;
INIT_WORK(&ei->i_rsv_conversion_work, ext4_end_io_rsv_work);
+ INIT_WORK(&ei->i_iomap_ioend_work, ext4_iomap_end_io);
ext4_fc_init_inode(&ei->vfs_inode);
spin_lock_init(&ei->i_fc_lock);
mmb_init(&ei->i_metadata_bhs, &ei->vfs_inode.i_data);
diff --git a/fs/iomap/ioend.c b/fs/iomap/ioend.c
index acf3cf98b23a..89bbd3027b81 100644
--- a/fs/iomap/ioend.c
+++ b/fs/iomap/ioend.c
@@ -305,7 +305,7 @@ ssize_t iomap_add_to_ioend(struct iomap_writepage_ctx *wpc, struct folio *folio,
}
EXPORT_SYMBOL_GPL(iomap_add_to_ioend);
-static u32 iomap_finish_ioend(struct iomap_ioend *ioend, int error)
+u32 iomap_finish_ioend(struct iomap_ioend *ioend, int error)
{
if (ioend->io_parent) {
struct bio *bio = &ioend->io_bio;
@@ -333,6 +333,7 @@ static u32 iomap_finish_ioend(struct iomap_ioend *ioend, int error)
return iomap_finish_ioend_buffered_read(ioend);
return iomap_finish_ioend_buffered_write(ioend);
}
+EXPORT_SYMBOL_GPL(iomap_finish_ioend);
/*
* Ioend completion routine for merged bios. This can only be called from task
diff --git a/include/linux/iomap.h b/include/linux/iomap.h
index 2c5685adf3a9..7974ed441300 100644
--- a/include/linux/iomap.h
+++ b/include/linux/iomap.h
@@ -479,6 +479,7 @@ struct iomap_ioend *iomap_init_ioend(struct inode *inode, struct bio *bio,
loff_t file_offset, u16 ioend_flags);
struct iomap_ioend *iomap_split_ioend(struct iomap_ioend *ioend,
unsigned int max_len, bool is_append);
+u32 iomap_finish_ioend(struct iomap_ioend *ioend, int error);
void iomap_finish_ioends(struct iomap_ioend *ioend, int error);
void iomap_ioend_try_merge(struct iomap_ioend *ioend,
struct list_head *more_ioends);
--
2.52.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox