背景:
对于U盘挂载本身linux就有udev这样的工具支持;但是由于依赖udev挂载规则,有时候不太可控(其实我碰到的问题就是在设备终端插着U盘开机,无法识别并且挂载U盘,需要重新插拔一次,才能识别U盘。),所以决定编写代码来检测并且挂载。
1.挂载U盘呢,无非就是mkdir创建目录,然后使用mount命令挂载;(这里假设U盘的设备节点是/dev/sda1)
mkdir -p /mnt/usb
mount /dev/sda1 /mnt/usb // mount命令挂载根据需要加一些参数,例如指定挂载U盘的系统格式或者U盘内容显示字符编码等等
2.那如何检测U盘的热插拔呢?可以使用定时器轮询检测/dev目录下的设备节点的变化,从而去实现热插拔。这种方法可行,但是太耗资源,不推荐。我这里使用到的是QT自带的一个类-------QFileSystemWatcher
稍微说一下这个类哈。
QFileSystemWatcher通过监控指定路径的列表,监视文件系统中文件和目录的变更。调用addPath()函数可以监控一个特定的文件或目录。如果需要监控多个路径,可以使用addPaths()。通过使用removePath()和removePaths()函数来移除现有路径。QFileSystemWatcher检查添加到它的每个路径,已添加到QFileSystemWatcher的文件可以使用的files()函数进行访问,目录则使用directories()函数进行访问。当一个文件被修改、重命名或从磁盘上删除时,会发出fileChanged()信号。同样,当一个目录或它的内容被修改或删除时,会发射directoryChanged()信号。需要注意:文件一旦被重命名或从硬盘删除,目录一旦从磁盘上删除,QFileSystemWatcher将停止监控。也不能只是定义一个临时对象,需要new出一个对象,不然无法起到监测的效果。其实这个类依赖linux自己的一个inotify工具。
3.这样思路就很明显了。通过QFileSystemWatcher这个类监测/dev目录下的变化,然后通过检测到的信号去获取U盘的设备信息。然后拿到信息之后,对设备进行挂载或者卸载的操作。
4.那么,怎么获取U盘设备的信息呢?这里使用到的方法是,使用linux的blkid命令,将命令输出到控制台的U盘设备信息拿到,然后解析出所需要的U盘设备信息;
blkid -d -c /dev/null
/dev/mmcblk3p2: UUID="bbe3ba31-a0cb-4982-86dd-959f9e01d32c" TYPE="ext2"
/dev/sda1: UUID="D87072FE7072E324" TYPE="ntfs"
/dev/sdb4: LABEL="Ubuntu 16.0" UUID="B4FE-5315" TYPE="vfat"
/dev/sdc1: LABEL="机密文件" UUID="12E2766CE27653C7" TYPE="ntfs"
5.那执行命令后,如何去获取控制台信息呢?这里使用的QT的QProcess这个类;
6.那获取到控制台信息后,如何解析所需的U盘信息呢?这里使用的是QT的QRegExp,通过正则表达式来解析。
注意:
a. blkid这个命令一定要 -c /dev/null这个参数。因为如果不加的话,blkid这个命令会在/dev目录下生成临时文件.blkid.tab .blkid.tab.old这就相当于在/dev目录下生成创建了文件,也就是修改了/dev目录,这个会混淆影响QFileSystemWatcher的监测。
b. 在设备开机的时候,最好是执行命令 mount -t tmpfs none /mnt 这样就把/mnt目录挂载在一个临时文件系统上。关机之后,/mnt目录下的所有文件都会清空。
c. 如果优盘是fat32格式的,那么如果卷标是中文,那么会在linux上显示乱码。(这个听说是可以通过转码解决掉的,但是我没有成功);如果是ntfs格式的U盘,那么中文卷标显示正常。所以,在这里如果是fat32的优盘,我回去显示它的UUID。ubuntu也是这么做的。
好啦,基本思路已经说完了,直接上代码吧。
UdiskWatcher.cpp
#include "UdiskWatcher.h"
#include
#include
#include
#include
#include
// 定义设备挂载U盘的最大个数
#define USB_COUNT 10
USBDeviceInfo::USBDeviceInfo()
: strDev("")
, strUUID("")
, strLABEL("")
, strTYPE("")
, isBusy(false)
{
}
bool USBDeviceInfo::operator==(const USBDeviceInfo &value)
{
return (strUUID == value.strUUID);
}
UdiskWatcher::UdiskWatcher(QObject *parent)
: QObject(parent)
, m_systemWatcher(NULL)
{
m_systemWatcher = new QFileSystemWatcher(this);
if (!m_systemWatcher->addPath("/dev"))
{
qDebug() << "fileSystemWatcher invalid path!";
}
// 由于QFileSystemWathcher初次开机时检测不到已经存在的U盘设备节点,所以不会挂载U盘;
// 所以选择在UdiskWatcher构造的时候尝试检测是否插入U盘,挂载一次U盘
udiskHandle();
connect(m_systemWatcher, &QFileSystemWatcher::directoryChanged, this, &UdiskWatcher::udiskHandle);
}
UdiskWatcher::~UdiskWatcher()
{
}
void UdiskWatcher::udiskHandle()
{
refreshUSBdeviceList();
int devListSize = m_usbDevList.size();
int count = 0;
for (int i = 0; i < devListSize; i++)
{
if (m_usbDevListMounted.contains(m_usbDevList.at(i)))
{
count++;
}
}
qDebug("count = %d , devListSize = %d , m_usbDevListMounted.size() = %d", count, devListSize, m_usbDevListMounted.size());
if ((count == m_usbDevListMounted.size()) && (count == devListSize))
{
qDebug("no usb changed");
return; // 两个list相等,不需要操作
}
QList usbDevListAdded; // 刷新U盘设备节点列表后,新增的U盘设备列表
QList usbDevListRemoved; // 刷新U盘设备节点列表后,移除的U盘设备列表
usbDevListAdded.clear();
usbDevListRemoved.clear();
for (int j = 0; j < m_usbDevListMounted.size(); j++)
{
if (!m_usbDevList.contains(m_usbDevListMounted.at(j)))
{
usbDevListRemoved.append(m_usbDevListMounted.at(j));
}
}
qDebug("need to umount usb count = %d", usbDevListRemoved.size());
// 卸载已经移除的U盘设备
for (int j = 0; j < usbDevListRemoved.size(); j++)
{
umountUsb(usbDevListRemoved.at(j));
}
if (m_usbDevListMounted.size() < USB_COUNT)
{
for (int i = 0; i < devListSize; i++)
{
if (!m_usbDevListMounted.contains(m_usbDevList.at(i)))
{
usbDevListAdded.append(m_usbDevList.at(i));
}
}
qDebug("need to mount usb count = %d", usbDevListAdded.size());
// 挂载新增的U盘设备
for (int i = 0; i < usbDevListAdded.size(); i++)
{
mountUsb(usbDevListAdded.at(i));
}
}
}
QList UdiskWatcher::getCurrentUSBDevice()
{
return m_usbDevListMounted;
}
bool UdiskWatcher::mountUsb(USBDeviceInfo dev)
{
qDebug("DeviceManager::mountUsb\n");
#ifndef _WIN32
QString output;
QString usbName;
// 只有U盘是ntfs类型的时候,中文卷标才能正常显示;所以当U盘类型是fat32或者其他类型的时候,显示UUID;这里与ubuntu的处理是一样的;
if (dev.strTYPE == "ntfs" && !dev.strLABEL.isEmpty())
{
usbName = "/mnt/" + dev.strLABEL;
}
else
{
usbName = "/mnt/" + dev.strUUID;
}
if (QFile::exists(usbName))
{
qDebug() << "derectory has exist!";
return true;
}
if (!myProcess("mkdir", QStringList() << "-p" << usbName, output))
{
qDebug() << "mkdir usb derectory failed!";
return false;
}
if (!myProcess("/bin/mount", QStringList() << "-o" << "sync" << "-o" << "shortname=mixed" << "-o" << "utf8=yes" << dev.strDev << usbName, output))
{
qDebug() << "mount usb failed!";
return false;
}
if (!myProcess("touch", QStringList() << usbName, output))
{
qDebug() << "touch usb derectory failed!";
return false;
}
m_usbDevListMounted.append(dev);
qDebug("DeviceManager::mountUsb success! m_usbDevListMounted.size = %d\n", m_usbDevListMounted.size());
return true;
#else
Q_UNUSED(dev)
return false;
#endif
}
bool UdiskWatcher::umountUsb(USBDeviceInfo dev)
{
qDebug("DeviceManager::umountUsb\n");
#ifndef _WIN32
QString output;
QString usbName;
if (dev.strTYPE == "ntfs" && !dev.strLABEL.isEmpty())
{
usbName = "/mnt/" + dev.strLABEL;
}
else
{
usbName = "/mnt/" + dev.strUUID;
}
if (!QFile::exists(usbName))
{
qDebug() << "derectory has not exist!";
return true;
}
if (!myProcess("/bin/umount", QStringList() << usbName, output))
{
qDebug() << "umount usb failed!";
return false;
}
if (!myProcess("rmdir", QStringList() << usbName, output))
{
qDebug() << "rmdir usb derectory failed!";
return false;
}
m_usbDevListMounted.removeAll(dev);
qDebug("DeviceManager::umountUsb success! m_usbDevListMounted.size = %d\n", m_usbDevListMounted.size());
return true;
#else
return false;
#endif
}
void UdiskWatcher::refreshUSBdeviceList()
{
#ifndef _WIN32
m_usbDevList.clear();
QString output;
myProcess("blkid", QStringList() << "-d" << "-c" << "/dev/null", output);
// 正则表达式,取出U盘设备节点列表
QStringList itemList;
myRegExp("/dev/sd[a-z].*\\n", output, itemList);
for (int i = 0; i < itemList.size(); i++)
{
USBDeviceInfo info;
QStringList tempList;
if (myRegExp("/dev/sd[a-z][1-9]", itemList.at(i), tempList) > 0)
{
info.strDev = tempList.at(0);
tempList.clear();
}
if (myRegExp("LABEL=\".*\"", itemList.at(i), tempList) > 0)
{
int size = tempList.at(0).size();
info.strLABEL = tempList.at(0).mid(7, (size-8));
tempList.clear();
}
if (myRegExp("UUID=\".*\"", itemList.at(i), tempList) > 0)
{
int size = tempList.at(0).size();
info.strUUID = tempList.at(0).mid(6, (size-7));
tempList.clear();
}
if (myRegExp("TYPE=\".*\"", itemList.at(i), tempList) > 0)
{
int size = tempList.at(0).size();
info.strTYPE = tempList.at(0).mid(6, (size-7));
tempList.clear();
}
m_usbDevList.append(info);
}
if (m_usbDevList.size() < 1)
{
qDebug() << "未检测到u盘设备!";
return;
}
#endif
}
int UdiskWatcher::myRegExp(const QString pattern, const QString &text, QStringList &list)
{
QRegExp regExp;
int capCount = 0;
int pos = 0;
if (pattern.isEmpty() || text.isEmpty())
{
return -1;
}
regExp.setMinimal(true);
regExp.setPattern(pattern);
regExp.indexIn(text);
list.clear();
while ((pos = regExp.indexIn(text, pos)) != -1)
{
list.push_back(regExp.cap(0));
pos += regExp.matchedLength();
capCount++;
}
return capCount;
}
bool UdiskWatcher::myProcess(QString cmd, QStringList param, QString &output)
{
QProcess process;
process.start(cmd, param);
// 等待进程启动
if (!process.waitForStarted())
{
qDebug() << "命令执行开始失败\n";
return false;
}
process.closeWriteChannel();
// 用于保存进程的控制台输出
QByteArray procOutput;
while (!process.waitForFinished(300))
{
qDebug() << "命令执行结束失败\n";
return false;
}
procOutput = process.readAll();
output = procOutput;
return true;
}
摘自:使用QT在linux上识别挂载多个U盘 - 代码先锋网
windows磁盘显示:https://www.freesion.com/article/93071348301/
https://www.cnblogs.com/gethope5/p/15662175.html
QT读写U盘文件 :https://baijiahao.baidu.com/s?id=1652637076957647136&wfr=spider&for=pc
ARM平台QT检测U盘插拔
Arm平台 Qt检测U盘插拔(三)-----蛋疼的recv()阻塞