Add 'sparse' option to copy-{device,file}-to-{device,file} calls.

Setting the 'sparse' optional boolean causes writes to be omitted if
the block to be written contains all zero bytes.

This should help with sparse backing files (eg. raw, qcow2, dm-thin, etc).

Also, modify virt-resize to use this option by default when copying
devices.  The savings in virt-resize can be quite startling, eg
'du -sh' (ie. true size) of a resized disk image:

8.1G      /tmp/f15x32-resized.img    # before this change
3.2G      /tmp/f15x32-resized.img    # after this change
This commit is contained in:
Richard W.M. Jones
2013-04-04 13:45:42 +01:00
parent 289fd29f0b
commit 33c087ea9c
4 changed files with 69 additions and 17 deletions

View File

@@ -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);
}

View File

@@ -8784,7 +8784,7 @@ See also C<guestfs_part_to_dev>." };
{ 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<sparse> 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 = "\

View File

@@ -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.

View File

@@ -671,6 +671,24 @@ C<UNMOUNTABLE_BOOT_VOLUME> BSOD. This error is caused by having
C<ExtendOemPartition=1> 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<C<virt-resize source.img /dev/sda4>>) 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<C<dd if=/dev/zero of=/dev/sdXN>> first to ensure the target
partition is zeroed.
=head1 ALTERNATIVE TOOLS
There are several proprietary tools for resizing partitions. We