diff --git a/image_creator/bundle_volume.py b/image_creator/bundle_volume.py index 20517382fe57f2e59725e7c0ffee1d5eb1e32dd9..311dd24d4d792f94eadb1fd8a07b36b7306f8e5e 100644 --- a/image_creator/bundle_volume.py +++ b/image_creator/bundle_volume.py @@ -31,10 +31,90 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. +import os +import re +import parted +import uuid + +from image_creator.util import get_command from image_creator.util import FatalError -def bundle_volume(): - raise FatalError("Creating an image out of a running system is not yet " - "supported.") +findfs = get_command('findfs') +truncate = get_command('truncate') +dd = get_command('dd') + +def get_root_partition(): + if not os.path.isfile('/etc/fstab'): + raise FatalError("Unable to open `/etc/fstab'. File is missing.") + + with open('/etc/fstab') as fstab: + for line in iter(fstab): + entry = line.split('#')[0].strip().split() + if len(entry) != 6: + continue + + if entry[1] == "/": + return entry[0] + + raise FatalError("Unable to find root device in /etc/fstab") + +def mnt_mounted(): + if not os.path.isfile('/etc/mtab'): + raise FatalError("Unable to open `/etc/fstab'. File is missing.") + + with open('/etc/mtab') as mtab: + for line in iter(mtab): + entry = line.split('#')[0].strip().split() + if len(entry) != 6: + continue + + if entry[1] == '/mnt': + return True + + return False + + +def part_to_dev(part): + return re.split('[0-9]', part)[0] + +def part_to_num(part): + return re.split('[^0-9]+', part)[-1] + +def bundle_volume(out): + + if mnt_mounted(): + raise FatalError('The directory /mnt where the image will be hosted' + 'is mounted. Please unmount it and start over again.') + + out.output('Searching for root device...', False) + root_part = get_root_partition() + + if root_part.startswith("UUID=") or root_part.startswith("LABEL="): + root_part = findfs(root_part) + elif not root_part.startswith("/"): + raise FatalError("Unable to find a block device for: %s" % root_dev) + + if not re.match('/dev/[hsv]d[a-z][1-9]*$', root_part): + raise FatalError("Don't know how to handle root device: %s" % root_dev) + + device = parted.Device(part_to_dev(root_part)) + + image = '/mnt/%s.diskdump' % uuid.uuid4().hex + + # Create sparse file to host the image + truncate("-s", "%d" % (device.getLength() * device.sectorSize), image) + + disk = parted.Disk(device) + if disk.type != 'msdos': + raise FatalError('For now we can only handle msdos partition tables') + + # Copy the MBR and the space between the MBR and the first partition. + # In Grub version 1 Stage 1.5 is located there. + first_sector = disk.getPrimaryPartitions()[0].geometry.start + + dd('if=%s' % device.path, 'of=%s' % image, 'bs=%d' % device.sectorSize, + 'count=%d' % first_sector, 'conv=notrunc') + + return image # vim: set sta sts=4 shiftwidth=4 sw=4 et ai : diff --git a/image_creator/disk.py b/image_creator/disk.py index f3df5e8ce9648db42341be47e2abb5be980f7fb7..deafb95befc12b60a6c3ecca294ae046791835d7 100644 --- a/image_creator/disk.py +++ b/image_creator/disk.py @@ -79,7 +79,7 @@ class Disk(object): def _dir_to_disk(self): if self.source == '/': - return bundle_volume() + return bundle_volume(self.out) raise FatalError("Using a directory as media source is supported") def cleanup(self):