X-Git-Url: https://code.grnet.gr/git/snf-image-creator/blobdiff_plain/279f2c7d3b9bf9eee3d9e2af48e96e90d01db9ef..b686bde920ca2e103391662499817174b40545f8:/image_creator/disk.py diff --git a/image_creator/disk.py b/image_creator/disk.py index 2191df3..40e1316 100644 --- a/image_creator/disk.py +++ b/image_creator/disk.py @@ -84,13 +84,16 @@ class Disk(object): """Cleanup internal data. This needs to be called before the program ends. """ - while len(self._devices): - device = self._devices.pop() - device.destroy() - - while len(self._cleanup_jobs): - job, args = self._cleanup_jobs.pop() - job(*args) + try: + while len(self._devices): + device = self._devices.pop() + device.destroy() + finally: + # Make sure those are executed even if one of the device.destroy + # methods throws exeptions. + while len(self._cleanup_jobs): + job, args = self._cleanup_jobs.pop() + job(*args) def snapshot(self): """Creates a snapshot of the original source media of the Disk @@ -108,25 +111,25 @@ class Disk(object): sourcedev = self._losetup(self.source) elif not stat.S_ISBLK(mode): raise ValueError("Invalid media source. Only block devices, " - "regular files and directories are supported.") + "regular files and directories are supported.") else: self.out.success('looks like a block device') # Take a snapshot and return it to the user self.out.output("Snapshotting media source...", False) - size = blockdev('--getsize', sourcedev) + size = blockdev('--getsz', sourcedev) cowfd, cow = tempfile.mkstemp() os.close(cowfd) self._add_cleanup(os.unlink, cow) - # Create 1G cow sparse file - dd('if=/dev/null', 'of=%s' % cow, 'bs=1k', 'seek=%d' % (1024 * 1024)) + # Create cow sparse file + dd('if=/dev/null', 'of=%s' % cow, 'bs=512', 'seek=%d' % int(size)) cowdev = self._losetup(cow) snapshot = uuid.uuid4().hex tablefd, table = tempfile.mkstemp() try: - os.write(tablefd, "0 %d snapshot %s %s n 8" % \ - (int(size), sourcedev, cowdev)) + os.write(tablefd, "0 %d snapshot %s %s n 8" % + (int(size), sourcedev, cowdev)) dmsetup('create', snapshot, table) self._add_cleanup(dmsetup, 'remove', snapshot) # Sometimes dmsetup remove fails with Device or resource busy, @@ -168,6 +171,7 @@ class DiskDevice(object): self.bootable = bootable self.progress_bar = None self.guestfs_device = None + self.size = 0 self.meta = {} self.g = guestfs.GuestFS() @@ -180,9 +184,10 @@ class DiskDevice(object): # feature of libguestfs, it's better to disable it. self.g.set_recovery_proc(0) version = self.g.version() - if version['major'] > 1 or (version['major'] == 1 and - (version['minor'] >= 18 or \ - (version['minor'] == 17 and version['release'] >= 14))): + if version['major'] > 1 or \ + (version['major'] == 1 and (version['minor'] >= 18 or + (version['minor'] == 17 and + version['release'] >= 14))): self.g.set_recovery_proc(1) self.out.output("Enabling recovery proc") @@ -196,7 +201,7 @@ class DiskDevice(object): self.progressbar = self.out.Progress(100, "Launching helper VM", "percent") eh = self.g.set_event_callback(self.progress_callback, - guestfs.EVENT_PROGRESS) + guestfs.EVENT_PROGRESS) self.g.launch() self.guestfs_enabled = True self.g.delete_event_callback(eh) @@ -209,12 +214,12 @@ class DiskDevice(object): raise FatalError("No operating system found") if len(roots) > 1: raise FatalError("Multiple operating systems found." - "We only support images with one filesystem.") + "We only support images with one OS.") self.root = roots[0] self.guestfs_device = self.g.part_to_dev(self.root) - self.meta['SIZE'] = self.g.blockdev_getsize64(self.guestfs_device) + self.size = self.g.blockdev_getsize64(self.guestfs_device) self.meta['PARTITION_TABLE'] = \ - self.g.part_get_parttype(self.guestfs_device) + self.g.part_get_parttype(self.guestfs_device) self.ostype = self.g.inspect_get_type(self.root) self.distro = self.g.inspect_get_distro(self.root) @@ -223,12 +228,14 @@ class DiskDevice(object): def destroy(self): """Destroy this DiskDevice instance.""" - if self.guestfs_enabled: - self.g.umount_all() - self.g.sync() - - # Close the guestfs handler if open - self.g.close() + # In new guestfs versions, there is a handy shutdown method for this + try: + if self.guestfs_enabled: + self.g.umount_all() + self.g.sync() + finally: + # Close the guestfs handler if open + self.g.close() def progress_callback(self, ev, eh, buf, array): position = array[2] @@ -240,7 +247,8 @@ class DiskDevice(object): """Mount all disk partitions in a correct order.""" mount = self.g.mount_ro if readonly else self.g.mount - self.out.output("Mounting image...", False) + msg = " read-only" if readonly else "" + self.out.output("Mounting the media%s..." % msg, False) mps = self.g.inspect_get_mountpoints(self.root) # Sort the keys to mount the fs in a correct order. @@ -267,13 +275,13 @@ class DiskDevice(object): def _last_partition(self): if self.meta['PARTITION_TABLE'] not in 'msdos' 'gpt': msg = "Unsupported partition table: %s. Only msdos and gpt " \ - "partition tables are supported" % self.meta['PARTITION_TABLE'] + "partition tables are supported" % self.meta['PARTITION_TABLE'] raise FatalError(msg) - is_extended = lambda p: self.g.part_get_mbr_id( - self.guestfs_device, p['part_num']) == 5 - is_logical = lambda p: self.meta['PARTITION_TABLE'] != 'msdos' and \ - p['part_num'] > 4 + is_extended = lambda p: \ + self.g.part_get_mbr_id(self.guestfs_device, p['part_num']) == 5 + is_logical = lambda p: \ + self.meta['PARTITION_TABLE'] != 'msdos' and p['part_num'] > 4 partitions = self.g.part_list(self.guestfs_device) last_partition = partitions[-1] @@ -298,28 +306,31 @@ class DiskDevice(object): ATTENTION: make sure unmount is called before shrink """ - get_fstype = lambda p: self.g.vfs_type("%s%d" % \ - (self.guestfs_device, p['part_num'])) - is_logical = lambda p: self.meta['PARTITION_TABLE'] == 'msdos' and \ - p['part_num'] > 4 - is_extended = lambda p: self.meta['PARTITION_TABLE'] == 'msdos' and \ - self.g.part_get_mbr_id(self.guestfs_device, p['part_num']) == 5 + get_fstype = lambda p: \ + self.g.vfs_type("%s%d" % (self.guestfs_device, p['part_num'])) + is_logical = lambda p: \ + self.meta['PARTITION_TABLE'] == 'msdos' and p['part_num'] > 4 + is_extended = lambda p: \ + self.meta['PARTITION_TABLE'] == 'msdos' and \ + self.g.part_get_mbr_id(self.guestfs_device, p['part_num']) == 5 part_add = lambda ptype, start, stop: \ - self.g.part_add(self.guestfs_device, ptype, start, stop) + self.g.part_add(self.guestfs_device, ptype, start, stop) part_del = lambda p: self.g.part_del(self.guestfs_device, p) part_get_id = lambda p: self.g.part_get_mbr_id(self.guestfs_device, p) - part_set_id = lambda p, id: self.g.part_set_mbr_id( - self.guestfs_device, p, id) - part_get_bootable = lambda p: self.g.part_get_bootable( - self.guestfs_device, p) - part_set_bootable = lambda p, bootable: self.g.part_set_bootable( - self.guestfs_device, p, bootable) + part_set_id = lambda p, id: \ + self.g.part_set_mbr_id(self.guestfs_device, p, id) + part_get_bootable = lambda p: \ + self.g.part_get_bootable(self.guestfs_device, p) + part_set_bootable = lambda p, bootable: \ + self.g.part_set_bootable(self.guestfs_device, p, bootable) MB = 2 ** 20 self.out.output("Shrinking image (this may take a while)...", False) + sector_size = self.g.blockdev_getss(self.guestfs_device) + last_part = None fstype = None while True: @@ -328,20 +339,23 @@ class DiskDevice(object): if fstype == 'swap': self.meta['SWAP'] = "%d:%s" % \ - (last_part['part_num'], - (last_part['part_size'] + MB - 1) // MB) + (last_part['part_num'], + (last_part['part_size'] + MB - 1) // MB) part_del(last_part['part_num']) continue elif is_extended(last_part): part_del(last_part['part_num']) continue - self.meta['SIZE'] = last_part['part_end'] + 1 + # Most disk manipulation programs leave 2048 sectors after the last + # partition + new_size = last_part['part_end'] + 1 + 2048 * sector_size + self.size = min(self.size, new_size) break if not re.match("ext[234]", fstype): self.out.warn("Don't know how to resize %s partitions." % fstype) - return self.meta['SIZE'] + return self.size part_dev = "%s%d" % (self.guestfs_device, last_part['part_num']) self.g.e2fsck_f(part_dev) @@ -353,7 +367,6 @@ class DiskDevice(object): block_cnt = int( filter(lambda x: x[0] == 'Block count', out)[0][1]) - sector_size = self.g.blockdev_getss(self.guestfs_device) start = last_part['part_start'] / sector_size end = start + (block_size * block_cnt) / sector_size - 1 @@ -398,15 +411,18 @@ class DiskDevice(object): part_set_id(last_part['part_num'], last_part['id']) new_size = (end + 1) * sector_size - self.out.success("new size is %dMB" % ((new_size + MB - 1) // MB)) + + assert (new_size <= self.size) if self.meta['PARTITION_TABLE'] == 'gpt': ptable = GPTPartitionTable(self.real_device) - self.meta['SIZE'] = ptable.shrink(new_size) + self.size = ptable.shrink(new_size, self.size) else: - self.meta['SIZE'] = new_size + self.size = min(new_size + 2048 * sector_size, self.size) + + self.out.success("new size is %dMB" % ((self.size + MB - 1) // MB)) - return self.meta['SIZE'] + return self.size def dump(self, outfile): """Dumps the content of device into a file. @@ -416,7 +432,7 @@ class DiskDevice(object): """ MB = 2 ** 20 blocksize = 4 * MB # 4MB - size = self.meta['SIZE'] + size = self.size progr_size = (size + MB - 1) // MB # in MB progressbar = self.out.Progress(progr_size, "Dumping image file", 'mb') @@ -427,7 +443,8 @@ class DiskDevice(object): progressbar.next() while left > 0: length = min(left, blocksize) - sent = sendfile(dst.fileno(), src.fileno(), offset, length) + _, sent = sendfile(dst.fileno(), src.fileno(), offset, + length) offset += sent left -= sent progressbar.goto((size - left) // MB)