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(a)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)