f2fs: fix SSA updates resulting in corruption
The f2fs_collapse_range and f2fs_insert_range changes the block addresses directly. But that can cause uncovered SSA updates. In that case, we need to give up to change the block addresses and do buffered writes to keep filesystem consistency. Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
This commit is contained in:
217
fs/f2fs/file.c
217
fs/f2fs/file.c
@@ -826,86 +826,100 @@ static int punch_hole(struct inode *inode, loff_t offset, loff_t len)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int f2fs_do_collapse(struct inode *inode, pgoff_t start, pgoff_t end)
|
||||
static int __exchange_data_block(struct inode *inode, pgoff_t src,
|
||||
pgoff_t dst, bool full)
|
||||
{
|
||||
struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
|
||||
struct dnode_of_data dn;
|
||||
block_t new_addr;
|
||||
bool do_replace = false;
|
||||
int ret;
|
||||
|
||||
set_new_dnode(&dn, inode, NULL, NULL, 0);
|
||||
ret = get_dnode_of_data(&dn, src, LOOKUP_NODE_RA);
|
||||
if (ret && ret != -ENOENT) {
|
||||
return ret;
|
||||
} else if (ret == -ENOENT) {
|
||||
new_addr = NULL_ADDR;
|
||||
} else {
|
||||
new_addr = dn.data_blkaddr;
|
||||
if (!is_checkpointed_data(sbi, new_addr)) {
|
||||
dn.data_blkaddr = NULL_ADDR;
|
||||
/* do not invalidate this block address */
|
||||
set_data_blkaddr(&dn);
|
||||
f2fs_update_extent_cache(&dn);
|
||||
do_replace = true;
|
||||
}
|
||||
f2fs_put_dnode(&dn);
|
||||
}
|
||||
|
||||
if (new_addr == NULL_ADDR)
|
||||
return full ? truncate_hole(inode, dst, dst + 1) : 0;
|
||||
|
||||
if (do_replace) {
|
||||
struct page *ipage = get_node_page(sbi, inode->i_ino);
|
||||
struct node_info ni;
|
||||
|
||||
if (IS_ERR(ipage)) {
|
||||
ret = PTR_ERR(ipage);
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
set_new_dnode(&dn, inode, ipage, NULL, 0);
|
||||
ret = f2fs_reserve_block(&dn, dst);
|
||||
if (ret)
|
||||
goto err_out;
|
||||
|
||||
truncate_data_blocks_range(&dn, 1);
|
||||
|
||||
get_node_info(sbi, dn.nid, &ni);
|
||||
f2fs_replace_block(sbi, &dn, dn.data_blkaddr, new_addr,
|
||||
ni.version, true);
|
||||
f2fs_put_dnode(&dn);
|
||||
} else {
|
||||
struct page *psrc, *pdst;
|
||||
|
||||
psrc = get_lock_data_page(inode, src);
|
||||
if (IS_ERR(psrc))
|
||||
return PTR_ERR(psrc);
|
||||
pdst = get_new_data_page(inode, NULL, dst, false);
|
||||
if (IS_ERR(pdst)) {
|
||||
f2fs_put_page(psrc, 1);
|
||||
return PTR_ERR(pdst);
|
||||
}
|
||||
f2fs_copy_page(psrc, pdst);
|
||||
set_page_dirty(pdst);
|
||||
f2fs_put_page(pdst, 1);
|
||||
f2fs_put_page(psrc, 1);
|
||||
|
||||
return truncate_hole(inode, src, src + 1);
|
||||
}
|
||||
return 0;
|
||||
|
||||
err_out:
|
||||
if (!get_dnode_of_data(&dn, src, LOOKUP_NODE)) {
|
||||
dn.data_blkaddr = new_addr;
|
||||
set_data_blkaddr(&dn);
|
||||
f2fs_update_extent_cache(&dn);
|
||||
f2fs_put_dnode(&dn);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int f2fs_do_collapse(struct inode *inode, pgoff_t start, pgoff_t end)
|
||||
{
|
||||
struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
|
||||
pgoff_t nrpages = (i_size_read(inode) + PAGE_SIZE - 1) / PAGE_SIZE;
|
||||
int ret = 0;
|
||||
|
||||
for (; end < nrpages; start++, end++) {
|
||||
block_t new_addr, old_addr;
|
||||
|
||||
f2fs_balance_fs(sbi);
|
||||
f2fs_lock_op(sbi);
|
||||
|
||||
set_new_dnode(&dn, inode, NULL, NULL, 0);
|
||||
ret = get_dnode_of_data(&dn, end, LOOKUP_NODE_RA);
|
||||
if (ret && ret != -ENOENT) {
|
||||
goto out;
|
||||
} else if (ret == -ENOENT) {
|
||||
new_addr = NULL_ADDR;
|
||||
} else {
|
||||
new_addr = dn.data_blkaddr;
|
||||
truncate_data_blocks_range(&dn, 1);
|
||||
f2fs_put_dnode(&dn);
|
||||
}
|
||||
|
||||
if (new_addr == NULL_ADDR) {
|
||||
set_new_dnode(&dn, inode, NULL, NULL, 0);
|
||||
ret = get_dnode_of_data(&dn, start, LOOKUP_NODE_RA);
|
||||
if (ret && ret != -ENOENT) {
|
||||
goto out;
|
||||
} else if (ret == -ENOENT) {
|
||||
f2fs_unlock_op(sbi);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dn.data_blkaddr == NULL_ADDR) {
|
||||
f2fs_put_dnode(&dn);
|
||||
f2fs_unlock_op(sbi);
|
||||
continue;
|
||||
} else {
|
||||
truncate_data_blocks_range(&dn, 1);
|
||||
}
|
||||
|
||||
f2fs_put_dnode(&dn);
|
||||
} else {
|
||||
struct page *ipage;
|
||||
|
||||
ipage = get_node_page(sbi, inode->i_ino);
|
||||
if (IS_ERR(ipage)) {
|
||||
ret = PTR_ERR(ipage);
|
||||
goto out;
|
||||
}
|
||||
|
||||
set_new_dnode(&dn, inode, ipage, NULL, 0);
|
||||
ret = f2fs_reserve_block(&dn, start);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
old_addr = dn.data_blkaddr;
|
||||
if (old_addr != NEW_ADDR && new_addr == NEW_ADDR) {
|
||||
dn.data_blkaddr = NULL_ADDR;
|
||||
f2fs_update_extent_cache(&dn);
|
||||
invalidate_blocks(sbi, old_addr);
|
||||
|
||||
dn.data_blkaddr = new_addr;
|
||||
set_data_blkaddr(&dn);
|
||||
} else if (new_addr != NEW_ADDR) {
|
||||
struct node_info ni;
|
||||
|
||||
get_node_info(sbi, dn.nid, &ni);
|
||||
f2fs_replace_block(sbi, &dn, old_addr, new_addr,
|
||||
ni.version, true);
|
||||
}
|
||||
|
||||
f2fs_put_dnode(&dn);
|
||||
}
|
||||
ret = __exchange_data_block(inode, end, start, true);
|
||||
f2fs_unlock_op(sbi);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
out:
|
||||
f2fs_unlock_op(sbi);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -944,7 +958,12 @@ static int f2fs_collapse_range(struct inode *inode, loff_t offset, loff_t len)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* write out all moved pages, if possible */
|
||||
filemap_write_and_wait_range(inode->i_mapping, offset, LLONG_MAX);
|
||||
truncate_pagecache(inode, offset);
|
||||
|
||||
new_size = i_size_read(inode) - len;
|
||||
truncate_pagecache(inode, new_size);
|
||||
|
||||
ret = truncate_blocks(inode, new_size, true);
|
||||
if (!ret)
|
||||
@@ -1067,7 +1086,7 @@ static int f2fs_insert_range(struct inode *inode, loff_t offset, loff_t len)
|
||||
struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
|
||||
pgoff_t pg_start, pg_end, delta, nrpages, idx;
|
||||
loff_t new_size;
|
||||
int ret;
|
||||
int ret = 0;
|
||||
|
||||
new_size = i_size_read(inode) + len;
|
||||
if (new_size > inode->i_sb->s_maxbytes)
|
||||
@@ -1105,57 +1124,19 @@ static int f2fs_insert_range(struct inode *inode, loff_t offset, loff_t len)
|
||||
nrpages = (i_size_read(inode) + PAGE_SIZE - 1) / PAGE_SIZE;
|
||||
|
||||
for (idx = nrpages - 1; idx >= pg_start && idx != -1; idx--) {
|
||||
struct dnode_of_data dn;
|
||||
struct page *ipage;
|
||||
block_t new_addr, old_addr;
|
||||
|
||||
f2fs_lock_op(sbi);
|
||||
|
||||
set_new_dnode(&dn, inode, NULL, NULL, 0);
|
||||
ret = get_dnode_of_data(&dn, idx, LOOKUP_NODE_RA);
|
||||
if (ret && ret != -ENOENT) {
|
||||
goto out;
|
||||
} else if (ret == -ENOENT) {
|
||||
goto next;
|
||||
} else if (dn.data_blkaddr == NULL_ADDR) {
|
||||
f2fs_put_dnode(&dn);
|
||||
goto next;
|
||||
} else {
|
||||
new_addr = dn.data_blkaddr;
|
||||
truncate_data_blocks_range(&dn, 1);
|
||||
f2fs_put_dnode(&dn);
|
||||
}
|
||||
|
||||
ipage = get_node_page(sbi, inode->i_ino);
|
||||
if (IS_ERR(ipage)) {
|
||||
ret = PTR_ERR(ipage);
|
||||
goto out;
|
||||
}
|
||||
|
||||
set_new_dnode(&dn, inode, ipage, NULL, 0);
|
||||
ret = f2fs_reserve_block(&dn, idx + delta);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
old_addr = dn.data_blkaddr;
|
||||
f2fs_bug_on(sbi, old_addr != NEW_ADDR);
|
||||
|
||||
if (new_addr != NEW_ADDR) {
|
||||
struct node_info ni;
|
||||
|
||||
get_node_info(sbi, dn.nid, &ni);
|
||||
f2fs_replace_block(sbi, &dn, old_addr, new_addr,
|
||||
ni.version, true);
|
||||
}
|
||||
f2fs_put_dnode(&dn);
|
||||
next:
|
||||
ret = __exchange_data_block(inode, idx, idx + delta, false);
|
||||
f2fs_unlock_op(sbi);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
|
||||
i_size_write(inode, new_size);
|
||||
return 0;
|
||||
out:
|
||||
f2fs_unlock_op(sbi);
|
||||
/* write out all moved pages, if possible */
|
||||
filemap_write_and_wait_range(inode->i_mapping, offset, LLONG_MAX);
|
||||
truncate_pagecache(inode, offset);
|
||||
|
||||
if (!ret)
|
||||
i_size_write(inode, new_size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user