#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
# conditions defined in the file COPYING, which is part of this source code package.

# See https://utcc.utoronto.ca/~cks/space/blog/linux/NFSStaleUnmounting
# Output changes from
# knvmsapprd:/transreorg/sap/trans /transreorg/sap/trans nfs4 rw,relatime,vers=4.0,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=172.24.98.63,local_lock=none,addr=172.24.98.57 0 0
# to
# knvmsapprd:/transreorg/sap/trans /transreorg/sap/trans\040(deleted) nfs4 rw,relatime,vers=4.0,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=172.24.98.63,local_lock=none,addr=172.24.98.57 0 0

#
# (mo): This plugin is a good example for plugins that would massively benefit from a
#       section definition (parse_function).
#       However, I need an exapmle for a plugin without one, so for now I keep it this way.
#


def discovery_mounts(info):
    devices = []
    for dev, mountpoint, fstype, options, _dump, _fsck in info:
        if fstype == "tmpfs":
            continue

        if mountpoint in ["/etc/resolv.conf", "/etc/hostname", "/etc/hosts"]:
            continue

        if dev not in devices:
            devices.append(dev)
            opts = sorted(options.split(","))
            yield mountpoint.replace("\\040(deleted)", ""), opts


def _should_ignore_option(option):
    for ignored_option in ["commit=", "localalloc=", "subvol=", "subvolid="]:
        if option.startswith(ignored_option):
            return True
    return False


def check_mounts(item, targetopts, info):
    # Ignore options that are allowed to change
    for _dev, mountpoint, _fstype, options, _dump, _fsck in info:
        if item == mountpoint.replace("\\040(deleted)", ""):
            if mountpoint.endswith("\\040(deleted)"):
                yield 1, "Mount point detected as stale"
                return

            opts = options.split(",")
            # Now compute the exact difference.

            exceeding = [
                opt for opt in opts if opt not in targetopts and not _should_ignore_option(opt)
            ]

            missing = [
                opt for opt in targetopts if opt not in opts and not _should_ignore_option(opt)
            ]

            if not missing and not exceeding:
                yield 0, "Mount options exactly as expected"
                return

            infos = []
            if missing:
                infos.append("Missing: %s" % ",".join(missing))
            if exceeding:
                infos.append("Exceeding: %s" % ",".join(exceeding))
            infotext = ", ".join(infos)

            yield 1, infotext
            if "ro" in exceeding:
                yield 2, "Filesystem has switched to read-only and is probably corrupted"
            return


check_info["mounts"] = {
    "check_function": check_mounts,
    "inventory_function": discovery_mounts,
    "service_description": "Mount options of %s",
    "group": "fs_mount_options",
}
