[Fedora-livecd-list] [PATCH] Enable live-mounting of LiveOS device together with an active overlay

James Heather j.heather at surrey.ac.uk
Thu Mar 31 07:19:54 UTC 2011


One good application for this would be to migrate the changes in a
persistent overlay into the squashfs image. Has anyone considered this?

The idea would be that you put your live USB stick in, and run a command
that mounts the squashfs along with the overlay, and then creates a new
squashfs image that contains everything in the combined old
squashfs/overlay. This can then be put back onto the USB stick,
replacing the old image, with a fresh empty overlay. If you do this
every so often, you never run out of overlay space. You could even boot
up your live installation, yum update, and then migrate the changes
across.

I also wonder whether it's possible to establish which files have been
modified in the overlay, and save them somewhere? I have a USB stick I
use all the time, and occasionally I edit something in /etc or similar.
But if I then regenerate the iso image, which I do from time to time to
get the latest packages, I lose my customised files. Perhaps it would be
possible to find a way to identify the modified files, save them, and
put them back into the new overlay once the iso image has been updated.
I can put the customised files into a separate RPM, or edit them
programmatically in the kickstart file, but it's a lot of hassle.

James

On Thu, 2011-03-17 at 04:43 +0000, Frederick Grose wrote:
> https://bugzilla.redhat.com/show_bug.cgi?id=448030#c38
> 
> 
> Author: Frederick Grose <fgrose at gmail.com>
> Date:   Thu Mar 17 00:15:22 2011 -0400
> 
> 
>     Enable standalone live-mounting of an installed LiveOS image with
> access to
>     its overlay.
>     
>     Detect the presence of an overlay on a device with an installed
> LiveOS, and
>     use loop mounts and the device-mapper service to create a
> 'live-mounted'
>     block device for the attached LiveOS-bearing partition.  Provide
> an option
>     to --persist the mounting for chaining to another script.
>     
>     The script continues to be a standalone tool with limited error
> checking.
>     The script requires 3 to 4 free loop devices, so is not yet
> suitable for use
>     within a booted LiveOS image.
> 
> 
> diff --git a/tools/liveimage-mount b/tools/liveimage-mount
> index 76602a7..185f070 100755
> --- a/tools/liveimage-mount
> +++ b/tools/liveimage-mount
> @@ -1,9 +1,11 @@
>  #!/usr/bin/python -tt
>  #
> -# livecd-mount: Mount a live CD at the specified point, and log
> +# liveimage-mount: Mount a LiveOS at the specified point, and log
>  # into a shell.
>  #
> -# Copyright 2010, Red Hat  Inc.
> +# Copyright 2011, Red Hat  Inc.
> +#   Code for Live mounting an attached LiveOS device added by
> Frederick Grose,
> +#   <fgrose at sugarlabs.org>
>  #
>  # This program is free software; you can redistribute it and/or
> modify
>  # it under the terms of the GNU General Public License as published
> by
> @@ -20,22 +22,101 @@
>  
>  import os
>  import sys
> +import stat
>  import getopt
>  import tempfile
>  import subprocess
>  
> +
>  def usage(ecode):
> -    print "Usage: %s ISO.iso MOUNTPOINT [COMMAND] [ARGS]"
> +    print """Usage:
> +        liveimage-mount [opts] ISO.iso|LiveOSdevice MOUNTPOINT
> [COMMAND] [ARGS]
> +
> +                  where [opts] = [-h|--help
> +                                 [--chroot
> +                                 [--mount-hacks
> +                                 [--persist]]]]]
> +
> +                    and [ARGS] = [arg1[ arg2[ ...]]]\n"""
>      sys.exit(ecode)
>  
> +
> +def call(*popenargs, **kwargs):
> +    '''
> +        Calls subprocess.Popen() with the provided arguments.  All
> stdout and
> +        stderr output is sent to print.  The return value is the exit
> +        code of the command.
> +    '''
> +    p = subprocess.Popen(*popenargs, stdout=subprocess.PIPE,
> +                         stderr=subprocess.STDOUT, **kwargs)
> +    rc = p.wait()
> +
> +    # Log output using logging module
> +    while True:
> +        # FIXME choose a more appropriate buffer size
> +        buf = p.stdout.read(4096)
> +        if not buf:
> +            break
> +        print buf
> +
> +    return rc
> +
> +
> +def rcall(args, env=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()
> +    except OSError, e:
> +        raise CreatorError(u"Failed to execute:\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))
> +    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))
> +        return out
> +
> +
> +def get_device_mountpoint(path):
> +    """Return the device and mountpoint for a file or device path."""
> +
> +    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 main():
> +    if os.geteuid() != 0:
> +        print >> sys.stderr, """\n  Exiting...
> +        \r  You must run liveimage-mount with root priviledges.\n"""
> +        return 1
> +
>      try:
> -        opts,args = getopt.getopt(sys.argv[1:], 'h', ['help',
> 'chroot', 'mount-hacks'])
> +        opts,args = getopt.getopt(sys.argv[1:], 'h', ['help',
> +                                                       'chroot',
> 'persist',
> +
> 'mount-hacks'])
>      except getopt.GetoptError, e:
>          usage(1)
>  
> -    rw = False
> +    img_type = None
>      chroot = False
> +    persist = False
>      mount_hacks = False
>      for o,a in opts:
>          if o in ('-h', '--help'):
> @@ -44,34 +125,121 @@ def main():
>              chroot = True
>          elif o in ('--mount-hacks', ):
>              mount_hacks = True
> +        elif o in ('--persist', ):
> +            """Option used to run a command in a spawned process."""
> +            persist = True
>  
>      if len(args) < 2:
>          usage(1)
>  
> -    isopath = args[0]
> +    liveos = args[0]
>      destmnt = args[1]
>  
> +    if not os.path.exists(liveos):
> +        print """\n     Exiting...
> +        %s is not a file, directory, or device.\n""" % liveos
> +        ecode = 1
> +        return
> +
> +    if stat.S_ISBLK(os.stat(liveos).st_mode):
> +        img_type = 'blk'
> +        imgloop = None
> +        overlayloop = None
> +    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
> +
>      command = args[2:]
>      verbose = not command
>  
> -    isomnt = tempfile.mkdtemp(prefix='livemnt-iso')
> -    squashmnt = tempfile.mkdtemp(prefix='livemnt-squash')
> -
> -    mountflags = ['loop', 'ro']
> -    mountflags_str = ','.join(mountflags)
> +    squashmnt = tempfile.mkdtemp(prefix='livemnt-squash-')
> +    squashloop = None
> +    imgloop = None
> +    losetup_args = ['/sbin/losetup', '-f', '--show']
>  
>      try:
> -        subprocess.check_call(['mount', '-o', mountflags_str,
> isopath, isomnt], stderr=sys.stderr)
> -        squash_img_path = os.path.join(isomnt, 'LiveOS',
> 'squashfs.img')
> -        subprocess.check_call(['mount', '-o', mountflags_str,
> squash_img_path, squashmnt], stderr=sys.stderr)
> -        ext3_img_path = os.path.join(squashmnt, 'LiveOS',
> 'ext3fs.img')
> -        subprocess.check_call(['mount', '-o', mountflags_str,
> ext3_img_path, destmnt], stderr=sys.stderr)
> +        if img_type is 'blk':
> +            call(['/bin/mount', liveos, liveosmnt])
> +        elif img_type is 'iso':
> +            liveosloop = rcall(losetup_args + [liveos]).rstrip()
> +            call(['/bin/mount', '-o', 'ro', liveosloop, liveosmnt])
> +
> +        squash_img = os.path.join(liveosdir, 'squashfs.img')
> +        if not os.path.exists(squash_img):
> +            ext3_img = os.path.join(liveosdir, 'ext3fs.img')
> +            if not os.path.exists(ext3_img):
> +                print """
> +                \r\tNo LiveOS was found on %s\n\t  Exiting...\n""" %
> liveos
> +                ecode = 1
> +                return
> +        else:
> +            squashloop = rcall(losetup_args + [squash_img]).rstrip()
> +            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()
> +            imgsize = rcall(['/sbin/blockdev', '--getsz',
> imgloop]).rstrip()
> +            files = os.listdir(liveosdir)
> +            overlay = None
> +            for f in files:
> +                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'
> +            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'
> +            call(['/sbin/dmsetup', '--noudevrules', '--noudevsync',
> +                 'create', dm_cow, '--table=0 %s snapshot %s %s p 8'
> %
> +                 (imgsize, imgloop, overlayloop)])
> +            call(['/bin/mount', os.path.join('/dev/mapper', dm_cow),
> destmnt])
> +            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()
> +                call(['/bin/mount', homeloop, homemnt])
> +            mntlive = os.path.join(destmnt, 'mnt', 'live')
> +            if not os.path.exists(mntlive):
> +                os.makedirs(mntlive)
> +            call(['/bin/mount', '--bind', liveosmnt, mntlive])
> +        elif img_type is 'iso':
> +            imgloop = rcall(losetup_args + [ext3_img]).rstrip()
> +            call(['/bin/mount', '-o', 'ro', imgloop, destmnt])
>  
>          if mount_hacks:
>              subprocess.check_call(['mount', '-t', 'proc', 'proc',
> os.path.join(destmnt, 'proc')], stderr=sys.stderr)
>              subprocess.check_call(['mount', '-t', 'tmpfs', 'tmpfs',
> os.path.join(destmnt, 'var', 'run')], stderr=sys.stderr)
>  
> -        if len(command) > 0:
> +        if len(command) > 0 and persist:
> +            args = []
> +            args.extend(command)
> +            live = ''
> +            if img_type is 'blk':
> +                live = 'live-'
> +            print """Starting process with this command line:
> +            \r%s\n  %s is %smounted.""" % (' '.join(command), liveos,
> live)
> +            p = subprocess.Popen(args, close_fds=True)
> +            print "Process id: %s" % p.pid
> +            ecode = p.returncode
> +        elif len(command) > 0:
>              args = ['chroot', destmnt]
>              args.extend(command)
>              ecode = subprocess.call(args, stdin=sys.stdin,
> stdout=sys.stdout, stderr=sys.stderr)
> @@ -79,21 +247,49 @@ 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:
> -            print "Starting subshell, press Ctrl-D to exit..."
> +            if dm_cow == 'live-ro':
> +                status = ' with NO LiveOS persistence,'
> +            else:
> +                status = ''
> +            print "Entering subshell,%s press Ctrl-D to exit..." %
> status
>              ecode = subprocess.call([os.environ['SHELL']],
> cwd=destmnt, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr)
>      finally:
> -        if verbose:
> -            print "Cleaning up..."
> -        if mount_hacks:
> -            subprocess.call(['umount', os.path.join(destmnt, 'var',
> 'run')])
> -            subprocess.call(['umount', os.path.join(destmnt,
> 'proc')])
> -        subprocess.call(['umount', destmnt])
> -        subprocess.call(['umount', squashmnt])
> -        os.rmdir(squashmnt)
> -        subprocess.call(['umount', isomnt])
> -        os.rmdir(isomnt)
> -        if verbose:
> -            print "Cleanup complete"
> +        call(['/bin/sync'])
> +        if not persist:
> +            if verbose:
> +                print """Cleaning up...
> +                Please wait if large files were written."""
> +            if mount_hacks:
> +                subprocess.call(['umount', os.path.join(destmnt,
> 'var', 'run')])
> +                subprocess.call(['umount', os.path.join(destmnt,
> 'proc')])
> +            if homemnt:
> +                call(['/bin/umount', homemnt])
> +                call(['/sbin/losetup', '-d', homeloop])
> +            if img_type is 'blk':
> +                if mntlive:
> +                    call(['/bin/umount', mntlive])
> +                    os.rmdir(mntlive)
> +            if os.path.ismount(destmnt):
> +                call(['/bin/umount', destmnt])
> +            if img_type is 'blk':
> +                if dm_cow:
> +                    call(['/sbin/dmsetup', '--noudevrules',
> '--noudevsync',
> +                          'remove', dm_cow])
> +                if overlayloop:
> +                    call(['/sbin/losetup', '-d', overlayloop])
> +            if imgloop:
> +                call(['/sbin/losetup', '-d', imgloop])
> +            if squashloop:
> +                call(['/bin/umount', squashloop])
> +                call(['/sbin/losetup', '-d', squashloop])
> +            call(['/bin/umount', liveosmnt])
> +            if not img_type is 'blk':
> +                call(['/sbin/losetup', '-d', liveosloop])
> +            os.rmdir(squashmnt)
> +            if not os.path.ismount(liveosmnt):
> +                os.rmdir(liveosmnt)
> +            if verbose:
> +                print "Cleanup complete"
>  
>      sys.exit(ecode)

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.fedoraproject.org/pipermail/livecd/attachments/20110331/8b5d974a/attachment.html 


More information about the livecd mailing list