File 0018-0004-LU-16843-ldiskfs-merge-extent-blocks.patch of Package lustre_2_15

From 0f7e6c02a9ea5b85d8d97f724bed318268cea60f Mon Sep 17 00:00:00 2001
From: Alex Zhuravlev <bzzz@whamcloud.com>
Date: Tue, 23 May 2023 16:30:58 +0300
Subject: [PATCH] LU-16843 ldiskfs: merge extent blocks

There are cases (e.g. file written synchronously with discontiguous
blocks that are later filled in) when a lot of extents are created
initially, then the extents get merged over time, but there is no
way to merge the index blocks.  This can cause a very deep extent
index tree (above 5 levels) and cause problems like:

inode has invalid extent depth: 6

Merge leave/index blocks (one at each level at most) to right/left
when extents are removed from the index.

submitted to ext4@ maillist:
https://lore.kernel.org/linux-ext4/7A2B8861-96AA-4815-BB58-180F63F62436@whamcloud.com/

Signed-off-by: Alex Zhuravlev <bzzz@whamcloud.com>
Change-Id: I746c0917e746eb442d3c69a23f591d9cdade76fa
Reviewed-on: https://review.whamcloud.com/c/fs/lustre-release/+/51096
Tested-by: jenkins <devops@whamcloud.com>
Tested-by: Maloo <maloo@whamcloud.com>
Reviewed-by: Artem Blagodarenko <ablagodarenko@ddn.com>
Reviewed-by: Andreas Dilger <adilger@whamcloud.com>
Reviewed-by: Oleg Drokin <green@whamcloud.com>
---
 .../patches/rhel7.9/ext4-ext-merge.patch      | 220 ++++++++++++++++++
 .../patches/rhel8/ext4-ext-merge.patch        | 220 ++++++++++++++++++
 .../series/ldiskfs-3.10-rhel7.9.series        |   1 +
 .../series/ldiskfs-4.12-sles15sp1-7.series    |   1 +
 .../series/ldiskfs-4.12-sles15sp1.series      |   1 +
 .../series/ldiskfs-4.18-rhel8.7.series        |   1 +
 .../series/ldiskfs-4.18-rhel8.8.series        |   1 +
 .../series/ldiskfs-5.14-rhel9.1.series        |   1 +
 .../series/ldiskfs-5.14-rhel9.2.series        |   1 +
 .../series/ldiskfs-5.14.21-sles15sp4.series   |   1 +
 .../series/ldiskfs-5.14.21-sles15sp5.series   |   1 +
 .../series/ldiskfs-5.3.18-sles15sp3.series    |   1 +
 12 files changed, 450 insertions(+)
 create mode 100644 ldiskfs/kernel_patches/patches/rhel7.9/ext4-ext-merge.patch
 create mode 100644 ldiskfs/kernel_patches/patches/rhel8/ext4-ext-merge.patch

diff --git a/ldiskfs/kernel_patches/patches/rhel7.9/ext4-ext-merge.patch b/ldiskfs/kernel_patches/patches/rhel7.9/ext4-ext-merge.patch
new file mode 100644
index 000000000000..40d0b1e7145a
--- /dev/null
+++ b/ldiskfs/kernel_patches/patches/rhel7.9/ext4-ext-merge.patch
@@ -0,0 +1,220 @@
+Index: linux-4.18.0-80.1.2.el8_0/fs/ext4/extents.c
+===================================================================
+--- linux-4.18.0-80.1.2.el8_0.orig/fs/ext4/extents.c
++++ linux-4.18.0-80.1.2.el8_0/fs/ext4/extents.c
+@@ -1888,7 +1944,7 @@ static void ext4_ext_try_to_merge_up(
+  * This function tries to merge the @ex extent to neighbours in the tree, then
+  * tries to collapse the extent tree into the inode.
+  */
+-static void ext4_ext_try_to_merge(handle_t *handle,
++static int ext4_ext_try_to_merge(handle_t *handle,
+ 				  struct inode *inode,
+ 				  struct ext4_ext_path *path,
+ 				  struct ext4_extent *ex)
+@@ -1905,9 +1961,178 @@ static void ext4_ext_try_to_merge(han
+ 		merge_done = ext4_ext_try_to_merge_right(inode, path, ex - 1);
+ 
+ 	if (!merge_done)
+-		(void) ext4_ext_try_to_merge_right(inode, path, ex);
++		merge_done = ext4_ext_try_to_merge_right(inode, path, ex);
+ 
+ 	ext4_ext_try_to_merge_up(handle, inode, path);
++
++	return merge_done;
++}
++
++/*
++ * This function tries to merge blocks from @path into @npath
++ */
++static int ext4_ext_merge_blocks(handle_t *handle,
++				struct inode *inode,
++				struct ext4_ext_path *path,
++				struct ext4_ext_path *npath)
++{
++	unsigned int depth = ext_depth(inode);
++	int used, nused, free, i, k, err;
++	ext4_lblk_t next;
++
++	if (path[depth].p_hdr == npath[depth].p_hdr)
++		return 0;
++
++	used = le16_to_cpu(path[depth].p_hdr->eh_entries);
++	free = le16_to_cpu(npath[depth].p_hdr->eh_max) -
++		le16_to_cpu(npath[depth].p_hdr->eh_entries);
++	if (free < used)
++		return 0;
++
++	err = ext4_ext_get_access(handle, inode, path + depth);
++	if (err)
++		return err;
++	err = ext4_ext_get_access(handle, inode, npath + depth);
++	if (err)
++		return err;
++
++	/* move entries from the current leave to the next one */
++	nused = le16_to_cpu(npath[depth].p_hdr->eh_entries);
++	memmove(EXT_FIRST_EXTENT(npath[depth].p_hdr) + used,
++		EXT_FIRST_EXTENT(npath[depth].p_hdr),
++		nused * sizeof(struct ext4_extent));
++	memcpy(EXT_FIRST_EXTENT(npath[depth].p_hdr),
++		EXT_FIRST_EXTENT(path[depth].p_hdr),
++		used * sizeof(struct ext4_extent));
++	le16_add_cpu(&npath[depth].p_hdr->eh_entries, used);
++	le16_add_cpu(&path[depth].p_hdr->eh_entries, -used);
++	ext4_ext_try_to_merge_right(inode, npath,
++					EXT_FIRST_EXTENT(npath[depth].p_hdr));
++
++	err = ext4_ext_dirty(handle, inode, path + depth);
++	if (err)
++		return err;
++	err = ext4_ext_dirty(handle, inode, npath + depth);
++	if (err)
++		return err;
++
++	/* otherwise the index won't get corrected */
++	npath[depth].p_ext = EXT_FIRST_EXTENT(npath[depth].p_hdr);
++	err = ext4_ext_correct_indexes(handle, inode, npath);
++	if (err)
++		return err;
++
++	for (i = depth - 1; i >= 0; i--) {
++
++		next = ext4_idx_pblock(path[i].p_idx);
++		ext4_free_blocks(handle, inode, NULL, next, 1,
++				EXT4_FREE_BLOCKS_METADATA |
++				EXT4_FREE_BLOCKS_FORGET);
++		err = ext4_ext_get_access(handle, inode, path + i);
++		if (err)
++			return err;
++		le16_add_cpu(&path[i].p_hdr->eh_entries, -1);
++		if (le16_to_cpu(path[i].p_hdr->eh_entries) == 0) {
++			/* whole index block collapsed, go up */
++			continue;
++		}
++		/* remove index pointer */
++		used = EXT_LAST_INDEX(path[i].p_hdr) - path[i].p_idx + 1;
++		memmove(path[i].p_idx, path[i].p_idx + 1,
++			used * sizeof(struct ext4_extent_idx));
++
++		err = ext4_ext_dirty(handle, inode, path + i);
++		if (err)
++			return err;
++
++		if (path[i].p_hdr == npath[i].p_hdr)
++			break;
++
++		/* try to move index pointers */
++		used = le16_to_cpu(path[i].p_hdr->eh_entries);
++		free = le16_to_cpu(npath[i].p_hdr->eh_max) -
++			le16_to_cpu(npath[i].p_hdr->eh_entries);
++		if (used > free)
++			break;
++		err = ext4_ext_get_access(handle, inode, npath + i);
++		if (err)
++			return err;
++		memmove(EXT_FIRST_INDEX(npath[i].p_hdr) + used,
++			EXT_FIRST_INDEX(npath[i].p_hdr),
++			npath[i].p_hdr->eh_entries * sizeof(struct ext4_extent_idx));
++		memcpy(EXT_FIRST_INDEX(npath[i].p_hdr), EXT_FIRST_INDEX(path[i].p_hdr),
++			used * sizeof(struct ext4_extent_idx));
++		le16_add_cpu(&path[i].p_hdr->eh_entries, -used);
++		le16_add_cpu(&npath[i].p_hdr->eh_entries, used);
++		err = ext4_ext_dirty(handle, inode, path + i);
++		if (err)
++			return err;
++		err = ext4_ext_dirty(handle, inode, npath + i);
++		if (err)
++			return err;
++
++		/* correct index above */
++		for (k = i; k > 0; k--) {
++			err = ext4_ext_get_access(handle, inode, npath + k - 1);
++			if (err)
++				return err;
++			npath[k-1].p_idx->ei_block =
++				EXT_FIRST_INDEX(npath[k].p_hdr)->ei_block;
++			err = ext4_ext_dirty(handle, inode, npath + k - 1);
++			if (err)
++				return err;
++		}
++	}
++
++	/*
++	 * TODO: given we've got two paths, it should be possible to
++	 * collapse those two blocks into the root one in some cases
++	 */
++	return 1;
++}
++
++static int ext4_ext_try_to_merge_blocks(handle_t *handle,
++		struct inode *inode,
++		struct ext4_ext_path *path)
++{
++	struct ext4_ext_path *npath = NULL;
++	unsigned int depth = ext_depth(inode);
++	ext4_lblk_t next;
++	int used, rc = 0;
++
++	if (depth == 0)
++		return 0;
++
++	used = le16_to_cpu(path[depth].p_hdr->eh_entries);
++	/* don't be too agressive as checking space in
++	 * the next block is not free */
++	if (used > ext4_ext_space_block(inode, 0) / 4)
++		return 0;
++
++	/* try to merge to the next block */
++	next = ext4_ext_next_leaf_block(path);
++	if (next == EXT_MAX_BLOCKS)
++		return 0;
++	npath = ext4_ext_find_extent(inode, next, NULL, 0);
++	if (IS_ERR(npath))
++		return 0;
++	rc = ext4_ext_merge_blocks(handle, inode, path, npath);
++	ext4_ext_drop_refs(npath);
++	kfree(npath);
++	if (rc)
++		return rc > 0 ? 0 : rc;
++
++	/* try to merge with the previous block */
++	if (EXT_FIRST_EXTENT(path[depth].p_hdr)->ee_block == 0)
++		return 0;
++	next = EXT_FIRST_EXTENT(path[depth].p_hdr)->ee_block - 1;
++	npath = ext4_ext_find_extent(inode, next, NULL, 0);
++	if (IS_ERR(npath))
++		return 0;
++	rc = ext4_ext_merge_blocks(handle, inode, npath, path);
++	ext4_ext_drop_refs(npath);
++	kfree(npath);
++	return rc > 0 ? 0 : rc;
+ }
+ 
+ /*
+@@ -1979,6 +2205,7 @@ int ext4_ext_insert_extent(handle_t *
+ 	int depth, len, err;
+ 	ext4_lblk_t next;
+ 	int mb_flags = 0, unwritten;
++	int merged = 0;
+ 
+ 	if (gb_flags & EXT4_GET_BLOCKS_DELALLOC_RESERVE)
+ 		mb_flags |= EXT4_MB_DELALLOC_RESERVED;
+@@ -2172,8 +2399,7 @@ has_space:
+ merge:
+ 	/* try to merge extents */
+ 	if (!(gb_flags & EXT4_GET_BLOCKS_PRE_IO))
+-		ext4_ext_try_to_merge(handle, inode, path, nearex);
+-
++		merged = ext4_ext_try_to_merge(handle, inode, path, nearex);
+ 
+ 	/* time to correct all indexes above */
+ 	err = ext4_ext_correct_indexes(handle, inode, path);
+@@ -2181,6 +2407,8 @@ merge:
+ 		goto cleanup;
+ 
+ 	err = ext4_ext_dirty(handle, inode, path + path->p_depth);
++	if (!err && merged)
++		err = ext4_ext_try_to_merge_blocks(handle, inode, path);
+ 
+ cleanup:
+ 	ext4_ext_drop_refs(npath);
diff --git a/ldiskfs/kernel_patches/patches/rhel8/ext4-ext-merge.patch b/ldiskfs/kernel_patches/patches/rhel8/ext4-ext-merge.patch
new file mode 100644
index 000000000000..cc2142e4dd35
--- /dev/null
+++ b/ldiskfs/kernel_patches/patches/rhel8/ext4-ext-merge.patch
@@ -0,0 +1,220 @@
+Index: linux-4.18.0-80.1.2.el8_0/fs/ext4/extents.c
+===================================================================
+--- linux-4.18.0-80.1.2.el8_0.orig/fs/ext4/extents.c
++++ linux-4.18.0-80.1.2.el8_0/fs/ext4/extents.c
+@@ -1888,7 +1944,7 @@ static void ext4_ext_try_to_merge_up(
+  * This function tries to merge the @ex extent to neighbours in the tree, then
+  * tries to collapse the extent tree into the inode.
+  */
+-static void ext4_ext_try_to_merge(handle_t *handle,
++static int ext4_ext_try_to_merge(handle_t *handle,
+ 				  struct inode *inode,
+ 				  struct ext4_ext_path *path,
+ 				  struct ext4_extent *ex)
+@@ -1905,9 +1961,178 @@ static void ext4_ext_try_to_merge(han
+ 		merge_done = ext4_ext_try_to_merge_right(inode, path, ex - 1);
+ 
+ 	if (!merge_done)
+-		(void) ext4_ext_try_to_merge_right(inode, path, ex);
++		merge_done = ext4_ext_try_to_merge_right(inode, path, ex);
+ 
+ 	ext4_ext_try_to_merge_up(handle, inode, path);
++
++	return merge_done;
++}
++
++/*
++ * This function tries to merge blocks from @path into @npath
++ */
++static int ext4_ext_merge_blocks(handle_t *handle,
++				struct inode *inode,
++				struct ext4_ext_path *path,
++				struct ext4_ext_path *npath)
++{
++	unsigned int depth = ext_depth(inode);
++	int used, nused, free, i, k, err;
++	ext4_lblk_t next;
++
++	if (path[depth].p_hdr == npath[depth].p_hdr)
++		return 0;
++
++	used = le16_to_cpu(path[depth].p_hdr->eh_entries);
++	free = le16_to_cpu(npath[depth].p_hdr->eh_max) -
++		le16_to_cpu(npath[depth].p_hdr->eh_entries);
++	if (free < used)
++		return 0;
++
++	err = ext4_ext_get_access(handle, inode, path + depth);
++	if (err)
++		return err;
++	err = ext4_ext_get_access(handle, inode, npath + depth);
++	if (err)
++		return err;
++
++	/* move entries from the current leave to the next one */
++	nused = le16_to_cpu(npath[depth].p_hdr->eh_entries);
++	memmove(EXT_FIRST_EXTENT(npath[depth].p_hdr) + used,
++		EXT_FIRST_EXTENT(npath[depth].p_hdr),
++		nused * sizeof(struct ext4_extent));
++	memcpy(EXT_FIRST_EXTENT(npath[depth].p_hdr),
++		EXT_FIRST_EXTENT(path[depth].p_hdr),
++		used * sizeof(struct ext4_extent));
++	le16_add_cpu(&npath[depth].p_hdr->eh_entries, used);
++	le16_add_cpu(&path[depth].p_hdr->eh_entries, -used);
++	ext4_ext_try_to_merge_right(inode, npath,
++					EXT_FIRST_EXTENT(npath[depth].p_hdr));
++
++	err = ext4_ext_dirty(handle, inode, path + depth);
++	if (err)
++		return err;
++	err = ext4_ext_dirty(handle, inode, npath + depth);
++	if (err)
++		return err;
++
++	/* otherwise the index won't get corrected */
++	npath[depth].p_ext = EXT_FIRST_EXTENT(npath[depth].p_hdr);
++	err = ext4_ext_correct_indexes(handle, inode, npath);
++	if (err)
++		return err;
++
++	for (i = depth - 1; i >= 0; i--) {
++
++		next = ext4_idx_pblock(path[i].p_idx);
++		ext4_free_blocks(handle, inode, NULL, next, 1,
++				EXT4_FREE_BLOCKS_METADATA |
++				EXT4_FREE_BLOCKS_FORGET);
++		err = ext4_ext_get_access(handle, inode, path + i);
++		if (err)
++			return err;
++		le16_add_cpu(&path[i].p_hdr->eh_entries, -1);
++		if (le16_to_cpu(path[i].p_hdr->eh_entries) == 0) {
++			/* whole index block collapsed, go up */
++			continue;
++		}
++		/* remove index pointer */
++		used = EXT_LAST_INDEX(path[i].p_hdr) - path[i].p_idx + 1;
++		memmove(path[i].p_idx, path[i].p_idx + 1,
++			used * sizeof(struct ext4_extent_idx));
++
++		err = ext4_ext_dirty(handle, inode, path + i);
++		if (err)
++			return err;
++
++		if (path[i].p_hdr == npath[i].p_hdr)
++			break;
++
++		/* try to move index pointers */
++		used = le16_to_cpu(path[i].p_hdr->eh_entries);
++		free = le16_to_cpu(npath[i].p_hdr->eh_max) -
++			le16_to_cpu(npath[i].p_hdr->eh_entries);
++		if (used > free)
++			break;
++		err = ext4_ext_get_access(handle, inode, npath + i);
++		if (err)
++			return err;
++		memmove(EXT_FIRST_INDEX(npath[i].p_hdr) + used,
++			EXT_FIRST_INDEX(npath[i].p_hdr),
++			npath[i].p_hdr->eh_entries * sizeof(struct ext4_extent_idx));
++		memcpy(EXT_FIRST_INDEX(npath[i].p_hdr), EXT_FIRST_INDEX(path[i].p_hdr),
++			used * sizeof(struct ext4_extent_idx));
++		le16_add_cpu(&path[i].p_hdr->eh_entries, -used);
++		le16_add_cpu(&npath[i].p_hdr->eh_entries, used);
++		err = ext4_ext_dirty(handle, inode, path + i);
++		if (err)
++			return err;
++		err = ext4_ext_dirty(handle, inode, npath + i);
++		if (err)
++			return err;
++
++		/* correct index above */
++		for (k = i; k > 0; k--) {
++			err = ext4_ext_get_access(handle, inode, npath + k - 1);
++			if (err)
++				return err;
++			npath[k-1].p_idx->ei_block =
++				EXT_FIRST_INDEX(npath[k].p_hdr)->ei_block;
++			err = ext4_ext_dirty(handle, inode, npath + k - 1);
++			if (err)
++				return err;
++		}
++	}
++
++	/*
++	 * TODO: given we've got two paths, it should be possible to
++	 * collapse those two blocks into the root one in some cases
++	 */
++	return 1;
++}
++
++static int ext4_ext_try_to_merge_blocks(handle_t *handle,
++		struct inode *inode,
++		struct ext4_ext_path *path)
++{
++	struct ext4_ext_path *npath = NULL;
++	unsigned int depth = ext_depth(inode);
++	ext4_lblk_t next;
++	int used, rc = 0;
++
++	if (depth == 0)
++		return 0;
++
++	used = le16_to_cpu(path[depth].p_hdr->eh_entries);
++	/* don't be too agressive as checking space in
++	 * the next block is not free */
++	if (used > ext4_ext_space_block(inode, 0) / 4)
++		return 0;
++
++	/* try to merge to the next block */
++	next = ext4_ext_next_leaf_block(path);
++	if (next == EXT_MAX_BLOCKS)
++		return 0;
++	npath = ext4_find_extent(inode, next, NULL, 0);
++	if (IS_ERR(npath))
++		return 0;
++	rc = ext4_ext_merge_blocks(handle, inode, path, npath);
++	ext4_ext_drop_refs(npath);
++	kfree(npath);
++	if (rc)
++		return rc > 0 ? 0 : rc;
++
++	/* try to merge with the previous block */
++	if (EXT_FIRST_EXTENT(path[depth].p_hdr)->ee_block == 0)
++		return 0;
++	next = EXT_FIRST_EXTENT(path[depth].p_hdr)->ee_block - 1;
++	npath = ext4_find_extent(inode, next, NULL, 0);
++	if (IS_ERR(npath))
++		return 0;
++	rc = ext4_ext_merge_blocks(handle, inode, npath, path);
++	ext4_ext_drop_refs(npath);
++	kfree(npath);
++	return rc > 0 ? 0 : rc;
+ }
+ 
+ /*
+@@ -1979,6 +2205,7 @@ int ext4_ext_insert_extent(handle_t *
+ 	int depth, len, err;
+ 	ext4_lblk_t next;
+ 	int mb_flags = 0, unwritten;
++	int merged = 0;
+ 
+ 	if (gb_flags & EXT4_GET_BLOCKS_DELALLOC_RESERVE)
+ 		mb_flags |= EXT4_MB_DELALLOC_RESERVED;
+@@ -2172,8 +2399,7 @@ has_space:
+ merge:
+ 	/* try to merge extents */
+ 	if (!(gb_flags & EXT4_GET_BLOCKS_PRE_IO))
+-		ext4_ext_try_to_merge(handle, inode, path, nearex);
+-
++		merged = ext4_ext_try_to_merge(handle, inode, path, nearex);
+ 
+ 	/* time to correct all indexes above */
+ 	err = ext4_ext_correct_indexes(handle, inode, path);
+@@ -2181,6 +2407,8 @@ merge:
+ 		goto cleanup;
+ 
+ 	err = ext4_ext_dirty(handle, inode, path + path->p_depth);
++	if (!err && merged)
++		err = ext4_ext_try_to_merge_blocks(handle, inode, path);
+ 
+ cleanup:
+ 	ext4_ext_drop_refs(npath);
diff --git a/ldiskfs/kernel_patches/series/ldiskfs-4.12-sles15sp1-7.series b/ldiskfs/kernel_patches/series/ldiskfs-4.12-sles15sp1-7.series
index e8a84209436b..a497b848a590 100644
--- a/ldiskfs/kernel_patches/series/ldiskfs-4.12-sles15sp1-7.series
+++ b/ldiskfs/kernel_patches/series/ldiskfs-4.12-sles15sp1-7.series
@@ -24,6 +24,7 @@ suse15/ext4-dont-check-before-replay.patch
 rhel7.6/ext4-use-GFP_NOFS-in-ext4_inode_attach_jinode.patch
 rhel7.6/ext4-optimize-ext4_find_delalloc_range-in-nodelalloc.patch
 rhel7.6/ext4-export-orphan-add.patch
+rhel8/ext4-ext-merge.patch
 suse15/ext4-export-mb-stream-allocator-variables.patch
 base/ext4-no-max-dir-size-limit-for-iam-objects.patch
 rhel7.7/ext4-ialloc-uid-gid-and-pass-owner-down.patch
diff --git a/ldiskfs/kernel_patches/series/ldiskfs-4.12-sles15sp1.series b/ldiskfs/kernel_patches/series/ldiskfs-4.12-sles15sp1.series
index a51161a740f2..124d2ccabe12 100644
--- a/ldiskfs/kernel_patches/series/ldiskfs-4.12-sles15sp1.series
+++ b/ldiskfs/kernel_patches/series/ldiskfs-4.12-sles15sp1.series
@@ -24,6 +24,7 @@ suse15/ext4-dont-check-before-replay.patch
 rhel7.6/ext4-use-GFP_NOFS-in-ext4_inode_attach_jinode.patch
 rhel7.6/ext4-optimize-ext4_find_delalloc_range-in-nodelalloc.patch
 rhel7.6/ext4-export-orphan-add.patch
+rhel8/ext4-ext-merge.patch
 suse15/ext4-export-mb-stream-allocator-variables.patch
 base/ext4-no-max-dir-size-limit-for-iam-objects.patch
 rhel7.7/ext4-ialloc-uid-gid-and-pass-owner-down.patch
diff --git a/ldiskfs/kernel_patches/series/ldiskfs-5.14.21-sles15sp4.series b/ldiskfs/kernel_patches/series/ldiskfs-5.14.21-sles15sp4.series
index d9ade0491663..e07353da9d42 100644
--- a/ldiskfs/kernel_patches/series/ldiskfs-5.14.21-sles15sp4.series
+++ b/ldiskfs/kernel_patches/series/ldiskfs-5.14.21-sles15sp4.series
@@ -27,6 +27,7 @@ linux-5.8/ext4-no-max-dir-size-limit-for-iam-objects.patch
 linux-5.14/ext4-ialloc-uid-gid-and-pass-owner-down.patch
 linux-5.14/ext4-projid-xattrs.patch
 base/ext4-delayed-iput.patch
+rhel8/ext4-ext-merge.patch
 linux-5.14/ext4-xattr-disable-credits-check.patch
 linux-5.10/ext4-fiemap-kernel-data.patch
 rhel8/ext4-old_ea_inodes_handling_fix.patch
diff --git a/ldiskfs/kernel_patches/series/ldiskfs-5.3.18-sles15sp3.series b/ldiskfs/kernel_patches/series/ldiskfs-5.3.18-sles15sp3.series
index 7c650b04ad9b..8eae8ad05868 100644
--- a/ldiskfs/kernel_patches/series/ldiskfs-5.3.18-sles15sp3.series
+++ b/ldiskfs/kernel_patches/series/ldiskfs-5.3.18-sles15sp3.series
@@ -28,6 +28,7 @@ rhel8/ext4-simple-blockalloc.patch
 rhel8/ext4-xattr-disable-credits-check.patch
 base/ext4-no-max-dir-size-limit-for-iam-objects.patch
 rhel8/ext4-ialloc-uid-gid-and-pass-owner-down.patch
+rhel8/ext4-ext-merge.patch
 base/ext4-projid-xattrs.patch
 linux-5.8/ext4-enc-flag.patch
 rhel8/ext4-old_ea_inodes_handling_fix.patch
-- 
2.40.1

openSUSE Build Service is sponsored by