diff options
Diffstat (limited to 'fs/btrfs/send.c')
| -rw-r--r-- | fs/btrfs/send.c | 299 | 
1 files changed, 157 insertions, 142 deletions
| diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c index eb6537a08c1b..6528aa662181 100644 --- a/fs/btrfs/send.c +++ b/fs/btrfs/send.c @@ -360,10 +360,13 @@ static int fs_path_ensure_buf(struct fs_path *p, int len)  	/*  	 * First time the inline_buf does not suffice  	 */ -	if (p->buf == p->inline_buf) +	if (p->buf == p->inline_buf) {  		tmp_buf = kmalloc(len, GFP_NOFS); -	else +		if (tmp_buf) +			memcpy(tmp_buf, p->buf, old_buf_len); +	} else {  		tmp_buf = krealloc(p->buf, len, GFP_NOFS); +	}  	if (!tmp_buf)  		return -ENOMEM;  	p->buf = tmp_buf; @@ -972,7 +975,7 @@ static int iterate_dir_item(struct btrfs_root *root, struct btrfs_path *path,  	struct btrfs_dir_item *di;  	struct btrfs_key di_key;  	char *buf = NULL; -	const int buf_len = PATH_MAX; +	int buf_len;  	u32 name_len;  	u32 data_len;  	u32 cur; @@ -982,6 +985,11 @@ static int iterate_dir_item(struct btrfs_root *root, struct btrfs_path *path,  	int num;  	u8 type; +	if (found_key->type == BTRFS_XATTR_ITEM_KEY) +		buf_len = BTRFS_MAX_XATTR_SIZE(root); +	else +		buf_len = PATH_MAX; +  	buf = kmalloc(buf_len, GFP_NOFS);  	if (!buf) {  		ret = -ENOMEM; @@ -1003,12 +1011,23 @@ static int iterate_dir_item(struct btrfs_root *root, struct btrfs_path *path,  		type = btrfs_dir_type(eb, di);  		btrfs_dir_item_key_to_cpu(eb, di, &di_key); -		/* -		 * Path too long -		 */ -		if (name_len + data_len > buf_len) { -			ret = -ENAMETOOLONG; -			goto out; +		if (type == BTRFS_FT_XATTR) { +			if (name_len > XATTR_NAME_MAX) { +				ret = -ENAMETOOLONG; +				goto out; +			} +			if (name_len + data_len > buf_len) { +				ret = -E2BIG; +				goto out; +			} +		} else { +			/* +			 * Path too long +			 */ +			if (name_len + data_len > buf_len) { +				ret = -ENAMETOOLONG; +				goto out; +			}  		}  		read_extent_buffer(eb, buf, (unsigned long)(di + 1), @@ -1346,7 +1365,7 @@ static int find_extent_clone(struct send_ctx *sctx,  		ret = -EIO;  		btrfs_err(sctx->send_root->fs_info, "did not find backref in "  				"send_root. inode=%llu, offset=%llu, " -				"disk_byte=%llu found extent=%llu\n", +				"disk_byte=%llu found extent=%llu",  				ino, data_offset, disk_byte, found_key.objectid);  		goto out;  	} @@ -1625,6 +1644,10 @@ static int lookup_dir_item_inode(struct btrfs_root *root,  		goto out;  	}  	btrfs_dir_item_key_to_cpu(path->nodes[0], di, &key); +	if (key.type == BTRFS_ROOT_ITEM_KEY) { +		ret = -ENOENT; +		goto out; +	}  	*found_inode = key.objectid;  	*found_type = btrfs_dir_type(path->nodes[0], di); @@ -1668,7 +1691,7 @@ static int get_first_ref(struct btrfs_root *root, u64 ino,  		goto out;  	} -	if (key.type == BTRFS_INODE_REF_KEY) { +	if (found_key.type == BTRFS_INODE_REF_KEY) {  		struct btrfs_inode_ref *iref;  		iref = btrfs_item_ptr(path->nodes[0], path->slots[0],  				      struct btrfs_inode_ref); @@ -1690,10 +1713,12 @@ static int get_first_ref(struct btrfs_root *root, u64 ino,  		goto out;  	btrfs_release_path(path); -	ret = get_inode_info(root, parent_dir, NULL, dir_gen, NULL, NULL, -			NULL, NULL); -	if (ret < 0) -		goto out; +	if (dir_gen) { +		ret = get_inode_info(root, parent_dir, NULL, dir_gen, NULL, +				     NULL, NULL, NULL); +		if (ret < 0) +			goto out; +	}  	*dir = parent_dir; @@ -1709,13 +1734,12 @@ static int is_first_ref(struct btrfs_root *root,  	int ret;  	struct fs_path *tmp_name;  	u64 tmp_dir; -	u64 tmp_dir_gen;  	tmp_name = fs_path_alloc();  	if (!tmp_name)  		return -ENOMEM; -	ret = get_first_ref(root, ino, &tmp_dir, &tmp_dir_gen, tmp_name); +	ret = get_first_ref(root, ino, &tmp_dir, NULL, tmp_name);  	if (ret < 0)  		goto out; @@ -2026,7 +2050,6 @@ static int __get_cur_name_and_parent(struct send_ctx *sctx,  {  	int ret;  	int nce_ret; -	struct btrfs_path *path = NULL;  	struct name_cache_entry *nce = NULL;  	/* @@ -2052,10 +2075,6 @@ static int __get_cur_name_and_parent(struct send_ctx *sctx,  		}  	} -	path = alloc_path_for_send(); -	if (!path) -		return -ENOMEM; -  	/*  	 * If the inode is not existent yet, add the orphan name and return 1.  	 * This should only happen for the parent dir that we determine in @@ -2131,7 +2150,6 @@ out_cache:  	name_cache_clean_unused(sctx);  out: -	btrfs_free_path(path);  	return ret;  } @@ -2942,7 +2960,9 @@ static void free_waiting_dir_move(struct send_ctx *sctx,  static int add_pending_dir_move(struct send_ctx *sctx,  				u64 ino,  				u64 ino_gen, -				u64 parent_ino) +				u64 parent_ino, +				struct list_head *new_refs, +				struct list_head *deleted_refs)  {  	struct rb_node **p = &sctx->pending_dir_moves.rb_node;  	struct rb_node *parent = NULL; @@ -2974,12 +2994,12 @@ static int add_pending_dir_move(struct send_ctx *sctx,  		}  	} -	list_for_each_entry(cur, &sctx->deleted_refs, list) { +	list_for_each_entry(cur, deleted_refs, list) {  		ret = dup_ref(cur, &pm->update_refs);  		if (ret < 0)  			goto out;  	} -	list_for_each_entry(cur, &sctx->new_refs, list) { +	list_for_each_entry(cur, new_refs, list) {  		ret = dup_ref(cur, &pm->update_refs);  		if (ret < 0)  			goto out; @@ -3022,6 +3042,48 @@ static struct pending_dir_move *get_pending_dir_moves(struct send_ctx *sctx,  	return NULL;  } +static int path_loop(struct send_ctx *sctx, struct fs_path *name, +		     u64 ino, u64 gen, u64 *ancestor_ino) +{ +	int ret = 0; +	u64 parent_inode = 0; +	u64 parent_gen = 0; +	u64 start_ino = ino; + +	*ancestor_ino = 0; +	while (ino != BTRFS_FIRST_FREE_OBJECTID) { +		fs_path_reset(name); + +		if (is_waiting_for_rm(sctx, ino)) +			break; +		if (is_waiting_for_move(sctx, ino)) { +			if (*ancestor_ino == 0) +				*ancestor_ino = ino; +			ret = get_first_ref(sctx->parent_root, ino, +					    &parent_inode, &parent_gen, name); +		} else { +			ret = __get_cur_name_and_parent(sctx, ino, gen, +							&parent_inode, +							&parent_gen, name); +			if (ret > 0) { +				ret = 0; +				break; +			} +		} +		if (ret < 0) +			break; +		if (parent_inode == start_ino) { +			ret = 1; +			if (*ancestor_ino == 0) +				*ancestor_ino = ino; +			break; +		} +		ino = parent_inode; +		gen = parent_gen; +	} +	return ret; +} +  static int apply_dir_move(struct send_ctx *sctx, struct pending_dir_move *pm)  {  	struct fs_path *from_path = NULL; @@ -3033,6 +3095,7 @@ static int apply_dir_move(struct send_ctx *sctx, struct pending_dir_move *pm)  	struct waiting_dir_move *dm = NULL;  	u64 rmdir_ino = 0;  	int ret; +	u64 ancestor = 0;  	name = fs_path_alloc();  	from_path = fs_path_alloc(); @@ -3051,34 +3114,33 @@ static int apply_dir_move(struct send_ctx *sctx, struct pending_dir_move *pm)  	if (ret < 0)  		goto out; -	if (parent_ino == sctx->cur_ino) { -		/* child only renamed, not moved */ -		ASSERT(parent_gen == sctx->cur_inode_gen); -		ret = get_cur_path(sctx, sctx->cur_ino, sctx->cur_inode_gen, -				   from_path); -		if (ret < 0) -			goto out; -		ret = fs_path_add_path(from_path, name); -		if (ret < 0) -			goto out; -	} else { -		/* child moved and maybe renamed too */ -		sctx->send_progress = pm->ino; -		ret = get_cur_path(sctx, pm->ino, pm->gen, from_path); +	ret = get_cur_path(sctx, parent_ino, parent_gen, +			   from_path); +	if (ret < 0) +		goto out; +	ret = fs_path_add_path(from_path, name); +	if (ret < 0) +		goto out; + +	sctx->send_progress = sctx->cur_ino + 1; +	ret = path_loop(sctx, name, pm->ino, pm->gen, &ancestor); +	if (ret) { +		LIST_HEAD(deleted_refs); +		ASSERT(ancestor > BTRFS_FIRST_FREE_OBJECTID); +		ret = add_pending_dir_move(sctx, pm->ino, pm->gen, ancestor, +					   &pm->update_refs, &deleted_refs);  		if (ret < 0)  			goto out; -	} - -	fs_path_free(name); -	name = NULL; - -	to_path = fs_path_alloc(); -	if (!to_path) { -		ret = -ENOMEM; +		if (rmdir_ino) { +			dm = get_waiting_dir_move(sctx, pm->ino); +			ASSERT(dm); +			dm->rmdir_ino = rmdir_ino; +		}  		goto out;  	} - -	sctx->send_progress = sctx->cur_ino + 1; +	fs_path_reset(name); +	to_path = name; +	name = NULL;  	ret = get_cur_path(sctx, pm->ino, pm->gen, to_path);  	if (ret < 0)  		goto out; @@ -3202,127 +3264,74 @@ out:  static int wait_for_parent_move(struct send_ctx *sctx,  				struct recorded_ref *parent_ref)  { -	int ret; +	int ret = 0;  	u64 ino = parent_ref->dir;  	u64 parent_ino_before, parent_ino_after; -	u64 old_gen;  	struct fs_path *path_before = NULL;  	struct fs_path *path_after = NULL;  	int len1, len2; -	int register_upper_dirs; -	u64 gen; - -	if (is_waiting_for_move(sctx, ino)) -		return 1; - -	if (parent_ref->dir <= sctx->cur_ino) -		return 0; - -	ret = get_inode_info(sctx->parent_root, ino, NULL, &old_gen, -			     NULL, NULL, NULL, NULL); -	if (ret == -ENOENT) -		return 0; -	else if (ret < 0) -		return ret; - -	if (parent_ref->dir_gen != old_gen) -		return 0; - -	path_before = fs_path_alloc(); -	if (!path_before) -		return -ENOMEM; - -	ret = get_first_ref(sctx->parent_root, ino, &parent_ino_before, -			    NULL, path_before); -	if (ret == -ENOENT) { -		ret = 0; -		goto out; -	} else if (ret < 0) { -		goto out; -	}  	path_after = fs_path_alloc(); -	if (!path_after) { +	path_before = fs_path_alloc(); +	if (!path_after || !path_before) {  		ret = -ENOMEM;  		goto out;  	} -	ret = get_first_ref(sctx->send_root, ino, &parent_ino_after, -			    &gen, path_after); -	if (ret == -ENOENT) { -		ret = 0; -		goto out; -	} else if (ret < 0) { -		goto out; -	} - -	len1 = fs_path_len(path_before); -	len2 = fs_path_len(path_after); -	if (parent_ino_before != parent_ino_after || len1 != len2 || -	     memcmp(path_before->start, path_after->start, len1)) { -		ret = 1; -		goto out; -	} -	ret = 0; -  	/* -	 * Ok, our new most direct ancestor has a higher inode number but -	 * wasn't moved/renamed. So maybe some of the new ancestors higher in -	 * the hierarchy have an higher inode number too *and* were renamed -	 * or moved - in this case we need to wait for the ancestor's rename -	 * or move operation before we can do the move/rename for the current -	 * inode. +	 * Our current directory inode may not yet be renamed/moved because some +	 * ancestor (immediate or not) has to be renamed/moved first. So find if +	 * such ancestor exists and make sure our own rename/move happens after +	 * that ancestor is processed.  	 */ -	register_upper_dirs = 0; -	ino = parent_ino_after; -again: -	while ((ret == 0 || register_upper_dirs) && ino > sctx->cur_ino) { -		u64 parent_gen; +	while (ino > BTRFS_FIRST_FREE_OBJECTID) { +		if (is_waiting_for_move(sctx, ino)) { +			ret = 1; +			break; +		}  		fs_path_reset(path_before);  		fs_path_reset(path_after);  		ret = get_first_ref(sctx->send_root, ino, &parent_ino_after, -				    &parent_gen, path_after); +				    NULL, path_after);  		if (ret < 0)  			goto out;  		ret = get_first_ref(sctx->parent_root, ino, &parent_ino_before,  				    NULL, path_before); -		if (ret == -ENOENT) { -			ret = 0; -			break; -		} else if (ret < 0) { +		if (ret < 0 && ret != -ENOENT) {  			goto out; +		} else if (ret == -ENOENT) { +			ret = 1; +			break;  		}  		len1 = fs_path_len(path_before);  		len2 = fs_path_len(path_after); -		if (parent_ino_before != parent_ino_after || len1 != len2 || -		    memcmp(path_before->start, path_after->start, len1)) { +		if (ino > sctx->cur_ino && +		    (parent_ino_before != parent_ino_after || len1 != len2 || +		     memcmp(path_before->start, path_after->start, len1))) {  			ret = 1; -			if (register_upper_dirs) { -				break; -			} else { -				register_upper_dirs = 1; -				ino = parent_ref->dir; -				gen = parent_ref->dir_gen; -				goto again; -			} -		} else if (register_upper_dirs) { -			ret = add_pending_dir_move(sctx, ino, gen, -						   parent_ino_after); -			if (ret < 0 && ret != -EEXIST) -				goto out; +			break;  		} -  		ino = parent_ino_after; -		gen = parent_gen;  	}  out:  	fs_path_free(path_before);  	fs_path_free(path_after); +	if (ret == 1) { +		ret = add_pending_dir_move(sctx, +					   sctx->cur_ino, +					   sctx->cur_inode_gen, +					   ino, +					   &sctx->new_refs, +					   &sctx->deleted_refs); +		if (!ret) +			ret = 1; +	} +  	return ret;  } @@ -3483,10 +3492,6 @@ verbose_printk("btrfs: process_recorded_refs %llu\n", sctx->cur_ino);  				if (ret < 0)  					goto out;  				if (ret) { -					ret = add_pending_dir_move(sctx, -							   sctx->cur_ino, -							   sctx->cur_inode_gen, -							   cur->dir);  					*pending_move = 1;  				} else {  					ret = send_rename(sctx, valid_path, @@ -5487,7 +5492,7 @@ static void btrfs_root_dec_send_in_progress(struct btrfs_root* root)  	 */  	if (root->send_in_progress < 0)  		btrfs_err(root->fs_info, -			"send_in_progres unbalanced %d root %llu\n", +			"send_in_progres unbalanced %d root %llu",  			root->send_in_progress, root->root_key.objectid);  	spin_unlock(&root->root_item_lock);  } @@ -5515,7 +5520,7 @@ long btrfs_ioctl_send(struct file *mnt_file, void __user *arg_)  	/*  	 * The subvolume must remain read-only during send, protect against -	 * making it RW. +	 * making it RW. This also protects against deletion.  	 */  	spin_lock(&send_root->root_item_lock);  	send_root->send_in_progress++; @@ -5575,6 +5580,15 @@ long btrfs_ioctl_send(struct file *mnt_file, void __user *arg_)  	}  	sctx->send_root = send_root; +	/* +	 * Unlikely but possible, if the subvolume is marked for deletion but +	 * is slow to remove the directory entry, send can still be started +	 */ +	if (btrfs_root_dead(sctx->send_root)) { +		ret = -EPERM; +		goto out; +	} +  	sctx->clone_roots_cnt = arg->clone_sources_count;  	sctx->send_max_size = BTRFS_SEND_BUF_SIZE; @@ -5664,7 +5678,8 @@ long btrfs_ioctl_send(struct file *mnt_file, void __user *arg_)  		spin_lock(&sctx->parent_root->root_item_lock);  		sctx->parent_root->send_in_progress++; -		if (!btrfs_root_readonly(sctx->parent_root)) { +		if (!btrfs_root_readonly(sctx->parent_root) || +				btrfs_root_dead(sctx->parent_root)) {  			spin_unlock(&sctx->parent_root->root_item_lock);  			srcu_read_unlock(&fs_info->subvol_srcu, index);  			ret = -EPERM; | 
