def create_image(self, image): """Given an image filename, this method will create an image out of the running system. """ size = self.disk.device.length * self.disk.device.sectorSize # Create sparse file to host the image fd = os.open(image, os.O_WRONLY | os.O_CREAT) try: os.ftruncate(fd, size) finally: os.close(fd) self._create_partition_table(image) end_sector, partitions = self._shrink_partitions(image) if self.disk.type == 'gpt': old_size = size size = (end_sector + 1) * self.disk.device.sectorSize ptable = GPTPartitionTable(image) size = ptable.shrink(size, old_size) else: # Align to 2048 end_sector = ((end_sector + 2047) // 2048) * 2048 size = (end_sector + 1) * self.disk.device.sectorSize # Truncate image to the new size. fd = os.open(image, os.O_RDWR) try: os.ftruncate(fd, size) finally: os.close(fd) # Check if the available space is enough to host the image dirname = os.path.dirname(image) self.out.info("Examining available space ...", False) if free_space(dirname) <= size: raise FatalError("Not enough space under %s to host the temporary " "image" % dirname) self.out.success("sufficient") self._create_filesystems(image, partitions) return image
def shrink(self, silent=False): """Shrink the image. This is accomplished by shrinking the last file system of the image and then updating the partition table. The shrinked device is returned. ATTENTION: make sure umount is called before shrink """ def get_fstype(partition): """Get file system type""" device = "%s%d" % (self.guestfs_device, partition['part_num']) return self.g.vfs_type(device) def is_extended(partition): """Returns True if the partition is an extended partition""" if self.meta['PARTITION_TABLE'] == 'msdos': mbr_id = self.g.part_get_mbr_id(self.guestfs_device, partition['part_num']) return mbr_id in (0x5, 0xf) return False def part_del(partnum): """Delete a partition""" self.g.part_del(self.guestfs_device, partnum) MB = 2**20 if self.is_unsupported(): if not silent: self.out.warn("Shrinking is disabled for unsupported images") return None sector_size = self.g.blockdev_getss(self.guestfs_device) last_part = None fstype = None while True: last_part = self._last_partition() fstype = get_fstype(last_part) if fstype == 'swap': self.meta['SWAP'] = "%d:%s" % \ (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 # 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 fstype: if not silent: self.out.warn("Unable to shrink partition: %s. Reason: " "Could not detect file system." % last_part['part_num']) return None if not re.match("ext[234]", fstype): if not silent: self.out.warn("Unable to shrink partition: %s. Reason: " "Don't know how to shrink %s file systems." % (last_part['part_num'], fstype)) return None part_dev = "%s%d" % (self.guestfs_device, last_part['part_num']) out = self.g.tune2fs_l(part_dev) block_size = int(filter(lambda x: x[0] == 'Block size', out)[0][1]) old_block_cnt = int(filter(lambda x: x[0] == 'Block count', out)[0][1]) try: if self.check_guestfs_version(1, 15, 17) >= 0: self.g.e2fsck(part_dev, forceall=1) else: self.g.e2fsck_f(part_dev) except RuntimeError as e: # There is a bug in some versions of libguestfs and a RuntimeError # is thrown although the command has successfully corrected the # found file system errors. if str(e).find('***** FILE SYSTEM WAS MODIFIED *****') == -1: raise self.g.resize2fs_M(part_dev) out = self.g.tune2fs_l(part_dev) assert block_size == \ int(filter(lambda x: x[0] == 'Block size', out)[0][1]) block_cnt = int(filter(lambda x: x[0] == 'Block count', out)[0][1]) # Add some extra space for the image to be able to run. block_cnt = min(old_block_cnt, block_cnt + 8388608 / block_size) start = last_part['part_start'] / sector_size end = start + (block_size * block_cnt) / sector_size - 1 self._resize_partition(end) # Enlarge the underlying file system to consume the available space self.g.resize2fs(part_dev) new_size = (end + 1) * sector_size assert new_size <= self.size if self.meta['PARTITION_TABLE'] == 'gpt': with self.raw_device(readonly=False) as raw: ptable = GPTPartitionTable(raw) self.size = ptable.shrink(new_size, self.size) else: self.size = min(new_size + 2048 * sector_size, self.size) if not silent: self.out.success("Image size is %dMB" % ((self.size + MB - 1) // MB)) return part_dev
def shrink(self, silent=False): """Shrink the image. This is accomplished by shrinking the last file system of the image and then updating the partition table. The shrinked device is returned. ATTENTION: make sure umount is called before shrink """ def get_fstype(partition): """Get file system type""" device = "%s%d" % (self.guestfs_device, partition['part_num']) return self.g.vfs_type(device) def is_logical(partition): """Returns True if the partition is a logical partition""" return self.meta['PARTITION_TABLE'] == 'msdos' and \ partition['part_num'] > 4 def is_extended(partition): """Returns True if the partition is an extended partition""" if self.meta['PARTITION_TABLE'] == 'msdos': mbr_id = self.g.part_get_mbr_id(self.guestfs_device, partition['part_num']) return mbr_id in (0x5, 0xf) return False def part_add(ptype, start, stop): """Add partition""" self.g.part_add(self.guestfs_device, ptype, start, stop) def part_del(partnum): """Delete a partition""" self.g.part_del(self.guestfs_device, partnum) def part_get_id(partnum): """Returns the MBR id of the partition""" return self.g.part_get_mbr_id(self.guestfs_device, partnum) def part_set_id(partnum, id): """Sets the MBR id of the partition""" self.g.part_set_mbr_id(self.guestfs_device, partnum, id) def part_get_bootable(partnum): """Returns the bootable flag of the partition""" return self.g.part_get_bootable(self.guestfs_device, partnum) def part_set_bootable(partnum, bootable): """Sets the bootable flag for a partition""" self.g.part_set_bootable(self.guestfs_device, partnum, bootable) MB = 2 ** 20 if self.is_unsupported(): if not silent: self.out.warn("Shrinking is disabled for unsupported images") return None sector_size = self.g.blockdev_getss(self.guestfs_device) last_part = None fstype = None while True: last_part = self._last_partition() fstype = get_fstype(last_part) if fstype == 'swap': self.meta['SWAP'] = "%d:%s" % \ (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 # 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 (fstype or silent): self.out.warn( "Unable to shrink partition: %s. Reason: " "Could not detect file system." % last_part['part_num']) return None if not (re.match("ext[234]", fstype) or silent): self.out.warn( "Unable to shrink partition: %s. Reason: " "Don't know how to shrink %s file systems." % (last_part['part_num'], fstype)) return None part_dev = "%s%d" % (self.guestfs_device, last_part['part_num']) try: if self.check_guestfs_version(1, 15, 17) >= 0: self.g.e2fsck(part_dev, forceall=1) else: self.g.e2fsck_f(part_dev) except RuntimeError as e: # There is a bug in some versions of libguestfs and a RuntimeError # is thrown although the command has successfully corrected the # found file system errors. if e.message.find('***** FILE SYSTEM WAS MODIFIED *****') == -1: raise self.g.resize2fs_M(part_dev) out = self.g.tune2fs_l(part_dev) block_size = int(filter(lambda x: x[0] == 'Block size', out)[0][1]) block_cnt = int(filter(lambda x: x[0] == 'Block count', out)[0][1]) start = last_part['part_start'] / sector_size end = start + (block_size * block_cnt) / sector_size - 1 if is_logical(last_part): partitions = self.g.part_list(self.guestfs_device) logical = [] # logical partitions for partition in partitions: if partition['part_num'] < 4: continue logical.append({ 'num': partition['part_num'], 'start': partition['part_start'] / sector_size, 'end': partition['part_end'] / sector_size, 'id': part_get_id(partition['part_num']), 'bootable': part_get_bootable(partition['part_num']) }) logical[-1]['end'] = end # new end after resize # Recreate the extended partition extended = filter(is_extended, partitions)[0] part_del(extended['part_num']) part_add('e', extended['part_start'] / sector_size, end) # Create all the logical partitions back for l in logical: part_add('l', l['start'], l['end']) part_set_id(l['num'], l['id']) part_set_bootable(l['num'], l['bootable']) else: # Recreate the last partition if self.meta['PARTITION_TABLE'] == 'msdos': last_part['id'] = part_get_id(last_part['part_num']) last_part['bootable'] = part_get_bootable(last_part['part_num']) part_del(last_part['part_num']) part_add('p', start, end) part_set_bootable(last_part['part_num'], last_part['bootable']) if self.meta['PARTITION_TABLE'] == 'msdos': part_set_id(last_part['part_num'], last_part['id']) new_size = (end + 1) * sector_size assert new_size <= self.size if self.meta['PARTITION_TABLE'] == 'gpt': with self.raw_device(readonly=False) as raw: ptable = GPTPartitionTable(raw) self.size = ptable.shrink(new_size, self.size) else: self.size = min(new_size + 2048 * sector_size, self.size) if not silent: self.out.success("Image size is %dMB" % ((self.size + MB - 1) // MB)) return part_dev
def shrink(self, silent=False): """Shrink the image. This is accomplished by shrinking the last file system of the image and then updating the partition table. The shrinked device is returned. ATTENTION: make sure umount is called before shrink """ def get_fstype(partition): """Get file system type""" device = "%s%d" % (self.guestfs_device, partition['part_num']) return self.g.vfs_type(device) def is_extended(partition): """Returns True if the partition is an extended partition""" if self.meta['PARTITION_TABLE'] == 'msdos': mbr_id = self.g.part_get_mbr_id(self.guestfs_device, partition['part_num']) return mbr_id in (0x5, 0xf) return False def part_del(partnum): """Delete a partition""" self.g.part_del(self.guestfs_device, partnum) MB = 2 ** 20 if self.is_unsupported(): if not silent: self.out.warn("Shrinking is disabled for unsupported images") return None sector_size = self.g.blockdev_getss(self.guestfs_device) last_part = None fstype = None while True: last_part = self._last_partition() fstype = get_fstype(last_part) if fstype == 'swap': self.meta['SWAP'] = "%d:%s" % \ (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 # 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 fstype: if not silent: self.out.warn( "Unable to shrink partition: %s. Reason: " "Could not detect file system." % last_part['part_num']) return None if not re.match("ext[234]", fstype): if not silent: self.out.warn( "Unable to shrink partition: %s. Reason: " "Don't know how to shrink %s file systems." % (last_part['part_num'], fstype)) return None part_dev = "%s%d" % (self.guestfs_device, last_part['part_num']) out = self.g.tune2fs_l(part_dev) block_size = int(filter(lambda x: x[0] == 'Block size', out)[0][1]) old_block_cnt = int(filter(lambda x: x[0] == 'Block count', out)[0][1]) try: if self.check_guestfs_version(1, 15, 17) >= 0: self.g.e2fsck(part_dev, forceall=1) else: self.g.e2fsck_f(part_dev) except RuntimeError as e: # There is a bug in some versions of libguestfs and a RuntimeError # is thrown although the command has successfully corrected the # found file system errors. if str(e).find('***** FILE SYSTEM WAS MODIFIED *****') == -1: raise self.g.resize2fs_M(part_dev) out = self.g.tune2fs_l(part_dev) assert block_size == \ int(filter(lambda x: x[0] == 'Block size', out)[0][1]) block_cnt = int(filter(lambda x: x[0] == 'Block count', out)[0][1]) # Add some extra space for the image to be able to run. block_cnt = min(old_block_cnt, block_cnt + 8388608/block_size) start = last_part['part_start'] / sector_size end = start + (block_size * block_cnt) / sector_size - 1 self._resize_partition(end) # Enlarge the underlying file system to consume the available space self.g.resize2fs(part_dev) new_size = (end + 1) * sector_size assert new_size <= self.size if self.meta['PARTITION_TABLE'] == 'gpt': with self.raw_device(readonly=False) as raw: ptable = GPTPartitionTable(raw) self.size = ptable.shrink(new_size, self.size) else: self.size = min(new_size + 2048 * sector_size, self.size) if not silent: self.out.success("Image size is %dMB" % ((self.size + MB - 1) // MB)) return part_dev