#!/usr/bin/env python

# Copyright C 2007 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.

'''
Check configuration files for syntax errors.

Only checks xml files in /etc/mcp (and subdirs) for now.
'''

from popen2 import Popen3
import sys
from getopt import getopt, GetoptError
import os

DEFAULT_CHECK_PATH = '/etc/mcp'

class BadSyntax(object):
    def __init__(self, description, filename=None):
        self.description = description
        self.filename = filename

    def __str__(self):
        if self.filename:
            return '%s: %s' % (self.filename, self.description)
        else:
            return self.description

def stripNewline(line):
    'Strip a single newline from the end of line, if it has one.'
    if line.endswith('\n'):
        return line[:-1]
    return line

# SYNTAX CHECKERS
def checkXmlFiles(filenames):
    '''
    Checks the given file or list of files using the xmlwf program. Returns a
    list of errors. Supports a list because we can speed things up by checking
    more than one file at a time.
    '''
    if type(filenames) != list:
        filenames = [filenames]

    errors = []
    for filename in filenames:
        # Note that xmlwf does not return a useful exit code
        xmlwfProc = Popen3('xmlwf "%s"' % filename)
        result = ''.join(xmlwfProc.fromchild.readlines())
        result = stripNewline(result)
        if result:
            # We don't include the filename in the BadSyntax object because
            # the xmlwf output already includes it.
            errors.append(BadSyntax(result))

    return errors

def checkXml(path):
    if os.path.isfile(path):
        files = [path]
    else:
        findProc = Popen3('find "%s" -name "*.xml"' % path)
        lines = findProc.fromchild.readlines()
        files = [stripNewline(line) for line in lines]

    errors = checkXmlFiles(files)

    return errors

# REPORTING METHODS
def reportStderr(error):
    'Write the error to stderr. Useful when run interactively.'
    print >> sys.stderr, str(error)

def reportSyslog(error):
    import syslog
    syslog.openlog('isi_check_syntax', 0, syslog.LOG_IFS)
    syslog.syslog(syslog.LOG_ERR, str(error))

def reportAlert(error):
    '''
    Not implemented. Reporting via alerts seems problematic and potentially
    complex. For one thing, syntax errors in xml files could prevent alerts
    from working properly, so there are cases where this would definitely
    not work.

    It's probably best for this script to be as stand-alone as possible.
    '''
    raise NotImplementedError()

def usage(errorMsg=None):
    if errorMsg:
        print errorMsg
    print 'Usage: isi_check_syntax [--print] [--log]'
    print ''
    print '  --print    write error messages to stderr (defualt if neither'
    print '             --print nor --log are given)'
    print '  --log      write error messages using syslog (usually to'
    print '             /var/log/messages)'
    print '  --path=<file or directory>'
    print '             Checks the given file or directory, instead of'
    print '             %s' % DEFAULT_CHECK_PATH

def main(argv):
    # Option handling
    try:
        (options, args) = getopt(argv, '', [ 'log', 'print', 'path=' ])
    except GetoptError, e:
        usage(str(e))
        return 2

    if args:
        # I don't take any arguments!
        argsString = ' '.join(args)
        usage('invalid argument(s): %s' % argsString)
        return 2

    logErrors = printErrors = False
    checkPath = DEFAULT_CHECK_PATH
    for option, value in options:
        if option == '--log':
            logErrors = True
        elif option == '--print':
            printErrors = True
        elif option == '--path':
            checkPath = value
    if not logErrors and not printErrors:
        # No reporting method specified? Default to print.
        printErrors = True

    # Syntax checking
    errors = []
    errors.extend(checkXml(checkPath))
    # Add additional syntax checks here
    # errors.extend(checkFooBar()), etc.

    # Error reporting
    for error in errors:
        if printErrors:
            reportStderr(str(error))
        if logErrors:
            reportSyslog(str(error))

    if errors:
        return 1
    else:
        return 0

if __name__ == '__main__':
    sys.exit(main(sys.argv[1:]))
