#!/bin/bash
# build date: 2011-02-12 00:45:56 -0500
#
# To unbundle and launch, bash this file.
#
export PS4='+(${LINENO}:${BASH_SOURCE}:${EUID}): 
        ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
TMPDIR=${TMPDIR:-/tmp}
cat >|${TMPDIR}/liveos-disk-to-iso <<'End of liveos-disk-to-iso'
#!/bin/bash
#  version date: 2011-02-11 23:14:14 -0500
#
#  liveos-disk-to-iso:  launches edit-liveos.py with optional common parameters
#
#  Copyright 2011, Sugar Labs
#  Frederick Grose <fgrose@sugarlabs.org>
#
#  Usage:
#       Designed to be called from SoaS-remix bundle with edit-liveos options.
#
#       You may uncomment and edit the set command below (at about line 86) to
#       control the options and arguments that will be passed to edit-liveos.py
#
#    When the SoaS-remix bundle is run, the packaged scripts will be unbundled
#    and copied to /tmp.  Then this liveos-disk-to-iso script is launched,
#    which in turn launches edit-liveos.py with the supplied arguments.
#
#  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
#  the Free Software Foundation; version 2 of the License.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU Library General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

export PS4='+(${LINENO}:${BASH_SOURCE}:${EUID}): 
        ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
TMPDIR=${TMPDIR:-/tmp}

# test su feature
su --session-command="pwd" 2>/dev/null 1>&2&
case $? in
    0)
        su_option=--session-command
        ;;
    *)
        su_option=--command
        ;;
esac

cleanup() {
    [[ -f ${TMPDIR}/liveos-disk-to-iso ]] && rm ${TMPDIR}/liveos-disk-to-iso
    [[ -f ${TMPDIR}/edit-liveos.py ]] && rm ${TMPDIR}/edit-liveos.py
    [[ -f ${TMPDIR}/fs2.py ]] && rm ${TMPDIR}/fs2.*
    [[ -f ${TMPDIR}/live2.py ]] && rm ${TMPDIR}/live2.*
    [[ -f ${TMPDIR}/creator2.py ]] && rm ${TMPDIR}/creator2.*
    [[ -d $SRCMNT ]] && umount $SRCMNT 2>/dev/null && rmdir $SRCMNT
}

exitclean() {
    echo -e "\n   Cleaning up to exit..."
    cleanup
    echo -e "   Waiting for devices to settle...\n"
    /sbin/udevadm settle
    sleep 5
    if [[ $1 -eq 0 ]]; then
        echo -e "   Done... Your SoaSremix is ready!\n"
    else
        echo -e "   Sorry... Your SoaSremix did not succeed.\n"
    fi
    exit "${1:-1}"
}

echo $1
SoaSremixPath="$1"
shift
cmdline_parms=("$@")
clparm_count=$#

# Example command line. Edit and uncomment the set statement below to suit.
# set -- -v -n SoaSremix -o /media/WD-ext4 -t /media/WD-ext4 -i /GPL \
#        -r /boot/olpc_build --builder fgrose /dev/live
echo -e "$*\n"

SRC=$(readlink -f "${@: -1}")
if [[ -b $SRC ]]; then
    SRCMNT=$(mktemp -d /media/srcdev.XXXXXX)
    mount $SRC $SRCMNT || { echo "$SRC could not be mounted"; exitclean 1 ;}
    trap exitclean SIGINT SIGTERM
    cp $SoaSremixPath ${SRCMNT}/LiveOS/
    umount $SRCMNT && rmdir $SRCMNT
fi

if [[ -n $cmdline_parms ]]; then
    if [[ ${@: -1} == ${cmdline_parms[@]: -1} ]] && (($clparm_count > 1)); then
        set " ${cmdline_parms[@]:0:$clparm_count-1} $SRC"
    elif  (($clparm_count == 1)); then
        set $SRC
    else
        set " ${@:1:$#-1} ${cmdline_parms[@]} $SRC"
    fi
fi
echo $*

su ${su_option}="${TMPDIR}/edit-liveos.py $* " root

exitCode=$?
if [[ $exitCode -gt 0 ]]; then
    echo -e "\n   PROBLEM: Building of SoaSremix failed.\n
    Exit code $exitCode was reported.  Exiting...\n"
fi

exitclean $exitCode
exit 0
End of liveos-disk-to-iso
chmod 777 ${TMPDIR}/liveos-disk-to-iso
cat >|${TMPDIR}/edit-liveos.py <<'End of edit-liveos.py'
#!/usr/bin/python -ttd
# version date: 2011-02-11 03:15:03 -0500
#
# edit-liveos: Edit a LiveOS to insert files or to clone an instance onto a new
#                   iso image file.
#
# Copyright 2009, Red Hat  Inc.
# Written by Perry Myers <pmyers at redhat.com> & David Huff <dhuff at redhat.com>
#   Cloning code 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
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

import os
import sys
import stat
import tempfile
import shutil
import subprocess
import optparse
import logging

from imgcreate.debug import *
from imgcreate.errors import *
#from imgcreate.fs import *
#from imgcreate.live import *
#from imgcreate.creator import *

from fs2 import *
from live2 import *
from creator2 import *

class ExistingSparseLoopbackDisk(SparseLoopbackDisk):
    """don't want to expand the disk"""
    def __init__(self, lofile, size):
        SparseLoopbackDisk.__init__(self, lofile, size)

    def create(self):
        #self.expand(create = True)
        LoopbackDisk.create(self)

class LiveImageEditor(LiveImageCreator):
    """class for editing LiveOS images.

    We need an instance of LiveImageCreator, however, we do not have a kickstart
    file and we may not need to create a new image.  We just want to reuse some
    of LiveImageCreators methods on an existing LiveOS image.

    """

    def __init__(self, name):
        """Initialize a LiveImageEditor instance.

        creates a dummy instance of LiveImageCreator
        We do not initialize any sub classes b/c we have no ks file.

        """
        self.name = name

        self.tmpdir = "/var/tmp"
        """The directory in which all temporary files will be created."""

        self.clone = False
        """Signals when to copy a running LiveOS image as base."""

        self._include = None
        """A string of file or directory paths to include in __copy_img_root."""

        self._builder = os.getlogin()
        """The name of the Remix builder for _branding.
        Default = os.getlogin()"""

        self.compress_type = None
        """mksquashfs compressor to use. Use 'None' to force reading of the
        existing image, or enter a -p --compress_type value to override the
        current compression or lack thereof. Compression type options vary with
        the version of the kernel and SquashFS used."""

        self.skip_compression = False
        """Controls whether to use squashfs to compress the image."""

        self.skip_minimize = False
        """Controls whether an image minimizing snapshot should be created."""

        self._isofstype = "iso9660"
        self.__isodir = None

        self._ImageCreator__builddir = None
        """working directory"""

#        self._ImageCreator_instroot = None
        """where the extfs.img is mounted for modification"""

        self._ImageCreator_outdir = None
        """where final iso gets written"""

        self._ImageCreator__bindmounts = []

#        self._LoopImageCreator__imagedir = None
        """dir for the extfs.img"""

        self._LoopImageCreator__blocksize = 4096
        self._LoopImageCreator__fslabel = None
        self._LoopImageCreator__instloop = None
        self._LoopImageCreator__fstype = None
        self._LoopImageCreator__image_size = None

        self.__instroot = None

        self._LiveImageCreatorBase__isodir = None
        """directory where the iso is staged"""

    # properties
    def __get_image(self):
        if self._LoopImageCreator__imagedir is None:
            self.__ensure_builddir()
            self._LoopImageCreator__imagedir = \
                tempfile.mkdtemp(dir = os.path.abspath(self.tmpdir),
                                                       prefix = self.name + "-")
        rtn = self._LoopImageCreator__imagedir + "/ext3fs.img"
        return rtn
    _image = property(__get_image)
    """The location of the filesystem image file."""

    def _get_fslabel(self):
        dev_null = os.open("/dev/null", os.O_WRONLY)
        try:
            out = subprocess.Popen(["/sbin/e2label", self._image],
                                   stdout = subprocess.PIPE,
                                   stderr = dev_null).communicate()[0]

            self._LoopImageCreator__fslabel = out.strip()

        except IOError, e:
            raise CreatorError("Failed to determine fsimage LABEL: %s" % e )
        finally:
            os.close(dev_null)

    def __ensure_builddir(self):
        if not self._ImageCreator__builddir is None:
            return

        try:
            self._ImageCreator__builddir = tempfile.mkdtemp(dir =  os.path.abspath(self.tmpdir),
                                               prefix = "edit-liveos-")
        except OSError, (err, msg):
            raise CreatorError("Failed create build directory in %s: %s" %
                               (self.tmpdir, msg))

    def _run_script(self, script):

        (fd, path) = tempfile.mkstemp(prefix = "script-",
                                          dir = self._instroot + "/tmp")

        logging.debug("copying script to install root: %s" % path)
        shutil.copy(os.path.abspath(script), path)
        os.close(fd)
        os.chmod(path, 0700)

        script = "/tmp/" + os.path.basename(path)

        try:
            subprocess.call([script], preexec_fn = self._chroot)
        except OSError, e:
            raise CreatorError("Failed to execute script %s, %s " % (script, e))
        finally:
            os.unlink(path)

    def mount(self, base_on, cachedir = None):
        """mount existing file system.

        We have to override mount b/c we many not be creating an new install
        root nor do we need to setup the file system, i.e., makedirs(/etc/,
        /boot, ...), nor do we want to overwrite fstab, or create selinuxfs.

        We also need to get some info about the image before we can mount it.

        base_on --  the <LIVEIMG.src> a LiveOS.iso file or an attached LiveOS
                    device, such as, /dev/live for a currently running image.

        cachedir -- a directory in which to store a Yum cache;
                    Not used in edit-liveos.

        """

        if not base_on:
            raise CreatorError("No base LiveOS image specified.")

        self.__ensure_builddir()

        self._ImageCreator_instroot = self._ImageCreator__builddir + "/install_root"
        self._LoopImageCreator__imagedir = self._ImageCreator__builddir + "/ex"
        self._ImageCreator_outdir = self._ImageCreator__builddir + "/out"

        makedirs(self._ImageCreator_instroot)
        makedirs(self._LoopImageCreator__imagedir)
        makedirs(self._ImageCreator_outdir)

        if self.clone:
            # Need to clone base_on into ext3fs.img at this point
            self._LoopImageCreator__fslabel = self.name
            self._base_on(base_on)
        else:
            LiveImageCreator._base_on(self, base_on)
            self._LoopImageCreator__fstype = get_fsvalue(self._image, 'TYPE')
            self._get_fslabel()

        self.fslabel = self._LoopImageCreator__fslabel
        self._LoopImageCreator__image_size = os.stat(self._image)[stat.ST_SIZE]

        self._LoopImageCreator__instloop = ExtDiskMount(
                ExistingSparseLoopbackDisk(self._image,
                                           self._LoopImageCreator__image_size),
                self._ImageCreator_instroot,
                self._fstype,
                self._LoopImageCreator__blocksize,
                self.fslabel,
                self.tmpdir)
        try:
            self._LoopImageCreator__instloop.mount()
        except MountError, e:
            raise CreatorError("Failed to loopback mount '%s' : %s" %
                               (self._image, e))

        cachesrc = cachedir or (self._ImageCreator__builddir + "/yum-cache")
        makedirs(cachesrc)

        for (f, dest) in [("/sys", None), ("/proc", None),
                          ("/dev/pts", None), ("/dev/shm", None),
                          (cachesrc, "/var/cache/yum")]:
            self._ImageCreator__bindmounts.append(BindChrootMount(f, self._instroot, dest))

        self._do_bindmounts()

        os.symlink("../proc/mounts", self._instroot + "/etc/mtab")

        self.__copy_img_root(base_on)
        self._brand(self._builder)

    def _base_on(self, base_on):
        """Clone the running LiveOS image as the basis for the new image."""

        self.__fstype = 'ext4'
        self.__image_size = 4096L * 1024 * 1024
        self.__blocksize = 4096

        self.__instloop = ExtDiskMount(SparseLoopbackDisk(self._image,
                                                          self.__image_size),
                                       self._instroot,
                                       self.__fstype,
                                       self.__blocksize,
                                       self.fslabel,
                                       self.tmpdir)
        try:
            self.__instloop.mount()
        except MountError, e:
            raise CreatorError("Failed to loopback mount '%s' : %s" %
                               (self._image, e))

        subprocess.call(['rsync', '-ptgorlHASx', '--specials',
                         '--include', '/*/',
                         '--exclude', '/etc/mtab',
                         '--exclude', '/etc/blkid/*',
                         '--exclude', '/dev/*',
                         '--exclude', '/proc/*',
                         '--exclude', '/home/*',
                         '--exclude', '/media/*',
                         '--exclude', '/mnt/live',
                         '--exclude', '/sys/*',
                         '--exclude', '/tmp/*',
                         '--exclude', '/.liveimg*',
                         '--exclude', '/.autofsck',
                         '/', self._instroot])
        subprocess.call(['sync'])

        self._ImageCreator__create_minimal_dev()

        self.__instloop.cleanup()


    def __copy_img_root(self, base_on):
        """helper function to copy root content of the base LiveIMG to 
        ISOdir"""

        ignore_list = ['ext3fs.img', 'squashfs.img', 'osmin.img', 'home.img',
                       'overlay-*']

        if self.clone:
            ignore_list.remove('home.img')
            includes = 'boot, /EFI, /syslinux, /LiveOS'
            if self._include:
                includes += ", " + self._include

            imgmnt = DiskMount(RawDisk(0, base_on), self._mkdtemp())
        else:
            imgmnt = DiskMount(LoopbackDisk(base_on, 0), self._mkdtemp())

        self._LiveImageCreatorBase__isodir = self._ImageCreator__builddir + "/iso"

        try:
            imgmnt.mount()
        except MountError, e:
            raise CreatorError("Failed to mount '%s' : %s" % (base_on, e))
        else:
            # include specified files or directories
            if self.clone:
                baseimg = os.path.join(imgmnt.mountdir, 'LiveOS',
                                       'squashfs.img')
                # 'self.compress_type = None' will force reading it from
                # base_on.
                if self.compress_type is None:
                    self.compress_type = squashfs_compression_type(baseimg)
                    if self.compress_type == 'undetermined':
                        # 'gzip' for compatibility with older versions.
                        self.compress_type = 'gzip'

                dst = self._LiveImageCreatorBase__isodir
                print includes
                for fd in includes.split(', /'):
                    src = os.path.join(imgmnt.mountdir, fd)
                    if os.path.isfile(src):
                        shutil.copy2(src, os.path.join(dst, fd))
                    elif os.path.isdir(src):
                        shutil.copytree(src, os.path.join(dst, fd),
                                        symlinks=True,
                                        ignore=shutil.ignore_patterns(
                                            *ignore_list))
            else:
                #copy over everything but squashfs.img or ext3fs.img
                shutil.copytree(imgmnt.mountdir,
                                self._LiveImageCreatorBase__isodir,
                                ignore=shutil.ignore_patterns(*ignore_list))
            subprocess.call(['sync'])
        finally:
            imgmnt.cleanup()


    def _brand (self, _builder):
        """Adjust the image branding to show its variation from original
        source by builder and build date."""

        self.fslabel = self.name
        dt = time.strftime('%d-%b-%Y')

        lst = ['isolinux/isolinux.cfg', 'syslinux/syslinux.cfg',
               'syslinux/extlinux.conf']
        for f in lst:
            fpath = os.path.join(self._LiveImageCreatorBase__isodir, f)
            if os.path.exists(fpath):
                break

        # Get build name from boot configuration file.
        try:
            cfgf = open(fpath, 'r')
        except IOError, e:
            raise CreatorError("Failed to open '%s' : %s" % (fpath, e))
        else:
            for line in cfgf:
                i = line.find('Welcome to ')
                if i > -1:
                    release = line[i+11:-2]
                    break
            cfgf.close()

        ntext = dt.translate(None, '-') + '-' + _builder + '-Remix-' + release

        # Update fedora-release message with Remix details.
        releasefiles = '/etc/fedora-release, /etc/generic-release'
        if self._releasefile:
            releasefiles += ', ' + self._releasefile
        for fn in releasefiles.split(', '):
            if os.path.exists(fn):
                try:
                    with open(self._instroot + fn, 'r') as f:
                        text = ntext + '\n' + f.read()
                        open(f.name, 'w').write(text)
                except IOError, e:
                    raise CreatorError("Failed to open or write '%s' : %s" %
                                       (f.name, e))

        self._releasefile = ntext
        self.name += '-' + os.uname()[4] + '-' + time.strftime('%Y%m%d.%H%M')


    def _configure_bootloader(self, isodir):
        """Restore the boot configuration files for an iso image boot."""

        bootfolder = os.path.join(isodir, 'isolinux')
        oldpath = os.path.join(isodir, 'syslinux')
        if os.path.exists(oldpath):
            os.rename(oldpath, bootfolder)

        cfgf = os.path.join(bootfolder, 'isolinux.cfg')
        for f in ['syslinux.cfg', 'extlinux.conf']:
            src = os.path.join(bootfolder, f)
            if os.path.exists(src):
                os.rename(src, cfgf)

        args = ['/bin/sed', '-i',
                '-e', 's/Welcome to .*/Welcome to ' + self._releasefile + '!/',
                '-e', 's/root=[^ ]*/root=live:CDLABEL=' + self.name + '/',
                '-e', 's/rootfstype=[^ ]* [^ ]*/rootfstype=auto ro/',
                '-e', 's/liveimg .* quiet/liveimg quiet/', cfgf]

        dev_null = os.open("/dev/null", os.O_WRONLY)
        try:
            subprocess.Popen(args,
                             stdout = subprocess.PIPE,
                             stderr = dev_null).communicate()[0]
            return 0

        except IOError, e:
            raise CreatorError("Failed to configure bootloader file: %s" % e)
            return 1
        finally:
            os.close(dev_null)

def parse_options(args):
    parser = optparse.OptionParser(usage = """
       %prog [-n=<name>]
                      [-o=<output>]
                      [-s=<script.sh>]
                      [-t=<tmpdir>]
                      [-e=<excludes>]
                      [-f=<exclude-file>]
                      [-i=<includes>]
                      [-r=<releasefile>]
                      [-b=<builder>]
                      [--clone]
                      [-c=<compress_type>]
                      [--skip-compression]
                      [--skip-minimize]
                      <LIVEIMG.src>""")

    parser.add_option("-n", "--name", type="string", dest="name",
                      help="name of new LiveOS (don't include .iso, it will "
                           "be added)")

    parser.add_option("-o", "--output", type="string", dest="output",
                      help="specify directory for new iso file.")

    parser.add_option("-s", "--script", type="string", dest="script",
                      help="specify script to run chrooted in the LiveOS "
                           "fsimage")

    parser.add_option("-t", "--tmpdir", type="string",
                      dest="tmpdir", default="/var/tmp",
                      help="Temporary directory to use (default: /var/tmp)")

    parser.add_option("-e", "--exclude", type="string", dest="exclude",
                      help="Specify directory or file patterns to be excluded "
                           "from the rsync copy of the filesystem.")

    parser.add_option("-f", "--exclude-file", type="string",
                      dest="exclude_file",
                      help="Specify a file containing file patterns to be "
                           "excluded from the rsync copy of the filesystem.")

    parser.add_option("-i", "--include", type="string", dest="include",
                      help="Specify directory or file patterns to be included "
                           "in copy_img_root.")

    parser.add_option("-r", "--releasefile", type="string", dest="releasefile",
                      help="Specify release file/s for branding.")

    parser.add_option("-b", "--builder", type="string",
                      dest="builder", default=os.getlogin(),
                      help="Specify the builder of a Remix.")

    parser.add_option("", "--clone", action="store_true", dest="clone",
                      help="Specify that source image is LiveOS block device.")

    parser.add_option("-c", "--compress_type", type="string",
                      dest="compress_type",
                      help="Specify the compression type for SquashFS. Will "
                           "override the current compression or lack thereof.")

    parser.add_option("", "--skip-compression", action="store_true",
                      dest="skip_compression", default=False,
                      help="Specify no compression of filesystem, ext3fs.img")

    parser.add_option("", "--skip-minimize", action="store_true",
                      dest="skip_minimize", default=False,
                      help="Specify no osmin.img minimal snapshot.")

    setup_logging(parser)

    (options, args) = parser.parse_args()

    if len(args) != 1:
        parser.print_usage()
        sys.exit(1)

    print args[0]

    return (args[0], options)

def get_fsvalue(filesystem, tag):
    dev_null = os.open('/dev/null', os.O_WRONLY)
    args = ['/sbin/blkid', '-s', tag, '-o', 'value', filesystem]
    try:
        fs_type = subprocess.Popen(args,
                               stdout=subprocess.PIPE,
                               stderr=dev_null).communicate()[0]
    except IOError, e:
        raise CreatorError("Failed to determine fs %s: %s" % value, e )
    finally:
        os.close(dev_null)

    return fs_type.rstrip()

def rebuild_iso_symlinks(isodir):
    # remove duplicate files and rebuild symlinks to reduce iso size
    efi_vmlinuz = "%s/EFI/boot/vmlinuz0" % isodir
    isolinux_vmlinuz = "%s/isolinux/vmlinuz0" % isodir
    efi_initrd = "%s/EFI/boot/initrd0.img" % isodir
    isolinux_initrd = "%s/isolinux/initrd0.img" % isodir

    if os.path.exists(efi_vmlinuz):
        os.remove(efi_vmlinuz)
        os.remove(efi_initrd)
        os.symlink(isolinux_vmlinuz,efi_vmlinuz)
        os.symlink(isolinux_initrd,efi_initrd)

def main():
    # LiveOS set to <LIVEIMG.src>
    (LiveOS, options) = parse_options(sys.argv[1:])

    if os.geteuid () != 0:
        print >> sys.stderr, "You must run edit-liveos as root"
        return 1

    if stat.S_ISBLK(os.stat(LiveOS).st_mode):
        name = get_fsvalue(LiveOS, 'LABEL') + '.edited'
    elif options.name and options.name != os.path.basename(LiveOS):
        name = options.name
    else:
        name = os.path.basename(LiveOS) + ".edited"

    if options.output:
        output = options.output
    else:
        output = os.path.dirname(LiveOS)
        if output == '/dev':
            output = options.tmpdir

    editor = LiveImageEditor(name)
    editor._exclude = options.exclude
    editor._exclude_file = options.exclude_file
    editor._include = options.include
    editor.clone = options.clone
    editor.tmpdir = options.tmpdir
    editor._builder = options.builder
    editor._releasefile = options.releasefile
    editor.compress_type = options.compress_type
    editor.skip_compression = options.skip_compression
    editor.skip_minimize = options.skip_minimize

    try:
        editor.mount(LiveOS, cachedir = None)
        editor._configure_bootloader(editor._LiveImageCreatorBase__isodir)
#        if options.script:
#            print "Running edit script '%s'" % options.script
#            editor._run_script(options.script)
#        else:
#            print "Launching shell. Exit to continue."
#            print "----------------------------------"
#            editor.launch_shell()
        rebuild_iso_symlinks(editor._LiveImageCreatorBase__isodir)
        editor.unmount()
        editor.package(output)
        logging.info("%s.iso saved to %s"  % (editor.name, output))
    except CreatorError, e:
        logging.error(u"Error editing LiveOS : %s" % e)
        return 1
    finally:
        editor.cleanup()

    return 0

if __name__ == "__main__":
    sys.exit(main())

arch = rpmUtils.arch.getBaseArch()
if arch in ("i386", "x86_64"):
    LiveImageCreator = x86LiveImageCreator
elif arch in ("ppc",):
    LiveImageCreator = ppcLiveImageCreator
elif arch in ("ppc64",):
    LiveImageCreator = ppc64LiveImageCreator
else:
    raise CreatorError("Architecture not supported!")
End of edit-liveos.py
chmod 777 ${TMPDIR}/edit-liveos.py
cat >|${TMPDIR}/live2.py <<'End of live2.py'
#
# live.py : LiveImageCreator class for creating Live CD images
#
# Copyright 2007, Red Hat  Inc.
#
# 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
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

import os
import os.path
import glob
import shutil
import subprocess
import logging

from imgcreate.errors import *
from fs2 import *
from creator2 import *

class LiveImageCreatorBase(LoopImageCreator):
    """A base class for LiveCD image creators.

    This class serves as a base class for the architecture-specific LiveCD
    image creator subclass, LiveImageCreator.

    LiveImageCreator creates a bootable ISO containing the system image,
    bootloader, bootloader configuration, kernel and initramfs.

    """

    def __init__(self, ks, name, fslabel=None, releasever=None):
        """Initialise a LiveImageCreator instance.

        This method takes the same arguments as LoopImageCreator.__init__().

        """
        LoopImageCreator.__init__(self, ks, name, fslabel=fslabel, releasever=releasever)

        self.compress_type = "xz"
        """mksquashfs compressor to use."""

        self.skip_compression = False
        """Controls whether to use squashfs to compress the image."""

        self.skip_minimize = False
        """Controls whether an image minimizing snapshot should be created.

        This snapshot can be used when copying the system image from the ISO in
        order to minimize the amount of data that needs to be copied; simply,
        it makes it possible to create a version of the image's filesystem with
        no spare space.

        """

        self._timeout = kickstart.get_timeout(self.ks, 10)
        """The bootloader timeout from kickstart."""

        self._default_kernel = kickstart.get_default_kernel(self.ks, "kernel")
        """The default kernel type from kickstart."""

        self.__isodir = None

        self.__modules = ["=ata", "sym53c8xx", "aic7xxx", "=usb", "=firewire", "=mmc", "=pcmcia", "mptsas", "udf"]
        self.__modules.extend(kickstart.get_modules(self.ks))

        self._isofstype = "iso9660"

    #
    # Hooks for subclasses
    #
    def _configure_bootloader(self, isodir):
        """Create the architecture specific booloader configuration.

        This is the hook where subclasses must create the booloader
        configuration in order to allow a bootable ISO to be built.

        isodir -- the directory where the contents of the ISO are to be staged

        """
        raise CreatorError("Bootloader configuration is arch-specific, "
                           "but not implemented for this arch!")

    def _get_kernel_options(self):
        """Return a kernel options string for bootloader configuration.

        This is the hook where subclasses may specify a set of kernel options
        which should be included in the images bootloader configuration.

        A sensible default implementation is provided.

        """
        r = kickstart.get_kernel_args(self.ks)
        if os.path.exists(self._instroot + "/usr/bin/rhgb"):
            r += " rhgb"
        if os.path.exists(self._instroot + "/usr/bin/plymouth"):
            r += " rhgb"
        return r
        
    def _get_mkisofs_options(self, isodir):
        """Return the architecture specific mkisosfs options.

        This is the hook where subclasses may specify additional arguments to
        mkisofs, e.g. to enable a bootable ISO to be built.

        By default, an empty list is returned.

        """
        return []

    #
    # Helpers for subclasses
    #
    def _has_checkisomd5(self):
        """Check whether checkisomd5 is available in the install root."""
        def exists(instroot, path):
            return os.path.exists(instroot + path)

        if (exists(self._instroot, "/usr/lib/anaconda-runtime/checkisomd5") or
            exists(self._instroot, "/usr/bin/checkisomd5")):
            return True

        return False

    #
    # Actual implementation
    #
    def _base_on(self, base_on):
        """helper function to extract ext3 file system from a live CD ISO"""
        isoloop = DiskMount(LoopbackDisk(base_on, 0), self._mkdtemp())

        try:
            isoloop.mount()
        except MountError, e:
            raise CreatorError("Failed to loopback mount '%s' : %s" %
                               (base_on, e))

        # legacy LiveOS filesystem layout support, remove for F9 or F10
        if os.path.exists(isoloop.mountdir + "/squashfs.img"):
            squashimg = isoloop.mountdir + "/squashfs.img"
        else:
            squashimg = isoloop.mountdir + "/LiveOS/squashfs.img"
            
        squashloop = DiskMount(LoopbackDisk(squashimg, 0), self._mkdtemp(), "squashfs")

        # 'self.compress_type = None' will force reading it from base_on.
        if self.compress_type is None:
            self.compress_type = squashfs_compression_type(squashimg)
            if self.compress_type == 'undetermined':
                # Default to 'gzip' for compatibility with older versions.
                self.compress_type = 'gzip'
        try:
            if not squashloop.disk.exists():
                raise CreatorError("'%s' is not a valid live CD ISO : "
                                   "squashfs.img doesn't exist" % base_on)

            try:
                squashloop.mount()
            except MountError, e:
                raise CreatorError("Failed to loopback mount squashfs.img "
                                   "from '%s' : %s" % (base_on, e))

            # legacy LiveOS filesystem layout support, remove for F9 or F10
            if os.path.exists(squashloop.mountdir + "/os.img"):
                os_image = squashloop.mountdir + "/os.img"
            else:
                os_image = squashloop.mountdir + "/LiveOS/ext3fs.img"

            if not os.path.exists(os_image):
                raise CreatorError("'%s' is not a valid live CD ISO : neither "
                                   "LiveOS/ext3fs.img nor os.img exist" %
                                   base_on)

            try:
                shutil.copyfile(os_image, self._image)
            except IOError, e:
                raise CreatorError("Failed to copy base live image to %s for modification: %s" %(self._image, e))
        finally:
            squashloop.cleanup()
            isoloop.cleanup()

    def _mount_instroot(self, base_on = None):
        LoopImageCreator._mount_instroot(self, base_on)
        self.__write_initrd_conf(self._instroot + "/etc/sysconfig/mkinitrd")
        self.__write_dracut_conf(self._instroot + "/etc/dracut.conf")

    def _unmount_instroot(self):
        self.__restore_file(self._instroot + "/etc/sysconfig/mkinitrd")
        self.__restore_file(self._instroot + "/etc/dracut.conf")
        LoopImageCreator._unmount_instroot(self)

    def __ensure_isodir(self):
        if self.__isodir is None:
            self.__isodir = self._mkdtemp("iso-")
        return self.__isodir

    def _create_bootconfig(self):
        """Configure the image so that it's bootable."""
        self._configure_bootloader(self.__ensure_isodir())

    def _get_post_scripts_env(self, in_chroot):
        env = LoopImageCreator._get_post_scripts_env(self, in_chroot)

        if not in_chroot:
            env["LIVE_ROOT"] = self.__ensure_isodir()

        return env

    def __extra_filesystems(self):
        return "squashfs ext4 ext3 ext2 vfat msdos ";

    def __extra_drivers(self):
        retval = "sr_mod sd_mod ide-cd cdrom "
        for module in self.__modules:
            if module == "=usb":
                retval = retval + "ehci_hcd uhci_hcd ohci_hcd "
                retval = retval + "usb_storage usbhid "
            elif module == "=firewire":
                retval = retval + "firewire-sbp2 firewire-ohci "
                retval = retval + "sbp2 ohci1394 ieee1394 "
            elif module == "=mmc":
                retval = retval + "mmc_block sdhci sdhci-pci "
            elif module == "=pcmcia":
                retval = retval + "pata_pcmcia "
            else:
                retval = retval + module + " "
        return retval

    def __restore_file(self,path):
        try:
            os.unlink(path)
        except:
            pass
        if os.path.exists(path + '.rpmnew'):
            os.rename(path + '.rpmnew', path)

    def __write_initrd_conf(self, path):
        if not os.path.exists(os.path.dirname(path)):
            makedirs(os.path.dirname(path))
        f = open(path, "a")
        f.write('LIVEOS="yes"\n')
        f.write('PROBE="no"\n')
        f.write('MODULES+="' + self.__extra_filesystems() + '"\n')
        f.write('MODULES+="' + self.__extra_drivers() + '"\n')
        f.close()

    def __write_dracut_conf(self, path):
        if not os.path.exists(os.path.dirname(path)):
            makedirs(os.path.dirname(path))
        f = open(path, "a")
        f.write('filesystems+="' + self.__extra_filesystems() + ' "\n')
        f.write('drivers+="' + self.__extra_drivers() + ' "\n')
        f.close()

    def __create_iso(self, isodir):
        iso = self._outdir + "/" + self.name + ".iso"

        args = ["/usr/bin/mkisofs",
                "-J", "-r",
                "-hide-rr-moved", "-hide-joliet-trans-tbl",
                "-V", self.fslabel,
                "-o", iso]

        args.extend(self._get_mkisofs_options(isodir))
        if self._isofstype == "udf":
            args.append("-allow-limited-size")

        args.append(isodir)

        if subprocess.call(args) != 0:
            raise CreatorError("ISO creation failed!")

        if os.path.exists("/usr/bin/isohybrid"):
            subprocess.call(["/usr/bin/isohybrid", iso])

        self.__implant_md5sum(iso)

    def __implant_md5sum(self, iso):
        """Implant an isomd5sum."""
        if os.path.exists("/usr/bin/implantisomd5"):
            implantisomd5 = "/usr/bin/implantisomd5"
        elif os.path.exists("/usr/lib/anaconda-runtime/implantisomd5"):
            implantisomd5 = "/usr/lib/anaconda-runtime/implantisomd5"
        else:
            logging.warn("isomd5sum not installed; not setting up mediacheck")
            return
            
        subprocess.call([implantisomd5, iso])

    def _stage_final_image(self):
        try:
            makedirs(self.__ensure_isodir() + "/LiveOS")

            self._resparse()

            if not self.skip_minimize:
                create_image_minimizer(self.__isodir + "/LiveOS/osmin.img",
                                       self._image, self.compress_type,
                                       tmpdir = self.tmpdir)

            if self.skip_compression:
                shutil.move(self._image, self.__isodir + "/LiveOS/ext3fs.img")
                if os.stat(self.__isodir + "/LiveOS/ext3fs.img").st_size >= 4*1024*1024*1024:
                    self._isofstype = "udf"
                    logging.warn("Switching to UDF due to size of LiveOS/ext3fs.img")
            else:
                makedirs(os.path.join(os.path.dirname(self._image), "LiveOS"))
                shutil.move(self._image,
                            os.path.join(os.path.dirname(self._image),
                                         "LiveOS", "ext3fs.img"))
                mksquashfs(os.path.dirname(self._image),
                           self.__isodir + "/LiveOS/squashfs.img",
                           self.compress_type)
                if os.stat(self.__isodir + "/LiveOS/squashfs.img").st_size >= 4*1024*1024*1024:
                    self._isofstype = "udf"
                    logging.warn("Switching to UDF due to size of LiveOS/squashfs.img")


            self.__create_iso(self.__isodir)
        finally:
            shutil.rmtree(self.__isodir, ignore_errors = True)
            self.__isodir = None

class x86LiveImageCreator(LiveImageCreatorBase):
    """ImageCreator for x86 machines"""
    def _get_mkisofs_options(self, isodir):
        return [ "-b", "isolinux/isolinux.bin",
                 "-c", "isolinux/boot.cat",
                 "-no-emul-boot", "-boot-info-table",
                 "-boot-load-size", "4" ]

    def _get_required_packages(self):
        return ["syslinux"] + LiveImageCreatorBase._get_required_packages(self)

    def _get_isolinux_stanzas(self, isodir):
        return ""

    def __find_syslinux_menu(self):
        for menu in ("vesamenu.c32", "menu.c32"):
            for dir in ("/usr/lib/syslinux/", "/usr/share/syslinux/"):
                if os.path.isfile(self._instroot + dir + menu):
                    return menu

        raise CreatorError("syslinux not installed : "
                           "no suitable *menu.c32 found")

    def __find_syslinux_mboot(self):
        #
        # We only need the mboot module if we have any xen hypervisors
        #
        if not glob.glob(self._instroot + "/boot/xen.gz*"):
            return None

        return "mboot.c32"

    def __copy_syslinux_files(self, isodir, menu, mboot = None):
        files = ["isolinux.bin", menu]
        if mboot:
            files += [mboot]

        for f in files:
            if os.path.exists(self._instroot + "/usr/lib/syslinux/" + f):
                path = self._instroot + "/usr/lib/syslinux/" + f
            elif os.path.exists(self._instroot + "/usr/share/syslinux/" + f):
                path = self._instroot + "/usr/share/syslinux/" + f
            if not os.path.isfile(path):
                raise CreatorError("syslinux not installed : "
                                   "%s not found" % path)

            shutil.copy(path, isodir + "/isolinux/")

    def __copy_syslinux_background(self, isodest):
        background_path = self._instroot + \
                          "/usr/share/anaconda/boot/syslinux-vesa-splash.jpg"

        if not os.path.exists(background_path):
            # fallback to F13 location
            background_path = self._instroot + \
                              "/usr/lib/anaconda-runtime/syslinux-vesa-splash.jpg"

            if not os.path.exists(background_path):
                return False

        shutil.copyfile(background_path, isodest)

        return True

    def __copy_kernel_and_initramfs(self, isodir, version, index):
        bootdir = self._instroot + "/boot"

        shutil.copyfile(bootdir + "/vmlinuz-" + version,
                        isodir + "/isolinux/vmlinuz" + index)

        isDracut = False
        if os.path.exists(bootdir + "/initramfs-" + version + ".img"):
            shutil.copyfile(bootdir + "/initramfs-" + version + ".img",
                            isodir + "/isolinux/initrd" + index + ".img")
            isDracut = True
        else:
            shutil.copyfile(bootdir + "/initrd-" + version + ".img",
                            isodir + "/isolinux/initrd" + index + ".img")

        is_xen = False
        if os.path.exists(bootdir + "/xen.gz-" + version[:-3]):
            shutil.copyfile(bootdir + "/xen.gz-" + version[:-3],
                            isodir + "/isolinux/xen" + index + ".gz")
            is_xen = True

        return (is_xen, isDracut)

    def __is_default_kernel(self, kernel, kernels):
        if len(kernels) == 1:
            return True

        if kernel == self._default_kernel:
            return True

        if kernel.startswith("kernel-") and kernel[7:] == self._default_kernel:
            return True

        return False

    def __get_basic_syslinux_config(self, **args):
        return """
default %(menu)s
timeout %(timeout)d

%(background)s
menu title Welcome to %(name)s!
menu color border 0 #ffffffff #00000000
menu color sel 7 #ffffffff #ff000000
menu color title 0 #ffffffff #00000000
menu color tabmsg 0 #ffffffff #00000000
menu color unsel 0 #ffffffff #00000000
menu color hotsel 0 #ff000000 #ffffffff
menu color hotkey 7 #ffffffff #ff000000
menu color timeout_msg 0 #ffffffff #00000000
menu color timeout 0 #ffffffff #00000000
menu color cmdline 0 #ffffffff #00000000
menu hidden
menu hiddenrow 5
""" % args

    def __get_image_stanza(self, is_xen, isDracut, **args):
        if isDracut:
            args["rootlabel"] = "live:CDLABEL=%(fslabel)s" % args
        else:
            args["rootlabel"] = "CDLABEL=%(fslabel)s" % args

        if not is_xen:
            template = """label %(short)s
  menu label %(long)s
  kernel vmlinuz%(index)s
  append initrd=initrd%(index)s.img root=%(rootlabel)s rootfstype=%(isofstype)s %(liveargs)s %(xdriver)s %(extra)s
"""
        else:
            template = """label %(short)s
  menu label %(long)s
  kernel mboot.c32
  append xen%(index)s.gz --- vmlinuz%(index)s root=%(rootlabel)s rootfstype=%(isofstype)s %(liveargs)s %(xdriver)s %(extra)s --- initrd%(index)s.img
"""
        return template % args

    def __get_image_stanzas(self, isodir):
        versions = []
        kernels = self._get_kernel_versions()
        for kernel in kernels:
            for version in kernels[kernel]:
                versions.append(version)

        kernel_options = self._get_kernel_options()

        checkisomd5 = self._has_checkisomd5()

        cfg = ""

        index = "0"
        for version in versions:
            (is_xen, isDracut) = self.__copy_kernel_and_initramfs(isodir, version, index)
            if index == "0":
                self._isDracut = isDracut

            default = self.__is_default_kernel(kernel, kernels)

            if default:
                long = "Boot"
            elif kernel.startswith("kernel-"):
                long = "Boot %s(%s)" % (self.name, kernel[7:])
            else:
                long = "Boot %s(%s)" % (self.name, kernel)

            # Basic video driver
            basic = "system with basic video driver"
            xdriver = "xdriver=vesa nomodeset"


            # tell dracut not to ask for LUKS passwords or activate mdraid sets
            if isDracut:
                kern_opts = kernel_options + " rd.luks=0 rd.md=0 rd.dm=0"
            else:
                kern_opts = kernel_options

            cfg += self.__get_image_stanza(is_xen, isDracut,
                                           fslabel = self.fslabel,
                                           isofstype = "auto",
                                           liveargs = kern_opts,
                                           long = long,
                                           short = "linux" + index,
                                           basicvideo = "",
                                           xdriver = "",
                                           extra = "",
                                           index = index)

            if default:
                cfg += "menu default\n"

            cfg += self.__get_image_stanza(is_xen, isDracut,
                                           fslabel = self.fslabel,
                                           isofstype = "auto",
                                           liveargs = kern_opts,
                                           long = long + " (Basic Video)",
                                           short = "linux" + index,
                                           basicvideo = basic,
                                           xdriver = xdriver,
                                           extra = "",
                                           index = index)

            if checkisomd5:
                cfg += self.__get_image_stanza(is_xen, isDracut,
                                               fslabel = self.fslabel,
                                               isofstype = "auto",
                                               liveargs = kern_opts,
                                               long = "Verify and " + long,
                                               short = "rd.live.check" + index,
                                               basicvideo = "",
                                               xdriver = "",
                                               extra = "rd.live.check",
                                               index = index)

            index = str(int(index) + 1)

        return cfg

    def __get_memtest_stanza(self, isodir):
        memtest = glob.glob(self._instroot + "/boot/memtest86*")
        if not memtest:
            return ""

        shutil.copyfile(memtest[0], isodir + "/isolinux/memtest")

        return """label memtest
  menu label Memory Test
  kernel memtest
"""

    def __get_local_stanza(self, isodir):
        return """label local
  menu label Boot from local drive
  localboot 0xffff
"""

    def _configure_syslinux_bootloader(self, isodir):
        """configure the boot loader"""
        makedirs(isodir + "/isolinux")

        menu = self.__find_syslinux_menu()

        self.__copy_syslinux_files(isodir, menu,
                                   self.__find_syslinux_mboot())

        background = ""
        if self.__copy_syslinux_background(isodir + "/isolinux/splash.jpg"):
            background = "menu background splash.jpg"

        cfg = self.__get_basic_syslinux_config(menu = menu,
                                               background = background,
                                               name = self.name,
                                               timeout = self._timeout * 10)

        cfg += self.__get_image_stanzas(isodir)
        cfg += self.__get_memtest_stanza(isodir)
        cfg += self.__get_local_stanza(isodir)
        cfg += self._get_isolinux_stanzas(isodir)

        cfgf = open(isodir + "/isolinux/isolinux.cfg", "w")
        cfgf.write(cfg)
        cfgf.close()

    def __copy_efi_files(self, isodir):
        if not os.path.exists(self._instroot + "/boot/efi/EFI/redhat/grub.efi"):
            return False
        shutil.copy(self._instroot + "/boot/efi/EFI/redhat/grub.efi",
                    isodir + "/EFI/boot/grub.efi")
        shutil.copy(self._instroot + "/boot/grub/splash.xpm.gz",
                    isodir + "/EFI/boot/splash.xpm.gz")

        return True

    def __get_basic_efi_config(self, **args):
        return """
default=0
splashimage=/EFI/boot/splash.xpm.gz
timeout %(timeout)d
hiddenmenu

""" %args

    def __get_efi_image_stanza(self, **args):
        if self._isDracut:
            args["rootlabel"] = "live:LABEL=%(fslabel)s" % args
        else:
            args["rootlabel"] = "CDLABEL=%(fslabel)s" % args
        return """title %(long)s
  kernel /EFI/boot/vmlinuz%(index)s root=%(rootlabel)s rootfstype=%(isofstype)s %(liveargs)s %(extra)s
  initrd /EFI/boot/initrd%(index)s.img
""" %args

    def __get_efi_image_stanzas(self, isodir, name):
        # FIXME: this only supports one kernel right now...

        kernel_options = self._get_kernel_options()
        checkisomd5 = self._has_checkisomd5()

        cfg = ""

        for index in range(0, 9):
            # we don't support xen kernels
            if os.path.exists("%s/EFI/boot/xen%d.gz" %(isodir, index)):
                continue
            cfg += self.__get_efi_image_stanza(fslabel = self.fslabel,
                                               isofstype = "auto",
                                               liveargs = kernel_options,
                                               long = name,
                                               extra = "", index = index)
            if checkisomd5:
                cfg += self.__get_efi_image_stanza(fslabel = self.fslabel,
                                                   isofstype = "auto",
                                                   liveargs = kernel_options,
                                                   long = "Verify and Boot " + name,
                                                   extra = "rd.live.check",
                                                   index = index)
            break

        return cfg

    def _configure_efi_bootloader(self, isodir):
        """Set up the configuration for an EFI bootloader"""
        makedirs(isodir + "/EFI/boot")

        if not self.__copy_efi_files(isodir):
            shutil.rmtree(isodir + "/EFI")
            return

        for f in os.listdir(isodir + "/isolinux"):
            os.link("%s/isolinux/%s" %(isodir, f),
                    "%s/EFI/boot/%s" %(isodir, f))


        cfg = self.__get_basic_efi_config(name = self.name,
                                          timeout = self._timeout)
        cfg += self.__get_efi_image_stanzas(isodir, self.name)

        cfgf = open(isodir + "/EFI/boot/grub.conf", "w")
        cfgf.write(cfg)
        cfgf.close()

        # first gen mactel machines get the bootloader name wrong apparently
        if rpmUtils.arch.getBaseArch() == "i386":
            os.link(isodir + "/EFI/boot/grub.efi", isodir + "/EFI/boot/boot.efi")
            os.link(isodir + "/EFI/boot/grub.conf", isodir + "/EFI/boot/boot.conf")

        # for most things, we want them named boot$efiarch
        efiarch = {"i386": "ia32", "x86_64": "x64"}
        efiname = efiarch[rpmUtils.arch.getBaseArch()]
        os.rename(isodir + "/EFI/boot/grub.efi", isodir + "/EFI/boot/boot%s.efi" %(efiname,))
        os.link(isodir + "/EFI/boot/grub.conf", isodir + "/EFI/boot/boot%s.conf" %(efiname,))


    def _configure_bootloader(self, isodir):
        self._configure_syslinux_bootloader(isodir)
        self._configure_efi_bootloader(isodir)

class ppcLiveImageCreator(LiveImageCreatorBase):
    def _get_mkisofs_options(self, isodir):
        return [ "-hfs", "-no-desktop", "-part",
                 "-map", isodir + "/ppc/mapping",
                 "-hfs-bless", isodir + "/ppc/mac",
                 "-hfs-volid", self.fslabel ]

    def _get_required_packages(self):
        return ["yaboot"] + \
               LiveImageCreatorBase._get_required_packages(self)

    def _get_excluded_packages(self):
        # kind of hacky, but exclude memtest86+ on ppc so it can stay in cfg
        return ["memtest86+"] + \
               LiveImageCreatorBase._get_excluded_packages(self)

    def __copy_boot_file(self, destdir, file):
        for dir in ["/usr/share/ppc64-utils",
                    "/usr/lib/anaconda-runtime/boot"]:
            path = self._instroot + dir + "/" + file
            if not os.path.exists(path):
                continue
            
            makedirs(destdir)
            shutil.copy(path, destdir)
            return

        raise CreatorError("Unable to find boot file " + file)

    def __kernel_bits(self, kernel):
        testpath = (self._instroot + "/lib/modules/" +
                    kernel + "/kernel/arch/powerpc/platforms")

        if not os.path.exists(testpath):
            return { "32" : True, "64" : False }
        else:
            return { "32" : False, "64" : True }

    def __copy_kernel_and_initramfs(self, destdir, version):
        isDracut = False
        bootdir = self._instroot + "/boot"

        makedirs(destdir)

        shutil.copyfile(bootdir + "/vmlinuz-" + version,
                        destdir + "/vmlinuz")

        if os.path.exists(bootdir + "/initramfs-" + version + ".img"):
            shutil.copyfile(bootdir + "/initramfs-" + version + ".img",
                            destdir + "/initrd.img")
            isDracut = True
        else:
            shutil.copyfile(bootdir + "/initrd-" + version + ".img",
                            destdir + "/initrd.img")

        return isDracut

    def __get_basic_yaboot_config(self, **args):
        return """
init-message = "Welcome to %(name)s"
timeout=%(timeout)d
""" % args

    def __get_image_stanza(self, **args):
        if args["isDracut"]:
            args["rootlabel"] = "live:LABEL=%(fslabel)s" % args
        else:
            args["rootlabel"] = "CDLABEL=%(fslabel)s" % args
        return """

image=/ppc/ppc%(bit)s/vmlinuz
  label=%(short)s
  initrd=/ppc/ppc%(bit)s/initrd.img
  read-only
  append="root=%(rootlabel)s rootfstype=%(isofstype)s %(liveargs)s %(extra)s"
""" % args


    def __write_yaboot_config(self, isodir, bit, isDracut = False):
        cfg = self.__get_basic_yaboot_config(name = self.name,
                                             timeout = self._timeout * 100)

        kernel_options = self._get_kernel_options()

        cfg += self.__get_image_stanza(fslabel = self.fslabel,
                                       isofstype = "auto",
                                       short = "linux",
                                       long = "Run from image",
                                       extra = "",
                                       bit = bit,
                                       liveargs = kernel_options,
                                       isDracut = isDracut)

        if self._has_checkisomd5():
            cfg += self.__get_image_stanza(fslabel = self.fslabel,
                                           isofstype = "auto",
                                           short = "rd.live.check",
                                           long = "Verify and run from image",
                                           extra = "rd.live.check",
                                           bit = bit,
                                           liveargs = kernel_options,
                                           isDracut = isDracut)

        f = open(isodir + "/ppc/ppc" + bit + "/yaboot.conf", "w")
        f.write(cfg)
        f.close()

    def __write_not_supported(self, isodir, bit):
        makedirs(isodir + "/ppc/ppc" + bit)

        message = "Sorry, this LiveCD does not support your hardware"

        f = open(isodir + "/ppc/ppc" + bit + "/yaboot.conf", "w")
        f.write('init-message = "' + message + '"')
        f.close()


    def __write_dualbits_yaboot_config(isodir, **args):
        cfg = """
init-message = "\nWelcome to %(name)s!\nUse 'linux32' for 32-bit kernel.\n\n"
timeout=%(timeout)d
default=linux

image=/ppc/ppc64/vmlinuz
	label=linux64
	alias=linux
	initrd=/ppc/ppc64/initrd.img
	read-only

image=/ppc/ppc32/vmlinuz
	label=linux32
	initrd=/ppc/ppc32/initrd.img
	read-only
""" % args

        f = open(isodir + "/etc/yaboot.conf", "w")
        f.write(cfg)
        f.close()

    def _configure_bootloader(self, isodir):
        """configure the boot loader"""
        havekernel = { 32: False, 64: False }

        self.__copy_boot_file(isodir + "/ppc", "mapping")
        self.__copy_boot_file(isodir + "/ppc", "bootinfo.txt")
        self.__copy_boot_file(isodir + "/ppc/mac", "ofboot.b")

        shutil.copyfile(self._instroot + "/usr/lib/yaboot/yaboot",
                        isodir + "/ppc/mac/yaboot")

        makedirs(isodir + "/ppc/chrp")
        shutil.copyfile(self._instroot + "/usr/lib/yaboot/yaboot",
                        isodir + "/ppc/chrp/yaboot")

        subprocess.call(["/usr/sbin/addnote", isodir + "/ppc/chrp/yaboot"])

        #
        # FIXME: ppc should support multiple kernels too...
        #
        kernel = self._get_kernel_versions().values()[0][0]

        kernel_bits = self.__kernel_bits(kernel)

        for (bit, present) in kernel_bits.items():
            if not present:
                self.__write_not_supported(isodir, bit)
                continue

            isDracut = self.__copy_kernel_and_initramfs(isodir + "/ppc/ppc" + bit, kernel)
            self.__write_yaboot_config(isodir, bit, isDracut)

        makedirs(isodir + "/etc")
        if kernel_bits["32"] and not kernel_bits["64"]:
            shutil.copyfile(isodir + "/ppc/ppc32/yaboot.conf",
                            isodir + "/etc/yaboot.conf")
        elif kernel_bits["64"] and not kernel_bits["32"]:
            shutil.copyfile(isodir + "/ppc/ppc64/yaboot.conf",
                            isodir + "/etc/yaboot.conf")
        else:
            self.__write_dualbits_yaboot_config(isodir,
                                                name = self.name,
                                                timeout = self._timeout * 100)

        #
        # FIXME: build 'netboot' images with kernel+initrd, like mk-images.ppc
        #

class ppc64LiveImageCreator(ppcLiveImageCreator):
    def _get_excluded_packages(self):
        # FIXME:
        #   while kernel.ppc and kernel.ppc64 co-exist,
        #   we can't have both
        return ["kernel.ppc"] + \
               ppcLiveImageCreator._get_excluded_packages(self)

arch = rpmUtils.arch.getBaseArch()
if arch in ("i386", "x86_64"):
    LiveImageCreator = x86LiveImageCreator
elif arch in ("ppc",):
    LiveImageCreator = ppcLiveImageCreator
elif arch in ("ppc64",):
    LiveImageCreator = ppc64LiveImageCreator
else:
    raise CreatorError("Architecture not supported!")
End of live2.py
chmod 777 ${TMPDIR}/live2.py
cat >|${TMPDIR}/creator2.py <<'End of creator2.py'
#
# creator.py : ImageCreator and LoopImageCreator base classes
#
# Copyright 2007, Red Hat  Inc.
#
# 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
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

import os
import os.path
import stat
import sys
import tempfile
import shutil
import logging
import subprocess

import selinux
import yum
import rpm

from imgcreate.errors import *
#from imgcreate.fs import *
from fs2 import *
from imgcreate.yuminst import *
from imgcreate import kickstart

FSLABEL_MAXLEN = 32
"""The maximum string length supported for LoopImageCreator.fslabel."""

class ImageCreator(object):
    """Installs a system to a chroot directory.

    ImageCreator is the simplest creator class available; it will install and
    configure a system image according to the supplied kickstart file.

    e.g.

      import imgcreate
      ks = imgcreate.read_kickstart("foo.ks")
      imgcreate.ImageCreator(ks, "foo").create()

    """

    def __init__(self, ks, name, releasever=None, tmpdir="/tmp"):
        """Initialize an ImageCreator instance.

        ks -- a pykickstart.KickstartParser instance; this instance will be
              used to drive the install by e.g. providing the list of packages
              to be installed, the system configuration and %post scripts

        name -- a name for the image; used for e.g. image filenames or
                filesystem labels

        releasever -- Value to substitute for $releasever in repo urls

        tmpdir -- Top level directory to use for temporary files and dirs
        """
        self.ks = ks
        """A pykickstart.KickstartParser instance."""

        self.name = name
        """A name for the image."""

        self.releasever = releasever

        self.tmpdir = tmpdir
        """The directory in which all temporary files will be created."""
        if not os.path.exists(self.tmpdir):
            makedirs(self.tmpdir)

        self.__builddir = None
        self.__bindmounts = []

        self.__sanity_check()

    def __del__(self):
        self.cleanup()

    #
    # Properties
    #
    def __get_instroot(self):
        if self.__builddir is None:
            raise CreatorError("_instroot is not valid before calling mount()")
        return self.__builddir + "/install_root"
    _instroot = property(__get_instroot)
    """The location of the install root directory.

    This is the directory into which the system is installed. Subclasses may
    mount a filesystem image here or copy files to/from here.

    Note, this directory does not exist before ImageCreator.mount() is called.

    Note also, this is a read-only attribute.

    """

    def __get_outdir(self):
        if self.__builddir is None:
            raise CreatorError("_outdir is not valid before calling mount()")
        return self.__builddir + "/out"
    _outdir = property(__get_outdir)
    """The staging location for the final image.

    This is where subclasses should stage any files that are part of the final
    image. ImageCreator.package() will copy any files found here into the
    requested destination directory.

    Note, this directory does not exist before ImageCreator.mount() is called.

    Note also, this is a read-only attribute.

    """

    #
    # Hooks for subclasses
    #
    def _mount_instroot(self, base_on = None):
        """Mount or prepare the install root directory.

        This is the hook where subclasses may prepare the install root by e.g.
        mounting creating and loopback mounting a filesystem image to
        _instroot.

        There is no default implementation.

        base_on -- this is the value passed to mount() and can be interpreted
                   as the subclass wishes; it might e.g. be the location of
                   a previously created ISO containing a system image.

        """
        pass

    def _unmount_instroot(self):
        """Undo anything performed in _mount_instroot().

        This is the hook where subclasses must undo anything which was done
        in _mount_instroot(). For example, if a filesystem image was mounted
        onto _instroot, it should be unmounted here.

        There is no default implementation.

        """
        pass

    def _create_bootconfig(self):
        """Configure the image so that it's bootable.

        This is the hook where subclasses may prepare the image for booting by
        e.g. creating an initramfs and bootloader configuration.

        This hook is called while the install root is still mounted, after the
        packages have been installed and the kickstart configuration has been
        applied, but before the %post scripts have been executed.

        There is no default implementation.

        """
        pass

    def _stage_final_image(self):
        """Stage the final system image in _outdir.

        This is the hook where subclasses should place the image in _outdir
        so that package() can copy it to the requested destination directory.

        By default, this moves the install root into _outdir.

        """
        shutil.move(self._instroot, self._outdir + "/" + self.name)

    def _get_required_packages(self):
        """Return a list of required packages.

        This is the hook where subclasses may specify a set of packages which
        it requires to be installed.

        This returns an empty list by default.

        Note, subclasses should usually chain up to the base class
        implementation of this hook.

        """
        return []

    def _get_excluded_packages(self):
        """Return a list of excluded packages.

        This is the hook where subclasses may specify a set of packages which
        it requires _not_ to be installed.

        This returns an empty list by default.

        Note, subclasses should usually chain up to the base class
        implementation of this hook.

        """
        return []

    def _get_fstab(self):
        """Return the desired contents of /etc/fstab.

        This is the hook where subclasses may specify the contents of
        /etc/fstab by returning a string containing the desired contents.

        A sensible default implementation is provided.

        """
        s =  "/dev/root  /         %s    defaults,noatime 0 0\n" %(self._fstype)
        s += self._get_fstab_special()
        return s

    def _get_fstab_special(self):
        s = "devpts     /dev/pts  devpts  gid=5,mode=620   0 0\n"
        s += "tmpfs      /dev/shm  tmpfs   defaults         0 0\n"
        s += "proc       /proc     proc    defaults         0 0\n"
        s += "sysfs      /sys      sysfs   defaults         0 0\n"
        return s

    def _get_post_scripts_env(self, in_chroot):
        """Return an environment dict for %post scripts.

        This is the hook where subclasses may specify some environment
        variables for %post scripts by return a dict containing the desired
        environment.

        By default, this returns an empty dict.

        in_chroot -- whether this %post script is to be executed chroot()ed
                     into _instroot.

        """
        return {}

    def _get_kernel_versions(self):
        """Return a dict detailing the available kernel types/versions.

        This is the hook where subclasses may override what kernel types and
        versions should be available for e.g. creating the booloader
        configuration.

        A dict should be returned mapping the available kernel types to a list
        of the available versions for those kernels.

        The default implementation uses rpm to iterate over everything
        providing 'kernel', finds /boot/vmlinuz-* and returns the version
        obtained from the vmlinuz filename. (This can differ from the kernel
        RPM's n-v-r in the case of e.g. xen)

        """
        def get_version(header):
            version = None
            for f in header['filenames']:
                if f.startswith('/boot/vmlinuz-'):
                    version = f[14:]
            return version

        ts = rpm.TransactionSet(self._instroot)

        ret = {}
        for header in ts.dbMatch('provides', 'kernel'):
            version = get_version(header)
            if version is None:
                continue

            name = header['name']
            if not name in ret:
                ret[name] = [version]
            elif not version in ret[name]:
                ret[name].append(version)

        return ret

    #
    # Helpers for subclasses
    #
    def _do_bindmounts(self):
        """Mount various system directories onto _instroot.

        This method is called by mount(), but may also be used by subclasses
        in order to re-mount the bindmounts after modifying the underlying
        filesystem.

        """
        for b in self.__bindmounts:
            b.mount()

    def _undo_bindmounts(self):
        """Unmount the bind-mounted system directories from _instroot.

        This method is usually only called by unmount(), but may also be used
        by subclasses in order to gain access to the filesystem obscured by
        the bindmounts - e.g. in order to create device nodes on the image
        filesystem.

        """
        self.__bindmounts.reverse()
        for b in self.__bindmounts:
            b.unmount()

    def _chroot(self):
        """Chroot into the install root.

        This method may be used by subclasses when executing programs inside
        the install root e.g.

          subprocess.call(["/bin/ls"], preexec_fn = self.chroot)

        """
        os.chroot(self._instroot)
        os.chdir("/")

    def _mkdtemp(self, prefix = "tmp-"):
        """Create a temporary directory.

        This method may be used by subclasses to create a temporary directory
        for use in building the final image - e.g. a subclass might create
        a temporary directory in order to bundle a set of files into a package.

        The subclass may delete this directory if it wishes, but it will be
        automatically deleted by cleanup().

        The absolute path to the temporary directory is returned.

        Note, this method should only be called after mount() has been called.

        prefix -- a prefix which should be used when creating the directory;
                  defaults to "tmp-".

        """
        self.__ensure_builddir()
        return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)

    def _mkstemp(self, prefix = "tmp-"):
        """Create a temporary file.

        This method may be used by subclasses to create a temporary file
        for use in building the final image - e.g. a subclass might need
        a temporary location to unpack a compressed file.

        The subclass may delete this file if it wishes, but it will be
        automatically deleted by cleanup().

        A tuple containing a file descriptor (returned from os.open() and the
        absolute path to the temporary directory is returned.

        Note, this method should only be called after mount() has been called.

        prefix -- a prefix which should be used when creating the file;
                  defaults to "tmp-".

        """
        self.__ensure_builddir()
        return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)

    def _mktemp(self, prefix = "tmp-"):
        """Create a temporary file.

        This method simply calls _mkstemp() and closes the returned file
        descriptor.

        The absolute path to the temporary file is returned.

        Note, this method should only be called after mount() has been called.

        prefix -- a prefix which should be used when creating the file;
                  defaults to "tmp-".

        """

        (f, path) = self._mkstemp(prefix)
        os.close(f)
        return path

    #
    # Actual implementation
    #
    def __ensure_builddir(self):
        if not self.__builddir is None:
            return

        try:
            self.__builddir = tempfile.mkdtemp(dir =  os.path.abspath(self.tmpdir),
                                               prefix = "imgcreate-")
        except OSError, e:
            raise CreatorError("Failed create build directory in %s: %s" %
                               (self.tmpdir, e.strerror))

    def __sanity_check(self):
        """Ensure that the config we've been given is sane."""
        if not (kickstart.get_packages(self.ks) or
                kickstart.get_groups(self.ks)):
            raise CreatorError("No packages or groups specified")

        kickstart.convert_method_to_repo(self.ks)

        if not kickstart.get_repos(self.ks):
            raise CreatorError("No repositories specified")

    def __write_fstab(self):
        fstab = open(self._instroot + "/etc/fstab", "w")
        fstab.write(self._get_fstab())
        fstab.close()

    def __create_minimal_dev(self):
        """Create a minimal /dev so that we don't corrupt the host /dev"""
        origumask = os.umask(0000)
        devices = (('null',   1, 3, 0666),
                   ('urandom',1, 9, 0666),
                   ('random', 1, 8, 0666),
                   ('full',   1, 7, 0666),
                   ('ptmx',   5, 2, 0666),
                   ('tty',    5, 0, 0666),
                   ('zero',   1, 5, 0666))
        links = (("/proc/self/fd", "/dev/fd"),
                 ("/proc/self/fd/0", "/dev/stdin"),
                 ("/proc/self/fd/1", "/dev/stdout"),
                 ("/proc/self/fd/2", "/dev/stderr"))

        for (node, major, minor, perm) in devices:
            if not os.path.exists(self._instroot + "/dev/" + node):
                os.mknod(self._instroot + "/dev/" + node, perm | stat.S_IFCHR, os.makedev(major,minor))
        for (src, dest) in links:
            if not os.path.exists(self._instroot + dest):
                os.symlink(src, self._instroot + dest)
        os.umask(origumask)

    def __getbooleans(self):
        booleans = []
        if not kickstart.selinux_enabled(self.ks) or not os.path.exists("/selinux/enforce"):
            return booleans
        for i in  selinux.security_get_boolean_names()[1]:
            on = selinux.security_get_boolean_active(i)
            booleans.append(("/booleans/%s" % i, "%d %d" % (on, on)))
        return booleans

    def __create_selinuxfs(self):
        # if selinux exists on the host we need to lie to the chroot
        if os.path.exists("/selinux/enforce"):
            selinux_dir = self._instroot + "/selinux"

            # enforce=0 tells the chroot selinux is not enforcing
            # policyvers=999 tell the chroot to make the highest version of policy it can

            files = [('/enforce', '0'),
                     ('/policyvers', '999'),
                     ('/commit_pending_bools', ''),
                     ('/mls', str(selinux.is_selinux_mls_enabled()))]

            for (file, value) in files + self.__getbooleans():
                fd = os.open(selinux_dir + file, os.O_WRONLY | os.O_TRUNC | os.O_CREAT)
                os.write(fd, value)
                os.close(fd)

            # we steal mls from the host system for now, might be best to always set it to 1????
            # make /load -> /dev/null so chroot policy loads don't hurt anything
            os.mknod(selinux_dir + "/load", 0666 | stat.S_IFCHR, os.makedev(1, 3))

        # selinux is on in the kickstart, so clean up as best we can to start
        if kickstart.selinux_enabled(self.ks):
            # label the fs like it is a root before the bind mounting
            arglist = ["/sbin/setfiles", "-F", "-r", self._instroot, selinux.selinux_file_context_path(), self._instroot]
            subprocess.call(arglist, close_fds = True)
            # these dumb things don't get magically fixed, so make the user generic
            for f in ("/proc", "/sys", "/selinux"):
                arglist = ["/usr/bin/chcon", "-u", "system_u", self._instroot + f]
                subprocess.call(arglist, close_fds = True)

    def __destroy_selinuxfs(self):
        # if the system was running selinux clean up our lies
        if os.path.exists("/selinux/enforce"):
            for root, dirs, files in os.walk(self._instroot + "/selinux"):
                for name in files:
                    try:
                        os.remove(os.path.join(root, name))
                    except OSError:
                        pass
                for name in dirs:
                    if os.path.join(root, name) == self._instroot + "/selinux":
                        continue
                    try:
                        os.rmdir(os.path.join(root, name))
                    except OSError:
                        pass

    def mount(self, base_on = None, cachedir = None):
        """Setup the target filesystem in preparation for an install.

        This function sets up the filesystem which the ImageCreator will
        install into and configure. The ImageCreator class merely creates an
        install root directory, bind mounts some system directories (e.g. /dev)
        and writes out /etc/fstab. Other subclasses may also e.g. create a
        sparse file, format it and loopback mount it to the install root.

        base_on -- a previous install on which to base this install; defaults
                   to None, causing a new image to be created

        cachedir -- a directory in which to store the Yum cache; defaults to
                    None, causing a new cache to be created; by setting this
                    to another directory, the same cache can be reused across
                    multiple installs.

        """
        self.__ensure_builddir()

        makedirs(self._instroot)
        makedirs(self._outdir)

        self._mount_instroot(base_on)

        for d in ("/dev/pts", "/etc", "/boot", "/var/log", "/var/cache/yum", "/sys", "/proc", "/selinux/booleans"):
            makedirs(self._instroot + d)

        cachesrc = cachedir or (self.__builddir + "/yum-cache")
        makedirs(cachesrc)

        # bind mount system directories into _instroot
        for (f, dest) in [("/sys", None), ("/proc", None),
                          ("/dev/pts", None), ("/dev/shm", None),
                          (cachesrc, "/var/cache/yum")]:
            self.__bindmounts.append(BindChrootMount(f, self._instroot, dest))

        self.__create_selinuxfs()

        self._do_bindmounts()

        self.__create_minimal_dev()

        os.symlink("../proc/mounts", self._instroot + "/etc/mtab")

        self.__write_fstab()

    def unmount(self):
        """Unmounts the target filesystem.

        The ImageCreator class detaches the system from the install root, but
        other subclasses may also detach the loopback mounted filesystem image
        from the install root.

        """
        try:
            os.unlink(self._instroot + "/etc/mtab")
        except OSError:
            pass

        self.__destroy_selinuxfs()

        self._undo_bindmounts()

        self._unmount_instroot()

    def cleanup(self):
        """Unmounts the target filesystem and deletes temporary files.

        This method calls unmount() and then deletes any temporary files and
        directories that were created on the host system while building the
        image.

        Note, make sure to call this method once finished with the creator
        instance in order to ensure no stale files are left on the host e.g.:

          creator = ImageCreator(ks, name)
          try:
              creator.create()
          finally:
              creator.cleanup()

        """
        if not self.__builddir:
            return

        self.unmount()

        shutil.rmtree(self.__builddir, ignore_errors = True)
        self.__builddir = None

    def __select_packages(self, ayum):
        skipped_pkgs = []
        for pkg in kickstart.get_packages(self.ks,
                                          self._get_required_packages()):
            try:
                ayum.selectPackage(pkg)
            except yum.Errors.InstallError, e:
                if kickstart.ignore_missing(self.ks):
                    skipped_pkgs.append(pkg)
                else:
                    raise CreatorError("Failed to find package '%s' : %s" %
                                       (pkg, e))

        for pkg in skipped_pkgs:
            logging.warn("Skipping missing package '%s'" % (pkg,))

    def __select_groups(self, ayum):
        skipped_groups = []
        for group in kickstart.get_groups(self.ks):
            try:
                ayum.selectGroup(group.name, group.include)
            except (yum.Errors.InstallError, yum.Errors.GroupsError), e:
                if kickstart.ignore_missing(self.ks):
                    raise CreatorError("Failed to find group '%s' : %s" %
                                       (group.name, e))
                else:
                    skipped_groups.append(group)

        for group in skipped_groups:
            logging.warn("Skipping missing group '%s'" % (group.name,))

    def __deselect_packages(self, ayum):
        for pkg in kickstart.get_excluded(self.ks,
                                          self._get_excluded_packages()):
            ayum.deselectPackage(pkg)

    # if the system is running selinux and the kickstart wants it disabled
    # we need /usr/sbin/lokkit
    def __can_handle_selinux(self, ayum):
        file = "/usr/sbin/lokkit"
        if not kickstart.selinux_enabled(self.ks) and os.path.exists("/selinux/enforce") and not ayum.installHasFile(file):
            raise CreatorError("Unable to disable SELinux because the installed package set did not include the file %s" % (file))

    def install(self, repo_urls = {}):
        """Install packages into the install root.

        This function installs the packages listed in the supplied kickstart
        into the install root. By default, the packages are installed from the
        repository URLs specified in the kickstart.

        repo_urls -- a dict which maps a repository name to a repository URL;
                     if supplied, this causes any repository URLs specified in
                     the kickstart to be overridden.

        """
        yum_conf = self._mktemp(prefix = "yum.conf-")

        ayum = LiveCDYum(releasever=self.releasever)
        ayum.setup(yum_conf, self._instroot)

        for repo in kickstart.get_repos(self.ks, repo_urls):
            (name, baseurl, mirrorlist, proxy, inc, exc) = repo

            yr = ayum.addRepository(name, baseurl, mirrorlist)
            if inc:
                yr.includepkgs = inc
            if exc:
                yr.exclude = exc
            if proxy:
                yr.proxy = proxy

        if kickstart.exclude_docs(self.ks):
            rpm.addMacro("_excludedocs", "1")
        if not kickstart.selinux_enabled(self.ks):
            rpm.addMacro("__file_context_path", "%{nil}")
        if kickstart.inst_langs(self.ks) != None:
            rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))

        try:
            self.__select_packages(ayum)
            self.__select_groups(ayum)
            self.__deselect_packages(ayum)

            self.__can_handle_selinux(ayum)

            ayum.runInstall()
        except yum.Errors.RepoError, e:
            raise CreatorError("Unable to download from repo : %s" % (e,))
        except yum.Errors.YumBaseError, e:
            raise CreatorError("Unable to install: %s" % (e,))
        finally:
            ayum.closeRpmDB()
            ayum.close()
            os.unlink(yum_conf)

        # do some clean up to avoid lvm info leakage.  this sucks.
        for subdir in ("cache", "backup", "archive"):
            lvmdir = self._instroot + "/etc/lvm/" + subdir
            try:
                for f in os.listdir(lvmdir):
                    os.unlink(lvmdir + "/" + f)
            except:
                pass

    def __run_post_scripts(self):
        for s in kickstart.get_post_scripts(self.ks):
            (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
                                          dir = self._instroot + "/tmp")

            os.write(fd, s.script)
            os.close(fd)
            os.chmod(path, 0700)

            env = self._get_post_scripts_env(s.inChroot)

            if not s.inChroot:
                env["INSTALL_ROOT"] = self._instroot
                preexec = None
                script = path
            else:
                preexec = self._chroot
                script = "/tmp/" + os.path.basename(path)

            try:
                subprocess.check_call([s.interp, script],
                                      preexec_fn = preexec, env = env)
            except OSError, e:
                raise CreatorError("Failed to execute %%post script "
                                   "with '%s' : %s" % (s.interp, e.strerror))
            except subprocess.CalledProcessError, err:
                if s.errorOnFail:
                    raise CreatorError("%%post script failed with code %d "
                                       % err.returncode)
                logging.warning("ignoring %%post failure (code %d)"
                                % err.returncode)
            finally:
                os.unlink(path)

    def configure(self):
        """Configure the system image according to the kickstart.

        This method applies the (e.g. keyboard or network) configuration
        specified in the kickstart and executes the kickstart %post scripts.

        If neccessary, it also prepares the image to be bootable by e.g.
        creating an initrd and bootloader configuration.

        """
        ksh = self.ks.handler

        kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
        kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
        kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
        kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
        kickstart.SelinuxConfig(self._instroot).apply(ksh.selinux)
        kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
        kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
        kickstart.ServicesConfig(self._instroot).apply(ksh.services)
        kickstart.XConfig(self._instroot).apply(ksh.xconfig)
        kickstart.NetworkConfig(self._instroot).apply(ksh.network)
        kickstart.RPMMacroConfig(self._instroot).apply(self.ks)

        self._create_bootconfig()

        self.__run_post_scripts()

    def launch_shell(self):
        """Launch a shell in the install root.

        This method is launches a bash shell chroot()ed in the install root;
        this can be useful for debugging.

        """
        subprocess.call(["/bin/bash"], preexec_fn = self._chroot)

    def package(self, destdir = "."):
        """Prepares the created image for final delivery.

        In its simplest form, this method merely copies the install root to the
        supplied destination directory; other subclasses may choose to package
        the image by e.g. creating a bootable ISO containing the image and
        bootloader configuration.

        destdir -- the directory into which the final image should be moved;
                   this defaults to the current directory.

        """
        self._stage_final_image()

        for f in os.listdir(self._outdir):
            shutil.move(os.path.join(self._outdir, f),
                        os.path.join(destdir, f))

    def create(self):
        """Install, configure and package an image.

        This method is a utility method which creates and image by calling some
        of the other methods in the following order - mount(), install(),
        configure(), unmount and package().

        """
        self.mount()
        self.install()
        self.configure()
        self.unmount()
        self.package()

class LoopImageCreator(ImageCreator):
    """Installs a system into a loopback-mountable filesystem image.

    LoopImageCreator is a straightforward ImageCreator subclass; the system
    is installed into an ext3 filesystem on a sparse file which can be
    subsequently loopback-mounted.

    """

    def __init__(self, ks, name, fslabel=None, releasever=None, tmpdir="/tmp"):
        """Initialize a LoopImageCreator instance.

        This method takes the same arguments as ImageCreator.__init__() with
        the addition of:

        fslabel -- A string used as a label for any filesystems created.

        """
        ImageCreator.__init__(self, ks, name, releasever=releasever, tmpdir=tmpdir)

        self.__fslabel = None
        self.fslabel = fslabel

        self.__minsize_KB = 0
        self.__blocksize = 4096
        self.__fstype = kickstart.get_image_fstype(self.ks, "ext3")

        self.__instloop = None
        self.__imgdir = None

        self.__image_size = kickstart.get_image_size(self.ks,
                                                     4096L * 1024 * 1024)

    #
    # Properties
    #
    def __get_fslabel(self):
        if self.__fslabel is None:
            return self.name
        else:
            return self.__fslabel
    def __set_fslabel(self, val):
        if val is None:
            self.__fslabel = None
        else:
            self.__fslabel = val[:FSLABEL_MAXLEN]
    fslabel = property(__get_fslabel, __set_fslabel)
    """A string used to label any filesystems created.

    Some filesystems impose a constraint on the maximum allowed size of the
    filesystem label. In the case of ext3 it's 16 characters, but in the case
    of ISO9660 it's 32 characters.

    mke2fs silently truncates the label, but mkisofs aborts if the label is too
    long. So, for convenience sake, any string assigned to this attribute is
    silently truncated to FSLABEL_MAXLEN (32) characters.

    """

    def __get_image(self):
        if self.__imgdir is None:
            raise CreatorError("_image is not valid before calling mount()")
        return self.__imgdir + "/ext3fs.img"
    _image = property(__get_image)
    """The location of the image file.

    This is the path to the filesystem image. Subclasses may use this path
    in order to package the image in _stage_final_image().

    Note, this directory does not exist before ImageCreator.mount() is called.

    Note also, this is a read-only attribute.

    """

    def __get_blocksize(self):
        return self.__blocksize
    def __set_blocksize(self, val):
        if self.__instloop:
            raise CreatorError("_blocksize must be set before calling mount()")
        try:
            self.__blocksize = int(val)
        except ValueError:
            raise CreatorError("'%s' is not a valid integer value "
                               "for _blocksize" % val)
    _blocksize = property(__get_blocksize, __set_blocksize)
    """The block size used by the image's filesystem.

    This is the block size used when creating the filesystem image. Subclasses
    may change this if they wish to use something other than a 4k block size.

    Note, this attribute may only be set before calling mount().

    """

    def __get_fstype(self):
        return self.__fstype
    def __set_fstype(self, val):
        if val not in ("ext2", "ext3", "ext4"):
            raise CreatorError("Unknown _fstype '%s' supplied" % val)
        self.__fstype = val
    _fstype = property(__get_fstype, __set_fstype)
    """The type of filesystem used for the image.

    This is the filesystem type used when creating the filesystem image.
    Subclasses may change this if they wish to use something other ext3.

    Note, only ext2, ext3, ext4 are currently supported.

    Note also, this attribute may only be set before calling mount().

    """

    #
    # Helpers for subclasses
    #
    def _resparse(self, size = None):
        """Rebuild the filesystem image to be as sparse as possible.

        This method should be used by subclasses when staging the final image
        in order to reduce the actual space taken up by the sparse image file
        to be as little as possible.

        This is done by resizing the filesystem to the minimal size (thereby
        eliminating any space taken up by deleted files) and then resizing it
        back to the supplied size.

        size -- the size in, in bytes, which the filesystem image should be
                resized to after it has been minimized; this defaults to None,
                causing the original size specified by the kickstart file to
                be used (or 4GiB if not specified in the kickstart).

        """
        return self.__instloop.resparse(size)

    def _base_on(self, base_on):
        shutil.copyfile(base_on, self._image)
        
    #
    # Actual implementation
    #
    def _mount_instroot(self, base_on = None):
        self.__imgdir = self._mkdtemp()

        if not base_on is None:
            self._base_on(base_on)

        self.__instloop = ExtDiskMount(SparseLoopbackDisk(self._image,
                                                          self.__image_size),
                                       self._instroot,
                                       self.__fstype,
                                       self.__blocksize,
                                       self.fslabel,
                                       self.tmpdir)

        try:
            self.__instloop.mount()
        except MountError, e:
            raise CreatorError("Failed to loopback mount '%s' : %s" %
                               (self._image, e))

    def _unmount_instroot(self):
        if not self.__instloop is None:
            self.__instloop.cleanup()

    def _stage_final_image(self):
        self._resparse()
        shutil.move(self._image, self._outdir + "/" + self.name + ".img")
End of creator2.py
chmod 777 ${TMPDIR}/creator2.py
cat >|${TMPDIR}/fs2.py <<'End of fs2.py'
#
# fs.py : Filesystem related utilities and classes
#
# Copyright 2007, Red Hat  Inc.
#
# 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
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

import os
import os.path
import sys
import errno
import stat
import subprocess
import random
import string
import logging
import tempfile
import time

from imgcreate.errors import *

def call(*popenargs, **kwargs):
    '''
        Calls subprocess.Popen() with the provided arguments.  All stdout and
        stderr output is sent to logging.debug().  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 "%s", buf

    return rc

def makedirs(dirname):
    """A version of os.makedirs() that doesn't throw an
    exception if the leaf directory already exists.
    """
    try:
        os.makedirs(dirname)
    except OSError, e:
        if e.errno != errno.EEXIST:
            raise

def squashfs_compression_type(sqfs_img):
    """Check the compression type of a SquashFS image. If the type cannot be
    ascertained, return 'undetermined'. The calling code must decide what to
    do."""

    env = os.environ.copy()
    env['LC_ALL'] = 'C'
    args = ['/usr/sbin/unsquashfs', '-s', sqfs_img]
    try:
        p = subprocess.Popen(args, stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE, env=env)
        out, err = p.communicate()
    except OSError, e:
        raise SquashfsError(u"Error while stat-ing '%s'\n'%s'" % (args, e))
    except:
        raise SquashfsError(u"Error while stat-ing '%s'" % args)
    else:
        if p.returncode != 0:
            raise SquashfsError(
                u"Error while stat-ing '%s'\n'%s'\nreturncode: '%s'" %
                (args, err, p.returncode))
        else:
            compress_type = 'undetermined'
            for l in out.splitlines():
                if l.split(None, 1)[0] == 'Compression':
                    compress_type = l.split()[1]
                    break
    return compress_type

def mksquashfs(in_img, out_img, compress_type):
# Allow gzip to work for older versions of mksquashfs
    if compress_type == "gzip":
        args = ["/sbin/mksquashfs", in_img, out_img]
    else:
        args = ["/sbin/mksquashfs", in_img, out_img, "-comp", compress_type]

    if not sys.stdout.isatty():
        args.append("-no-progress")

    ret = call(args)
    if ret != 0:
        raise SquashfsError("'%s' exited with error (%d)" %
                            (string.join(args, " "), ret))

def resize2fs(fs, size = None, minimal = False, tmpdir = "/tmp"):
    if minimal and size is not None:
        raise ResizeError("Can't specify both minimal and a size for resize!")
    if not minimal and size is None:
        raise ResizeError("Must specify either a size or minimal for resize!")

    e2fsck(fs)
    (fd, saved_image) = tempfile.mkstemp("", "resize-image-", tmpdir)
    os.close(fd)
    call(["/sbin/e2image", "-r", fs, saved_image])

    args = ["/sbin/resize2fs", fs]
    if minimal:
        args.append("-M")
    else:
        args.append("%sK" %(size / 1024,))
    ret = call(args)
    if ret != 0:
        raise ResizeError("resize2fs returned an error (%d)!  image to debug at %s" %(ret, saved_image))

    if e2fsck(fs) != 0:
        raise ResizeError("fsck after resize returned an error!  image to debug at %s" %(saved_image,))
    os.unlink(saved_image)
    return 0

def e2fsck(fs):
    logging.info("Checking filesystem %s" % fs)
    rc = call(["/sbin/e2fsck", "-f", "-y", fs])
    return rc

class BindChrootMount:
    """Represents a bind mount of a directory into a chroot."""
    def __init__(self, src, chroot, dest = None):
        self.src = src
        self.root = chroot

        if not dest:
            dest = src
        self.dest = self.root + "/" + dest

        self.mounted = False

    def mount(self):
        if self.mounted:
            return

        makedirs(self.dest)
        rc = call(["/bin/mount", "--bind", self.src, self.dest])
        if rc != 0:
            raise MountError("Bind-mounting '%s' to '%s' failed" %
                             (self.src, self.dest))
        self.mounted = True

    def unmount(self):
        if not self.mounted:
            return

        rc = call(["/bin/umount", self.dest])
        if rc != 0:
            logging.info("Unable to unmount %s normally, using lazy unmount" % self.dest)
            rc = call(["/bin/umount", "-l", self.dest])
            if rc != 0:
                raise MountError("Unable to unmount fs at %s" % self.dest)
            else:
                logging.info("lazy umount succeeded on %s" % self.dest)
                print >> sys.stdout, "lazy umount succeeded on %s" % self.dest
 
        self.mounted = False

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)
        self.losetup = False
        
    def cleanup(self):
        self.diskmount.cleanup()

    def unmount(self):
        self.diskmount.unmount()

    def lounsetup(self):
        if self.losetup:
            rc = call(["/sbin/losetup", "-d", self.loopdev])
            self.losetup = False
            self.loopdev = None

    def loopsetup(self):
        if self.losetup:
            return

        losetupProc = subprocess.Popen(["/sbin/losetup", "-f"],
                                       stdout=subprocess.PIPE)
        losetupOutput = losetupProc.communicate()[0]

        if losetupProc.returncode:
            raise MountError("Failed to allocate loop device for '%s'" %
                             self.lofile)

        self.loopdev = losetupOutput.split()[0]

        rc = call(["/sbin/losetup", self.loopdev, self.lofile])
        if rc != 0:
            raise MountError("Failed to allocate loop device for '%s'" %
                             self.lofile)

        self.losetup = True

    def mount(self):
        self.diskmount.mount()

class SparseLoopbackMount(LoopbackMount):
    """SparseLoopbackMount  compatibility layer for old API"""
    def __init__(self, lofile, mountdir, size, fstype = None):
        self.diskmount = DiskMount(SparseLoopbackDisk(lofile,size),mountdir,fstype,rmmountdir = True)

    def expand(self, create = False, size = None):
        self.diskmount.disk.expand(create, size)

    def truncate(self, size = None):
        self.diskmount.disk.truncate(size)

    def create(self):
        self.diskmount.disk.create()

class SparseExtLoopbackMount(SparseLoopbackMount):
    """SparseExtLoopbackMount  compatibility layer for old API"""
    def __init__(self, lofile, mountdir, size, fstype, blocksize, fslabel):
        self.diskmount = ExtDiskMount(SparseLoopbackDisk(lofile,size),
                                      mountdir, fstype, blocksize, fslabel,
                                      rmmountdir = True, tmpdir = "/tmp")


    def __format_filesystem(self):
        self.diskmount.__format_filesystem()

    def create(self):
        self.diskmount.disk.create()

    def resize(self, size = None):
        return self.diskmount.__resize_filesystem(size)

    def mount(self):
        self.diskmount.mount()
        
    def __fsck(self):
        self.extdiskmount.__fsck()

    def __get_size_from_filesystem(self):
        return self.diskmount.__get_size_from_filesystem()
        
    def __resize_to_minimal(self):
        return self.diskmount.__resize_to_minimal()
        
    def resparse(self, size = None):
        return self.diskmount.resparse(size)
        
class Disk:
    """Generic base object for a disk

    The 'create' method must make the disk visible as a block device - eg
    by calling losetup. For RawDisk, this is obviously a no-op. The 'cleanup'
    method must undo the 'create' operation.
    """
    def __init__(self, size, device = None):
        self._device = device
        self._size = size

    def create(self):
        pass

    def cleanup(self):
        pass

    def get_device(self):
        return self._device
    def set_device(self, path):
        self._device = path
    device = property(get_device, set_device)

    def get_size(self):
        return self._size
    size = property(get_size)


class RawDisk(Disk):
    """A Disk backed by a block device.
    Note that create() is a no-op.
    """  
    def __init__(self, size, device):
        Disk.__init__(self, size, device)

    def fixed(self):
        return True

    def exists(self):
        return True

class LoopbackDisk(Disk):
    """A Disk backed by a file via the loop module."""
    def __init__(self, lofile, size):
        Disk.__init__(self, size)
        self.lofile = lofile

    def fixed(self):
        return False

    def exists(self):
        return os.path.exists(self.lofile)

    def create(self):
        if self.device is not None:
            return

        losetupProc = subprocess.Popen(["/sbin/losetup", "-f"],
                                       stdout=subprocess.PIPE)
        losetupOutput = losetupProc.communicate()[0]

        if losetupProc.returncode:
            raise MountError("Failed to allocate loop device for '%s'" %
                             self.lofile)

        device = losetupOutput.split()[0]

        logging.info("Losetup add %s mapping to %s"  % (device, self.lofile))
        rc = call(["/sbin/losetup", device, self.lofile])
        if rc != 0:
            raise MountError("Failed to allocate loop device for '%s'" %
                             self.lofile)
        self.device = device

    def cleanup(self):
        if self.device is None:
            return
        logging.info("Losetup remove %s" % self.device)
        rc = call(["/sbin/losetup", "-d", self.device])
        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 expand(self, create = False, size = None):
        flags = os.O_WRONLY
        if create:
            flags |= os.O_CREAT
            makedirs(os.path.dirname(self.lofile))

        if size is None:
            size = self.size

        logging.info("Extending sparse file %s to %d" % (self.lofile, size))
        fd = os.open(self.lofile, flags)

        if size <= 0:
            size = 1
        os.lseek(fd, size-1, 0)
        os.write(fd, '\x00')
        os.close(fd)

    def truncate(self, size = None):
        if size is None:
            size = self.size

        logging.info("Truncating sparse file %s to %d" % (self.lofile, size))
        fd = os.open(self.lofile, os.O_WRONLY)
        os.ftruncate(fd, size)
        os.close(fd)

    def create(self):
        self.expand(create = True)
        LoopbackDisk.create(self)

class Mount:
    """A generic base class to deal with mounting things."""
    def __init__(self, mountdir):
        self.mountdir = mountdir

    def cleanup(self):
        self.unmount()

    def mount(self):
        pass

    def unmount(self):
        pass

class DiskMount(Mount):
    """A Mount object that handles mounting of a Disk."""
    def __init__(self, disk, mountdir, fstype = None, rmmountdir = True):
        Mount.__init__(self, mountdir)

        self.disk = disk
        self.fstype = fstype
        self.rmmountdir = rmmountdir

        self.mounted = False
        self.rmdir   = False

    def cleanup(self):
        Mount.cleanup(self)
        self.disk.cleanup()

    def unmount(self):
        if self.mounted:
            logging.info("Unmounting directory %s" % self.mountdir)
            rc = call(["/bin/umount", self.mountdir])
            if rc == 0:
                self.mounted = False
            else:
                logging.warn("Unmounting directory %s failed, using lazy umount" % self.mountdir)
                print >> sys.stdout, "Unmounting directory %s failed, using lazy umount" %self.mountdir
                rc = call(["/bin/umount", "-l", self.mountdir])
                if rc != 0:
                    raise MountError("Unable to unmount filesystem at %s" % self.mountdir)
                else:
                    logging.info("lazy umount succeeded on %s" % self.mountdir)
                    print >> sys.stdout, "lazy umount succeeded on %s" % self.mountdir
                    self.mounted = False

        if self.rmdir and not self.mounted:
            try:
                os.rmdir(self.mountdir)
            except OSError, e:
                pass
            self.rmdir = False


    def __create(self):
        self.disk.create()


    def mount(self):
        if self.mounted:
            return

        if not os.path.isdir(self.mountdir):
            logging.info("Creating mount point %s" % self.mountdir)
            os.makedirs(self.mountdir)
            self.rmdir = self.rmmountdir

        self.__create()

        logging.info("Mounting %s at %s" % (self.disk.device, self.mountdir))
        args = [ "/bin/mount", self.disk.device, self.mountdir ]
        if self.fstype:
            args.extend(["-t", self.fstype])

        rc = call(args)
        if rc != 0:
            raise MountError("Failed to mount '%s' to '%s'" %
                             (self.disk.device, self.mountdir))

        self.mounted = True

class ExtDiskMount(DiskMount):
    """A DiskMount object that is able to format/resize ext[23] filesystems."""
    def __init__(self, disk, mountdir, fstype, blocksize, fslabel,
                 rmmountdir=True, tmpdir="/tmp"):
        DiskMount.__init__(self, disk, mountdir, fstype, rmmountdir)
        self.blocksize = blocksize
        self.fslabel = "_" + fslabel
        self.tmpdir = tmpdir

    def __format_filesystem(self):
        logging.info("Formating %s filesystem on %s" % (self.fstype, self.disk.device))
        rc = call(["/sbin/mkfs." + self.fstype,
                   "-F", "-L", self.fslabel,
                   "-m", "1", "-b", str(self.blocksize),
                   self.disk.device])
        #          str(self.disk.size / self.blocksize)])

        if rc != 0:
            raise MountError("Error creating %s filesystem" % (self.fstype,))
        logging.info("Tuning filesystem on %s" % self.disk.device)
        call(["/sbin/tune2fs", "-c0", "-i0", "-Odir_index",
              "-ouser_xattr,acl", self.disk.device])

    def __resize_filesystem(self, size = None):
        current_size = os.stat(self.disk.lofile)[stat.ST_SIZE]

        if size is None:
            size = self.disk.size

        if size == current_size:
            return

        if size > current_size:
            self.disk.expand(size)

        resize2fs(self.disk.lofile, size, tmpdir = self.tmpdir)
        return size

    def __create(self):
        resize = False
        if not self.disk.fixed() and self.disk.exists():
            resize = True

        self.disk.create()

        if resize:
            self.__resize_filesystem()
        else:
            self.__format_filesystem()

    def mount(self):
        self.__create()
        DiskMount.mount(self)

    def __fsck(self):
        return e2fsck(self.disk.lofile)
        return rc

    def __get_size_from_filesystem(self):
        def parse_field(output, field):
            for line in output.split("\n"):
                if line.startswith(field + ":"):
                    return line[len(field) + 1:].strip()

            raise KeyError("Failed to find field '%s' in output" % field)

        dev_null = os.open("/dev/null", os.O_WRONLY)
        try:
            out = subprocess.Popen(['/sbin/dumpe2fs', '-h', self.disk.lofile],
                                   stdout = subprocess.PIPE,
                                   stderr = dev_null).communicate()[0]
        finally:
            os.close(dev_null)

        return int(parse_field(out, "Block count")) * self.blocksize

    def __resize_to_minimal(self):
        resize2fs(self.disk.lofile, minimal = True, tmpdir = self.tmpdir)
        return self.__get_size_from_filesystem()

    def resparse(self, size = None):
        self.cleanup()
        minsize = self.__resize_to_minimal()
        self.disk.truncate(minsize)
        self.__resize_filesystem(size)
        return minsize

class DeviceMapperSnapshot(object):
    def __init__(self, imgloop, cowloop):
        self.imgloop = imgloop
        self.cowloop = cowloop

        self.__created = False
        self.__name = None

    def get_path(self):
        if self.__name is None:
            return None
        return os.path.join("/dev/mapper", self.__name)
    path = property(get_path)

    def create(self):
        if self.__created:
            return

        self.imgloop.create()
        self.cowloop.create()

        self.__name = "imgcreate-%d-%d" % (os.getpid(),
                                           random.randint(0, 2**16))

        size = os.stat(self.imgloop.lofile)[stat.ST_SIZE]

        table = "0 %d snapshot %s %s p 8" % (size / 512,
                                             self.imgloop.device,
                                             self.cowloop.device)

        args = ["/sbin/dmsetup", "create", self.__name,
                "--uuid", "LIVECD-%s" % self.__name, "--table", table]
        if call(args) != 0:
            self.cowloop.cleanup()
            self.imgloop.cleanup()
            raise SnapshotError("Could not create snapshot device using: " +
                                string.join(args, " "))

        self.__created = True

    def remove(self, ignore_errors = False):
        if not self.__created:
            return

        # sleep to try to avoid any dm shenanigans
        time.sleep(2)
        rc = call(["/sbin/dmsetup", "remove", self.__name])
        if not ignore_errors and rc != 0:
            raise SnapshotError("Could not remove snapshot device")

        self.__name = None
        self.__created = False

        self.cowloop.cleanup()
        self.imgloop.cleanup()

    def get_cow_used(self):
        if not self.__created:
            return 0

        dev_null = os.open("/dev/null", os.O_WRONLY)
        try:
            out = subprocess.Popen(["/sbin/dmsetup", "status", self.__name],
                                   stdout = subprocess.PIPE,
                                   stderr = dev_null).communicate()[0]
        finally:
            os.close(dev_null)

        #
        # dmsetup status on a snapshot returns e.g.
        #   "0 8388608 snapshot 416/1048576"
        # or, more generally:
        #   "A B snapshot C/D"
        # where C is the number of 512 byte sectors in use
        #
        try:
            return int((out.split()[3]).split('/')[0]) * 512
        except ValueError:
            raise SnapshotError("Failed to parse dmsetup status: " + out)

def create_image_minimizer(path, image, compress_type, target_size = None,
                           tmpdir = "/tmp"):
    """
    Builds a copy-on-write image which can be used to
    create a device-mapper snapshot of an image where
    the image's filesystem is as small as possible

    The steps taken are:
      1) Create a sparse COW
      2) Loopback mount the image and the COW
      3) Create a device-mapper snapshot of the image
         using the COW
      4) Resize the filesystem to the minimal size
      5) Determine the amount of space used in the COW
      6) Restroy the device-mapper snapshot
      7) Truncate the COW, removing unused space
      8) Create a squashfs of the COW
    """
    imgloop = LoopbackDisk(image, None) # Passing bogus size - doesn't matter

    cowloop = SparseLoopbackDisk(os.path.join(os.path.dirname(path), "osmin"),
                                 64L * 1024L * 1024L)

    snapshot = DeviceMapperSnapshot(imgloop, cowloop)

    try:
        snapshot.create()

        if target_size is not None:
            resize2fs(snapshot.path, target_size, tmpdir = tmpdir)
        else:
            resize2fs(snapshot.path, minimal = True, tmpdir = tmpdir)

        cow_used = snapshot.get_cow_used()
    finally:
        snapshot.remove(ignore_errors = (not sys.exc_info()[0] is None))

    cowloop.truncate(cow_used)

    mksquashfs(cowloop.lofile, path, compress_type)

    os.unlink(cowloop.lofile)

End of fs2.py
chmod 777 ${TMPDIR}/fs2.py
thisScriptPath=$( readlink -f $0 )
sudo ${TMPDIR}/liveos-disk-to-iso $thisScriptPath $*
exit 0
