From 60594341975112eb05e84ecbf319f99db268548e Mon Sep 17 00:00:00 2001 From: Chao Yu <yuchao0@huawei.com> Date: Wed, 22 Mar 2017 14:45:05 +0800 Subject: [PATCH 4/4] f2fs: fix race condition in between free nid allocator/initializer commit 30a61ddf8117c26ac5b295e1233eaa9629a94ca3 upstream. In below concurrent case, allocated nid can be loaded into free nid cache and be allocated again. Thread A Thread B - f2fs_create - f2fs_new_inode - alloc_nid - __insert_nid_to_list(ALLOC_NID_LIST) - f2fs_balance_fs_bg - build_free_nids - __build_free_nids - scan_nat_page - add_free_nid - __lookup_nat_cache - f2fs_add_link - init_inode_metadata - new_inode_page - new_node_page - set_node_addr - alloc_nid_done - __remove_nid_from_list(ALLOC_NID_LIST) - __insert_nid_to_list(FREE_NID_LIST) This patch makes nat cache lookup and free nid list operation being atomical to avoid this race condition. Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org> Signed-off-by: Chao Yu <yuchao0@huawei.com> Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org> [ Srivatsa: Backported to 4.4.y ] Signed-off-by: Srivatsa S. Bhat <srivatsa@csail.mit.edu> --- fs/f2fs/node.c | 72 +++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/fs/f2fs/node.c b/fs/f2fs/node.c index 1b10cd3..53a023b 100644 --- a/fs/f2fs/node.c +++ b/fs/f2fs/node.c @@ -1428,8 +1428,10 @@ static void __del_from_free_nid_list(struct f2fs_nm_info *nm_i, static int add_free_nid(struct f2fs_sb_info *sbi, nid_t nid, bool build) { struct f2fs_nm_info *nm_i = NM_I(sbi); - struct free_nid *i; + struct free_nid *i, *e; struct nat_entry *ne; + int err = -EINVAL; + int ret = 0; if (!available_free_memory(sbi, FREE_NIDS)) return -1; @@ -1438,35 +1440,63 @@ static int add_free_nid(struct f2fs_sb_info *sbi, nid_t nid, bool build) if (unlikely(nid == 0)) return 0; - if (build) { - /* do not add allocated nids */ - ne = __lookup_nat_cache(nm_i, nid); - if (ne && (!get_nat_flag(ne, IS_CHECKPOINTED) || - nat_get_blkaddr(ne) != NULL_ADDR)) - return 0; - } - i = f2fs_kmem_cache_alloc(free_nid_slab, GFP_NOFS); i->nid = nid; i->state = NID_NEW; - if (radix_tree_preload(GFP_NOFS)) { - kmem_cache_free(free_nid_slab, i); - return 0; - } + if (radix_tree_preload(GFP_NOFS)) + goto err; spin_lock(&nm_i->free_nid_list_lock); - if (radix_tree_insert(&nm_i->free_nid_root, i->nid, i)) { - spin_unlock(&nm_i->free_nid_list_lock); - radix_tree_preload_end(); - kmem_cache_free(free_nid_slab, i); - return 0; + + if (build) { + + /* + * Thread A Thread B + * - f2fs_create + * - f2fs_new_inode + * - alloc_nid + * - __insert_nid_to_list(ALLOC_NID_LIST) + * - f2fs_balance_fs_bg + * - build_free_nids + * - __build_free_nids + * - scan_nat_page + * - add_free_nid + * - __lookup_nat_cache + * - f2fs_add_link + * - init_inode_metadata + * - new_inode_page + * - new_node_page + * - set_node_addr + * - alloc_nid_done + * - __remove_nid_from_list(ALLOC_NID_LIST) + * - __insert_nid_to_list(FREE_NID_LIST) + */ + ne = __lookup_nat_cache(nm_i, nid); + if (ne && (!get_nat_flag(ne, IS_CHECKPOINTED) || + nat_get_blkaddr(ne) != NULL_ADDR)) + goto err_out; + + e = __lookup_free_nid_list(nm_i, nid); + if (e) { + if (e->state == NID_NEW) + ret = 1; + goto err_out; + } + } + ret = 1; + err = radix_tree_insert(&nm_i->free_nid_root, i->nid, i); + if (!err) { + list_add_tail(&i->list, &nm_i->free_nid_list); + nm_i->fcnt++; } - list_add_tail(&i->list, &nm_i->free_nid_list); - nm_i->fcnt++; +err_out: spin_unlock(&nm_i->free_nid_list_lock); radix_tree_preload_end(); - return 1; +err: + if (err) + kmem_cache_free(free_nid_slab, i); + return ret; } static void remove_free_nid(struct f2fs_nm_info *nm_i, nid_t nid) -- 2.7.4