diff mbox series

[f2fs-dev,3/3] f2fs: support NOCoW flag

Message ID 20241018062638.2619214-3-chao@kernel.org (mailing list archive)
State New
Headers show
Series [f2fs-dev,1/3] f2fs: zone: introduce first_zoned_segno in f2fs_sb_info | expand

Commit Message

Chao Yu Oct. 18, 2024, 6:26 a.m. UTC
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 mbox series

Patch

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);