#!/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.path, sys, getopt, md5, stat, tempfile, string, time, random, \
       crypt, signal, types, shutil, traceback, popen2

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.yum
import pyrpm.se_linux
from pyrpm.installer import *

log = pyrpm.installer.log
flog = pyrpm.installer.flog

if not PARTED_MODULE_LOADED:
    log.error("Import of parted module failed. Please install the pyparted "
              "package if it is not installed already.")
    sys.exit(1)

# 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

def usage():
    log.info1("""
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.

  --arch=<arch>            Set installation arch for guest. This is needed to
                           to install a i586 guest from a i686 installation
                           tree. It is not usable for multilib guest systems at
                           the moment.
  --autoerase              Drop packages, which have unresolved dependencies in
                           installation mode and erase packages with
                           unresolved dependencies in update mode. Please keep
                           in mind that this could result in an incomplete and
                           unusable system.
  --beta-key-verify        Use beta key verification code for RHEL >= 5.
  --external-yum           Do not use internal yum class for pyrpmyum. Use
                           popen call. This is also used for '--yum' flag.
  --label-prefix=<string>  Prepend prefix before labels on partitions
  --no-cache               Do not cache RPM's (e.g. for http and ftp sources).
  --no-cleanup             Do not cleanup temporary directory and files.
  --no-dmsetup-init        Do not initialize dmsetup
  --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.
  --repo-comps             Load comps file in repos and use them for package
                           and group selection.
  --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.
  --wait                   Wait after installation before umounting.
  --yum                    Use yum instead of pyrpmyum.
  --yum-verbose            Be verbose in yum and pyrpmyum.

""")
# 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 function ################################

def main():
    global target_chroot, nocleanup, tempdir, diskmap, partitionmap, devmap, \
           wait, target_dir, raidmap, stage2_chroot, \
           vgmap, source
    verbose = False
    yum_verbose = 0
    confirm = True
    orig_yum = False
    label_prefix = ""
    standalone = False
    stage2_img_dir = None
    user_arch = None
    repo_comps = False
    upgrade = None
    no_stage2 = False
    no_cache = False
    autoerase = False
    has_raid = False
    beta_key_verify = False
    external_yum = False
    dmsetup_init = True

    log.setInfoLogLevel(log.INFO1)
    log.setDebugLogLevel(log.NO_DEBUG)
    log.setFormat("%(label)s%(message)s")
    log.setDateFormat("%Y-%m-%d %H:%M:%S")
    flog.setFormat("%(date)s %(label)s%(message)s")
    flog.setDateFormat(log.getDateFormat())

    try:
        (opts, args) = getopt.getopt(sys.argv[1:], "hvdy",
                                     [ "help", "verbose", "debug", "yes",
                                       "label-prefix", "no-cleanup",
                                       "standalone", "wait", "yum",
                                       "repo-comps", "no-stage2", "upgrade=",
                                       "no-cache", "autoerase",
                                       "beta-key-verify", "external-yum",
                                       "yum-verbose", "no-dmsetup-init" ])
    except:
        usage()
        return

    for (opt, val) in opts:
        if opt in [ "-h", "--help" ]:
            usage()
            return
        elif opt in [ "-v", "--verbose" ]:
            verbose = True
            log.setInfoLogLevel(log.getInfoLogLevel() + 1)
            flog.setInfoLogLevel(flog.getInfoLogLevel() + 1)
        elif opt in [ "-d", "--debug" ]:
            log.setDebugLogLevel(log.getDebugLogLevel() + 1)
            flog.setDebugLogLevel(flog.getDebugLogLevel() + 1)
        elif opt in [ "-y", "--yes" ]:
            confirm = False
        elif opt == "--no-cleanup":
            nocleanup = True
        elif opt == "--wait":
            wait = 1
        elif opt == "--yum":
            orig_yum = True
            external_yum = True
        elif opt == "--label-prefix":
            label_prefix = "%x_" % int(time.strftime("%m%d%H%M"))
        elif opt == "--standalone":
            standalone = True
        elif opt == "--repo-comps":
            repo_comps = True
        elif opt == "--no-stage2":
            no_stage2 = True
        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 = True
        elif opt == "--autoerase":
            autoerase = True
        elif opt == "--beta-key-verify":
            beta_key_verify = True
        elif opt == "--external-yum":
            external_yum = True
        elif opt == "--yum-verbose":
            yum_verbose += 1
        elif opt == "--no-dmsetup-init":
            dmsetup_init = False
        else:
            log.error("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]:
            log.error("User requested arch is not compatible with "
                      "host system arch.")
            return 1
        if user_arch == "noarch":
            log.error("Noarch is now allowed as installation arch.")
            return 1

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

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

    ks = KickstartConfig(ks_file)

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

    if not no_stage2:
        # mount tmpfs for stage2
        log.info2("Mounting tmpfs on '%s'.", stage2_dir)
        try:
            mount("None", stage2_dir, fstype="tmpfs")
        except Exception, msg:
            log.error("Unable to create stage2 tmpfs: %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)
    create_dir("", cache_dir)

    # create cache dir
    pyrpm.rpmconfig.cachedir = cache_dir

    # global logging
    create_dir(tempdir, "/tmp")
    log_filename = tempdir+"/tmp/pyrpmkickstart.log"
    log_file = pyrpm.logger.FileLog(log_filename)
    log.info1("Logging to '%s'.", log_filename)
    log.addInfoLogging("*", log_file, fmt=flog.getFormat())
    log.addDebugLogging("*", log_file, fmt=flog.getFormat())
    # set extra file logging
    flog.setInfoLogging("*", log_file)
    flog.setDebugLogging("*", log_file)

    # installation methods

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

    ############################### load source ###############################

    source = Source()
    signals = pyrpm.blockSignals()
    ret = source.load(ks, source_dir, beta_key_verify)
    pyrpm.unblockSignals(signals)
    if not ret:
        return 1

    # verify source
    if (source.isRHEL() and source.cmpVersion("3") < 0) or \
           (source.isFedora() and source.cmpVersion("2") < 0):
        log.error("%s is not supported as installation source",
                  source.getRelease())
        return 1

    if source.getArch() not in pyrpm.arch_compats[pyrpm.rpmconfig.machine]:
        log.error("Installation source arch '%s' is not compatible with "
                  "system arch '%s'.", source.getArch(),
                  pyrpm.rpmconfig.machine)
        return 1

    if user_arch and user_arch not in pyrpm.arch_compats[source.getArch()]:
        log.error("User requested arch '%s' is not compatible with "
                  "installation source arch '%s'.", user_arch,
                  source.getArch())
        return 1

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

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

        stage2 = source.getStage2()
        if not stage2 or not os.path.exists(stage2):
            log.error("Could not get stage2 image from installation source."
                      "Try to use 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 and (magic == "hsqs" or magic == "sqsh"):
            fstype = "squashfs"

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

        ### analyze stage2 ###
        if not os.path.exists("%s/.buildstamp" % stage2_img_dir):
            log.warning("Buildstamp information is missing in stage2.")
        bs = get_buildstamp_info(stage2_img_dir)
        if not bs:
            log.warning("Failed to get buildstamp information from stage2.")
        if bs[0] != source.getName() or bs[1] != source.getVersion() or \
               bs[2] != source.getArch():
            log.warning("Release info does not match buildstamp: \n"
                        "  %s-%s.%s <=> %s-%s.%s", bs[0], bs[1], bs[2],
                        source.getName(), source.getVersion(),
                        source.getArch())

        ### create stage2 chroot ###

        log.info1("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
        signals = 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(signals)


        log.info2("Umounting '%s'.", stage2_img_dir)
        umount(stage2_img_dir)
        stage2_img_dir = None

        # selinux is not working reliable in the guest at install time
        # disable now, but mark for relabel at first boot
        pyrpm.rpmconfig.selinux_enabled = False
#        if not (ks.has_key("install") and ks.has_key("selinux") and \
#                not ks["selinux"].has_key("disabled")):
#            pyrpm.rpmconfig.selinux_enabled = False
        
        # setup selinux to use stage2 contexts
        if pyrpm.rpmconfig.selinux_enabled:
            pyrpm.se_linux.matchpathcon_fini()
            pyrpm.se_linux.matchpathcon_init_from_chroot(stage2_dir)

        if pyrpm.rpmconfig.selinux_enabled:
            # set context of target chroot
            set_SE_context(stage2_dir, "/")

        # fix selinux contexts of stage2 content
        for root, dirs, files in os.walk(stage2_dir):
            local_root = root.split(stage2_dir, 1)[1]
            # set context of dir
            set_SE_context(stage2_dir, local_root)
            # context of files
            for entry in files:
                set_SE_context(stage2_dir, "%s/%s" % (local_root, entry))

        # 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
        log.info1("Testing if /bin/bash is usable in stage2")
        if run_script("/bin/bash --version", stage2_chroot) != 0:
            log.error("/bin/bash in stage2 is not usable, exiting.")
            return 1

    else: # no_stage2
        stage2_dir = ""

    ################### start networking in standalone mode ###################

    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
        log.error("TODO: start network")
        return 1

    ########################## 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:
        log.info1("Running pre script.")
        if not run_ks_script(ks["pre"], stage2_chroot):
            return 1
        # parse file again
        ks.reload()

    # 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("/")))):
            log.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):
                    log.error("Volume group '%s' already exists.", group)
                    return 1
    else:
        # no upgrade checks
        pass

    # check for existance of essential tags

    # langsupport
    if (source.isRHEL() and source.cmpVersion("5") < 0) or \
           (source.isFedora() and source.cmpVersion("6") < 0):
        # langsupport is deprecated for RHEL>=5 and FC>=6, but required before
        if not ks.has_key("langsupport"):
            log.error("langsupport is not defined, exiting.")
            return 1
    else:
        log.warning("langsupport is deprecated.")

    # monitor and xconfig::monitor
    if (source.isRHEL() and source.cmpVersion("5") < 0) or \
           (source.isFedora() and source.cmpVersion("6") < 0):
        if ks.has_key("monitor"):
            log.warning("Monitor tag is not supported for this "
                        "distribution, using for compatibility mode.")
    elif ks.has_key("xconfig") and ks["xconfig"].has_key("monitor"):
        log.warning("xconfig --monitor is deprecated, "
                    "using for compatibility mode.")

    # keyboard
    if not keyboard_models.has_key(ks["keyboard"]):
        log.error("Keyboard model '%s' is not defined, exiting.",
                  ks["keyboard"])
        return 1

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

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

    devices = Devices()

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

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

    # TODO: add other usable disklabels
    usable_disklabels = [ "msdos" ]

    ##########################################################################
    #################### 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):
                log.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:
                    log.error("Unable to find an unused disk name for '%s'",
                              val)
                    return 1
            if not key:
                log.error("Unsupported type of '%s'.", val)
                return 1
            disk_image = 0
            if not val in system["disks"] and not stat.S_ISBLK(mode):
                log.info1("Using '%s' as disk '%s'.", val, key)
                disk_image = 1
            if diskmap.has_key(key):
                log.error("'%s' is already defined.", key)
                return 1

            try:
                diskmap[key] = Disk(val, alloc_loop=1, as_image=disk_image)
            except Exception, msg:
                log.error(msg)
                return 1

    # use system disks?
    if len(diskmap) == 0:
        log.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:
        log.error("No harddisks specified or found.")
        return 1

    # print disks
    if verbose:
        diskmap_keys = diskmap.keys()
        diskmap_keys.sort()
        for disk in diskmap_keys:
            if not diskmap[disk].has_disklabel():
                log.info2("No disklabel for '%s'.", disk)
                continue
            diskmap[disk].print_info()
            diskmap[disk].print_partitions()

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

    # initialize dmsetup
    if dmsetup_init and \
           (source.isRHEL() and source.cmpVersion("5") >= 0) or \
           (source.isFedora() and source.cmpVersion("4") >= 0):
        if os.path.exists(stage2_dir+"/sbin/dmsetup"):
            log.info1("Initializing device mapper.")
            dmsetup = "/sbin/dmsetup info 2>/dev/ull"
            if run_script(dmsetup, stage2_chroot) != 0:
                log.error("dmsetup failed.")
                return 1
        else:
            log.warning("dmsetup is missing.")

    # clearpart, initialize disks
    modified = [ ]
    zero_mbr = [ ]

    if ks.has_key("install"):
        if ks.has_key("zerombr") and ks["zerombr"] != "no":
            for disk in diskmap.keys():
                if not diskmap[disk].has_disklabel() or \
                       diskmap[disk]["disklabel"] not in usable_disklabels:
                    log.info1("Initializing disk '%s' to '%s' type.",
                              disk, usable_disklabels[0])
                    diskmap[disk].new_disklabel(usable_disklabels[0])
                    if not disk in modified:
                        modified.append(disk)
                else:
                    log.info1("Clearing MBR on disk '%s'.", disk)
                    zero_mbr.append(disk)

        if ks.has_key("clearpart"):
            linuxtypes = [ 0x82, 0x83, 0x8e, 0xfd ]
            drives = diskmap.keys()
            if ks["clearpart"].has_key("drives"):
                drives = ks["clearpart"]["drives"]
                normalizeList(drives)
                for disk in drives:
                    if not diskmap.has_key(disk):
                        log.error("clearpart --drives: " + \
                                  "disk %s is not available", disk)
                        return 1
            for disk in drives:
                if ks["clearpart"].has_key("initlabel") and \
                       disk not in modified:
                    # disk has to be initialized and disk is not modified
                    log.info1("Initializing disk '%s' to '%s' type.",
                              disk, usable_disklabels[0])
                    diskmap[disk].new_disklabel(usable_disklabels[0])
                    if not disk in modified:
                        modified.append(disk)
                elif not diskmap[disk].has_disklabel():
                    pass
                elif ks["clearpart"].has_key("all"):
                    log.info1("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:
                            log.info1("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):
                    log.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:
                        log.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"]
                    err = False
                    if pesize[-1] not in [ "k", "K", "m", "M", "g", "G",
                                           "t", "T" ]:
                        err = True
                    else:
                        try:
                            err = (long(string.strip(pesize[:-1])) > 0)
                        except:
                            err = True
                    if err:
                        log.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):
                        log.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):
                    log.error("Multiple definitions of logical volume '%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

        log.info1("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:
                            log.error("Label %s is not unique.", label)
                            return
                        labelmap[label] = onpart

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

                elif partition[part]["fstype"] == "lvm":
                    log.info1("%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
                    log.info1("Trying '/dev/%s'", onpart)
                    inf = get_installation_info(partition[part]["device"],
                                                partition[part]["fstype"],
                                                fstemp_dir)
                    if inf:
                        installed[onpart] = inf
                        log.info1("Found '%s'.", installed[onpart]["release"])

        # check volume groups and logical volumes
        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:
                inf = get_installation_info(device, fstype, fstemp_dir)
                if inf:
                    installed[device] = inf

        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"]):
                log.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"]):
                    log.warning("'%s': Partition %d is out of range.", dev, d)
                    to_delete.append(dev)
                    break
                if d in devs:
                    log.warning("'%s': Partition %d is defined more than "
                                "once.", dev, d)
                    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 = devices.getNextFree(dev)
            if dev != device:
                raid.mapTo(device)
            raid.assemble()
            devices.add(device)
            raidmap[dev] = raid

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

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

            raid.stop()

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

        if len(installed) > 1:
            log.info1("Available installations:")
            for onpart in release:
                log.info1("  /dev/%s: %s", onpart,
                          installed[onpart]["release"])

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

        log.info1("About to upgrade installation on '/dev/%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:
                            log.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:
                            log.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)

    # 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:]
            err = 0
            try:
                disk = getName(onpart)
                i = getId(onpart)
            except:
                log.error("'%s' is not a valid partition name.", onpart)
                return 1
            if onpart in partitionmap:
                log.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:
                log.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
            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:]
            else:
                # No hardware detection: the SCSI driver is not in the initrd,
                # the user has to use the driver tag in the kickstart file
                if diskmap.has_key("hda"):
                    ondisk = "hda"
                elif diskmap.has_key("dasda"):
                    ondisk = "dasda"
                elif diskmap.has_key("sda"):
                    ondisk = "sda"
                else:
                    disks = diskmap.keys()
                    if len(disks) < 1:
                        log.error("No disk found.")
                        return 1
                    if len(disks) > 1:
                        disks.sort()
                        log.info1("Using first entry in sorted disk list.")
                    ondisk = disks[0]
            # TODO: allow to switch disks later if not user specified and
            # if there are multiple disks

            if not part.has_key("size"):
                log.error("No size given for partition '%s'.", name)
                return 1
            if not diskmap.has_key(ondisk):
                log.error("'%s' is not defined.", ondisk)
                return 1
            if not diskmap[ondisk].has_disklabel():
                log.error("Disk '%s' is not initialized. Use 'zerombr' or "
                          "'clearpart --initlabel'", ondisk)
                return 1
            if not diskmap[disk]["disklabel"] in usable_disklabels:
                log.error("Disk '%s' has no usable disklabel.", 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)):
            log.error("'%s' is not a disk and no lvm volgroup.", disk)
            return 1

        # 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"])
            if not part_size.has_key(name):
                log.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"]

            # TODO: allow disks with partitions
            if len(diskmap[disk]["primary"]) + \
                   len(diskmap[disk]["extended"]) > 0:
                log.error("Disk '%s' has already partitions.", 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"]:
            log.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]
                log.info1("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:
                        log.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"]:
                        log.error("Size calculation wrong for the "
                                  "extended partition: Size is %d "
                                  "and should be %d.",
                                  end-start+1, match["end"]-start+1)
                        return 1

                    log.info1("Creating extended partition on disk %s", disk)
                    try:
                        diskmap[disk].add_partition(0, start, end,
                                                    Partition.PARTITION_EXTENDED,
                                                    None)
                    except Exception, msg:
                        log.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:
                log.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"]:
                log.error("Partition %s does not fit onto %s.", name, disk)
                return 1

            if part.has_key("volgroup"):
                log.info1("Creating new partition '%s' on disk '%s' "
                          "as lvm volume group '%s'.", name, disk,
                          part["volgroup"])
            else:
                log.info1("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:
#                log.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()

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

    # zero mbr's
    for disk in zero_mbr:
        if diskmap[disk]["disklabel"] == "msdos":
            zero_device(diskmap[disk]["device"], 446)
        else:
            log.error("Do not know how to clear mbr on '%s' with disklabel "
                      "'%s'.", disk, diskmap[disk]["disklabel"])
            return 1

    # commit changes
    for disk in modified:
        try:
            diskmap[disk].commit()
        except Exception, msg:
            if not diskmap[disk].has_key("image"):
                log.error(msg)
                return 1
        diskmap[disk].reload()
        # wait one second before going on
        time.sleep(1)

    # sanity check
    if len(partitionmap) == 0:
        log.error("Partitionmap is empty.")
        return 1

    # check partitionmap with diskmap
    err = 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):
            log.error("Disk '%s' is not defined.", disk)
            err = 1
        if err:
            continue
        id = partitionmap[onpart]["id"]
        if not id in diskmap[disk]["partition"]:
            log.error("Partition '%s' does not exist.", onpart)
            err = 1
        if err:
            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 err:
        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:
                log.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"):
                    log.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"]:
                log.error("Logical volume '%s': Physical entent size '%s'"
                          " does not match '%s'.", group,
                          vgmap[group].extent, 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:
            log.error("Device '%s' has not been created.",
                      partitionmap[onpart]["device"])
            return 1

    # partition matches filesystem?
    err = 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 diskmap[disk]["disklabel"] == "msdos":
            if not p["native_type"]:
                log.error("Partition '%s' has no type.", onpart)
                err = 1
            elif p["native_type"] < 1:
                log.error("Partition '%s' has illegal type %d.",
                          onpart, p["native_type"])
                err = 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:
                    log.error("Filesystem '%s' with code 0x%x on"
                              " '%s' is not supported", p["fstype"],
                              p["native_type"], onpart)
                    err = 1
                    continue
                log.error("Partition '%s' does not match filesystem '%s'"
                          " for '%s'", onpart, part["fstype"], name)
                err = 1
        elif diskmap[disk]["disklabel"] == "gpt":
            # TODO: partition checks for gpt
            continue
        else:
            log.error("Unsupported disklabel '%s'.",
                      diskmap[disk]["disklabel"])
            return 1

    if err:
        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
    err = 0
    for onpart in partitionmap:
        name = partitionmap[onpart]["name"]
        if partitionmap[onpart].has_key("raid"):
            log.info1("Disabling mount by label for raid partition '%s'.",
                      onpart)
            partitionmap[onpart]["label"] = None
            continue
        if partitionmap[onpart].has_key("lvm"):
            log.info1("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:
            log.error("Could not determine filesystem type of '%s'", onpart)
            err = 1
        if part.has_key("label"):
            label = part["label"]
        if len(label) > 16:
            log.warning("Label '%s' for '%s' is too long, tuncating to 16 "
                        "chars.", label, onpart)
            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:
                    log.error("'%s' is no valid label name.", label)
                    return 1
                label = "%s%d" % (l_name, (id+1))

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

    err = 0
    for disk in diskmap:
        if not os.path.exists(diskmap[disk]["device"]):
            log.error("Disk %s: %s is no valid device.", disk,
                      diskmap[disk]["device"])
            err = 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"]):
            log.error("Partition %s: %s is no valid device.", onpart,
                      partitionmap[onpart]["device"])
            err = 1
    if err:
        return 1

    # check if partitions are already mounted (in use)
    mounted_devs = mounted_devices()
    mounted = 0
    for onpart in partitionmap:
        if partitionmap[onpart]["device"] in mounted_devs:
            log.error("Device '/dev/%s' is already mounted on '%s'.", onpart,
                      "', '".join(mounted_devs[partitionmap[onpart]["device"]]))
            mounted += 1
    if mounted > 0:
        return 1

    ############################ 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"):
            log.error("Could not find %s/sbin/mdadm in %s.", prefix,
                      location)
            return 1
    if volgroup:
        if not os.path.exists(dir+"/usr/sbin/lvm"):
            log.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:
                log.error("Could not find %s/sbin/mkswap in %s.", prefix,
                          location)
                return
        else:
            try:
                check_exists(dir, "%s/sbin/mkfs.%s" % (prefix, fstype))
            except:
                log.error("Could not find %s/sbin/mkfs.%s in %s.", prefix,
                          fstype, location)
                return
    if "ext3" in needed_fstypes and source.getRelease() != "RHEL-2.1":
        try:
            check_exists(dir, "%s/sbin/tune2fs" % prefix)
        except:
            log.error("Could not find %s/sbin/tune2fs in %s.", prefix,
                      location)
    del prefix
    del dir
    del location

    # print confirm message
    if confirm and len(to_format) > 0:
        log.info1("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"):
                log.info1("  '%s': raid '%s' [ %s ]", name, onpart,
                          ", ".join(partitions[name]["partitions"]))
            elif partitionmap[onpart].has_key("volgroup"):
                log.info1("  '%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"]
                log.info1("  '%s': '/dev/%s' on disk '%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 source.getRelease() != "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)
                        log.info2("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 = 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"):
                    log.info1("Assembling raid '%s'.", onpart)
                    if not raid.assemble():
                        return 1
                else:
                    log.info1("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
                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"]

                log.info1("Formatting '%s': '%s'", name, onpart)
                flog.info2(mkfs, nofmt=1)

                if run_script(mkfs, stage2_chroot) != 0:
                    log.error("mkfs.%s failed.", part["fstype"])
                    return 1

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

                    log.info1("Tuning filesystem '%s'", name)
                    flog.info2(tune2fs, nofmt=1)

                    if run_script(tune2fs, stage2_chroot) != 0:
                        log.error("tune2fs failed.")
                        return 1

            elif name[:4] == "swap" or \
                     (part.has_key("fstype") and part["fstype"] == "swap"):
                l = ""
                if (source.isRHEL() and source.cmpVersion("4") > 0) or \
                       (source.isFedora() and source.cmpVersion("2") > 0):
                    # 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"])
                if label:
                    log.info1("Formatting '%s': '%s'", label, onpart)
                else:
                    log.info1("Formatting '%s': '%s'", name[:4], onpart)
                flog.info2(mkswap, nofmt=1)

                if run_script(mkswap, stage2_chroot) != 0:
                    log.error("mkswap failed.")
                    return 1
            elif name[:5] == "raid.":
                pass
            elif name[:3] == "pv.":
                pass
            else:
                log.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:
                        log.error("Could not create '%s': %s", dir, msg)
                        return 1

            part = partitions[mntpnt]

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

    target_chroot = target_dir
    pyrpm.rpmconfig.buildroot = target_chroot

    ################################ languages ################################

    languages = [ ]
#    if (source.isRHEL() and source.cmpVersion("4.9") < 0) or \
#           (source.isFedora() and source.cmpVersion("6") < 0):
    # langsupport is deprecated for RHEL>=5 and FC>=6
    # get langsupport packages
    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)

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

    if ks.has_key("install"):
        pkgs = source.getPackages(ks, languages, repo_comps, has_raid,
                                  needed_fstypes)
        pyrpm.normalizeList(pkgs)

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

    # else: upgrade
    # ignore package selection


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

    # create essential directories
    os.umask(022)

    if pyrpm.rpmconfig.selinux_enabled:
        # set context of target chroot
        set_SE_context(target_chroot, "/")

    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 (source.isRHEL() and source.cmpVersion("4") >= 0) or \
           (source.isFedora() and source.cmpVersion("2") >= 0):
        create_dir(target_chroot, "/sys")
    if (source.isRHEL() and source.cmpVersion("5") >= 0) or \
           (source.isFedora() and source.cmpVersion("4") >= 0):
        create_dir(target_chroot, "/selinux")
    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
    log.info1("Mounting temporary '/proc'.")
    try:
        mount("None", target_dir+"/proc", fstype="tmpfs")
    except Exception, msg:
        log.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:
        log.error("Unable to mount temporary /proc/.real_proc: %s", msg)
        return 1
    if (source.isRHEL() and source.cmpVersion("4") >= 0) or \
           (source.isFedora() and source.cmpVersion("2") >= 0):
        # /sys
        log.info1("Mounting temporary '/sys'.")
        try:
            mount("None", target_dir+"/sys", fstype="tmpfs")
        except Exception, msg:
            log.error("Unable to mount temporary /sys: %s", msg)
            return 1

#    # /tmp
#    log.info1("Mounting temporary '/tmp'.")
#    try:
#        mount("None", target_dir+"/tmp", fstype="tmpfs")
#    except Exception, msg:
#        log.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 (source.isRHEL() and source.cmpVersion("4") >= 0) or \
           (source.isFedora() and source.cmpVersion("2") >= 0):
        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"):
        log.info1("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"):
            log.info1("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 part.has_key("fsoptions"):
                opts = part["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:
            log.warning("Unknown filesystem for '%s'.", onpart)
            continue
    if (source.isRHEL() and source.cmpVersion("4") < 0) or \
           (source.isFedora() and source.cmpVersion("3") < 0):
        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")
    else:
        fstab_content.extend([ "devpts\t\t/dev/pts\t\tdevpts\tgid=5,mode=620\t0 0\n",
                           "tmpfs\t\t/dev/shm\t\ttmpfs\tdefaults\t0 0\n",
                           "proc\t\t/proc\t\tproc\tdefaults\t0 0\n" ])
        mtab_content.append("proc /proc proc defaults 0 0\n")
    if (source.isRHEL() and source.cmpVersion("4") >= 0) or \
           (source.isFedora() and source.cmpVersion("2") >= 0):
        fstab_content.append("sysfs\t/sys\t\tsysfs\tdefaults\t0 0\n")
        mtab_content.append("sysfs /sys sysfs defaults 0 0\n")
    for name in parts:
        if name[:4] != "swap":
            continue
        onpart = onparts[name]
        if (source.isRHEL() and source.cmpVersion("4") < 0) or \
               (source.isFedora() and source.cmpVersion("2") <= 0) 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"):
        log.info1("Writing '/etc/fstab'.")
        create_file(target_chroot, "/etc/fstab", fstab_content)

    log.info1("Writing temporary '/etc/mtab'.")
    create_file(target_chroot, "/etc/mtab", mtab_content)

    log.info1("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)))
    log.info1("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"):
        log.info1("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"):
        log.info1("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 (source.isRHEL() and source.cmpVersion("4") < 0) or \
               (source.isFedora() and source.cmpVersion("2") < 0):
            # pre RHEL-4, pre FC-2 use modules.conf
            conf = "/etc/modules.conf"
        else:
            conf = "/etc/modprobe.conf"
        log.info1("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" % source.getArch() ])

    # 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:
                    log.info1("Detected '%s' installation.",
                              string.strip(string.join(lines)))

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

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

    if ks.has_key("install"):
        log.info1("Preparing installation...")
    else:
        log.info1("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:
        log.error("Configuration of yum failed: %s", msg)
        return 1
    fd.write("[main]\n")
    fd.write("cachedir=%s/yum.cache\n" % tempdir)
    fd.write("debuglevel=%d\n" % (yum_verbose + 2))
    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("plugins=1\n")
    fd.write("reposdir=%s/yum.repos.d\n" % tempdir)
    fd.write("\n")
    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(source.getYumConf())
    fd.close()

    # TODO: user_arch
    # TODO: checkinstalled for upgrade?

    if not external_yum:
        config = pyrpm.rpmconfig.copy()
        config.yumconf = [ yum_conf ]
        config.printhash = 1
        config.nofileconflicts = 1
        config.nocache = int(no_cache)

        info_level = log.getInfoLogLevel()
        debug_level = log.getDebugLogLevel()
        log.setInfoLogLevel(log.INFO2 + yum_verbose)
        log.setDebugLogLevel(log.NO_DEBUG)

        try:
            yum = pyrpm.yum.RpmYum(config)
            
            if not yum.lock():
                log.error("couldn't lock pyrpmyum")
                return 0
            try:
                # do not asky any questions
                yum.setConfirm(False)
                yum.setLanguages(languages[:])

                if ks.has_key("install"):
                    command = "install"
                    if autoerase:
                        yum.setAutoerase(True)
                else:
                    command = "update"
                    # allow to automatically erase packages with unresolved
                    # dependencies
                    yum.setAutoerase(True)
                if not yum.setCommand(command):
                    return 0
                if not yum.prepareTransaction():
                    return 0
                if not yum.runArgs(pkgs):
                    return 0
                if not yum.runDepRes():
                    return 0
                if not yum.runCommand(clearrepos=True):
                    return 0
            finally:
                if yum.pydb:
                    yum.pydb.close()
                if not yum.unLock():
                    log.error("couldn't unlock pyrpmyum")
        finally:
            log.setInfoLogLevel(info_level)
            log.setDebugLogLevel(debug_level)
    else:
        # external call
        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'" % (yum_conf) + \
                  " --root='%s'" % (target_chroot) + \
                  " --cachedir='%s'" % (cache_dir)
            if len(languages) > 0:
                yum += " --languages='%s'" % (" ".join(languages))
            if no_cache:
                yum += " --nocache"
            if autoerase:
                yum += " --autoerase"
        if ks.has_key("packages") and \
               ks["packages"].has_key("ignoredeps") and \
               ks["packages"].has_key("ignoremissing"):
            yum += " --nodeps"
        if ks.has_key("install"):
            yum += " install "
            yum += string.join(pkgs)
        else:
            if not orig_yum and not autoerase:
                yum += " --autoerase"
            yum += " update "
        flog.info2(yum, nofmt=1)

        # install / upgrade
        try:
            p = popen2.Popen4(yum)
            ch = p.fromchild.read(1)
            while ch:
                log.info1(ch, nl=0, nofmt=1)
                ch = p.fromchild.read(1)
            status = p.poll()
            if status != 0:
                raise RuntimeError, "failed"
        except:
            if ks.has_key("install"):
                log.error("\nInstallation failed, aborting.")
            else:
                log.error("\nUpgrade failed, aborting.")
            return 1

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

    # check system dirs
    if not check_dir(target_chroot, "/etc") or \
           not check_dir(target_chroot, "/etc/sysconfig"):
        log.info1("'/etc' and '/etc/sysconfig' is missing, aborting.")
        return 1

    # run authconfig if set
    if ks.has_key("install") and ks.has_key("authconfig"):
        log.info1("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:
            log.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
            if run_script(authconfig, target_chroot) != 0:
                log.error("authconfig failed.")

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

        try:
            check_exists(target_chroot, "/usr/sbin/usermod")
        except:
            log.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)
            if run_script("/usr/sbin/usermod -p '%s' root" % password,
                          target_chroot) != 0:
                log.error("Setting root password failed.")

    # configure services
    if ks.has_key("install") and ks.has_key("services"):
        log.info1("Configuring services")

        if ks["services"].has_key("disabled"):
            for service in ks["services"]["disabled"]:
                if run_script("/sbin/chkconfig '%s' off" % service,
                              target_chroot) != 0:
                    log.info2("No service '%s', can not disable.", service)
                else:
                    log.info1("Disabled service '%s'", service)
        if ks["services"].has_key("enabled"):
            for service in ks["services"]["enabled"]:
                if run_script("/sbin/chkconfig '%s' on" % service,
                              target_chroot) != 0:
                    log.info2("No service '%s', can not enable.", service)
                else:
                    log.info1("Enabled service '%s'", service)

    # setup users
    if ks.has_key("install") and ks.has_key("user"):
        try:
            check_exists(target_chroot, "/usr/sbin/useradd")
        except:
            log.error("/usr/sbin/useradd does not exist, skipping.")
        else:
            md5pw = ks["authconfig"].has_key("enablemd5")
            for user in ks["user"]:
                log.info1("Creating user '%s'", user["name"])
                
                useradd = "/usr/sbin/useradd -m '%s' " % user["name"]
                if user.has_key("homedir"):
                    useradd += " -d '%s'" % user["homedir"]
                else:
                    useradd += " -d '/home/%s'" % user["name"]
                if user.has_key("groups"):
                    useradd += " -G '%s'" % ",".join(user["groups"])
                if user.has_key("shell"):
                    useradd += " -s '%s'" % user["shell"]
                if user.has_key("uid"):
                    useradd += " -u %d" % user["uid"]
                if user.has_key("password"):
                    password = user["password"]
                    if not user.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)
                    useradd += " -p '%s'" % password
                if run_script(useradd, target_chroot) != 0:
                    log.error("Creation of user '%s' failed.")

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

        try:
            fd_in = open(target_chroot+"/etc/inittab", "r")
            fd_out = open(target_chroot+"/etc/inittab.pyrpmkickstart", "w")
        except Exception, msg:
            log.error("Configuration of '/etc/inittab' failed: %s", 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:
                        log.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 \
           ((source.isRHEL() and source.cmpVersion("3") > 0) or \
            (source.isFedora() and source.cmpVersion("2") > 0)):
        log.info1("Configuring selinux")

        try:
            check_exists(target_chroot, "/usr/sbin/lokkit")
        except:
            log.error("/usr/sbin/lokkit does not exist, skipping.")
        else:
            lokkit = "/usr/sbin/lokkit --quiet --nostart --selinux='%s'" % \
                     ks["selinux"].keys()[0]
            if run_script(lokkit, target_chroot) != 0:
                log.error("Configuration of selinux failed.")

    # /etc/sysconfig/clock
    if ks.has_key("install") and ks.has_key("timezone"):
        log.info1("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:
            log.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:
                log.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"]
        log.info1("Setting default desktop to '%s'.", desktop)
        create_file(target_chroot, "/etc/sysconfig/desktop",
                    [ 'DESKTOP="%s"\n' % desktop ])

    if (source.isRHEL() and source.cmpVersion("5") < 0) or \
           (source.isFedora() and source.cmpVersion("6") < 0):
        # langsupport is deprecated for RHEL>=5 and FC>=6
        # /etc/sysconfig/i18n
        if ks.has_key("langsupport"):
            log.info1("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)
    else:
        create_file(target_chroot, "/etc/sysconfig/i18n",
                    [ "LANG=%s\n" % ks["lang"] ])

    # /etc/sysconfig/installinfo
    if ks.has_key("install"):
        log.info1("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"):
        log.info1("Configuring firewall")
        firewall_config(ks, target_chroot, source)

    # /etc/sysconfig/keyboard
    if ks.has_key("keyboard"):
        log.info1("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"):
        log.info1("Configuring generic IMPS/2 mouse")
        try:
            check_exists(target_chroot, "/usr/sbin/mouseconfig")
        except:
            # this is no error or warning anymore
            pass
        else:
            if run_script("/usr/sbin/mouseconfig --noui genericwheelps/2",
                          target_chroot) != 0:
                log.error("mouseconfig failed.")

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

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

    # /etc/sysconfig/firstboot
    if ks.has_key("install") and ks.has_key("firstboot"):
        log.info1("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 = "do not have"
        if mountmap.has_key("/boot"):
            dir = "/boot"
            prefix = ""
            have = "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

        log.info1("Searching for installed kernels")
        kernels = get_installed_kernels(target_chroot)

        if len(kernels) < 1:
            log.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
            log.info1("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' % dir,
                '#          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:
                vmlinuz = '%s/vmlinuz-%s' % (prefix, kernel)
                initrd = '%s/initrd-%s.img' % (prefix, kernel)
                if not os.path.exists(target_chroot+vmlinuz):
                    log.warning("Kernel image for kernel "
                                "'%s' does not exist, skipping." % \
                                kernel)
                    continue
                if not os.path.exists(target_chroot+vmlinuz):
                    log.warning("Initrd for kernel '%s' does not exist." % \
                                kernel)
                content.extend( \
                    [ 'title %s (%s)\n' % (source.getName(), kernel),
                      '\troot (%s)\n' % hd_boot,
                      '\tkernel %s ro root=%s%s\n' % (vmlinuz, root, append),
                      '\tinitrd %s\n' % initrd ] )

            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/" % source.getArch()
            list = os.listdir(target_chroot+t)
            if len(list) == 0:
                log.error("grub stage files are missing in installation tree.")
                return 1
            for f in [ "stage1", "stage2" ]:
                if not f in list:
                    log.error("grub stage file '%s' is missing in "
                              "installation tree.", f)
                    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:
                        log.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):
                log.error("grub setup failed.")
            else:
                try:
                    check_exists(target_chroot, "/sbin/grub")
                except:
                    log.error("/sbin/grub does not exist, skipping.")
                else:
                    if run_script("/bin/sh /tmp/grub-setup",
                                  target_chroot) != 0:
                        log.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):
            log.info1("Sanitizing lvm: Removing cache file.")
            try:
                os.unlink(lvm_cache)
            except:
                log.error("Unable to remove cache file.")


    # relabel on first boot if selinux is enabled for guest and not available
    # on host because of missing module or no support for selinux
    if ks.has_key("install") and ks.has_key("selinux") and \
           not ks["selinux"].has_key("disabled") and \
           ((source.isRHEL() and source.cmpVersion("3") > 0) or \
            (source.isFedora() and source.cmpVersion("2") > 0)):

        if not pyrpm.rpmconfig.selinux_enabled or \
               not pyrpm.se_linux.is_selinux_enabled() > 0:
        
            log.info1("Enabled SELinux file contexts relabling at first boot.")
            
            # selinux not enforcing and not permissive in host system
            # touch .autorelabel in target chroot
            if not create_file(target_chroot, "/.autorelabel"):
                log.error("Failed to create .autorelabel file.")
                return 1

        else:
            # copy the stage2 file contexts file and relabel the differences
            # to the installed file if the stage2 file is different

            fname_1 = se_linux._gen_file_context_path(stage2_chroot)
            digest = md5.new()
            fd = open(fname_1)
            pyrpm.updateDigestFromFile(digest, fd)
            fd.close()
            md5_1 = digest.hexdigest()

            fname_2 = se_linux._gen_file_context_path(target_chroot)
            digest = md5.new()
            fd = open(fname_2)
            pyrpm.updateDigestFromFile(digest, fd)
            fd.close()
            md5_2 = digest.hexdigest()

            if md5_1 != md5_2:
                # relabel if file_contexts file from stage2 is not identical to
                # the installed one!
                copy_file(fname_1, "%s.pre" % fname_2)
                log.info1("SELinux file contexts difference relabling from "
                          "stage2 to installed...")
                run_script("fixfiles -C %s.pre restore" % fname_2,
                           target_chroot)

    # run post script
    if ks.has_key("install") and ks.has_key("post") and len(ks["post"]) > 0:
        i = 0
        for dict in ks["post"]:
            if dict.has_key("script") and len(dict["script"]) > 0:
                if len(ks["post"]) > 1:
                    log.info1("Running post script #%d.", i)
                else:
                    log.info1("Running post script.")
                chroot = None
                if not dict.has_key("nochroot"):
                    chroot = target_chroot
                elif stage2_chroot:
                    chroot = stage2_chroot
                if not run_ks_script(dict, chroot):
                    return 1
            i += 1

    # copy log file into target_chroot+/root/
    log.close()
    fd = open(log_filename, "r")
    target = "/root/pyrpmkickstart.log"
    try:
        target_fd = open(target_chroot+target, "w")
    except Exception, msg:
        errro("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

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

verbose = False
target_chroot = ""
nocleanup = False
tempdir = None
diskmap = None
partitionmap = None
devmap = None
wait = 0
target_dir = None
raidmap = None
stage2_chroot = None
vgmap = None
source = None

# abort if user is not root
if os.geteuid() != 0:
    log.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])
signals = pyrpm.setSignals(exitHandler)

try:
    status = main()
except Exception:
    log.error("\n"
              "- - - - - - - - - - - - - - - - - - - -"
              " - - - - - - - - - - - - - - - - - - - -"
              "\n" + traceback.format_exc() +
              "- - - - - - - - - - - - - - - - - - - -"
              " - - - - - - - - - - - - - - - - - - - -", nofmt=1)
    status = 1

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

# block signals for cleanup
signals = pyrpm.blockSignals()

# wait one second before going on
time.sleep(1)

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)

log.info1("Cleaning up, please wait...")

if source:
    source.close()
    source = None

if devmap:
    # remove temporary devices
    for dev in devmap:
        if not os.path.exists(target_chroot+devmap[dev]):
            log.warning1("'%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(signals)
sys.exit(status)

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