diff options
Diffstat (limited to 'fs/btrfs/tree-log.c')
| -rw-r--r-- | fs/btrfs/tree-log.c | 85 | 
1 files changed, 77 insertions, 8 deletions
diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c index d31a0c4f56be..fff3f3efa436 100644 --- a/fs/btrfs/tree-log.c +++ b/fs/btrfs/tree-log.c @@ -4469,7 +4469,8 @@ static int btrfs_log_trailing_hole(struct btrfs_trans_handle *trans,  static int btrfs_check_ref_name_override(struct extent_buffer *eb,  					 const int slot,  					 const struct btrfs_key *key, -					 struct inode *inode) +					 struct inode *inode, +					 u64 *other_ino)  {  	int ret;  	struct btrfs_path *search_path; @@ -4528,7 +4529,16 @@ static int btrfs_check_ref_name_override(struct extent_buffer *eb,  					   search_path, parent,  					   name, this_name_len, 0);  		if (di && !IS_ERR(di)) { -			ret = 1; +			struct btrfs_key di_key; + +			btrfs_dir_item_key_to_cpu(search_path->nodes[0], +						  di, &di_key); +			if (di_key.type == BTRFS_INODE_ITEM_KEY) { +				ret = 1; +				*other_ino = di_key.objectid; +			} else { +				ret = -EAGAIN; +			}  			goto out;  		} else if (IS_ERR(di)) {  			ret = PTR_ERR(di); @@ -4722,16 +4732,71 @@ again:  		if ((min_key.type == BTRFS_INODE_REF_KEY ||  		     min_key.type == BTRFS_INODE_EXTREF_KEY) &&  		    BTRFS_I(inode)->generation == trans->transid) { +			u64 other_ino = 0; +  			ret = btrfs_check_ref_name_override(path->nodes[0],  							    path->slots[0], -							    &min_key, inode); +							    &min_key, inode, +							    &other_ino);  			if (ret < 0) {  				err = ret;  				goto out_unlock;  			} else if (ret > 0) { -				err = 1; -				btrfs_set_log_full_commit(root->fs_info, trans); -				goto out_unlock; +				struct btrfs_key inode_key; +				struct inode *other_inode; + +				if (ins_nr > 0) { +					ins_nr++; +				} else { +					ins_nr = 1; +					ins_start_slot = path->slots[0]; +				} +				ret = copy_items(trans, inode, dst_path, path, +						 &last_extent, ins_start_slot, +						 ins_nr, inode_only, +						 logged_isize); +				if (ret < 0) { +					err = ret; +					goto out_unlock; +				} +				ins_nr = 0; +				btrfs_release_path(path); +				inode_key.objectid = other_ino; +				inode_key.type = BTRFS_INODE_ITEM_KEY; +				inode_key.offset = 0; +				other_inode = btrfs_iget(root->fs_info->sb, +							 &inode_key, root, +							 NULL); +				/* +				 * If the other inode that had a conflicting dir +				 * entry was deleted in the current transaction, +				 * we don't need to do more work nor fallback to +				 * a transaction commit. +				 */ +				if (IS_ERR(other_inode) && +				    PTR_ERR(other_inode) == -ENOENT) { +					goto next_key; +				} else if (IS_ERR(other_inode)) { +					err = PTR_ERR(other_inode); +					goto out_unlock; +				} +				/* +				 * We are safe logging the other inode without +				 * acquiring its i_mutex as long as we log with +				 * the LOG_INODE_EXISTS mode. We're safe against +				 * concurrent renames of the other inode as well +				 * because during a rename we pin the log and +				 * update the log with the new name before we +				 * unpin it. +				 */ +				err = btrfs_log_inode(trans, root, other_inode, +						      LOG_INODE_EXISTS, +						      0, LLONG_MAX, ctx); +				iput(other_inode); +				if (err) +					goto out_unlock; +				else +					goto next_key;  			}  		} @@ -4799,7 +4864,7 @@ next_slot:  			ins_nr = 0;  		}  		btrfs_release_path(path); - +next_key:  		if (min_key.offset < (u64)-1) {  			min_key.offset++;  		} else if (min_key.type < max_key.type) { @@ -4993,8 +5058,12 @@ static noinline int check_parent_dirs_for_sync(struct btrfs_trans_handle *trans,  		if (!parent || d_really_is_negative(parent) || sb != parent->d_sb)  			break; -		if (IS_ROOT(parent)) +		if (IS_ROOT(parent)) { +			inode = d_inode(parent); +			if (btrfs_must_commit_transaction(trans, inode)) +				ret = 1;  			break; +		}  		parent = dget_parent(parent);  		dput(old_parent);  | 
