/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2021, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of maveod.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "factoryresethandler.h"
#include "systemdservice.h"
#include "loggingcategories.h"

#include <QFileInfo>

FactoryResetHandler::FactoryResetHandler(SystemdService *systemdService, QObject *parent) :
    QObject(parent),
    m_systemdService(systemdService)
{
    // [1] Enable the recovery boot flag as fallback in case the process gets interrupted by power cut or the process fails
    // [2] Make sure the factory reset binary exists and is executable, otherwise just reboot to recovery and we are done -> [6]
    // [3] Start the factory reset binary:
    //     - on success -> [4]
    //           * disable recovery boot, we want to reboot into the current system we successfully factory-resetted
    //           * continue with reboot into clean system
    //     - on failure -> [5]
    //           * the recovery since still enabled, continue with reboot into recovery mode
    // [6] reboot
}

bool FactoryResetHandler::factoryResetRunning() const
{
    return m_factoryResetRunning;
}

void FactoryResetHandler::startFactoryReset()
{
    if (m_factoryResetRunning) {
        qCWarning(dcMaveod()) << "Start factory reset process called but the process is already running. Ignoring the request and wait for the process to finish";
        return;
    }

    m_factoryResetRunning = true;
    emit factoryResetRunningChanged(m_factoryResetRunning);

    // [1]
    if (!enableRecoveryBoot()) {
        qCWarning(dcMaveod()) << "Cannot factory reset. The recovery boot could not be enabled. Cannot continue here...";
        m_factoryResetRunning = false;
        emit factoryResetRunningChanged(m_factoryResetRunning);
        return;
    }

    // [2]
    bool useFactoryResetBinary = true;
    QFileInfo factoryResetBinary("/usr/bin/system-factoryreset-data");
    if (!factoryResetBinary.exists() && useFactoryResetBinary) {
        qCWarning(dcMaveod()) << "Could not find factory reset binary" << factoryResetBinary.fileName();
        useFactoryResetBinary = false;
    }

    if (!factoryResetBinary.isExecutable() && useFactoryResetBinary) {
        qCWarning(dcMaveod()) << "The factory reset binary" << factoryResetBinary.fileName() << "is not executable.";
        useFactoryResetBinary = false;
    }

    if (useFactoryResetBinary) {
        // [3]
        if (m_factoryResetProcess) {
            m_factoryResetProcess->kill();
            delete m_factoryResetProcess;
            m_factoryResetProcess = nullptr;
        }

        m_factoryResetProcess = new QProcess(this);
        m_factoryResetProcess->setProcessChannelMode(QProcess::MergedChannels);

        connect(m_factoryResetProcess, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &FactoryResetHandler::onFactoryResetProcessFinished);
        connect(m_factoryResetProcess, &QProcess::readyRead, this, &FactoryResetHandler::onFactoryResetProcessReadyRead);

        qCDebug(dcMaveod()) << "Starting the factory reset binary...";
        m_factoryResetProcess->start("system-factoryreset-data", { "-f" });

    } else {
        // [6]
        qCWarning(dcMaveod()) << "System factory reset binary seems not to be available, performing a recovery instead.";
        if (!m_systemdService->rebootSystem()) {
            qCWarning(dcMaveod()) << "Could not restart the system. Factory reset failed.";
        } else {
            qCDebug(dcMaveod()) << "Requested successfully to start system recovery mechanism.";
        }
    }
}

bool FactoryResetHandler::enableRecoveryBoot()
{
    // Set the boot_recovery u-boot ENV to 1
    qCDebug(dcMaveod()) << "Enable recovery boot mode";
    QProcess process;
    process.start("fw_setenv", {"boot_recovery", "1"});
    if (!process.waitForStarted()) {
        qCWarning(dcMaveod()) << "Failed to start process to enable recovery boot.";
        return false;
    }

    process.waitForFinished();
    if (process.exitCode() != 0) {
        qCWarning(dcMaveod()) << "Could not enable recovery boot mode. Stderr:";
        qCWarning(dcMaveod()) << process.readAllStandardError();
        return false;
    }

    qCDebug(dcMaveod()) << "Recovery boot mode enabled successfully.";
    return true;
}

bool FactoryResetHandler::disableRecoveryBoot()
{
    // Set the boot_recovery u-boot ENV to 0
    qCDebug(dcMaveod()) << "Disable recovery boot mode";
    QProcess process;
    process.start("fw_setenv", {"boot_recovery", "0"});
    if (!process.waitForStarted()) {
        qCWarning(dcMaveod()) << "Failed to start process to disable recovery boot.";
        return false;
    }

    process.waitForFinished();
    if (process.exitCode() != 0) {
        qCWarning(dcMaveod()) << "Could not disable recovery boot mode. Stderr:";
        qCWarning(dcMaveod()) << process.readAllStandardError();
        return false;
    }

    qCDebug(dcMaveod()) << "Recovery boot mode disabled successfully.";
    return true;
}

void FactoryResetHandler::onFactoryResetProcessReadyRead()
{
    qCDebug(dcMaveod()) << "FactoryResetProcess:" << qUtf8Printable(m_factoryResetProcess->readAll());
}

void FactoryResetHandler::onFactoryResetProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
    qCDebug(dcMaveod()) << "Factory reset data process finished with exit code" << exitCode << exitStatus;
    m_factoryResetProcess->deleteLater();
    m_factoryResetProcess = nullptr;

    // Verify success
    if (exitCode == 0) {
        // [4]
        qCDebug(dcMaveod()) << "Factory reset process finished successfully.";
        // Try to disable the recovery boot...if it will not succeed,
        // the reboot will trigger a recovery, in any case we are done here
        disableRecoveryBoot();
    } else {
        // [5]
        qCWarning(dcMaveod()) << "Factory reset process finished with error. Performing reboot into recovery mode";
    }

    // [6]
    if (!m_systemdService->rebootSystem()) {
        qCWarning(dcMaveod()) << "Could not restart the system. Factory reset failed.";
    } else {
        qCDebug(dcMaveod()) << "Requested successfully to reboot system.";
    }
}
