import os import stat from ceph_volume import process def get_partuuid(device): """ If a device is a partition, it will probably have a PARTUUID on it that will persist and can be queried against `blkid` later to detect the actual device """ out, err, rc = process.call( ['blkid', '-s', 'PARTUUID', '-o', 'value', device] ) return ' '.join(out).strip() def get_device_from_partuuid(partuuid): """ If a device has a partuuid, query blkid so that it can tell us what that device is """ out, err, rc = process.call( ['blkid', '-t', 'PARTUUID="%s"' % partuuid, '-o', 'device'] ) return ' '.join(out).strip() def _stat_is_device(stat_obj): """ Helper function that will interpret ``os.stat`` output directly, so that other functions can call ``os.stat`` once and interpret that result several times """ return stat.S_ISBLK(stat_obj) def lsblk(device, columns=None): """ Create a dictionary of identifying values for a device using ``lsblk``. Each supported column is a key, in its *raw* format (all uppercase usually). ``lsblk`` has support for certain "columns" (in blkid these would be labels), and these columns vary between distributions and ``lsblk`` versions. The newer versions support a richer set of columns, while older ones were a bit limited. These are the default lsblk columns reported which are safe to use for Ubuntu 14.04.5 LTS: NAME device name KNAME internal kernel device name MAJ:MIN major:minor device number FSTYPE filesystem type MOUNTPOINT where the device is mounted LABEL filesystem LABEL UUID filesystem UUID RO read-only device RM removable device MODEL device identifier SIZE size of the device STATE state of the device OWNER user name GROUP group name MODE device node permissions ALIGNMENT alignment offset MIN-IO minimum I/O size OPT-IO optimal I/O size PHY-SEC physical sector size LOG-SEC logical sector size ROTA rotational device SCHED I/O scheduler name RQ-SIZE request queue size TYPE device type DISC-ALN discard alignment offset DISC-GRAN discard granularity DISC-MAX discard max bytes DISC-ZERO discard zeroes data There is a bug in ``lsblk`` where using all the available (supported) columns will result in no output (!), in order to workaround this the following columns have been removed from the default reporting columns: * RQ-SIZE (request queue size) * MIN-IO minimum I/O size * OPT-IO optimal I/O size These should be available however when using `columns`. For example:: >>> lsblk('/dev/sda1', columns=['OPT-IO']) {'OPT-IO': '0'} Normal CLI output, as filtered by the flags in this function will look like :: $ lsblk --nodeps -P -o NAME,KNAME,MAJ:MIN,FSTYPE,MOUNTPOINT NAME="sda1" KNAME="sda1" MAJ:MIN="8:1" FSTYPE="ext4" MOUNTPOINT="/" :param columns: A list of columns to report as keys in its original form. """ default_columns = [ 'NAME', 'KNAME', 'MAJ:MIN', 'FSTYPE', 'MOUNTPOINT', 'LABEL', 'UUID', 'RO', 'RM', 'MODEL', 'SIZE', 'STATE', 'OWNER', 'GROUP', 'MODE', 'ALIGNMENT', 'PHY-SEC', 'LOG-SEC', 'ROTA', 'SCHED', 'TYPE', 'DISC-ALN', 'DISC-GRAN', 'DISC-MAX', 'DISC-ZERO' ] device = device.rstrip('/') columns = columns or default_columns # --nodeps -> Avoid adding children/parents to the device, only give information # on the actual device we are querying for # -P -> Produce pairs of COLUMN="value" # -o -> Use the columns specified or default ones provided by this function command = ['lsblk', '--nodeps', '-P', '-o'] command.append(','.join(columns)) command.append(device) out, err, rc = process.call(command) if rc != 0: return {} # parse the COLUMN="value" output to construct the dictionary pairs = ' '.join(out).split() parsed = {} for pair in pairs: try: column, value = pair.split('=') except ValueError: continue parsed[column] = value.strip().strip().strip('"') return parsed def _lsblk_type(device): """ Helper function that will use the ``TYPE`` label output of ``lsblk`` to determine if a device is a partition or disk. It does not process the output to return a boolean, but it does process it to return the """ out, err, rc = process.call( ['blkid', '-s', 'PARTUUID', '-o', 'value', device] ) return ' '.join(out).strip() def is_device(dev): """ Boolean to determine if a given device is a block device (**not** a partition!) For example: /dev/sda would return True, but not /dev/sdc1 """ if not os.path.exists(dev): return False # use lsblk first, fall back to using stat TYPE = lsblk(dev).get('TYPE') if TYPE: return TYPE == 'disk' # fallback to stat return _stat_is_device(os.lstat(dev).st_mode) if stat.S_ISBLK(os.lstat(dev)): return True return False def is_partition(dev): """ Boolean to determine if a given device is a partition, like /dev/sda1 """ if not os.path.exists(dev): return False # use lsblk first, fall back to using stat TYPE = lsblk(dev).get('TYPE') if TYPE: return TYPE == 'part' # fallback to stat stat_obj = os.stat(dev) if _stat_is_device(stat_obj.st_mode): return False major = os.major(stat_obj.st_rdev) minor = os.minor(stat_obj.st_rdev) if os.path.exists('/sys/dev/block/%d:%d/partition' % (major, minor)): return True return False