S.M.A.R.T. drive checking has never been easier
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 

346 строки
11 KiB

#!/usr/bin/python3
from argparse import ArgumentParser
from os import listdir, getuid
from shutil import copyfile, move
from subprocess import TimeoutExpired, Popen
from urllib.request import urlretrieve
from devices import Device
__version__ = '6.3.1'
__versionDate__ = '2020-04-10'
supported = ('sd', 'mmcblk', 'sr', 'vd', 'nvme')
cols = [('Vendor / Model', 26), #25
('Serial', 21), #16
('Firmware', 10), #7
('Size', 9), #7
('Runtime', 12), #PoH 6
('Written', 10),
('Rpm', 6), # 5
('Life', 6), # 5
('S.M.A.R.T.', 10)]
alternatives = { 'model': ['/sys/block/{}/device/model', '/sys/block/{}/device/name'],
'serial': ['/sys/block/{}/device/serial'],
'firmware': ['/sys/block/{}/device/rev', '/sys/block/{}/device/fwrev'],
'size': ['/sys/block/{}/size'],
'vendor': ['/sys/block/{}/device/vendor']}
hdparmRex = { 'model': b'\sModel=([\w\s\-]*)[\,\n]',
'firmware': b'\s*FwRev=([\w\s\-\.]*)[\,\n]',
'serial': b'\s*SerialNo=([\w\s\-]*)[\,\n]'}
updateSmartUrl = 'https://raw.githubusercontent.com/mirror/smartmontools/master/drivedb.h'
parser = ArgumentParser(description='List all conntected drives and monitore the S.M.A.R.T.-status', epilog='Baba {} ({}) by Schluggi'.format(__version__, __versionDate__))
parser.add_argument('device', help='only show specific device', nargs='?')
parser.add_argument('-m', '--mib', help='show sizes in KiB, MiB, GiB, TiB and PiB', action='store_true')
parser.add_argument('-u', '--update-drivedb', help='updating drivedb.h to increase the S.M.A.R.T. compatibility. This is equal to "update-smart-drivedb"', action='store_true')
parser.add_argument('-s', '--self-update', help='installs the newest version of baba', action='store_true')
parser.add_argument('-t', '--timeout', help='the time to wait for a timeout in seconds (default 4)', nargs='?', default=4)
parser.add_argument('-v', '--verbose', help='increase output verbosity', action='store_true')
parser.add_argument('-w', '--written', help='use 32 KB LBAs instead of the default 512 Bytes to calculate the "written" value (only for non-nvme devices)', action='store_true')
args = parser.parse_args()
def from_file(devname, keys):
rv = ''
if type(keys) is str:
keys = [keys]
for key in keys:
for filename in alternatives[key]:
try:
with open(filename.format(devname)) as f:
if rv:
rv += ' '
rv += f.read().rstrip()
except FileNotFoundError:
pass
if rv:
return rv
return '-'
def update_drivedb():
"""Downloading and update the drivedb.h"""
print('Downloading new drivedb...', flush=True, end='')
urlretrieve(updateSmartUrl, '/var/lib/smartmontools/drivedb/drivedb.h.new')
print('OK\nBackuping current drivedb...', flush=True, end='')
copyfile('/var/lib/smartmontools/drivedb/drivedb.h', '/var/lib/smartmontools/drivedb/drivedb.h.old')
print('OK\nActivate new drivedb...', flush=True, end='')
move('/var/lib/smartmontools/drivedb/drivedb.h.new', '/var/lib/smartmontools/drivedb/drivedb.h')
print('OK\nFinish!')
def calc_size(bytes, factor=1000, precision=0):
units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
template = '{:.?f} {}'.replace('?', str(precision))
if factor == 1024:
units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB']
if bytes is 0:
rv = '-'
elif bytes < factor:
rv = '{} {}'.format(bytes, units[0])
elif bytes < factor**2:
rv = template.format(bytes/factor, units[1])
elif bytes < factor**3:
rv = template.format(bytes/factor**2, units[2])
elif bytes < factor**4:
rv = template.format(bytes/factor**3, units[3])
elif bytes < factor**5:
rv = template.format(bytes/factor**4, units[4])
elif bytes < factor**6:
rv = template.format(bytes/factor**5, units[5])
return rv
def grabber(d, attributes):
rv = ''
for attr in attributes:
if attr in d:
if rv:
rv += ' '
rv += d[attr]
if rv:
return rv
return '-'
def valuechecker(dev, factor):
rv = {'vendor': '-',
'model': '-',
'serial': '-',
'firmware': '-',
'size': '-',
'runtime': '-',
'written': '-',
'rotation': '-',
'lifetime': '-',
'smart': '-'}
device = Device(dev, args.timeout)
try:
device.fetch_smart()
except TimeoutExpired:
rv['smart'] = 'TIMEOUT'
rv['lifetime'] = '?'
rv['rotation'] = '?'
rv['runtime'] = '?'
rv['written'] = '?'
if device.name.startswith(('nvme', 'sd', 'sr', 'vd')) and rv['smart'] != 'TIMEOUT':
if device.name.startswith('nvme'):
rv['model'] = grabber(device.smart_info, ['Model Number', 'Device Model'])
elif device.name.startswith(('sd', 'vd')):
rv['model'] = grabber(device.smart_info, ['Model Family', 'Vendor', 'Device Model', 'Product'])
elif device.name.startswith('sr'):
rv['model'] = grabber(device.smart_info, ['Vendor', 'Product'])
rv['firmware'] = grabber(device.smart_info, ['Firmware Version', 'Revision'])
rv['serial'] = grabber(device.smart_info, ['Serial Number', 'Serial number'])
if device.name.startswith('sr') is False:
rv['smart'] = device.analyse('health')
size = device.analyse('size')
if size:
rv['size'] = calc_size(int(size), factor)
runtime = device.analyse('runtime')
if runtime:
rv['runtime'] = runtime
rotation = device.analyse('rotation')
if rotation:
rv['rotation'] = rotation
lifetime = device.analyse('lifetime')
if lifetime:
rv['lifetime'] = lifetime
written = device.analyse('written')
if written:
written = int(written.split(' [')[0].replace('.', ''))
if device.name.startswith('nvme'):
rv['written'] = calc_size(written*512*1000, factor, precision=1)
else:
if args.written:
rv['written'] = calc_size(written*32000, factor, precision=1)
else:
rv['written'] = calc_size(written*512, factor, precision=1)
if rv['model'] == '-':
rv['model'] = from_file(device.name, ['vendor', 'model'])
if rv['serial'] == '-':
rv['serial'] = from_file(device.name, 'serial')
if rv['firmware'] == '-':
rv['firmware'] = from_file(device.name, 'firmware')
if rv['size'] == '-' and device.name.startswith('sr') is False:
size = int(from_file(device.name, 'size'))*512
rv['size'] = calc_size(size, factor=factor)
return [rv['model'],
rv['serial'],
rv['firmware'],
rv['size'],
rv['runtime'],
rv['written'],
rv['rotation'],
rv['lifetime'],
rv['smart']]
def short(s, max_len):
if args.verbose:
return '{} | '.format(s)
elif len(s) > max_len:
split_str = '[..]'
split_len = int(max_len/2 - len(split_str)/2)
return '{}{}{}'.format(s[:split_len], split_str, s[-split_len:])
else:
return s
def colored(color, s):
if color == 'red':
return '\x1b[0m\x1b[41m\x1b[1m{}\x1b[0m'.format(s)
elif color == 'green':
return '\x1b[0m\x1b[42m\x1b[1m{}\x1b[0m'.format(s)
elif color == 'purple':
return '\x1b[0m\x1b[45m\x1b[1m{}\x1b[0m'.format(s)
elif color == 'blue':
return '\x1b[0m\x1b[44m\x1b[1m{}\x1b[0m'.format(s)
elif color == 'dark':
return '\x1b[0m\x1b[40m\x1b[1m{}\x1b[0m'.format(s)
elif color == 'turkey':
return '\x1b[0m\x1b[46m\x1b[1m{}\x1b[0m'.format(s)
elif color == 'yellow':
return '\x1b[0m\x1b[1m\x1b[43m\x1b[30m{}\x1b[0m'.format(s)
if getuid() != 0:
exit('Please run as root!')
elif args.update_drivedb:
update_drivedb()
exit()
elif args.self_update:
proc = Popen('/usr/share/baba/update.sh')
if proc.wait() != 0:
print('Oops. Please run /usr/share/baba/update.sh manually!')
exit('Update finish!')
elif args.device:
if args.device.startswith('/dev/'):
devices = [args.device.split('/')[-1]]
else:
devices = [args.device]
else:
devices = [f for f in sorted(listdir('/sys/block/'), key=lambda x: (len(x), x)) if f.startswith(supported)]
factor = 1000
if args.mib:
factor = 1024
print('\x1b[1m{}'.format('Device'.ljust(8)), end='', flush=False)
for c in cols:
print(c[0].ljust(c[1]), end='', flush=False)
print('\x1b[0m')
for lno, filename in enumerate(devices):
#: colored lines
if lno % 2:
print('\x1b[33m', end='')
else:
print('\x1b[36m', end='')
#: print device name
print(filename.ljust(8), flush=True, end='')
#: get and print the other values
for i, value in enumerate(valuechecker('/dev/{}'.format(filename), factor=factor)):
if value is None:
value = '-'
if i is 8: # smart
if value in ('PASSED', 'OK'):
value = colored('green', ' OK ')
elif value == 'DSBLD':
value = colored('red', ' DISABLED ')
elif value == 'UDMA':
value = colored('red', ' UltraDMA')
elif value == 'TIMEOUT':
value = colored('purple', ' TIME-OUT ')
elif value == 'UNKNOWN':
value = colored('blue', ' UNKNOWN ')
elif value == 'USBB':
value = colored('blue', 'USB-BRIDGE')
elif value == '-':
value = colored('dark', ' NO SMART ')
elif value != '-':
value = colored('red', value.ljust(cols[i][1]))
print(value, end='')
elif i is 7 and value not in ['-', '?']: # lifetime
value_str = str(value)
just = cols[i][1] - len(value_str) - 1
if value <= 45:
value = colored('red', '{}%'.format(value))
elif value < 80:
value = colored('yellow', '{}%'.format(value))
else:
value = colored('green', '{}%'.format(value))
print(value.ljust(len(value) + just), end='')
else:
print(short(value, cols[i][1]-1).ljust(cols[i][1]), end='')
print('\x1b[0m')
exit()