Patch 1a of 2 substitute patches to replace an earlier patch with new
features:

commit 7389913c708d3f19ca029cf3f150bbfb08a768d3
Author: Frederick Grose <fgrose@sugarlabs.org>
Date:   Sat Sep 17 17:05:17 2011 -0400

    Enable mount options, particularly 'read-only', for greater safety.
    
    Provide parameters for mounting options, and code to recognize and
    implement various 'read-only' flag possibilities.

diff --git a/imgcreate/fs.py b/imgcreate/fs.py
index 69fb5f1..e71032e 100644
--- a/imgcreate/fs.py
+++ b/imgcreate/fs.py
@@ -122,9 +122,10 @@ def e2fsck(fs):
 
 class BindChrootMount:
     """Represents a bind mount of a directory into a chroot."""
-    def __init__(self, src, chroot, dest = None):
+    def __init__(self, src, chroot, dest=None, ops=None):
         self.src = src
         self.root = chroot
+        self.ops = ops
 
         if not dest:
             dest = src
@@ -132,17 +133,35 @@ class BindChrootMount:
 
         self.mounted = False
 
-    def mount(self):
+    def mount(self, ops=None):
         if self.mounted:
             return
 
         makedirs(self.dest)
-        rc = call(["/bin/mount", "--bind", self.src, self.dest])
+        args = ['/bin/mount', '--bind', self.src, self.dest]
+        rc = call(args)
         if rc != 0:
             raise MountError("Bind-mounting '%s' to '%s' failed" %
                              (self.src, self.dest))
+        if ops is None:
+            ops = self.ops
+        if ops in (['-o', 'remount,ro'], 'remount,ro,bind', 'remount,ro',
+                   ['-o', 'ro'], ['ro'], ['-r'], 'ro', '-r'):
+            self.remount('ro')
+
         self.mounted = True
 
+    def remount(self, ops):
+        if not self.mounted:
+            return
+
+        remount_ops = ''.join(('remount,', ops))
+        args = ['/bin/mount', '-o', remount_ops, self.dest]
+        rc = call(args)
+        if rc != 0:
+            raise MountError("%s of '%s' to '%s' failed." %
+                             (remount_ops, self.src, self.dest))
+
     def unmount(self):
         if not self.mounted:
             return
@@ -161,9 +180,11 @@ class BindChrootMount:
 
 class LoopbackMount:
     """LoopbackMount  compatibility layer for old API"""
-    def __init__(self, lofile, mountdir, fstype = None):
-        self.diskmount = DiskMount(LoopbackDisk(lofile,size = 0),mountdir,fstype,rmmountdir = True)
+    def __init__(self, lofile, mountdir, fstype=None, ops=None):
+        self.diskmount = DiskMount(LoopbackDisk(lofile, size=0), mountdir,
+                                   fstype, rmmountdir=True, ops=None)
         self.losetup = False
+        self.ops = ops
         
     def cleanup(self):
         self.diskmount.cleanup()
@@ -177,7 +198,7 @@ class LoopbackMount:
             self.losetup = False
             self.loopdev = None
 
-    def loopsetup(self):
+    def loopsetup(self, ops=None):
         if self.losetup:
             return
 
@@ -191,15 +212,22 @@ class LoopbackMount:
 
         self.loopdev = losetupOutput.split()[0]
 
-        rc = call(["/sbin/losetup", self.loopdev, self.lofile])
+        args = ['/sbin/losetup', self.loopdev, self.lofile]
+        if ops is None:
+            ops = self.ops
+        if ops in (['-o', 'ro'], ['ro'], ['-r'], 'ro', '-r'):
+            ops = ['-r']
+            # This is the only additional option supported, so far.
+            args += ops
+        rc = call(args)
         if rc != 0:
             raise MountError("Failed to allocate loop device for '%s'" %
                              self.lofile)
 
         self.losetup = True
 
-    def mount(self):
-        self.diskmount.mount()
+    def mount(self, ops=None):
+        self.diskmount.mount(ops)
 
 class SparseLoopbackMount(LoopbackMount):
     """SparseLoopbackMount  compatibility layer for old API"""
@@ -290,9 +318,10 @@ class RawDisk(Disk):
 
 class LoopbackDisk(Disk):
     """A Disk backed by a file via the loop module."""
-    def __init__(self, lofile, size):
+    def __init__(self, lofile, size, ops=None):
         Disk.__init__(self, size)
         self.lofile = lofile
+        self.ops = ops
 
     def fixed(self):
         return False
@@ -300,7 +329,7 @@ class LoopbackDisk(Disk):
     def exists(self):
         return os.path.exists(self.lofile)
 
-    def create(self):
+    def create(self, ops=None):
         if self.device is not None:
             return
 
@@ -313,9 +342,16 @@ class LoopbackDisk(Disk):
                              self.lofile)
 
         device = losetupOutput.split()[0]
+        args = ['/sbin/losetup', device, self.lofile]
+        if ops is None:
+            ops = self.ops
+        if ops in (['-o', 'ro'], ['ro'], ['-r'], 'ro', '-r'):
+            ops = ['-r']
+            # This is the only additional option supported, so far.
+            args += ops
 
         logging.info("Losetup add %s mapping to %s"  % (device, self.lofile))
-        rc = call(["/sbin/losetup", device, self.lofile])
+        rc = call(args)
         if rc != 0:
             raise MountError("Failed to allocate loop device for '%s'" %
                              self.lofile)
@@ -329,11 +365,10 @@ class LoopbackDisk(Disk):
         self.device = None
 
 
-
 class SparseLoopbackDisk(LoopbackDisk):
     """A Disk backed by a sparse file via the loop module."""
-    def __init__(self, lofile, size):
-        LoopbackDisk.__init__(self, lofile, size)
+    def __init__(self, lofile, size, ops=None):
+        LoopbackDisk.__init__(self, lofile, size, ops=None)
 
     def expand(self, create = False, size = None):
         flags = os.O_WRONLY
@@ -362,9 +397,9 @@ class SparseLoopbackDisk(LoopbackDisk):
         os.ftruncate(fd, size)
         os.close(fd)
 
-    def create(self):
+    def create(self, ops=None):
         self.expand(create = True)
-        LoopbackDisk.create(self)
+        LoopbackDisk.create(self, ops=None)
 
 class Mount:
     """A generic base class to deal with mounting things."""
@@ -382,15 +417,17 @@ class Mount:
 
 class DiskMount(Mount):
     """A Mount object that handles mounting of a Disk."""
-    def __init__(self, disk, mountdir, fstype = None, rmmountdir = True):
+    def __init__(self, disk, mountdir, fstype=None, rmmountdir=True, ops=None):
         Mount.__init__(self, mountdir)
 
         self.disk = disk
         self.fstype = fstype
+        self.ops = ops
         self.rmmountdir = rmmountdir
 
         self.mounted = False
         self.rmdir   = False
+        self.created = False
 
     def cleanup(self):
         Mount.cleanup(self)
@@ -420,12 +457,12 @@ class DiskMount(Mount):
                 pass
             self.rmdir = False
 
-
     def __create(self):
-        self.disk.create()
-
+        if not self.created:
+            self.disk.create()
+            self.created = True
 
-    def mount(self):
+    def mount(self, ops=None):
         if self.mounted:
             return
 
@@ -440,6 +477,10 @@ class DiskMount(Mount):
         args = [ "/bin/mount", self.disk.device, self.mountdir ]
         if self.fstype:
             args.extend(["-t", self.fstype])
+        if ops is None:
+            ops = self.ops
+        if ops is not None:
+            args.extend(['-o', ops])
 
         rc = call(args)
         if rc != 0:
@@ -448,6 +489,17 @@ class DiskMount(Mount):
 
         self.mounted = True
 
+    def remount(self, ops):
+        if not self.mounted:
+            return
+
+        remount_ops = ''.join(('remount,', ops))
+        args = ['/bin/mount', '-o', remount_ops, self.mountdir]
+        rc = call(args)
+        if rc != 0:
+            raise MountError("%s of '%s' to '%s' failed." %
+                             (remount_ops, self.disk.device, self.mountdir))
+
 class ExtDiskMount(DiskMount):
     """A DiskMount object that is able to format/resize ext[23] filesystems."""
     def __init__(self, disk, mountdir, fstype, blocksize, fslabel,
@@ -456,6 +508,7 @@ class ExtDiskMount(DiskMount):
         self.blocksize = blocksize
         self.fslabel = "_" + fslabel
         self.tmpdir = tmpdir
+        self.created = False
 
     def __format_filesystem(self):
         logging.info("Formating %s filesystem on %s" % (self.fstype, self.disk.device))
@@ -487,6 +540,8 @@ class ExtDiskMount(DiskMount):
         return size
 
     def __create(self):
+        if self.created:
+            return
         resize = False
         if not self.disk.fixed() and self.disk.exists():
             resize = True
@@ -498,6 +553,8 @@ class ExtDiskMount(DiskMount):
         else:
             self.__format_filesystem()
 
+        self.created = True
+
     def mount(self):
         self.__create()
         DiskMount.mount(self)
@@ -535,10 +592,12 @@ class ExtDiskMount(DiskMount):
         self.__resize_filesystem(size)
         return minsize
 
+
 class DeviceMapperSnapshot(object):
-    def __init__(self, imgloop, cowloop):
+    def __init__(self, imgloop, cowloop, ops=None):
         self.imgloop = imgloop
         self.cowloop = cowloop
+        self.ops = ops
 
         self.__created = False
         self.__name = None
@@ -546,15 +605,16 @@ class DeviceMapperSnapshot(object):
     def get_path(self):
         if self.__name is None:
             return None
+        return self.device
         return os.path.join("/dev/mapper", self.__name)
     path = property(get_path)
 
-    def create(self):
+    def create(self, ops=None):
         if self.__created:
             return
 
-        self.imgloop.create()
-        self.cowloop.create()
+        self.imgloop.create(self.ops)
+        self.cowloop.create(self.ops)
 
         self.__name = "imgcreate-%d-%d" % (os.getpid(),
                                            random.randint(0, 2**16))
@@ -567,13 +627,22 @@ class DeviceMapperSnapshot(object):
 
         args = ["/sbin/dmsetup", "create", self.__name,
                 "--uuid", "LIVECD-%s" % self.__name, "--table", table]
+        if ops is None:
+            ops = self.ops
+        if ops in (['-o', 'ro'], ['ro'], ['-r'], 'ro', '-r',
+                   ['--readonly'], '--readonly'):
+            ops = ['--readonly']
+            # This is the only additional option supported, so far.
+            args += ops
         if call(args) != 0:
+            time.sleep(1)
             self.cowloop.cleanup()
             self.imgloop.cleanup()
             raise SnapshotError("Could not create snapshot device using: " +
                                 string.join(args, " "))
 
         self.__created = True
+        self.device = os.path.join('/dev/mapper', self.__name)
 
     def remove(self, ignore_errors = False):
         if not self.__created:




On Thu, Apr 14, 2011 at 12:02 PM, Frederick Grose <fgrose@gmail.com> wrote:
[Patch 1/5]
Author: Frederick Grose <fgrose@sugarlabs.org>
Date:   Thu Apr 14 09:29:14 2011 -0400

   Support Live image mounting.
   
   Provide DiskMount with mount options, DeviceMapperSnapshot with a
   device attribute, and a new LiveImageMount class.

diff --git a/imgcreate/fs.py b/imgcreate/fs.py
index d5307a2..0871182 100644
--- a/imgcreate/fs.py
+++ b/imgcreate/fs.py
@@ -324,7 +324,6 @@ class LoopbackDisk(Disk):
        self.device = None


-
class SparseLoopbackDisk(LoopbackDisk):
    """A Disk backed by a sparse file via the loop module."""
    def __init__(self, lofile, size):
@@ -377,11 +376,12 @@ class Mount:

class DiskMount(Mount):
    """A Mount object that handles mounting of a Disk."""
-    def __init__(self, disk, mountdir, fstype = None, rmmountdir = True):
+    def __init__(self, disk, mountdir, fstype=None, rmmountdir=True, ops=None):
        Mount.__init__(self, mountdir)

        self.disk = disk
        self.fstype = fstype
+        self.ops = ops
        self.rmmountdir = rmmountdir

        self.mounted = False
@@ -435,6 +435,8 @@ class DiskMount(Mount):
        args = [ "/bin/mount", self.disk.device, self.mountdir ]
        if self.fstype:
            args.extend(["-t", self.fstype])
+        if self.ops:
+            args.extend(['-o', self.ops])

        rc = call(args)
        if rc != 0:
@@ -530,6 +532,7 @@ class ExtDiskMount(DiskMount):
        self.__resize_filesystem(size)
        return minsize

+
class DeviceMapperSnapshot(object):
    def __init__(self, imgloop, cowloop):
        self.imgloop = imgloop
@@ -541,6 +544,7 @@ class DeviceMapperSnapshot(object):
    def get_path(self):
        if self.__name is None:
            return None
+        return self.device
        return os.path.join("/dev/mapper", self.__name)
    path = property(get_path)

@@ -569,6 +573,7 @@ class DeviceMapperSnapshot(object):
                                string.join(args, " "))

        self.__created = True
+        self.device = os.path.join('/dev/mapper', self.__name)

    def remove(self, ignore_errors = False):
        if not self.__created:
@@ -610,6 +615,89 @@ class DeviceMapperSnapshot(object):
        except ValueError:
            raise SnapshotError("Failed to parse dmsetup status: " + out)

+
+class LiveImageMount(object):
+    """A class for mounting a LiveOS image installed with an active overlay."""
+
+    def __init__(self, disk, mountdir, overlay, tmpdir='/tmp'):
+        self.disk = disk
+        self.mountdir = mountdir
+        self.overlay = overlay
+        self.tmpdir = tmpdir
+        self.__created = False
+        self.squashmnt = None
+        self.homemnt = None
+        self.mntlive = None
+
+    def __create(self):
+        if self.__created:
+            return
+        self.liveosdevmnt = DiskMount(self.disk,
+                                   os.path.join(self.tmpdir, 'device'))
+        self.liveosdevmnt.mount()
+        liveosdir = os.path.join(self.liveosdevmnt.mountdir, 'LiveOS')
+        sqfs_img = os.path.join(liveosdir, 'squashfs.img')
+        if os.path.exists(sqfs_img):
+            self.squashloop = LoopbackDisk(sqfs_img, None)
+            self.squashmnt = DiskMount(self.squashloop,
+                                       os.path.join(self.tmpdir, 'squash'))
+            self.squashmnt.mount()
+            rootfs_img = os.path.join(self.squashmnt.mountdir,
+                                      'LiveOS', 'ext3fs.img')
+        else:
+            rootfs_img = os.path.join(liveosdir, 'ext3fs.img')
+            if not os.path.exists(rootfs_img):
+                raise SnapshotError("Failed to find a LiveOS root image.")
+        self.imgloop = LoopbackDisk(rootfs_img, None)
+        self.overlay = os.path.join(liveosdir, self.overlay)
+        self.cowloop = LoopbackDisk(self.overlay, None)
+        home_img = os.path.join(liveosdir, 'home.img')
+        self.dm_liveimage = DeviceMapperSnapshot(self.imgloop, self.cowloop)
+        self.dm_livemount = DiskMount(self.dm_liveimage, self.mountdir)
+        if os.path.exists(home_img):
+            homedir = os.path.join(self.mountdir, 'home')
+            self.homemnt = LoopbackMount(home_img, homedir)
+        self.__created = True
+
+    def mount(self):
+        try:
+            self.__create()
+            if not self.liveosdevmnt.mounted:
+                self.liveosdevmnt.mount()
+            if not self.squashmnt.mounted:
+                self.squashmnt.mount()
+            self.dm_livemount.mount()
+            if self.homemnt:
+                self.homemnt.mount()
+            mntlivedir = os.path.join(self.mountdir, 'mnt', 'live')
+            if not os.path.exists(mntlivedir):
+                os.makedirs(mntlivedir)
+            self.mntlive = BindChrootMount(self.liveosdevmnt.mountdir,
+                                           '', mntlivedir)
+            self.mntlive.mount()
+        except MountError, e:
+            raise SnapshotError("Failed to mount %s : %s" % (self.disk, e))
+            self.cleanup()
+
+    def unmount(self):
+        if self.mntlive:
+            self.mntlive.unmount()
+        if self.homemnt:
+            self.homemnt.unmount()
+        self.dm_livemount.unmount()
+        self.liveosdevmnt.unmount()
+
+    def cleanup(self):
+        self.unmount()
+        if self.homemnt:
+            self.homemnt.cleanup()
+        self.dm_liveimage.remove()
+        if self.squashmnt:
+            self.squashmnt.cleanup()
+        self.liveosdevmnt.cleanup()
+        self.__created = False
+
+
def create_image_minimizer(path, image, compress_type, target_size = None,
                           tmpdir = "/tmp"):
    """