S.M.A.R.T. drive checking has never been easier
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 

346 lignes
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()