[PATCH 3/3] f2fs: support NOCoW flag

Chao Yu
2024-10-18T06:26:51Z

Overhead of pinfile is heavy due to:
1) GC tries to skip migrating section which contains pinfile's data
block, it will cost more time during GC,
2) and also it will take high risk when GC unpins file and migrates
it's data block once inode.i_gc_failure exceeds threshold.

This patch proposes to support NOCoW flag on inode, which has similar
semantics to pinfile:

Pinfile NOCoW
[Enable]
only regular yes yes
only empty file yes no
blkzoned=on,mode=lfs Support Support
blkzoned=off,mode=lfs Not support Not support

[fsync/wb/dio IO behavior]
block in regular device IPU IPU
block in zoned device N/A OPU

[Misc IO behavior]
GC skip/OPU OPU
defrag ioc/swapon Not support Support

If userspace doesn't need pinned physical block address for following
direct access, we can tag file w/ NOCoW flag instead of pinfile flag,
we will be benefit from it for below cases:
1) user's write won't fragment NOCoW file's data block due to IPU
2) GC/defrag/swapon won't fail on NOCoW file.

Please note that, this patch changes to not show NOCoW flag for a
pinned file, the logic is as below:

Before
Pinned file
lsattr C
f2fs_io pinfile get pinned

After
Pinned file NOCoW file
lsattr - C
f2fs_io pinfile get pinned un-pinned

So, to consider backward compatibility, if NOCoW flag shown on a
target inode, we need to check pinned flag to see whether NOCoW
flag was really tagged or not.

Signed-off-by: Chao Yu <chao@kernel.org>
---
fs/f2fs/data.c | 21 ++++++++++++++++++++-
fs/f2fs/f2fs.h | 10 ++++++++--
fs/f2fs/file.c | 22 ++++++++++++++++++----
3 files changed, 46 insertions(+), 7 deletions(-)

diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
index c6d0e4aa12e3..b8ba97b2c11f 100644
--- a/fs/f2fs/data.c
+++ b/fs/f2fs/data.c
@@ -1636,7 +1636,7 @@ int f2fs_map_blocks(struct inode *inode, struct f2fs_map_blocks *map, int flag)
/* use out-place-update for direct IO under LFS mode */
if (map->m_may_create && (is_hole ||
(flag == F2FS_GET_BLOCK_DIO && f2fs_lfs_mode(sbi) &&
- !f2fs_is_pinned_file(inode)))) {
+ !f2fs_is_pinned_file(inode) && !f2fs_is_nocow_file(inode)))) {
if (unlikely(f2fs_cp_error(sbi))) {
err = -EIO;
goto sync_out;
@@ -2636,6 +2636,11 @@ bool f2fs_should_update_inplace(struct inode *inode, struct f2fs_io_info *fio)
if (file_is_cold(inode) && !is_inode_flag_set(inode, FI_OPU_WRITE))
return true;

+ /* allows NOCoW file to be migrated during defragmentation and swapon */
+ if (f2fs_is_nocow_file(inode) &&
+ !is_inode_flag_set(inode, FI_OPU_WRITE))
+ return true;
+
return check_inplace_update_policy(inode, fio);
}

@@ -2658,6 +2663,9 @@ bool f2fs_should_update_outplace(struct inode *inode, struct f2fs_io_info *fio)
sbi->first_zoned_segno)
return true;
}
+ /* should check NOCoW flag after lfs_mode check condition */
+ if (f2fs_is_nocow_file(inode))
+ return false;
if (S_ISDIR(inode->i_mode))
return true;
if (IS_NOQUOTA(inode))
@@ -2722,11 +2730,22 @@ int f2fs_do_write_data_page(struct f2fs_io_info *fio)
DATA_GENERIC_ENHANCE))
return -EFSCORRUPTED;

+ /*
+ * if data block of NOCoW inode locates in sequential-zone,
+ * let's trigger OPU.
+ */
+ if (f2fs_is_nocow_file(inode) &&
+ f2fs_blkzoned_has_regular_section(fio->sbi) &&
+ GET_SEGNO(fio->sbi, fio->old_blkaddr) >=
+ fio->sbi->first_zoned_segno)
+ goto get_dnode;
+
ipu_force = true;
fio->need_lock = LOCK_DONE;
goto got_it;
}

+get_dnode:
/* Deadlock due to between page->lock and f2fs_lock_op */
if (fio->need_lock == LOCK_REQ && !f2fs_trylock_op(fio->sbi))
return -EAGAIN;
diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
index 6781b4fab43f..9b7fa960cd8b 100644
--- a/fs/f2fs/f2fs.h
+++ b/fs/f2fs/f2fs.h
@@ -3047,6 +3047,7 @@ static inline void f2fs_change_bit(unsigned int nr, char *addr)
#define F2FS_NOCOMP_FL 0x00000400 /* Don't compress */
#define F2FS_INDEX_FL 0x00001000 /* hash-indexed directory */
#define F2FS_DIRSYNC_FL 0x00010000 /* dirsync behaviour (directories only) */
+#define F2FS_NOCOW_FL 0x00800000 /* Do not cow file */
#define F2FS_PROJINHERIT_FL 0x20000000 /* Create with parents projid */
#define F2FS_CASEFOLD_FL 0x40000000 /* Casefolded file */
#define F2FS_DEVICE_ALIAS_FL 0x80000000 /* File for aliasing a device */
@@ -3332,6 +3333,11 @@ static inline bool f2fs_is_cow_file(struct inode *inode)
return is_inode_flag_set(inode, FI_COW_FILE);
}

+static inline bool f2fs_is_nocow_file(struct inode *inode)
+{
+ return F2FS_I(inode)->i_flags & F2FS_NOCOW_FL;
+}
+
static inline void *inline_data_addr(struct inode *inode, struct page *page)
{
__le32 *addr = get_dnode_addr(inode, page);
@@ -4658,8 +4664,8 @@ static inline bool f2fs_low_mem_mode(struct f2fs_sb_info *sbi)
static inline bool f2fs_may_compress(struct inode *inode)
{
if (IS_SWAPFILE(inode) || f2fs_is_pinned_file(inode) ||
- f2fs_is_atomic_file(inode) || f2fs_has_inline_data(inode) ||
- f2fs_is_mmap_file(inode))
+ f2fs_is_nocow_file(inode) || f2fs_is_atomic_file(inode) ||
+ f2fs_has_inline_data(inode) || f2fs_is_mmap_file(inode))
return false;
return S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode);
}
diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
index 92d7c62eba29..580609223799 100644
--- a/fs/f2fs/file.c
+++ b/fs/f2fs/file.c
@@ -1998,6 +1998,20 @@ static int f2fs_setflags_common(struct inode *inode, u32 iflags, u32 mask)
if (IS_NOQUOTA(inode))
return -EPERM;

+ if ((iflags ^ masked_flags) & F2FS_NOCOW_FL) {
+ if (iflags & F2FS_NOCOW_FL) {
+ int ret;
+
+ if (!S_ISREG(inode->i_mode))
+ return -EINVAL;
+ if (f2fs_should_update_outplace(inode, NULL))
+ return -EINVAL;
+ ret = f2fs_convert_inline_inode(inode);
+ if (ret)
+ return ret;
+ }
+ }
+
if ((iflags ^ masked_flags) & F2FS_CASEFOLD_FL) {
if (!f2fs_sb_has_casefold(F2FS_I_SB(inode)))
return -EOPNOTSUPP;
@@ -2077,6 +2091,7 @@ static const struct {
{ F2FS_NOCOMP_FL, FS_NOCOMP_FL },
{ F2FS_INDEX_FL, FS_INDEX_FL },
{ F2FS_DIRSYNC_FL, FS_DIRSYNC_FL },
+ { F2FS_NOCOW_FL, FS_NOCOW_FL },
{ F2FS_PROJINHERIT_FL, FS_PROJINHERIT_FL },
{ F2FS_CASEFOLD_FL, FS_CASEFOLD_FL },
};
@@ -2108,7 +2123,8 @@ static const struct {
FS_NOCOMP_FL | \
FS_DIRSYNC_FL | \
FS_PROJINHERIT_FL | \
- FS_CASEFOLD_FL)
+ FS_CASEFOLD_FL | \
+ FS_NOCOW_FL)

/* Convert f2fs on-disk i_flags to FS_IOC_{GET,SET}FLAGS flags */
static inline u32 f2fs_iflags_to_fsflags(u32 iflags)
@@ -2171,7 +2187,7 @@ static int f2fs_ioc_start_atomic_write(struct file *filp, bool truncate)
inode_lock(inode);

if (!f2fs_disable_compressed_file(inode) ||
- f2fs_is_pinned_file(inode)) {
+ f2fs_is_pinned_file(inode) || f2fs_is_nocow_file(inode)) {
ret = -EINVAL;
goto out;
}
@@ -3273,8 +3289,6 @@ int f2fs_fileattr_get(struct dentry *dentry, struct fileattr *fa)
fsflags |= FS_VERITY_FL;
if (f2fs_has_inline_data(inode) || f2fs_has_inline_dentry(inode))
fsflags |= FS_INLINE_DATA_FL;
- if (is_inode_flag_set(inode, FI_PIN_FILE))
- fsflags |= FS_NOCOW_FL;

fileattr_fill_flags(fa, fsflags & F2FS_GETTABLE_FS_FL);

--
2.40.1