#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# check_smartmon - script for analyze and parsing smartctl output
#
# Copyright (C) 2006  daemogorgon.net
# Copytight (C) 2010  mdevaev, Etersoft
#
# 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.


"""Package versioning
"""


import os.path
import sys
import subprocess

from optparse import OptionParser


__author__ = "fuller <fuller@daemogorgon.net>"
__version__ = "$Revision$"


# path to smartctl
_smartctlPath = "/usr/sbin/smartctl"

# application wide verbosity (can be adjusted with -v [0-3])
_verbosity = 0


def parseCmdLine(args):
	"""Commandline parsing."""

	usage = "usage: %prog [options] device"
	version = "%%prog %s" % (__version__)

	parser = OptionParser(usage=usage, version=version)
	parser.add_option("-d", "--device", action="store", dest="device", default="", metavar="DEVICE",
			help="device to check")
	parser.add_option("-v", "--verbosity", action="store",
			dest="verbosity", type="int", default=0,
			metavar="LEVEL", help="set verbosity level to LEVEL; defaults to 0 (quiet), \
					possible values go up to 3")
	parser.add_option("-w", "--warning-threshold", metavar="TEMP", action="store",
			type="int", dest="warningThreshold", default=55,
			help="set temperature warning threshold to given temperature (defaults to 55)")
	parser.add_option("-c", "--critical-threshold", metavar="TEMP", action="store",
			type="int", dest="criticalThreshold", default="60",
			help="set temperature critical threshold to given temperature (defaults to 60)")

	return parser.parse_args(args)
# end


def checkDevice(path):
	"""Check if device exists and permissions are ok.
	
	Returns:
		- 0 ok
		- 1 no such device
		- 2 no read permission given
	"""

	vprint(3, "Check if %s does exist and can be read" % path)
	if not os.access(path, os.F_OK):
		return (1, "UNKNOWN: no such device found")
	elif not os.access(path, os.R_OK):
		return (2, "UNKNOWN: no read permission given")
	else:
		return (0, "")
	# fi
# end


def checkSmartMonTools(path):
	"""Check if smartctl is available and can be executed.

	Returns:
		- 0 ok
		- 1 no such file
		- 2 cannot execute file
	"""

	vprint(3, "Check if %s does exist and can be read" % path)
	if not os.access(path, os.F_OK):
		return (1, "UNKNOWN: cannot find %s" % path)
	elif not os.access(path, os.X_OK):
		return (2, "UNKNOWN: cannot execute %s" % path)
	else:
		return (0, "")
	# fi
# end


def callSmartMonTools(path, device):
	# get health status
	cmd = "%s -H %s" % (path, device)
	vprint(3, "Get device health status: %s" % cmd)
	#(child_stdin, child_stdout, child_stderr) = os.popen3(cmd)
	proc = subprocess.Popen(cmd, shell=True, bufsize=1024, close_fds=True,
			stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
	(child_stdin, child_stdout, child_stderr) = (proc.stdin, proc.stdout, proc.stderr)
	line = child_stderr.readline()
	if len(line):
		return (3, "UNKNOWN: call exits unexpectedly (%s)" % line, "",
				"")
	healthStatusOutput = ""
	for line in child_stdout:
		healthStatusOutput = healthStatusOutput + line
	# done

	# get temperature
	cmd = "%s -A %s" % (path, device)
	vprint(3, "Get device temperature: %s" % cmd)
	#(child_stdin, child_stdout, child_stderr) = os.popen3(cmd)
	proc = subprocess.Popen(cmd, shell=True, bufsize=1024, close_fds=True,
			stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
	(child_stdin, child_stdout, child_stderr) = (proc.stdin, proc.stdout, proc.stderr)
	line = child_stderr.readline()
	if len(line):
		return (3, "UNKNOWN: call exits unexpectedly (%s)" % line, "",
				"")

	temperatureOutput = ""
	for line in child_stdout:
		temperatureOutput = temperatureOutput + line
	# done

	return (0 ,"", healthStatusOutput, temperatureOutput)
# end


def parseOutput(healthMessage, temperatureMessage):
	"""Parse smartctl output

	Returns (health status, temperature).
	"""

	# parse health status
	#
	# look for line '=== START OF READ SMART DATA SECTION ==='
	statusLine = ""
	lines = healthMessage.split("\n")
	getNext = 0
	for line in lines:
		if getNext:
			statusLine = line
			break
		elif line == "=== START OF READ SMART DATA SECTION ===":
			getNext = 1
		# fi
	# done
	parts = statusLine.split()
	healthStatus = parts[-1]
	vprint(3, "Health status: %s" % healthStatus)


	# parse temperature attribute line
	temperature = 0
	lines = temperatureMessage.split("\n")
	for line in lines:
		parts = line.split()
		if len(parts):
			# 194 is the temperature value id
			if parts[0] == "194":
				temperature = int(parts[9])
				break
			# fi
		# fi
	# done
	vprint(3, "Temperature: %d" %temperature)

	return (healthStatus, temperature)
# end


def createReturnInfo(healthStatus, temperature, warningThreshold,
		criticalThreshold):
	"""Create return information according to given thresholds."""

	# this is absolutely critical!
	if healthStatus != "PASSED":
		return (2, "CRITICAL: device does not pass health status")
	# fi

	if temperature > criticalThreshold:
		return (2, "CRITICAL: device temperature (%d) exceeds critical temperature threshold (%s)" % (temperature, criticalThreshold))
	elif temperature > warningThreshold:
		return (1, "WARNING: device temperature (%d) exceeds warning temperature threshold (%s)" % (temperature, warningThreshold))
	else:
		return (0, "OK: device is functional and stable (temperature: %d)" % temperature)
	# fi
# end


def exitWithMessage(value, message):
	"""Exit with given value and status message."""

	print(message)
	sys.exit(value)
# end


def vprint(level, message):
	"""Verbosity print.

	Decide according to the given verbosity level if the message will be
	printed to stdout.
	"""

	if level <= verbosity:
		print(message)
	# fi
# end


if __name__ == "__main__":
	(options, args) = parseCmdLine(sys.argv)
	verbosity = options.verbosity

	vprint(2, "Get device name")
	device = options.device
	vprint(1, "Device: %s" % device)

	# check if we can access 'path'
	vprint(2, "Check device")
	(value, message) = checkDevice(device)
	if value != 0:
		exitWithMessage(3, message)
	# fi

	# check if we have smartctl available
	(value, message) = checkSmartMonTools(_smartctlPath)
	if value != 0:
		exitWithMessage(3, message)
	# fi
	vprint(1, "Path to smartctl: %s" % _smartctlPath)

	# call smartctl and parse output
	vprint(2, "Call smartctl")
	(value, message, healthStatusOutput, temperatureOutput) = callSmartMonTools(_smartctlPath, device)
	if value != 0:
		exitWithMessage(value, message)
	vprint(2, "Parse smartctl output")
	(healthStatus, temperature) = parseOutput(healthStatusOutput, temperatureOutput)
	vprint(2, "Generate return information")
	(value, message) = createReturnInfo(healthStatus, temperature,
			options.warningThreshold, options.criticalThreshold)

	# exit program
	exitWithMessage(value, message)

# fi
