#!/usr/bin/env python

# Copyright C 2002-2005 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 ctypes
import sys
import getopt
import os
import logging
import traceback

import isi.app.lib.procs as procs
import isi.hw.bay as bay
import isi.hw.imdd.devices as devices
import isi.hw.imdd.log as imddlog
import isi.hw.imdd.report as imddreport
import isi.misc as misc
import isi.sys.bootdisk as bootdisk
import isi.sys.disk as disk

class IsiPrepDisk:
    def __init__(self):
        self.drives = []
        self.log_file = None
        self.verbose = False
        self.is_geom = None
        self.force_imdd = False

    def usage(self):
        print "%s: [Options] <drive0> [<drive1>[<drive2>]..]" % sys.argv[0]
        print "\tWhere each <drive> can be specified as:"
        print "\t/dev/daN, daN, portN, pN, unitN, uN, lnumN, " \
                "lN, bayN, bayXY, N[bay], XY[bay]"
        print "Options:"
        print "\t-g\tDebug verbose output mode"
        print "\t-h,-?\tdisplays this message"
        sys.exit(1)

    def process_args(self):
        failure = 0

        try:
            opts, args = getopt.getopt(sys.argv[1:], 'gh?', ["force_imdd"])
        except getopt.GetoptError:
            self.usage()

        for o, a in opts:
            if o in ['-g']:
                self.verbose = True
            elif o in ['-h','-?']:
                self.usage()
            elif o in ['--force_imdd']:
                self.force_imdd = True
            else:
                self.usage()

        if len(args) == 0:
            self.usage()

        # grab device paths 
        for drive_str in args:
            b = bay.any2bay(drive_str)
            if (b < 0):
                imddreport.report("error",
                        "Error: unknown bay for device %s, errno %d" %
                        (drive_str, ctypes.get_errno()))
            dev = bay.bay2dev(b)
            if (dev):
                dev = dev.strip()
                if (dev[0:4] != "/dev"):
                    dev = "/dev/" + dev
            if not dev or not os.path.exists(dev):
                imddreport.report("error",
                            "Error: device %s (%d:%s) does not exist" %
                            (drive_str, b, dev))
                failure = 1
            self.drives.append(dev)

        if failure:
            sys.exit(1)
            
    def start_logging(self, log_file = "/var/log/isi_drive_d.log"):
        self.log_file = log_file
        hdlr = logging.FileHandler(self.log_file)
        formatter = logging.Formatter('%(asctime)s %(process)d %(message)s')
        imddreport.report_hash["popen"] = 1
        hdlr.setFormatter(formatter)
        imddlog.log_start(self.verbose, hdlr)

    def main(self):
        try:
            self.is_geom = bootdisk.future_system_is_geom()

            self.start_logging()
        
            self.process_args()

            # log the parent pid to make the logs easier to follow
            imddreport.report("status", 
                "parent %s isi_prep_disk %s" % (os.getppid(), self.drives))

            if self.is_geom and not self.force_imdd:
                imddreport.report("status", 
                                  "Prepping gpt/gmirror drives %s" % (self.drives))
                for dev in self.drives:
                    ret = disk.prep_disk(os.path.split(dev)[1])
                if (len(self.drives) == 1):
                    sys.exit(ret)
                sys.exit(0)

            # Check that the imdd.ko module is loaded and load it if not.
            if self.is_geom:
                cmd = "/sbin/kldstat -m imdd || /sbin/kldload imdd.ko"
                (err, out) = procs.get_cmd_output(cmd)
                if err:
                    imddreport.report("error", "Error: cannot kldload imdd.ko")
                    sys.exit(1)

            imddreport.report("status", "Prepping IMDD drives %s" % (self.drives))
        
            # populates globals such as the uninit_drives, ...
            devices.mount_list_build()
            # Don't do boot-from-/root-on-imdd0 check, panics/aborts if fails.
            # Allows CF boots to continue with fixing disks.
            devices.device_lists_init(check_imdd0_root=False)

            # Don't repair all devices if not forcing.
            if self.force_imdd:
                devices.device_lists_build(ignore_gpt=True)
                devices.repair(do_imdds = False)

            for dev in self.drives:
                # Add to devices.uninit_drives
                # Get the chassis number
                chas = 0
                hd_drives = misc.hd_drives()
                for hd in hd_drives:
                    if "/dev/" + hd[0] == dev:
                        chas = hd[1]
                # Create the drive
                devices.Drive(dev, chasn = chas)

                repaired = False

                # Repair the MBR if the drive wound up on the list of uninitialized drives
                drv = devices.uninit_drives.lookup(dev)
                if drv:
                    imddreport.report("status", "%s: repairing disk." % dev)
                    drv.repair()
                    repaired = True

                # Repair the slice if the drive wound up on the list of uninitialized drives
                slc = devices.uninit_slices.lookup(dev + "s1")
                if slc:
                    imddreport.report("status", "%s: repairing slice." % dev)
                    slc.repair()
                    repaired = True

                # Update boot code
                slc = devices.slices.lookup(dev + "s1")
                if slc == None:
                    imddreport.report("error",
                            "Error: slice %s is not initialized" % (dev + "s1"))
                    sys.exit(1)
                if slc.fix_boot():
                    repaired = True

                if not repaired:
                    imddreport.report("error",
                            "Warning: No repairs made to " + dev)

            imddreport.report("status", "Done.")
        # Ignore exceptions from sys.exit() calls
        except SystemExit as e:
            sys.exit(e)
        except:
            etype, evalue, etb = sys.exc_info()
            lines = traceback.format_exception(etype, evalue, etb)
            for line in lines:
                imddreport.report("error", line.strip())
            raise

if __name__ == "__main__":
    devices.log_start(log_cons=False, handler=logging.StreamHandler(sys.stdout))
    IsiPrepDisk().main()
    sys.exit(0)

# End of Code, old comments/notes follow

# 
# Create Drive object for each new drive.  A bunch of stuff
# happens in the Drive() constructor without proper error
# reporting back to us.  Yay.
#
# Inside the Drive() constructor:
# - Drive.probe() creates a Slice()
#   - Slice.probe() checks disklabel
#       - Creates Partition()s p0, p1, ...
#         - Partition() calls p.probe(), gets imddvars
#            - If any is idead, mark all in slice idead         
#            - Call p.assign()
#            - mount_lookup_by_device
#               * depends on mount_list_build()
#            - if None, imdds.lookup, if ! None, imdd.addpart()
#               - if imdd.attached: if p.labled then
#                 imdd.merge(p), else imdd.add(p)
#               * depends on imdd and imdds instances
#            - imdds.lookup should be None, so calls p.clear():
#               - p.label(), uninit_partitions.append(p)
# - if Drive.probe() succeeded:
# - if not self.dongle and not dongle_load then
#   uninit_drives.append()
#   else self.repair()
#
