On 07/11, Daniel Rosenberg wrote: > 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. > > Signed-off-by: Daniel Rosenberg <dro...@google.com> > --- > > This probably needs some work in the mount/remount areas to ensure it > plays nicely with all combinations of other options. > I'm also unsure how it should interact with statfs. > > It currently handles accounting for free space in checkpoint disabled > mode by setting up addition tracking for free data blocks, node blocks, > and segments. These are used in inc_valid_block_cnt and inc_valid_node_cnt > to track what the state will be once the blocks are actually allocated. > We choose new current segments in SSR mode first to avoid the edge case > where the disk is not yet full, but we only have dirty segments remaining > that happen to not be of the right type. We also agressively add segments > to the dirty list instead of pre-free when it is possible to reuse them to > allow us to continue without a checkpoint as long as possible. > > Documentation/filesystems/f2fs.txt | 5 ++ > fs/f2fs/data.c | 21 ++++++ > fs/f2fs/f2fs.h | 63 +++++++++++++++- > fs/f2fs/file.c | 18 +++++ > fs/f2fs/gc.c | 4 + > fs/f2fs/segment.c | 96 +++++++++++------------- > fs/f2fs/segment.h | 66 +++++++++++++++++ > fs/f2fs/super.c | 115 +++++++++++++++++++++++++++-- > 8 files changed, 326 insertions(+), 62 deletions(-) > > diff --git a/Documentation/filesystems/f2fs.txt > b/Documentation/filesystems/f2fs.txt > index 69f8de9957397..a026b353a99d4 100644 > --- a/Documentation/filesystems/f2fs.txt > +++ b/Documentation/filesystems/f2fs.txt > @@ -193,6 +193,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 > diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c > index 83d4cff445f53..b3fa713fd42bf 100644 > --- a/fs/f2fs/data.c > +++ b/fs/f2fs/data.c > @@ -1654,9 +1654,20 @@ bool f2fs_should_update_inplace(struct inode *inode, > struct f2fs_io_info *fio) > bool f2fs_should_update_outplace(struct inode *inode, struct f2fs_io_info > *fio) > { > struct f2fs_sb_info *sbi = F2FS_I_SB(inode); > + struct seg_entry *se; > + unsigned int segno, offset; > > if (test_opt(sbi, LFS)) > return true; > + if (test_opt(sbi, DISABLE_CHECKPOINT)) {
struct seg_entry *se; unsigned int segno, offset; > + if (fio->old_blkaddr == NULL_ADDR) --------- 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)) > @@ -1684,9 +1695,12 @@ 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}; > 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); > @@ -1750,6 +1764,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); > > @@ -1759,6 +1778,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: > diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h > index fe80eb637075c..024b6b971e214 100644 > --- a/fs/f2fs/f2fs.h > +++ b/fs/f2fs/f2fs.h > @@ -97,6 +97,7 @@ extern char *fault_name[FAULT_MAX]; > #define F2FS_MOUNT_QUOTA 0x00400000 > #define F2FS_MOUNT_INLINE_XATTR_SIZE 0x00800000 > #define F2FS_MOUNT_RESERVE_ROOT 0x01000000 > +#define F2FS_MOUNT_DISABLE_CHECKPOINT 0x02000000 > > #define F2FS_OPTION(sbi) ((sbi)->mount_opt) > #define clear_opt(sbi, option) (F2FS_OPTION(sbi).opt &= > ~F2FS_MOUNT_##option) > @@ -175,6 +176,7 @@ enum { > #define CP_RECOVERY 0x00000008 > #define CP_DISCARD 0x00000010 > #define CP_TRIMMED 0x00000020 > +#define CP_PAUSE 0x00000040 > > #define MAX_DISCARD_BLOCKS(sbi) BLKS_PER_SEC(sbi) > #define DEF_MAX_DISCARD_REQUEST 8 /* issue 8 discards per > round */ > @@ -1067,6 +1069,7 @@ enum { > SBI_NEED_SB_WRITE, /* need to recover superblock */ > SBI_NEED_CP, /* need to checkpoint */ > SBI_IS_SHUTDOWN, /* shutdown by ioctl */ > + SBI_CP_DISABLED, /* CP was disabled last mount */ > }; > > enum { > @@ -1192,6 +1195,12 @@ struct f2fs_sb_info { > block_t reserved_blocks; /* configurable reserved blocks > */ > block_t current_reserved_blocks; /* current reserved blocks */ > > + /* Additional tracking for no checkpoint mode */ > + block_t unusable_block_count; /* # of blocks saved by last cp > */ > + block_t free_ssr_data_block; > + block_t free_ssr_node_block; > + block_t free_segments; > + > unsigned int nquota_files; /* # of quota sysfile */ > > u32 s_next_generation; /* for NFS support */ > @@ -1643,7 +1652,7 @@ static inline void f2fs_i_blocks_write(struct inode *, > block_t, bool, bool); > static inline int inc_valid_block_count(struct f2fs_sb_info *sbi, > struct inode *inode, blkcnt_t *count) > { > - blkcnt_t diff = 0, release = 0; > + blkcnt_t diff = 0, release = 0, seg_diff = 0, seg_rel = 0; > block_t avail_user_block_count; > int ret; > > @@ -1671,6 +1680,8 @@ static inline int inc_valid_block_count(struct > f2fs_sb_info *sbi, > > if (!__allow_reserved_blocks(sbi, inode, true)) > avail_user_block_count -= F2FS_OPTION(sbi).root_reserved_blocks; > + if (test_opt(sbi, DISABLE_CHECKPOINT)) > + avail_user_block_count -= sbi->unusable_block_count; > > if (unlikely(sbi->total_valid_block_count > avail_user_block_count)) { > diff = sbi->total_valid_block_count - avail_user_block_count; > @@ -1681,18 +1692,51 @@ static inline int inc_valid_block_count(struct > f2fs_sb_info *sbi, > sbi->total_valid_block_count -= diff; > if (!*count) { > spin_unlock(&sbi->stat_lock); > - percpu_counter_sub(&sbi->alloc_valid_block_count, diff); Please rebase on top of another fix for this. > goto enospc; > } > } if (likely(!test_opt(sbi, DISABLE_CHECKPOINT))) goto normal; > + if (test_opt(sbi, DISABLE_CHECKPOINT)) { > + if (unlikely(*count > sbi->free_ssr_data_block)) { > + /* We'll need to pull from free. */ > + blkcnt_t needed = *count - sbi->free_ssr_data_block; > + blkcnt_t new_segs = ((needed - 1) >> > + sbi->log_blocks_per_seg) + 1; > + > + /* Check if we have enough free */ > + if (unlikely(new_segs > sbi->free_segments)) { > + seg_diff = new_segs - sbi->free_segments; > + > + seg_rel = ((needed - 1) % > + sbi->log_blocks_per_seg) + 1; > + seg_rel += (seg_diff - 1) << > + sbi->log_blocks_per_seg; > + new_segs -= seg_diff; > + *count -= seg_rel; > + release += seg_rel; > + if (!*count) { > + spin_unlock(&sbi->stat_lock); > + goto enospc; > + } > + } > + > + sbi->free_segments -= new_segs; > + sbi->free_ssr_data_block += new_segs << > + sbi->log_blocks_per_seg; > + > + } > + sbi->free_ssr_data_block -= *count; > + } normal: > spin_unlock(&sbi->stat_lock); > > - if (unlikely(release)) > + if (unlikely(release)) { > + percpu_counter_sub(&sbi->alloc_valid_block_count, release); > dquot_release_reservation_block(inode, release); > + } > f2fs_i_blocks_write(inode, *count, true, true); > return 0; > > enospc: > + percpu_counter_sub(&sbi->alloc_valid_block_count, release); > dquot_release_reservation_block(inode, release); > return -ENOSPC; > } > @@ -1878,6 +1922,8 @@ static inline int inc_valid_node_count(struct > f2fs_sb_info *sbi, > > if (!__allow_reserved_blocks(sbi, inode, false)) > valid_block_count += F2FS_OPTION(sbi).root_reserved_blocks; > + if (test_opt(sbi, DISABLE_CHECKPOINT)) > + valid_block_count += sbi->unusable_block_count; > > if (unlikely(valid_block_count > sbi->user_block_count)) { > spin_unlock(&sbi->stat_lock); > @@ -1890,6 +1936,17 @@ static inline int inc_valid_node_count(struct > f2fs_sb_info *sbi, > goto enospc; > } > > + if (test_opt(sbi, DISABLE_CHECKPOINT)) { > + if (unlikely(!sbi->free_ssr_node_block)) { > + if (unlikely(!sbi->free_segments)) { > + spin_unlock(&sbi->stat_lock); > + goto enospc; > + } > + sbi->free_segments--; > + } > + sbi->free_ssr_node_block--; > + } > + > sbi->total_valid_node_count++; > sbi->total_valid_block_count++; > spin_unlock(&sbi->stat_lock); > diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c > index 8af6683e022be..1f9a8119e17da 100644 > --- a/fs/f2fs/file.c > +++ b/fs/f2fs/file.c > @@ -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) > @@ -2046,6 +2049,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; > @@ -2088,6 +2094,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; > @@ -2123,6 +2132,12 @@ static int f2fs_ioc_f2fs_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; > @@ -2489,6 +2504,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; > diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c > index 9093be6e7a7db..4100dced6c309 100644 > --- a/fs/f2fs/gc.c > +++ b/fs/f2fs/gc.c > @@ -60,6 +60,9 @@ static int gc_thread_func(void *data) > } > #endif > > + if (test_opt(sbi, DISABLE_CHECKPOINT)) > + goto do_balance; > + > if (!sb_start_write_trylock(sbi->sb)) > continue; > > @@ -105,6 +108,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: > diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c > index 9efce174c51a9..608bf53d81f54 100644 > --- a/fs/f2fs/segment.c > +++ b/fs/f2fs/segment.c > @@ -179,6 +179,10 @@ bool f2fs_need_SSR(struct f2fs_sb_info *sbi) > return false; > if (sbi->gc_mode == GC_URGENT) > return true; > + if (test_opt(sbi, DISABLE_CHECKPOINT)) > + return true; > + if (sbi->gc_thread && sbi->gc_thread->gc_urgent) > + return true; > > return free_sections(sbi) <= (node_secs + 2 * dent_secs + imeta_secs + > SM_I(sbi)->min_ssr_sections + reserved_sections(sbi)); > @@ -479,7 +483,8 @@ void f2fs_balance_fs(struct f2fs_sb_info *sbi, bool need) > * We should do GC or end up with checkpoint, if there are so many dirty > * dir/node pages without enough free segments. > */ > - if (has_not_enough_free_secs(sbi, 0, 0)) { > + if (has_not_enough_free_secs(sbi, 0, 0) && > + !test_opt(sbi, DISABLE_CHECKPOINT)) { > mutex_lock(&sbi->gc_mutex); > f2fs_gc(sbi, false, false, NULL_SEGNO); > } > @@ -519,8 +524,10 @@ void f2fs_balance_fs_bg(struct f2fs_sb_info *sbi) > f2fs_sync_dirty_inodes(sbi, FILE_INODE); > blk_finish_plug(&plug); > } > - f2fs_sync_fs(sbi->sb, true); > - stat_inc_bg_cp_count(sbi->stat_info); > + if (!test_opt(sbi, DISABLE_CHECKPOINT)) { > + f2fs_sync_fs(sbi->sb, true); > + stat_inc_bg_cp_count(sbi->stat_info); > + } > } > } > > @@ -735,52 +742,6 @@ int f2fs_flush_device_cache(struct f2fs_sb_info *sbi) > return ret; > } > > -static void __locate_dirty_segment(struct f2fs_sb_info *sbi, unsigned int > segno, > - enum dirty_type dirty_type) > -{ > - struct dirty_seglist_info *dirty_i = DIRTY_I(sbi); > - > - /* need not be added */ > - if (IS_CURSEG(sbi, segno)) > - return; > - > - if (!test_and_set_bit(segno, dirty_i->dirty_segmap[dirty_type])) > - dirty_i->nr_dirty[dirty_type]++; > - > - if (dirty_type == DIRTY) { > - struct seg_entry *sentry = get_seg_entry(sbi, segno); > - enum dirty_type t = sentry->type; > - > - if (unlikely(t >= DIRTY)) { > - f2fs_bug_on(sbi, 1); > - return; > - } > - if (!test_and_set_bit(segno, dirty_i->dirty_segmap[t])) > - dirty_i->nr_dirty[t]++; > - } > -} > - > -static void __remove_dirty_segment(struct f2fs_sb_info *sbi, unsigned int > segno, > - enum dirty_type dirty_type) > -{ > - struct dirty_seglist_info *dirty_i = DIRTY_I(sbi); > - > - if (test_and_clear_bit(segno, dirty_i->dirty_segmap[dirty_type])) > - dirty_i->nr_dirty[dirty_type]--; > - > - if (dirty_type == DIRTY) { > - struct seg_entry *sentry = get_seg_entry(sbi, segno); > - enum dirty_type t = sentry->type; > - > - if (test_and_clear_bit(segno, dirty_i->dirty_segmap[t])) > - dirty_i->nr_dirty[t]--; > - > - if (get_valid_blocks(sbi, segno, true) == 0) > - clear_bit(GET_SEC_FROM_SEG(sbi, segno), > - dirty_i->victim_secmap); > - } > -} > - Can we keep the above functions, since it's a bit hard to review the code. Let me take a look at the change with more time. Thanks, > /* > * Should not occur error such as -ENOMEM. > * Adding dirty entry into seglist is not critical operation. > @@ -789,7 +750,7 @@ static void __remove_dirty_segment(struct f2fs_sb_info > *sbi, unsigned int segno, > static void locate_dirty_segment(struct f2fs_sb_info *sbi, unsigned int > segno) > { > struct dirty_seglist_info *dirty_i = DIRTY_I(sbi); > - unsigned short valid_blocks; > + unsigned short valid_blocks, ckpt_valid_blocks; > > if (segno == NULL_SEGNO || IS_CURSEG(sbi, segno)) > return; > @@ -797,8 +758,10 @@ static void locate_dirty_segment(struct f2fs_sb_info > *sbi, unsigned int segno) > mutex_lock(&dirty_i->seglist_lock); > > valid_blocks = get_valid_blocks(sbi, segno, false); > + ckpt_valid_blocks = get_ckpt_valid_blocks(sbi, segno); > > - if (valid_blocks == 0) { > + if (valid_blocks == 0 && (ckpt_valid_blocks == sbi->blocks_per_seg || > + !test_opt(sbi, DISABLE_CHECKPOINT))) { > __locate_dirty_segment(sbi, segno, PRE); > __remove_dirty_segment(sbi, segno, DIRTY); > } else if (valid_blocks < sbi->blocks_per_seg) { > @@ -1852,7 +1815,8 @@ static void update_sit_entry(struct f2fs_sb_info *sbi, > block_t blkaddr, int del) > sbi->discard_blks--; > > /* don't overwrite by SSR to keep node chain */ > - if (IS_NODESEG(se->type)) { > + if (IS_NODESEG(se->type) && > + !test_opt(sbi, DISABLE_CHECKPOINT)) { > if (!f2fs_test_and_set_bit(offset, se->ckpt_valid_map)) > se->ckpt_valid_blocks++; > } > @@ -1874,6 +1838,25 @@ static void update_sit_entry(struct f2fs_sb_info *sbi, > block_t blkaddr, int del) > f2fs_bug_on(sbi, 1); > se->valid_blocks++; > del = 0; > + } else { > + /* If checkpoints are off, we must not reuse data that > + * was used in the previous checkpoint. If it was used > + * before, we must track that to know how much space we > + * really have > + */ > + if (f2fs_test_bit(offset, se->ckpt_valid_map)) { > + spin_lock(&sbi->stat_lock); > + sbi->unusable_block_count++; > + spin_unlock(&sbi->stat_lock); > + } else { > + spin_lock(&sbi->stat_lock); > + if (IS_DATASEG(se->type)) > + sbi->free_ssr_data_block++; > + else > + sbi->free_ssr_node_block++; > + spin_unlock(&sbi->stat_lock); > + } > + > } > > if (f2fs_discard_en(sbi) && > @@ -2163,7 +2146,8 @@ static unsigned int __get_next_segno(struct > f2fs_sb_info *sbi, int type) > return SIT_I(sbi)->last_victim[ALLOC_NEXT]; > > /* find segments from 0 to reuse freed segments */ > - if (F2FS_OPTION(sbi).alloc_mode == ALLOC_MODE_REUSE) > + if (F2FS_OPTION(sbi).alloc_mode == ALLOC_MODE_REUSE > + || test_opt(sbi, DISABLE_CHECKPOINT)) > return 0; > > return CURSEG_I(sbi, type)->segno; > @@ -2315,7 +2299,8 @@ static void allocate_segment_by_default(struct > f2fs_sb_info *sbi, > else if (!is_set_ckpt_flags(sbi, CP_CRC_RECOVERY_FLAG) && > type == CURSEG_WARM_NODE) > new_curseg(sbi, type, false); > - else if (curseg->alloc_type == LFS && is_next_segment_free(sbi, type)) > + else if (curseg->alloc_type == LFS && is_next_segment_free(sbi, type) && > + !test_opt(sbi, DISABLE_CHECKPOINT)) > new_curseg(sbi, type, false); > else if (f2fs_need_SSR(sbi) && get_ssr_segment(sbi, type)) > change_curseg(sbi, type); > @@ -3476,6 +3461,9 @@ void f2fs_flush_sit_entries(struct f2fs_sb_info *sbi, > struct cp_control *cpc) > sit_i->dirty_sentries--; > ses->entry_cnt--; > } > + spin_lock(&sbi->stat_lock); > + sbi->unusable_block_count = 0; > + spin_unlock(&sbi->stat_lock); > > if (to_journal) > up_write(&curseg->journal_rwsem); > diff --git a/fs/f2fs/segment.h b/fs/f2fs/segment.h > index f18fc82fbe998..9789cadc16569 100644 > --- a/fs/f2fs/segment.h > +++ b/fs/f2fs/segment.h > @@ -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) > { > @@ -521,6 +527,66 @@ static inline unsigned int dirty_segments(struct > f2fs_sb_info *sbi) > DIRTY_I(sbi)->nr_dirty[DIRTY_COLD_NODE]; > } > > +static inline void __locate_dirty_segment(struct f2fs_sb_info *sbi, > + unsigned int segno, enum dirty_type dirty_type) > +{ > + struct dirty_seglist_info *dirty_i = DIRTY_I(sbi); > + > + /* need not be added */ > + if (IS_CURSEG(sbi, segno)) > + return; > + > + if (!test_and_set_bit(segno, dirty_i->dirty_segmap[dirty_type])) > + dirty_i->nr_dirty[dirty_type]++; > + > + if (dirty_type == DIRTY) { > + struct seg_entry *sentry = get_seg_entry(sbi, segno); > + enum dirty_type t = sentry->type; > + > + if (unlikely(t >= DIRTY)) { > + f2fs_bug_on(sbi, 1); > + return; > + } > + if (!test_and_set_bit(segno, dirty_i->dirty_segmap[t])) > + dirty_i->nr_dirty[t]++; > + } > +} > + > +static inline void __remove_dirty_segment(struct f2fs_sb_info *sbi, > + unsigned int segno, enum dirty_type dirty_type) > +{ > + struct dirty_seglist_info *dirty_i = DIRTY_I(sbi); > + > + if (test_and_clear_bit(segno, dirty_i->dirty_segmap[dirty_type])) > + dirty_i->nr_dirty[dirty_type]--; > + > + if (dirty_type == DIRTY) { > + struct seg_entry *sentry = get_seg_entry(sbi, segno); > + enum dirty_type t = sentry->type; > + > + if (test_and_clear_bit(segno, dirty_i->dirty_segmap[t])) > + dirty_i->nr_dirty[t]--; > + > + if (get_valid_blocks(sbi, segno, true) == 0) > + clear_bit(GET_SEC_FROM_SEG(sbi, segno), > + dirty_i->victim_secmap); > + } > +} > + > +/* 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; > diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c > index 1cb5d1e4fcfd2..78b46f1b9000e 100644 > --- a/fs/f2fs/super.c > +++ b/fs/f2fs/super.c > @@ -132,6 +132,7 @@ enum { > Opt_alloc, > Opt_fsync, > Opt_test_dummy_encryption, > + Opt_checkpoint, > Opt_err, > }; > > @@ -189,6 +190,7 @@ static match_table_t f2fs_tokens = { > {Opt_alloc, "alloc_mode=%s"}, > {Opt_fsync, "fsync_mode=%s"}, > {Opt_test_dummy_encryption, "test_dummy_encryption"}, > + {Opt_checkpoint, "checkpoint=%s"}, > {Opt_err, NULL}, > }; > > @@ -764,6 +766,23 @@ static int parse_options(struct super_block *sb, char > *options) > "Test dummy encryption mount option > ignored"); > #endif > break; > + case Opt_checkpoint: > + name = match_strdup(&args[0]); > + if (!name) > + return -ENOMEM; > + > + if (strlen(name) == 6 && > + !strncmp(name, "enable", 6)) { > + clear_opt(sbi, DISABLE_CHECKPOINT); > + } else if (strlen(name) == 7 && > + !strncmp(name, "disable", 7)) { > + set_opt(sbi, DISABLE_CHECKPOINT); > + } else { > + kfree(name); > + return -EINVAL; > + } > + kfree(name); > + break; > default: > f2fs_msg(sb, KERN_ERR, > "Unrecognized mount option \"%s\" or missing > value", > @@ -809,6 +828,11 @@ static int parse_options(struct super_block *sb, char > *options) > } > } > > + if (test_opt(sbi, DISABLE_CHECKPOINT) && test_opt(sbi, LFS)) { > + f2fs_msg(sb, KERN_ERR, > + "LFS not compatible with checkpoint=disable\n"); > + } > + > /* Not pass down write hints if the number of active logs is lesser > * than NR_CURSEG_TYPE. > */ > @@ -996,8 +1020,9 @@ static void f2fs_put_super(struct super_block *sb) > * But, the previous checkpoint was not done by umount, it needs to do > * clean checkpoint again. > */ > - if (is_sbi_flag_set(sbi, SBI_IS_DIRTY) || > - !is_set_ckpt_flags(sbi, CP_UMOUNT_FLAG)) { > + if ((is_sbi_flag_set(sbi, SBI_IS_DIRTY) || > + !is_set_ckpt_flags(sbi, CP_UMOUNT_FLAG)) && > + !test_opt(sbi, DISABLE_CHECKPOINT)) { > struct cp_control cpc = { > .reason = CP_UMOUNT, > }; > @@ -1007,7 +1032,8 @@ static void f2fs_put_super(struct super_block *sb) > /* be sure to wait for any on-going discard commands */ > dropped = f2fs_wait_discard_bios(sbi); > > - if (f2fs_discard_en(sbi) && !sbi->discard_blks && !dropped) { > + if (f2fs_discard_en(sbi) && !sbi->discard_blks && !dropped && > + !test_opt(sbi, DISABLE_CHECKPOINT)) { > struct cp_control cpc = { > .reason = CP_UMOUNT | CP_TRIMMED, > }; > @@ -1064,6 +1090,8 @@ int f2fs_sync_fs(struct super_block *sb, int sync) > > if (unlikely(f2fs_cp_error(sbi))) > return 0; > + if (test_opt(sbi, DISABLE_CHECKPOINT)) > + return 0; > > trace_f2fs_sync_fs(sb, sync); > > @@ -1162,7 +1190,8 @@ static int f2fs_statfs(struct dentry *dentry, struct > kstatfs *buf) > > buf->f_blocks = total_count - start_count; > buf->f_bfree = user_block_count - valid_user_blocks(sbi) - > - sbi->current_reserved_blocks; > + sbi->current_reserved_blocks - > + sbi->unusable_block_count; > if (buf->f_bfree > F2FS_OPTION(sbi).root_reserved_blocks) > buf->f_bavail = buf->f_bfree - > F2FS_OPTION(sbi).root_reserved_blocks; > @@ -1338,6 +1367,9 @@ static int f2fs_show_options(struct seq_file *seq, > struct dentry *root) > else if (F2FS_OPTION(sbi).alloc_mode == ALLOC_MODE_REUSE) > seq_printf(seq, ",alloc_mode=%s", "reuse"); > > + if (test_opt(sbi, DISABLE_CHECKPOINT)) > + seq_puts(seq, ",checkpoint=disable"); > + > if (F2FS_OPTION(sbi).fsync_mode == FSYNC_MODE_POSIX) > seq_printf(seq, ",fsync_mode=%s", "posix"); > else if (F2FS_OPTION(sbi).fsync_mode == FSYNC_MODE_STRICT) > @@ -1362,6 +1394,7 @@ static void default_options(struct f2fs_sb_info *sbi) > set_opt(sbi, INLINE_DENTRY); > set_opt(sbi, EXTENT_CACHE); > set_opt(sbi, NOHEAP); > + clear_opt(sbi, DISABLE_CHECKPOINT); > sbi->sb->s_flags |= SB_LAZYTIME; > set_opt(sbi, FLUSH_MERGE); > if (f2fs_sb_has_blkzoned(sbi->sb)) { > @@ -1384,6 +1417,60 @@ static void default_options(struct f2fs_sb_info *sbi) > #ifdef CONFIG_QUOTA > static int f2fs_enable_quotas(struct super_block *sb); > #endif > + > +static void f2fs_disable_checkpoint(struct f2fs_sb_info *sbi) > +{ > + struct cp_control cpc; > + struct dirty_seglist_info *dirty_i = DIRTY_I(sbi); > + unsigned int segno; > + int type; > + > + set_sbi_flag(sbi, SBI_CP_DISABLED); > + > + cpc.reason = CP_PAUSE; > + > + mutex_lock(&sbi->gc_mutex); > + write_checkpoint(sbi, &cpc); > + mutex_unlock(&sbi->gc_mutex); > + > + mutex_lock(&dirty_i->seglist_lock); > + for (type = 0; type < NR_CURSEG_TYPE; type++) { > + for_each_set_bit(segno, dirty_i->dirty_segmap[type], > + MAIN_SEGS(sbi)) { > + if (IS_DATASEG(type)) > + sbi->free_ssr_data_block += > + get_valid_blocks(sbi, segno, false); > + else > + sbi->free_ssr_node_block += > + get_valid_blocks(sbi, segno, false); > + } > + } > + sbi->free_segments = FREE_I(sbi)->free_segments; > + mutex_unlock(&dirty_i->seglist_lock); > +} > + > +static void f2fs_enable_checkpoint(struct f2fs_sb_info *sbi) > +{ > + struct super_block *sb = sbi->sb; > + struct dirty_seglist_info *dirty_i = DIRTY_I(sbi); > + > + clear_sbi_flag(sbi, SBI_CP_DISABLED); > + writeback_inodes_sb(sb, WB_REASON_SYNC); > + sync_inodes_sb(sb); > + > + mutex_lock(&dirty_i->seglist_lock); > + dirty_to_prefree(sbi); > + sbi->free_segments = 0; > + sbi->free_ssr_data_block = 0; > + sbi->free_ssr_node_block = 0; > + mutex_unlock(&dirty_i->seglist_lock); > + > + set_sbi_flag(sbi, SBI_IS_DIRTY); > + set_sbi_flag(sbi, SBI_IS_CLOSE); > + f2fs_sync_fs(sb, 1); > + clear_sbi_flag(sbi, SBI_IS_CLOSE); > +} > + > static int f2fs_remount(struct super_block *sb, int *flags, char *data) > { > struct f2fs_sb_info *sbi = F2FS_SB(sb); > @@ -1393,6 +1480,8 @@ static int f2fs_remount(struct super_block *sb, int > *flags, char *data) > bool need_restart_gc = false; > bool need_stop_gc = false; > bool no_extent_cache = !test_opt(sbi, EXTENT_CACHE); > + bool disable_checkpoint = test_opt(sbi, DISABLE_CHECKPOINT); > + bool checkpoint_changed; > #ifdef CONFIG_QUOTA > int i, j; > #endif > @@ -1437,6 +1526,8 @@ static int f2fs_remount(struct super_block *sb, int > *flags, char *data) > err = parse_options(sb, data); > if (err) > goto restore_opts; > + checkpoint_changed = > + disable_checkpoint != test_opt(sbi, DISABLE_CHECKPOINT); > > /* > * Previous and new state of filesystem is RO, > @@ -1498,6 +1589,13 @@ static int f2fs_remount(struct super_block *sb, int > *flags, char *data) > clear_sbi_flag(sbi, SBI_IS_CLOSE); > } > > + if (checkpoint_changed) { > + if (test_opt(sbi, DISABLE_CHECKPOINT)) > + f2fs_disable_checkpoint(sbi); > + else > + f2fs_enable_checkpoint(sbi); > + } > + > /* > * We stop issue flush thread if FS is mounted as RO > * or if flush_merge is not passed in mount option. > @@ -2944,7 +3042,8 @@ static int f2fs_fill_super(struct super_block *sb, void > *data, int silent) > goto free_meta; > > /* recover fsynced data */ > - if (!test_opt(sbi, DISABLE_ROLL_FORWARD)) { > + if (!test_opt(sbi, DISABLE_ROLL_FORWARD) && > + !is_sbi_flag_set(sbi, SBI_CP_DISABLED)) { > /* > * mount should be failed, when device has readonly mode, and > * previous checkpoint was not done by clean system shutdown. > @@ -3010,6 +3109,12 @@ static int f2fs_fill_super(struct super_block *sb, > void *data, int silent) > cur_cp_version(F2FS_CKPT(sbi))); > f2fs_update_time(sbi, CP_TIME); > f2fs_update_time(sbi, REQ_TIME); > + > + if (test_opt(sbi, DISABLE_CHECKPOINT)) > + f2fs_disable_checkpoint(sbi); > + else if (is_sbi_flag_set(sbi, SBI_CP_DISABLED)) > + f2fs_enable_checkpoint(sbi); > + > return 0; > > free_meta: > -- > 2.18.0.203.gfac676dfb9-goog