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
path = property(get_path)return self.device return os.path.join("/dev/mapper", self.__name)
@@ -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"): """