* [RFC v4 0/7] ext4: Add extsize support
@ 2025-07-20 20:57 Ojaswin Mujoo
2025-07-20 20:57 ` [RFC v4 1/7] ext4: add aligned allocation hint in mballoc Ojaswin Mujoo
` (6 more replies)
0 siblings, 7 replies; 8+ messages in thread
From: Ojaswin Mujoo @ 2025-07-20 20:57 UTC (permalink / raw)
To: linux-ext4, Theodore Ts'o
Cc: Jan Kara, Baokun Li, Ritesh Harjani, Zhang Yi, linux-kernel,
Darrick J . Wong, linux-fsdevel
This is the v4 for adding extsize support in ext4. extsize is primarily
being implemented as a building block to eventually support multiblock
atomic writes in ext4 without having to reformat the filesystem with
bigalloc. The long term goal behind implementing extsize is two fold:
1. We eventually want to give users a way to perform atomic writes
without needing a FS reformat to bigalloc.
- this can be achieved via configurations like extsize + software
fallback or extsize + forcealign. (More about forcealign can be
found in previous RFC [1])
2. We want to implement a software atomic write fallback for ext4 (just
like XFS) and at the same time we want to give users the choice of
whether they want only HW accelerated (fast) atomic writes or are they
okay with falling back to software emulation (slow). Wanting to opt out
of SW fallback was also a point raised by some attendees in LSFMM.
a) For users wanting guaranteed HW atomic writes, we want to implement
extsize + forcealign. This ensures atomic writes are always HW
accelerated however the write is bound to fail if the allocator can't
guarantee HW acceleration for any reason (eg no aligned blocks
available).
b) For users which prefer software fallback rather than failing the
write, we want to implement extsize + software fallback. extsize
ensures we try to get aligned blocks for HW accelerated atomic writes
on best effort basis, and SW fallback ensures we don't fail the write
in case HW atomic writes are not possible. This is inline with how XFS
has implemented multi block atomic writes.
The above approach helps ext4 provide more choice to the user about how
they want to perform the write based on what is more suitable for their
workload.
Both the approaches need extsize as a building block for the solutions
hence we are pushing the extsize changes separately and once community
is happy with these we can work on the next steps.
changes in v4 :
- removed forcealign patches so we can independently review extsize and
then build on that later
- refactored previous implementation of ext4_map_query/create_blocks to
use EXT4_EX_QUERY_FILTER
- removed some extra warn ons that were expected to hit in certain cases
[1] RFC v3: https://lore.kernel.org/linux-ext4/cover.1742800203.git.ojaswin@linux.ibm.com/
Testing: I've tested with xfstests auto and don't see any regressions.
Also tested with internal extsize related tests that I plan to upstream
soon.
Ojaswin Mujoo (7):
ext4: add aligned allocation hint in mballoc
ext4: allow inode preallocation for aligned alloc
ext4: support for extsize hint using FS_IOC_FS(GET/SET)XATTR
ext4: pass lblk and len explicitly to ext4_split_extent*()
ext4: add extsize hint support
ext4: make extsize work with EOF allocations
ext4: add ext4_map_blocks_extsize() wrapper to handle overwrites
fs/ext4/ext4.h | 15 +-
fs/ext4/ext4_jbd2.h | 15 ++
fs/ext4/extents.c | 229 ++++++++++++++---
fs/ext4/inode.c | 485 ++++++++++++++++++++++++++++++++----
fs/ext4/ioctl.c | 122 +++++++++
fs/ext4/mballoc.c | 123 +++++++--
fs/ext4/super.c | 1 +
include/trace/events/ext4.h | 1 +
8 files changed, 881 insertions(+), 110 deletions(-)
--
2.49.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [RFC v4 1/7] ext4: add aligned allocation hint in mballoc
2025-07-20 20:57 [RFC v4 0/7] ext4: Add extsize support Ojaswin Mujoo
@ 2025-07-20 20:57 ` Ojaswin Mujoo
2025-07-20 20:57 ` [RFC v4 2/7] ext4: allow inode preallocation for aligned alloc Ojaswin Mujoo
` (5 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Ojaswin Mujoo @ 2025-07-20 20:57 UTC (permalink / raw)
To: linux-ext4, Theodore Ts'o
Cc: Jan Kara, Baokun Li, Ritesh Harjani, Zhang Yi, linux-kernel,
Darrick J . Wong, linux-fsdevel
Add support in mballoc for allocating blocks that are aligned
to a certain power-of-2 offset.
1. We define a new flag EXT4_MB_ALIGNED_HINT to indicate that we want
an aligned allocation. This is just a hint, mballoc tries its best to
provide aligned blocks but if it can't then it'll fallback to normal
allocation
2. The alignment is determined by the length of the allocation, for
example if we ask for 8192 bytes, then the alignment of physical blocks
will also be 8192 bytes aligned (ie 2 blocks aligned on 4k blocksize).
3. We dont yet support arbitrary alignment. For aligned writes, the
length/alignment must be power of 2 in blocks, ie for 4k blocksize we
can get 4k byte aligned, 8k byte aligned, 16k byte aligned ...
allocation but not 12k byte aligned.
4. We use CR_POWER2_ALIGNED criteria for aligned allocation which by
design allocates in an aligned manner. Since CR_POWER2_ALIGNED needs the
ac->ac_g_ex.fe_len to be power of 2, thats where the restriction in
point 3 above comes from. Since right now aligned allocation support is
added mainly for atomic writes use case, this restriction should be fine
since atomic write capable devices usually support only power of 2
alignments
5. For ease of review enabling inode preallocation support is done in
upcoming patches and is disabled in this patch.
Signed-off-by: Ojaswin Mujoo <ojaswin@linux.ibm.com>
---
fs/ext4/ext4.h | 2 ++
fs/ext4/mballoc.c | 57 +++++++++++++++++++++++++++++++++----
include/trace/events/ext4.h | 1 +
3 files changed, 55 insertions(+), 5 deletions(-)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 9ac0a7d4fa0c..7b353d1af580 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -222,6 +222,8 @@ enum criteria {
/* Avg fragment size rb tree lookup succeeded at least once for
* CR_BEST_AVAIL_LEN */
#define EXT4_MB_CR_BEST_AVAIL_LEN_OPTIMIZED 0x00020000
+/* mballoc will try to align physical start to length (aka natural alignment) */
+#define EXT4_MB_HINT_ALIGNED 0x40000
struct ext4_allocation_request {
/* target inode for block we're allocating */
diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c
index 1e98c5be4e0a..d8d9aa717a26 100644
--- a/fs/ext4/mballoc.c
+++ b/fs/ext4/mballoc.c
@@ -2177,8 +2177,11 @@ static void ext4_mb_use_best_found(struct ext4_allocation_context *ac,
* user requested originally, we store allocated
* space in a special descriptor.
*/
- if (ac->ac_o_ex.fe_len < ac->ac_b_ex.fe_len)
+ if (ac->ac_o_ex.fe_len < ac->ac_b_ex.fe_len) {
+ /* Aligned allocation doesn't have preallocation support */
+ WARN_ON(ac->ac_flags & EXT4_MB_HINT_ALIGNED);
ext4_mb_new_preallocation(ac);
+ }
}
@@ -2814,10 +2817,15 @@ ext4_mb_regular_allocator(struct ext4_allocation_context *ac)
BUG_ON(ac->ac_status == AC_STATUS_FOUND);
- /* first, try the goal */
- err = ext4_mb_find_by_goal(ac, &e4b);
- if (err || ac->ac_status == AC_STATUS_FOUND)
- goto out;
+ /*
+ * first, try the goal. Skip trying goal for aligned allocations since
+ * goal determination logic is not alignment aware (yet)
+ */
+ if (!(ac->ac_flags & EXT4_MB_HINT_ALIGNED)) {
+ err = ext4_mb_find_by_goal(ac, &e4b);
+ if (err || ac->ac_status == AC_STATUS_FOUND)
+ goto out;
+ }
if (unlikely(ac->ac_flags & EXT4_MB_HINT_GOAL_ONLY))
goto out;
@@ -2861,6 +2869,16 @@ ext4_mb_regular_allocator(struct ext4_allocation_context *ac)
repeat:
for (; cr < EXT4_MB_NUM_CRS && ac->ac_status == AC_STATUS_CONTINUE; cr++) {
ac->ac_criteria = cr;
+
+ if (ac->ac_criteria > CR_POWER2_ALIGNED &&
+ ac->ac_flags & EXT4_MB_HINT_ALIGNED &&
+ ac->ac_g_ex.fe_len > 1) {
+ ext4_warning_inode(
+ ac->ac_inode,
+ "Aligned allocation not possible, using unaligned allocation");
+ ac->ac_flags &= ~EXT4_MB_HINT_ALIGNED;
+ }
+
/*
* searching for the right group start
* from the goal value specified
@@ -2993,6 +3011,24 @@ ext4_mb_regular_allocator(struct ext4_allocation_context *ac)
if (!err && ac->ac_status != AC_STATUS_FOUND && first_err)
err = first_err;
+ if (ac->ac_flags & EXT4_MB_HINT_ALIGNED && ac->ac_status == AC_STATUS_FOUND) {
+ ext4_fsblk_t start = ext4_grp_offs_to_block(sb, &ac->ac_b_ex);
+ ext4_grpblk_t len = EXT4_C2B(sbi, ac->ac_b_ex.fe_len);
+
+ if (!len) {
+ ext4_warning_inode(ac->ac_inode,
+ "Expected a non zero len extent");
+ ac->ac_status = AC_STATUS_BREAK;
+ goto exit;
+ }
+
+ WARN_ON_ONCE(!is_power_of_2(len));
+ WARN_ON_ONCE(start % len);
+ /* We don't support preallocation yet */
+ WARN_ON_ONCE(ac->ac_b_ex.fe_len != ac->ac_o_ex.fe_len);
+ }
+
+ exit:
mb_debug(sb, "Best len %d, origin len %d, ac_status %u, ac_flags 0x%x, cr %d ret %d\n",
ac->ac_b_ex.fe_len, ac->ac_o_ex.fe_len, ac->ac_status,
ac->ac_flags, cr, err);
@@ -4438,6 +4474,13 @@ ext4_mb_normalize_request(struct ext4_allocation_context *ac,
if (ac->ac_flags & EXT4_MB_HINT_NOPREALLOC)
return;
+ /*
+ * caller may have strict alignment requirements. In this case, avoid
+ * normalization since it is not alignment aware.
+ */
+ if (ac->ac_flags & EXT4_MB_HINT_ALIGNED)
+ return;
+
if (ac->ac_flags & EXT4_MB_HINT_GROUP_ALLOC) {
ext4_mb_normalize_group_request(ac);
return ;
@@ -4792,6 +4835,10 @@ ext4_mb_use_preallocated(struct ext4_allocation_context *ac)
if (!(ac->ac_flags & EXT4_MB_HINT_DATA))
return false;
+ /* using preallocated blocks is not alignment aware. */
+ if (ac->ac_flags & EXT4_MB_HINT_ALIGNED)
+ return false;
+
/*
* first, try per-file preallocation by searching the inode pa rbtree.
*
diff --git a/include/trace/events/ext4.h b/include/trace/events/ext4.h
index 845451077c41..d5cec574984c 100644
--- a/include/trace/events/ext4.h
+++ b/include/trace/events/ext4.h
@@ -36,6 +36,7 @@ struct partial_cluster;
{ EXT4_MB_STREAM_ALLOC, "STREAM_ALLOC" }, \
{ EXT4_MB_USE_ROOT_BLOCKS, "USE_ROOT_BLKS" }, \
{ EXT4_MB_USE_RESERVED, "USE_RESV" }, \
+ { EXT4_MB_HINT_ALIGNED, "HINT_ALIGNED" }, \
{ EXT4_MB_STRICT_CHECK, "STRICT_CHECK" })
#define show_map_flags(flags) __print_flags(flags, "|", \
--
2.49.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [RFC v4 2/7] ext4: allow inode preallocation for aligned alloc
2025-07-20 20:57 [RFC v4 0/7] ext4: Add extsize support Ojaswin Mujoo
2025-07-20 20:57 ` [RFC v4 1/7] ext4: add aligned allocation hint in mballoc Ojaswin Mujoo
@ 2025-07-20 20:57 ` Ojaswin Mujoo
2025-07-20 20:57 ` [RFC v4 3/7] ext4: support for extsize hint using FS_IOC_FS(GET/SET)XATTR Ojaswin Mujoo
` (4 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Ojaswin Mujoo @ 2025-07-20 20:57 UTC (permalink / raw)
To: linux-ext4, Theodore Ts'o
Cc: Jan Kara, Baokun Li, Ritesh Harjani, Zhang Yi, linux-kernel,
Darrick J . Wong, linux-fsdevel
Enable inode preallocation support for aligned allocations. Inode
preallocation will only be used if the preallocated blocks are able to
satisfy the length and alignment requirements of the allocations, else
we disable preallocation for this particular allocation and proceed as
usual. Disabling inode preallocation is required otherwise we might end
up with overlapping preallocated ranges which can trigger a BUG() later.
Further, during normalizing, we usually try to round it up to a power of
2 which can still give us aligned allocation. We also make sure not
change the goal start so aligned allocation is more straightforward. If for
whatever reason the goal is not power of 2 or doesn't contain the original
request, then we throw a warning and proceed as normal.
For now, group preallocation is disabled for aligned allocations.
Signed-off-by: Ojaswin Mujoo <ojaswin@linux.ibm.com>
---
fs/ext4/mballoc.c | 96 +++++++++++++++++++++++++++++++----------------
1 file changed, 63 insertions(+), 33 deletions(-)
diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c
index d8d9aa717a26..090564b6e6d4 100644
--- a/fs/ext4/mballoc.c
+++ b/fs/ext4/mballoc.c
@@ -2178,8 +2178,6 @@ static void ext4_mb_use_best_found(struct ext4_allocation_context *ac,
* space in a special descriptor.
*/
if (ac->ac_o_ex.fe_len < ac->ac_b_ex.fe_len) {
- /* Aligned allocation doesn't have preallocation support */
- WARN_ON(ac->ac_flags & EXT4_MB_HINT_ALIGNED);
ext4_mb_new_preallocation(ac);
}
@@ -3024,8 +3022,7 @@ ext4_mb_regular_allocator(struct ext4_allocation_context *ac)
WARN_ON_ONCE(!is_power_of_2(len));
WARN_ON_ONCE(start % len);
- /* We don't support preallocation yet */
- WARN_ON_ONCE(ac->ac_b_ex.fe_len != ac->ac_o_ex.fe_len);
+ WARN_ON_ONCE(ac->ac_b_ex.fe_len < ac->ac_o_ex.fe_len);
}
exit:
@@ -4474,13 +4471,6 @@ ext4_mb_normalize_request(struct ext4_allocation_context *ac,
if (ac->ac_flags & EXT4_MB_HINT_NOPREALLOC)
return;
- /*
- * caller may have strict alignment requirements. In this case, avoid
- * normalization since it is not alignment aware.
- */
- if (ac->ac_flags & EXT4_MB_HINT_ALIGNED)
- return;
-
if (ac->ac_flags & EXT4_MB_HINT_GROUP_ALLOC) {
ext4_mb_normalize_group_request(ac);
return ;
@@ -4537,6 +4527,21 @@ ext4_mb_normalize_request(struct ext4_allocation_context *ac,
size = (loff_t) EXT4_C2B(sbi,
ac->ac_o_ex.fe_len) << bsbits;
}
+
+ /*
+ * For aligned allocations, we need to ensure 2 things:
+ *
+ * 1. The start should remain same as original start so that finding
+ * aligned physical blocks for it is straight forward.
+ *
+ * 2. The new_size should not be less than the original len. This
+ * can sometimes happen due to the way we predict size above.
+ */
+ if (ac->ac_flags & EXT4_MB_HINT_ALIGNED) {
+ start_off = ac->ac_o_ex.fe_logical << bsbits;
+ size = max_t(loff_t, size,
+ EXT4_C2B(sbi, ac->ac_o_ex.fe_len) << bsbits);
+ }
size = size >> bsbits;
start = start_off >> bsbits;
@@ -4787,32 +4792,46 @@ ext4_mb_check_group_pa(ext4_fsblk_t goal_block,
}
/*
- * check if found pa meets EXT4_MB_HINT_GOAL_ONLY
+ * check if found pa meets EXT4_MB_HINT_GOAL_ONLY or EXT4_MB_HINT_ALIGNED
*/
static bool
-ext4_mb_pa_goal_check(struct ext4_allocation_context *ac,
+ext4_mb_pa_check(struct ext4_allocation_context *ac,
struct ext4_prealloc_space *pa)
{
struct ext4_sb_info *sbi = EXT4_SB(ac->ac_sb);
ext4_fsblk_t start;
- if (likely(!(ac->ac_flags & EXT4_MB_HINT_GOAL_ONLY)))
+ if (likely(!(ac->ac_flags & EXT4_MB_HINT_GOAL_ONLY ||
+ ac->ac_flags & EXT4_MB_HINT_ALIGNED)))
return true;
- /*
- * If EXT4_MB_HINT_GOAL_ONLY is set, ac_g_ex will not be adjusted
- * in ext4_mb_normalize_request and will keep same with ac_o_ex
- * from ext4_mb_initialize_context. Choose ac_g_ex here to keep
- * consistent with ext4_mb_find_by_goal.
- */
- start = pa->pa_pstart +
- (ac->ac_g_ex.fe_logical - pa->pa_lstart);
- if (ext4_grp_offs_to_block(ac->ac_sb, &ac->ac_g_ex) != start)
- return false;
+ if (ac->ac_flags & EXT4_MB_HINT_GOAL_ONLY) {
+ /*
+ * If EXT4_MB_HINT_GOAL_ONLY is set, ac_g_ex will not be adjusted
+ * in ext4_mb_normalize_request and will keep same with ac_o_ex
+ * from ext4_mb_initialize_context. Choose ac_g_ex here to keep
+ * consistent with ext4_mb_find_by_goal.
+ */
+ start = pa->pa_pstart +
+ (ac->ac_g_ex.fe_logical - pa->pa_lstart);
+ if (ext4_grp_offs_to_block(ac->ac_sb, &ac->ac_g_ex) != start)
+ return false;
- if (ac->ac_g_ex.fe_len > pa->pa_len -
- EXT4_B2C(sbi, ac->ac_g_ex.fe_logical - pa->pa_lstart))
- return false;
+ if (ac->ac_g_ex.fe_len >
+ pa->pa_len - EXT4_B2C(sbi, ac->ac_g_ex.fe_logical -
+ pa->pa_lstart))
+ return false;
+ } else if (ac->ac_flags & EXT4_MB_HINT_ALIGNED) {
+ start = pa->pa_pstart +
+ (ac->ac_g_ex.fe_logical - pa->pa_lstart);
+ if (start % EXT4_C2B(sbi, ac->ac_g_ex.fe_len))
+ return false;
+
+ if (EXT4_C2B(sbi, ac->ac_g_ex.fe_len) >
+ (EXT4_C2B(sbi, pa->pa_len) -
+ (ac->ac_g_ex.fe_logical - pa->pa_lstart)))
+ return false;
+ }
return true;
}
@@ -4835,10 +4854,6 @@ ext4_mb_use_preallocated(struct ext4_allocation_context *ac)
if (!(ac->ac_flags & EXT4_MB_HINT_DATA))
return false;
- /* using preallocated blocks is not alignment aware. */
- if (ac->ac_flags & EXT4_MB_HINT_ALIGNED)
- return false;
-
/*
* first, try per-file preallocation by searching the inode pa rbtree.
*
@@ -4944,7 +4959,7 @@ ext4_mb_use_preallocated(struct ext4_allocation_context *ac)
goto try_group_pa;
}
- if (tmp_pa->pa_free && likely(ext4_mb_pa_goal_check(ac, tmp_pa))) {
+ if (tmp_pa->pa_free && likely(ext4_mb_pa_check(ac, tmp_pa))) {
atomic_inc(&tmp_pa->pa_count);
ext4_mb_use_inode_pa(ac, tmp_pa);
spin_unlock(&tmp_pa->pa_lock);
@@ -4979,6 +4994,19 @@ ext4_mb_use_preallocated(struct ext4_allocation_context *ac)
* pa_free == 0.
*/
WARN_ON_ONCE(tmp_pa->pa_free == 0);
+
+ /*
+ * If, for any reason, we reach here then we need to disable PA
+ * because otherwise ext4_mb_normalize_request() will try to
+ * allocate a new PA for this logical range where another PA
+ * already exists. This is not allowed and will trigger BUG_ONs.
+ * Hence, as a workaround we disable PA.
+ *
+ * NOTE: ideally we would want to have some logic to take care
+ * of the unusable PA. Maybe a more fine grained discard logic
+ * that could allow us to discard only specific PAs.
+ */
+ ac->ac_flags |= EXT4_MB_HINT_NOPREALLOC;
}
spin_unlock(&tmp_pa->pa_lock);
try_group_pa:
@@ -5785,6 +5813,7 @@ static void ext4_mb_group_or_file(struct ext4_allocation_context *ac)
int bsbits = ac->ac_sb->s_blocksize_bits;
loff_t size, isize;
bool inode_pa_eligible, group_pa_eligible;
+ bool is_aligned = (ac->ac_flags & EXT4_MB_HINT_ALIGNED);
if (!(ac->ac_flags & EXT4_MB_HINT_DATA))
return;
@@ -5792,7 +5821,8 @@ static void ext4_mb_group_or_file(struct ext4_allocation_context *ac)
if (unlikely(ac->ac_flags & EXT4_MB_HINT_GOAL_ONLY))
return;
- group_pa_eligible = sbi->s_mb_group_prealloc > 0;
+ /* Aligned allocation does not support group pa */
+ group_pa_eligible = (!is_aligned && sbi->s_mb_group_prealloc > 0);
inode_pa_eligible = true;
size = extent_logical_end(sbi, &ac->ac_o_ex);
isize = (i_size_read(ac->ac_inode) + ac->ac_sb->s_blocksize - 1)
--
2.49.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [RFC v4 3/7] ext4: support for extsize hint using FS_IOC_FS(GET/SET)XATTR
2025-07-20 20:57 [RFC v4 0/7] ext4: Add extsize support Ojaswin Mujoo
2025-07-20 20:57 ` [RFC v4 1/7] ext4: add aligned allocation hint in mballoc Ojaswin Mujoo
2025-07-20 20:57 ` [RFC v4 2/7] ext4: allow inode preallocation for aligned alloc Ojaswin Mujoo
@ 2025-07-20 20:57 ` Ojaswin Mujoo
2025-07-20 20:57 ` [RFC v4 4/7] ext4: pass lblk and len explicitly to ext4_split_extent*() Ojaswin Mujoo
` (3 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Ojaswin Mujoo @ 2025-07-20 20:57 UTC (permalink / raw)
To: linux-ext4, Theodore Ts'o
Cc: Jan Kara, Baokun Li, Ritesh Harjani, Zhang Yi, linux-kernel,
Darrick J . Wong, linux-fsdevel
This patch adds support for getting and setting extsize hint using
FS_IOC_GETXATTR and FS_IOC_SETXATTR interface. The extsize is stored
in xattr of type EXT4_XATTR_INDEX_SYSTEM.
Restrictions on setting extsize:
1. extsize can't be set on files with data
2. extsize can't be set on non regular files
3. extsize hint can't be used with bigalloc (yet)
4. extsize (in blocks) should be power-of-2 for simplicity.
5. extsize must be a multiple of block size
The ioctl behavior has been kept as close to the XFS equivalent
as possible.
Signed-off-by: Ojaswin Mujoo <ojaswin@linux.ibm.com>
---
fs/ext4/ext4.h | 6 +++
fs/ext4/inode.c | 89 +++++++++++++++++++++++++++++++++++
fs/ext4/ioctl.c | 122 ++++++++++++++++++++++++++++++++++++++++++++++++
fs/ext4/super.c | 1 +
4 files changed, 218 insertions(+)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 7b353d1af580..d00870cb15f2 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -1199,6 +1199,8 @@ struct ext4_inode_info {
__u32 i_csum_seed;
kprojid_t i_projid;
+ /* The extentsize hint for the inode in blocks */
+ ext4_grpblk_t i_extsize;
};
/*
@@ -3081,6 +3083,10 @@ extern void ext4_da_update_reserve_space(struct inode *inode,
int used, int quota_claim);
extern int ext4_issue_zeroout(struct inode *inode, ext4_lblk_t lblk,
ext4_fsblk_t pblk, ext4_lblk_t len);
+int ext4_inode_xattr_get_extsize(struct inode *inode);
+int ext4_inode_xattr_set_extsize(struct inode *inode, ext4_grpblk_t extsize);
+ext4_grpblk_t ext4_inode_get_extsize(struct ext4_inode_info *ei);
+void ext4_inode_set_extsize(struct ext4_inode_info *ei, ext4_grpblk_t extsize);
static inline bool is_special_ino(struct super_block *sb, unsigned long ino)
{
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 8bdf2029ebc7..664218228fd5 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -5432,6 +5432,20 @@ struct inode *__ext4_iget(struct super_block *sb, unsigned long ino,
}
}
+ ret = ext4_inode_xattr_get_extsize(&ei->vfs_inode);
+ if (ret >= 0) {
+ ei->i_extsize = ret;
+ } else if (ret == -ENODATA) {
+ /* extsize is not set */
+ ei->i_extsize = 0;
+ } else {
+ ext4_error_inode(
+ inode, function, line, 0,
+ "iget: error while retrieving extsize from xattr: %ld", ret);
+ ret = -EFSCORRUPTED;
+ goto bad_inode;
+ }
+
EXT4_INODE_GET_CTIME(inode, raw_inode);
EXT4_INODE_GET_ATIME(inode, raw_inode);
EXT4_INODE_GET_MTIME(inode, raw_inode);
@@ -6779,3 +6793,78 @@ vm_fault_t ext4_page_mkwrite(struct vm_fault *vmf)
sb_end_pagefault(inode->i_sb);
return ret;
}
+
+/*
+ * Returns positive extsize if set, 0 if not set else error
+ */
+ext4_grpblk_t ext4_inode_xattr_get_extsize(struct inode *inode)
+{
+ char *buf;
+ int size, ret = 0;
+ ext4_grpblk_t extsize = 0;
+
+ size = ext4_xattr_get(inode, EXT4_XATTR_INDEX_SYSTEM, "extsize", NULL, 0);
+
+ if (size == -ENODATA || size == 0) {
+ return 0;
+ } else if (size < 0) {
+ ret = size;
+ goto exit;
+ }
+
+ buf = kmalloc(size + 1, GFP_KERNEL);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ size = ext4_xattr_get(inode, EXT4_XATTR_INDEX_SYSTEM, "extsize", buf,
+ size);
+ if (size == -ENODATA)
+ /* No extsize is set */
+ extsize = 0;
+ else if (size < 0)
+ ret = size;
+ else {
+ buf[size] = '\0';
+ ret = kstrtoint(buf, 10, &extsize);
+ }
+
+ kfree(buf);
+exit:
+ if (ret)
+ return ret;
+ return extsize;
+}
+
+int ext4_inode_xattr_set_extsize(struct inode *inode, ext4_grpblk_t extsize)
+{
+ int err = 0;
+ /* max value of extsize should fit within 11 chars */
+ char extsize_str[11];
+
+ err = snprintf(extsize_str, 10, "%u", extsize);
+ if (err < 0)
+ return err;
+
+ /* Try to replace the xattr if it exists, else try to create it */
+ err = ext4_xattr_set(inode, EXT4_XATTR_INDEX_SYSTEM, "extsize",
+ extsize_str, strlen(extsize_str), XATTR_REPLACE);
+
+ if (err == -ENODATA)
+ err = ext4_xattr_set(inode, EXT4_XATTR_INDEX_SYSTEM, "extsize",
+ extsize_str, strlen(extsize_str),
+ XATTR_CREATE);
+
+ return err;
+}
+
+ext4_grpblk_t ext4_inode_get_extsize(struct ext4_inode_info *ei)
+{
+ return ei->i_extsize;
+}
+
+void ext4_inode_set_extsize(struct ext4_inode_info *ei, ext4_grpblk_t extsize)
+{
+ ei->i_extsize = extsize;
+}
diff --git a/fs/ext4/ioctl.c b/fs/ext4/ioctl.c
index 5668a17458ae..64a394869317 100644
--- a/fs/ext4/ioctl.c
+++ b/fs/ext4/ioctl.c
@@ -708,6 +708,93 @@ static int ext4_ioctl_setflags(struct inode *inode,
return err;
}
+static u32 ext4_ioctl_getextsize(struct inode *inode)
+{
+ ext4_grpblk_t extsize;
+
+ extsize = ext4_inode_get_extsize(EXT4_I(inode));
+
+ return (u32) extsize << inode->i_blkbits;
+}
+
+
+static int ext4_ioctl_setextsize(struct inode *inode, u32 extsize, u32 xflags)
+{
+ int err;
+ ext4_grpblk_t extsize_blks = extsize >> inode->i_blkbits;
+ struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
+ int blksize = 1 << inode->i_blkbits;
+ char *msg = NULL;
+
+ if (!S_ISREG(inode->i_mode)) {
+ msg = "Cannot set extsize on non regular file";
+ err = -EOPNOTSUPP;
+ goto error;
+ }
+
+ /*
+ * We are okay with a non-zero i_size as long as there is no data.
+ */
+ if (ext4_has_inline_data(inode) ||
+ READ_ONCE(EXT4_I(inode)->i_disksize) ||
+ EXT4_I(inode)->i_reserved_data_blocks) {
+ msg = "Cannot set extsize on file with data";
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (extsize % blksize) {
+ msg = "extsize must be multiple of blocksize";
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (sbi->s_cluster_ratio > 1) {
+ msg = "Can't use extsize hint with bigalloc";
+ err = -EINVAL;
+ goto error;
+ }
+
+ if ((xflags & FS_XFLAG_EXTSIZE) && extsize == 0) {
+ msg = "fsx_extsize can't be 0 if FS_XFLAG_EXTSIZE is passed";
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (extsize_blks > sbi->s_blocks_per_group) {
+ msg = "extsize cannot exceed number of bytes in block group";
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (extsize && !is_power_of_2(extsize_blks)) {
+ msg = "extsize must be either power-of-2 in fs blocks or 0";
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (!ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)) {
+ msg = "extsize can't be set on non-extent based files";
+ err = -EINVAL;
+ goto error;
+ }
+
+ /* update the extsize in inode xattr */
+ err = ext4_inode_xattr_set_extsize(inode, extsize_blks);
+ if (err < 0)
+ return err;
+
+ /* Update the new extsize in the in-core inode */
+ ext4_inode_set_extsize(EXT4_I(inode), extsize_blks);
+ return 0;
+
+error:
+ if (msg)
+ ext4_warning_inode(inode, "%s\n", msg);
+
+ return err;
+}
+
#ifdef CONFIG_QUOTA
static int ext4_ioctl_setproject(struct inode *inode, __u32 projid)
{
@@ -985,6 +1072,7 @@ int ext4_fileattr_get(struct dentry *dentry, struct fileattr *fa)
struct inode *inode = d_inode(dentry);
struct ext4_inode_info *ei = EXT4_I(inode);
u32 flags = ei->i_flags & EXT4_FL_USER_VISIBLE;
+ u32 extsize = 0;
if (S_ISREG(inode->i_mode))
flags &= ~FS_PROJINHERIT_FL;
@@ -993,6 +1081,13 @@ int ext4_fileattr_get(struct dentry *dentry, struct fileattr *fa)
if (ext4_has_feature_project(inode->i_sb))
fa->fsx_projid = from_kprojid(&init_user_ns, ei->i_projid);
+ extsize = ext4_ioctl_getextsize(inode);
+ /* Flag is only set if extsize is non zero */
+ if (extsize > 0) {
+ fa->fsx_extsize = extsize;
+ fa->fsx_xflags |= FS_XFLAG_EXTSIZE;
+ }
+
return 0;
}
@@ -1022,6 +1117,33 @@ int ext4_fileattr_set(struct mnt_idmap *idmap,
if (err)
goto out;
err = ext4_ioctl_setproject(inode, fa->fsx_projid);
+ if (err)
+ goto out;
+
+ if (fa->fsx_xflags & FS_XFLAG_EXTSIZE) {
+ err = ext4_ioctl_setextsize(inode, fa->fsx_extsize,
+ fa->fsx_xflags);
+ if (err)
+ goto out;
+ } else if (fa->fsx_extsize == 0) {
+ /*
+ * Even when user explicitly passes extsize=0 the flag is cleared in
+ * fileattr_set_prepare().
+ */
+ if (ext4_inode_get_extsize(EXT4_I(inode)) != 0) {
+ err = ext4_ioctl_setextsize(inode, fa->fsx_extsize,
+ fa->fsx_xflags);
+ if (err)
+ goto out;
+ }
+
+ } else {
+ /* Unexpected usage, reset extsize to 0 */
+ err = ext4_ioctl_setextsize(inode, 0, fa->fsx_xflags);
+ if (err)
+ goto out;
+ fa->fsx_xflags = 0;
+ }
out:
return err;
}
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index c7d39da7e733..2237cb2240f8 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -1409,6 +1409,7 @@ static struct inode *ext4_alloc_inode(struct super_block *sb)
spin_lock_init(&ei->i_completed_io_lock);
ei->i_sync_tid = 0;
ei->i_datasync_tid = 0;
+ ei->i_extsize = 0;
INIT_WORK(&ei->i_rsv_conversion_work, ext4_end_io_rsv_work);
ext4_fc_init_inode(&ei->vfs_inode);
spin_lock_init(&ei->i_fc_lock);
--
2.49.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [RFC v4 4/7] ext4: pass lblk and len explicitly to ext4_split_extent*()
2025-07-20 20:57 [RFC v4 0/7] ext4: Add extsize support Ojaswin Mujoo
` (2 preceding siblings ...)
2025-07-20 20:57 ` [RFC v4 3/7] ext4: support for extsize hint using FS_IOC_FS(GET/SET)XATTR Ojaswin Mujoo
@ 2025-07-20 20:57 ` Ojaswin Mujoo
2025-07-20 20:57 ` [RFC v4 5/7] ext4: add extsize hint support Ojaswin Mujoo
` (2 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Ojaswin Mujoo @ 2025-07-20 20:57 UTC (permalink / raw)
To: linux-ext4, Theodore Ts'o
Cc: Jan Kara, Baokun Li, Ritesh Harjani, Zhang Yi, linux-kernel,
Darrick J . Wong, linux-fsdevel
Since these functions only use the map to determine lblk and len of
the split, pass them explicitly. This is in preparation for making
them work with extent size hints cleanly.
No functional change in this patch.
Signed-off-by: Ojaswin Mujoo <ojaswin@linux.ibm.com>
---
fs/ext4/extents.c | 57 +++++++++++++++++++++++++----------------------
1 file changed, 30 insertions(+), 27 deletions(-)
diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
index f0f155458697..3233ab89c99e 100644
--- a/fs/ext4/extents.c
+++ b/fs/ext4/extents.c
@@ -3350,7 +3350,8 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle,
static struct ext4_ext_path *ext4_split_extent(handle_t *handle,
struct inode *inode,
struct ext4_ext_path *path,
- struct ext4_map_blocks *map,
+ ext4_lblk_t lblk,
+ unsigned int len,
int split_flag, int flags,
unsigned int *allocated)
{
@@ -3366,7 +3367,7 @@ static struct ext4_ext_path *ext4_split_extent(handle_t *handle,
ee_len = ext4_ext_get_actual_len(ex);
unwritten = ext4_ext_is_unwritten(ex);
- if (map->m_lblk + map->m_len < ee_block + ee_len) {
+ if (lblk + len < ee_block + ee_len) {
split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
flags1 = flags | EXT4_GET_BLOCKS_PRE_IO;
if (unwritten)
@@ -3375,28 +3376,28 @@ static struct ext4_ext_path *ext4_split_extent(handle_t *handle,
if (split_flag & EXT4_EXT_DATA_VALID2)
split_flag1 |= EXT4_EXT_DATA_VALID1;
path = ext4_split_extent_at(handle, inode, path,
- map->m_lblk + map->m_len, split_flag1, flags1);
+ lblk + len, split_flag1, flags1);
if (IS_ERR(path))
return path;
/*
* Update path is required because previous ext4_split_extent_at
* may result in split of original leaf or extent zeroout.
*/
- path = ext4_find_extent(inode, map->m_lblk, path, flags);
+ path = ext4_find_extent(inode, lblk, path, flags);
if (IS_ERR(path))
return path;
depth = ext_depth(inode);
ex = path[depth].p_ext;
if (!ex) {
EXT4_ERROR_INODE(inode, "unexpected hole at %lu",
- (unsigned long) map->m_lblk);
+ (unsigned long) lblk);
ext4_free_ext_path(path);
return ERR_PTR(-EFSCORRUPTED);
}
unwritten = ext4_ext_is_unwritten(ex);
}
- if (map->m_lblk >= ee_block) {
+ if (lblk >= ee_block) {
split_flag1 = split_flag & EXT4_EXT_DATA_VALID2;
if (unwritten) {
split_flag1 |= EXT4_EXT_MARK_UNWRIT1;
@@ -3404,16 +3405,16 @@ static struct ext4_ext_path *ext4_split_extent(handle_t *handle,
EXT4_EXT_MARK_UNWRIT2);
}
path = ext4_split_extent_at(handle, inode, path,
- map->m_lblk, split_flag1, flags);
+ lblk, split_flag1, flags);
if (IS_ERR(path))
return path;
}
if (allocated) {
- if (map->m_lblk + map->m_len > ee_block + ee_len)
- *allocated = ee_len - (map->m_lblk - ee_block);
+ if (lblk + len > ee_block + ee_len)
+ *allocated = ee_len - (lblk - ee_block);
else
- *allocated = map->m_len;
+ *allocated = len;
}
ext4_ext_show_leaf(inode, path);
return path;
@@ -3661,8 +3662,8 @@ ext4_ext_convert_to_initialized(handle_t *handle, struct inode *inode,
}
fallback:
- path = ext4_split_extent(handle, inode, path, &split_map, split_flag,
- flags, NULL);
+ path = ext4_split_extent(handle, inode, path, split_map.m_lblk,
+ split_map.m_len, split_flag, flags, NULL);
if (IS_ERR(path))
return path;
out:
@@ -3702,11 +3703,11 @@ ext4_ext_convert_to_initialized(handle_t *handle, struct inode *inode,
* allocated pointer. Return an extent path pointer on success, or an error
* pointer on failure.
*/
-static struct ext4_ext_path *ext4_split_convert_extents(handle_t *handle,
- struct inode *inode,
- struct ext4_map_blocks *map,
- struct ext4_ext_path *path,
- int flags, unsigned int *allocated)
+static struct ext4_ext_path *
+ext4_split_convert_extents(handle_t *handle, struct inode *inode,
+ ext4_lblk_t lblk, unsigned int len,
+ struct ext4_ext_path *path, int flags,
+ unsigned int *allocated)
{
ext4_lblk_t eof_block;
ext4_lblk_t ee_block;
@@ -3715,12 +3716,12 @@ static struct ext4_ext_path *ext4_split_convert_extents(handle_t *handle,
int split_flag = 0, depth;
ext_debug(inode, "logical block %llu, max_blocks %u\n",
- (unsigned long long)map->m_lblk, map->m_len);
+ (unsigned long long)lblk, len);
eof_block = (EXT4_I(inode)->i_disksize + inode->i_sb->s_blocksize - 1)
>> inode->i_sb->s_blocksize_bits;
- if (eof_block < map->m_lblk + map->m_len)
- eof_block = map->m_lblk + map->m_len;
+ if (eof_block < lblk + len)
+ eof_block = lblk + len;
/*
* It is safe to convert extent to initialized via explicit
* zeroout only if extent is fully inside i_size or new_size.
@@ -3740,8 +3741,8 @@ static struct ext4_ext_path *ext4_split_convert_extents(handle_t *handle,
split_flag |= (EXT4_EXT_MARK_UNWRIT2 | EXT4_EXT_DATA_VALID2);
}
flags |= EXT4_GET_BLOCKS_PRE_IO;
- return ext4_split_extent(handle, inode, path, map, split_flag, flags,
- allocated);
+ return ext4_split_extent(handle, inode, path, lblk, len, split_flag,
+ flags, allocated);
}
static struct ext4_ext_path *
@@ -3776,7 +3777,7 @@ ext4_convert_unwritten_extents_endio(handle_t *handle, struct inode *inode,
inode->i_ino, (unsigned long long)ee_block, ee_len,
(unsigned long long)map->m_lblk, map->m_len);
#endif
- path = ext4_split_convert_extents(handle, inode, map, path,
+ path = ext4_split_convert_extents(handle, inode, map->m_lblk, map->m_len, path,
EXT4_GET_BLOCKS_CONVERT, NULL);
if (IS_ERR(path))
return path;
@@ -3840,8 +3841,9 @@ convert_initialized_extent(handle_t *handle, struct inode *inode,
(unsigned long long)ee_block, ee_len);
if (ee_block != map->m_lblk || ee_len > map->m_len) {
- path = ext4_split_convert_extents(handle, inode, map, path,
- EXT4_GET_BLOCKS_CONVERT_UNWRITTEN, NULL);
+ path = ext4_split_convert_extents(
+ handle, inode, map->m_lblk, map->m_len, path,
+ EXT4_GET_BLOCKS_CONVERT_UNWRITTEN, NULL);
if (IS_ERR(path))
return path;
@@ -3912,8 +3914,9 @@ ext4_ext_handle_unwritten_extents(handle_t *handle, struct inode *inode,
/* get_block() before submitting IO, split the extent */
if (flags & EXT4_GET_BLOCKS_PRE_IO) {
- path = ext4_split_convert_extents(handle, inode, map, path,
- flags | EXT4_GET_BLOCKS_CONVERT, allocated);
+ path = ext4_split_convert_extents(
+ handle, inode, map->m_lblk, map->m_len, path,
+ flags | EXT4_GET_BLOCKS_CONVERT, allocated);
if (IS_ERR(path))
return path;
/*
--
2.49.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [RFC v4 5/7] ext4: add extsize hint support
2025-07-20 20:57 [RFC v4 0/7] ext4: Add extsize support Ojaswin Mujoo
` (3 preceding siblings ...)
2025-07-20 20:57 ` [RFC v4 4/7] ext4: pass lblk and len explicitly to ext4_split_extent*() Ojaswin Mujoo
@ 2025-07-20 20:57 ` Ojaswin Mujoo
2025-07-20 20:57 ` [RFC v4 6/7] ext4: make extsize work with EOF allocations Ojaswin Mujoo
2025-07-20 20:57 ` [RFC v4 7/7] ext4: add ext4_map_blocks_extsize() wrapper to handle overwrites Ojaswin Mujoo
6 siblings, 0 replies; 8+ messages in thread
From: Ojaswin Mujoo @ 2025-07-20 20:57 UTC (permalink / raw)
To: linux-ext4, Theodore Ts'o
Cc: Jan Kara, Baokun Li, Ritesh Harjani, Zhang Yi, linux-kernel,
Darrick J . Wong, linux-fsdevel
Now that the ioctl is in place, add the underlying infrastructure
to support extent size hints.
** MOTIVATION **
1. This feature allows us to ask the allocator for blocks that are
logically AS WELL AS physically aligned to an extent size hint (aka
extsize), that is generally a power of 2.
2. This means both start and the length of the physical and logical range
should be aligned to the extsize.
3. This sets up the infra we'll eventually need for supporting
non-torn/atomic writes that need to follow a certain alignment as
required by hardware.
4. This can also be extent to other use cases like stripe alignment
** DESIGN NOTES **
* Physical Alignment *
1. Since the extsize is always a power-of-2 (for now) in fs blocks, we
leverage CR_POWER2_ALIGNED allocation to get the blocks. This ensures the
blocks are physically aligned
2. Since this is just a hint, incase we are not able to get any aligned
blocks we simply drop back to non aligned allocation.
* Logical Alignment *
The flow of extsize aligned allocation with buffered and
direct IO:
+--------------------------------------------------------+
| Buffered IO |
+--------------------------------------------------------+
| ext4_map_blocks() call with extsize allocation |
+--------------------------------------------------------+
|
+--------------------------------------------+
| Adjust lblk and len to align to extsize |
+--------------------------------------------+
|
+--------------------------------------------------------+
|Pre-existing written/unwritten blocks in extsize range? |
+--------------------------+-----------------------------+
YES NO
| |
+---------------v---------------+ +----------------v--------------+
| Covers orig range? | | Allocate extsize range |
+---------------+---------------+ +----------------+--------------+
| | |
YES NO |
| | +--------------v--------------+
+--------v-------+ +-------v---------+ | Mark allocated extent as |
| Return blocks | | Fallback to | | unwritten |
+----------------+ | non-extsize | +--------------+--------------+
| allocation | |
+-----------------+ +--------------v--------------+
| Insert extsize extent |
| into tree |
+--------------+--------------+
|
+--------------v--------------+
| Return allocated blocks |
+-----------------------------+
+--------------------------------------------+
| During writeback: |
+--------------------------------------------+
| Use PRE_IO to split only the dirty extent |
+--------------------------------------------+
+--------------------------------------------+
| After IO: |
+--------------------------------------------+
| Convert the extent under IO to written |
+--------------------------------------------+
Same flow for direct IO:
+----------------------------------------------------------------------+
| Direct IO |
+----------------------------------------------------------------------+
| ext4_map_blocks() called with extsize allocation and PRE-IO |
+----------------------------------------------------------------------+
|
+----------------------------------------------------------------------+
| Adjust lblk and len to align to extsize |
+----------------------------------------------------------------------+
|
+----------------------------------------------------------------------+
| Pre-existing written blocks in extsize range? |
+----------------------------------+-----------------------------------+
YES NO
| |
+---------v----------+ +------------------v-----------------+
| Covers orig range? | | Unwritten blocks in extsize range? |
+---------+----------+ +------------------+-----------------+
| | | |
YES NO YES NO
| | | |
+-------v----+ +-----v--------+ +----------v----------+ +-------v----------+
| Return | | Fallback to | | Call ext4_ext_map_ | | Allocate extsize |
| blocks | | non-extsize | | blocks() ->ext4_ext | | range |
+------------+ | allocation | | _handle_unwritten_ | +-------+----------+
+--------------+ | extents() | |
+----------+----------+ +-------v----------+
| | Mark complete |
+----------v----------+ | range unwritten |
| Split orig range | | & insert in |
| from bigger | | tree |
| unwritten extent | +-------+----------+
+----------+----------+ |
| +-------v----------+
+----------v----------+ | Split orig range |
| Mark split extent | | from bigger |
| as unwritten | | allocated extent |
+----------+----------+ +-------+----------+
| |
+----------v----------+ +-------v----------+
| Return split extent | | Mark split extent|
| to user | | as unwritten |
+---------------------+ +-------+----------+
|
+-------v----------+
| Return split |
| extent to user |
+------------------+
+--------------------------------------------+
| After IO: |
+--------------------------------------------+
| Convert the extent under IO to written |
+--------------------------------------------+
** IMPLEMENTATION NOTES **
* Callers of ext4_map_blocks work under the assumption that
ext4_map_blocks will always only return as much as requested or less
but now we might end up allocating more so make changes to
ext4_map_blocks to make sure we adjust the allocated map to only
return as much as user requested.
* Further, we now maintain 2 maps in ext4_map_blocks - the original map
and the extsize map that is used when extsize hint allocation is taking
place. We also pass these 2 maps down because some functions might now
need information of the original map as well as the extsize map.
* For example, when we go for direct IO and there's a hole in the orig
range requested, we allocate based on extsize range and then split the
bigger unwritten extent onto smaller unwritten extents based on orig
range. (Needed so we dont have to split after IO). For this, we need
the information of extsize range as well as orig range hence 2 maps.
* Since now we allocate more than the user requested, to avoid stale
data exposure, we mark the bigger extsize extent as unwritten and then
use the similar flow of dioread_nolock to only mark the extent under
write as written.
* We disable extsize hints when writes are beyond EOF (for now)
* When extsize is set on an inode, we drop to no delalloc allocations
similar to XFS.
Signed-off-by: Ojaswin Mujoo <ojaswin@linux.ibm.com>
---
fs/ext4/ext4.h | 7 +-
fs/ext4/ext4_jbd2.h | 15 ++
fs/ext4/extents.c | 174 +++++++++++++++++++--
fs/ext4/inode.c | 359 +++++++++++++++++++++++++++++++++++++++-----
4 files changed, 497 insertions(+), 58 deletions(-)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index d00870cb15f2..4b69326a0f2f 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -746,6 +746,7 @@ enum {
* Look EXT4_MAP_QUERY_LAST_IN_LEAF.
*/
#define EXT4_GET_BLOCKS_QUERY_LAST_IN_LEAF 0x1000
+#define EXT4_GET_BLOCKS_EXTSIZE 0x2000
/*
* The bit position of these flags must not overlap with any of the
@@ -765,7 +766,8 @@ enum {
*/
#define EXT4_EX_QUERY_FILTER (EXT4_EX_NOCACHE | EXT4_EX_FORCE_CACHE |\
EXT4_EX_NOFAIL |\
- EXT4_GET_BLOCKS_QUERY_LAST_IN_LEAF)
+ EXT4_GET_BLOCKS_QUERY_LAST_IN_LEAF |\
+ EXT4_GET_BLOCKS_EXTSIZE)
/*
* Flags used by ext4_free_blocks
@@ -3755,7 +3757,8 @@ struct ext4_extent;
extern void ext4_ext_tree_init(handle_t *handle, struct inode *inode);
extern int ext4_ext_index_trans_blocks(struct inode *inode, int extents);
extern int ext4_ext_map_blocks(handle_t *handle, struct inode *inode,
- struct ext4_map_blocks *map, int flags);
+ struct ext4_map_blocks *orig_map,
+ struct ext4_map_blocks *extsize_map, int flags);
extern int ext4_ext_truncate(handle_t *, struct inode *);
extern int ext4_ext_remove_space(struct inode *inode, ext4_lblk_t start,
ext4_lblk_t end);
diff --git a/fs/ext4/ext4_jbd2.h b/fs/ext4/ext4_jbd2.h
index 63d17c5201b5..dbac8d4f7f78 100644
--- a/fs/ext4/ext4_jbd2.h
+++ b/fs/ext4/ext4_jbd2.h
@@ -458,4 +458,19 @@ static inline int ext4_journal_destroy(struct ext4_sb_info *sbi, journal_t *jour
return err;
}
+static inline int ext4_should_use_extsize(struct inode *inode)
+{
+ if (!S_ISREG(inode->i_mode))
+ return 0;
+ if (!(ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)))
+ return 0;
+ return (ext4_inode_get_extsize(EXT4_I(inode)) > 0);
+}
+
+static inline int ext4_should_use_unwrit_extents(struct inode *inode)
+{
+ return (ext4_should_dioread_nolock(inode) ||
+ ext4_should_use_extsize(inode));
+}
+
#endif /* _EXT4_JBD2_H */
diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
index 3233ab89c99e..8ea8b03a4a16 100644
--- a/fs/ext4/extents.c
+++ b/fs/ext4/extents.c
@@ -3892,15 +3892,24 @@ convert_initialized_extent(handle_t *handle, struct inode *inode,
static struct ext4_ext_path *
ext4_ext_handle_unwritten_extents(handle_t *handle, struct inode *inode,
- struct ext4_map_blocks *map,
+ struct ext4_map_blocks *orig_map,
+ struct ext4_map_blocks *extsize_map,
struct ext4_ext_path *path, int flags,
unsigned int *allocated, ext4_fsblk_t newblock)
{
+ struct ext4_map_blocks *map;
int err = 0;
- ext_debug(inode, "logical block %llu, max_blocks %u, flags 0x%x, allocated %u\n",
- (unsigned long long)map->m_lblk, map->m_len, flags,
- *allocated);
+ if (flags & EXT4_GET_BLOCKS_EXTSIZE) {
+ BUG_ON(extsize_map == NULL);
+ map = extsize_map;
+ } else
+ map = orig_map;
+
+ ext_debug(
+ inode,
+ "logical block %llu, max_blocks %u, flags 0x%x, allocated %u\n",
+ (unsigned long long)map->m_lblk, map->m_len, flags, *allocated);
ext4_ext_show_leaf(inode, path);
/*
@@ -3909,13 +3918,14 @@ ext4_ext_handle_unwritten_extents(handle_t *handle, struct inode *inode,
*/
flags |= EXT4_GET_BLOCKS_METADATA_NOFAIL;
- trace_ext4_ext_handle_unwritten_extents(inode, map, flags,
- *allocated, newblock);
+ trace_ext4_ext_handle_unwritten_extents(inode, map, flags, *allocated,
+ newblock);
/* get_block() before submitting IO, split the extent */
if (flags & EXT4_GET_BLOCKS_PRE_IO) {
+ /* Split should always happen based on original mapping */
path = ext4_split_convert_extents(
- handle, inode, map->m_lblk, map->m_len, path,
+ handle, inode, orig_map->m_lblk, orig_map->m_len, path,
flags | EXT4_GET_BLOCKS_CONVERT, allocated);
if (IS_ERR(path))
return path;
@@ -3930,11 +3940,19 @@ ext4_ext_handle_unwritten_extents(handle_t *handle, struct inode *inode,
err = -EFSCORRUPTED;
goto errout;
}
+
+ /*
+ * For extsize case we need to adjust lblk to start of split
+ * extent because the m_len will be set to len of split extent.
+ * No change for non extsize case
+ */
+ map->m_lblk = orig_map->m_lblk;
map->m_flags |= EXT4_MAP_UNWRITTEN;
goto out;
}
/* IO end_io complete, convert the filled extent to written */
if (flags & EXT4_GET_BLOCKS_CONVERT) {
+ BUG_ON(map == extsize_map);
path = ext4_convert_unwritten_extents_endio(handle, inode,
map, path);
if (IS_ERR(path))
@@ -4192,7 +4210,8 @@ static ext4_lblk_t ext4_ext_determine_insert_hole(struct inode *inode,
* return < 0, error case.
*/
int ext4_ext_map_blocks(handle_t *handle, struct inode *inode,
- struct ext4_map_blocks *map, int flags)
+ struct ext4_map_blocks *orig_map,
+ struct ext4_map_blocks *extsize_map, int flags)
{
struct ext4_ext_path *path = NULL;
struct ext4_extent newex, *ex, ex2;
@@ -4203,6 +4222,17 @@ int ext4_ext_map_blocks(handle_t *handle, struct inode *inode,
unsigned int allocated_clusters = 0;
struct ext4_allocation_request ar;
ext4_lblk_t cluster_offset;
+ struct ext4_map_blocks *map;
+#ifdef CONFIG_EXT4_DEBUG
+ struct ext4_ext_path *test_path = NULL;
+#endif
+
+ if (flags & EXT4_GET_BLOCKS_EXTSIZE) {
+ BUG_ON(extsize_map == NULL);
+ map = extsize_map;
+ } else
+ map = orig_map;
+
ext_debug(inode, "blocks %u/%u requested\n", map->m_lblk, map->m_len);
trace_ext4_ext_map_blocks_enter(inode, map->m_lblk, map->m_len, flags);
@@ -4259,6 +4289,7 @@ int ext4_ext_map_blocks(handle_t *handle, struct inode *inode,
*/
if ((!ext4_ext_is_unwritten(ex)) &&
(flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN)) {
+ BUG_ON(map == extsize_map);
path = convert_initialized_extent(handle,
inode, map, path, &allocated);
if (IS_ERR(path))
@@ -4275,8 +4306,8 @@ int ext4_ext_map_blocks(handle_t *handle, struct inode *inode,
}
path = ext4_ext_handle_unwritten_extents(
- handle, inode, map, path, flags,
- &allocated, newblock);
+ handle, inode, orig_map, extsize_map, path,
+ flags, &allocated, newblock);
if (IS_ERR(path))
err = PTR_ERR(path);
goto out;
@@ -4309,6 +4340,7 @@ int ext4_ext_map_blocks(handle_t *handle, struct inode *inode,
*/
if (cluster_offset && ex &&
get_implied_cluster_alloc(inode->i_sb, map, ex, path)) {
+ BUG_ON(map == extsize_map);
ar.len = allocated = map->m_len;
newblock = map->m_pblk;
goto got_allocated_blocks;
@@ -4329,6 +4361,7 @@ int ext4_ext_map_blocks(handle_t *handle, struct inode *inode,
* cluster we can use. */
if ((sbi->s_cluster_ratio > 1) && err &&
get_implied_cluster_alloc(inode->i_sb, map, &ex2, path)) {
+ BUG_ON(map == extsize_map);
ar.len = allocated = map->m_len;
newblock = map->m_pblk;
err = 0;
@@ -4383,6 +4416,8 @@ int ext4_ext_map_blocks(handle_t *handle, struct inode *inode,
ar.flags |= EXT4_MB_DELALLOC_RESERVED;
if (flags & EXT4_GET_BLOCKS_METADATA_NOFAIL)
ar.flags |= EXT4_MB_USE_RESERVED;
+ if (flags & EXT4_GET_BLOCKS_EXTSIZE)
+ ar.flags |= EXT4_MB_HINT_ALIGNED;
newblock = ext4_mb_new_blocks(handle, &ar, &err);
if (!newblock)
goto out;
@@ -4404,9 +4439,114 @@ int ext4_ext_map_blocks(handle_t *handle, struct inode *inode,
map->m_flags |= EXT4_MAP_UNWRITTEN;
}
- path = ext4_ext_insert_extent(handle, inode, path, &newex, flags);
- if (IS_ERR(path)) {
- err = PTR_ERR(path);
+ if ((flags & EXT4_GET_BLOCKS_EXTSIZE) &&
+ (flags & EXT4_GET_BLOCKS_PRE_IO)) {
+ /*
+ * With EXTSIZE and PRE-IO (direct io case) we have to be careful
+ * because we want to insert the complete extent but split only the
+ * originally requested range.
+ *
+ * Below are the different (S)cenarios and the (A)ction we take:
+ *
+ * S1: New extent covers the original range completely/partially.
+ * A1: Insert new extent, allow merges. Then split the original
+ * range from this. Adjust the length of split if new extent only
+ * partially covers original.
+ *
+ * S2: New extent doesn't cover original range at all
+ * A2: Just insert this range and return. Rest is handled in
+ * ext4_map_blocks()
+ * NOTE: We can handle this as an error with EAGAIN in future.
+ */
+ ext4_lblk_t newex_lblk = le32_to_cpu(newex.ee_block);
+ loff_t newex_len = ext4_ext_get_actual_len(&newex);
+
+ if (in_range(orig_map->m_lblk, newex_lblk, newex_len)) {
+ /* S1 */
+ loff_t split_len = 0;
+
+ BUG_ON(!ext4_ext_is_unwritten(&newex));
+
+ if (newex_lblk + newex_len >=
+ orig_map->m_lblk + (loff_t)orig_map->m_len)
+ split_len = orig_map->m_len;
+ else
+ split_len = newex_len -
+ (orig_map->m_lblk - newex_lblk);
+
+ path = ext4_ext_insert_extent(
+ handle, inode, path, &newex,
+ (flags & ~EXT4_GET_BLOCKS_PRE_IO));
+ if (IS_ERR(path)) {
+ err = PTR_ERR(path);
+ goto insert_error;
+ }
+
+ /*
+ * Update path before split
+ * NOTE: This might no longer be needed with recent
+ * changes in ext4_ext_insert_extent()
+ */
+ path = ext4_find_extent(inode, orig_map->m_lblk, path, 0);
+ if (IS_ERR(path)) {
+ err = PTR_ERR(path);
+ goto insert_error;
+ }
+
+ /*
+ * GET_BLOCKS_CONVERT is needed to make sure split
+ * extent is marked unwritten although the flags itself
+ * means that the extent should be converted to written.
+ *
+ * TODO: This is because ext4_split_convert_extents()
+ * doesn't respect the flags at all but fixing this
+ * needs more involved design changes.
+ */
+ path = ext4_split_convert_extents(
+ handle, inode, orig_map->m_lblk, split_len,
+ path, flags | EXT4_GET_BLOCKS_CONVERT, NULL);
+ if (IS_ERR(path)) {
+ err = PTR_ERR(path);
+ goto insert_error;
+ }
+
+#ifdef CONFIG_EXT4_DEBUG
+ test_path = ext4_find_extent(inode, orig_map->m_lblk,
+ NULL, 0);
+ if (!IS_ERR(test_path)) {
+ /* Confirm we've correctly split and marked the extent unwritten */
+ struct ext4_extent *test_ex =
+ test_path[ext_depth(inode)].p_ext;
+ WARN_ON(!ext4_ext_is_unwritten(test_ex));
+ WARN_ON(test_ex->ee_block != orig_map->m_lblk);
+ WARN_ON(ext4_ext_get_actual_len(test_ex) !=
+ orig_map->m_len);
+ kfree(test_path);
+ }
+#endif
+ } else {
+ /* S2 */
+ BUG_ON(orig_map->m_lblk < newex_lblk + newex_len);
+
+ path = ext4_ext_insert_extent(
+ handle, inode, path, &newex,
+ (flags & ~EXT4_GET_BLOCKS_PRE_IO));
+ if (IS_ERR(path)) {
+ err = PTR_ERR(path);
+ goto insert_error;
+ }
+ }
+ } else {
+ path = ext4_ext_insert_extent(handle, inode, path, &newex,
+ flags);
+ if (IS_ERR(path)) {
+ err = PTR_ERR(path);
+ goto insert_error;
+ }
+ }
+
+insert_error:
+ if (err) {
if (allocated_clusters) {
int fb_flags = 0;
@@ -4690,7 +4830,7 @@ static long ext4_do_fallocate(struct file *file, loff_t offset,
loff_t end = offset + len;
loff_t new_size = 0;
ext4_lblk_t start_lblk, len_lblk;
- int ret;
+ int ret, flags;
trace_ext4_fallocate_enter(inode, offset, len, mode);
WARN_ON_ONCE(!inode_is_locked(inode));
@@ -4712,8 +4852,12 @@ static long ext4_do_fallocate(struct file *file, loff_t offset,
goto out;
}
+ flags = EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT;
+ if (ext4_should_use_extsize(inode))
+ flags |= EXT4_GET_BLOCKS_EXTSIZE;
+
ret = ext4_alloc_file_blocks(file, start_lblk, len_lblk, new_size,
- EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT);
+ flags);
if (ret)
goto out;
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 664218228fd5..385fbd745e12 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -459,7 +459,7 @@ static void ext4_map_blocks_es_recheck(handle_t *handle,
*/
down_read(&EXT4_I(inode)->i_data_sem);
if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)) {
- retval = ext4_ext_map_blocks(handle, inode, map, 0);
+ retval = ext4_ext_map_blocks(handle, inode, map, NULL, 0);
} else {
retval = ext4_ind_map_blocks(handle, inode, map, 0);
}
@@ -501,7 +501,7 @@ static int ext4_map_query_blocks_next_in_leaf(handle_t *handle,
map2.m_lblk = map->m_lblk + map->m_len;
map2.m_len = orig_mlen - map->m_len;
map2.m_flags = 0;
- retval = ext4_ext_map_blocks(handle, inode, &map2, 0);
+ retval = ext4_ext_map_blocks(handle, inode, &map2, NULL, 0);
if (retval <= 0) {
ext4_es_insert_extent(inode, map->m_lblk, map->m_len,
@@ -539,17 +539,37 @@ static int ext4_map_query_blocks_next_in_leaf(handle_t *handle,
}
static int ext4_map_query_blocks(handle_t *handle, struct inode *inode,
- struct ext4_map_blocks *map, int flags)
+ struct ext4_map_blocks *orig_map,
+ struct ext4_map_blocks *extsize_map,
+ int flags)
{
unsigned int status;
int retval;
- unsigned int orig_mlen = map->m_len;
+ unsigned int orig_mlen;
+ struct ext4_map_blocks *map;
flags &= EXT4_EX_QUERY_FILTER;
+
+ if (flags & EXT4_GET_BLOCKS_EXTSIZE) {
+ BUG_ON(extsize_map == NULL);
+ map = extsize_map;
+ } else
+ map = orig_map;
+
+ orig_mlen = map->m_len;
+
if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))
- retval = ext4_ext_map_blocks(handle, inode, map, flags);
- else
+ if (flags & EXT4_GET_BLOCKS_EXTSIZE) {
+ retval = ext4_ext_map_blocks(handle, inode, orig_map,
+ map, flags);
+ } else {
+ retval = ext4_ext_map_blocks(handle, inode, map, NULL,
+ flags);
+ }
+ else {
+ BUG_ON(flags & EXT4_GET_BLOCKS_EXTSIZE);
retval = ext4_ind_map_blocks(handle, inode, map, flags);
+ }
if (retval <= 0)
return retval;
@@ -581,11 +601,20 @@ static int ext4_map_query_blocks(handle_t *handle, struct inode *inode,
}
static int ext4_map_create_blocks(handle_t *handle, struct inode *inode,
- struct ext4_map_blocks *map, int flags)
+ struct ext4_map_blocks *orig_map,
+ struct ext4_map_blocks *extsize_map,
+ int flags)
{
struct extent_status es;
unsigned int status;
int err, retval = 0;
+ struct ext4_map_blocks *map;
+
+ if (flags & EXT4_GET_BLOCKS_EXTSIZE) {
+ BUG_ON(extsize_map == NULL);
+ map = extsize_map;
+ } else
+ map = orig_map;
/*
* We pass in the magic EXT4_GET_BLOCKS_DELALLOC_RESERVE
@@ -606,8 +635,15 @@ static int ext4_map_create_blocks(handle_t *handle, struct inode *inode,
* changed the inode type in between.
*/
if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)) {
- retval = ext4_ext_map_blocks(handle, inode, map, flags);
+ if (flags & EXT4_GET_BLOCKS_EXTSIZE) {
+ retval = ext4_ext_map_blocks(handle, inode, orig_map,
+ map, flags);
+ } else {
+ retval = ext4_ext_map_blocks(handle, inode, map, NULL,
+ flags);
+ }
} else {
+ BUG_ON(flags & EXT4_GET_BLOCKS_EXTSIZE);
retval = ext4_ind_map_blocks(handle, inode, map, flags);
/*
@@ -662,6 +698,80 @@ static int ext4_map_create_blocks(handle_t *handle, struct inode *inode,
return retval;
}
+/**
+ * Extsize hint will change the mapped range and hence we'll end up mapping more.
+ * To not confuse the caller, adjust the struct ext4_map_blocks to reflect the
+ * original mapping requested by them.
+ *
+ * @cur_map: The block mapping we are working with (for sanity check)
+ * @orig_map: The originally requested mapping
+ * @extsize_map: The mapping after adjusting for extsize hint
+ * @flags Get block flags (for sanity check)
+ *
+ * This function assumes that the orig_mlblk is contained within the mapping
+ * held in extsize_map. Caller must make sure this is true.
+ */
+static inline unsigned int ext4_extsize_adjust_map(struct ext4_map_blocks *cur_map,
+ struct ext4_map_blocks *orig_map,
+ struct ext4_map_blocks *extsize_map,
+ int flags)
+{
+ __u64 map_end = (__u64)extsize_map->m_lblk + extsize_map->m_len;
+
+ BUG_ON(cur_map != extsize_map || !(flags & EXT4_GET_BLOCKS_EXTSIZE));
+
+ orig_map->m_len = min(orig_map->m_len, map_end - orig_map->m_lblk);
+ orig_map->m_pblk =
+ extsize_map->m_pblk + (orig_map->m_lblk - extsize_map->m_lblk);
+ orig_map->m_flags = extsize_map->m_flags;
+
+ return orig_map->m_len;
+}
+
+/**
+ * ext4_error_adjust_map - Adjust map returned upon error in ext4_map_blocks()
+ *
+ * @cur_map: current map we are working with
+ * @orig_map: original map that would be returned to the user.
+ *
+ * Most of the callers of ext4_map_blocks() ignore the map on error, however
+ * some use it for debug logging. In this case, they log state of the map just
+ * before the error, hence this function ensures that map returned to caller is
+ * the one we were working with when error happened. Mostly useful when extsize
+ * hints are enabled.
+ */
+static inline void ext4_error_adjust_map(struct ext4_map_blocks *cur_map,
+ struct ext4_map_blocks *orig_map)
+{
+ if (cur_map != orig_map)
+ memcpy(orig_map, cur_map, sizeof(*cur_map));
+}
+
+/*
+ * This functions resets the mapping to it's original state after it has been
+ * modified due to extent size hint and drops the extsize hint. To be used
+ * incase we want to fallback from extsize based aligned allocation to normal
+ * allocation
+ *
+ * @map: The block mapping where lblk and len have been modified
+ * because of extsize hint
+ * @flags: The get_block flags
+ * @orig_mlblk: The originally requested logical block to map
+ * @orig_mlen: The originally requested len to map
+ * @orig_flags: The originally requested get_block flags
+ */
+static inline void ext4_extsize_reset_map(struct ext4_map_blocks *map,
+ int *flags, ext4_lblk_t orig_mlblk,
+ unsigned int orig_mlen,
+ int orig_flags)
+{
+ /* Drop the extsize hint from original flags */
+ *flags = orig_flags & ~EXT4_GET_BLOCKS_EXTSIZE;
+ map->m_lblk = orig_mlblk;
+ map->m_len = orig_mlen;
+ map->m_flags = 0;
+}
+
/*
* The ext4_map_blocks() function tries to look up the requested blocks,
* and returns if the blocks are already mapped.
@@ -686,30 +796,40 @@ static int ext4_map_create_blocks(handle_t *handle, struct inode *inode,
* It returns the error in case of allocation failure.
*/
int ext4_map_blocks(handle_t *handle, struct inode *inode,
- struct ext4_map_blocks *map, int flags)
+ struct ext4_map_blocks *orig_map, int flags)
{
struct extent_status es;
int retval;
int ret = 0;
- unsigned int orig_mlen = map->m_len;
+
+ ext4_lblk_t orig_mlblk, extsize_mlblk;
+ unsigned int orig_mlen, extsize_mlen;
+ int orig_flags;
+
+ struct ext4_map_blocks *map = NULL;
+ struct ext4_map_blocks extsize_map = {0};
+
+ __u32 extsize = ext4_inode_get_extsize(EXT4_I(inode));
+ bool should_extsize = false;
+
#ifdef ES_AGGRESSIVE_TEST
- struct ext4_map_blocks orig_map;
+ struct ext4_map_blocks test_map;
- memcpy(&orig_map, map, sizeof(*map));
+ memcpy(&test_map, map, sizeof(*map));
#endif
- map->m_flags = 0;
- ext_debug(inode, "flag 0x%x, max_blocks %u, logical block %lu\n",
- flags, map->m_len, (unsigned long) map->m_lblk);
+ orig_map->m_flags = 0;
+ ext_debug(inode, "flag 0x%x, max_blocks %u, logical block %lu\n", flags,
+ orig_map->m_len, (unsigned long)orig_map->m_lblk);
/*
* ext4_map_blocks returns an int, and m_len is an unsigned int
*/
- if (unlikely(map->m_len > INT_MAX))
- map->m_len = INT_MAX;
+ if (unlikely(orig_map->m_len > INT_MAX))
+ orig_map->m_len = INT_MAX;
/* We can handle the block number less than EXT_MAX_BLOCKS */
- if (unlikely(map->m_lblk >= EXT_MAX_BLOCKS))
+ if (unlikely(orig_map->m_lblk >= EXT_MAX_BLOCKS))
return -EFSCORRUPTED;
/*
@@ -722,6 +842,73 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode,
else
ext4_check_map_extents_env(inode);
+ orig_mlblk = orig_map->m_lblk;
+ orig_mlen = orig_map->m_len;
+ orig_flags = flags;
+
+set_map:
+ should_extsize = (extsize && (flags & EXT4_GET_BLOCKS_CREATE) &&
+ (flags & EXT4_GET_BLOCKS_EXTSIZE));
+ if (should_extsize) {
+ /*
+ * We adjust the extent size here but we still return the
+ * original lblk and len while returning to keep the behavior
+ * compatible.
+ */
+ int len, align;
+ /*
+ * NOTE: Should we import EXT_UNWRITTEN_MAX_LEN from
+ * ext4_extents.h here?
+ */
+ int max_unwrit_len = ((1UL << 15) - 1);
+ loff_t end;
+
+ align = orig_map->m_lblk % extsize;
+ len = orig_map->m_len + align;
+
+ extsize_map.m_lblk = orig_map->m_lblk - align;
+ extsize_map.m_len =
+ max_t(unsigned int, roundup_pow_of_two(len), extsize);
+
+ /*
+ * For now allocations beyond EOF don't use extsize hints so
+ * that we can avoid dealing with extra blocks allocated past
+ * EOF. We have inode lock since extsize allocations are
+ * non-delalloc so i_size can be accessed safely
+ */
+ end = (extsize_map.m_lblk + (loff_t)extsize_map.m_len) << inode->i_blkbits;
+ if (end > inode->i_size) {
+ flags = orig_flags & ~EXT4_GET_BLOCKS_EXTSIZE;
+ goto set_map;
+ }
+
+ /* Fallback to normal allocation if we go beyond max len */
+ if (extsize_map.m_len >= max_unwrit_len) {
+ flags = orig_flags & ~EXT4_GET_BLOCKS_EXTSIZE;
+ goto set_map;
+ }
+
+ /*
+ * We are allocating more than requested. We'll have to convert
+ * the extent to unwritten and then convert only the part
+ * requested to written. For now we are using the same flow as
+ * dioread nolock to achieve this. Hence the caller has to pass
+ * CREATE_UNWRIT with EXTSIZE
+ */
+ if (WARN_ON_ONCE(!(flags & EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT))) {
+ /* Fallback to non extsize allocation */
+ flags = orig_flags & ~EXT4_GET_BLOCKS_EXTSIZE;
+ goto set_map;
+ }
+
+ extsize_mlblk = extsize_map.m_lblk;
+ extsize_mlen = extsize_map.m_len;
+
+ extsize_map.m_flags = orig_map->m_flags;
+ map = &extsize_map;
+ } else
+ map = orig_map;
+
/* Lookup extent status tree firstly */
if (ext4_es_lookup_extent(inode, map->m_lblk, NULL, &es)) {
if (ext4_es_is_written(&es) || ext4_es_is_unwritten(&es)) {
@@ -750,7 +937,7 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode,
return retval;
#ifdef ES_AGGRESSIVE_TEST
ext4_map_blocks_es_recheck(handle, inode, map,
- &orig_map, flags);
+ &test_map, flags);
#endif
if (!(flags & EXT4_GET_BLOCKS_QUERY_LAST_IN_LEAF) ||
orig_mlen == map->m_len)
@@ -770,19 +957,58 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode,
* file system block.
*/
down_read(&EXT4_I(inode)->i_data_sem);
- retval = ext4_map_query_blocks(handle, inode, map, flags);
+ if (should_extsize) {
+ BUG_ON(map != &extsize_map);
+ retval = ext4_map_query_blocks(handle, inode, orig_map, map, flags);
+ } else {
+ BUG_ON(map != orig_map);
+ retval = ext4_map_query_blocks(handle, inode, map, NULL, flags);
+ }
up_read((&EXT4_I(inode)->i_data_sem));
found:
if (retval > 0 && map->m_flags & EXT4_MAP_MAPPED) {
ret = check_block_validity(inode, map);
- if (ret != 0)
+ if (ret != 0) {
+ ext4_error_adjust_map(map, orig_map);
return ret;
+ }
}
/* If it is only a block(s) look up */
- if ((flags & EXT4_GET_BLOCKS_CREATE) == 0)
+ if ((flags & EXT4_GET_BLOCKS_CREATE) == 0) {
+ BUG_ON(flags & EXT4_GET_BLOCKS_EXTSIZE);
return retval;
+ }
+
+ /* Handle some special cases when extsize based allocation is needed */
+ if (retval >= 0 && flags & EXT4_GET_BLOCKS_EXTSIZE) {
+ bool orig_in_range =
+ in_range(orig_mlblk, (__u64)map->m_lblk, map->m_len);
+ /*
+ * Special case: if the extsize range is mapped already and
+ * covers the original start, we return it.
+ */
+ if (map->m_flags & EXT4_MAP_MAPPED && orig_in_range) {
+ /*
+ * We don't use EXTSIZE with CONVERT_UNWRITTEN so
+ * we can directly return the written extent
+ */
+ return ext4_extsize_adjust_map(map, orig_map, &extsize_map, flags);
+ }
+
+ /*
+ * Fallback case: if the found mapping (or hole) doesn't cover
+ * the extsize required, then just fall back to normal
+ * allocation to keep things simple.
+ */
+
+ if (map->m_lblk != extsize_mlblk ||
+ map->m_len != extsize_mlen) {
+ flags = orig_flags & ~EXT4_GET_BLOCKS_EXTSIZE;
+ goto set_map;
+ }
+ }
/*
* Returns if the blocks have already allocated
@@ -808,12 +1034,22 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode,
* with create == 1 flag.
*/
down_write(&EXT4_I(inode)->i_data_sem);
- retval = ext4_map_create_blocks(handle, inode, map, flags);
+ if (should_extsize) {
+ BUG_ON(map != &extsize_map);
+ retval = ext4_map_create_blocks(handle, inode, orig_map, map,
+ flags);
+ } else {
+ BUG_ON(map != orig_map);
+ retval = ext4_map_create_blocks(handle, inode, map, NULL,
+ flags);
+ }
up_write((&EXT4_I(inode)->i_data_sem));
if (retval > 0 && map->m_flags & EXT4_MAP_MAPPED) {
ret = check_block_validity(inode, map);
- if (ret != 0)
+ if (ret != 0) {
+ ext4_error_adjust_map(map, orig_map);
return ret;
+ }
/*
* Inodes with freshly allocated blocks where contents will be
@@ -835,16 +1071,38 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode,
else
ret = ext4_jbd2_inode_add_write(handle, inode,
start_byte, length);
- if (ret)
+ if (ret) {
+ ext4_error_adjust_map(map, orig_map);
return ret;
+ }
}
}
if (retval > 0 && (map->m_flags & EXT4_MAP_UNWRITTEN ||
map->m_flags & EXT4_MAP_MAPPED))
ext4_fc_track_range(handle, inode, map->m_lblk,
map->m_lblk + map->m_len - 1);
- if (retval < 0)
+
+ if (retval > 0 && flags & EXT4_GET_BLOCKS_EXTSIZE) {
+ /*
+ * In the rare case that we have a short allocation and orig
+ * lblk doesn't lie in mapped range just try to retry with
+ * actual allocation. This is not ideal but this should be an
+ * edge case near ENOSPC.
+ *
+ * NOTE: This has a side effect that blocks are allocated but
+ * not used. Can we avoid that?
+ */
+ if (!in_range(orig_mlblk, (__u64)map->m_lblk, map->m_len)) {
+ flags = orig_flags & ~EXT4_GET_BLOCKS_EXTSIZE;
+ goto set_map;
+ }
+ return ext4_extsize_adjust_map(map, orig_map, &extsize_map, flags);
+ }
+
+ if (retval < 0) {
+ ext4_error_adjust_map(map, orig_map);
ext_debug(inode, "failed with err %d\n", retval);
+ }
return retval;
}
@@ -900,18 +1158,20 @@ static int _ext4_get_block(struct inode *inode, sector_t iblock,
{
struct ext4_map_blocks map;
int ret = 0;
+ unsigned int orig_mlen = bh->b_size >> inode->i_blkbits;
if (ext4_has_inline_data(inode))
return -ERANGE;
map.m_lblk = iblock;
- map.m_len = bh->b_size >> inode->i_blkbits;
+ map.m_len = orig_mlen;
ret = ext4_map_blocks(ext4_journal_current_handle(), inode, &map,
flags);
if (ret > 0) {
map_bh(bh, inode->i_sb, map.m_pblk);
ext4_update_bh_state(bh, map.m_flags);
+ WARN_ON(map.m_len != orig_mlen);
bh->b_size = inode->i_sb->s_blocksize * map.m_len;
ret = 0;
} else if (ret == 0) {
@@ -937,11 +1197,14 @@ int ext4_get_block_unwritten(struct inode *inode, sector_t iblock,
struct buffer_head *bh_result, int create)
{
int ret = 0;
+ int flags = EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT;
+
+ if (ext4_should_use_extsize(inode))
+ flags |= EXT4_GET_BLOCKS_EXTSIZE;
ext4_debug("ext4_get_block_unwritten: inode %lu, create flag %d\n",
inode->i_ino, create);
- ret = _ext4_get_block(inode, iblock, bh_result,
- EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT);
+ ret = _ext4_get_block(inode, iblock, bh_result, flags);
/*
* If the buffer is marked unwritten, mark it as new to make sure it is
@@ -1298,7 +1561,8 @@ static int ext4_write_begin(struct file *file, struct address_space *mapping,
ext4_journal_blocks_per_folio(inode)) + 1;
index = pos >> PAGE_SHIFT;
- if (ext4_test_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA)) {
+ if (!ext4_should_use_extsize(inode) &&
+ ext4_test_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA)) {
ret = ext4_try_to_write_inline_data(mapping, inode, pos, len,
foliop);
if (ret < 0)
@@ -1354,7 +1618,7 @@ static int ext4_write_begin(struct file *file, struct address_space *mapping,
/* In case writeback began while the folio was unlocked */
folio_wait_stable(folio);
- if (ext4_should_dioread_nolock(inode))
+ if (ext4_should_use_unwrit_extents(inode))
ret = ext4_block_write_begin(handle, folio, pos, len,
ext4_get_block_unwritten);
else
@@ -1948,7 +2212,7 @@ static int ext4_da_map_blocks(struct inode *inode, struct ext4_map_blocks *map)
if (ext4_has_inline_data(inode))
retval = 0;
else
- retval = ext4_map_query_blocks(NULL, inode, map, 0);
+ retval = ext4_map_query_blocks(NULL, inode, map, NULL, 0);
up_read(&EXT4_I(inode)->i_data_sem);
if (retval)
return retval < 0 ? retval : 0;
@@ -1971,7 +2235,7 @@ static int ext4_da_map_blocks(struct inode *inode, struct ext4_map_blocks *map)
goto found;
}
} else if (!ext4_has_inline_data(inode)) {
- retval = ext4_map_query_blocks(NULL, inode, map, 0);
+ retval = ext4_map_query_blocks(NULL, inode, map, NULL, 0);
if (retval) {
up_write(&EXT4_I(inode)->i_data_sem);
return retval < 0 ? retval : 0;
@@ -2344,6 +2608,7 @@ static int mpage_map_one_extent(handle_t *handle, struct mpage_da_data *mpd)
struct ext4_map_blocks *map = &mpd->map;
int get_blocks_flags;
int err, dioread_nolock;
+ int extsize = ext4_should_use_extsize(inode);
/* Make sure transaction has enough credits for this extent */
err = ext4_journal_ensure_extent_credits(handle, inode);
@@ -2371,11 +2636,14 @@ static int mpage_map_one_extent(handle_t *handle, struct mpage_da_data *mpd)
dioread_nolock = ext4_should_dioread_nolock(inode);
if (dioread_nolock)
get_blocks_flags |= EXT4_GET_BLOCKS_IO_CREATE_EXT;
+ if (extsize)
+ get_blocks_flags |= EXT4_GET_BLOCKS_PRE_IO;
err = ext4_map_blocks(handle, inode, map, get_blocks_flags);
if (err < 0)
return err;
- if (dioread_nolock && (map->m_flags & EXT4_MAP_UNWRITTEN)) {
+ if ((extsize || dioread_nolock) &&
+ (map->m_flags & EXT4_MAP_UNWRITTEN)) {
if (!mpd->io_submit.io_end->handle &&
ext4_handle_valid(handle)) {
mpd->io_submit.io_end->handle = handle->h_rsv_handle;
@@ -2832,12 +3100,13 @@ static int ext4_do_writepages(struct mpage_da_data *mpd)
}
mpd->journalled_more_data = 0;
- if (ext4_should_dioread_nolock(inode)) {
- int bpf = ext4_journal_blocks_per_folio(inode);
+ if (ext4_should_use_unwrit_extents(inode)) {
/*
- * We may need to convert up to one extent per block in
- * the folio and we may dirty the inode.
+ * For extsize allocation or dioread_nolock, we may need to
+ * convert up to one extent per block in the page and we may
+ * dirty the inode.
*/
+ int bpf = ext4_journal_blocks_per_folio(inode);
rsv_blocks = 1 + ext4_ext_index_trans_blocks(inode, bpf);
}
@@ -3125,7 +3394,8 @@ static int ext4_da_write_begin(struct file *file, struct address_space *mapping,
index = pos >> PAGE_SHIFT;
- if (ext4_nonda_switch(inode->i_sb) || ext4_verity_in_progress(inode)) {
+ if (ext4_nonda_switch(inode->i_sb) || ext4_verity_in_progress(inode) ||
+ ext4_should_use_extsize(inode)) {
*fsdata = (void *)FALL_BACK_TO_NONDELALLOC;
return ext4_write_begin(file, mapping, pos,
len, foliop, fsdata);
@@ -3740,12 +4010,19 @@ static int ext4_iomap_alloc(struct inode *inode, struct ext4_map_blocks *map,
* can complete at any point during the I/O and subsequently push the
* i_disksize out to i_size. This could be beyond where direct I/O is
* happening and thus expose allocated blocks to direct I/O reads.
+ *
+ * NOTE for extsize hints: We only support it for writes inside
+ * EOF (for now) to not have to deal with blocks past EOF
*/
else if (((loff_t)map->m_lblk << blkbits) >= i_size_read(inode))
m_flags = EXT4_GET_BLOCKS_CREATE;
- else if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))
+ else if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)) {
m_flags = EXT4_GET_BLOCKS_IO_CREATE_EXT;
+ if (ext4_should_use_extsize(inode))
+ m_flags |= EXT4_GET_BLOCKS_EXTSIZE;
+ }
+
if (flags & IOMAP_ATOMIC)
ret = ext4_map_blocks_atomic_write(handle, inode, map, m_flags,
&force_commit);
@@ -6778,7 +7055,7 @@ vm_fault_t ext4_page_mkwrite(struct vm_fault *vmf)
}
folio_unlock(folio);
/* OK, we need to fill the hole... */
- if (ext4_should_dioread_nolock(inode))
+ if (ext4_should_use_unwrit_extents(inode))
get_block = ext4_get_block_unwritten;
retry_alloc:
/* Start journal and allocate blocks */
--
2.49.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [RFC v4 6/7] ext4: make extsize work with EOF allocations
2025-07-20 20:57 [RFC v4 0/7] ext4: Add extsize support Ojaswin Mujoo
` (4 preceding siblings ...)
2025-07-20 20:57 ` [RFC v4 5/7] ext4: add extsize hint support Ojaswin Mujoo
@ 2025-07-20 20:57 ` Ojaswin Mujoo
2025-07-20 20:57 ` [RFC v4 7/7] ext4: add ext4_map_blocks_extsize() wrapper to handle overwrites Ojaswin Mujoo
6 siblings, 0 replies; 8+ messages in thread
From: Ojaswin Mujoo @ 2025-07-20 20:57 UTC (permalink / raw)
To: linux-ext4, Theodore Ts'o
Cc: Jan Kara, Baokun Li, Ritesh Harjani, Zhang Yi, linux-kernel,
Darrick J . Wong, linux-fsdevel
Make extsize hints work with EOF allocations. We deviate from XFS here
because in case we have blocks left past EOF, we don't truncate them.
There are 2 main reasons:
1. Since the user is opting for extsize allocations, chances are
that they will use the blocks in future.
2. If we start truncating all EOF blocks in ext4_release_file like
XFS, then we will have to always truncate blocks even if they
have been intentionally preallocated using fallocate w/ KEEP_SIZE
which might cause confusion for users. This is mainly because
ext4 doesn't have a way to distinguish if the blocks beyond EOF
have been allocated intentionally. We can work around this by
using an ondisk inode flag like XFS (XFS_DIFLAG_PREALLOC) but
that would be an overkill. It's much simpler to just let the EOF
blocks stick around.
NOTE:
One thing that changes in this patch is that for direct IO we need to
pass the EXT4_GET_BLOCKS_IO_CREATE_EXT even if we are allocating beyond
i_size.
Signed-off-by: Ojaswin Mujoo <ojaswin@linux.ibm.com>
---
fs/ext4/inode.c | 22 ++++++----------------
1 file changed, 6 insertions(+), 16 deletions(-)
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 385fbd745e12..1b60e45a593e 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -861,7 +861,6 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode,
* ext4_extents.h here?
*/
int max_unwrit_len = ((1UL << 15) - 1);
- loff_t end;
align = orig_map->m_lblk % extsize;
len = orig_map->m_len + align;
@@ -870,18 +869,6 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode,
extsize_map.m_len =
max_t(unsigned int, roundup_pow_of_two(len), extsize);
- /*
- * For now allocations beyond EOF don't use extsize hints so
- * that we can avoid dealing with extra blocks allocated past
- * EOF. We have inode lock since extsize allocations are
- * non-delalloc so i_size can be accessed safely
- */
- end = (extsize_map.m_lblk + (loff_t)extsize_map.m_len) << inode->i_blkbits;
- if (end > inode->i_size) {
- flags = orig_flags & ~EXT4_GET_BLOCKS_EXTSIZE;
- goto set_map;
- }
-
/* Fallback to normal allocation if we go beyond max len */
if (extsize_map.m_len >= max_unwrit_len) {
flags = orig_flags & ~EXT4_GET_BLOCKS_EXTSIZE;
@@ -4011,10 +3998,13 @@ static int ext4_iomap_alloc(struct inode *inode, struct ext4_map_blocks *map,
* i_disksize out to i_size. This could be beyond where direct I/O is
* happening and thus expose allocated blocks to direct I/O reads.
*
- * NOTE for extsize hints: We only support it for writes inside
- * EOF (for now) to not have to deal with blocks past EOF
+ * NOTE: For extsize hint based EOF allocations, we still need
+ * IO_CREATE_EXT flag because we will be allocating more than the write
+ * hence the extra blocks need to be marked unwritten and split before
+ * the I/O.
*/
- else if (((loff_t)map->m_lblk << blkbits) >= i_size_read(inode))
+ else if (((loff_t)map->m_lblk << blkbits) >= i_size_read(inode) &&
+ !ext4_should_use_extsize(inode))
m_flags = EXT4_GET_BLOCKS_CREATE;
else if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)) {
m_flags = EXT4_GET_BLOCKS_IO_CREATE_EXT;
--
2.49.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [RFC v4 7/7] ext4: add ext4_map_blocks_extsize() wrapper to handle overwrites
2025-07-20 20:57 [RFC v4 0/7] ext4: Add extsize support Ojaswin Mujoo
` (5 preceding siblings ...)
2025-07-20 20:57 ` [RFC v4 6/7] ext4: make extsize work with EOF allocations Ojaswin Mujoo
@ 2025-07-20 20:57 ` Ojaswin Mujoo
6 siblings, 0 replies; 8+ messages in thread
From: Ojaswin Mujoo @ 2025-07-20 20:57 UTC (permalink / raw)
To: linux-ext4, Theodore Ts'o
Cc: Jan Kara, Baokun Li, Ritesh Harjani, Zhang Yi, linux-kernel,
Darrick J . Wong, linux-fsdevel
Currently, with the extsize hints, if we consider a scenario where
the hint is set to 16k and we do a write of (0,4k) we get the below
mapping:
[ 4k written ] [ 12k unwritten ]
Now, if we do a (4k,4k) write, ext4_map_blocks will again try for a
extsize aligned write, adjust the range to (0, 16k) and then run into
issues since the new range is already has a mapping in it. Although this
does not lead to a failure since we eventually fallback to a non extsize
allocation, this is not a good approach.
Hence, implement a wrapper over ext4_map_blocks() which detects if a
mapping already exists for an extsize based allocation and then reuses
the same mapping.
In case the mapping completely covers the original request we simply
disable extsize allocation and call map_blocks to correctly process the
mapping and set the map flags. Otherwise, if there is a hole or partial
mapping, then we just let ext4_map_blocks() handle the allocation.
Signed-off-by: Ojaswin Mujoo <ojaswin@linux.ibm.com>
---
fs/ext4/inode.c | 45 +++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 43 insertions(+), 2 deletions(-)
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 1b60e45a593e..010ca890b29c 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -772,6 +772,41 @@ static inline void ext4_extsize_reset_map(struct ext4_map_blocks *map,
map->m_flags = 0;
}
+static int ext4_map_blocks_extsize(handle_t *handle, struct inode *inode,
+ struct ext4_map_blocks *map, int flags)
+{
+ int orig_mlen = map->m_len;
+ int ret = 0;
+ int tmp_flags;
+
+ WARN_ON(!ext4_inode_get_extsize(EXT4_I(inode)));
+ WARN_ON(!(flags & EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT));
+
+ /*
+ * First check if there are any existing allocations
+ */
+ ret = ext4_map_blocks(handle, inode, map, 0);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * the present mapping fully covers the requested range. In this
+ * case just go for a non extsize based allocation. Note that we won't
+ * really be allocating new blocks but the call to ext4_map_blocks is
+ * important to ensure things like extent splitting and proper map flags
+ * are taken care of. For all other cases, just let ext4_map_blocks handle
+ * the allocations
+ */
+ if (ret > 0 && map->m_len == orig_mlen)
+ tmp_flags = flags & ~EXT4_GET_BLOCKS_EXTSIZE;
+ else
+ tmp_flags = flags;
+
+ ret = ext4_map_blocks(handle, inode, map, tmp_flags);
+
+ return ret;
+}
+
/*
* The ext4_map_blocks() function tries to look up the requested blocks,
* and returns if the blocks are already mapped.
@@ -1153,8 +1188,12 @@ static int _ext4_get_block(struct inode *inode, sector_t iblock,
map.m_lblk = iblock;
map.m_len = orig_mlen;
- ret = ext4_map_blocks(ext4_journal_current_handle(), inode, &map,
- flags);
+ if ((flags & EXT4_GET_BLOCKS_CREATE) && ext4_should_use_extsize(inode))
+ ret = ext4_map_blocks_extsize(ext4_journal_current_handle(), inode,
+ &map, flags);
+ else
+ ret = ext4_map_blocks(ext4_journal_current_handle(), inode,
+ &map, flags);
if (ret > 0) {
map_bh(bh, inode->i_sb, map.m_pblk);
ext4_update_bh_state(bh, map.m_flags);
@@ -4016,6 +4055,8 @@ static int ext4_iomap_alloc(struct inode *inode, struct ext4_map_blocks *map,
if (flags & IOMAP_ATOMIC)
ret = ext4_map_blocks_atomic_write(handle, inode, map, m_flags,
&force_commit);
+ else if (ext4_should_use_extsize(inode))
+ ret = ext4_map_blocks_extsize(handle, inode, map, m_flags);
else
ret = ext4_map_blocks(handle, inode, map, m_flags);
--
2.49.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
end of thread, other threads:[~2025-07-20 20:58 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-07-20 20:57 [RFC v4 0/7] ext4: Add extsize support Ojaswin Mujoo
2025-07-20 20:57 ` [RFC v4 1/7] ext4: add aligned allocation hint in mballoc Ojaswin Mujoo
2025-07-20 20:57 ` [RFC v4 2/7] ext4: allow inode preallocation for aligned alloc Ojaswin Mujoo
2025-07-20 20:57 ` [RFC v4 3/7] ext4: support for extsize hint using FS_IOC_FS(GET/SET)XATTR Ojaswin Mujoo
2025-07-20 20:57 ` [RFC v4 4/7] ext4: pass lblk and len explicitly to ext4_split_extent*() Ojaswin Mujoo
2025-07-20 20:57 ` [RFC v4 5/7] ext4: add extsize hint support Ojaswin Mujoo
2025-07-20 20:57 ` [RFC v4 6/7] ext4: make extsize work with EOF allocations Ojaswin Mujoo
2025-07-20 20:57 ` [RFC v4 7/7] ext4: add ext4_map_blocks_extsize() wrapper to handle overwrites Ojaswin Mujoo
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).