#!/usr/bin/python
#
# Copyright (c) 2012 Scott Bahling, SUSE Linux Products GmbH
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program (see the file COPYING); if not, write to the
# Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
#
################################################################
#
# SUSE Driver Tools
# Tool for building and using Driver Kits
# based on Build SUSE Kernel ISO by Tejun Heo <teheo@suse.de>
#

import sys
import os
import shutil
import glob
import re
import fnmatch
import logging
import yaml

import SUSEDriverTools.cli as cli
from SUSEDriverTools.utils import *
from SUSEDriverTools.scripts import *
from SUSEDriverTools.dk_kiwi import DKOptions, DKKiwi
from SUSEDriverTools.contentfile import ContentFile
from SUSEDriverTools.package import Pkg
from SUSEDriverTools.config import Config, updatefile_types
from SUSEDriverTools import bootable_support
from SUSEDriverTools import __version__
import SUSEDriverTools

#################################################################
# Gather update packages and find installation kernel package to
# use
#################################################################

def get_linuxrc_config(initrd):
    tmpdir = os.path.join(work_path.root, 'lxrc-tmp')
    makedirs(tmpdir)
    unpack_initrd(initrd, 'linuxrc.config', tmpdir)
    config = read_file(os.path.join(tmpdir, 'linuxrc.config'))
    remove(tmpdir)
    return config

def get_base_kernel(path):
    kernel_pkgs = []
    for cur, dirs, files in os.walk(path):
        for filename in fnmatch.filter(files, 'kernel-default*.rpm'):
            pkg = Pkg(os.path.join(cur, filename))
            if pkg.is_install_kernel:
                kernel_pkgs.append(pkg)
    return kernel_pkgs


def process_updates():
    workflow.start()

    if not (conf.addon or conf.updatedir):
        logger.info("No updates to process")
        workflow.end()
        return

    # scan addon or update directory for any packages to add to kISO
    if conf.addon:
        searchpath = conf.addon
    else:
        if conf.cmd == 'driverkit' and \
                not same_fs(conf.updatedir, work_path.root):
            updatedir = os.path.join(work_path.root, 'updates')
            if conf.steps.unpack:
                makedirs(updatedir)

                for item in os.listdir(conf.updatedir):
                    src = os.path.join(conf.updatedir, item)
                    copy(src, updatedir)

            conf.updatedir = updatedir

        searchpath = conf.updatedir

    if conf.base:
        basearchs = conf.base_arch

    logger.info("Scanning %s for rpm packages to add to media..." % searchpath)
    for cur, dirs, files in os.walk(searchpath):
        for filename in fnmatch.filter(files, '*.rpm'):
            file_path = os.path.join(cur, filename)
            pkg = Pkg(file_path)
            if pkg.is_source():
                # Only binaries, sources will be picked up by
                # kiwi automatically
                conf.source_rpms = True
                continue
            if conf.base and pkg.is_install_kernel and \
               pkg.arch in basearchs:
                logger.info("Adding update kernel: %s" % pkg.filename)
                conf.update_kernel.append(pkg)
            elif conf.base and not pkg.arch in basearchs:
                logger.warning("Package arch does not match base arch\
                                skipping. %s" % pkg.filename)
                continue
            else:
                logger.info("Adding package: %s" % pkg.filename)
                conf.updates.append(pkg)
                conf.update_archs.add(pkg.arch)

    # Search for instsys packages in updatedir
    for cur, dirs, files in os.walk(conf.updatedir):
        for filename in fnmatch.filter(files, '*.rpm'):
            file_path = os.path.join(cur, filename)
            pkg = Pkg(file_path)
            # if package in inst-sys subdirectory it will only be
            # unpacked into DUD inst-sys location. Do not add to
            # updates.
            if 'inst-sys' in cur:
                logger.debug("Found %s for inst-sys" % pkg.name)
                conf.instsys_packages.append(pkg)

    logger.info("Update archs: %s" % conf.update_archs)

    # scan updatedir for other user provided files
    logger.info("Scanning %s for other files..." % conf.updatedir)
    for cur, dirs, files in os.walk(conf.updatedir):
        find_update_files(cur, files)

    # if bootable, and no update kernel provided, use kernel from base
    # skip if just creating DUD
    if not conf.cmd == 'dud' and conf.base and not conf.update_kernel:
        logger.info("No update kernel found... using kernel from base media")
        pkgs = get_base_kernel(conf.base)
        if pkgs:
            conf.base_kernel = pkgs
        else:
            fatal("No default kernel flavor found on base media.")

    workflow.end()


def find_update_files(path, files):
    # scan updatedir for other user provided files
    updatefiles = conf.updatefiles
    for item, match, default in updatefile_types:
        for filename in fnmatch.filter(files, match):
            file_path = os.path.join(path, filename)
            logger.info("Found: %s" % file_path)
            try:
                updatefiles[item].append(file_path)
            except AttributeError:
                updatefiles[item] = file_path

    conf.updatefiles = updatefiles


#################################################################
# Functions for unpack step
#################################################################
def determine_dud_path(config='', dst=''):
    # determine dud path based on linuxrc.config file
    # or dud_distro and dud_version options
    # config = linuxrc.config text
    workflow.start()
    if not config and conf.base:
        config = read_file(os.path.join(work_path.initrd_staging,
                                        'linuxrc.config'))

    if config:
        path = ''
        for line in config.split('\n'):
            line = line.strip()
            if line.startswith('UpdateDir:'):
                path = line.split(':', 1)[1].strip().strip('/')
                break
        if not path:
            fatal('No valid UpdateDir in linuxrc.config')

    elif conf.dud_version and conf.dud_distro:
        path = os.path.join('linux',
                            conf.dud_distro, conf.dud_version)
    else:
        debug.info("Missing --base or --dud_distro & --dud_config.")
        fatal("Unable to determine DUD path!")

    workflow.end()

    return os.path.join(dst, path)


def create_work_directory():
    workflow.start()

    # if not unpacking, then we use existing work directory
    if not conf.steps.unpack: 
        if check_dir(work_path.root, mode='rwx'):
            logger.info("Using work directory %s..." % work_path.root)
            return
        else:
            fatal("Work directory %s does not exist or is not accessible"
                    % work_path.root)

    if check_dir(work_path.root, mode='rwx'):
        if empty_dir(work_path.root):
            logger.info("Work directory %s exists and is empty... using..." 
                    % work_path.root)
        else:
            fatal("Work directory %s exists but is not empty!"
                    % work_path.root)
    else:
        try:
            os.mkdir(work_path.root, 0775)
        except Exception, e:
            fatal('failed to create work directory (%s)' % str(e))

    logger.debug('work_path "%s"' % work_path.root)

    if conf.cmd in ('pxe', 'kiso') or (conf.cmd == 'driverkit' and conf.base):
        os.mkdir(work_path.boot_staging, 0755)
        os.mkdir(work_path.initrd_staging, 0755)

    os.mkdir(work_path.kernel_staging, 0755)

    workflow.end()


def load_isolinux_cfg():
    workflow.start()
    # if not user provided isolinux.cfg, read default file from boot.staging
    logger.info('loading isolinux.cfg')
    if not conf.updatefiles['isolinux.cfg']:
        conf.updatefiles['isolinux.cfg'] = os.path.join(work_path.loader,
                                                        'isolinux.cfg')
    workflow.end()
    return read_file(conf.updatefiles['isolinux.cfg'])


def find_module_updates(path, onlyweak=False):
    logger.debug('find_module_updates: %s, %s' % (path, onlyweak))
    updates = []
    prefix = os.path.join(path, 'lib/modules')
    paths = glob.glob(os.path.join(prefix, '*'))
    for path in paths:
        logger.debug('Searching %s for kernel modules' % path)
        version = os.path.basename(path)
        logger.debug('version: %s, kernel_ver: %s' %
                     (version, work_path.kernel_ver))
        if onlyweak and version == work_path.kernel_ver:
            logger.debug('skipping...')
            # skip standard installation kernel module path
            continue
        for cur, dirs, files in os.walk(os.path.join(path, 'updates')):
            logger.debug('collecting updates: %s' % cur)
            for filename in files:
                updates.append(os.path.join(cur, filename))
    return updates


def weakupdates():
    workflow.start()
    # check for KMP weakupdates and create links
    # simulates what the module-init-tools does
    # Instead of a symlink we copy (or hardlink) the file so that
    # the true file gets copied in the prepare_modules routine.
    # This does NOT check for kABI compatibility
    logger.debug('weekupdates()...')
    weakupdates_path = os.path.join(work_path.new_modules, 'weak-updates')
    logger.debug('weakupdates_path: %s' % weakupdates_path)
    if not os.path.exists(weakupdates_path):
        os.mkdir(weakupdates_path, 0755)

    searchpath = work_path.kernel_staging
    for filepath in find_module_updates(searchpath, onlyweak=True):
        logger.debug('Creating weakupdate links for %s' % (filepath))
        filename = os.path.split(filepath)[1]
        copy(filepath, os.path.join(weakupdates_path, filename))

    workflow.end()


def unpack_bootloader(media_path):
    workflow.start()
    # copy boot loader from source media
    # to boot loader staging

    # first setup work paths
    work_path.determine_loader_paths(conf.base_arch)
    logger.debug("loader path: %s" % work_path.loader)
    makedirs(work_path.loader)

    # copy
    src = os.path.join(media_path, work_path.loader_rel)
    logger.info("unpack_bootloader: src=%s" % src)
    for fname in os.listdir(src):
        copy(os.path.join(src, fname),
             os.path.join(work_path.loader, fname))

    efisrc = os.path.join(media_path, work_path.efi_rel)
    if conf.efi and os.path.isfile(efisrc):
        efidst = os.path.join(work_path.boot_staging, work_path.efi_rel)
        makedirs(os.path.split(efidst)[0])
        copy(efisrc, efidst)
        work_path.efi_image = efidst
        conf.efi = conf.efi and True
    else:
        # no efi on base media
        conf.efi = False

    args = "-R +w '%s'" % work_path.boot_staging
    cmds.chmod.ex(args)

    workflow.end()


def unpack_efi(efi_image):
    workflow.start()
    # make work directories
    os.mkdir(work_path.efi_staging, 0755)

    # mount and copy files from efi image
    mntpt = work_path.mount_efi(efi_image)
    if not mntpt:
        fatal("Mounting EFI image failed.")
    src = os.path.join(mntpt, "efi")
    dst = os.path.join(work_path.efi_staging, 'efi')
    copy(src, dst)
    work_path.umount_efi()

    workflow.end()


def process_install_kernel(pkg):
    # get kernel version
    if pkg.kernel_version:
        logger.info("Using kernel version %s-%s" %
                    (pkg.kernel_version, pkg.kernel_flavor))
        work_path.kernel_ver = "%s-%s" % (pkg.kernel_version,
                                          pkg.kernel_flavor)
    conf.kernel_provides.update(pkg.get_provides())
    logger.debug('unpacking %s' % pkg.path)
    unrpm(pkg, work_path.kernel_staging)


def unpack_kernel(packages):
    workflow.start()
    # unpack update packages (default flavor kmps only)
    for pkg in packages:
        if pkg.name in conf.exclude_from_initrd:
            logger.info("Not including %s in initrd." % pkg.path)
            continue
        if pkg.is_install_kernel:
            process_install_kernel(pkg)
        if not pkg.kmp_flavor == "default":
            continue
        # test for kabi compatibility
        # Broken!! FIXME
        conflicts = pkg.kmp_kabi_conflict(conf.kernel_provides)
        if conflicts:
            logger.warning("%s incompatible with boot kernel. \
                           Not unpacking..." % pkg.filename)
            for conflict in conflicts:
                logger.warning(" => %s not provided by kernel" % conflict)
        else:
            logger.debug('unpacking %s' % pkg.path)
            unrpm(pkg, work_path.kernel_staging)

    workflow.end()


#
# Unpack parts of base media
#
def unpack_base():
    workflow.start()

    ###################################################
    # unpack kernel and KMP packages to kernel.staging
    ###################################################
    if not conf.update_kernel:
        logger.debug("no update kernel... using base kernel")

    kernel_packs = conf.update_kernel or conf.base_kernel
    unpack_kernel(kernel_packs + conf.updates)
    work_path.determine_kernel_paths()

    ###################################################
    # unpack important parts of base media
    ###################################################
    unpack_bootloader(conf.base)

    ###################################################
    # locate and unpack initrd
    ###################################################
    initrd_file = os.path.join(work_path.loader, 'initrd')
    unpack_initrd(initrd_file, dst=work_path.initrd_staging)
    # get path to kernel modules in initrd
    work_path.determine_modules_path()

    ###################################################
    # locate and unpack efi image
    ###################################################
    if conf.merge:
        logger.info("Copying base to %s" % work_path.iso_staging)
        makedirs(work_path.iso_staging)
        for item in os.listdir(conf.base):
            if item == 'EFI':
                continue
            src = os.path.join(conf.base, item)
            dst = os.path.join(work_path.iso_staging, item)
            copy(src, dst)

    if conf.efi:
        newefipath = os.path.join(conf.base, 'EFI/BOOT')
        if os.path.exists(newefipath):
            src = os.path.join(conf.base, 'EFI')
            dst = os.path.join(work_path.efi_staging, 'EFI')
            copy(src, dst)
        else:
            unpack_efi(work_path.efi_image)

    weakupdates()

    # Once again make sure all paths are setup
    work_path.determine_paths(conf.base_arch)

    workflow.end()


#################################################################
# Functions for prepare step
#################################################################

def modules_in_modconf(line):
    fields = []
    modules = []
    in_quote = ''
    tok = ''

    for i in range(0, len(line)):
        c = line[i]

        if in_quote:
            if c != in_quote:
                tok += c
            else:
                in_quote = ''
        elif c == '"' or c == "'":
            in_quote = c
        elif c == ',':
            fields.append(tok)
            tok = ''
        else:
            tok += c
    if (len(tok)):
        fields.append(tok)

    if len(fields) >= 1:
        modules.append(fields[0])
    if len(fields) >= 4:
        modules += fields[3].split()
    if len(fields) >= 5:
        modules += fields[4].split()

    return modules


def process_module_config():
    workflow.start()

    mod_set = set()
    conf_mod = conf.module

    orig_path = os.path.join(work_path.old_modules, 'initrd/module.config')

    # later versions don't use module.config.  Skip if non-existent.
    if not os.path.exists(orig_path):
        logger.info('No module.config found')
        workflow.end()
        return mod_set

    #
    # Apply edits specified via --module parameter if apply_edits;
    # otherwise, just read in the file and build mod_set.
    #
    src_path = os.path.join(work_path.root, 'module.config.orig')
    copy(orig_path, src_path)
    dst_path = work_path.module_config

    src = read_file(src_path)
    section = ''
    blank_lines = ''
    newconfig = ''
    mod_updates = {}

    for oline in src.split('\n'):
        # add newline back to line
        oline += '\n'
        line = oline.strip()
        # collect blank lines
        if not line:
            blank_lines += oline
            continue

        # start of new section
        # append new modspecs to end of last section
        if line.startswith('[') and line.endswith(']'):
            for mod, newline in mod_updates.items():
                logger.info('Adding modspec %s to section %s' %
                            (newline, section))
                mod_set |= set(modules_in_modconf(newline))
                newconfig += newline + '\n'
                del conf_mod[section]

            # preserve blank lines between sections
            newconfig += blank_lines
            blank_lines = ''
            section = line.strip('[]')
            newconfig += oline
            logger.info('module.config: processing section: %s' % section)
            # queue up any module updates for this section
            modspecs = conf_mod.get(section, [])
            for modspec in modspecs:
                mod = modspec.split(',')[0]
                mod_updates[mod] = modspec
        else:
            if not re.match(r'^\s*(;|[^,]*=[^,]*)', line):
                mod = line.split(',')[0]
                # Replace old modspec if we have an update and remove from
                # list of updates.
                if mod in mod_updates:
                    logger.info('Replacing modspec %s with %s' %
                                (line, mod_updates[mod]))
                    line = mod_updates.pop(mod)
                    oline = line + '\n'
                mod_set |= set(modules_in_modconf(line))

            newconfig += blank_lines
            blank_lines = ''
            newconfig += oline

    newconfig += blank_lines
    write_file(dst_path, newconfig)
    # any --module w/ stray section?
    if len(conf_mod.keys()):
        logger.warning('invalid modules sections: ' + str(conf_mod.keys()))

    workflow.end()
    return mod_set


def fixup_linuxrc_config(config):
    workflow.start()
    # To use the same linuxrc.config for installing both SLES and SLED,
    # remove the product flavor from the Product: option
    newconfig = ''
    for line in config.split('\n'):
        if line.startswith('Product:'):
            line = line.replace('SUSE Linux Enterprise Desktop 11',
                                'SUSE Linux Enterprise 11')
            line = line.replace('SUSE Linux Enterprise Server 11',
                                'SUSE Linux Enterprise 11')
        elif line.startswith('UpdateDir:'):
            line = line.replace('sled', 'sle')
            line = line.replace('sles', 'sle')

        newconfig += '%s\n' % line

    workflow.end()
    return newconfig


def add_bootable_support():
    """Add bootable support to driver kit

    Adds custom YaST2 modules to SLE releases requiring them
    """

    content = conf.base_content
    if not content.is_sle():
        logger.warning("bootable driverkit supported only with \
                        SUSE Linux Enterprise as base")
        return
    distro = "%s.%s-%s" % (content.get_major_rel(),
                           content.get_minor_rel(),
                           content.get_base_archs(oldarch=True))

    logger.info("creating bootable for %s" % distro)
    for item in bootable_support(distro):
        logger.info("item: %s" % item)
        if item.endswith('ybc'):
            conf.updatefiles['y2updates'].append(item)
        if os.path.split(item)[1].startswith('initrd-'):
            conf.updatefiles['initrd_tarfiles'].append(item)


def prepare_dud(path=''):
    workflow.start()
    # This function will setup the DUD file system
    # in the given path (/linux/suse/<arch>-<prod>/)

    dud_path = path or os.path.join(work_path.root, 'DUD')

    modules_dir = os.path.join(dud_path, 'modules')
    install_dir = os.path.join(dud_path, 'install')
    instsys_dir = os.path.join(dud_path, 'inst-sys')
    y2modules_dir = os.path.join(dud_path, 'y2update/modules')

    # Put the packages under rpms subdirectory so that the
    # broken YaST installer does not see them.
    alt_install = "%s/rpms" % install_dir

    # FIXME We only support the modules directory at this time.
    # y2clients_dir = os.path.join(dud_path, 'y2update/clients')

    makedirs(install_dir, 0755)
    makedirs(instsys_dir, 0755)

    # load any user provided scripts
    scripts = {}
    for script in ('update.pre', 'update.post', 'update.post2'):
        if conf.updatefiles[script]:
            scripts[script] = read_file(conf.updatefiles[script])
        else:
            scripts[script] = ''

    # only create update directory and respective update.post if
    # not using add-on structure
    if conf.addon and dud_path.startswith(work_path.initrd_staging):
        # This initrd add-on repo is temporary. update.post2 script removes it
        # from the libzypp repos otherwise system will complain when
        # refreshing repos.
        majrel = conf.base_content.get_major_rel()
        minrel = conf.base_content.get_minor_rel()
        path = os.path.join(install_dir, 'update.post2')
        script = update_post2(scripts, majrel)
        scripts['update.post2'] = script

        # update.pre will detect addon= boot param and create proper
        # add_on_products.xml
        # Only required on releases before SLE 11 SP3
        rel = int(majrel) + int(minrel) * 0.1
        if rel <= 11.2:
            path = os.path.join(install_dir, 'update.pre')
            script = update_pre(scripts, conf.addon_name, majrel)
            scripts['update.pre'] = script

    elif conf.cmd in ('dud', 'kiso', 'driverkit'):
        # Initialize directories.
        # copy kernel, kmp and additional packages to update directory
        install_set = set()

        # Copy any kernel module updates to DUD module section
        modules = find_module_updates(work_path.kernel_staging)
        if modules:
            makedirs(modules_dir, 0755)
        for module in modules:
            copy(module, modules_dir)

        # Copy update packages to DUD install section
        packages = conf.updates + conf.update_kernel
        if packages:
            makedirs(alt_install, 0755)

        for pkg in packages:
            # Only include kernel packages in kiso DUD
            if pkg.is_kernel and conf.cmd != 'kiso':
                continue

            copy(pkg.path, alt_install)
            # flag non kernel packages to be installed with update.post script
            if not pkg.is_kernel:
                install_set.add(pkg.filename)
            if conf.dud_instsys and not (pkg.is_kernel or pkg.is_kmp):
                unrpm(pkg.path, instsys_dir)

        # create update.post file if there are any update packages
        # append to any user provided file
        if install_set or conf.update_kernel:
            script = update_post(scripts,
                                 install_set,
                                 conf.cmd,
                                 conf.update_kernel)
            scripts['update.post'] = script

    # Unpack any instsys packages to the DUD instsys section
    for pkg in conf.instsys_packages:
        unrpm(pkg.path, instsys_dir)

    # copy any YaST2 modules to DUD y2update
    if conf.updatefiles['y2updates']:
        makedirs(y2modules_dir, 0755)
        for y2file in conf.updatefiles['y2updates']:
            copy(y2file, y2modules_dir)

    # write DUD scripts do files
    for script in ('update.pre', 'update.post', 'update.post2'):
        if scripts.get(script, ''):
            path = os.path.join(install_dir, script)
            write_file(path, scripts[script])

    # add dud.config
    if conf.updatefiles['dud.config']:
        copy(conf.updatefiles['dud.config'], dud_path)

    # unpack any inst-sys tarfiles
    if conf.updatefiles['instsys_tarfiles']:
        instsys_dir = os.path.join(dud_dir, 'inst-sys')
        makedirs(instsys_dir, 0755)
        for tar_file in conf.updatefiles['instsys_tarfiles']:
            logger.info('extracting %s to %s...' % (tar_file, instsys_dir))
            extract_tarfile(tar_file, instsys_dir)

    # remove unused directories (shouldn't really be needed)
    for path in [modules_dir, instsys_dir, y2modules_dir, alt_install]:
        if os.path.exists(path) and empty_dir(path):
            remove(path)

    # Make DUD universal for both SLES and SLED installations.
    # For a DUD in the initrd use a symlink otherwise copy the structure.
    # Not needed for "merged" driver kits as they are SLES/SLED specific.
    if not (conf.merge or conf.no_uni_dud):
        alt_dud_path = ''
        base, sub = os.path.split(dud_path)
        if 'sles' in sub:
            alt_dud_path = os.path.join(base, sub.replace('sles', 'sled'))
        elif 'sled' in sub:
            alt_dud_path = os.path.join(base, sub.replace('sled', 'sles'))

        if alt_dud_path and dud_path.startswith(work_path.initrd_staging):
            symlink(sub, alt_dud_path)
        else:
            copy(dud_path, alt_dud_path)

    workflow.end()


def create_driverkit():
    workflow.start()

    ## make driver kit
    content = ContentFile(conf.updatefiles['content'])
    dk_opts = DKOptions()
    dk_opts.outdir = work_path.root
    dk_opts.repos = [conf.updatedir]
    if conf.base:
        dk_opts.archs = [conf.base_arch]
    else:
        dk_opts.archs = conf.update_archs
    dk_opts.label = content.LABEL or dk_opts.label
    dk_opts.version = content.VERSION or dk_opts.version
    dk_opts.release = content.RELEASE or dk_opts.release
    dk_opts.vendor = content.VENDOR or dk_opts.vendor
    dk_opts.name = content.NAME or content.LABEL.replace(' ', '_')
    dk_opts.releasenotesurl = content.RELNOTESURL
    dk_opts.updateurls = content.UPDATEURLS or ''
    if not conf.source_rpms:
        dk_opts.source_medium = 0

    for pkg in conf.updates + conf.update_kernel:
        if 'meta' in pkg.path.split(os.path.sep):
            dk_opts.metapackages.setdefault(pkg.name, []).append(pkg.arch)
        else:
            dk_opts.packages.setdefault(pkg.name, []).append(pkg.arch)

    opts = dk_opts.export()
    logger.debug("%s" % opts)

    kiwi = DKKiwi(dk_opts)
    kiwi_file = os.path.join(work_path.root, 'sdt.kiwi')
    xml = kiwi.generate_kiwi()
    logger.debug("KIWI file:\n%s" % xml)
    write_file(kiwi_file, xml)

    kiwi_build = os.path.join(work_path.root, 'kiwi_build')
    remove(kiwi_build)
    makedirs(kiwi_build)

    args = ' --root %s' % kiwi_build
    if conf.debug_enabled:
        args += ' --verbose 3'
    args += ' --create-instsource %s' % work_path.root

    cmds.kiwi.ex(args)

    workflow.end()
    return os.path.join(kiwi_build,
                        'main',
                        '%s-%s' % (dk_opts.name, '-'.join(dk_opts.archs))
                        )


def prepare_modules(mod_set):
    workflow.start()

    # Set up kernel modules in initrd.
    # SLE 10 puts them in /lib/modules/$kver-override-$kflav/initrd
    # SLE 11 puts them in /lib/modules/$kver-$krel-$kflav/initrd
    majrel = conf.base_content.get_major_rel()
    if majrel == '11':
        mod_path_rel = os.path.join('lib/modules',
                                    work_path.kernel_ver,
                                    'initrd')
    else:
        parts = work_path.kernel_ver.split('-')
        subdir = '-'.join([parts[0], 'override', parts[2]])
        mod_path_rel = os.path.join('lib/modules', subdir, 'initrd')

    mod_path = os.path.join(work_path.initrd_staging, mod_path_rel)
    new_mod_map = {}

    # build new_mod_map
    # find kernel modules from kernel package and any kmps installed
    # order is important 'weak-updates' take precidence over 'kernel'
    # and 'updates' take ultimate precidence
    for mod_dir in ['kernel', 'weak-updates', 'updates']:
        path = os.path.join(work_path.new_modules, mod_dir)
        for cur, dirs, files in os.walk(path):
            for filename in files:
                mod, ext = os.path.splitext(os.path.basename(filename))
                if ext == '.ko':
                    new_mod_map[mod] = os.path.join(cur, filename)

    remove(mod_path)
    makedirs(mod_path, 0755)

    for m in mod_set:
        if m in new_mod_map.keys():
            copy(new_mod_map[m], mod_path)
        else:
            logger.warning('module %s not found in kernel RPM' % m)
    if not mod_set:
        for module in new_mod_map:
            copy(new_mod_map[module], mod_path)

    if os.path.exists(work_path.module_config):
        copy(work_path.module_config, mod_path)

    # run depmod
    args = "-F '%s' -b '%s' %s" % (work_path.new_system_map,
                                   work_path.initrd_staging,
                                   work_path.kernel_ver)
    cmds.depmod.ex(args)

    # update modules symlink
    link_path = os.path.join(work_path.initrd_staging, 'modules')
    remove(link_path)
    symlink(mod_path_rel, link_path)

    workflow.end()


def prepare_firmware():
    workflow.start()
    # remove old firmware files if updating kernel package
    # TODO: remove everything under /lib/firmware??
    # TODO: add ability to update firmware with packages other than kernel
    if conf.update_kernel:
        fdir = os.path.join(work_path.initrd_staging, 'lib/firmware/')
        for kdir in fnmatch.filter(os.listdir(fdir), '*-default'):
            remove(os.path.join(fdir, kdir))

        # copy firmware from update kernel and/or kmp packages
        src = os.path.join(work_path.kernel_staging,
                           'lib/firmware/',
                           work_path.kernel_ver)
        dst = os.path.join(work_path.initrd_staging,
                           'lib/firmware/',
                           work_path.kernel_ver)
        copy(src, dst)

    workflow.end()


def prepare_initrd():
    workflow.start()

    initrd_staging = work_path.initrd_staging
    ## FIXME ##
    # MUST RUN process_module_config() BEFORE WE REMOVE
    # work_path.old_modules below
    mod_set = process_module_config()
    # if updating kernel, copy new kernel to initrd
    if conf.update_kernel:
        copy(work_path.new_kernel, work_path.boot_kernel)
        remove(work_path.old_modules)

    if conf.no_initrd_mod_updates:
        logger.info("...no-initrd-mod-updates flag set. \
                    Skipping processing of modules.")
    else:
        prepare_modules(mod_set)

    prepare_firmware()

    # fix up linuxrc.conf
    ## ONLY FOR DUAL SLES/SLED MEDIA
    ## WILL BREAK THIRD PARTY DUDs
    # config = read_file(os.path.join(initrd_staging, 'linuxrc.config'))
    # config = fixup_linuxrc_config(config)
    # write_file(configfile, config)

    # copy user provided add_on_products.xml file
    if conf.updatefiles['add_on_products.xml']:
        logger.info('copying user provided add_on_products.xml \
                     to initrd_staging...')
        copy(conf.updatefiles['add_on_products.xml'], initrd_staging)

    gpgfiles = conf.updatefiles.get('gpg', [])

    # Copy user provided installkey.gpg keyring into initrd
    # installkey.gpg is meant to _replace_ they default keyring
    # remove this from the list of key files as rest (if any)
    # will be imported into this keyring

    magictypes = ['GPG key public ring', 'PGP public key block']

    if gpgfiles:
        logger.debug('Processing gpg files...')
    for key in gpgfiles:
        logger.debug('    %s' % os.path.split(key)[1])
        if os.path.split(key)[1] != 'installkey.gpg':
            continue

        if file_type(key) in magictypes:
            logger.info('copying user provided installkey.gpg to \
                         initrd.staging...')
            copy(key, initrd_staging)
            gpgfiles.remove(key)
        else:
            logger.warn("Expected %s to be GPG key public ring, but is \
                         %s. Skipping..." % (key, file_type(key)))

    # Add any user provided gpg keys to installer keyring
    for key in gpgfiles:
        ftype = file_type(key)
        if ftype in magictypes:
            add_to_keyring(key, os.path.join(initrd_staging, 'installkey.gpg'))
        else:
            logger.warn('%s looks like gpg key, but is %s.' % ftype)
            logger.warn('   skipping...')

    # setup driver update section of initrd
    dud_path = determine_dud_path(dst=initrd_staging)
    prepare_dud(dud_path)
    # add driver kit to initrd
    if conf.addon and conf.embed_dk:
        embed_driverkit(conf.addon)

    if conf.merge:
        # create add_on_products.xml
        xmlfile = os.path.join(initrd_staging, 'add_on_products.xml')
        url = 'relurl://%s' % conf.addon_content.NAME.lower()
        xml = add_on_products_xml(conf.addon_name, url=url)
        write_file(xmlfile, xml)
    elif not (conf.embed_dk or conf.cmd in ('pxe', 'kiso')):
        # scripts called by linuxrc to copy driver kit into instsys dynamically
        # at boot. Not needed if driver kit is embedded in initrd
        majrel = conf.base_content.get_major_rel()
        path = os.path.join(initrd_staging, 'load-dk.sh')
        script = load_dk_script(conf.addon_name, conf.application, majrel)
        write_file(path, script)

        path = os.path.join(initrd_staging, 'eatkeys.sh')
        write_file(path, eatkeys_script())

    if conf.cmd in ('kiso', 'driverkit') and not conf.merge:
        # Create nextmedia file to customize the prompt that asked the user
        # to insert CD1 of the distro media.
        path = os.path.join(initrd_staging, 'nextmedia')
        if conf.updatefiles['nextmedia']:
            copy(conf.updatefiles['nextmedia'], path)
        else:
            xml = nextmedia_xml(conf.base_content.LABEL, conf.cmd)
            write_file(path, xml)

    # extract any initrd tar files into initrd filesystem
    # final step - tar files can overwrite anything else setup in initrd.
    # flexible but dangerous.
    for tar_file in conf.updatefiles['initrd_tarfiles']:
        logger.info('extracting %s to initrd_staging...' % tar_file)
        extract_tarfile(tar_file, initrd_staging)

    workflow.end()


def prepare_efi():
    workflow.start()
    efi_staging = work_path.efi_staging
    base_arch = conf.base_arch

    elilo_conf_file = ''
    grub_conf_file = ''
    files = find_file(efi_staging, 'elilo.conf')
    if files:
        elilo_conf_file = files[0]
    files = find_file(efi_staging, 'grub.cfg')
    if files:
        grub_conf_file = files[0]

    if elilo_conf_file:
        conf.elilo_conf = read_file(elilo_conf_file)
        # if updating kernel, link new kernel to efi
        if conf.update_kernel:
            remove(os.path.join(efi_staging, 'efi/boot/linux'))
            link(work_path.new_kernel,
                 os.path.join(efi_staging, 'efi/boot/linux'))
    elif grub_conf_file:
        conf.grub_conf = read_file(grub_conf_file)
    else:
        fatal("elilo.conf or grub.cfg not found in efi image")

    # update elilo.conf or grub2.cfg file
    if conf.elilo_conf:
        cfg = conf.elilo_conf
        add_boot_opt = add_elilo_boot_option
        install_entries = ('linux', 'linux-noGOP')
        rescue_entries = ('rescue', 'rescue-noGOP')
    elif conf.grub_conf:
        cfg = conf.grub_conf
        add_boot_opt = add_grub2_boot_option
        install_entries = ('Installation',)
        rescue_entries = ('Rescue System',)
    else:
        fatal("No elilo or grub2 config for EFI!")

    # Add call to load-dk on boot comand line
    if conf.cmd == 'driverkit' and not conf.merge:
        logger.info('adding boot options to elilo.conf/grub.conf...')
        if conf.embed_dk:
            option = "exec='/eatkeys.sh'"
        else:
            option = "exec='/load-dk.sh %s'" % base_arch

        for entry in install_entries:
            cfg = add_boot_opt(cfg, entry, option)

        option = "exec='/eatkeys.sh'"
        for entry in rescue_entries:
            cfg = add_boot_opt(cfg, entry, option)

    # add any user provided boot params
    for bparam in conf.bootparams:
        section, param = bparam.split(':', 1)
        cfg = add_boot_option(cfg, section, param, hidden=False)

    # save modifications...
    if elilo_conf_file:
        logger.info('Writing %s' % elilo_conf_file)
        write_file(elilo_conf_file, cfg)
        logger.debug('%s' % cfg)
    elif grub_conf_file:
        logger.info('Writing %s' % grub_conf_file)
        write_file(grub_conf_file, cfg)
        logger.debug('%s' % cfg)

    workflow.end()


def embed_driverkit(add_on_path):
    workflow.start()
    ## Copy addon structure to initrd
    ## Install add_on_product.xml file that will register the addon during
    ## installation.

    base_arch = conf.base_arch
    # for the initrd we should ignore unused files to save space
    ignore_files = {'x86_64': shutil.ignore_patterns('i586', '*.i586.rpm'),
                    'i386': shutil.ignore_patterns('*.x86_64.rpm'),
                    'i586': shutil.ignore_patterns('*.x86_64.rpm'),
                    }

    initrd_addon_path = os.path.join(work_path.initrd_staging, 'add-on')
    makedirs(initrd_addon_path, 0755)
    # copy add-on product repository to initrd and to base iso image.
    for item in os.listdir(add_on_path):
        src = os.path.join(add_on_path, item)
        dst1 = os.path.join(initrd_addon_path, item)

        # do not copy any dud structures to the initrd
        if not item == 'linux':
            copy(src, dst1, ignore=ignore_files[base_arch])
        xml_file = os.path.join(work_path.initrd_staging,
                                'add_on_products.xml')
        xml = add_on_products_xml(conf.addon_content.get_attr('LABEL'),
                                  url='file:///add-on')
        write_file(xml_file, xml)

    workflow.end()


def get_addon_info(addon_path):
    contentpath = os.path.join(addon_path, 'content')
    content = conf.addon_content = ContentFile(contentpath)
    conf.addon_name = content.LABEL
    conf.application = '%s %s-%s' % (content.LABEL,
                                     content.get_version(),
                                     content.get_release())
    conf.volume = conf.volume or conf.application[:32].replace(' ', '_')
    logger.info("Addon Name: %s" % conf.addon_name)
    if not conf.output:
        sle_prod = conf.base_content.SHORTLABEL.lower()
        sle_prod = sle_prod.replace('SLES', 'SLE').replace('SLED', 'SLE')
        isoname = '%s-%s-%s-%s-%s.iso' % (content.NAME, sle_prod,
                                          content.base_archs,
                                          content.get_version(),
                                          content.get_release())
        conf.output = isoname.lower()


def prepare_addon():
    workflow.start()
    # copy user provided driverkit addon to base of media
    # if one not provided, create it and link to iso.staging
    # also get addon info (meta data) used during bootloader
    # preparation for add_on_products.xml generation.
    dst = work_path.iso_staging
    if conf.addon:
        dkpath = conf.addon
        get_addon_info(dkpath)
        if conf.merge:
            name = conf.addon_content.NAME.lower()
            dst = os.path.join(work_path.iso_staging, name)
        copy(dkpath, dst)
        os.chmod(dst, 0755)
    elif conf.updates:
        print conf.updates
        dkpath = create_driverkit()
        conf.addon = dkpath
        get_addon_info(dkpath)
        if conf.merge:
            name = conf.addon_content.NAME.lower()
            dst = os.path.join(work_path.iso_staging, name)
        remove(dst)
        move(dkpath, dst)
    workflow.end()


def prepare_bootloader():
    workflow.start()
    # Setup everything under /boot
    prepare_initrd()
    if conf.efi:
        prepare_efi()
    # update and copy isolinux.cfg to boot loader directory
    base_arch = conf.base_arch
    cfg = load_isolinux_cfg()
    if not conf.no_dk and not conf.merge:
        logger.info('adding boot options to isolinux.cfg...')
        if conf.embed_dk:
            boption = 'exec="/eatkeys.sh"'
        else:
            boption = 'exec="/load-dk.sh %s"' % base_arch
        cfg = add_boot_option(cfg, 'linux', boption)
        cfg = add_boot_option(cfg, 'repair', 'exec="/eatkeys.sh"')
        cfg = add_boot_option(cfg, 'rescue', 'exec="/eatkeys.sh"')

    # add any user provided boot params
    for bparam in conf.bootparams:
        section, param = bparam.split(':', 1)
        cfg = add_boot_option(cfg, section, param, hidden=False)
    logger.info('Writing isolinux.cfg to bootloader...')
    write_file(os.path.join(work_path.loader, 'isolinux.cfg'), cfg)
    logger.debug('%s' % cfg)

    # copy message file to boot loader directory
    if conf.updatefiles['message']:
        logger.info('copying user provided message file to bootloader...')
        copy(conf.updatefiles['message'], work_path.loader)

    # copy boot loader background to boot loader directory
    if conf.updatefiles['back.jpg']:
        logger.info('copying user provided background image to bootloader...')
        copy(conf.updatefiles['back.jpg'],
             os.path.join(work_path.loader, 'back.jpg'))

    workflow.end()


#
# Setup media
#
def prepare_iso():
    workflow.start()
    # build up install repo
    iso_dir = work_path.iso_staging

    # If bootable, link files from boot_staging to iso_staging
    if conf.base:
        boot_dir = work_path.boot_staging
        for cur, dirs, files in os.walk(boot_dir):
            dst_dir = os.path.join(iso_dir, cur[len(boot_dir)+1:])
            for dir in dirs:
                makedirs(os.path.join(dst_dir, dir))
            for filename in files:
                link(os.path.join(cur, filename),
                     os.path.join(dst_dir, filename))

    for readme in conf.updatefiles['READMES']:
        logger.info("Copying %s to iso.staging..." % readme)
        copy(readme, iso_dir)

    # extract media tar files into base of media
    # final step - tar files can overwrite anything else setup
    # on the media. Flexible but dangerous.
    for tar_file in conf.updatefiles['media_tarfiles']:
        logger.info('extracting %s to iso_staging...' % tar_file)
        extract_tarfile(tar_file, iso_dir)

    workflow.end()


#
# Steps to prepare the kiso
#
def prepare_kiso():
    workflow.start()

    # Setup bootloader
    prepare_bootloader()

    # Update iso parts
    prepare_iso()

    workflow.end()


#
# Steps to prepare the driver kit iso
#
def prepare_dk():
    workflow.start()

    # prepare addon repo
    if not conf.no_dk:
        prepare_addon()

    # If bootable driver kit, setup bootloader
    if conf.base:
        prepare_bootloader()

    if conf.include_dud:
        lxrc_config = ''
        if conf.initrd:
            lxrc_config = get_linuxrc_config(conf.initrd)
        elif conf.base:
            lxrc_config = read_file(os.path.join(work_path.initrd_staging,
                                        'linuxrc.config'))
        dud_path = determine_dud_path(lxrc_config, dst=work_path.iso_staging)
        prepare_dud(dud_path)

    # update iso parts
    prepare_iso()

    workflow.end()


#
# Steps to prepare the PXE files
#
def prepare_pxe():
    workflow.start()

    # setup initrd
    prepare_initrd()

    workflow.end()


#################################################################
# Functions for finish step
#################################################################

def pack_initrd(initrd_path):
    workflow.start()

    find_cmd = 'find . -print0'
    cpio_cmd = "%s --owner root.root --quiet --null -H newc -o" % cmds.cpio
    gzip_cmd = "%s -9" % cmds.gzip
    # pack initrd
    cmd = ' | '.join([find_cmd, cpio_cmd, gzip_cmd])
    cmd += " > '%s'" % initrd_path

    shellcmd(cmd, cwd=work_path.initrd_staging)

    # Make sure initrd is readable
    os.chmod(initrd_path, stat.S_IREAD | stat.S_IWRITE)

    workflow.end()


def pack_efi(image_path):
    workflow.start()
    efi_staging = work_path.efi_staging
    if work_path.new_efi():
        src = os.path.join(efi_staging, 'EFI')
        dst = os.path.join(work_path.iso_staging, 'EFI')
        copy(src, dst)
    else:
        # link initrd from boot loader to efi staging
        src = os.path.join(work_path.iso_staging,
                           work_path.loader_rel,
                           'initrd')
        link(src, os.path.join(efi_staging, 'efi/boot/initrd'))
        if os.path.isdir(image_path):
            image_file = os.path.join(image_path, 'efi')
        else:
            image_file = image_path

        # remove old efi image from
        if os.path.isfile(image_file):
            remove(image_file)
        # create new efi image
        create_efi_image(efi_staging, image_file)

    workflow.end()


def generate_sort_file():
    staging = work_path.iso_staging
    sortfile = ''
    bootcat = os.path.join(staging, os.path.split(work_path.loader_rel)[0],             
                                       'boot.catalog')
    isolinux_bin = os.path.join(staging, work_path.loader_rel, 'isolinux.bin')
    loader_path = os.path.join(staging, work_path.loader_rel)

    for fn in os.listdir(loader_path):
        if fn == 'isolinux.bin':
            continue
        sortfile += '%s 1\n' % os.path.join(loader_path, fn)
    sortfile += '%s 2\n' % isolinux_bin
    sortfile += '%s 3\n' % bootcat

    return sortfile


#
# Generate ISO image
#
def iso_pack():
    workflow.start()

    # If bootable pack initrd and efi
    if conf.base:
        initrd_path = os.path.join(work_path.iso_staging,
                                   work_path.loader_rel,
                                   'initrd')
        efi_path = os.path.join(work_path.iso_staging, work_path.efi_rel)
        pack_initrd(initrd_path)
        if conf.efi:
            pack_efi(efi_path)

    content = conf.addon_content or conf.base_content
    sortfile = os.path.join(work_path.root, 'isosort')
    write_file(sortfile, generate_sort_file())

    bootcat = os.path.join(os.path.split(work_path.loader_rel)[0],             
                                       'boot.catalog')
    loader = os.path.join(work_path.loader_rel, 'isolinux.bin')
    default_vol = 'SDT_%s_%s' % (conf.cmd.upper(),
                                  content.get_base_archs(oldarch=True))
    conf.volume = conf.volume or default_vol

    args = ' -pad -r -J -l -D -allow-leading-dots'
    #args += ' -graft-points '
    args += ' -input-charset default'
    if conf.debug_enabled:
        args += ''
        # mkisofs verbosity does not provide much useful info
        #args += ' -v -v'
    else:
        args += ' -quiet'
    if conf.volume:
        args += " -V '%s'" % conf.volume.replace(' ', '_')
    if conf.publisher:
        args += " -publisher '%s'" % conf.publisher
    if conf.preparer:
        args += " -p '%s'" % conf.preparer
    if conf.application:
        args += " -A '%s'" % conf.application
    if conf.application:
        args += " -volset '%s'" % conf.application
    args += " -sysid LINUX"
    args += " -o '%s'" % conf.output
    if conf.base:  # bootable
        args += " -sort %s" % sortfile
        args += " %s" % ' -no-emul-boot'
        args += " %s" % ' -boot-load-size 4'
        args += " %s" % ' -boot-info-table'
        args += " -b '%s'" % loader
        args += " -c '%s'" % bootcat
        args += " -hide '%s'" % bootcat
        args += " -hide-joliet '%s'" % bootcat
    if conf.base and conf.efi:
        args += " -eltorito-alt-boot"
        args += " -no-emul-boot"
        args += " -boot-load-size 1"
        # -e arg used with patched genisoimage (mkisofs)
        # args += " -e boot/x86_64/efi"
        args += " -eltorito-boot boot/x86_64/efi"
    args += ' .'

    cmds.mkisofs.ex(args, cwd=work_path.iso_staging)

    if conf.base:
        fixiso(conf.output)

    tagmedia(conf.output)

    workflow.end()


def export_pxe():
    workflow.start()
    if not os.path.exists(conf.outputdir):
        makedirs(conf.outputdir, 0755)
    pack_initrd(os.path.join(conf.outputdir, 'initrd'))
    copy(os.path.join(work_path.loader, 'linux'), conf.outputdir)

    workflow.end()


#
# Main steps to generate DUD
#
def do_dud():
    workflow.start()
    create_work_directory()
    process_updates()
    if conf.initrd:
        # get linuxrc.config from user provided initrd
        initrd_file = conf.initrd
    elif conf.base:
        # get linuxrc.config from base initrd
        work_path.determine_loader_paths(conf.base_arch)
        initrd_file = os.path.join(conf.base,
                                   work_path.loader_rel, 'initrd')
        unpack_kernel(conf.updates)

    if initrd_file:
        lxrc_config = get_linuxrc_config(initrd_file)
    else:
        fatal("Can't determine dud path structure.\n \
               Use --base or --version option.")

    dud_path = determine_dud_path(lxrc_config)
    prepare_dud(os.path.join(conf.outputdir, dud_path))

    workflow.end()


#
# Main steps to generate PXE files
#
def do_pxe():
    workflow.start()
    steps = conf.steps

    # Create work directory and unpack stuff
    create_work_directory()

    # Scan for specified update files and/or update directory
    process_updates()

    if steps.unpack and conf.base:
        unpack_base()

    if not (check_dir(work_path.boot_staging) and
            check_dir(work_path.initrd_staging) and
            check_dir(work_path.kernel_staging)):
        fatal('unpack step is skipped but staging directories inaccessible')

    work_path.determine_paths(conf.base_arch)

    # Prepare and install new kernel
    if steps.prepare:
        prepare_pxe()

    # Pack it
    if steps.finish:
        export_pxe()

    workflow.end()


#
# Main steps to generate kiso
#
def do_kiso():
    workflow.start()
    steps = conf.steps

    ###################################################
    # Create work directory
    ###################################################
    create_work_directory()

    ###################################################
    # Scan for specified update files and/or update directory
    ###################################################
    process_updates()

    ###################################################
    # Unpack stuff
    ###################################################
    work_path.determine_paths(conf.base_arch)
    if steps.unpack and conf.base:
        unpack_base()
    elif not staging_exists():
        fatal('unpack step is skipped but staging directories inaccessible')

    ###################################################
    # Prepare and install new kernel
    ###################################################
    if steps.prepare:
        prepare_kiso()

    ###################################################
    # Pack it
    ###################################################
    if steps.finish:
        iso_pack()

    workflow.end()


#
# Main steps to generate driver kit
#
def do_dk():
    workflow.start()
    steps = conf.steps

    ###################################################
    # Create work directory
    ###################################################
    create_work_directory()

    ###################################################
    # Scan for specified update files and/or update directory
    ###################################################
    process_updates()

    if conf.cmd == 'driverkit' and conf.base:
        add_bootable_support()


    ###################################################
    # Unpack stuff
    ###################################################
    work_path.determine_paths(conf.base_arch)
    if steps.unpack and conf.base:
        unpack_base()
    elif conf.base and not staging_exists():
        fatal('unpack step is skipped but staging directories inaccessible')

    ###################################################
    # Prepare and install new kernel
    ###################################################
    if steps.prepare:
        prepare_dk()

    ###################################################
    # Pack it
    ###################################################
    if steps.finish:
        iso_pack()

    workflow.end()

#
# 
#
def do_fixiso():
    if not os.path.exists(conf.input):
        logger.error("%s not found"% conf.input)
    if not os.path.isfile(conf.input):
        logger.error("%s is not a file"% conf.input)
    fixiso(conf.input)

#
# 
#
def do_unpack_initrd():
    if not os.path.exists(conf.input):
        logger.error("%s not found"% conf.input)
    if not os.path.isfile(conf.input):
        logger.error("%s is not a file"% conf.input)
    dst = './initrd-unpacked'
    dst = conf.output or conf.outputdir or dst
    logger.info("Unpacking initrd %s to %s" % (conf.input, dst))
    unpack_initrd(conf.input, dst=dst)


def init_logger(args):

    # The logger initialization is required before we officially
    # read in the user configuration options because the config
    # parsing code needs the logger.
    # Therefore the logger init needs to do a bit of configuration
    # parsing first to get the loglevel and logfile specifications.
    #
    # Command line settings take precedence over config files passed
    # by command line which take precedence over the user's global
    # configfile.

    config = {}
    # load user config if exists
    userconfig = os.path.expanduser('~/.sdt/config.yml')
    logger.info("User config file: %s" % userconfig)
    if os.path.isfile(userconfig):
        config.update(yaml.load(open(userconfig, 'r')))

    # load config file passed on command line
    if os.path.isfile(args.configfile):
        config.update(yaml.load(open(args.configfile, 'r')))

    debug = args.debug_enabled or config.get('debug_enabled', False)
    if debug:
        loglevel = logging.DEBUG
    else:
        loglevel = logging.INFO

    logger.setLevel(loglevel)

    logfile = args.logfile or config.get('logfile', 'sdt.log')
    # create file handler which logs even debug messages
    fh = logging.FileHandler(logfile)
    fh.setLevel(loglevel)
    # create console handler with a higher log level
    ch = logging.StreamHandler()
    ch.setLevel(loglevel)
    # create formatter and add it to the handlers
    formatter = logging.Formatter('%(name)s:%(levelname)s: %(message)s')
    fh.setFormatter(formatter)
    ch.setFormatter(formatter)
    # add the handlers to the logger
    logger.addHandler(fh)
    logger.addHandler(ch)

    cmdline = ' '.join(sys.argv)
    logger.info('*********************************************************')
    logger.info('*** SUSE Driver Tools version: %s' % __version__)
    logger.info('*** Logging started. Loglevel: %s' % loglevel)
    logger.info('*********************************************************')
    logger.info('*** Command Line:')
    logger.info('*** %s' % cmdline)
    logger.info('*********************************************************')


def staging_exists():
    if (check_dir(work_path.boot_staging) and
            check_dir(work_path.initrd_staging) and
            check_dir(work_path.kernel_staging)):
        return True
    else:
        return False


def fatal(msg):
    if logger:
        logger.critical(msg)
        logger.critical("Exiting...")
    if conf and conf.workpath:
        conf.workpath.cleanup()
    sys.exit(2)


# register global fatal handler
# this is sort of a hack. Need to rethink the handling of
# fatal errors
SUSEDriverTools._fatal = fatal

#
# Master function
#
if __name__ == "__main__":
    CMD = {'dud': do_dud,
           'pxe': do_pxe,
           'kiso': do_kiso,
           'driverkit': do_dk,
           'fixiso': do_fixiso,
           'unpackinitrd': do_unpack_initrd,
           }

    work_path = None
    conf = None
    # setup some global objects
    logger = ''
    workflow = Workflow()
    cmds = Cmds()
    logger = logging.getLogger('SUSE Driver Tools')

    # parse cli arguments
    args = cli.init_argparser().parse_args()
    # create config based on cli arguments
    init_logger(args)
    conf = Config(args)
    if not conf.cmd in ('fixiso', 'unpackinitrd'):
        work_path = conf.workpath
        SUSEDriverTools.utils.work_path = work_path
    # kickoff sdt sub command...
    CMD.get(conf.cmd, exit)()
    if conf.workflow:
        write_file('workflow.log', workflow.show())

    if work_path:
        work_path.cleanup()

    sys.exit(0)
