f2fs: fix to add swap extent correctly
authorChao Yu <yuchao0@huawei.com>
Fri, 27 Dec 2019 10:44:56 +0000 (18:44 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 5 Mar 2020 15:43:48 +0000 (16:43 +0100)
commit 3e5e479a39ce9ed60cd63f7565cc1d9da77c2a4e upstream.

As Youling reported in mailing list:

https://www.linuxquestions.org/questions/linux-newbie-8/the-file-system-f2fs-is-broken-4175666043/

https://www.linux.org/threads/the-file-system-f2fs-is-broken.26490/

There is a test case can corrupt f2fs image:
- dd if=/dev/zero of=/swapfile bs=1M count=4096
- chmod 600 /swapfile
- mkswap /swapfile
- swapon --discard /swapfile

The root cause is f2fs_swap_activate() intends to return zero value
to setup_swap_extents() to enable SWP_FS mode (swap file goes through
fs), in this flow, setup_swap_extents() setups swap extent with wrong
block address range, result in discard_swap() erasing incorrect address.

Because f2fs_swap_activate() has pinned swapfile, its data block
address will not change, it's safe to let swap to handle IO through
raw device, so we can get rid of SWAP_FS mode and initial swap extents
inside f2fs_swap_activate(), by this way, later discard_swap() can trim
in right address range.

Fixes: 4969c06a0d83 ("f2fs: support swap file w/ DIO")
Signed-off-by: Chao Yu <yuchao0@huawei.com>
Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/f2fs/data.c

index 5d6fd94..ec9a1f9 100644 (file)
@@ -3030,7 +3030,8 @@ int f2fs_migrate_page(struct address_space *mapping,
 
 #ifdef CONFIG_SWAP
 /* Copied from generic_swapfile_activate() to check any holes */
-static int check_swap_activate(struct file *swap_file, unsigned int max)
+static int check_swap_activate(struct swap_info_struct *sis,
+                               struct file *swap_file, sector_t *span)
 {
        struct address_space *mapping = swap_file->f_mapping;
        struct inode *inode = mapping->host;
@@ -3041,6 +3042,8 @@ static int check_swap_activate(struct file *swap_file, unsigned int max)
        sector_t last_block;
        sector_t lowest_block = -1;
        sector_t highest_block = 0;
+       int nr_extents = 0;
+       int ret;
 
        blkbits = inode->i_blkbits;
        blocks_per_page = PAGE_SIZE >> blkbits;
@@ -3052,7 +3055,8 @@ static int check_swap_activate(struct file *swap_file, unsigned int max)
        probe_block = 0;
        page_no = 0;
        last_block = i_size_read(inode) >> blkbits;
-       while ((probe_block + blocks_per_page) <= last_block && page_no < max) {
+       while ((probe_block + blocks_per_page) <= last_block &&
+                       page_no < sis->max) {
                unsigned block_in_page;
                sector_t first_block;
 
@@ -3092,13 +3096,27 @@ static int check_swap_activate(struct file *swap_file, unsigned int max)
                                highest_block = first_block;
                }
 
+               /*
+                * We found a PAGE_SIZE-length, PAGE_SIZE-aligned run of blocks
+                */
+               ret = add_swap_extent(sis, page_no, 1, first_block);
+               if (ret < 0)
+                       goto out;
+               nr_extents += ret;
                page_no++;
                probe_block += blocks_per_page;
 reprobe:
                continue;
        }
-       return 0;
-
+       ret = nr_extents;
+       *span = 1 + highest_block - lowest_block;
+       if (page_no == 0)
+               page_no = 1;    /* force Empty message */
+       sis->max = page_no;
+       sis->pages = page_no - 1;
+       sis->highest_bit = page_no - 1;
+out:
+       return ret;
 bad_bmap:
        pr_err("swapon: swapfile has holes\n");
        return -EINVAL;
@@ -3120,14 +3138,14 @@ static int f2fs_swap_activate(struct swap_info_struct *sis, struct file *file,
        if (ret)
                return ret;
 
-       ret = check_swap_activate(file, sis->max);
-       if (ret)
+       ret = check_swap_activate(sis, file, span);
+       if (ret < 0)
                return ret;
 
        set_inode_flag(inode, FI_PIN_FILE);
        f2fs_precache_extents(inode);
        f2fs_update_time(F2FS_I_SB(inode), REQ_TIME);
-       return 0;
+       return ret;
 }
 
 static void f2fs_swap_deactivate(struct file *file)