更好的方式在Linux中脚本化USB设备挂载
我正在为一个设备编写一个Python模块,这个设备可以和用户插入的USB闪存盘进行交互。用户可以把USB闪存盘插入设备的USB插槽,设备会自动把数据写入闪存盘,而不需要用户干预。如果设备在用户插入USB闪存盘时已经开机,我已经通过D-Bus连接上了,并且设置好了自动挂载的程序。但是现在出现了一个新问题:如果在设备关机时插入了闪存盘,会发生什么呢?这时我就收不到D-Bus的插入事件,也无法获取关于闪存盘的任何信息,直到设备开机后。
我找到了一种方法,可以通过扫描/proc中的USB设备来获取设备节点(/dev/sd?),具体做法是调用:
ls /proc/scsi/usb-storage
这样可以获取到SCSI设备的信息,如果你查看那个文件夹里的每个文件,就能看到详细信息。
接着,我从usb-storage记录中提取出厂商、产品和序列号字段,生成一个标识字符串,然后用这个字符串来查找:
ll /dev/disc/by-id/usb_[vendor]_[product]_[serial_number]-0:0
这样我就可以解析结果,得到相对路径:
../../sdc
然后,我就可以挂载USB闪存盘了。
这个过程比较繁琐,几乎都是基于文本的,而且当有人输入奇怪的字符或者非标准的序列号字符串时,很容易出错。这个方法在我拥有的两个USB闪存盘上都能工作。我尝试过从/var/log/messages中提取输出,但那也只是文本比较而已。其他命令如lsusb、fdisk、udevinfo、lsmod等的输出也只显示了一半所需的数据。
我的问题是:在没有D-Bus消息的情况下,如何在不需要用户干预的情况下,确定分配给USB闪存盘的/dev设备,而不需要提前知道插入设备的具体信息?
谢谢,抱歉写了这么长的内容。
5 个回答
在看了这个关于如何像Ubuntu的Nautilus那样操作的帖子后,我找到了一些建议,决定通过命令行来访问udisks。
你需要关注的是大容量存储设备类。只需提供设备文件,比如说:/dev/sdb。然后你可以使用d.mount()和d.mount_point来查看它被挂载到哪里。
之后还有一个类可以帮助你找到许多相同的USB设备,以便控制挂载、卸载和弹出一大堆都有相同标签的设备。
(如果你不加任何参数运行这个,它会对所有SD设备生效。这对于一个“自动挂载所有设备”的脚本来说可能很有用。)
import re
import subprocess
#used as a quick way to handle shell commands
def getFromShell_raw(command):
p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
return p.stdout.readlines()
def getFromShell(command):
result = getFromShell_raw(command)
for i in range(len(result)):
result[i] = result[i].strip() # strip out white space
return result
class Mass_storage_device(object):
def __init__(self, device_file):
self.device_file = device_file
self.mount_point = None
def as_string(self):
return "%s -> %s" % (self.device_file, self.mount_point)
""" check if we are already mounted"""
def is_mounted(self):
result = getFromShell('mount | grep %s' % self.device_file)
if result:
dev, on, self.mount_point, null = result[0].split(' ', 3)
return True
return False
""" If not mounted, attempt to mount """
def mount(self):
if not self.is_mounted():
result = getFromShell('udisks --mount %s' % self.device_file)[0] #print result
if re.match('^Mounted',result):
mounted, dev, at, self.mount_point = result.split(' ')
return self.mount_point
def unmount(self):
if self.is_mounted():
result = getFromShell('udisks --unmount %s' % self.device_file) #print result
self.mount_point=None
def eject(self):
if self.is_mounted():
self.unmount()
result = getFromShell('udisks --eject %s' % self.device_file) #print result
self.mount_point=None
class Mass_storage_management(object):
def __init__(self, label=None):
self.label = label
self.devices = []
self.devices_with_label(label=label)
def refresh(self):
self.devices_with_label(self.label)
""" Uses udisks to retrieve a raw list of all the /dev/sd* devices """
def get_sd_list(self):
devices = []
for d in getFromShell('udisks --enumerate-device-files'):
if re.match('^/dev/sd.$',d):
devices.append(Mass_storage_device(device_file=d))
return devices
""" takes a list of devices and uses udisks --show-info
to find their labels, then returns a filtered list"""
def devices_with_label(self, label=None):
self.devices = []
for d in self.get_sd_list():
if label is None:
self.devices.append(d)
else:
match_string = 'label:\s+%s' % (label)
for info in getFromShell('udisks --show-info %s' % d.device_file):
if re.match(match_string,info): self.devices.append(d)
return self
def as_string(self):
string = ""
for d in self.devices:
string+=d.as_string()+'\n'
return string
def mount_all(self):
for d in self.devices: d.mount()
def unmount_all(self):
for d in self.devices: d.unmount()
def eject_all(self):
for d in self.devices: d.eject()
self.devices = []
if __name__ == '__main__':
name = 'my devices'
m = Mass_storage_management(name)
print m.as_string()
print "mounting"
m.mount_all()
print m.as_string()
print "un mounting"
m.unmount_all()
print m.as_string()
print "ejecting"
m.eject_all()
print m.as_string()
我不太确定这个东西有多通用。此外,这些信息可能也可以通过 D-Bus 从 udisks 或 HAL 获取,但我系统上没有这两个,所以我无法尝试。不过在这里看起来还是相当准确的:
$ for i in /sys/class/block/*; do > /sbin/udevadm info -a -p $i | grep -qx ' SUBSYSTEMS=="usb"' && > echo ${i##*/} > done sde sdf sdg sdh sdi sdj sdj1 $ cd /sys/class/block/ $ for i in *; do [[ $(cd $i; pwd -P) = */usb*/* ]] && echo $i; done sde sdf sdg sdh sdi sdj sdj1
这个方法看起来是把 /proc/partitions
和 /sys/class/block
结合起来,像ephimient那样使用。
#!/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
我不太确定这个方法有多通用或者可靠,但对我的USB闪存驱动器来说是有效的。当然,find("/usb")
可以做得更严格一些,使用更复杂的正则表达式。用16取模的方法可能也不是找到磁盘和过滤分区的最佳方式,但到目前为止对我来说是有效的。