<font face="'courier new', monospace"><div>This code is ready for Rawhide testing and comment.</div><div>I've used it on Fedora 11 - 15.</div><div><br></div><div>commit b9cdca9fb0ab97239bc68d2184ce16818401a0e2</div>
<div>Author: Frederick Grose <<a href="mailto:fgrose@sugarlabs.org">fgrose@sugarlabs.org</a>></div><div>Date: Sun Sep 18 20:49:01 2011 -0400</div><div><br></div><div> Support liveimage mounting from within a booted LiveOS instance.</div>
<div> </div><div> Provide functions to add loop devices and remove them on exit, a</div><div> LiveImageMountError(Exception) class for better error handling,</div><div> random device-mapper node names, and the ability to liveimage-mount</div>
<div> an image (in the LiveOS folder) saved in a plain directory.</div><div> </div><div> Provide a yum cache mount point option,</div><div> bind mount /etc/resolv.conf to permit Internet updating,</div><div> bind mount /dev/mapper to permit device checking,</div>
<div> support dmsetup versions lacking --noudevrules & --noudevsync,</div><div> miscellaneous code and whitespace optimizations.</div><div><br></div><div>diff --git a/tools/liveimage-mount b/tools/liveimage-mount</div>
<div>index 80d67d1..aed7606 100755</div><div>--- a/tools/liveimage-mount</div><div>+++ b/tools/liveimage-mount</div><div>@@ -1,9 +1,10 @@</div><div> #!/usr/bin/python -tt</div><div>+# coding: latin-1</div><div>+# 2011-09-18 11:47:42 -0400</div>
<div> #</div><div>-# liveimage-mount: Mount a LiveOS at the specified point, and log</div><div>-# into a shell.</div><div>+# liveimage-mount: Mount a LiveOS at the specified point, and log into a shell.</div><div> #</div>
<div>-# Copyright 2011, Red Hat Inc.</div><div>+# Copyright 2011, Red Hat Inc., Sugar Labs®</div><div> # Code for Live mounting an attached LiveOS device added by Frederick Grose,</div><div> # <fgrose at <a href="http://sugarlabs.org">sugarlabs.org</a>></div>
<div> #</div><div>@@ -23,23 +24,39 @@</div><div> import os</div><div> import sys</div><div> import stat</div><div>+import time</div><div> import getopt</div><div>+import random</div><div> import tempfile</div><div> import subprocess</div>
<div> </div><div>+extra_loops = []</div><div> </div><div> def usage(ecode):</div><div> print """Usage:</div><div>- liveimage-mount [opts] ISO.iso|LiveOSdevice MOUNTPOINT [COMMAND] [ARGS]</div><div>
+ liveimage-mount [opts] ISO.iso|LiveOSdev|dir MOUNTPOINT [COMMAND] [ARGS]</div><div> </div><div> where [opts] = [-h|--help</div><div> [--chroot</div><div> [--mount-hacks</div>
<div>- [--persist]]]]]</div><div>+ [--persist</div><div>+ [-y|--yumcache=YUMCACHEPATH]]]]]</div><div> </div><div> and [ARGS] = [arg1[ arg2[ ...]]]\n"""</div>
<div> sys.exit(ecode)</div><div> </div><div>+class LiveImageMountError(Exception):</div><div>+ """An exception base class for all liveimage-mount errors."""</div><div>+ def __init__(self, msg):</div>
<div>+ Exception.__init__(self, msg)</div><div>+ def __str__(self):</div><div>+ try:</div><div>+ return str(self.message)</div><div>+ except UnicodeEncodeError:</div><div>+ return repr(self.message)</div>
<div>+</div><div>+ def __unicode__(self):</div><div>+ return unicode(self.message)</div><div> </div><div> def call(*popenargs, **kwargs):</div><div> '''</div><div>@@ -62,43 +79,81 @@ def call(*popenargs, **kwargs):</div>
<div> return rc</div><div> </div><div> </div><div>-def rcall(args, env=None):</div><div>- if env:</div><div>+def rcall(args, stdin=None, rpterr=True, env=None):</div><div>+ '''Return stdout, stderr, & returncode from a subprocess call.'''</div>
<div>+</div><div>+ out, err, p, environ = '', '', None, None</div><div>+ if env is not None:</div><div> environ = os.environ.copy()</div><div> environ.update(env)</div><div>- else:</div>
<div>- environ = None</div><div> try:</div><div>- p = subprocess.Popen(args, stdout=subprocess.PIPE,</div><div>- stderr=subprocess.PIPE, env=environ)</div><div>- out, err = p.communicate()</div>
<div>+ p = subprocess.Popen(args, stdin=subprocess.PIPE, env=environ,</div><div>+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)</div><div>+ out, err = p.communicate(stdin)</div>
<div> except OSError, e:</div><div>- raise CreatorError(u"Failed to execute:\n'%s'\n'%s'" % (args, e))</div><div>+ out, err = out, u'Failed executing:\n%s\nerror: %s' % (args, e)</div>
<div>+ if rpterr:</div><div>+ raise LiveImageMountError(u'Failed executing:\n%s\nerror: %s' %</div><div>+ (args, e))</div><div> except:</div><div>- raise CreatorError(u"""Failed to execute:\n'%s'</div>
<div>- \renviron: '%s'\nstdout: '%s'\nstderr: '%s'\nreturncode: '%s'""" %</div><div>- (args, environ, out, err, p.returncode))</div><div>+ out, err = (out, u'Failed to execute:\n%s\nenviron: %s' %</div>
<div>+ (args, environ))</div><div>+ if rpterr:</div><div>+ raise LiveImageMountError(u'''Failed to execute:\n%s</div><div>+ \renviron: %s\nstdout: %s\nstderr: %s''' %</div>
<div>+ (args, environ, out, err))</div><div> else:</div><div>- if p.returncode != 0:</div><div>- raise CreatorError(u"""Error in call:\n'%s'\nenviron: '%s'</div>
<div>- \rstdout: '%s'\nstderr: '%s'\nreturncode: '%s'""" %</div><div>- (args, environ, out, err, p.returncode))</div><div>- return out</div><div>
+ if p.returncode != 0 and rpterr:</div><div>+ raise LiveImageMountError(u'''Error in call:\n%s\nenviron: %s</div><div>+ \rstdout: %s\nstderr: %s\nreturncode: %s''' %</div>
<div>+ (args, environ, out, err, p.returncode))</div><div>+ finally:</div><div>+ return out, err</div><div>+</div><div>+</div><div>+def get_fsvalue(format=None, tag=None, token=None, filesystem=None):</div>
<div>+ """Return filesystem information based on a blkid tag, token, or device."""</div><div>+</div><div>+ dev_null = os.open('/dev/null', os.O_WRONLY)</div><div>+ args = ['/sbin/blkid', '-c', '/dev/null']</div>
<div>+ if format:</div><div>+ args.extend(['-o', format])</div><div>+ if tag:</div><div>+ args.extend(['-s', tag])</div><div>+ if token:</div><div>+ args.extend(['-l', '-t', token])</div>
<div>+ if filesystem:</div><div>+ args.extend([filesystem])</div><div>+ try:</div><div>+ fs_value = subprocess.Popen(args,</div><div>+ stdout=subprocess.PIPE,</div><div>+ stderr=dev_null).communicate()[0].rstrip()</div>
<div>+ except IOError, e:</div><div>+ raise LiveImageMountError("Failed to determine fs %s: %s" % value, e)</div><div>+ finally:</div><div>+ os.close(dev_null)</div><div>+ return fs_value</div>
<div> </div><div> </div><div>-def get_device_mountpoint(path):</div><div>- """Return the device and mountpoint for a file or device path."""</div><div>+def add_loop(n=8):</div><div>+ """Add a loop device."""</div>
<div> </div><div>- info = list()</div><div>- info[0:5] = [None] * 6</div><div>- if os.path.exists(path):</div><div>- st_mode = os.stat(path).st_mode</div><div>- if stat.S_ISBLK(st_mode) or os.path.ismount(path):</div>
<div>- devinfo = rcall(['/bin/df', path]).splitlines()</div><div>- info = devinfo[1].split(None)</div><div>- if len(info) == 1:</div><div>- info.extend(devinfo[2].split(None))</div>
<div>- return [info[0], info[5]]</div><div>+ while os.path.exists('/dev/loop%s' % n):</div><div>+ n += 1</div><div>+ os.mknod('/dev/loop%s' % n, 0660 | stat.S_IFBLK, os.makedev(7, n))</div>
<div>+ extra_loops.extend(['/dev/loop%s' % n])</div><div>+ return '/dev/loop%s' % n</div><div>+</div><div>+</div><div>+def loop_setup(path, ops=None):</div><div>+ """Make and associate a loop device with an image file or device."""</div>
<div>+</div><div>+ loop = add_loop()</div><div>+ args = ['/sbin/losetup', loop, path]</div><div>+ if ops is not None:</div><div>+ args += ops</div><div>+ call(args)</div><div>+ return loop</div>
<div> </div><div> </div><div> def main():</div><div>@@ -108,9 +163,8 @@ def main():</div><div> return 1</div><div> </div><div> try:</div><div>- opts,args = getopt.getopt(sys.argv[1:], 'h', ['help',</div>
<div>- 'chroot', 'persist',</div><div>- 'mount-hacks'])</div><div>+ opts, args = getopt.getopt(sys.argv[1:], 'hy:', ['help', 'chroot',</div>
<div>+ 'persist', 'mount-hacks', 'yumcache='])</div><div> except getopt.GetoptError, e:</div><div> usage(1)</div><div> </div><div>@@ -118,7 +172,9 @@ def main():</div>
<div> chroot = False</div><div> persist = False</div><div> mount_hacks = False</div><div>- for o,a in opts:</div><div>+ yumcache = None</div><div>+ rm_dir = None</div><div>+ for o, a in opts:</div>
<div> if o in ('-h', '--help'):</div><div> usage(0)</div><div> elif o in ('--chroot', ):</div><div>@@ -128,49 +184,64 @@ def main():</div><div> elif o in ('--persist', ):</div>
<div> """Option used to run a command in a spawned process."""</div><div> persist = True</div><div>+ elif o in ('-y', '--yumcache'):</div><div>
+ yumcache = a</div>
<div> </div><div> if len(args) < 2:</div><div> usage(1)</div><div> </div><div> liveos = args[0]</div><div> destmnt = args[1]</div><div>-</div><div>+ if not os.path.isdir(destmnt):</div><div>+ os.makedirs(destmnt)</div>
<div>+ rm_mnt = destmnt</div><div>+ if os.path.ismount(destmnt):</div><div>+ print """\n Exiting...</div><div>+ %s is already in use as a mount point.\n""" % destmnt</div>
<div>+ ecode = 1</div><div>+ return</div><div>+ </div><div> if not os.path.exists(liveos):</div><div> print """\n Exiting...</div><div> %s is not a file, directory, or device.\n""" % liveos</div>
<div> ecode = 1</div><div> return</div><div> </div><div>- if stat.S_ISBLK(os.stat(liveos).st_mode):</div><div>+ liveos_stat = os.stat(liveos).st_mode</div><div>+ if stat.S_ISBLK(liveos_stat):</div>
<div> img_type = 'blk'</div><div>- imgloop = None</div><div> overlayloop = None</div><div>+ liveosmnt = tempfile.mkdtemp(prefix='livemnt-device-')</div><div>+ elif stat.S_ISDIR(liveos_stat):</div>
<div>+ img_type = 'dir'</div><div>+ liveosmnt = liveos</div><div> else:</div><div> img_type = 'iso'</div><div>-</div><div>- if img_type is 'blk':</div><div>- liveosmnt = tempfile.mkdtemp(prefix='livemnt-device-')</div>
<div>- if img_type is 'iso':</div><div> liveosmnt = tempfile.mkdtemp(prefix='livemnt-iso-')</div><div> </div><div> liveosdir = os.path.join(liveosmnt, 'LiveOS')</div><div> homemnt = None</div>
<div> dm_cow = None</div><div> mntlive = None</div><div>+ rmmntdir = None</div><div>+ rm_mnt = None</div><div> </div><div> command = args[2:]</div><div> verbose = not command</div><div> </div><div>+ dmsetup_cmd = ['/sbin/dmsetup']</div>
<div>+ if '--noudevsync' in rcall(['/sbin/dmsetup', '-h'])[1]:</div><div>+ dmsetup_cmd = ['/sbin/dmsetup', '--noudevrules', '--noudevsync']</div><div>+</div><div>
squashmnt = tempfile.mkdtemp(prefix='livemnt-squash-')</div>
<div> squashloop = None</div><div> imgloop = None</div><div>- losetup_args = ['/sbin/losetup', '-f', '--show']</div><div> </div><div> try:</div><div>- if img_type is 'blk':</div>
<div>+ if img_type == 'blk':</div><div> call(['/bin/mount', liveos, liveosmnt])</div><div>- elif img_type is 'iso':</div><div>- liveosloop = rcall(losetup_args + [liveos]).rstrip()</div>
<div>+ elif img_type == 'iso':</div><div>+ liveosloop = loop_setup(liveos)</div><div> call(['/bin/mount', '-o', 'ro', liveosloop, liveosmnt])</div><div> </div>
<div> squash_img = os.path.join(liveosdir, 'squashfs.img')</div><div>@@ -182,57 +253,89 @@ def main():</div><div> ecode = 1</div><div> return</div><div> else:</div>
<div>
- squashloop = rcall(losetup_args + [squash_img]).rstrip()</div><div>+ squashloop = loop_setup(squash_img, ['-r'])</div><div> call(['/bin/mount', '-o', 'ro', squashloop, squashmnt])</div>
<div> ext3_img = os.path.join(squashmnt, 'LiveOS', 'ext3fs.img')</div><div> </div><div>- if img_type is 'blk':</div><div>- imgloop = rcall(losetup_args + [ext3_img]).rstrip()</div>
<div>- imgsize = rcall(['/sbin/blockdev', '--getsz', imgloop]).rstrip()</div><div>- files = os.listdir(liveosdir)</div><div>- overlay = None</div><div>- for f in files:</div>
<div>- if f.find('overlay-') == 0:</div><div>- overlay = f</div><div>- break</div><div>- overlayloop = rcall(['/sbin/losetup', '-f']).rstrip()</div>
<div>- if overlay:</div><div>- call(['/sbin/losetup', overlayloop, os.path.join(liveosdir,</div><div>- overlay)])</div><div>
- dm_cow = 'live-rw'</div><div>- else:</div><div>- overlay = tempfile.NamedTemporaryFile(dir='/dev/shm')</div><div>- print "\npreparing temporary overlay..."</div>
<div>- call(['/bin/dd', 'if=/dev/null', 'of=%s' % <a href="http://overlay.name">overlay.name</a>,</div><div>- 'bs=1024', 'count=1', 'seek=%s' % (512 * 1024)])</div>
<div>- call(['/sbin/losetup', overlayloop, <a href="http://overlay.name">overlay.name</a>])</div><div>- dm_cow = 'live-ro'</div><div>- call(['/sbin/dmsetup', '--noudevrules', '--noudevsync',</div>
<div>- 'create', dm_cow, '--table=0 %s snapshot %s %s p 8' %</div><div>- (imgsize, imgloop, overlayloop)])</div><div>- call(['/bin/mount', os.path.join('/dev/mapper', dm_cow), destmnt])</div>
<div>- home_path = os.path.join(liveosdir, 'home.img')</div><div>- if os.path.exists(home_path):</div><div>- homemnt = os.path.join(destmnt, 'home')</div><div>- homeloop = rcall(losetup_args + [home_path]).rstrip()</div>
<div>- call(['/bin/mount', homeloop, homemnt])</div><div>- mntlive = os.path.join(destmnt, 'mnt', 'live')</div><div>- if not os.path.exists(mntlive):</div><div>
- os.makedirs(mntlive)</div><div>- call(['/bin/mount', '--bind', liveosmnt, mntlive])</div><div>- elif img_type is 'iso':</div><div>- imgloop = rcall(losetup_args + [ext3_img]).rstrip()</div>
<div>- call(['/bin/mount', '-o', 'ro', imgloop, destmnt])</div><div>+ if img_type in ('blk', 'dir'):</div><div>+ imgloop = loop_setup(ext3_img)</div><div>
+ elif img_type == 'iso':</div><div>+ imgloop = loop_setup(ext3_img, ['-r'])</div><div>+ imgsize = rcall(['/sbin/blockdev', '--getsz', imgloop])[0].rstrip()</div><div>
+ overlay = None</div><div>+ for f in os.listdir(liveosdir):</div><div>+ if f.find('overlay-') == 0:</div><div>+ overlay = f</div><div>+ break</div><div>+ fd, dm_cow = tempfile.mkstemp(prefix='dm-')</div>
<div>+ os.close(fd)</div><div>+ os.unlink(dm_cow)</div><div>+ dm_cow = os.path.basename(dm_cow)</div><div>+ if overlay:</div><div>+ overlayloop = loop_setup(os.path.join(liveosdir, overlay))</div>
<div>+ else:</div><div>+ overlay = tempfile.NamedTemporaryFile(dir='/dev/shm')</div><div>+ print "\npreparing temporary overlay..."</div><div>+ call(['/bin/dd', 'if=/dev/null', 'of=%s' % <a href="http://overlay.name">overlay.name</a>,</div>
<div>+ 'bs=1024', 'count=1', 'seek=%s' % (512 * 1024)])</div><div>+ overlayloop = loop_setup(<a href="http://overlay.name">overlay.name</a>)</div><div>+ call(dmsetup_cmd + ['create', dm_cow,</div>
<div>+ '--table=0 %s snapshot %s %s p 8' %</div><div>+ (imgsize, imgloop, overlayloop)])</div><div>+ call(['/bin/mount', os.path.join('/dev/mapper', dm_cow), destmnt])</div>
<div>+ home_path = os.path.join(liveosdir, 'home.img')</div><div>+ if os.path.exists(home_path):</div><div>+ homemnt = os.path.join(destmnt, 'home')</div><div>+ homedev = loop_setup(home_path)</div>
<div>+ if get_fsvalue('value', 'TYPE', None,</div><div>+ homedev) == 'crypto_LUKS':</div><div>+ call(['/sbin/cryptsetup', 'luksOpen', homedev, 'EncHome'])</div>
<div>+ homedev = os.path.join('/dev', 'mapper', 'EncHome')</div><div>+ if img_type in ('blk', 'dir'):</div><div>+ call(['/bin/mount', homedev, homemnt])</div>
<div>+ elif img_type == 'iso':</div><div>+ call(['/bin/mount', '-o', 'ro', homedev, homemnt])</div><div>+ mntlive = os.path.join(destmnt, 'mnt', 'live')</div>
<div>+ if not os.path.isdir(mntlive):</div><div>+ os.makedirs(mntlive)</div><div>+ rmmntdir = True</div><div>+ call(['/bin/mount', '--bind', liveosmnt, mntlive])</div><div>
</div><div> if mount_hacks:</div><div>- subprocess.check_call(['mount', '-t', 'proc', 'proc', os.path.join(destmnt, 'proc')], stderr=sys.stderr)</div><div>- subprocess.check_call(['mount', '-t', 'tmpfs', 'tmpfs', os.path.join(destmnt, 'var', 'run')], stderr=sys.stderr)</div>
<div>+ subprocess.check_call(['mount', '-t', 'proc', 'proc',</div><div>+ os.path.join(destmnt, 'proc')], stderr=sys.stderr)</div><div>+ subprocess.check_call(['mount', '-t', 'tmpfs', 'tmpfs',</div>
<div>+ os.path.join(destmnt, 'var', 'run')], stderr=sys.stderr)</div><div>+ subprocess.check_call(['mount', '-t', 'sysfs', 'sys',</div><div>+ os.path.join(destmnt, 'sys')], stderr=sys.stderr)</div>
<div>+ subprocess.check_call(['mount', '-t', 'devpts', 'devpts',</div><div>+ os.path.join(destmnt, 'dev', 'pts')], stderr=sys.stderr)</div><div>+ tmpfs = os.path.join(destmnt, 'dev', 'shm')</div>
<div>+ if not os.path.exists(tmpfs):</div><div>+ os.makedirs(tmpfs)</div><div>+ subprocess.check_call(['mount', '-t', 'tmpfs', 'tmpfs', tmpfs],</div><div>
+ stderr=sys.stderr)</div><div>+ run_dir = os.path.join(destmnt, 'run')</div><div>+ if not os.path.exists(run_dir):</div><div>+ os.makedirs(run_dir)</div>
<div>+ call(['/bin/mount', '-t', 'tmpfs', 'run', run_dir])</div><div>+ if yumcache is not None:</div><div>+ call(['/bin/mount', '--bind', yumcache,</div>
<div>+ os.path.join(destmnt, 'var', 'cache', 'yum')])</div><div>+ else:</div><div>+ subprocess.check_call(['mount', '-t', 'tmpfs', 'varcacheyum',</div>
<div>+ os.path.join(destmnt, 'var', 'cache', 'yum')],</div><div>+ stderr=sys.stderr)</div><div>+ call(['/bin/mount', '--bind', '/etc/resolv.conf',</div>
<div>+ os.path.join(destmnt, 'etc', 'resolv.conf')])</div><div>+ call(['/bin/mount', '--bind', '/dev/mapper',</div><div>+ os.path.join(destmnt, 'dev', 'mapper')])</div>
<div> </div><div> if len(command) > 0 and persist:</div><div> args = []</div><div> args.extend(command)</div><div> live = ''</div><div>- if img_type is 'blk':</div>
<div>+ if img_type in ('blk', 'dir'):</div><div> live = 'live-'</div><div> print """Starting process with this command line:</div><div> \r%s\n %s is %smounted.""" % (' '.join(command), liveos, live)</div>
<div>@@ -244,50 +347,68 @@ def main():</div><div> args.extend(command)</div><div> ecode = subprocess.call(args, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr)</div><div> elif chroot:</div>
<div>- print "Starting subshell in chroot, press Ctrl-D to exit..."</div><div>+ print "Starting subshell in chroot, press Ctrl D to exit..."</div><div> ecode = subprocess.call(['chroot', destmnt], stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr)</div>
<div> else:</div><div>- if dm_cow == 'live-ro':</div><div>+ if img_type in ('blk', 'dir') and dm_cow[:7] == 'live-ro':</div><div> status = ' with NO LiveOS persistence,'</div>
<div> else:</div><div> status = ''</div><div>- print "Entering subshell,%s press Ctrl-D to exit..." % status</div><div>+ print "Entering subshell,%s press Ctrl D to exit..." % status</div>
<div> ecode = subprocess.call([os.environ['SHELL']], cwd=destmnt, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr)</div><div> finally:</div><div>+ if verbose:</div><div>+ print """Cleaning up...</div>
<div>+ Please wait if large files were written."""</div><div> call(['/bin/sync'])</div><div> if not persist:</div><div>- if verbose:</div><div>- print """Cleaning up...</div>
<div>- Please wait if large files were written."""</div><div> if mount_hacks:</div><div>- subprocess.call(['umount', os.path.join(destmnt, 'var', 'run')])</div>
<div>- subprocess.call(['umount', os.path.join(destmnt, 'proc')])</div><div>+ call(['umount', os.path.join(destmnt, 'var', 'run')])</div><div>+ call(['umount', os.path.join(destmnt, 'proc')])</div>
<div>+ call(['umount', os.path.join(destmnt, 'dev', 'pts')])</div><div>+ call(['umount', os.path.join(destmnt, 'sys')])</div><div>+ call(['umount', os.path.join(destmnt, 'dev', 'shm')])</div>
<div>+ call(['umount', os.path.join(destmnt, 'run')])</div><div>+ call(['umount', os.path.join(destmnt, 'var', 'cache', 'yum')])</div><div>+ call(['umount', os.path.join(destmnt, 'etc', 'resolv.conf')])</div>
<div>+ call(['umount', os.path.join(destmnt, 'dev', 'mapper')])</div><div>+</div><div> if homemnt:</div><div> call(['/bin/umount', homemnt])</div>
<div>- call(['/sbin/losetup', '-d', homeloop])</div><div>- if img_type is 'blk':</div><div>- if mntlive:</div><div>- call(['/bin/umount', mntlive])</div>
<div>- os.rmdir(mntlive)</div><div>+ if homedev == os.path.join('/dev', 'mapper', 'EncHome'):</div><div>+ call(dmsetup_cmd + ['remove', 'EncHome'])</div>
<div>+ else:</div><div>+ call(['/sbin/losetup', '-d', homedev])</div><div>+ if mntlive:</div><div>+ call(['/bin/umount', mntlive])</div>
<div>
+ if rmmntdir:</div><div>+ time.sleep(2)</div><div>+ os.removedirs(mntlive)</div><div> if os.path.ismount(destmnt):</div><div>+ os.chdir('..')</div>
<div> call(['/bin/umount', destmnt])</div><div>- if img_type is 'blk':</div><div>- m_cow:</div><div>- call(['/sbin/dmsetup', '--noudevrules', '--noudevsync',</div>
<div>- 'remove', dm_cow])</div><div>- if overlayloop:</div><div>- call(['/sbin/losetup', '-d', overlayloop])</div><div>+ if dm_cow:</div>
<div>+ #FIXME -f force option is sometimes needed when there are</div><div>+ # multiple invocations of liveimage-mount active.</div><div>+ call(dmsetup_cmd + ['remove', '-f', dm_cow])</div>
<div>+ if overlayloop:</div><div>+ call(['/sbin/losetup', '-d', overlayloop])</div><div> if imgloop:</div><div> call(['/sbin/losetup', '-d', imgloop])</div>
<div> if squashloop:</div><div> call(['/bin/umount', squashloop])</div><div> call(['/sbin/losetup', '-d', squashloop])</div><div>- call(['/bin/umount', liveosmnt])</div>
<div>- if not img_type is 'blk':</div><div>+ if os.path.ismount(liveosmnt):</div><div>+ call(['/bin/umount', liveosmnt])</div><div>+ if img_type is 'iso':</div>
<div> call(['/sbin/losetup', '-d', liveosloop])</div><div> os.rmdir(squashmnt)</div><div>- if not os.path.ismount(liveosmnt):</div><div>+ if not os.path.ismount(liveosmnt) and img_type != 'dir':</div>
<div> os.rmdir(liveosmnt)</div><div>+ time.sleep(2)</div><div>+ [call(['/bin/rm', '-f', loop]) for loop in extra_loops]</div><div>+ if rm_mnt is not None:</div>
<div>+ os.removedirs(rm_mnt)</div><div> if verbose:</div><div> print "Cleanup complete"</div><div> </div></font><br><div class="gmail_quote">On Thu, Apr 14, 2011 at 12:10 PM, Frederick Grose <span dir="ltr"><<a href="mailto:fgrose@gmail.com">fgrose@gmail.com</a>></span> wrote:<br>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;"><span style="font-family:courier new,monospace">[Patch 3/5]<br>Author: Frederick Grose <<a href="mailto:fgrose@sugarlabs.org" target="_blank">fgrose@sugarlabs.org</a>><br>
Date: Thu Apr 14 10:37:13 2011 -0400<br><br> Support usage within a booted LiveOS instance.<br>
<br> Provide functions to add loop devices and remove then on exit, a<br> LiveImageMountError(Exception) class for better error handling, and<br> random device mapper names.<br><br>diff --git a/tools/liveimage-mount b/tools/liveimage-mount<br>
index 80d67d1..1ee5b16 100755<br>--- a/tools/liveimage-mount<br>+++ b/tools/liveimage-mount<br>@@ -23,10 +23,13 @@<br> import os<br> import sys<br> import stat<br>+import time<br> import getopt<br>+import random<br> import tempfile<br>
import subprocess<br> <br>+extra_loops = []<br> <br> def usage(ecode):<br> print """Usage:<br>@@ -40,6 +43,18 @@ def usage(ecode):<br> and [ARGS] = [arg1[ arg2[ ...]]]\n"""<br>
sys.exit(ecode)<br> <br>+class LiveImageMountError(Exception):<br>+ """An exception base class for all liveimage-mount errors."""<br>+ def __init__(self, msg):<br>+ Exception.__init__(self, msg)<br>
+ def __str__(self):<br>+ try:<br>+ return str(self.message)<br>+ except UnicodeEncodeError:<br>+ return repr(self.message)<br>+<br>+ def __unicode__(self):<br>+ return unicode(self.message)<br>
<br> def call(*popenargs, **kwargs):<br> '''<br>@@ -62,43 +77,57 @@ def call(*popenargs, **kwargs):<br> return rc<br> <br> <br>-def rcall(args, env=None):<br>+def rcall(args, stdin=None, stderr=True, env=None):<br>
+ out, err, p, environ = None, None, None, None<br> if env:<br> environ = os.environ.copy()<br> environ.update(env)<br>- else:<br>- environ = None<br> try:<br>- p = subprocess.Popen(args, stdout=subprocess.PIPE,<br>
- stderr=subprocess.PIPE, env=environ)<br>- out, err = p.communicate()<br>+ p = subprocess.Popen(args, stdin=subprocess.PIPE, env=environ,<br>+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)<br>
+ out, err = p.communicate(stdin)<br> except OSError, e:<br>- raise CreatorError(u"Failed to execute:\n'%s'\n'%s'" % (args, e))<br>+ raise LiveImageMountError(u"Failed executing:\n'%s'\n'%s'" % (args, e))<br>
except:<br>- raise CreatorError(u"""Failed to execute:\n'%s'<br>- \renviron: '%s'\nstdout: '%s'\nstderr: '%s'\nreturncode: '%s'""" %<br>
- (args, environ, out, err, p.returncode))<br>+ raise LiveImageMountError(u"""Failed to execute:\n'%s'<br>+ \renviron: '%s'\nstdout: '%s'\nstderr: '%s'""" %<br>
+ (args, environ, out, err))<br> else:<br>- if p.returncode != 0:<br>- raise CreatorError(u"""Error in call:\n'%s'\nenviron: '%s'<br>- \rstdout: '%s'\nstderr: '%s'\nreturncode: '%s'""" %<br>
- (args, environ, out, err, p.returncode))<br>+ if p.returncode != 0 and stderr:<br>+ raise LiveImageMountError(u'''Error in call:\n'%s'\nenviron: '%s'<br>+ \rstdout: %s\nstderr: %sreturncode: %s''' %<br>
+ (args, environ, out, err, p.returncode))<br> return out<br> <br> <br>-def get_device_mountpoint(path):<br>- """Return the device and mountpoint for a file or device path."""<br>
+def get_loop(path):<br>+ """Return the loop device for a given mount point path."""<br>+<br>+ loopdevice = None<br>+ for loop in rcall(['/sbin/losetup', '-a']).splitlines():<br>
+ info = loop.split()<br>+ if path == info[2].strip('()'):<br>+ loopdevice = info[0].rstrip(':')<br>+ break<br>+ return loopdevice<br>+<br>+<br>+def add_loop(n=8):<br>
+ """Add a loop device."""<br>+<br>+ while os.path.exists('/dev/loop%s' % n):<br>+ n += 1<br>+ os.mknod('/dev/loop%s' % n, 0660 | stat.S_IFBLK, os.makedev(7, n))<br>
+ extra_loops.extend(['/dev/loop%s' % n])<br>+ return '/dev/loop%s' % n<br> <br>- info = list()<br>- info[0:5] = [None] * 6<br>- if os.path.exists(path):<br>- st_mode = os.stat(path).st_mode<br>
- if stat.S_ISBLK(st_mode) or os.path.ismount(path):<br>- devinfo = rcall(['/bin/df', path]).splitlines()<br>- info = devinfo[1].split(None)<br>- if len(info) == 1:<br>- info.extend(devinfo[2].split(None))<br>
- return [info[0], info[5]]<br>+<br>+def loop_setup(path):<br>+ """Make and associate a loop device with an image file or device."""<br>+<br>+ loop = add_loop()<br>+ call(['/sbin/losetup', loop, path])<br>
+ return loop<br> <br> <br> def main():<br>@@ -145,18 +174,16 @@ def main():<br> img_type = 'blk'<br> imgloop = None<br> overlayloop = None<br>+ liveosmnt = tempfile.mkdtemp(prefix='livemnt-device-')<br>
else:<br> img_type = 'iso'<br>-<br>- if img_type is 'blk':<br>- liveosmnt = tempfile.mkdtemp(prefix='livemnt-device-')<br>- if img_type is 'iso':<br> liveosmnt = tempfile.mkdtemp(prefix='livemnt-iso-')<br>
<br> liveosdir = os.path.join(liveosmnt, 'LiveOS')<br> homemnt = None<br> dm_cow = None<br> mntlive = None<br>+ rmmntdir = None<br> <br> command = args[2:]<br> verbose = not command<br>
@@ -164,13 +191,12 @@ def main():<br> squashmnt = tempfile.mkdtemp(prefix='livemnt-squash-')<br> squashloop = None<br> imgloop = None<br>- losetup_args = ['/sbin/losetup', '-f', '--show']<br>
<br> try:<br>- if img_type is 'blk':<br>+ if img_type == 'blk':<br> call(['/bin/mount', liveos, liveosmnt])<br>- elif img_type is 'iso':<br>- liveosloop = rcall(losetup_args + [liveos]).rstrip()<br>
+ elif img_type == 'iso':<br>+ liveosloop = loop_setup(liveos)<br> call(['/bin/mount', '-o', 'ro', liveosloop, liveosmnt])<br> <br> squash_img = os.path.join(liveosdir, 'squashfs.img')<br>
@@ -182,12 +208,12 @@ def main():<br> ecode = 1<br> return<br> else:<br>- squashloop = rcall(losetup_args + [squash_img]).rstrip()<br>+ squashloop = loop_setup(squash_img)<br>
call(['/bin/mount', '-o', 'ro', squashloop, squashmnt])<br> ext3_img = os.path.join(squashmnt, 'LiveOS', 'ext3fs.img')<br> <br>- if img_type is 'blk':<br>
- imgloop = rcall(losetup_args + [ext3_img]).rstrip()<br>+ if img_type == 'blk':<br>+ imgloop = loop_setup(ext3_img)<br> imgsize = rcall(['/sbin/blockdev', '--getsz', imgloop]).rstrip()<br>
files = os.listdir(liveosdir)<br> overlay = None<br>@@ -195,18 +221,19 @@ def main():<br> if f.find('overlay-') == 0:<br> overlay = f<br> break<br>
- overlayloop = rcall(['/sbin/losetup', '-f']).rstrip()<br> if overlay:<br>- call(['/sbin/losetup', overlayloop, os.path.join(liveosdir,<br>- overlay)])<br>
- dm_cow = 'live-rw'<br>+ overlayloop = loop_setup(os.path.join(liveosdir, overlay))<br>+ dm_cow = "live-rw-%d-%d" % (os.getpid(),<br>+ random.randint(0, 2**16))<br>
+<br> else:<br> overlay = tempfile.NamedTemporaryFile(dir='/dev/shm')<br> print "\npreparing temporary overlay..."<br> call(['/bin/dd', 'if=/dev/null', 'of=%s' % <a href="http://overlay.name" target="_blank">overlay.name</a>,<br>
'bs=1024', 'count=1', 'seek=%s' % (512 * 1024)])<br>- call(['/sbin/losetup', overlayloop, <a href="http://overlay.name" target="_blank">overlay.name</a>])<br>
- dm_cow = 'live-ro'<br>
+ overlayloop = loop_setup(<a href="http://overlay.name" target="_blank">overlay.name</a>)<br>+ dm_cow = "live-ro-%d-%d" % (os.getpid(),<br>+ random.randint(0, 2**16))<br>
call(['/sbin/dmsetup', '--noudevrules', '--noudevsync',<br> 'create', dm_cow, '--table=0 %s snapshot %s %s p 8' %<br> (imgsize, imgloop, overlayloop)])<br>
@@ -214,14 +241,15 @@ def main():<br> home_path = os.path.join(liveosdir, 'home.img')<br> if os.path.exists(home_path):<br> homemnt = os.path.join(destmnt, 'home')<br>
- homeloop = rcall(losetup_args + [home_path]).rstrip()<br>+ homeloop = loop_setup(home_path)<br> call(['/bin/mount', homeloop, homemnt])<br> mntlive = os.path.join(destmnt, 'mnt', 'live')<br>
if not os.path.exists(mntlive):<br> os.makedirs(mntlive)<br>+ rmmntdir = True<br> call(['/bin/mount', '--bind', liveosmnt, mntlive])<br>- elif img_type is 'iso':<br>
- imgloop = rcall(losetup_args + [ext3_img]).rstrip()<br>+ elif img_type == 'iso':<br>+ imgloop = loop_setup(ext3_img)<br> call(['/bin/mount', '-o', 'ro', imgloop, destmnt])<br>
<br> if mount_hacks:<br>@@ -232,7 +260,7 @@ def main():<br> args = []<br> args.extend(command)<br> live = ''<br>- if img_type is 'blk':<br>+ if img_type == 'blk':<br>
live = 'live-'<br> print """Starting process with this command line:<br> \r%s\n %s is %smounted.""" % (' '.join(command), liveos, live)<br>
@@ -247,7 +275,7 @@ def main():<br> print "Starting subshell in chroot, press Ctrl-D to exit..."<br> ecode = subprocess.call(['chroot', destmnt], stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr)<br>
else:<br>- if dm_cow == 'live-ro':<br>+ if img_type == 'blk' and dm_cow[:7] == 'live-ro':<br> status = ' with NO LiveOS persistence,'<br> else:<br>
status = ''<br>@@ -268,11 +296,13 @@ def main():<br> if img_type is 'blk':<br> if mntlive:<br> call(['/bin/umount', mntlive])<br>- os.rmdir(mntlive)<br>
+ if rmmntdir:<br>+ time.sleep(2)<br>+ os.rmdir(mntlive)<br> if os.path.ismount(destmnt):<br> call(['/bin/umount', destmnt])<br>
if img_type is 'blk':<br>- m_cow:<br>+ if dm_cow:<br> call(['/sbin/dmsetup', '--noudevrules', '--noudevsync',<br> 'remove', dm_cow])<br>
if overlayloop:<br>@@ -288,6 +318,10 @@ def main():<br> os.rmdir(squashmnt)<br> if not os.path.ismount(liveosmnt):<br> os.rmdir(liveosmnt)<br>+ time.sleep(2)<br>
+ for loop in extra_loops:<br>+ if loop:<br>+ call(['/bin/rm', '-f', loop])<br> if verbose:<br> print "Cleanup complete"</span>
</blockquote></div><br>