diff --git a/API b/API
index 9783c74..ef9c1aa 100644
--- a/API
+++ b/API
@@ -76,3 +76,7 @@ switching from the LiveImageCreator to another ImageCreator object.
build live images which use dm-snapshot, etc. This is what is used
by livecd-creator.
+* DiskImageCreator: This generates disk images containing multiple
+ partitions in a loopback file. It installs grub in the MBR and
+ can be directly booted in any virtual machine
+
diff --git a/Makefile b/Makefile
index 050fe88..d3d4ab7 100644
--- a/Makefile
+++ b/Makefile
@@ -18,6 +18,7 @@ all:
install:
$(INSTALL_PROGRAM) -D tools/livecd-creator $(DESTDIR)/usr/bin/livecd-creator
$(INSTALL_PROGRAM) -D tools/image-creator $(DESTDIR)/usr/bin/image-creator
+ $(INSTALL_PROGRAM) -D tools/disk-creator $(DESTDIR)/usr/bin/disk-creator
$(INSTALL_PROGRAM) -D tools/livecd-iso-to-disk.sh $(DESTDIR)/usr/bin/livecd-iso-to-disk
$(INSTALL_PROGRAM) -D tools/livecd-iso-to-pxeboot.sh $(DESTDIR)/usr/bin/livecd-iso-to-pxeboot
$(INSTALL_PROGRAM) -D tools/mayflower $(DESTDIR)/usr/lib/livecd-creator/mayflower
@@ -34,6 +35,8 @@ install:
uninstall:
rm -f $(DESTDIR)/usr/bin/livecd-creator
rm -rf $(DESTDIR)/usr/lib/livecd-creator
+ rm -rf $(DESTDIR)/usr/bin/disk-creator
+ rm -rf $(DESTDIR)/usr/bin/image-creator
rm -rf $(DESTDIR)/usr/share/doc/livecd-tools-$(VERSION)
rm -rf $(DESTDIR)/usr/share/livecd-tools
diff --git a/imgcreate/__init__.py b/imgcreate/__init__.py
index e535014..44f3ec5 100644
--- a/imgcreate/__init__.py
+++ b/imgcreate/__init__.py
@@ -21,6 +21,7 @@ from imgcreate.creator import *
from imgcreate.yuminst import *
from imgcreate.kickstart import *
from imgcreate.fs import *
+from imgcreate.disk import *
"""A set of classes for building Fedora system images.
@@ -28,6 +29,7 @@ The following image creators are available:
- ImageCreator - installs to a directory
- LoopImageCreator - installs to an ext3 image
- LiveImageCreator - installs to a bootable ISO
+ - DiskImageCreator - installs to a partitioned disk image
Also exported are:
- CreatorError - all exceptions throw are of this type
@@ -60,6 +62,7 @@ __all__ = (
'ImageCreator',
'LiveImageCreator',
'LoopImageCreator',
+ 'DiskImageCreator',
'FSLABEL_MAXLEN',
'read_kickstart',
'construct_name'
diff --git a/imgcreate/creator.py b/imgcreate/creator.py
index c2ed770..fb3b309 100644
--- a/imgcreate/creator.py
+++ b/imgcreate/creator.py
@@ -207,7 +207,11 @@ class ImageCreator(object):
"""
s = "/dev/root / %s defaults,noatime 0 0\n" %(self._fstype)
- s += "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
+ s += self._get_fstab_special()
+ return s
+
+ def _get_fstab_special(self):
+ s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
s += "proc /proc proc defaults 0 0\n"
s += "sysfs /sys sysfs defaults 0 0\n"
@@ -816,12 +820,11 @@ class LoopImageCreator(ImageCreator):
if not base_on is None:
shutil.copyfile(base_on, self._image)
- self.__instloop = SparseExtLoopbackMount(self._image,
- self._instroot,
- self.__image_size,
- self.__fstype,
- self.__blocksize,
- self.fslabel)
+ self.__instloop = ExtDiskMount(SparseLoopbackDisk(self._image, self.__image_size),
+ self._instroot,
+ self.__fstype,
+ self.__blocksize,
+ self.fslabel)
try:
self.__instloop.mount()
diff --git a/imgcreate/disk.py b/imgcreate/disk.py
index a0dd6e9..6027b75 100644
--- a/imgcreate/disk.py
+++ b/imgcreate/disk.py
@@ -37,7 +37,7 @@ class DiskImageCreator(ImageCreator):
"""
- def __init__(self, ks, name):
+ def __init__(self, ks, name, disks, format="raw"):
"""Initialize a DiskImageCreator instance.
This method takes the same arguments as ImageCreator.__init__()
@@ -47,32 +47,20 @@ class DiskImageCreator(ImageCreator):
self.__instloop = None
self.__imgdir = None
-
- # XXX configure me :-)
- self.__disk_size = 8192L * 1024 * 1024
-
- def __get_disk(self):
- if self.__imgdir is None:
- raise CreatorError("_disk is not valid before calling mount()")
- return self.__imgdir + "/disk.dsk"
- _disk = property(__get_disk)
- """The location of the disk file.
-
- This is the path to the filesystem disk. Subclasses may use this path
- in order to package the disk in _stage_final_disk().
-
- Note, this directory does not exist before DiskCreator.mount() is called.
-
- Note also, this is a read-only attribute.
-
- """
+ self.__disks = disks
+ self.__format = format
def _get_fstab(self):
s = ""
- # XXX > 1 disk, don't assume sda
- for p in self.__instloop.partitions:
+ for mp in self.__instloop.mountOrder:
+ p = None
+ for p1 in self.__instloop.partitions:
+ if p1['mountpoint'] == mp:
+ p = p1
+ break
+
s += "%(device)s %(mountpoint)s %(fstype)s defaults,noatime 0 0\n" % {
- 'device': "/dev/sda%-d" % p['num'],
+ 'device': "/dev/%s%-d" % (p['disk'], p['num']),
'mountpoint': p['mountpoint'],
'fstype': p['fstype'] }
@@ -86,18 +74,35 @@ class DiskImageCreator(ImageCreator):
self.__imgdir = self._mkdtemp()
parts = kickstart.get_partitions(self.ks)
+
+ devnames = []
+ for p in parts:
+ dev = p.disk
+ if not dev in devnames:
+ devnames.append(dev)
+ devnames.sort()
- self.__instloop = PartitionedMount(SparseLoopbackDisk(self._disk, self.__disk_size),
+ disks = []
+
+ if len(devnames) != len(self.__disks):
+ raise CreatorError("Not enough disks for declared partitions")
+
+ for i in range(len(self.__disks)):
+ disk = SparseLoopbackDisk("%s/disk%d.raw" % (self.__imgdir, i),
+ self.__disks[i]['size'])
+ self.__disks[i]['disk'] = disk
+ disks.append(disk)
+
+ self.__instloop = PartitionedMount(disks, devnames,
self._instroot)
for p in parts:
- self.__instloop.add_partition(int(p.size), p.mountpoint, p.fstype)
+ self.__instloop.add_partition(int(p.size), p.disk, p.mountpoint, p.fstype)
try:
self.__instloop.mount()
except MountError, e:
- raise CreatorError("Failed mount '%s' : %s" %
- (self._disk, e))
+ raise CreatorError("Failed mount disks : %s" % e)
def _get_required_packages(self):
return ["grub"]
@@ -110,6 +115,8 @@ class DiskImageCreator(ImageCreator):
if not dev in devs:
devs.append(dev)
+ devs.sort()
+
n = 0
devmap = ""
for dev in devs:
@@ -124,7 +131,6 @@ class DiskImageCreator(ImageCreator):
bootdevnum = None
rootdevnum = None
rootdev = None
- # XXX > 1 disk, don't assume sda
for p in self.__instloop.partitions:
if p['mountpoint'] == "/boot":
bootdevnum = p['num'] - 1
@@ -133,7 +139,7 @@ class DiskImageCreator(ImageCreator):
if p['mountpoint'] == "/":
rootdevnum = p['num'] - 1
- rootdev = "/dev/sda%-d" % p['num']
+ rootdev = "/dev/%s%-d" % (p['disk'], p['num'])
prefix = ""
if bootdevnum == rootdevnum:
@@ -144,11 +150,15 @@ class DiskImageCreator(ImageCreator):
def _create_grub_config(self):
(bootdevnum, rootdevnum, rootdev, prefix) = self._get_grub_boot_config()
+ # NB we're assuming that grub config is on the first physical disk
+ # ie /boot must be on sda, or if there's no /boot, then / must be sda
+
+ # XXX don't hardcode default kernel - see livecd code
grub = ""
grub += "default=0\n"
grub += "timeout=5\n"
- grub += "splashimage=(hd0,%-d)%s/grub/splash.xpm.gz\n" % (rootdevnum, prefix)
- grub += "hiddenemnu\n"
+ grub += "splashimage=(hd0,%d)%s/grub/splash.xpm.gz\n" % (bootdevnum, prefix)
+ grub += "hiddenmenu\n"
versions = []
kernels = self._get_kernel_versions()
@@ -158,11 +168,11 @@ class DiskImageCreator(ImageCreator):
for v in versions:
grub += "title Fedora (%s)\n" % v
- grub += " root (hd0,%-d)\n" % bootdevnum
+ grub += " root (hd0,%d)\n" % bootdevnum
grub += " kernel %s/vmlinuz-%s ro root=%s\n" % (prefix, v, rootdev)
grub += " initrd %s/initrd-%s.img\n" % (prefix, v)
- cfg = open(self._instroot + "/boot/grub/grub.cfg", "w")
+ cfg = open(self._instroot + "/boot/grub/grub.conf", "w")
cfg.write(grub)
cfg.close()
@@ -185,11 +195,18 @@ class DiskImageCreator(ImageCreator):
def _install_grub(self):
(bootdevnum, rootdevnum, rootdev, prefix) = self._get_grub_boot_config()
+ # Ensure all data is flushed to disk before doing grub install
+ subprocess.call(["sync"])
+
+ stage2 = self._instroot + "/boot/grub/stage2"
+
setup = ""
- setup += "device (hd0) %s\n" % (self.__instloop.disk.device)
- setup += "root (hd0,%d)\n" % rootdevnum
- setup += "configfile (hd0,%d)%s/grub/grub.cfg" % (bootdevnum, prefix)
- setup += "setup --prefix=%s/grub (hd0) (hd0,%d)\n" % (prefix, bootdevnum)
+ for i in range(len(self.__disks)):
+ loopdev = self.__disks[i]['disk'].device
+ setup += "device (hd%d) %s\n" % (i, loopdev)
+ setup += "root (hd0,%d)\n" % bootdevnum
+ setup += "setup --stage2=%s --prefix=%s/grub (hd0)\n" % (stage2, prefix)
+ setup += "quit\n"
grub = subprocess.Popen(["grub", "--batch", "--no-floppy"],
stdin=subprocess.PIPE)
@@ -211,7 +228,58 @@ class DiskImageCreator(ImageCreator):
def _resparse(self, size = None):
return self.__instloop.resparse(size)
+
+ def _write_image_xml(self):
+ xml = "\n"
+ xml += " %s\n" % self.name
+ xml += " \n"
+ # XXX don't hardcode - determine based on the kernel we installed for grub
+ # baremetal vs xen
+ xml += " \n"
+ xml += " \n"
+ xml += " %s\n" % os.uname()[4]
+ xml += " \n"
+ xml += " \n"
+ xml += " \n"
+ xml += " \n"
+ for i in range(len(self.__disks)):
+ xml += " \n" % (self.__disks[i]['name'], self.__format, chr(ord('a')+i))
+ xml += " \n"
+ xml += " \n"
+ xml += " 1\n"
+ xml += " %d\n" %(256 * 1024)
+ xml += " \n"
+ xml += " \n"
+ xml += " \n"
+ xml += " \n"
+ xml += " \n"
+ for i in range(len(self.__disks)):
+ # XXX don't hardcode raw
+ xml += " \n" % (self.__disks[i]['name'], self.__format, self.__format)
+ xml += " \n"
+ xml += "\n"
+
+ print "Writing %s/%s.xml" % (self._outdir, self.name)
+ cfg = open("%s/%s.xml" % (self._outdir, self.name), "w")
+ cfg.write(xml)
+ cfg.close()
+
+
def _stage_final_image(self):
self._resparse()
- print "Copying image to %s " % (self._outdir + "/" + self.name + ".dsk",)
- shutil.move(self._disk, self._outdir + "/" + self.name + ".dsk")
+
+ print "Writing image XML"
+ self._write_image_xml()
+ print "Moving disks to final location"
+ for i in range(len(self.__disks)):
+ dst = "%s/%s.%s" % (self._outdir, self.__disks[i]['name'], self.__format)
+ if self.__format == "raw":
+ print "Moving tmp %s image to %s " % (self.__disks[i]['disk'].lofile, dst)
+ shutil.move(self.__disks[i]['disk'].lofile, dst)
+ else:
+ rc = subprocess.call(["qemu-img", "convert",
+ "-f", "raw", self.__disks[i]['disk'].lofile,
+ "-O", self.__format, dst])
+ if rc != 0:
+ raise CreatorError("Unable to convert disk to %s" % (self.__format))
+ print "All done"
diff --git a/imgcreate/fs.py b/imgcreate/fs.py
index 9ca3a3e..8d73102 100644
--- a/imgcreate/fs.py
+++ b/imgcreate/fs.py
@@ -86,42 +86,51 @@ class BindChrootMount:
subprocess.call(["/bin/umount", self.dest])
self.mounted = False
-class LoopbackMount:
- def __init__(self, lofile, mountdir, fstype = None):
- self.lofile = lofile
- self.mountdir = mountdir
- self.fstype = fstype
+class Disk:
+ def __init__(self, size, device = None):
+ self._device = device
+ self._size = size
- self.mounted = False
- self.losetup = False
- self.rmdir = False
- self.loopdev = None
+ def create(self):
+ pass
def cleanup(self):
- self.unmount()
- self.lounsetup()
+ pass
- def unmount(self):
- if self.mounted:
- rc = subprocess.call(["/bin/umount", self.mountdir])
- if rc == 0:
- self.mounted = False
+ def get_device(self):
+ return self._device
+ def set_device(self, path):
+ self._device = path
+ device = property(get_device, set_device)
- if self.rmdir and not self.mounted:
- try:
- os.rmdir(self.mountdir)
- except OSError, e:
- pass
- self.rmdir = False
+ def get_size(self):
+ return self._size
+ size = property(get_size)
+
+
+class RawDisk(Disk):
+ def __init__(self, size, device):
+ Disk.__init__(self, size, device)
+
+ def fixed(self):
+ return True
- def lounsetup(self):
- if self.losetup:
- rc = subprocess.call(["/sbin/losetup", "-d", self.loopdev])
- self.losetup = False
- self.loopdev = None
+ def exists(self):
+ return True
- def loopsetup(self):
- if self.losetup:
+class LoopbackDisk(Disk):
+ def __init__(self, lofile, size):
+ Disk.__init__(self, size)
+ self.lofile = lofile
+
+ def fixed(self):
+ return False
+
+ def exists(self):
+ return os.path.exists(self.lofile)
+
+ def create(self):
+ if self.device is not None:
return
losetupProc = subprocess.Popen(["/sbin/losetup", "-f"],
@@ -132,40 +141,27 @@ class LoopbackMount:
raise MountError("Failed to allocate loop device for '%s'" %
self.lofile)
- self.loopdev = losetupOutput.split()[0]
+ device = losetupOutput.split()[0]
- rc = subprocess.call(["/sbin/losetup", self.loopdev, self.lofile])
+ print "Losetup add %s %s" % (device, self.lofile)
+ rc = subprocess.call(["/sbin/losetup", device, self.lofile])
if rc != 0:
raise MountError("Failed to allocate loop device for '%s'" %
self.lofile)
+ self.device = device
- self.losetup = True
-
- def mount(self):
- if self.mounted:
+ def cleanup(self):
+ if self.device is None:
return
+ print "Losetup remove %s" % self.device
+ rc = subprocess.call(["/sbin/losetup", "-d", self.device])
+ self.device = None
- self.loopsetup()
-
- if not os.path.isdir(self.mountdir):
- os.makedirs(self.mountdir)
- self.rmdir = True
-
- args = [ "/bin/mount", self.loopdev, self.mountdir ]
- if self.fstype:
- args.extend(["-t", self.fstype])
-
- rc = subprocess.call(args)
- if rc != 0:
- raise MountError("Failed to mount '%s' to '%s'" %
- (self.loopdev, self.mountdir))
- self.mounted = True
-class SparseLoopbackMount(LoopbackMount):
- def __init__(self, lofile, mountdir, size, fstype = None):
- LoopbackMount.__init__(self, lofile, mountdir, fstype)
- self.size = size
+class SparseLoopbackDisk(LoopbackDisk):
+ def __init__(self, lofile, size):
+ LoopbackDisk.__init__(self, lofile, size)
def expand(self, create = False, size = None):
flags = os.O_WRONLY
@@ -191,10 +187,81 @@ class SparseLoopbackMount(LoopbackMount):
def create(self):
self.expand(create = True)
+ LoopbackDisk.create(self)
-class SparseExtLoopbackMount(SparseLoopbackMount):
- def __init__(self, lofile, mountdir, size, fstype, blocksize, fslabel):
- SparseLoopbackMount.__init__(self, lofile, mountdir, size, fstype)
+class Mount:
+ def __init__(self, mountdir):
+ self.mountdir = mountdir
+
+ def cleanup(self):
+ self.unmount()
+
+ def mount(self):
+ pass
+
+ def unmount(self):
+ pass
+
+class DiskMount(Mount):
+ def __init__(self, disk, mountdir, fstype = None, rmmountdir = True):
+ Mount.__init__(self, mountdir)
+
+ self.disk = disk
+ self.fstype = fstype
+ self.rmmountdir = rmmountdir
+
+ self.mounted = False
+ self.rmdir = False
+
+ def cleanup(self):
+ Mount.cleanup(self)
+ self.disk.cleanup()
+
+ def unmount(self):
+ if self.mounted:
+ rc = subprocess.call(["/bin/umount", self.mountdir])
+ if rc == 0:
+ self.mounted = False
+
+ if self.rmdir and not self.mounted:
+ try:
+ os.rmdir(self.mountdir)
+ except OSError, e:
+ pass
+ self.rmdir = False
+
+
+ def __create(self):
+ print "Disk create %s" % str(self)
+ self.disk.create()
+
+
+ def mount(self):
+ if self.mounted:
+ return
+
+ print "Disk mount"
+ if not os.path.isdir(self.mountdir):
+ os.makedirs(self.mountdir)
+ self.rmdir = self.rmmountdir
+
+ self.__create()
+
+ print "Do mount %s " % self.disk.device
+ args = [ "/bin/mount", self.disk.device, self.mountdir ]
+ if self.fstype:
+ args.extend(["-t", self.fstype])
+
+ rc = subprocess.call(args)
+ if rc != 0:
+ raise MountError("Failed to mount '%s' to '%s'" %
+ (self.disk.device, self.mountdir))
+
+ self.mounted = True
+
+class ExtDiskMount(DiskMount):
+ def __init__(self, disk, mountdir, fstype, blocksize, fslabel, rmmountdir=True):
+ DiskMount.__init__(self, disk, mountdir, fstype, rmmountdir)
self.blocksize = blocksize
self.fslabel = fslabel
@@ -202,19 +269,15 @@ class SparseExtLoopbackMount(SparseLoopbackMount):
rc = subprocess.call(["/sbin/mkfs." + self.fstype,
"-F", "-L", self.fslabel,
"-m", "1", "-b", str(self.blocksize),
- self.lofile,
- str(self.size / self.blocksize)])
+ self.disk.device])
+ # str(self.disk.size / self.blocksize)])
if rc != 0:
raise MountError("Error creating %s filesystem" % (self.fstype,))
subprocess.call(["/sbin/tune2fs", "-c0", "-i0", "-Odir_index",
- "-ouser_xattr,acl", self.lofile])
-
- def create(self):
- SparseLoopbackMount.create(self)
- self.__format_filesystem()
+ "-ouser_xattr,acl", self.disk.device])
- def resize(self, size = None):
- current_size = os.stat(self.lofile)[stat.ST_SIZE]
+ def __resize_filesystem(self, size = None):
+ current_size = os.stat(self.disk.lofile)[stat.ST_SIZE]
if size is None:
size = self.size
@@ -227,21 +290,30 @@ class SparseExtLoopbackMount(SparseLoopbackMount):
self.__fsck()
- resize2fs(self.lofile, size)
-
- if size < current_size:
- self.truncate(size)
+ resize2fs(self.disk.lofile, size)
return size
- def mount(self):
- if not os.path.isfile(self.lofile):
- self.create()
+ def __create(self):
+ resize = False
+ if not self.disk.fixed() and self.disk.exists():
+ resize = True
+
+ #DiskMount.__create(self)
+ self.disk.create()
+
+ if resize:
+ print "Fs resie"
+ self.__resize_filesystem()
else:
- self.resize()
- return SparseLoopbackMount.mount(self)
+ print "FS format"
+ self.__format_filesystem()
+
+ def mount(self):
+ self.__create()
+ DiskMount.mount(self)
def __fsck(self):
- subprocess.call(["/sbin/e2fsck", "-f", "-y", self.lofile])
+ subprocess.call(["/sbin/e2fsck", "-f", "-y", self.disk.lofile])
def __get_size_from_filesystem(self):
def parse_field(output, field):
@@ -253,7 +325,7 @@ class SparseExtLoopbackMount(SparseLoopbackMount):
dev_null = os.open("/dev/null", os.O_WRONLY)
try:
- out = subprocess.Popen(['/sbin/dumpe2fs', '-h', self.lofile],
+ out = subprocess.Popen(['/sbin/dumpe2fs', '-h', self.disk.lofile],
stdout = subprocess.PIPE,
stderr = dev_null).communicate()[0]
finally:
@@ -273,7 +345,7 @@ class SparseExtLoopbackMount(SparseLoopbackMount):
while top != (bot + 1):
t = bot + ((top - bot) / 2)
- if not resize2fs(self.lofile, t):
+ if not resize2fs(self.disk.lofile, t):
top = t
else:
bot = t
@@ -281,12 +353,243 @@ class SparseExtLoopbackMount(SparseLoopbackMount):
def resparse(self, size = None):
self.cleanup()
-
minsize = self.__resize_to_minimal()
+ self.disk.truncate(minsize)
+ return minsize
+
+class PartitionedMount(Mount):
+ def __init__(self, disks, devnames, mountdir):
+ Mount.__init__(self, mountdir)
+ self.disks = {}
+ for i in range(len(disks)):
+ d = disks[i]
+ devname = devnames[i]
+ print "Assigned disk %s" % devname
+ self.disks[devname] = { 'disk': d, # Disk object
+ 'mapped': False, # True if kpartx mapping exists
+ 'numpart': 0, # Number of allocate partitions
+ 'partitions': [], # indexes to self.partitions
+ 'extended': 0, # Size of extended partition
+ 'offset': 0 } # Offset of next partition
+ self.partitions = []
+ self.mapped = False
+ self.mountOrder = []
+ self.unmountOrder = []
+
+ def add_partition(self, size, disk, mountpoint, fstype = None):
+ self.partitions.append({'size': size,
+ 'mountpoint': mountpoint, # Mount relative to chroot
+ 'fstype': fstype,
+ 'disk': disk, # physical disk name holding partition
+ 'device': None, # kpartx device node for partition
+ 'mount': None, # Mount object
+ 'num': None}) # Partition number
+
+ def __format_disks(self):
+ print "Assigning disks"
+ for dev in self.disks.keys():
+ d = self.disks[dev]
+ print "Initializing partition table for %s" % (d['disk'].device)
+ rc = subprocess.call(["/sbin/parted", "-s", d['disk'].device, "mklabel", "msdos"])
+ if rc != 0:
+ raise MountError("Error writing partition table on %s" % d.device)
+
+ print "Assigning partitions"
+ for n in range(len(self.partitions)):
+ p = self.partitions[n]
+
+ if not self.disks.has_key(p['disk']):
+ raise MountError("No disk %s for partition %s" % (p['disk'], p['mountpoint']))
+
+ d = self.disks[p['disk']]
+ d['numpart'] += 1
+ if d['numpart'] > 3:
+ # Increase allocation of extended partition to hold this partition
+ d['extended'] += p['size']
+ p['type'] = 'logical'
+ p['num'] = d['numpart'] + 1
+ else:
+ p['type'] = 'primary'
+ p['num'] = d['numpart']
+
+ p['start'] = d['offset']
+ d['offset'] += p['size']
+ d['partitions'].append(n)
+ print "Assigned %s to %s%d at %d at size %d" % (p['mountpoint'], p['disk'], p['num'], p['start'], p['size'])
+
+ # XXX we should probably work in cylinder units to keep fdisk happier..
+ start = 0
+ print "Creating partitions"
+ for p in self.partitions:
+ d = self.disks[p['disk']]
+ if p['num'] == 5:
+ print "Added extended part at %d of size %d" % (p['start'], d['extended'])
+ rc = subprocess.call(["/sbin/parted", "-s", d['disk'].device, "mkpart", "extended",
+ "%dM" % p['start'], "%dM" % (p['start'] + d['extended'])])
+
+ print "Add %s part at %d of size %d" % (p['type'], p['start'], p['size'])
+ rc = subprocess.call(["/sbin/parted", "-s", d['disk'].device, "mkpart",
+ p['type'], "%dM" % p['start'], "%dM" % (p['start']+p['size'])])
+
+ # XXX disabled return code check because parted always fails to
+ # reload part table with loop devices. Annoying because we can't
+ # distinguish this failure from real partition failures :-(
+ if rc != 0 and 1 == 0:
+ raise MountError("Error creating partition on %s" % d['disk'].device)
+
+ def __map_partitions(self):
+ for dev in self.disks.keys():
+ d = self.disks[dev]
+ if d['mapped']:
+ continue
+
+ kpartx = subprocess.Popen(["/sbin/kpartx", "-l", d['disk'].device],
+ stdout=subprocess.PIPE)
+
+ kpartxOutput = kpartx.communicate()[0].split("\n")
+ # Strip trailing blank
+ kpartxOutput = kpartxOutput[0:len(kpartxOutput)-1]
+ print "Partx planned mapping %s" % str(kpartxOutput)
+ if kpartx.returncode:
+ raise MountError("Failed to query partition mapping for '%s'" %
+ d.device)
+
+ # Quick sanity check that the number of partitions matches
+ # our expectation. If it doesn't, someone broke the code
+ # further up
+ if len(kpartxOutput) != d['numpart']:
+ raise MountError("Unexpected number of partitions from kpartx: %d != %d" %
+ (len(kpartxOutput), d['numpart']))
+
+ for i in range(len(kpartxOutput)):
+ print " Got %s" % (kpartxOutput[i])
+ line = kpartxOutput[i]
+ newdev = line.split()[0]
+ mapperdev = "/dev/mapper/" + newdev
+ loopdev = d['disk'].device + newdev[-1]
+
+ print "Dev %s: %s -> %s" % (newdev, loopdev, mapperdev)
+ pnum = d['partitions'][i]
+ self.partitions[pnum]['device'] = loopdev
+
+ # grub's install wants partitions to be named
+ # to match their parent device + partition num
+ # kpartx doesn't work like this, so we add compat
+ # symlinks to point to /dev/mapper
+ os.symlink(mapperdev, loopdev)
+
+ print "Adding partx mapping for %s" % d['disk'].device
+ rc = subprocess.call(["/sbin/kpartx", "-a", d['disk'].device])
+ if rc != 0:
+ raise MountError("Failed to map partitions for '%s'" %
+ d['disk'].device)
+ d['mapped'] = True
+
+
+ def __unmap_partitions(self):
+ for dev in self.disks.keys():
+ d = self.disks[dev]
+ if not d['mapped']:
+ continue
+
+ print "Removing compat symlinks"
+ for pnum in d['partitions']:
+ if self.partitions[pnum]['device'] != None:
+ os.unlink(self.partitions[pnum]['device'])
+ self.partitions[pnum]['device'] = None
+
+ print "Unmapping"
+ rc = subprocess.call(["/sbin/kpartx", "-d", d['disk'].device])
+ if rc != 0:
+ raise MountError("Failed to unmap partitions for '%s'" %
+ d['disk'].device)
+
+ d['mapped'] = False
+
+
+ def __calculate_mountorder(self):
+ for p in self.partitions:
+ self.mountOrder.append(p['mountpoint'])
+ self.unmountOrder.append(p['mountpoint'])
+
+ self.mountOrder.sort()
+ self.unmountOrder.sort()
+ self.unmountOrder.reverse()
+ print str(self.mountOrder)
- self.truncate(minsize)
+ def cleanup(self):
+ print "Doing cleanup"
+ import time
+ #time.sleep(20)
+ Mount.cleanup(self)
+ print "Unmap parts"
+ self.__unmap_partitions()
+ print "Cleanup disks"
+ for dev in self.disks.keys():
+ d = self.disks[dev]
+ try:
+ print "Cleanup disk %s -> %s" % (d['disk'].lofile, d['disk'].device)
+ d['disk'].cleanup()
+ except:
+ pass
+
+ def unmount(self):
+ print "Do unmount"
+ for mp in self.unmountOrder:
+ if mp == 'swap':
+ continue
+ p = None
+ for p1 in self.partitions:
+ if p1['mountpoint'] == mp:
+ p = p1
+ break
+
+ if p['mount'] != None:
+ print "Clenaup %s " % p['mountpoint']
+ try:
+ p['mount'].cleanup()
+ except:
+ pass
+ p['mount'] = None
+ print "Done unmount"
+
+ def mount(self):
+ for dev in self.disks.keys():
+ d = self.disks[dev]
+ d['disk'].create()
+
+ self.__format_disks()
+ self.__map_partitions()
+ self.__calculate_mountorder()
+
+ for mp in self.mountOrder:
+ p = None
+ for p1 in self.partitions:
+ if p1['mountpoint'] == mp:
+ p = p1
+ break
+
+ if mp == 'swap':
+ subprocess.call(["/sbin/mkswap", p['device']])
+ continue
+
+ print "Submount %s " % p['mountpoint']
+ rmmountdir = False
+ if p['mountpoint'] == "/":
+ rmmountdir = True
+ pdisk = ExtDiskMount(RawDisk(p['size'] * 1024 * 1024, p['device']),
+ self.mountdir + p['mountpoint'],
+ p['fstype'],
+ 4096,
+ p['mountpoint'],
+ rmmountdir)
+ pdisk.mount()
+ p['mount'] = pdisk
+
+ def resparse(self, size = None):
+ # Can't re-sparse a disk image - too hard
+ pass
- return self.resize(size)
class DeviceMapperSnapshot(object):
def __init__(self, imgloop, cowloop):
@@ -306,8 +609,8 @@ class DeviceMapperSnapshot(object):
if self.__created:
return
- self.imgloop.loopsetup()
- self.cowloop.loopsetup()
+ self.imgloop.create()
+ self.cowloop.create()
self.__name = "imgcreate-%d-%d" % (os.getpid(),
random.randint(0, 2**16))
@@ -315,8 +618,8 @@ class DeviceMapperSnapshot(object):
size = os.stat(self.imgloop.lofile)[stat.ST_SIZE]
table = "0 %d snapshot %s %s p 8" % (size / 512,
- self.imgloop.loopdev,
- self.cowloop.loopdev)
+ self.imgloop.device,
+ self.cowloop.device)
args = ["/sbin/dmsetup", "create", self.__name, "--table", table]
if subprocess.call(args) != 0:
@@ -382,15 +685,14 @@ class DeviceMapperSnapshot(object):
# 8) Create a squashfs of the COW
#
def create_image_minimizer(path, image, minimal_size):
- imgloop = LoopbackMount(image, "None")
+ imgloop = LoopbackDisk(image, None) # Passing bogus size - doesn't matter
- cowloop = SparseLoopbackMount(os.path.join(os.path.dirname(path), "osmin"),
- None, 64L * 1024L * 1024L)
+ cowloop = SparseLoopbackDisk(os.path.join(os.path.dirname(path), "osmin"),
+ 64L * 1024L * 1024L)
snapshot = DeviceMapperSnapshot(imgloop, cowloop)
try:
- cowloop.create()
snapshot.create()
resize2fs(snapshot.path, minimal_size)
diff --git a/imgcreate/kickstart.py b/imgcreate/kickstart.py
index a7e0723..a92f877 100644
--- a/imgcreate/kickstart.py
+++ b/imgcreate/kickstart.py
@@ -468,6 +468,9 @@ def get_groups(ks, required = []):
def get_excluded(ks, required = []):
return ks.handler.packages.excludedList + required
+def get_partitions(ks, required = []):
+ return ks.handler.partition.partitions
+
def ignore_missing(ks):
return ks.handler.packages.handleMissing == ksconstants.KS_MISSING_IGNORE
diff --git a/imgcreate/live.py b/imgcreate/live.py
index bbb17ef..dc2aea9 100644
--- a/imgcreate/live.py
+++ b/imgcreate/live.py
@@ -130,7 +130,7 @@ class LiveImageCreatorBase(LoopImageCreator):
#
def __base_on_iso(self, base_on):
"""helper function to extract ext3 file system from a live CD ISO"""
- isoloop = LoopbackMount(base_on, self._mkdtemp())
+ isoloop = Mount(LoopbackDisk(base_on), self._mkdtemp())
try:
isoloop.mount()
@@ -144,10 +144,10 @@ class LiveImageCreatorBase(LoopImageCreator):
else:
squashimg = isoloop.mountdir + "/LiveOS/squashfs.img"
- squashloop = LoopbackMount(squashimg, self._mkdtemp(), "squashfs")
+ squashloop = Mount(LoopbackDisk(squashimg), self._mkdtemp(), "squashfs")
try:
- if not os.path.exists(squashloop.lofile):
+ if not squashloop.disk.exists():
raise CreatorError("'%s' is not a valid live CD ISO : "
"squashfs.img doesn't exist" % base_on)
diff --git a/livecd-tools.spec b/livecd-tools.spec
index a789460..d9118ba 100644
--- a/livecd-tools.spec
+++ b/livecd-tools.spec
@@ -58,6 +58,7 @@ rm -rf $RPM_BUILD_ROOT
%dir %{_datadir}/livecd-tools
%{_datadir}/livecd-tools/*
%{_bindir}/image-creator
+%{_bindir}/disk-creator
%dir %{python_sitelib}/imgcreate
%{python_sitelib}/imgcreate/*.py
%{python_sitelib}/imgcreate/*.pyo
diff --git a/tools/disk-creator b/tools/disk-creator
index 52584f6..acd6bd7 100755
--- a/tools/disk-creator
+++ b/tools/disk-creator
@@ -25,10 +25,16 @@ import optparse
import imgcreate
def parse_options(args):
- parser = optparse.OptionParser(usage = "%prog [--name=] ")
+ parser = optparse.OptionParser(usage = "%prog [--disk=] [--size=] ")
parser.add_option("-n", "--name", type="string", dest="name",
- help="Disk name")
+ help="Appliance name")
+ parser.add_option("-d", "--disk", type="string", dest="disk",
+ action="append", help="Disk file name")
+ parser.add_option("-s", "--size", type="float", dest="size",
+ action="append", help="Disk size in MB")
+ parser.add_option("-f", "--format", type="string", dest="format",
+ help="Disk format (raw, qcow2, vmdk, ...)")
(options, args) = parser.parse_args()
@@ -51,12 +57,45 @@ def main():
print >> sys.stderr, "Error loading kickstart file '%s' : %s" % (kscfg, e)
return 1
+ disks = []
+
+ print str(options.disk)
+ print str(options.size)
+ if options.disk is None:
+ name = imgcreate.build_name(kscfg)
+ size = 4096L * 1024L * 1024L
+ if options.size is not None:
+ if type(options.size) != float:
+ print >> sys.stderr, "Error too many disk sizes provided"
+ return 1
+ size = options.size * 1024L * 1024L
+
+ disks.append({ 'name': name, 'size': size })
+ elif type(options.disk) == list:
+ if type(options.size) != list or len(options.size) != len(options.disk):
+ print >> sys.stderr, "Error must provide a size for each disk"
+ return 1
+
+ for i in range(len(options.disk)):
+ disks.append({ 'name': options.disk[i], 'size': options.size[i] * 1024L * 1024L })
+ else:
+ size = 4096L * 1024L * 1024L
+ if options.size is not None:
+ if type(options.size) != float:
+ print >> sys.stderr, "Error too many disk sizes provided"
+ return 1
+ size = options.size * 1024L * 1024L
+
+ disks.append({ 'name': options.disk, 'size': size })
+
+ name = imgcreate.build_name(kscfg)
if options.name:
name = options.name
- else:
- name = imgcreate.build_name(kscfg)
- creator = imgcreate.DiskImageCreator(ks, name)
+ format = "raw"
+ if options.format:
+ format = options.format
+ creator = imgcreate.DiskImageCreator(ks, name, disks, format)
try:
creator.create()