qemu-img: add the 'dd' subcommand

This patch adds a basic dd subcommand analogous to dd(1) to qemu-img.

For the start, this implements the bs, if, of and count options and requires
both if and of to be specified (no stdin/stdout if not specified) and doesn't
support tty, pipes, etc.

The image format must be specified with -O for the output if the raw format
is not the intended one.

Two tests are added to test qemu-img dd.

Signed-off-by: Reda Sallahi <fullmanet@gmail.com>
Message-id: 20160810024312.14544-1-fullmanet@gmail.com
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
[mreitz: Moved test 158 to 170]
Signed-off-by: Max Reitz <mreitz@redhat.com>
This commit is contained in:
Reda Sallahi 2016-08-10 04:43:12 +02:00 committed by Max Reitz
parent a008535b9f
commit 86ce1f6e2b
10 changed files with 584 additions and 5 deletions

View File

@ -45,6 +45,12 @@ STEXI
@item convert [--object @var{objectdef}] [--image-opts] [-c] [-p] [-q] [-n] [-f @var{fmt}] [-t @var{cache}] [-T @var{src_cache}] [-O @var{output_fmt}] [-o @var{options}] [-s @var{snapshot_id_or_name}] [-l @var{snapshot_param}] [-S @var{sparse_size}] @var{filename} [@var{filename2} [...]] @var{output_filename} @item convert [--object @var{objectdef}] [--image-opts] [-c] [-p] [-q] [-n] [-f @var{fmt}] [-t @var{cache}] [-T @var{src_cache}] [-O @var{output_fmt}] [-o @var{options}] [-s @var{snapshot_id_or_name}] [-l @var{snapshot_param}] [-S @var{sparse_size}] @var{filename} [@var{filename2} [...]] @var{output_filename}
ETEXI ETEXI
DEF("dd", img_dd,
"dd [--image-opts] [-f fmt] [-O output_fmt] [bs=block_size] [count=blocks] if=input of=output")
STEXI
@item dd [--image-opts] [-f @var{fmt}] [-O @var{output_fmt}] [bs=@var{block_size}] [count=@var{blocks}] if=@var{input} of=@var{output}
ETEXI
DEF("info", img_info, DEF("info", img_info,
"info [--object objectdef] [--image-opts] [-f fmt] [--output=ofmt] [--backing-chain] filename") "info [--object objectdef] [--image-opts] [-f fmt] [--output=ofmt] [--backing-chain] filename")
STEXI STEXI

View File

@ -166,7 +166,14 @@ static void QEMU_NORETURN help(void)
"Parameters to compare subcommand:\n" "Parameters to compare subcommand:\n"
" '-f' first image format\n" " '-f' first image format\n"
" '-F' second image format\n" " '-F' second image format\n"
" '-s' run in Strict mode - fail on different image size or sector allocation\n"; " '-s' run in Strict mode - fail on different image size or sector allocation\n"
"\n"
"Parameters to dd subcommand:\n"
" 'bs=BYTES' read and write up to BYTES bytes at a time "
"(default: 512)\n"
" 'count=N' copy only N input blocks\n"
" 'if=FILE' read from FILE\n"
" 'of=FILE' write to FILE\n";
printf("%s\nSupported formats:", help_msg); printf("%s\nSupported formats:", help_msg);
bdrv_iterate_format(format_print, NULL); bdrv_iterate_format(format_print, NULL);
@ -3796,6 +3803,300 @@ out:
return 0; return 0;
} }
#define C_BS 01
#define C_COUNT 02
#define C_IF 04
#define C_OF 010
struct DdInfo {
unsigned int flags;
int64_t count;
};
struct DdIo {
int bsz; /* Block size */
char *filename;
uint8_t *buf;
};
struct DdOpts {
const char *name;
int (*f)(const char *, struct DdIo *, struct DdIo *, struct DdInfo *);
unsigned int flag;
};
static int img_dd_bs(const char *arg,
struct DdIo *in, struct DdIo *out,
struct DdInfo *dd)
{
char *end;
int64_t res;
res = qemu_strtosz_suffix(arg, &end, QEMU_STRTOSZ_DEFSUFFIX_B);
if (res <= 0 || res > INT_MAX || *end) {
error_report("invalid number: '%s'", arg);
return 1;
}
in->bsz = out->bsz = res;
return 0;
}
static int img_dd_count(const char *arg,
struct DdIo *in, struct DdIo *out,
struct DdInfo *dd)
{
char *end;
dd->count = qemu_strtosz_suffix(arg, &end, QEMU_STRTOSZ_DEFSUFFIX_B);
if (dd->count < 0 || *end) {
error_report("invalid number: '%s'", arg);
return 1;
}
return 0;
}
static int img_dd_if(const char *arg,
struct DdIo *in, struct DdIo *out,
struct DdInfo *dd)
{
in->filename = g_strdup(arg);
return 0;
}
static int img_dd_of(const char *arg,
struct DdIo *in, struct DdIo *out,
struct DdInfo *dd)
{
out->filename = g_strdup(arg);
return 0;
}
static int img_dd(int argc, char **argv)
{
int ret = 0;
char *arg = NULL;
char *tmp;
BlockDriver *drv = NULL, *proto_drv = NULL;
BlockBackend *blk1 = NULL, *blk2 = NULL;
QemuOpts *opts = NULL;
QemuOptsList *create_opts = NULL;
Error *local_err = NULL;
bool image_opts = false;
int c, i;
const char *out_fmt = "raw";
const char *fmt = NULL;
int64_t size = 0;
int64_t block_count = 0, out_pos, in_pos;
struct DdInfo dd = {
.flags = 0,
.count = 0,
};
struct DdIo in = {
.bsz = 512, /* Block size is by default 512 bytes */
.filename = NULL,
.buf = NULL
};
struct DdIo out = {
.bsz = 512,
.filename = NULL,
.buf = NULL
};
const struct DdOpts options[] = {
{ "bs", img_dd_bs, C_BS },
{ "count", img_dd_count, C_COUNT },
{ "if", img_dd_if, C_IF },
{ "of", img_dd_of, C_OF },
{ NULL, NULL, 0 }
};
const struct option long_options[] = {
{ "help", no_argument, 0, 'h'},
{ "image-opts", no_argument, 0, OPTION_IMAGE_OPTS},
{ 0, 0, 0, 0 }
};
while ((c = getopt_long(argc, argv, "hf:O:", long_options, NULL))) {
if (c == EOF) {
break;
}
switch (c) {
case 'O':
out_fmt = optarg;
break;
case 'f':
fmt = optarg;
break;
case '?':
error_report("Try 'qemu-img --help' for more information.");
ret = -1;
goto out;
case 'h':
help();
break;
case OPTION_IMAGE_OPTS:
image_opts = true;
break;
}
}
for (i = optind; i < argc; i++) {
int j;
arg = g_strdup(argv[i]);
tmp = strchr(arg, '=');
if (tmp == NULL) {
error_report("unrecognized operand %s", arg);
ret = -1;
goto out;
}
*tmp++ = '\0';
for (j = 0; options[j].name != NULL; j++) {
if (!strcmp(arg, options[j].name)) {
break;
}
}
if (options[j].name == NULL) {
error_report("unrecognized operand %s", arg);
ret = -1;
goto out;
}
if (options[j].f(tmp, &in, &out, &dd) != 0) {
ret = -1;
goto out;
}
dd.flags |= options[j].flag;
g_free(arg);
arg = NULL;
}
if (!(dd.flags & C_IF && dd.flags & C_OF)) {
error_report("Must specify both input and output files");
ret = -1;
goto out;
}
blk1 = img_open(image_opts, in.filename, fmt, 0, false, false);
if (!blk1) {
ret = -1;
goto out;
}
drv = bdrv_find_format(out_fmt);
if (!drv) {
error_report("Unknown file format");
ret = -1;
goto out;
}
proto_drv = bdrv_find_protocol(out.filename, true, &local_err);
if (!proto_drv) {
error_report_err(local_err);
ret = -1;
goto out;
}
if (!drv->create_opts) {
error_report("Format driver '%s' does not support image creation",
drv->format_name);
ret = -1;
goto out;
}
if (!proto_drv->create_opts) {
error_report("Protocol driver '%s' does not support image creation",
proto_drv->format_name);
ret = -1;
goto out;
}
create_opts = qemu_opts_append(create_opts, drv->create_opts);
create_opts = qemu_opts_append(create_opts, proto_drv->create_opts);
opts = qemu_opts_create(create_opts, NULL, 0, &error_abort);
size = blk_getlength(blk1);
if (size < 0) {
error_report("Failed to get size for '%s'", in.filename);
ret = -1;
goto out;
}
if (dd.flags & C_COUNT && dd.count <= INT64_MAX / in.bsz &&
dd.count * in.bsz < size) {
size = dd.count * in.bsz;
}
qemu_opt_set_number(opts, BLOCK_OPT_SIZE, size, &error_abort);
ret = bdrv_create(drv, out.filename, opts, &local_err);
if (ret < 0) {
error_reportf_err(local_err,
"%s: error while creating output image: ",
out.filename);
ret = -1;
goto out;
}
blk2 = img_open(image_opts, out.filename, out_fmt, BDRV_O_RDWR,
false, false);
if (!blk2) {
ret = -1;
goto out;
}
in.buf = g_new(uint8_t, in.bsz);
for (in_pos = 0, out_pos = 0; in_pos < size; block_count++) {
int in_ret, out_ret;
if (in_pos + in.bsz > size) {
in_ret = blk_pread(blk1, in_pos, in.buf, size - in_pos);
} else {
in_ret = blk_pread(blk1, in_pos, in.buf, in.bsz);
}
if (in_ret < 0) {
error_report("error while reading from input image file: %s",
strerror(-in_ret));
ret = -1;
goto out;
}
in_pos += in_ret;
out_ret = blk_pwrite(blk2, out_pos, in.buf, in_ret, 0);
if (out_ret < 0) {
error_report("error while writing to output image file: %s",
strerror(-out_ret));
ret = -1;
goto out;
}
out_pos += out_ret;
}
out:
g_free(arg);
qemu_opts_del(opts);
qemu_opts_free(create_opts);
blk_unref(blk1);
blk_unref(blk2);
g_free(in.filename);
g_free(out.filename);
g_free(in.buf);
g_free(out.buf);
if (ret) {
return 1;
}
return 0;
}
static const img_cmd_t img_cmds[] = { static const img_cmd_t img_cmds[] = {
#define DEF(option, callback, arg_string) \ #define DEF(option, callback, arg_string) \

View File

@ -139,6 +139,20 @@ Parameters to convert subcommand:
Skip the creation of the target volume Skip the creation of the target volume
@end table @end table
Parameters to dd subcommand:
@table @option
@item bs=@var{block_size}
defines the block size
@item count=@var{blocks}
sets the number of input blocks to copy
@item if=@var{input}
sets the input file
@item of=@var{output}
sets the output file
@end table
Command description: Command description:
@table @option @table @option
@ -310,6 +324,17 @@ skipped. This is useful for formats such as @code{rbd} if the target
volume has already been created with site specific options that cannot volume has already been created with site specific options that cannot
be supplied through qemu-img. be supplied through qemu-img.
@item dd [-f @var{fmt}] [-O @var{output_fmt}] [bs=@var{block_size}] [count=@var{blocks}] if=@var{input} of=@var{output}
Dd copies from @var{input} file to @var{output} file converting it from
@var{fmt} format to @var{output_fmt} format.
The data is by default read and written using blocks of 512 bytes but can be
modified by specifying @var{block_size}. If count=@var{blocks} is specified
dd will stop reading input after reading @var{blocks} input blocks.
The size syntax is similar to dd(1)'s size syntax.
@item info [-f @var{fmt}] [--output=@var{ofmt}] [--backing-chain] @var{filename} @item info [-f @var{fmt}] [--output=@var{ofmt}] [--backing-chain] @var{filename}
Give information about the disk image @var{filename}. Use it in Give information about the disk image @var{filename}. Use it in

70
tests/qemu-iotests/159 Executable file
View File

@ -0,0 +1,70 @@
#! /bin/bash
#
# qemu-img dd test with different block sizes
#
# Copyright (C) 2016 Reda Sallahi
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
owner=fullmanet@gmail.com
seq="$(basename $0)"
echo "QA output created by $seq"
here="$PWD"
status=1
_cleanup()
{
_cleanup_test_img
rm -f "$TEST_IMG.out"
}
trap "_cleanup; exit \$status" 0 1 2 3 15
. ./common.rc
. ./common.filter
. ./common.pattern
_supported_fmt generic
_supported_proto file
_supported_os Linux
TEST_SIZES="5 512 1024 1999 1K 64K 1M"
for bs in $TEST_SIZES; do
echo
echo "== Creating image =="
size=1M
_make_test_img $size
_check_test_img
$QEMU_IO -c "write -P 0xa 0 $size" "$TEST_IMG" | _filter_qemu_io
echo
echo "== Converting the image with dd with a block size of $bs =="
$QEMU_IMG dd if="$TEST_IMG" of="$TEST_IMG.out" bs=$bs -O "$IMGFMT"
TEST_IMG="$TEST_IMG.out" _check_test_img
echo
echo "== Compare the images with qemu-img compare =="
$QEMU_IMG compare "$TEST_IMG" "$TEST_IMG.out"
done
echo
echo "*** done"
rm -f "$seq.full"
status=0

View File

@ -0,0 +1,87 @@
QA output created by 159
== Creating image ==
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576
No errors were found on the image.
wrote 1048576/1048576 bytes at offset 0
1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
== Converting the image with dd with a block size of 5 ==
No errors were found on the image.
== Compare the images with qemu-img compare ==
Images are identical.
== Creating image ==
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576
No errors were found on the image.
wrote 1048576/1048576 bytes at offset 0
1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
== Converting the image with dd with a block size of 512 ==
No errors were found on the image.
== Compare the images with qemu-img compare ==
Images are identical.
== Creating image ==
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576
No errors were found on the image.
wrote 1048576/1048576 bytes at offset 0
1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
== Converting the image with dd with a block size of 1024 ==
No errors were found on the image.
== Compare the images with qemu-img compare ==
Images are identical.
== Creating image ==
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576
No errors were found on the image.
wrote 1048576/1048576 bytes at offset 0
1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
== Converting the image with dd with a block size of 1999 ==
No errors were found on the image.
== Compare the images with qemu-img compare ==
Images are identical.
== Creating image ==
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576
No errors were found on the image.
wrote 1048576/1048576 bytes at offset 0
1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
== Converting the image with dd with a block size of 1K ==
No errors were found on the image.
== Compare the images with qemu-img compare ==
Images are identical.
== Creating image ==
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576
No errors were found on the image.
wrote 1048576/1048576 bytes at offset 0
1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
== Converting the image with dd with a block size of 64K ==
No errors were found on the image.
== Compare the images with qemu-img compare ==
Images are identical.
== Creating image ==
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576
No errors were found on the image.
wrote 1048576/1048576 bytes at offset 0
1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
== Converting the image with dd with a block size of 1M ==
No errors were found on the image.
== Compare the images with qemu-img compare ==
Images are identical.
*** done

67
tests/qemu-iotests/170 Executable file
View File

@ -0,0 +1,67 @@
#! /bin/bash
#
# qemu-img dd test
#
# Copyright (C) 2016 Reda Sallahi
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
owner=fullmanet@gmail.com
seq="$(basename $0)"
echo "QA output created by $seq"
here="$PWD"
status=1
_cleanup()
{
_cleanup_test_img
rm -f "$TEST_IMG.out"
}
trap "_cleanup; exit \$status" 0 1 2 3 15
. ./common.rc
. ./common.filter
. ./common.pattern
_supported_fmt generic
_supported_proto file
_supported_os Linux
echo
echo "== Creating image =="
size=1M
_make_test_img $size
_check_test_img
$QEMU_IO -c "write -P 0xa 0 $size" "$TEST_IMG" | _filter_qemu_io
echo
echo "== Converting the image with dd =="
$QEMU_IMG dd if="$TEST_IMG" of="$TEST_IMG.out" -O "$IMGFMT"
TEST_IMG="$TEST_IMG.out" _check_test_img
echo
echo "== Compare the images with qemu-img compare =="
$QEMU_IMG compare "$TEST_IMG" "$TEST_IMG.out"
echo
echo "*** done"
rm -f "$seq.full"
status=0

View File

@ -0,0 +1,15 @@
QA output created by 170
== Creating image ==
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576
No errors were found on the image.
wrote 1048576/1048576 bytes at offset 0
1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
== Converting the image with dd ==
No errors were found on the image.
== Compare the images with qemu-img compare ==
Images are identical.
*** done

View File

@ -44,6 +44,15 @@ _filter_imgfmt()
sed -e "s#$IMGFMT#IMGFMT#g" sed -e "s#$IMGFMT#IMGFMT#g"
} }
# Replace error message when the format is not supported and delete
# the output lines after the first one
_filter_qemu_img_check()
{
sed -e '/allocated.*fragmented.*compressed clusters/d' \
-e 's/qemu-img: This image format does not support checks/No errors were found on the image./' \
-e '/Image end offset: [0-9]\+/d'
}
# Removes \r from messages # Removes \r from messages
_filter_win32() _filter_win32()
{ {

View File

@ -234,10 +234,7 @@ _check_test_img()
else else
$QEMU_IMG check "$@" -f $IMGFMT "$TEST_IMG" 2>&1 $QEMU_IMG check "$@" -f $IMGFMT "$TEST_IMG" 2>&1
fi fi
) | _filter_testdir | \ ) | _filter_testdir | _filter_qemu_img_check
sed -e '/allocated.*fragmented.*compressed clusters/d' \
-e 's/qemu-img: This image format does not support checks/No errors were found on the image./' \
-e '/Image end offset: [0-9]\+/d'
} }
_img_info() _img_info()

View File

@ -157,4 +157,6 @@
155 rw auto 155 rw auto
156 rw auto quick 156 rw auto quick
157 auto 157 auto
159 rw auto quick
162 auto quick 162 auto quick
170 rw auto quick