#!/usr/bin/python # Copyright (c) 2012 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """This is a module to scan /sys/block/ virtual FS, query udev It provides a list of all removable or USB devices connected to the machine on which the module is running. It can be used from command line or from a python script. To use it as python module it's enough to call the get_all() function. @see |get_all| documentation for the output format |get_all()| output is human readable (as oppposite to python's data structures) """ import os, re # this script can be run at command line on DUT (ie /usr/local/autotest # contains only the client/ subtree), on a normal autotest # installation/repository or as a python module used on a client-side test. import common from autotest_lib.client.common_lib import base_utils as utils INFO_PATH = "/sys/block" def get_udev_info(blockdev, method='udev'): """Get information about |blockdev| @param blockdev: a block device, e.g., /dev/sda1 or /dev/sda @param method: either 'udev' (default) or 'blkid' @return a dictionary with two or more of the followig keys: "ID_BUS", "ID_MODEL": always present "ID_FS_UUID", "ID_FS_TYPE", "ID_FS_LABEL": present only if those info are meaningul and present for the queried device """ ret = {} cmd = None ignore_status = False if method == "udev": cmd = "udevadm info --name %s --query=property" % blockdev elif method == "blkid": # this script is run as root in a normal autotest run, # so this works: It doesn't have access to the necessary info # when run as a non-privileged user cmd = "blkid -c /dev/null -o udev %s" % blockdev ignore_status = True if cmd: output = utils.system_output(cmd, ignore_status=ignore_status) udev_keys = ("ID_BUS", "ID_MODEL", "ID_FS_UUID", "ID_FS_TYPE", "ID_FS_LABEL") for line in output.splitlines(): udev_key, udev_val = line.split('=') if udev_key in udev_keys: ret[udev_key] = udev_val return ret def get_partition_info(part_path, bus, model, partid=None, fstype=None, label=None, block_size=0, is_removable=False): """Return information about a device as a list of dictionaries Normally a single device described by the passed parameters will match a single device on the system, and thus a single element list as return value; although it's possible that a single block device is associated with several mountpoints, this scenario will lead to a dictionary for each mountpoint. @param part_path: full partition path under |INFO_PATH| e.g., /sys/block/sda or /sys/block/sda/sda1 @param bus: bus, e.g., 'usb' or 'ata', according to udev @param model: device moduel, e.g., according to udev @param partid: partition id, if present @param fstype: filesystem type, if present @param label: filesystem label, if present @param block_size: filesystem block size @param is_removable: whether it is a removable device @return a list of dictionaries contaning each a partition info. An empty list can be returned if no matching device is found """ ret = [] # take the partitioned device name from the /sys/block/ path name part = part_path.split('/')[-1] device = "/dev/%s" % part if not partid: info = get_udev_info(device, "blkid") partid = info.get('ID_FS_UUID', None) if not fstype: fstype = info.get('ID_FS_TYPE', None) if not label: label = partid readonly = open("%s/ro" % part_path).read() if not int(readonly): partition_blocks = open("%s/size" % part_path).read() size = block_size * int(partition_blocks) stub = {} stub['device'] = device stub['bus'] = bus stub['model'] = model stub['size'] = size # look for it among the mounted devices first mounts = open("/proc/mounts").readlines() seen = False for line in mounts: dev, mount, proc_fstype, flags = line.split(' ', 3) if device == dev: if 'rw' in flags.split(','): seen = True # at least one match occurred # Sorround mountpoint with quotes, to make it parsable in # case of spaces. Also information retrieved from # /proc/mount override the udev passed ones (e.g., # proc_fstype instead of fstype) dev = stub.copy() dev['fs_uuid'] = partid dev['fstype'] = proc_fstype dev['is_mounted'] = True dev['mountpoint'] = mount ret.append(dev) # If not among mounted devices, it's just attached, print about the # same information but suggest a place where the user can mount the # device instead if not seen: # we consider it if it's removable and and a partition id # OR it's on the USB bus. # Some USB HD do not get announced as removable, but they should be # showed. # There are good changes that if it's on a USB bus it's removable # and thus interesting for us, independently whether it's declared # removable if (is_removable and partid) or bus == 'usb': if not label: info = get_udev_info(device, 'blkid') label = info.get('ID_FS_LABEL', partid) dev = stub.copy() dev['fs_uuid'] = partid dev['fstype'] = fstype dev['is_mounted'] = False dev['mountpoint'] = "/media/removable/%s" % label ret.append(dev) return ret def get_device_info(blockdev): """Retrieve information about |blockdev| @see |get_partition_info()| doc for the dictionary format @param blockdev: a block device name, e.g., "sda". @return a list of dictionary, with each item representing a found device """ ret = [] spath = "%s/%s" % (INFO_PATH, blockdev) block_size = int(open("%s/queue/physical_block_size" % spath).read()) is_removable = bool(int(open("%s/removable" % spath).read())) info = get_udev_info(blockdev, "udev") dev_bus = info['ID_BUS'] dev_model = info['ID_MODEL'] dev_fs = info.get('ID_FS_TYPE', None) dev_uuid = info.get('ID_FS_UUID', None) dev_label = info.get('ID_FS_LABEL', dev_uuid) has_partitions = False for basename in os.listdir(spath): partition_path = "%s/%s" % (spath, basename) # we want to check if within |spath| there are subdevices with # partitions # e.g., if within /sys/block/sda sda1 and other partition are present if not re.match("%s[0-9]+" % blockdev, basename): continue # ignore what is not a subdevice # |blockdev| has subdevices: get info for them has_partitions = True devs = get_partition_info(partition_path, dev_bus, dev_model, block_size=block_size, is_removable=is_removable) ret.extend(devs) if not has_partitions: devs = get_partition_info(spath, dev_bus, dev_model, dev_uuid, dev_fs, dev_label, block_size=block_size, is_removable=is_removable) ret.extend(devs) return ret def get_all(): """Return all removable or USB storage devices attached @return a list of dictionaries, each list element describing a device """ ret = [] for dev in os.listdir(INFO_PATH): # Among block devices we need to filter out what are virtual if re.match("s[a-z]+", dev): # for each of them try to obtain some info ret.extend(get_device_info(dev)) return ret def main(): for device in get_all(): print ("%(device)s %(bus)s %(model)s %(size)d %(fs_uuid)s %(fstype)s " "%(is_mounted)d %(mountpoint)s" % device) if __name__ == "__main__": main()