block copy: report the amount of copied data This patch changes blkdev_issue_copy so that it returns the number of copied sectors in the variable "copied". The kernel makes best effort to copy as much data as possible, but because of device mapper mapping, it may be possible that copying fails at some stage. If we just returned the error number, the caller wouldn't know if all or part of the operation failed and the caller would be required to redo the whole copy operation. We return the number of copied sectors so that the caller can skip these sectors when doing the copy manually. On success (zero return code), the number of copied sectors is equal to the number of requested sectors. On error (negative return code), the number of copied sectors is smaller than the number of requested sectors. The number of copied bytes is returned as a fourth uint64_t argument in the BLKCOPY ioctl. Signed-off-by: Mikulas Patocka --- block/blk-lib.c | 30 +++++++++++++++++++++++++----- block/ioctl.c | 12 +++++------- include/linux/blk_types.h | 2 ++ include/linux/blkdev.h | 2 +- 4 files changed, 33 insertions(+), 13 deletions(-) Index: linux-4.11-rc2/block/blk-lib.c =================================================================== --- linux-4.11-rc2.orig/block/blk-lib.c +++ linux-4.11-rc2/block/blk-lib.c @@ -411,8 +411,17 @@ static void bio_copy_end_io(struct bio * bio_put(bio); if (atomic_dec_and_test(&bc->in_flight)) { struct bio_batch *bb = bc->private; - if (unlikely(bc->error < 0) && !ACCESS_ONCE(bb->error)) - ACCESS_ONCE(bb->error) = bc->error; + if (unlikely(bc->error < 0)) { + u64 first_error; + if (!ACCESS_ONCE(bb->error)) + ACCESS_ONCE(bb->error) = bc->error; + do { + first_error = atomic64_read(bc->first_error); + if (bc->offset >= first_error) + break; + } while (unlikely(atomic64_cmpxchg(bc->first_error, + first_error, bc->offset) != first_error)); + } kfree(bc); if (atomic_dec_and_test(&bb->done)) complete(bb->wait); @@ -433,7 +442,7 @@ static void bio_copy_end_io(struct bio * */ int blkdev_issue_copy(struct block_device *src_bdev, sector_t src_sector, struct block_device *dst_bdev, sector_t dst_sector, - sector_t nr_sects, gfp_t gfp_mask) + sector_t nr_sects, sector_t *copied, gfp_t gfp_mask) { DECLARE_COMPLETION_ONSTACK(wait); struct request_queue *sq = bdev_get_queue(src_bdev); @@ -441,6 +450,11 @@ int blkdev_issue_copy(struct block_devic sector_t max_copy_sectors; struct bio_batch bb; int ret = 0; + atomic64_t first_error = ATOMIC64_INIT(nr_sects); + sector_t offset = 0; + + if (copied) + *copied = 0; if (!sq || !dq) return -ENXIO; @@ -464,10 +478,10 @@ int blkdev_issue_copy(struct block_devic bb.error = 0; bb.wait = &wait; - while (nr_sects) { + while (nr_sects && !ACCESS_ONCE(bb.error)) { struct bio *read_bio, *write_bio; struct bio_copy *bc; - unsigned int chunk = min(nr_sects, max_copy_sectors); + unsigned chunk = (unsigned)min(nr_sects, (sector_t)max_copy_sectors); bc = kmalloc(sizeof(struct bio_copy), gfp_mask); if (!bc) { @@ -495,6 +509,8 @@ int blkdev_issue_copy(struct block_devic bc->pair[0] = NULL; bc->pair[1] = NULL; bc->private = &bb; + bc->first_error = &first_error; + bc->offset = offset; spin_lock_init(&bc->spinlock); bio_set_op_attrs(read_bio, REQ_OP_COPY_READ, 0); @@ -518,12 +534,16 @@ int blkdev_issue_copy(struct block_devic src_sector += chunk; dst_sector += chunk; nr_sects -= chunk; + offset += chunk; } /* Wait for bios in-flight */ if (!atomic_dec_and_test(&bb.done)) wait_for_completion_io(&wait); + if (copied) + *copied = min((sector_t)atomic64_read(&first_error), offset); + if (likely(!ret)) ret = bb.error; Index: linux-4.11-rc2/include/linux/blk_types.h =================================================================== --- linux-4.11-rc2.orig/include/linux/blk_types.h +++ linux-4.11-rc2/include/linux/blk_types.h @@ -101,6 +101,8 @@ struct bio_copy { atomic_t in_flight; struct bio *pair[2]; void *private; + atomic64_t *first_error; + sector_t offset; spinlock_t spinlock; }; Index: linux-4.11-rc2/include/linux/blkdev.h =================================================================== --- linux-4.11-rc2.orig/include/linux/blkdev.h +++ linux-4.11-rc2/include/linux/blkdev.h @@ -1352,7 +1352,7 @@ extern int blkdev_issue_zeroout(struct b sector_t nr_sects, gfp_t gfp_mask, bool discard); extern int blkdev_issue_copy(struct block_device *src_bdev, sector_t src_sector, struct block_device *dst_bdev, sector_t dst_sector, - sector_t nr_sects, gfp_t gfp_gfp_mask); + sector_t nr_sects, sector_t *copied, gfp_t gfp_gfp_mask); static inline int sb_issue_discard(struct super_block *sb, sector_t block, sector_t nr_blocks, gfp_t gfp_mask, unsigned long flags) { Index: linux-4.11-rc2/block/ioctl.c =================================================================== --- linux-4.11-rc2.orig/block/ioctl.c +++ linux-4.11-rc2/block/ioctl.c @@ -262,7 +262,7 @@ static int blk_ioctl_copy(struct block_d unsigned long arg) { uint64_t range[4]; - sector_t src_offset, dst_offset, len; + sector_t src_offset, dst_offset, len, copied_sec; int ret; range[3] = 0; @@ -306,13 +306,11 @@ static int blk_ioctl_copy(struct block_d return -EINVAL; ret = blkdev_issue_copy(bdev, src_offset, bdev, dst_offset, len, - GFP_KERNEL); + &copied_sec, GFP_KERNEL); - if (!ret) { - range[3] = range[2] << 9; - if (unlikely(copy_to_user((void __user *)(arg + 24), &range[3], 8))) - return -EFAULT; - } + range[3] = (uint64_t)copied_sec << 9; + if (unlikely(copy_to_user((void __user *)(arg + 24), &range[3], 8))) + return -EFAULT; return ret; }