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 9It provides a list of all removable or USB devices connected to the machine on 10which the module is running. 11It can be used from command line or from a python script. 12 13To 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 18import 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. 23import common 24from autotest_lib.client.common_lib import utils 25 26INFO_PATH = "/sys/block" 27 28 29def 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 67def 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 161def 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 208def 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 222def 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 228if __name__ == "__main__": 229 main() 230