* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
+
#include "qemu/osdep.h"
+#include <getopt.h>
+
#include "qemu-version.h"
#include "qapi/error.h"
-#include "qapi/util.h"
-#include "qapi-visit.h"
+#include "qapi/qapi-visit-block-core.h"
#include "qapi/qobject-output-visitor.h"
-#include "qapi/qmp/qerror.h"
#include "qapi/qmp/qjson.h"
-#include "qapi/qmp/qbool.h"
+#include "qapi/qmp/qdict.h"
+#include "qapi/qmp/qstring.h"
#include "qemu/cutils.h"
#include "qemu/config-file.h"
#include "qemu/option.h"
#include "block/qapi.h"
#include "crypto/init.h"
#include "trace/control.h"
-#include <getopt.h>
-#define QEMU_IMG_VERSION "qemu-img version " QEMU_VERSION QEMU_PKGVERSION \
+#define QEMU_IMG_VERSION "qemu-img version " QEMU_FULL_VERSION \
"\n" QEMU_COPYRIGHT "\n"
typedef struct img_cmd_t {
OPTION_TARGET_IMAGE_OPTS = 263,
OPTION_SIZE = 264,
OPTION_PREALLOCATION = 265,
+ OPTION_SHRINK = 266,
};
typedef enum OutputFormat {
" " arg_string "\n"
#include "qemu-img-cmds.h"
#undef DEF
-#undef GEN_DOCS
"\n"
"Command parameters:\n"
" 'filename' is a disk image filename\n"
" 'snapshot_param' is param used for internal snapshot, format\n"
" is 'snapshot.id=[ID],snapshot.name=[NAME]', or\n"
" '[ID_OR_NAME]'\n"
- " 'snapshot_id_or_name' is deprecated, use 'snapshot_param'\n"
- " instead\n"
" '-c' indicates that target image must be compressed (qcow format only)\n"
" '-u' allows unsafe backing chains. For rebasing, it is assumed that old and\n"
" new backing file match exactly. The image doesn't need a working\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);
- printf("\n");
+ bdrv_iterate_format(format_print, NULL, false);
+ printf("\n\n" QEMU_HELP_BOTTOM "\n");
exit(EXIT_SUCCESS);
}
return 1;
}
+ if (!drv->create_opts) {
+ error_report("Format driver '%s' does not support image creation", fmt);
+ return 1;
+ }
+
create_opts = qemu_opts_append(create_opts, drv->create_opts);
if (filename) {
proto_drv = bdrv_find_protocol(filename, true, &local_err);
qemu_opts_free(create_opts);
return 1;
}
+ if (!proto_drv->create_opts) {
+ error_report("Protocol driver '%s' does not support image creation",
+ proto_drv->format_name);
+ qemu_opts_free(create_opts);
+ return 1;
+ }
create_opts = qemu_opts_append(create_opts, proto_drv->create_opts);
}
- qemu_opts_print_help(create_opts);
+ printf("Supported options:\n");
+ qemu_opts_print_help(create_opts, false);
qemu_opts_free(create_opts);
return 0;
}
options = qemu_opts_to_qdict(opts, NULL);
if (force_share) {
if (qdict_haskey(options, BDRV_OPT_FORCE_SHARE)
- && !qdict_get_bool(options, BDRV_OPT_FORCE_SHARE)) {
+ && strcmp(qdict_get_str(options, BDRV_OPT_FORCE_SHARE), "on")) {
error_report("--force-share/-U conflicts with image options");
- QDECREF(options);
+ qobject_unref(options);
return NULL;
}
- qdict_put_bool(options, BDRV_OPT_FORCE_SHARE, true);
+ qdict_put_str(options, BDRV_OPT_FORCE_SHARE, "on");
}
blk = blk_new_open(NULL, NULL, options, flags, &local_err);
if (!blk) {
return 0;
}
-static BlockBackend *img_open_new_file(const char *filename,
- QemuOpts *create_opts,
- const char *fmt, int flags,
- bool writethrough, bool quiet,
- bool force_share)
-{
- QDict *options = NULL;
-
- options = qdict_new();
- qemu_opt_foreach(create_opts, img_add_key_secrets, options, &error_abort);
-
- return img_open_file(filename, options, fmt, flags, writethrough, quiet,
- force_share);
-}
-
static BlockBackend *img_open(bool image_opts,
const char *filename,
if (qemu_opts_foreach(&qemu_object_opts,
user_creatable_add_opts_foreach,
- NULL, NULL)) {
+ NULL, &error_fatal)) {
goto fail;
}
str = qobject_to_json_pretty(obj);
assert(str != NULL);
qprintf(quiet, "%s\n", qstring_get_str(str));
- qobject_decref(obj);
+ qobject_unref(obj);
visit_free(v);
- QDECREF(str);
+ qobject_unref(str);
}
static void dump_human_image_check(ImageCheck *check, bool quiet)
if (qemu_opts_foreach(&qemu_object_opts,
user_creatable_add_opts_foreach,
- NULL, NULL)) {
+ NULL, &error_fatal)) {
return 1;
}
int ret = 0;
aio_context_acquire(aio_context);
- block_job_ref(job);
+ job_ref(&job->job);
do {
+ float progress = 0.0f;
aio_poll(aio_context, true);
- qemu_progress_print(job->len ?
- ((float)job->offset / job->len * 100.f) : 0.0f, 0);
- } while (!job->ready && !job->completed);
+ if (job->job.progress_total) {
+ progress = (float)job->job.progress_current /
+ job->job.progress_total * 100.f;
+ }
+ qemu_progress_print(progress, 0);
+ } while (!job_is_ready(&job->job) && !job_is_completed(&job->job));
- if (!job->completed) {
- ret = block_job_complete_sync(job, errp);
+ if (!job_is_completed(&job->job)) {
+ ret = job_complete_sync(&job->job, errp);
} else {
- ret = job->ret;
+ ret = job->job.ret;
}
- block_job_unref(job);
+ job_unref(&job->job);
aio_context_release(aio_context);
/* publish completion progress only when success */
if (qemu_opts_foreach(&qemu_object_opts,
user_creatable_add_opts_foreach,
- NULL, NULL)) {
+ NULL, &error_fatal)) {
return 1;
}
aio_context = bdrv_get_aio_context(bs);
aio_context_acquire(aio_context);
- commit_active_start("commit", bs, base_bs, BLOCK_JOB_DEFAULT, 0,
+ commit_active_start("commit", bs, base_bs, JOB_DEFAULT, 0,
BLOCKDEV_ON_ERROR_REPORT, NULL, common_block_job_cb,
&cbi, false, &local_err);
aio_context_release(aio_context);
}
job = block_job_get("commit");
+ assert(job);
run_block_job(job, &local_err);
if (local_err) {
goto unref_backing;
return 0;
}
+/*
+ * Returns -1 if 'buf' contains only zeroes, otherwise the byte index
+ * of the first sector boundary within buf where the sector contains a
+ * non-zero byte. This function is robust to a buffer that is not
+ * sector-aligned.
+ */
+static int64_t find_nonzero(const uint8_t *buf, int64_t n)
+{
+ int64_t i;
+ int64_t end = QEMU_ALIGN_DOWN(n, BDRV_SECTOR_SIZE);
+
+ for (i = 0; i < end; i += BDRV_SECTOR_SIZE) {
+ if (!buffer_is_zero(buf + i, BDRV_SECTOR_SIZE)) {
+ return i;
+ }
+ }
+ if (i < n && !buffer_is_zero(buf + i, n - end)) {
+ return i;
+ }
+ return -1;
+}
+
/*
* Returns true iff the first sector pointed to by 'buf' contains at least
* a non-NUL byte.
*
* 'pnum' is set to the number of sectors (including and immediately following
* the first one) that are known to be in the same allocated/unallocated state.
+ * The function will try to align the end offset to alignment boundaries so
+ * that the request will at least end aligned and consequtive requests will
+ * also start at an aligned offset.
*/
-static int is_allocated_sectors(const uint8_t *buf, int n, int *pnum)
+static int is_allocated_sectors(const uint8_t *buf, int n, int *pnum,
+ int64_t sector_num, int alignment)
{
bool is_zero;
- int i;
+ int i, tail;
if (n <= 0) {
*pnum = 0;
break;
}
}
+
+ tail = (sector_num + i) & (alignment - 1);
+ if (tail) {
+ if (is_zero && i <= tail) {
+ /* treat unallocated areas which only consist
+ * of a small tail as allocated. */
+ is_zero = false;
+ }
+ if (!is_zero) {
+ /* align up end offset of allocated areas. */
+ i += alignment - tail;
+ i = MIN(i, n);
+ } else {
+ /* align down end offset of zero areas. */
+ i -= tail;
+ }
+ }
*pnum = i;
return !is_zero;
}
* breaking up write requests for only small sparse areas.
*/
static int is_allocated_sectors_min(const uint8_t *buf, int n, int *pnum,
- int min)
+ int min, int64_t sector_num, int alignment)
{
int ret;
int num_checked, num_used;
min = n;
}
- ret = is_allocated_sectors(buf, n, pnum);
+ ret = is_allocated_sectors(buf, n, pnum, sector_num, alignment);
if (!ret) {
return ret;
}
num_used = *pnum;
buf += BDRV_SECTOR_SIZE * *pnum;
n -= *pnum;
+ sector_num += *pnum;
num_checked = num_used;
while (n > 0) {
- ret = is_allocated_sectors(buf, n, pnum);
+ ret = is_allocated_sectors(buf, n, pnum, sector_num, alignment);
buf += BDRV_SECTOR_SIZE * *pnum;
n -= *pnum;
+ sector_num += *pnum;
num_checked += *pnum;
if (ret) {
num_used = num_checked;
}
/*
- * Compares two buffers sector by sector. Returns 0 if the first sector of both
- * buffers matches, non-zero otherwise.
+ * Compares two buffers sector by sector. Returns 0 if the first
+ * sector of each buffer matches, non-zero otherwise.
*
- * pnum is set to the number of sectors (including and immediately following
- * the first one) that are known to have the same comparison result
+ * pnum is set to the sector-aligned size of the buffer prefix that
+ * has the same matching status as the first sector.
*/
-static int compare_sectors(const uint8_t *buf1, const uint8_t *buf2, int n,
- int *pnum)
+static int compare_buffers(const uint8_t *buf1, const uint8_t *buf2,
+ int64_t bytes, int64_t *pnum)
{
bool res;
- int i;
+ int64_t i = MIN(bytes, BDRV_SECTOR_SIZE);
- if (n <= 0) {
- *pnum = 0;
- return 0;
- }
+ assert(bytes > 0);
- res = !!memcmp(buf1, buf2, 512);
- for(i = 1; i < n; i++) {
- buf1 += 512;
- buf2 += 512;
+ res = !!memcmp(buf1, buf2, i);
+ while (i < bytes) {
+ int64_t len = MIN(bytes - i, BDRV_SECTOR_SIZE);
- if (!!memcmp(buf1, buf2, 512) != res) {
+ if (!!memcmp(buf1 + i, buf2 + i, len) != res) {
break;
}
+ i += len;
}
*pnum = i;
#define IO_BUF_SIZE (2 * 1024 * 1024)
-static int64_t sectors_to_bytes(int64_t sectors)
-{
- return sectors << BDRV_SECTOR_BITS;
-}
-
-static int64_t sectors_to_process(int64_t total, int64_t from)
-{
- return MIN(total - from, IO_BUF_SIZE >> BDRV_SECTOR_BITS);
-}
-
/*
* Check if passed sectors are empty (not allocated or contain only 0 bytes)
*
- * Returns 0 in case sectors are filled with 0, 1 if sectors contain non-zero
- * data and negative value on error.
+ * Intended for use by 'qemu-img compare': Returns 0 in case sectors are
+ * filled with 0, 1 if sectors contain non-zero data (this is a comparison
+ * failure), and 4 on error (the exit status for read errors), after emitting
+ * an error message.
*
* @param blk: BlockBackend for the image
- * @param sect_num: Number of first sector to check
- * @param sect_count: Number of sectors to check
+ * @param offset: Starting offset to check
+ * @param bytes: Number of bytes to check
* @param filename: Name of disk file we are checking (logging purpose)
* @param buffer: Allocated buffer for storing read data
* @param quiet: Flag for quiet mode
*/
-static int check_empty_sectors(BlockBackend *blk, int64_t sect_num,
- int sect_count, const char *filename,
+static int check_empty_sectors(BlockBackend *blk, int64_t offset,
+ int64_t bytes, const char *filename,
uint8_t *buffer, bool quiet)
{
- int pnum, ret = 0;
- ret = blk_pread(blk, sect_num << BDRV_SECTOR_BITS, buffer,
- sect_count << BDRV_SECTOR_BITS);
+ int ret = 0;
+ int64_t idx;
+
+ ret = blk_pread(blk, offset, buffer, bytes);
if (ret < 0) {
error_report("Error while reading offset %" PRId64 " of %s: %s",
- sectors_to_bytes(sect_num), filename, strerror(-ret));
- return ret;
+ offset, filename, strerror(-ret));
+ return 4;
}
- ret = is_allocated_sectors(buffer, sect_count, &pnum);
- if (ret || pnum != sect_count) {
+ idx = find_nonzero(buffer, bytes);
+ if (idx >= 0) {
qprintf(quiet, "Content mismatch at offset %" PRId64 "!\n",
- sectors_to_bytes(ret ? sect_num : sect_num + pnum));
+ offset + idx);
return 1;
}
const char *fmt1 = NULL, *fmt2 = NULL, *cache, *filename1, *filename2;
BlockBackend *blk1, *blk2;
BlockDriverState *bs1, *bs2;
- int64_t total_sectors1, total_sectors2;
+ int64_t total_size1, total_size2;
uint8_t *buf1 = NULL, *buf2 = NULL;
- int pnum1, pnum2;
+ int64_t pnum1, pnum2;
int allocated1, allocated2;
int ret = 0; /* return value - 0 Ident, 1 Different, >1 Error */
bool progress = false, quiet = false, strict = false;
int flags;
bool writethrough;
- int64_t total_sectors;
- int64_t sector_num = 0;
- int64_t nb_sectors;
- int c, pnum;
+ int64_t total_size;
+ int64_t offset = 0;
+ int64_t chunk;
+ int c;
uint64_t progress_base;
bool image_opts = false;
bool force_share = false;
if (qemu_opts_foreach(&qemu_object_opts,
user_creatable_add_opts_foreach,
- NULL, NULL)) {
+ NULL, &error_fatal)) {
ret = 2;
goto out4;
}
buf1 = blk_blockalign(blk1, IO_BUF_SIZE);
buf2 = blk_blockalign(blk2, IO_BUF_SIZE);
- total_sectors1 = blk_nb_sectors(blk1);
- if (total_sectors1 < 0) {
+ total_size1 = blk_getlength(blk1);
+ if (total_size1 < 0) {
error_report("Can't get size of %s: %s",
- filename1, strerror(-total_sectors1));
+ filename1, strerror(-total_size1));
ret = 4;
goto out;
}
- total_sectors2 = blk_nb_sectors(blk2);
- if (total_sectors2 < 0) {
+ total_size2 = blk_getlength(blk2);
+ if (total_size2 < 0) {
error_report("Can't get size of %s: %s",
- filename2, strerror(-total_sectors2));
+ filename2, strerror(-total_size2));
ret = 4;
goto out;
}
- total_sectors = MIN(total_sectors1, total_sectors2);
- progress_base = MAX(total_sectors1, total_sectors2);
+ total_size = MIN(total_size1, total_size2);
+ progress_base = MAX(total_size1, total_size2);
qemu_progress_print(0, 100);
- if (strict && total_sectors1 != total_sectors2) {
+ if (strict && total_size1 != total_size2) {
ret = 1;
qprintf(quiet, "Strict mode: Image size mismatch!\n");
goto out;
}
- for (;;) {
- int64_t status1, status2;
- BlockDriverState *file;
+ while (offset < total_size) {
+ int status1, status2;
- nb_sectors = sectors_to_process(total_sectors, sector_num);
- if (nb_sectors <= 0) {
- break;
- }
- status1 = bdrv_get_block_status_above(bs1, NULL, sector_num,
- total_sectors1 - sector_num,
- &pnum1, &file);
+ status1 = bdrv_block_status_above(bs1, NULL, offset,
+ total_size1 - offset, &pnum1, NULL,
+ NULL);
if (status1 < 0) {
ret = 3;
error_report("Sector allocation test failed for %s", filename1);
}
allocated1 = status1 & BDRV_BLOCK_ALLOCATED;
- status2 = bdrv_get_block_status_above(bs2, NULL, sector_num,
- total_sectors2 - sector_num,
- &pnum2, &file);
+ status2 = bdrv_block_status_above(bs2, NULL, offset,
+ total_size2 - offset, &pnum2, NULL,
+ NULL);
if (status2 < 0) {
ret = 3;
error_report("Sector allocation test failed for %s", filename2);
goto out;
}
allocated2 = status2 & BDRV_BLOCK_ALLOCATED;
- if (pnum1) {
- nb_sectors = MIN(nb_sectors, pnum1);
- }
- if (pnum2) {
- nb_sectors = MIN(nb_sectors, pnum2);
- }
+
+ assert(pnum1 && pnum2);
+ chunk = MIN(pnum1, pnum2);
if (strict) {
- if ((status1 & ~BDRV_BLOCK_OFFSET_MASK) !=
- (status2 & ~BDRV_BLOCK_OFFSET_MASK)) {
+ if (status1 != status2) {
ret = 1;
qprintf(quiet, "Strict mode: Offset %" PRId64
- " block status mismatch!\n",
- sectors_to_bytes(sector_num));
+ " block status mismatch!\n", offset);
goto out;
}
}
if ((status1 & BDRV_BLOCK_ZERO) && (status2 & BDRV_BLOCK_ZERO)) {
- nb_sectors = MIN(pnum1, pnum2);
+ /* nothing to do */
} else if (allocated1 == allocated2) {
if (allocated1) {
- ret = blk_pread(blk1, sector_num << BDRV_SECTOR_BITS, buf1,
- nb_sectors << BDRV_SECTOR_BITS);
+ int64_t pnum;
+
+ chunk = MIN(chunk, IO_BUF_SIZE);
+ ret = blk_pread(blk1, offset, buf1, chunk);
if (ret < 0) {
- error_report("Error while reading offset %" PRId64 " of %s:"
- " %s", sectors_to_bytes(sector_num), filename1,
- strerror(-ret));
+ error_report("Error while reading offset %" PRId64
+ " of %s: %s",
+ offset, filename1, strerror(-ret));
ret = 4;
goto out;
}
- ret = blk_pread(blk2, sector_num << BDRV_SECTOR_BITS, buf2,
- nb_sectors << BDRV_SECTOR_BITS);
+ ret = blk_pread(blk2, offset, buf2, chunk);
if (ret < 0) {
error_report("Error while reading offset %" PRId64
- " of %s: %s", sectors_to_bytes(sector_num),
- filename2, strerror(-ret));
+ " of %s: %s",
+ offset, filename2, strerror(-ret));
ret = 4;
goto out;
}
- ret = compare_sectors(buf1, buf2, nb_sectors, &pnum);
- if (ret || pnum != nb_sectors) {
+ ret = compare_buffers(buf1, buf2, chunk, &pnum);
+ if (ret || pnum != chunk) {
qprintf(quiet, "Content mismatch at offset %" PRId64 "!\n",
- sectors_to_bytes(
- ret ? sector_num : sector_num + pnum));
+ offset + (ret ? 0 : pnum));
ret = 1;
goto out;
}
}
} else {
-
+ chunk = MIN(chunk, IO_BUF_SIZE);
if (allocated1) {
- ret = check_empty_sectors(blk1, sector_num, nb_sectors,
+ ret = check_empty_sectors(blk1, offset, chunk,
filename1, buf1, quiet);
} else {
- ret = check_empty_sectors(blk2, sector_num, nb_sectors,
+ ret = check_empty_sectors(blk2, offset, chunk,
filename2, buf1, quiet);
}
if (ret) {
- if (ret < 0) {
- error_report("Error while reading offset %" PRId64 ": %s",
- sectors_to_bytes(sector_num), strerror(-ret));
- ret = 4;
- }
goto out;
}
}
- sector_num += nb_sectors;
- qemu_progress_print(((float) nb_sectors / progress_base)*100, 100);
+ offset += chunk;
+ qemu_progress_print(((float) chunk / progress_base) * 100, 100);
}
- if (total_sectors1 != total_sectors2) {
+ if (total_size1 != total_size2) {
BlockBackend *blk_over;
- int64_t total_sectors_over;
const char *filename_over;
qprintf(quiet, "Warning: Image size mismatch!\n");
- if (total_sectors1 > total_sectors2) {
- total_sectors_over = total_sectors1;
+ if (total_size1 > total_size2) {
blk_over = blk1;
filename_over = filename1;
} else {
- total_sectors_over = total_sectors2;
blk_over = blk2;
filename_over = filename2;
}
- for (;;) {
- int64_t count;
-
- nb_sectors = sectors_to_process(total_sectors_over, sector_num);
- if (nb_sectors <= 0) {
- break;
- }
- ret = bdrv_is_allocated_above(blk_bs(blk_over), NULL,
- sector_num * BDRV_SECTOR_SIZE,
- nb_sectors * BDRV_SECTOR_SIZE,
- &count);
+ while (offset < progress_base) {
+ ret = bdrv_block_status_above(blk_bs(blk_over), NULL, offset,
+ progress_base - offset, &chunk,
+ NULL, NULL);
if (ret < 0) {
ret = 3;
error_report("Sector allocation test failed for %s",
goto out;
}
- /* TODO relax this once bdrv_is_allocated_above does not enforce
- * sector alignment */
- assert(QEMU_IS_ALIGNED(count, BDRV_SECTOR_SIZE));
- nb_sectors = count >> BDRV_SECTOR_BITS;
- if (ret) {
- ret = check_empty_sectors(blk_over, sector_num, nb_sectors,
+ if (ret & BDRV_BLOCK_ALLOCATED && !(ret & BDRV_BLOCK_ZERO)) {
+ chunk = MIN(chunk, IO_BUF_SIZE);
+ ret = check_empty_sectors(blk_over, offset, chunk,
filename_over, buf1, quiet);
if (ret) {
- if (ret < 0) {
- error_report("Error while reading offset %" PRId64
- " of %s: %s", sectors_to_bytes(sector_num),
- filename_over, strerror(-ret));
- ret = 4;
- }
goto out;
}
}
- sector_num += nb_sectors;
- qemu_progress_print(((float) nb_sectors / progress_base)*100, 100);
+ offset += chunk;
+ qemu_progress_print(((float) chunk / progress_base) * 100, 100);
}
}
BlockBackend *target;
bool has_zero_init;
bool compressed;
+ bool unallocated_blocks_are_zero;
bool target_has_backing;
+ int64_t target_backing_sectors; /* negative if unknown */
bool wr_in_order;
+ bool copy_range;
int min_sparse;
+ int alignment;
size_t cluster_sectors;
size_t buf_sectors;
long num_coroutines;
static int convert_iteration_sectors(ImgConvertState *s, int64_t sector_num)
{
- int64_t ret, src_cur_offset;
- int n, src_cur;
+ int64_t src_cur_offset;
+ int ret, n, src_cur;
+ bool post_backing_zero = false;
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->target_backing_sectors >= 0) {
+ if (sector_num >= s->target_backing_sectors) {
+ post_backing_zero = s->unallocated_blocks_are_zero;
+ } else if (sector_num + n > s->target_backing_sectors) {
+ /* Split requests around target_backing_sectors (because
+ * starting from there, zeros are handled differently) */
+ n = s->target_backing_sectors - sector_num;
+ }
+ }
+
if (s->sector_next_status <= sector_num) {
- BlockDriverState *file;
+ int64_t count = n * BDRV_SECTOR_SIZE;
+
if (s->target_has_backing) {
- ret = bdrv_get_block_status(blk_bs(s->src[src_cur]),
- sector_num - src_cur_offset,
- n, &n, &file);
+
+ ret = bdrv_block_status(blk_bs(s->src[src_cur]),
+ (sector_num - src_cur_offset) *
+ BDRV_SECTOR_SIZE,
+ count, &count, NULL, NULL);
} else {
- ret = bdrv_get_block_status_above(blk_bs(s->src[src_cur]), NULL,
- sector_num - src_cur_offset,
- n, &n, &file);
+ ret = bdrv_block_status_above(blk_bs(s->src[src_cur]), NULL,
+ (sector_num - src_cur_offset) *
+ BDRV_SECTOR_SIZE,
+ count, &count, NULL, NULL);
}
if (ret < 0) {
+ error_report("error while reading block status of sector %" PRId64
+ ": %s", sector_num, strerror(-ret));
return ret;
}
+ n = DIV_ROUND_UP(count, BDRV_SECTOR_SIZE);
if (ret & BDRV_BLOCK_ZERO) {
- s->status = BLK_ZERO;
+ s->status = post_backing_zero ? BLK_BACKING_FILE : BLK_ZERO;
} else if (ret & BDRV_BLOCK_DATA) {
s->status = BLK_DATA;
} else {
{
int n, ret;
QEMUIOVector qiov;
- struct iovec iov;
assert(nb_sectors <= s->buf_sectors);
while (nb_sectors > 0) {
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);
+ qemu_iovec_init_buf(&qiov, buf, n << BDRV_SECTOR_BITS);
ret = blk_co_preadv(
blk, (sector_num - src_cur_offset) << BDRV_SECTOR_BITS,
{
int ret;
QEMUIOVector qiov;
- struct iovec iov;
while (nb_sectors > 0) {
int n = nb_sectors;
* zeroed. */
if (!s->min_sparse ||
(!s->compressed &&
- is_allocated_sectors_min(buf, n, &n, s->min_sparse)) ||
+ is_allocated_sectors_min(buf, n, &n, s->min_sparse,
+ sector_num, s->alignment)) ||
(s->compressed &&
!buffer_is_zero(buf, n * BDRV_SECTOR_SIZE)))
{
- iov.iov_base = buf;
- iov.iov_len = n << BDRV_SECTOR_BITS;
- qemu_iovec_init_external(&qiov, &iov, 1);
+ qemu_iovec_init_buf(&qiov, buf, n << BDRV_SECTOR_BITS);
ret = blk_co_pwritev(s->target, sector_num << BDRV_SECTOR_BITS,
n << BDRV_SECTOR_BITS, &qiov, flags);
}
ret = blk_co_pwrite_zeroes(s->target,
sector_num << BDRV_SECTOR_BITS,
- n << BDRV_SECTOR_BITS, 0);
+ n << BDRV_SECTOR_BITS,
+ BDRV_REQ_MAY_UNMAP);
if (ret < 0) {
return ret;
}
return 0;
}
+static int coroutine_fn convert_co_copy_range(ImgConvertState *s, int64_t sector_num,
+ int nb_sectors)
+{
+ int n, ret;
+
+ while (nb_sectors > 0) {
+ BlockBackend *blk;
+ int src_cur;
+ int64_t bs_sectors, src_cur_offset;
+ int64_t offset;
+
+ convert_select_part(s, sector_num, &src_cur, &src_cur_offset);
+ offset = (sector_num - src_cur_offset) << BDRV_SECTOR_BITS;
+ blk = s->src[src_cur];
+ bs_sectors = s->src_sectors[src_cur];
+
+ n = MIN(nb_sectors, bs_sectors - (sector_num - src_cur_offset));
+
+ ret = blk_co_copy_range(blk, offset, s->target,
+ sector_num << BDRV_SECTOR_BITS,
+ n << BDRV_SECTOR_BITS, 0, 0);
+ if (ret < 0) {
+ return ret;
+ }
+
+ sector_num += n;
+ nb_sectors -= n;
+ }
+ return 0;
+}
+
static void coroutine_fn convert_co_do_copy(void *opaque)
{
ImgConvertState *s = opaque;
int n;
int64_t sector_num;
enum ImgConvertBlockStatus status;
+ bool copy_range;
qemu_co_mutex_lock(&s->lock);
if (s->ret != -EINPROGRESS || s->sector_num >= s->total_sectors) {
s->allocated_sectors, 0);
}
- if (status == BLK_DATA) {
+retry:
+ copy_range = s->copy_range && s->status == BLK_DATA;
+ if (status == BLK_DATA && !copy_range) {
ret = convert_co_read(s, sector_num, n, buf);
if (ret < 0) {
error_report("error while reading sector %" PRId64
}
if (s->ret == -EINPROGRESS) {
- ret = convert_co_write(s, sector_num, n, buf, status);
+ if (copy_range) {
+ ret = convert_co_copy_range(s, sector_num, n);
+ if (ret) {
+ s->copy_range = false;
+ goto retry;
+ }
+ } else {
+ 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));
if (!s->has_zero_init && !s->target_has_backing &&
bdrv_can_write_zeroes_with_unmap(blk_bs(s->target)))
{
- ret = blk_make_zero(s->target, BDRV_REQ_MAY_UNMAP);
+ ret = blk_make_zero(s->target, BDRV_REQ_MAY_UNMAP | BDRV_REQ_NO_FALLBACK);
if (ret == 0) {
s->has_zero_init = true;
}
return s->ret;
}
+#define MAX_BUF_SECTORS 32768
+
static int img_convert(int argc, char **argv)
{
int c, bs_i, flags, src_flags = 0;
BlockDriverState *out_bs;
QemuOpts *opts = NULL, *sn_opts = NULL;
QemuOptsList *create_opts = NULL;
+ QDict *open_opts = NULL;
char *options = NULL;
Error *local_err = NULL;
bool writethrough, src_writethrough, quiet = false, image_opts = false,
skip_create = false, progress = false, tgt_image_opts = false;
int64_t ret = -EINVAL;
bool force_share = false;
+ bool explict_min_sparse = false;
ImgConvertState s = (ImgConvertState) {
/* Need at least 4k of zeros for sparse detection */
.min_sparse = 8,
+ .copy_range = false,
.buf_sectors = IO_BUF_SIZE / BDRV_SECTOR_SIZE,
.wr_in_order = true,
.num_coroutines = 8,
{"target-image-opts", no_argument, 0, OPTION_TARGET_IMAGE_OPTS},
{0, 0, 0, 0}
};
- c = getopt_long(argc, argv, ":hf:O:B:co:s:l:S:pt:T:qnm:WU",
+ c = getopt_long(argc, argv, ":hf:O:B:Cco:l:S:pt:T:qnm:WU",
long_options, NULL);
if (c == -1) {
break;
case 'B':
out_baseimg = optarg;
break;
+ case 'C':
+ s.copy_range = true;
+ break;
case 'c':
s.compressed = true;
break;
g_free(old_options);
}
break;
- case 's':
- snapshot_name = optarg;
- break;
case 'l':
if (strstart(optarg, SNAPSHOT_OPT_BASE, NULL)) {
sn_opts = qemu_opts_parse_noisily(&internal_snapshot_opts,
int64_t sval;
sval = cvtnum(optarg);
- if (sval < 0) {
- error_report("Invalid minimum zero buffer size for sparse output specified");
+ if (sval < 0 || sval & (BDRV_SECTOR_SIZE - 1) ||
+ sval / BDRV_SECTOR_SIZE > MAX_BUF_SECTORS) {
+ error_report("Invalid buffer size for sparse output specified. "
+ "Valid sizes are multiples of %llu up to %llu. Select "
+ "0 to disable sparse detection (fully allocates output).",
+ BDRV_SECTOR_SIZE, MAX_BUF_SECTORS * BDRV_SECTOR_SIZE);
goto fail_getopt;
}
s.min_sparse = sval / BDRV_SECTOR_SIZE;
+ explict_min_sparse = true;
break;
}
case 'p':
if (qemu_opts_foreach(&qemu_object_opts,
user_creatable_add_opts_foreach,
- NULL, NULL)) {
+ NULL, &error_fatal)) {
goto fail_getopt;
}
- if (!s.wr_in_order && s.compressed) {
- error_report("Out of order write and compress are mutually exclusive");
+ if (s.compressed && s.copy_range) {
+ error_report("Cannot enable copy offloading when -c is used");
+ goto fail_getopt;
+ }
+
+ if (explict_min_sparse && s.copy_range) {
+ error_report("Cannot enable copy offloading when -S is used");
goto fail_getopt;
}
}
}
+ /*
+ * The later open call will need any decryption secrets, and
+ * bdrv_create() will purge "opts", so extract them now before
+ * they are lost.
+ */
+ if (!skip_create) {
+ open_opts = qdict_new();
+ qemu_opt_foreach(opts, img_add_key_secrets, open_opts, &error_abort);
+ }
+
if (!skip_create) {
/* Create the new image */
ret = bdrv_create(drv, out_filename, opts, &local_err);
* That has to wait for bdrv_create to be improved
* to allow filenames in option syntax
*/
- s.target = img_open_new_file(out_filename, opts, out_fmt,
- flags, writethrough, quiet, false);
+ s.target = img_open_file(out_filename, open_opts, out_fmt,
+ flags, writethrough, quiet, false);
+ open_opts = NULL; /* blk_new_open will have freed it */
}
if (!s.target) {
ret = -1;
}
/* increase bufsectors from the default 4096 (2M) if opt_transfer
- * or discard_alignment of the out_bs is greater. Limit to 32768 (16MB)
- * as maximum. */
- s.buf_sectors = MIN(32768,
+ * or discard_alignment of the out_bs is greater. Limit to
+ * MAX_BUF_SECTORS as maximum which is currently 32768 (16MB). */
+ s.buf_sectors = MIN(MAX_BUF_SECTORS,
MAX(s.buf_sectors,
MAX(out_bs->bl.opt_transfer >> BDRV_SECTOR_BITS,
out_bs->bl.pdiscard_alignment >>
BDRV_SECTOR_BITS)));
+ /* try to align the write requests to the destination to avoid unnecessary
+ * RMW cycles. */
+ s.alignment = MAX(pow2floor(s.min_sparse),
+ DIV_ROUND_UP(out_bs->bl.request_alignment,
+ BDRV_SECTOR_SIZE));
+ assert(is_power_of_2(s.alignment));
+
if (skip_create) {
int64_t output_sectors = blk_nb_sectors(s.target);
if (output_sectors < 0) {
}
}
+ if (s.target_has_backing) {
+ /* Errors are treated as "backing length unknown" (which means
+ * s.target_backing_sectors has to be negative, which it will
+ * be automatically). The backing file length is used only
+ * for optimizations, so such a case is not fatal. */
+ s.target_backing_sectors = bdrv_nb_sectors(out_bs->backing->bs);
+ } else {
+ s.target_backing_sectors = -1;
+ }
+
ret = bdrv_get_info(out_bs, &bdi);
if (ret < 0) {
if (s.compressed) {
} else {
s.compressed = s.compressed || bdi.needs_compressed_writes;
s.cluster_sectors = bdi.cluster_size / BDRV_SECTOR_SIZE;
+ s.unallocated_blocks_are_zero = bdi.unallocated_blocks_are_zero;
}
ret = convert_do_copy(&s);
qemu_opts_del(opts);
qemu_opts_free(create_opts);
qemu_opts_del(sn_opts);
+ qobject_unref(open_opts);
blk_unref(s.target);
if (s.src) {
for (bs_i = 0; bs_i < s.src_num; bs_i++) {
str = qobject_to_json_pretty(obj);
assert(str != NULL);
printf("%s\n", qstring_get_str(str));
- qobject_decref(obj);
+ qobject_unref(obj);
visit_free(v);
- QDECREF(str);
+ qobject_unref(str);
}
static void dump_json_image_info(ImageInfo *info)
str = qobject_to_json_pretty(obj);
assert(str != NULL);
printf("%s\n", qstring_get_str(str));
- qobject_decref(obj);
+ qobject_unref(obj);
visit_free(v);
- QDECREF(str);
+ qobject_unref(str);
}
static void dump_human_image_info_list(ImageInfoList *list)
if (qemu_opts_foreach(&qemu_object_opts,
user_creatable_add_opts_foreach,
- NULL, NULL)) {
+ NULL, &error_fatal)) {
return 1;
}
return 0;
}
-static void dump_map_entry(OutputFormat output_format, MapEntry *e,
- MapEntry *next)
+static int dump_map_entry(OutputFormat output_format, MapEntry *e,
+ MapEntry *next)
{
switch (output_format) {
case OFORMAT_HUMAN:
if (e->data && !e->has_offset) {
error_report("File contains external, encrypted or compressed clusters.");
- exit(1);
+ return -1;
}
if (e->data && !e->zero) {
printf("%#-16"PRIx64"%#-16"PRIx64"%#-16"PRIx64"%s\n",
}
break;
}
+ return 0;
}
-static int get_block_status(BlockDriverState *bs, int64_t sector_num,
- int nb_sectors, MapEntry *e)
+static int get_block_status(BlockDriverState *bs, int64_t offset,
+ int64_t bytes, MapEntry *e)
{
- int64_t ret;
+ int ret;
int depth;
BlockDriverState *file;
bool has_offset;
+ int64_t map;
+ char *filename = NULL;
/* As an optimization, we could cache the current range of unallocated
* clusters in each file of the chain, and avoid querying the same
depth = 0;
for (;;) {
- ret = bdrv_get_block_status(bs, sector_num, nb_sectors, &nb_sectors,
- &file);
+ ret = bdrv_block_status(bs, offset, bytes, &bytes, &map, &file);
if (ret < 0) {
return ret;
}
- assert(nb_sectors);
+ assert(bytes);
if (ret & (BDRV_BLOCK_ZERO|BDRV_BLOCK_DATA)) {
break;
}
has_offset = !!(ret & BDRV_BLOCK_OFFSET_VALID);
+ if (file && has_offset) {
+ bdrv_refresh_filename(file);
+ filename = file->filename;
+ }
+
*e = (MapEntry) {
- .start = sector_num * BDRV_SECTOR_SIZE,
- .length = nb_sectors * BDRV_SECTOR_SIZE,
+ .start = offset,
+ .length = bytes,
.data = !!(ret & BDRV_BLOCK_DATA),
.zero = !!(ret & BDRV_BLOCK_ZERO),
- .offset = ret & BDRV_BLOCK_OFFSET_MASK,
+ .offset = map,
.has_offset = has_offset,
.depth = depth,
- .has_filename = file && has_offset,
- .filename = file && has_offset ? file->filename : NULL,
+ .has_filename = filename,
+ .filename = filename,
};
return 0;
if (qemu_opts_foreach(&qemu_object_opts,
user_creatable_add_opts_foreach,
- NULL, NULL)) {
+ NULL, &error_fatal)) {
return 1;
}
length = blk_getlength(blk);
while (curr.start + curr.length < length) {
- int64_t nsectors_left;
- int64_t sector_num;
- int n;
-
- sector_num = (curr.start + curr.length) >> BDRV_SECTOR_BITS;
+ int64_t offset = curr.start + curr.length;
+ int64_t n;
/* Probe up to 1 GiB at a time. */
- nsectors_left = DIV_ROUND_UP(length, BDRV_SECTOR_SIZE) - sector_num;
- n = MIN(1 << (30 - BDRV_SECTOR_BITS), nsectors_left);
- ret = get_block_status(bs, sector_num, n, &next);
+ n = MIN(1 << 30, length - offset);
+ ret = get_block_status(bs, offset, n, &next);
if (ret < 0) {
error_report("Could not read file metadata: %s", strerror(-ret));
}
if (curr.length > 0) {
- dump_map_entry(output_format, &curr, &next);
+ ret = dump_map_entry(output_format, &curr, &next);
+ if (ret < 0) {
+ goto out;
+ }
}
curr = next;
}
- dump_map_entry(output_format, &curr, NULL);
+ ret = dump_map_entry(output_format, &curr, NULL);
out:
blk_unref(blk);
if (qemu_opts_foreach(&qemu_object_opts,
user_creatable_add_opts_foreach,
- NULL, NULL)) {
+ NULL, &error_fatal)) {
return 1;
}
break;
case SNAPSHOT_APPLY:
- ret = bdrv_snapshot_goto(bs, snapshot_name);
+ ret = bdrv_snapshot_goto(bs, snapshot_name, &err);
if (ret) {
- error_report("Could not apply snapshot '%s': %d (%s)",
- snapshot_name, ret, strerror(-ret));
+ error_reportf_err(err, "Could not apply snapshot '%s': ",
+ snapshot_name);
}
break;
case SNAPSHOT_DELETE:
- bdrv_snapshot_delete_by_id_or_name(bs, snapshot_name, &err);
- if (err) {
- error_reportf_err(err, "Could not delete snapshot '%s': ",
- snapshot_name);
+ ret = bdrv_snapshot_find(bs, &sn, snapshot_name);
+ if (ret < 0) {
+ error_report("Could not delete snapshot '%s': snapshot not "
+ "found", snapshot_name);
ret = 1;
+ } else {
+ ret = bdrv_snapshot_delete(bs, sn.id_str, sn.name, &err);
+ if (ret < 0) {
+ error_reportf_err(err, "Could not delete snapshot '%s': ",
+ snapshot_name);
+ ret = 1;
+ }
}
break;
}
if (qemu_opts_foreach(&qemu_object_opts,
user_creatable_add_opts_foreach,
- NULL, NULL)) {
+ NULL, &error_fatal)) {
return 1;
}
}
if (out_baseimg[0]) {
+ const char *overlay_filename;
+ char *out_real_path;
+
options = qdict_new();
if (out_basefmt) {
qdict_put_str(options, "driver", out_basefmt);
qdict_put_bool(options, BDRV_OPT_FORCE_SHARE, true);
}
- blk_new_backing = blk_new_open(out_baseimg, NULL,
+ bdrv_refresh_filename(bs);
+ overlay_filename = bs->exact_filename[0] ? bs->exact_filename
+ : bs->filename;
+ out_real_path =
+ bdrv_get_full_backing_filename_from_filename(overlay_filename,
+ out_baseimg,
+ &local_err);
+ if (local_err) {
+ error_reportf_err(local_err,
+ "Could not resolve backing filename: ");
+ ret = -1;
+ goto out;
+ }
+
+ blk_new_backing = blk_new_open(out_real_path, NULL,
options, src_flags, &local_err);
+ g_free(out_real_path);
if (!blk_new_backing) {
error_reportf_err(local_err,
"Could not open new backing file '%s': ",
* the image is the same as the original one at any time.
*/
if (!unsafe) {
- int64_t num_sectors;
- int64_t old_backing_num_sectors;
- int64_t new_backing_num_sectors = 0;
- uint64_t sector;
- int n;
- int64_t count;
+ int64_t size;
+ int64_t old_backing_size;
+ int64_t new_backing_size = 0;
+ uint64_t offset;
+ int64_t n;
float local_progress = 0;
buf_old = blk_blockalign(blk, IO_BUF_SIZE);
buf_new = blk_blockalign(blk, IO_BUF_SIZE);
- num_sectors = blk_nb_sectors(blk);
- if (num_sectors < 0) {
+ size = blk_getlength(blk);
+ if (size < 0) {
error_report("Could not get size of '%s': %s",
- filename, strerror(-num_sectors));
+ filename, strerror(-size));
ret = -1;
goto out;
}
- old_backing_num_sectors = blk_nb_sectors(blk_old_backing);
- if (old_backing_num_sectors < 0) {
+ old_backing_size = blk_getlength(blk_old_backing);
+ if (old_backing_size < 0) {
char backing_name[PATH_MAX];
bdrv_get_backing_filename(bs, backing_name, sizeof(backing_name));
error_report("Could not get size of '%s': %s",
- backing_name, strerror(-old_backing_num_sectors));
+ backing_name, strerror(-old_backing_size));
ret = -1;
goto out;
}
if (blk_new_backing) {
- new_backing_num_sectors = blk_nb_sectors(blk_new_backing);
- if (new_backing_num_sectors < 0) {
+ new_backing_size = blk_getlength(blk_new_backing);
+ if (new_backing_size < 0) {
error_report("Could not get size of '%s': %s",
- out_baseimg, strerror(-new_backing_num_sectors));
+ out_baseimg, strerror(-new_backing_size));
ret = -1;
goto out;
}
}
- if (num_sectors != 0) {
- local_progress = (float)100 /
- (num_sectors / MIN(num_sectors, IO_BUF_SIZE / 512));
+ if (size != 0) {
+ local_progress = (float)100 / (size / MIN(size, IO_BUF_SIZE));
}
- for (sector = 0; sector < num_sectors; sector += n) {
-
- /* How many sectors can we handle with the next read? */
- if (sector + (IO_BUF_SIZE / 512) <= num_sectors) {
- n = (IO_BUF_SIZE / 512);
- } else {
- n = num_sectors - sector;
- }
+ for (offset = 0; offset < size; offset += n) {
+ /* How many bytes can we handle with the next read? */
+ n = MIN(IO_BUF_SIZE, size - offset);
/* If the cluster is allocated, we don't need to take action */
- ret = bdrv_is_allocated(bs, sector << BDRV_SECTOR_BITS,
- n << BDRV_SECTOR_BITS, &count);
+ ret = bdrv_is_allocated(bs, offset, n, &n);
if (ret < 0) {
error_report("error while reading image metadata: %s",
strerror(-ret));
goto out;
}
- /* TODO relax this once bdrv_is_allocated does not enforce
- * sector alignment */
- assert(QEMU_IS_ALIGNED(count, BDRV_SECTOR_SIZE));
- n = count >> BDRV_SECTOR_BITS;
if (ret) {
continue;
}
* Read old and new backing file and take into consideration that
* backing files may be smaller than the COW image.
*/
- if (sector >= old_backing_num_sectors) {
- memset(buf_old, 0, n * BDRV_SECTOR_SIZE);
+ if (offset >= old_backing_size) {
+ memset(buf_old, 0, n);
} else {
- if (sector + n > old_backing_num_sectors) {
- n = old_backing_num_sectors - sector;
+ if (offset + n > old_backing_size) {
+ n = old_backing_size - offset;
}
- ret = blk_pread(blk_old_backing, sector << BDRV_SECTOR_BITS,
- buf_old, n << BDRV_SECTOR_BITS);
+ ret = blk_pread(blk_old_backing, offset, buf_old, n);
if (ret < 0) {
error_report("error while reading from old backing file");
goto out;
}
}
- if (sector >= new_backing_num_sectors || !blk_new_backing) {
- memset(buf_new, 0, n * BDRV_SECTOR_SIZE);
+ if (offset >= new_backing_size || !blk_new_backing) {
+ memset(buf_new, 0, n);
} else {
- if (sector + n > new_backing_num_sectors) {
- n = new_backing_num_sectors - sector;
+ if (offset + n > new_backing_size) {
+ n = new_backing_size - offset;
}
- ret = blk_pread(blk_new_backing, sector << BDRV_SECTOR_BITS,
- buf_new, n << BDRV_SECTOR_BITS);
+ ret = blk_pread(blk_new_backing, offset, buf_new, n);
if (ret < 0) {
error_report("error while reading from new backing file");
goto out;
uint64_t written = 0;
while (written < n) {
- int pnum;
+ int64_t pnum;
- if (compare_sectors(buf_old + written * 512,
- buf_new + written * 512, n - written, &pnum))
+ if (compare_buffers(buf_old + written, buf_new + written,
+ n - written, &pnum))
{
- ret = blk_pwrite(blk,
- (sector + written) << BDRV_SECTOR_BITS,
- buf_old + written * 512,
- pnum << BDRV_SECTOR_BITS, 0);
+ ret = blk_pwrite(blk, offset + written,
+ buf_old + written, pnum, 0);
if (ret < 0) {
error_report("Error while writing to COW image: %s",
strerror(-ret));
Error *err = NULL;
int c, ret, relative;
const char *filename, *fmt, *size;
- int64_t n, total_size, current_size;
+ int64_t n, total_size, current_size, new_size;
bool quiet = false;
BlockBackend *blk = NULL;
PreallocMode prealloc = PREALLOC_MODE_OFF;
},
};
bool image_opts = false;
+ bool shrink = false;
/* Remove size from argv manually so that negative numbers are not treated
* as options by getopt. */
{"object", required_argument, 0, OPTION_OBJECT},
{"image-opts", no_argument, 0, OPTION_IMAGE_OPTS},
{"preallocation", required_argument, 0, OPTION_PREALLOCATION},
+ {"shrink", no_argument, 0, OPTION_SHRINK},
{0, 0, 0, 0}
};
c = getopt_long(argc, argv, ":f:hq",
image_opts = true;
break;
case OPTION_PREALLOCATION:
- prealloc = qapi_enum_parse(PreallocMode_lookup, optarg,
- PREALLOC_MODE__MAX, PREALLOC_MODE__MAX,
- NULL);
+ prealloc = qapi_enum_parse(&PreallocMode_lookup, optarg,
+ PREALLOC_MODE__MAX, NULL);
if (prealloc == PREALLOC_MODE__MAX) {
error_report("Invalid preallocation mode '%s'", optarg);
return 1;
}
break;
+ case OPTION_SHRINK:
+ shrink = true;
+ break;
}
}
if (optind != argc - 1) {
- error_exit("Expecting one image file name");
+ error_exit("Expecting image file name and size");
}
filename = argv[optind++];
if (qemu_opts_foreach(&qemu_object_opts,
user_creatable_add_opts_foreach,
- NULL, NULL)) {
+ NULL, &error_fatal)) {
return 1;
}
goto out;
}
+ if (total_size < current_size && !shrink) {
+ warn_report("Shrinking an image will delete all data beyond the "
+ "shrunken image's end. Before performing such an "
+ "operation, make sure there is no important data there.");
+
+ if (g_strcmp0(bdrv_get_format_name(blk_bs(blk)), "raw") != 0) {
+ error_report(
+ "Use the --shrink option to perform a shrink operation.");
+ ret = -1;
+ goto out;
+ } else {
+ warn_report("Using the --shrink option will suppress this message. "
+ "Note that future versions of qemu-img may refuse to "
+ "shrink images without this option.");
+ }
+ }
+
ret = blk_truncate(blk, total_size, prealloc, &err);
- if (!ret) {
- qprintf(quiet, "Image resized.\n");
- } else {
+ if (ret < 0) {
error_report_err(err);
+ goto out;
+ }
+
+ new_size = blk_getlength(blk);
+ if (new_size < 0) {
+ error_report("Failed to verify truncated image length: %s",
+ strerror(-new_size));
+ ret = -1;
+ goto out;
+ }
+
+ /* Some block drivers implement a truncation method, but only so
+ * the user can cause qemu to refresh the image's size from disk.
+ * The idea is that the user resizes the image outside of qemu and
+ * then invokes block_resize to inform qemu about it.
+ * (This includes iscsi and file-posix for device files.)
+ * Of course, that is not the behavior someone invoking
+ * qemu-img resize would find useful, so we catch that behavior
+ * here and tell the user. */
+ if (new_size != total_size && new_size == current_size) {
+ error_report("Image was not resized; resizing may not be supported "
+ "for this image");
+ ret = -1;
+ goto out;
+ }
+
+ if (new_size != total_size) {
+ warn_report("Image should have been resized to %" PRIi64
+ " bytes, but was resized to %" PRIi64 " bytes",
+ total_size, new_size);
}
+
+ qprintf(quiet, "Image resized.\n");
+
out:
blk_unref(blk);
if (ret) {
qemu_progress_print(100.f * offset / total_work_size, 0);
}
+static int print_amend_option_help(const char *format)
+{
+ BlockDriver *drv;
+
+ /* Find driver and parse its options */
+ drv = bdrv_find_format(format);
+ if (!drv) {
+ error_report("Unknown file format '%s'", format);
+ return 1;
+ }
+
+ if (!drv->bdrv_amend_options) {
+ error_report("Format driver '%s' does not support option amendment",
+ format);
+ return 1;
+ }
+
+ /* Every driver supporting amendment must have create_opts */
+ assert(drv->create_opts);
+
+ printf("Creation options for '%s':\n", format);
+ qemu_opts_print_help(drv->create_opts, false);
+ printf("\nNote that not all of these options may be amendable.\n");
+ return 0;
+}
+
static int img_amend(int argc, char **argv)
{
Error *err = NULL;
if (qemu_opts_foreach(&qemu_object_opts,
user_creatable_add_opts_foreach,
- NULL, NULL)) {
+ NULL, &error_fatal)) {
ret = -1;
goto out_no_progress;
}
if (fmt && has_help_option(options)) {
/* If a format is explicitly specified (and possibly no filename is
* given), print option help here */
- ret = print_block_option_help(filename, fmt);
+ ret = print_amend_option_help(fmt);
goto out;
}
if (has_help_option(options)) {
/* If the format was auto-detected, print option help here */
- ret = print_block_option_help(filename, fmt);
+ ret = print_amend_option_help(fmt);
goto out;
}
- if (!bs->drv->create_opts) {
- error_report("Format driver '%s' does not support any options to amend",
+ if (!bs->drv->bdrv_amend_options) {
+ error_report("Format driver '%s' does not support option amendment",
fmt);
ret = -1;
goto out;
}
+ /* Every driver supporting amendment must have create_opts */
+ assert(bs->drv->create_opts);
+
create_opts = qemu_opts_append(create_opts, bs->drv->create_opts);
opts = qemu_opts_create(create_opts, NULL, 0, &error_abort);
qemu_opts_do_parse(opts, options, NULL, &err);
/* In case the driver does not call amend_status_cb() */
qemu_progress_print(0.f, 0);
- ret = bdrv_amend_options(bs, opts, &amend_status_cb, NULL);
+ ret = bdrv_amend_options(bs, opts, &amend_status_cb, NULL, &err);
qemu_progress_print(100.f, 0);
if (ret < 0) {
- error_report("Error while amending options: %s", strerror(-ret));
+ error_report_err(err);
goto out;
}
struct timeval t1, t2;
int i;
bool force_share = false;
+ size_t buf_size;
for (;;) {
static const struct option long_options[] = {
printf("Sending flush every %d requests\n", flush_interval);
}
- data.buf = blk_blockalign(blk, data.nrreq * data.bufsize);
+ buf_size = data.nrreq * data.bufsize;
+ data.buf = blk_blockalign(blk, buf_size);
memset(data.buf, pattern, data.nrreq * data.bufsize);
+ blk_register_buf(blk, data.buf, buf_size);
+
data.qiov = g_new(QEMUIOVector, data.nrreq);
for (i = 0; i < data.nrreq; i++) {
qemu_iovec_init(&data.qiov[i], 1);
+ ((double)(t2.tv_usec - t1.tv_usec) / 1000000));
out:
+ if (data.buf) {
+ blk_unregister_buf(blk, data.buf);
+ }
qemu_vfree(data.buf);
blk_unref(blk);
if (qemu_opts_foreach(&qemu_object_opts,
user_creatable_add_opts_foreach,
- NULL, NULL)) {
+ NULL, &error_fatal)) {
ret = -1;
goto out;
}
str = qobject_to_json_pretty(obj);
assert(str != NULL);
printf("%s\n", qstring_get_str(str));
- qobject_decref(obj);
+ qobject_unref(obj);
visit_free(v);
- QDECREF(str);
+ qobject_unref(str);
}
static int img_measure(int argc, char **argv)
if (qemu_opts_foreach(&qemu_object_opts,
user_creatable_add_opts_foreach,
- NULL, NULL)) {
+ NULL, &error_fatal)) {
goto out;
}
{ option, callback },
#include "qemu-img-cmds.h"
#undef DEF
-#undef GEN_DOCS
{ NULL, NULL, },
};
return 0;
}
argv += optind;
- optind = 0;
+ qemu_reset_optind();
if (!trace_init_backends()) {
exit(1);