bdaf61259a
Update to v2.4.0 OBS-URL: https://build.opensuse.org/request/show/329598 OBS-URL: https://build.opensuse.org/package/show/Virtualization/qemu?expand=0&rev=271
443 lines
13 KiB
Diff
443 lines
13 KiB
Diff
From be6c4a9c1ee466c5b6d6f0e7e78eab322f722232 Mon Sep 17 00:00:00 2001
|
|
From: Alexander Graf <agraf@suse.de>
|
|
Date: Wed, 5 Aug 2009 17:28:38 +0200
|
|
Subject: [PATCH] block: Add tar container format
|
|
MIME-Version: 1.0
|
|
Content-Type: text/plain; charset=UTF-8
|
|
Content-Transfer-Encoding: 8bit
|
|
|
|
Tar is a very widely used format to store data in. Sometimes people even put
|
|
virtual machine images in there.
|
|
|
|
So it makes sense for qemu to be able to read from tar files. I implemented a
|
|
written from scratch reader that also knows about the GNU sparse format, which
|
|
is what pigz creates.
|
|
|
|
This version checks for filenames that end on well-known extensions. The logic
|
|
could be changed to search for filenames given on the command line, but that
|
|
would require changes to more parts of qemu.
|
|
|
|
The tar reader in conjunctiuon with dzip gives us the chance to download
|
|
tar'ed up virtual machine images (even via http) and instantly make use of
|
|
them.
|
|
|
|
Signed-off-by: Alexander Graf <agraf@suse.de>
|
|
Signed-off-by: Bruce Rogers <brogers@novell.com>
|
|
Signed-off-by: Andreas Färber <afaerber@suse.de>
|
|
[TH: Use bdrv_open options instead of filename]
|
|
Signed-off-by: Tim Hardeck <thardeck@suse.de>
|
|
[AF: bdrv_file_open got an Error **errp argument, bdrv_delete -> brd_unref]
|
|
[AF: qemu_opts_create_nofail() -> qemu_opts_create(),
|
|
bdrv_file_open() -> bdrv_open(), based on work by brogers]
|
|
[AF: error_is_set() dropped for v2.1.0-rc0]
|
|
[AF: BlockDriverAIOCB -> BlockAIOCB,
|
|
BlockDriverCompletionFunc -> BlockCompletionFunc,
|
|
qemu_aio_release() -> qemu_aio_unref(),
|
|
drop tar_aio_cancel()]
|
|
Signed-off-by: Andreas Färber <afaerber@suse.de>
|
|
---
|
|
block/Makefile.objs | 1 +
|
|
block/tar.c | 381 ++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
2 files changed, 382 insertions(+)
|
|
create mode 100644 block/tar.c
|
|
|
|
diff --git a/block/Makefile.objs b/block/Makefile.objs
|
|
index 246f167..a757960 100644
|
|
--- a/block/Makefile.objs
|
|
+++ b/block/Makefile.objs
|
|
@@ -27,6 +27,7 @@ common-obj-y += stream.o
|
|
common-obj-y += commit.o
|
|
common-obj-y += backup.o
|
|
common-obj-y += dictzip.o
|
|
+common-obj-y += tar.o
|
|
|
|
iscsi.o-cflags := $(LIBISCSI_CFLAGS)
|
|
iscsi.o-libs := $(LIBISCSI_LIBS)
|
|
diff --git a/block/tar.c b/block/tar.c
|
|
new file mode 100644
|
|
index 0000000..d8218a3
|
|
--- /dev/null
|
|
+++ b/block/tar.c
|
|
@@ -0,0 +1,381 @@
|
|
+/*
|
|
+ * Tar block driver
|
|
+ *
|
|
+ * Copyright (c) 2009 Alexander Graf <agraf@suse.de>
|
|
+ *
|
|
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
+ * of this software and associated documentation files (the "Software"), to deal
|
|
+ * in the Software without restriction, including without limitation the rights
|
|
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
+ * copies of the Software, and to permit persons to whom the Software is
|
|
+ * furnished to do so, subject to the following conditions:
|
|
+ *
|
|
+ * The above copyright notice and this permission notice shall be included in
|
|
+ * all copies or substantial portions of the Software.
|
|
+ *
|
|
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
+ * THE SOFTWARE.
|
|
+ */
|
|
+
|
|
+#include "qemu-common.h"
|
|
+#include "block/block_int.h"
|
|
+
|
|
+// #define DEBUG
|
|
+
|
|
+#ifdef DEBUG
|
|
+#define dprintf(fmt, ...) do { printf("tar: " fmt, ## __VA_ARGS__); } while (0)
|
|
+#else
|
|
+#define dprintf(fmt, ...) do { } while (0)
|
|
+#endif
|
|
+
|
|
+#define SECTOR_SIZE 512
|
|
+
|
|
+#define POSIX_TAR_MAGIC "ustar"
|
|
+#define OFFS_LENGTH 0x7c
|
|
+#define OFFS_TYPE 0x9c
|
|
+#define OFFS_MAGIC 0x101
|
|
+
|
|
+#define OFFS_S_SP 0x182
|
|
+#define OFFS_S_EXT 0x1e2
|
|
+#define OFFS_S_LENGTH 0x1e3
|
|
+#define OFFS_SX_EXT 0x1f8
|
|
+
|
|
+typedef struct SparseCache {
|
|
+ uint64_t start;
|
|
+ uint64_t end;
|
|
+} SparseCache;
|
|
+
|
|
+typedef struct BDRVTarState {
|
|
+ BlockDriverState *hd;
|
|
+ size_t file_sec;
|
|
+ uint64_t file_len;
|
|
+ SparseCache *sparse;
|
|
+ int sparse_num;
|
|
+ uint64_t last_end;
|
|
+ char longfile[2048];
|
|
+} BDRVTarState;
|
|
+
|
|
+static int tar_probe(const uint8_t *buf, int buf_size, const char *filename)
|
|
+{
|
|
+ if (buf_size < OFFS_MAGIC + 5)
|
|
+ return 0;
|
|
+
|
|
+ /* we only support newer tar */
|
|
+ if (!strncmp((char*)buf + OFFS_MAGIC, POSIX_TAR_MAGIC, 5))
|
|
+ return 100;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int str_ends(char *str, const char *end)
|
|
+{
|
|
+ int end_len = strlen(end);
|
|
+ int str_len = strlen(str);
|
|
+
|
|
+ if (str_len < end_len)
|
|
+ return 0;
|
|
+
|
|
+ return !strncmp(str + str_len - end_len, end, end_len);
|
|
+}
|
|
+
|
|
+static int is_target_file(BlockDriverState *bs, char *filename)
|
|
+{
|
|
+ int retval = 0;
|
|
+
|
|
+ if (str_ends(filename, ".raw"))
|
|
+ retval = 1;
|
|
+
|
|
+ if (str_ends(filename, ".qcow"))
|
|
+ retval = 1;
|
|
+
|
|
+ if (str_ends(filename, ".qcow2"))
|
|
+ retval = 1;
|
|
+
|
|
+ if (str_ends(filename, ".vmdk"))
|
|
+ retval = 1;
|
|
+
|
|
+ dprintf("does filename %s match? %s\n", filename, retval ? "yes" : "no");
|
|
+
|
|
+ /* make sure we're not using this name again */
|
|
+ filename[0] = '\0';
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+static uint64_t tar2u64(char *ptr)
|
|
+{
|
|
+ uint64_t retval;
|
|
+ char oldend = ptr[12];
|
|
+
|
|
+ ptr[12] = '\0';
|
|
+ if (*ptr & 0x80) {
|
|
+ /* XXX we only support files up to 64 bit length */
|
|
+ retval = be64_to_cpu(*(uint64_t *)(ptr+4));
|
|
+ dprintf("Convert %lx -> %#lx\n", *(uint64_t*)(ptr+4), retval);
|
|
+ } else {
|
|
+ retval = strtol(ptr, NULL, 8);
|
|
+ dprintf("Convert %s -> %#lx\n", ptr, retval);
|
|
+ }
|
|
+
|
|
+ ptr[12] = oldend;
|
|
+
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+static void tar_sparse(BDRVTarState *s, uint64_t offs, uint64_t len)
|
|
+{
|
|
+ SparseCache *sparse;
|
|
+
|
|
+ if (!len)
|
|
+ return;
|
|
+ if (!(offs - s->last_end)) {
|
|
+ s->last_end += len;
|
|
+ return;
|
|
+ }
|
|
+ if (s->last_end > offs)
|
|
+ return;
|
|
+
|
|
+ dprintf("Last chunk until %lx new chunk at %lx\n", s->last_end, offs);
|
|
+
|
|
+ s->sparse = g_realloc(s->sparse, (s->sparse_num + 1) * sizeof(SparseCache));
|
|
+ sparse = &s->sparse[s->sparse_num];
|
|
+ sparse->start = s->last_end;
|
|
+ sparse->end = offs;
|
|
+ s->last_end = offs + len;
|
|
+ s->sparse_num++;
|
|
+ dprintf("Sparse at %lx end=%lx\n", sparse->start,
|
|
+ sparse->end);
|
|
+}
|
|
+
|
|
+static QemuOptsList runtime_opts = {
|
|
+ .name = "tar",
|
|
+ .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
|
|
+ .desc = {
|
|
+ {
|
|
+ .name = "filename",
|
|
+ .type = QEMU_OPT_STRING,
|
|
+ .help = "URL to the tar file",
|
|
+ },
|
|
+ { /* end of list */ }
|
|
+ },
|
|
+};
|
|
+
|
|
+static int tar_open(BlockDriverState *bs, QDict *options, int flags, Error **errp)
|
|
+{
|
|
+ BDRVTarState *s = bs->opaque;
|
|
+ char header[SECTOR_SIZE];
|
|
+ char *real_file = header;
|
|
+ char *magic;
|
|
+ size_t header_offs = 0;
|
|
+ int ret;
|
|
+ QemuOpts *opts;
|
|
+ Error *local_err = NULL;
|
|
+ const char *filename;
|
|
+
|
|
+ opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
|
+ qemu_opts_absorb_qdict(opts, options, &local_err);
|
|
+ if (local_err != NULL) {
|
|
+ error_propagate(errp, local_err);
|
|
+ ret = -EINVAL;
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ filename = qemu_opt_get(opts, "filename");
|
|
+
|
|
+ if (!strncmp(filename, "tar://", 6))
|
|
+ filename += 6;
|
|
+ else if (!strncmp(filename, "tar:", 4))
|
|
+ filename += 4;
|
|
+
|
|
+ ret = bdrv_open(&s->hd, filename, NULL, NULL, flags | BDRV_O_PROTOCOL, NULL, &local_err);
|
|
+ if (ret < 0) {
|
|
+ error_propagate(errp, local_err);
|
|
+ qemu_opts_del(opts);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ /* Search the file for an image */
|
|
+
|
|
+ do {
|
|
+ /* tar header */
|
|
+ if (bdrv_pread(s->hd, header_offs, header, SECTOR_SIZE) != SECTOR_SIZE)
|
|
+ goto fail;
|
|
+
|
|
+ if ((header_offs > 1) && !header[0]) {
|
|
+ fprintf(stderr, "Tar: No image file found in archive\n");
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ magic = &header[OFFS_MAGIC];
|
|
+ if (strncmp(magic, POSIX_TAR_MAGIC, 5)) {
|
|
+ fprintf(stderr, "Tar: Invalid magic: %s\n", magic);
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ dprintf("file type: %c\n", header[OFFS_TYPE]);
|
|
+
|
|
+ /* file length*/
|
|
+ s->file_len = (tar2u64(&header[OFFS_LENGTH]) + (SECTOR_SIZE - 1)) &
|
|
+ ~(SECTOR_SIZE - 1);
|
|
+ s->file_sec = (header_offs / SECTOR_SIZE) + 1;
|
|
+
|
|
+ header_offs += s->file_len + SECTOR_SIZE;
|
|
+
|
|
+ if (header[OFFS_TYPE] == 'L') {
|
|
+ bdrv_pread(s->hd, header_offs - s->file_len, s->longfile,
|
|
+ sizeof(s->longfile));
|
|
+ s->longfile[sizeof(s->longfile)-1] = '\0';
|
|
+ } else if (s->longfile[0]) {
|
|
+ real_file = s->longfile;
|
|
+ } else {
|
|
+ real_file = header;
|
|
+ }
|
|
+ } while(!is_target_file(bs, real_file));
|
|
+
|
|
+ /* We found an image! */
|
|
+
|
|
+ if (header[OFFS_TYPE] == 'S') {
|
|
+ uint8_t isextended;
|
|
+ int i;
|
|
+
|
|
+ for (i = OFFS_S_SP; i < (OFFS_S_SP + (4 * 24)); i += 24)
|
|
+ tar_sparse(s, tar2u64(&header[i]), tar2u64(&header[i+12]));
|
|
+
|
|
+ s->file_len = tar2u64(&header[OFFS_S_LENGTH]);
|
|
+ isextended = header[OFFS_S_EXT];
|
|
+
|
|
+ while (isextended) {
|
|
+ if (bdrv_pread(s->hd, s->file_sec * SECTOR_SIZE, header,
|
|
+ SECTOR_SIZE) != SECTOR_SIZE)
|
|
+ goto fail;
|
|
+
|
|
+ for (i = 0; i < (21 * 24); i += 24)
|
|
+ tar_sparse(s, tar2u64(&header[i]), tar2u64(&header[i+12]));
|
|
+ isextended = header[OFFS_SX_EXT];
|
|
+ s->file_sec++;
|
|
+ }
|
|
+ tar_sparse(s, s->file_len, 1);
|
|
+ }
|
|
+ qemu_opts_del(opts);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+fail:
|
|
+ fprintf(stderr, "Tar: Error opening file\n");
|
|
+ bdrv_unref(s->hd);
|
|
+ qemu_opts_del(opts);
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+typedef struct TarAIOCB {
|
|
+ BlockAIOCB common;
|
|
+ QEMUBH *bh;
|
|
+} TarAIOCB;
|
|
+
|
|
+/* This callback gets invoked when we have pure sparseness */
|
|
+static void tar_sparse_cb(void *opaque)
|
|
+{
|
|
+ TarAIOCB *acb = (TarAIOCB *)opaque;
|
|
+
|
|
+ acb->common.cb(acb->common.opaque, 0);
|
|
+ qemu_bh_delete(acb->bh);
|
|
+ qemu_aio_unref(acb);
|
|
+}
|
|
+
|
|
+static AIOCBInfo tar_aiocb_info = {
|
|
+ .aiocb_size = sizeof(TarAIOCB),
|
|
+};
|
|
+
|
|
+/* This is where we get a request from a caller to read something */
|
|
+static BlockAIOCB *tar_aio_readv(BlockDriverState *bs,
|
|
+ int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
|
|
+ BlockCompletionFunc *cb, void *opaque)
|
|
+{
|
|
+ BDRVTarState *s = bs->opaque;
|
|
+ SparseCache *sparse;
|
|
+ int64_t sec_file = sector_num + s->file_sec;
|
|
+ int64_t start = sector_num * SECTOR_SIZE;
|
|
+ int64_t end = start + (nb_sectors * SECTOR_SIZE);
|
|
+ int i;
|
|
+ TarAIOCB *acb;
|
|
+
|
|
+ for (i = 0; i < s->sparse_num; i++) {
|
|
+ sparse = &s->sparse[i];
|
|
+ if (sparse->start > end) {
|
|
+ /* We expect the cache to be start increasing */
|
|
+ break;
|
|
+ } else if ((sparse->start < start) && (sparse->end <= start)) {
|
|
+ /* sparse before our offset */
|
|
+ sec_file -= (sparse->end - sparse->start) / SECTOR_SIZE;
|
|
+ } else if ((sparse->start <= start) && (sparse->end >= end)) {
|
|
+ /* all our sectors are sparse */
|
|
+ char *buf = g_malloc0(nb_sectors * SECTOR_SIZE);
|
|
+
|
|
+ acb = qemu_aio_get(&tar_aiocb_info, bs, cb, opaque);
|
|
+ qemu_iovec_from_buf(qiov, 0, buf, nb_sectors * SECTOR_SIZE);
|
|
+ g_free(buf);
|
|
+ acb->bh = qemu_bh_new(tar_sparse_cb, acb);
|
|
+ qemu_bh_schedule(acb->bh);
|
|
+
|
|
+ return &acb->common;
|
|
+ } else if (((sparse->start >= start) && (sparse->start < end)) ||
|
|
+ ((sparse->end >= start) && (sparse->end < end))) {
|
|
+ /* we're semi-sparse (worst case) */
|
|
+ /* let's go synchronous and read all sectors individually */
|
|
+ char *buf = g_malloc(nb_sectors * SECTOR_SIZE);
|
|
+ uint64_t offs;
|
|
+
|
|
+ for (offs = 0; offs < (nb_sectors * SECTOR_SIZE);
|
|
+ offs += SECTOR_SIZE) {
|
|
+ bdrv_pread(bs, (sector_num * SECTOR_SIZE) + offs,
|
|
+ buf + offs, SECTOR_SIZE);
|
|
+ }
|
|
+
|
|
+ qemu_iovec_from_buf(qiov, 0, buf, nb_sectors * SECTOR_SIZE);
|
|
+ acb = qemu_aio_get(&tar_aiocb_info, bs, cb, opaque);
|
|
+ acb->bh = qemu_bh_new(tar_sparse_cb, acb);
|
|
+ qemu_bh_schedule(acb->bh);
|
|
+
|
|
+ return &acb->common;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return bdrv_aio_readv(s->hd, sec_file, qiov, nb_sectors,
|
|
+ cb, opaque);
|
|
+}
|
|
+
|
|
+static void tar_close(BlockDriverState *bs)
|
|
+{
|
|
+ dprintf("Close\n");
|
|
+}
|
|
+
|
|
+static int64_t tar_getlength(BlockDriverState *bs)
|
|
+{
|
|
+ BDRVTarState *s = bs->opaque;
|
|
+ dprintf("getlength -> %ld\n", s->file_len);
|
|
+ return s->file_len;
|
|
+}
|
|
+
|
|
+static BlockDriver bdrv_tar = {
|
|
+ .format_name = "tar",
|
|
+ .protocol_name = "tar",
|
|
+
|
|
+ .instance_size = sizeof(BDRVTarState),
|
|
+ .bdrv_file_open = tar_open,
|
|
+ .bdrv_close = tar_close,
|
|
+ .bdrv_getlength = tar_getlength,
|
|
+ .bdrv_probe = tar_probe,
|
|
+
|
|
+ .bdrv_aio_readv = tar_aio_readv,
|
|
+};
|
|
+
|
|
+static void tar_block_init(void)
|
|
+{
|
|
+ bdrv_register(&bdrv_tar);
|
|
+}
|
|
+
|
|
+block_init(tar_block_init);
|