f2fs: compress: fix to cover normal cluster write with cp_rwsem
authorChao Yu <chao@kernel.org>
Fri, 12 Jan 2024 19:41:28 +0000 (03:41 +0800)
committerJaegeuk Kim <jaegeuk@kernel.org>
Tue, 6 Feb 2024 02:58:38 +0000 (18:58 -0800)
When we overwrite compressed cluster w/ normal cluster, we should
not unlock cp_rwsem during f2fs_write_raw_pages(), otherwise data
will be corrupted if partial blocks were persisted before CP & SPOR,
due to cluster metadata wasn't updated atomically.

Fixes: 4c8ff7095bef ("f2fs: support data compression")
Reviewed-by: Daeho Jeong <daehojeong@google.com>
Signed-off-by: Chao Yu <chao@kernel.org>
Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
fs/f2fs/compress.c
fs/f2fs/data.c

index 3a8d8a213b406382b8481c11d77d0c78b2c8ee75..ff26b49c0d71ffb21e58a0fcfd76d5b6a7c605f1 100644 (file)
@@ -1443,12 +1443,14 @@ void f2fs_compress_write_end_io(struct bio *bio, struct page *page)
 }
 
 static int f2fs_write_raw_pages(struct compress_ctx *cc,
-                                       int *submitted,
+                                       int *submitted_p,
                                        struct writeback_control *wbc,
                                        enum iostat_type io_type)
 {
        struct address_space *mapping = cc->inode->i_mapping;
-       int _submitted, compr_blocks, ret, i;
+       struct f2fs_sb_info *sbi = F2FS_M_SB(mapping);
+       int submitted, compr_blocks, i;
+       int ret = 0;
 
        compr_blocks = f2fs_compressed_blocks(cc);
 
@@ -1463,6 +1465,10 @@ static int f2fs_write_raw_pages(struct compress_ctx *cc,
        if (compr_blocks < 0)
                return compr_blocks;
 
+       /* overwrite compressed cluster w/ normal cluster */
+       if (compr_blocks > 0)
+               f2fs_lock_op(sbi);
+
        for (i = 0; i < cc->cluster_size; i++) {
                if (!cc->rpages[i])
                        continue;
@@ -1487,7 +1493,7 @@ continue_unlock:
                if (!clear_page_dirty_for_io(cc->rpages[i]))
                        goto continue_unlock;
 
-               ret = f2fs_write_single_data_page(cc->rpages[i], &_submitted,
+               ret = f2fs_write_single_data_page(cc->rpages[i], &submitted,
                                                NULL, NULL, wbc, io_type,
                                                compr_blocks, false);
                if (ret) {
@@ -1495,26 +1501,29 @@ continue_unlock:
                                unlock_page(cc->rpages[i]);
                                ret = 0;
                        } else if (ret == -EAGAIN) {
+                               ret = 0;
                                /*
                                 * for quota file, just redirty left pages to
                                 * avoid deadlock caused by cluster update race
                                 * from foreground operation.
                                 */
                                if (IS_NOQUOTA(cc->inode))
-                                       return 0;
-                               ret = 0;
+                                       goto out;
                                f2fs_io_schedule_timeout(DEFAULT_IO_TIMEOUT);
                                goto retry_write;
                        }
-                       return ret;
+                       goto out;
                }
 
-               *submitted += _submitted;
+               *submitted_p += submitted;
        }
 
-       f2fs_balance_fs(F2FS_M_SB(mapping), true);
+out:
+       if (compr_blocks > 0)
+               f2fs_unlock_op(sbi);
 
-       return 0;
+       f2fs_balance_fs(sbi, true);
+       return ret;
 }
 
 int f2fs_write_multi_pages(struct compress_ctx *cc,
index d00e92b6c9025039fe8eb9c71c3e7452e2feb674..7a93a99fbd04bea53e68e8643c3538cc95ac9b75 100644 (file)
@@ -2839,7 +2839,7 @@ int f2fs_write_single_data_page(struct page *page, int *submitted,
                .encrypted_page = NULL,
                .submitted = 0,
                .compr_blocks = compr_blocks,
-               .need_lock = LOCK_RETRY,
+               .need_lock = compr_blocks ? LOCK_DONE : LOCK_RETRY,
                .post_read = f2fs_post_read_required(inode) ? 1 : 0,
                .io_type = io_type,
                .io_wbc = wbc,
@@ -2920,6 +2920,7 @@ write:
        if (err == -EAGAIN) {
                err = f2fs_do_write_data_page(&fio);
                if (err == -EAGAIN) {
+                       f2fs_bug_on(sbi, compr_blocks);
                        fio.need_lock = LOCK_REQ;
                        err = f2fs_do_write_data_page(&fio);
                }