Python Udisks - Enumerating Device Information

python udisks - enumerating device information

You can use Udisks via dbus directly in python.

import dbus

bus = dbus.SystemBus()
ud_manager_obj = bus.get_object("org.freedesktop.UDisks", "/org/freedesktop/UDisks")
ud_manager = dbus.Interface(ud_manager_obj, 'org.freedesktop.UDisks')

for dev in ud_manager.EnumerateDevices():
device_obj = bus.get_object("org.freedesktop.UDisks", dev)
device_props = dbus.Interface(device_obj, dbus.PROPERTIES_IFACE)
print device_props.Get('org.freedesktop.UDisks.Device', "DriveVendor")
print device_props.Get('org.freedesktop.UDisks.Device', "DeviceMountPaths")
print device_props.Get('org.freedesktop.UDisks.Device', "DriveSerial")
print device_props.Get('org.freedesktop.UDisks.Device', "PartitionSize")

The full list of properties available is here http://hal.freedesktop.org/docs/udisks/Device.html

Listing details of USB drives using python and udisk2

After lot of hit and trial, I could get what I wanted. Just posting it so that some one can benefit in the future. Here is the code:-

#!/usr/bin/python2.7
# coding: utf-8
import dbus

def get_usb():
devices = []
bus = dbus.SystemBus()
ud_manager_obj = bus.get_object('org.freedesktop.UDisks2', '/org/freedesktop/UDisks2')
om = dbus.Interface(ud_manager_obj, 'org.freedesktop.DBus.ObjectManager')
try:
for k,v in om.GetManagedObjects().iteritems():
drive_info = v.get('org.freedesktop.UDisks2.Block', {})
if drive_info.get('IdUsage') == "filesystem" and not drive_info.get('HintSystem') and not drive_info.get('ReadOnly'):
device = drive_info.get('Device')
device = bytearray(device).replace(b'\x00', b'').decode('utf-8')
devices.append(device)
except:
print "No device found..."
return devices

def usb_details(device):
bus = dbus.SystemBus()
bd = bus.get_object('org.freedesktop.UDisks2', '/org/freedesktop/UDisks2/block_devices%s'%device[4:])
try:
device = bd.Get('org.freedesktop.UDisks2.Block', 'Device', dbus_interface='org.freedesktop.DBus.Properties')
device = bytearray(device).replace(b'\x00', b'').decode('utf-8')
print "printing " + device
label = bd.Get('org.freedesktop.UDisks2.Block', 'IdLabel', dbus_interface='org.freedesktop.DBus.Properties')
print 'Name od partition is %s'%label
uuid = bd.Get('org.freedesktop.UDisks2.Block', 'IdUUID', dbus_interface='org.freedesktop.DBus.Properties')
print 'UUID is %s'%uuid
size = bd.Get('org.freedesktop.UDisks2.Block', 'Size', dbus_interface='org.freedesktop.DBus.Properties')
print 'Size is %s'%uuid
file_system = bd.Get('org.freedesktop.UDisks2.Block', 'IdType', dbus_interface='org.freedesktop.DBus.Properties')
print 'Filesystem is %s'%file_system
except:
print "Error detecting USB details..."

The complete block device properties can be found here http://udisks.freedesktop.org/docs/latest/gdbus-org.freedesktop.UDisks2.Block.html

Python: OS Independent list of available storage devices

The following should work for Linux and Windows.
This will list ALL drives, not just externals!

import subprocess
import sys

#on windows
#Get the fixed drives
#wmic logicaldisk get name,description
if 'win' in sys.platform:
drivelist = subprocess.Popen('wmic logicaldisk get name,description', shell=True, stdout=subprocess.PIPE)
drivelisto, err = drivelist.communicate()
driveLines = drivelisto.split('\n')
elif 'linux' in sys.platform:
listdrives=subprocess.Popen('mount', shell=True, stdout=subprocess.PIPE)
listdrivesout, err=listdrives.communicate()
for idx,drive in enumerate(filter(None,listdrivesout)):
listdrivesout[idx]=drive.split()[2]
# guess how it should be on mac os, similar to linux , the mount command should
# work, but I can't verify it...
elif 'macosx' ...
do the rest....

The above method for Linux is very crude, and will return drives like sys and procfs etc., if you want something more fine tuned, look into querying with python-dbus.

Finding usb properties/details using ruby-dbus and udisk

#!/usr/bin/env ruby

require 'dbus'

bus = DBus::SystemBus.instance
udisk_service = bus.service("org.freedesktop.UDisks")
udisk_udisk = udisk_service.object("/org/freedesktop/UDisks")
udisk_udisk.introspect
udisk_udisk.default_iface = "org.freedesktop.UDisks"
disk_list = udisk_udisk.EnumerateDevices[0]
disk_list.each { |disk|
udisk_udisk = udisk_service.object("#{disk}")
udisk_udisk.introspect
udisk_udisk_i = udisk_udisk["org.freedesktop.UDisks.Device"]
if udisk_udisk_i["DriveConnectionInterface"] == "usb" && udisk_udisk_i["DeviceIsPartition"]
#puts disk #Debug
puts "Device file: #{udisk_udisk_i["DeviceFile"]}"
puts "Is device mounted: #{udisk_udisk_i["DeviceIsMounted"]}"
puts "Is device mount path: #{udisk_udisk_i["DeviceMountPaths"]}"
puts "Device uuid: #{udisk_udisk_i["IdUuid"]}"
puts "Device label: #{udisk_udisk_i["IdLabel"]}"
puts ""
end
}

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

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/Debian\040wheezy\04020140723-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 :(

USB devices UDev and D-BUS

The udisks D-Bus service, obviously, only reports disks.

Just monitor udev directly (through libudev, through pyudev).

import pyudev
context = pyudev.Context()
monitor = pyudev.Monitor.from_netlink(context)
observer = pyudev.pygtk.GUDevMonitorObserver(monitor)
observer.connect('device-added', device_added_callback)
observer.connect('device-changed', device_changed_callback)
monitor.enable_receiving()
mainloop = gobject.MainLoop()
mainloop.run()

How to tell if a usb mass storage device is plugged in using dbus

If you could go with UDisks2 DBus interface the answer would be quite simple :-)
First of all, it seems that UDisks2 (and probably UDisks1, but I haven't it installed) handles only Mass Storage devices connected via USB, completely ignoring USB-connected MTP-devices and other kinds of USB devices such as printers and mice. Have a look:

Right now I have 6 USB devices connected:


alex@galene ~ $ /usr/sbin/lsusb | grep -v 'root hub'
Bus 001 Device 004: ID 058f:6362 Alcor Micro Corp. Flash Card Reader/Writer
Bus 001 Device 003: ID 09da:000a A4 Tech Co., Ltd Optical Mouse Opto 510D
Bus 008 Device 090: ID 04e8:6860 Samsung Electronics Co., Ltd GT-I9100 Phone [Galaxy S II], GT-P7500 [Galaxy Tab 10.1]
Bus 008 Device 075: ID 04b8:0007 Seiko Epson Corp. Printer
Bus 009 Device 002: ID 058f:6387 Alcor Micro Corp. Flash Drive
Bus 009 Device 088: ID 1058:1010 Western Digital Technologies, Inc. Elements External HDD

058f:6362 is an "all-in one" card reader w/ a SD-card currently plugged in.
058f:6387 and 1058:1010 are real USB Mass Storage devs, a flash and an external HDD respectively. 04e8:6860 is a Samsung Galaxy Note1 (an MTP-device) and the rest are the mouse and the printer.

Also I have an internal SATA drive.

Here's the current udisksctl status output (as you may see Galaxy Note is missing):


MODEL REVISION SERIAL DEVICE
--------------------------------------------------------------------------
Hitachi HDT725040VLA360 V5COA7EA VFM301R3196R5F sda
Generic Flash Disk 8.07 8D0147DF sdb
WDC WD1200BEVS-22RST0 04.01G04 WD-WXE108V72690 sdc
Generic USB CF Reader 1.01 058F312D81B sde
Generic USB SM Reader 1.02 058F312D81B sdf
Generic USB MS Reader 1.03 058F312D81B sdg
Generic USB SD Reader 1.00 058F312D81B sdd

To distinguish pluggable USB Mass storage devices from stationary drives one may check
'ConnectedBus' and ''Removable' properties. Please note that there's another property 'MediaRemovable' which corresponds to the physical media of the device. Here's the script which selects removable USB Mass Storage devices and prints their 'MediaRemovable' property


alex@galene ~ $ cat <<EOF | python -
> import dbus
> bus = dbus.SystemBus()
> ud_manager_obj = bus.get_object('org.freedesktop.UDisks2', '/org/freedesktop/UDisks2')
> om = dbus.Interface(ud_manager_obj, 'org.freedesktop.DBus.ObjectManager')
> for k,v in om.GetManagedObjects().iteritems():
> drive_info = v.get('org.freedesktop.UDisks2.Drive', {})
> if drive_info.get('ConnectionBus') == 'usb' and drive_info.get('Removable'):
> print("Device Path: %s, MediaRemovable: %s" % (k, drive_info['MediaRemovable']))
> block_devices = [''.join(chr(c) for c in device[dbus.String(u'org.freedesktop.UDisks2.Block')][dbus.String(u'PreferredDev‌​ice')] if chr(c) != '\x00' ) for device in om.GetManagerObjects().values() if dbus.String(u'org.freedesktop.UDisks2.Block') in device]
> EOF
Device Path: /org/freedesktop/UDisks2/drives/Generic_USB_SM_Reader_058F312D81B, MediaRemovable: 1
Device Path: /org/freedesktop/UDisks2/drives/Generic_Flash_Disk_8D0147DF, MediaRemovable: 1
Device Path: /org/freedesktop/UDisks2/drives/Generic_USB_SD_Reader_058F312D81B, MediaRemovable: 1
Device Path: /org/freedesktop/UDisks2/drives/WDC_WD1200BEVS_22RST0_WD_WXE108V72690, MediaRemovable: 0
Device Path: /org/freedesktop/UDisks2/drives/Generic_USB_CF_Reader_058F312D81B, MediaRemovable: 1
Device Path: /org/freedesktop/UDisks2/drives/Generic_USB_MS_Reader_058F312D81B, MediaRemovable: 1

Basically it solves the problem you're trying to solve. Rumors say that there're non-removable USB-connected storage devices but I haven't seen such.

Finding only disk drives using pyudev

Since udev only runs on Linux kernel (at least as of now), you could filter out by MAJOR number 8 which represents all SCSI/SATA disk driver based devices.

for device in context.list_devices(MAJOR='8'):
if (device.device_type == 'disk'):
print "{}, ({})".format(device.device_node, device.device_type)

On my system, your code outputs the following:

/dev/sda, (disk)
/dev/sdf, (disk)
/dev/sdb, (disk)
/dev/sdc, (disk)
/dev/sdd, (disk)
/dev/sde, (disk)
/dev/sr0, (disk)
/dev/loop0, (disk)
/dev/loop1, (disk)
/dev/loop2, (disk)
/dev/loop3, (disk)
/dev/loop4, (disk)
/dev/loop5, (disk)
/dev/loop6, (disk)
/dev/loop7, (disk)

After filtering by major number 8, I see the following output:

/dev/sda, (disk)
/dev/sdf, (disk)
/dev/sdb, (disk)
/dev/sdc, (disk)
/dev/sdd, (disk)
/dev/sde, (disk)

Note that you would also get USB hard drives and USB sticks in the list, since they also tend to use the same SCSI disk driver.

I'm not really sure if IDE hard drives are mapped as sdX or hdX with the latest 2.6 or 3.x kernels. Do not have an IDE hard drive to verify and long since I have had one. :D

UPDATE: The same device number page lists /dev/hdX to be the one used by IDE hard drives (and IDE cdroms too may be ?). If you want to filter these as well, I believe you could do something like this:

for device in context.list_devices(DEVTYPE='disk'):
major = device['MAJOR']
if major == '8' or major == '3':
print "{}, ({})".format(device.device_node, device.device_type)

Better way to script USB device mount in Linux

This seems to work combining /proc/partitions and the /sys/class/block approach ephimient took.

#!/usr/bin/python
import os
partitionsFile = open("/proc/partitions")
lines = partitionsFile.readlines()[2:]#Skips the header lines
for line in lines:
words = [x.strip() for x in line.split()]
minorNumber = int(words[1])
deviceName = words[3]
if minorNumber % 16 == 0:
path = "/sys/class/block/" + deviceName
if os.path.islink(path):
if os.path.realpath(path).find("/usb") > 0:
print "/dev/%s" % deviceName

I'm not sure how portable or reliable this is, but it works for my USB stick. Of course find("/usb") could be made into a more rigorous regular expression. Doing mod 16 may also not be the best approach to find the disk itself and filter out the partitions, but it works for me so far.



Related Topics



Leave a reply



Submit