diff options
Diffstat (limited to 'fs/btrfs/send.c')
| -rw-r--r-- | fs/btrfs/send.c | 52 | 
1 files changed, 49 insertions, 3 deletions
diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c index dd38dfe174df..f7fe4770f0e5 100644 --- a/fs/btrfs/send.c +++ b/fs/btrfs/send.c @@ -4999,6 +4999,12 @@ static int send_hole(struct send_ctx *sctx, u64 end)  	if (offset >= sctx->cur_inode_size)  		return 0; +	/* +	 * Don't go beyond the inode's i_size due to prealloc extents that start +	 * after the i_size. +	 */ +	end = min_t(u64, end, sctx->cur_inode_size); +  	if (sctx->flags & BTRFS_SEND_FLAG_NO_FILE_DATA)  		return send_update_extent(sctx, offset, end - offset); @@ -5218,10 +5224,50 @@ static int clone_range(struct send_ctx *sctx,  		clone_len = min_t(u64, ext_len, len);  		if (btrfs_file_extent_disk_bytenr(leaf, ei) == disk_byte && -		    clone_data_offset == data_offset) -			ret = send_clone(sctx, offset, clone_len, clone_root); -		else +		    clone_data_offset == data_offset) { +			const u64 src_end = clone_root->offset + clone_len; +			const u64 sectorsize = SZ_64K; + +			/* +			 * We can't clone the last block, when its size is not +			 * sector size aligned, into the middle of a file. If we +			 * do so, the receiver will get a failure (-EINVAL) when +			 * trying to clone or will silently corrupt the data in +			 * the destination file if it's on a kernel without the +			 * fix introduced by commit ac765f83f1397646 +			 * ("Btrfs: fix data corruption due to cloning of eof +			 * block). +			 * +			 * So issue a clone of the aligned down range plus a +			 * regular write for the eof block, if we hit that case. +			 * +			 * Also, we use the maximum possible sector size, 64K, +			 * because we don't know what's the sector size of the +			 * filesystem that receives the stream, so we have to +			 * assume the largest possible sector size. +			 */ +			if (src_end == clone_src_i_size && +			    !IS_ALIGNED(src_end, sectorsize) && +			    offset + clone_len < sctx->cur_inode_size) { +				u64 slen; + +				slen = ALIGN_DOWN(src_end - clone_root->offset, +						  sectorsize); +				if (slen > 0) { +					ret = send_clone(sctx, offset, slen, +							 clone_root); +					if (ret < 0) +						goto out; +				} +				ret = send_extent_data(sctx, offset + slen, +						       clone_len - slen); +			} else { +				ret = send_clone(sctx, offset, clone_len, +						 clone_root); +			} +		} else {  			ret = send_extent_data(sctx, offset, clone_len); +		}  		if (ret < 0)  			goto out;  |