使用Golang实现跨平台USB存储设备的热插拔检测和文件获取

发表时间: 2024-01-05 23:43

摘要

使用程序来监测USB存储设备热插拔检测, 主要原理是先熟练使用各系统自带的管理工具及命令使用方法, 先在各平台系统中通过命令获取执行结果, 分析命令返回的结果是否是我们需要的, 在这个过程中可能需要反复尝试和验证。然后通过程序解析命令返回的结果, 在此基础之上再逐步加入自己的功能。

Windows平台检测原理及实现

要实现在Windows平台检测USB存储设备的插入, 需要先了解Windows平台的一款工具: WIndows Management Instrumentation Command-line,简称:WMIC, 该工具主要用于检索计算机上逻辑磁盘驱动器的信息, 而满足我们需求的命令如下:

wmic logicaldisk where dirvetype=2 get deviceid

下面解释一下这条命令的参数含义:

  • wmic - "wmic"是Windows中用于执行WMI查询的命令行工具,WMI(Windows Management Instrumentation)是一种用于管理和监视系统信息的基于Windows操作系统的架构。
  • logicaldisk - 该参数实际是WMI的一个类, 用于表示计算机上的逻辑磁盘。逻辑磁盘是计算机中的磁盘分区或硬盘驱动器。
  • where dirvetype=2 - 这是一个筛选条件,用于仅选择磁盘类型为2的逻辑磁盘,在这里,类型为2表示逻辑驱动器是一个硬盘驱动器。
  • get deviceid - 这部分指定了要检索的信息, 即:逻辑磁盘的设备标识符(Device ID)。设备标识符是逻辑磁盘的唯一标识符, 通常是一个字母, 表示逻辑磁盘的盘符。

综合来讲, 该命令的目的是检索计算机上所有硬盘驱动器的设备标识符,在执行命令后, 我们会得到一个列表, 其中包含了所有硬盘驱动器的设备标识符。例如C:、D:等。

下面这个是我的电脑, 上面已经接入一个U盘外存储设备, 如图:

本地电脑移动设备盘符

从图中可以看到, 电脑本身的驱动器盘符为C:和D: , U盘外存储设备的盘符为F:。

以Windows10为例, 打开系统自带的Windows PowerShell, 尝试执行以下命令:

wmic logicaldisk get deviceid

注意, 该命令我没有加上筛选条件:where dirvetype=2, 看看返回什么结果,如图:

从图中可以看到, wmic命令给我们返回了与上面一样的三个盘符: C:、D:和F:

返回本地电脑所有盘符

现在我们再加上筛选条件:where drivetype=2 看下结果:

加上筛选条件返回的盘符

从图中可以看到, 只返回了U盘的盘符F: , 因此, 利用Go语言实现Windows平台的USB存储设备的方法总结如下:

  • 直接执行命令:wmic logicaldisk where dirvetype=2 get deviceid, 处理该命令的返回内容, 从中提取盘符。
  • 将该盘符作为根目录, 遍历该盘符下的所有文件和文件夹, 实现USB存储设备中所有文件的全路径。

实现上述步骤的参考代码如下:

func DetectWinDevice() error {	driveMap := make(map[string]bool)	cmd := "wmic"	args := []string{"logicaldisk", "where", "drivetype=2", "get", "deviceid"}	out, err := exec.Command(cmd, args...).Output()	if err != nil {		return err	}	s := bufio.NewScanner(bytes.NewReader(out))	for s.Scan() {		line := s.Text()		if strings.Contains(line, ":") {			rootPath := strings.TrimSpace(line) + string(os.PathSeparator)			driveMap[rootPath] = true		}	}	for k := range driveMap {		err = filepath.Walk(k, visit)		if err != nil {			return err		}	}	return nil}

在上面的代码中, 利用Go语言执行命令:wmic logical disk where drivetype=2 get device, 之后按行读取命令返回的结果, 并判断每一行中是否包含冒号, 如果找到则将盘符保存, 该盘符便是USB存储设备的盘符。

将获取的盘符作为根目录,开始进行文件编译, 以下是文件遍历的函数:

func visit(path string, info os.FileInfo, err error) error {	if err != nil {		return nil	}	if info.IsDir() {		fmt.Printf("Directory: %s\n", path)	} else {		fmt.Printf("文件路径: %s\n", path)	}	return nil}

将程序编译运行一下, 成功打印出我的U盘里的所有文件全路径,如图:

在Windows上获取U盘所有文件

Linux平台检测原理及实现

在Linux平台中,可以首先使用系统自带的"df"命令查看一下当前所有的文件系统对应的挂载点,。这里我在电脑上安装了虚拟机,在虚拟机中安装了ubuntu系统, 通过虚拟机设置添加USB设备, 在ubuntu系统中通过df命令可以看到加载的U盘设备,如图:

使用df命令显示所有磁盘

从图中可以看到, U盘存储设备对应的文件系统路径为:/dev/sdb1。那么对于Linux系统中所有的设备类型, 我们怎么知道是USB存储设备呢, 这里需要使用到Linux系统自带的一个名为udevadm的工具, 完整命令如下:

udevadm info -q property -n device

下面详细了解一下udevadm的原理和使用方法。

udev是Linux系统中用于管理设备的守护程序, 它通过监听内核事件并在设备插入、删除或其它事件发生时触发相应的规则来执行操作。udevadm工具用于与udev守护程序进行交互, 提供了一种方便的方式来查询和管理设备信息。

info参数实际是udevadm工具的一个子命令, 用于显示有关指定设备的信息。

-q property这部分参数指定了要查询的信息的类型。在这种情况下, property表示要获取设备的属性信息。

-n device这里的device就是上面的设备路径, 例如:/dev/sdb1, 这部分参数指定要查询信息的设备节点, 在后面的程序实现中,一般都是遍历所有的设备节点, 然后对命令返回结果进行过滤。

在Linux中输入以上命令, 看看命令返回结果:

找到识别USB设备的关键特征

从上图中可以看到, 有一个关键条目内容为: ID_USB_DRIVER=usb-storage,该条内容可作为识别USB存储器的关键特征。如果感兴趣, 可以自行选择其它设备查看下结果。

到这里, 在Linux系统上检测USB存储设备插入的方法逐渐清晰:

  • 首先通过"df"命令获取当前所有的设备节点标识符, 保存到一个列表中。
  • 封装一个USB存储设备检测的函数, 通过执行上面命令, 处理命令返回内容并判断是否有关键内容:ID_USB_DRIVER=usb-storage来检测是否有USB存储设备插入。
  • 遍历设备节点列表,通过检测函数自动判断, 如果有找到对应的设备节点。
  • 遍历该设备节点下所有文件夹和文件, 获取所有文件的全路径。

按照以上思路, 检测Linux系统USB存储设备插入的参考代码如下:

func Detect() error {	driveMap := make(map[string]bool)	dfPattern := regexp.MustCompile("^(\/[^ ]+)[^%]+%[ ]+(.+)$")	cmd := "df"	out, err := exec.Command(cmd).Output()	if err != nil {		log.Printf("Error calling df: %s", err)	}	s := bufio.NewScanner(bytes.NewReader(out))	for s.Scan() {		line := s.Text()		if dfPattern.MatchString(line) {			device := dfPattern.FindStringSubmatch(line)[1]			rootPath := dfPattern.FindStringSubmatch(line)[2]			if ok := isUSBStorage(device); ok {				driveMap[rootPath] = true			}		}	}	for k := range driveMap {		err = filepath.Walk(k, visit)		if err != nil {			fmt.Printf("error walking the path %v: %v\n", k, err)		}	}	return nil}

判断是否为USB设备的关键函数代码如下:

func isUSBStorage(device string) bool {	deviceVerifier := "ID_USB_DRIVER=usb-storage"	cmd := "udevadm"	args := []string{"info", "-q", "property", "-n", device}	out, err := exec.Command(cmd, args...).Output()	if err != nil {		log.Printf("Error checking device %s: %s", device, err)		return false	}	if strings.Contains(string(out), deviceVerifier) {		return true	}	return false}

在上面的检测代码中, 我们使用了正则来匹配df命令返回的每行结果, 从结果中取出根路径和设备名, 利用udevadm的ID_USB_DRIVER=usb-storage属性判断其是否为USB设备。

由于Linux系统是一个虚拟机镜像,因此要将代码编译成Linux平台的二进制程序并运行, 当Linux系统能够识别USB设备后, 运行程序, 可以看到能够成功获取U盘上所有文件的全路径,如图:

在Linux中成功获取到U盘所有文件

MacOS平台检测原理及实现

在MacOS平台中, 有一个名为system_profiler的命令行工具, 该工具可用于获取系统硬件和软件信息的详细报告。以下是system_profiler工具常用的参数和对应的信息类型:

基本系统信息

  • system_profiler SPSoftwareDataType:提供关于操作系统版本、内核版本等信息。
  • system_profiler SPHardwareDataType:显示硬件相关信息,例如:处理器、内存、图形卡等。

网络信息

  • system_profiler SPNetworkDataType:显示网络相关信息, 包括网络连接状态、接口、IP地址等。

存储信息

  • system_profiler SPSerialATADataType:提供关于SATA存储设备的信息。
  • system_profiler SPNVMeDataType:显示NVMe存储设备信息。

图形与显示

  • system_profiler SPDisplayDataType: 提供关于图形卡和显示器的信息。

音频与音视频设备

  • system_profiler SPAudioDataType:显示音频设备的信息。
  • system_profiler SPFireWireDataType:提供FireWire设备的信息。

USB设备信息

  • system_profiler SPUSBDataType: 显示USB设备的信息,如:厂商、型号、连接状态等。

蓝牙设备信息

  • system_profiler SPBluetoothDataType:提供关于蓝牙设备的信息。

电源信息

  • system_profiler SPPowerDataType: 显示与电源相关的信息, 如:电池状态、充电器信息等。

软件安装信息

  • system_profiler SPInstallHistoryDataType:显示系统上安装的软件的历史记录。

用户信息

  • system_profiler SPUserDataType:提供关于用户的信息,如:用户名、用户组等。

这里我们主要用到获取USB设备信息, 执行以下命令先看下结果:

system_profiler SPUSBDataType

在插入U盘存储设备的情况下,返回信息如下:

在MacOS上识别USB设备关键特征

这里可以通过匹配Mount Point行内容来作为移动存储设备插入规则, 参考代码如下:

func Detect() error {	driveMap := make(map[string]bool)	macOSPattern := regexp.MustCompile("^.*Mount Point: (.+)$")	cmd := "system_profiler"	args := []string{"SPUSBDataType"}	out, err := exec.Command(cmd, args...).Output()	if err != nil {		return err	}	s := bufio.NewScanner(bytes.NewReader(out))	for s.Scan() {		line := s.Text()		if macOSPattern.MatchString(line) {			d := macOSPattern.FindStringSubmatch(line)[1]			driveMap[d] = true		}	}	for k := range driveMap {		err = filepath.Walk(k, visit)		if err != nil {			fmt.Printf("error walking the path %v: %v\n", k, err)		}	}	return nil}

将该代码编译成在macos平台上执行, 结果如下:

成功获取到U盘文件

另外还有一个命令也能列出磁盘信息,输入:diskutil list 命令,返回结果如下:

使用diskutil list命令查找移动磁盘

从上图可以看到, U盘对应的设备路径为:/dev/disk4, 接着使用命令:diskutil info /dev/disk4, 返回结果如下:

找到匹配移动USB设备的特征

总结

在本文中, 我们分别在Windows、Linux和macOS平台上探索了USB存储设备插入检测的原理及使用Go语言实现自动化检测的方法, 其中,MacOS平台具备多种检测方法, 如何稳定的检测多种USB存储设备还有待进一步测试。虽然初步达到了我们想要的结果, 但因为时间和实验条件有限, 没有对更多的移动存储设备和不同版本的系统做兼容性测试, 以后有机会会顺便做测试, 遇到需要兼容的情况再做整体总结和更新。