Skip to contentSkip to conversation

f2fs: checkpoint disabling

PATCH New Daniel Rosenberg 1 participant 1 message Aug 21, 2018

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

PATCH v4 1/1 f2fs: checkpoint disabling

This adds a lightweight non-persistent snapshotting scheme to f2fs.

To use, mount with the option checkpoint=disable, and to return to normal operation, remount with checkpoint=enable. If the filesystem is shut down before remounting with checkpoint=enable, it will revert back to its apparent state when it was first mounted with checkpoint=disable. This is useful for situations where you wish to be able to roll back the state of the disk in case of some critical failure.

Change-Id I8a672396d1049315415444f001ccada0bbda7691
bug 111306604

---
v4:
Added call to garbage collection on mounting without checkpoints enabled to allow for a longer period of running smoothly

Disallowed mounting read-only and with checkpoints disabled

Switched tracking of free storage space to be under a separate struct for clarity. The purpose of this tracking is to avoid some edge case scenarios where you would normally need garbage collection to continue.

One way to hit this in testing is to fill the filesystem with folders, and then fill the filesystem with a single large file. This causes us to have a bunch of dirty node segments, and insufficient free segments to fill the fs with data segments. The tracking does not need to be 100% accurate, but it must guarantee that it does not overestimage free space. For instance, we may inaccurately predict that we'll end up needing another data segment, but blocks may free up to fill that request later on. This leads to us believing that an extra segment is no longer free, and assigned data blocks, which might make us fail a request for a new node block a little earlier under extreme cases. One possible improvement would be to move segments that were unsused in the previous checkpoint, and now are unused, to free blocks instead of dirty. This would let them switch between node and data blocks. That test case would still exist anyways in the event that you had, say, a single node block per segment.

Additionally, moved changes to tracking variables to be only within [inc/dec]_valid_[block/node]_count, which makes the code easier to reason about and corrects some cases the previous version misses.

 Documentation/filesystems/f2fs.txt |   5 ++
 fs/f2fs/checkpoint.c               |   5 ++
 fs/f2fs/data.c                     |  23 ++++++
 fs/f2fs/f2fs.h                     |  87 ++++++++++++++++++++
 fs/f2fs/file.c                     |  18 +++++
 fs/f2fs/gc.c                       |   4 +
 fs/f2fs/segment.c                  |  55 ++++++++++---
 fs/f2fs/segment.h                  |  26 ++++++
 fs/f2fs/super.c                    | 124 +++++++++++++++++++++++++++--
 9 files changed, 331 insertions(+), 16 deletions(-)
Documentation/filesystems/f2fs.txt +5 -0
index e5edd29687b50..b14f10eb3cbf2 100644
@@ -211,6 +211,11 @@ fsync_mode=%s          Control the policy of fsync. Currently supports "posix",
                        non-atomic files likewise "nobarrier" mount option.
 test_dummy_encryption  Enable dummy encryption, which provides a fake fscrypt
                        context. The fake fscrypt context is used by xfstests.
+checkpoint=%s          Set to "disable" to turn off checkpointing. Set to "enable"
+                       to reenable checkpointing. Is enabled by default. While
+                       disabled, any unmounting or unexpected shutdowns will cause
+                       the filesystem contents to appear as they did when the
+                       filesystem was mounted with that option.
 
 ================================================================================
 DEBUGFS ENTRIES
fs/f2fs/checkpoint.c +5 -0
index e8b6b89bddb86..518c1a12224f2 100644
@@ -1446,6 +1446,11 @@ int f2fs_write_checkpoint(struct f2fs_sb_info *sbi, struct cp_control *cpc)
 	unsigned long long ckpt_ver;
 	int err = 0;
 
+	if (unlikely(test_opt(sbi, DISABLE_CHECKPOINT))) {
+		f2fs_msg(sbi->sb, KERN_WARNING,
+				"Checkpoints disabled, but took checkpoint!");
+		WARN_ON(1);
+	}
 	mutex_lock(&sbi->cp_mutex);
 
 	if (!is_sbi_flag_set(sbi, SBI_IS_DIRTY) &&
fs/f2fs/data.c +23 -0
index 86bf3826db825..505498f8f054c 100644
@@ -1690,6 +1690,19 @@ bool f2fs_should_update_outplace(struct inode *inode, struct f2fs_io_info *fio)
 
 	if (test_opt(sbi, LFS))
 		return true;
+	if (test_opt(sbi, DISABLE_CHECKPOINT)) {
+		struct seg_entry *se;
+		unsigned int segno, offset;
+
+		if (!fio || fio->old_blkaddr == NULL_ADDR ||
+				fio->old_blkaddr == NEW_ADDR)
+			return true;
+		segno = GET_SEGNO(sbi, fio->old_blkaddr);
+		se = get_seg_entry(sbi, segno);
+		offset = GET_BLKOFF_FROM_SEG0(sbi, fio->old_blkaddr);
+		if (f2fs_test_bit(offset, se->ckpt_valid_map))
+			return true;
+	}
 	if (S_ISDIR(inode->i_mode))
 		return true;
 	if (f2fs_is_atomic_file(inode))
@@ -1717,10 +1730,13 @@ int f2fs_do_write_data_page(struct f2fs_io_info *fio)
 {
 	struct page *page = fio->page;
 	struct inode *inode = page->mapping->host;
+	struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
 	struct dnode_of_data dn;
 	struct extent_info ei = {0,0,0};
 	struct node_info ni;
 	bool ipu_force = false;
+	bool need_tmp_grab = test_opt(sbi, DISABLE_CHECKPOINT);
+	blkcnt_t tmp_block = 1;
 	int err = 0;
 
 	set_new_dnode(&dn, inode, NULL, NULL, 0);
@@ -1798,6 +1814,11 @@ int f2fs_do_write_data_page(struct f2fs_io_info *fio)
 	if (err)
 		goto out_writepage;
 
+	if (need_tmp_grab) {
+		err = inc_valid_block_count(sbi, dn.inode, &tmp_block);
+		if (err)
+			goto out_writepage;
+	}
 	set_page_writeback(page);
 	ClearPageError(page);
 
@@ -1807,6 +1828,8 @@ int f2fs_do_write_data_page(struct f2fs_io_info *fio)
 	set_inode_flag(inode, FI_APPEND_WRITE);
 	if (page->index == 0)
 		set_inode_flag(inode, FI_FIRST_BLOCK_WRITTEN);
+	if (need_tmp_grab)
+		dec_valid_block_count(sbi, dn.inode, tmp_block);
 out_writepage:
 	f2fs_put_dnode(&dn);
 out:
fs/f2fs/f2fs.h +87 -0

153 lines not on this page · raw message

fs/f2fs/file.c +18 -0
index 5474aaa274b91..4d6f12be51b6e 100644
@@ -150,6 +150,9 @@ static inline enum cp_reason_type need_do_checkpoint(struct inode *inode)
 	struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
 	enum cp_reason_type cp_reason = CP_NO_NEEDED;
 
+	if (test_opt(sbi, DISABLE_CHECKPOINT))
+		return CP_NO_NEEDED;
+
 	if (!S_ISREG(inode->i_mode))
 		cp_reason = CP_NON_REGULAR;
 	else if (inode->i_nlink != 1)
@@ -2085,6 +2088,9 @@ static int f2fs_ioc_gc(struct file *filp, unsigned long arg)
 	if (f2fs_readonly(sbi->sb))
 		return -EROFS;
 
+	if (test_opt(sbi, DISABLE_CHECKPOINT))
+		return -EINVAL;
+
 	ret = mnt_want_write_file(filp);
 	if (ret)
 		return ret;
@@ -2127,6 +2133,9 @@ static int f2fs_ioc_gc_range(struct file *filp, unsigned long arg)
 		return -EINVAL;
 	}
 
+	if (test_opt(sbi, DISABLE_CHECKPOINT))
+		return -EINVAL;
+
 	ret = mnt_want_write_file(filp);
 	if (ret)
 		return ret;
@@ -2162,6 +2171,12 @@ static int f2fs_ioc_write_checkpoint(struct file *filp, unsigned long arg)
 	if (f2fs_readonly(sbi->sb))
 		return -EROFS;
 
+	if (test_opt(sbi, DISABLE_CHECKPOINT)) {
+		f2fs_msg(sbi->sb, KERN_INFO,
+			"Skipping Checkpoint. Checkpoints currently disabled.");
+		return -EINVAL;
+	}
+
 	ret = mnt_want_write_file(filp);
 	if (ret)
 		return ret;
@@ -2533,6 +2548,9 @@ static int f2fs_ioc_flush_device(struct file *filp, unsigned long arg)
 	if (f2fs_readonly(sbi->sb))
 		return -EROFS;
 
+	if (test_opt(sbi, DISABLE_CHECKPOINT))
+		return -EINVAL;
+
 	if (copy_from_user(&range, (struct f2fs_flush_device __user *)arg,
 							sizeof(range)))
 		return -EFAULT;
fs/f2fs/gc.c +4 -0
index c598ae5ecbfa4..35c4e9bd44f6c 100644
@@ -58,6 +58,9 @@ static int gc_thread_func(void *data)
 			f2fs_stop_checkpoint(sbi, false);
 		}
 
+		if (test_opt(sbi, DISABLE_CHECKPOINT))
+			goto do_balance;
+
 		if (!sb_start_write_trylock(sbi->sb))
 			continue;
 
@@ -103,6 +106,7 @@ static int gc_thread_func(void *data)
 		trace_f2fs_background_gc(sbi->sb, wait_ms,
 				prefree_segments(sbi), free_segments(sbi));
 
+do_balance:
 		/* balancing f2fs's metadata periodically */
 		f2fs_balance_fs_bg(sbi);
 next:
fs/f2fs/segment.c +44 -11

134 lines not on this page · raw message

fs/f2fs/segment.h +26 -0
index b3d9e317ff0c1..422b0ceb1eaaf 100644
@@ -342,6 +342,12 @@ static inline unsigned int get_valid_blocks(struct f2fs_sb_info *sbi,
 		return get_seg_entry(sbi, segno)->valid_blocks;
 }
 
+static inline unsigned int get_ckpt_valid_blocks(struct f2fs_sb_info *sbi,
+				unsigned int segno)
+{
+	return get_seg_entry(sbi, segno)->ckpt_valid_blocks;
+}
+
 static inline void seg_info_from_raw_sit(struct seg_entry *se,
 					struct f2fs_sit_entry *rs)
 {
@@ -524,6 +530,26 @@ static inline unsigned int dirty_segments(struct f2fs_sb_info *sbi)
 		DIRTY_I(sbi)->nr_dirty[DIRTY_COLD_NODE];
 }
 
+void __locate_dirty_segment(struct f2fs_sb_info *sbi,
+		unsigned int segno, enum dirty_type dirty_type);
+
+void __remove_dirty_segment(struct f2fs_sb_info *sbi,
+		unsigned int segno, enum dirty_type dirty_type);
+
+/* This moves currently empty dirty blocks to prefree. Must hold seglist_lock */
+static inline void dirty_to_prefree(struct f2fs_sb_info *sbi)
+{
+	struct dirty_seglist_info *dirty_i = DIRTY_I(sbi);
+	unsigned int segno;
+
+	for_each_set_bit(segno, dirty_i->dirty_segmap[DIRTY], MAIN_SEGS(sbi)) {
+		if (!get_valid_blocks(sbi, segno, false)) {
+			__locate_dirty_segment(sbi, segno, PRE);
+			__remove_dirty_segment(sbi, segno, DIRTY);
+		}
+	}
+}
+
 static inline int overprovision_segments(struct f2fs_sb_info *sbi)
 {
 	return SM_I(sbi)->ovp_segments;
fs/f2fs/super.c +119 -5

226 lines not on this page · raw message

Signature · 1 line
2.18.0.865.gffc8e1a3cd6-goog