yhkylin-backup-tools/backup-daemon/systemrestoreproxy.cpp

618 lines
22 KiB
C++
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "systemrestoreproxy.h"
#include <QDateTime>
#include <QDir>
#include <QDebug>
#include <unistd.h>
#include <sys/reboot.h>
#include "../common/utils.h"
#include "mymountproxy.h"
IMPLEMENT_DYNCREATE(SystemRestoreProxy)
/**
* @brief 构造函数
*/
SystemRestoreProxy::SystemRestoreProxy()
{
m_bSuccess = false;
m_p = nullptr;
m_bRetainUserData = false;
}
/**
* @brief 析构函数
*/
SystemRestoreProxy::~SystemRestoreProxy()
{
delete m_p;
}
/**
* @brief 环境检测
* @return false,检测失败;true,检测成功
*/
bool SystemRestoreProxy::checkEnvEx()
{
qDebug() << "SystemRestoreProxy::checkEnvEx invoke begin";
// 1、检测.user.txt是否存在
m_userFile = Utils::getSysRootPath() + BACKUP_SNAPSHOTS_PATH + "/" + m_backupWrapper.m_uuid + "/" + PATHS_USER_FILE;
m_userFile.replace("//", "/");
if (!Utils::fileExists(m_userFile)) {
qCritical(".user.txt文件不存在");
emit checkResult(int(BackupResult::WRITE_BACKUP_PATHS_TO_USER_FAILED));
return false;
}
// 2、检测.exclude.user.txt是否存在
m_excludeUserFile = Utils::getSysRootPath() + BACKUP_SNAPSHOTS_PATH + "/" + m_backupWrapper.m_uuid + "/" + EXCLUDE_PATHS_USER_FILE;
m_excludeUserFile.replace("//", "/");
if (!Utils::fileExists(m_excludeUserFile)) {
qCritical(".exclude.user.txt文件不存在");
emit checkResult(int(BackupResult::WRITE_EXCLUDE_BACKUP_PATHS_TO_USER_FAILED));
return false;
}
// 3、检测还原点是否存在
m_backupPath = Utils::getSysRootPath() + BACKUP_SNAPSHOTS_PATH + "/" + m_backupWrapper.m_uuid + "/data";
m_backupPath.replace("//", "/");
if (Utils::isDirEmpty(m_backupPath)) {
qCritical("还原点{uuid}/data目录不存在");
emit checkResult(int(BackupResult::INC_NOT_FOUND_DIR));
return false;
}
// 4、检测xml中的还原点是否还存在
QString xmlPath = Utils::getSysRootPath() + BACKUP_XML_PATH;
xmlPath.replace("//", "/");
ParseBackupList parse(xmlPath);
m_backupPoint = parse.findBackupPointByUuid(m_backupWrapper.m_uuid);
if (m_backupPoint.m_uuid.isEmpty()) {
qCritical("xml中还原点不存在");
emit checkResult(int(BackupResult::INC_NOT_FOUND_DIR));
return false;
}
m_curUuid = m_backupWrapper.m_uuid;
emit checkResult(int(BackupResult::CHECK_ENV_SUCCESS));
qDebug() << "SystemRestoreProxy::checkEnvEx invoke end";
return true;
}
/**
* @brief 执行还原逻辑
*/
void SystemRestoreProxy::doWorkEx()
{
qDebug() << "SystemRestoreProxy::doWorkEx invoke begin";
// 1、校验
if (!checkEnvEx())
return ;
// 2、还原efi(兼容旧版本的备份)
if (!restoreEfi()) {
qCritical("/boot/efi目录同步失败");
emit checkResult(int(BackupResult::EFI_RSYNC_FAIL));
return ;
}
// 3、还原系统
restoreSystem();
qDebug() << "SystemRestoreProxy::doWorkEx invoke end";
}
/**
* @brief 还原efi(兼容旧版本的备份)
* @return
*/
bool SystemRestoreProxy::restoreEfi()
{
qDebug() << "SystemRestoreProxy::restoreEfi invoke begin";
// 以读写方式重新挂载boot分区因为有的机器默认以只读挂载
Utils::remountBoot();
// 是否有/boot/efi目录
QString efiPath = Utils::getSysRootPath() + "/boot/efi";
efiPath.replace("//", "/");
if (!Utils::isDirEmpty(efiPath)) {
// 1、修复源数据
repairEfi();
sync();
// 2、重新rw读写挂载
Utils::remountEfi();
// 3、同步efi
return rsyncEfi();
}
qDebug() << "SystemRestoreProxy::restoreEfi invoke end";
return true;
}
/**
* @brief 修复efi目录
*/
void SystemRestoreProxy::repairEfi()
{
QString qsEfiPath = m_backupPath + "/efi";
if (!Utils::isDirEmpty(qsEfiPath)) {
// 存在/efi说明是老备份数据尽量修正老数据
QStringList args;
args << "-f";
args << qsEfiPath;
QString newPath = m_backupPath + "/boot";
args << newPath;
QProcess::execute("mv", args);
}
}
/**
* @brief 同步efi
*/
bool SystemRestoreProxy::rsyncEfi()
{
QString efiPath = m_backupPath + "/boot/efi/";
if (Utils::isDirEmpty(efiPath))
efiPath = efiPath = m_backupPath + "/efi/";
if (Utils::isDirEmpty(efiPath))
return true;
QStringList args = getRsyncArgs(SystemRestoreScene::EFI_RESTORE);
QString mountPath = Utils::getSysRootPath() + "/boot/efi/";
mountPath.replace("//", "/");
args << efiPath << mountPath;
m_p = new RsyncPathToDirProcess(this);
bool result = m_p->start(args);
delete m_p;
m_p = nullptr;
return result;
}
/**
* @brief 根据场景获取rsync命令参数
* @param scene场景
* @return 组装好的rsync的参数信息
*/
QStringList SystemRestoreProxy::getRsyncArgs(SystemRestoreScene scene)
{
QStringList args;
args << "-avAHXr";
args << "--info=progress2";
args << "--no-inc-recursive";
args << "--ignore-missing-args";
args << "--delete";
QDir dataDir(m_srcPath + "/data");
QFile file(m_srcPath + "/etc/uid_list");
QDir efiDir(m_srcPath + "/boot/efi");
QStringList excludes;
switch (scene) {
case SystemRestoreScene::RESTORE_SYSTEM_WITH_DATA :
args << "--exclude=/home";
args << "--exclude=/root";
if (Utils::isHuawei990()) {
args << "--exclude=/data";
} else {
args << "--exclude=/data/usershare";
}
// 保留指纹数据,用户密码、角色、权限、生物识别等信息不需要改变
args << "--exclude=/var/lib/biometric-auth";
args << "--exclude=/data/sec_storage_data";
args << "--exclude=/etc/passwd";
args << "--exclude=/etc/shadow";
args << "--exclude=/etc/group";
args << "--exclude=/etc/gshadow";
args << "--exclude=/etc/sudoers";
args << "--exclude=/data/home";
args << "--exclude=/data/root";
// 云图片作为桌面背景的路径属于用户数据
args << "--exclude=/var/lib/AccountsService";
// 域用户相关信息,还原后保持不退域
args << "--exclude=/etc/sssd";
args << "--exclude=/var/lib/sss";
args << "--exclude=/usr/share/sssd";
args << "--exclude=/etc/ipa";
args << "--exclude=/etc/krb5.keytab";
args << "--exclude=/etc/krb5.conf";
args << "--exclude=/var/lib/ipa-client";
args << "--exclude=/etc/nsswitch.conf";
args << "--exclude=/etc/pam.d";
args << "--exclude=/etc/hosts";
args << "--exclude=/etc/hostname";
args << "--exclude=/etc/hedron";
args << "--exclude=/etc/kcm";
args << "--exclude=/usr/hedron/hedronagent";
args << "--exclude=/etc/.kyinfo";
args << "--exclude=/etc/LICENSE";
args << "--exclude=/etc/ssl/certs";
args << "--exclude=/usr/share/ca-certificates";
args << "--exclude=/etc/NetworkManager";
args << "--exclude=/var/lib/pam";
// 安装kylin
args << "--exclude=/usr/share/applications/kylin-os-installer.desktop";
args << "--exclude=*/.local/share/applications/kylin-os-installer.desktop";
args << "--exclude=/etc/xdg/autostart/kylin-os-installer.desktop";
// 此处不要break因为还需要排除SYSTEM_RESTORE中的项
case SystemRestoreScene::SYSTEM_RESTORE :
// 还原工具不还原自身
if (Utils::getSysRootPath() != CASPER_ROOT_PATH) {
// 试用模式时可以还原/target下面的备份还原工具
args << "--exclude=/usr/bin/backup-daemon";
args << "--exclude=/usr/bin/kybackup";
}
args << "--exclude=/usr/bin/mount_fstab_efi";
args << "--exclude=/usr/bin/backup-auto-efi";
args << "--exclude=/usr/bin/backup-auto";
args << "--exclude=/usr/bin/rsync";
args << "--exclude=/usr/share/rsync";
args << "--exclude=/usr/share/initramfs-tools/hooks/kybackup-hooks";
args << "--exclude=/usr/share/initramfs-tools/scripts/*/kybackup";
// 还原后仍然保持激活状态
args << "--exclude=/etc/LICENSE";
args << "--exclude=/etc/.kyinfo";
args << "--exclude=/etc/.kyactivation";
args << "--exclude=/etc/.kyhwid";
// 文件安全箱
args << "--exclude=/data/security-dir";
// 以前的出厂备份和grub备份没有备份/data还原时需要判断/data是否存在如不存在需要屏蔽掉不然会将主机上的/data删除造成问题
// 此为兼容以前备份的老数据而改,等以后老的备份估计不存在了可已去掉
if (!dataDir.exists()) {
args << "--exclude=/data";
}
if (!file.exists()) {
args << "--exclude=/etc/uid_list";
}
// 为兼容以前的老备份数据,增加下面几行
if (efiDir.isEmpty()) {
args << QString("--exclude=/boot/efi");
}
// 系统安装后有的会将/data/home /data/root挂载到的/home /root上实际文件是存放在/data/home /data/root下面
Utils::excludeFstabBindPath(excludes);
// 自定义备份的路径也需要跳过,不进行还原
Utils::excludeCustomizePath(excludes);
for (const QString& item : excludes) {
QDir itemDir(m_srcPath + item);
// 以后统一用/home /root这种路径 兼容老备份数据原来的U盘备份在mksquashfs时排除bind挂载的任意一方时都备份不上
if (item == "/data/home") {
QDir homeDir(m_srcPath + "/home");
if (!homeDir.isEmpty()) {
args << QString("--exclude=/data/home");
} else if (!itemDir.isEmpty()) {
args << QString("--exclude=/home");
} else {
args << QString("--exclude=/data/home");
args << QString("--exclude=/home");
}
continue;
} else if (item == "/data/root") {
QDir homeDir(m_srcPath + "/root");
if (!homeDir.isEmpty()) {
args << QString("--exclude=/data/root");
} else if (!itemDir.isEmpty()) {
args << QString("--exclude=/root");
} else {
args << QString("--exclude=/data/root");
args << QString("--exclude=/root");
}
continue;
}
args << QString("--exclude=") + item;
}
args << "--exclude-from" << m_excludeUserFile;
args << "--files-from" << m_userFile;
break ;
case SystemRestoreScene::EFI_RESTORE :
break ;
default:
return args;
}
return args;
}
/**
* @brief 系统还原
*/
void SystemRestoreProxy::restoreSystem()
{
// 停止安全初始化服务以防过高的CPU占有率因为还原时安全初始化服务会逐个文件打标记造成cpu占有率超高系统卡顿
Utils::stopKysecInit();
// 停止网络服务,以防网络更新
Utils::stopNetwork();
// 本地系统备份没有img挂载故下面两个路径相等
m_srcPath = m_backupPath;
QString destPath = Utils::getSysRootPath();
QStringList args;
// 自动更新的备份还原时保留用户数据
if (m_curUuid == AUTO_BACKUP_UUID || m_backupWrapper.m_type == BackupType::RESTORE_SYSTEM_WITH_DATA) {
args = getRsyncArgs(SystemRestoreScene::RESTORE_SYSTEM_WITH_DATA);
m_bRetainUserData = true;
} else {
args = getRsyncArgs(SystemRestoreScene::SYSTEM_RESTORE);
}
args << m_srcPath + "/";
destPath += "/";
destPath.replace("//", "/");
args << destPath;
m_p = new RsyncPathToDirProcess(this);
connect(m_p, &RsyncPathToDirProcess::progress, this, &SystemRestoreProxy::progress);
connect(m_p, &RsyncPathToDirProcess::finished, this, [&](bool result) {
if (result) {
QString time = QDateTime::currentDateTime().toString("yy-MM-dd hh:mm:ss");
// Utils::writeBackupLog(time + "," + m_curUuid + "," + QString::number(m_backupWrapper.m_type) + ",,," + QString::number(m_backupWrapper.m_frontUid));
Utils::writeBackupLog(time + "," + m_curUuid + "," + QString::number(m_backupWrapper.m_type) + ",,,," + m_backupPoint.m_backupName);
// 华为9a0等机型设定1分钟后关机重启原因运行时还原备份还原工具本身可能会造成运行中的备份还原崩溃从而无法执行到后面的自动重启代码故在此预设。
// 不再只华为机型使用此方法,将全部机型都改为此方法,缩短操作链
// if (Utils::isHuawei990())
{
QString msg;
Utils::executeCMD("shutdown -r +1", msg);
qDebug() << msg;
}
Utils::updateSyncFile();
Utils::wait(2);
QString fileIfSync = Utils::getSysRootPath() + FILE_IF_SYNC;
fileIfSync.replace("//", "/");
QFileInfo file(fileIfSync);
QDateTime beginTime = file.fileTime(QFileDevice::FileModificationTime);
QProcess::execute("sync");
Utils::wait(20);
Utils::updateSyncFile();
while (1) {
Utils::wait(2);
QFileInfo file1(fileIfSync);
QDateTime UpdateTime = file1.fileTime(QFileDevice::FileModificationTime);
if (UpdateTime > beginTime)
break;
}
if (FACTORY_BACKUP_UUID == m_curUuid && m_backupWrapper.m_type == BackupType::RESTORE_SYSTEM) {
// 出厂还原有的机器上删除/home/xxx有残留故在此再强制删除一下sudo rm -rf命令一遍还删除不了报错无法删除/home/xx/.config:目录非空,应该是删除后又自动生成了),多删除几次还不是非常干净,^_^
removeHome(QString(Utils::getSysRootPath() + "/home").replace("//", "/"));
removeHome(QString(Utils::getSysRootPath() + "/data/home").replace("//", "/"));
}
// 最后再一次还原home目录以查缺补漏因为系统还原过程中~/.config等目录中的部分文件可能会更新会引起部分bug如任务栏固定图标还原不完整等
this->rsyncAgain();
::sync();
// 2209后新增了一些依赖还原到以前的4.0.13版后缺少依赖无法运行故此最后还得还原backup-daemon和kybackup本身
// 兼容Qt库从5.12.8升级到5.15,系统还原的最后也将备份还原工具本身还原
// QString version = Utils::getBackupVersion();
// if (version.contains("4.0.13"))
if (Utils::getSysRootPath() != CASPER_ROOT_PATH)
{
// initrd.img已经还原为旧状态需要将新版本的backup-auto-efi等脚本文件更新到initrd.img中这样我们修改后的新逻辑才能生效
// QString msg;
// Utils::executeCMD("update-initramfs -u", msg);
// qDebug() << msg;
// 解决bug:182576 【2203自适应升级2309】【2303自适应升级2309】升级成功还原系统失败应该是有更新包修改了UEFI固件查找引导器文件的引导地址
// 故系统还原完成后重新执行下grub-install命令
QString msg;
Utils::executeCMD("grub-install", msg);
qDebug() << msg;
// 写入标记rsync_backup_self:${UUID}到文件/etc/file_if_sync中表示需要还原backup-daemon和kybackup本身
// QString line("rsync_backup_self:");
// line += m_curUuid;
// Utils::syncWriteFile(fileIfSync, line);
// this->undoRestoreData();
// 加一层保险(因为有些机型上使用"update-initramfs -u"命令会失败华为9006c的2203版等
// QString version = Utils::getBackupVersion();
// if (Utils::isHuawei990() && version.contains("4.0.13"))
this->rsyncBackupSelf();
::sync();
}
emit this->workResult(result);
Utils::wait(3);
reboot(RB_AUTOBOOT);
} else {
emit this->workResult(result);
}
});
m_p->start(args, false);
}
/**
* @brief 获取备份点里存在家目录的用户列表
* @param void
* @return 获取备份点里存在家目录的用户列表
*/
QStringList SystemRestoreProxy::getBackupPointUsers()
{
// 根据备份点里的/home或/data/home的子目录进行统计
QString homePath = m_backupPath + "/home";
homePath.replace("//","/");
QStringList users = getBackupPointUsers(homePath);
QString dataHomePath = m_backupPath + "/data/home";
dataHomePath.replace("//","/");
users.append(getBackupPointUsers(dataHomePath));
return users;
}
QStringList SystemRestoreProxy::getBackupPointUsers(const QString& path)
{
QDir dir(path);
if (dir.exists()) {
return dir.entryList(QDir::NoDotAndDotDot | QDir::Dirs);
} else {
return QStringList();
}
}
/**
* @brief 删除home子目录此处主要用于删除用户残留信息
* @param path
* @return
*/
void SystemRestoreProxy::removeHome(const QString& path)
{
qDebug() << "SystemRestoreProxy::removeHome invoke begin";
QDir dir(path);
if (dir.exists()) {
QStringList retainUsers = getBackupPointUsers();
qDebug() << retainUsers;
QString subcmd("rm -rf ");
QStringList list = dir.entryList(QDir::NoDotAndDotDot | QDir::Dirs);
for (const QString& item : list) {
// 出厂备份里面的/home/oem等保留
if (item == "oem")
continue ;
if (retainUsers.contains(item))
continue ;
QString subPath = path;
subPath += "/";
subPath += item;
QDir subDir(subPath);
subDir.removeRecursively();
qDebug() << (subcmd + subPath);
QProcess::execute(subcmd + subPath);
}
// bool needRm = false;
// QString subcmd("rm -rf ");
// QString delDirs;
// QStringList list = dir.entryList(QDir::NoDotAndDotDot | QDir::Dirs);
// for (const QString& item : list) {
// // 出厂备份里面的/home/oem等保留
// if (item == "oem")
// continue ;
// if (retainUsers.contains(item))
// continue ;
// QString subPath = path;
// subPath += "/";
// subPath += item;
// QDir subDir(subPath);
// subDir.removeRecursively();
// delDirs += "${rootmnt}";
// delDirs += path;
// delDirs += "/";
// delDirs += item;
// delDirs += " ";
// needRm = true;
// }
// if (needRm) {
// QString cmd = QString("bash -c \"%1\" ").arg(subcmd + delDirs);
// cmd.replace("${rootmnt}", "");
// QProcess::execute(cmd);
// }
}
qDebug() << "SystemRestoreProxy::removeHome invoke end";
}
/**
* @brief 再一次同步
* @note 意图通过多次同步查缺补漏,如:
* 1、因为系统还原过程中~/.config等目录中的部分文件可能会更新会引起部分bug
* a. 配网,打开奇安信浏览器,触发自动更新(或者软件商店手动更新),且奇安信默认固定在任务栏;
* b. 下载安装微信固定任务栏新装任意第三方软件均可会删除desktop文件
* c. 上面两种操作都会触发任务栏配置更新通过监控任务栏配置文件在系统还原过程中可以明显的看到在还原1%的时候,配置文件还原到历史文件,桌面图标还原,然后下一秒配置文件就又更新成当前最新的了
*/
void SystemRestoreProxy::rsyncAgain()
{
// 保留用户数据还原直接
if (m_bRetainUserData)
return ;
QString destPath = Utils::getSysRootPath() + "/home/";
destPath.replace("//", "/");
Utils::mkpath(destPath);
RsyncPathToDirProcess * process = new RsyncPathToDirProcess(this);
QStringList args;
args << "-avAHXr";
args << m_srcPath + "/home/";
args << destPath;
process->start(args);
}
/**
* @brief 未做完的还原需要在重启后initrd阶段执行备份还原脚本时再进行还原
* @note 当前只有备份工具自身未还原
*/
void SystemRestoreProxy::undoRestoreData()
{
QString destPath = Utils::getSysRootPath() + "/var/log/yhkylin-backup-tools/";
destPath.replace("//", "/");
Utils::mkpath(destPath);
RsyncPathToDirProcess * process = new RsyncPathToDirProcess(this);
QStringList args;
args << "-avAHXr";
args << m_srcPath + "/usr/bin/backup-daemon";
args << destPath;
process->start(args);
args.clear();
args << "-avAHXr";
args << m_srcPath + "/usr/bin/kybackup";
args << destPath;
process->start(args);
}
/**
* @brief 还原备份还原工具自身
* @note 在undoRestoreData基础上增加的一层保险因为有些机型上使用"update-initramfs -u"命令会失败华为9006c的2203版等
* 本来想在/etc/profile.d/目录或/etc/init.d/目录等下面增加一个.sh脚本文件以开机时自动执行但试验了几种方式均都未执行原因未知。
*/
void SystemRestoreProxy::rsyncBackupSelf()
{
QString destPath = Utils::getSysRootPath() + "/usr/bin/";
destPath.replace("//", "/");
Utils::mkpath(destPath);
RsyncPathToDirProcess * process = new RsyncPathToDirProcess(this);
QStringList args;
args << "-avAHXr";
args << m_srcPath + "/usr/bin/kybackup";
args << destPath;
process->start(args);
args.clear();
args << "-avAHXr";
args << m_srcPath + "/usr/bin/backup-daemon";
args << destPath;
process->start(args);
}