diff options
author | Chao Yu <chao@kernel.org> | 2021-04-12 16:15:12 +0800 |
---|---|---|
committer | Chao Yu <chao@kernel.org> | 2022-03-29 13:10:35 +0800 |
commit | 3f75783c1f20e52b81471b577266d8eee74786e5 (patch) | |
tree | d9825ab32be4e314031021c50f665acdeb5eefd6 | |
parent | f96877a2dec6a9577ceecf26e27897182013a996 (diff) | |
download | linux-dev.tar.gz |
f2fs: fix to keep isolation of atomic writedev
As Yi Chen reported, there is a potential race case described as below:
Thread A Thread B
- f2fs_ioc_start_atomic_write
- mkwrite
- set_page_dirty
- f2fs_set_page_private(page, 0)
- set_inode_flag(FI_ATOMIC_FILE)
- mkwrite same page
- set_page_dirty
- f2fs_register_inmem_page
- f2fs_set_page_private(ATOMIC_WRITTEN_PAGE)
failed due to PagePrivate flag has been set
- list_add_tail
- truncate_inode_pages
- f2fs_invalidate_page
- clear page private but w/o remove it from
inmem_list
- set page->mapping to NULL
- f2fs_ioc_commit_atomic_write
- __f2fs_commit_inmem_pages
- __revoke_inmem_pages
- f2fs_put_page panic as page->mapping is NULL
The root cause is we missed to keep isolation of atomic write in the case
of start_atomic_write vs mkwrite, let start_atomic_write helds i_mmap_sem
lock to avoid this issue.
Reported-by: Yi Chen <chenyi77@huawei.com>
Signed-off-by: Chao Yu <chao@kernel.org>
-rw-r--r-- | fs/f2fs/file.c | 3 | ||||
-rw-r--r-- | fs/f2fs/segment.c | 6 |
2 files changed, 9 insertions, 0 deletions
diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index e0a83fee7f605..799c2c9c32208 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -2028,6 +2028,7 @@ static int f2fs_ioc_start_atomic_write(struct file *filp) goto out; f2fs_down_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]); + filemap_invalidate_lock(inode->i_mapping); /* * Should wait end_io to count F2FS_WB_CP_DATA correctly by @@ -2038,6 +2039,7 @@ static int f2fs_ioc_start_atomic_write(struct file *filp) inode->i_ino, get_dirty_pages(inode)); ret = filemap_write_and_wait_range(inode->i_mapping, 0, LLONG_MAX); if (ret) { + filemap_invalidate_unlock(inode->i_mapping); f2fs_up_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]); goto out; } @@ -2051,6 +2053,7 @@ static int f2fs_ioc_start_atomic_write(struct file *filp) /* add inode in inmem_list first and set atomic_file */ set_inode_flag(inode, FI_ATOMIC_FILE); clear_inode_flag(inode, FI_ATOMIC_REVOKE_REQUEST); + filemap_invalidate_unlock(inode->i_mapping); f2fs_up_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]); f2fs_update_time(F2FS_I_SB(inode), REQ_TIME); diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c index 1c20d7c9eca3e..5fa14e2bd1ae6 100644 --- a/fs/f2fs/segment.c +++ b/fs/f2fs/segment.c @@ -328,6 +328,7 @@ void f2fs_drop_inmem_pages(struct inode *inode) struct f2fs_inode_info *fi = F2FS_I(inode); do { + filemap_invalidate_lock(inode->i_mapping); mutex_lock(&fi->inmem_lock); if (list_empty(&fi->inmem_pages)) { fi->i_gc_failures[GC_FAILURE_ATOMIC] = 0; @@ -342,11 +343,13 @@ void f2fs_drop_inmem_pages(struct inode *inode) spin_unlock(&sbi->inode_lock[ATOMIC_FILE]); mutex_unlock(&fi->inmem_lock); + filemap_invalidate_unlock(inode->i_mapping); break; } __revoke_inmem_pages(inode, &fi->inmem_pages, true, false, true); mutex_unlock(&fi->inmem_lock); + filemap_invalidate_unlock(inode->i_mapping); } while (1); } @@ -472,6 +475,7 @@ int f2fs_commit_inmem_pages(struct inode *inode) f2fs_balance_fs(sbi, true); f2fs_down_write(&fi->i_gc_rwsem[WRITE]); + filemap_invalidate_lock(inode->i_mapping); f2fs_lock_op(sbi); set_inode_flag(inode, FI_ATOMIC_COMMIT); @@ -483,6 +487,8 @@ int f2fs_commit_inmem_pages(struct inode *inode) clear_inode_flag(inode, FI_ATOMIC_COMMIT); f2fs_unlock_op(sbi); + + filemap_invalidate_unlock(inode->i_mapping); f2fs_up_write(&fi->i_gc_rwsem[WRITE]); return err; |