diff options
Diffstat (limited to 'fs/btrfs/send.c')
| -rw-r--r-- | fs/btrfs/send.c | 162 | 
1 files changed, 114 insertions, 48 deletions
diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c index a60d5bfb8a49..e937c10b8287 100644 --- a/fs/btrfs/send.c +++ b/fs/btrfs/send.c @@ -1069,6 +1069,12 @@ static int iterate_dir_item(struct btrfs_root *root, struct btrfs_path *path,  			}  		} +		ret = btrfs_is_name_len_valid(eb, path->slots[0], +			  (unsigned long)(di + 1), name_len + data_len); +		if (!ret) { +			ret = -EIO; +			goto out; +		}  		if (name_len + data_len > buf_len) {  			buf_len = name_len + data_len;  			if (is_vmalloc_addr(buf)) { @@ -1083,7 +1089,7 @@ static int iterate_dir_item(struct btrfs_root *root, struct btrfs_path *path,  				buf = tmp;  			}  			if (!buf) { -				buf = vmalloc(buf_len); +				buf = kvmalloc(buf_len, GFP_KERNEL);  				if (!buf) {  					ret = -ENOMEM;  					goto out; @@ -2769,15 +2775,20 @@ out:  struct recorded_ref {  	struct list_head list; -	char *dir_path;  	char *name;  	struct fs_path *full_path;  	u64 dir;  	u64 dir_gen; -	int dir_path_len;  	int name_len;  }; +static void set_ref_path(struct recorded_ref *ref, struct fs_path *path) +{ +	ref->full_path = path; +	ref->name = (char *)kbasename(ref->full_path->start); +	ref->name_len = ref->full_path->end - ref->name; +} +  /*   * We need to process new refs before deleted refs, but compare_tree gives us   * everything mixed. So we first record all refs and later process them. @@ -2794,17 +2805,7 @@ static int __record_ref(struct list_head *head, u64 dir,  	ref->dir = dir;  	ref->dir_gen = dir_gen; -	ref->full_path = path; - -	ref->name = (char *)kbasename(ref->full_path->start); -	ref->name_len = ref->full_path->end - ref->name; -	ref->dir_path = ref->full_path->start; -	if (ref->name == ref->full_path->start) -		ref->dir_path_len = 0; -	else -		ref->dir_path_len = ref->full_path->end - -				ref->full_path->start - 1 - ref->name_len; - +	set_ref_path(ref, path);  	list_add_tail(&ref->list, head);  	return 0;  } @@ -3546,9 +3547,17 @@ static int is_ancestor(struct btrfs_root *root,  		       struct fs_path *fs_path)  {  	u64 ino = ino2; +	bool free_path = false; +	int ret = 0; + +	if (!fs_path) { +		fs_path = fs_path_alloc(); +		if (!fs_path) +			return -ENOMEM; +		free_path = true; +	}  	while (ino > BTRFS_FIRST_FREE_OBJECTID) { -		int ret;  		u64 parent;  		u64 parent_gen; @@ -3557,13 +3566,18 @@ static int is_ancestor(struct btrfs_root *root,  		if (ret < 0) {  			if (ret == -ENOENT && ino == ino2)  				ret = 0; -			return ret; +			goto out; +		} +		if (parent == ino1) { +			ret = parent_gen == ino1_gen ? 1 : 0; +			goto out;  		} -		if (parent == ino1) -			return parent_gen == ino1_gen ? 1 : 0;  		ino = parent;  	} -	return 0; + out: +	if (free_path) +		fs_path_free(fs_path); +	return ret;  }  static int wait_for_parent_move(struct send_ctx *sctx, @@ -3686,6 +3700,7 @@ static int process_recorded_refs(struct send_ctx *sctx, int *pending_move)  	int is_orphan = 0;  	u64 last_dir_ino_rm = 0;  	bool can_rename = true; +	bool orphanized_ancestor = false;  	btrfs_debug(fs_info, "process_recorded_refs %llu", sctx->cur_ino); @@ -3837,9 +3852,16 @@ static int process_recorded_refs(struct send_ctx *sctx, int *pending_move)  				 * might contain the pre-orphanization name of  				 * ow_inode, which is no longer valid.  				 */ -				fs_path_reset(valid_path); -				ret = get_cur_path(sctx, sctx->cur_ino, -					   sctx->cur_inode_gen, valid_path); +				ret = is_ancestor(sctx->parent_root, +						  ow_inode, ow_gen, +						  sctx->cur_ino, NULL); +				if (ret > 0) { +					orphanized_ancestor = true; +					fs_path_reset(valid_path); +					ret = get_cur_path(sctx, sctx->cur_ino, +							   sctx->cur_inode_gen, +							   valid_path); +				}  				if (ret < 0)  					goto out;  			} else { @@ -3960,6 +3982,43 @@ static int process_recorded_refs(struct send_ctx *sctx, int *pending_move)  			if (ret < 0)  				goto out;  			if (!ret) { +				/* +				 * If we orphanized any ancestor before, we need +				 * to recompute the full path for deleted names, +				 * since any such path was computed before we +				 * processed any references and orphanized any +				 * ancestor inode. +				 */ +				if (orphanized_ancestor) { +					struct fs_path *new_path; + +					/* +					 * Our reference's name member points to +					 * its full_path member string, so we +					 * use here a new path. +					 */ +					new_path = fs_path_alloc(); +					if (!new_path) { +						ret = -ENOMEM; +						goto out; +					} +					ret = get_cur_path(sctx, cur->dir, +							   cur->dir_gen, +							   new_path); +					if (ret < 0) { +						fs_path_free(new_path); +						goto out; +					} +					ret = fs_path_add(new_path, +							  cur->name, +							  cur->name_len); +					if (ret < 0) { +						fs_path_free(new_path); +						goto out; +					} +					fs_path_free(cur->full_path); +					set_ref_path(cur, new_path); +				}  				ret = send_unlink(sctx, cur->full_path);  				if (ret < 0)  					goto out; @@ -5184,13 +5243,19 @@ static int is_extent_unchanged(struct send_ctx *sctx,  	while (key.offset < ekey->offset + left_len) {  		ei = btrfs_item_ptr(eb, slot, struct btrfs_file_extent_item);  		right_type = btrfs_file_extent_type(eb, ei); -		if (right_type != BTRFS_FILE_EXTENT_REG) { +		if (right_type != BTRFS_FILE_EXTENT_REG && +		    right_type != BTRFS_FILE_EXTENT_INLINE) {  			ret = 0;  			goto out;  		}  		right_disknr = btrfs_file_extent_disk_bytenr(eb, ei); -		right_len = btrfs_file_extent_num_bytes(eb, ei); +		if (right_type == BTRFS_FILE_EXTENT_INLINE) { +			right_len = btrfs_file_extent_inline_len(eb, slot, ei); +			right_len = PAGE_ALIGN(right_len); +		} else { +			right_len = btrfs_file_extent_num_bytes(eb, ei); +		}  		right_offset = btrfs_file_extent_offset(eb, ei);  		right_gen = btrfs_file_extent_generation(eb, ei); @@ -5204,6 +5269,19 @@ static int is_extent_unchanged(struct send_ctx *sctx,  			goto out;  		} +		/* +		 * We just wanted to see if when we have an inline extent, what +		 * follows it is a regular extent (wanted to check the above +		 * condition for inline extents too). This should normally not +		 * happen but it's possible for example when we have an inline +		 * compressed extent representing data with a size matching +		 * the page size (currently the same as sector size). +		 */ +		if (right_type == BTRFS_FILE_EXTENT_INLINE) { +			ret = 0; +			goto out; +		} +  		left_offset_fixed = left_offset;  		if (key.offset < ekey->offset) {  			/* Fix the right offset for 2a and 7. */ @@ -6360,22 +6438,16 @@ long btrfs_ioctl_send(struct file *mnt_file, void __user *arg_)  	sctx->clone_roots_cnt = arg->clone_sources_count;  	sctx->send_max_size = BTRFS_SEND_BUF_SIZE; -	sctx->send_buf = kmalloc(sctx->send_max_size, GFP_KERNEL | __GFP_NOWARN); +	sctx->send_buf = kvmalloc(sctx->send_max_size, GFP_KERNEL);  	if (!sctx->send_buf) { -		sctx->send_buf = vmalloc(sctx->send_max_size); -		if (!sctx->send_buf) { -			ret = -ENOMEM; -			goto out; -		} +		ret = -ENOMEM; +		goto out;  	} -	sctx->read_buf = kmalloc(BTRFS_SEND_READ_SIZE, GFP_KERNEL | __GFP_NOWARN); +	sctx->read_buf = kvmalloc(BTRFS_SEND_READ_SIZE, GFP_KERNEL);  	if (!sctx->read_buf) { -		sctx->read_buf = vmalloc(BTRFS_SEND_READ_SIZE); -		if (!sctx->read_buf) { -			ret = -ENOMEM; -			goto out; -		} +		ret = -ENOMEM; +		goto out;  	}  	sctx->pending_dir_moves = RB_ROOT; @@ -6384,25 +6456,19 @@ long btrfs_ioctl_send(struct file *mnt_file, void __user *arg_)  	alloc_size = sizeof(struct clone_root) * (arg->clone_sources_count + 1); -	sctx->clone_roots = kzalloc(alloc_size, GFP_KERNEL | __GFP_NOWARN); +	sctx->clone_roots = kzalloc(alloc_size, GFP_KERNEL);  	if (!sctx->clone_roots) { -		sctx->clone_roots = vzalloc(alloc_size); -		if (!sctx->clone_roots) { -			ret = -ENOMEM; -			goto out; -		} +		ret = -ENOMEM; +		goto out;  	}  	alloc_size = arg->clone_sources_count * sizeof(*arg->clone_sources);  	if (arg->clone_sources_count) { -		clone_sources_tmp = kmalloc(alloc_size, GFP_KERNEL | __GFP_NOWARN); +		clone_sources_tmp = kvmalloc(alloc_size, GFP_KERNEL);  		if (!clone_sources_tmp) { -			clone_sources_tmp = vmalloc(alloc_size); -			if (!clone_sources_tmp) { -				ret = -ENOMEM; -				goto out; -			} +			ret = -ENOMEM; +			goto out;  		}  		ret = copy_from_user(clone_sources_tmp, arg->clone_sources,  |