Is it possible to get the mountpoint of a pendrive knowing its serialnumber using python?

Question:

I have 2 PC’s(a Linux and a Windows) connected to a local network which is on a different floor. People on that floor connect their USB Pen-drives to either one of the PC and I am suppose to copy different specific set of files to different people.

Previously,

  • what i did was so hard (get to the floor and do it manually)
  • Later i wrote a python program that copies specific set of file to
    specific person on my decision through ssh. (ie,. I log in to the
    specific machine through ssh, ask users (over a phone call) one by one to insert their
    pen-drive and then i execute the python program which accepted an
    argument. This argument is nothing but a name whom i wanted to copy,
    and by receiving the argument the program decides which files to be
    copied to the pen-drive).
    Still the process is bit tedious,..
    Because only one pen-drive could connected and i had to do this repeatedly for every user.

So to reduce the total time consumed, i connected USB hubs on both the systems to make multiple insert of pen-drive’s in a given time. And here comes the problem, decide which device belonged to whom.

Question : Is it possible to find the mount point of a pen-drive from the SerialNumber using python ? (it would be great if its python, since the main program is written in python)

The reason i am considering SerialNumber over,

  • UUID – It changes when the device is formatted
  • Vendor, ProdID and Manufacturer – Not sure that if they will be
    different. (ie,. what if its from the same manufacturer and a same
    model)

I tried wmi for windows.. and got this code from SO,(Sorry i don’t have the link. Took it long back)

import win32com.client

wmi = win32com.client.GetObject ("winmgmts:")
for usb in wmi.InstancesOf ("Win32_USBHub"):
    print usb.DeviceID

the output i get is

USBVID_5986&PID_02926&4817B6D&0&6
USBVID_8087&PID_00245&55D1EEC&0&1
USBVID_8087&PID_00245&88B8ABA&0&1
USBROOT_HUB204&11F77F7&0
USBROOT_HUB204&62BF53D&0
USBVID_03F0&PID_3307JN0W5LAB0ZHQ5VK8

its the similar case in linux, all i able to get is serialnumber, using usb-devices. But unable to get its corresponding mount-point

Any Ideas please…

Asked By: arvindh

||

Answers:

To do that on Linux you will need to parse /proc/mounts to determine mapping of device names to mountpoints, i.e. /dev/sdc2 -> /var/run/media/myaut/hyperx.

The trick is to find out what device name has required serial number. Easiest approach to do so is to use udev – it uses serial when generates symlinks in /dev/disk/by-id:

/dev/disk/by-id/usb-Generic_Flash_Disk_12345678-0:0 -> ../../sdd

But we didn’t seek for easiest solutions, are we? The trick is that udev rules may be altered, and sysfs (which come from kernel) is more reliable. I implemented a script that does that:

import os
import sys
import glob

SYS_USB_DEVICES = '/sys/bus/usb/devices'
SYS_BLOCK_DEVICES = '/sys/class/block'

try:
    serial = sys.argv[1]
except IndexError:
    print >> sys.stderr, "Usage: findflash.py SERIAL"
    sys.exit(1)

# PASS 1 Find USB node with corresponding to serial

for usbid in os.listdir(SYS_USB_DEVICES):
    usbserpath = os.path.join(SYS_USB_DEVICES, usbid, 'serial')    
    if not os.path.exists(usbserpath):
        continue    
    with open(usbserpath) as f:
        usb_serial = f.read().strip()

    if serial == usb_serial:
        # Found it!
        break
else:
    print >> sys.stderr, "Cannot find usb device with serial {0}".format(serial)
    sys.exit(1)

# Find SCSI ids corresponding to this device
# I didn't check SYSFS documentation, but tested it on openSUSE 13.1
# The form of path is:
#   <SUBDEVICE>/host<SCSI_HOST_ID>/target<SCSI_TARGET_ID>/<CTRL><CHANNEL>:<TGT>:<LUN>
# We need only basename

devs = glob.glob(os.path.join(SYS_USB_DEVICES, usbid, 
                            '*/host*/target*/*:*:*:*'))

devs = map(os.path.basename, devs)

# PASS 2 - find mountpoints for devices with SCSI ids we discover earlier

# Parse mountpoint formatted as "/dev/... /path/to/mntpt ..." 
def parse_mntpt(line):
    dev, mntpt, _ = line.split(None, 2)
    dev = os.path.basename(dev)
    return dev, mntpt

mntpts = {}
with open('/proc/mounts') as f:
    mntpts = dict(map(parse_mntpt, f.readlines()))

# List of ('scsi id', 'dev name', 'mnt pt (if exists)')
devlist = []

def create_dev(scsiid, devname):
    global mntpts
    devlist.append((scsiid, devname, mntpts.get(devname)))

for devname in os.listdir(SYS_BLOCK_DEVICES):
    devpath = os.path.join(SYS_BLOCK_DEVICES, devname)
    devlink = os.path.join(devpath, 'device')

    # Node is "virtual", i.e. partition, ignore it
    if not os.path.islink(devlink):
        continue

    scsiid = os.path.basename(os.readlink(devlink))    
    if scsiid not in devs:
        continue

    create_dev(scsiid, devname)

    # Find partition names
    parts = glob.glob(os.path.join(devpath, '*/partition'))

    for partpath in parts:
        partname = os.path.basename(os.path.dirname(partpath))
        create_dev(scsiid, partname)

# END - print results
fmtstr = '{0:8} {1:5} {2}'
print fmtstr.format('SCSI ID', 'DEV', 'MOUNT POINT')
for scsiid, devname, mntpt in devlist:
    print fmtstr.format(scsiid, devname, mntpt)

Here is example output:

$ python findflash.py 12345678
SCSI ID  DEV   MOUNT POINT
8:0:0:0  sdd   None
8:0:0:0  sdd1  /var/run/media/myaut/Debian40wheezy4020140723-17:30
8:0:0:0  sdd2  None
8:0:0:0  sdd5  None
8:0:0:1  sr0   None

I can’t say that on Windows that would be easy. I have a code (in C/WinAPI) that able to collect all disk devices from system, but it’s logic is far away from filesystems representation, so I still didn’t find a solution for that.

Complexity of Windows come from that:

  • There is set of functions like SetupDi* that allow you enumerate disk devices. Their names are come in PnP style, unrelated to volume names.
  • There is DOS-style API (i.e. working on level of A: and C:)
  • There is WinNT-style API that knows about volumes, and probably, mountpoints.

Of course linking that three layers is not obvious (there is approach to match partitions by their size/offset, but that is crazy). I’m still scared to implement it in my library 🙁

Answered By: myaut

You can also use lsblk for linux if you don’t mind running an external process.
Mind that a disk has a serial number while a partition, which is mounted, does not.

p_handler = subprocess.run(
    ['lsblk', '-J', '-o', 'PATH,SERIAL,MOUNTPOINT'],
    check=True,
    capture_output=True)
json_output = json.loads(p_handler.stdout.decode("utf-8"))
# (serial, device_path, mount_point)
drives = [(dev['serial'], dev['path'], dev['mountpoint'])
          for dev in json_output['blockdevices']]
print(drives)
Answered By: Igor Wojnicki