Ritesh Harjani (IBM) <ritesh.list@xxxxxxxxx> writes: > Hi John, > > John Garry <john.g.garry@xxxxxxxxxx> writes: > >> On 27/10/2024 18:17, Ritesh Harjani (IBM) wrote: >>> This patch adds base support for atomic writes via statx getattr. >>> On bs < ps systems, we can create FS with say bs of 16k. That means >>> both atomic write min and max unit can be set to 16k for supporting >>> atomic writes. >>> >>> Co-developed-by: Ojaswin Mujoo <ojaswin@xxxxxxxxxxxxx> >>> Signed-off-by: Ojaswin Mujoo <ojaswin@xxxxxxxxxxxxx> >>> Signed-off-by: Ritesh Harjani (IBM) <ritesh.list@xxxxxxxxx> >>> --- >>> fs/ext4/ext4.h | 9 +++++++++ >>> fs/ext4/inode.c | 14 ++++++++++++++ >>> fs/ext4/super.c | 31 +++++++++++++++++++++++++++++++ >>> 3 files changed, 54 insertions(+) >>> >>> diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h >>> index 44b0d418143c..6ee49aaacd2b 100644 >>> --- a/fs/ext4/ext4.h >>> +++ b/fs/ext4/ext4.h >>> @@ -1729,6 +1729,10 @@ struct ext4_sb_info { >>> */ >>> struct work_struct s_sb_upd_work; >>> >>> + /* Atomic write unit values in bytes */ >>> + unsigned int s_awu_min; >>> + unsigned int s_awu_max; >>> + >>> /* Ext4 fast commit sub transaction ID */ >>> atomic_t s_fc_subtid; >>> >>> @@ -3855,6 +3859,11 @@ static inline int ext4_buffer_uptodate(struct buffer_head *bh) >>> return buffer_uptodate(bh); >>> } >>> >>> +static inline bool ext4_can_atomic_write(struct super_block *sb) >>> +{ >>> + return EXT4_SB(sb)->s_awu_min > 0; >>> +} >>> + >>> extern int ext4_block_write_begin(handle_t *handle, struct folio *folio, >>> loff_t pos, unsigned len, >>> get_block_t *get_block); >>> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c >>> index 54bdd4884fe6..fcdee27b9aa2 100644 >>> --- a/fs/ext4/inode.c >>> +++ b/fs/ext4/inode.c >>> @@ -5578,6 +5578,20 @@ int ext4_getattr(struct mnt_idmap *idmap, const struct path *path, >>> } >>> } >>> >>> + if (S_ISREG(inode->i_mode) && (request_mask & STATX_WRITE_ATOMIC)) { >>> + struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb); >>> + unsigned int awu_min, awu_max; >>> + >>> + if (ext4_can_atomic_write(inode->i_sb)) { >>> + awu_min = sbi->s_awu_min; >>> + awu_max = sbi->s_awu_max; >>> + } else { >>> + awu_min = awu_max = 0; >>> + } >>> + >>> + generic_fill_statx_atomic_writes(stat, awu_min, awu_max); >>> + } >>> + >>> flags = ei->i_flags & EXT4_FL_USER_VISIBLE; >>> if (flags & EXT4_APPEND_FL) >>> stat->attributes |= STATX_ATTR_APPEND; >>> diff --git a/fs/ext4/super.c b/fs/ext4/super.c >>> index 16a4ce704460..d6e3201a48be 100644 >>> --- a/fs/ext4/super.c >>> +++ b/fs/ext4/super.c >>> @@ -4425,6 +4425,36 @@ static int ext4_handle_clustersize(struct super_block *sb) >>> return 0; >>> } >>> >>> +/* >>> + * ext4_atomic_write_init: Initializes filesystem min & max atomic write units. >>> + * @sb: super block >>> + * TODO: Later add support for bigalloc >>> + */ >>> +static void ext4_atomic_write_init(struct super_block *sb) >>> +{ >>> + struct ext4_sb_info *sbi = EXT4_SB(sb); >>> + struct block_device *bdev = sb->s_bdev; >>> + >>> + if (!bdev_can_atomic_write(bdev)) >> >> this check is duplicated, since bdev_atomic_write_unit_{min, >> max}_bytes() has this check >> > > Right, yes. I can mention a comment and remove this check perhaps. > Looks like XFS also got it duplicated then. > > If we remove above bdev check logic, than we are relying on the min max comparison in below code to decide whether we support atomic writes or not. It's rather cleaner and straight forward to check if bdev supports atomic write in the beginning itself. So I feel it's better to keep the above check as is since it's only once during mount. (Note - bdev_can_atomic_write() either ways gets called twice when bdev_atomic_write_unit_min|max_bytes() is getting called). So I would like to keep the check as is please. Thanks for the review! -ritesh >>> + return; >>> + >>> + if (!ext4_has_feature_extents(sb)) >>> + return; >>> + >>> + sbi->s_awu_min = max(sb->s_blocksize, >>> + bdev_atomic_write_unit_min_bytes(bdev)); >>> + sbi->s_awu_max = min(sb->s_blocksize, >>> + bdev_atomic_write_unit_max_bytes(bdev)); >>> + if (sbi->s_awu_min && sbi->s_awu_max && >>> + sbi->s_awu_min <= sbi->s_awu_max) { >> >> This looks a bit complicated. I would just follow the XFS example and >> ensure bdev_atomic_write_unit_min_bytes() <= sb->s_blocksize <= >> bdev_atomic_write_unit_max_bytes() [which you are doing, but in a >> complicated way] >> > > In here we are checking for min and max supported units against fs > blocksize at one place during mount time itself and then caching the > supported atomic write unit. The supported atomic write unit can change > when bigalloc gets introduced. > > XFS caches the blockdevice min, max units and then defers these checks > against fs blocksize during file open time using xfs_inode_can_atomicwrite(). > > I just preferrd the 1st aproach in case of EXT4 here because it will be simpler > this way for when bigalloc also gets introduced. > > BTW none of these are ondisk changes, so whenever extsize gets > introduced, we might still have to add something like > ext4_inode_can_atomic_write() (similar to XFS) based on inode extsize > value. But for now it was not needed in EXT4. > > I hope the reasoning is clear and make sense. > > >>> + ext4_msg(sb, KERN_NOTICE, "Supports atomic writes awu_min: %u, awu_max: %u", >>> + sbi->s_awu_min, sbi->s_awu_max); > > BTW - I was wondering if we should add "experimental" in above msg. > > -ritesh > >>> + } else { >>> + sbi->s_awu_min = 0; >>> + sbi->s_awu_max = 0; >>> + } >>> +} >>> + >>> static void ext4_fast_commit_init(struct super_block *sb) >>> { >>> struct ext4_sb_info *sbi = EXT4_SB(sb); >>> @@ -5336,6 +5366,7 @@ static int __ext4_fill_super(struct fs_context *fc, struct super_block *sb) >>> >>> spin_lock_init(&sbi->s_bdev_wb_lock); >>> >>> + ext4_atomic_write_init(sb); >>> ext4_fast_commit_init(sb); >>> >>> sb->s_root = NULL;