#!/usr/bin/env python

# Copyright C 2002-2013 by Isilon Systems LLC. All rights not expressly
# granted herein are reserved.
# Created in the United States of America.
# 
# This digital media is protected by U.S. and international copyright and
# intellectual property laws. Unless otherwise specified, all information
# and screens appearing as part of this digital medium, including
# software, services, documents, text, images, icons, and logos design;
# the selection, assembly, arrangement, and design thereof; and the code
# that enables its presentation, are the sole property of Isilon Systems,
# Inc.
# 
# Except as otherwise required by applicable law, no part of any
# copyrighted material may be reproduced, modified, translated,
# distributed, published, or transmitted by any means, electronic or
# mechanical, for any purpose, without the prior written consent of Isilon
# Systems. 
#  
# Isilon and Isilon Systems are registered trademarks of Isilon Systems,
# Inc.  Legion is a trademark of Isilon Systems, Inc.  All other
# trademarks and registered trademarks are the property of their
# respective holders.

# KEYWORD: nostart

import os
import signal
import stat
import sys
import time
import subprocess
import pwd
import uuid
import tarfile

import isi.hw.hal as hal
import isi.hw.imdd.devices as devices
import isi.hw.imdd.log as imddlog
import isi.hw.imdd.util as imddutil
import isi.hw.imdd.report as imddreport
import isi.sys.update.handlers as handlers
import isi.sys.bootdisk as bootdisk
import isi.sys.disk as disk
import isi.sys.geom as geom

FLASH_REIMAGE_MARKER = '/tmp/doing_flash_reimage'

def first_boot():
    # XXX: These paths need fixin`.

    for p in ['/etc/ifs/isi_first_boot_already_ran',
            '/etc/rc.d/isi_first_boot_already_ran',
            '/etc/rc.isi_first_boot_already_ran']:
        if os.path.exists(p):
            return False

    return True

def create_tar(source_dir, prefix='bkp'):
    tries = 0
    max_tries = 10
    filename = '/tmp/%s_%s.tar' % (prefix, str(uuid.uuid4()))
    while (os.path.exists('%s' % filename) and tries < max_tries):
        filename = '/tmp/%s_%s.tar' % (prefix, str(uuid.uuid4()))
        tries += 1
    if tries >= max_tries:
        return None
    try:
        fh = tarfile.open(filename, "w:gz")
        fh.add(source_dir, arcname=os.path.basename(source_dir))
    except:
        raise
    return filename

def extract_tar(archive, path):
    try:
        fh = tarfile.open(archive)
        fh.extractall(path=path)
    except:
        raise
    return None

def checkflash():
    flashtype = devices.flashtype()

    if (flashtype == None):
        return (0)

    # Remount the root as async to speed-up and increase durability
    # of the flash.
    print 'Performing automatic fsck of /'
    devices.fsck("/")
    imddutil.dd_popen("/sbin/mount -u -o noasync /")

    if (flashtype == "imdd_disabled"):
        import isi.hw.imdd.manufacturing as manufacturing
        if (devices.is_hash_yes(devices.rc_hash, 'manufacturing')):
            manufacturing.manufacturing(devices.imdds, 0)
        elif (devices.is_hash_yes(devices.rc_hash, 'preformat')):
            manufacturing.manufacturing(devices.imdds, 1)
            imddutil.dd_popen("shutdown -p now")
            while True:
                time.sleep(5)
            return (1)
        devices.mount_list = []
        devices.mount_list_build()
        mounts = devices.mount_list_lookup("/")
        if (len(mounts) != 1):
            imddutil.panic("Invalid mount on /")
        if mounts[0].isset("read-only"):
            imddutil.panic("R/W mount of / denied.  Filesystem is not "
                    "clean and '/sbin/fsck -y /' can not fix "
                    "it. Please make a new Flash or try to fix "
                    "it manually.")
        if (devices.is_hash_yes(devices.rc_hash, 'fwupdate')):
            # Execute script installed by mkflash
            sys.path.append('/fwupdate')
            import fwupdate_flash

            fwupdate_flash.do_fwupdate_flash()

            print("Powering down")
            imddutil.dd_popen('/sbin/shutdown -p now')
            # Continue to hold up startup scripts while shutdown 
            # takes effect
            while True:
                time.sleep(5)
        return 1


    # we need to avoid snatching up devices when we're destined to
    # use GEOM's gmirror, so only attach IMDD if we're destined for IMDD
    if bootdisk.future_system_is_imdd():
        # Finish setting up disks & imdd
        devices.attach()
        # (imdds are repaired during rebalance)
        devices.repair(do_imdds = False)

        if (imddreport.report("lists")):
            devices.device_lists_print()

        devices.cleardevs = 0

        # Do a full rebalance
        devices.rebalance()
    else:
        # If we're on a reimage flash, we're better off destroying and
        # creating a new mirror.
        #
        # A warning and user prompt should precede this action!
        # Log all prompt input/output to logfile, separately, but only once.
        wipe = False
        skip = None
        if bootdisk.booted_from_reimage_flash():
            backups = {}
            logged1 = False
            prompt1 = ("You are booting from a reimage flash, which should "
                       "destroy all data\non this node, including the boot "
                       "drive partitions.")
            prompt2 = ("Are you sure you wish to destroy the "
                       "boot drive partitions (yes/no)? ")
            prompt3 = ("Boot drive partitions, if valid, will be preserved.")
            while 1:
                print ""
                print prompt1
                if not logged1:
                    bootdisk.log(prompt1, console=False)
                    logged1 = True
                print ""
                response = raw_input(prompt2).lower()
                if response in ['yes', 'no', 'wipeall']:
                    bootdisk.log("%s %s" % (prompt2, response), console=False)
                    if response == 'yes':
                        wipe = True
                    elif response == 'wipeall':
                        # HIDDEN
                        wipe = True
                        skip = []
                    elif response == 'no':
                        wipe = False
                        print ""
                        bootdisk.log(prompt3)
                        print ""
                    else:
                        continue
                    break

            do_backup = False
            if wipe and skip is not None and len(skip) == 0:
                do_backup = True

            # back up any providers in default_save_providers
            if do_backup:
                geom.stable_wrapper(bootdisk.unlock_bootdisks, bootdisk.mesh)
                mounts = bootdisk.mountlist()
                for provider_id in bootdisk.default_save_providers:
                    try:
                        try:
                            dev = bootdisk.get_provider_dev(provider_id)
                        except:
                            # if we don't have it, so be it, move on to
                            # next provider_id
                            continue
                        bootdisk.log('Creating backup of provider %s' %
                            provider_id)
                        if bootdisk.is_mounted(dev):
                            bootdisk.umount_ufs(dev)
                        bootdisk.mount_filesystem([], mounts, provider_id,
                            '/mnt', fatal=True)
                        filename = create_tar('/mnt', provider_id)
                        if bootdisk.is_mounted(dev):
                            bootdisk.umount_ufs(dev)
                        backups[provider_id] = filename
                        time.sleep(2)
                    except:
                        raise
                for k, v in backups.iteritems():
                    if v is None:
                        raise 'Unable to create backup for provider %s' % k

            # init providers
            bootdisk.init_providers(wipe=wipe, skip=skip)

            # restore changes to save_providers if we wiped everything
            if do_backup:
                mounts = bootdisk.mountlist()
                for provider_id in bootdisk.default_save_providers:
                    try:
                        dev = bootdisk.get_provider_dev(provider_id)
                    except:
                        # if we don't have it, so be it, move on to
                        # next provider_id
                        continue
                    # Backup for a given provider was not generated, continue
                    if provider_id not in backups:
                        bootdisk.log('no backup for provider %s' % provider_id)
                        continue
                    bootdisk.log('restoring backup of provider %s' %
                        provider_id)
                    try:
                        filename = backups[provider_id]
                        bootdisk.mount_filesystem([], mounts, provider_id,
                            '/mnt', fatal=True)
                        filename = extract_tar(filename, '/')
                        bootdisk.umount_ufs('/mnt')
                        time.sleep(2)
                    except:
                        raise ('restoring backup of provider %s failed' %
                            provider_id)

            # For USB reimage, re-mount the keystore in order to release drives.
            mounts = bootdisk.mountlist()
            bootdisk.mount_filesystem([], mounts, bootdisk.PROVIDER_ID_KEYSTORE,
                '/keystore', fatal=False)
            #Keystore partition should not be accessible by anybody other
            #than root.See bug: 105844
            os.chmod("/keystore", 0700)

    if (flashtype == "update"):
        # Create a temp file to let the boot system know we're reimaging
        open(FLASH_REIMAGE_MARKER,'a').close()
        handlers.create_handler(handlers.FLASH_UPDATE)
        sys.exit(1)
    elif (flashtype == "reimage"):
        if bootdisk.booted_from_reimage_flash():
            os.system('/usr/bin/isi_vpd_pull --reImage')
            hal.reInitialize()
            if hal.supportsCto():
                os.system('/usr/bin/isi_inventory_tool --startUp --reImage')
                hal.reInitialize()
        # Create a temp file to let the boot system know we're reimaging
        open(FLASH_REIMAGE_MARKER,'a').close()
        handlers.create_handler(handlers.FLASH_REIMAGE)
        sys.exit(1)
    else:
        imddutil.panic("Unknown flash disk: " + flashtype)

    print "Please remove the flash disk after the node has powered down."

    # Umount all drives
    for dev in devices.imdds.list:
        dev.umount()

    # Put the drives into standby mode -- parking the heads
    imddutil.dd_popen("/usr/bin/isi_radish -s")
    
    # Shutdown
    imddutil.dd_popen("shutdown -p now; sleep 500")
    return (1)

def first_startup():
    if not first_boot():
        return
    try:
        mode = os.stat("/etc/rc.d/isi_first_boot")[stat.ST_MODE]
    except OSError:
        mode = None

    # If we've already booted once..
    if (mode == None):
        return

    if (len(devices.partitions.list) != 2):
        return

    # If we're a 1U...

    root = devices.imdds.lookup("root")
    root.mount(opt="-o noatime")

    var = devices.imdds.lookup("var")
    if (var.count() < 1):
        imddutil.panic("No devices available for var")
    var.mountpoint = "/usr/update/var"
    # newfs if we have to
    var.mount(opt="-o noatime", newfs = 1)

    # Copy over the stuff from /var onto the var partition
    imddutil.replicate("/var", var.mountpoint)
    var.umount()
    var.mountpoint = "/var"

def check_imdd0_root(imdd0):
    rootmounts = devices.mount_list_lookup('/')
    if rootmounts is []:
        imddutil.panic("Unable to find mount for root")
    elif rootmounts[-1].device == '/dev/imdd3a':
        print("Booted off non-root partition")
        print("Reboot in 15 seconds.  Press ^C to abort")
        time.sleep(15)
        imddutil.dd_popen_quiet("/sbin/shutdown -r now")
        time.sleep(100000000)

# Custom version of usable_part that allows root on non-boot drives
def boot_usable_part(part, imdd = None):
    # Most of the time it's just a matter of whether it's in chas 0
    result = (part.slice.drive.chasn == 0)

    if imdd != None:
        # Require boot disks for all partitions.
        result = result and (disk.is_bios_disk(part.device))
        # However, never allow conflicts
        if imdd.partitions.lookup(part.device) == None and \
                imdd.conflicts(part) == 1:
            result = False
    return result

# Implementation of devices.ImddList.target_mir_cnts() for this script
def boot_target_mir_cnts(imddsort, bios_parts, total_non_bios_disks):
    # Target 2 for each imdd.  ECC's are less likely during boot when the
    # system is fairly quiet.  Hopefully 2 mirrors is sufficient to keep
    # later background mirroring successful.
    for x in range(2):
        for imddr in imddsort:
            if bios_parts == 0:
                break
            if (imddr.count >= devices.imdd_maxpart):
                continue
            imddr.count += 1
            bios_parts -= 1

def main():
    def progress(progress):
        try:
            screen._progress = progress
            if hal.hasFrontPanelDisplay():
                screen.show()
                lcd.service()
        except:
            pass
        return

    def handle_sigint(signal, stack_frame):
        # stop boot and prompt for shell
        os._exit(1)

    signal.signal(signal.SIGINT, handle_sigint)

    try:
        import isi.ui.lcd.library as lcdlib
        import isi.ui.lcd.screens as screens

        screen = screens.ProgressMeterScreen("IQ Startup (IMDD)")
        if hal.hasFrontPanelDisplay():
            lcd = lcdlib.LCDClient(screen)
            lcd.start()
    except:
        lcd = None
        screen = None

    progress(0)

    # After the node is reset/reimaged, we should clear the
    # dead flags from imdd partitions.
    if not first_boot():
        devices.cleardevs = 0
    else:
        devices.cleardevs = 1

    # Probe, attach, and repair devices
    devices.disks_file_2 = ""

    # If the user were to configure dumpdev="OFF" after imdd had run,
    # we could remain with dumping pointing at an FS partition!  We have
    # to clear this on startup.
    if (0 != os.system("/sbin/dumpon off")):
        imddreport.report("error", "Error disabling dump device")

    imddlog.log_start()

    devices.probe()

    # If this is a flash boot, execute the appropriate script and return
    if (checkflash() == 1):
        return

    devices.attach(check_eio=True)
    # (imdds are repaired during rebalance)
    devices.repair(do_imdds = False)

    if (imddreport.report("lists")):
        devices.device_lists_print()

    devices.cleardevs = 0
    progress(15)

    # Do a minimal rebalance
    devices.imdds.target_mir_cnts = boot_target_mir_cnts
    devices.imdds.usable_part = boot_usable_part
    devices.rebalance()
    progress(30)

    first_startup()
    progress(40)
    root = devices.imdds.lookup("root")
    var = devices.imdds.lookup("var")
    crash = devices.imdds.lookup("crash")
    fulldump = devices.get_fulldump_drive()
    if fulldump is None:
        dump = devices.imdds.lookup("dump")    
    progress(60)

    check_imdd0_root(root)

    if (root.mount(opt="-o noatime") == 0):
        imddutil.panic("Couldn't mount root")

    bootdisk.rewrite_fstab()
    progress(70)

    if (not os.path.exists("/var")):
        os.makedirs("/var/crash")
    if (var.mount(opt="-o noatime") == 0):
        imddutil.panic("Couldn't mount var")
    crash.mount()

    #Allow daemon group to create coredumps.  See bug 85376.
    os.chown("/var/crash", -1, pwd.getpwnam("daemon").pw_gid)
    os.chmod("/var/crash", 0775)
    progress(80)

    devices.fix_boot()

    progress(90)
    imddlog.log_flush()
    if fulldump is not None:
        subprocess.call(['/sbin/savecore', '-v', '/var/crash', fulldump])
        subprocess.call(['/sbin/dumpon', '-v', fulldump])
    elif dump is not None:
        dump.setup_dump()    

    # Clear indications that we might be doing a flash reimage from this point
    try:
        os.remove(FLASH_REIMAGE_MARKER)
    except OSError:
        pass

    progress(100)

try:
    main()
except:
    sys.stdout = open('/dev/console', 'w')
    sys.stderr = open('/dev/console', 'w')
    raise

