diff --git a/daemon/copy.c b/daemon/copy.c index b4f01543a..15873b79c 100644 --- a/daemon/copy.c +++ b/daemon/copy.c @@ -42,7 +42,7 @@ static int copy (const char *src, const char *src_display, const char *dest, const char *dest_display, int wrflags, int wrmode, - int64_t srcoffset, int64_t destoffset, int64_t size) + int64_t srcoffset, int64_t destoffset, int64_t size, int sparse) { int64_t saved_size = size; int src_fd, dest_fd; @@ -77,6 +77,9 @@ copy (const char *src, const char *src_display, else size = -1; + if (! (optargs_bitmask & GUESTFS_COPY_DEVICE_TO_DEVICE_SPARSE_BITMASK)) + sparse = 0; + /* Open source and destination. */ src_fd = open (src, O_RDONLY|O_CLOEXEC); if (src_fd == -1) { @@ -133,6 +136,18 @@ copy (const char *src, const char *src_display, return -1; } + if (sparse && is_zero (buf, r)) { + if (lseek (dest_fd, r, SEEK_CUR) == -1) { + if (size == -1) + pulse_mode_cancel (); + reply_with_perror ("%s: seek (because of sparse flag)", dest_display); + close (src_fd); + close (dest_fd); + return -1; + } + goto sparse_skip; + } + if (xwrite (dest_fd, buf, r) == -1) { if (size == -1) pulse_mode_cancel (); @@ -141,6 +156,7 @@ copy (const char *src, const char *src_display, close (dest_fd); return -1; } + sparse_skip: if (size != -1) { size -= r; @@ -167,15 +183,17 @@ copy (const char *src, const char *src_display, int do_copy_device_to_device (const char *src, const char *dest, - int64_t srcoffset, int64_t destoffset, int64_t size) + int64_t srcoffset, int64_t destoffset, int64_t size, + int sparse) { return copy (src, src, dest, dest, DEST_DEVICE_FLAGS, - srcoffset, destoffset, size); + srcoffset, destoffset, size, sparse); } int do_copy_device_to_file (const char *src, const char *dest, - int64_t srcoffset, int64_t destoffset, int64_t size) + int64_t srcoffset, int64_t destoffset, int64_t size, + int sparse) { CLEANUP_FREE char *dest_buf = sysroot_path (dest); @@ -185,12 +203,13 @@ do_copy_device_to_file (const char *src, const char *dest, } return copy (src, src, dest_buf, dest, DEST_FILE_FLAGS, - srcoffset, destoffset, size); + srcoffset, destoffset, size, sparse); } int do_copy_file_to_device (const char *src, const char *dest, - int64_t srcoffset, int64_t destoffset, int64_t size) + int64_t srcoffset, int64_t destoffset, int64_t size, + int sparse) { CLEANUP_FREE char *src_buf = sysroot_path (src); @@ -200,12 +219,13 @@ do_copy_file_to_device (const char *src, const char *dest, } return copy (src_buf, src, dest, dest, DEST_DEVICE_FLAGS, - srcoffset, destoffset, size); + srcoffset, destoffset, size, sparse); } int do_copy_file_to_file (const char *src, const char *dest, - int64_t srcoffset, int64_t destoffset, int64_t size) + int64_t srcoffset, int64_t destoffset, int64_t size, + int sparse) { CLEANUP_FREE char *src_buf = NULL, *dest_buf = NULL; @@ -222,5 +242,5 @@ do_copy_file_to_file (const char *src, const char *dest, } return copy (src_buf, src, dest_buf, dest, DEST_FILE_FLAGS, - srcoffset, destoffset, size); + srcoffset, destoffset, size, sparse); } diff --git a/generator/actions.ml b/generator/actions.ml index 3b03a6bde..b501f8487 100644 --- a/generator/actions.ml +++ b/generator/actions.ml @@ -8784,7 +8784,7 @@ See also C." }; { defaults with name = "copy_device_to_device"; - style = RErr, [Device "src"; Device "dest"], [OInt64 "srcoffset"; OInt64 "destoffset"; OInt64 "size"]; + style = RErr, [Device "src"; Device "dest"], [OInt64 "srcoffset"; OInt64 "destoffset"; OInt64 "size"; OBool "sparse"]; proc_nr = Some 294; progress = true; shortdesc = "copy from source device to destination device"; @@ -8806,11 +8806,17 @@ The source and destination may be the same object. However overlapping regions may not be copied correctly. If the destination is a file, it is created if required. If -the destination file is not large enough, it is extended." }; +the destination file is not large enough, it is extended. + +If the C flag is true then the call avoids writing +blocks that contain only zeroes, which can help in some situations +where the backing disk is thin-provisioned. Note that unless +the target is already zeroed, using this option will result +in incorrect copying." }; { defaults with name = "copy_device_to_file"; - style = RErr, [Device "src"; Pathname "dest"], [OInt64 "srcoffset"; OInt64 "destoffset"; OInt64 "size"]; + style = RErr, [Device "src"; Pathname "dest"], [OInt64 "srcoffset"; OInt64 "destoffset"; OInt64 "size"; OBool "sparse"]; proc_nr = Some 295; progress = true; shortdesc = "copy from source device to destination file"; @@ -8820,7 +8826,7 @@ of this call." }; { defaults with name = "copy_file_to_device"; - style = RErr, [Pathname "src"; Device "dest"], [OInt64 "srcoffset"; OInt64 "destoffset"; OInt64 "size"]; + style = RErr, [Pathname "src"; Device "dest"], [OInt64 "srcoffset"; OInt64 "destoffset"; OInt64 "size"; OBool "sparse"]; proc_nr = Some 296; progress = true; shortdesc = "copy from source file to destination device"; @@ -8830,15 +8836,23 @@ of this call." }; { defaults with name = "copy_file_to_file"; - style = RErr, [Pathname "src"; Pathname "dest"], [OInt64 "srcoffset"; OInt64 "destoffset"; OInt64 "size"]; + style = RErr, [Pathname "src"; Pathname "dest"], [OInt64 "srcoffset"; OInt64 "destoffset"; OInt64 "size"; OBool "sparse"]; proc_nr = Some 297; progress = true; tests = [ InitScratchFS, Always, TestOutputBuffer ( [["mkdir"; "/copyff"]; ["write"; "/copyff/src"; "hello, world"]; - ["copy_file_to_file"; "/copyff/src"; "/copyff/dest"; ""; ""; ""]; - ["read_file"; "/copyff/dest"]], "hello, world") + ["copy_file_to_file"; "/copyff/src"; "/copyff/dest"; ""; ""; ""; ""]; + ["read_file"; "/copyff/dest"]], "hello, world"); + let size = 1024 * 1024 in + InitScratchFS, Always, TestOutputTrue ( + [["mkdir"; "/copyff2"]; + ["fill"; "0"; string_of_int size; "/copyff2/src"]; + ["touch"; "/copyff2/dest"]; + ["truncate_size"; "/copyff2/dest"; string_of_int size]; + ["copy_file_to_file"; "/copyff2/src"; "/copyff2/dest"; ""; ""; ""; "true"]; + ["is_zero"; "/copyff2/dest"]]) ]; shortdesc = "copy from source file to destination file"; longdesc = "\ diff --git a/resize/resize.ml b/resize/resize.ml index d78630341..b720f45dd 100644 --- a/resize/resize.ml +++ b/resize/resize.ml @@ -1035,7 +1035,7 @@ let () = (match p.p_type with | ContentUnknown | ContentPV _ | ContentFS _ -> - g#copy_device_to_device ~size:copysize source target + g#copy_device_to_device ~size:copysize ~sparse:true source target | ContentExtendedPartition -> (* You can't just copy an extended partition by name, eg. diff --git a/resize/virt-resize.pod b/resize/virt-resize.pod index 4829a1573..1f27cbc34 100644 --- a/resize/virt-resize.pod +++ b/resize/virt-resize.pod @@ -671,6 +671,24 @@ C BSOD. This error is caused by having C in the sysprep.inf file. Removing this line before sysprepping should fix the problem. +=head2 SPARSE COPYING + +You must create a fresh, zeroed target disk image for virt-resize to +use. Do not reuse a target, especially one which contains data +already. + +Virt-resize performs sparse copying. This means that it does not copy +blocks from the source disk which are all zeroes. This improves speed +and efficiency, but will produce incorrect results if the target disk +image contains unzeroed data. + +The main time this can be a problem is if the target is a host +partition (eg. S>) because the +usual partitioning tools tend to leave whatever data happened to be on +the disk when making partitions. In rare cases you may need to +S> first to ensure the target +partition is zeroed. + =head1 ALTERNATIVE TOOLS There are several proprietary tools for resizing partitions. We