f2fs: fix race on allocating and deallocating a dentry block

There are two threads:
 f2fs_delete_entry()              get_new_data_page()
                                  f2fs_reserve_block()
				  dn.blkaddr = XXX
 lock_page(dentry_block)
 truncate_hole()
 dn.blkaddr = NULL
 unlock_page(dentry_block)
                                  lock_page(dentry_block)
                                  fill the block from XXX address
                                  add new dentries
                                  unlock_page(dentry_block)

Later, f2fs_write_data_page() will truncate the dentry_block, since
its block address is NULL.

The reason for this was due to the wrong lock order.
In this case, we should do f2fs_reserve_block() after locking its dentry block.

Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
This commit is contained in:
Jaegeuk Kim 2015-04-29 11:18:42 -07:00
parent eaa693f4dc
commit 01f28610a1
1 changed files with 12 additions and 15 deletions

View File

@ -1076,20 +1076,22 @@ struct page *get_new_data_page(struct inode *inode,
struct page *page; struct page *page;
struct dnode_of_data dn; struct dnode_of_data dn;
int err; int err;
repeat:
page = grab_cache_page(mapping, index);
if (!page)
return ERR_PTR(-ENOMEM);
set_new_dnode(&dn, inode, ipage, NULL, 0); set_new_dnode(&dn, inode, ipage, NULL, 0);
err = f2fs_reserve_block(&dn, index); err = f2fs_reserve_block(&dn, index);
if (err) if (err) {
f2fs_put_page(page, 1);
return ERR_PTR(err); return ERR_PTR(err);
repeat:
page = grab_cache_page(mapping, index);
if (!page) {
err = -ENOMEM;
goto put_err;
} }
if (!ipage)
f2fs_put_dnode(&dn);
if (PageUptodate(page)) if (PageUptodate(page))
return page; goto got_it;
if (dn.data_blkaddr == NEW_ADDR) { if (dn.data_blkaddr == NEW_ADDR) {
zero_user_segment(page, 0, PAGE_CACHE_SIZE); zero_user_segment(page, 0, PAGE_CACHE_SIZE);
@ -1104,20 +1106,19 @@ struct page *get_new_data_page(struct inode *inode,
}; };
err = f2fs_submit_page_bio(&fio); err = f2fs_submit_page_bio(&fio);
if (err) if (err)
goto put_err; return ERR_PTR(err);
lock_page(page); lock_page(page);
if (unlikely(!PageUptodate(page))) { if (unlikely(!PageUptodate(page))) {
f2fs_put_page(page, 1); f2fs_put_page(page, 1);
err = -EIO; return ERR_PTR(-EIO);
goto put_err;
} }
if (unlikely(page->mapping != mapping)) { if (unlikely(page->mapping != mapping)) {
f2fs_put_page(page, 1); f2fs_put_page(page, 1);
goto repeat; goto repeat;
} }
} }
got_it:
if (new_i_size && if (new_i_size &&
i_size_read(inode) < ((index + 1) << PAGE_CACHE_SHIFT)) { i_size_read(inode) < ((index + 1) << PAGE_CACHE_SHIFT)) {
i_size_write(inode, ((index + 1) << PAGE_CACHE_SHIFT)); i_size_write(inode, ((index + 1) << PAGE_CACHE_SHIFT));
@ -1125,10 +1126,6 @@ struct page *get_new_data_page(struct inode *inode,
set_inode_flag(F2FS_I(inode), FI_UPDATE_DIR); set_inode_flag(F2FS_I(inode), FI_UPDATE_DIR);
} }
return page; return page;
put_err:
f2fs_put_dnode(&dn);
return ERR_PTR(err);
} }
static int __allocate_data_block(struct dnode_of_data *dn) static int __allocate_data_block(struct dnode_of_data *dn)