[Fedora-livecd-list] [PATCH] re-factor the imgcreate/fs.py module
David Huff
dhuff at redhat.com
Thu Jul 10 21:26:55 UTC 2008
From: Daniel P. Berrange <berrange redhat com>
---
imgcreate/fs.py | 328 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 files changed, 320 insertions(+), 8 deletions(-)
diff --git a/imgcreate/fs.py b/imgcreate/fs.py
index 98c0db4..5d94018 100644
--- a/imgcreate/fs.py
+++ b/imgcreate/fs.py
@@ -24,6 +24,7 @@ import stat
import subprocess
import random
import string
+import logging
from imgcreate.errors import *
@@ -86,6 +87,9 @@ class BindChrootMount:
subprocess.call(["/bin/umount", self.dest])
self.mounted = False
+#===============================================================================
+# LoopbackMount compatibility layer for old API
+#===============================================================================
class LoopbackMount:
def __init__(self, lofile, mountdir, fstype = None):
self.lofile = lofile
@@ -162,6 +166,9 @@ class LoopbackMount:
self.mounted = True
+#===============================================================================
+# SparseLoopbackMount compatibility layer for old API
+#===============================================================================
class SparseLoopbackMount(LoopbackMount):
def __init__(self, lofile, mountdir, size, fstype = None):
LoopbackMount.__init__(self, lofile, mountdir, fstype)
@@ -192,6 +199,9 @@ class SparseLoopbackMount(LoopbackMount):
def create(self):
self.expand(create = True)
+#===============================================================================
+# SparseExtLoopbackMount compatibility layer for old API
+#===============================================================================
class SparseExtLoopbackMount(SparseLoopbackMount):
def __init__(self, lofile, mountdir, size, fstype, blocksize, fslabel):
SparseLoopbackMount.__init__(self, lofile, mountdir, size, fstype)
@@ -288,6 +298,308 @@ class SparseExtLoopbackMount(SparseLoopbackMount):
self.resize(size)
return minsize
+#===============================================================================
+# Disk
+#Most of the change here involves re-factoring the imgcreate/fs.py module.
+#The SparseExtLoopbackMount, SparseLoopbackMount, LoopbackMount classes all
+#have the built-in limitation that the image being produced corresponds to
+#a single filesystem / loop mount. With the disk creator, the image being
+#produced can be partitioned into multiple chunks, with many filesystems.
+#Furthermore, not all of them require loopback mounts, as the partitions
+#themselves are already visible via /dev/mapper/
+#
+#So this patch separates the roles. There are now classes which deal with
+#accessing / creating disks:
+#
+# Disk - generic base for disks
+# RawDisk - a disk backed by a block device
+# LoopbackDisk - a disk backed by a file
+# SparseLoopbackDisk - a disk backed by a sparse file
+#
+#The 'create' method must make the disk visible as a block device - eg
+#by calling losetup. For RawDisk, this is obviously a no-op. The 'cleanup'
+#method must undo the 'create' operation.
+#
+#There are then classes which deal with mounting things:
+#
+# Mount - generic base for mounts
+# DiskMount - able to mount a Disk object
+# ExtDiskMount - able to format/resize ext3 filesystems when mounting
+#===============================================================================
+class Disk:
+ def __init__(self, size, device = None):
+ self._device = device
+ self._size = size
+
+ def create(self):
+ pass
+
+ def cleanup(self):
+ pass
+
+ def get_device(self):
+ return self._device
+ def set_device(self, path):
+ self._device = path
+ device = property(get_device, set_device)
+
+ 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 exists(self):
+ return True
+
+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"],
+ stdout=subprocess.PIPE)
+ losetupOutput = losetupProc.communicate()[0]
+
+ if losetupProc.returncode:
+ raise MountError("Failed to allocate loop device for '%s'" %
+ self.lofile)
+
+ device = losetupOutput.split()[0]
+
+ logging.debug("Losetup add %s mapping to %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
+
+ def cleanup(self):
+ if self.device is None:
+ return
+ logging.debug("Losetup remove %s" % self.device)
+ rc = subprocess.call(["/sbin/losetup", "-d", self.device])
+ self.device = None
+
+
+
+class SparseLoopbackDisk(LoopbackDisk):
+ def __init__(self, lofile, size):
+ LoopbackDisk.__init__(self, lofile, size)
+
+ def expand(self, create = False, size = None):
+ flags = os.O_WRONLY
+ if create:
+ flags |= os.O_CREAT
+ makedirs(os.path.dirname(self.lofile))
+
+ if size is None:
+ size = self.size
+
+ logging.debug("Extending sparse file %s to %d" % (self.lofile, size))
+ fd = os.open(self.lofile, flags)
+
+ os.lseek(fd, size, 0)
+ os.write(fd, '\x00')
+ os.close(fd)
+
+ def truncate(self, size = None):
+ if size is None:
+ size = self.size
+
+ logging.debug("Truncating sparse file %s to %d" % (self.lofile, size))
+ fd = os.open(self.lofile, os.O_WRONLY)
+ os.ftruncate(fd, size)
+ os.close(fd)
+
+ def create(self):
+ self.expand(create = True)
+ LoopbackDisk.create(self)
+
+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:
+ logging.debug("Unmounting directory %s" % self.mountdir)
+ 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):
+ self.disk.create()
+
+
+ def mount(self):
+ if self.mounted:
+ return
+
+ if not os.path.isdir(self.mountdir):
+ logging.debug("Creating mount point %s" % self.mountdir)
+ os.makedirs(self.mountdir)
+ self.rmdir = self.rmmountdir
+
+ self.__create()
+
+ logging.debug("Mounting %s at %s" % (self.disk.device, self.mountdir))
+ 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
+
+ def __format_filesystem(self):
+ logging.debug("Formating %s filesystem on %s" % (self.fstype, self.disk.device))
+ rc = subprocess.call(["/sbin/mkfs." + self.fstype,
+ "-F", "-L", self.fslabel,
+ "-m", "1", "-b", str(self.blocksize),
+ self.disk.device])
+ # str(self.disk.size / self.blocksize)])
+ if rc != 0:
+ raise MountError("Error creating %s filesystem" % (self.fstype,))
+ logging.debug("Tuning filesystem on %s" % self.disk.device)
+ subprocess.call(["/sbin/tune2fs", "-c0", "-i0", "-Odir_index",
+ "-ouser_xattr,acl", self.disk.device])
+
+ def __resize_filesystem(self, size = None):
+ current_size = os.stat(self.disk.lofile)[stat.ST_SIZE]
+
+ if size is None:
+ size = self.size
+
+ if size == current_size:
+ return
+
+ if size > current_size:
+ self.expand(size)
+
+ self.__fsck()
+
+ resize2fs(self.disk.lofile, size)
+ return size
+
+ def __create(self):
+ resize = False
+ if not self.disk.fixed() and self.disk.exists():
+ resize = True
+
+ self.disk.create()
+
+ if resize:
+ self.__resize_filesystem()
+ else:
+ self.__format_filesystem()
+
+ def mount(self):
+ self.__create()
+ DiskMount.mount(self)
+
+ def __fsck(self):
+ logging.debug("Checking filesystem %s" % self.disk.lofile)
+ subprocess.call(["/sbin/e2fsck", "-f", "-y", self.disk.lofile])
+
+ def __get_size_from_filesystem(self):
+ def parse_field(output, field):
+ for line in output.split("\n"):
+ if line.startswith(field + ":"):
+ return line[len(field) + 1:].strip()
+
+ raise KeyError("Failed to find field '%s' in output" % field)
+
+ dev_null = os.open("/dev/null", os.O_WRONLY)
+ try:
+ out = subprocess.Popen(['/sbin/dumpe2fs', '-h', self.disk.lofile],
+ stdout = subprocess.PIPE,
+ stderr = dev_null).communicate()[0]
+ finally:
+ os.close(dev_null)
+
+ return int(parse_field(out, "Block count")) * self.blocksize
+
+ def __resize_to_minimal(self):
+ self.__fsck()
+
+ #
+ # Use a binary search to find the minimal size
+ # we can resize the image to
+ #
+ bot = 0
+ top = self.__get_size_from_filesystem()
+ while top != (bot + 1):
+ t = bot + ((top - bot) / 2)
+
+ if not resize2fs(self.disk.lofile, t):
+ top = t
+ else:
+ bot = t
+ return top
+
+ def resparse(self, size = None):
+ self.cleanup()
+ minsize = self.__resize_to_minimal()
+ self.disk.truncate(minsize)
+ return minsize
+
class DeviceMapperSnapshot(object):
def __init__(self, imgloop, cowloop):
self.imgloop = imgloop
@@ -306,8 +618,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 +627,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 +694,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)
@@ -404,3 +715,4 @@ def create_image_minimizer(path, image, minimal_size):
mksquashfs(cowloop.lofile, path)
os.unlink(cowloop.lofile)
+
--
1.5.4.1
More information about the Fedora-livecd-list
mailing list