#!/usr/bin/python
#
# Copyright (C) 2005,2006 Red Hat, Inc.
# Author: Thomas Woerner <twoerner@redhat.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Library General Public License as published by
# the Free Software Foundation; version 2 only
#
# 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 Library 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.
# Copyright 2004, 2005 Red Hat, Inc.
#
# AUTHOR: Thomas Woerner <twoerner@redhat.com>
#

# TODO
# - overwrite rpmconfig.machine for yum call
# - lvm update
#


import os, sys, getopt, md5, stat, tempfile, string, time, random, crypt, \
       signal, types, shutil, traceback, popen2
import pprint

PYRPMDIR = "/usr/share/pyrpm"
if not PYRPMDIR in sys.path:
    sys.path.append(PYRPMDIR)
PYBINDIR = os.path.dirname(sys.argv[0])

import pyrpm
import pyrpm.database
from pyrpm.installer import *

# do not use first 4 loop devices
# mkinitrd in old Fedora or Red Hat releases needs one and can only use
# loop0 to loop3
LOOP.LOOP_FIRST = 4


#################################### dicts ####################################

autopart = { }
autopart["/boot"] = { "fstype": "ext3", "size": 100 }
autopart["/"] = { "fstype": "ext3", "size": 650, "grow": 1 }
autopart["swap"] = { "size": 128 }

################################## functions ##################################

def exitHandler(signum, frame):
    raise RuntimeError

################################ main function ################################

def main():
    global verbose, target_chroot, xen, nocleanup, confirm, tempdir, diskmap, \
           partitionmap, devmap, wait, orig_yum, target_dir, \
           label_prefix, standalone, stage2_img_dir, repo_comps, upgrade, \
           no_stage2, no_cache, raidmap, log, stage2_chroot, \
           vgmap
    user_arch = None
    no_default_groups = 0
    autoerase = 0
    no_buildstamp = 0
    no_discinfo = 0

    try:
        (opts, args) = getopt.getopt(sys.argv[1:], "hvy",
                                     [ "help", "verbose", "yes",
                                       "label-prefix", "no-cleanup",
                                       "standalone", "wait", "xen", "yum",
                                       "repo-comps", "no-stage2", "upgrade=",
                                       "no-cache", "no-default-groups",
                                       "drop-unresolved", "no-buildstamp",
                                       "no-discinfo"])
    except:
        usage()
        return

    for (opt, val) in opts:
        if opt in [ "-h", "--help" ]:
            usage()
            return
        elif opt in [ "-v", "--verbose" ]:
            verbose += 1
            pyrpm.rpmconfig.verbose += 1
        elif opt in [ "-y", "--yes" ]:
            confirm = 0
        elif opt == "--xen":
            xen = 1
        elif opt == "--no-cleanup":
            nocleanup = 1
        elif opt == "--wait":
            wait = 1
        elif opt == "--yum":
            orig_yum = 1
        elif opt == "--label-prefix":
            label_prefix = "%x_" % int(time.strftime("%m%d%H%M"))
        elif opt == "--standalone":
            standalone = 1
        elif opt == "--repo-comps":
            repo_comps = 1
        elif opt == "--no-stage2":
            no_stage2 = 1
        elif opt == "--upgrade":
            if val[:5] == "/dev/":
                upgrade = val[5:]
            else:
                upgrade = val
        elif opt == "--arch":
            user_arch = val
        elif opt == "--no-cache":
            no_cache = 1
        elif opt == "--no-default-groups":
            no_default_groups = 1
        elif opt == "--drop-unresolved":
            autoerase = 1
        elif opt == "--no-buildstamp":
            no_buildstamp = 1
        elif opt == "--no-discinfo":
            no_discinfo = 1
        else:
            print "Unknown option '%s'" % opt
            usage()
            return

    if len(args) < 1:
        usage()
        return

    if user_arch:
        if user_arch not in pyrpm.arch_compats[pyrpm.rpmconfig.machine]:
            print "ERROR: User requested arch is not compatible with " \
                  "system arch."
            return 1
        if user_arch == "noarch":
            print "ERROR: Noarch is now allowed as instalaltion arch."
            return 1

    # kickstart file
    ks_file = args[0]
    if not os.path.exists(ks_file):
        print "ERROR: '%s' does not exist." % ks_file
        return 1

    ######################## kickstart first pass ###########################

    try:
        ks = KickstartConfig(ks_file)
    except Exception, msg:
        print msg
        return 1

    if standalone:
        # use /tmp in standalone mode, pyrmkickstart is already running in
        # own environment like stage2
        tempdir = "/tmp"
        stage2_dir = "/"
        no_stage2 = 1
    else:
        # create temp dir in standard mode
        try:
            tempdir = tempfile.mkdtemp(prefix="pyrpmkickstart_")
        except Exception, msg:
            print "ERROR: Unable to create temporary directory: %s" % msg
            return 1
        stage2_dir = tempdir+"/stage2"
        os.mkdir(stage2_dir)

    if not no_stage2:
        # mount ramfs for stage2, needed here for nfs sources
        if verbose:
            print "Mounting ramfs on '%s'" % stage2_dir
        try:
            mount("None", stage2_dir, fstype="ramfs")
        except Exception, msg:
            print "ERROR: Unable to create stage2 ramfs: %s" % msg
            return 1

    source_dir = stage2_dir+"/mnt/source"
    target_dir = stage2_dir+"/mnt/sysimage"
    repos_dir = stage2_dir+"/mnt/repos"           # nfs repo base directory
    cache_dir = stage2_dir+"/tmp/cache"

    # create mount points
    create_dir("", source_dir)
    create_dir("", target_dir)
    create_dir("", repos_dir)
    if ks.has_key("repo"):
        for repo in ks["repo"]:
            create_dir("", "%s/%s" % (repos_dir, repo))
    create_dir("", cache_dir)

    # create cache dir
    pyrpm.rpmconfig.cachedir = cache_dir

    # global logging
    create_dir(tempdir, "/tmp")
    log_file = tempdir+"/tmp/pyrpmkickstart.log"
    print "Logging to '%s'" % log_file
    logger.open(log_file)

    # installation methods

    if not ks.has_key("nfs") and not ks.has_key("url") and \
           not ks.has_key("cdrom"):
        print "ERROR: Only installation with nfs or url is supported, exiting."
        return 1

    ### load source ###

    source = Repository(pyrpm.rpmconfig, source_dir, "base")

    if ks.has_key("cdrom"):
        url = "cdrom"
    elif ks.has_key("nfs"):
        url = "nfs://%s:%s" % (ks["nfs"]["server"], ks["nfs"]["dir"])
    else:
        url = ks["url"]["url"]

    print "Reading repodata from installation source ..."
    if not source.load(url):
        return 1

    ### get source information via release package ###
    # will be overwritte later by .buildstamp or .discinfo if not disabled

    # get source information via release package
    source_release = "Red Hat Enterprise Linux"
    pkgs = source.repo.getPkgsByName("redhat-release")
    if len(pkgs) == 0:
        source_release = "Fedora Core"
        pkgs = source.repo.getPkgsByName("fedora-release")
    if len(pkgs) == 0:
        print "ERROR: Could not find release package in source."
        return
    if len(pkgs) > 1:
        print "ERROR: Found more than one release package, exiting."
        return
    source_version = pkgs[0]["version"]
    # drop all letters from source_version
    source_version = source_version.strip(string.letters)
    if len(source_version) == 0:
        print "ERROR: No valid version of installation source"
        return 1
    i = string.find(pkgs[0]["release"], "rawhide")
    if i != -1 and string.find(source_version, ".") == -1:
        source_version += ".90" # bad fix for rawhide
    del i

    # get installation arch
    pkgs = source.repo.getPkgsByName("filesystem")
    if len(pkgs) != 1 or pkgs[0]["arch"] == "noarch":
        pkgs = source.repo.getPkgsByName("coreutils")
    if len(pkgs) != 1 or pkgs[0]["arch"] == "noarch":
        print "ERROR: Could not determine installation architecture."
        return 1
    source_arch = pkgs[0]["arch"]

    ############################## mount stage2  ##############################

    if not no_stage2:
        stage2_img_dir = tempdir+"/stage2_img"
        os.mkdir(stage2_img_dir)

        # This could also use .discinfo:
        stage2 = source.get("RedHat/base/stage2.img")
        if not stage2:
            stage2 = source.get("Fedora/base/stage2.img")
        if not stage2:
            stage2 = source.get("images/stage2.img")
        if not stage2:
            print "ERROR: Could not find stage2 image in installation source."
            print "Maybe use the option --no-stage2."
            return 1
        if not os.path.exists(stage2):
            print "ERROR: Could not get stage2 image from installation source."
            print "Maybe use the option --no-stage2."
            return 1

        fstype = None # no fstype needed for compressed rom fs
        # determine stage2.img filesystem type
        fd = open(stage2, "r")
        magic = fd.read(4)
        fd.close()
        if len(magic) != 4:
            print "ERROR: %s is not a valid stage2 image" % stage2
            return 1
        if magic == "hsqs" or magic == "sqsh":
            fstype = "squashfs"

        if verbose:
            print "Mounting '%s' on '%s'" % (stage2, stage2_img_dir)
        try:
            mount(stage2, stage2_img_dir, fstype, options="loop,defaults,ro")
        except Exception, msg:
            print "Unable to mount stage2 image.\n" + \
                  "You could try to disable the use of the stage2 image" +\
                  " with the\noption --no-stage2."
            traceback.print_exc()
            print msg
            return 1

        ### analyze stage2 ###
        if not no_buildstamp:
            if not os.path.exists("%s/.buildstamp" % stage2_img_dir):
                print "ERROR: Buildstamp information is missing in stage2."
                return 1
            bs = get_buildstamp_info(stage2_img_dir)
            if not bs:
                print "ERROR: Failed to get buildstamp information from stage2"
                return 1
            (source_release, source_version, source_arch) = bs

        ### create stage2 chroot ###

        print "Preparing stage2 changeroot ..."

        # check if ther is a bash
        check_exists(stage2_img_dir, "/usr/bin/bash")

        # copytree does not like to get a signal
        pyrpm.blockSignals()
        for f in os.listdir(stage2_img_dir):
            s = "%s/%s" % (stage2_img_dir, f)
            d = "%s/%s" % (stage2_dir, f)
            if os.path.isdir(s):
                shutil.copytree(s, d, symlinks=True)
            else:
                shutil.copy2(s, d)
        pyrpm.unblockSignals()

        if verbose:
            print "Umounting '%s' " % stage2_img_dir
        umount(stage2_img_dir)
        stage2_img_dir = None

        # create dirs
        create_dir(stage2_dir, "/bin")
        create_dir(stage2_dir, "/dev")
        create_dir(stage2_dir, "/etc")
        create_dir(stage2_dir, "/etc/lvm")
        create_dir(stage2_dir, "/mnt/sysimage")
        create_dir(stage2_dir, "/proc")
        create_dir(stage2_dir, "/sbin")
        create_dir(stage2_dir, "/tmp")
        create_dir(stage2_dir, "/var")
        create_dir(stage2_dir, "/var/log")
        create_dir(stage2_dir, "/var/tmp")

        # create needed links
        create_link(stage2_dir, "/usr/bin/bash", "/bin/sh")
        create_link(stage2_dir, "/usr/bin/bash", "/bin/bash")
        create_link(stage2_dir, "/usr/bin/sync", "/bin/sync")
        if os.path.exists(stage2_dir+"/usr/sbin/dmsetup"):
            create_link(stage2_dir, "/usr/sbin/dmsetup", "/sbin/dmsetup")

        # create needed devices
        create_min_devices(stage2_dir)

        # create all possible loop devices
        for i in xrange(LOOP.getMax()):
            LOOP.createDevice(i, chroot=stage2_dir)

        # create empty files
        if not create_file(stage2_dir, "/etc/mtab"):
            return 1
        if not create_file(stage2_dir, "/proc/cmdline"):
            return 1
        if not create_file(stage2_dir, "/proc/mounts"):
            return 1

        # create usable /proc/devices for lvm
        if not create_file(stage2_dir, "/proc/devices",
                           [ "Character devices:\n",
                             " 10 misc",
                             "\n",
                             "Block devices:\n",
                             "  7 loop\n" ]):
            return 1
        # create usable /proc/misc for lvm
        if not create_file(stage2_dir, "/proc/misc",
                           [ " 63 device-mapper" ]):
            return 1

        stage2_chroot = stage2_dir
        # test if bash is usable in changeroot
        print "Testing if /bin/bash is usable in stage2"
        (status, rusage, msg) = pyrpm.runScript( \
            script="/bin/bash --version 2>&1 >/dev/null",
            chroot=stage2_chroot)
        log(msg)
        if status != 0:
            print "ERROR: /bin/bash in stage2 is not usable, exiting."
            return 1

    else: # no_stage2
        stage2_dir = ""

        # get discinfo if available
        if not no_discinfo and source.get(".discinfo"):
            di = get_discinfo(source.get(".discinfo"))
            if di:
                (source_release, source_version, source_arch) = di

    # verify source release and version
    if source_release == "Red Hat Enterprise Linux":
        short_release = "RHEL"
        prefix = "RedHat"
    elif source_release == "Fedora Core":
        short_release = "FC"
        prefix = "Fedora"
    else:
        print "ERROR: Unknown source release '%s'." % source_release
        return 1

    try:
        installation = Installation(short_release, int(source_version),
                                    source_arch)
    except:
        try:
            installation = Installation(short_release, float(source_version),
                                        source_arch)
        except:
            print "ERROR: The installation version '%s' is not valid." % \
                  source_version
            return 1

    if (installation.release == "RHEL" and installation.version < 3) or \
           (installation.release == "FC" and installation.version < 2):
        print "ERROR: %s is not supported as installation source" % \
              installation
        return 1

    print "Installation source: %s [%s]" % (installation, installation.arch)

    if installation.arch not in pyrpm.arch_compats[pyrpm.rpmconfig.machine]:
        print "ERROR: Installation source is not compatible with system arch."
        return 1

    if user_arch and user_arch not in pyrpm.arch_compats[installation.arch]:
        print "ERROR: User requested arch '%s' " % (user_arch) + \
              "is not compatible with installation source."
        return 1

    ####################### get used system ressources #######################

    system = { }
    # get system disks
    system["disks"] = get_system_disks()

    # get system raidmap
    system["raidmap"] = get_system_md_devices()
    for dev in system["raidmap"]:
        installation.devices.add(dev)

    # get system logical volumes
    system["lvmap"] = LVM_VOLGROUP.display()

    ##########################################################################
    #################### prepare installation environment ####################
    ##########################################################################

    # create user supplied disks and diskmap

    diskmap = { }
    if len(args) > 1:
        for arg in args[1:]:
            splits = string.split(arg, ":")
            if len(splits) != 2:
                if len(splits) != 1:
                    usage()
                    return 1
                key = None
                val = string.strip(arg)
            else:
                key = string.strip(splits[0])
                val = string.strip(splits[1])
            if not os.path.exists(val):
                print "ERROR: '%s' does not exist." % val
                return 1
            mode = os.stat(val).st_mode
            if stat.S_ISBLK(mode):
                if not key:
                    if len(val) > 7 and (val[:7] == "/dev/hd" or \
                                         val[:7] == "/dev/sd"):
                        key = val[5:]
                    elif len(val) > 9 and val[:9] == "/dev/dasd":
                        key = val[5:]
            elif stat.S_ISREG(mode):
                if not key:
                    for i in xrange(ord("a"), ord("z"), 1):
                        _key = "hd%c" % chr(i)
                        if not diskmap.has_key(_key):
                            key = _key
                            break;
                if not key:
                    print "ERROR: Unable to find an unused disk name for '%s'" % val
                    return 1
            if not key:
                print "ERROR: Unsupported type of '%s'." % val
                return 1
            disk_image = 0
            if val not in system["disks"]:
                print "Using '%s' as disk '%s'." % (val, key)
                disk_image = 1
            if diskmap.has_key(key):
                print "ERROR: '%s' already exists in diskmap." % key
                return 1

            try:
                diskmap[key] = Disk(val, alloc_loop=1, as_image=disk_image)
            except Exception, msg:
                print "ERROR: %s" % msg
                return 1

    # use system disks?
    if len(diskmap) == 0:
        print "WARNING: No device maps defined, using system harddisks!"
        if confirm:
            if not pyrpm.is_this_ok():
                return 0

        for disk in system["disks"]:
            hd = disk[5:] # strip '/dev/'
            diskmap[hd] = Disk(disk, as_image=0)

    # are there any disks at all?
    if len(diskmap) == 0:
        print "ERROR: No harddisks specified or found."
        return 1

    # print disks
    if verbose:
        disks = diskmap.keys()
        disks.sort()
        for disk in disks:
            if not diskmap[disk].has_disklabel():
                continue
            diskmap[disk].print_info()
            diskmap[disk].print_partitions()

    ### start installation process ###

    if standalone and ks.has_key("network") and \
           (ks.has_key("nfs") or \
            (ks.has_key("url") and ks["url"]["url"][:5] != "file:")):
        ### standalone network setup ###

        # TODO:
        # 1) write network config
        # 2) (re)start networking
        print "TODO: start network"

    has_raid = False
    fstypes = [ ]

    ########################## kickstart second pass ##########################

    # run pre script and reload kickstart file
    if ks.has_key("install") and \
           ks.has_key("pre") and ks["pre"].has_key("script") and \
           len(ks["pre"]["script"]) > 0:
        print "Running pre script"
        interpreter = "/bin/sh"
        if ks["pre"].has_key("interpreter"):
            interpreter = ks["pre"]["interpreter"]
        (status, rusage, msg) = pyrpm.runScript(interpreter,
                                                ks["pre"]["script"],
                                                chroot=stage2_chroot)
        log(msg)
        if status != 0:
            if ks["pre"].has_key("erroronfail"):
                print "ERROR: Pre script failed, aborting."
                return 1
            else:
                print "WARNING: Pre script failed, aborting."

        # parse file again
        ks.clear()
        ks.parse(ks_file)

    # kickstart sanity checks
    if ks.has_key("install"):
        if not ks.has_key("autopart") and \
               (not ks.has_key("partition") or \
                len(ks["partition"]) < 1 or \
                (not ks["partition"].has_key("/") and \
                 not (ks.has_key("raid") and \
                      ks["raid"].has_key("/")) and \
                 not (ks.has_key("logvol") and \
                      ks["logvol"].has_key("/")))):
            print "ERROR: root partition not defined"
            return 1

        # check if volgroups already exists
        if ks.has_key("volgroup") and len(ks["volgroup"]) > 0:
            for group in ks["volgroup"]:
                if system["lvmap"].has_key(group):
                    print "ERROR: Volume group '%s'" % (group) + \
                          " already exists."
                    return 1
    else:
        # no upgrade checks
        pass
    if not keyboard_models.has_key(ks["keyboard"]):
        print "ERROR: Keyboard model '%s' is not defined, exiting." % \
              ks["keyboard"]
        return 1

    ########################### create repo caches ###########################

    # TODO: check repo names for invalid chars
    repos = { }
    if ks.has_key("repo"):
        for repo in ks["repo"]:
            repos[repo] = Repository(pyrpm.rpmconfig,
                                     "%s/%s" % (repos_dir, repo), repo)
            print "Reading repodata from repository '%s'" % repo
            baseurl = mirrorlist = exclude = None
            if ks["repo"][repo].has_key("baseurl"):
                baseurl = ks["repo"][repo]["baseurl"]
            if ks["repo"][repo].has_key("mirrorlist"):
                # TODO: as soon as RepoDB supports mirrorlist directly...
                print "TODO: RepoDB has to support mirrorlists."
#                mirrorlist = ks["repo"][repo]["mirrorlist"]
            if ks["repo"][repo].has_key("exclude"):
                exclude = ks["repo"][repo]["exclude"]
            if not repos[repo].load(baseurl=baseurl, mirrorlist=mirrorlist,
                                       exclude=exclude):
                return 1

    ############## clear and generate partitions and disklabels ##############

    # initialize dmsetup
    if (installation.release == "RHEL" and installation.version >= 5) or \
           (installation.release == "FC" and installation.version >= 4):
        if os.path.exists(stage2_dir+"/sbin/dmsetup"):
            print "Initializing device mapper"
            dmsetup = "/sbin/dmsetup info 2>/dev/ull"
            (status, rusage, msg) = pyrpm.runScript(script=dmsetup,
                                                    chroot=stage2_chroot)
            log(msg)
            if status != 0:
                print "ERROR: dmsetup failed."
                return 1
        else:
            print "WARNING: dmsetup is missing."

    # clearpart, initialize disks
    modified = [ ]
    if ks.has_key("install") and \
           ks.has_key("clearpart") and not ks["clearpart"].has_key("none"):
        disklabel = "msdos"
        linuxtypes = [ 0x82, 0x83, 0x8e, 0xfd ]
        drives = diskmap.keys()
        if ks["clearpart"].has_key("drives"):
            drives = ks["clearpart"]["drives"]
            for disk in drives:
                if diskmap.has_key(disk):
                    continue
                print "ERROR: clearpart --drives: disk %s is not available" % \
                      disk
                return 1
        for disk in drives:
            if ks["clearpart"].has_key("initlabel") or \
                   (ks.has_key("zerombr") and \
                    ks["zerombr"].has_key("yes")) or \
                   not diskmap[disk].has_disklabel():
                print "Initializing disk '%s'" % disk
                diskmap[disk].new_disklabel(disklabel)
                if not disk in modified:
                    modified.append(disk)
            elif diskmap[disk]["disklabel"] != disklabel:
                print "Initializing disk '%s' to '%s' disklabel" % \
                      (disk, disklabel)
                diskmap[disk].new_disklabel(disklabel)
                if not disk in modified:
                    modified.append(disk)
            elif ks["clearpart"].has_key("all"):
                print "Removing all partitions on disk '%s'" % disk
                diskmap[disk].delete_all_partitions()
                if not disk in modified:
                    modified.append(disk)
            elif ks["clearpart"].has_key("linux"):
                partition = diskmap[disk]["partition"].keys()
                partition.reverse()
                for i in partition:
                    partition = diskmap[disk]["partition"][i]
                    if partition["native_type"] in linuxtypes:
                        print "Removing partition %d on %s" % (i, disk)
                        diskmap[disk].delete_partition(i)
                        if not disk in modified:
                            modified.append(disk)

    ####################### disk / partition selection #######################

    # create partitions dict
    # 1) copy autopart key value pair if set
    # 2) overwrite with kickstart partitions
    partitions = { }
    volgroup = { }
    raidmap = { }
    if ks.has_key("install"):
        if ks.has_key("autopart"):
            for name in autopart.keys():
                partitions[name] = autopart[name]
        if ks.has_key("partition"):
            for name in ks["partition"].keys():
                partitions[name] = ks["partition"][name]
        # add raid partitions
        if ks.has_key("raid") and len(ks["raid"]) > 0:
            has_raid = True
            for name in ks["raid"]:
                if partitions.has_key(name):
                    print "ERROR: Multiple definitions of %s" % onpart
                    return
                partitions[name] = { }
                partitions[name]["raid"] = 1
                partitions[name]["device"] = ks["raid"][name]["device"]
                # sanity checks
                for part in ks["raid"][name]["partitions"]:
                    if not part in partitions:
                        print "ERROR: Partition '%s' is not defined." % part
                        return
                if ks["raid"][name].has_key("fstype"):
                    partitions[name]["fstype"] = ks["raid"][name]["fstype"]
                if not partitions[name].has_key("fstype"):
                    # TODO: fstype may depend on release
                    partitions[name]["fstype"] = "ext3"
                if ks["raid"][name].has_key("noformat"):
                    partitions[name]["noformat"] = 1
                if ks["raid"][name].has_key("useexisting"):
                    partitions[name]["useexisting"] = 1
        if ks.has_key("volgroup") and len(ks["volgroup"]) > 0:
            for group in ks["volgroup"]:
                volgroup[group] = { }
                vg = ks["volgroup"][group]
                pesize = LVM_VOLGROUP.default_pesize
                if vg.has_key("pesize"):
                    pesize = vg["pesize"]
                    error = False
                    if pesize[-1] not in [ "k", "K", "m", "M", "g", "G",
                                           "t", "T" ]:
                        error = True
                    else:
                        try:
                            error = (long(string.strip(pesize[:-1])) > 0)
                        except:
                            error = True
                    if error:
                        print "ERROR: Extent size '%s' of '%s' is not valid" %\
                              (pesize, group)
                        return 1
                s = get_size_in_byte(pesize)
                volgroup[group]["pesize"] = pesize
                volgroup[group]["pebytes"] = s
                volgroup[group]["partitions"] = vg["partitions"]
                for onpart in vg["partitions"]:
                    if not partitions.has_key(onpart):
                        print "ERROR: Partition '%s' is not defined." % onpart
                        return 1
                    partitions[onpart]["physical-volume"] = 1
                    partitions[onpart]["volgroup"] = group
#                    partitions[onpart]["pebytes"] = s
                    if vg.has_key("noformat") or vg.has_key("useexisting"):
                        partitions[onpart]["noformat"] = 1

        if ks.has_key("logvol") and len(ks["logvol"]) > 0:
            for name in ks["logvol"]:
                if partitions.has_key(name):
                    print "ERROR: Multiple definitions of %s" % onpart
                    return
                vgname = ks["logvol"][name]["vgname"]
                partitions[name] = {}
                partitions[name]["lvm"] = 1
                partitions[name]["vgname"] = ks["logvol"][name]["vgname"]
                partitions[name]["pebytes"] = volgroup[vgname]["pebytes"]
                partitions[name]["name"] = ks["logvol"][name]["name"]
                partitions[name]["size"] = ks["logvol"][name]["size"]
                if ks["logvol"][name].has_key("fstype"):
                    partitions[name]["fstype"] = ks["logvol"][name]["fstype"]
                if name[:5] != "swap." and \
                       not partitions[name].has_key("fstype"):
                    # TODO: fstype may depend on release
                    partitions[name]["fstype"] = "ext3"
                if ks["logvol"][name].has_key("grow"):
                    partitions[name]["grow"] = 1
                if ks["logvol"][name].has_key("noformat"):
                    partitions[name]["noformat"] = 1
                if ks["logvol"][name].has_key("useexisting"):
                    partitions[name]["useexisting"] = 1
    else:
        # search for installations and load fstab file
        # generate partitions according to fstab entries as onpart and noformat

        print "Searching for installations ..."

        fstemp_dir = tempdir+"/fstemp"
        os.mkdir(fstemp_dir)

        installed = { }
        labelmap = { }
        map = { }
        mdmap = { }
        volgroup = { }
        vgmap = { }
        for disk in diskmap:
            partition = diskmap[disk]["partition"]
            for part in partition:
                onpart = "%s%s" % (disk, part)

                # get labels
                if partition[part]["fstype"] in [ "ext2", "ext3", "xfs",
                                                  "jfs", "reiserfs", "swap" ]:
                    label = getLabel(partition[part]["device"])
                    if label and label != "":
                        if label in labelmap:
                            print "ERROR: Label %s is not unique" % label
                            return
                        labelmap[label] = onpart

                # add raid partitions to mdmap
                if partition[part]["fstype"] == "raid":
                    info = RAID.examine(partition[part]["device"])
                    if not info:
                        continue
                    dev = "md%d" % info["preferred-minor"]
                    if info["failed-devices"] != 0:
                        # ignore faulty raid arrays
                        print "WARNING: Ignoring partition '%s'" % (onpart) + \
                              " from faulty raid array '%s'." % (dev)
                        continue
                    info["onpart"] = onpart
                    if mdmap.has_key(dev):
                        dict = mdmap[dev]
                        for tag in [ "uuid", "magic", "level",
                                     "raid-devices" ]:
                            if dict[tag] != info[tag]:
                                print "WARNING: Ignoring partition '%s'," % \
                                      (onpart) + \
                                      " because of uuid mismatch." % dev
                                continue
                    else:
                        mdmap[dev] = { }
                        dict = mdmap[dev]
                        dict["magic"] = info["magic"]
                        dict["uuid"] = info["uuid"]
                        dict["level"] = info["level"]
                        dict["raid-devices"] = info["raid-devices"]
                        dict["devices"] = { }
                    dict["devices"][info["device-number"]] = { }
                    d = dict["devices"][info["device-number"]]
                    d["onpart"] = info["onpart"]
                    d["device"] = info["device"]

                elif partition[part]["fstype"] == "lvm":
                    print "%s: partition['%s']['fstype']='%s' device='%s'" % \
                          (disk, part, partition[part]["fstype"],
                           partition[part]["device"])

                    dict = LVM_PHYSICAL_VOLUME.info(partition[part]["device"],
                                                    chroot=stage2_chroot)

                    if dict and dict.has_key("vgname"):
                        volgroup.setdefault(dict["vgname"], { }).setdefault("partitions", [ ]).append(partition[part]["device"])
                elif partition[part]["fstype"] in \
                       [ "ext2", "ext3", "xfs", "jfs", "reiserfs" ] and \
                       partition[part]["native_type"] == 0x83:
                    # TODO: check if partition is already mounted
                    print "Trying '%s'" % onpart
                    info = get_installation_info(partition[part]["device"],
                                                 partition[part]["fstype"],
                                                 fstemp_dir)
                    if info:
                        installed[onpart] = info

        # check volume groups and logical volumes
#        print "volgroup=%s" % pprint.pformat(volgroup)
        to_delete = [ ]
        for group in volgroup:
            vgmap[group] = LVM_VOLGROUP(group, chroot=stage2_chroot)
            if not vgmap[group].start():
                to_delete.append(group)

        for group in to_delete:
#            vgmap[group].stop()
            del vgmap[group]
            del volgroup[group]

        volumes = LVM_LOGICAL_VOLUME.display(chroot=stage2_chroot)
        for device in volumes:
            if not volumes[device].has_key("vgname") or \
                   not volumes[device]["vgname"] in volgroup:
                continue
            fstype = detectFstype(device)
            if fstype:
                info = get_installation_info(device, fstype, fstemp_dir)
                if info:
                    installed[device] = info

        for group in vgmap:
            vgmap[group].stop()

        # check mdmap and delete unusable entries
        to_delete = [ ]
        for dev in mdmap:
            if mdmap[dev]["raid-devices"] != len(mdmap[dev]["devices"]):
                print "WARNING: '%s': Number of devices does not match." % dev
                to_delete.append(dev)
                continue
            devs = [ ]
            for d in mdmap[dev]["devices"]:
                if d < 0 or d >= len(mdmap[dev]["devices"]):
                    print "WARNING: '%s': Partition %d is out of range." % \
                          (dev, d)
                    to_delete.append(dev)
                    break
                if d in devs:
                    print "WARNING: '%s': Partition %d" % (dev, d) + \
                          " is defined more than once."
                    to_delete.append(dev)
                    break
                devs.append(d)
            del devs
        for dev in to_delete:
            del mdmap[dev]

        # assemble mdmap entries
        for dev in mdmap:
            devs = [ ]
            for d in mdmap[dev]["devices"]:
                devs.append(mdmap[dev]["devices"][d]["device"])

            raid = RAID(dev, devs)
            device = installation.devices.getNextFree(dev)
            if dev != device:
                raid.mapTo(device)
            raid.assemble()
            installation.devices.add(device)
            raidmap[dev] = raid

            device = "/dev/%s" % device
            fstype = detectFstype(device)

            if fstype:
                info = get_installation_info(device, fstype, fstemp_dir)
                if info:
                    installed[dev] = info

            raid.stop()

#        print "installed=%s" % pprint.pformat(installed)
#        sys.stdout.write("\nWaiting - Press any key to continue.")
#        c = sys.stdin.read(1)

        if len(installed) < 1:
            print "ERROR: Could not find any installations."
            return 1

        if len(installed) > 1:
            print "Available installations:"
            for onpart in release:
                print "%s: %s" % (onpart, installed[onpart]["release"])

        if upgrade:
            if upgrade[:6] == "LABEL=":
                if upgrade[6:] in labelmap:
                    upgrade = labelmap[upgrade[6:]]
                else:
                    print "ERROR: Could not find %s." % upgrade
            elif not upgrade in installed.keys():
                print "ERROR: Could not find %s." % upgrade
                return 1
        elif len(installed) == 1:
            upgrade = installed.keys()[0]
        elif len(installed) > 1:
            print "ERROR: More than one installation found, exiting."
            return 1

        print "About to upgrade installation on '%s'" % upgrade
        if not pyrpm.is_this_ok():
            return 0

        swap_id = 0
        if upgrade:
            for splits in installed[upgrade]["fstab"]:
                if splits[2] in [ "ext2", "ext3", "xfs", "jfs", "reiserfs",
                                  "swap" ]:
                    onpart = None
                    if splits[0][:6] == "LABEL=":
                        label = splits[0][6:]
                        if label in labelmap:
                            onpart = labelmap[label]
                        if not onpart:
                            print "ERROR: Could not find partition for" + \
                                  "label '%s'" % label
                            return 1
                    else:
                        onpart = splits[0]
                    if onpart[:7] == "/dev/md":
                        md = onpart[5:]
                        if md not in mdmap:
                            print "ERROR: Could not find '%s' in mdmap." % md
                            return 1
                        minor = getId(md)
                        for id in mdmap[md]["devices"]:
                            i = id
                            name = "raid.%02d%02d" % (minor, i)
                            while name in partitions:
                                i += 1
                                name = "raid.%02d%02d" % (minor, i)
                            partitions[name] = {
                                "onpart": mdmap[md]["devices"][i]["onpart"] }
                        partitions[splits[1]] = { "fstype": splits[2],
                                                  "device": onpart,
                                                  "noformat": 1,
                                                  "raid": 1 }
                    else:
                        name = splits[1]
                        if name == "swap":
                            name = "swap.%d" % swap_id
                            while name in partitions:
                                swap_id += 1
                                name = "swap.%d" % swap_id
                        partitions[name] = { "fstype": splits[2],
                                             "onpart": onpart,
                                             "noformat": 1 }
        del labelmap
        del mdmap
        del installed
        os.rmdir(fstemp_dir)
#        print "partitions=%s" % pprint.pformat(partitions)

    # create partitionmap
    partitionmap = { }
    to_create = { }
    for name in partitions.keys():
        part = partitions[name]
        if part.has_key("onpart"):
            # partition is defined
            onpart = part["onpart"]
            # remove leading '/dev/' to be compatible with old kickstart
            # versions
            if onpart[:5] == "/dev/":
                onpart = onpart[5:]
            error = 0
            try:
                disk = getName(onpart)
                i = getId(onpart)
            except:
                print "ERROR: '%s' is not a valid partition name." % onpart
                return 1
            if onpart in partitionmap:
                print "ERROR: '%s' is used more than once." %  part["onpart"]
                return 1
            partitionmap[onpart] = { }
            partitionmap[onpart]["name"] = name
            partitionmap[onpart]["disk"] = disk
            partitionmap[onpart]["id"] = i
            if partitionmap[onpart]["id"] < 1:
                print "ERROR: Partition id '%d' is not valid." % \
                      partitionmap[onpart]["id"]
                return 1
        else:
            if part.has_key("raid"):
                onpart = part[name]["device"]
                partitionmap[onpart] = { }
                partitionmap[onpart]["name"] = name
                partitionmap[onpart]["raid"] = 1
                continue
            if part.has_key("lvm"):
                onpart = part["name"]
                partitionmap[onpart] = { }
                partitionmap[onpart]["name"] = name
                partitionmap[onpart]["lvm"] = 1
                partitionmap[onpart]["volgroup"] = part["vgname"]
                partitionmap[onpart]["lvname"] = part["name"]
                to_create.setdefault(part["vgname"], [ ]).append(name)
                continue
            # create partition
            # default disk is hda
            ondisk = "hda"
            # TODO: use SCSI disks
            # Not possible right now, because there is no hardware detection.
            # Therefore the SCSI driver is not in the initrd.
            #if not diskmap.has_key(ondisk):
            #    if diskmap.has_key("sda"):
            #         ondisk = "sda"
            if not diskmap.has_key(ondisk):
                if diskmap.has_key("dasda"):
                    ondisk = "dasda"
            if part.has_key("ondisk"):
                ondisk = part["ondisk"]
                # drop leading '/dev/' to be compatible with old kickstart
                # versions
                if ondisk[:5] == "/dev/":
                    ondisk = ondisk[5:]
            if not part.has_key("size"):
                print "ERROR: No size given for %s." % name
                return 1
            if not diskmap.has_key(ondisk):
                print "ERROR: '%s' is not defined." % ondisk
                return 1
            to_create.setdefault(ondisk, [ ]).append(name)

    # sort to_create keys to first create partitions on physical disks and then
    # on lvm volume groups
    sorted_create_keys = [ ]
    for disk in to_create.keys():
        if diskmap.has_key(disk):
            sorted_create_keys.append(disk)
    sorted_create_keys.sort()
    for disk in to_create.keys():
        if not diskmap.has_key(disk):
            sorted_create_keys.append(disk)

    part_size = { }
    # create primary and logical lists
    for disk in sorted_create_keys:
        if not (disk in diskmap or (volgroup and disk in volgroup)):
            print "INTERNAL ERROR: '%s' is not a disk and no lvm volgroup."
            return

        # check to have only one grow partition per disk
        grow = None
        primary = [ ]
        logical = [ ]
        part_maxsize = { }
        size_needed = 0
        for name in to_create[disk]:
            part = partitions[name]

            if part.has_key("size"):
                part_size[name] = long(part["size"])
            else:
                if name[:4] == "swap" or name == "/boot":
                    # use recommeneded memory size for swap partition if no
                    # size is given
                    part_size[name] = long(autopart[name]["size"])
                else:
                    print "ERROR: '%s' has no size." % name
                    return
            if not part_size.has_key(name):
                print "ERROR: Partition '%s' has no size." % name
                return 1

            if disk in diskmap:
                # physical hard disk (sizes in cylinders)
                part_size[name] = (((part_size[name] * 1024L*1024L) / \
                                    diskmap[disk]["units"] + \
                                    diskmap[disk]["sector_size"] - 1) / \
                                   diskmap[disk]["sector_size"])
                size_needed += part_size[name]
                if part.has_key("maxsize"):
                    part_maxsize[name] = (((part["maxsize"] * 1024L*1024L) / \
                                           diskmap[disk]["units"] + \
                                           diskmap[disk]["sector_size"]-1) / \
                                          diskmap[disk]["sector_size"])
                if part.has_key("asprimary"):
                    primary.append(name)
                else:
                    logical.append(name)
            else:
                # lvm volume group (sizes in physical extents)
                part_size[name] = (part_size[name] * 1024L*1024L + \
                                   part["pebytes"] - 1) / part["pebytes"]
                size_needed += part_size[name]
                if part.has_key("maxsize"):
                    part_maxsize[name] = long(part["maxsize"])
                    part_maxsize[name] = (part_maxsize[name] * 1024L*1024L + \
                                          part["pebytes"] - 1) / \
                                          part["pebytes"]
        if disk in diskmap:
            freespace = diskmap[disk]["freespace_primary"]

            if len(diskmap[disk]["primary"]) + \
                   len(diskmap[disk]["extended"]) > 0:
                print "ERROR: Disk '%s' is already partitioned." % disk
                return 1

        else:
            # lvm volgroup (sizes in physical extents)
            size = 0
            for p in volgroup[disk]["partitions"]:
                d = partitionmap[partitions[p]["onpart"]]["disk"]
                size += (part_size[p] * diskmap[d]["units"] * \
                         diskmap[d]["sector_size"]) / part["pebytes"]
            freespace = [ { "unit-length": size } ]

        if len(freespace) == 0 or size_needed > freespace[0]["unit-length"]:
            print "ERROR: Not enough free blocks on '%s'." % disk
            return 1

        for name in to_create[disk]:
            part = partitions[name]

            if part.has_key("grow"):
                size_needed -= part_size[name]
                # there is only one freespace
                part_size[name] = freespace[0]["unit-length"] - size_needed
                if part_maxsize.has_key(name) and \
                       part_size[name] > part_maxsize[name]:
                    part_size[name] = part_maxsize[name]
                size_needed += part_size[name]

        if not disk in diskmap:
            for name in to_create[disk]:
                part = partitions[name]
                print "Creating new partition '%s' on volgroup '%s'." % \
                      (name, disk)
                onpart = part["name"]
                # size in byte
                partitionmap[onpart]["size"] = part_size[name] * \
                                               part["pebytes"]
            continue

        primary.sort()
        logical.sort()
        if len(primary) + len(logical) + len(diskmap[disk]["partition"]) > 4:
            # more than 4 partitions
            if len(primary) + len(diskmap[disk]["partition"]) < 3:
                # Try to move "/boot" to primary if it is not in primary
                # already and if it exists. If there is no "/boot", try to
                # move "/".
                if "/boot" in logical:
                    primary.insert(0, "/boot")
                    logical.remove("/boot")
                elif not "/boot" in primary and "/" in logical:
                    primary.insert(0, "/")
                    logical.remove("/")
        else:
            # all can be primary
            primary.extend(logical)
            logical = [ ]

            # move /boot to the top, if it is in primary and not on top
            if "/boot" in primary and primary.index("/boot") > 0:
                primary.remove("/boot")
                primary.insert(0, "/boot")

        size_primary = 0
        size_logical = 0
        for name in primary:
            size_primary += part_size[name]
        for name in logical:
            size_logical += part_size[name]

        # create partitions
        for name in primary+logical:
            part = partitions[name]

            # get freespace
            if name in primary:
                freespace = diskmap[disk]["freespace_primary"]
            else:
                if len(diskmap[disk]["extended"]) == 0:
                    # create extended partition

                    freespace = diskmap[disk]["freespace"]
                    match = freespace[0]

                    match = None
                    for free in freespace:
                        if free["unit-length"] >= size_logical:
                            if not match or \
                                   long(match["unit-length"]) > \
                                   long(free["unit-length"]):
                                match = free
                        if free["unit-length"] == size_logical:
                            match = free
                            break
                    if not match:
                        print "ERROR: Extended partition does not fit onto %s." % \
                              disk
                        return 1

                    start = long(match["start"])
                    end = start + (size_logical) * diskmap[disk]["units"] - 1

                    if end > match["end"]:
                        print "ERROR: Size calculation wrong for the extended " + \
                              "partition: Size is "+ (end-start+1) \
                              +" and should be "+ (match["end"]-start+1) + "."
                        return 1

                    print "Creating extended partition on disk %s" % disk
                    try:
                        diskmap[disk].add_partition(0, start, end,
                                                    Partition.PARTITION_EXTENDED,
                                                    None)
                    except Exception, msg:
                        print "ERROR: Failed to create extended partition: %s" % msg
                        return 1

                freespace = diskmap[disk]["freespace_logical"]

            match = None
            for free in freespace:
                if free["unit-length"] >= part_size[name]:
                    if not match or \
                           long(match["unit-length"]) > long(free["unit-length"]):
                        match = free
                if free["unit-length"] == part_size[name]:
                    match = free
                    break
            if not match:
                print "ERROR: Partition %s does not fit onto %s." % \
                      (name, disk)
                return 1

            if match["type"] & Partition.PARTITION_LOGICAL:
                type = Partition.PARTITION_LOGICAL
                # create extended if there is none, yet
            else:
                type = Partition.PARTITION_PRIMARY
            if name[:4] == "swap" or \
                   (part.has_key("fstype") and part["fstype"] == "swap"):
                fstype = "linux-swap"
            elif name[:5] == "raid.":
                fstype = "raid"
            elif name[:3] == "pv.":
                fstype = "lvm"
            else:
                fstype = "ext3"

            start = long(match["start"])
            end = start + part_size[name] * diskmap[disk]["units"] - 1

            if end > match["end"]:
                print "ERROR: Partition %s does not fit onto %s." % \
                      (name, disk)
                return 1

            if part.has_key("volgroup"):
                print "Creating new partition '%s' on disk '%s' as lvm volume group '%s'." % (name, disk, part["volgroup"])
            else:
                print "Creating new partition '%s' on disk '%s'." % \
                      (name, disk)
            num = diskmap[disk].add_partition(0, start, end, type, fstype)
#            try:
#                num = diskmap[disk].add_partition(0, start, end, type, fstype)
#            except Exception, msg:
#                print "ERROR: Failed to create partition '%s': %s" % \
#                      (name, msg)
#                return 1

            onpart = "%s%d" % (disk, num)
            part["onpart"] = onpart
            partitionmap[onpart] = { }
            partitionmap[onpart]["name"] = name
            partitionmap[onpart]["disk"] = disk
            partitionmap[onpart]["id"] = num

            if not disk in modified:
                modified.append(disk)

        if verbose:
            diskmap[disk].print_info()
            diskmap[disk].print_partitions()

#    print "partitions=%s" % pprint.pformat(partitions)
#    print "partitionmap=%s" % pprint.pformat(partitionmap)
#    print "volgroup=%s" % pprint.pformat(volgroup)
#    print "to_create=%s" % pprint.pformat(to_create)

    if len(modified) > 0 and confirm == 1:
        if not pyrpm.is_this_ok():
            return 0

    # commit changes
    for disk in modified:
        try:
            diskmap[disk].commit()
        except Exception, msg:
            if not diskmap[disk].has_key("image"):
                print msg
                return 1
        diskmap[disk].reload()

    # sanity check
    if len(partitionmap) == 0:
        print "INTERNAL ERROR: Partitionmap is empty."
        return 1

    # check partitionmap with diskmap
    error = 0
    for onpart in partitionmap:
        # skip raid devices
        if partitionmap[onpart].has_key("raid"):
            continue
        if partitionmap[onpart].has_key("volgroup"):
            continue
        name = partitionmap[onpart]["name"]
        disk = partitionmap[onpart]["disk"]
        if not diskmap.has_key(disk):
            print "ERROR: Disk '%s' is not defined." % disk
            error = 1
        if error:
            continue
        id = partitionmap[onpart]["id"]
        if not id in diskmap[disk]["partition"]:
            print "ERROR: Partition '%s' does not exist." % onpart
            error = 1
        if error:
            continue
        p = diskmap[disk]["partition"][id]
        sector_size = diskmap[disk]["sector_size"]
        partitionmap[onpart]["start"] = p["start"] * sector_size
        partitionmap[onpart]["end"] = p["end"] * sector_size
        partitionmap[onpart]["size"] = p["length"] * sector_size
        partitionmap[onpart]["device"] = p["device"]
    if error:
        return 1

    # create devices for unmapped disks in stage2
    if not no_stage2:
        # create disk and partition devices if they do not exist
        for disk in diskmap:
            try:
                check_exists(stage2_dir, diskmap[disk]["device"])
            except:
                copy_device(diskmap[disk]["device"], stage2_dir)
        partition = diskmap[disk]["partition"]
        for part in partition:
            try:
                check_exists(stage2_dir, partition[part]["device"])
            except:
                copy_device(partition[part]["device"], stage2_dir)
        for onpart in partitionmap:
            # these devices are managed by the disk class
            if diskmap[partitionmap[onpart]["disk"]].has_key("image"):
                continue
            if partitionmap[onpart].has_key("raid"):
                continue
            if partitionmap[onpart].has_key("volgroup"):
                # create later
                continue
            try:
                check_exists(stage2_dir, partitionmap[onpart]["device"])
            except:
                copy_device(partitionmap[onpart]["device"], stage2_dir)

    # create lvm physical layer
    for onpart in partitionmap:
        name = partitionmap[onpart]["name"]
        part = partitions[name]

        if not part.has_key("physical-volume"):
            continue

        # volgroup: noformat or useexisting: no pvcreate
        if part.has_key("noformat"):
            pv = LVM_PHYSICAL_VOLUME.info(partitionmap[onpart]["device"],
                                          chroot=stage2_chroot)
            if not pv:
                print "ERROR: '%s' is no pysical volume." % onpart
                return 1
        else:
            pv = LVM_PHYSICAL_VOLUME(partitionmap[onpart]["device"],
                                     chroot=stage2_chroot)
            if not pv.create():
                return 1

    # create volume groups
    if volgroup:
        vgmap = { }
        for group in volgroup:
            devs = [ ]
            for p in volgroup[group]["partitions"]:
                onpart = partitions[p]["onpart"]
                if not partitions[p].has_key("volgroup"):
                    print "ERROR: Partition '%s' is no volume group." % onpart
                    return 1
                devs.append(partitionmap[onpart]["device"])

            vgmap[group] = LVM_VOLGROUP(group, chroot=stage2_chroot)
            # TODO: noformat
            if not vgmap[group].create(devs, volgroup[group]["pesize"]):
                return 1
            create_dir(stage2_chroot, "/dev/%s" % group)

            if vgmap[group].extent != volgroup[group]["pebytes"]:
                print "ERROR: Logical volume '%s':" % (group) + \
                      " Physical entent size '%s'" % (vgmap[group].extent) + \
                      " does not match '%s'." % (volgroup[group]["pesize"])
                return 1

#            # create lvm volume devices
#            if not no_stage2:
#                create_dir(stage2_dir, "/dev/%s" % group)
#                copy_device(partitionmap[onpart]["device"], stage2_dir)

    # create logical volumes
    for onpart in partitionmap:
        name = partitionmap[onpart]["name"]
        part = partitions[name]

        if not partitionmap[onpart].has_key("volgroup"):
            continue

        lv = LVM_LOGICAL_VOLUME(onpart, partitionmap[onpart]["volgroup"],
                                chroot=stage2_chroot)
        # TODO: noformat
        if not lv.create(partitionmap[onpart]["size"]):
            return 1
        partitionmap[onpart]["device"] = "/dev/%s/%s" % \
                                         (partitionmap[onpart]["volgroup"],
                                          onpart)
        try:
            check_exists(stage2_chroot, partitionmap[onpart]["device"])
        except:
            print "ERROR: Device '%s' has not been created." % \
                  partitionmap[onpart]["device"]
            return 1

    # partition matches filesystem?
    error = 0
    for onpart in partitionmap:
        # skip raid partitions
        if partitionmap[onpart].has_key("raid"):
            continue
        # skip lvm partitions
        if partitionmap[onpart].has_key("volgroup"):
            continue
        name = partitionmap[onpart]["name"]
        disk = partitionmap[onpart]["disk"]
        id = partitionmap[onpart]["id"]
        p = diskmap[disk]["partition"][id]
        part = partitions[name]

        # check partition types
        if not p["native_type"]:
            print "ERROR: Partition '%s' has no type." % onpart
            error = 1
#        elif p["native_type"] < 1:
#            if disk["disklabel"] != "gpt":
#                print "ERROR: Partition '%s' has illegal type %d." % \
#                      (onpart, p["native_type"])
#                error = 1
        elif p["fstype"] == None:
            # no filesystem type, nothing to do
            pass
        else:
            if p["native_type"] == 0x83 and part.has_key("fstype") and \
                   part["fstype"] in \
                   [ "ext2", "ext3", "xfs", "jfs", "reiserfs" ]:
                continue
            elif p["native_type"] == 0x82 and name[:4] == "swap":
                continue
            elif p["native_type"] == 0xfd: # raid
                continue
            elif p["native_type"] == 0x8e: # lvm
                continue
            else:
                print "ERROR: Filesystem '%s'" % (p["fstype"]) + \
                      " with code 0x%x on" % (p["native_type"]) + \
                      " '%s' is not supported" % (onpart)
                error = 1
                continue
            print "ERROR: Partition '%s'" % (onpart) +\
                  " does not match filesystem '%s'" % (part["fstype"]) + \
                  " for '%s'" % (name)
            error = 1

    if error:
        return 1

    labels = [ ]
    # get all labels
    for disk in diskmap.keys():
        partition = diskmap[disk]["partition"]
        for i in partition.keys():
            label = getLabel(partition[i]["device"])
            if not label:
                continue
            onpart = "%s%d" % (disk, i)
            if onpart in partitionmap:
                name = partitionmap[onpart]["name"]
                partitionmap[onpart]["label"] = label
                part = partitions[name]
                if part.has_key("noformat"):
                    labels.append(label)
            else:
                labels.append(label)

    # create labels
    error = 0
    for onpart in partitionmap:
        name = partitionmap[onpart]["name"]
        if partitionmap[onpart].has_key("raid"):
            print "Disabling mount by label for raid partition '%s'" % onpart
            partitionmap[onpart]["label"] = None
            continue
        if partitionmap[onpart].has_key("lvm"):
            print "Disabling mount by label for logical volume '%s'" % onpart
            partitionmap[onpart]["label"] = None
            continue
        label = label_prefix
        part = partitions[name]

        if part.has_key("noformat"):
            # filesystem marked not to get formatted
            continue

        if part.has_key("fstype") and \
               part["fstype"] in [ "ext2", "ext3", "xfs", "jfs", "reiserfs" ]:
            label += name
        elif name[:4] == "swap" or \
                 (part.has_key("fstype") and part["fstype"] == "swap"):
            label += "%s-swap" % onpart
        elif name[:5] == "raid.":
            pass
        elif name[:3] == "pv.":
            pass
        else:
            print "ERROR: Could not determine filesystem type of '%s'" % onpart
            error = 1
        if part.has_key("label"):
            label = part["label"]
        if len(label) > 16:
            print "WARNING: Label '%s' for '%s' is too long," % (label, onpart) + \
                  ", tuncating to 16 chars."
            label = label[:16]

        # add or increase label id as long as label in labels
        while label in labels:
            try:
                id = getId(label)
            except:
                label = label + "1"
            else:
                try:
                    l_name = getName(label)
                except:
                    print "ERROR: '%s' is no valid label name" % label
                    return 1
                label = "%s%d" % (l_name, (id+1))

        partitionmap[onpart]["label"] = label
    if error:
        return 1

    error = 0
    for disk in diskmap:
        if not os.path.exists(diskmap[disk]["device"]):
            print "ERROR: Disk %s: %s is no valid device." % \
                  (disk, diskmap[disk]["device"])
            error = 1
    for onpart in partitionmap:
        if partitionmap[onpart].has_key("raid"):
            continue
        if partitionmap[onpart].has_key("volgroup"):
            continue
        if not os.path.exists(partitionmap[onpart]["device"]):
            print "ERROR: Partition %s: %s is no valid device." % \
                  (onpart, partitionmap[onpart]["device"])
            error = 1
    if error:
        return 1

    #if verbose:
    #    sys.stdout.write("diskmap=")
    #    pprint.pprint(diskmap)
    #    sys.stdout.write("partitionmap=")
    #    pprint.pprint(partitionmap)
    #    sys.stdout.write("labels=")
    #    pprint.pprint(labels)

    ############################ format partitions ############################

    # prepare to format partitions
    to_format = [ ]
    needed_fstypes = [ ]
    for onpart in partitionmap:
        name = partitionmap[onpart]["name"]
        if name[:5] == "raid.":
            continue
        if name[:3] == "pv.":
            continue
        part = partitions[name]
        if not part.has_key("noformat"):
            to_format.append(onpart)
        if part.has_key("fstype"):
            if not part["fstype"] in needed_fstypes:
                needed_fstypes.append(part["fstype"])
            # TODO: check filesystem size against fs_maxsize (dist and release
            # dependent)
        elif name[:4] == "swap" and not "swap" in needed_fstypes:
            needed_fstypes.append("swap")
            # TODO: check filesystem size against fs_maxsize (dist and release
            # dependent)
    to_format.sort()
    to_format.reverse()

    # check if all needed programs are there: mdadm, lvm, mkswap, mkfs.X and
    # tune2fs
    prefix = ""
    dir = ""
    location = "host system"
    if not no_stage2:
        prefix = "/usr"
        dir = stage2_dir
        location = "stage2 image"
    if has_raid:
        if not os.path.exists(dir+prefix+"/sbin/mdadm"):
            print "ERROR: Could not find %s/sbin/mdadm in %s." % \
                  (prefix, location)
            return 1
    if volgroup:
        if not os.path.exists(dir+"/usr/sbin/lvm"):
            print "ERROR: Could not find /usr/sbin/lvm in %s." % (location)
            return 1
    # TODO: check needed_fstypes early (before paritioning and format)
    for fstype in needed_fstypes:
        if fstype == "swap":
            try:
                check_exists(dir, "%s/sbin/mkswap" % prefix)
            except:
                print "ERROR: Could not find %s/sbin/mkswap in %s." % \
                      (prefix, location)
                return
        else:
            try:
                check_exists(dir, "%s/sbin/mkfs.%s" % (prefix, fstype))
            except:
                print "ERROR: Could not find %s/sbin/mkfs.%s in %s." % \
                      (prefix, fstype, location)
                return
    if "ext3" in needed_fstypes and installation != "RHEL-2.1":
        try:
            check_exists(dir, "%s/sbin/tune2fs" % prefix)
        except:
            print "ERROR: Could not find %s/sbin/tune2fs in %s." % \
                  (prefix, location)
    del prefix
    del dir
    del location

    # print confirm message
    if confirm == 1 and len(to_format) > 0:
        print "About to format these partitions:"
        for onpart in to_format:
            name = partitionmap[onpart]["name"]
            if name[:4] == "swap":
                name = "swap"
            if partitionmap[onpart].has_key("raid"):
                print "\t'%s': raid '%s' [ %s ]" % \
                      (name, onpart, ", ".join(partitions[name]["partitions"]))
            elif partitionmap[onpart].has_key("volgroup"):
                print "\t'%s': lvm logical volume '%s' on '%s'" % \
                      (name, partitionmap[onpart]["lvname"],
                       partitionmap[onpart]["volgroup"])
            else:
                if diskmap[partitionmap[onpart]["disk"]].has_key("image"):
                    disk = "%s:%s" % \
                           (partitionmap[onpart]["disk"],
                            diskmap[partitionmap[onpart]["disk"]]["image"])
                else:
                    disk = partitionmap[onpart]["disk"]
                print "\t'%s': '%s' on '%s'" % (name, onpart, disk)
        if not pyrpm.is_this_ok():
            return 0

    # if there is something to format:
    # 1) copy stage2
    # 2) format partitions
    # 3) free stage2 copy
    if len(to_format) > 0 or has_raid:

        if not no_stage2:
            # create needed links
            for fstype in needed_fstypes:
                if fstype == "swap":
                    create_link(stage2_dir, "/usr/sbin/mkswap", "/sbin/mkswap")
                else:
                    name = "mkfs.%s" % fstype
                    create_link(stage2_dir, "/usr/sbin/%s" % name,
                                "/sbin/%s" % name)
            if "ext3" in needed_fstypes and installation != "RHEL-2.1":
                create_link(stage2_dir, "/usr/sbin/tune2fs", "/sbin/tune2fs")

            if has_raid:
                create_link(stage2_dir, "/usr/sbin/mdadm", "/sbin/mdadm")

        raid_devices = [ ]

        # create/activate raid partitions
        if ks.has_key("install") and has_raid:
            for name in partitions:
                if not partitions[name].has_key("raid"):
                    continue
                onpart = partitions[name]["device"]
                devs = [ ]
                for part in partitions[name]["partitions"]:
                    p = partitions[part]["onpart"]

                    if not partitions[name].has_key("useexisting") and \
                           not partitions[name].has_key("noformat"):
                        # zero raid header (superblock)
                        if verbose:
                            print "Clearing raid header of partition '%s'" % \
                                  part
                        # get chunk blocks / 1024
                        # leaves space at the end of the drive for the RAID
                        # superblock
                        offset = ((((long(partitionmap[p]["size"]) \
                                     / RAID.chunk_size) - 1) \
                                   * RAID.chunk_size) / 1024L) * 1024L
                        zero_device(partitionmap[p]["device"], 4096, offset)

                    devs.append(partitionmap[p]["device"])

                raid = RAID(onpart, devs, chroot=stage2_chroot)
                device = installation.devices.getNextFree(onpart)
                if onpart != device:
                    raid.mapTo(device)

                # raid.assemble and raid.create are generating the device if
                # it is missing
                if partitions[name].has_key("useexisting") or \
                       partitions[name].has_key("noformat"):
                    print "Assembling raid %s" % onpart
                    if not raid.assemble():
                        return 1
                else:
                    print "Creating raid %s" % onpart
                    spares = 0
                    if partitions[name].has_key("spares"):
                        spares = partitions[name]["spares"]
                    if not raid.create(partitions[name]["level"], spares):
                        return 1
                raidmap[onpart] = raid
                installation.devices.add(device)
                partitionmap[onpart]["device"] = "/dev/%s" % device
                partitionmap[onpart]["size"] = raid.size

        ### format partitions ###

        for onpart in to_format:
            name = partitionmap[onpart]["name"]
            label = partitionmap[onpart]["label"]
            part = partitions[name]

            if part.has_key("fstype") and \
                   part["fstype"] in [ "ext2", "ext3", "xfs", "jfs",
                                       "reiserfs" ]:
                mkfs = "/sbin/mkfs.%s" % part["fstype"]

                if part["fstype"] == "ext3":
                    mkfs += " -j" # enable journal for ext3
                    if label:
                        mkfs += " -L '%s'" % label
                elif part["fstype"] == "xfs":
                    mkfs += " -f" # force to overwrite existing filesystem
                    if label:
                        mkfs += " -L '%s'" % label
                elif part["fstype"] == "jfs":
                    if label:
                        mkfs += " -L '%s'" % label
                elif part["fstype"] == "reiserfs":
                    if label:
                        mkfs += " -l '%s'" % label

                mkfs += " '%s'" % partitionmap[onpart]["device"]

                print "Formatting '%s': '%s'" % (name, onpart)
                if verbose:
                    print mkfs

                (status, rusage, msg) = pyrpm.runScript(script=mkfs,
                                                        chroot=stage2_chroot)
                log(msg)
                if status != 0:
                    print "ERROR: mkfs.%s failed." % part["fstype"]
                    return 1

                if part["fstype"] == "ext3" and installation != "RHEL-2.1":
                    # set ext options
                    tune2fs = "/sbin/tune2fs -c 0 -i 0 -O dir_index '%s'" % \
                              partitionmap[onpart]["device"]

                    print "Tuning filesystem '%s'" % onpart
                    if verbose:
                        print tune2fs

                    (status, rusage, msg) = pyrpm.runScript(script=tune2fs,
                                                            chroot=stage2_chroot)
                    log(msg)
                    if status != 0:
                        print "ERROR: tune2fs failed."
                        return 1

            elif name[:4] == "swap" or \
                     (part.has_key("fstype") and part["fstype"] == "swap"):
                l = ""
                if (installation.release == "RHEL" and installation.version >= 4) or \
                       (installation.release == "FC" and installation.version > 2):
                    # no swap labels for RHEL-2.1, RHEL-3, FC-1
                    l = "-L '%s'" % label
                mkswap = "/sbin/mkswap %s '%s' >/dev/null" % \
                         (l, partitionmap[onpart]["device"])

                print "Formatting '%s': '%s'" % (name, onpart)
                if verbose:
                    print mkswap

                (status, rusage, msg) = pyrpm.runScript(script=mkswap,
                                                        chroot=stage2_chroot)
                log(msg)
                if status != 0:
                    print "ERROR: mkswap failed."
                    return 1
            elif name[:5] == "raid.":
                pass
            elif name[:3] == "pv.":
                pass
            else:
                print "ERROR: Unknown filesystem for '%s' (%s)" % (onpart,
                                                                   name)
                return 1

        del to_format

    ############################# mount target ################################

    # prepare to mount partitions
    to_mount = [ ]
    mountmap = { }
    for onpart in partitionmap:
        name = partitionmap[onpart]["name"]
        if name[:5] == "raid.":
            continue
        if name[:3] == "pv.":
            continue
        to_mount.append(name)
        mountmap[name] = onpart
    to_mount.sort()

    for mntpnt in to_mount:
        if mntpnt[:4] == "swap":
            if standalone:
                # swapon if in standalone mode
                d = chroot_device(partitionmap[mountmap[mntpnt]]["device"],
                                  stage2_chroot)
                if swapon(d) == 1:
                    return 1
                partitionmap[mountmap[mntpnt]]["on"] = None
        else:
            if mntpnt == "/":
                dir = target_dir
            else:
                dir = target_dir+mntpnt
                if not os.path.exists(dir):
                    try:
                        os.mkdir(dir)
                    except Exception, msg:
                        print "ERROR: Could not create '%s': %s" % (dir, msg)
                        return 1

            part = partitions[mntpnt]

            d = chroot_device(partitionmap[mountmap[mntpnt]]["device"],
                              stage2_chroot)
            if verbose:
                print "Mounting '%s' on '%s'" % (d, dir)
            opts = "defaults"
            if part.has_key("fsoptions"):
                opts = part["fsoptions"]
            print "Mounting '%s'" % mntpnt
            mount(d, dir, fstype=part["fstype"], options=opts)

    target_chroot = target_dir
    pyrpm.rpmconfig.buildroot = target_chroot

    ############################ package selection ############################

    if ks.has_key("install"):
        groups = [ ]
        if ks["packages"].has_key("groups") and \
               len(ks["packages"]["groups"]) > 0:
            # only add group ids to group array
            for group in ks["packages"]["groups"]:
                found = False
                group_id = source.comps.getGroup(group)
                if group_id:
                    groups.append(group_id)
                    found = True
                if repo_comps:
                    for repo in repos:
                        if not repos[repo].comps:
                            continue
                        group_id = repos[repo].comps.getGroup(group)
                        if group_id:
                            groups.append(group_id)
                            found = True
                if not found:
                    print "WARNING: Could not find group '%s'." % group

        # load comps file for groups and default packages
        if not source.comps:
            print "ERROR: Could not find comps file, exiting."
            return 1

        pkgs = [ ]
        # everything install
        if "everything" in groups:
            for group in source.comps.getGroups():
                pkgs.extend(source.comps.getPackageNames(group))
                pkgs.extend(source.comps.getConditionalPackageNames(group))
            if repo_comps:
                for repo in repos:
                    if not repos[repo].comps:
                        continue
                    comps = repos[repo].comps
                    for group in comps.getGroups():
                        pkgs.extend(comps.getPackageNames(group))
                        pkgs.extend(comps.getConditionalPackageNames(group))
            # add and remove packages from package list
            if ks.has_key("packages"):
                if ks["packages"].has_key("drop"):
                    for pkg in ks["packages"]["drop"]:
                        if pkg in pkgs:
                            pkgs.remove(pkg)
                if ks["packages"].has_key("add"):
                    for pkg in ks["packages"]["add"]:
                        if not pkg in pkgs:
                            pkgs.append(pkg)
        else:
            # add default group "base" if it is not in groups and
            # no_default_groups is not set
            if not no_default_groups:
                if not "base" in groups:
                    groups.append("base")
                if not "core" in groups:
                    groups.append("core")

            pyrpm.normalizeList(groups)

            # add default desktop
            if ks.has_key("xconfig"):
                if ks["xconfig"].has_key("startxonboot"):
                    if not "base-x" in groups:
                        groups.append("base-x")
                    desktop = "GNOME"
                    if ks["xconfig"].has_key("defaultdesktop"):
                        desktop = ks["xconfig"]["defaultdesktop"]
                    desktop = "%s-desktop" % desktop.lower()
                    if not desktop in groups:
                        print "Adding group %s" % desktop
                        groups.append(desktop)

            # add package for selected groups
            for group in groups:
                if source.comps.hasGroup(group):
                    pkgs.extend(source.comps.getPackageNames(group))
                if repo_comps:
                    for repo in repos:
                        if not repos[repo].comps:
                            continue
                        comps = repos[repo].comps
                        if comps.hasGroup(group):
                            pkgs.extend(comps.getPackageNames(group))

            # add xorg driver package for FC-5
            if ks.has_key("xconfig"):
                if installation.release == "FC" and installation.version > 4:
                    if ks["xconfig"].has_key("driver"):
                        pkgs.append("xorg-x11-drv-%s" % \
                                    ks["xconfig"]["driver"])
                    else:
                        if not "xorg-x11-drivers" in pkgs:
                            pkgs.append("xorg-x11-drivers")

            # add packages for needed filesystem types
            for fstype in needed_fstypes:
                if fstype == "swap":
                    continue
                mkfs = "/sbin/mkfs.%s" % fstype
                try:
                    addPkgByFileProvide(source.repo, mkfs, pkgs,
                                        "%s filesystem creation" % fstype)
                except Exception, msg:
                    print "ERROR: %s" % msg
                    return 1

            # add comps package
            if not "comps" in pkgs:
                pkgs.append("comps")

            # append mdadm
            if has_raid:
                try:
                    addPkgByFileProvide(source.repo, "/sbin/mdadm", pkgs,
                                        "raid configuration")
                except Exception, msg:
                    print "WARNING: %s" % msg

            # append authconfig
            if ks.has_key("authconfig"):
                try:
                    addPkgByFileProvide(source.repo, "/usr/sbin/authconfig",
                                        pkgs, "authentication configuration")
                except Exception, msg:
                    print "WARNING: %s" % msg

            # append iptables and config tool
            if ks.has_key("firewall") and \
                   not ks["firewall"].has_key("disabled") and \
                   not "iptables" in pkgs:
                print "Adding package 'iptables'"
                pkgs.append("iptables")

                # no firewall config tool in RHEL-3
                if (installation.release == "RHEL" and \
                    installation.version >= 4) or \
                   (installation.release == "FC" and \
                    installation.version >= 3):
                    try:
                        addPkgByFileProvide(source.repo, "/usr/sbin/lokkit",
                                            pkgs, "firewall configuration")
                    except Exception, msg:
                        print "WARNING: %s" % msg

            # append lokkit
            if ks.has_key("selinux") and \
                   (installation.release == "RHEL" and \
                    installation.version >= 4) or \
                   (installation.release == "FC" and \
                    installation.version >= 3):
                try:
                    addPkgByFileProvide(source.repo, "/usr/sbin/lokkit", pkgs,
                                        "selinux configuration")
                except Exception, msg:
                    print "WARNING: %s" % msg

            # append kernel
            if not "kernel" in pkgs and not "kernel-smp" in pkgs:
                print "Adding package 'kernel'"
                pkgs.append("kernel")

            # append firstboot
            if ks.has_key("firstboot") and \
                   not ks["firstboot"].has_key("disabled") and \
                   not "firstboot" in pkgs:
                print "Adding package 'firstboot'"
                pkgs.append("firstboot")

            # append dhclient
            if ks.has_key("bootloader") and not "grub" in pkgs:
                print "Adding package 'grub'"
                pkgs.append("grub")

            # append grub
            if ks.has_key("network") and len(ks["network"]) > 0:
                for net in ks["network"]:
                    if net["bootproto"] == "dhcp" and \
                           not "dhclient" in pkgs:
                        print "Adding package 'dhclient'"
                        pkgs.append("dhclient")

            # add and remove packages from package list
            if ks.has_key("packages"):
                if ks["packages"].has_key("drop"):
                    for pkg in ks["packages"]["drop"]:
                        if pkg in pkgs:
                            pkgs.remove(pkg)
                if ks["packages"].has_key("add"):
                    for pkg in ks["packages"]["add"]:
                        if not pkg in pkgs:
                            pkgs.append(pkg)

            # get langsupport packages
            languages = [ ]
            if ks.has_key("langsupport"):
                if ks["langsupport"].has_key("default"):
                    languages.append(ks["langsupport"]["default"][:2])
                    languages.append(ks["langsupport"]["default"][:5])
                for lang in ks["langsupport"]["supported"]:
                    languages.append(lang[:2])
                    languages.append(lang[:5])
                pyrpm.normalizeList(languages)
            compsLangsupport(pkgs, source.comps, languages)
            if repo_comps:
                for repo in repos:
                    if not repos[repo].comps:
                        continue
                    compsLangsupport(pkgs, repos[repo].comps, languages)

        pyrpm.normalizeList(pkgs)

        # no packages?
        if len(pkgs) < 1:
            print "Nothing to do."
            return 0

    # else: upgrade
    # ignore package selection

    ############################## setup target ##############################

    # create essential directories
    os.umask(022)

    create_dir(target_chroot, "/boot/grub")
    create_dir(target_chroot, "/dev")
    create_dir(target_chroot, "/etc/rpm")
    create_dir(target_chroot, "/etc/sysconfig")
    create_dir(target_chroot, "/proc")
    create_dir(target_chroot, "/root")
    if (installation.release == "RHEL" and installation.version >= 4) or \
           (installation.release == "FC" and installation.version >= 2):
        create_dir(target_chroot, "/sys")
    create_dir(target_chroot, "/tmp", mode=1777)
    create_dir(target_chroot, "/var/log")
    create_dir(target_chroot, "/var/tmp", mode=1777)

    # mount temporary filesystems for /proc, /tmp
    # /proc
    print "Mounting temporary /proc"
    try:
        mount("None", target_dir+"/proc", fstype="ramfs")
    except Exception, msg:
        print "ERROR: Unable to mount temporary /proc: %s" % msg
        return 1
    create_dir(target_chroot, "/proc/.real_proc")
    try:
        mount("None", target_dir+"/proc/.real_proc", fstype="proc")
    except Exception, msg:
        print "ERROR: Unable to mount temporary /proc/.real_proc: %s" % msg
        return 1
    if (installation.release == "RHEL" and installation.version >= 4) or \
           (installation.release == "FC" and installation.version >= 2):
        # /sys
        print "Mounting temporary /sys"
        try:
            mount("None", target_dir+"/sys", fstype="ramfs")
        except Exception, msg:
            print "ERROR: Unable to mount temporary /sys: %s" % msg
            return 1
#    # /tmp
#    print "Mounting temporary /tmp"
#    try:
#        mount("None", target_dir+"/tmp", fstype="ramfs")
#    except Exception, msg:
#        print "ERROR: Unable to mount temporary /tmp: %s" % msg
#        return 1

    # create essential devices
    create_min_devices(target_chroot)

    # create /dev/mapper/control
    if volgroup:
        create_dir(target_chroot, "/dev/mapper")
        create_device(target_chroot, "/dev/mapper/control",
                      0600 | stat.S_IFCHR, 10, 63)

    # create needed devices
    for disk in diskmap:
        copy_device(diskmap[disk]["device"], target_chroot,
                    source_dir=stage2_dir)
        partition = diskmap[disk]["partition"]
        for part in partition:
            copy_device(partition[part]["device"], target_chroot,
                        source_dir=stage2_dir)
    for onpart in partitionmap:
        if partitionmap[onpart].has_key("volgroup"):
            create_dir(target_chroot, "/dev/%s" % \
                       partitionmap[onpart]["volgroup"])
            copy_device(partitionmap[onpart]["device"], target_chroot,
                        source_dir=stage2_dir)
        else:
            copy_device(partitionmap[onpart]["device"], target_chroot,
                        source_dir=stage2_dir)

    if (installation.release == "RHEL" and installation.version >= 4) or \
           (installation.release == "FC" and installation.version >= 2):
        create_dir(target_chroot, "/sys/block")
        create_dir(target_chroot, "/sys/bus")
        create_dir(target_chroot, "/sys/class")
        create_dir(target_chroot, "/sys/devices")
        create_dir(target_chroot, "/sys/kernel")
        create_dir(target_chroot, "/sys/module")

    # create essential files in proc
    # /proc/1/ is needed to calm down 'killall <process name>'
    create_dir(target_chroot, "/proc/1/fd")
    create_file(target_chroot, "/proc/1/fd/0", [ ])
    create_file(target_chroot, "/proc/1/stat", [ ])
    create_link(target_chroot, "/proc/.real_proc/self", "/proc/self")
    create_dir(target_chroot, "/proc/bus/pci")
    create_file(target_chroot, "/proc/bus/pci/devices", [ ])
    create_file(target_chroot, "/proc/cmdline", [ ])
    create_file(target_chroot, "/proc/cpuinfo", [ ])
    # /proc/devices
    if ks.has_key("install") and \
           not os.path.exists(target_chroot+"/proc/devices"):
        # create usable /proc/devices for lvm
        create_file(target_chroot, "/proc/devices",
                    [ "Character devices:\n",
                      " 10 misc\n",
                      "\n",
                      "Block devices:\n",
                      "  7 loop\n" ])
    create_file(target_chroot, "/proc/modules", [ ])
    create_dir(target_chroot, "/proc/net")
    create_file(target_chroot, "/proc/net/unix", [ ])
    create_file(target_chroot, "/proc/stat",
        ["cpu  90726 6 9635 1623012 38482 1961 0 0\n",
         "cpu0 90726 6 9635 1623012 38482 1961 0 0\n",
         "processes 26341\n",
         "procs_running 1\n",
         "procs_blocked 0\n"])
    create_file(target_chroot, "/proc/swaps", [ ])
    create_file(target_chroot, "/proc/uptime", [ ])
    create_file(target_chroot, "/proc/version", [ ])
    create_file(target_chroot, "/proc/vmstat", [ ])

    # /proc/mdstat
    if ks.has_key("install") and raidmap and \
           not os.path.exists(target_chroot+"/proc/mdstat"):
        print "Writing '/proc/mdstat'"
        content = [ ]
        for raid in raidmap:
            content.append("%s : active raid%d \n" % \
                           (raid, raidmap[raid].level))
        create_file(target_chroot, "/proc/mdstat", content)

    # /proc/lvm/global
    if ks.has_key("install") and volgroup:
        create_dir(target_chroot, "/proc/lvm")
        if not os.path.exists(target_chroot+"/proc/lvm/global"):
            print "Writing '/proc/lvm/global'"
            content = [ ]
            for group in volgroup:
                content.append("VG:  %s\n" % group)
            create_file(target_chroot, "/proc/lvm/group", content)

    if ks.has_key("install"):
        # /etc/sysconfig/kernel
        content = [ \
            '# UPDATEDEFAULT specifies if new-kernel-pkg should make\n',
            '# new kernels the default\n',
            'UPDATEDEFAULT=yes\n',
            '\n',
            '# DEFAULTKERNEL specifies the default kernel package type\n',
            'DEFAULTKERNEL=kernel\n' ]
        create_file(target_chroot, "/etc/sysconfig/kernel", content)

    # /etc/fstab, /etc/mtab, /proc/paritions and /proc/mounts
    fstab_content = [ ]
    mtab_content = [ ]
    partition = partitionmap.keys()
    partition.sort()
    partition.reverse()

    # sort by partition names and not onpart
    onparts = { }
    for onpart in partition:
        name = partitionmap[onpart]["name"]
        if name[:5] == "raid." or name[:3] == "pv.":
            continue
        onparts[name] = onpart
    parts = onparts.keys()
    parts.sort()

    for name in parts:
        onpart = onparts[name]
        part = partitions[name]

        major = minor = 0
        if part.has_key("fstype") and name[:4] != "swap":
            fs = part["fstype"]
            major = 1
            if name == "/":
                minor = 1
            else:
                minor = 2
            label = partitionmap[onpart]["label"]
            if partitionmap[onpart]["label"]:
                src = "LABEL=%s" % partitionmap[onpart]["label"]
            elif partitionmap[onpart].has_key("lvm"):
                # there is no device mapping for lvm, therefore use real
                # device
                src = partitionmap[onpart]["device"]
            else:
                src = "/dev/%s" % onpart
            opts = "defaults"
            if partitionmap[onpart].has_key("fsoptions"):
                opts = partitionmap[onpart]["fsoptions"]
            fstab_content.append("%s\t\t%s\t\t%s\t%s\t%d %d\n" % \
                                 (src, name, fs, opts, major, minor))
            mtab_content.append("/dev/%s %s %s rw %d %d\n" % \
                                (onpart, name, fs, major, minor))
        elif name[:4] == "swap":
            continue
        else:
            print "WARNING: Unknown filesystem for '%s'" % onpart
            continue
    fstab_content.extend([ "none\t\t/dev/pts\t\tdevpts\tgid=5,mode=620\t0 0\n",
                           "none\t\t/dev/shm\t\ttmpfs\tdefaults\t0 0\n",
                           "none\t\t/proc\t\tproc\tdefaults\t0 0\n" ])
    mtab_content.append("none /proc proc defaults 0 0\n")
    if (installation.release == "RHEL" and installation.version >= 4) or \
           (installation.release == "FC" and installation.version >= 2):
        fstab_content.append("/dev/sys\t\t/sys\t\tsysfs\tdefaults\t0 0\n")
        mtab_content.append("/dev/sys /sys sysfs defaults 0 0\n")
    for name in parts:
        if name[:4] != "swap":
            continue
        onpart = onparts[name]
        if (installation.release == "RHEL" and installation.version < 4) or \
               (installation.release == "FC" and installation.version <= 2) or \
               not partitionmap[onpart]["label"]:
            # pre RHEL-4, pre FC-2 does not support swap labels
            if partitionmap[onpart].has_key("lvm"):
                what = partitionmap[onpart]["device"]
            else:
                what = "/dev/%s" % onpart
        else:
            what = "LABEL=%s" % partitionmap[onpart]["label"]
        fstab_content.append("%s\t\tswap\t\tswap\tdefaults\t0 0\n" % what)

    if ks.has_key("install") and \
           not os.path.exists(target_chroot+"/etc/fstab"):
        print "Writing '/etc/fstab'"
        create_file(target_chroot, "/etc/fstab", fstab_content)

    print "Writing temporary '/etc/mtab'"
    create_file(target_chroot, "/etc/mtab", mtab_content)

    print "Writing temporary '/proc/mounts'"
    create_file(target_chroot, "/proc/mounts", mtab_content)

    partitions_content = [ "major minor  #blocks  name\n", "\n" ]
    for disk in diskmap:
        stats = os.stat(target_chroot + diskmap[disk]["device"])
        partitions_content.append("%4d  %4d  %8d %s\n" % \
                                  (os.major(stats.st_rdev),
                                   os.minor(stats.st_rdev),
                                   diskmap[disk]["length"], disk))
        partition = diskmap[disk]["partition"]
        for part in partition:
            stats = os.stat(target_chroot + partition[part]["device"])
            partitions_content.append("%4d  %4d  %8d %s\n" % \
                                      (os.major(stats.st_rdev),
                                       os.minor(stats.st_rdev),
                                       partition[part]["length"],
                                       "%s%d" % (disk, part)))
    print "Writing temporary '/proc/partitions'"
    create_file(target_chroot, "/proc/partitions", partitions_content)

    # /etc/mdadm.conf
    if ks.has_key("install") and has_raid and \
           not os.path.exists(target_chroot+"/etc/mdadm.conf"):
        print "Writing '/etc/mdadm.conf'"
        content = [ "# mdadm.conf written by pyrpmkickstart\n",
                    "DEVICE partitions\n",
                    "MAILADDR root\n" ]
        for name in partitions:
            if not partitions[name].has_key("raid"):
                continue
            onpart = partitions[name]["device"]
            devs = [ ]
            for part in partitions[name]["partitions"]:
                p = partitions[part]["onpart"]
                dev = "/dev/%s%d" % (partitionmap[p]["disk"],
                                     partitionmap[p]["id"])
                devs.append(dev)
            content.append("ARRAY /dev/%s level=raid%d devices=%s\n" % \
                           (onpart, partitions[name]["level"],
                            ",".join(devs)))
        create_file(target_chroot, "/etc/mdadm.conf", content)

    # /etc/hosts
    if ks.has_key("install") and \
           not os.path.exists(target_chroot+"/etc/hosts"):
        print "Writing '/etc/hosts'"

        hostname = ""
        hosts = ""
        if ks.has_key("network") and len(ks["network"]) > 0:
            for net in ks["network"]:
                if net.has_key("hostname"):
                    if not net.has_key("ip"):
                        if not hostname:
                            # only use first hostname
                            hostname = "%s\t" % net["hostname"]
                    else:
                        hosts += "%s\t\t%s\n" % (net["ip"], net["hostname"])

        create_file(target_chroot, "/etc/hosts", [ \
            "# Do not remove the following line, or various programs\n",
            "# that require network functionality will fail.\n",
            "127.0.0.1\t\t%slocalhost.localdomain\tlocalhost\n" % hostname,
            hosts ])

    # /etc/resolv.conf
    if ks.has_key("install") and not \
           os.path.exists(target_chroot+"/etc/resolv.conf"):
        content = [ ]
        if ks.has_key("network") and len(ks["network"]) > 0:
            hostname = domainname = None
            for net in ks["network"]:
                if net.has_key("hostname") and not hostname:
                    hostname = net["hostname"]
                    idx = string.find(hostname, ".")
                    if idx >= 0:
                        domainname = hostname[(idx+1):]
                        content.append("search %s\n" % domainname)
            if not domainname:
                content.append("search localdomain\n")
            if net.has_key("nameserver") and not net.has_key("nodns"):
                for server in net["nameserver"]:
                    content.append("nameserver %s\n" % server)
        create_file(target_chroot, "/etc/resolv.conf", content)

    # /etc/modprobe.conf and /etc/modules.conf
    if ks.has_key("install"):
        if (installation.release == "RHEL" and installation.version < 4) or \
               (installation.release == "FC" and installation.version < 2):
            # pre RHEL-4, pre FC-2 use modules.conf
            conf = "/etc/modules.conf"
        else:
            conf = "/etc/modprobe.conf"
        print "Writing '%s'" % conf

        content = [ ]
        adapter = 0
        # add options for defined devices in ks["device"]
        if ks.has_key("device") and len(ks["device"]) > 0:
            for type in ks["device"]:
                if ks["device"][type] and len(ks["device"][type]) > 0:
                    for module in ks["device"][type]:
                        if type == "scsi":
                            if adapter == 0:
                                content.append('alias scsi_hostadapter %s\n' % \
                                               module)
                            else:
                                content.append('alias scsi_hostadapter%d %s\n' % \
                                               adapter, module)
                        if ks["device"][type][module].has_key("opts"):
                            content.append('options %s %s\n' % \
                                           (module,
                                            ks["device"][type][module]["opts"]))
        create_file(target_chroot, conf, content)

    # /etc/rpm/platform
    # disable for now, it writes i386 instead of i686 and then rpm won't
    # install i586 rpms anymore: (TODO: re-enable this again)
#    if ks.has_key("install") and not os.path.exists(target_chroot+"/etc/rpm/platform"):
#    create_file(target_chroot, "/etc/rpm/platform",
#                [ "%s-redhat-linux" % installation.arch ])

    # create /var/log/lastlog and /var/log/messages
    if ks.has_key("install"):
        create_file(target_chroot, "/var/log/lastlog", mode=0644)
        create_file(target_chroot, "/var/log/messages", mode=0600)

    # print previous installed release
    if ks.has_key("upgrade"):
        f = target_dir+"/etc/redhat-release"
        if not os.path.exists(f):
            f = target_dir+"/etc/fedora-release"
        if os.path.exists(f):
            try:
                fd = open(f)
            except:
                pass
            else:
                lines = fd.readlines()
                fd.close()
                if lines:
                    print "Detected '%s' installation" % \
                          string.strip(string.join(lines))

#    choice = raw_input("Continue with installation? [y/N]: ")
#    log(choice+"\n")
#    if len(choice) == 0 or choice[0] != "y" and choice[0] != "Y":
#        return

    ##########################################################################
    ############################## installation ##############################
    ##########################################################################

    if ks.has_key("install"):
        print "Preparing installation"
    else:
        print "Preparing upgrade"

    # configure yum
    os.mkdir("%s/yum.cache" % tempdir)
    os.mkdir("%s/yum.repos.d" % tempdir)
    yum_conf = tempdir+"/yum.conf"
    try:
        fd = open(yum_conf, "w")
    except Exception, msg:
        print "ERROR: Configuration of yum failed:", msg
        return 1
    fd.write("[main]\n")
    fd.write("cachedir=%s/yum.cache\n" % tempdir)
    fd.write("debuglevel=0\n")
    fd.write("errorlevel=0\n")
    fd.write("pkgpolicy=newest\n")
    fd.write("distroverpkg=redhat-release\n")
    fd.write("tolerant=1\n")
    fd.write("exactarch=1\n")
    fd.write("retries=20\n")
    fd.write("obsoletes=1\n")
    fd.write("reposdir=%s/yum.repos.d\n" % tempdir)
    if ks.has_key("install") and \
           ks.has_key("packages") and ks["packages"].has_key("drop"):
        fd.write("exclude=%s\n" % string.join(ks["packages"]["drop"]))
    fd.write("\n")
    fd.write("[base]\n")
    fd.write("name=dist %s\n" % installation)
    if source.baseurl:
        fd.write("baseurl=%s\n" % source.baseurl)
    # no repo.mirrorlist and repo.exclude for base
    fd.write("\n")
    # add repos
    for repo in repos:
        fd.write("[%s]\n" % repo)
        fd.write("name=%s\n" % repo)
        if repos[repo].baseurl:
            fd.write("baseurl=%s\n" % repos[repo].baseurl)
        if repos[repo].mirrorlist:
            fd.write("mirrorlist=%s\n" % repos[repo].mirrorlist)
        if repos[repo].exclude:
            fd.write("exclude=%s\n" % string.join(repos[repo].exclude))
        fd.write("\n")
    fd.close()

    # yum
    if orig_yum:
        yum = "yum -y -c '%s' --installroot='%s'" % (yum_conf, target_chroot)
    else:
        yum = "PATH=%s:${PATH}; export PATH; " % (PYBINDIR) + \
              "PYTHONPATH=%s; export PYTHONPATH; " % (PYRPMDIR) + \
              "pyrpmyum -y --hash -c '%s' --root='%s' --cachedir='%s'" % \
              (yum_conf, target_chroot, cache_dir)
        # --servicehack
        if no_cache:
            yum += " --nocache"
    if ks.has_key("packages") and ks["packages"].has_key("ignoredeps") and \
           ks["packages"].has_key("ignoremissing"):
        yum += " --nodeps"
    if ks.has_key("install"):
        if autoerase:
            yum += " --autoerase "
        yum += " install "
        yum += string.join(pkgs)
    else:
        if not orig_yum:
            yum += " --autoerase "
        yum += " update "
    #print yum
    pid = os.fork()
    if pid != 0:
        (rpid, status) = os.waitpid(pid, 0)
        if status != 0:
            if ks.has_key("install"):
                print "ERROR: Installation failed, aborting."
            else:
                print "ERROR: Upgrade failed, aborting."
            return 1
    else:
        status = 255
        try:
            p = popen2.Popen4(yum)
            ch = p.fromchild.read(1)
            while ch:
                sys.stdout.write(ch)
                sys.stdout.flush()
                ch = p.fromchild.read(1)
            status = p.poll()
        except:
            os._exit(255)
        if status != 0:
            os._exit(255)
        os._exit(0)

    ##########################################################################
    ############################## configuration #############################
    ##########################################################################

    # check system dirs
    if not check_dir(target_chroot, "/etc") or \
           not check_dir(target_chroot, "/etc/sysconfig"):
        print "Aborting."
        return 1

    # run authconfig if set
    if ks.has_key("install") and ks.has_key("authconfig"):
        print "Configuring authentication"

        # Create /etc/samba for authconfig
        if not os.path.exists(target_chroot+"/etc/samba"):
            os.mkdir(target_chroot+"/etc/samba")

        try:
            check_exists(target_chroot, "/usr/sbin/authconfig")
        except:
            print "ERROR: /usr/sbin/authconfig does not exist, skipping."
        else:
            authconfig = "/usr/sbin/authconfig --kickstart --nostart"
            for tag in ks["authconfig"]:
                if ks["authconfig"][tag]:
                    authconfig += " --%s=%s" % (tag, ks["authconfig"][tag])
                else:
                    authconfig += " --%s" % tag
            (status, rusage, msg) = pyrpm.runScript(script=authconfig,
                                                    chroot=target_chroot)
            log(msg)
            if status != 0:
                print "ERROR: authconfig failed"

    # setting root password
    if ks.has_key("install") and ks.has_key("rootpw"):
        print "Setting root password"

        try:
            check_exists(target_chroot, "/usr/sbin/usermod")
        except:
            print "ERROR: /usr/sbin/usermod does not exist, skipping."
        else:
            password = ks["rootpw"].keys()[0]
            if not ks["rootpw"][password].has_key("iscrypted"):
                salt = ""
                salt_len = 2
                if ks["authconfig"].has_key("enablemd5"):
                    salt = "$1$"
                    salt_len = 8
                for i in range(salt_len):
                    salt += random.choice(string.letters+string.digits+"./")
                password = crypt.crypt(password, salt)
            (status, rusage, msg) = pyrpm.runScript( \
                    script="/usr/sbin/usermod -p '%s' root" % password,
                    chroot=target_chroot)
            log(msg)
            if status != 0:
                print "ERROR: Setting root password failed"

    # /etc/inittab
    if ks.has_key("install") and \
           ks.has_key("xconfig") and ks["xconfig"].has_key("startxonboot"):
        print "Configuring runlevel"

        try:
            fd_in = open(target_chroot+"/etc/inittab", "r")
            fd_out = open(target_chroot+"/etc/inittab.pyrpmkickstart", "w")
        except Exception, msg:
            print "ERROR: Configuration of '/etc/inittab' failed:", msg
        else:
            while 1:
                line = fd_in.readline()
                if not line:
                    break
                if len(line) > 3 and line[:3] == "id:":
                    id = string.split(line, ":")
                    if len(id) > 1:
                        id[1] = "5"
                        line = string.join(id, ":")
                    else:
                        print "ERROR: Malformed '/etc/inittab'"
                fd_out.write(line)
            fd_in.close()
            fd_out.close()
            os.unlink(target_chroot+"/etc/inittab")
            os.rename(target_chroot+"/etc/inittab.pyrpmkickstart",
                      target_chroot+"/etc/inittab")

    # /etc/sysconfig/selinux
    if ks.has_key("install") and ks.has_key("selinux") and \
           ((installation.release == "RHEL" and installation.version > 3) or \
            (installation.release == "FC" and installation.version > 2)):
        print "Configuring selinux"

        try:
            check_exists(target_chroot, "/usr/sbin/lokkit")
        except:
            print "ERROR: /usr/sbin/lokkit does not exist, skipping."
        else:
            lokkit = "/usr/sbin/lokkit --quiet --nostart --selinux='%s'" % \
                     ks["selinux"].keys()[0]
            (status, rusage, msg) = pyrpm.runScript(script=lokkit,
                                                    chroot=target_chroot)
            log(msg)
            if status != 0:
                print "ERROR: Configuring selinux failed."

    # /etc/sysconfig/clock
    if ks.has_key("install") and ks.has_key("timezone"):
        print "Configuring timezone"
        zone = ks["timezone"].keys()[0]
        if ks["timezone"][zone].has_key("utc"):
            _utc = 'UTC=true\n'
        else:
            _utc = 'UTC=false\n'
        create_file(target_chroot, "/etc/sysconfig/clock",
                    [ 'ZONE="%s"\n' % zone, _utc, 'ARC=false\n' ])

        tzfile = "/usr/share/zoneinfo/" + zone
        try:
            check_exists(target_chroot, tzfile)
        except:
            print "ERROR: %s does not exist, skipping." % tzfile
        else:
            # Create /etc/localtime
            # do not hardlink /etc/localtime, has to be copy for
            # system-config-date
            try:
                check_exists(target_chroot, "/etc/localtime")
            except:
                pass
            else:
                os.unlink(target_chroot+"/etc/localtime")
            try:
                buildroot_copy(target_chroot, tzfile, "/etc/localtime")
            except:
                print "ERROR: Could not create /etc/localtime."

    if ks.has_key("install"):
        # /etc/sysconfig/desktop
        desktop = "GNOME"
        if ks.has_key("xconfig") and ks["xconfig"].has_key("defaultdesktop"):
            desktop = ks["xconfig"]["defaultdesktop"]
        print "Setting default desktop to '%s'" % desktop
        create_file(target_chroot, "/etc/sysconfig/desktop",
                    [ 'DESKTOP="%s"\n' % desktop ])

    # /etc/sysconfig/i18n
    if ks.has_key("langsupport"):
        print "Configuring languages"
        content = [ ]
        if ks["langsupport"].has_key("default"):
            content.append('LANG="%s"\n' % ks["langsupport"]["default"])
            if ks["langsupport"].has_key("supported") and \
                   len(ks["langsupport"]["supported"]) > 0:
                content.append('SUPPORTED="%s"\n' % \
                               string.join(ks["langsupport"]["supported"], ":"))
        else:
            content.append('LANG="%s"\n' % \
                           string.join(ks["langsupport"]["supported"], ":"))
        create_file(target_chroot, "/etc/sysconfig/i18n", content)

    # /etc/sysconfig/installinfo
    if ks.has_key("install"):
        print "Configuring installinfo"
        method = ""
        for key in [ "cdrom", "harddrive", "nfs", "url" ]:
            if ks.has_key(key):
                method = key
                break
        create_file(target_chroot, "/etc/sysconfig/installinfo",
                    [ 'INSTALLMETHOD=%s\n' % method ])

    # /etc/sysconfig/iptables
    if ks.has_key("install") and \
           ks.has_key("firewall") and not ks["firewall"].has_key("disabled"):
        print "Configuring firewall"
        firewall_config(ks, target_chroot, installation)

    # /etc/sysconfig/keyboard
    if ks.has_key("keyboard"):
        print "Configuring keyboard"
        create_file(target_chroot, "/etc/sysconfig/keyboard",
                    [ 'KEYBOARDTYPE="pc"\n', 'KEYTABLE="%s"\n' % \
                      ks["keyboard"] ])

    # /etc/sysconfig/mouse
    if ks.has_key("install"):
        print "Configuring generic IMPS/2 mouse"
        try:
            check_exists(target_chroot, "/usr/sbin/mouseconfig")
        except:
            print "ERROR: /usr/sbin/mouseconfig does not exist, skipping."
        else:
            (status, rusage, msg) = pyrpm.runScript( \
                    script="/usr/sbin/mouseconfig --noui genericwheelps/2",
                    chroot=target_chroot)
            log(msg)
            if status != 0:
                print "ERROR: mouseconfig failed"

    # setup networking
    if ks.has_key("install"):
        print "Configuring network"
        network_config(ks, target_chroot)

    # /etc/X11/xorg.conf
    if ks.has_key("install") and ks.has_key("xconfig"):
        print "Configuring X"
        x_config(ks, target_chroot, installation)

    # /etc/sysconfig/firstboot
    if ks.has_key("install") and ks.has_key("firstboot"):
        print "Configuring firstboot"
        if ks["firstboot"].has_key("reconfig"):
            create_file(target_chroot, "/etc/reconfigSys")
        if ks["firstboot"].has_key("disabled"):
            create_file(target_chroot, "/etc/sysconfig/firstboot",
                        [ 'RUN_FIRSTBOOT=NO\n' ])

    # /boot/grub/grub.conf
    if ks.has_key("bootloader") and \
           not (ks["bootloader"].has_key("location") and \
                ks["bootloader"]["location"] == "none"):
#TODO:                          and not (ks.has_key("upgrade") and \
#                                  not ks["bootloader"].has_key("upgrade")):

        prefix = "/boot"
        dir = "/"
        have = "have"
        if mountmap.has_key("/boot"):
            dir = "/boot"
            prefix = ""
            have = "do not have"

        append=""
        if ks["bootloader"].has_key("append"):
            append = " %s" % ks["bootloader"]["append"]

        hds = diskmap.keys()
        hds.sort()
        # driveorder
        if ks["bootloader"].has_key("driveorder"):
            hds_ = ks["bootloader"]["driveorder"]
            for hd in hds:
                if hd not in hds_:
                    hds_.append(hd)
            hds = hds_
        hdmap = { }
        id = 0
        for hd in hds:
            hdmap[hd] = "hd%d" % id
            id += 1

        print "Searching for installed kernels"
        kernels = get_installed_kernels(target_chroot)

        if len(kernels) < 1:
            print "ERROR: No kernels are installed."
            return 1

        if not os.path.exists(target_chroot+"/boot/grub/grub.conf") or \
               ks.has_key("upgrade"):
#TODO:           ks["bootloader"].has_key("upgrade"):
# do not update grub entries for old kernels
# delete erased and add installed
            print "Configuring grub"

            location = "mbr"
            if ks["bootloader"].has_key("location"):
                location = ks["bootloader"]["location"]
            if location == "mbr":
                if partitionmap[mountmap[dir]].has_key("raid"):
                    location = mountmap[dir]
                else:
                    location = partitionmap[mountmap[dir]]["disk"]
            elif location == "partition":
                location = mountmap[dir]

            if partitionmap[mountmap["/"]].has_key("lvm"):
                # there is no device mapping for lvm, therefore use real device
                root = partitionmap[mountmap["/"]]["device"]
            elif partitionmap[mountmap["/"]].has_key("raid"):
                root = "/dev/%s" % mountmap["/"]
            elif partitionmap[mountmap["/"]]["label"]:
                # use mount by label if label is set
                root = "LABEL=%s" % partitionmap[mountmap["/"]]["label"]
            else:
                root = "/dev/%s" % mountmap["/"]

            if partitionmap[mountmap[dir]].has_key("raid"):
                # take one of the raid level 1 partitions
                part = partitions[dir]["partitions"][0]
                p = partitions[part]["onpart"]
                hd_boot = "%s,%d" % (hdmap[partitionmap[p]["disk"]],
                                     (partitionmap[p]["id"] - 1))
            else:
                hd_boot = "%s,%d" % (hdmap[partitionmap[mountmap[dir]]["disk"]],
                                     (partitionmap[mountmap[dir]]["id"] - 1))

            _password = _lba = ""
            if ks["bootloader"].has_key("password"):
                _password = 'password %s\n' % ks["bootloader"]["password"]
            if ks["bootloader"].has_key("md5pass"):
                _password = 'password --md5 %s\n' % ks["bootloader"]["md5pass"]
            if ks["bootloader"].has_key("lba32"):
                _lba = 'lba32\n'

            # create grub.conf
            content = [ \
                '# grub.conf generated by pyrpmkickstart\n',
                '#\n',
                '# Note that you do not have to rerun grub after making changes to this file\n',
                '# NOTICE:  You %s a /boot partition.  This means that\n' % have,
                '#          all kernel and initrd paths are relative to %s/, eg.\n' % prefix,
                '#          root (%s)\n' % hd_boot,
                '#          kernel %s/vmlinuz-version ro root=%s\n' % \
                (prefix, root),
                '#          initrd %s/initrd-version.img\n' % prefix,
                '#boot=/dev/%s\n' % location,
                'default=0\n',
                'timeout=5\n',
                'splashimage=(%s)%s/grub/splash.xpm.gz\n' % (hd_boot, prefix),
                _password,
                _lba ]

            # add all kernels:
            for kernel in kernels:
                kernel = string.strip(kernel)
                content.extend( \
                    [ 'title %s (%s)\n' % (source_release, kernel),
                      '\troot (%s)\n' % hd_boot,
                      '\tkernel %s/vmlinuz-%s ro root=%s%s\n' % \
                      (prefix, kernel, root, append),
                      '\tinitrd %s/initrd-%s.img\n' % (prefix, kernel) ] )

            create_file(target_chroot, "/boot/grub/grub.conf", content,
                        force=1, mode=0600)
            if not os.access(target_chroot + "/etc/grub.conf", os.R_OK):
                os.symlink("../boot/grub/grub.conf", target_chroot + "/etc/grub.conf")

            # copy all grub stage files
            t = "/usr/share/grub/%s-redhat/" % installation.arch
            list = os.listdir(target_chroot+t)
            if len(list) == 0:
                print "ERROR: grub stage files are missing in " + \
                      "installation tree."
                return 1
            for f in [ "stage1", "stage2", "e2fs_stage1_5" ]:
                if not f in list:
                    print "ERROR: grub stage file '%s' " % f + \
                          "is missing in installation tree."
                    return 1
            for entry in list:
                buildroot_copy(target_chroot, t + entry, "/boot/grub/" + entry)
            del t

            # create temporary devices which are usable by grub (hda, hda1, ..)
            devmap = { } # device mapping
            for disk in hds:
                dev = "/tmp/%s" % disk
                copy_device(diskmap[disk]["device"], target_chroot,
                            source_dir=stage2_dir, target=dev)
                devmap[diskmap[disk]["device"]] = dev
            for onpart in partitionmap:
                if partitionmap[onpart].has_key("raid"):
                    pass
                elif partitionmap[onpart].has_key("volgroup"):
                    # already done
                    pass
                else:
                    dev = "/tmp/%s%d" % (partitionmap[onpart]["disk"],
                                         partitionmap[onpart]["id"])
                    copy_device(partitionmap[onpart]["device"], target_chroot,
                                source_dir=stage2_dir, target=dev)
                    devmap[partitionmap[onpart]["device"]] = dev

            # create devices.map file
            content = [ '(fd0) /dev/fd0\n' ]
            for disk in hds:
                content.append('(%s) %s\n' % (hdmap[disk], disk))
            if not create_file(target_chroot, "/boot/grub/devices.map",
                               content):
                return 1

            # grub setup
            content = [ '/sbin/grub --batch >/tmp/grub-setup.log <<EOF\n' ]
            for disk in hds:
                content.append('device (%s) %s\n' % \
                               (hdmap[disk], devmap[diskmap[disk]["device"]]))
                if diskmap[disk].has_key("image"):
                    content.append('geometry (%s) %d %d %d\n' % \
                                   (hdmap[disk], diskmap[disk]["cylinders"],
                                    diskmap[disk]["heads"],
                                    diskmap[disk]["sectors"]))
            if partitionmap[mountmap[dir]].has_key("raid"):
                for i in xrange(len(partitions[dir]["partitions"])):
                    part = partitions[dir]["partitions"][i]
                    p = partitions[part]["onpart"]
                    hd = hdmap[partitionmap[p]["disk"]]
                    _hd_boot = "%s,%d" % (hdmap[partitionmap[p]["disk"]],
                                         (partitionmap[p]["id"] - 1))
                    content.append('root (%s)\n' % _hd_boot)
                    content.append('setup (%s)\n' % hd)
            else:
                if hdmap.has_key(location):
                    hd = hdmap[location]
                else:
                    try:
                        hd_name = getName(location)
                        id = getId(location)
                    except:
                        print "ERROR: Failed to get id for '%s'" % location
                    hd = "%s,%d" % (hdmap[hd_name], (id-1))
                content.append('root (%s)\n' % hd_boot)
                content.append('setup (%s)\n' % hd)
            content.append('quit\n')
            content.append('EOF\n')
            if not create_file(target_chroot, "/tmp/grub-setup", content, mode=0600):
                print "ERROR: grub setup failed"
            else:
                try:
                    check_exists(target_chroot, "/sbin/grub")
                except:
                    print "ERROR: /sbin/grub does not exist, skipping."
                else:
                    (status, rusage, msg) = pyrpm.runScript( \
                        script="/bin/sh /tmp/grub-setup", chroot=target_chroot)
                    log(msg)
                    if status != 0:
                        print "ERROR: grub setup failed"

    # sanitize lvm - remove cache file
    if ks.has_key("install"):
        lvm_cache = "%s/etc/lvm/.cache" % target_chroot
        if os.path.exists(lvm_cache):
            print "Sanitizing lvm: Removing cache file."
            try:
                os.unlink(lvm_cache)
            except:
                print "ERROR: Unable to remove cache file."

    # run post script
    if ks.has_key("install") and \
           ks.has_key("post") and ks["post"].has_key("script") and \
           len(ks["post"]["script"]) > 0:
        print "Running post script"

        chroot = None
        if not ks["post"].has_key("nochroot"):
            chroot = target_chroot
        elif stage2_chroot:
            chroot = stage2_chroot
        interpreter = "/bin/sh"
        if ks["post"].has_key("interpreter"):
            interpreter = ks["post"]["interpreter"]

        (status, rusage, msg) = pyrpm.runScript(interpreter,
                                                ks["post"]["script"],
                                                chroot=chroot)
        log(msg)
        if status != 0:
            if ks["post"].has_key("erroronfail"):
                print "ERROR: post script failed"
                return 1
            else:
                print "WARNING: post script failed"

    if xen:
        print "Writing xen.conf"
        try:
            fd = open("xen.conf", "w")
        except Exception, msg:
            print "ERROR: Configuration of 'xen.conf' failed:", msg
        else:
            fd.write('kernel = "/boot/vmlinuz-VERSION"\n')
            fd.write('ramdisk = "/boot/initrd-VERSION.img"\n')
            fd.write('memory = 128\n')
            fd.write('name = "xen"\n')
            fd.write('nics = 1\n')
            for disk in diskmap:
                if diskmap[disk].has_key("image") and \
                       diskmap[disk].has_key("device"):
                    fd.write('disk = [ "file:%s,%s,r" ]\n' % \
                             (diskmap[disk]["image"], disk))
                else:
                    fd.write('disk = [ "phy:%s,%s,r" ]\n' % \
                             (diskmap[disk]["device"], disk))
            fd.write('root = "%s"\n' % mountmap["/"])
            fd.write('extra = "ro"\n')
            fd.close()

    # copy log file into target_chroot+/root/
    logger.flush()
    logger.close()
    fd = open(log_file, "r")
    target = "/root/pyrpmkickstart.log"
    try:
        target_fd = open(target_chroot+target, "w")
    except Exception, msg:
        print "ERROR: Failed to open '%s':" % target_chroot+target, msg
        return 1
    data = fd.read(65536)
    while data:
        target_fd.write(data)
        data = fd.read(65536)
    target_fd.close()
    fd.close()

    if standalone:
        # honour reboot option
        pass

    return 0

##################################### usage ###################################

def usage():
    print """
Usage: pyrpmkickstart <options> <kickstartfile>
                      [[<disk name>:]<disk image>|<disk device>]*

OPTIONS
  -h  | --help     Print help.
  -v  | --verbose  Be verbose, and more, ..
  -y  | --yes      Do not ask questions, assume yes.

  --label-prefix       Prepend prefix before labels on partitions
  --no-cleanup         Do not cleanup temporary directory and files.
  --wait               Wait after installation before umounting.
  --xen                Write a simple xen config file.
  --yum                Use yum instead of pyrpmyum.
  --repo-comps         Load comps file in repos and use them for package and
                       group selection.
  --no-stage2          Do not mount stage2.img from installation source, use
                       host system tools to format partitions. This does not
                       work for all host and client system combinations, but
                       could help with others.
  --upgrade=<part>     Upgrade installation in partition <part>. This is only
                       useful for upgrades and if there is more than one
                       installation on the supplied disks.
  --no-cache           Do not cache RPM's (e.g. for http and ftp sources).
  --no-default-groups  Do not use base and core as default groups. This is
                       useful for minimal installations.
  --drop-unresolved    Drop packages, which have unresolved dependencies in
                       installation mode. Please keep in mind that this could
                       result in a unusable system.
  --no-buildstamp      Do not use .buildstamp information in stage2 images.
                       Only usable without --no-stage2.
  --no-discinfo        Do not use .discinfo information. Only usable with
                       --no-stage2 option.

"""
# TODO:
#  --standalone     Start in standalone mode: start network before running
#                   pre script, swapon swap partitions
#  --secure         Secure installation with firewall enabled in standalone mode

##################################### main ####################################

verbose = 0
target_chroot = ""
xen = 0
nocleanup = 0
confirm = 1
tempdir = None
diskmap = None
partitionmap = None
devmap = None
wait = 0
orig_yum = 0
target_dir = None
label_prefix = ""
standalone = 0
stage2_img_dir = None
raidmap = None
repo_comps = 0
upgrade = None
no_stage2 = 0
no_cache = 0
stage2_chroot = None
vgmap = None

# abort if user is not root
if os.geteuid() != 0:
    print "ERROR: You have to be root to perform an installation."
    sys.exit(1)

pyrpm.rpmconfig.supported_signals.extend([signal.SIGSEGV, signal.SIGBUS, signal.SIGABRT, signal.SIGILL, signal.SIGFPE])
pyrpm.setSignals(exitHandler)

try:
    status = main()
except Exception:
    traceback.print_exc()
    status = 1

################################### cleanup ###################################

# block signals for cleanup
pyrpm.blockSignals()

# wait one second before going on
time.sleep(1)
os.system("/bin/sync")

if wait:
    if stage2_chroot or target_chroot:
        sys.stdout.write("\nAvailable trees\n")
        if stage2_chroot:
            sys.stdout.write("  Stage2:       %s\n" % stage2_chroot)
        if target_chroot:
            sys.stdout.write("  Installation: %s\n" % target_chroot)
        sys.stdout.write("Use 'chroot <dir>' to change into the tree.\n")
    sys.stdout.write("\nWaiting - Press any key to continue.")
    sys.stdout.flush()
    c = sys.stdin.read(1)

if devmap:
    # remove temporary devices
    for dev in devmap:
        if not os.path.exists(target_chroot+devmap[dev]):
            print "WARNING: '%s' does not exist in '%s'." % (devmap[dev],
                                                             target_chroot)
            continue
        os.unlink(target_chroot+devmap[dev])
    devmap = None

if tempdir and target_chroot:
    stat = umount_all(target_chroot)

if partitionmap:
    for onpart in partitionmap:
        if partitionmap[onpart]["name"][:4] == "swap" and \
               partitionmap[onpart].has_key("on"):
            swapoff(partitionmap[onpart]["device"])
            del partitionmap[onpart]["on"]
    paritionmap = None

if vgmap:
    for vg in vgmap:
        vgmap[vg].stop()
    vgmap = None

if raidmap:
    for raid in raidmap:
        raidmap[raid].stop()
    raidmap = None

if diskmap:
    for disk in diskmap:
        diskmap[disk].close()
    diskmap = None

if tempdir:
    stat = umount_all(tempdir+"/")
    if stat == 0 and not nocleanup:
        shutil.rmtree(tempdir)
    tempdir = None

pyrpm.unblockSignals()
sys.exit(status)

# vim:ts=4:sw=4:showmatch:expandtab
