597 lines
18 KiB
C++
Executable File
597 lines
18 KiB
C++
Executable File
#include "utils.h"
|
||
#include <QByteArray>
|
||
#include <QDateTime>
|
||
#include <QFile>
|
||
#include <QTextStream>
|
||
#include <QDir>
|
||
#include <QRegularExpression>
|
||
#include <QThread>
|
||
#include <QUuid>
|
||
#include <iostream>
|
||
#include <unistd.h>
|
||
#include <fcntl.h>
|
||
#include <sys/file.h>
|
||
#include <sys/stat.h>
|
||
#include <sys/types.h>
|
||
#include <memory>
|
||
#include <functional>
|
||
#include <QRegularExpression>
|
||
#include <QProcess>
|
||
|
||
#include "mylittleparse.h"
|
||
#include "mydefine.h"
|
||
|
||
QString Utils::m_sysRootPath = "/";
|
||
|
||
/**
|
||
* @brief initSysRootPath, 根据应用程序路径推断系统根目录
|
||
* @param qsAppPath,应用程序所在路径
|
||
* @note
|
||
* 本方法依赖于发布后应用程序会部署到目录/usr/bin中
|
||
* 1. grub引导中根目录为/root
|
||
* 2. livecd中根目录为/target
|
||
* 3. 正常使用中根目录为/
|
||
*/
|
||
void Utils::initSysRootPath(const QString& qsAppPath)
|
||
{
|
||
QString sysRootPath = qsAppPath;
|
||
if (sysRootPath.contains(DEFAULT_APP_PATH)) {
|
||
sysRootPath.replace(DEFAULT_APP_PATH, "/");
|
||
} else {
|
||
sysRootPath = "/";
|
||
}
|
||
|
||
m_sysRootPath = sysRootPath;
|
||
}
|
||
|
||
/**
|
||
* @brief customMessageHandler,日志重定向句柄
|
||
* @param type 日志类型,debug等
|
||
* @param context 上下文
|
||
* @param msg 日志信息
|
||
*/
|
||
void Utils::customMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg)
|
||
{
|
||
QByteArray localMsg = msg.toLocal8Bit();
|
||
QString strMsg("");
|
||
switch (type) {
|
||
case QtDebugMsg:
|
||
strMsg = QString("[Debug]");
|
||
break;
|
||
case QtWarningMsg:
|
||
strMsg = QString("[Warning]");
|
||
break;
|
||
case QtCriticalMsg:
|
||
strMsg = QString("[Critical]");
|
||
break;
|
||
case QtFatalMsg:
|
||
strMsg = QString("[Fatal]");
|
||
break;
|
||
default:
|
||
strMsg = QString("[Debug]");
|
||
break;
|
||
}
|
||
|
||
// 设置输出信息格式
|
||
QString strDateTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss ddd");
|
||
QString strMessage = strMsg + QString("DateTime:%1 ThreadId:%2 Message:%3 File:%4(%5)")
|
||
.arg(strDateTime).arg(QString::number(quintptr(QThread::currentThreadId()))).arg(localMsg.constData()).arg(context.file).arg(context.line);
|
||
|
||
std::cout << strMessage.toUtf8().data() << std::endl;
|
||
|
||
// 输出信息至文件中(读写、追加形式)
|
||
QString fileName = m_sysRootPath + PROC_LOG;
|
||
fileName.replace("//", "/");
|
||
QFile file(fileName);
|
||
file.open(QIODevice::ReadWrite | QIODevice::Append);
|
||
QTextStream stream(&file);
|
||
stream << strMessage << endl;
|
||
stream.flush();
|
||
file.close();
|
||
}
|
||
|
||
/**
|
||
* @brief 文件锁,锁定应用程序
|
||
* @param frontUid,锁定程序的用户id
|
||
* @return 锁文件的文件描述符
|
||
* @note 使用方式类似于QLockFile,后面时间充足时也可以自行封装一个独立类来处理文件锁
|
||
* 这个文件锁的作用是,在关机时关机模块会自行校验/tmp/lock路径下的锁文件,如果存在则提醒用户xx程序在运行不允许关机等
|
||
*/
|
||
int Utils::lockProgram(int frontUid)
|
||
{
|
||
QDir dir(LOCK_FILE_PATH);
|
||
if (!dir.exists()) {
|
||
dir.mkdir(LOCK_FILE_PATH);
|
||
chmod(LOCK_FILE_PATH, S_IRWXU | S_IRWXG | S_IRWXO);
|
||
}
|
||
|
||
int lock_file_fd = ::open(LOCK_FILE, O_CREAT | O_RDWR, 0666);
|
||
if (0 > lock_file_fd) {
|
||
return -2;
|
||
}
|
||
fchmod(lock_file_fd, S_IRWXU | S_IRWXG | S_IRWXO);
|
||
|
||
int lock_ret = flock(lock_file_fd, LOCK_EX | LOCK_NB);
|
||
if (0 > lock_ret) {
|
||
return -11;
|
||
}
|
||
|
||
ftruncate(lock_file_fd, 0);
|
||
|
||
char write_string[PID_STRING_LEN] = { 0 };
|
||
snprintf(write_string, PID_STRING_LEN, "%d\n%s\n", frontUid, BACKUP_CLI_NAME);
|
||
write(lock_file_fd, write_string, strlen(write_string));
|
||
fdatasync(lock_file_fd);
|
||
return lock_file_fd;
|
||
}
|
||
|
||
/**
|
||
* @brief 解除应用程序文件锁
|
||
* @param lock_file_fd 锁文件的文件描述符
|
||
* @return 0,解除成功;1,解除失败
|
||
*/
|
||
int Utils::unLockProgram(int lock_file_fd)
|
||
{
|
||
int lock_ret = flock(lock_file_fd, LOCK_UN);
|
||
if (lock_ret < 0) {
|
||
qCritical("unlock fail!");
|
||
return 1;
|
||
}
|
||
qDebug("unlock success!");
|
||
rmLockFile();
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* @brief 删除锁文件
|
||
* @return bool
|
||
*/
|
||
bool Utils::rmLockFile()
|
||
{
|
||
bool res = QFile::remove(LOCK_FILE);
|
||
if (!res)
|
||
qCritical() << QString("remove %s fail").arg(LOCK_FILE);
|
||
return res;
|
||
}
|
||
|
||
/**
|
||
* @brief 检查/etc/.bootinfo是否存在并可读,里面存放备份分区的UUID等信息
|
||
* @return bool
|
||
*/
|
||
bool Utils::checkBootInfoExists()
|
||
{
|
||
QString bootinfoPath = Utils::m_sysRootPath + BOOTINFO_PATH;
|
||
bootinfoPath.replace("//", "/");
|
||
QFile bootinfoFile(bootinfoPath);
|
||
if (!bootinfoFile.exists()) {
|
||
qCritical("%s is not exists!", qUtf8Printable(bootinfoPath));
|
||
return false;
|
||
}
|
||
if (!bootinfoFile.open(QIODevice::ReadOnly)) {
|
||
qCritical("%s file can't open!", qUtf8Printable(bootinfoPath));
|
||
return false;
|
||
}
|
||
bootinfoFile.close();
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @brief 获取备份分区的UUID
|
||
* @return 备份分区的UUID
|
||
*/
|
||
QString Utils::getBackupPartitionUuid()
|
||
{
|
||
QString bootinfoPath = Utils::m_sysRootPath + BOOTINFO_PATH;
|
||
bootinfoPath.replace("//", "/");
|
||
|
||
MyLittleParse parse(bootinfoPath);
|
||
QString restoreUuid;
|
||
parse.find("RECOVERY_DEV_UUID", restoreUuid);
|
||
|
||
if (restoreUuid.isEmpty()) {
|
||
QString fstab = Utils::m_sysRootPath + FSTAB_PATH;
|
||
fstab.replace("//", "/");
|
||
QFile file(fstab);
|
||
if (file.exists()) {
|
||
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
QTextStream in(&file);
|
||
while (!in.atEnd()) {
|
||
QString line = in.readLine();
|
||
if (line.startsWith("UUID=") && line.contains("/backup")) {
|
||
int indexOfSpace = line.indexOf(QRegularExpression("[ \t]"), 0);
|
||
QString uuid = line.mid(0, indexOfSpace);
|
||
uuid.replace("UUID=", "");
|
||
restoreUuid = uuid.trimmed();
|
||
|
||
break ;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return restoreUuid;
|
||
}
|
||
|
||
/**
|
||
* @brief 路径不存在则创建,不支持递归创建
|
||
* @param path
|
||
*/
|
||
void Utils::mkdir(const QString& path)
|
||
{
|
||
QDir dir(path);
|
||
if (!dir.exists())
|
||
dir.mkdir(path);
|
||
}
|
||
|
||
/**
|
||
* @brief 创建路径,支持递归创建
|
||
* @param path
|
||
* @return bool
|
||
*/
|
||
bool Utils::mkpath(const QString& path)
|
||
{
|
||
QDir dir(path);
|
||
if (!dir.exists()) {
|
||
return dir.mkpath(path);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @brief 备份还原时,需要排除绑定挂载的路径,因为数据不是存放在挂载路径内
|
||
* @param excludes,存放需要排除的绑定路径
|
||
*/
|
||
void Utils::excludeFstabBindPath(QStringList &excludes)
|
||
{
|
||
QString fstabPath = Utils::m_sysRootPath + FSTAB_PATH;
|
||
fstabPath.replace("//", "/");
|
||
|
||
QFile file(fstabPath);
|
||
if (!file.open(QIODevice::ReadOnly))
|
||
return;
|
||
QTextStream in(&file);
|
||
while (!in.atEnd()) {
|
||
const QString &line = in.readLine();
|
||
if (line.startsWith("#"))
|
||
continue ;
|
||
if (line.startsWith("UUID="))
|
||
continue ;
|
||
if (line.startsWith("/dev/"))
|
||
continue ;
|
||
if (line.isEmpty())
|
||
continue ;
|
||
if (!line.contains("bind"))
|
||
continue ;
|
||
|
||
// 配置文件/etc/fstab每行6个域: <file system> <mount point> <type> <options> <dump> <pass>, 形如:
|
||
// UUID=232f5fb4-53e0-46b9-ba9b-22bfec64f2a2 /boot ext4 rw,relatime 0 0
|
||
QStringList list = line.split(QRegularExpression("[ \t]"));
|
||
QStringList fields;
|
||
for (int i = 0; i < list.size(); ++i) {
|
||
QString field = list.at(i);
|
||
field = field.trimmed();
|
||
if (field.isEmpty())
|
||
continue;
|
||
fields << field;
|
||
}
|
||
// 配置文件/etc/fstab每行6个域,第二个域为挂载路径, 第一个域就是待排除路径
|
||
if (6 == fields.size())
|
||
excludes << fields.at(0);
|
||
}
|
||
|
||
file.close();
|
||
}
|
||
|
||
/**
|
||
* @brief 生成rsync --exclude-from排除路径规则文件
|
||
* @return
|
||
*/
|
||
bool Utils::generateExcludePathsFile()
|
||
{
|
||
QString excludeFile = Utils::m_sysRootPath + EXCLUDE_FILE_PATH;
|
||
excludeFile.replace("//", "/");
|
||
|
||
QFile excludePathFile(excludeFile);
|
||
// 暂时改为每次都重写文件内容,以便能随版本更新排除路径
|
||
if (!excludePathFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||
qCritical("%s create failed", qUtf8Printable(excludeFile));
|
||
return false;
|
||
}
|
||
|
||
QTextStream in(&excludePathFile);
|
||
in << "/backup" << endl; //分区
|
||
// in << "/boot/efi" << endl;
|
||
in << "/cdrom" << endl;
|
||
in << "/dev" << endl;
|
||
// efi原始目录在/boot/efi,备份到目标目录为/efi下,再还原时已经单独处理了,批量还原时应该屏蔽此目录
|
||
in << "/efi" << endl;
|
||
// 安全模块会将文件/usr/share/kysec-utils/data/readonly_list中的文件列表限制只读,无法修改、备份(包含扩展属性时)、删除等
|
||
// 现在里面仅有/etc/uid_list,先暂时排除掉;等后续安全模块有其它保护方案后再进一步修改
|
||
in << "/etc/uid_list" << endl;
|
||
in << "/ghost" << endl; //ghost镜像文件
|
||
in << "/lost+found" << endl;
|
||
in << "/media" << endl;
|
||
in << "/mnt" << endl;
|
||
in << "/proc" << endl;
|
||
in << "/run" << endl;
|
||
in << "/swap_file" << endl;
|
||
in << "/sys" << endl; //添加*(/sys/*),表示如果/sys目录不存在,则会拷贝/sys,但不会拷贝/sys下的内容
|
||
in << "/tmp" << endl;
|
||
in << "/var/lib/docker/overlay2" << endl;
|
||
// 安卓兼容的这个里面很多文件都是设置了特殊扩展文件属性,lsetxattr无法设置成功,听取安卓兼容模块同事的意见不用管这个文件夹,其实是从home下挂载的
|
||
in << "/var/lib/kmre" << endl;
|
||
in << "/var/lib/udisks2" << endl;
|
||
in << "/var/log" << endl;
|
||
|
||
// 系统安装后有的会将/data/home /data/root挂载到的/home /root上,实际文件是存放在/data/home /data/root下面,为了统一标准保留/home /root排除/data/home /data/root
|
||
QStringList excludes;
|
||
Utils::excludeFstabBindPath(excludes);
|
||
for (const QString& item : excludes) {
|
||
in << item << endl;
|
||
}
|
||
in.flush();
|
||
|
||
excludePathFile.close();
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @brief 判断目录是否存在
|
||
* @param 目录路径
|
||
* @return true,存在;false,不存在
|
||
*/
|
||
bool Utils::isDirExist(const QString& fullDirName)
|
||
{
|
||
QDir dir(fullDirName);
|
||
if (dir.exists())
|
||
return true;
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* @brief 获取系统备份还原排除路径列表, rsync --exclude-from排除路径规则文件中读取
|
||
* @return 系统备份还原排除路径列表
|
||
*/
|
||
QStringList Utils::getFromExcludePathsFile()
|
||
{
|
||
QStringList list;
|
||
list << "/backup";
|
||
// list << "/boot/efi";
|
||
list << "/cdrom";
|
||
list << "/dev";
|
||
list << "/efi";
|
||
list << "/etc/uid_list";
|
||
list << "/ghost";
|
||
list << "/lost+found";
|
||
list << "/media";
|
||
list << "/mnt";
|
||
list << "/proc";
|
||
list << "/run";
|
||
list << "/swap_file";
|
||
list << "/sys";
|
||
list << "/tmp";
|
||
list << "/var/lib/docker/overlay2";
|
||
list << "/var/lib/kmre";
|
||
list << "/var/lib/udisks2";
|
||
list << "/var/log";
|
||
|
||
// 系统安装后有的会将/data/home /data/root挂载到的/home /root上,实际文件是存放在/data/home /data/root下面
|
||
QStringList excludes;
|
||
Utils::excludeFstabBindPath(excludes);
|
||
for (const QString& item : excludes) {
|
||
list << item;
|
||
}
|
||
|
||
return list;
|
||
}
|
||
|
||
/**
|
||
* @brief 生成Uuid
|
||
* @return UUID
|
||
*/
|
||
QString Utils::createUuid()
|
||
{
|
||
QString uuid;
|
||
do {
|
||
uuid = QUuid::createUuid().toString();
|
||
} while (uuid == AUTO_BACKUP_UUID || uuid == FACTORY_BACKUP_UUID);
|
||
return uuid;
|
||
}
|
||
|
||
/**
|
||
* @brief 将列表中内容写入指定文件中
|
||
* @param fileName,文件全路径
|
||
* @param lines,内容列表
|
||
* @return true,成功写入;false,写入失败
|
||
*/
|
||
bool Utils::writeFileByLines(const QString& fileName, const QStringList& lines)
|
||
{
|
||
QFile file(fileName);
|
||
if (!file.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
|
||
return false;
|
||
}
|
||
|
||
QTextStream out(&file);
|
||
for (const QString& line : lines) {
|
||
out << line << endl;
|
||
}
|
||
out.flush();
|
||
file.close();
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @brief 判断文件是否存在
|
||
* @param fileName 文件明
|
||
* @return bool,true-存在;false-不存在
|
||
* @author zhaominyong
|
||
* @since 2021/05/29
|
||
*/
|
||
bool Utils::filsExists(const QString &fileName)
|
||
{
|
||
struct stat buffer;
|
||
return (stat(fileName.toStdString().data(), &buffer) == 0);
|
||
}
|
||
|
||
/**
|
||
* @brief 记录备份日志
|
||
* @param line,日志内容
|
||
* @return bool, true-成功;false-失败
|
||
* @author zhaominyong
|
||
* @since 2021/05/29
|
||
* @note
|
||
* 因为系统恢复成功后马上需要reboot,但是此时的文件缓存可能还未能落盘,故此增加此函数,其中调用了系统函数fdatasync确保缓存落盘
|
||
*/
|
||
bool Utils::writeBackupLog(QString line)
|
||
{
|
||
line = line + "\n";
|
||
QString logFile = Utils::getSysRootPath() + BACKUP_LOG_TEXT_PATH;
|
||
logFile.replace("//", "/");
|
||
std::string fileName(logFile.toStdString());
|
||
// 判断文件是否存在
|
||
bool exists = filsExists(logFile);
|
||
|
||
std::unique_ptr<std::FILE, int (*)(std::FILE*)> fp(std::fopen(fileName.data(), "a+"), std::fclose);
|
||
|
||
if (!fp) {
|
||
std::perror("file opening failed!");
|
||
return false;
|
||
}
|
||
|
||
if (!exists) {
|
||
std::fputs("#This file must not be deleted and it is for the backup tool.\n", fp.get());
|
||
std::fputs("#0:new system backup\n", fp.get());
|
||
std::fputs("#1:update system backup\n", fp.get());
|
||
std::fputs("#2:new data backup\n", fp.get());
|
||
std::fputs("#3:update data backup\n", fp.get());
|
||
std::fputs("#4:restore system\n", fp.get());
|
||
std::fputs("#5:retaining user data\n", fp.get());
|
||
std::fputs("#6:restore data\n", fp.get());
|
||
std::fputs("#8:delete backup\n", fp.get());
|
||
std::fputs("#for example: 17-04-25 10:43:56,{uuidxxxxx},0,this is a note\n", fp.get());
|
||
}
|
||
std::fputs(line.toStdString().data(), fp.get());
|
||
std::fflush(fp.get());
|
||
fdatasync(fileno(fp.get()));
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @brief 立即写文件
|
||
* @param fileName 文件名,包含路径
|
||
* @param content 文件内容
|
||
* @return bool
|
||
*/
|
||
bool Utils::syncWriteFile(const QString &fileName, const QString& content)
|
||
{
|
||
std::unique_ptr<std::FILE, int (*)(std::FILE*)> fp(std::fopen(fileName.toStdString().data(), "a+"), std::fclose);
|
||
|
||
if (!fp) {
|
||
std::perror("file opening failed!");
|
||
return false;
|
||
}
|
||
|
||
QStringList lines = content.split("\n");
|
||
for (const QString& line : lines) {
|
||
std::fputs(line.toStdString().data(), fp.get());
|
||
}
|
||
std::fflush(fp.get());
|
||
fdatasync(fileno(fp.get()));
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @brief 将字节大小转换为GB等表示的字符串
|
||
* @param size,qint64,空间大小,单位字节
|
||
* @return GB/MB/KB等表示的字符串型大小
|
||
*/
|
||
QString Utils::StringBySize(qint64 size)
|
||
{
|
||
#define GB (1024 * 1024 * 1024)
|
||
#define MB (1024 * 1024)
|
||
#define KB (1024)
|
||
|
||
float fcount = size * 1.0;
|
||
|
||
if (size > GB)
|
||
return QString(QString::number(fcount / GB, 10, 2) + "GB");
|
||
else if (size > MB)
|
||
return QString(QString::number(fcount / MB, 10, 2) + "MB");
|
||
else
|
||
return QString(QString::number(fcount / KB, 10, 2) + "KB");
|
||
}
|
||
|
||
/**
|
||
* @brief 获取挂接的移动设备列表
|
||
* @return MOUNTPOINT,PATH键值对列表
|
||
* @author zhaominyong
|
||
* @since 2021/06/07
|
||
* @note
|
||
* for bug 59636 【cpm】【HUAWEI】【KOS】【L0】【V3试制】备份时选择移动设备,会出现一个dev/sdc的路径(一般+必现+不常用功能)
|
||
* QStorageInfo::mountedVolumes获取的磁盘列表区分不出来是否移动设备
|
||
*/
|
||
QHash<QString, QString> Utils::getRemovableStorages()
|
||
{
|
||
QHash<QString, QString> removalbeStorages;
|
||
|
||
QProcess process_lsblk;
|
||
process_lsblk.start("lsblk -P -o PATH,RM,TYPE,MOUNTPOINT,TRAN");
|
||
if (!process_lsblk.waitForStarted(-1)) {
|
||
return removalbeStorages;
|
||
}
|
||
if (!process_lsblk.waitForFinished(-1)) {
|
||
return removalbeStorages;
|
||
}
|
||
QString result = process_lsblk.readAllStandardOutput();
|
||
/*
|
||
result like bellow :
|
||
PATH="/dev/sdb" RM="1" TYPE="disk" MOUNTPOINT="" TRAN="usb"
|
||
PATH="/dev/sdb4" RM="1" TYPE="part" MOUNTPOINT="/media/zhaominyong/31 GB" TRAN=""
|
||
PATH="/dev/sr0" RM="1" TYPE="rom" MOUNTPOINT="/media/zhaominyong/Kylin-Desktop-V10-SP1" TRAN=""
|
||
*/
|
||
QStringList items = result.split("\n");
|
||
QStringList usbDevs;
|
||
for (QStringList::const_iterator it = items.constBegin(); it != items.constEnd(); ++it) {
|
||
const QString &line = (*it);
|
||
if (line.contains(" TRAN=\"usb\"")) {
|
||
QStringList storageAttrs = line.split("\" ");
|
||
if (5 != storageAttrs.size())
|
||
continue ;
|
||
QString path = storageAttrs.at(0);
|
||
path = path.replace("PATH=\"", "");
|
||
usbDevs.append(path);
|
||
}
|
||
if (line.contains(" RM=\"1\" ") || line.contains(" MOUNTPOINT=\"/media/")) {
|
||
if (line.contains(" TYPE=\"rom\" "))
|
||
continue;
|
||
if (line.contains(" MOUNTPOINT=\"\""))
|
||
continue;
|
||
|
||
// "PATH RM TYPE MOUNTPOINT TRAN" for each row, split by "\" "
|
||
QStringList storageAttrs = line.split("\" ");
|
||
if (5 != storageAttrs.size())
|
||
continue ;
|
||
QString path = storageAttrs.at(0);
|
||
path = path.replace("PATH=\"", "");
|
||
bool isSubPart = false;
|
||
for (const QString& usbDev : usbDevs) {
|
||
if (path.contains(usbDev)) {
|
||
isSubPart = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!isSubPart)
|
||
continue;
|
||
QString mount_point = storageAttrs.at(3);
|
||
mount_point = mount_point.replace("MOUNTPOINT=\"", "");
|
||
mount_point = mount_point.left(mount_point.length() - 1);
|
||
removalbeStorages.insert(path, mount_point);
|
||
}
|
||
}
|
||
|
||
return removalbeStorages;
|
||
}
|