[Patch 3/5]
Author: Frederick Grose <fgrose(a)sugarlabs.org>
Date: Thu Apr 14 10:37:13 2011 -0400
Support usage within a booted LiveOS instance.
Provide functions to add loop devices and remove then on exit, a
LiveImageMountError(Exception) class for better error handling, and
random device mapper names.
diff --git a/tools/liveimage-mount b/tools/liveimage-mount
index 80d67d1..1ee5b16 100755
--- a/tools/liveimage-mount
+++ b/tools/liveimage-mount
@@ -23,10 +23,13 @@
import os
import sys
import stat
+import time
import getopt
+import random
import tempfile
import subprocess
+extra_loops = []
def usage(ecode):
print """Usage:
@@ -40,6 +43,18 @@ def usage(ecode):
and [ARGS] = [arg1[ arg2[ ...]]]\n"""
sys.exit(ecode)
+class LiveImageMountError(Exception):
+ """An exception base class for all liveimage-mount errors."""
+ def __init__(self, msg):
+ Exception.__init__(self, msg)
+ def __str__(self):
+ try:
+ return str(self.message)
+ except UnicodeEncodeError:
+ return repr(self.message)
+
+ def __unicode__(self):
+ return unicode(self.message)
def call(*popenargs, **kwargs):
'''
@@ -62,43 +77,57 @@ def call(*popenargs, **kwargs):
return rc
-def rcall(args, env=None):
+def rcall(args, stdin=None, stderr=True, env=None):
+ out, err, p, environ = None, None, None, None
if env:
environ = os.environ.copy()
environ.update(env)
- else:
- environ = None
try:
- p = subprocess.Popen(args, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE, env=environ)
- out, err = p.communicate()
+ p = subprocess.Popen(args, stdin=subprocess.PIPE, env=environ,
+ stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
+ out, err = p.communicate(stdin)
except OSError, e:
- raise CreatorError(u"Failed to execute:\n'%s'\n'%s'" % (args, e))
+ raise LiveImageMountError(u"Failed executing:\n'%s'\n'%s'" % (args,
e))
except:
- raise CreatorError(u"""Failed to execute:\n'%s'
- \renviron: '%s'\nstdout: '%s'\nstderr: '%s'\nreturncode:
'%s'""" %
- (args, environ, out, err, p.returncode))
+ raise LiveImageMountError(u"""Failed to execute:\n'%s'
+ \renviron: '%s'\nstdout: '%s'\nstderr:
'%s'""" %
+ (args, environ, out, err))
else:
- if p.returncode != 0:
- raise CreatorError(u"""Error in call:\n'%s'\nenviron: '%s'
- \rstdout: '%s'\nstderr: '%s'\nreturncode: '%s'""" %
- (args, environ, out, err, p.returncode))
+ if p.returncode != 0 and stderr:
+ raise LiveImageMountError(u'''Error in call:\n'%s'\nenviron:
'%s'
+ \rstdout: %s\nstderr: %sreturncode: %s''' %
+ (args, environ, out, err, p.returncode))
return out
-def get_device_mountpoint(path):
- """Return the device and mountpoint for a file or device path."""
+def get_loop(path):
+ """Return the loop device for a given mount point path."""
+
+ loopdevice = None
+ for loop in rcall(['/sbin/losetup', '-a']).splitlines():
+ info = loop.split()
+ if path == info[2].strip('()'):
+ loopdevice = info[0].rstrip(':')
+ break
+ return loopdevice
+
+
+def add_loop(n=8):
+ """Add a loop device."""
+
+ while os.path.exists('/dev/loop%s' % n):
+ n += 1
+ os.mknod('/dev/loop%s' % n, 0660 | stat.S_IFBLK, os.makedev(7, n))
+ extra_loops.extend(['/dev/loop%s' % n])
+ return '/dev/loop%s' % n
- info = list()
- info[0:5] = [None] * 6
- if os.path.exists(path):
- st_mode = os.stat(path).st_mode
- if stat.S_ISBLK(st_mode) or os.path.ismount(path):
- devinfo = rcall(['/bin/df', path]).splitlines()
- info = devinfo[1].split(None)
- if len(info) == 1:
- info.extend(devinfo[2].split(None))
- return [info[0], info[5]]
+
+def loop_setup(path):
+ """Make and associate a loop device with an image file or device."""
+
+ loop = add_loop()
+ call(['/sbin/losetup', loop, path])
+ return loop
def main():
@@ -145,18 +174,16 @@ def main():
img_type = 'blk'
imgloop = None
overlayloop = None
+ liveosmnt = tempfile.mkdtemp(prefix='livemnt-device-')
else:
img_type = 'iso'
-
- if img_type is 'blk':
- liveosmnt = tempfile.mkdtemp(prefix='livemnt-device-')
- if img_type is 'iso':
liveosmnt = tempfile.mkdtemp(prefix='livemnt-iso-')
liveosdir = os.path.join(liveosmnt, 'LiveOS')
homemnt = None
dm_cow = None
mntlive = None
+ rmmntdir = None
command = args[2:]
verbose = not command
@@ -164,13 +191,12 @@ def main():
squashmnt = tempfile.mkdtemp(prefix='livemnt-squash-')
squashloop = None
imgloop = None
- losetup_args = ['/sbin/losetup', '-f', '--show']
try:
- if img_type is 'blk':
+ if img_type == 'blk':
call(['/bin/mount', liveos, liveosmnt])
- elif img_type is 'iso':
- liveosloop = rcall(losetup_args + [liveos]).rstrip()
+ elif img_type == 'iso':
+ liveosloop = loop_setup(liveos)
call(['/bin/mount', '-o', 'ro', liveosloop, liveosmnt])
squash_img = os.path.join(liveosdir, 'squashfs.img')
@@ -182,12 +208,12 @@ def main():
ecode = 1
return
else:
- squashloop = rcall(losetup_args + [squash_img]).rstrip()
+ squashloop = loop_setup(squash_img)
call(['/bin/mount', '-o', 'ro', squashloop, squashmnt])
ext3_img = os.path.join(squashmnt, 'LiveOS', 'ext3fs.img')
- if img_type is 'blk':
- imgloop = rcall(losetup_args + [ext3_img]).rstrip()
+ if img_type == 'blk':
+ imgloop = loop_setup(ext3_img)
imgsize = rcall(['/sbin/blockdev', '--getsz',
imgloop]).rstrip()
files = os.listdir(liveosdir)
overlay = None
@@ -195,18 +221,19 @@ def main():
if f.find('overlay-') == 0:
overlay = f
break
- overlayloop = rcall(['/sbin/losetup', '-f']).rstrip()
if overlay:
- call(['/sbin/losetup', overlayloop, os.path.join(liveosdir,
- overlay)])
- dm_cow = 'live-rw'
+ overlayloop = loop_setup(os.path.join(liveosdir, overlay))
+ dm_cow = "live-rw-%d-%d" % (os.getpid(),
+ random.randint(0, 2**16))
+
else:
overlay = tempfile.NamedTemporaryFile(dir='/dev/shm')
print "\npreparing temporary overlay..."
call(['/bin/dd', 'if=/dev/null', 'of=%s' % overlay.name,
'bs=1024', 'count=1', 'seek=%s' % (512 * 1024)])
- call(['/sbin/losetup', overlayloop, overlay.name])
- dm_cow = 'live-ro'
+ overlayloop = loop_setup(overlay.name)
+ dm_cow = "live-ro-%d-%d" % (os.getpid(),
+ random.randint(0, 2**16))
call(['/sbin/dmsetup', '--noudevrules', '--noudevsync',
'create', dm_cow, '--table=0 %s snapshot %s %s p 8' %
(imgsize, imgloop, overlayloop)])
@@ -214,14 +241,15 @@ def main():
home_path = os.path.join(liveosdir, 'home.img')
if os.path.exists(home_path):
homemnt = os.path.join(destmnt, 'home')
- homeloop = rcall(losetup_args + [home_path]).rstrip()
+ homeloop = loop_setup(home_path)
call(['/bin/mount', homeloop, homemnt])
mntlive = os.path.join(destmnt, 'mnt', 'live')
if not os.path.exists(mntlive):
os.makedirs(mntlive)
+ rmmntdir = True
call(['/bin/mount', '--bind', liveosmnt, mntlive])
- elif img_type is 'iso':
- imgloop = rcall(losetup_args + [ext3_img]).rstrip()
+ elif img_type == 'iso':
+ imgloop = loop_setup(ext3_img)
call(['/bin/mount', '-o', 'ro', imgloop, destmnt])
if mount_hacks:
@@ -232,7 +260,7 @@ def main():
args = []
args.extend(command)
live = ''
- if img_type is 'blk':
+ if img_type == 'blk':
live = 'live-'
print """Starting process with this command line:
\r%s\n %s is %smounted.""" % (' '.join(command), liveos, live)
@@ -247,7 +275,7 @@ def main():
print "Starting subshell in chroot, press Ctrl-D to exit..."
ecode = subprocess.call(['chroot', destmnt], stdin=sys.stdin,
stdout=sys.stdout, stderr=sys.stderr)
else:
- if dm_cow == 'live-ro':
+ if img_type == 'blk' and dm_cow[:7] == 'live-ro':
status = ' with NO LiveOS persistence,'
else:
status = ''
@@ -268,11 +296,13 @@ def main():
if img_type is 'blk':
if mntlive:
call(['/bin/umount', mntlive])
- os.rmdir(mntlive)
+ if rmmntdir:
+ time.sleep(2)
+ os.rmdir(mntlive)
if os.path.ismount(destmnt):
call(['/bin/umount', destmnt])
if img_type is 'blk':
- m_cow:
+ if dm_cow:
call(['/sbin/dmsetup', '--noudevrules', '--noudevsync',
'remove', dm_cow])
if overlayloop:
@@ -288,6 +318,10 @@ def main():
os.rmdir(squashmnt)
if not os.path.ismount(liveosmnt):
os.rmdir(liveosmnt)
+ time.sleep(2)
+ for loop in extra_loops:
+ if loop:
+ call(['/bin/rm', '-f', loop])
if verbose:
print "Cleanup complete"