- self._devices.remove(device)
- device.destroy()
-
-
-class DiskDevice(object):
- """This class represents a block device hosting an Operating System
- as created by the device-mapper.
- """
-
- def __init__(self, device, bootable=True):
- """Create a new DiskDevice."""
-
- self.device = device
- self.bootable = bootable
- self.progress_bar = None
-
- self.g = guestfs.GuestFS()
- self.g.add_drive_opts(self.device, readonly=0)
-
- #self.g.set_trace(1)
- #self.g.set_verbose(1)
-
- self.guestfs_enabled = False
-
- def enable(self):
- """Enable a newly created DiskDevice"""
- self.progressbar = progress("Launching helper VM: ", "percent")
- self.progressbar.max = 100
- self.progressbar.goto(1)
- eh = self.g.set_event_callback(self.progress_callback,
- guestfs.EVENT_PROGRESS)
- self.g.launch()
- self.guestfs_enabled = True
- self.g.delete_event_callback(eh)
- if self.progressbar is not None:
- output("\rLaunching helper VM...\033[K", False)
- success("done")
- self.progressbar = None
-
- output('Inspecting Operating System...', False)
- roots = self.g.inspect_os()
- if len(roots) == 0:
- raise FatalError("No operating system found")
- if len(roots) > 1:
- raise FatalError("Multiple operating systems found."
- "We only support images with one filesystem.")
- self.root = roots[0]
- self.gdev = self.g.part_to_dev(self.root)
- self.parttype = self.g.part_get_parttype(self.gdev)
-
- self.ostype = self.g.inspect_get_type(self.root)
- self.distro = self.g.inspect_get_distro(self.root)
- success('found a(n) %s system' % self.distro)
-
- 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()
-
- def progress_callback(self, ev, eh, buf, array):
- position = array[2]
- total = array[3]
-
- self.progressbar.goto((position * 100) // total)
-
- def mount(self):
- """Mount all disk partitions in a correct order."""
-
- output("Mounting image...", False)
- mps = self.g.inspect_get_mountpoints(self.root)
-
- # Sort the keys to mount the fs in a correct order.
- # / should be mounted befor /boot, etc
- def compare(a, b):
- if len(a[0]) > len(b[0]):
- return 1
- elif len(a[0]) == len(b[0]):
- return 0
- else:
- return -1
- mps.sort(compare)
- for mp, dev in mps:
- try:
- self.g.mount(dev, mp)
- except RuntimeError as msg:
- warn("%s (ignored)" % msg)
- success("done")
-
- def umount(self):
- """Umount all mounted filesystems."""
- self.g.umount_all()
-
- def shrink(self):
- """Shrink the disk.
-
- This is accomplished by shrinking the last filesystem in the
- disk and then updating the partition table. The new disk size
- (in bytes) is returned.
- """
- output("Shrinking image (this may take a while)...", False)
-
- if self.parttype not in 'msdos' 'gpt':
- raise FatalError("You have a %s partition table. "
- "Only msdos and gpt partitions are supported" % self.parttype)
-
- last_partition = self.g.part_list(self.gdev)[-1]
-
- if last_partition['part_num'] > 4:
- raise FatalError("This disk contains logical partitions. "
- "Only primary partitions are supported.")
-
- part_dev = "%s%d" % (self.gdev, last_partition['part_num'])
- fs_type = self.g.vfs_type(part_dev)
- if not re.match("ext[234]", fs_type):
- warn("Don't know how to resize %s partitions." % vfs_type)
- return self.size()
-
- self.g.e2fsck_f(part_dev)
- 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])
-
- sector_size = self.g.blockdev_getss(self.gdev)
-
- start = last_partition['part_start'] / sector_size
- end = start + (block_size * block_cnt) / sector_size - 1
-
- self.g.part_del(self.gdev, last_partition['part_num'])
- self.g.part_add(self.gdev, 'p', start, end)
-
- new_size = (end + 1) * sector_size
- success("new image size is %dMB" %
- ((new_size + 2 ** 20 - 1) // 2 ** 20))
-
- if self.parttype == 'gpt':
- ptable = GPTPartitionTable(self.device)
- return ptable.shrink(new_size)
-
- return new_size
-
- def size(self):
- """Returns the "payload" size of the device.
-
- The size returned by this method is the size of the space occupied by
- the partitions (including the space before the first partition).
- """
-
- if self.parttype == 'msdos':
- dev = self.g.part_to_dev(self.root)
- last = self.g.part_list(dev)[-1]
- return last['part_end'] + 1
- elif self.parttype == 'gpt':
- ptable = GPTPartitionTable(self.device)
- return ptable.size()
- else:
- raise FatalError("Unsupported partition table type: %s" % parttype)
-
- def dump(self, outfile):
- """Dumps the content of device into a file.
-
- This method will only dump the actual payload, found by reading the
- partition table. Empty space in the end of the device will be ignored.
- """
- blocksize = 2 ** 22 # 4MB
- size = self.size()
- progress_size = (size + 2 ** 20 - 1) // 2 ** 20 # in MB
- progressbar = progress("Dumping image file: ", 'mb')
- progressbar.max = progress_size
- source = open(self.device, "r")
- try:
- dest = open(outfile, "w")
- try:
- left = size
- offset = 0
- progressbar.next()
- while left > 0:
- length = min(left, blocksize)
- sent = sendfile(dest.fileno(), source.fileno(), offset,
- length)
- offset += sent
- left -= sent
- progressbar.goto((size - left) // 2 ** 20)
- finally:
- dest.close()
- finally:
- source.close()
- output("\rDumping image file...\033[K", False)
- success('image file %s was successfully created' % outfile)