A smart S.M.A.R.T. check
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

110 lines
4.1 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. from argparse import ArgumentParser
  4. import os
  5. import shlex
  6. import sys
  7. import logging
  8. import subprocess
  9. from smartcheck.check import SMARTCheck, AttributeWarning, DEFAULT_DISKS_FILE
  10. def execute_smartctl(drive, interface=None, sudo=None, smartctl_path=None, smartctl_args=''):
  11. command_line = "%s %s %s %s -a %s" % (
  12. "sudo" if sudo else '',
  13. smartctl_path,
  14. '-d %s' % interface if interface else '',
  15. smartctl_args,
  16. drive
  17. )
  18. logging.debug("Executing smartctl command: %s" % command_line)
  19. cmd = shlex.split(command_line)
  20. process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env={'LC_ALL': 'C'})
  21. output = process.communicate()[0]
  22. # TODO: See if we can get hints from the smartctl exit codes:
  23. # https://www.freebsd.org/cgi/man.cgi?query=smartctl&manpath=FreeBSD+9.0-RELEASE+and+Ports&format=html#RETURN_VALUES
  24. # at least we should not handle if the drive is in low-power mode (spindown)
  25. # if process.returncode:
  26. # raise Exception("smartctl failed with status code %s" % process.returncode)
  27. return output
  28. if __name__ == "__main__":
  29. parser = ArgumentParser()
  30. parser.add_argument('--disks-file', default=DEFAULT_DISKS_FILE)
  31. parser.add_argument('-s', '--sudo', help="Use sudo to execute smartctl", action='store_true', default=False)
  32. parser.add_argument('--smartctl-path', default="/usr/sbin/smartctl", help='Path to smartctl (default: %(default)s)')
  33. parser.add_argument('-a', '--smartctl-args', default='-n standby', help="Other arguments passed to smartctl (default: %(default)s)")
  34. parser.add_argument('-i', '--interface', help="The smartctl interface specification (passed to smartctl's -d parameter")
  35. parser.add_argument('drive', type=str, nargs='?', help="The device as passed to smartctl's positional argument")
  36. parser.add_argument('-f', '--file', help="Use S.M.A.R.T. report from file instead of calling smartctl (Use - to read from stdin)")
  37. parser.add_argument('-x', '--exclude-notices', help='Do not report NOTICE warnings (default: %(default)s)', action='store_true', default=False)
  38. parser.add_argument('--ignore-attributes', help='Ignore this S.M.A.R.T. attributes (id or name)', nargs='*')
  39. parser.add_argument('-v', '--verbose', help='Verbose messages', action='store_true', default=False)
  40. parser.add_argument('--debug', help="Print debug messages", action="store_true", default=False)
  41. args = parser.parse_args()
  42. if args.file and any([args.interface, args.drive]):
  43. parser.error('-f/--file cannot be used with a device and/or -i/--interface')
  44. if args.debug:
  45. logging.basicConfig(level=logging.DEBUG, format='%(levelname)-8s %(message)s')
  46. exit_code = 0
  47. msg = ""
  48. try:
  49. stream = None
  50. if args.file:
  51. if args.file == '-':
  52. stream = sys.stdin
  53. else:
  54. stream = open(args.file, 'r')
  55. else:
  56. stream = execute_smartctl(args.drive, args.interface, args.sudo, args.smartctl_path, args.smartctl_args)
  57. check = SMARTCheck(stream, args.disks_file)
  58. if check.data_parsed:
  59. attribute_errors = check.check_attributes()
  60. if args.exclude_notices:
  61. for k in [x for x, y in attribute_errors.items() if y.level == AttributeWarning.Notice]:
  62. del attribute_errors[k]
  63. if attribute_errors:
  64. msg = ', '.join([ae.long_message if args.verbose else ae.short_message for ae in attribute_errors.values()])
  65. if any((ae.level == AttributeWarning.Warning for ae in attribute_errors.values())):
  66. exit_code = 1
  67. if any((ae.level == AttributeWarning.Critical for ae in attribute_errors.values())):
  68. exit_code = 2
  69. if not check.check_tests():
  70. msg = (msg.strip() + '; S.M.A.R.T. self test reported an error').lstrip(';').strip()
  71. exit_code = 2
  72. if check.ata_error_count:
  73. msg = (msg.strip() + '; %s ATA errors found' % check.ata_error_count).lstrip(';').strip()
  74. exit_code = 2
  75. if not exit_code:
  76. msg = "S.M.A.R.T. data OK"
  77. msg = "%s: %s" % (check.device_model, msg)
  78. else:
  79. msg = "Could not read S.M.A.R.T. data (executed as root?)"
  80. exit_code = 3
  81. except Exception as ex:
  82. msg = "Plugin failed: %s" % ex
  83. if args.debug:
  84. logging.exception("Plugin failed")
  85. exit_code = 3
  86. print(msg)
  87. sys.exit(exit_code)