diff options
Diffstat (limited to 'fs/btrfs/tree-log.c')
| -rw-r--r-- | fs/btrfs/tree-log.c | 114 | 
1 files changed, 110 insertions, 4 deletions
diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c index 4fd19b4d6675..434457794c27 100644 --- a/fs/btrfs/tree-log.c +++ b/fs/btrfs/tree-log.c @@ -967,7 +967,9 @@ static noinline int backref_in_log(struct btrfs_root *log,  	ptr = btrfs_item_ptr_offset(path->nodes[0], path->slots[0]);  	if (key->type == BTRFS_INODE_EXTREF_KEY) { -		if (btrfs_find_name_in_ext_backref(path, ref_objectid, +		if (btrfs_find_name_in_ext_backref(path->nodes[0], +						   path->slots[0], +						   ref_objectid,  						   name, namelen, NULL))  			match = 1; @@ -1191,7 +1193,8 @@ static int extref_get_fields(struct extent_buffer *eb, unsigned long ref_ptr,  	read_extent_buffer(eb, *name, (unsigned long)&extref->name,  			   *namelen); -	*index = btrfs_inode_extref_index(eb, extref); +	if (index) +		*index = btrfs_inode_extref_index(eb, extref);  	if (parent_objectid)  		*parent_objectid = btrfs_inode_extref_parent(eb, extref); @@ -1212,12 +1215,102 @@ static int ref_get_fields(struct extent_buffer *eb, unsigned long ref_ptr,  	read_extent_buffer(eb, *name, (unsigned long)(ref + 1), *namelen); -	*index = btrfs_inode_ref_index(eb, ref); +	if (index) +		*index = btrfs_inode_ref_index(eb, ref);  	return 0;  }  /* + * Take an inode reference item from the log tree and iterate all names from the + * inode reference item in the subvolume tree with the same key (if it exists). + * For any name that is not in the inode reference item from the log tree, do a + * proper unlink of that name (that is, remove its entry from the inode + * reference item and both dir index keys). + */ +static int unlink_old_inode_refs(struct btrfs_trans_handle *trans, +				 struct btrfs_root *root, +				 struct btrfs_path *path, +				 struct btrfs_inode *inode, +				 struct extent_buffer *log_eb, +				 int log_slot, +				 struct btrfs_key *key) +{ +	int ret; +	unsigned long ref_ptr; +	unsigned long ref_end; +	struct extent_buffer *eb; + +again: +	btrfs_release_path(path); +	ret = btrfs_search_slot(NULL, root, key, path, 0, 0); +	if (ret > 0) { +		ret = 0; +		goto out; +	} +	if (ret < 0) +		goto out; + +	eb = path->nodes[0]; +	ref_ptr = btrfs_item_ptr_offset(eb, path->slots[0]); +	ref_end = ref_ptr + btrfs_item_size_nr(eb, path->slots[0]); +	while (ref_ptr < ref_end) { +		char *name = NULL; +		int namelen; +		u64 parent_id; + +		if (key->type == BTRFS_INODE_EXTREF_KEY) { +			ret = extref_get_fields(eb, ref_ptr, &namelen, &name, +						NULL, &parent_id); +		} else { +			parent_id = key->offset; +			ret = ref_get_fields(eb, ref_ptr, &namelen, &name, +					     NULL); +		} +		if (ret) +			goto out; + +		if (key->type == BTRFS_INODE_EXTREF_KEY) +			ret = btrfs_find_name_in_ext_backref(log_eb, log_slot, +							     parent_id, name, +							     namelen, NULL); +		else +			ret = btrfs_find_name_in_backref(log_eb, log_slot, name, +							 namelen, NULL); + +		if (!ret) { +			struct inode *dir; + +			btrfs_release_path(path); +			dir = read_one_inode(root, parent_id); +			if (!dir) { +				ret = -ENOENT; +				kfree(name); +				goto out; +			} +			ret = btrfs_unlink_inode(trans, root, BTRFS_I(dir), +						 inode, name, namelen); +			kfree(name); +			iput(dir); +			if (ret) +				goto out; +			goto again; +		} + +		kfree(name); +		ref_ptr += namelen; +		if (key->type == BTRFS_INODE_EXTREF_KEY) +			ref_ptr += sizeof(struct btrfs_inode_extref); +		else +			ref_ptr += sizeof(struct btrfs_inode_ref); +	} +	ret = 0; + out: +	btrfs_release_path(path); +	return ret; +} + +/*   * replay one inode back reference item found in the log tree.   * eb, slot and key refer to the buffer and key found in the log tree.   * root is the destination we are replaying into, and path is for temp @@ -1345,6 +1438,19 @@ static noinline int add_inode_ref(struct btrfs_trans_handle *trans,  		}  	} +	/* +	 * Before we overwrite the inode reference item in the subvolume tree +	 * with the item from the log tree, we must unlink all names from the +	 * parent directory that are in the subvolume's tree inode reference +	 * item, otherwise we end up with an inconsistent subvolume tree where +	 * dir index entries exist for a name but there is no inode reference +	 * item with the same name. +	 */ +	ret = unlink_old_inode_refs(trans, root, path, BTRFS_I(inode), eb, slot, +				    key); +	if (ret) +		goto out; +  	/* finally write the back reference in the inode */  	ret = overwrite_item(trans, root, path, eb, slot, key);  out: @@ -5853,7 +5959,7 @@ int btrfs_log_new_name(struct btrfs_trans_handle *trans,  	 * this will force the logging code to walk the dentry chain  	 * up for the file  	 */ -	if (S_ISREG(inode->vfs_inode.i_mode)) +	if (!S_ISDIR(inode->vfs_inode.i_mode))  		inode->last_unlink_trans = trans->transid;  	/*  | 
