Added macOS support for disk details and SMART

This commit is contained in:
2Shirt 2019-10-25 19:13:04 -06:00
parent 70248ef0b5
commit c0242ad55c
Signed by: 2Shirt
GPG key ID: 152FAC923B0E132C

View file

@ -115,7 +115,7 @@ class Disk():
def __init__(self, path): def __init__(self, path):
self.attributes = {} self.attributes = {}
self.description = 'Unknown' self.description = 'Unknown'
self.lsblk = {} self.details = {}
self.nvme_smart_notes = {} self.nvme_smart_notes = {}
self.path = pathlib.Path(path).resolve() self.path = pathlib.Path(path).resolve()
self.smartctl = {} self.smartctl = {}
@ -138,38 +138,39 @@ class Disk():
run_program(cmd, check=False) run_program(cmd, check=False)
def get_details(self): def get_details(self):
"""Get details via lsblk. """Get disk details using OS specific methods.
Required details default to generic descriptions and Required details default to generic descriptions
are converted to the correct type. and are converted to the correct type.
""" """
cmd = ['lsblk', '--bytes', '--json', '--output-all', '--paths', self.path] if platform.system() == 'Darwin':
json_data = get_json_from_command(cmd) self.details = get_disk_details_macos(self.path)
self.lsblk = json_data.get('blockdevices', [{}])[0] elif platform.system() == 'Linux':
self.details = get_disk_details_linux(self.path)
# Set necessary details # Set necessary details
self.lsblk['model'] = self.lsblk.get('model', 'Unknown Model') self.details['model'] = self.details.get('model', 'Unknown Model')
self.lsblk['name'] = self.lsblk.get('name', self.path) self.details['name'] = self.details.get('name', self.path)
self.lsblk['log-sec'] = self.lsblk.get('log-sec', 512) self.details['log-sec'] = self.details.get('log-sec', 512)
self.lsblk['phy-sec'] = self.lsblk.get('phy-sec', 512) self.details['phy-sec'] = self.details.get('phy-sec', 512)
self.lsblk['rota'] = self.lsblk.get('rota', True) self.details['proto'] = self.details.get('proto', '???')
self.lsblk['serial'] = self.lsblk.get('serial', 'Unknown Serial') self.details['proto'] = self.details['proto'].upper().replace('NVME', 'NVMe')
self.lsblk['size'] = self.lsblk.get('size', -1) self.details['rota'] = self.details.get('rota', True)
self.lsblk['tran'] = self.lsblk.get('tran', '???') self.details['serial'] = self.details.get('serial', 'Unknown Serial')
self.lsblk['tran'] = self.lsblk['tran'].upper().replace('NVME', 'NVMe') self.details['size'] = self.details.get('size', -1)
# Ensure certain attributes types # Ensure certain attributes types
for attr in ['model', 'name', 'serial', 'tran']: for attr in ['model', 'name', 'proto', 'serial']:
if not isinstance(self.lsblk[attr], str): if not isinstance(self.details[attr], str):
self.lsblk[attr] = str(self.lsblk[attr]) self.details[attr] = str(self.details[attr])
for attr in ['log-sec', 'phy-sec', 'size']: for attr in ['log-sec', 'phy-sec', 'size']:
if not isinstance(self.lsblk[attr], int): if not isinstance(self.details[attr], int):
self.lsblk[attr] = int(self.lsblk[attr]) self.details[attr] = int(self.details[attr])
# Set description # Set description
self.description = '{size_str} ({tran}) {model} {serial}'.format( self.description = '{size_str} ({proto}) {model} {serial}'.format(
size_str=bytes_to_string(self.lsblk['size'], use_binary=False), size_str=bytes_to_string(self.details['size'], use_binary=False),
**self.lsblk, **self.details,
) )
def get_labels(self): def get_labels(self):
@ -177,7 +178,7 @@ class Disk():
labels = [] labels = []
# Add all labels from lsblk # Add all labels from lsblk
for disk in [self.lsblk, *self.lsblk.get('children', [])]: for disk in [self.details, *self.details.get('children', [])]:
labels.append(disk.get('label', '')) labels.append(disk.get('label', ''))
labels.append(disk.get('partlabel', '')) labels.append(disk.get('partlabel', ''))
@ -235,6 +236,71 @@ class Disk():
# Functions # Functions
def get_disk_details_linux(path):
"""Get disk details using lsblk, returns dict."""
cmd = ['lsblk', '--bytes', '--json', '--output-all', '--paths', path]
json_data = get_json_from_command(cmd, check=False)
details = json_data.get('blockdevices', [{}])[0]
return details
def get_disk_details_macos(path):
"""Get disk details using diskutil, returns dict."""
details = {}
# Get "list" details
cmd = ['diskutil', 'list', '-plist', path]
proc = run_program(cmd, check=False, encoding=None, errors=None)
try:
plist_data = plistlib.loads(proc.stdout)
except (TypeError, ValueError):
LOG.error('Failed to get diskutil list for %s', path)
# TODO: Figure this out
return details #Bail
# Parse "list" details
details = plist_data.get('AllDisksAndPartitions', [{}])[0]
details['children'] = details.pop('Partitions', [])
details['path'] = path
for child in details['children']:
child['path'] = path.with_name(child.get('DeviceIdentifier', 'null'))
# Get "info" details
for dev in [details, *details['children']]:
cmd = ['diskutil', 'info', '-plist', dev['path']]
proc = run_program(cmd, check=False, encoding=None, errors=None)
try:
plist_data = plistlib.loads(proc.stdout)
except (TypeError, ValueError):
LOG.error('Failed to get diskutil info for %s', path)
continue #Skip
# Parse "info" details
dev.update(plist_data)
dev['size'] = dev.pop('Size', -1)
dev['phy-sec'] = dev.pop('DeviceBlockSize', 512)
dev['ssd'] = dev.pop('SolidState', False)
dev['proto'] = dev.pop('BusProtocol', '???')
dev['vendor'] = ''
dev['model'] = dev.pop('MediaName', 'Unknown')
dev['serial'] = get_disk_serial_macos(dev['path'])
dev['label'] = dev.pop('VolumeName', '')
dev['fstype'] = dev.pop('FilesystemType', '')
dev['mountpoint'] = dev.pop('MountPoint', '')
if not dev.get('WholeDisk', True):
dev['parent'] = dev.pop('ParentWholeDisk', None)
# Done
return details
def get_disk_serial_macos(path):
"""Get disk serial using system_profiler, returns str."""
serial = 'Unknown Serial'
# TODO: Make it real
return serial
def get_ram_list_linux(): def get_ram_list_linux():
"""Get RAM list using dmidecode.""" """Get RAM list using dmidecode."""
cmd = ['sudo', 'dmidecode', '--type', 'memory'] cmd = ['sudo', 'dmidecode', '--type', 'memory']