ext4: Add extsize and forcealign support (groundwork for multi block atomic writes)
Subsystems (3)
Ojaswin Mujoo
reviewerv3 cover ext4: Add extsize and forcealign support (groundwork for multi block atomic writes)
These patches lay the ground work for supporting multi block HW-accelerated atomic writes without the use of bigalloc. Multiblock atomic write support with bigalloc is already posted as an RFC here [3]. Without bigalloc, we need a mechanism to get aligned blocks from the allocator so that HW accelerated atomic writes can be performed. extsize
+ forcealign provide this mechanism in ext4.
[3] https://lore.kernel.org/linux-ext4/[email protected]/
- extsize is a per inode hint to physically and logically align blocks to a certain value.
- forcealign gives a **strict guarantee** that allocator will physically as well as logically align blocks to the extsize value
The extsize support is almost same as v2 with rebase to latest ext4 dev branch. Patches 7 - 11 adds the new forcealign feature that can be seen like a sort of per file bigalloc. Some points about forcealign:
* Allocation on a forcealign inode is guaranteed to get an extent aligned to extsize physicall and logically, else error is returned. This mimicks bigalloc but on a per file level
* Deallocations are also only allowed in extsize aligned units. This is pretty strict and can be relaxed in later revisions.
* FS_XFLAG_FORCEALIGN can be set via FS_IOC_GET/SETXATTR ioctl to enable forcealign. As of now, we can only enable forcealign if extsize is set on the inode
* Reused the EXT4_EOFBLOCKS_FL flag for forcealig since it is no longer used. Incase this is not feasible, we can explore other ways to set the flag (eg xattr or overriding a field)
Some of the TODOs and open questions regarding the design:
1. I want to design forcealign in such a way that FS formatting is not required. For that Im exploring 2 options:
- Add an RO_COMPAT feature flag. tune2fs can be used to enable it on existing filesystems without formatting. Simplest but this has a drawback that even for a single forcealign file, the FS would become RO on older kernels
- To avoid that, we can instead expose an ioctl to fix a misaligned forcealign file. However this is an overhead for sys admins/end users. Maybe fsck can help with this?
2. For extsize, I'm not planning to support FS-wide tunable since we already have bigalloc for that.
3. Also, we are not supporting non-power-of-2 extsizes (atleast for now) as there are no clear use cases to justify the added complexity
4. directory wide extsize is not yet supported however can be added in future revision
We are passing quick xfstests with these patches along with a lot of custom allocation scenarios that I'll eventually add to xfstest, however this series is still largely an RFC and might have bugs.
Posting this here for review and suggestions on the design as well as implementation.
** Changes since rfc v2 [2] **
- Patch 0-6 are same as v2 just rebased. Patch 7-11 are new in this series.
- Patch 7 adds a wrapper on ext4_map_blocks to better handle some allocation scenarios
- Patch 8-11 Add a new called forcealign. More about it below.
[2] https://lore.kernel.org/linux-ext4/[email protected]/
** Changes since rfc v1 [1] **
1. Allocations beyond EOF also respect extsize hint however we unlink XFS, we don't trim the blocks allocated beyond EOF due to extsize hints. The reasoning behind this is explained in patch 6/6.
2. Minor fixes in extsize ioctl handling logic.
Rest of the design detials can be in individual patches as well as the original cover leter which can be found here:
[1]
https://lore.kernel.org/linux-ext4/[email protected]/
Comments and suggestions are welcome!
Regards,
ojaswin
Ojaswin Mujoo (11):
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
ext4: add forcealign support of mballoc
ext4: add forcealign support to ext4_map_blocks
ext4: add support for adding focealign via SETXATTR ioctl
ext4: disallow unaligned deallocations on forcealign inodes
fs/ext4/ext4.h | 20 +- fs/ext4/ext4_jbd2.h | 23 ++ fs/ext4/extents.c | 294 ++++++++++++++++--- fs/ext4/inode.c | 543 +++++++++++++++++++++++++++++++++--- fs/ext4/ioctl.c | 191 +++++++++++++ fs/ext4/mballoc.c | 141 ++++++++-- fs/ext4/super.c | 1 + include/trace/events/ext4.h | 3 + include/uapi/linux/fs.h | 6 +- 9 files changed, 1111 insertions(+), 111 deletions(-)
Signature · 1 line
2.48.1
Ojaswin Mujoo
reviewerRFC v3 1/11 ext4: add aligned allocation hint in mballoc
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.
--- fs/ext4/ext4.h | 2 ++ fs/ext4/mballoc.c | 60 +++++++++++++++++++++++++++++++++---- include/trace/events/ext4.h | 1 + 3 files changed, 58 insertions(+), 5 deletions(-)
fs/ext4/ext4.h +2 -0
@@ -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 0x40000struct ext4_allocation_request {/* target inode for block we're allocating */
fs/ext4/mballoc.c +55 -5
100 lines not on this page · raw message
include/trace/events/ext4.h +1 -0
@@ -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, "|", \
Signature · 1 line
2.48.1
Ojaswin Mujoo
reviewerRFC v3 2/11 ext4: allow inode preallocation for aligned alloc
In reply to Ojaswin MujooEnable 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.
--- fs/ext4/mballoc.c | 96 +++++++++++++++++++++++++++++++---------------- 1 file changed, 63 insertions(+), 33 deletions(-)
fs/ext4/mballoc.c +63 -33
@@ -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); }
@@ -3027,8 +3025,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:
@@ -4479,13 +4476,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 ;
@@ -4542,6 +4532,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;
@@ -4792,32 +4797,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; }
@@ -4840,10 +4859,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. *
@@ -4949,7 +4964,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);
@@ -4984,6 +4999,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:
@@ -5790,6 +5818,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 ;
@@ -5797,7 +5826,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 )
Signature · 1 line
2.48.1
Ojaswin Mujoo
reviewerRFC v3 3/11 ext4: support for extsize hint using FS_IOC_FS(GET/SET)XATTR
In reply to Ojaswin MujooThis 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.
--- 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(+)
fs/ext4/ext4.h +6 -0
@@ -1173,6 +1173,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; };/*
@@ -3051,6 +3053,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);/* indirect.c */ extern int ext4_ind_map_blocks(handle_t *handle, struct inode *inode,
fs/ext4/inode.c +89 -0
98 lines not on this page · raw message
fs/ext4/ioctl.c +122 -0
146 lines not on this page · raw message
fs/ext4/super.c +1 -0
@@ -1413,6 +1413,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);mutex_init (&ei->i_fc_lock);
Signature · 1 line
2.48.1
Ojaswin Mujoo
reviewerRFC v3 4/11 ext4: pass lblk and len explicitly to ext4_split_extent*()
In reply to Ojaswin MujooSince 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.
--- fs/ext4/extents.c | 57 +++++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 27 deletions(-)
fs/ext4/extents.c +30 -27
@@ -3347,7 +3347,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) {
@@ -3363,7 +3364,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)
@@ -3372,28 +3373,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 ;
@@ -3401,16 +3402,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;
@@ -3658,8 +3659,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:
@@ -3699,11 +3700,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;
@@ -3712,12 +3713,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.
@@ -3737,8 +3738,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 *
@@ -3773,7 +3774,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;
@@ -3837,8 +3838,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;
@@ -3909,8 +3911,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;/*
Signature · 1 line
2.48.1
Ojaswin Mujoo
reviewerRFC v3 5/11 ext4: add extsize hint support
In reply to Ojaswin MujooNow 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.
--- fs/ext4/ext4.h | 4 +- fs/ext4/ext4_jbd2.h | 15 ++ fs/ext4/extents.c | 174 ++++++++++++++++-- fs/ext4/inode.c | 354 ++++++++++++++++++++++++++++++++---- include/trace/events/ext4.h | 1 + 5 files changed, 495 insertions(+), 53 deletions(-)
fs/ext4/ext4.h +3 -1
@@ -727,6 +727,7 @@ enum {#define EXT4_GET_BLOCKS_IO_SUBMIT 0x0400/* Caller is in the atomic contex, find extent if it has been cached */ #define EXT4_GET_BLOCKS_CACHED_NOWAIT 0x0800 +#define EXT4_GET_BLOCKS_EXTSIZE 0x1000/* * The bit position of these flags must not overlap with any of the
@@ -3708,7 +3709,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);
fs/ext4/ext4_jbd2.h +15 -0
@@ -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 */
fs/ext4/extents.c +159 -15
267 lines not on this page · raw message
fs/ext4/inode.c +317 -37
559 lines not on this page · raw message
include/trace/events/ext4.h +1 -0
@@ -50,6 +50,7 @@ struct partial_cluster; { EXT4_GET_BLOCKS_CONVERT_UNWRITTEN, "CONVERT_UNWRITTEN" }, \ { EXT4_GET_BLOCKS_ZERO, "ZERO" }, \ { EXT4_GET_BLOCKS_IO_SUBMIT, "IO_SUBMIT" }, \ + { EXT4_GET_BLOCKS_EXTSIZE, "EXTSIZE" }, \ { EXT4_EX_NOCACHE, "EX_NOCACHE" })/*
Signature · 1 line
2.48.1
Ojaswin Mujoo
reviewerRFC v3 6/11 ext4: make extsize work with EOF allocations
In reply to Ojaswin MujooMake 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.
--- fs/ext4/inode.c | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-)
fs/ext4/inode.c +6 -16
@@ -757,7 +757,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;
@@ -766,18 +765,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 ;
@@ -3645,10 +3632,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 ;
Signature · 1 line
2.48.1
Ojaswin Mujoo
reviewerRFC v3 7/11 ext4: add ext4_map_blocks_extsize() wrapper to handle overwrites
In reply to Ojaswin MujooCurrently, with the extsize hints, if we consider a scenario where the hint is 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.
--- fs/ext4/inode.c | 49 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-)
fs/ext4/inode.c +46 -3
@@ -678,6 +678,42 @@ 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 | +EXT4_GET_BLOCKS_FORCEALIGN ); +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.
@@ -1028,8 +1064,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);
@@ -3647,7 +3687,10 @@ static int ext4_iomap_alloc(struct inode *inode, struct ext4_map_blocks *map, m_flags |=EXT4_GET_BLOCKS_EXTSIZE ; } - ret =ext4_map_blocks (handle, inode, map, m_flags); +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);/* * We cannot fill holes in indirect tree based inodes as that could
Signature · 1 line
2.48.1
Ojaswin Mujoo
reviewerRFC v3 8/11 ext4: add forcealign support of mballoc
In reply to Ojaswin MujooIntroduce an EXT4_MB_FORCE_ALIGN flag that essentially enforces the same behavior as EXT4_MB_HINT_ALIGNED however the alignment requirements are no longer a hint but must be respected. If the allocator can't return aligned blocks, then ENOSPC will be thrown.
This will be eventually used to guarantee aligned blocks to perform H/W accelerated atomic writes.
--- fs/ext4/ext4.h | 2 ++ fs/ext4/mballoc.c | 31 +++++++++++++++++++++++-------- include/trace/events/ext4.h | 1 + 3 files changed, 26 insertions(+), 8 deletions(-)
fs/ext4/ext4.h +2 -0
@@ -224,6 +224,8 @@ enum criteria {#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 +/* Same as HINT_ALIGNED but fail allocation if alginment can't be guaranteed */ +#define EXT4_MB_FORCE_ALIGN 0x80000struct ext4_allocation_request {/* target inode for block we're allocating */
fs/ext4/mballoc.c +23 -8
@@ -2872,12 +2872,21 @@ ext4_mb_regular_allocator(struct ext4_allocation_context *ac) 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 ; +if (ac->ac_flags &EXT4_MB_FORCE_ALIGN ) { +ext4_warning_inode ( + ac->ac_inode, +"Aligned allocation not possible, failing allocation" ); + ac->ac_status =AC_STATUS_BREAK ; + goto exit; + } + +if (ac->ac_flags &EXT4_MB_HINT_ALIGNED ) { +ext4_warning_inode ( + ac->ac_inode, +"Aligned allocation not possible, using unaligned allocation" ); + ac->ac_flags &= ~EXT4_MB_HINT_ALIGNED ; + } }/*
@@ -3023,9 +3032,15 @@ ext4_mb_regular_allocator(struct ext4_allocation_context *ac) goto exit; } -WARN_ON_ONCE (!is_power_of_2 (len)); -WARN_ON_ONCE (start % len); -WARN_ON_ONCE (ac->ac_b_ex.fe_len < ac->ac_o_ex.fe_len); +if (WARN_ON_ONCE (!is_power_of_2 (len)) || +WARN_ON_ONCE (start % len) || +WARN_ON_ONCE (ac->ac_b_ex.fe_len < ac->ac_o_ex.fe_len)) { +/* FORCE_ALIGN should error out if aligned blocks can't be found */ +if (ac->ac_flags &EXT4_MB_FORCE_ALIGN ) { + ac->ac_status =AC_STATUS_BREAK ; + goto exit; + } + } } exit:
include/trace/events/ext4.h +1 -0
@@ -37,6 +37,7 @@ struct partial_cluster; { EXT4_MB_USE_ROOT_BLOCKS, "USE_ROOT_BLKS" }, \ { EXT4_MB_USE_RESERVED, "USE_RESV" }, \ { EXT4_MB_HINT_ALIGNED, "HINT_ALIGNED" }, \ + { EXT4_MB_FORCE_ALIGN, "FORCE_ALIGN" }, \ { EXT4_MB_STRICT_CHECK, "STRICT_CHECK" }) #define show_map_flags(flags) __print_flags(flags, "|", \
Signature · 1 line
2.48.1
Ojaswin Mujoo
reviewerRFC v3 9/11 ext4: add forcealign support to ext4_map_blocks
In reply to Ojaswin MujooIntroduce EXT4_GET_BLOCKS_FORCEALIGN that works with EXT4_GET_BLOCKS_EXTSIZE and guarantees that the extent returned by allocator is physically as well as logically aligned to the extsize hint set on the inode.
This feature will be used to guranatee aligned allocations for HW accelerated atomic writes
--- fs/ext4/ext4.h | 1 + fs/ext4/extents.c | 24 ++++++++++++++++++++++-- fs/ext4/inode.c | 43 ++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 63 insertions(+), 5 deletions(-)
fs/ext4/ext4.h +1 -0
@@ -730,6 +730,7 @@ enum {/* Caller is in the atomic contex, find extent if it has been cached */ #define EXT4_GET_BLOCKS_CACHED_NOWAIT 0x0800#define EXT4_GET_BLOCKS_EXTSIZE 0x1000 +#define EXT4_GET_BLOCKS_FORCEALIGN 0x2000/* * The bit position of these flags must not overlap with any of the
fs/ext4/extents.c +22 -2
@@ -4414,13 +4414,27 @@ int ext4_ext_map_blocks(handle_t *handle, struct inode *inode, ar.flags |=EXT4_MB_USE_RESERVED ;if (flags &EXT4_GET_BLOCKS_EXTSIZE ) ar.flags |=EXT4_MB_HINT_ALIGNED ; +if (flags &EXT4_GET_BLOCKS_FORCEALIGN ) { +if (WARN_ON (ar.logical != map->m_lblk || ar.len != map->m_len || + !(flags &EXT4_GET_BLOCKS_EXTSIZE ))) { +/* +* This should ideally not happen but if does then error +* out +*/ + err = -ENOSPC ; + goto out; + } + ar.flags |=EXT4_MB_FORCE_ALIGN ; + } newblock =ext4_mb_new_blocks (handle, &ar, &err);if (!newblock) goto out; allocated_clusters = ar.len; ar.len =EXT4_C2B (sbi, ar.len) - offset; -ext_debug (inode,"allocate new block: goal %llu, found %llu/%u, requested %u\n" , - ar.goal, newblock, ar.len, allocated); +ext_debug ( + inode, +"allocate new block: goal %llu, found %llu/%u, requested %u\n" , + ar.goal, newblock, ar.len, allocated);if (ar.len > allocated) ar.len = allocated;
@@ -4435,6 +4449,12 @@ int ext4_ext_map_blocks(handle_t *handle, struct inode *inode, map->m_flags |=EXT4_MAP_UNWRITTEN ; } +if ((flags &EXT4_GET_BLOCKS_FORCEALIGN ) && + (ar.len != map->m_len || pblk % map->m_len)) { + err = -ENOSPC ; + goto insert_error; + } +if ((flags &EXT4_GET_BLOCKS_EXTSIZE ) && (flags &EXT4_GET_BLOCKS_PRE_IO )) {/*
fs/ext4/inode.c +40 -3
85 lines not on this page · raw message
Signature · 1 line
2.48.1
Ojaswin Mujoo
reviewerRFC v3 10/11 ext4: add support for adding focealign via SETXATTR ioctl
In reply to Ojaswin MujooWith forcealign set on an inode, we should always either get an extent physically aligned to the extsize or we should error out. This is suitable for hardware accelerated atomic writes since it allows us to exit early rather than sending the bio and then getting an error from the device.
This patch adds the SET/GETXATTR ioctl level support to set/get this flag. Right now, this can only be set if extsize is set on an inode.
Since we are almost out of inode flags, we reuse the unused EXT4_EOFBLOCKS_FL.
--- fs/ext4/ext4.h | 5 ++- fs/ext4/ext4_jbd2.h | 8 +++++ fs/ext4/extents.c | 7 ++++- fs/ext4/inode.c | 16 ++++++++-- fs/ext4/ioctl.c | 69 +++++++++++++++++++++++++++++++++++++++++ include/uapi/linux/fs.h | 6 ++-- 6 files changed, 104 insertions(+), 7 deletions(-)
fs/ext4/ext4.h +4 -1
@@ -514,6 +514,9 @@ struct flex_groups {#define EXT4_CASEFOLD_FL 0x40000000/* Casefolded directory */ #define EXT4_RESERVED_FL 0x80000000/* reserved for ext4 lib */ +/* Extended flags, can only be set via FS_SETXATTR ioctl */ +#define EXT4_FORCEALIGN_XFL 0x00400000/* Inode must do algined allocation */ +/* User modifiable flags */ #define EXT4_FL_USER_MODIFIABLE (EXT4_SECRM_FL | \ EXT4_UNRM_FL | \
@@ -528,7 +531,6 @@ struct flex_groups {EXT4_DIRSYNC_FL | \EXT4_TOPDIR_FL | \EXT4_EXTENTS_FL | \ -0x00400000 /* EXT4_EOFBLOCKS_FL */ | \EXT4_DAX_FL | \EXT4_PROJINHERIT_FL | \ EXT4_CASEFOLD_FL)
@@ -605,6 +607,7 @@ enum {EXT4_INODE_VERITY =20 ,/* Verity protected inode */ EXT4_INODE_EA_INODE =21 ,/* Inode used for large EA */ /* 22 was formerly EXT4_INODE_EOFBLOCKS */ +EXT4_INODE_FORCEALIGN =22 ,/* Inode should do aligned allocation */ EXT4_INODE_DAX =25 ,/* Inode is DAX */ EXT4_INODE_INLINE_DATA =28 ,/* Data in inode. */ EXT4_INODE_PROJINHERIT = 29, /* Create with parents projid */
fs/ext4/ext4_jbd2.h +8 -0
@@ -467,6 +467,14 @@ static inline int ext4_should_use_extsize(struct inode *inode)return (ext4_inode_get_extsize (EXT4_I (inode)) >0 ); } +static inline int ext4_should_use_forcealign (struct inode *inode) +{ +if (!ext4_should_use_extsize (inode)) +return 0 ; + +return (ext4_test_inode_flag (inode,EXT4_INODE_FORCEALIGN )); +} +static inline int ext4_should_use_unwrit_extents (struct inode *inode) { return (ext4_should_dioread_nolock(inode) ||
fs/ext4/extents.c +6 -1
@@ -4855,9 +4855,14 @@ static long ext4_do_fallocate(struct file *file, loff_t offset, } flags =EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT ; -if (ext4_should_use_extsize (inode)) +if (ext4_should_use_extsize (inode)) { flags |=EXT4_GET_BLOCKS_EXTSIZE ; +if (ext4_should_use_forcealign (inode)) { + flags |=EXT4_GET_BLOCKS_FORCEALIGN ; + } + } + ret =ext4_alloc_file_blocks (file, start_lblk, len_lblk, new_size, flags);if (ret)
fs/ext4/inode.c +13 -3
@@ -922,7 +922,7 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode,* deallocation both respect extsize. If * not, something has gone terribly wrong. */ -if (WARN_ON ((map->m_lblk != extsize_mlblk) || +if (WARN_ON_ONCE ((map->m_lblk != extsize_mlblk) || (map->m_len != extsize_mlen))) {ext4_error_adjust_map (map, orig_map); ext4_warning(
@@ -1138,9 +1138,14 @@ int ext4_get_block_unwritten(struct inode *inode, sector_t iblock,int ret =0 ;int flags =EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT ; -if (ext4_should_use_extsize (inode)) +if (ext4_should_use_extsize (inode)) { flags |=EXT4_GET_BLOCKS_EXTSIZE ; +if (ext4_should_use_forcealign (inode)) { + flags |=EXT4_GET_BLOCKS_FORCEALIGN ; + } + } +ext4_debug ("ext4_get_block_unwritten: inode %lu, create flag %d\n" , inode->i_ino, create); ret =_ext4_get_block (inode, iblock, bh_result, flags);
@@ -3720,8 +3725,13 @@ static int ext4_iomap_alloc(struct inode *inode, struct ext4_map_blocks *map,else if (ext4_test_inode_flag (inode,EXT4_INODE_EXTENTS )) { m_flags =EXT4_GET_BLOCKS_IO_CREATE_EXT ; -if (ext4_should_use_extsize (inode)) +if (ext4_should_use_extsize (inode)) { m_flags |=EXT4_GET_BLOCKS_EXTSIZE ; + +if (ext4_should_use_forcealign (inode)) { + m_flags |=EXT4_GET_BLOCKS_FORCEALIGN ; + } + } }if (ext4_should_use_extsize (inode))
fs/ext4/ioctl.c +69 -0
87 lines not on this page · raw message
include/uapi/linux/fs.h +4 -2
@@ -167,7 +167,9 @@ struct fsxattr {#define FS_XFLAG_FILESTREAM 0x00004000/* use filestream allocator */ #define FS_XFLAG_DAX 0x00008000/* use DAX for IO */ #define FS_XFLAG_COWEXTSIZE 0x00010000/* CoW extent size allocator hint */ -#define FS_XFLAG_HASATTR 0x80000000/* no DIFLAG for this */ +/* data extent mappings for regular files must be aligned to extent size hint */ +#define FS_XFLAG_FORCEALIGN 0x00020000 +#define FS_XFLAG_HASATTR 0x80000000/* no DIFLAG for this */ /* the read-only stuff doesn't really belong here, but any other place is probably as bad and I don't want to create yet another include file. */
@@ -295,7 +297,7 @@ struct fsxattr {#define FS_EXTENT_FL 0x00080000/* Extents */ #define FS_VERITY_FL 0x00100000/* Verity protected inode */ #define FS_EA_INODE_FL 0x00200000/* Inode used for large EA */ -#define FS_EOFBLOCKS_FL 0x00400000/* Reserved for ext4 */ +/* Was previously FS_EOFBLOCKS_FL (reserved for ext4) */ #define FS_NOCOW_FL 0x00800000/* Do not cow file */ #define FS_DAX_FL 0x02000000/* Inode is DAX */ #define FS_INLINE_DATA_FL 0x10000000/* Reserved for ext4 */
Signature · 1 line
2.48.1
Ojaswin Mujoo
reviewerRFC v3 11/11 ext4: disallow unaligned deallocations on forcealign inodes
In reply to Ojaswin MujooWhen forcealign is set, an unaligned deallocation can disturb the alignment of the mappings of the file by introducing unaligned holes/unwritten extents to it. This could then lead to increased allocations failures on the file in future.
To avoid this, disallow deallocations or extent shifting operations (like insert range) unless they are aligned to extsize as well.
Note that this is a relatively strict approach which can be relaxed a bit more in the future by using partial zeroing tricks etc.
--- fs/ext4/extents.c | 36 ++++++++++++++++++++++++++++++++++++ fs/ext4/inode.c | 12 ++++++++++++ 2 files changed, 48 insertions(+)
fs/ext4/extents.c +36 -0
@@ -4754,6 +4754,18 @@ static long ext4_zero_range(struct file *file, loff_t offset,return ret; } +if (ext4_should_use_forcealign (inode)) { +u32 extsize_bytes =ext4_inode_get_extsize (EXT4_I (inode)) + << inode->i_blkbits; + +if (!IS_ALIGNED (offset | end, extsize_bytes)) { +ext4_warning ( + inode->i_sb, +"tried unaligned operation on forcealign inode" ); +return -EINVAL ; + } + } + flags =EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT ;/* Preallocate the range including the unaligned edges */ if (!IS_ALIGNED (offset | end, blocksize)) {
@@ -5473,6 +5485,18 @@ static int ext4_collapse_range(struct file *file, loff_t offset, loff_t len)if (end >= inode->i_size)return -EINVAL ; +if (ext4_should_use_forcealign (inode)) { +u32 extsize_bytes =ext4_inode_get_extsize (EXT4_I (inode)) + << inode->i_blkbits; + +if (!IS_ALIGNED (offset | end, extsize_bytes)) { +ext4_warning ( + inode->i_sb, +"tried unaligned operation on forcealign inode" ); +return -EINVAL ; + } + } +/* * Write tail of the last page before removed range and data that * will be shifted since they will get removed from the page cache
@@ -5573,6 +5597,18 @@ static int ext4_insert_range(struct file *file, loff_t offset, loff_t len)if (len > inode->i_sb->s_maxbytes - inode->i_size)return -EFBIG ; +if (ext4_should_use_forcealign (inode)) { +u32 extsize_bytes =ext4_inode_get_extsize (EXT4_I (inode)) + << inode->i_blkbits; + +if (!IS_ALIGNED (offset | (offset + len), extsize_bytes)) { +ext4_warning ( + sb, +"tried unaligned operation on forcealign inode" ); +return -EINVAL ; + } + } +/* * Write out all dirty pages. Need to round down to align start offset * to page size boundary for page size > block size.
fs/ext4/inode.c +12 -0
@@ -4397,6 +4397,18 @@ int ext4_punch_hole(struct file *file, loff_t offset, loff_t length) end = max_end; length = end - offset; +if (ext4_should_use_forcealign (inode)) { +u32 extsize_bytes =ext4_inode_get_extsize (EXT4_I (inode)) + << inode->i_blkbits; + +if (!IS_ALIGNED (offset | end, extsize_bytes)) { +ext4_warning ( + sb, +"tried unaligned operation on forcealign inode" ); +return -EINVAL ; + } + } +/* * Attach jinode to inode for jbd2 if we do any zeroing of partial * block.
Signature · 1 line
2.48.1