#!/usr/bin/python
# -*- encoding: utf-8; py-indent-offset: 4 -*-
# +------------------------------------------------------------------+
# |             ____ _               _        __  __ _  __           |
# |            / ___| |__   ___  ___| | __   |  \/  | |/ /           |
# |           | |   | '_ \ / _ \/ __| |/ /   | |\/| | ' /            |
# |           | |___| | | |  __/ (__|   <    | |  | | . \            |
# |            \____|_| |_|\___|\___|_|\_\___|_|  |_|_|\_\           |
# |                                                                  |
# | Copyright Mathias Kettner 2014             mk@mathias-kettner.de |
# +------------------------------------------------------------------+
#
# This file is part of Check_MK.
# The official homepage is at http://mathias-kettner.de/check_mk.
#
# check_mk is free software;  you can redistribute it and/or modify it
# under the  terms of the  GNU General Public License  as published by
# the Free Software Foundation in version 2.  check_mk is  distributed
# in the hope that it will be useful, but WITHOUT ANY WARRANTY;  with-
# out even the implied warranty of  MERCHANTABILITY  or  FITNESS FOR A
# PARTICULAR PURPOSE. See the  GNU General Public License for more de-
# ails.  You should have  received  a copy of the  GNU  General Public
# License along with GNU Make; see the file  COPYING.  If  not,  write
# to the Free Software Foundation, Inc., 51 Franklin St,  Fifth Floor,
# Boston, MA 02110-1301 USA.

# Example output:
# <<<fileinfo:sep(124)>>>
# 12968175080
# M:\check_mk.ini|missing
# M:\check_mk.ini|1390|12968174867
# M:\check_mk_agent.cc|86277|12968174554
# M:\Makefile|1820|12964010975
# M:\check_mk_agent.exe|102912|12968174364
# M:\crash.cc|1672|12964010975
# M:\crash.exe|20024|12968154426

# Parameters
# "minsize" : ( 5000,  4000 ),  in bytes
# "maxsize" : ( 8000,  9000 ),  in bytes
# "minage"  : ( 600,  1200 ),  in seconds
# "maxage"  : ( 6000, 12000 ), in seconds
fileinfo_groups = []

def inventory_fileinfo(info, case):
    inventory = []
    added_groups = []
    if info:
        reftime = int(info[0][0])
    for line in info:
        if len(line) >= 3:
            groups = fileinfo_groups_of_file(line[0], reftime)
            if case == 'single' and not groups and line[1] != 'missing':
                inventory.append((line[0], {}));

            if case == 'group' and groups:
                for group in groups:
                    if group not in added_groups:
                        added_groups.append(group)
                        inventory.append((group, {}))
    return inventory

def fileinfo_process_date(pattern,reftime):
    disect = re.match('(/.*/)\$DATE:((?:%\w.?){1,})\$(.*)',pattern)
    if disect:
        prefix = disect.group(1)
        datepattern =  time.strftime(disect.group(2),time.localtime(reftime))
        postfix = disect.group(3)
        pattern = prefix+datepattern+postfix
    return pattern

def fileinfo_groups_of_file(check_filename,reftime):
    import fnmatch
    groups = []
    for line in host_extra_conf(g_hostname, fileinfo_groups):
        for group_name, pattern in line:
            if type(pattern) == str: # support old format
                pattern = ( pattern, '' )
            inclusion, exclusion = pattern
            inclusion = fileinfo_process_date(inclusion,reftime)
            if fnmatch.fnmatch(check_filename, inclusion) \
                    and not fnmatch.fnmatch(check_filename, exclusion):
                groups.append(group_name)
    return groups

def check_fileinfo(item, params, info):
    if len(info) == 0:
        return (3, "no information sent by agent")

    reftime = int(info[0][0])
    check_definition = False
    for line in info[1:]:
        if item == line[0]:
            if line[1] == "missing":
                return (3, "File not found")
            state = 0
            size = int(line[1])
            age = reftime - int(line[2])

            check_definition = [
                ("size", size, get_filesize_human_readable),
                ("age",  age,  get_age_human_readable) ]
    if check_definition == False:
        return (3, "File not found")
    return fileinfo_check_function(check_definition, params)

# Extracts patterns that are relevant for the current host and item.
# Constructs simple list of patterns and makes them available for the check
def fileinfo_groups_precompile(hostname, item, params):
    patterns = []
    for line in host_extra_conf(hostname, fileinfo_groups):
        for group_name, pattern in line:
            if group_name == item:
                if type(pattern) == str: # support old format
                    pattern = (pattern, '')
                patterns.append(pattern)
    params['precompiled_patterns'] = patterns
    return params

def check_fileinfo_groups(item, params, info):
    if not info:
        return 3, "No information sent by agent"
    import fnmatch
    reftime = int(info[0][0])

    # Get the grouping patterns (either compile or reuse the precompiled ones)
    # Check_MK creates an empty string if the precompile function has
    # not been exectued yet. The precompile function creates an empty
    # list when no rules/patterns are defined.
    if 'precompiled_patterns' not in params:
        params = fileinfo_groups_precompile(g_hostname, item, params)

    count_all = 0
    age_oldest = None
    age_newest = 0
    size_all = 0
    size_smallest = None
    size_largest  = 0
    date_inclusion = ""
    # Start counting values on all files
    for line in info[1:]:
        for pattern in params['precompiled_patterns']:
            inclusion, exclusion = pattern
            inclusion_tmp = fileinfo_process_date(inclusion,reftime)
            if inclusion != inclusion_tmp:
                inclusion = inclusion_tmp
                date_inclusion = inclusion_tmp
            # endswith("No such file...") is needed to
            # support the solaris perl based version of fileinfo
            if not line[0].endswith("No such file or directory") \
                        and fnmatch.fnmatch(line[0], inclusion) and str(line[1]) not in ['missing',''] \
                        and not fnmatch.fnmatch(line[0], exclusion):
                size = int(line[1])
                size_all += size
                if size_smallest == None:
                    size_smallest = size
                else:
                    size_smallest = min(size_smallest, size)
                size_largest = max(size_largest, size)

                age = reftime - int(line[2])
                if age_oldest == None: # very first match
                    age_oldest = age
                    age_newest = age
                else:
                    age_oldest = max(age_oldest, age)
                    age_newest = min(age_newest, age)
                count_all += 1

    if age_oldest == None:
        age_oldest = 0

    # Start Checking
    check_definition = [
        ("age_oldest",    age_oldest,    get_age_human_readable),
        ("age_newest",    age_newest,    get_age_human_readable),
        ("count",         count_all,     saveint),
        ("size",          size_all,      get_filesize_human_readable),
    ]

    if size_smallest is not None:
        check_definition.append(("size_smallest", size_smallest, get_filesize_human_readable))
    if size_largest != 0:
        check_definition.append(("size_largest",  size_largest,  get_filesize_human_readable))
    if date_inclusion:
        check_definition.append(("date pattern",  date_inclusion, str ))

    return fileinfo_check_function(check_definition, params)

def fileinfo_check_function(check_definition, params):
    import operator
    state = 0
    infos = []
    perfdata = []
    for what, val, verbfunc in check_definition:
        infos.append("%s is %s" % (what, verbfunc(val)))
        if type(val) in [long, int]: # because strings go into infos but not into perfdata
            warn, crit = "", ""
            for how, comp, cfunc in [ ("min", "<", operator.lt), ("max", ">", operator.gt) ]:
                p = params.get(how + what)
                if p:
                    warn, crit = p
                    if cfunc(val, crit):
                        state = 2
                        infos[-1] += " (%s %s)(!!)" % (comp, verbfunc(crit))
                    elif cfunc(val, warn):
                        state = max(state, 1)
                        infos[-1] += " (%s %s)(!)" % (comp, verbfunc(warn))
            perfdata.append((what, val, warn, crit))
    infotext = ", ".join(infos)
    return (state, infotext, perfdata)


check_info["fileinfo"] = {
    "check_function"          : check_fileinfo,
    "inventory_function"      : lambda info: inventory_fileinfo(info, 'single'),
    "service_description"     : "File %s",
    "has_perfdata"            : True,
    "group"                   : "fileinfo",
}


check_info['fileinfo.groups'] = {
    "check_function"          : check_fileinfo_groups,
    "inventory_function"      : lambda info: inventory_fileinfo(info, 'group'),
    "service_description"     : "File group %s",
    "has_perfdata"            : True,
    "group"                   : "fileinfo-groups",
}

precompile_params['fileinfo.groups'] = fileinfo_groups_precompile
