#!/usr/bin/env python

# Copyright (c) 2013 OpenStack Foundation
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

# NOTE: XenServer still only supports Python 2.4 in it's dom0 userspace
# which means the Nova xenapi plugins must use only Python 2.4 features

# TODO(sfinucan): Resolve all 'noqa' items once the above is no longer true

"""Inject network configuration into iPXE ISO for boot."""

import logging
import os
import shutil

import utils

# FIXME(sirp): should this use pluginlib from 5.6?
import dom0_pluginlib
dom0_pluginlib.configure_logging('ipxe')


ISOLINUX_CFG = """SAY iPXE ISO boot image
TIMEOUT 30
DEFAULT ipxe.krn
LABEL ipxe.krn
 KERNEL ipxe.krn
 INITRD netcfg.ipxe
"""

NETCFG_IPXE = """#!ipxe
:start
imgfree
ifclose net0
set net0/ip %(ip_address)s
set net0/netmask %(netmask)s
set net0/gateway %(gateway)s
set dns %(dns)s
ifopen net0
goto menu

:menu
chain %(boot_menu_url)s
goto boot

:boot
sanboot --no-describe --drive 0x80
"""


def _write_file(filename, data):
    # If the ISO was tampered with such that the destination is a symlink,
    # that could allow a malicious user to write to protected areas of the
    # dom0 filesystem. /HT to comstud for pointing this out.
    #
    # Short-term, checking that the destination is not a symlink should be
    # sufficient.
    #
    # Long-term, we probably want to perform all file manipulations within a
    # chroot jail to be extra safe.
    if os.path.islink(filename):
        raise RuntimeError('SECURITY: Cannot write to symlinked destination')

    logging.debug("Writing to file '%s'" % filename)
    f = open(filename, 'w')
    try:
        f.write(data)
    finally:
        f.close()


def _unbundle_iso(sr_path, filename, path):
    logging.debug("Unbundling ISO '%s'" % filename)
    read_only_path = utils.make_staging_area(sr_path)
    try:
        utils.run_command(['mount', '-o', 'loop', filename, read_only_path])
        try:
            shutil.copytree(read_only_path, path)
        finally:
            utils.run_command(['umount', read_only_path])
    finally:
        utils.cleanup_staging_area(read_only_path)


def _create_iso(mkisofs_cmd, filename, path):
    logging.debug("Creating ISO '%s'..." % filename)
    orig_dir = os.getcwd()
    os.chdir(path)
    try:
        utils.run_command([mkisofs_cmd, '-quiet', '-l', '-o', filename,
                           '-c', 'boot.cat', '-b', 'isolinux.bin',
                           '-no-emul-boot', '-boot-load-size', '4',
                           '-boot-info-table', '.'])
    finally:
        os.chdir(orig_dir)


def inject(session, sr_path, vdi_uuid, boot_menu_url, ip_address, netmask,
           gateway, dns, mkisofs_cmd):

    iso_filename = '%s.img' % os.path.join(sr_path, 'iso', vdi_uuid)

    # Create staging area so we have a unique path but remove it since
    # shutil.copytree will recreate it
    staging_path = utils.make_staging_area(sr_path)
    utils.cleanup_staging_area(staging_path)

    try:
        _unbundle_iso(sr_path, iso_filename, staging_path)

        # Write Configs
        _write_file(os.path.join(staging_path, 'netcfg.ipxe'),
                    NETCFG_IPXE % {"ip_address": ip_address,
                                   "netmask": netmask,
                                   "gateway": gateway,
                                   "dns": dns,
                                   "boot_menu_url": boot_menu_url})

        _write_file(os.path.join(staging_path, 'isolinux.cfg'),
                    ISOLINUX_CFG)

        _create_iso(mkisofs_cmd, iso_filename, staging_path)
    finally:
        utils.cleanup_staging_area(staging_path)


if __name__ == "__main__":
    utils.register_plugin_calls(inject)
