From 2fdc70452a59484c85359dc8d5a0650245288781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Golembiovsk=C3=BD?= Date: Mon, 31 Oct 2016 11:27:40 +0100 Subject: raw_bsd: add offset and size options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added two new options 'offset' and 'size'. This makes it possible to use only part of the file as a device. This can be used e.g. to limit the access only to single partition in a disk image or use a disk inside a tar archive (like OVA). When 'size' is specified we do our best to honour it. Signed-off-by: Tomáš Golembiovský Signed-off-by: Kevin Wolf --- block/raw_bsd.c | 217 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 214 insertions(+), 3 deletions(-) (limited to 'block') diff --git a/block/raw_bsd.c b/block/raw_bsd.c index fc16ec1f74..7c9bebb507 100644 --- a/block/raw_bsd.c +++ b/block/raw_bsd.c @@ -31,6 +31,30 @@ #include "qapi/error.h" #include "qemu/option.h" +typedef struct BDRVRawState { + uint64_t offset; + uint64_t size; + bool has_size; +} BDRVRawState; + +static QemuOptsList raw_runtime_opts = { + .name = "raw", + .head = QTAILQ_HEAD_INITIALIZER(raw_runtime_opts.head), + .desc = { + { + .name = "offset", + .type = QEMU_OPT_SIZE, + .help = "offset in the disk where the image starts", + }, + { + .name = "size", + .type = QEMU_OPT_SIZE, + .help = "virtual disk size", + }, + { /* end of list */ } + }, +}; + static QemuOptsList raw_create_opts = { .name = "raw-create-opts", .head = QTAILQ_HEAD_INITIALIZER(raw_create_opts.head), @@ -44,16 +68,108 @@ static QemuOptsList raw_create_opts = { } }; +static int raw_read_options(QDict *options, BlockDriverState *bs, + BDRVRawState *s, Error **errp) +{ + Error *local_err = NULL; + QemuOpts *opts = NULL; + int64_t real_size = 0; + int ret; + + real_size = bdrv_getlength(bs->file->bs); + if (real_size < 0) { + error_setg_errno(errp, -real_size, "Could not get image size"); + return real_size; + } + + opts = qemu_opts_create(&raw_runtime_opts, NULL, 0, &error_abort); + qemu_opts_absorb_qdict(opts, options, &local_err); + if (local_err) { + error_propagate(errp, local_err); + ret = -EINVAL; + goto end; + } + + s->offset = qemu_opt_get_size(opts, "offset", 0); + if (qemu_opt_find(opts, "size") != NULL) { + s->size = qemu_opt_get_size(opts, "size", 0); + s->has_size = true; + } else { + s->has_size = false; + s->size = real_size - s->offset; + } + + /* Check size and offset */ + if (real_size < s->offset || (real_size - s->offset) < s->size) { + error_setg(errp, "The sum of offset (%" PRIu64 ") and size " + "(%" PRIu64 ") has to be smaller or equal to the " + " actual size of the containing file (%" PRId64 ")", + s->offset, s->size, real_size); + ret = -EINVAL; + goto end; + } + + /* Make sure size is multiple of BDRV_SECTOR_SIZE to prevent rounding + * up and leaking out of the specified area. */ + if (!QEMU_IS_ALIGNED(s->size, BDRV_SECTOR_SIZE)) { + error_setg(errp, "Specified size is not multiple of %llu", + BDRV_SECTOR_SIZE); + ret = -EINVAL; + goto end; + } + + ret = 0; + +end: + + qemu_opts_del(opts); + + return ret; +} + static int raw_reopen_prepare(BDRVReopenState *reopen_state, BlockReopenQueue *queue, Error **errp) { - return 0; + assert(reopen_state != NULL); + assert(reopen_state->bs != NULL); + + reopen_state->opaque = g_new0(BDRVRawState, 1); + + return raw_read_options( + reopen_state->options, + reopen_state->bs, + reopen_state->opaque, + errp); +} + +static void raw_reopen_commit(BDRVReopenState *state) +{ + BDRVRawState *new_s = state->opaque; + BDRVRawState *s = state->bs->opaque; + + memcpy(s, new_s, sizeof(BDRVRawState)); + + g_free(state->opaque); + state->opaque = NULL; +} + +static void raw_reopen_abort(BDRVReopenState *state) +{ + g_free(state->opaque); + state->opaque = NULL; } static int coroutine_fn raw_co_preadv(BlockDriverState *bs, uint64_t offset, uint64_t bytes, QEMUIOVector *qiov, int flags) { + BDRVRawState *s = bs->opaque; + + if (offset > UINT64_MAX - s->offset) { + return -EINVAL; + } + offset += s->offset; + BLKDBG_EVENT(bs->file, BLKDBG_READ_AIO); return bdrv_co_preadv(bs->file, offset, bytes, qiov, flags); } @@ -62,11 +178,23 @@ static int coroutine_fn raw_co_pwritev(BlockDriverState *bs, uint64_t offset, uint64_t bytes, QEMUIOVector *qiov, int flags) { + BDRVRawState *s = bs->opaque; void *buf = NULL; BlockDriver *drv; QEMUIOVector local_qiov; int ret; + if (s->has_size && (offset > s->size || bytes > (s->size - offset))) { + /* There's not enough space for the data. Don't write anything and just + * fail to prevent leaking out of the size specified in options. */ + return -ENOSPC; + } + + if (offset > UINT64_MAX - s->offset) { + ret = -EINVAL; + goto fail; + } + if (bs->probed && offset < BLOCK_PROBE_BUF_SIZE && bytes) { /* Handling partial writes would be a pain - so we just * require that guests have 512-byte request alignment if @@ -101,6 +229,8 @@ static int coroutine_fn raw_co_pwritev(BlockDriverState *bs, uint64_t offset, qiov = &local_qiov; } + offset += s->offset; + BLKDBG_EVENT(bs->file, BLKDBG_WRITE_AIO); ret = bdrv_co_pwritev(bs->file, offset, bytes, qiov, flags); @@ -117,8 +247,10 @@ static int64_t coroutine_fn raw_co_get_block_status(BlockDriverState *bs, int nb_sectors, int *pnum, BlockDriverState **file) { + BDRVRawState *s = bs->opaque; *pnum = nb_sectors; *file = bs->file->bs; + sector_num += s->offset / BDRV_SECTOR_SIZE; return BDRV_BLOCK_RAW | BDRV_BLOCK_OFFSET_VALID | BDRV_BLOCK_DATA | (sector_num << BDRV_SECTOR_BITS); } @@ -127,18 +259,49 @@ static int coroutine_fn raw_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset, int count, BdrvRequestFlags flags) { + BDRVRawState *s = bs->opaque; + if (offset > UINT64_MAX - s->offset) { + return -EINVAL; + } + offset += s->offset; return bdrv_co_pwrite_zeroes(bs->file, offset, count, flags); } static int coroutine_fn raw_co_pdiscard(BlockDriverState *bs, int64_t offset, int count) { + BDRVRawState *s = bs->opaque; + if (offset > UINT64_MAX - s->offset) { + return -EINVAL; + } + offset += s->offset; return bdrv_co_pdiscard(bs->file->bs, offset, count); } static int64_t raw_getlength(BlockDriverState *bs) { - return bdrv_getlength(bs->file->bs); + int64_t len; + BDRVRawState *s = bs->opaque; + + /* Update size. It should not change unless the file was externally + * modified. */ + len = bdrv_getlength(bs->file->bs); + if (len < 0) { + return len; + } + + if (len < s->offset) { + s->size = 0; + } else { + if (s->has_size) { + /* Try to honour the size */ + s->size = MIN(s->size, len - s->offset); + } else { + s->size = len - s->offset; + } + } + + return s->size; } static int raw_get_info(BlockDriverState *bs, BlockDriverInfo *bdi) @@ -158,6 +321,18 @@ static void raw_refresh_limits(BlockDriverState *bs, Error **errp) static int raw_truncate(BlockDriverState *bs, int64_t offset) { + BDRVRawState *s = bs->opaque; + + if (s->has_size) { + return -ENOTSUP; + } + + if (INT64_MAX - offset < s->offset) { + return -EINVAL; + } + + s->size = offset; + offset += s->offset; return bdrv_truncate(bs->file->bs, offset); } @@ -178,6 +353,10 @@ static void raw_lock_medium(BlockDriverState *bs, bool locked) static int raw_co_ioctl(BlockDriverState *bs, unsigned long int req, void *buf) { + BDRVRawState *s = bs->opaque; + if (s->offset || s->has_size) { + return -ENOTSUP; + } return bdrv_co_ioctl(bs->file->bs, req, buf); } @@ -194,6 +373,9 @@ static int raw_create(const char *filename, QemuOpts *opts, Error **errp) static int raw_open(BlockDriverState *bs, QDict *options, int flags, Error **errp) { + BDRVRawState *s = bs->opaque; + int ret; + bs->sg = bs->file->bs->sg; bs->supported_write_flags = BDRV_REQ_FUA & bs->file->bs->supported_write_flags; @@ -211,6 +393,16 @@ static int raw_open(BlockDriverState *bs, QDict *options, int flags, bs->file->bs->filename); } + ret = raw_read_options(options, bs, s, errp); + if (ret < 0) { + return ret; + } + + if (bs->sg && (s->offset || s->has_size)) { + error_setg(errp, "Cannot use offset/size with SCSI generic devices"); + return -EINVAL; + } + return 0; } @@ -228,18 +420,37 @@ static int raw_probe(const uint8_t *buf, int buf_size, const char *filename) static int raw_probe_blocksizes(BlockDriverState *bs, BlockSizes *bsz) { - return bdrv_probe_blocksizes(bs->file->bs, bsz); + BDRVRawState *s = bs->opaque; + int ret; + + ret = bdrv_probe_blocksizes(bs->file->bs, bsz); + if (ret < 0) { + return ret; + } + + if (!QEMU_IS_ALIGNED(s->offset, MAX(bsz->log, bsz->phys))) { + return -ENOTSUP; + } + + return 0; } static int raw_probe_geometry(BlockDriverState *bs, HDGeometry *geo) { + BDRVRawState *s = bs->opaque; + if (s->offset || s->has_size) { + return -ENOTSUP; + } return bdrv_probe_geometry(bs->file->bs, geo); } BlockDriver bdrv_raw = { .format_name = "raw", + .instance_size = sizeof(BDRVRawState), .bdrv_probe = &raw_probe, .bdrv_reopen_prepare = &raw_reopen_prepare, + .bdrv_reopen_commit = &raw_reopen_commit, + .bdrv_reopen_abort = &raw_reopen_abort, .bdrv_open = &raw_open, .bdrv_close = &raw_close, .bdrv_create = &raw_create, -- cgit v1.2.1