• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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