aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChunhai Guo <guochunhai@vivo.com>2023-06-26 17:30:46 +0800
committerJaegeuk Kim <jaegeuk@kernel.org>2023-06-30 15:07:42 -0700
commit057d65c98e7a7171264dd857983caaf6f530a078 (patch)
treeac39113f2b243983281a1c5e093129b54d200fff
parentc05ab3493c3dc65ce984bf78a36d25c6506790bc (diff)
downloadf2fs-tools-057d65c98e7a7171264dd857983caaf6f530a078.tar.gz
fsck.f2fs: Detect and fix looped node chain efficiently
find_fsync_inode() detect the looped node chain by comparing the loop counter with free blocks. While it may take tens of seconds to quit when the free blocks are large enough. We can use Floyd's cycle detection algorithm to make the detection more efficient, and fix the issue by filling a NULL address in the last node of the chain. Below is the log we encounter on a 256GB UFS storage and it takes about 25 seconds to detect looped node chain. After changing the algorithm, it takes about 20ms to finish the same job. [ 10.822904] fsck.f2fs: Info: version timestamp cur: 17, prev: 430 [ 10.822949] fsck.f2fs: [update_superblock: 762] Info: Done to update superblock [ 10.822953] fsck.f2fs: Info: superblock features = 1499 : encrypt verity extra_attr project_quota quota_ino casefold [ 10.822956] fsck.f2fs: Info: superblock encrypt level = 0, salt = 00000000000000000000000000000000 [ 10.822960] fsck.f2fs: Info: total FS sectors = 59249811 (231444 MB) [ 35.852827] fsck.f2fs:detect looped node chain, blkaddr:1114802, next:1114803 [ 35.852842] fsck.f2fs: [f2fs_do_mount:3846] record_fsync_data failed [ 35.856106] fsck.f2fs: fsck.f2fs terminated by exit(255) Suggested-by: Chao Yu <chao@kernel.org> Signed-off-by: Chunhai Guo <guochunhai@vivo.com> Reviewed-by: Chao Yu <chao@kernel.org> Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
-rw-r--r--fsck/mount.c135
1 files changed, 115 insertions, 20 deletions
diff --git a/fsck/mount.c b/fsck/mount.c
index 8db2a97..1fbf187 100644
--- a/fsck/mount.c
+++ b/fsck/mount.c
@@ -3492,22 +3492,118 @@ static void destroy_fsync_dnodes(struct list_head *head)
del_fsync_inode(entry);
}
+static int loop_node_chain_fix(block_t blkaddr_fast,
+ struct f2fs_node *node_blk_fast,
+ block_t blkaddr, struct f2fs_node *node_blk)
+{
+ block_t blkaddr_entry, blkaddr_tmp;
+ int err;
+
+ /* find the entry point of the looped node chain */
+ while (blkaddr_fast != blkaddr) {
+ err = dev_read_block(node_blk_fast, blkaddr_fast);
+ if (err)
+ return err;
+ blkaddr_fast = next_blkaddr_of_node(node_blk_fast);
+
+ err = dev_read_block(node_blk, blkaddr);
+ if (err)
+ return err;
+ blkaddr = next_blkaddr_of_node(node_blk);
+ }
+ blkaddr_entry = blkaddr;
+
+ /* find the last node of the chain */
+ do {
+ blkaddr_tmp = blkaddr;
+ err = dev_read_block(node_blk, blkaddr);
+ if (err)
+ return err;
+ blkaddr = next_blkaddr_of_node(node_blk);
+ } while (blkaddr != blkaddr_entry);
+
+ /* fix the blkaddr of last node with NULL_ADDR. */
+ node_blk->footer.next_blkaddr = NULL_ADDR;
+ if (IS_INODE(node_blk))
+ err = write_inode(node_blk, blkaddr_tmp);
+ else
+ err = dev_write_block(node_blk, blkaddr_tmp);
+ if (!err)
+ FIX_MSG("Fix looped node chain on blkaddr %u\n",
+ blkaddr_tmp);
+ return err;
+}
+
+/* Detect looped node chain with Floyd's cycle detection algorithm. */
+static int sanity_check_node_chain(struct f2fs_sb_info *sbi,
+ block_t *blkaddr_fast, struct f2fs_node *node_blk_fast,
+ block_t blkaddr, struct f2fs_node *node_blk,
+ bool *is_detecting)
+{
+ int i, err;
+
+ if (!*is_detecting)
+ return 0;
+
+ for (i = 0; i < 2; i++) {
+ if (!f2fs_is_valid_blkaddr(sbi, *blkaddr_fast, META_POR)) {
+ *is_detecting = false;
+ return 0;
+ }
+
+ err = dev_read_block(node_blk_fast, *blkaddr_fast);
+ if (err)
+ return err;
+
+ if (!is_recoverable_dnode(sbi, node_blk_fast)) {
+ *is_detecting = false;
+ return 0;
+ }
+
+ *blkaddr_fast = next_blkaddr_of_node(node_blk_fast);
+ }
+
+ if (*blkaddr_fast != blkaddr)
+ return 0;
+
+ ASSERT_MSG("\tdetect looped node chain, blkaddr:%u\n", blkaddr);
+
+ /* return -ELOOP will coninue fsck rather than exiting directly */
+ if (!c.fix_on)
+ return -ELOOP;
+
+ err = loop_node_chain_fix(NEXT_FREE_BLKADDR(sbi,
+ CURSEG_I(sbi, CURSEG_WARM_NODE)),
+ node_blk_fast, blkaddr, node_blk);
+ if (err)
+ return err;
+
+ /* Since we call get_fsync_inode() to ensure there are no
+ * duplicate inodes in the inode_list even if there are
+ * duplicate blkaddr, we can continue running after fixing the
+ * looped node chain.
+ */
+ *is_detecting = false;
+
+ return 0;
+}
+
static int find_fsync_inode(struct f2fs_sb_info *sbi, struct list_head *head)
{
struct curseg_info *curseg;
- struct f2fs_node *node_blk;
- block_t blkaddr;
- unsigned int loop_cnt = 0;
- unsigned int free_blocks = MAIN_SEGS(sbi) * sbi->blocks_per_seg -
- sbi->total_valid_block_count;
+ struct f2fs_node *node_blk, *node_blk_fast;
+ block_t blkaddr, blkaddr_fast;
+ bool is_detecting = true;
int err = 0;
+ node_blk = calloc(F2FS_BLKSIZE, 1);
+ node_blk_fast = calloc(F2FS_BLKSIZE, 1);
+ ASSERT(node_blk && node_blk_fast);
+
/* get node pages in the current segment */
curseg = CURSEG_I(sbi, CURSEG_WARM_NODE);
blkaddr = NEXT_FREE_BLKADDR(sbi, curseg);
-
- node_blk = calloc(F2FS_BLKSIZE, 1);
- ASSERT(node_blk);
+ blkaddr_fast = blkaddr;
while (1) {
struct fsync_inode_entry *entry;
@@ -3538,19 +3634,16 @@ static int find_fsync_inode(struct f2fs_sb_info *sbi, struct list_head *head)
if (IS_INODE(node_blk) && is_dent_dnode(node_blk))
entry->last_dentry = blkaddr;
next:
- /* sanity check in order to detect looped node chain */
- if (++loop_cnt >= free_blocks ||
- blkaddr == next_blkaddr_of_node(node_blk)) {
- MSG(0, "\tdetect looped node chain, blkaddr:%u, next:%u\n",
- blkaddr,
- next_blkaddr_of_node(node_blk));
- err = -1;
- break;
- }
-
blkaddr = next_blkaddr_of_node(node_blk);
+
+ err = sanity_check_node_chain(sbi, &blkaddr_fast,
+ node_blk_fast, blkaddr, node_blk,
+ &is_detecting);
+ if (err)
+ break;
}
+ free(node_blk_fast);
free(node_blk);
return err;
}
@@ -3825,9 +3918,11 @@ out:
return -1;
}
- if (record_fsync_data(sbi)) {
+ ret = record_fsync_data(sbi);
+ if (ret) {
ERR_MSG("record_fsync_data failed\n");
- return -1;
+ if (ret != -ELOOP)
+ return -1;
}
if (!f2fs_should_proceed(sb, get_cp(ckpt_flags)))