From 183d02ded57cd72e084e3ef5d25ae409a1a3affd Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Tue, 12 Apr 2016 16:16:56 +0200 Subject: [PATCH] Restore qcow2 module --- gns3server/hypervisor/qemu/qcow2.py | 100 ++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 gns3server/hypervisor/qemu/qcow2.py diff --git a/gns3server/hypervisor/qemu/qcow2.py b/gns3server/hypervisor/qemu/qcow2.py new file mode 100644 index 00000000..d9bc438a --- /dev/null +++ b/gns3server/hypervisor/qemu/qcow2.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python +# +# Copyright (C) 2016 GNS3 Technologies 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 3 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 . + +import os +import asyncio +import struct + + +class Qcow2Error(Exception): + pass + + +class Qcow2: + """ + Allow to parse a Qcow2 file + """ + + def __init__(self, path): + + self.path = path + self._reload() + + def _reload(self): + # Each QCOW2 file begins with a header, in big endian format, as follows: + # + # typedef struct QCowHeader { + # uint32_t magic; + # uint32_t version; + # + # uint64_t backing_file_offset; + # uint32_t backing_file_size; + # + # uint32_t cluster_bits; + # uint64_t size; /* in bytes */ + # uint32_t crypt_method; + # + # uint32_t l1_size; + # uint64_t l1_table_offset; + # + # uint64_t refcount_table_offset; + # uint32_t refcount_table_clusters; + # + # uint32_t nb_snapshots; + # uint64_t snapshots_offset; + # } QCowHeader; + struct_format = ">IIQi" + + with open(self.path, 'rb') as f: + content = f.read(struct.calcsize(struct_format)) + + self.magic, self.version, self.backing_file_offset, self.backing_file_size = struct.unpack_from(struct_format, content) + + if self.magic != 1363560955: # The first 4 bytes contain the characters 'Q', 'F', 'I' followed by 0xfb. + raise Qcow2Error("Invalid magic for {}".format(self.path)) + + @property + def backing_file(self): + """ + When using linked clone this will return the path to the base image + + :returns: None if it's not a linked clone, the path otherwise + """ + with open(self.path, 'rb') as f: + f.seek(self.backing_file_offset) + content = f.read(self.backing_file_size) + path = content.decode() + if len(path) == 0: + return None + return path + + @asyncio.coroutine + def rebase(self, qemu_img, base_image): + """ + Rebase a linked clone in order to use the correct disk + + :param qemu_img: Path to the qemu-img binary + :param base_image: Path to the base image + """ + if not os.path.exists(base_image): + raise FileNotFoundError(base_image) + command = [qemu_img, "rebase", "-u", "-b", base_image, self.path] + process = yield from asyncio.create_subprocess_exec(*command) + retcode = yield from process.wait() + if retcode != 0: + raise Qcow2Error("Could not rebase the image") + self._reload()