#!/usr/bin/env python

# Copyright C 2002-2010 by Isilon Systems, Inc. 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.

import os
import re
import signal
import sys
import syslog

import isi.config.arrcfg as arrcfg
import isi.config.rc as rclib
import isi.error as ierr
import isi.fs.journal as jlib
import isi.gconfig as gconf
import isi.hw.hal as hal
import isi.misc
import isi.ui.lcd.screens as screens
import isi.ui.lcd.library as lcd
import isi.ui.lib.format as fmt

def init_syslog():
    syslog.openlog(sys.argv[0])

def clean_syslog():
    syslog.closelog()

def log_print(msg):
    syslog.syslog(syslog.LOG_NOTICE, msg)
    print msg

def kill_other(pid):
    try:
        os.kill(pid, signal.SIGINT)
    except os.error:
        pass

def stop_boot(msg):
    print msg
    # This allows isi_testjournal_rc to fail out and handle stop_boot
    sys.exit(1)

def is_newjournaled():
    ctx = gconf.open(gconf.gcfg_nodestatus).ctx_new()
    return ctx.read("journal_info.newjournaled_without_mount")

def disk_restore_to_dram():
    if os.system('/usr/sbin/isi_restore_journal') == 0:
        jlib.clear_bad()
        return True
    else:
        return False

def dram_new_journal():
    os.system('/usr/sbin/isi_newjournal --force -f %s' %
            hal.getJournalDevicePath())
    jlib.clear_bad()

def dram_save_to_disk():
    return os.system("/usr/sbin/isi_save_journal") == 0

def recover_journal():
    # If there hasn't been a mount since the journal was newjournal'd,
    # try reinitializing the journal to clear any ECC errors and try
    # the validation again.
    if is_newjournaled():
        log_print("Attempting to reinitialize journal...")
        dram_new_journal()
        if jlib.dram_valid():
            log_print("Reinitialize succeeded")
            return True

        log_print("Reinitialize failed")

    # Prefer disk backup over flash copy (likely to change post-Bonneville)
    if jlib.disk_valid():
        log_print("Attempting to restore journal from disk backup...")
        if disk_restore_to_dram():
            if jlib.dram_valid():
                log_print("Restore from disk succeeded")
                return True

        log_print("Restore from disk failed")

    if jlib.flash_valid():
        log_print("Attempting to restore journal from flash backup...")
        exc_str = None
        try:
            jlib.flash_restore_to_dram()
            if jlib.dram_valid():
                log_print("Restore from flash succeeded")
                return True
        except ierr.IsiError, e:
            exc_str = ": encountered error: %s" % str(e)

        log_print("Restore from flash failed%s" %
                exc_str if exc_str is not None else "")
 
    log_print("Attempting to save and restore journal to clear any "
            "ECC errors in unused DRAM blocks...")

    if dram_save_to_disk():
        log_print("Save succeeded, restoring...")
        if disk_restore_to_dram():
            if jlib.dram_valid():
                log_print("Restore succeeded")
                return True

    log_print("Restore failed")
    return False

def main():
    # Exit immediately if we're running on a node with no NVRAM.
    if not hal.isStorageClass():
        sys.exit(0)

    # Exit immediately if we're running from a manufacturing CF.
    rc_hash = rclib.parse()
    if rc_hash.has_key("manufacturing") and \
            re.search("[Yy][Ee][Ss]", rc_hash["manufacturing"]) != None:
        sys.exit(0)

    #
    # When a node comes from manufacturing, it should be in "ship mode".
    # This state keeps the batteries from being totally drained while the
    # node is on the shelf. Check to see if the node is in ship mode. If it
    # is, turn it off. This will allow the batteries to get fully charged
    # and support the nvram.
    #
    if hal.hasBatteryShipMode():
        bus = hal.getBatteryBus()
        if bus is None:
            log_print("Unable to get battery I2C bus number.")
        else:
            shipMode = hal.getBatteryShipMode(bus)
            if shipMode is None:
                log_print("Unable to get battery ship mode.")
            elif shipMode == 1:
                hal.setBatteryShipMode(bus, 0)
                log_print("Batteries have been taken out of ship mode.")

    log_print("Checking Isilon Journal integrity...")
    sys.stdout.flush()

    # If /ifs is mounted, just to the basic battery, block, and ECC tests;
    # do not actually scan/parse (i.e. walk) the journal data.
    if isi.misc.ifs_mounted():
        if jlib.dram_valid(do_walk=False):
            log_print("Mounted DRAM journal check: good")
        else:
            log_print("Mounted DRAM journal check: BAD")

        sys.exit(0)

    devFile = hal.getJournalDevicePath()
    if devFile is None or not os.path.exists(devFile):
        stop_boot("The node does not have the expected NVRAM device %s. "
                  "Contact EMC Customer Support immediately." % devFile)

    # At initial boot, when /ifs isn't mounted yet, we do a full journal
    # walk. For safety, we should never trust an auxiliary file to tell us
    # the journal is bad. If we have a valid journal in NVRAM, we should
    # never overwrite it. If a user needs to force an overwrite for some
    # reason, they can always use dd to zero the journal superblock
    # (the first 512 bytes).
    if not jlib.dram_valid():

        # Set the journal bad 'flag' to make the other journal utilities
        # behave correctly.
        jlib.mark_bad()

        log_print("DRAM journal is invalid, initiating recovery...")

        if not recover_journal():
            stop_boot("Could not recover journal. Contact Isilon "
                    "Customer Support immediately.")
        else:
            log_print("DRAM journal recovery succeeded")

    jlib.clear_bad()

    # Check the journal version. If it isn't current, something's horribly
    # wrong (e.g., unsupported upgrade from pre-5.5 to 6.1) and we need to
    # stop boot.
    jver = jlib.get_version()
    if not jlib.version_supported(jver):
        log_print("Unsupported journal version %d" % jver)
        stop_boot("This node has an unsupported version of the journal: "
                "v%d. Contact EMC Customer Support immediately." % jver)

    try:
        (jguid, jdevid) = jlib.get_guid_and_devid()
        if long(jguid, 16) == 0L and jdevid == 0 and is_newjournaled():
            if arrcfg.is_local_configured():
                log_print("This node belongs to a cluster but appears "
                        "to have a new journal. Continuing anyway.")
            else:
                log_print("Journal new")
            sys.exit(0)

        acfg = arrcfg.config_load()
        aguid = acfg.guid
        adevid = acfg.local_devid
        if (jguid != aguid) or (jdevid != adevid):
            # About the only way to get here is messing up disk tango.
            # The journal is self-consistent, but doesn't match the node.
            stop_boot("The journal state is internally consistent, but "
                    "the journal state does not match the node and cluster "
                    "state. Journal: cluster guid %s, device id %d. Disk: "
                    "cluster guid %s, device id %d. Contact EMC Customer "
                    "Support immediately." % (jguid, jdevid, aguid, adevid))
    except Exception, e:
        # We don't expect any of this to fail so if it does, stop the boot
        # and display the error.
        stop_boot("Internal error occurred while comparing node and "
                "journal identifiers: %s. Contact EMC Customer Support "
                "immediately" % str(e))

    log_print("Journal good")

    sys.exit(0)

if __name__ == "__main__":
    init_syslog()
    try:
        main()
    finally:
        clean_syslog()
