diff --git a/image_creator/disk.py b/image_creator/disk.py index 2fc7a73d6d1a6448ed980cbc787c8c3a628faf5d..0b02d0504006dcb36afa4f064536dc57452ba015 100644 --- a/image_creator/disk.py +++ b/image_creator/disk.py @@ -18,7 +18,7 @@ """Module hosting the Disk class.""" from image_creator.util import get_command, try_fail_repeat, free_space, \ - FatalError, image_info + FatalError, create_snapshot from image_creator.bundle_volume import BundleVolume from image_creator.image import Image @@ -156,32 +156,18 @@ class Disk(object): self.out.warn("Snapshotting ignored for host bundling mode.") return self.file - mode = os.stat(self.file).st_mode - device = self.file if stat.S_ISBLK(mode) else self._losetup(self.file) - size = int(blockdev('--getsz', device)) - virtual_size = (image_info(device)['virtual-size'] + 511) // 512 - - # If the virtual_size is bigger than the block device size, we need to - # create a bigger block device padded with zeros, otherwise QEMU will - # freeze if it tries to enlarge the underlying file. - if virtual_size > size: - zeros = virtual_size - size - tablefd, table = tempfile.mkstemp() - try: - try: - os.write(tablefd, '0 %d linear %s 0\n' % (size, device)) - os.write(tablefd, '%d %d zero\n' % (size, zeros)) - finally: - os.close(tablefd) - padded = 'snf-image-creator-padded-%s' % uuid.uuid4().hex - dmsetup('create', padded, table) - self._add_cleanup(try_fail_repeat, dmsetup, 'remove', padded) - finally: - os.unlink(table) - device = "/dev/mapper/%s" % padded - size = virtual_size - self.out.output("Snapshotting media source ...", False) + + # Create a qcow2 snapshot for image files + if not stat.S_ISBLK(os.stat(self.file).st_mode): + snapshot = create_snapshot(self.file, self.tmp) + self._add_cleanup(os.unlink, snapshot) + self.out.success('done') + return snapshot + + # Create a device-mapper snapshot for block devices + size = int(blockdev('--getsz', self.file)) + cowfd, cow = tempfile.mkstemp(dir=self.tmp) os.close(cowfd) self._add_cleanup(os.unlink, cow) @@ -193,8 +179,8 @@ class Disk(object): tablefd, table = tempfile.mkstemp() try: try: - os.write(tablefd, - "0 %d snapshot %s %s n 8\n" % (size, device, cowdev)) + os.write(tablefd, "0 %d snapshot %s %s n 8\n" % + (size, self.file, cowdev)) finally: os.close(tablefd) diff --git a/image_creator/image.py b/image_creator/image.py index 88074090b4bf20e5ce724d53068df409570197f0..da3777df927ee34c828b19044972863e2514ebe9 100644 --- a/image_creator/image.py +++ b/image_creator/image.py @@ -224,6 +224,7 @@ class Image(object): def __enter__(self): return img.device if img.info['format'] == 'raw' else \ img.nbd.connect(readonly) + def __exit__(self, exc_type, exc_value, traceback): if img.info['format'] != 'raw': img.nbd.disconnect() diff --git a/image_creator/util.py b/image_creator/util.py index 4d0fb34dff833bb22608c2396ed30e903589052b..dfae024be68350b58352b8c00e491ef9905e75d7 100644 --- a/image_creator/util.py +++ b/image_creator/util.py @@ -24,6 +24,7 @@ import time import os import re import json +import tempfile from sh import qemu_img from sh import qemu_nbd from sh import modprobe @@ -40,6 +41,16 @@ def image_info(image): return json.loads(str(info)) +def create_snapshot(source, target_dir): + """Returns a qcow2 snapshot of an image file""" + + snapfd, snap = tempfile.mkstemp(prefix='snapshot-', dir=target_dir) + os.close(snapfd) + qemu_img('create', '-f', 'qcow2', '-o', + 'backing_file=%s' % os.path.abspath(source), snap) + return snap + + def get_command(command): """Return a file system binary command""" def find_sbin_command(command, exception):