Block layer patches
- Make blockdev-reopen stable - Remove deprecated qemu-img backing file without format - rbd: Convert to coroutines and add write zeroes support - rbd: Updated MAINTAINERS - export/fuse: Allow other users access to the export - vhost-user: Fix backends without multiqueue support - Fix drive-backup transaction endless drained section -----BEGIN PGP SIGNATURE----- iQJFBAABCAAvFiEE3D3rFZqa+V09dFb+fwmycsiPL9YFAmDoRdIRHGt3b2xmQHJl ZGhhdC5jb20ACgkQfwmycsiPL9bvgQ/+Ogq24n1UOQc8FEKRYfyhajNToQ9ofzWN iLiblSGx2QDq+CauD3qdu6z7DLlqEXeoM4NYM462oIPumptQj+9XZt7ftfh6FLWW 4yJEbjfnVKOba+vFdJ+E0DStwnPaxYdnrPGd53cwHZfbZh4ZmkpTM350mzHHiLTb KYKOgWd+UHZbkYeCVNYTGe30SRBiKeAecTpsVZ5HVhe7LstjByuy5stk8dytLpdV YqdKOToZfOp77XiHr8YcLLp1HHBGlr5hw73V4SDas0beCp7hqtnAqsTYyXBue4xO 4zfD4Gujr5JVOCb0crDTyOmOQY5E+y2dqFoOUF00D5AoN2vj4nfQ9ESkbqlE9BVh mgJ1izSokYlN2X8rIwGXNR5fbxRmxxfkAA4rScNRytj1KxDHyrDxrp/k8YFemxSQ qwgb/FBm0fcr69evPRzovKwZFhcyPremksluHQE4rZZ66qBQ2cGuDJPE7PWVTpPH 67JCrIVK/O6n5p+4ilFHmQQ3aP3ol0frMFcboYVRchJ2MhIDTsfFL3F/tTK8hy86 AmrrdQ1BQIAoKNOKnAmOSOUdExM55OcfPmX69+AhEk2GeWP6kgz5Pks4H3qCiKGf YoRk8F1V+N4q+C0mFFovB61bNQ6COIlBuzmD9EtmpDD/Ta3Wib+3ZnoGVIdPS+OI jyj+qJxd9z4= =kH+r -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/kevin/tags/for-upstream' into staging Block layer patches - Make blockdev-reopen stable - Remove deprecated qemu-img backing file without format - rbd: Convert to coroutines and add write zeroes support - rbd: Updated MAINTAINERS - export/fuse: Allow other users access to the export - vhost-user: Fix backends without multiqueue support - Fix drive-backup transaction endless drained section # gpg: Signature made Fri 09 Jul 2021 13:49:22 BST # gpg: using RSA key DC3DEB159A9AF95D3D7456FE7F09B272C88F2FD6 # gpg: issuer "kwolf@redhat.com" # gpg: Good signature from "Kevin Wolf <kwolf@redhat.com>" [full] # Primary key fingerprint: DC3D EB15 9A9A F95D 3D74 56FE 7F09 B272 C88F 2FD6 * remotes/kevin/tags/for-upstream: (28 commits) block: Make blockdev-reopen stable API iotests: Test reopening multiple devices at the same time block: Support multiple reopening with x-blockdev-reopen block: Acquire AioContexts during bdrv_reopen_multiple() block: Add bdrv_reopen_queue_free() qcow2: Fix dangling pointer after reopen for 'file' qemu-img: Improve error for rebase without backing format qemu-img: Require -F with -b backing image qcow2: Prohibit backing file changes in 'qemu-img amend' blockdev: fix drive-backup transaction endless drained section vhost-user: Fix backends without multiqueue support MAINTAINERS: add block/rbd.c reviewer block/rbd: fix type of task->complete iotests/fuse-allow-other: Test allow-other iotests/308: Test +w on read-only FUSE exports export/fuse: Let permissions be adjustable export/fuse: Give SET_ATTR_SIZE its own branch export/fuse: Add allow-other option export/fuse: Pass default_permissions for mount util/uri: do not check argument of uri_free() ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
42e1d798a6
@ -3081,7 +3081,8 @@ S: Supported
|
|||||||
F: block/vmdk.c
|
F: block/vmdk.c
|
||||||
|
|
||||||
RBD
|
RBD
|
||||||
M: Jason Dillaman <dillaman@redhat.com>
|
M: Ilya Dryomov <idryomov@gmail.com>
|
||||||
|
R: Peter Lieven <pl@kamp.de>
|
||||||
L: qemu-block@nongnu.org
|
L: qemu-block@nongnu.org
|
||||||
S: Supported
|
S: Supported
|
||||||
F: block/rbd.c
|
F: block/rbd.c
|
||||||
|
106
block.c
106
block.c
@ -4095,6 +4095,19 @@ BlockReopenQueue *bdrv_reopen_queue(BlockReopenQueue *bs_queue,
|
|||||||
NULL, 0, keep_old_opts);
|
NULL, 0, keep_old_opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void bdrv_reopen_queue_free(BlockReopenQueue *bs_queue)
|
||||||
|
{
|
||||||
|
if (bs_queue) {
|
||||||
|
BlockReopenQueueEntry *bs_entry, *next;
|
||||||
|
QTAILQ_FOREACH_SAFE(bs_entry, bs_queue, entry, next) {
|
||||||
|
qobject_unref(bs_entry->state.explicit_options);
|
||||||
|
qobject_unref(bs_entry->state.options);
|
||||||
|
g_free(bs_entry);
|
||||||
|
}
|
||||||
|
g_free(bs_queue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Reopen multiple BlockDriverStates atomically & transactionally.
|
* Reopen multiple BlockDriverStates atomically & transactionally.
|
||||||
*
|
*
|
||||||
@ -4111,19 +4124,26 @@ BlockReopenQueue *bdrv_reopen_queue(BlockReopenQueue *bs_queue,
|
|||||||
*
|
*
|
||||||
* All affected nodes must be drained between bdrv_reopen_queue() and
|
* All affected nodes must be drained between bdrv_reopen_queue() and
|
||||||
* bdrv_reopen_multiple().
|
* bdrv_reopen_multiple().
|
||||||
|
*
|
||||||
|
* To be called from the main thread, with all other AioContexts unlocked.
|
||||||
*/
|
*/
|
||||||
int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp)
|
int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp)
|
||||||
{
|
{
|
||||||
int ret = -1;
|
int ret = -1;
|
||||||
BlockReopenQueueEntry *bs_entry, *next;
|
BlockReopenQueueEntry *bs_entry, *next;
|
||||||
|
AioContext *ctx;
|
||||||
Transaction *tran = tran_new();
|
Transaction *tran = tran_new();
|
||||||
g_autoptr(GHashTable) found = NULL;
|
g_autoptr(GHashTable) found = NULL;
|
||||||
g_autoptr(GSList) refresh_list = NULL;
|
g_autoptr(GSList) refresh_list = NULL;
|
||||||
|
|
||||||
|
assert(qemu_get_current_aio_context() == qemu_get_aio_context());
|
||||||
assert(bs_queue != NULL);
|
assert(bs_queue != NULL);
|
||||||
|
|
||||||
QTAILQ_FOREACH(bs_entry, bs_queue, entry) {
|
QTAILQ_FOREACH(bs_entry, bs_queue, entry) {
|
||||||
|
ctx = bdrv_get_aio_context(bs_entry->state.bs);
|
||||||
|
aio_context_acquire(ctx);
|
||||||
ret = bdrv_flush(bs_entry->state.bs);
|
ret = bdrv_flush(bs_entry->state.bs);
|
||||||
|
aio_context_release(ctx);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
error_setg_errno(errp, -ret, "Error flushing drive");
|
error_setg_errno(errp, -ret, "Error flushing drive");
|
||||||
goto abort;
|
goto abort;
|
||||||
@ -4132,7 +4152,10 @@ int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp)
|
|||||||
|
|
||||||
QTAILQ_FOREACH(bs_entry, bs_queue, entry) {
|
QTAILQ_FOREACH(bs_entry, bs_queue, entry) {
|
||||||
assert(bs_entry->state.bs->quiesce_counter > 0);
|
assert(bs_entry->state.bs->quiesce_counter > 0);
|
||||||
|
ctx = bdrv_get_aio_context(bs_entry->state.bs);
|
||||||
|
aio_context_acquire(ctx);
|
||||||
ret = bdrv_reopen_prepare(&bs_entry->state, bs_queue, tran, errp);
|
ret = bdrv_reopen_prepare(&bs_entry->state, bs_queue, tran, errp);
|
||||||
|
aio_context_release(ctx);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto abort;
|
goto abort;
|
||||||
}
|
}
|
||||||
@ -4175,7 +4198,10 @@ int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp)
|
|||||||
* to first element.
|
* to first element.
|
||||||
*/
|
*/
|
||||||
QTAILQ_FOREACH_REVERSE(bs_entry, bs_queue, entry) {
|
QTAILQ_FOREACH_REVERSE(bs_entry, bs_queue, entry) {
|
||||||
|
ctx = bdrv_get_aio_context(bs_entry->state.bs);
|
||||||
|
aio_context_acquire(ctx);
|
||||||
bdrv_reopen_commit(&bs_entry->state);
|
bdrv_reopen_commit(&bs_entry->state);
|
||||||
|
aio_context_release(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
tran_commit(tran);
|
tran_commit(tran);
|
||||||
@ -4184,7 +4210,10 @@ int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp)
|
|||||||
BlockDriverState *bs = bs_entry->state.bs;
|
BlockDriverState *bs = bs_entry->state.bs;
|
||||||
|
|
||||||
if (bs->drv->bdrv_reopen_commit_post) {
|
if (bs->drv->bdrv_reopen_commit_post) {
|
||||||
|
ctx = bdrv_get_aio_context(bs);
|
||||||
|
aio_context_acquire(ctx);
|
||||||
bs->drv->bdrv_reopen_commit_post(&bs_entry->state);
|
bs->drv->bdrv_reopen_commit_post(&bs_entry->state);
|
||||||
|
aio_context_release(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4195,17 +4224,38 @@ abort:
|
|||||||
tran_abort(tran);
|
tran_abort(tran);
|
||||||
QTAILQ_FOREACH_SAFE(bs_entry, bs_queue, entry, next) {
|
QTAILQ_FOREACH_SAFE(bs_entry, bs_queue, entry, next) {
|
||||||
if (bs_entry->prepared) {
|
if (bs_entry->prepared) {
|
||||||
|
ctx = bdrv_get_aio_context(bs_entry->state.bs);
|
||||||
|
aio_context_acquire(ctx);
|
||||||
bdrv_reopen_abort(&bs_entry->state);
|
bdrv_reopen_abort(&bs_entry->state);
|
||||||
|
aio_context_release(ctx);
|
||||||
}
|
}
|
||||||
qobject_unref(bs_entry->state.explicit_options);
|
|
||||||
qobject_unref(bs_entry->state.options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
QTAILQ_FOREACH_SAFE(bs_entry, bs_queue, entry, next) {
|
bdrv_reopen_queue_free(bs_queue);
|
||||||
g_free(bs_entry);
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bdrv_reopen(BlockDriverState *bs, QDict *opts, bool keep_old_opts,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
AioContext *ctx = bdrv_get_aio_context(bs);
|
||||||
|
BlockReopenQueue *queue;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
bdrv_subtree_drained_begin(bs);
|
||||||
|
if (ctx != qemu_get_aio_context()) {
|
||||||
|
aio_context_release(ctx);
|
||||||
}
|
}
|
||||||
g_free(bs_queue);
|
|
||||||
|
queue = bdrv_reopen_queue(NULL, bs, opts, keep_old_opts);
|
||||||
|
ret = bdrv_reopen_multiple(queue, errp);
|
||||||
|
|
||||||
|
if (ctx != qemu_get_aio_context()) {
|
||||||
|
aio_context_acquire(ctx);
|
||||||
|
}
|
||||||
|
bdrv_subtree_drained_end(bs);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -4213,18 +4263,11 @@ cleanup:
|
|||||||
int bdrv_reopen_set_read_only(BlockDriverState *bs, bool read_only,
|
int bdrv_reopen_set_read_only(BlockDriverState *bs, bool read_only,
|
||||||
Error **errp)
|
Error **errp)
|
||||||
{
|
{
|
||||||
int ret;
|
|
||||||
BlockReopenQueue *queue;
|
|
||||||
QDict *opts = qdict_new();
|
QDict *opts = qdict_new();
|
||||||
|
|
||||||
qdict_put_bool(opts, BDRV_OPT_READ_ONLY, read_only);
|
qdict_put_bool(opts, BDRV_OPT_READ_ONLY, read_only);
|
||||||
|
|
||||||
bdrv_subtree_drained_begin(bs);
|
return bdrv_reopen(bs, opts, true, errp);
|
||||||
queue = bdrv_reopen_queue(NULL, bs, opts, true);
|
|
||||||
ret = bdrv_reopen_multiple(queue, errp);
|
|
||||||
bdrv_subtree_drained_end(bs);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -4573,6 +4616,8 @@ static void bdrv_reopen_commit(BDRVReopenState *reopen_state)
|
|||||||
/* set BDS specific flags now */
|
/* set BDS specific flags now */
|
||||||
qobject_unref(bs->explicit_options);
|
qobject_unref(bs->explicit_options);
|
||||||
qobject_unref(bs->options);
|
qobject_unref(bs->options);
|
||||||
|
qobject_ref(reopen_state->explicit_options);
|
||||||
|
qobject_ref(reopen_state->options);
|
||||||
|
|
||||||
bs->explicit_options = reopen_state->explicit_options;
|
bs->explicit_options = reopen_state->explicit_options;
|
||||||
bs->options = reopen_state->options;
|
bs->options = reopen_state->options;
|
||||||
@ -5074,7 +5119,7 @@ int coroutine_fn bdrv_co_check(BlockDriverState *bs,
|
|||||||
* -ENOTSUP - format driver doesn't support changing the backing file
|
* -ENOTSUP - format driver doesn't support changing the backing file
|
||||||
*/
|
*/
|
||||||
int bdrv_change_backing_file(BlockDriverState *bs, const char *backing_file,
|
int bdrv_change_backing_file(BlockDriverState *bs, const char *backing_file,
|
||||||
const char *backing_fmt, bool warn)
|
const char *backing_fmt, bool require)
|
||||||
{
|
{
|
||||||
BlockDriver *drv = bs->drv;
|
BlockDriver *drv = bs->drv;
|
||||||
int ret;
|
int ret;
|
||||||
@ -5088,10 +5133,8 @@ int bdrv_change_backing_file(BlockDriverState *bs, const char *backing_file,
|
|||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (warn && backing_file && !backing_fmt) {
|
if (require && backing_file && !backing_fmt) {
|
||||||
warn_report("Deprecated use of backing file without explicit "
|
return -EINVAL;
|
||||||
"backing format, use of this image requires "
|
|
||||||
"potentially unsafe format probing");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (drv->bdrv_change_backing_file != NULL) {
|
if (drv->bdrv_change_backing_file != NULL) {
|
||||||
@ -6601,24 +6644,11 @@ void bdrv_img_create(const char *filename, const char *fmt,
|
|||||||
goto out;
|
goto out;
|
||||||
} else {
|
} else {
|
||||||
if (!backing_fmt) {
|
if (!backing_fmt) {
|
||||||
warn_report("Deprecated use of backing file without explicit "
|
error_setg(&local_err,
|
||||||
"backing format (detected format of %s)",
|
"Backing file specified without backing format");
|
||||||
|
error_append_hint(&local_err, "Detected format of %s.",
|
||||||
bs->drv->format_name);
|
bs->drv->format_name);
|
||||||
if (bs->drv != &bdrv_raw) {
|
goto out;
|
||||||
/*
|
|
||||||
* A probe of raw deserves the most attention:
|
|
||||||
* leaving the backing format out of the image
|
|
||||||
* will ensure bs->probed is set (ensuring we
|
|
||||||
* don't accidentally commit into the backing
|
|
||||||
* file), and allow more spots to warn the users
|
|
||||||
* to fix their toolchain when opening this image
|
|
||||||
* later. For other images, we can safely record
|
|
||||||
* the format that we probed.
|
|
||||||
*/
|
|
||||||
backing_fmt = bs->drv->format_name;
|
|
||||||
qemu_opt_set(opts, BLOCK_OPT_BACKING_FMT, backing_fmt,
|
|
||||||
NULL);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (size == -1) {
|
if (size == -1) {
|
||||||
/* Opened BS, have no size */
|
/* Opened BS, have no size */
|
||||||
@ -6635,9 +6665,9 @@ void bdrv_img_create(const char *filename, const char *fmt,
|
|||||||
}
|
}
|
||||||
/* (backing_file && !(flags & BDRV_O_NO_BACKING)) */
|
/* (backing_file && !(flags & BDRV_O_NO_BACKING)) */
|
||||||
} else if (backing_file && !backing_fmt) {
|
} else if (backing_file && !backing_fmt) {
|
||||||
warn_report("Deprecated use of unopened backing file without "
|
error_setg(&local_err,
|
||||||
"explicit backing format, use of this image requires "
|
"Backing file specified without backing format");
|
||||||
"potentially unsafe format probing");
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (size == -1) {
|
if (size == -1) {
|
||||||
|
@ -46,6 +46,12 @@ typedef struct FuseExport {
|
|||||||
char *mountpoint;
|
char *mountpoint;
|
||||||
bool writable;
|
bool writable;
|
||||||
bool growable;
|
bool growable;
|
||||||
|
/* Whether allow_other was used as a mount option or not */
|
||||||
|
bool allow_other;
|
||||||
|
|
||||||
|
mode_t st_mode;
|
||||||
|
uid_t st_uid;
|
||||||
|
gid_t st_gid;
|
||||||
} FuseExport;
|
} FuseExport;
|
||||||
|
|
||||||
static GHashTable *exports;
|
static GHashTable *exports;
|
||||||
@ -57,7 +63,7 @@ static void fuse_export_delete(BlockExport *exp);
|
|||||||
static void init_exports_table(void);
|
static void init_exports_table(void);
|
||||||
|
|
||||||
static int setup_fuse_export(FuseExport *exp, const char *mountpoint,
|
static int setup_fuse_export(FuseExport *exp, const char *mountpoint,
|
||||||
Error **errp);
|
bool allow_other, Error **errp);
|
||||||
static void read_from_fuse_export(void *opaque);
|
static void read_from_fuse_export(void *opaque);
|
||||||
|
|
||||||
static bool is_regular_file(const char *path, Error **errp);
|
static bool is_regular_file(const char *path, Error **errp);
|
||||||
@ -118,7 +124,29 @@ static int fuse_export_create(BlockExport *blk_exp,
|
|||||||
exp->writable = blk_exp_args->writable;
|
exp->writable = blk_exp_args->writable;
|
||||||
exp->growable = args->growable;
|
exp->growable = args->growable;
|
||||||
|
|
||||||
ret = setup_fuse_export(exp, args->mountpoint, errp);
|
/* set default */
|
||||||
|
if (!args->has_allow_other) {
|
||||||
|
args->allow_other = FUSE_EXPORT_ALLOW_OTHER_AUTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
exp->st_mode = S_IFREG | S_IRUSR;
|
||||||
|
if (exp->writable) {
|
||||||
|
exp->st_mode |= S_IWUSR;
|
||||||
|
}
|
||||||
|
exp->st_uid = getuid();
|
||||||
|
exp->st_gid = getgid();
|
||||||
|
|
||||||
|
if (args->allow_other == FUSE_EXPORT_ALLOW_OTHER_AUTO) {
|
||||||
|
/* Ignore errors on our first attempt */
|
||||||
|
ret = setup_fuse_export(exp, args->mountpoint, true, NULL);
|
||||||
|
exp->allow_other = ret == 0;
|
||||||
|
if (ret < 0) {
|
||||||
|
ret = setup_fuse_export(exp, args->mountpoint, false, errp);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
exp->allow_other = args->allow_other == FUSE_EXPORT_ALLOW_OTHER_ON;
|
||||||
|
ret = setup_fuse_export(exp, args->mountpoint, exp->allow_other, errp);
|
||||||
|
}
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
@ -146,15 +174,20 @@ static void init_exports_table(void)
|
|||||||
* Create exp->fuse_session and mount it.
|
* Create exp->fuse_session and mount it.
|
||||||
*/
|
*/
|
||||||
static int setup_fuse_export(FuseExport *exp, const char *mountpoint,
|
static int setup_fuse_export(FuseExport *exp, const char *mountpoint,
|
||||||
Error **errp)
|
bool allow_other, Error **errp)
|
||||||
{
|
{
|
||||||
const char *fuse_argv[4];
|
const char *fuse_argv[4];
|
||||||
char *mount_opts;
|
char *mount_opts;
|
||||||
struct fuse_args fuse_args;
|
struct fuse_args fuse_args;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
/* Needs to match what fuse_init() sets. Only max_read must be supplied. */
|
/*
|
||||||
mount_opts = g_strdup_printf("max_read=%zu", FUSE_MAX_BOUNCE_BYTES);
|
* max_read needs to match what fuse_init() sets.
|
||||||
|
* max_write need not be supplied.
|
||||||
|
*/
|
||||||
|
mount_opts = g_strdup_printf("max_read=%zu,default_permissions%s",
|
||||||
|
FUSE_MAX_BOUNCE_BYTES,
|
||||||
|
allow_other ? ",allow_other" : "");
|
||||||
|
|
||||||
fuse_argv[0] = ""; /* Dummy program name */
|
fuse_argv[0] = ""; /* Dummy program name */
|
||||||
fuse_argv[1] = "-o";
|
fuse_argv[1] = "-o";
|
||||||
@ -316,7 +349,6 @@ static void fuse_getattr(fuse_req_t req, fuse_ino_t inode,
|
|||||||
int64_t length, allocated_blocks;
|
int64_t length, allocated_blocks;
|
||||||
time_t now = time(NULL);
|
time_t now = time(NULL);
|
||||||
FuseExport *exp = fuse_req_userdata(req);
|
FuseExport *exp = fuse_req_userdata(req);
|
||||||
mode_t mode;
|
|
||||||
|
|
||||||
length = blk_getlength(exp->common.blk);
|
length = blk_getlength(exp->common.blk);
|
||||||
if (length < 0) {
|
if (length < 0) {
|
||||||
@ -331,17 +363,12 @@ static void fuse_getattr(fuse_req_t req, fuse_ino_t inode,
|
|||||||
allocated_blocks = DIV_ROUND_UP(allocated_blocks, 512);
|
allocated_blocks = DIV_ROUND_UP(allocated_blocks, 512);
|
||||||
}
|
}
|
||||||
|
|
||||||
mode = S_IFREG | S_IRUSR;
|
|
||||||
if (exp->writable) {
|
|
||||||
mode |= S_IWUSR;
|
|
||||||
}
|
|
||||||
|
|
||||||
statbuf = (struct stat) {
|
statbuf = (struct stat) {
|
||||||
.st_ino = inode,
|
.st_ino = inode,
|
||||||
.st_mode = mode,
|
.st_mode = exp->st_mode,
|
||||||
.st_nlink = 1,
|
.st_nlink = 1,
|
||||||
.st_uid = getuid(),
|
.st_uid = exp->st_uid,
|
||||||
.st_gid = getgid(),
|
.st_gid = exp->st_gid,
|
||||||
.st_size = length,
|
.st_size = length,
|
||||||
.st_blksize = blk_bs(exp->common.blk)->bl.request_alignment,
|
.st_blksize = blk_bs(exp->common.blk)->bl.request_alignment,
|
||||||
.st_blocks = allocated_blocks,
|
.st_blocks = allocated_blocks,
|
||||||
@ -387,21 +414,55 @@ static int fuse_do_truncate(const FuseExport *exp, int64_t size,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Let clients set file attributes. Only resizing is supported.
|
* Let clients set file attributes. Only resizing and changing
|
||||||
|
* permissions (st_mode, st_uid, st_gid) is allowed.
|
||||||
|
* Changing permissions is only allowed as far as it will actually
|
||||||
|
* permit access: Read-only exports cannot be given +w, and exports
|
||||||
|
* without allow_other cannot be given a different UID or GID, and
|
||||||
|
* they cannot be given non-owner access.
|
||||||
*/
|
*/
|
||||||
static void fuse_setattr(fuse_req_t req, fuse_ino_t inode, struct stat *statbuf,
|
static void fuse_setattr(fuse_req_t req, fuse_ino_t inode, struct stat *statbuf,
|
||||||
int to_set, struct fuse_file_info *fi)
|
int to_set, struct fuse_file_info *fi)
|
||||||
{
|
{
|
||||||
FuseExport *exp = fuse_req_userdata(req);
|
FuseExport *exp = fuse_req_userdata(req);
|
||||||
|
int supported_attrs;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (!exp->writable) {
|
supported_attrs = FUSE_SET_ATTR_SIZE | FUSE_SET_ATTR_MODE;
|
||||||
fuse_reply_err(req, EACCES);
|
if (exp->allow_other) {
|
||||||
|
supported_attrs |= FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to_set & ~supported_attrs) {
|
||||||
|
fuse_reply_err(req, ENOTSUP);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (to_set & ~FUSE_SET_ATTR_SIZE) {
|
/* Do some argument checks first before committing to anything */
|
||||||
fuse_reply_err(req, ENOTSUP);
|
if (to_set & FUSE_SET_ATTR_MODE) {
|
||||||
|
/*
|
||||||
|
* Without allow_other, non-owners can never access the export, so do
|
||||||
|
* not allow setting permissions for them
|
||||||
|
*/
|
||||||
|
if (!exp->allow_other &&
|
||||||
|
(statbuf->st_mode & (S_IRWXG | S_IRWXO)) != 0)
|
||||||
|
{
|
||||||
|
fuse_reply_err(req, EPERM);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* +w for read-only exports makes no sense, disallow it */
|
||||||
|
if (!exp->writable &&
|
||||||
|
(statbuf->st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)) != 0)
|
||||||
|
{
|
||||||
|
fuse_reply_err(req, EROFS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to_set & FUSE_SET_ATTR_SIZE) {
|
||||||
|
if (!exp->writable) {
|
||||||
|
fuse_reply_err(req, EACCES);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -410,6 +471,20 @@ static void fuse_setattr(fuse_req_t req, fuse_ino_t inode, struct stat *statbuf,
|
|||||||
fuse_reply_err(req, -ret);
|
fuse_reply_err(req, -ret);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to_set & FUSE_SET_ATTR_MODE) {
|
||||||
|
/* Ignore FUSE-supplied file type, only change the mode */
|
||||||
|
exp->st_mode = (statbuf->st_mode & 07777) | S_IFREG;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to_set & FUSE_SET_ATTR_UID) {
|
||||||
|
exp->st_uid = statbuf->st_uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to_set & FUSE_SET_ATTR_GID) {
|
||||||
|
exp->st_gid = statbuf->st_gid;
|
||||||
|
}
|
||||||
|
|
||||||
fuse_getattr(req, inode, fi);
|
fuse_getattr(req, inode, fi);
|
||||||
}
|
}
|
||||||
|
@ -147,9 +147,7 @@ out:
|
|||||||
if (qp) {
|
if (qp) {
|
||||||
query_params_free(qp);
|
query_params_free(qp);
|
||||||
}
|
}
|
||||||
if (uri) {
|
|
||||||
uri_free(uri);
|
uri_free(uri);
|
||||||
}
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1926,6 +1926,7 @@ static void qcow2_refresh_limits(BlockDriverState *bs, Error **errp)
|
|||||||
static int qcow2_reopen_prepare(BDRVReopenState *state,
|
static int qcow2_reopen_prepare(BDRVReopenState *state,
|
||||||
BlockReopenQueue *queue, Error **errp)
|
BlockReopenQueue *queue, Error **errp)
|
||||||
{
|
{
|
||||||
|
BDRVQcow2State *s = state->bs->opaque;
|
||||||
Qcow2ReopenState *r;
|
Qcow2ReopenState *r;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
@ -1956,6 +1957,16 @@ static int qcow2_reopen_prepare(BDRVReopenState *state,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Without an external data file, s->data_file points to the same BdrvChild
|
||||||
|
* as bs->file. It needs to be resynced after reopen because bs->file may
|
||||||
|
* be changed. We can't use it in the meantime.
|
||||||
|
*/
|
||||||
|
if (!has_data_file(state->bs)) {
|
||||||
|
assert(s->data_file == state->bs->file);
|
||||||
|
s->data_file = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
@ -1966,7 +1977,16 @@ fail:
|
|||||||
|
|
||||||
static void qcow2_reopen_commit(BDRVReopenState *state)
|
static void qcow2_reopen_commit(BDRVReopenState *state)
|
||||||
{
|
{
|
||||||
|
BDRVQcow2State *s = state->bs->opaque;
|
||||||
|
|
||||||
qcow2_update_options_commit(state->bs, state->opaque);
|
qcow2_update_options_commit(state->bs, state->opaque);
|
||||||
|
if (!s->data_file) {
|
||||||
|
/*
|
||||||
|
* If we don't have an external data file, s->data_file was cleared by
|
||||||
|
* qcow2_reopen_prepare() and needs to be updated.
|
||||||
|
*/
|
||||||
|
s->data_file = state->bs->file;
|
||||||
|
}
|
||||||
g_free(state->opaque);
|
g_free(state->opaque);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1990,6 +2010,15 @@ static void qcow2_reopen_commit_post(BDRVReopenState *state)
|
|||||||
|
|
||||||
static void qcow2_reopen_abort(BDRVReopenState *state)
|
static void qcow2_reopen_abort(BDRVReopenState *state)
|
||||||
{
|
{
|
||||||
|
BDRVQcow2State *s = state->bs->opaque;
|
||||||
|
|
||||||
|
if (!s->data_file) {
|
||||||
|
/*
|
||||||
|
* If we don't have an external data file, s->data_file was cleared by
|
||||||
|
* qcow2_reopen_prepare() and needs to be restored.
|
||||||
|
*/
|
||||||
|
s->data_file = state->bs->file;
|
||||||
|
}
|
||||||
qcow2_update_options_abort(state->bs, state->opaque);
|
qcow2_update_options_abort(state->bs, state->opaque);
|
||||||
g_free(state->opaque);
|
g_free(state->opaque);
|
||||||
}
|
}
|
||||||
@ -5620,15 +5649,10 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
|
|||||||
if (backing_file || backing_format) {
|
if (backing_file || backing_format) {
|
||||||
if (g_strcmp0(backing_file, s->image_backing_file) ||
|
if (g_strcmp0(backing_file, s->image_backing_file) ||
|
||||||
g_strcmp0(backing_format, s->image_backing_format)) {
|
g_strcmp0(backing_format, s->image_backing_format)) {
|
||||||
warn_report("Deprecated use of amend to alter the backing file; "
|
error_setg(errp, "Cannot amend the backing file");
|
||||||
"use qemu-img rebase instead");
|
error_append_hint(errp,
|
||||||
}
|
"You can use 'qemu-img rebase' instead.\n");
|
||||||
ret = qcow2_change_backing_file(bs,
|
return -EINVAL;
|
||||||
backing_file ?: s->image_backing_file,
|
|
||||||
backing_format ?: s->image_backing_format);
|
|
||||||
if (ret < 0) {
|
|
||||||
error_setg_errno(errp, -ret, "Failed to change the backing file");
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
769
block/rbd.c
769
block/rbd.c
@ -55,49 +55,30 @@
|
|||||||
* leading "\".
|
* leading "\".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* rbd_aio_discard added in 0.1.2 */
|
|
||||||
#if LIBRBD_VERSION_CODE >= LIBRBD_VERSION(0, 1, 2)
|
|
||||||
#define LIBRBD_SUPPORTS_DISCARD
|
|
||||||
#else
|
|
||||||
#undef LIBRBD_SUPPORTS_DISCARD
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define OBJ_MAX_SIZE (1UL << OBJ_DEFAULT_OBJ_ORDER)
|
#define OBJ_MAX_SIZE (1UL << OBJ_DEFAULT_OBJ_ORDER)
|
||||||
|
|
||||||
#define RBD_MAX_SNAPS 100
|
#define RBD_MAX_SNAPS 100
|
||||||
|
|
||||||
/* The LIBRBD_SUPPORTS_IOVEC is defined in librbd.h */
|
#define RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN 8
|
||||||
#ifdef LIBRBD_SUPPORTS_IOVEC
|
|
||||||
#define LIBRBD_USE_IOVEC 1
|
static const char rbd_luks_header_verification[
|
||||||
#else
|
RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
|
||||||
#define LIBRBD_USE_IOVEC 0
|
'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 1
|
||||||
#endif
|
};
|
||||||
|
|
||||||
|
static const char rbd_luks2_header_verification[
|
||||||
|
RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
|
||||||
|
'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 2
|
||||||
|
};
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
RBD_AIO_READ,
|
RBD_AIO_READ,
|
||||||
RBD_AIO_WRITE,
|
RBD_AIO_WRITE,
|
||||||
RBD_AIO_DISCARD,
|
RBD_AIO_DISCARD,
|
||||||
RBD_AIO_FLUSH
|
RBD_AIO_FLUSH,
|
||||||
|
RBD_AIO_WRITE_ZEROES
|
||||||
} RBDAIOCmd;
|
} RBDAIOCmd;
|
||||||
|
|
||||||
typedef struct RBDAIOCB {
|
|
||||||
BlockAIOCB common;
|
|
||||||
int64_t ret;
|
|
||||||
QEMUIOVector *qiov;
|
|
||||||
char *bounce;
|
|
||||||
RBDAIOCmd cmd;
|
|
||||||
int error;
|
|
||||||
struct BDRVRBDState *s;
|
|
||||||
} RBDAIOCB;
|
|
||||||
|
|
||||||
typedef struct RADOSCB {
|
|
||||||
RBDAIOCB *acb;
|
|
||||||
struct BDRVRBDState *s;
|
|
||||||
int64_t size;
|
|
||||||
char *buf;
|
|
||||||
int64_t ret;
|
|
||||||
} RADOSCB;
|
|
||||||
|
|
||||||
typedef struct BDRVRBDState {
|
typedef struct BDRVRBDState {
|
||||||
rados_t cluster;
|
rados_t cluster;
|
||||||
rados_ioctx_t io_ctx;
|
rados_ioctx_t io_ctx;
|
||||||
@ -106,8 +87,16 @@ typedef struct BDRVRBDState {
|
|||||||
char *snap;
|
char *snap;
|
||||||
char *namespace;
|
char *namespace;
|
||||||
uint64_t image_size;
|
uint64_t image_size;
|
||||||
|
uint64_t object_size;
|
||||||
} BDRVRBDState;
|
} BDRVRBDState;
|
||||||
|
|
||||||
|
typedef struct RBDTask {
|
||||||
|
BlockDriverState *bs;
|
||||||
|
Coroutine *co;
|
||||||
|
bool complete;
|
||||||
|
int64_t ret;
|
||||||
|
} RBDTask;
|
||||||
|
|
||||||
static int qemu_rbd_connect(rados_t *cluster, rados_ioctx_t *io_ctx,
|
static int qemu_rbd_connect(rados_t *cluster, rados_ioctx_t *io_ctx,
|
||||||
BlockdevOptionsRbd *opts, bool cache,
|
BlockdevOptionsRbd *opts, bool cache,
|
||||||
const char *keypairs, const char *secretid,
|
const char *keypairs, const char *secretid,
|
||||||
@ -251,14 +240,6 @@ done:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void qemu_rbd_refresh_limits(BlockDriverState *bs, Error **errp)
|
|
||||||
{
|
|
||||||
/* XXX Does RBD support AIO on less than 512-byte alignment? */
|
|
||||||
bs->bl.request_alignment = 512;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int qemu_rbd_set_auth(rados_t cluster, BlockdevOptionsRbd *opts,
|
static int qemu_rbd_set_auth(rados_t cluster, BlockdevOptionsRbd *opts,
|
||||||
Error **errp)
|
Error **errp)
|
||||||
{
|
{
|
||||||
@ -340,17 +321,203 @@ static int qemu_rbd_set_keypairs(rados_t cluster, const char *keypairs_json,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void qemu_rbd_memset(RADOSCB *rcb, int64_t offs)
|
#ifdef LIBRBD_SUPPORTS_ENCRYPTION
|
||||||
|
static int qemu_rbd_convert_luks_options(
|
||||||
|
RbdEncryptionOptionsLUKSBase *luks_opts,
|
||||||
|
char **passphrase,
|
||||||
|
size_t *passphrase_len,
|
||||||
|
Error **errp)
|
||||||
{
|
{
|
||||||
if (LIBRBD_USE_IOVEC) {
|
return qcrypto_secret_lookup(luks_opts->key_secret, (uint8_t **)passphrase,
|
||||||
RBDAIOCB *acb = rcb->acb;
|
passphrase_len, errp);
|
||||||
iov_memset(acb->qiov->iov, acb->qiov->niov, offs, 0,
|
|
||||||
acb->qiov->size - offs);
|
|
||||||
} else {
|
|
||||||
memset(rcb->buf + offs, 0, rcb->size - offs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int qemu_rbd_convert_luks_create_options(
|
||||||
|
RbdEncryptionCreateOptionsLUKSBase *luks_opts,
|
||||||
|
rbd_encryption_algorithm_t *alg,
|
||||||
|
char **passphrase,
|
||||||
|
size_t *passphrase_len,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
int r = 0;
|
||||||
|
|
||||||
|
r = qemu_rbd_convert_luks_options(
|
||||||
|
qapi_RbdEncryptionCreateOptionsLUKSBase_base(luks_opts),
|
||||||
|
passphrase, passphrase_len, errp);
|
||||||
|
if (r < 0) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (luks_opts->has_cipher_alg) {
|
||||||
|
switch (luks_opts->cipher_alg) {
|
||||||
|
case QCRYPTO_CIPHER_ALG_AES_128: {
|
||||||
|
*alg = RBD_ENCRYPTION_ALGORITHM_AES128;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case QCRYPTO_CIPHER_ALG_AES_256: {
|
||||||
|
*alg = RBD_ENCRYPTION_ALGORITHM_AES256;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
r = -ENOTSUP;
|
||||||
|
error_setg_errno(errp, -r, "unknown encryption algorithm: %u",
|
||||||
|
luks_opts->cipher_alg);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* default alg */
|
||||||
|
*alg = RBD_ENCRYPTION_ALGORITHM_AES256;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int qemu_rbd_encryption_format(rbd_image_t image,
|
||||||
|
RbdEncryptionCreateOptions *encrypt,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
int r = 0;
|
||||||
|
g_autofree char *passphrase = NULL;
|
||||||
|
size_t passphrase_len;
|
||||||
|
rbd_encryption_format_t format;
|
||||||
|
rbd_encryption_options_t opts;
|
||||||
|
rbd_encryption_luks1_format_options_t luks_opts;
|
||||||
|
rbd_encryption_luks2_format_options_t luks2_opts;
|
||||||
|
size_t opts_size;
|
||||||
|
uint64_t raw_size, effective_size;
|
||||||
|
|
||||||
|
r = rbd_get_size(image, &raw_size);
|
||||||
|
if (r < 0) {
|
||||||
|
error_setg_errno(errp, -r, "cannot get raw image size");
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (encrypt->format) {
|
||||||
|
case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: {
|
||||||
|
memset(&luks_opts, 0, sizeof(luks_opts));
|
||||||
|
format = RBD_ENCRYPTION_FORMAT_LUKS1;
|
||||||
|
opts = &luks_opts;
|
||||||
|
opts_size = sizeof(luks_opts);
|
||||||
|
r = qemu_rbd_convert_luks_create_options(
|
||||||
|
qapi_RbdEncryptionCreateOptionsLUKS_base(&encrypt->u.luks),
|
||||||
|
&luks_opts.alg, &passphrase, &passphrase_len, errp);
|
||||||
|
if (r < 0) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
luks_opts.passphrase = passphrase;
|
||||||
|
luks_opts.passphrase_size = passphrase_len;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2: {
|
||||||
|
memset(&luks2_opts, 0, sizeof(luks2_opts));
|
||||||
|
format = RBD_ENCRYPTION_FORMAT_LUKS2;
|
||||||
|
opts = &luks2_opts;
|
||||||
|
opts_size = sizeof(luks2_opts);
|
||||||
|
r = qemu_rbd_convert_luks_create_options(
|
||||||
|
qapi_RbdEncryptionCreateOptionsLUKS2_base(
|
||||||
|
&encrypt->u.luks2),
|
||||||
|
&luks2_opts.alg, &passphrase, &passphrase_len, errp);
|
||||||
|
if (r < 0) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
luks2_opts.passphrase = passphrase;
|
||||||
|
luks2_opts.passphrase_size = passphrase_len;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
r = -ENOTSUP;
|
||||||
|
error_setg_errno(
|
||||||
|
errp, -r, "unknown image encryption format: %u",
|
||||||
|
encrypt->format);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r = rbd_encryption_format(image, format, opts, opts_size);
|
||||||
|
if (r < 0) {
|
||||||
|
error_setg_errno(errp, -r, "encryption format fail");
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = rbd_get_size(image, &effective_size);
|
||||||
|
if (r < 0) {
|
||||||
|
error_setg_errno(errp, -r, "cannot get effective image size");
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = rbd_resize(image, raw_size + (raw_size - effective_size));
|
||||||
|
if (r < 0) {
|
||||||
|
error_setg_errno(errp, -r, "cannot resize image after format");
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int qemu_rbd_encryption_load(rbd_image_t image,
|
||||||
|
RbdEncryptionOptions *encrypt,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
int r = 0;
|
||||||
|
g_autofree char *passphrase = NULL;
|
||||||
|
size_t passphrase_len;
|
||||||
|
rbd_encryption_luks1_format_options_t luks_opts;
|
||||||
|
rbd_encryption_luks2_format_options_t luks2_opts;
|
||||||
|
rbd_encryption_format_t format;
|
||||||
|
rbd_encryption_options_t opts;
|
||||||
|
size_t opts_size;
|
||||||
|
|
||||||
|
switch (encrypt->format) {
|
||||||
|
case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: {
|
||||||
|
memset(&luks_opts, 0, sizeof(luks_opts));
|
||||||
|
format = RBD_ENCRYPTION_FORMAT_LUKS1;
|
||||||
|
opts = &luks_opts;
|
||||||
|
opts_size = sizeof(luks_opts);
|
||||||
|
r = qemu_rbd_convert_luks_options(
|
||||||
|
qapi_RbdEncryptionOptionsLUKS_base(&encrypt->u.luks),
|
||||||
|
&passphrase, &passphrase_len, errp);
|
||||||
|
if (r < 0) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
luks_opts.passphrase = passphrase;
|
||||||
|
luks_opts.passphrase_size = passphrase_len;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2: {
|
||||||
|
memset(&luks2_opts, 0, sizeof(luks2_opts));
|
||||||
|
format = RBD_ENCRYPTION_FORMAT_LUKS2;
|
||||||
|
opts = &luks2_opts;
|
||||||
|
opts_size = sizeof(luks2_opts);
|
||||||
|
r = qemu_rbd_convert_luks_options(
|
||||||
|
qapi_RbdEncryptionOptionsLUKS2_base(&encrypt->u.luks2),
|
||||||
|
&passphrase, &passphrase_len, errp);
|
||||||
|
if (r < 0) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
luks2_opts.passphrase = passphrase;
|
||||||
|
luks2_opts.passphrase_size = passphrase_len;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
r = -ENOTSUP;
|
||||||
|
error_setg_errno(
|
||||||
|
errp, -r, "unknown image encryption format: %u",
|
||||||
|
encrypt->format);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r = rbd_encryption_load(image, format, opts, opts_size);
|
||||||
|
if (r < 0) {
|
||||||
|
error_setg_errno(errp, -r, "encryption load fail");
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/* FIXME Deprecate and remove keypairs or make it available in QMP. */
|
/* FIXME Deprecate and remove keypairs or make it available in QMP. */
|
||||||
static int qemu_rbd_do_create(BlockdevCreateOptions *options,
|
static int qemu_rbd_do_create(BlockdevCreateOptions *options,
|
||||||
const char *keypairs, const char *password_secret,
|
const char *keypairs, const char *password_secret,
|
||||||
@ -368,6 +535,13 @@ static int qemu_rbd_do_create(BlockdevCreateOptions *options,
|
|||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef LIBRBD_SUPPORTS_ENCRYPTION
|
||||||
|
if (opts->has_encrypt) {
|
||||||
|
error_setg(errp, "RBD library does not support image encryption");
|
||||||
|
return -ENOTSUP;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (opts->has_cluster_size) {
|
if (opts->has_cluster_size) {
|
||||||
int64_t objsize = opts->cluster_size;
|
int64_t objsize = opts->cluster_size;
|
||||||
if ((objsize - 1) & objsize) { /* not a power of 2? */
|
if ((objsize - 1) & objsize) { /* not a power of 2? */
|
||||||
@ -393,6 +567,28 @@ static int qemu_rbd_do_create(BlockdevCreateOptions *options,
|
|||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef LIBRBD_SUPPORTS_ENCRYPTION
|
||||||
|
if (opts->has_encrypt) {
|
||||||
|
rbd_image_t image;
|
||||||
|
|
||||||
|
ret = rbd_open(io_ctx, opts->location->image, &image, NULL);
|
||||||
|
if (ret < 0) {
|
||||||
|
error_setg_errno(errp, -ret,
|
||||||
|
"error opening image '%s' for encryption format",
|
||||||
|
opts->location->image);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = qemu_rbd_encryption_format(image, opts->encrypt, errp);
|
||||||
|
rbd_close(image);
|
||||||
|
if (ret < 0) {
|
||||||
|
/* encryption format fail, try removing the image */
|
||||||
|
rbd_remove(io_ctx, opts->location->image);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
ret = 0;
|
ret = 0;
|
||||||
out:
|
out:
|
||||||
rados_ioctx_destroy(io_ctx);
|
rados_ioctx_destroy(io_ctx);
|
||||||
@ -405,6 +601,43 @@ static int qemu_rbd_co_create(BlockdevCreateOptions *options, Error **errp)
|
|||||||
return qemu_rbd_do_create(options, NULL, NULL, errp);
|
return qemu_rbd_do_create(options, NULL, NULL, errp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int qemu_rbd_extract_encryption_create_options(
|
||||||
|
QemuOpts *opts,
|
||||||
|
RbdEncryptionCreateOptions **spec,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
QDict *opts_qdict;
|
||||||
|
QDict *encrypt_qdict;
|
||||||
|
Visitor *v;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
opts_qdict = qemu_opts_to_qdict(opts, NULL);
|
||||||
|
qdict_extract_subqdict(opts_qdict, &encrypt_qdict, "encrypt.");
|
||||||
|
qobject_unref(opts_qdict);
|
||||||
|
if (!qdict_size(encrypt_qdict)) {
|
||||||
|
*spec = NULL;
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert options into a QAPI object */
|
||||||
|
v = qobject_input_visitor_new_flat_confused(encrypt_qdict, errp);
|
||||||
|
if (!v) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
visit_type_RbdEncryptionCreateOptions(v, NULL, spec, errp);
|
||||||
|
visit_free(v);
|
||||||
|
if (!*spec) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
exit:
|
||||||
|
qobject_unref(encrypt_qdict);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
|
static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
|
||||||
const char *filename,
|
const char *filename,
|
||||||
QemuOpts *opts,
|
QemuOpts *opts,
|
||||||
@ -413,6 +646,7 @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
|
|||||||
BlockdevCreateOptions *create_options;
|
BlockdevCreateOptions *create_options;
|
||||||
BlockdevCreateOptionsRbd *rbd_opts;
|
BlockdevCreateOptionsRbd *rbd_opts;
|
||||||
BlockdevOptionsRbd *loc;
|
BlockdevOptionsRbd *loc;
|
||||||
|
RbdEncryptionCreateOptions *encrypt = NULL;
|
||||||
Error *local_err = NULL;
|
Error *local_err = NULL;
|
||||||
const char *keypairs, *password_secret;
|
const char *keypairs, *password_secret;
|
||||||
QDict *options = NULL;
|
QDict *options = NULL;
|
||||||
@ -441,6 +675,13 @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
|
|||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ret = qemu_rbd_extract_encryption_create_options(opts, &encrypt, errp);
|
||||||
|
if (ret < 0) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
rbd_opts->encrypt = encrypt;
|
||||||
|
rbd_opts->has_encrypt = !!encrypt;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Caution: while qdict_get_try_str() is fine, getting non-string
|
* Caution: while qdict_get_try_str() is fine, getting non-string
|
||||||
* types would require more care. When @options come from -blockdev
|
* types would require more care. When @options come from -blockdev
|
||||||
@ -469,53 +710,6 @@ exit:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* This aio completion is being called from rbd_finish_bh() and runs in qemu
|
|
||||||
* BH context.
|
|
||||||
*/
|
|
||||||
static void qemu_rbd_complete_aio(RADOSCB *rcb)
|
|
||||||
{
|
|
||||||
RBDAIOCB *acb = rcb->acb;
|
|
||||||
int64_t r;
|
|
||||||
|
|
||||||
r = rcb->ret;
|
|
||||||
|
|
||||||
if (acb->cmd != RBD_AIO_READ) {
|
|
||||||
if (r < 0) {
|
|
||||||
acb->ret = r;
|
|
||||||
acb->error = 1;
|
|
||||||
} else if (!acb->error) {
|
|
||||||
acb->ret = rcb->size;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (r < 0) {
|
|
||||||
qemu_rbd_memset(rcb, 0);
|
|
||||||
acb->ret = r;
|
|
||||||
acb->error = 1;
|
|
||||||
} else if (r < rcb->size) {
|
|
||||||
qemu_rbd_memset(rcb, r);
|
|
||||||
if (!acb->error) {
|
|
||||||
acb->ret = rcb->size;
|
|
||||||
}
|
|
||||||
} else if (!acb->error) {
|
|
||||||
acb->ret = r;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g_free(rcb);
|
|
||||||
|
|
||||||
if (!LIBRBD_USE_IOVEC) {
|
|
||||||
if (acb->cmd == RBD_AIO_READ) {
|
|
||||||
qemu_iovec_from_buf(acb->qiov, 0, acb->bounce, acb->qiov->size);
|
|
||||||
}
|
|
||||||
qemu_vfree(acb->bounce);
|
|
||||||
}
|
|
||||||
|
|
||||||
acb->common.cb(acb->common.opaque, (acb->ret > 0 ? 0 : acb->ret));
|
|
||||||
|
|
||||||
qemu_aio_unref(acb);
|
|
||||||
}
|
|
||||||
|
|
||||||
static char *qemu_rbd_mon_host(BlockdevOptionsRbd *opts, Error **errp)
|
static char *qemu_rbd_mon_host(BlockdevOptionsRbd *opts, Error **errp)
|
||||||
{
|
{
|
||||||
const char **vals;
|
const char **vals;
|
||||||
@ -702,6 +896,7 @@ static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
const QDictEntry *e;
|
const QDictEntry *e;
|
||||||
Error *local_err = NULL;
|
Error *local_err = NULL;
|
||||||
char *keypairs, *secretid;
|
char *keypairs, *secretid;
|
||||||
|
rbd_image_info_t info;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
keypairs = g_strdup(qdict_get_try_str(options, "=keyvalue-pairs"));
|
keypairs = g_strdup(qdict_get_try_str(options, "=keyvalue-pairs"));
|
||||||
@ -766,30 +961,49 @@ static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
goto failed_open;
|
goto failed_open;
|
||||||
}
|
}
|
||||||
|
|
||||||
r = rbd_get_size(s->image, &s->image_size);
|
if (opts->has_encrypt) {
|
||||||
|
#ifdef LIBRBD_SUPPORTS_ENCRYPTION
|
||||||
|
r = qemu_rbd_encryption_load(s->image, opts->encrypt, errp);
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
error_setg_errno(errp, -r, "error getting image size from %s",
|
goto failed_post_open;
|
||||||
s->image_name);
|
|
||||||
rbd_close(s->image);
|
|
||||||
goto failed_open;
|
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
r = -ENOTSUP;
|
||||||
|
error_setg(errp, "RBD library does not support image encryption");
|
||||||
|
goto failed_post_open;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
r = rbd_stat(s->image, &info, sizeof(info));
|
||||||
|
if (r < 0) {
|
||||||
|
error_setg_errno(errp, -r, "error getting image info from %s",
|
||||||
|
s->image_name);
|
||||||
|
goto failed_post_open;
|
||||||
|
}
|
||||||
|
s->image_size = info.size;
|
||||||
|
s->object_size = info.obj_size;
|
||||||
|
|
||||||
/* If we are using an rbd snapshot, we must be r/o, otherwise
|
/* If we are using an rbd snapshot, we must be r/o, otherwise
|
||||||
* leave as-is */
|
* leave as-is */
|
||||||
if (s->snap != NULL) {
|
if (s->snap != NULL) {
|
||||||
r = bdrv_apply_auto_read_only(bs, "rbd snapshots are read-only", errp);
|
r = bdrv_apply_auto_read_only(bs, "rbd snapshots are read-only", errp);
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
rbd_close(s->image);
|
goto failed_post_open;
|
||||||
goto failed_open;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef LIBRBD_SUPPORTS_WRITE_ZEROES
|
||||||
|
bs->supported_zero_flags = BDRV_REQ_MAY_UNMAP | BDRV_REQ_NO_FALLBACK;
|
||||||
|
#endif
|
||||||
|
|
||||||
/* When extending regular files, we get zeros from the OS */
|
/* When extending regular files, we get zeros from the OS */
|
||||||
bs->supported_truncate_flags = BDRV_REQ_ZERO_WRITE;
|
bs->supported_truncate_flags = BDRV_REQ_ZERO_WRITE;
|
||||||
|
|
||||||
r = 0;
|
r = 0;
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
|
failed_post_open:
|
||||||
|
rbd_close(s->image);
|
||||||
failed_open:
|
failed_open:
|
||||||
rados_ioctx_destroy(s->io_ctx);
|
rados_ioctx_destroy(s->io_ctx);
|
||||||
g_free(s->snap);
|
g_free(s->snap);
|
||||||
@ -849,229 +1063,213 @@ static int qemu_rbd_resize(BlockDriverState *bs, uint64_t size)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const AIOCBInfo rbd_aiocb_info = {
|
static void qemu_rbd_finish_bh(void *opaque)
|
||||||
.aiocb_size = sizeof(RBDAIOCB),
|
|
||||||
};
|
|
||||||
|
|
||||||
static void rbd_finish_bh(void *opaque)
|
|
||||||
{
|
{
|
||||||
RADOSCB *rcb = opaque;
|
RBDTask *task = opaque;
|
||||||
qemu_rbd_complete_aio(rcb);
|
task->complete = true;
|
||||||
|
aio_co_wake(task->co);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This is the callback function for rbd_aio_read and _write
|
* This is the completion callback function for all rbd aio calls
|
||||||
|
* started from qemu_rbd_start_co().
|
||||||
*
|
*
|
||||||
* Note: this function is being called from a non qemu thread so
|
* Note: this function is being called from a non qemu thread so
|
||||||
* we need to be careful about what we do here. Generally we only
|
* we need to be careful about what we do here. Generally we only
|
||||||
* schedule a BH, and do the rest of the io completion handling
|
* schedule a BH, and do the rest of the io completion handling
|
||||||
* from rbd_finish_bh() which runs in a qemu context.
|
* from qemu_rbd_finish_bh() which runs in a qemu context.
|
||||||
*/
|
*/
|
||||||
static void rbd_finish_aiocb(rbd_completion_t c, RADOSCB *rcb)
|
static void qemu_rbd_completion_cb(rbd_completion_t c, RBDTask *task)
|
||||||
{
|
{
|
||||||
RBDAIOCB *acb = rcb->acb;
|
task->ret = rbd_aio_get_return_value(c);
|
||||||
|
|
||||||
rcb->ret = rbd_aio_get_return_value(c);
|
|
||||||
rbd_aio_release(c);
|
rbd_aio_release(c);
|
||||||
|
aio_bh_schedule_oneshot(bdrv_get_aio_context(task->bs),
|
||||||
replay_bh_schedule_oneshot_event(bdrv_get_aio_context(acb->common.bs),
|
qemu_rbd_finish_bh, task);
|
||||||
rbd_finish_bh, rcb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int rbd_aio_discard_wrapper(rbd_image_t image,
|
static int coroutine_fn qemu_rbd_start_co(BlockDriverState *bs,
|
||||||
uint64_t off,
|
uint64_t offset,
|
||||||
uint64_t len,
|
uint64_t bytes,
|
||||||
rbd_completion_t comp)
|
|
||||||
{
|
|
||||||
#ifdef LIBRBD_SUPPORTS_DISCARD
|
|
||||||
return rbd_aio_discard(image, off, len, comp);
|
|
||||||
#else
|
|
||||||
return -ENOTSUP;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static int rbd_aio_flush_wrapper(rbd_image_t image,
|
|
||||||
rbd_completion_t comp)
|
|
||||||
{
|
|
||||||
#ifdef LIBRBD_SUPPORTS_AIO_FLUSH
|
|
||||||
return rbd_aio_flush(image, comp);
|
|
||||||
#else
|
|
||||||
return -ENOTSUP;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static BlockAIOCB *rbd_start_aio(BlockDriverState *bs,
|
|
||||||
int64_t off,
|
|
||||||
QEMUIOVector *qiov,
|
QEMUIOVector *qiov,
|
||||||
int64_t size,
|
int flags,
|
||||||
BlockCompletionFunc *cb,
|
|
||||||
void *opaque,
|
|
||||||
RBDAIOCmd cmd)
|
RBDAIOCmd cmd)
|
||||||
{
|
{
|
||||||
RBDAIOCB *acb;
|
BDRVRBDState *s = bs->opaque;
|
||||||
RADOSCB *rcb = NULL;
|
RBDTask task = { .bs = bs, .co = qemu_coroutine_self() };
|
||||||
rbd_completion_t c;
|
rbd_completion_t c;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
BDRVRBDState *s = bs->opaque;
|
assert(!qiov || qiov->size == bytes);
|
||||||
|
|
||||||
acb = qemu_aio_get(&rbd_aiocb_info, bs, cb, opaque);
|
r = rbd_aio_create_completion(&task,
|
||||||
acb->cmd = cmd;
|
(rbd_callback_t) qemu_rbd_completion_cb, &c);
|
||||||
acb->qiov = qiov;
|
|
||||||
assert(!qiov || qiov->size == size);
|
|
||||||
|
|
||||||
rcb = g_new(RADOSCB, 1);
|
|
||||||
|
|
||||||
if (!LIBRBD_USE_IOVEC) {
|
|
||||||
if (cmd == RBD_AIO_DISCARD || cmd == RBD_AIO_FLUSH) {
|
|
||||||
acb->bounce = NULL;
|
|
||||||
} else {
|
|
||||||
acb->bounce = qemu_try_blockalign(bs, qiov->size);
|
|
||||||
if (acb->bounce == NULL) {
|
|
||||||
goto failed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (cmd == RBD_AIO_WRITE) {
|
|
||||||
qemu_iovec_to_buf(acb->qiov, 0, acb->bounce, qiov->size);
|
|
||||||
}
|
|
||||||
rcb->buf = acb->bounce;
|
|
||||||
}
|
|
||||||
|
|
||||||
acb->ret = 0;
|
|
||||||
acb->error = 0;
|
|
||||||
acb->s = s;
|
|
||||||
|
|
||||||
rcb->acb = acb;
|
|
||||||
rcb->s = acb->s;
|
|
||||||
rcb->size = size;
|
|
||||||
r = rbd_aio_create_completion(rcb, (rbd_callback_t) rbd_finish_aiocb, &c);
|
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
goto failed;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case RBD_AIO_WRITE: {
|
|
||||||
/*
|
|
||||||
* RBD APIs don't allow us to write more than actual size, so in order
|
|
||||||
* to support growing images, we resize the image before write
|
|
||||||
* operations that exceed the current size.
|
|
||||||
*/
|
|
||||||
if (off + size > s->image_size) {
|
|
||||||
r = qemu_rbd_resize(bs, off + size);
|
|
||||||
if (r < 0) {
|
|
||||||
goto failed_completion;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#ifdef LIBRBD_SUPPORTS_IOVEC
|
|
||||||
r = rbd_aio_writev(s->image, qiov->iov, qiov->niov, off, c);
|
|
||||||
#else
|
|
||||||
r = rbd_aio_write(s->image, off, size, rcb->buf, c);
|
|
||||||
#endif
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case RBD_AIO_READ:
|
case RBD_AIO_READ:
|
||||||
#ifdef LIBRBD_SUPPORTS_IOVEC
|
r = rbd_aio_readv(s->image, qiov->iov, qiov->niov, offset, c);
|
||||||
r = rbd_aio_readv(s->image, qiov->iov, qiov->niov, off, c);
|
break;
|
||||||
#else
|
case RBD_AIO_WRITE:
|
||||||
r = rbd_aio_read(s->image, off, size, rcb->buf, c);
|
r = rbd_aio_writev(s->image, qiov->iov, qiov->niov, offset, c);
|
||||||
#endif
|
|
||||||
break;
|
break;
|
||||||
case RBD_AIO_DISCARD:
|
case RBD_AIO_DISCARD:
|
||||||
r = rbd_aio_discard_wrapper(s->image, off, size, c);
|
r = rbd_aio_discard(s->image, offset, bytes, c);
|
||||||
break;
|
break;
|
||||||
case RBD_AIO_FLUSH:
|
case RBD_AIO_FLUSH:
|
||||||
r = rbd_aio_flush_wrapper(s->image, c);
|
r = rbd_aio_flush(s->image, c);
|
||||||
break;
|
break;
|
||||||
|
#ifdef LIBRBD_SUPPORTS_WRITE_ZEROES
|
||||||
|
case RBD_AIO_WRITE_ZEROES: {
|
||||||
|
int zero_flags = 0;
|
||||||
|
#ifdef RBD_WRITE_ZEROES_FLAG_THICK_PROVISION
|
||||||
|
if (!(flags & BDRV_REQ_MAY_UNMAP)) {
|
||||||
|
zero_flags = RBD_WRITE_ZEROES_FLAG_THICK_PROVISION;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
r = rbd_aio_write_zeroes(s->image, offset, bytes, c, zero_flags, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
default:
|
default:
|
||||||
r = -EINVAL;
|
r = -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
goto failed_completion;
|
error_report("rbd request failed early: cmd %d offset %" PRIu64
|
||||||
}
|
" bytes %" PRIu64 " flags %d r %d (%s)", cmd, offset,
|
||||||
return &acb->common;
|
bytes, flags, r, strerror(-r));
|
||||||
|
|
||||||
failed_completion:
|
|
||||||
rbd_aio_release(c);
|
rbd_aio_release(c);
|
||||||
failed:
|
return r;
|
||||||
g_free(rcb);
|
|
||||||
if (!LIBRBD_USE_IOVEC) {
|
|
||||||
qemu_vfree(acb->bounce);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qemu_aio_unref(acb);
|
while (!task.complete) {
|
||||||
return NULL;
|
qemu_coroutine_yield();
|
||||||
}
|
}
|
||||||
|
|
||||||
static BlockAIOCB *qemu_rbd_aio_preadv(BlockDriverState *bs,
|
if (task.ret < 0) {
|
||||||
uint64_t offset, uint64_t bytes,
|
error_report("rbd request failed: cmd %d offset %" PRIu64 " bytes %"
|
||||||
QEMUIOVector *qiov, int flags,
|
PRIu64 " flags %d task.ret %" PRIi64 " (%s)", cmd, offset,
|
||||||
BlockCompletionFunc *cb,
|
bytes, flags, task.ret, strerror(-task.ret));
|
||||||
void *opaque)
|
return task.ret;
|
||||||
{
|
}
|
||||||
return rbd_start_aio(bs, offset, qiov, bytes, cb, opaque,
|
|
||||||
RBD_AIO_READ);
|
|
||||||
}
|
|
||||||
|
|
||||||
static BlockAIOCB *qemu_rbd_aio_pwritev(BlockDriverState *bs,
|
/* zero pad short reads */
|
||||||
uint64_t offset, uint64_t bytes,
|
if (cmd == RBD_AIO_READ && task.ret < qiov->size) {
|
||||||
QEMUIOVector *qiov, int flags,
|
qemu_iovec_memset(qiov, task.ret, 0, qiov->size - task.ret);
|
||||||
BlockCompletionFunc *cb,
|
}
|
||||||
void *opaque)
|
|
||||||
{
|
|
||||||
return rbd_start_aio(bs, offset, qiov, bytes, cb, opaque,
|
|
||||||
RBD_AIO_WRITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef LIBRBD_SUPPORTS_AIO_FLUSH
|
|
||||||
static BlockAIOCB *qemu_rbd_aio_flush(BlockDriverState *bs,
|
|
||||||
BlockCompletionFunc *cb,
|
|
||||||
void *opaque)
|
|
||||||
{
|
|
||||||
return rbd_start_aio(bs, 0, NULL, 0, cb, opaque, RBD_AIO_FLUSH);
|
|
||||||
}
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
static int qemu_rbd_co_flush(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
#if LIBRBD_VERSION_CODE >= LIBRBD_VERSION(0, 1, 1)
|
|
||||||
/* rbd_flush added in 0.1.1 */
|
|
||||||
BDRVRBDState *s = bs->opaque;
|
|
||||||
return rbd_flush(s->image);
|
|
||||||
#else
|
|
||||||
return 0;
|
return 0;
|
||||||
#endif
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
coroutine_fn qemu_rbd_co_preadv(BlockDriverState *bs, uint64_t offset,
|
||||||
|
uint64_t bytes, QEMUIOVector *qiov,
|
||||||
|
int flags)
|
||||||
|
{
|
||||||
|
return qemu_rbd_start_co(bs, offset, bytes, qiov, flags, RBD_AIO_READ);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
coroutine_fn qemu_rbd_co_pwritev(BlockDriverState *bs, uint64_t offset,
|
||||||
|
uint64_t bytes, QEMUIOVector *qiov,
|
||||||
|
int flags)
|
||||||
|
{
|
||||||
|
BDRVRBDState *s = bs->opaque;
|
||||||
|
/*
|
||||||
|
* RBD APIs don't allow us to write more than actual size, so in order
|
||||||
|
* to support growing images, we resize the image before write
|
||||||
|
* operations that exceed the current size.
|
||||||
|
*/
|
||||||
|
if (offset + bytes > s->image_size) {
|
||||||
|
int r = qemu_rbd_resize(bs, offset + bytes);
|
||||||
|
if (r < 0) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return qemu_rbd_start_co(bs, offset, bytes, qiov, flags, RBD_AIO_WRITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int coroutine_fn qemu_rbd_co_flush(BlockDriverState *bs)
|
||||||
|
{
|
||||||
|
return qemu_rbd_start_co(bs, 0, 0, NULL, 0, RBD_AIO_FLUSH);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int coroutine_fn qemu_rbd_co_pdiscard(BlockDriverState *bs,
|
||||||
|
int64_t offset, int count)
|
||||||
|
{
|
||||||
|
return qemu_rbd_start_co(bs, offset, count, NULL, 0, RBD_AIO_DISCARD);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef LIBRBD_SUPPORTS_WRITE_ZEROES
|
||||||
|
static int
|
||||||
|
coroutine_fn qemu_rbd_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset,
|
||||||
|
int count, BdrvRequestFlags flags)
|
||||||
|
{
|
||||||
|
return qemu_rbd_start_co(bs, offset, count, NULL, flags,
|
||||||
|
RBD_AIO_WRITE_ZEROES);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static int qemu_rbd_getinfo(BlockDriverState *bs, BlockDriverInfo *bdi)
|
static int qemu_rbd_getinfo(BlockDriverState *bs, BlockDriverInfo *bdi)
|
||||||
{
|
{
|
||||||
BDRVRBDState *s = bs->opaque;
|
BDRVRBDState *s = bs->opaque;
|
||||||
rbd_image_info_t info;
|
bdi->cluster_size = s->object_size;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ImageInfoSpecific *qemu_rbd_get_specific_info(BlockDriverState *bs,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
BDRVRBDState *s = bs->opaque;
|
||||||
|
ImageInfoSpecific *spec_info;
|
||||||
|
char buf[RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {0};
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
r = rbd_stat(s->image, &info, sizeof(info));
|
if (s->image_size >= RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) {
|
||||||
|
r = rbd_read(s->image, 0,
|
||||||
|
RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN, buf);
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
return r;
|
error_setg_errno(errp, -r, "cannot read image start for probe");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bdi->cluster_size = info.obj_size;
|
spec_info = g_new(ImageInfoSpecific, 1);
|
||||||
return 0;
|
*spec_info = (ImageInfoSpecific){
|
||||||
|
.type = IMAGE_INFO_SPECIFIC_KIND_RBD,
|
||||||
|
.u.rbd.data = g_new0(ImageInfoSpecificRbd, 1),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (memcmp(buf, rbd_luks_header_verification,
|
||||||
|
RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) {
|
||||||
|
spec_info->u.rbd.data->encryption_format =
|
||||||
|
RBD_IMAGE_ENCRYPTION_FORMAT_LUKS;
|
||||||
|
spec_info->u.rbd.data->has_encryption_format = true;
|
||||||
|
} else if (memcmp(buf, rbd_luks2_header_verification,
|
||||||
|
RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) {
|
||||||
|
spec_info->u.rbd.data->encryption_format =
|
||||||
|
RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2;
|
||||||
|
spec_info->u.rbd.data->has_encryption_format = true;
|
||||||
|
} else {
|
||||||
|
spec_info->u.rbd.data->has_encryption_format = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return spec_info;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int64_t qemu_rbd_getlength(BlockDriverState *bs)
|
static int64_t qemu_rbd_getlength(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
BDRVRBDState *s = bs->opaque;
|
BDRVRBDState *s = bs->opaque;
|
||||||
rbd_image_info_t info;
|
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
r = rbd_stat(s->image, &info, sizeof(info));
|
r = rbd_get_size(s->image, &s->image_size);
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
return info.size;
|
return s->image_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int coroutine_fn qemu_rbd_co_truncate(BlockDriverState *bs,
|
static int coroutine_fn qemu_rbd_co_truncate(BlockDriverState *bs,
|
||||||
@ -1210,19 +1408,6 @@ static int qemu_rbd_snap_list(BlockDriverState *bs,
|
|||||||
return snap_count;
|
return snap_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef LIBRBD_SUPPORTS_DISCARD
|
|
||||||
static BlockAIOCB *qemu_rbd_aio_pdiscard(BlockDriverState *bs,
|
|
||||||
int64_t offset,
|
|
||||||
int bytes,
|
|
||||||
BlockCompletionFunc *cb,
|
|
||||||
void *opaque)
|
|
||||||
{
|
|
||||||
return rbd_start_aio(bs, offset, NULL, bytes, cb, opaque,
|
|
||||||
RBD_AIO_DISCARD);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef LIBRBD_SUPPORTS_INVALIDATE
|
|
||||||
static void coroutine_fn qemu_rbd_co_invalidate_cache(BlockDriverState *bs,
|
static void coroutine_fn qemu_rbd_co_invalidate_cache(BlockDriverState *bs,
|
||||||
Error **errp)
|
Error **errp)
|
||||||
{
|
{
|
||||||
@ -1232,7 +1417,6 @@ static void coroutine_fn qemu_rbd_co_invalidate_cache(BlockDriverState *bs,
|
|||||||
error_setg_errno(errp, -r, "Failed to invalidate the cache");
|
error_setg_errno(errp, -r, "Failed to invalidate the cache");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
static QemuOptsList qemu_rbd_create_opts = {
|
static QemuOptsList qemu_rbd_create_opts = {
|
||||||
.name = "rbd-create-opts",
|
.name = "rbd-create-opts",
|
||||||
@ -1253,6 +1437,22 @@ static QemuOptsList qemu_rbd_create_opts = {
|
|||||||
.type = QEMU_OPT_STRING,
|
.type = QEMU_OPT_STRING,
|
||||||
.help = "ID of secret providing the password",
|
.help = "ID of secret providing the password",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.name = "encrypt.format",
|
||||||
|
.type = QEMU_OPT_STRING,
|
||||||
|
.help = "Encrypt the image, format choices: 'luks', 'luks2'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "encrypt.cipher-alg",
|
||||||
|
.type = QEMU_OPT_STRING,
|
||||||
|
.help = "Name of encryption cipher algorithm"
|
||||||
|
" (allowed values: aes-128, aes-256)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "encrypt.key-secret",
|
||||||
|
.type = QEMU_OPT_STRING,
|
||||||
|
.help = "ID of secret providing LUKS passphrase",
|
||||||
|
},
|
||||||
{ /* end of list */ }
|
{ /* end of list */ }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1274,7 +1474,6 @@ static BlockDriver bdrv_rbd = {
|
|||||||
.format_name = "rbd",
|
.format_name = "rbd",
|
||||||
.instance_size = sizeof(BDRVRBDState),
|
.instance_size = sizeof(BDRVRBDState),
|
||||||
.bdrv_parse_filename = qemu_rbd_parse_filename,
|
.bdrv_parse_filename = qemu_rbd_parse_filename,
|
||||||
.bdrv_refresh_limits = qemu_rbd_refresh_limits,
|
|
||||||
.bdrv_file_open = qemu_rbd_open,
|
.bdrv_file_open = qemu_rbd_open,
|
||||||
.bdrv_close = qemu_rbd_close,
|
.bdrv_close = qemu_rbd_close,
|
||||||
.bdrv_reopen_prepare = qemu_rbd_reopen_prepare,
|
.bdrv_reopen_prepare = qemu_rbd_reopen_prepare,
|
||||||
@ -1282,31 +1481,25 @@ static BlockDriver bdrv_rbd = {
|
|||||||
.bdrv_co_create_opts = qemu_rbd_co_create_opts,
|
.bdrv_co_create_opts = qemu_rbd_co_create_opts,
|
||||||
.bdrv_has_zero_init = bdrv_has_zero_init_1,
|
.bdrv_has_zero_init = bdrv_has_zero_init_1,
|
||||||
.bdrv_get_info = qemu_rbd_getinfo,
|
.bdrv_get_info = qemu_rbd_getinfo,
|
||||||
|
.bdrv_get_specific_info = qemu_rbd_get_specific_info,
|
||||||
.create_opts = &qemu_rbd_create_opts,
|
.create_opts = &qemu_rbd_create_opts,
|
||||||
.bdrv_getlength = qemu_rbd_getlength,
|
.bdrv_getlength = qemu_rbd_getlength,
|
||||||
.bdrv_co_truncate = qemu_rbd_co_truncate,
|
.bdrv_co_truncate = qemu_rbd_co_truncate,
|
||||||
.protocol_name = "rbd",
|
.protocol_name = "rbd",
|
||||||
|
|
||||||
.bdrv_aio_preadv = qemu_rbd_aio_preadv,
|
.bdrv_co_preadv = qemu_rbd_co_preadv,
|
||||||
.bdrv_aio_pwritev = qemu_rbd_aio_pwritev,
|
.bdrv_co_pwritev = qemu_rbd_co_pwritev,
|
||||||
|
|
||||||
#ifdef LIBRBD_SUPPORTS_AIO_FLUSH
|
|
||||||
.bdrv_aio_flush = qemu_rbd_aio_flush,
|
|
||||||
#else
|
|
||||||
.bdrv_co_flush_to_disk = qemu_rbd_co_flush,
|
.bdrv_co_flush_to_disk = qemu_rbd_co_flush,
|
||||||
#endif
|
.bdrv_co_pdiscard = qemu_rbd_co_pdiscard,
|
||||||
|
#ifdef LIBRBD_SUPPORTS_WRITE_ZEROES
|
||||||
#ifdef LIBRBD_SUPPORTS_DISCARD
|
.bdrv_co_pwrite_zeroes = qemu_rbd_co_pwrite_zeroes,
|
||||||
.bdrv_aio_pdiscard = qemu_rbd_aio_pdiscard,
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
.bdrv_snapshot_create = qemu_rbd_snap_create,
|
.bdrv_snapshot_create = qemu_rbd_snap_create,
|
||||||
.bdrv_snapshot_delete = qemu_rbd_snap_remove,
|
.bdrv_snapshot_delete = qemu_rbd_snap_remove,
|
||||||
.bdrv_snapshot_list = qemu_rbd_snap_list,
|
.bdrv_snapshot_list = qemu_rbd_snap_list,
|
||||||
.bdrv_snapshot_goto = qemu_rbd_snap_rollback,
|
.bdrv_snapshot_goto = qemu_rbd_snap_rollback,
|
||||||
#ifdef LIBRBD_SUPPORTS_INVALIDATE
|
|
||||||
.bdrv_co_invalidate_cache = qemu_rbd_co_invalidate_cache,
|
.bdrv_co_invalidate_cache = qemu_rbd_co_invalidate_cache,
|
||||||
#endif
|
|
||||||
|
|
||||||
.strong_runtime_opts = qemu_rbd_strong_runtime_opts,
|
.strong_runtime_opts = qemu_rbd_strong_runtime_opts,
|
||||||
};
|
};
|
||||||
|
@ -390,7 +390,14 @@ static void reopen_backing_file(BlockDriverState *bs, bool writable,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (reopen_queue) {
|
if (reopen_queue) {
|
||||||
|
AioContext *ctx = bdrv_get_aio_context(bs);
|
||||||
|
if (ctx != qemu_get_aio_context()) {
|
||||||
|
aio_context_release(ctx);
|
||||||
|
}
|
||||||
bdrv_reopen_multiple(reopen_queue, errp);
|
bdrv_reopen_multiple(reopen_queue, errp);
|
||||||
|
if (ctx != qemu_get_aio_context()) {
|
||||||
|
aio_context_acquire(ctx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bdrv_subtree_drained_end(s->hidden_disk->bs);
|
bdrv_subtree_drained_end(s->hidden_disk->bs);
|
||||||
|
@ -237,9 +237,7 @@ static int parse_uri(const char *filename, QDict *options, Error **errp)
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
err:
|
err:
|
||||||
if (uri) {
|
|
||||||
uri_free(uri);
|
uri_free(uri);
|
||||||
}
|
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
33
blockdev.c
33
blockdev.c
@ -1714,6 +1714,7 @@ static void drive_backup_prepare(BlkActionState *common, Error **errp)
|
|||||||
aio_context = bdrv_get_aio_context(bs);
|
aio_context = bdrv_get_aio_context(bs);
|
||||||
aio_context_acquire(aio_context);
|
aio_context_acquire(aio_context);
|
||||||
|
|
||||||
|
state->bs = bs;
|
||||||
/* Paired with .clean() */
|
/* Paired with .clean() */
|
||||||
bdrv_drained_begin(bs);
|
bdrv_drained_begin(bs);
|
||||||
|
|
||||||
@ -1813,8 +1814,6 @@ static void drive_backup_prepare(BlkActionState *common, Error **errp)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state->bs = bs;
|
|
||||||
|
|
||||||
state->job = do_backup_common(qapi_DriveBackup_base(backup),
|
state->job = do_backup_common(qapi_DriveBackup_base(backup),
|
||||||
bs, target_bs, aio_context,
|
bs, target_bs, aio_context,
|
||||||
common->block_job_txn, errp);
|
common->block_job_txn, errp);
|
||||||
@ -3560,13 +3559,18 @@ fail:
|
|||||||
visit_free(v);
|
visit_free(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
void qmp_x_blockdev_reopen(BlockdevOptions *options, Error **errp)
|
void qmp_blockdev_reopen(BlockdevOptionsList *reopen_list, Error **errp)
|
||||||
{
|
{
|
||||||
|
BlockReopenQueue *queue = NULL;
|
||||||
|
GSList *drained = NULL;
|
||||||
|
|
||||||
|
/* Add each one of the BDS that we want to reopen to the queue */
|
||||||
|
for (; reopen_list != NULL; reopen_list = reopen_list->next) {
|
||||||
|
BlockdevOptions *options = reopen_list->value;
|
||||||
BlockDriverState *bs;
|
BlockDriverState *bs;
|
||||||
AioContext *ctx;
|
AioContext *ctx;
|
||||||
QObject *obj;
|
QObject *obj;
|
||||||
Visitor *v = qobject_output_visitor_new(&obj);
|
Visitor *v;
|
||||||
BlockReopenQueue *queue;
|
|
||||||
QDict *qdict;
|
QDict *qdict;
|
||||||
|
|
||||||
/* Check for the selected node name */
|
/* Check for the selected node name */
|
||||||
@ -3583,23 +3587,32 @@ void qmp_x_blockdev_reopen(BlockdevOptions *options, Error **errp)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Put all options in a QDict and flatten it */
|
/* Put all options in a QDict and flatten it */
|
||||||
|
v = qobject_output_visitor_new(&obj);
|
||||||
visit_type_BlockdevOptions(v, NULL, &options, &error_abort);
|
visit_type_BlockdevOptions(v, NULL, &options, &error_abort);
|
||||||
visit_complete(v, &obj);
|
visit_complete(v, &obj);
|
||||||
|
visit_free(v);
|
||||||
|
|
||||||
qdict = qobject_to(QDict, obj);
|
qdict = qobject_to(QDict, obj);
|
||||||
|
|
||||||
qdict_flatten(qdict);
|
qdict_flatten(qdict);
|
||||||
|
|
||||||
/* Perform the reopen operation */
|
|
||||||
ctx = bdrv_get_aio_context(bs);
|
ctx = bdrv_get_aio_context(bs);
|
||||||
aio_context_acquire(ctx);
|
aio_context_acquire(ctx);
|
||||||
|
|
||||||
bdrv_subtree_drained_begin(bs);
|
bdrv_subtree_drained_begin(bs);
|
||||||
queue = bdrv_reopen_queue(NULL, bs, qdict, false);
|
queue = bdrv_reopen_queue(queue, bs, qdict, false);
|
||||||
bdrv_reopen_multiple(queue, errp);
|
drained = g_slist_prepend(drained, bs);
|
||||||
bdrv_subtree_drained_end(bs);
|
|
||||||
aio_context_release(ctx);
|
aio_context_release(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Perform the reopen operation */
|
||||||
|
bdrv_reopen_multiple(queue, errp);
|
||||||
|
queue = NULL;
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
visit_free(v);
|
bdrv_reopen_queue_free(queue);
|
||||||
|
g_slist_free_full(drained, (GDestroyNotify) bdrv_subtree_drained_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
void qmp_blockdev_del(const char *node_name, Error **errp)
|
void qmp_blockdev_del(const char *node_name, Error **errp)
|
||||||
|
@ -300,38 +300,6 @@ this CPU is also deprecated.
|
|||||||
Related binaries
|
Related binaries
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
qemu-img amend to adjust backing file (since 5.1)
|
|
||||||
'''''''''''''''''''''''''''''''''''''''''''''''''
|
|
||||||
|
|
||||||
The use of ``qemu-img amend`` to modify the name or format of a qcow2
|
|
||||||
backing image is deprecated; this functionality was never fully
|
|
||||||
documented or tested, and interferes with other amend operations that
|
|
||||||
need access to the original backing image (such as deciding whether a
|
|
||||||
v3 zero cluster may be left unallocated when converting to a v2
|
|
||||||
image). Rather, any changes to the backing chain should be performed
|
|
||||||
with ``qemu-img rebase -u`` either before or after the remaining
|
|
||||||
changes being performed by amend, as appropriate.
|
|
||||||
|
|
||||||
qemu-img backing file without format (since 5.1)
|
|
||||||
''''''''''''''''''''''''''''''''''''''''''''''''
|
|
||||||
|
|
||||||
The use of ``qemu-img create``, ``qemu-img rebase``, or ``qemu-img
|
|
||||||
convert`` to create or modify an image that depends on a backing file
|
|
||||||
now recommends that an explicit backing format be provided. This is
|
|
||||||
for safety: if QEMU probes a different format than what you thought,
|
|
||||||
the data presented to the guest will be corrupt; similarly, presenting
|
|
||||||
a raw image to a guest allows a potential security exploit if a future
|
|
||||||
probe sees a non-raw image based on guest writes.
|
|
||||||
|
|
||||||
To avoid the warning message, or even future refusal to create an
|
|
||||||
unsafe image, you must pass ``-o backing_fmt=`` (or the shorthand
|
|
||||||
``-F`` during create) to specify the intended backing format. You may
|
|
||||||
use ``qemu-img rebase -u`` to retroactively add a backing format to an
|
|
||||||
existing image. However, be aware that there are already potential
|
|
||||||
security risks to blindly using ``qemu-img info`` to probe the format
|
|
||||||
of an untrusted backing image, when deciding what format to add into
|
|
||||||
an existing image.
|
|
||||||
|
|
||||||
Backwards compatibility
|
Backwards compatibility
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
|
@ -491,6 +491,37 @@ topologies described with -smp include all possible cpus, i.e.
|
|||||||
The ``enforce-config-section`` property was replaced by the
|
The ``enforce-config-section`` property was replaced by the
|
||||||
``-global migration.send-configuration={on|off}`` option.
|
``-global migration.send-configuration={on|off}`` option.
|
||||||
|
|
||||||
|
qemu-img amend to adjust backing file (removed in 6.1)
|
||||||
|
''''''''''''''''''''''''''''''''''''''''''''''''''''''
|
||||||
|
|
||||||
|
The use of ``qemu-img amend`` to modify the name or format of a qcow2
|
||||||
|
backing image was never fully documented or tested, and interferes
|
||||||
|
with other amend operations that need access to the original backing
|
||||||
|
image (such as deciding whether a v3 zero cluster may be left
|
||||||
|
unallocated when converting to a v2 image). Any changes to the
|
||||||
|
backing chain should be performed with ``qemu-img rebase -u`` either
|
||||||
|
before or after the remaining changes being performed by amend, as
|
||||||
|
appropriate.
|
||||||
|
|
||||||
|
qemu-img backing file without format (removed in 6.1)
|
||||||
|
'''''''''''''''''''''''''''''''''''''''''''''''''''''
|
||||||
|
|
||||||
|
The use of ``qemu-img create``, ``qemu-img rebase``, or ``qemu-img
|
||||||
|
convert`` to create or modify an image that depends on a backing file
|
||||||
|
now requires that an explicit backing format be provided. This is
|
||||||
|
for safety: if QEMU probes a different format than what you thought,
|
||||||
|
the data presented to the guest will be corrupt; similarly, presenting
|
||||||
|
a raw image to a guest allows a potential security exploit if a future
|
||||||
|
probe sees a non-raw image based on guest writes.
|
||||||
|
|
||||||
|
To avoid creating unsafe backing chains, you must pass ``-o
|
||||||
|
backing_fmt=`` (or the shorthand ``-F`` during create) to specify the
|
||||||
|
intended backing format. You may use ``qemu-img rebase -u`` to
|
||||||
|
retroactively add a backing format to an existing image. However, be
|
||||||
|
aware that there are already potential security risks to blindly using
|
||||||
|
``qemu-img info`` to probe the format of an untrusted backing image,
|
||||||
|
when deciding what format to add into an existing image.
|
||||||
|
|
||||||
Block devices
|
Block devices
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
@ -1913,7 +1913,10 @@ static int vhost_user_backend_init(struct vhost_dev *dev, void *opaque,
|
|||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
return -EPROTO;
|
return -EPROTO;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
dev->max_queues = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dev->num_queues && dev->max_queues < dev->num_queues) {
|
if (dev->num_queues && dev->max_queues < dev->num_queues) {
|
||||||
error_setg(errp, "The maximum number of queues supported by the "
|
error_setg(errp, "The maximum number of queues supported by the "
|
||||||
"backend is %" PRIu64, dev->max_queues);
|
"backend is %" PRIu64, dev->max_queues);
|
||||||
|
@ -386,7 +386,10 @@ BlockDriverState *bdrv_new_open_driver(BlockDriver *drv, const char *node_name,
|
|||||||
BlockReopenQueue *bdrv_reopen_queue(BlockReopenQueue *bs_queue,
|
BlockReopenQueue *bdrv_reopen_queue(BlockReopenQueue *bs_queue,
|
||||||
BlockDriverState *bs, QDict *options,
|
BlockDriverState *bs, QDict *options,
|
||||||
bool keep_old_opts);
|
bool keep_old_opts);
|
||||||
|
void bdrv_reopen_queue_free(BlockReopenQueue *bs_queue);
|
||||||
int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp);
|
int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp);
|
||||||
|
int bdrv_reopen(BlockDriverState *bs, QDict *opts, bool keep_old_opts,
|
||||||
|
Error **errp);
|
||||||
int bdrv_reopen_set_read_only(BlockDriverState *bs, bool read_only,
|
int bdrv_reopen_set_read_only(BlockDriverState *bs, bool read_only,
|
||||||
Error **errp);
|
Error **errp);
|
||||||
int bdrv_pwrite_zeroes(BdrvChild *child, int64_t offset,
|
int bdrv_pwrite_zeroes(BdrvChild *child, int64_t offset,
|
||||||
|
@ -710,13 +710,16 @@ if not get_option('rbd').auto() or have_block
|
|||||||
int main(void) {
|
int main(void) {
|
||||||
rados_t cluster;
|
rados_t cluster;
|
||||||
rados_create(&cluster, NULL);
|
rados_create(&cluster, NULL);
|
||||||
|
#if LIBRBD_VERSION_CODE < LIBRBD_VERSION(1, 12, 0)
|
||||||
|
#error
|
||||||
|
#endif
|
||||||
return 0;
|
return 0;
|
||||||
}''', dependencies: [librbd, librados])
|
}''', dependencies: [librbd, librados])
|
||||||
rbd = declare_dependency(dependencies: [librbd, librados])
|
rbd = declare_dependency(dependencies: [librbd, librados])
|
||||||
elif get_option('rbd').enabled()
|
elif get_option('rbd').enabled()
|
||||||
error('could not link librados')
|
error('librbd >= 1.12.0 required')
|
||||||
else
|
else
|
||||||
warning('could not link librados, disabling')
|
warning('librbd >= 1.12.0 not found, disabling')
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
@ -127,6 +127,18 @@
|
|||||||
'extents': ['ImageInfo']
|
'extents': ['ImageInfo']
|
||||||
} }
|
} }
|
||||||
|
|
||||||
|
##
|
||||||
|
# @ImageInfoSpecificRbd:
|
||||||
|
#
|
||||||
|
# @encryption-format: Image encryption format
|
||||||
|
#
|
||||||
|
# Since: 6.1
|
||||||
|
##
|
||||||
|
{ 'struct': 'ImageInfoSpecificRbd',
|
||||||
|
'data': {
|
||||||
|
'*encryption-format': 'RbdImageEncryptionFormat'
|
||||||
|
} }
|
||||||
|
|
||||||
##
|
##
|
||||||
# @ImageInfoSpecific:
|
# @ImageInfoSpecific:
|
||||||
#
|
#
|
||||||
@ -141,7 +153,8 @@
|
|||||||
# If we need to add block driver specific parameters for
|
# If we need to add block driver specific parameters for
|
||||||
# LUKS in future, then we'll subclass QCryptoBlockInfoLUKS
|
# LUKS in future, then we'll subclass QCryptoBlockInfoLUKS
|
||||||
# to define a ImageInfoSpecificLUKS
|
# to define a ImageInfoSpecificLUKS
|
||||||
'luks': 'QCryptoBlockInfoLUKS'
|
'luks': 'QCryptoBlockInfoLUKS',
|
||||||
|
'rbd': 'ImageInfoSpecificRbd'
|
||||||
} }
|
} }
|
||||||
|
|
||||||
##
|
##
|
||||||
@ -3613,6 +3626,94 @@
|
|||||||
{ 'enum': 'RbdAuthMode',
|
{ 'enum': 'RbdAuthMode',
|
||||||
'data': [ 'cephx', 'none' ] }
|
'data': [ 'cephx', 'none' ] }
|
||||||
|
|
||||||
|
##
|
||||||
|
# @RbdImageEncryptionFormat:
|
||||||
|
#
|
||||||
|
# Since: 6.1
|
||||||
|
##
|
||||||
|
{ 'enum': 'RbdImageEncryptionFormat',
|
||||||
|
'data': [ 'luks', 'luks2' ] }
|
||||||
|
|
||||||
|
##
|
||||||
|
# @RbdEncryptionOptionsLUKSBase:
|
||||||
|
#
|
||||||
|
# @key-secret: ID of a QCryptoSecret object providing a passphrase
|
||||||
|
# for unlocking the encryption
|
||||||
|
#
|
||||||
|
# Since: 6.1
|
||||||
|
##
|
||||||
|
{ 'struct': 'RbdEncryptionOptionsLUKSBase',
|
||||||
|
'data': { 'key-secret': 'str' } }
|
||||||
|
|
||||||
|
##
|
||||||
|
# @RbdEncryptionCreateOptionsLUKSBase:
|
||||||
|
#
|
||||||
|
# @cipher-alg: The encryption algorithm
|
||||||
|
#
|
||||||
|
# Since: 6.1
|
||||||
|
##
|
||||||
|
{ 'struct': 'RbdEncryptionCreateOptionsLUKSBase',
|
||||||
|
'base': 'RbdEncryptionOptionsLUKSBase',
|
||||||
|
'data': { '*cipher-alg': 'QCryptoCipherAlgorithm' } }
|
||||||
|
|
||||||
|
##
|
||||||
|
# @RbdEncryptionOptionsLUKS:
|
||||||
|
#
|
||||||
|
# Since: 6.1
|
||||||
|
##
|
||||||
|
{ 'struct': 'RbdEncryptionOptionsLUKS',
|
||||||
|
'base': 'RbdEncryptionOptionsLUKSBase',
|
||||||
|
'data': { } }
|
||||||
|
|
||||||
|
##
|
||||||
|
# @RbdEncryptionOptionsLUKS2:
|
||||||
|
#
|
||||||
|
# Since: 6.1
|
||||||
|
##
|
||||||
|
{ 'struct': 'RbdEncryptionOptionsLUKS2',
|
||||||
|
'base': 'RbdEncryptionOptionsLUKSBase',
|
||||||
|
'data': { } }
|
||||||
|
|
||||||
|
##
|
||||||
|
# @RbdEncryptionCreateOptionsLUKS:
|
||||||
|
#
|
||||||
|
# Since: 6.1
|
||||||
|
##
|
||||||
|
{ 'struct': 'RbdEncryptionCreateOptionsLUKS',
|
||||||
|
'base': 'RbdEncryptionCreateOptionsLUKSBase',
|
||||||
|
'data': { } }
|
||||||
|
|
||||||
|
##
|
||||||
|
# @RbdEncryptionCreateOptionsLUKS2:
|
||||||
|
#
|
||||||
|
# Since: 6.1
|
||||||
|
##
|
||||||
|
{ 'struct': 'RbdEncryptionCreateOptionsLUKS2',
|
||||||
|
'base': 'RbdEncryptionCreateOptionsLUKSBase',
|
||||||
|
'data': { } }
|
||||||
|
|
||||||
|
##
|
||||||
|
# @RbdEncryptionOptions:
|
||||||
|
#
|
||||||
|
# Since: 6.1
|
||||||
|
##
|
||||||
|
{ 'union': 'RbdEncryptionOptions',
|
||||||
|
'base': { 'format': 'RbdImageEncryptionFormat' },
|
||||||
|
'discriminator': 'format',
|
||||||
|
'data': { 'luks': 'RbdEncryptionOptionsLUKS',
|
||||||
|
'luks2': 'RbdEncryptionOptionsLUKS2' } }
|
||||||
|
|
||||||
|
##
|
||||||
|
# @RbdEncryptionCreateOptions:
|
||||||
|
#
|
||||||
|
# Since: 6.1
|
||||||
|
##
|
||||||
|
{ 'union': 'RbdEncryptionCreateOptions',
|
||||||
|
'base': { 'format': 'RbdImageEncryptionFormat' },
|
||||||
|
'discriminator': 'format',
|
||||||
|
'data': { 'luks': 'RbdEncryptionCreateOptionsLUKS',
|
||||||
|
'luks2': 'RbdEncryptionCreateOptionsLUKS2' } }
|
||||||
|
|
||||||
##
|
##
|
||||||
# @BlockdevOptionsRbd:
|
# @BlockdevOptionsRbd:
|
||||||
#
|
#
|
||||||
@ -3628,6 +3729,8 @@
|
|||||||
#
|
#
|
||||||
# @snapshot: Ceph snapshot name.
|
# @snapshot: Ceph snapshot name.
|
||||||
#
|
#
|
||||||
|
# @encrypt: Image encryption options. (Since 6.1)
|
||||||
|
#
|
||||||
# @user: Ceph id name.
|
# @user: Ceph id name.
|
||||||
#
|
#
|
||||||
# @auth-client-required: Acceptable authentication modes.
|
# @auth-client-required: Acceptable authentication modes.
|
||||||
@ -3650,6 +3753,7 @@
|
|||||||
'image': 'str',
|
'image': 'str',
|
||||||
'*conf': 'str',
|
'*conf': 'str',
|
||||||
'*snapshot': 'str',
|
'*snapshot': 'str',
|
||||||
|
'*encrypt': 'RbdEncryptionOptions',
|
||||||
'*user': 'str',
|
'*user': 'str',
|
||||||
'*auth-client-required': ['RbdAuthMode'],
|
'*auth-client-required': ['RbdAuthMode'],
|
||||||
'*key-secret': 'str',
|
'*key-secret': 'str',
|
||||||
@ -4115,15 +4219,17 @@
|
|||||||
{ 'command': 'blockdev-add', 'data': 'BlockdevOptions', 'boxed': true }
|
{ 'command': 'blockdev-add', 'data': 'BlockdevOptions', 'boxed': true }
|
||||||
|
|
||||||
##
|
##
|
||||||
# @x-blockdev-reopen:
|
# @blockdev-reopen:
|
||||||
#
|
#
|
||||||
# Reopens a block device using the given set of options. Any option
|
# Reopens one or more block devices using the given set of options.
|
||||||
# not specified will be reset to its default value regardless of its
|
# Any option not specified will be reset to its default value regardless
|
||||||
# previous status. If an option cannot be changed or a particular
|
# of its previous status. If an option cannot be changed or a particular
|
||||||
# driver does not support reopening then the command will return an
|
# driver does not support reopening then the command will return an
|
||||||
# error.
|
# error. All devices in the list are reopened in one transaction, so
|
||||||
|
# if one of them fails then the whole transaction is cancelled.
|
||||||
#
|
#
|
||||||
# The top-level @node-name option (from BlockdevOptions) must be
|
# The command receives a list of block devices to reopen. For each one
|
||||||
|
# of them, the top-level @node-name option (from BlockdevOptions) must be
|
||||||
# specified and is used to select the block device to be reopened.
|
# specified and is used to select the block device to be reopened.
|
||||||
# Other @node-name options must be either omitted or set to the
|
# Other @node-name options must be either omitted or set to the
|
||||||
# current name of the appropriate node. This command won't change any
|
# current name of the appropriate node. This command won't change any
|
||||||
@ -4143,18 +4249,18 @@
|
|||||||
#
|
#
|
||||||
# 4) NULL: the current child (if any) is detached.
|
# 4) NULL: the current child (if any) is detached.
|
||||||
#
|
#
|
||||||
# Options (1) and (2) are supported in all cases, but at the moment
|
# Options (1) and (2) are supported in all cases. Option (3) is
|
||||||
# only @backing allows replacing or detaching an existing child.
|
# supported for @file and @backing, and option (4) for @backing only.
|
||||||
#
|
#
|
||||||
# Unlike with blockdev-add, the @backing option must always be present
|
# Unlike with blockdev-add, the @backing option must always be present
|
||||||
# unless the node being reopened does not have a backing file and its
|
# unless the node being reopened does not have a backing file and its
|
||||||
# image does not have a default backing file name as part of its
|
# image does not have a default backing file name as part of its
|
||||||
# metadata.
|
# metadata.
|
||||||
#
|
#
|
||||||
# Since: 4.0
|
# Since: 6.1
|
||||||
##
|
##
|
||||||
{ 'command': 'x-blockdev-reopen',
|
{ 'command': 'blockdev-reopen',
|
||||||
'data': 'BlockdevOptions', 'boxed': true }
|
'data': { 'options': ['BlockdevOptions'] } }
|
||||||
|
|
||||||
##
|
##
|
||||||
# @blockdev-del:
|
# @blockdev-del:
|
||||||
@ -4403,13 +4509,15 @@
|
|||||||
# point to a snapshot.
|
# point to a snapshot.
|
||||||
# @size: Size of the virtual disk in bytes
|
# @size: Size of the virtual disk in bytes
|
||||||
# @cluster-size: RBD object size
|
# @cluster-size: RBD object size
|
||||||
|
# @encrypt: Image encryption options. (Since 6.1)
|
||||||
#
|
#
|
||||||
# Since: 2.12
|
# Since: 2.12
|
||||||
##
|
##
|
||||||
{ 'struct': 'BlockdevCreateOptionsRbd',
|
{ 'struct': 'BlockdevCreateOptionsRbd',
|
||||||
'data': { 'location': 'BlockdevOptionsRbd',
|
'data': { 'location': 'BlockdevOptionsRbd',
|
||||||
'size': 'size',
|
'size': 'size',
|
||||||
'*cluster-size' : 'size' } }
|
'*cluster-size' : 'size',
|
||||||
|
'*encrypt' : 'RbdEncryptionCreateOptions' } }
|
||||||
|
|
||||||
##
|
##
|
||||||
# @BlockdevVmdkSubformat:
|
# @BlockdevVmdkSubformat:
|
||||||
|
@ -120,6 +120,23 @@
|
|||||||
'*logical-block-size': 'size',
|
'*logical-block-size': 'size',
|
||||||
'*num-queues': 'uint16'} }
|
'*num-queues': 'uint16'} }
|
||||||
|
|
||||||
|
##
|
||||||
|
# @FuseExportAllowOther:
|
||||||
|
#
|
||||||
|
# Possible allow_other modes for FUSE exports.
|
||||||
|
#
|
||||||
|
# @off: Do not pass allow_other as a mount option.
|
||||||
|
#
|
||||||
|
# @on: Pass allow_other as a mount option.
|
||||||
|
#
|
||||||
|
# @auto: Try mounting with allow_other first, and if that fails, retry
|
||||||
|
# without allow_other.
|
||||||
|
#
|
||||||
|
# Since: 6.1
|
||||||
|
##
|
||||||
|
{ 'enum': 'FuseExportAllowOther',
|
||||||
|
'data': ['off', 'on', 'auto'] }
|
||||||
|
|
||||||
##
|
##
|
||||||
# @BlockExportOptionsFuse:
|
# @BlockExportOptionsFuse:
|
||||||
#
|
#
|
||||||
@ -132,11 +149,25 @@
|
|||||||
# @growable: Whether writes beyond the EOF should grow the block node
|
# @growable: Whether writes beyond the EOF should grow the block node
|
||||||
# accordingly. (default: false)
|
# accordingly. (default: false)
|
||||||
#
|
#
|
||||||
|
# @allow-other: If this is off, only qemu's user is allowed access to
|
||||||
|
# this export. That cannot be changed even with chmod or
|
||||||
|
# chown.
|
||||||
|
# Enabling this option will allow other users access to
|
||||||
|
# the export with the FUSE mount option "allow_other".
|
||||||
|
# Note that using allow_other as a non-root user requires
|
||||||
|
# user_allow_other to be enabled in the global fuse.conf
|
||||||
|
# configuration file.
|
||||||
|
# In auto mode (the default), the FUSE export driver will
|
||||||
|
# first attempt to mount the export with allow_other, and
|
||||||
|
# if that fails, try again without.
|
||||||
|
# (since 6.1; default: auto)
|
||||||
|
#
|
||||||
# Since: 6.0
|
# Since: 6.0
|
||||||
##
|
##
|
||||||
{ 'struct': 'BlockExportOptionsFuse',
|
{ 'struct': 'BlockExportOptionsFuse',
|
||||||
'data': { 'mountpoint': 'str',
|
'data': { 'mountpoint': 'str',
|
||||||
'*growable': 'bool' },
|
'*growable': 'bool',
|
||||||
|
'*allow-other': 'FuseExportAllowOther' },
|
||||||
'if': 'defined(CONFIG_FUSE)' }
|
'if': 'defined(CONFIG_FUSE)' }
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -2508,8 +2508,10 @@ static int img_convert(int argc, char **argv)
|
|||||||
|
|
||||||
if (out_baseimg_param) {
|
if (out_baseimg_param) {
|
||||||
if (!qemu_opt_get(opts, BLOCK_OPT_BACKING_FMT)) {
|
if (!qemu_opt_get(opts, BLOCK_OPT_BACKING_FMT)) {
|
||||||
warn_report("Deprecated use of backing file without explicit "
|
error_report("Use of backing file requires explicit "
|
||||||
"backing format");
|
"backing format");
|
||||||
|
ret = -1;
|
||||||
|
goto out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3765,6 +3767,9 @@ static int img_rebase(int argc, char **argv)
|
|||||||
if (ret == -ENOSPC) {
|
if (ret == -ENOSPC) {
|
||||||
error_report("Could not change the backing file to '%s': No "
|
error_report("Could not change the backing file to '%s': No "
|
||||||
"space left in the file header", out_baseimg);
|
"space left in the file header", out_baseimg);
|
||||||
|
} else if (ret == -EINVAL && out_baseimg && !out_basefmt) {
|
||||||
|
error_report("Could not change the backing file to '%s': backing "
|
||||||
|
"format must be specified", out_baseimg);
|
||||||
} else if (ret < 0) {
|
} else if (ret < 0) {
|
||||||
error_report("Could not change the backing file to '%s': %s",
|
error_report("Could not change the backing file to '%s': %s",
|
||||||
out_baseimg, strerror(-ret));
|
out_baseimg, strerror(-ret));
|
||||||
|
@ -2116,8 +2116,6 @@ static int reopen_f(BlockBackend *blk, int argc, char **argv)
|
|||||||
bool writethrough = !blk_enable_write_cache(blk);
|
bool writethrough = !blk_enable_write_cache(blk);
|
||||||
bool has_rw_option = false;
|
bool has_rw_option = false;
|
||||||
bool has_cache_option = false;
|
bool has_cache_option = false;
|
||||||
|
|
||||||
BlockReopenQueue *brq;
|
|
||||||
Error *local_err = NULL;
|
Error *local_err = NULL;
|
||||||
|
|
||||||
while ((c = getopt(argc, argv, "c:o:rw")) != -1) {
|
while ((c = getopt(argc, argv, "c:o:rw")) != -1) {
|
||||||
@ -2210,10 +2208,7 @@ static int reopen_f(BlockBackend *blk, int argc, char **argv)
|
|||||||
qdict_put_bool(opts, BDRV_OPT_CACHE_NO_FLUSH, flags & BDRV_O_NO_FLUSH);
|
qdict_put_bool(opts, BDRV_OPT_CACHE_NO_FLUSH, flags & BDRV_O_NO_FLUSH);
|
||||||
}
|
}
|
||||||
|
|
||||||
bdrv_subtree_drained_begin(bs);
|
bdrv_reopen(bs, opts, true, &local_err);
|
||||||
brq = bdrv_reopen_queue(NULL, bs, opts, true);
|
|
||||||
bdrv_reopen_multiple(brq, &local_err);
|
|
||||||
bdrv_subtree_drained_end(bs);
|
|
||||||
|
|
||||||
if (local_err) {
|
if (local_err) {
|
||||||
error_report_err(local_err);
|
error_report_err(local_err);
|
||||||
|
@ -920,8 +920,8 @@ class TestCommitWithOverriddenBacking(iotests.QMPTestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
qemu_img('create', '-f', iotests.imgfmt, self.img_base_a, '1M')
|
qemu_img('create', '-f', iotests.imgfmt, self.img_base_a, '1M')
|
||||||
qemu_img('create', '-f', iotests.imgfmt, self.img_base_b, '1M')
|
qemu_img('create', '-f', iotests.imgfmt, self.img_base_b, '1M')
|
||||||
qemu_img('create', '-f', iotests.imgfmt, '-b', self.img_base_a, \
|
qemu_img('create', '-f', iotests.imgfmt, '-b', self.img_base_a,
|
||||||
self.img_top)
|
'-F', iotests.imgfmt, self.img_top)
|
||||||
|
|
||||||
self.vm = iotests.VM()
|
self.vm = iotests.VM()
|
||||||
self.vm.launch()
|
self.vm.launch()
|
||||||
|
@ -1295,8 +1295,10 @@ class TestReplaces(iotests.QMPTestCase):
|
|||||||
class TestFilters(iotests.QMPTestCase):
|
class TestFilters(iotests.QMPTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
qemu_img('create', '-f', iotests.imgfmt, backing_img, '1M')
|
qemu_img('create', '-f', iotests.imgfmt, backing_img, '1M')
|
||||||
qemu_img('create', '-f', iotests.imgfmt, '-b', backing_img, test_img)
|
qemu_img('create', '-f', iotests.imgfmt, '-b', backing_img,
|
||||||
qemu_img('create', '-f', iotests.imgfmt, '-b', backing_img, target_img)
|
'-F', iotests.imgfmt, test_img)
|
||||||
|
qemu_img('create', '-f', iotests.imgfmt, '-b', backing_img,
|
||||||
|
'-F', iotests.imgfmt, target_img)
|
||||||
|
|
||||||
qemu_io('-c', 'write -P 1 0 512k', backing_img)
|
qemu_io('-c', 'write -P 1 0 512k', backing_img)
|
||||||
qemu_io('-c', 'write -P 2 512k 512k', test_img)
|
qemu_io('-c', 'write -P 2 512k 512k', test_img)
|
||||||
|
@ -167,6 +167,9 @@ _make_test_img -o "compat=1.1" 64M
|
|||||||
TEST_IMG="$TEST_IMG.base" _make_test_img -o "compat=1.1" 64M
|
TEST_IMG="$TEST_IMG.base" _make_test_img -o "compat=1.1" 64M
|
||||||
$QEMU_IO -c "write -P 0x2a 0 128k" "$TEST_IMG.base" | _filter_qemu_io
|
$QEMU_IO -c "write -P 0x2a 0 128k" "$TEST_IMG.base" | _filter_qemu_io
|
||||||
$QEMU_IO -c "read -P 0 0 128k" "$TEST_IMG" | _filter_qemu_io
|
$QEMU_IO -c "read -P 0 0 128k" "$TEST_IMG" | _filter_qemu_io
|
||||||
|
$QEMU_IMG amend -o "backing_file=$TEST_IMG.base,backing_fmt=qcow2" \
|
||||||
|
"$TEST_IMG" && echo "unexpected pass"
|
||||||
|
$QEMU_IMG rebase -u -b "$TEST_IMG.base" -F qcow2 "$TEST_IMG"
|
||||||
$QEMU_IMG amend -o "backing_file=$TEST_IMG.base,backing_fmt=qcow2" "$TEST_IMG"
|
$QEMU_IMG amend -o "backing_file=$TEST_IMG.base,backing_fmt=qcow2" "$TEST_IMG"
|
||||||
$QEMU_IO -c "read -P 0x2a 0 128k" "$TEST_IMG" | _filter_qemu_io
|
$QEMU_IO -c "read -P 0x2a 0 128k" "$TEST_IMG" | _filter_qemu_io
|
||||||
_check_test_img
|
_check_test_img
|
||||||
|
@ -370,7 +370,8 @@ wrote 131072/131072 bytes at offset 0
|
|||||||
128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
read 131072/131072 bytes at offset 0
|
read 131072/131072 bytes at offset 0
|
||||||
128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
qemu-img: warning: Deprecated use of amend to alter the backing file; use qemu-img rebase instead
|
qemu-img: Cannot amend the backing file
|
||||||
|
You can use 'qemu-img rebase' instead.
|
||||||
read 131072/131072 bytes at offset 0
|
read 131072/131072 bytes at offset 0
|
||||||
128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
No errors were found on the image.
|
No errors were found on the image.
|
||||||
|
@ -808,12 +808,14 @@ Amend options for 'qcow2':
|
|||||||
size=<size> - Virtual disk size
|
size=<size> - Virtual disk size
|
||||||
|
|
||||||
Testing: amend -f qcow2 -o backing_file=TEST_DIR/t.qcow2,,help TEST_DIR/t.qcow2
|
Testing: amend -f qcow2 -o backing_file=TEST_DIR/t.qcow2,,help TEST_DIR/t.qcow2
|
||||||
qemu-img: warning: Deprecated use of amend to alter the backing file; use qemu-img rebase instead
|
qemu-img: Cannot amend the backing file
|
||||||
|
You can use 'qemu-img rebase' instead.
|
||||||
|
|
||||||
Testing: rebase -u -b -f qcow2 TEST_DIR/t.qcow2
|
Testing: rebase -u -b -f qcow2 TEST_DIR/t.qcow2
|
||||||
|
|
||||||
Testing: amend -f qcow2 -o backing_file=TEST_DIR/t.qcow2,,? TEST_DIR/t.qcow2
|
Testing: amend -f qcow2 -o backing_file=TEST_DIR/t.qcow2,,? TEST_DIR/t.qcow2
|
||||||
qemu-img: warning: Deprecated use of amend to alter the backing file; use qemu-img rebase instead
|
qemu-img: Cannot amend the backing file
|
||||||
|
You can use 'qemu-img rebase' instead.
|
||||||
|
|
||||||
Testing: rebase -u -b -f qcow2 TEST_DIR/t.qcow2
|
Testing: rebase -u -b -f qcow2 TEST_DIR/t.qcow2
|
||||||
|
|
||||||
|
@ -44,16 +44,16 @@ _supported_os Linux
|
|||||||
# qcow2.py does not work too well with external data files
|
# qcow2.py does not work too well with external data files
|
||||||
_unsupported_imgopts data_file
|
_unsupported_imgopts data_file
|
||||||
|
|
||||||
# Intentionally specify backing file without backing format; demonstrate
|
# Older qemu-img could set up backing file without backing format; modern
|
||||||
# the difference in warning messages when backing file could be probed.
|
# qemu can't but we can use qcow2.py to simulate older files.
|
||||||
# Note that only a non-raw probe result will affect the resulting image.
|
|
||||||
truncate -s $((64 * 1024 * 1024)) "$TEST_IMG.orig"
|
truncate -s $((64 * 1024 * 1024)) "$TEST_IMG.orig"
|
||||||
_make_test_img -b "$TEST_IMG.orig" 64M
|
_make_test_img -b "$TEST_IMG.orig" -F raw 64M
|
||||||
|
$PYTHON qcow2.py "$TEST_IMG" del-header-ext 0xE2792ACA
|
||||||
|
|
||||||
TEST_IMG="$TEST_IMG.base" _make_test_img 64M
|
TEST_IMG="$TEST_IMG.base" _make_test_img 64M
|
||||||
$QEMU_IMG convert -O qcow2 -B "$TEST_IMG.orig" "$TEST_IMG.orig" "$TEST_IMG"
|
$QEMU_IMG convert -O qcow2 -B "$TEST_IMG.orig" "$TEST_IMG.orig" "$TEST_IMG"
|
||||||
_make_test_img -b "$TEST_IMG.base" 64M
|
_make_test_img -b "$TEST_IMG.base" -F $IMGFMT 64M
|
||||||
_make_test_img -u -b "$TEST_IMG.base" 64M
|
_make_test_img -u -b "$TEST_IMG.base" -F $IMGFMT 64M
|
||||||
|
|
||||||
# Set an invalid backing file format
|
# Set an invalid backing file format
|
||||||
$PYTHON qcow2.py "$TEST_IMG" add-header-ext 0xE2792ACA "foo"
|
$PYTHON qcow2.py "$TEST_IMG" add-header-ext 0xE2792ACA "foo"
|
||||||
@ -64,9 +64,9 @@ _img_info
|
|||||||
$QEMU_IO -c "open $TEST_IMG" -c "read 0 4k" 2>&1 | _filter_qemu_io | _filter_testdir
|
$QEMU_IO -c "open $TEST_IMG" -c "read 0 4k" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
$QEMU_IO -c "open -o backing.driver=$IMGFMT $TEST_IMG" -c "read 0 4k" | _filter_qemu_io
|
$QEMU_IO -c "open -o backing.driver=$IMGFMT $TEST_IMG" -c "read 0 4k" | _filter_qemu_io
|
||||||
|
|
||||||
# Rebase the image, to show that omitting backing format triggers a warning,
|
# Rebase the image, to show that backing format is required.
|
||||||
# but probing now lets us use the backing file.
|
($QEMU_IMG rebase -u -b "$TEST_IMG.base" "$TEST_IMG" 2>&1 && echo "unexpected pass") | _filter_testdir
|
||||||
$QEMU_IMG rebase -u -b "$TEST_IMG.base" "$TEST_IMG"
|
$QEMU_IMG rebase -u -b "$TEST_IMG.base" -F $IMGFMT "$TEST_IMG"
|
||||||
$QEMU_IO -c "open $TEST_IMG" -c "read 0 4k" 2>&1 | _filter_qemu_io | _filter_testdir
|
$QEMU_IO -c "open $TEST_IMG" -c "read 0 4k" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
|
|
||||||
# success, all done
|
# success, all done
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
QA output created by 114
|
QA output created by 114
|
||||||
qemu-img: warning: Deprecated use of backing file without explicit backing format (detected format of raw)
|
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file=TEST_DIR/t.IMGFMT.orig backing_fmt=raw
|
||||||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file=TEST_DIR/t.IMGFMT.orig
|
|
||||||
Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=67108864
|
Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=67108864
|
||||||
qemu-img: warning: Deprecated use of backing file without explicit backing format
|
qemu-img: Use of backing file requires explicit backing format
|
||||||
qemu-img: warning: Deprecated use of backing file without explicit backing format (detected format of IMGFMT)
|
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file=TEST_DIR/t.IMGFMT.base backing_fmt=IMGFMT
|
||||||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file=TEST_DIR/t.IMGFMT.base backing_fmt=IMGFMT
|
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file=TEST_DIR/t.IMGFMT.base backing_fmt=IMGFMT
|
||||||
qemu-img: warning: Deprecated use of unopened backing file without explicit backing format, use of this image requires potentially unsafe format probing
|
|
||||||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file=TEST_DIR/t.IMGFMT.base
|
|
||||||
image: TEST_DIR/t.IMGFMT
|
image: TEST_DIR/t.IMGFMT
|
||||||
file format: IMGFMT
|
file format: IMGFMT
|
||||||
virtual size: 64 MiB (67108864 bytes)
|
virtual size: 64 MiB (67108864 bytes)
|
||||||
@ -17,7 +14,7 @@ qemu-io: can't open device TEST_DIR/t.qcow2: Could not open backing file: Unknow
|
|||||||
no file open, try 'help open'
|
no file open, try 'help open'
|
||||||
read 4096/4096 bytes at offset 0
|
read 4096/4096 bytes at offset 0
|
||||||
4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
qemu-img: warning: Deprecated use of backing file without explicit backing format, use of this image requires potentially unsafe format probing
|
qemu-img: Could not change the backing file to 'TEST_DIR/t.qcow2.base': backing format must be specified
|
||||||
read 4096/4096 bytes at offset 0
|
read 4096/4096 bytes at offset 0
|
||||||
4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
*** done
|
*** done
|
||||||
|
@ -261,9 +261,12 @@ class TestBlockdevMirrorReopen(MirrorBaseClass):
|
|||||||
result = self.vm.qmp('blockdev-add', node_name="backing",
|
result = self.vm.qmp('blockdev-add', node_name="backing",
|
||||||
driver="null-co")
|
driver="null-co")
|
||||||
self.assert_qmp(result, 'return', {})
|
self.assert_qmp(result, 'return', {})
|
||||||
result = self.vm.qmp('x-blockdev-reopen', node_name="target",
|
result = self.vm.qmp('blockdev-reopen', options=[{
|
||||||
driver=iotests.imgfmt, file="target-file",
|
'node-name': "target",
|
||||||
backing="backing")
|
'driver': iotests.imgfmt,
|
||||||
|
'file': "target-file",
|
||||||
|
'backing': "backing"
|
||||||
|
}])
|
||||||
self.assert_qmp(result, 'return', {})
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
class TestBlockdevMirrorReopenIothread(TestBlockdevMirrorReopen):
|
class TestBlockdevMirrorReopenIothread(TestBlockdevMirrorReopen):
|
||||||
|
@ -137,7 +137,7 @@ class TestPersistentDirtyBitmap(iotests.QMPTestCase):
|
|||||||
assert sha256_1 == self.getSha256()
|
assert sha256_1 == self.getSha256()
|
||||||
|
|
||||||
# Reopen to RW
|
# Reopen to RW
|
||||||
result = self.vm.qmp('x-blockdev-reopen', **{
|
result = self.vm.qmp('blockdev-reopen', options=[{
|
||||||
'node-name': 'node0',
|
'node-name': 'node0',
|
||||||
'driver': iotests.imgfmt,
|
'driver': iotests.imgfmt,
|
||||||
'file': {
|
'file': {
|
||||||
@ -145,7 +145,7 @@ class TestPersistentDirtyBitmap(iotests.QMPTestCase):
|
|||||||
'filename': disk
|
'filename': disk
|
||||||
},
|
},
|
||||||
'read-only': False
|
'read-only': False
|
||||||
})
|
}])
|
||||||
self.assert_qmp(result, 'return', {})
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
# Check that bitmap is reopened to RW and we can write to it.
|
# Check that bitmap is reopened to RW and we can write to it.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# group: rw
|
# group: rw
|
||||||
#
|
#
|
||||||
# Test cases for the QMP 'x-blockdev-reopen' command
|
# Test cases for the QMP 'blockdev-reopen' command
|
||||||
#
|
#
|
||||||
# Copyright (C) 2018-2019 Igalia, S.L.
|
# Copyright (C) 2018-2019 Igalia, S.L.
|
||||||
# Author: Alberto Garcia <berto@igalia.com>
|
# Author: Alberto Garcia <berto@igalia.com>
|
||||||
@ -85,8 +85,18 @@ class TestBlockdevReopen(iotests.QMPTestCase):
|
|||||||
"Expected output of %d qemu-io commands, found %d" %
|
"Expected output of %d qemu-io commands, found %d" %
|
||||||
(found, self.total_io_cmds))
|
(found, self.total_io_cmds))
|
||||||
|
|
||||||
# Run x-blockdev-reopen with 'opts' but applying 'newopts'
|
# Run blockdev-reopen on a list of block devices
|
||||||
# on top of it. The original 'opts' dict is unmodified
|
def reopenMultiple(self, opts, errmsg = None):
|
||||||
|
result = self.vm.qmp('blockdev-reopen', conv_keys=False, options=opts)
|
||||||
|
if errmsg:
|
||||||
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
||||||
|
self.assert_qmp(result, 'error/desc', errmsg)
|
||||||
|
else:
|
||||||
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
|
# Run blockdev-reopen on a single block device (specified by
|
||||||
|
# 'opts') but applying 'newopts' on top of it. The original 'opts'
|
||||||
|
# dict is unmodified
|
||||||
def reopen(self, opts, newopts = {}, errmsg = None):
|
def reopen(self, opts, newopts = {}, errmsg = None):
|
||||||
opts = copy.deepcopy(opts)
|
opts = copy.deepcopy(opts)
|
||||||
|
|
||||||
@ -101,12 +111,7 @@ class TestBlockdevReopen(iotests.QMPTestCase):
|
|||||||
subdict = opts[prefix]
|
subdict = opts[prefix]
|
||||||
subdict[key] = value
|
subdict[key] = value
|
||||||
|
|
||||||
result = self.vm.qmp('x-blockdev-reopen', conv_keys = False, **opts)
|
self.reopenMultiple([ opts ], errmsg)
|
||||||
if errmsg:
|
|
||||||
self.assert_qmp(result, 'error/class', 'GenericError')
|
|
||||||
self.assert_qmp(result, 'error/desc', errmsg)
|
|
||||||
else:
|
|
||||||
self.assert_qmp(result, 'return', {})
|
|
||||||
|
|
||||||
|
|
||||||
# Run query-named-block-nodes and return the specified entry
|
# Run query-named-block-nodes and return the specified entry
|
||||||
@ -142,10 +147,10 @@ class TestBlockdevReopen(iotests.QMPTestCase):
|
|||||||
# We cannot change any of these
|
# We cannot change any of these
|
||||||
self.reopen(opts, {'node-name': 'not-found'}, "Failed to find node with node-name='not-found'")
|
self.reopen(opts, {'node-name': 'not-found'}, "Failed to find node with node-name='not-found'")
|
||||||
self.reopen(opts, {'node-name': ''}, "Failed to find node with node-name=''")
|
self.reopen(opts, {'node-name': ''}, "Failed to find node with node-name=''")
|
||||||
self.reopen(opts, {'node-name': None}, "Invalid parameter type for 'node-name', expected: string")
|
self.reopen(opts, {'node-name': None}, "Invalid parameter type for 'options[0].node-name', expected: string")
|
||||||
self.reopen(opts, {'driver': 'raw'}, "Cannot change the option 'driver'")
|
self.reopen(opts, {'driver': 'raw'}, "Cannot change the option 'driver'")
|
||||||
self.reopen(opts, {'driver': ''}, "Invalid parameter ''")
|
self.reopen(opts, {'driver': ''}, "Invalid parameter ''")
|
||||||
self.reopen(opts, {'driver': None}, "Invalid parameter type for 'driver', expected: string")
|
self.reopen(opts, {'driver': None}, "Invalid parameter type for 'options[0].driver', expected: string")
|
||||||
self.reopen(opts, {'file': 'not-found'}, "Cannot find device='' nor node-name='not-found'")
|
self.reopen(opts, {'file': 'not-found'}, "Cannot find device='' nor node-name='not-found'")
|
||||||
self.reopen(opts, {'file': ''}, "Cannot find device='' nor node-name=''")
|
self.reopen(opts, {'file': ''}, "Cannot find device='' nor node-name=''")
|
||||||
self.reopen(opts, {'file': None}, "Invalid parameter type for 'file', expected: BlockdevRef")
|
self.reopen(opts, {'file': None}, "Invalid parameter type for 'file', expected: BlockdevRef")
|
||||||
@ -154,9 +159,9 @@ class TestBlockdevReopen(iotests.QMPTestCase):
|
|||||||
self.reopen(opts, {'file.filename': hd_path[1]}, "Cannot change the option 'filename'")
|
self.reopen(opts, {'file.filename': hd_path[1]}, "Cannot change the option 'filename'")
|
||||||
self.reopen(opts, {'file.aio': 'native'}, "Cannot change the option 'aio'")
|
self.reopen(opts, {'file.aio': 'native'}, "Cannot change the option 'aio'")
|
||||||
self.reopen(opts, {'file.locking': 'off'}, "Cannot change the option 'locking'")
|
self.reopen(opts, {'file.locking': 'off'}, "Cannot change the option 'locking'")
|
||||||
self.reopen(opts, {'file.filename': None}, "Invalid parameter type for 'file.filename', expected: string")
|
self.reopen(opts, {'file.filename': None}, "Invalid parameter type for 'options[0].file.filename', expected: string")
|
||||||
|
|
||||||
# node-name is optional in BlockdevOptions, but x-blockdev-reopen needs it
|
# node-name is optional in BlockdevOptions, but blockdev-reopen needs it
|
||||||
del opts['node-name']
|
del opts['node-name']
|
||||||
self.reopen(opts, {}, "node-name not specified")
|
self.reopen(opts, {}, "node-name not specified")
|
||||||
|
|
||||||
@ -644,6 +649,53 @@ class TestBlockdevReopen(iotests.QMPTestCase):
|
|||||||
'-c', 'read -P 0x40 0x40008 1',
|
'-c', 'read -P 0x40 0x40008 1',
|
||||||
'-c', 'read -P 0x80 0x40010 1', hd_path[0])
|
'-c', 'read -P 0x80 0x40010 1', hd_path[0])
|
||||||
|
|
||||||
|
# Swap the disk images of two active block devices
|
||||||
|
def test_swap_files(self):
|
||||||
|
# Add hd0 and hd2 (none of them with backing files)
|
||||||
|
opts0 = hd_opts(0)
|
||||||
|
result = self.vm.qmp('blockdev-add', conv_keys = False, **opts0)
|
||||||
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
|
opts2 = hd_opts(2)
|
||||||
|
result = self.vm.qmp('blockdev-add', conv_keys = False, **opts2)
|
||||||
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
|
# Write different data to both block devices
|
||||||
|
self.run_qemu_io("hd0", "write -P 0xa0 0 1k")
|
||||||
|
self.run_qemu_io("hd2", "write -P 0xa2 0 1k")
|
||||||
|
|
||||||
|
# Check that the data reads correctly
|
||||||
|
self.run_qemu_io("hd0", "read -P 0xa0 0 1k")
|
||||||
|
self.run_qemu_io("hd2", "read -P 0xa2 0 1k")
|
||||||
|
|
||||||
|
# It's not possible to make a block device use an image that
|
||||||
|
# is already being used by the other device.
|
||||||
|
self.reopen(opts0, {'file': 'hd2-file'},
|
||||||
|
"Permission conflict on node 'hd2-file': permissions "
|
||||||
|
"'write, resize' are both required by node 'hd2' (uses "
|
||||||
|
"node 'hd2-file' as 'file' child) and unshared by node "
|
||||||
|
"'hd0' (uses node 'hd2-file' as 'file' child).")
|
||||||
|
self.reopen(opts2, {'file': 'hd0-file'},
|
||||||
|
"Permission conflict on node 'hd0-file': permissions "
|
||||||
|
"'write, resize' are both required by node 'hd0' (uses "
|
||||||
|
"node 'hd0-file' as 'file' child) and unshared by node "
|
||||||
|
"'hd2' (uses node 'hd0-file' as 'file' child).")
|
||||||
|
|
||||||
|
# But we can swap the images if we reopen both devices at the
|
||||||
|
# same time
|
||||||
|
opts0['file'] = 'hd2-file'
|
||||||
|
opts2['file'] = 'hd0-file'
|
||||||
|
self.reopenMultiple([opts0, opts2])
|
||||||
|
self.run_qemu_io("hd0", "read -P 0xa2 0 1k")
|
||||||
|
self.run_qemu_io("hd2", "read -P 0xa0 0 1k")
|
||||||
|
|
||||||
|
# And we can of course come back to the original state
|
||||||
|
opts0['file'] = 'hd0-file'
|
||||||
|
opts2['file'] = 'hd2-file'
|
||||||
|
self.reopenMultiple([opts0, opts2])
|
||||||
|
self.run_qemu_io("hd0", "read -P 0xa0 0 1k")
|
||||||
|
self.run_qemu_io("hd2", "read -P 0xa2 0 1k")
|
||||||
|
|
||||||
# Misc reopen tests with different block drivers
|
# Misc reopen tests with different block drivers
|
||||||
@iotests.skip_if_unsupported(['quorum', 'throttle'])
|
@iotests.skip_if_unsupported(['quorum', 'throttle'])
|
||||||
def test_misc_drivers(self):
|
def test_misc_drivers(self):
|
||||||
|
@ -17,8 +17,8 @@ read 1/1 bytes at offset 262152
|
|||||||
read 1/1 bytes at offset 262160
|
read 1/1 bytes at offset 262160
|
||||||
1 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
1 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
|
||||||
..............
|
...............
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
Ran 24 tests
|
Ran 25 tests
|
||||||
|
|
||||||
OK
|
OK
|
||||||
|
@ -62,8 +62,8 @@ vm.event_wait('JOB_STATUS_CHANGE', timeout=3.0,
|
|||||||
vm.get_qmp_events()
|
vm.get_qmp_events()
|
||||||
|
|
||||||
del blockdev_opts['file']['size']
|
del blockdev_opts['file']['size']
|
||||||
vm.qmp_log('x-blockdev-reopen', filters=[filter_qmp_testfiles],
|
vm.qmp_log('blockdev-reopen', filters=[filter_qmp_testfiles],
|
||||||
**blockdev_opts)
|
options = [ blockdev_opts ])
|
||||||
|
|
||||||
vm.qmp_log('block-job-resume', device='drive0')
|
vm.qmp_log('block-job-resume', device='drive0')
|
||||||
vm.event_wait('JOB_STATUS_CHANGE', timeout=1.0,
|
vm.event_wait('JOB_STATUS_CHANGE', timeout=1.0,
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{"return": {}}
|
{"return": {}}
|
||||||
{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "on-target-error": "enospc", "sync": "full", "target": "target"}}
|
{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "on-target-error": "enospc", "sync": "full", "target": "target"}}
|
||||||
{"return": {}}
|
{"return": {}}
|
||||||
{"execute": "x-blockdev-reopen", "arguments": {"driver": "qcow2", "file": {"driver": "raw", "file": {"driver": "file", "filename": "TEST_DIR/PID-target"}}, "node-name": "target"}}
|
{"execute": "blockdev-reopen", "arguments": {"options": [{"driver": "qcow2", "file": {"driver": "raw", "file": {"driver": "file", "filename": "TEST_DIR/PID-target"}}, "node-name": "target"}]}}
|
||||||
{"return": {}}
|
{"return": {}}
|
||||||
{"execute": "block-job-resume", "arguments": {"device": "drive0"}}
|
{"execute": "block-job-resume", "arguments": {"device": "drive0"}}
|
||||||
{"return": {}}
|
{"return": {}}
|
||||||
|
@ -118,10 +118,9 @@ class EncryptionSetupTestCase(iotests.QMPTestCase):
|
|||||||
def openImageQmp(self, vm, id, file, secret,
|
def openImageQmp(self, vm, id, file, secret,
|
||||||
readOnly = False, reOpen = False):
|
readOnly = False, reOpen = False):
|
||||||
|
|
||||||
command = 'x-blockdev-reopen' if reOpen else 'blockdev-add'
|
command = 'blockdev-reopen' if reOpen else 'blockdev-add'
|
||||||
|
|
||||||
result = vm.qmp(command, **
|
opts = {
|
||||||
{
|
|
||||||
'driver': iotests.imgfmt,
|
'driver': iotests.imgfmt,
|
||||||
'node-name': id,
|
'node-name': id,
|
||||||
'read-only': readOnly,
|
'read-only': readOnly,
|
||||||
@ -131,7 +130,11 @@ class EncryptionSetupTestCase(iotests.QMPTestCase):
|
|||||||
'filename': test_img,
|
'filename': test_img,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
if reOpen:
|
||||||
|
result = vm.qmp(command, options=[opts])
|
||||||
|
else:
|
||||||
|
result = vm.qmp(command, **opts)
|
||||||
self.assert_qmp(result, 'return', {})
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ class TestPreallocateFilter(TestPreallocateBase):
|
|||||||
self.check_big()
|
self.check_big()
|
||||||
|
|
||||||
def test_reopen_opts(self):
|
def test_reopen_opts(self):
|
||||||
result = self.vm.qmp('x-blockdev-reopen', **{
|
result = self.vm.qmp('blockdev-reopen', options=[{
|
||||||
'node-name': 'disk',
|
'node-name': 'disk',
|
||||||
'driver': iotests.imgfmt,
|
'driver': iotests.imgfmt,
|
||||||
'file': {
|
'file': {
|
||||||
@ -112,7 +112,7 @@ class TestPreallocateFilter(TestPreallocateBase):
|
|||||||
'filename': disk
|
'filename': disk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}])
|
||||||
self.assert_qmp(result, 'return', {})
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
self.vm.hmp_qemu_io('drive0', 'write 0 1M')
|
self.vm.hmp_qemu_io('drive0', 'write 0 1M')
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
#
|
#
|
||||||
# Test qcow backing file warnings
|
# Test qcow backing file warnings
|
||||||
#
|
#
|
||||||
# Copyright (C) 2020 Red Hat, Inc.
|
# Copyright (C) 2020-2021 Red Hat, Inc.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
@ -46,7 +46,6 @@ echo "== qcow backed by qcow =="
|
|||||||
|
|
||||||
TEST_IMG="$TEST_IMG.base" _make_test_img $size
|
TEST_IMG="$TEST_IMG.base" _make_test_img $size
|
||||||
_make_test_img -b "$TEST_IMG.base" $size
|
_make_test_img -b "$TEST_IMG.base" $size
|
||||||
_img_info
|
|
||||||
_make_test_img -b "$TEST_IMG.base" -F $IMGFMT $size
|
_make_test_img -b "$TEST_IMG.base" -F $IMGFMT $size
|
||||||
_img_info
|
_img_info
|
||||||
|
|
||||||
@ -71,7 +70,6 @@ echo "== qcow backed by raw =="
|
|||||||
rm "$TEST_IMG.base"
|
rm "$TEST_IMG.base"
|
||||||
truncate --size=$size "$TEST_IMG.base"
|
truncate --size=$size "$TEST_IMG.base"
|
||||||
_make_test_img -b "$TEST_IMG.base" $size
|
_make_test_img -b "$TEST_IMG.base" $size
|
||||||
_img_info
|
|
||||||
_make_test_img -b "$TEST_IMG.base" -F raw $size
|
_make_test_img -b "$TEST_IMG.base" -F raw $size
|
||||||
_img_info
|
_img_info
|
||||||
|
|
||||||
|
@ -2,13 +2,7 @@ QA output created by 301
|
|||||||
|
|
||||||
== qcow backed by qcow ==
|
== qcow backed by qcow ==
|
||||||
Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=33554432
|
Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=33554432
|
||||||
qemu-img: warning: Deprecated use of backing file without explicit backing format (detected format of IMGFMT)
|
qemu-img: TEST_DIR/t.IMGFMT: Backing file specified without backing format
|
||||||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=33554432 backing_file=TEST_DIR/t.IMGFMT.base backing_fmt=IMGFMT
|
|
||||||
image: TEST_DIR/t.IMGFMT
|
|
||||||
file format: IMGFMT
|
|
||||||
virtual size: 32 MiB (33554432 bytes)
|
|
||||||
cluster_size: 512
|
|
||||||
backing file: TEST_DIR/t.IMGFMT.base
|
|
||||||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=33554432 backing_file=TEST_DIR/t.IMGFMT.base backing_fmt=IMGFMT
|
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=33554432 backing_file=TEST_DIR/t.IMGFMT.base backing_fmt=IMGFMT
|
||||||
image: TEST_DIR/t.IMGFMT
|
image: TEST_DIR/t.IMGFMT
|
||||||
file format: IMGFMT
|
file format: IMGFMT
|
||||||
@ -36,13 +30,7 @@ cluster_size: 512
|
|||||||
backing file: TEST_DIR/t.IMGFMT.base
|
backing file: TEST_DIR/t.IMGFMT.base
|
||||||
|
|
||||||
== qcow backed by raw ==
|
== qcow backed by raw ==
|
||||||
qemu-img: warning: Deprecated use of backing file without explicit backing format (detected format of raw)
|
qemu-img: TEST_DIR/t.IMGFMT: Backing file specified without backing format
|
||||||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=33554432 backing_file=TEST_DIR/t.IMGFMT.base
|
|
||||||
image: TEST_DIR/t.IMGFMT
|
|
||||||
file format: IMGFMT
|
|
||||||
virtual size: 32 MiB (33554432 bytes)
|
|
||||||
cluster_size: 512
|
|
||||||
backing file: TEST_DIR/t.IMGFMT.base
|
|
||||||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=33554432 backing_file=TEST_DIR/t.IMGFMT.base backing_fmt=raw
|
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=33554432 backing_file=TEST_DIR/t.IMGFMT.base backing_fmt=raw
|
||||||
image: TEST_DIR/t.IMGFMT
|
image: TEST_DIR/t.IMGFMT
|
||||||
file format: IMGFMT
|
file format: IMGFMT
|
||||||
|
@ -58,6 +58,9 @@ _supported_os Linux # We need /dev/urandom
|
|||||||
# $4: Node to export (defaults to 'node-format')
|
# $4: Node to export (defaults to 'node-format')
|
||||||
fuse_export_add()
|
fuse_export_add()
|
||||||
{
|
{
|
||||||
|
# The grep -v is a filter for errors when /etc/fuse.conf does not contain
|
||||||
|
# user_allow_other. (The error is benign, but it is printed by fusermount
|
||||||
|
# on the first mount attempt, so our export code cannot hide it.)
|
||||||
_send_qemu_cmd $QEMU_HANDLE \
|
_send_qemu_cmd $QEMU_HANDLE \
|
||||||
"{'execute': 'block-export-add',
|
"{'execute': 'block-export-add',
|
||||||
'arguments': {
|
'arguments': {
|
||||||
@ -67,7 +70,8 @@ fuse_export_add()
|
|||||||
$2
|
$2
|
||||||
} }" \
|
} }" \
|
||||||
"${3:-return}" \
|
"${3:-return}" \
|
||||||
| _filter_imgfmt
|
| _filter_imgfmt \
|
||||||
|
| grep -v 'option allow_other only allowed if'
|
||||||
}
|
}
|
||||||
|
|
||||||
# $1: Export ID
|
# $1: Export ID
|
||||||
@ -166,6 +170,17 @@ fuse_export_add 'export-mp' "'mountpoint': '$EXT_MP'"
|
|||||||
# Check that the export presents the same data as the original image
|
# Check that the export presents the same data as the original image
|
||||||
$QEMU_IMG compare -f raw -F $IMGFMT -U "$EXT_MP" "$TEST_IMG"
|
$QEMU_IMG compare -f raw -F $IMGFMT -U "$EXT_MP" "$TEST_IMG"
|
||||||
|
|
||||||
|
# Some quick chmod tests
|
||||||
|
stat -c 'Permissions pre-chmod: %a' "$EXT_MP"
|
||||||
|
|
||||||
|
# Verify that we cannot set +w
|
||||||
|
chmod u+w "$EXT_MP" 2>&1 | _filter_testdir | _filter_imgfmt
|
||||||
|
stat -c 'Permissions post-+w: %a' "$EXT_MP"
|
||||||
|
|
||||||
|
# But that we can set, say, +x (if we are so inclined)
|
||||||
|
chmod u+x "$EXT_MP" 2>&1 | _filter_testdir | _filter_imgfmt
|
||||||
|
stat -c 'Permissions post-+x: %a' "$EXT_MP"
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo '=== Mount over existing file ==='
|
echo '=== Mount over existing file ==='
|
||||||
|
|
||||||
@ -215,7 +230,8 @@ echo '=== Writable export ==='
|
|||||||
fuse_export_add 'export-mp' "'mountpoint': '$EXT_MP', 'writable': true"
|
fuse_export_add 'export-mp' "'mountpoint': '$EXT_MP', 'writable': true"
|
||||||
|
|
||||||
# Check that writing to the read-only export fails
|
# Check that writing to the read-only export fails
|
||||||
$QEMU_IO -f raw -c 'write -P 42 1M 64k' "$TEST_IMG" | _filter_qemu_io
|
$QEMU_IO -f raw -c 'write -P 42 1M 64k' "$TEST_IMG" 2>&1 \
|
||||||
|
| _filter_qemu_io | _filter_testdir | _filter_imgfmt
|
||||||
|
|
||||||
# But here it should work
|
# But here it should work
|
||||||
$QEMU_IO -f raw -c 'write -P 42 1M 64k' "$EXT_MP" | _filter_qemu_io
|
$QEMU_IO -f raw -c 'write -P 42 1M 64k' "$EXT_MP" | _filter_qemu_io
|
||||||
|
@ -50,6 +50,10 @@ wrote 67108864/67108864 bytes at offset 0
|
|||||||
} }
|
} }
|
||||||
{"return": {}}
|
{"return": {}}
|
||||||
Images are identical.
|
Images are identical.
|
||||||
|
Permissions pre-chmod: 400
|
||||||
|
chmod: changing permissions of 'TEST_DIR/t.IMGFMT.fuse': Read-only file system
|
||||||
|
Permissions post-+w: 400
|
||||||
|
Permissions post-+x: 500
|
||||||
|
|
||||||
=== Mount over existing file ===
|
=== Mount over existing file ===
|
||||||
{'execute': 'block-export-add',
|
{'execute': 'block-export-add',
|
||||||
@ -91,7 +95,7 @@ virtual size: 0 B (0 bytes)
|
|||||||
'mountpoint': 'TEST_DIR/t.IMGFMT.fuse', 'writable': true
|
'mountpoint': 'TEST_DIR/t.IMGFMT.fuse', 'writable': true
|
||||||
} }
|
} }
|
||||||
{"return": {}}
|
{"return": {}}
|
||||||
write failed: Permission denied
|
qemu-io: can't open device TEST_DIR/t.IMGFMT: Could not open 'TEST_DIR/t.IMGFMT': Permission denied
|
||||||
wrote 65536/65536 bytes at offset 1048576
|
wrote 65536/65536 bytes at offset 1048576
|
||||||
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
wrote 65536/65536 bytes at offset 1048576
|
wrote 65536/65536 bytes at offset 1048576
|
||||||
|
@ -512,9 +512,13 @@ _make_test_img()
|
|||||||
# Usually, users would export formatted nodes. But we present fuse as a
|
# Usually, users would export formatted nodes. But we present fuse as a
|
||||||
# protocol-level driver here, so we have to leave the format to the
|
# protocol-level driver here, so we have to leave the format to the
|
||||||
# client.
|
# client.
|
||||||
|
# Switch off allow-other, because in general we do not need it for
|
||||||
|
# iotests. The default allow-other=auto has the downside of printing a
|
||||||
|
# fusermount error on its first attempt if allow_other is not
|
||||||
|
# permissible, which we would need to filter.
|
||||||
QSD_NEED_PID=y $QSD \
|
QSD_NEED_PID=y $QSD \
|
||||||
--blockdev file,node-name=export-node,filename=$img_name,discard=unmap \
|
--blockdev file,node-name=export-node,filename=$img_name,discard=unmap \
|
||||||
--export fuse,id=fuse-export,node-name=export-node,mountpoint="$export_mp",writable=on,growable=on \
|
--export fuse,id=fuse-export,node-name=export-node,mountpoint="$export_mp",writable=on,growable=on,allow-other=off \
|
||||||
&
|
&
|
||||||
|
|
||||||
pidfile="$QEMU_TEST_DIR/qemu-storage-daemon.pid"
|
pidfile="$QEMU_TEST_DIR/qemu-storage-daemon.pid"
|
||||||
|
168
tests/qemu-iotests/tests/fuse-allow-other
Executable file
168
tests/qemu-iotests/tests/fuse-allow-other
Executable file
@ -0,0 +1,168 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# group: rw
|
||||||
|
#
|
||||||
|
# Test FUSE exports' allow-other option
|
||||||
|
#
|
||||||
|
# Copyright (C) 2021 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# 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/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
seq=$(basename "$0")
|
||||||
|
echo "QA output created by $seq"
|
||||||
|
|
||||||
|
status=1 # failure is the default!
|
||||||
|
|
||||||
|
_cleanup()
|
||||||
|
{
|
||||||
|
_cleanup_qemu
|
||||||
|
_cleanup_test_img
|
||||||
|
rm -f "$EXT_MP"
|
||||||
|
}
|
||||||
|
trap "_cleanup; exit \$status" 0 1 2 3 15
|
||||||
|
|
||||||
|
# get standard environment, filters and checks
|
||||||
|
. ../common.rc
|
||||||
|
. ../common.filter
|
||||||
|
. ../common.qemu
|
||||||
|
|
||||||
|
_supported_fmt generic
|
||||||
|
|
||||||
|
_supported_proto file # We create the FUSE export manually
|
||||||
|
|
||||||
|
sudo -n -u nobody true || \
|
||||||
|
_notrun 'Password-less sudo as nobody required to test allow_other'
|
||||||
|
|
||||||
|
# $1: Export ID
|
||||||
|
# $2: Options (beyond the node-name and ID)
|
||||||
|
# $3: Expected return value (defaults to 'return')
|
||||||
|
# $4: Node to export (defaults to 'node-format')
|
||||||
|
fuse_export_add()
|
||||||
|
{
|
||||||
|
allow_other_not_supported='option allow_other only allowed if'
|
||||||
|
|
||||||
|
output=$(
|
||||||
|
success_or_failure=yes _send_qemu_cmd $QEMU_HANDLE \
|
||||||
|
"{'execute': 'block-export-add',
|
||||||
|
'arguments': {
|
||||||
|
'type': 'fuse',
|
||||||
|
'id': '$1',
|
||||||
|
'node-name': '${4:-node-format}',
|
||||||
|
$2
|
||||||
|
} }" \
|
||||||
|
"${3:-return}" \
|
||||||
|
"$allow_other_not_supported" \
|
||||||
|
| _filter_imgfmt
|
||||||
|
)
|
||||||
|
|
||||||
|
if echo "$output" | grep -q "$allow_other_not_supported"; then
|
||||||
|
# Shut down qemu gracefully so it can unmount the export
|
||||||
|
_send_qemu_cmd $QEMU_HANDLE \
|
||||||
|
"{'execute': 'quit'}" \
|
||||||
|
'return'
|
||||||
|
|
||||||
|
wait=yes _cleanup_qemu
|
||||||
|
|
||||||
|
_notrun "allow_other not supported"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$output"
|
||||||
|
}
|
||||||
|
|
||||||
|
EXT_MP="$TEST_DIR/fuse-export"
|
||||||
|
|
||||||
|
_make_test_img 64k
|
||||||
|
touch "$EXT_MP"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo '=== Test permissions ==='
|
||||||
|
|
||||||
|
# $1: allow-other value ('on'/'off'/'auto')
|
||||||
|
run_permission_test()
|
||||||
|
{
|
||||||
|
_launch_qemu \
|
||||||
|
-blockdev \
|
||||||
|
"$IMGFMT,node-name=node-format,file.driver=file,file.filename=$TEST_IMG"
|
||||||
|
|
||||||
|
_send_qemu_cmd $QEMU_HANDLE \
|
||||||
|
"{'execute': 'qmp_capabilities'}" \
|
||||||
|
'return'
|
||||||
|
|
||||||
|
fuse_export_add 'export' \
|
||||||
|
"'mountpoint': '$EXT_MP',
|
||||||
|
'allow-other': '$1'"
|
||||||
|
|
||||||
|
# Should always work
|
||||||
|
echo '(Removing all permissions)'
|
||||||
|
chmod 000 "$EXT_MP" 2>&1 | _filter_testdir | _filter_imgfmt
|
||||||
|
stat -c 'Permissions post-chmod: %a' "$EXT_MP"
|
||||||
|
|
||||||
|
# Should always work
|
||||||
|
echo '(Granting u+r)'
|
||||||
|
chmod u+r "$EXT_MP" 2>&1 | _filter_testdir | _filter_imgfmt
|
||||||
|
stat -c 'Permissions post-chmod: %a' "$EXT_MP"
|
||||||
|
|
||||||
|
# Should only work with allow-other: Otherwise, no permissions can be
|
||||||
|
# granted to the group or others
|
||||||
|
echo '(Granting read permissions for everyone)'
|
||||||
|
chmod 444 "$EXT_MP" 2>&1 | _filter_testdir | _filter_imgfmt
|
||||||
|
stat -c 'Permissions post-chmod: %a' "$EXT_MP"
|
||||||
|
|
||||||
|
echo 'Doing operations as nobody:'
|
||||||
|
# Change to TEST_DIR, so nobody will not have to attempt a lookup
|
||||||
|
pushd "$TEST_DIR" >/dev/null
|
||||||
|
|
||||||
|
# This is already prevented by the permissions (without allow-other, FUSE
|
||||||
|
# exports always have o-r), but test it anyway
|
||||||
|
sudo -n -u nobody cat fuse-export >/dev/null
|
||||||
|
|
||||||
|
# If the only problem were the lack of permissions, we should still be able
|
||||||
|
# to stat the export as nobody; it should not work without allow-other,
|
||||||
|
# though
|
||||||
|
sudo -n -u nobody \
|
||||||
|
stat -c 'Permissions seen by nobody: %a' fuse-export 2>&1 \
|
||||||
|
| _filter_imgfmt
|
||||||
|
|
||||||
|
# To prove the point, revoke read permissions for others and try again
|
||||||
|
chmod o-r fuse-export 2>&1 | _filter_testdir | _filter_imgfmt
|
||||||
|
|
||||||
|
# Should fail
|
||||||
|
sudo -n -u nobody cat fuse-export >/dev/null
|
||||||
|
# Should work with allow_other
|
||||||
|
sudo -n -u nobody \
|
||||||
|
stat -c 'Permissions seen by nobody: %a' fuse-export 2>&1 \
|
||||||
|
| _filter_imgfmt
|
||||||
|
|
||||||
|
popd >/dev/null
|
||||||
|
|
||||||
|
_send_qemu_cmd $QEMU_HANDLE \
|
||||||
|
"{'execute': 'quit'}" \
|
||||||
|
'return'
|
||||||
|
|
||||||
|
wait=yes _cleanup_qemu
|
||||||
|
}
|
||||||
|
|
||||||
|
# 'auto' should behave exactly like 'on', because 'on' tests that
|
||||||
|
# allow_other works (otherwise, this test is skipped)
|
||||||
|
for ao in off on auto; do
|
||||||
|
echo
|
||||||
|
echo "--- allow-other=$ao ---"
|
||||||
|
|
||||||
|
run_permission_test "$ao"
|
||||||
|
done
|
||||||
|
|
||||||
|
# success, all done
|
||||||
|
echo "*** done"
|
||||||
|
rm -f $seq.full
|
||||||
|
status=0
|
88
tests/qemu-iotests/tests/fuse-allow-other.out
Normal file
88
tests/qemu-iotests/tests/fuse-allow-other.out
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
QA output created by fuse-allow-other
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=65536
|
||||||
|
|
||||||
|
=== Test permissions ===
|
||||||
|
|
||||||
|
--- allow-other=off ---
|
||||||
|
{'execute': 'qmp_capabilities'}
|
||||||
|
{"return": {}}
|
||||||
|
{'execute': 'block-export-add',
|
||||||
|
'arguments': {
|
||||||
|
'type': 'fuse',
|
||||||
|
'id': 'export',
|
||||||
|
'node-name': 'node-format',
|
||||||
|
'mountpoint': 'TEST_DIR/fuse-export',
|
||||||
|
'allow-other': 'off'
|
||||||
|
} }
|
||||||
|
{"return": {}}
|
||||||
|
(Removing all permissions)
|
||||||
|
Permissions post-chmod: 0
|
||||||
|
(Granting u+r)
|
||||||
|
Permissions post-chmod: 400
|
||||||
|
(Granting read permissions for everyone)
|
||||||
|
chmod: changing permissions of 'TEST_DIR/fuse-export': Operation not permitted
|
||||||
|
Permissions post-chmod: 400
|
||||||
|
Doing operations as nobody:
|
||||||
|
cat: fuse-export: Permission denied
|
||||||
|
stat: cannot statx 'fuse-export': Permission denied
|
||||||
|
cat: fuse-export: Permission denied
|
||||||
|
stat: cannot statx 'fuse-export': Permission denied
|
||||||
|
{'execute': 'quit'}
|
||||||
|
{"return": {}}
|
||||||
|
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}}
|
||||||
|
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_EXPORT_DELETED", "data": {"id": "export"}}
|
||||||
|
|
||||||
|
--- allow-other=on ---
|
||||||
|
{'execute': 'qmp_capabilities'}
|
||||||
|
{"return": {}}
|
||||||
|
{'execute': 'block-export-add',
|
||||||
|
'arguments': {
|
||||||
|
'type': 'fuse',
|
||||||
|
'id': 'export',
|
||||||
|
'node-name': 'node-format',
|
||||||
|
'mountpoint': 'TEST_DIR/fuse-export',
|
||||||
|
'allow-other': 'on'
|
||||||
|
} }
|
||||||
|
{"return": {}}
|
||||||
|
(Removing all permissions)
|
||||||
|
Permissions post-chmod: 0
|
||||||
|
(Granting u+r)
|
||||||
|
Permissions post-chmod: 400
|
||||||
|
(Granting read permissions for everyone)
|
||||||
|
Permissions post-chmod: 444
|
||||||
|
Doing operations as nobody:
|
||||||
|
Permissions seen by nobody: 444
|
||||||
|
cat: fuse-export: Permission denied
|
||||||
|
Permissions seen by nobody: 440
|
||||||
|
{'execute': 'quit'}
|
||||||
|
{"return": {}}
|
||||||
|
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}}
|
||||||
|
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_EXPORT_DELETED", "data": {"id": "export"}}
|
||||||
|
|
||||||
|
--- allow-other=auto ---
|
||||||
|
{'execute': 'qmp_capabilities'}
|
||||||
|
{"return": {}}
|
||||||
|
{'execute': 'block-export-add',
|
||||||
|
'arguments': {
|
||||||
|
'type': 'fuse',
|
||||||
|
'id': 'export',
|
||||||
|
'node-name': 'node-format',
|
||||||
|
'mountpoint': 'TEST_DIR/fuse-export',
|
||||||
|
'allow-other': 'auto'
|
||||||
|
} }
|
||||||
|
{"return": {}}
|
||||||
|
(Removing all permissions)
|
||||||
|
Permissions post-chmod: 0
|
||||||
|
(Granting u+r)
|
||||||
|
Permissions post-chmod: 400
|
||||||
|
(Granting read permissions for everyone)
|
||||||
|
Permissions post-chmod: 444
|
||||||
|
Doing operations as nobody:
|
||||||
|
Permissions seen by nobody: 444
|
||||||
|
cat: fuse-export: Permission denied
|
||||||
|
Permissions seen by nobody: 440
|
||||||
|
{'execute': 'quit'}
|
||||||
|
{"return": {}}
|
||||||
|
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}}
|
||||||
|
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_EXPORT_DELETED", "data": {"id": "export"}}
|
||||||
|
*** done
|
@ -41,6 +41,7 @@ log('Trying to remove persistent bitmap from r-o base node, should fail:')
|
|||||||
vm.qmp_log('block-dirty-bitmap-remove', node='base', name='bitmap0')
|
vm.qmp_log('block-dirty-bitmap-remove', node='base', name='bitmap0')
|
||||||
|
|
||||||
new_base_opts = {
|
new_base_opts = {
|
||||||
|
'options': [{
|
||||||
'node-name': 'base',
|
'node-name': 'base',
|
||||||
'driver': 'qcow2',
|
'driver': 'qcow2',
|
||||||
'file': {
|
'file': {
|
||||||
@ -48,18 +49,19 @@ new_base_opts = {
|
|||||||
'filename': base
|
'filename': base
|
||||||
},
|
},
|
||||||
'read-only': False
|
'read-only': False
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
# Don't want to bother with filtering qmp_log for reopen command
|
# Don't want to bother with filtering qmp_log for reopen command
|
||||||
result = vm.qmp('x-blockdev-reopen', **new_base_opts)
|
result = vm.qmp('blockdev-reopen', **new_base_opts)
|
||||||
if result != {'return': {}}:
|
if result != {'return': {}}:
|
||||||
log('Failed to reopen: ' + str(result))
|
log('Failed to reopen: ' + str(result))
|
||||||
|
|
||||||
log('Remove persistent bitmap from base node reopened to RW:')
|
log('Remove persistent bitmap from base node reopened to RW:')
|
||||||
vm.qmp_log('block-dirty-bitmap-remove', node='base', name='bitmap0')
|
vm.qmp_log('block-dirty-bitmap-remove', node='base', name='bitmap0')
|
||||||
|
|
||||||
new_base_opts['read-only'] = True
|
new_base_opts['options'][0]['read-only'] = True
|
||||||
result = vm.qmp('x-blockdev-reopen', **new_base_opts)
|
result = vm.qmp('blockdev-reopen', **new_base_opts)
|
||||||
if result != {'return': {}}:
|
if result != {'return': {}}:
|
||||||
log('Failed to reopen: ' + str(result))
|
log('Failed to reopen: ' + str(result))
|
||||||
|
|
||||||
|
12
util/uri.c
12
util/uri.c
@ -1340,7 +1340,7 @@ static void uri_clean(URI *uri)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* uri_free:
|
* uri_free:
|
||||||
* @uri: pointer to an URI
|
* @uri: pointer to an URI, NULL is ignored
|
||||||
*
|
*
|
||||||
* Free up the URI struct
|
* Free up the URI struct
|
||||||
*/
|
*/
|
||||||
@ -1939,15 +1939,9 @@ step_7:
|
|||||||
val = uri_to_string(res);
|
val = uri_to_string(res);
|
||||||
|
|
||||||
done:
|
done:
|
||||||
if (ref != NULL) {
|
|
||||||
uri_free(ref);
|
uri_free(ref);
|
||||||
}
|
|
||||||
if (bas != NULL) {
|
|
||||||
uri_free(bas);
|
uri_free(bas);
|
||||||
}
|
|
||||||
if (res != NULL) {
|
|
||||||
uri_free(res);
|
uri_free(res);
|
||||||
}
|
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2190,12 +2184,8 @@ done:
|
|||||||
if (remove_path != 0) {
|
if (remove_path != 0) {
|
||||||
ref->path = NULL;
|
ref->path = NULL;
|
||||||
}
|
}
|
||||||
if (ref != NULL) {
|
|
||||||
uri_free(ref);
|
uri_free(ref);
|
||||||
}
|
|
||||||
if (bas != NULL) {
|
|
||||||
uri_free(bas);
|
uri_free(bas);
|
||||||
}
|
|
||||||
|
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user