#!/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 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

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 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 bootdisk.booted_from_reimage_flash():
            bootdisk.init_providers()

    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()
            # Cleanup /etc/isilon_system_config
            os.system('/bin/rm -f /etc/isilon_system_config')
        # 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

