This patch extends qemu-img for working with locked images. It prints a helpful error message when trying to access a locked image read-write, and adds a 'qemu-img force-unlock' command as well as a 'qemu-img check -r all --force' option in order to override a lock left behind after a qemu crash.
Signed-off-by: Kevin Wolf <kw...@redhat.com> --- include/block/block.h | 1 + include/qapi/error.h | 1 + qapi/common.json | 3 +- qemu-img-cmds.hx | 10 ++++-- qemu-img.c | 96 +++++++++++++++++++++++++++++++++++++++++++-------- qemu-img.texi | 20 ++++++++++- 6 files changed, 113 insertions(+), 18 deletions(-) diff --git a/include/block/block.h b/include/block/block.h index 0d00ac1..1ae655c 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -101,6 +101,7 @@ typedef struct HDGeometry { #define BDRV_OPT_CACHE_DIRECT "cache.direct" #define BDRV_OPT_CACHE_NO_FLUSH "cache.no-flush" +#define BDRV_OPT_OVERRIDE_LOCK "override-lock" #define BDRV_SECTOR_BITS 9 #define BDRV_SECTOR_SIZE (1ULL << BDRV_SECTOR_BITS) diff --git a/include/qapi/error.h b/include/qapi/error.h index 6285cf5..53591bc 100644 --- a/include/qapi/error.h +++ b/include/qapi/error.h @@ -102,6 +102,7 @@ typedef enum ErrorClass { ERROR_CLASS_DEVICE_NOT_ACTIVE = QAPI_ERROR_CLASS_DEVICENOTACTIVE, ERROR_CLASS_DEVICE_NOT_FOUND = QAPI_ERROR_CLASS_DEVICENOTFOUND, ERROR_CLASS_KVM_MISSING_CAP = QAPI_ERROR_CLASS_KVMMISSINGCAP, + ERROR_CLASS_IMAGE_FILE_LOCKED = QAPI_ERROR_CLASS_IMAGEFILELOCKED, } ErrorClass; /* diff --git a/qapi/common.json b/qapi/common.json index 9353a7b..1bf6e46 100644 --- a/qapi/common.json +++ b/qapi/common.json @@ -27,7 +27,8 @@ { 'enum': 'QapiErrorClass', # Keep this in sync with ErrorClass in error.h 'data': [ 'GenericError', 'CommandNotFound', 'DeviceEncrypted', - 'DeviceNotActive', 'DeviceNotFound', 'KVMMissingCap' ] } + 'DeviceNotActive', 'DeviceNotFound', 'KVMMissingCap', + 'ImageFileLocked' ] } ## # @VersionTriple diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx index 9567774..dd4aebc 100644 --- a/qemu-img-cmds.hx +++ b/qemu-img-cmds.hx @@ -10,9 +10,9 @@ STEXI ETEXI DEF("check", img_check, - "check [-q] [-f fmt] [--output=ofmt] [-r [leaks | all]] [-T src_cache] filename") + "check [-q] [-f fmt] [--force] [--output=ofmt] [-r [leaks | all]] [-T src_cache] filename") STEXI -@item check [-q] [-f @var{fmt}] [--output=@var{ofmt}] [-r [leaks | all]] [-T @var{src_cache}] @var{filename} +@item check [-q] [-f @var{fmt}] [--force] [--output=@var{ofmt}] [-r [leaks | all]] [-T @var{src_cache}] @var{filename} ETEXI DEF("create", img_create, @@ -39,6 +39,12 @@ STEXI @item convert [-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 +DEF("force-unlock", img_force_unlock, + "force_unlock [-f fmt] filename") +STEXI +@item force-unlock [-f @var{fmt}] @var{filename} +ETEXI + DEF("info", img_info, "info [-f fmt] [--output=ofmt] [--backing-chain] filename") STEXI diff --git a/qemu-img.c b/qemu-img.c index 3d48b4f..4fe15cd 100644 --- a/qemu-img.c +++ b/qemu-img.c @@ -47,6 +47,7 @@ typedef struct img_cmd_t { enum { OPTION_OUTPUT = 256, OPTION_BACKING_CHAIN = 257, + OPTION_FORCE = 258, }; typedef enum OutputFormat { @@ -198,7 +199,7 @@ static int print_block_option_help(const char *filename, const char *fmt) static BlockBackend *img_open(const char *id, const char *filename, const char *fmt, int flags, - bool require_io, bool quiet) + bool require_io, bool quiet, bool force) { BlockBackend *blk; BlockDriverState *bs; @@ -206,12 +207,34 @@ static BlockBackend *img_open(const char *id, const char *filename, Error *local_err = NULL; QDict *options = NULL; + options = qdict_new(); if (fmt) { - options = qdict_new(); qdict_put(options, "driver", qstring_from_str(fmt)); } + QINCREF(options); blk = blk_new_open(id, filename, NULL, options, flags, &local_err); + if (!blk && error_get_class(local_err) == ERROR_CLASS_IMAGE_FILE_LOCKED) { + if (force) { + qdict_put(options, BDRV_OPT_OVERRIDE_LOCK, qstring_from_str("on")); + blk = blk_new_open(id, filename, NULL, options, flags, NULL); + if (blk) { + error_free(local_err); + } + } else { + error_report("The image file '%s' is locked and cannot be " + "opened for write access as this may cause image " + "corruption.", filename); + error_report("If it is locked in error (e.g. because " + "of an unclean shutdown) and you are sure that no " + "other processes are working on the image file, you " + "can use 'qemu-img force-unlock' or the --force flag " + "for 'qemu-img check' in order to override this " + "check."); + error_free(local_err); + goto fail; + } + } if (!blk) { error_report("Could not open '%s': %s", filename, error_get_pretty(local_err)); @@ -234,6 +257,7 @@ static BlockBackend *img_open(const char *id, const char *filename, return blk; fail: blk_unref(blk); + QDECREF(options); return NULL; } @@ -492,6 +516,7 @@ static int img_check(int argc, char **argv) int flags = BDRV_O_FLAGS | BDRV_O_CHECK; ImageCheck *check; bool quiet = false; + bool force = false; fmt = NULL; output = NULL; @@ -500,6 +525,7 @@ static int img_check(int argc, char **argv) int option_index = 0; static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, + {"force", no_argument, 0, OPTION_FORCE}, {"format", required_argument, 0, 'f'}, {"repair", required_argument, 0, 'r'}, {"output", required_argument, 0, OPTION_OUTPUT}, @@ -515,6 +541,9 @@ static int img_check(int argc, char **argv) case 'h': help(); break; + case OPTION_FORCE: + force = true; + break; case 'f': fmt = optarg; break; @@ -561,7 +590,7 @@ static int img_check(int argc, char **argv) return 1; } - blk = img_open("image", filename, fmt, flags, true, quiet); + blk = img_open("image", filename, fmt, flags, true, quiet, force); if (!blk) { return 1; } @@ -633,6 +662,44 @@ fail: return ret; } +static int img_force_unlock(int argc, char **argv) +{ + BlockBackend *blk; + const char *format = NULL; + const char *filename; + char c; + + for (;;) { + c = getopt(argc, argv, "hf:"); + if (c == -1) { + break; + } + switch (c) { + case '?': + case 'h': + help(); + break; + case 'f': + format = optarg; + break; + } + } + + if (optind != argc - 1) { + error_exit("Expecting one image file name"); + } + filename = argv[optind]; + + /* Just force-opening and closing the image is enough to unlock it */ + blk = img_open("image", filename, format, BDRV_O_FLAGS | BDRV_O_RDWR, + false, false, true); + if (blk) { + blk_unref(blk); + } + + return 0; +} + typedef struct CommonBlockJobCBInfo { BlockDriverState *bs; Error **errp; @@ -727,7 +794,7 @@ static int img_commit(int argc, char **argv) return 1; } - blk = img_open("image", filename, fmt, flags, true, quiet); + blk = img_open("image", filename, fmt, flags, true, quiet, false); if (!blk) { return 1; } @@ -1032,14 +1099,14 @@ static int img_compare(int argc, char **argv) goto out3; } - blk1 = img_open("image_1", filename1, fmt1, flags, true, quiet); + blk1 = img_open("image_1", filename1, fmt1, flags, true, quiet, false); if (!blk1) { ret = 2; goto out3; } bs1 = blk_bs(blk1); - blk2 = img_open("image_2", filename2, fmt2, flags, true, quiet); + blk2 = img_open("image_2", filename2, fmt2, flags, true, quiet, false); if (!blk2) { ret = 2; goto out2; @@ -1679,7 +1746,7 @@ static int img_convert(int argc, char **argv) char *id = bs_n > 1 ? g_strdup_printf("source_%d", bs_i) : g_strdup("source"); blk[bs_i] = img_open(id, argv[optind + bs_i], fmt, src_flags, - true, quiet); + true, quiet, false); g_free(id); if (!blk[bs_i]) { ret = -1; @@ -1823,7 +1890,8 @@ static int img_convert(int argc, char **argv) goto out; } - out_blk = img_open("target", out_filename, out_fmt, flags, true, quiet); + out_blk = img_open("target", out_filename, out_fmt, flags, true, quiet, + false); if (!out_blk) { ret = -1; goto out; @@ -2015,7 +2083,7 @@ static ImageInfoList *collect_image_info_list(const char *filename, g_hash_table_insert(filenames, (gpointer)filename, NULL); blk = img_open("image", filename, fmt, - BDRV_O_FLAGS | BDRV_O_NO_BACKING, false, false); + BDRV_O_FLAGS | BDRV_O_NO_BACKING, false, false, false); if (!blk) { goto err; } @@ -2279,7 +2347,7 @@ static int img_map(int argc, char **argv) return 1; } - blk = img_open("image", filename, fmt, BDRV_O_FLAGS, true, false); + blk = img_open("image", filename, fmt, BDRV_O_FLAGS, true, false, false); if (!blk) { return 1; } @@ -2401,7 +2469,7 @@ static int img_snapshot(int argc, char **argv) filename = argv[optind++]; /* Open the image */ - blk = img_open("image", filename, NULL, bdrv_oflags, true, quiet); + blk = img_open("image", filename, NULL, bdrv_oflags, true, quiet, false); if (!blk) { return 1; } @@ -2545,7 +2613,7 @@ static int img_rebase(int argc, char **argv) * Ignore the old backing file for unsafe rebase in case we want to correct * the reference to a renamed or moved backing file. */ - blk = img_open("image", filename, fmt, flags, true, quiet); + blk = img_open("image", filename, fmt, flags, true, quiet, false); if (!blk) { ret = -1; goto out; @@ -2858,7 +2926,7 @@ static int img_resize(int argc, char **argv) qemu_opts_del(param); blk = img_open("image", filename, fmt, BDRV_O_FLAGS | BDRV_O_RDWR, - true, quiet); + true, quiet, false); if (!blk) { ret = -1; goto out; @@ -2989,7 +3057,7 @@ static int img_amend(int argc, char **argv) goto out; } - blk = img_open("image", filename, fmt, flags, true, quiet); + blk = img_open("image", filename, fmt, flags, true, quiet, false); if (!blk) { ret = -1; goto out; diff --git a/qemu-img.texi b/qemu-img.texi index 55c6be3..70efac2 100644 --- a/qemu-img.texi +++ b/qemu-img.texi @@ -117,7 +117,7 @@ Skip the creation of the target volume Command description: @table @option -@item check [-f @var{fmt}] [--output=@var{ofmt}] [-r [leaks | all]] [-T @var{src_cache}] @var{filename} +@item check [-q] [-f @var{fmt}] [--force] [--output=@var{ofmt}] [-r [leaks | all]] [-T @var{src_cache}] @var{filename} Perform a consistency check on the disk image @var{filename}. The command can output in the format @var{ofmt} which is either @code{human} or @code{json}. @@ -153,6 +153,12 @@ If @code{-r} is specified, exit codes representing the image state refer to the state after (the attempt at) repairing it. That is, a successful @code{-r all} will yield the exit code 0, independently of the image state before. +The @code{-r} option requires read-write access to the image, which is +prohibited if a qcow2 file is still marked as in use. If you know for sure that +the information is outdated and the image is in fact not in use by another +process any more (e.g. because the QEMU process crashed), specifying +@code{--force} overrides this check. + @item create [-f @var{fmt}] [-o @var{options}] @var{filename} [@var{size}] Create the new disk image @var{filename} of size @var{size} and format @@ -261,6 +267,18 @@ skipped. This is useful for formats such as @code{rbd} if the target volume has already been created with site specific options that cannot be supplied through qemu-img. +@item force-unlock [-f @var{fmt}] @var{filename} + +Read-write disk images can generally be safely opened only from a single +process at the same time. In order to protect against corruption from +neglecting to follow this rule, qcow2 images are automatically flagged as +in use when they are opened and the flag is removed again on a clean +shutdown. + +However, in cases of an unclean shutdown, the image might be still marked as in +use so that any further read-write access is prohibited. You can use the +@code{force-unlock} command to manually remove the in-use flag then. + @item info [-f @var{fmt}] [--output=@var{ofmt}] [--backing-chain] @var{filename} Give information about the disk image @var{filename}. Use it in -- 1.8.3.1