#!/bin/bash

# This script searches for all attached USB storage devices and
# outputs information about the available block devices. Information
# is output for each partition on the device, but if the device has no
# partitions information for the whole device is output instead.  For
# each one partition one line is printed with the sysfs block device
# path, the block device node name, the disk partition size in 512
# byte blocks and the total disk size in 512 byte blocks; fields are
# separate by pipe symbols (|); note that the sysfs path may contain
# colons (:).

# WARNING: kernels 2.6.10 and 2.6.37 have significantly differing
# rules to walk sysfs. This script should remain compatible with both,
# so be extra careful when modifying how block devices under a usb bus
# are found.


# This returns the /sys/devices paths for all block devices that are
# connected via usb. For old kernels (e.g., 2.6.10) this is a link to
# the block device entry under /sys/block. For new kernels (e.g.,
# 2.6.37) this is a directory containing a single entry with the name
# of the block device (e.g., sda).
find_usb_block_paths() {
    local rlink
    local rpath
    # /sys/bus/usb/devices/ contains a flat list of links to all
    # connected USB devices, inluding end devices and hubs, as well as
    # the USB roots. Note that under /sys/devices these appear in a
    # hierarchical tree, representing the actual connection
    # topology. The usb roots are named "usbN", while connected USB
    # devices have name starting with a number (e.g., "1-1",
    # "1-0:1.0", "1-1.4:1.0")
    for rlink in /sys/bus/usb/devices/usb*; do
        rpath="$(readlink -f -q "$rlink")" || continue
        [ -e "$rpath" ] || continue
        find "$rpath" -name block -print
    done
}

# This returns the sysfs names for block devices as known by udev
# (partitions are not included, just complete devices).
find_usb_block_devices() {
    local paths
    local p
    local e

    paths="$(find_usb_block_paths)"
    for p in $paths ; do
        if [ -L "$p" ]; then
            # old style kernel, this is a link to the real directory
            # entry in /sys/block (e.g., -> /sys/block/sda)
            readlink -f -q "$p"
        else
            # new style kernel, this a real directory entry with
            # normally a single directory named after the block
            # device's name (e.g., sda); the entry under /sys/block
            # (e.g., /sys/block/sda) is a symlink to that.
            for d in "$p"/*; do
                # sanity check: the same base name must exist in /sys/block
                [ -e /sys/block/"${d##*/}" ] || continue
                echo "$d"
            done
        fi
    done
}

# Print out an entry for each block device, if there are partitions we
# omit the non-partitioned device.
for d in $(find_usb_block_devices); do
    n="${d##*/}"
    ds="$(< "$d"/size)"
    parts="$(find "$d" -maxdepth 1 -name "${n}[0-9]*" -print)"
    if [ -z "$parts" ]; then
        parts="$d" # No partitions, report whole block device
    fi
    for p in "$parts"; do
        if [ -d "$p" ]; then
            dev="$(udevadm info --query=name --path="${p#/sys}" 2>/dev/null)"
            [ -n "$dev" ] && dev=/dev/"$dev"
            ps="$(< "$p"/size)"
            [ -e "$p" ] || continue # just removed
            echo "$p|$dev|$ps|$ds"
        fi
    done
done
