#!/usr/bin/env python3
#
#   This program 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; either version 2 of the License, or
#   (at your option) any later version.
#
#   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; if not, write to the Free Software Foundation, Inc.,
#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.


import os
import re
import sys

try:
    import cPickle as pickle
except ImportError:
    import pickle

######################################################################################################
# HELPERS
######################################################################################################

def get_status_data(statusfile):
    data = {}
    data["timestamp"] = -1
    if os.path.exists(statusfile):
        mtime_statusfile = os.stat(statusfile).st_mtime
        mtime_kmsg = os.stat("/dev/kmsg").st_mtime
        if not mtime_statusfile < mtime_kmsg:
           try:
               data = pickle.load(open(statusfile, "rb"))
           except:
               sys.stderr.write("ERROR: unable to read statusfile %s" % statusfile)
    return data


######################################################################################################
# MAIN
######################################################################################################

EXECUTABLE = os.path.basename(__file__)

dmesg = os.popen('dmesg')

# kernel problems
kernel_problems = {}
kernel_problems["general protection fault"] = re.compile("^.*general protection fault.*$")
kernel_problems["bug"] = re.compile(".*(kernel BUG at|double fault:|Badness at|[Uu]nable to handle kernel|" +
                                    "sysctl table check failed|------------[ cut here ]------------|Oops:).*$")
kernel_problems["oom"] = re.compile(".*(Out of memory: Kill process).*")
kernel_problems["userland_kill"] = re.compile(".*killed by.*")
kernel_problems["ioerror"] = re.compile(r".*(print_req_error: I/O error, dev ..*|end_request: I/O error, dev ..*, sector ..*|error on device ..*, logical block ..*|Buffer I/O error on dev).*")

# [80508.690871] kauditd_printk_skb: 2 callbacks suppressed
parse_dmesg = re.compile(r"\[\s*(?P<timestamp>\d+\.\d+)\] (?P<msg>.*)$")

if os.path.exists('/var/run/zabbix'):
    status_file = "/var/run/zabbix/%s_%s.pickle" % (EXECUTABLE, os.getuid())
else:
    status_file = "/tmp/%s_%s.pickle" % (EXECUTABLE, os.getuid())

status_data = get_status_data(status_file)

latest_time_stamp = status_data["timestamp"]

status = "OK"
matched = []

total_lines = 0
valid_lines = 0
while True:
    line_str = dmesg.readline()
    if not (line_str):
        break

    total_lines += 1
    m = parse_dmesg.match(line_str)
    if not m:
        continue

    valid_lines += 1
    current_time_stamp = float(m.group("timestamp"))
    msg = m.group("msg")

    if current_time_stamp > latest_time_stamp:
        latest_time_stamp = current_time_stamp
        for ident, regex in kernel_problems.items():
            if regex.match(msg):
                status = "ERROR"
                #sys.stderr.write("%s : %s : %s\n" % (EXECUTABLE, ident, line_str))
                matched.append("%s/%s" % (ident, current_time_stamp))

status_data["timestamp"] = latest_time_stamp

if len(matched) == 0 and valid_lines > 0:
    matched.append("ALL OK (%s)" % latest_time_stamp)

if total_lines == 0:
    status = "OK"
    matched = []
    matched.append("dmesg was cleared / it is completely empty")
elif valid_lines <= 0:
    status = "ERROR"
    matched = []
    matched.append("parsing failed")

try:
    pickle.dump(status_data, open(status_file, "wb"))
except:
    status="ERROR"
    matched.append("but storing status to %s failed" % status_file)

sys.stdout.write("%s: %s\n" % (status, ", ".join(matched)))
