Commit message: fix: Do not use the "/tmp" directory

https://github.com/linuxdeepin/deepin-clone/issues/16
https://bugzilla.opensuse.org/show_bug.cgi?id=1130388
CVE: CVE-2019-13227
CWE: CWE-61
Vulnerability description: In GUI mode, deepin-clone before 1.1.3 creates a log file at the fixed path /tmp/.deepin-clone.log as root, and follows symlinks there. An unprivileged user can prepare a symlink attack there to create or overwrite files in arbitrary file system locations. The content is not attacker controlled.

Project: deepin-clone
File: helper.cpp

bool Helper::isPartcloneFile(const QString &fileName)
{
    return processExec(QStringLiteral("partclone.info %1").arg(fileName)) == 0;
}

Project: deepin-clone
File: helper.cpp

int Helper::processExec(QProcess *process, const QString &command, int timeout, QIODevice::OpenMode mode)
{
    m_processStandardOutput.clear();
    m_processStandardError.clear();

    QEventLoop loop;
    QTimer timer;

    timer.setSingleShot(true);
    timer.setInterval(timeout);

    timer.connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
    loop.connect(process, static_cast<void(QProcess::*)(int)>(&QProcess::finished), &loop, &QEventLoop::exit);

    // 防止子进程输出信息将管道塞满导致进程阻塞
    process->connect(process, &QProcess::readyReadStandardError, process, [process] {
        m_processStandardError.append(process->readAllStandardError());
    });
    process->connect(process, &QProcess::readyReadStandardOutput, process, [process] {
        m_processStandardOutput.append(process->readAllStandardOutput());
    });

    if (timeout > 0) {
        timer.start();
    } else {
        QTimer::singleShot(10000, process, [process] {
            dCWarning("\"%s %s\" running for more than 10 seconds, state=%d, pid_file_exist=%d",
                      qPrintable(process->program()), qPrintable(process->arguments().join(" ")),
                      (int)process->state(), (int)QFile::exists(QString("/proc/%1").arg(process->pid())));
        });
    }

    if (Global::debugLevel > 1)
        dCDebug("Exec: \"%s\", timeout: %d", qPrintable(command), timeout);

    process->start(command, mode);
    process->waitForStarted();

    if (process->error() != QProcess::UnknownError) {
        dCError(process->errorString());

        return -1;
    }

    if (process->state() == QProcess::Running) {
        loop.exec();
    }

    if (process->state() != QProcess::NotRunning) {
        dCDebug("The \"%s\" timeout, timeout: %d", qPrintable(command), timeout);

        // QT Bug，某种情况下(未知) QProcess::state 返回的状态有误，导致进程已退出却未能正确获取到其当前状态
        // 因此,额外通过系统文件判断进程是否还存在
        if (QFile::exists(QString("/proc/%1").arg(process->pid()))) {
            process->terminate();
            process->waitForFinished();
        } else {
            dCDebug("The \"%s\" is quit, but the QProcess object state is not NotRunning");
        }
    }

    m_processStandardOutput.append(process->readAllStandardOutput());
    m_processStandardError.append(process->readAllStandardError());

    if (Global::debugLevel > 1) {
        dCDebug("Done: \"%s\", exit code: %d", qPrintable(command), process->exitCode());

        if (process->exitCode() != 0) {
            dCError("error: \"%s\"\nstdout: \"%s\"", qPrintable(m_processStandardError), qPrintable(m_processStandardOutput));
        }
    }

    return process->exitCode();
}

Project: deepin-clone
File: helper.cpp

int Helper::partitionIndexNumber(const QString &partDevice)
{
    const QJsonArray &array = getBlockDevices(partDevice);

    if (array.isEmpty())
        return -1;

    const QJsonArray &p_array = getBlockDevices(array.first().toObject().value("pkname").toString() + " -x NAME");

    if (p_array.isEmpty())
        return -1;

    const QJsonArray &part_list = p_array.first().toObject().value("children").toArray();

    for (int i = 0; i < part_list.count(); ++i) {
        const QJsonObject &obj = part_list.at(i).toObject();

        if (obj.value("name").toString() == partDevice || obj.value("kname").toString() == partDevice)
            return i;
    }

    return -1;
}

Project: deepin-clone
File: helper.cpp

QString Helper::lastErrorString()
{
    return m_errorString;
}

Project: deepin-clone
File: ddevicediskinfo.cpp

qint64 DDeviceDiskInfoPrivate::totalReadableDataSize() const
{
    qint64 size = 0;

    if (hasScope(DDiskInfo::PartitionTable, DDiskInfo::Read)) {
        if (hasScope(DDiskInfo::Headgear, DDiskInfo::Read)) {
            size += 1048576;
        } else if (!children.isEmpty()) {
            size += children.first().sizeStart();
        }

        if (ptType == DDiskInfo::MBR) {
            size += 512;
        } else if (ptType == DDiskInfo::GPT) {
            size += 17408;
            size += 16896;
        }
    }

    for (const DPartInfo &part : children) {
        if (!part.isExtended())
            size += part.usedSize();
    }

    return size;
}

Project: deepin-clone
File: helper.cpp

void Helper::error(const QString &message)
{
    m_errorString = message;

    emit newError(message);
}

Project: deepin-clone
File: helper.cpp

void Helper::warning(const QString &message)
{
    m_warningString = message;

    emit newWarning(message);
}

Project: deepin-clone
File: ddevicediskinfo.cpp

QString DDeviceDiskInfoPrivate::errorString() const
{
    if (error.isEmpty()) {
        if (process) {
            if (process->error() == QProcess::UnknownError)
                return QString();

            return QString("%1 %2: %3").arg(process->program()).arg(process->arguments().join(' ')).arg(process->errorString());
        }

        if (!buffer.QIODevice::d_func()->errorString.isEmpty())
            return buffer.errorString();
    }

    return error;
}

Project: deepin-clone
File: helper.cpp

int Helper::restorePartition(const QString &from, const DPartInfo &to)
{
    QString command;

    if (isPartcloneFile(from)) {
        command = QStringLiteral("/usr/sbin/partclone.restore -s %1 -o %2").arg(from).arg(to.filePath());
    } else {
        command = QStringLiteral("dd if=%1 of=%2 status=none conv=fsync").arg(from).arg(to.filePath());
    }

    int code = processExec(command);

    if (code != 0)
        qDebug() << command << QString::fromUtf8(lastProcessStandardOutput());

    return code;
}

Project: deepin-clone
File: ddevicediskinfo.cpp

qint64 DDeviceDiskInfoPrivate::read(char *data, qint64 maxSize)
{
    if (!process) {
        return buffer.read(data, maxSize);
    }

    process->waitForReadyRead(-1);

    if (process->bytesAvailable() > Global::bufferSize) {
        dCWarning("The \"%s %s\" process bytes available: %s", qPrintable(process->program()), qPrintable(process->arguments().join(" ")), qPrintable(Helper::sizeDisplay(process->bytesAvailable())));
    }

    return process->read(data, maxSize);
}

Project: deepin-clone
File: helper.cpp

QByteArray Helper::lastProcessStandardError()
{
    return m_processStandardError;
}

Project: deepin-clone
File: ddevicediskinfo.cpp

qint64 DDeviceDiskInfoPrivate::readableDataSize(DDiskInfo::DataScope scope) const
{
    Q_UNUSED(scope)

    return -1;
}

Project: deepin-clone
File: helper.cpp

QByteArray Helper::lastProcessStandardOutput()
{
    return m_processStandardOutput;
}

Project: deepin-clone
File: main.cpp

inline static bool isTUIMode(int argc, char *argv[])
{
#ifndef ENABLE_GUI
    Q_UNUSED(argc)
    Q_UNUSED(argv)

    return true;
#endif

    if (qEnvironmentVariableIsEmpty("DISPLAY"))
        return true;

    const QByteArrayList in_tui_args = {
        "--tui", "-i", "--info", "--dim-info", "--to-serial-url",
        "--from-serial-url", "-f", "--fix-boot", "-v", "--version",
        "-h", "--help", "--re-checksum"
    };

    for (int i = 1; i < argc; ++i)
        if (in_tui_args.contains(argv[i]))
            return true;

    return false;
}

Project: deepin-clone
File: helper.cpp

bool Helper::isMounted(const QString &device)
{
    const QJsonArray &array = getBlockDevices("-l " + device);

    for (const QJsonValue &part : array) {
        const QJsonObject &obj = part.toObject();

        if (!obj.value("mountpoint").isNull())
            return true;
    }

    return false;
}

Project: deepin-clone
File: helper.cpp

QString Helper::parentDevice(const QString &device)
{
    const QJsonArray &blocks = getBlockDevices(device);

    if (blocks.isEmpty())
        return device;

    const QString &parent = blocks.first().toObject().value("pkname").toString();

    if (parent.isEmpty())
        return device;

    return parent;
}

Project: deepin-clone
File: helper.cpp

QString Helper::temporaryMountDevice(const QString &device, const QString &name, bool readonly)
{
    QString mount_point = mountPoint(device);

    if (!mount_point.isEmpty())
        return mount_point;

    mount_point = "%1/.%2/mount/%3";
    const QStringList &tmp_paths = QStandardPaths::standardLocations(QStandardPaths::RuntimeLocation);

    mount_point = mount_point.arg(tmp_paths.isEmpty() ? "/run/user/0" : tmp_paths.first()).arg(qApp->applicationName()).arg(name);

    if (!QDir::current().mkpath(mount_point)) {
        dCError("mkpath \"%s\" failed", qPrintable(mount_point));

        return QString();
    }

    if (!mountDevice(device, mount_point, readonly)) {
        dCError("Mount the device \"%s\" to \"%s\" failed", qPrintable(device), qPrintable(mount_point));

        return QString();
    }

    return mount_point;
}

Project: deepin-clone
File: ddevicediskinfo.cpp

bool DDeviceDiskInfoPrivate::atEnd() const
{
    if (!process) {
        return buffer.atEnd();
    }

    process->waitForReadyRead(-1);

    return process->atEnd();
}

Project: deepin-clone
File: ddevicediskinfo.cpp

void DDeviceDiskInfoPrivate::refresh()
{
    children.clear();

    const QJsonArray &block_devices = Helper::getBlockDevices(name);

    if (!block_devices.isEmpty())
        init(block_devices.first().toObject());
}

Project: deepin-clone
File: helper.cpp

QString Helper::getPartcloneExecuter(const DPartInfo &info)
{
    QString executor;

    switch (info.fileSystemType()) {
    case DPartInfo::Invalid:
        break;
    case DPartInfo::Btrfs:
        executor = "btrfs";
        break;
    case DPartInfo::EXT2:
    case DPartInfo::EXT3:
    case DPartInfo::EXT4:
        executor = "extfs";
        break;
    case DPartInfo::F2FS:
        executor = "f2fs";
        break;
    case DPartInfo::FAT12:
    case DPartInfo::FAT16:
    case DPartInfo::FAT32:
        executor = "fat";
        break;
    case DPartInfo::HFS_Plus:
        executor = "hfsplus";
        break;
    case DPartInfo::Minix:
        executor = "minix";
        break;
    case DPartInfo::Nilfs2:
        executor = "nilfs2";
        break;
    case DPartInfo::NTFS:
        executor = "ntfs -I";
        break;
    case DPartInfo::Reiser4:
        executor = "reiser4";
        break;
    case DPartInfo::VFAT:
        executor = "vfat";
        break;
    case DPartInfo::XFS:
        executor = "xfs";
        break;
    default:
        if (!QStandardPaths::findExecutable("partclone." + info.fileSystemTypeName().toLower()).isEmpty())
            executor = info.fileSystemTypeName().toLower();
        break;
    }

    if (executor.isEmpty())
        return "partclone.imager";

    return "partclone." + executor;
}

Project: deepin-clone
File: ddevicediskinfo.cpp

bool DDeviceDiskInfoPrivate::isClosing() const
{
    return closing;
}

Project: deepin-clone
File: helper.cpp

QByteArray Helper::callLsblk(const QString &extraArg)
{
    processExec(COMMAND_LSBLK.arg(extraArg));

    return lastProcessStandardOutput();
}

Project: deepin-clone
File: helper.cpp

bool Helper::umountDevice(const QString &device)
{
    const QJsonArray &array = getBlockDevices("-l " + device);

    for (const QJsonValue &device : array) {
        const QJsonObject &obj = device.toObject();

        if (!obj.value("mountpoint").isNull()) {
            if (processExec(QString("umount -d %1").arg(obj.value("name").toString())) != 0)
                return false;
        }
    }

    return true;
}

Project: deepin-clone
File: helper.cpp

bool Helper::saveToFile(const QString &fileName, const QByteArray &data, bool override)
{
    if (!override && QFile::exists(fileName))
        return false;

    QFile file(fileName);

    if (!file.open(QIODevice::WriteOnly)) {
        dCError(file.errorString());

        return false;
    }

    qint64 size = file.write(data);
    file.flush();
    file.close();

    return size == data.size();
}

Project: deepin-clone
File: helper.cpp

QString Helper::toSerialUrl(const QString &file)
{
    if (file.isEmpty())
        return QString();

    if (Helper::isBlockSpecialFile(file)) {
        DDiskInfo info;

        if (Helper::isDiskDevice(file))
            info = DDiskInfo::getInfo(file);
        else
            info = DDiskInfo::getInfo(Helper::parentDevice(file));

        if (!info)
            return QString();

        if (info.serial().isEmpty())
            return QString();

        int index = DDevicePartInfo(file).indexNumber();

        if (index == 0)
            return "serial://" + info.serial();

        return QString("serial://%1:%2").arg(info.serial()).arg(index);
    }

    QString root_path;
    QString url = toSerialUrl(getDeviceForFile(file, &root_path));

    if (root_path == "/")
        return url + QFileInfo(file).absoluteFilePath();

    return url + QFileInfo(file).absoluteFilePath().mid(root_path.length());
}

Project: deepin-clone
File: helper.cpp

bool Helper::resetPartUUID(const DPartInfo &part, QByteArray uuid)
{
    QString command;

    if (uuid.isEmpty()) {
        uuid = QUuid::createUuid().toByteArray().mid(1, 36);
    }

    switch (part.fileSystemType()) {
    case DPartInfo::EXT2:
    case DPartInfo::EXT3:
    case DPartInfo::EXT4:
        command = QString("tune2fs -U %1 %2").arg(QString::fromLatin1(uuid)).arg(part.filePath());
        break;
    case DPartInfo::JFS:
        command = QString("jfs_tune -U %1 %2").arg(QString::fromLatin1(uuid)).arg(part.filePath());
        break;
    case DPartInfo::NTFS:
        command = QString("ntfslabel --new-half-serial %1").arg(part.filePath());
        break;
    case DPartInfo::XFS:
        command = QString("xfs_admin -U %1 %2").arg(QString::fromLatin1(uuid)).arg(part.filePath());
        break;
    default:
        dCDebug("Not support the file system type: %s", qPrintable(part.fileSystemTypeName()));

        return false;
    }

    if (!umountDevice(part.filePath())) {
        dCDebug("Failed to umount the partition: %s", qPrintable(part.filePath()));

        return false;
    }

    // check the partition
    processExec("fsck -f -y " + part.filePath());

    bool ok = processExec(command) == 0;

    if (!ok) {
        dCError("Failed reset part uuid");
        dCDebug(qPrintable(lastProcessStandardOutput()));
        dCError(qPrintable(lastProcessStandardError()));
    }

    return ok;
}

Project: deepin-clone
File: helper.cpp

bool Helper::existLiveSystem()
{
    return QFile::exists("/recovery");
}

Project: deepin-clone
File: helper.cpp

bool Helper::refreshSystemPartList(const QString &device)
{
    int code = device.isEmpty() ? processExec("partprobe") : processExec(QString("partprobe %1").arg(device));

    if (code != 0)
        return false;

    QThread::sleep(1);

    return true;
}

Project: deepin-clone
File: ddevicediskinfo.cpp

DDeviceDiskInfo::DDeviceDiskInfo(const QString &filePath)
{
    const QJsonArray &block_devices = Helper::getBlockDevices(filePath);

    if (!block_devices.isEmpty()) {
        const QJsonObject &obj = block_devices.first().toObject();

        d = new DDeviceDiskInfoPrivate(this);
        d_func()->init(obj);

        if (d->type == Part) {
            const QJsonArray &parent = Helper::getBlockDevices(obj.value("pkname").toString());

            if (!parent.isEmpty()) {
                const QJsonObject &parent_obj = parent.first().toObject();

                d->transport = parent_obj.value("tran").toString();
                d->model = parent_obj.value("model").toString();
                d->serial = parent_obj.value("serial").toString();
            }

            if (!d->children.isEmpty())
                d->children.first().d->transport = d->transport;
        }
    }
}

Project: deepin-clone
File: helper.cpp

int Helper::clonePartition(const DPartInfo &part, const QString &to, bool override)
{
    QString executor = getPartcloneExecuter(part);
    QString command;

    if (executor.isEmpty() || executor == "partclone.imager") {
        if (part.guidType() == DPartInfo::InvalidGUID)
            return -1;

        command = QStringLiteral("dd if=%1 of=%2 status=none conv=fsync").arg(part.filePath()).arg(to);
    } else if (isBlockSpecialFile(to)) {
        command = QStringLiteral("/usr/sbin/%1 -b -c -s %2 -%3 %4").arg(executor).arg(part.filePath()).arg(override ? "O" : "o").arg(to);
    } else {
        command = QStringLiteral("/usr/sbin/%1 -c -s %2 -%3 %4").arg(executor).arg(part.filePath()).arg(override ? "O" : "o").arg(to);
    }

    int code = processExec(command);

    if (code != 0)
        qDebug() << command << QString::fromUtf8(lastProcessStandardOutput());

    return code;
}

Project: deepin-clone
File: helper.cpp

bool Helper::deviceHaveKinship(const QString &device1, const QString &device2)
{
    return device1 == device2 || parentDevice(device1) == parentDevice(device2);
}

Project: deepin-clone
File: helper.cpp

bool Helper::restartToLiveSystem(const QStringList &arguments)
{
    if (!existLiveSystem()) {
        dCDebug("Not install live system");

        return false;
    }

    if (!QDir::current().mkpath("/recovery/.tmp")) {
        dCDebug("mkpath failed");

        return false;
    }

    QFile file("/recovery/.tmp/deepin-clone.arguments");

    if (!file.open(QIODevice::WriteOnly)) {
        dCDebug("Open file failed: \"%s\"", qPrintable(file.fileName()));

        return false;
    }

    file.write(arguments.join('\n').toUtf8());
    file.close();

    if (processExec("grub-reboot \"Deepin Recovery\"") != 0) {
        dCDebug("Exec grub-reboot \"Deepin Recovery\" failed");

        file.remove();

        return false;
    }

    if (processExec("reboot") != 0)
        file.remove();

    return true;
}

Project: deepin-clone
File: ddevicediskinfo.cpp

bool DDeviceDiskInfoPrivate::hasScope(DDiskInfo::DataScope scope, DDiskInfo::ScopeMode mode, int index) const
{
    if (mode == DDiskInfo::Read) {
        if (scope == DDiskInfo::Headgear) {
            return havePartitionTable && (children.isEmpty() || children.first().sizeStart() >= 1048576);
        } else if (scope == DDiskInfo::JsonInfo) {
            return true;
        }

        if (scope == DDiskInfo::PartitionTable)
            return havePartitionTable;
    } else if (readonly || scope == DDiskInfo::JsonInfo) {
        return false;
    }

    if (scope == DDiskInfo::Partition) {
        if (index == 0 && mode == DDiskInfo::Write)
            return true;

        const DPartInfo &info = q->getPartByNumber(index);

        if (!info) {
            dCDebug("Can not find parition by number(device: \"%s\"): %d", qPrintable(q->filePath()), index);

            return false;
        }

        if (info.isExtended() || (mode == DDiskInfo::Read
                                  && info.type() == DPartInfo::Unknow
                                  && info.fileSystemType() == DPartInfo::Invalid
                                  && info.guidType() == DPartInfo::InvalidGUID)) {
            dCDebug("Skip the \"%s\" partition, type: %s", qPrintable(info.filePath()), qPrintable(info.typeDescription(info.type())));

            return false;
        }

        return mode != DDiskInfo::Write || !info.isReadonly();
    }

    return (scope == DDiskInfo::Headgear || scope == DDiskInfo::PartitionTable) ? type == DDiskInfo::Disk : true;
}

Project: deepin-clone
File: helper.cpp

bool Helper::isPartitionDevice(const QString &devicePath)
{
    const QJsonArray &blocks = getBlockDevices(devicePath);

    if (blocks.isEmpty())
        return false;

    if (!blocks.first().isObject())
        return false;

    return !blocks.first().toObject().value("pkname").isString();
}

Project: deepin-clone
File: bootdoctor.cpp

bool BootDoctor::fix(const QString &partDevice)
{
    m_lastErrorString.clear();

    DDevicePartInfo part_info(partDevice);
    const QString part_old_uuid = part_info.uuid();

    if (Helper::processExec("lsblk -s -d -n -o UUID") == 0) {
        if (Helper::lastProcessStandardOutput().contains(part_old_uuid.toLatin1())) {
            // reset uuid
            if (Helper::resetPartUUID(part_info)) {
                QThread::sleep(1);
                part_info.refresh();

                qDebug() << part_old_uuid << part_info.uuid();
            } else {
                dCWarning("Failed to reset uuid");
            }
        }
    }

    bool device_is_mounted = Helper::isMounted(partDevice);
    const QString &mount_root = Helper::temporaryMountDevice(partDevice, QFileInfo(partDevice).fileName());

    if (mount_root.isEmpty()) {
        m_lastErrorString = QObject::tr("Failed to mount partition \"%1\"").arg(partDevice);
        goto failed;
    }

    {
        const QString tmp_dir = "/var/cache/deepin-clone";

        if (!QDir::current().mkpath(tmp_dir)) {
            dCError("mkpath \"%s\" failed", qPrintable(tmp_dir));
            goto failed;
        }

        const QString &repo_path = tmp_dir + "/repo.iso";

        if (!QFile::exists(repo_path)
                && !QFile::copy(QString(":/repo_%1.iso").arg(HOST_ARCH), repo_path)) {
            dCError("copy file failed, new name: %s", qPrintable(repo_path));
            goto failed;
        }

        bool ok = false;

        const QString &repo_mount_point = mount_root + "/deepin-clone";
        QFile file_boot_fix(mount_root + "/boot_fix.sh");

        do {
            if (!QDir(mount_root).exists("deepin-clone") && !QDir(mount_root).mkdir("deepin-clone")) {
                dCError("Create \"deepin-clone\" dir failed(\"%s\")", qPrintable(mount_root));
                break;
            }

            if (!Helper::mountDevice(repo_path, repo_mount_point, true)) {
                m_lastErrorString = QObject::tr("Failed to mount partition \"%1\"").arg(repo_path);
                break;
            }

            if (file_boot_fix.exists()) {
                file_boot_fix.remove();
            }

            if (!QFile::copy(QString(":/scripts/boot_fix_%1.sh").arg(
                     #if defined(HOST_ARCH_x86_64) || defined(HOST_ARCH_i386) || defined(HOST_ARCH_i686)
                                 "x86"
                     #elif defined(HOST_ARCH_mips64) || defined(HOST_ARCH_mips32)
                                 "mips"
                     #elif defined(HOST_ARCH_sw_64)
                                 "sw_64"
                     #elif defined(HOST_ARCH_aarch64)
                                 "aarch64"
                     #else
                     #pragma message "Machine: " HOST_ARCH
                                "unknow"
                     #endif
                                 ), file_boot_fix.fileName())) {
                dCError("copy file failed, new name: %s", qPrintable(file_boot_fix.fileName()));
                break;
            }

            if (!file_boot_fix.setPermissions(file_boot_fix.permissions() | QFile::ExeUser)) {
                dCError("Set \"%s\" permissions failed", qPrintable(file_boot_fix.fileName()));
                break;
            }

            if (Helper::processExec(QString("mount --bind -v --bind /dev %1/dev").arg(mount_root)) != 0) {
                dCError("Failed to bind /dev");
                break;
            }

            if (Helper::processExec(QString("mount --bind -v --bind /dev/pts %1/dev/pts").arg(mount_root)) != 0) {
                dCError("Failed to bind /dev/pts");
                break;
            }

            if (Helper::processExec(QString("mount --bind -v --bind /proc %1/proc").arg(mount_root)) != 0) {
                dCError("Failed to bind /proc");
                break;
            }

            if (Helper::processExec(QString("mount --bind -v --bind /sys %1/sys").arg(mount_root)) != 0) {
                dCError("Failed to bind /sys");
                break;
            }

            ok = true;
        } while (0);

        QProcess process;

        if (ok) {
            const QString &parent_device = Helper::parentDevice(partDevice);

            bool is_efi = false;

            if (!parent_device.isEmpty()) {
                DDeviceDiskInfo info(parent_device);

                dCDebug("Disk partition table type: %d", info.ptType());

                if (info.ptType() == DDeviceDiskInfo::GPT) {
                    for (const DPartInfo &part : info.childrenPartList()) {
                        if (part.guidType() == DPartInfo::EFI_SP_None) {
                            const QString &efi_path = mount_root + "/boot/efi";

                            QDir::current().mkpath(efi_path);

                            if (Helper::processExec(QString("mount %1 %2").arg(part.filePath()).arg(efi_path)) != 0) {
                                dCError("Failed to mount EFI partition");
                                m_lastErrorString = QObject::tr("Failed to mount partition \"%1\"").arg(part.filePath());
                                ok = false;
                                break;
                            }

                            is_efi = true;

                            break;
                        }
                    }

                    if (!is_efi && m_lastErrorString.isEmpty()) {
                        m_lastErrorString = QObject::tr("EFI partition not found");
                        ok = false;
                    }
                } else if (info.ptType() == DDeviceDiskInfo::Unknow) {
                    m_lastErrorString = QObject::tr("Unknown partition style");
                    ok = false;
                }
            }

            if (ok) {
                process.setProcessChannelMode(QProcess::MergedChannels);
                process.start(QString("chroot %1 ./boot_fix.sh %2 %3 /deepin-clone")
                              .arg(mount_root)
                              .arg(parent_device)
                              .arg(is_efi ? "true" : "false"));

                while (process.waitForReadyRead()) {
                    const QByteArray &data = process.readAll().simplified().constData();

                    dCDebug(data.constData());
                }

                process.waitForFinished(-1);

                switch (process.exitCode()) {
                case 1:
                    m_lastErrorString = QObject::tr("Boot for install system failed");
                    break;
                case 2:
                    m_lastErrorString = QObject::tr("Boot for update system failed");
                    break;
                default:
                    break;
                }
            }
        }

        // clear
        Helper::processExec("umount " + repo_mount_point);
        QDir(mount_root).rmdir("deepin-clone");
        file_boot_fix.remove();
        Helper::processExec("umount " + mount_root + "/dev/pts");
        Helper::processExec("umount " + mount_root + "/dev");
        Helper::processExec("umount " + mount_root + "/proc");
        Helper::processExec("umount " + mount_root + "/sys");
        Helper::processExec("umount " + mount_root + "/boot/efi");

        if (ok && process.exitCode() == 0) {
            if (part_old_uuid != part_info.uuid()) {
                dCDebug("Reset the uuid from \"%s\" to \"%s\"", qPrintable(part_old_uuid), qPrintable(part_info.uuid()));

                // update /etc/fstab
                QFile file(mount_root + "/etc/fstab");

                if (file.exists() && file.open(QIODevice::ReadWrite)) {
                    QByteArray data = file.readAll();

                    if (file.seek(0)) {
                        file.write(data.replace(part_old_uuid.toLatin1(), part_info.uuid().toLatin1()));
                    }

                    file.close();
                } else {
                    dCWarning("Failed to update /etc/fstab, error: %s", qPrintable(file.errorString()));
                }

                file.setFileName(mount_root + "/etc/crypttab");

                if (file.exists() && file.open(QIODevice::ReadWrite)) {
                    QByteArray data = file.readAll();

                    if (file.seek(0)) {
                        file.write(data.replace(part_old_uuid.toLatin1(), part_info.uuid().toLatin1()));
                    }

                    file.close();
                } else {
                    dCWarning("Failed to update /etc/crypttab, error: %s", qPrintable(file.errorString()));
                }
            }

            if (!device_is_mounted)
                Helper::umountDevice(partDevice);

            return true;
        }
    }

failed:
    if (!device_is_mounted)
        Helper::umountDevice(partDevice);

    if (m_lastErrorString.isEmpty())
        m_lastErrorString = QObject::tr("Boot for repair system failed");

    dCDebug("Restore partition uuid");

    if (!Helper::resetPartUUID(part_info, part_old_uuid.toLatin1())) {
        dCWarning("Failed to restore partition uuid, part: %s, uuid: %s", qPrintable(partDevice), qPrintable(part_old_uuid));
    }

    return false;
}

Project: deepin-clone
File: main.cpp

int main(int argc, char *argv[])
{
    QCoreApplication *a;

    if (isTUIMode(argc, argv)) {
        Global::isTUIMode = true;

        a = new QCoreApplication(argc, argv);
    }
#ifdef ENABLE_GUI
    else {
        ConsoleAppender *consoleAppender = new ConsoleAppender;
        consoleAppender->setFormat(logFormat);

        const QString log_file("/var/log/deepin-clone.log");

        RollingFileAppender *rollingFileAppender = new RollingFileAppender(log_file);
        rollingFileAppender->setFormat(logFormat);
        rollingFileAppender->setLogFilesLimit(5);
        rollingFileAppender->setDatePattern(RollingFileAppender::DailyRollover);

        logger->registerAppender(rollingFileAppender);
        logger->registerAppender(consoleAppender);

        if (qEnvironmentVariableIsSet("PKEXEC_UID")) {
            const quint32 pkexec_uid = qgetenv("PKEXEC_UID").toUInt();

            DApplication::customQtThemeConfigPathByUserHome(getpwuid(pkexec_uid)->pw_dir);
        }

        DApplication::loadDXcbPlugin();
        DApplication *app = new DApplication(argc, argv);

        app->setAttribute(Qt::AA_UseHighDpiPixmaps);

        if (!qApp->setSingleInstance("_deepin_clone_")) {
            qCritical() << "As well as the process is running";

            return -1;
        }

        if (!app->loadTranslator()) {
            dError("Load translator failed");
        }

        app->setApplicationDisplayName(QObject::tr("Deepin Clone"));
        app->setApplicationDescription(QObject::tr("Deepin Clone is a backup and restore tool in deepin. "
                                                   "It supports disk or partition clone, backup and restore, and other functions."));
        app->setApplicationAcknowledgementPage("https://www.deepin.org/acknowledgments/deepin-clone/");
        app->setTheme("light");
        a = app;
    }
#endif

    a->setApplicationName("deepin-clone");
#ifdef ENABLE_GUI
    a->setApplicationVersion(DApplication::buildVersion("1.0.0.1"));
#else
    a->setApplicationVersion("1.0.0.1");
#endif
    a->setOrganizationName("deepin");

    CommandLineParser parser;

    QFile arguments_file("/lib/live/mount/medium/.tmp/deepin-clone.arguments");
    QStringList arguments;

    bool load_arg_from_file = arguments_file.exists() && !Global::isTUIMode && !a->arguments().contains("--tui");

    if (load_arg_from_file) {
        arguments.append(a->arguments().first());

        if (!arguments_file.open(QIODevice::ReadOnly)) {
            qCritical() << "Open \"/lib/live/mount/medium/.tmp/deepin-clone.arguments\" failed, error:" << arguments_file.errorString();
        } else {
            while (!arguments_file.atEnd()) {
                const QString &arg = QString::fromUtf8(arguments_file.readLine().trimmed());

                if (!arg.isEmpty())
                    arguments.append(arg);
            }

            arguments_file.close();
            arguments_file.remove();
        }

        qDebug() << arguments;
    } else {
        arguments = a->arguments();
    }

    parser.process(arguments);

    ConsoleAppender *consoleAppender = new ConsoleAppender;
    consoleAppender->setFormat(logFormat);

    RollingFileAppender *rollingFileAppender = new RollingFileAppender(parser.logFile());
    rollingFileAppender->setFormat(logFormat);
    rollingFileAppender->setLogFilesLimit(5);
    rollingFileAppender->setDatePattern(RollingFileAppender::DailyRollover);

    logger->registerCategoryAppender("deepin.ghost", consoleAppender);
    logger->registerCategoryAppender("deepin.ghost", rollingFileAppender);

    parser.parse();

    if (load_arg_from_file) {
        dCDebug("Load arguments from \"%s\"", qPrintable(arguments_file.fileName()));
    }

    dCInfo("Application command line: %s", qPrintable(arguments.join(' ')));

    if (Global::debugLevel == 0) {
        QLoggingCategory::setFilterRules("deepin.ghost.debug=false");
    }

    if (Global::isTUIMode) {
        if (!parser.target().isEmpty()) {
            CloneJob *job = new CloneJob;

            QObject::connect(job, &QThread::finished, a, &QCoreApplication::quit);

            job->start(parser.source(), parser.target());
        }
    }
#ifdef ENABLE_GUI
    else {
        if (!parser.isSetOverride())
            Global::isOverride = true;

        if (!parser.isSetDebug())
            Global::debugLevel = 2;

        MainWindow *window = new MainWindow;

        window->setFixedSize(860, 660);
        window->setStyleSheet(DThemeManager::instance()->getQssForWidget("main", window));
        window->setWindowIcon(QIcon::fromTheme("deepin-clone"));
        window->setWindowFlags(Qt::WindowCloseButtonHint | Qt::WindowMinimizeButtonHint | Qt::WindowSystemMenuHint);
        window->titlebar()->setIcon(window->windowIcon());
        window->titlebar()->setTitle(QString());
#if DTK_VERSION > DTK_VERSION_CHECK(2, 0, 6, 0)
        window->titlebar()->setBackgroundTransparent(true);
#endif
        window->show();

        qApp->setProductIcon(window->windowIcon());

        if (!parser.source().isEmpty()) {
            window->startWithFile(parser.source(), parser.target());
        }

        QObject::connect(a, &QCoreApplication::aboutToQuit, window, &MainWindow::deleteLater);
        QDesktopServices::setUrlHandler("https", window, "openUrl");
    }
#endif

    int exitCode = Global::isTUIMode ? a->exec() : qApp->exec();
    QString log_backup_file = parser.logBackupFile();

    if (log_backup_file.startsWith("serial://")) {
        log_backup_file = Helper::parseSerialUrl(log_backup_file);
    }

    if (log_backup_file.isEmpty()) {
        return exitCode;
    }

    if (!QFile::copy(parser.logFile(), log_backup_file)) {
        dCWarning("failed to copy log file to \"%s\"", qPrintable(log_backup_file));
    }

    return exitCode;
}

Project: deepin-clone
File: helper.cpp

QString Helper::secondsToString(qint64 seconds)
{
    int days = seconds / 86400;

    seconds = seconds % 86400;

    int hours = seconds / 3600;

    seconds = seconds % 3600;

    int minutes = seconds / 60;

    seconds = seconds % 60;

    if (days > 0)
        return QObject::tr("%1 d %2 h %3 m").arg(days).arg(hours).arg(minutes + 1);

    if (hours > 0)
        return QObject::tr("%1 h %2 m").arg(hours).arg(minutes + 1);

    if (minutes > 0)
        return QObject::tr("%1 m").arg(minutes + 1);

    return QObject::tr("%1 s").arg(seconds);
}

Project: deepin-clone
File: helper.cpp

bool Helper::isDeepinSystem(const DPartInfo &part)
{
    QString mout_root = part.mountPoint();
    bool umount_device = false;

    if (mout_root.isEmpty()) {
        mout_root = temporaryMountDevice(part.name(), QFileInfo(part.name()).fileName(), true);

        if (mout_root.isEmpty())
            return false;

        umount_device = true;
    }

    bool is = QFile::exists(mout_root + "/etc/deepin-version");

    if (umount_device)
        umountDevice(part.name());

    return is;
}

Project: deepin-clone
File: helper.cpp

bool Helper::setPartitionTable(const QString &devicePath, const QString &ptFile)
{
    QProcess process;

    process.setStandardInputFile(ptFile);

    if (processExec(&process, QStringLiteral("/sbin/sfdisk %1").arg(devicePath)) != 0)
        return false;

    int code = processExec(QStringLiteral("/sbin/partprobe %1").arg(devicePath));
    processExec("sleep 1");

    return code == 0;
}

Project: deepin-clone
File: helper.cpp

bool Helper::getPartitionSizeInfo(const QString &partDevice, qint64 *used, qint64 *free, int *blockSize)
{
    QProcess process;
    QStringList env_list = QProcess::systemEnvironment();

    env_list.append("LANG=C");
    process.setEnvironment(env_list);

    if (Helper::isMounted(partDevice)) {
        process.start(QString("df -B1 -P %1").arg(partDevice));
        process.waitForFinished();

        if (process.exitCode() != 0) {
            dCError("Call df failed: %s", qPrintable(process.readAllStandardError()));

            return false;
        }

        QByteArray output = process.readAll();
        const QByteArrayList &lines = output.trimmed().split('\n');

        if (lines.count() != 2)
            return false;

        output = lines.last().simplified();

        const QByteArrayList &values = output.split(' ');

        if (values.count() != 6)
            return false;

        bool ok = false;

        if (used)
            *used = values.at(2).toLongLong(&ok);

        if (!ok)
            return false;

        if (free)
            *free = values.at(3).toLongLong(&ok);

        if (!ok)
            return false;

        return true;
    } else {
        process.start(QString("%1 -s %2 -c -q -C -L /var/log/partclone.log").arg(getPartcloneExecuter(DDevicePartInfo(partDevice))).arg(partDevice));
        process.setStandardOutputFile("/dev/null");
        process.setReadChannel(QProcess::StandardError);
        process.waitForStarted();

        qint64 used_block = -1;
        qint64 free_block = -1;

        while (process.waitForReadyRead(5000)) {
            const QByteArray &data = process.readAll();

            for (QByteArray line : data.split('\n')) {
                line = line.simplified();

                if (QString::fromLatin1(line).contains(QRegularExpression("\\berror\\b"))) {
                    dCError("Call \"%s %s\" failed: \"%s\"", qPrintable(process.program()), qPrintable(process.arguments().join(' ')), line.constData());
                }

                if (line.startsWith("Space in use:")) {
                    bool ok = false;
                    const QByteArray &value = line.split(' ').value(6, "-1");

                    used_block = value.toLongLong(&ok);

                    if (!ok) {
                        dCError("String to LongLong failed, String: %s", value.constData());

                        return false;
                    }
                } else if (line.startsWith("Free Space:")) {
                    bool ok = false;
                    const QByteArray &value = line.split(' ').value(5, "-1");

                    free_block = value.toLongLong(&ok);

                    if (!ok) {
                        dCError("String to LongLong failed, String: %s", value.constData());

                        return false;
                    }
                } else if (line.startsWith("Block size:")) {
                    bool ok = false;
                    const QByteArray &value = line.split(' ').value(2, "-1");

                    int block_size = value.toInt(&ok);

                    if (!ok) {
                        dCError("String to Int failed, String: %s", value.constData());

                        return false;
                    }

                    if (used_block < 0 || free_block < 0 || block_size < 0)
                        return false;

                    if (used)
                        *used = used_block * block_size;

                    if (free)
                        *free = free_block * block_size;

                    if (blockSize)
                        *blockSize = block_size;

                    process.terminate();
                    process.waitForFinished();

                    return true;
                }
            }
        }
    }

    return false;
}

Project: deepin-clone
File: helper.cpp

QString Helper::mountPoint(const QString &device)
{
    const QJsonArray &array = getBlockDevices(device);

    if (array.isEmpty())
        return QString();

    return array.first().toObject().value("mountpoint").toString();
}

Project: deepin-clone
File: helper.cpp

QString Helper::sizeDisplay(qint64 size)
{
    constexpr qreal kb = 1024;
    constexpr qreal mb = kb * 1024;
    constexpr qreal gb = mb * 1024;
    constexpr qreal tb = gb * 1024;

    if (size > tb)
        return QString::asprintf("%.2f TB", size / tb);

    if (size > gb)
        return QString::asprintf("%.2f GB", size / gb);

    if (size > mb)
        return QString::asprintf("%.2f MB", size / mb);

    if (size > kb)
        return QString::asprintf("%.2f KB", size / kb);

    return QString("%1 B").arg(size);
}

Project: deepin-clone
File: ddevicediskinfo.cpp

DDeviceDiskInfoPrivate::~DDeviceDiskInfoPrivate()
{
    closeDataStream();

    if (process)
        process->deleteLater();
}

Project: deepin-clone
File: helper.cpp

QString Helper::findDiskBySerialIndexNumber(const QString &serialNumber, int partIndexNumber)
{
    const QJsonArray &array = getBlockDevices();

    for (const QJsonValue &disk : array) {
        const QJsonObject &obj = disk.toObject();

        if (obj.value("serial").toString().compare(serialNumber, Qt::CaseInsensitive) != 0) {
            continue;
        }

        if (partIndexNumber <= 0)
            return obj.value("name").toString();

        const QJsonArray &children = obj.value("children").toArray();

        for (const QJsonValue &v : children) {
            const QJsonObject &obj = v.toObject();
            const QString &name = obj.value("name").toString();

            if (DDevicePartInfo(name).indexNumber() == partIndexNumber)
                return name;
        }
    }

    return QString();
}

Project: deepin-clone
File: bootdoctor.cpp

QString BootDoctor::errorString()
{
    return m_lastErrorString;
}

Project: deepin-clone
File: ddevicediskinfo.cpp

void DDeviceDiskInfoPrivate::init(const QJsonObject &obj)
{
    model = obj.value("model").toString();
    name = obj.value("name").toString();
    kname = obj.value("kname").toString();
    size = obj.value("size").toString().toLongLong();
    typeName = obj.value("type").toString();
    readonly = obj.value("ro").toString() == "1" || typeName == "rom";
    removeable = obj.value("rm").toString() == "1";
    transport = obj.value("tran").toString();
    serial = obj.value("serial").toString();

    if (obj.value("pkname").isNull())
        type = DDiskInfo::Disk;
    else
        type = DDiskInfo::Part;

    const QJsonArray &list = obj.value("children").toArray();
    QStringList children_uuids;

    for (const QJsonValue &part : list) {
        const QJsonObject &obj = part.toObject();

        const QString &uuid = obj.value("partuuid").toString();

        if (!uuid.isEmpty() && children_uuids.contains(uuid))
            continue;

        DDevicePartInfo info;

        info.init(obj);

        if (!info.partUUID().isEmpty() && children_uuids.contains(info.partUUID()))
            continue;

        info.d->transport = transport;
        children << info;
        children_uuids << info.partUUID();
    }

    qSort(children.begin(), children.end(), [] (const DPartInfo &info1, const DPartInfo &info2) {
        return info1.sizeStart() < info2.sizeStart();
    });

    if (type == DDiskInfo::Disk)
        ptTypeName = getPTName(name);
    else
        ptTypeName = getPTName(obj.value("pkname").toString());

    if (ptTypeName == "dos") {
        ptType = DDiskInfo::MBR;
    } else if (ptTypeName == "gpt") {
        ptType = DDiskInfo::GPT;
    } else {
        ptType = DDiskInfo::Unknow;
        havePartitionTable = false;
    }

    if (type == DDiskInfo::Part)
        havePartitionTable = false;

    if ((!havePartitionTable && children.isEmpty()) || type == DDiskInfo::Part) {
        DDevicePartInfo info;

        info.init(obj);
        info.d->transport = transport;
        info.d->index = 0;
        children << info;
    }
}

Project: deepin-clone
File: helper.cpp

bool Helper::isBlockSpecialFile(const QString &fileName)
{
    if (fileName.startsWith("/dev/"))
        return true;

    processExec(QStringLiteral("env LANG=C stat -c %F %1").arg(fileName));

    return lastProcessStandardOutput() == "block special file\n";
}

Project: deepin-clone
File: ddevicediskinfo.cpp

static QString getPTName(const QString &device)
{
    Helper::processExec(QStringLiteral("/sbin/blkid -p -s PTTYPE -d -i %1").arg(device));

    const QByteArray &data = Helper::lastProcessStandardOutput();

    if (data.isEmpty())
        return QString();

    const QByteArrayList &list = data.split('=');

    if (list.count() != 3)
        return QString();

    return list.last().simplified();
}

Project: deepin-clone
File: helper.cpp

QByteArray Helper::getPartitionTable(const QString &devicePath)
{
    processExec(QStringLiteral("/sbin/sfdisk -d %1").arg(devicePath));

    return lastProcessStandardOutput();
}

Project: deepin-clone
File: helper.cpp

bool Helper::isDiskDevice(const QString &devicePath)
{
    const QJsonArray &blocks = getBlockDevices(devicePath);

    if (blocks.isEmpty())
        return false;

    if (!blocks.first().isObject())
        return false;

    return blocks.first().toObject().value("pkname").isNull();
}

Project: deepin-clone
File: ddevicediskinfo.cpp

void DDeviceDiskInfoPrivate::closeDataStream()
{
    closing = true;

    if (process) {
        if (process->state() != QProcess::NotRunning) {
            if (currentMode == DDiskInfo::Read) {
                process->closeReadChannel(QProcess::StandardOutput);
                process->terminate();
            } else {
                process->closeWriteChannel();
            }

            while (process->state() != QProcess::NotRunning) {
                QThread::currentThread()->sleep(1);

                if (!QFile::exists(QString("/proc/%2").arg(process->pid()))) {
                    process->waitForFinished(-1);

                    if (process->error() == QProcess::Timedout)
                        process->QIODevice::d_func()->errorString.clear();

                    break;
                }
            }
        }

        dCDebug("Process exit code: %d(%s %s)", process->exitCode(), qPrintable(process->program()), qPrintable(process->arguments().join(' ')));
    }

    if (currentMode == DDiskInfo::Write && currentScope == DDiskInfo::PartitionTable) {
        Helper::umountDevice(filePath());

        if (Helper::refreshSystemPartList(filePath())) {
            refresh();
        } else {
            dCWarning("Refresh the devcie %s failed", qPrintable(filePath()));
        }
    }

    if (currentScope == DDiskInfo::JsonInfo)
        buffer.close();

    closing = false;
}

Project: deepin-clone
File: helper.cpp

QString Helper::lastWarningString()
{
    return m_warningString;
}

Project: deepin-clone
File: ddevicediskinfo.cpp

qint64 DDeviceDiskInfoPrivate::maxReadableDataSize() const
{
    if (children.isEmpty()) {
        return totalReadableDataSize();
    }

    if (type == DDiskInfo::Disk)
        return children.last().sizeEnd() + 1;

    return children.first().totalSize();
}

Project: deepin-clone
File: helper.cpp

QString Helper::getDeviceForFile(const QString &file, QString *rootPath)
{
    if (file.isEmpty())
        return QString();

    if (Helper::isBlockSpecialFile(file))
        return file;

    QFileInfo info(file);

    while (!info.exists() && info.absoluteFilePath() != "/")
        info.setFile(info.absolutePath());

    QStorageInfo storage_info(info.absoluteFilePath());

    if (rootPath)
        *rootPath = storage_info.rootPath();

    return QString::fromUtf8(storage_info.device());
}

Project: deepin-clone
File: ddevicediskinfo.cpp

QString DDeviceDiskInfoPrivate::filePath() const
{
    return name;
}

Project: deepin-clone
File: ddevicediskinfo.cpp

QList<DDeviceDiskInfo> DDeviceDiskInfo::localeDiskList()
{
    const QJsonArray &block_devices = Helper::getBlockDevices();

    QList<DDeviceDiskInfo> list;

    for (const QJsonValue &value : block_devices) {
        const QJsonObject &obj = value.toObject();

        if (Global::disableLoopDevice && obj.value("type").toString() == "loop")
            continue;

        DDeviceDiskInfo info;

        info.d = new DDeviceDiskInfoPrivate(&info);
        info.d_func()->init(obj);
        list << info;
    }

    return list;
}

Project: deepin-clone
File: helper.cpp

bool Helper::tryUmountDevice(const QString &device)
{
    const QJsonArray &array = getBlockDevices("-l " + device);

    for (const QJsonValue &device : array) {
        const QJsonObject &obj = device.toObject();

        if (!obj.value("mountpoint").isNull()) {
            if (processExec(QString("umount -d %1 --fake").arg(obj.value("name").toString())) != 0)
                return false;
        }
    }

    return true;
}

Project: deepin-clone
File: helper.cpp

bool Helper::mountDevice(const QString &device, const QString &path, bool readonly)
{
    if (readonly)
        return processExec(QString("mount -r %1 %2").arg(device, path)) == 0;

    return processExec(QString("mount %1 %2").arg(device, path)) == 0;
}

Project: deepin-clone
File: ddevicediskinfo.cpp

DDeviceDiskInfoPrivate::DDeviceDiskInfoPrivate(DDeviceDiskInfo *qq)
    : DDiskInfoPrivate(qq)
{

}

Project: deepin-clone
File: ddevicediskinfo.cpp

qint64 DDeviceDiskInfoPrivate::totalWritableDataSize() const
{
    return size;
}

Project: deepin-clone
File: ddevicediskinfo.cpp

qint64 DDeviceDiskInfoPrivate::write(const char *data, qint64 maxSize)
{
    if (!process)
        return -1;

    if (process->state() != QProcess::Running)
        return -1;

    qint64 size = process->write(data, maxSize);

    QElapsedTimer timer;

    timer.start();

    int timeout = 5000;

    while (process->state() == QProcess::Running && process->bytesToWrite() > 0) {
        process->waitForBytesWritten();

        if (timer.elapsed() > timeout) {
            timeout += 5000;

            dCWarning("Wait for bytes written timeout, elapsed: %lld, bytes to write: %lld", timer.elapsed(), process->bytesToWrite());
        }
    }

    return size;
}

Project: deepin-clone
File: helper.cpp

QString Helper::parseSerialUrl(const QString &urlString, QString *errorString)
{
    if (urlString.isEmpty())
        return QString();

    const QUrl url(urlString);
    const QString serial_number = urlString.split("//").at(1).split(":").first();
    const int part_index = url.port();
    const QString &path = url.path();
    const QString &device = Helper::findDiskBySerialIndexNumber(serial_number, part_index);
    const QString &device_url = part_index > 0 ? QString("serial://%1:%2").arg(serial_number).arg(part_index) : "serial://" + serial_number;

    if (device.isEmpty()) {
        if (errorString) {
            if (part_index > 0)
                *errorString = QObject::tr("Partition \"%1\" not found").arg(device_url);
            else
                *errorString = QObject::tr("Disk \"%1\" not found").arg(device_url);
        }

        return device;
    }

    if (path.isEmpty())
        return device;

    const QString &mp = Helper::mountPoint(device);

    QDir mount_point(mp);

    if (mp.isEmpty()) {
        QString mount_name;

        if (part_index >= 0)
            mount_name = QString("%1-%2").arg(serial_number).arg(part_index);
        else
            mount_name = serial_number;

        const QString &_mount_point = Helper::temporaryMountDevice(device, mount_name);

        if (_mount_point.isEmpty()) {
            if (errorString)
                *errorString = QObject::tr("Failed to mount partition \"%1\"").arg(device_url);

            return QString();
        }

        mount_point.setPath(_mount_point);
    }

    if (mount_point.absolutePath() == "/")
        return path;

    return mount_point.absolutePath() + path;
}

Project: deepin-clone
File: ddevicediskinfo.cpp

bool DDeviceDiskInfoPrivate::openDataStream(int index)
{
    if (process) {
        process->deleteLater();
    }

    process = new QProcess();

    QObject::connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
                     process, [this] (int code, QProcess::ExitStatus status) {
        if (isClosing())
            return;

        if (status == QProcess::CrashExit) {
            setErrorString(QObject::tr("process \"%1 %2\" crashed").arg(process->program()).arg(process->arguments().join(" ")));
        } else if (code != 0) {
            setErrorString(QObject::tr("Failed to perform process \"%1 %2\", error: %3").arg(process->program()).arg(process->arguments().join(" ")).arg(QString::fromUtf8(process->readAllStandardError())));
        }
    });

    switch (currentScope) {
    case DDiskInfo::Headgear: {
        if (type != DDiskInfo::Disk) {
            setErrorString(QObject::tr("\"%1\" is not a disk device").arg(filePath()));

            return false;
        }

        if (currentMode == DDiskInfo::Read) {
            process->start(QStringLiteral("dd if=%1 bs=512 count=2048 status=none").arg(filePath()), QIODevice::ReadOnly);
        } else {
            process->start(QStringLiteral("dd of=%1 bs=512 status=none conv=fsync").arg(filePath()));
        }

        break;
    }
    case DDiskInfo::PartitionTable: {
        if (type != DDiskInfo::Disk) {
            setErrorString(QObject::tr("\"%1\" is not a disk device").arg(filePath()));

            return false;
        }

        if (currentMode == DDiskInfo::Read)
            process->start(QStringLiteral("sfdisk -d %1").arg(filePath()), QIODevice::ReadOnly);
        else
            process->start(QStringLiteral("sfdisk %1 --no-reread").arg(filePath()));

        break;
    }
    case DDiskInfo::Partition: {
        const DPartInfo &part = (index == 0 && currentMode == DDiskInfo::Write) ? DDevicePartInfo(filePath()) : q->getPartByNumber(index);

        if (!part) {
            dCDebug("Part is null(index: %d)", index);

            return false;
        }

        dCDebug("Try open device: %s, mode: %s", qPrintable(part.filePath()), currentMode == DDiskInfo::Read ? "Read" : "Write");

        if (Helper::isMounted(part.filePath())) {
            if (Helper::umountDevice(part.filePath())) {
                part.d->mountPoint.clear();
            } else {
                setErrorString(QObject::tr("\"%1\" is busy").arg(part.filePath()));

                return false;
            }
        }

        if (currentMode == DDiskInfo::Read) {
            const QString &executer = Helper::getPartcloneExecuter(part);
            process->start(QStringLiteral("%1 -s %2 -o - -c -z %3 -L /var/log/partclone.log").arg(executer).arg(part.filePath()).arg(Global::bufferSize), QIODevice::ReadOnly);
        } else {
            process->start(QStringLiteral("partclone.restore -s - -o %2 -z %3 -L /var/log/partclone.log").arg(part.filePath()).arg(Global::bufferSize));
        }

        break;
    }
    case DDiskInfo::JsonInfo: {
        process->deleteLater();
        process = 0;
        buffer.setData(q->toJson());
        break;
    }
    default:
        return false;
    }

    if (process) {
        if (!process->waitForStarted()) {
            setErrorString(QObject::tr("Failed to start \"%1 %2\", error: %3").arg(process->program()).arg(process->arguments().join(" ")).arg(process->errorString()));

            return false;
        }

        dCDebug("The \"%s %s\" command start finished", qPrintable(process->program()), qPrintable(process->arguments().join(" ")));
    }

    bool ok = process ? process->isOpen() : buffer.open(QIODevice::ReadOnly);

    if (!ok) {
        setErrorString(QObject::tr("Failed to open process, error: %1").arg(process ? process->errorString(): buffer.errorString()));
    }

    return ok;
}

Project: deepin-clone
File: helper.cpp

const QLoggingCategory &Helper::loggerCategory()
{
    return lcDeepinGhost();
}

Project: deepin-clone
File: helper.cpp

QJsonArray Helper::getBlockDevices(const QString &commandExtraArg)
{
    const QByteArray &array = Helper::callLsblk(commandExtraArg);

    QJsonParseError error;

    const QJsonDocument &jd = QJsonDocument::fromJson(QString::fromUtf8(array).toUtf8(), &error);

    if (error.error != QJsonParseError::NoError) {
        dCError(error.errorString());
    }

    return jd.object().value("blockdevices").toArray();
}

Project: deepin-clone
File: ddevicediskinfo.cpp

DDeviceDiskInfo::DDeviceDiskInfo()
{

}

Project: deepin-clone
File: helper.cpp

int Helper::processExec(const QString &command, int timeout)
{
    QProcess process;

    return processExec(&process, command, timeout);
}

Project: deepin-clone
File: helper.cpp

Helper *Helper::instance()
{
    return _g_globalHelper;
}