#include "qemu-version.h"
#include "qapi/error.h"
#include "qapi-visit.h"
-#include "qapi/qmp-output-visitor.h"
+#include "qapi/qobject-output-visitor.h"
#include "qapi/qmp/qerror.h"
#include "qapi/qmp/qjson.h"
#include "qemu/cutils.h"
#include <getopt.h>
#define QEMU_IMG_VERSION "qemu-img version " QEMU_VERSION QEMU_PKGVERSION \
- ", " QEMU_COPYRIGHT "\n"
+ "\n" QEMU_COPYRIGHT "\n"
typedef struct img_cmd_t {
const char *name;
" kinds of errors, with a higher risk of choosing the wrong fix or\n"
" hiding corruption that has already occurred.\n"
"\n"
+ "Parameters to convert subcommand:\n"
+ " '-m' specifies how many coroutines work in parallel during the convert\n"
+ " process (defaults to 8)\n"
+ " '-W' allow to write to the target out of order rather than sequential\n"
+ "\n"
"Parameters to snapshot subcommand:\n"
" 'snapshot' is the name of the snapshot to create, apply or delete\n"
" '-a' applies a snapshot (revert disk to saved state)\n"
"Parameters to compare subcommand:\n"
" '-f' first 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"
+ " 'skip=N' skip N bs-sized blocks at the start of input\n";
printf("%s\nSupported formats:", help_msg);
bdrv_iterate_format(format_print, NULL);
return 0;
}
+static int64_t cvtnum(const char *s)
+{
+ int err;
+ uint64_t value;
+
+ err = qemu_strtosz(s, NULL, &value);
+ if (err < 0) {
+ return err;
+ }
+ if (value > INT64_MAX) {
+ return -ERANGE;
+ }
+ return value;
+}
+
static int img_create(int argc, char **argv)
{
int c;
/* Get image size, if specified */
if (optind < argc) {
int64_t sval;
- char *end;
- sval = qemu_strtosz_suffix(argv[optind++], &end,
- QEMU_STRTOSZ_DEFSUFFIX_B);
- if (sval < 0 || *end) {
+
+ sval = cvtnum(argv[optind++]);
+ if (sval < 0) {
if (sval == -ERANGE) {
error_report("Image size must be less than 8 EiB!");
} else {
{
QString *str;
QObject *obj;
- Visitor *v = qmp_output_visitor_new(&obj);
+ Visitor *v = qobject_output_visitor_new(&obj);
visit_type_ImageCheck(v, NULL, &check, &error_abort);
visit_complete(v, &obj);
{
AioContext *aio_context = blk_get_aio_context(job->blk);
+ /* FIXME In error cases, the job simply goes away and we access a dangling
+ * pointer below. */
+ aio_context_acquire(aio_context);
do {
aio_poll(aio_context, true);
qemu_progress_print(job->len ?
} while (!job->ready);
block_job_complete_sync(job, errp);
+ aio_context_release(aio_context);
/* A block job may finish instantaneously without publishing any progress,
* so just signal completion here */
const char *filename, *fmt, *cache, *base;
BlockBackend *blk;
BlockDriverState *bs, *base_bs;
+ BlockJob *job;
bool progress = false, quiet = false, drop = false;
bool writethrough;
Error *local_err = NULL;
CommonBlockJobCBInfo cbi;
bool image_opts = false;
+ AioContext *aio_context;
fmt = NULL;
cache = BDRV_DEFAULT_CACHE;
if (base) {
base_bs = bdrv_find_backing_image(bs, base);
if (!base_bs) {
- error_setg(&local_err, QERR_BASE_NOT_FOUND, base);
+ error_setg(&local_err,
+ "Did not find '%s' in the backing chain of '%s'",
+ base, filename);
goto done;
}
} else {
.bs = bs,
};
- commit_active_start("commit", bs, base_bs, 0, BLOCKDEV_ON_ERROR_REPORT,
- common_block_job_cb, &cbi, &local_err, false);
+ aio_context = bdrv_get_aio_context(bs);
+ aio_context_acquire(aio_context);
+ commit_active_start("commit", bs, base_bs, BLOCK_JOB_DEFAULT, 0,
+ BLOCKDEV_ON_ERROR_REPORT, NULL, common_block_job_cb,
+ &cbi, &local_err, false);
+ aio_context_release(aio_context);
if (local_err) {
goto done;
}
bdrv_ref(bs);
}
- run_block_job(bs->job, &local_err);
+ job = block_job_get("commit");
+ run_block_job(job, &local_err);
if (local_err) {
goto unref_backing;
}
BLK_BACKING_FILE,
};
+#define MAX_COROUTINES 16
+
typedef struct ImgConvertState {
BlockBackend **src;
int64_t *src_sectors;
- int src_cur, src_num;
- int64_t src_cur_offset;
+ int src_num;
int64_t total_sectors;
int64_t allocated_sectors;
+ int64_t allocated_done;
+ int64_t sector_num;
+ int64_t wr_offs;
enum ImgConvertBlockStatus status;
int64_t sector_next_status;
BlockBackend *target;
bool has_zero_init;
bool compressed;
bool target_has_backing;
+ bool wr_in_order;
int min_sparse;
size_t cluster_sectors;
size_t buf_sectors;
+ int num_coroutines;
+ int running_coroutines;
+ Coroutine *co[MAX_COROUTINES];
+ int64_t wait_sector_num[MAX_COROUTINES];
+ CoMutex lock;
+ int ret;
} ImgConvertState;
-static void convert_select_part(ImgConvertState *s, int64_t sector_num)
+static void convert_select_part(ImgConvertState *s, int64_t sector_num,
+ int *src_cur, int64_t *src_cur_offset)
{
- assert(sector_num >= s->src_cur_offset);
- while (sector_num - s->src_cur_offset >= s->src_sectors[s->src_cur]) {
- s->src_cur_offset += s->src_sectors[s->src_cur];
- s->src_cur++;
- assert(s->src_cur < s->src_num);
+ *src_cur = 0;
+ *src_cur_offset = 0;
+ while (sector_num - *src_cur_offset >= s->src_sectors[*src_cur]) {
+ *src_cur_offset += s->src_sectors[*src_cur];
+ (*src_cur)++;
+ assert(*src_cur < s->src_num);
}
}
static int convert_iteration_sectors(ImgConvertState *s, int64_t sector_num)
{
- int64_t ret;
- int n;
+ int64_t ret, src_cur_offset;
+ int n, src_cur;
- convert_select_part(s, sector_num);
+ convert_select_part(s, sector_num, &src_cur, &src_cur_offset);
assert(s->total_sectors > sector_num);
n = MIN(s->total_sectors - sector_num, BDRV_REQUEST_MAX_SECTORS);
if (s->sector_next_status <= sector_num) {
BlockDriverState *file;
- ret = bdrv_get_block_status(blk_bs(s->src[s->src_cur]),
- sector_num - s->src_cur_offset,
+ ret = bdrv_get_block_status(blk_bs(s->src[src_cur]),
+ sector_num - src_cur_offset,
n, &n, &file);
if (ret < 0) {
return ret;
/* Check block status of the backing file chain to avoid
* needlessly reading zeroes and limiting the iteration to the
* buffer size */
- ret = bdrv_get_block_status_above(blk_bs(s->src[s->src_cur]), NULL,
- sector_num - s->src_cur_offset,
+ ret = bdrv_get_block_status_above(blk_bs(s->src[src_cur]), NULL,
+ sector_num - src_cur_offset,
n, &n, &file);
if (ret < 0) {
return ret;
return n;
}
-static int convert_read(ImgConvertState *s, int64_t sector_num, int nb_sectors,
- uint8_t *buf)
+static int coroutine_fn convert_co_read(ImgConvertState *s, int64_t sector_num,
+ int nb_sectors, uint8_t *buf)
{
- int n;
- int ret;
+ int n, ret;
+ QEMUIOVector qiov;
+ struct iovec iov;
assert(nb_sectors <= s->buf_sectors);
while (nb_sectors > 0) {
BlockBackend *blk;
- int64_t bs_sectors;
+ int src_cur;
+ int64_t bs_sectors, src_cur_offset;
/* In the case of compression with multiple source files, we can get a
* nb_sectors that spreads into the next part. So we must be able to
* read across multiple BDSes for one convert_read() call. */
- convert_select_part(s, sector_num);
- blk = s->src[s->src_cur];
- bs_sectors = s->src_sectors[s->src_cur];
-
- n = MIN(nb_sectors, bs_sectors - (sector_num - s->src_cur_offset));
- ret = blk_pread(blk,
- (sector_num - s->src_cur_offset) << BDRV_SECTOR_BITS,
- buf, n << BDRV_SECTOR_BITS);
+ convert_select_part(s, sector_num, &src_cur, &src_cur_offset);
+ blk = s->src[src_cur];
+ bs_sectors = s->src_sectors[src_cur];
+
+ n = MIN(nb_sectors, bs_sectors - (sector_num - src_cur_offset));
+ iov.iov_base = buf;
+ iov.iov_len = n << BDRV_SECTOR_BITS;
+ qemu_iovec_init_external(&qiov, &iov, 1);
+
+ ret = blk_co_preadv(
+ blk, (sector_num - src_cur_offset) << BDRV_SECTOR_BITS,
+ n << BDRV_SECTOR_BITS, &qiov, 0);
if (ret < 0) {
return ret;
}
return 0;
}
-static int convert_write(ImgConvertState *s, int64_t sector_num, int nb_sectors,
- const uint8_t *buf)
+
+static int coroutine_fn convert_co_write(ImgConvertState *s, int64_t sector_num,
+ int nb_sectors, uint8_t *buf,
+ enum ImgConvertBlockStatus status)
{
int ret;
+ QEMUIOVector qiov;
+ struct iovec iov;
while (nb_sectors > 0) {
int n = nb_sectors;
-
- switch (s->status) {
+ switch (status) {
case BLK_BACKING_FILE:
/* If we have a backing file, leave clusters unallocated that are
* unallocated in the source image, so that the backing file is
break;
}
- ret = blk_pwrite_compressed(s->target,
- sector_num << BDRV_SECTOR_BITS,
- buf, n << BDRV_SECTOR_BITS);
+ iov.iov_base = buf;
+ iov.iov_len = n << BDRV_SECTOR_BITS;
+ qemu_iovec_init_external(&qiov, &iov, 1);
+
+ ret = blk_co_pwritev(s->target, sector_num << BDRV_SECTOR_BITS,
+ n << BDRV_SECTOR_BITS, &qiov,
+ BDRV_REQ_WRITE_COMPRESSED);
if (ret < 0) {
return ret;
}
if (!s->min_sparse ||
is_allocated_sectors_min(buf, n, &n, s->min_sparse))
{
- ret = blk_pwrite(s->target, sector_num << BDRV_SECTOR_BITS,
- buf, n << BDRV_SECTOR_BITS, 0);
+ iov.iov_base = buf;
+ iov.iov_len = n << BDRV_SECTOR_BITS;
+ qemu_iovec_init_external(&qiov, &iov, 1);
+
+ ret = blk_co_pwritev(s->target, sector_num << BDRV_SECTOR_BITS,
+ n << BDRV_SECTOR_BITS, &qiov, 0);
if (ret < 0) {
return ret;
}
if (s->has_zero_init) {
break;
}
- ret = blk_pwrite_zeroes(s->target, sector_num << BDRV_SECTOR_BITS,
- n << BDRV_SECTOR_BITS, 0);
+ ret = blk_co_pwrite_zeroes(s->target,
+ sector_num << BDRV_SECTOR_BITS,
+ n << BDRV_SECTOR_BITS, 0);
if (ret < 0) {
return ret;
}
return 0;
}
-static int convert_do_copy(ImgConvertState *s)
+static void coroutine_fn convert_co_do_copy(void *opaque)
{
+ ImgConvertState *s = opaque;
uint8_t *buf = NULL;
- int64_t sector_num, allocated_done;
- int ret;
- int n;
+ int ret, i;
+ int index = -1;
+
+ for (i = 0; i < s->num_coroutines; i++) {
+ if (s->co[i] == qemu_coroutine_self()) {
+ index = i;
+ break;
+ }
+ }
+ assert(index >= 0);
+
+ s->running_coroutines++;
+ buf = blk_blockalign(s->target, s->buf_sectors * BDRV_SECTOR_SIZE);
+
+ while (1) {
+ int n;
+ int64_t sector_num;
+ enum ImgConvertBlockStatus status;
+
+ qemu_co_mutex_lock(&s->lock);
+ if (s->ret != -EINPROGRESS || s->sector_num >= s->total_sectors) {
+ qemu_co_mutex_unlock(&s->lock);
+ goto out;
+ }
+ n = convert_iteration_sectors(s, s->sector_num);
+ if (n < 0) {
+ qemu_co_mutex_unlock(&s->lock);
+ s->ret = n;
+ goto out;
+ }
+ /* save current sector and allocation status to local variables */
+ sector_num = s->sector_num;
+ status = s->status;
+ if (!s->min_sparse && s->status == BLK_ZERO) {
+ n = MIN(n, s->buf_sectors);
+ }
+ /* increment global sector counter so that other coroutines can
+ * already continue reading beyond this request */
+ s->sector_num += n;
+ qemu_co_mutex_unlock(&s->lock);
+
+ if (status == BLK_DATA || (!s->min_sparse && status == BLK_ZERO)) {
+ s->allocated_done += n;
+ qemu_progress_print(100.0 * s->allocated_done /
+ s->allocated_sectors, 0);
+ }
+
+ if (status == BLK_DATA) {
+ ret = convert_co_read(s, sector_num, n, buf);
+ if (ret < 0) {
+ error_report("error while reading sector %" PRId64
+ ": %s", sector_num, strerror(-ret));
+ s->ret = ret;
+ goto out;
+ }
+ } else if (!s->min_sparse && status == BLK_ZERO) {
+ status = BLK_DATA;
+ memset(buf, 0x00, n * BDRV_SECTOR_SIZE);
+ }
+
+ if (s->wr_in_order) {
+ /* keep writes in order */
+ while (s->wr_offs != sector_num) {
+ if (s->ret != -EINPROGRESS) {
+ goto out;
+ }
+ s->wait_sector_num[index] = sector_num;
+ qemu_coroutine_yield();
+ }
+ s->wait_sector_num[index] = -1;
+ }
+
+ ret = convert_co_write(s, sector_num, n, buf, status);
+ if (ret < 0) {
+ error_report("error while writing sector %" PRId64
+ ": %s", sector_num, strerror(-ret));
+ s->ret = ret;
+ goto out;
+ }
+
+ if (s->wr_in_order) {
+ /* reenter the coroutine that might have waited
+ * for this write to complete */
+ s->wr_offs = sector_num + n;
+ for (i = 0; i < s->num_coroutines; i++) {
+ if (s->co[i] && s->wait_sector_num[i] == s->wr_offs) {
+ /*
+ * A -> B -> A cannot occur because A has
+ * s->wait_sector_num[i] == -1 during A -> B. Therefore
+ * B will never enter A during this time window.
+ */
+ qemu_coroutine_enter(s->co[i]);
+ break;
+ }
+ }
+ }
+ }
+
+out:
+ qemu_vfree(buf);
+ s->co[index] = NULL;
+ s->running_coroutines--;
+ if (!s->running_coroutines && s->ret == -EINPROGRESS) {
+ /* the convert job finished successfully */
+ s->ret = 0;
+ }
+}
+
+static int convert_do_copy(ImgConvertState *s)
+{
+ int ret, i, n;
+ int64_t sector_num = 0;
/* Check whether we have zero initialisation or can get it efficiently */
s->has_zero_init = s->min_sparse && !s->target_has_backing
if (s->compressed) {
if (s->cluster_sectors <= 0 || s->cluster_sectors > s->buf_sectors) {
error_report("invalid cluster size");
- ret = -EINVAL;
- goto fail;
+ return -EINVAL;
}
s->buf_sectors = s->cluster_sectors;
}
- buf = blk_blockalign(s->target, s->buf_sectors * BDRV_SECTOR_SIZE);
- /* Calculate allocated sectors for progress */
- s->allocated_sectors = 0;
- sector_num = 0;
while (sector_num < s->total_sectors) {
n = convert_iteration_sectors(s, sector_num);
if (n < 0) {
- ret = n;
- goto fail;
+ return n;
}
if (s->status == BLK_DATA || (!s->min_sparse && s->status == BLK_ZERO))
{
}
/* Do the copy */
- s->src_cur = 0;
- s->src_cur_offset = 0;
s->sector_next_status = 0;
+ s->ret = -EINPROGRESS;
- sector_num = 0;
- allocated_done = 0;
-
- while (sector_num < s->total_sectors) {
- n = convert_iteration_sectors(s, sector_num);
- if (n < 0) {
- ret = n;
- goto fail;
- }
- if (s->status == BLK_DATA || (!s->min_sparse && s->status == BLK_ZERO))
- {
- allocated_done += n;
- qemu_progress_print(100.0 * allocated_done / s->allocated_sectors,
- 0);
- }
-
- if (s->status == BLK_DATA) {
- ret = convert_read(s, sector_num, n, buf);
- if (ret < 0) {
- error_report("error while reading sector %" PRId64
- ": %s", sector_num, strerror(-ret));
- goto fail;
- }
- } else if (!s->min_sparse && s->status == BLK_ZERO) {
- n = MIN(n, s->buf_sectors);
- memset(buf, 0, n * BDRV_SECTOR_SIZE);
- s->status = BLK_DATA;
- }
-
- ret = convert_write(s, sector_num, n, buf);
- if (ret < 0) {
- error_report("error while writing sector %" PRId64
- ": %s", sector_num, strerror(-ret));
- goto fail;
- }
+ qemu_co_mutex_init(&s->lock);
+ for (i = 0; i < s->num_coroutines; i++) {
+ s->co[i] = qemu_coroutine_create(convert_co_do_copy, s);
+ s->wait_sector_num[i] = -1;
+ qemu_coroutine_enter(s->co[i]);
+ }
- sector_num += n;
+ while (s->ret == -EINPROGRESS) {
+ main_loop_wait(false);
}
- if (s->compressed) {
+ if (s->compressed && !s->ret) {
/* signal EOF to align */
ret = blk_pwrite_compressed(s->target, 0, NULL, 0);
if (ret < 0) {
- goto fail;
+ return ret;
}
}
- ret = 0;
-fail:
- qemu_vfree(buf);
- return ret;
+ return s->ret;
}
static int img_convert(int argc, char **argv)
QemuOpts *sn_opts = NULL;
ImgConvertState state;
bool image_opts = false;
+ bool wr_in_order = true;
+ long num_coroutines = 8;
fmt = NULL;
out_fmt = "raw";
{"image-opts", no_argument, 0, OPTION_IMAGE_OPTS},
{0, 0, 0, 0}
};
- c = getopt_long(argc, argv, "hf:O:B:ce6o:s:l:S:pt:T:qn",
+ c = getopt_long(argc, argv, "hf:O:B:ce6o:s:l:S:pt:T:qnm:W",
long_options, NULL);
if (c == -1) {
break;
case 'S':
{
int64_t sval;
- char *end;
- sval = qemu_strtosz_suffix(optarg, &end, QEMU_STRTOSZ_DEFSUFFIX_B);
- if (sval < 0 || *end) {
+
+ sval = cvtnum(optarg);
+ if (sval < 0) {
error_report("Invalid minimum zero buffer size for sparse output specified");
ret = -1;
goto fail_getopt;
case 'n':
skip_create = 1;
break;
+ case 'm':
+ if (qemu_strtol(optarg, NULL, 0, &num_coroutines) ||
+ num_coroutines < 1 || num_coroutines > MAX_COROUTINES) {
+ error_report("Invalid number of coroutines. Allowed number of"
+ " coroutines is between 1 and %d", MAX_COROUTINES);
+ ret = -1;
+ goto fail_getopt;
+ }
+ break;
+ case 'W':
+ wr_in_order = false;
+ break;
case OPTION_OBJECT:
opts = qemu_opts_parse_noisily(&qemu_object_opts,
optarg, true);
goto fail_getopt;
}
+ if (!wr_in_order && compress) {
+ error_report("Out of order write and compress are mutually exclusive");
+ ret = -1;
+ goto fail_getopt;
+ }
+
/* Initialize before goto out */
if (quiet) {
progress = 0;
}
if (sn_opts) {
- ret = bdrv_snapshot_load_tmp(bs[0],
- qemu_opt_get(sn_opts, SNAPSHOT_OPT_ID),
- qemu_opt_get(sn_opts, SNAPSHOT_OPT_NAME),
- &local_err);
+ bdrv_snapshot_load_tmp(bs[0],
+ qemu_opt_get(sn_opts, SNAPSHOT_OPT_ID),
+ qemu_opt_get(sn_opts, SNAPSHOT_OPT_NAME),
+ &local_err);
} else if (snapshot_name != NULL) {
if (bs_n > 1) {
error_report("No support for concatenating multiple snapshot");
.min_sparse = min_sparse,
.cluster_sectors = cluster_sectors,
.buf_sectors = bufsectors,
+ .wr_in_order = wr_in_order,
+ .num_coroutines = num_coroutines,
};
ret = convert_do_copy(&state);
{
QString *str;
QObject *obj;
- Visitor *v = qmp_output_visitor_new(&obj);
+ Visitor *v = qobject_output_visitor_new(&obj);
visit_type_ImageInfoList(v, NULL, &list, &error_abort);
visit_complete(v, &obj);
{
QString *str;
QObject *obj;
- Visitor *v = qmp_output_visitor_new(&obj);
+ Visitor *v = qobject_output_visitor_new(&obj);
visit_type_ImageInfo(v, NULL, &info, &error_abort);
visit_complete(v, &obj);
error_reportf_err(local_err,
"Could not open old backing file '%s': ",
backing_name);
+ ret = -1;
goto out;
}
error_reportf_err(local_err,
"Could not open new backing file '%s': ",
out_baseimg);
+ ret = -1;
goto out;
}
}
qemu_opts_del(param);
blk = img_open(image_opts, filename, fmt,
- BDRV_O_RDWR, false, quiet);
+ BDRV_O_RDWR | BDRV_O_RESIZE, false, quiet);
if (!blk) {
ret = -1;
goto out;
create_opts = qemu_opts_append(create_opts, bs->drv->create_opts);
opts = qemu_opts_create(create_opts, NULL, 0, &error_abort);
- if (options) {
- qemu_opts_do_parse(opts, options, NULL, &err);
- if (err) {
- error_report_err(err);
- ret = -1;
- goto out;
- }
+ qemu_opts_do_parse(opts, options, NULL, &err);
+ if (err) {
+ error_report_err(err);
+ ret = -1;
+ goto out;
}
/* In case the driver does not call amend_status_cb() */
}
while (b->n > b->in_flight && b->in_flight < b->nrreq) {
+ int64_t offset = b->offset;
+ /* blk_aio_* might look for completed I/Os and kick bench_cb
+ * again, so make sure this operation is counted by in_flight
+ * and b->offset is ready for the next submission.
+ */
+ b->in_flight++;
+ b->offset += b->step;
+ b->offset %= b->image_size;
if (b->write) {
- acb = blk_aio_pwritev(b->blk, b->offset, b->qiov, 0,
- bench_cb, b);
+ acb = blk_aio_pwritev(b->blk, offset, b->qiov, 0, bench_cb, b);
} else {
- acb = blk_aio_preadv(b->blk, b->offset, b->qiov, 0,
- bench_cb, b);
+ acb = blk_aio_preadv(b->blk, offset, b->qiov, 0, bench_cb, b);
}
if (!acb) {
error_report("Failed to issue request");
exit(EXIT_FAILURE);
}
- b->in_flight++;
- b->offset += b->step;
- b->offset %= b->image_size;
}
}
break;
case 'c':
{
- char *end;
- errno = 0;
- count = strtoul(optarg, &end, 0);
- if (errno || *end || count > INT_MAX) {
+ unsigned long res;
+
+ if (qemu_strtoul(optarg, NULL, 0, &res) < 0 || res > INT_MAX) {
error_report("Invalid request count specified");
return 1;
}
+ count = res;
break;
}
case 'd':
{
- char *end;
- errno = 0;
- depth = strtoul(optarg, &end, 0);
- if (errno || *end || depth > INT_MAX) {
+ unsigned long res;
+
+ if (qemu_strtoul(optarg, NULL, 0, &res) < 0 || res > INT_MAX) {
error_report("Invalid queue depth specified");
return 1;
}
+ depth = res;
break;
}
case 'f':
break;
case 'o':
{
- char *end;
- errno = 0;
- offset = qemu_strtosz_suffix(optarg, &end,
- QEMU_STRTOSZ_DEFSUFFIX_B);
- if (offset < 0|| *end) {
+ offset = cvtnum(optarg);
+ if (offset < 0) {
error_report("Invalid offset specified");
return 1;
}
case 's':
{
int64_t sval;
- char *end;
- sval = qemu_strtosz_suffix(optarg, &end, QEMU_STRTOSZ_DEFSUFFIX_B);
- if (sval < 0 || sval > INT_MAX || *end) {
+ sval = cvtnum(optarg);
+ if (sval < 0 || sval > INT_MAX) {
error_report("Invalid buffer size specified");
return 1;
}
case 'S':
{
int64_t sval;
- char *end;
- sval = qemu_strtosz_suffix(optarg, &end, QEMU_STRTOSZ_DEFSUFFIX_B);
- if (sval < 0 || sval > INT_MAX || *end) {
+ sval = cvtnum(optarg);
+ if (sval < 0 || sval > INT_MAX) {
error_report("Invalid step size specified");
return 1;
}
break;
case OPTION_PATTERN:
{
- char *end;
- errno = 0;
- pattern = strtoul(optarg, &end, 0);
- if (errno || *end || pattern > 0xff) {
+ unsigned long res;
+
+ if (qemu_strtoul(optarg, NULL, 0, &res) < 0 || res > 0xff) {
error_report("Invalid pattern byte specified");
return 1;
}
+ pattern = res;
break;
}
case OPTION_FLUSH_INTERVAL:
{
- char *end;
- errno = 0;
- flush_interval = strtoul(optarg, &end, 0);
- if (errno || *end || flush_interval > INT_MAX) {
+ unsigned long res;
+
+ if (qemu_strtoul(optarg, NULL, 0, &res) < 0 || res > INT_MAX) {
error_report("Invalid flush interval specified");
return 1;
}
+ flush_interval = res;
break;
}
case OPTION_NO_DRAIN:
return 0;
}
+#define C_BS 01
+#define C_COUNT 02
+#define C_IF 04
+#define C_OF 010
+#define C_SKIP 020
+
+struct DdInfo {
+ unsigned int flags;
+ int64_t count;
+};
+
+struct DdIo {
+ int bsz; /* Block size */
+ char *filename;
+ uint8_t *buf;
+ int64_t offset;
+};
+
+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)
+{
+ int64_t res;
+
+ res = cvtnum(arg);
+
+ if (res <= 0 || res > INT_MAX) {
+ 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)
+{
+ dd->count = cvtnum(arg);
+
+ if (dd->count < 0) {
+ 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_skip(const char *arg,
+ struct DdIo *in, struct DdIo *out,
+ struct DdInfo *dd)
+{
+ in->offset = cvtnum(arg);
+
+ if (in->offset < 0) {
+ error_report("invalid number: '%s'", arg);
+ return 1;
+ }
+
+ 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,
+ .offset = 0
+ };
+ struct DdIo out = {
+ .bsz = 512,
+ .filename = NULL,
+ .buf = NULL,
+ .offset = 0
+ };
+
+ 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 },
+ { "skip", img_dd_skip, C_SKIP },
+ { 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;
+ }
+
+ /* Overflow means the specified offset is beyond input image's size */
+ if (dd.flags & C_SKIP && (in.offset > INT64_MAX / in.bsz ||
+ size < in.bsz * in.offset)) {
+ qemu_opt_set_number(opts, BLOCK_OPT_SIZE, 0, &error_abort);
+ } else {
+ qemu_opt_set_number(opts, BLOCK_OPT_SIZE,
+ size - in.bsz * in.offset, &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;
+ }
+
+ if (dd.flags & C_SKIP && (in.offset > INT64_MAX / in.bsz ||
+ size < in.offset * in.bsz)) {
+ /* We give a warning if the skip option is bigger than the input
+ * size and create an empty output disk image (i.e. like dd(1)).
+ */
+ error_report("%s: cannot skip to specified offset", in.filename);
+ in_pos = size;
+ } else {
+ in_pos = in.offset * in.bsz;
+ }
+
+ in.buf = g_new(uint8_t, in.bsz);
+
+ for (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[] = {
#define DEF(option, callback, arg_string) \
signal(SIGPIPE, SIG_IGN);
#endif
+ module_call_init(MODULE_INIT_TRACE);
error_set_progname(argv[0]);
qemu_init_exec_dir(argv[0]);