#!/usr/bin/python3 -s
# -*- coding: utf-8 -*-
"""
    SUSE Driver Tools: sdt command
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    Tool for working with driver updates (kernel modules) with SUSE
    Linux Enterprise OS products. The tools can build update media (DUD),
    kISOs, Driver Kits as well as prepare initrd images with updated
    kernel drivers.


    :copyright: Copyright (c) 2012-2020 Scott Bahling,
                SUSE Software Solutions Germany GmbH
        This program is free software; you can redistribute it and/or modify
        it under the terms of the GNU General Public License version 2 as
        published by the Free Software Foundation.

        This program is distributed in the hope that it will be useful,
        but WITHOUT ANY WARRANTY; without even the implied warranty of
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        GNU General Public License for more details.

        You should have received a copy of the GNU General Public License
        along with this program (see the file COPYING); if not, write to the
        Free Software Foundation, Inc.,
        51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
    :license: GPL-2.0, see COPYING for details
    :note: This code is loosely based on "Build SUSE Kernel ISO" by
           Tejun Heo.


    Usage:
        sdt [options] <command> [<args>...]

        Commands:
          dud
          initrd
          media
          kmodule
          obs

        Options:
          -h, --help                         Show this screen.
          --version                          Show version.
          -d, --debug                        Enable debuging output
          -L FILE, --log  FILE               Log file.
          -C FILE, --configfile FILE         Config File
"""

import sys
import signal
import os
import logging
import traceback
from datetime import datetime
from docopt import docopt

import SUSEDriverTools.utils as utils
from SUSEDriverTools.workpath import workpath
from SUSEDriverTools import __version__
from SUSEDriverTools.config import Config, config
import SUSEDriverTools


CONF_FILE_NAME = 'sdt.conf'
USER_CONFIG_DIR = os.environ.get('XDG_CONFIG_HOME',
                                 os.path.expanduser('~/.config/sdt'))
SYS_CONFIG_DIRS = os.environ.get('XDG_CONFIG_DIRS', '/etc/xdg').split(':')

# XDG_CONFIG_DIRS are in order of preference with early paths taking config
# precidence over latter paths. We reverse this order since our logic is
# to override previous settings with settings from latter config files
SYS_CONFIG_DIRS.reverse()

MOVE_LOGFILE = False


def killme(sig, fname):
    '''Used for SIGINT or SIGTERM signals to make sure we call cleanup.
    Simply exiting using sys.exit() doesn't work cleanly with running
    sh.py commands, therefore we kill ourselves instead.'''

    logger.info("Ouch! Hey! Exiting...")
    now = datetime.strftime(datetime.now(), '%Y-%m-%dT%H:%M:%S')
    logger.info('*******************************************************************')
    logger.info('*** sdt command killed at: %s' % now)
    logger.info('*******************************************************************')
    cleanup()
    os.kill(os.getpid(), 9)


def load_configs(args, config):
    subargs = args.get('<args>', [])
    try:
        update_dir = subargs[subargs.index('--updatedir') + 1]
    except Exception:
        update_dir = './updates'
    config_locations = SYS_CONFIG_DIRS + [USER_CONFIG_DIR, update_dir, './']

    # load configs from standard locations
    # sdt-config.yml is an old standard we still support
    for path in config_locations:
        for fname in ('sdt-config.yml', CONF_FILE_NAME):
            conf_file = os.path.abspath(os.path.join(path, fname))
            logger.debug('Checking for %s' % conf_file)
            if not os.path.isfile(conf_file):
                continue
            logger.debug("Loading config file: %s" % conf_file)
            config.load_configfile(conf_file)

    # load config file passed on command line
    configfile = args.get('--configfile')
    if configfile and os.path.isfile(configfile):
        config.load_configfile(configfile)


def init_user_config():
    conf_file = os.path.join(USER_CONFIG_DIR, CONF_FILE_NAME)
    if os.path.exists(USER_CONFIG_DIR) and not utils.check_dir(USER_CONFIG_DIR, 'rw'):
        logger.debug('Unable to access user config directory at %s' % USER_CONFIG_DIR)
        return
    if os.path.exists(conf_file):
        return
    try:
        utils.makedirs(USER_CONFIG_DIR)
        with open(conf_file, 'w') as f:
            f.write('# SUSE Driver Tools Config File')
    except Exception:
        logger.error('Failed to create user config file at %s') % conf_file

    return


def init_logger(args):
    '''Initialize logger

    The logger initialization is required before we officially
    read in the user configuration options because the config
    parsing code needs the logger.
    Therefore the logger init needs to do a bit of configuration
    parsing first to get the loglevel and logfile specifications.

    Command line settings take precedence over config files passed
    by command line which take precedence over the user's global
    configfile.

    :param args:
      command line arguments
    '''

    config = Config()
    load_configs(args, config)

    debug = args.get('--debug') or config.debug_enabled
    if debug:
        loglevel = logging.DEBUG
    else:
        loglevel = logging.INFO

    logger.setLevel(loglevel)

    # create formatter and add it to the handlers
    formatter = logging.Formatter('%(name)s:%(levelname)s: %(message)s')

    # create console handler
    ch = logging.StreamHandler()
    ch.setLevel(loglevel)
    ch.setFormatter(formatter)
    logger.addHandler(ch)

    # Log to file?
    logfile = args.get('--log') or config.logfile
    if logfile:
        path, fname = os.path.split(logfile)
        if fname == '__AUTO__':
            now = datetime.strftime(datetime.now(), '%Y-%m-%dT%H:%M:%S')
            logfile = os.path.join(path, 'sdt-%s.log' % now)
            global MOVE_LOGFILE
            MOVE_LOGFILE = logfile

        # create file handler which logs eve. debug messages
        if not utils.check_dir(os.path.dirname(logfile)):
            logger.error('Unable to create log file: %s' % logfile)
        fh = logging.FileHandler(logfile)
        fh.setLevel(loglevel)
        fh.setFormatter(formatter)
        logger.addHandler(fh)

    del(config)

    return loglevel


def create_log_header():
    cmdline = os.path.split(sys.argv[0])[1]
    cmdline = '%s %s' % (cmdline, ' '.join(sys.argv[1:]))
    loglevel = logger.getEffectiveLevel()
    now = datetime.strftime(datetime.now(), '%Y-%m-%dT%H:%M:%S')
    logger.info('*******************************************************************')
    logger.info('*** SUSE Driver Tools version: %s' % __version__)
    logger.info('***        Logging started at: %s' % now)
    logger.info('***                  LogLevel: %s' % loglevel)
    logger.info('*******************************************************************')
    logger.debug('*** sdt path: %s' % sys.argv[0])
    logger.info('*** Command Line:')
    logger.info('*** %s' % cmdline)
    logger.info('*******************************************************************')
    logger.info('')

    return


def closedown_logger(args):
    newlog = None
    if MOVE_LOGFILE and config.auto_logfile_name:
        path, fname = os.path.split(MOVE_LOGFILE)
        newlog = os.path.join(path, config.auto_logfile_name)
        logger.info('Log file will be moved from %s to %s' % (MOVE_LOGFILE, newlog))

    for handler in logger.handlers:
        logger.removeHandler(handler)

    if newlog:
        utils.move(MOVE_LOGFILE, newlog)

    return


def cleanup():
    # Clean up
    logger.info('Cleaning house...')
    utils.umount_all()
    if workpath.root:
        workpath.cleanup()
    logger.info('Close down logger...')
    closedown_logger(args)

    return


#
# It all starts here...
#
if __name__ == "__main__":
    signal.signal(signal.SIGTERM, killme)
    signal.signal(signal.SIGINT, killme)

    args = docopt(__doc__, options_first=True)

    # Setup logger
    logger = ''
    logger = logging.getLogger('SUSE Driver Tools')
    init_logger(args)

    if '--help' not in args['<args>']:
        create_log_header()

    init_user_config()
    load_configs(args, config)
    SUSEDriverTools.global_config = config

    logger.debug("Command Line Args: %s" % args)
    logger.debug("Importing tools...")
    import SUSEDriverTools.toolkit
    commands = (SUSEDriverTools.commands)
    logger.debug("Available commands: %s" % commands)

    command = args.get('<command>', '')

    # Is command valid?
    if command not in commands:
        docopt(__doc__, argv=['--help'])

    # Remove base arguments before passing to command
    cmdpos = sys.argv.index(command)
    sys.argv = sys.argv[0:1] + sys.argv[cmdpos:]

    # kickoff sdt command...
    try:
        logger.debug('Executing command: %s' % command)
        logger.debug(commands.get(command))
        commands.get(command, exit)()
    except Exception as e:
        logger.error(e)
        tb = traceback.format_exc()
        logger.debug(tb)
        now = datetime.strftime(datetime.now(), '%Y-%m-%dT%H:%M:%S')
        logger.info('*******************************************************************')
        logger.info('*** sdt command aborted at: %s' % now)
        logger.info('*******************************************************************')
        MOVE_LOGFILE = False
        cleanup()
        exit(2)

    now = datetime.strftime(datetime.now(), '%Y-%m-%dT%H:%M:%S')
    logger.info('*******************************************************************')
    logger.info('*** sdt command finished at: %s' % now)
    logger.info('*******************************************************************')

    cleanup()
    # Exit
    sys.exit(0)
