[SoaS] [PATCH 1a] substitutes was: [PATCH 1/5] Support Live image mounting in fs.py
Frederick Grose
fgrose at sugarlabs.org
Sat Sep 17 17:50:31 EDT 2011
Patch 1a of 2 substitute patches to replace an earlier patch with new
features:
commit 7389913c708d3f19ca029cf3f150bbfb08a768d3
Author: Frederick Grose <fgrose at 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 at gmail.com> wrote:
> [Patch 1/5]
> Author: Frederick Grose <fgrose at 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"):
> """
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.sugarlabs.org/archive/soas/attachments/20110917/8522210f/attachment-0001.html>
More information about the SoaS
mailing list