更好的方式在Linux中脚本化USB设备挂载

4 投票
5 回答
13222 浏览
提问于 2025-04-15 20:02

我正在为一个设备编写一个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 个回答

0

在看了这个关于如何像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()
1

我不太确定这个东西有多通用。此外,这些信息可能也可以通过 D-Bus 从 udisksHAL 获取,但我系统上没有这两个,所以我无法尝试。不过在这里看起来还是相当准确的:

$ 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
4

这个方法看起来是把 /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取模的方法可能也不是找到磁盘和过滤分区的最佳方式,但到目前为止对我来说是有效的。

撰写回答