f2fs: fix to handle segment allocation failure correctly
authorChao Yu <chao@kernel.org>
Thu, 22 Feb 2024 12:18:50 +0000 (20:18 +0800)
committerJaegeuk Kim <jaegeuk@kernel.org>
Thu, 29 Feb 2024 16:34:34 +0000 (08:34 -0800)
If CONFIG_F2FS_CHECK_FS is off, and for very rare corner case that
we run out of free segment, we should not panic kernel, instead,
let's handle such error correctly in its caller.

Signed-off-by: Chao Yu <chao@kernel.org>
Tested-by: Zhiguo Niu <zhiguo.niu@unisoc.com>
Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
fs/f2fs/data.c
fs/f2fs/f2fs.h
fs/f2fs/file.c
fs/f2fs/gc.c
fs/f2fs/segment.c

index 0c9aa3082fcf418a65bc4b189b168cf1c119ea3e..c21b92f18463e7532293eed7e63791fa78d51af3 100644 (file)
@@ -1416,8 +1416,11 @@ static int __allocate_data_block(struct dnode_of_data *dn, int seg_type)
 
        set_summary(&sum, dn->nid, dn->ofs_in_node, ni.version);
        old_blkaddr = dn->data_blkaddr;
-       f2fs_allocate_data_block(sbi, NULL, old_blkaddr, &dn->data_blkaddr,
-                               &sum, seg_type, NULL);
+       err = f2fs_allocate_data_block(sbi, NULL, old_blkaddr,
+                               &dn->data_blkaddr, &sum, seg_type, NULL);
+       if (err)
+               return err;
+
        if (GET_SEGNO(sbi, old_blkaddr) != NULL_SEGNO)
                f2fs_invalidate_internal_cache(sbi, old_blkaddr);
 
index 68a5003014b760e955dc9601c14e98f5540c9639..4ce48dbae55b8334f0756add1a2e55e24a8af91c 100644 (file)
@@ -3725,7 +3725,7 @@ void f2fs_replace_block(struct f2fs_sb_info *sbi, struct dnode_of_data *dn,
                        block_t old_addr, block_t new_addr,
                        unsigned char version, bool recover_curseg,
                        bool recover_newaddr);
-void f2fs_allocate_data_block(struct f2fs_sb_info *sbi, struct page *page,
+int f2fs_allocate_data_block(struct f2fs_sb_info *sbi, struct page *page,
                        block_t old_blkaddr, block_t *new_blkaddr,
                        struct f2fs_summary *sum, int type,
                        struct f2fs_io_info *fio);
index 891fa359f7e0aa09c4fb4a9f07f5590cfb123f46..6371bd33bf6db2704d8b0f99edc44a2f331a67f2 100644 (file)
@@ -2262,8 +2262,11 @@ static int f2fs_ioc_shutdown(struct file *filp, unsigned long arg)
        case F2FS_GOING_DOWN_METASYNC:
                /* do checkpoint only */
                ret = f2fs_sync_fs(sb, 1);
-               if (ret)
+               if (ret) {
+                       if (ret == -EIO)
+                               ret = 0;
                        goto out;
+               }
                f2fs_stop_checkpoint(sbi, false, STOP_CP_REASON_SHUTDOWN);
                break;
        case F2FS_GOING_DOWN_NOSYNC:
@@ -2279,6 +2282,8 @@ static int f2fs_ioc_shutdown(struct file *filp, unsigned long arg)
                set_sbi_flag(sbi, SBI_IS_DIRTY);
                /* do checkpoint only */
                ret = f2fs_sync_fs(sb, 1);
+               if (ret == -EIO)
+                       ret = 0;
                goto out;
        default:
                ret = -EINVAL;
index d194097c3da0ae80cffb66c65b2e0d2ea68eedef..e435e1f58cd5c3c7f33f2a099919dd6642fbb1e8 100644 (file)
@@ -1361,8 +1361,13 @@ static int move_data_block(struct inode *inode, block_t bidx,
        set_summary(&sum, dn.nid, dn.ofs_in_node, ni.version);
 
        /* allocate block address */
-       f2fs_allocate_data_block(fio.sbi, NULL, fio.old_blkaddr, &newaddr,
+       err = f2fs_allocate_data_block(fio.sbi, NULL, fio.old_blkaddr, &newaddr,
                                &sum, type, NULL);
+       if (err) {
+               f2fs_put_page(mpage, 1);
+               /* filesystem should shutdown, no need to recovery block */
+               goto up_out;
+       }
 
        fio.encrypted_page = f2fs_pagecache_get_page(META_MAPPING(fio.sbi),
                                newaddr, FGP_LOCK | FGP_CREAT, GFP_NOFS);
index dc511630eaa5b69ad4ed9975e3726ec81d2e8ef5..a5339dd7a9322fbe48a1fbdea3ea75404dc3f4e9 100644 (file)
@@ -400,6 +400,9 @@ int f2fs_commit_atomic_write(struct inode *inode)
  */
 void f2fs_balance_fs(struct f2fs_sb_info *sbi, bool need)
 {
+       if (f2fs_cp_error(sbi))
+               return;
+
        if (time_to_inject(sbi, FAULT_CHECKPOINT))
                f2fs_stop_checkpoint(sbi, false, STOP_CP_REASON_FAULT_INJECT);
 
@@ -2639,7 +2642,7 @@ static int is_next_segment_free(struct f2fs_sb_info *sbi,
  * Find a new segment from the free segments bitmap to right order
  * This function should be returned with success, otherwise BUG
  */
-static void get_new_segment(struct f2fs_sb_info *sbi,
+static int get_new_segment(struct f2fs_sb_info *sbi,
                        unsigned int *newseg, bool new_sec, bool pinning)
 {
        struct free_segmap_info *free_i = FREE_I(sbi);
@@ -2714,6 +2717,7 @@ out_unlock:
                f2fs_stop_checkpoint(sbi, false, STOP_CP_REASON_NO_SEGMENT);
                f2fs_bug_on(sbi, 1);
        }
+       return ret;
 }
 
 static void reset_curseg(struct f2fs_sb_info *sbi, int type, int modified)
@@ -2722,6 +2726,10 @@ static void reset_curseg(struct f2fs_sb_info *sbi, int type, int modified)
        struct summary_footer *sum_footer;
        unsigned short seg_type = curseg->seg_type;
 
+       /* only happen when get_new_segment() fails */
+       if (curseg->next_segno == NULL_SEGNO)
+               return;
+
        curseg->inited = true;
        curseg->segno = curseg->next_segno;
        curseg->zone = GET_ZONE_FROM_SEG(sbi, curseg->segno);
@@ -2786,7 +2794,10 @@ static int new_curseg(struct f2fs_sb_info *sbi, int type, bool new_sec)
                write_sum_page(sbi, curseg->sum_blk, GET_SUM_BLOCK(sbi, segno));
 
        segno = __get_next_segno(sbi, type);
-       get_new_segment(sbi, &segno, new_sec, pinning);
+       if (get_new_segment(sbi, &segno, new_sec, pinning)) {
+               curseg->segno = NULL_SEGNO;
+               return -ENOSPC;
+       }
        if (new_sec && pinning &&
            !f2fs_valid_pinned_area(sbi, START_BLOCK(sbi, segno))) {
                __set_free(sbi, segno);
@@ -3428,7 +3439,7 @@ static void f2fs_randomize_chunk(struct f2fs_sb_info *sbi,
                get_random_u32_inclusive(1, sbi->max_fragment_hole);
 }
 
-void f2fs_allocate_data_block(struct f2fs_sb_info *sbi, struct page *page,
+int f2fs_allocate_data_block(struct f2fs_sb_info *sbi, struct page *page,
                block_t old_blkaddr, block_t *new_blkaddr,
                struct f2fs_summary *sum, int type,
                struct f2fs_io_info *fio)
@@ -3445,6 +3456,9 @@ void f2fs_allocate_data_block(struct f2fs_sb_info *sbi, struct page *page,
        mutex_lock(&curseg->curseg_mutex);
        down_write(&sit_i->sentry_lock);
 
+       if (curseg->segno == NULL_SEGNO)
+               goto out_err;
+
        if (from_gc) {
                f2fs_bug_on(sbi, GET_SEGNO(sbi, old_blkaddr) == NULL_SEGNO);
                se = get_seg_entry(sbi, GET_SEGNO(sbi, old_blkaddr));
@@ -3504,6 +3518,9 @@ void f2fs_allocate_data_block(struct f2fs_sb_info *sbi, struct page *page,
                                change_curseg(sbi, type);
                        stat_inc_seg_type(sbi, curseg);
                }
+
+               if (curseg->segno == NULL_SEGNO)
+                       goto out_err;
        }
 
 skip_new_segment:
@@ -3538,8 +3555,15 @@ skip_new_segment:
        }
 
        mutex_unlock(&curseg->curseg_mutex);
-
        f2fs_up_read(&SM_I(sbi)->curseg_lock);
+       return 0;
+out_err:
+       *new_blkaddr = NULL_ADDR;
+       up_write(&sit_i->sentry_lock);
+       mutex_unlock(&curseg->curseg_mutex);
+       f2fs_up_read(&SM_I(sbi)->curseg_lock);
+       return -ENOSPC;
+
 }
 
 void f2fs_update_device_state(struct f2fs_sb_info *sbi, nid_t ino,
@@ -3577,8 +3601,16 @@ static void do_write_page(struct f2fs_summary *sum, struct f2fs_io_info *fio)
        if (keep_order)
                f2fs_down_read(&fio->sbi->io_order_lock);
 
-       f2fs_allocate_data_block(fio->sbi, fio->page, fio->old_blkaddr,
-                       &fio->new_blkaddr, sum, type, fio);
+       if (f2fs_allocate_data_block(fio->sbi, fio->page, fio->old_blkaddr,
+                       &fio->new_blkaddr, sum, type, fio)) {
+               if (fscrypt_inode_uses_fs_layer_crypto(fio->page->mapping->host))
+                       fscrypt_finalize_bounce_page(&fio->encrypted_page);
+               if (PageWriteback(fio->page))
+                       end_page_writeback(fio->page);
+               if (f2fs_in_warm_node_list(fio->sbi, fio->page))
+                       f2fs_del_fsync_node_entry(fio->sbi, fio->page);
+               goto out;
+       }
        if (GET_SEGNO(fio->sbi, fio->old_blkaddr) != NULL_SEGNO)
                f2fs_invalidate_internal_cache(fio->sbi, fio->old_blkaddr);
 
@@ -3586,7 +3618,7 @@ static void do_write_page(struct f2fs_summary *sum, struct f2fs_io_info *fio)
        f2fs_submit_page_write(fio);
 
        f2fs_update_device_state(fio->sbi, fio->ino, fio->new_blkaddr, 1);
-
+out:
        if (keep_order)
                f2fs_up_read(&fio->sbi->io_order_lock);
 }