Home | History | Annotate | Download | only in cros
      1 #!/usr/bin/python
      2 
      3 # Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
      4 # Use of this source code is governed by a BSD-style license that can be
      5 # found in the LICENSE file.
      6 
      7 """This is a module to scan /sys/block/ virtual FS, query udev
      8 
      9 It provides a list of all removable or USB devices connected to the machine on
     10 which the module is running.
     11 It can be used from command line or from a python script.
     12 
     13 To use it as python module it's enough to call the get_all() function.
     14 @see |get_all| documentation for the output format
     15 |get_all()| output is human readable (as oppposite to python's data structures)
     16 """
     17 
     18 import os, re
     19 
     20 # this script can be run at command line on DUT (ie /usr/local/autotest
     21 # contains only the client/ subtree), on a normal autotest
     22 # installation/repository or as a python module used on a client-side test.
     23 import common
     24 from autotest_lib.client.common_lib import base_utils as utils
     25 
     26 INFO_PATH = "/sys/block"
     27 
     28 
     29 def get_udev_info(blockdev, method='udev'):
     30     """Get information about |blockdev|
     31 
     32     @param blockdev: a block device, e.g., /dev/sda1 or /dev/sda
     33     @param method: either 'udev' (default) or 'blkid'
     34 
     35     @return a dictionary with two or more of the followig keys:
     36         "ID_BUS", "ID_MODEL": always present
     37         "ID_FS_UUID", "ID_FS_TYPE", "ID_FS_LABEL": present only if those info
     38          are meaningul and present for the queried device
     39     """
     40     ret = {}
     41     cmd = None
     42     ignore_status = False
     43 
     44     if method == "udev":
     45         cmd = "udevadm info --name %s --query=property" % blockdev
     46     elif method == "blkid":
     47         # this script is run as root in a normal autotest run,
     48         # so this works: It doesn't have access to the necessary info
     49         # when run as a non-privileged user
     50         cmd = "blkid -c /dev/null -o udev %s" % blockdev
     51         ignore_status = True
     52 
     53     if cmd:
     54         output = utils.system_output(cmd, ignore_status=ignore_status)
     55 
     56         udev_keys = ("ID_BUS", "ID_MODEL", "ID_FS_UUID", "ID_FS_TYPE",
     57                      "ID_FS_LABEL")
     58         for line in output.splitlines():
     59             udev_key, udev_val = line.split('=')
     60 
     61             if udev_key in udev_keys:
     62                 ret[udev_key] = udev_val
     63 
     64     return ret
     65 
     66 
     67 def get_partition_info(part_path, bus, model, partid=None, fstype=None,
     68                        label=None, block_size=0, is_removable=False):
     69     """Return information about a device as a list of dictionaries
     70 
     71     Normally a single device described by the passed parameters will match a
     72     single device on the system, and thus a single element list as return
     73     value; although it's possible that a single block device is associated with
     74     several mountpoints, this scenario will lead to a dictionary for each
     75     mountpoint.
     76 
     77     @param part_path: full partition path under |INFO_PATH|
     78                       e.g., /sys/block/sda or /sys/block/sda/sda1
     79     @param bus: bus, e.g., 'usb' or 'ata', according to udev
     80     @param model: device moduel, e.g., according to udev
     81     @param partid: partition id, if present
     82     @param fstype: filesystem type, if present
     83     @param label: filesystem label, if present
     84     @param block_size: filesystem block size
     85     @param is_removable: whether it is a removable device
     86 
     87     @return a list of dictionaries contaning each a partition info.
     88             An empty list can be returned if no matching device is found
     89     """
     90     ret = []
     91     # take the partitioned device name from the /sys/block/ path name
     92     part = part_path.split('/')[-1]
     93     device = "/dev/%s" % part
     94 
     95     if not partid:
     96         info = get_udev_info(device, "blkid")
     97         partid = info.get('ID_FS_UUID', None)
     98         if not fstype:
     99             fstype = info.get('ID_FS_TYPE', None)
    100         if not label:
    101             label = partid
    102 
    103     readonly = open("%s/ro" % part_path).read()
    104     if not int(readonly):
    105         partition_blocks = open("%s/size" % part_path).read()
    106         size = block_size * int(partition_blocks)
    107 
    108         stub = {}
    109         stub['device'] = device
    110         stub['bus'] = bus
    111         stub['model'] = model
    112         stub['size'] = size
    113 
    114         # look for it among the mounted devices first
    115         mounts = open("/proc/mounts").readlines()
    116         seen = False
    117         for line in mounts:
    118             dev, mount, proc_fstype, flags = line.split(' ', 3)
    119 
    120             if device == dev:
    121                 if 'rw' in flags.split(','):
    122                     seen = True # at least one match occurred
    123 
    124                     # Sorround mountpoint with quotes, to make it parsable in
    125                     # case of spaces. Also information retrieved from
    126                     # /proc/mount override the udev passed ones (e.g.,
    127                     # proc_fstype instead of fstype)
    128                     dev = stub.copy()
    129                     dev['fs_uuid'] = partid
    130                     dev['fstype'] = proc_fstype
    131                     dev['is_mounted'] = True
    132                     dev['mountpoint'] = mount
    133                     ret.append(dev)
    134 
    135         # If not among mounted devices, it's just attached, print about the
    136         # same information but suggest a place where the user can mount the
    137         # device instead
    138         if not seen:
    139             # we consider it if it's removable and and a partition id
    140             # OR it's on the USB bus.
    141             # Some USB HD do not get announced as removable, but they should be
    142             # showed.
    143             # There are good changes that if it's on a USB bus it's removable
    144             # and thus interesting for us, independently whether it's declared
    145             # removable
    146             if (is_removable and partid) or bus == 'usb':
    147                 if not label:
    148                     info = get_udev_info(device, 'blkid')
    149                     label = info.get('ID_FS_LABEL', partid)
    150 
    151                 dev = stub.copy()
    152                 dev['fs_uuid'] = partid
    153                 dev['fstype'] = fstype
    154                 dev['is_mounted'] = False
    155                 dev['mountpoint'] = "/media/removable/%s" % label
    156                 ret.append(dev)
    157 
    158         return ret
    159 
    160 
    161 def get_device_info(blockdev):
    162     """Retrieve information about |blockdev|
    163 
    164     @see |get_partition_info()| doc for the dictionary format
    165 
    166     @param blockdev: a block device name, e.g., "sda".
    167 
    168     @return a list of dictionary, with each item representing a found device
    169     """
    170     ret = []
    171 
    172     spath = "%s/%s" % (INFO_PATH, blockdev)
    173     block_size = int(open("%s/queue/physical_block_size" % spath).read())
    174     is_removable = bool(int(open("%s/removable" % spath).read()))
    175 
    176     info = get_udev_info(blockdev, "udev")
    177     dev_bus = info['ID_BUS']
    178     dev_model = info['ID_MODEL']
    179     dev_fs = info.get('ID_FS_TYPE', None)
    180     dev_uuid = info.get('ID_FS_UUID', None)
    181     dev_label = info.get('ID_FS_LABEL', dev_uuid)
    182 
    183     has_partitions = False
    184     for basename in os.listdir(spath):
    185         partition_path = "%s/%s" % (spath, basename)
    186         # we want to check if within |spath| there are subdevices with
    187         # partitions
    188         # e.g., if within /sys/block/sda sda1 and other partition are present
    189         if not re.match("%s[0-9]+" % blockdev, basename):
    190             continue # ignore what is not a subdevice
    191 
    192         # |blockdev| has subdevices: get info for them
    193         has_partitions = True
    194         devs = get_partition_info(partition_path, dev_bus, dev_model,
    195                                   block_size=block_size,
    196                                   is_removable=is_removable)
    197         ret.extend(devs)
    198 
    199     if not has_partitions:
    200         devs = get_partition_info(spath, dev_bus, dev_model, dev_uuid, dev_fs,
    201                                   dev_label, block_size=block_size,
    202                                   is_removable=is_removable)
    203         ret.extend(devs)
    204 
    205     return ret
    206 
    207 
    208 def get_all():
    209     """Return all removable or USB storage devices attached
    210 
    211     @return a list of dictionaries, each list element describing a device
    212     """
    213     ret = []
    214     for dev in os.listdir(INFO_PATH):
    215         # Among block devices we need to filter out what are virtual
    216         if re.match("s[a-z]+", dev):
    217             # for each of them try to obtain some info
    218             ret.extend(get_device_info(dev))
    219     return ret
    220 
    221 
    222 def main():
    223     for device in get_all():
    224         print ("%(device)s %(bus)s %(model)s %(size)d %(fs_uuid)s %(fstype)s "
    225                "%(is_mounted)d %(mountpoint)s" % device)
    226 
    227 
    228 if __name__ == "__main__":
    229     main()
    230