~aleteoryx/muditaos

a0677af340a1fec8e8652b64fb97ca7f0902de12 — Marek Niepieklo 4 years ago 5b04471
[CP-615] Update existing backup/restore implementation in OS

[CP-648] Handle updated API requests

Updated B&R to handle new API
Extended error reporting
Backup/Restore code clean up

[CP-649] Check OS version compatibility during restore
Added check of OS and backup versions
Fixed adding of version.json to PurePhone.img
29 files changed, 880 insertions(+), 525 deletions(-)

M cmake/modules/DiskImage.cmake
M module-platform/CommonPlatform.cpp
M module-services/service-db/ServiceDBCommon.cpp
R module-services/service-desktop/{endpoints/backup => }/BackupRestore.cpp
M module-services/service-desktop/CMakeLists.txt
M module-services/service-desktop/ServiceDesktop.cpp
M module-services/service-desktop/endpoints/CMakeLists.txt
M module-services/service-desktop/endpoints/backup/BackupEndpoint.cpp
A module-services/service-desktop/endpoints/backup/BackupHelper.cpp
M module-services/service-desktop/endpoints/include/endpoints/JsonKeyNames.hpp
M module-services/service-desktop/endpoints/include/endpoints/backup/BackupEndpoint.hpp
A module-services/service-desktop/endpoints/include/endpoints/backup/BackupHelper.hpp
D module-services/service-desktop/endpoints/include/endpoints/backup/BackupRestore.hpp
M module-services/service-desktop/endpoints/include/endpoints/restore/RestoreEndpoint.hpp
A module-services/service-desktop/endpoints/include/endpoints/restore/RestoreHelper.hpp
M module-services/service-desktop/endpoints/restore/RestoreEndpoint.cpp
A module-services/service-desktop/endpoints/restore/RestoreHelper.cpp
A module-services/service-desktop/include/service-desktop/BackupRestore.hpp
M module-services/service-desktop/include/service-desktop/ServiceDesktop.hpp
M module-vfs/paths/include/purefs/filesystem_paths.hpp
M products/PurePhone/services/db/ServiceDB.cpp
M products/PurePhone/services/desktop/endpoints/deviceInfo/DeviceInfoEndpoint.cpp
M test/get_os_log.py
M test/harness
M test/pytest/service-desktop/test_backup.py
M test/pytest/service-desktop/test_device_info.py
M test/pytest/service-desktop/test_restore.py
M third-party/microtar/CMakeLists.txt
M tools/generate_image.sh
M cmake/modules/DiskImage.cmake => cmake/modules/DiskImage.cmake +7 -2
@@ 10,6 10,7 @@ function(add_image)
    if(NOT ${PROJECT_TARGET_NAME} STREQUAL "linux")
        set(HAS_BOOTFILE YES)
        set(HAS_UPDATER YES)
        set(HAS_VERSION YES)
    endif()

    set(SCRIPT_PATH ${CMAKE_SOURCE_DIR}/tools/generate_image.sh)


@@ 29,7 30,11 @@ function(add_image)
        set(BIN_FILE_PATH "")
    endif()

    set(VERSION_FILE_PATH ${CMAKE_BINARY_DIR}/${_ARG_PRODUCT}-version.json)
    if(HAS_VERSION)
        set(VERSION_FILE_PATH ${CMAKE_BINARY_DIR}/${_ARG_PRODUCT}-version.json)
    else()
        set(VERSION_FILE_PATH "")
    endif()

    if(HAS_UPDATER)
        set(UPDATER_FILE_PATH ${CMAKE_BINARY_DIR}/updater.bin)


@@ 68,7 73,7 @@ function(add_image)
    message("Adding disk image target: ${DISK_IMAGE_NAME}")

    add_custom_target(${_ARG_PRODUCT}-disk-img
        DEPENDS ${DISK_IMAGE_NAME})
        DEPENDS ${DISK_IMAGE_NAME} ${VERSION_FILE_PATH})

endfunction()


M module-platform/CommonPlatform.cpp => module-platform/CommonPlatform.cpp +8 -0
@@ 19,5 19,13 @@ namespace platform
        if (!std::filesystem::exists(purefs::dir::getCrashDumpsPath())) {
            std::filesystem::create_directories(purefs::dir::getCrashDumpsPath());
        }

        if (!std::filesystem::exists(purefs::dir::getBackupOSPath())) {
            std::filesystem::create_directories(purefs::dir::getBackupOSPath());
        }

        if (!std::filesystem::exists(purefs::dir::getTemporaryPath())) {
            std::filesystem::create_directories(purefs::dir::getTemporaryPath());
        }
    }
} // namespace platform

M module-services/service-db/ServiceDBCommon.cpp => module-services/service-db/ServiceDBCommon.cpp +1 -0
@@ 60,6 60,7 @@ sys::ReturnCodes ServiceDBCommon::InitHandler()

sys::ReturnCodes ServiceDBCommon::DeinitHandler()
{
    Database::deinitialize();
    return sys::ReturnCodes::Success;
}


R module-services/service-desktop/endpoints/backup/BackupRestore.cpp => module-services/service-desktop/BackupRestore.cpp +226 -182
@@ 1,7 1,8 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <endpoints/backup/BackupRestore.hpp>
#include <service-desktop/BackupRestore.hpp>
#include <endpoints/JsonKeyNames.hpp>

#include <SystemManager/SystemManagerCommon.hpp>
#include <log/log.hpp>


@@ 17,160 18,130 @@
#include <memory>
#include <string>
#include <vector>
#include <fstream>
#include <sstream>
#include <sys/statvfs.h>
#include <gsl/gsl>
#include <json11.hpp>
#include <Split.hpp>

namespace sys
{
    class Service;
} // namespace sys

namespace bkp
{
    inline constexpr auto backupInfo = "backup.json";
};

static const long unsigned int empty_dirlist_size = 2;
static bool isValidDirentry(const std::filesystem::directory_entry &direntry)
{
    return direntry.path() != "." && direntry.path() != ".." && direntry.path() != "...";
}
// this replaces std::filesystem::copy_file that is broken at
// the time this is implemented (chown issues after copy)
// once fixed this can be replaced
static bool copyFile(const std::filesystem::path &from, const std::filesystem::path &to)
{
    std::FILE *fromFp = std::fopen(from.c_str(), "r");
    std::FILE *toFp   = std::fopen(to.c_str(), "w");

    auto end = gsl::finally([fromFp, toFp] {
        std::fclose(fromFp);
        std::fclose(toFp);
    });

    if (fromFp == nullptr) {
        LOG_ERROR("can't open file for reading");
        return false;
    }

    if (toFp == nullptr) {
        LOG_ERROR("can't open file for writing");
        return false;
    }

    std::unique_ptr<unsigned char[]> buffer = std::make_unique<unsigned char[]>(purefs::buffer::tar_buf);
    size_t bytes;

    constexpr size_t streamBufferSize = 64 * 1024;
    auto streamBufferFrom             = std::make_unique<char[]>(streamBufferSize);
    auto streamBufferTo               = std::make_unique<char[]>(streamBufferSize);
    setvbuf(fromFp, streamBufferFrom.get(), _IOFBF, streamBufferSize);
    setvbuf(toFp, streamBufferTo.get(), _IOFBF, streamBufferSize);

    while ((bytes = std::fread(buffer.get(), purefs::buffer::tar_buf, 1, fromFp)) != 0) {
        if (std::fwrite(buffer.get(), 1, bytes, toFp) != bytes) {
            return false;
        }
    }

    return true;
}

bool BackupRestore::BackupUserFiles(sys::Service *ownerService, std::filesystem::path &path)
BackupRestore::CompletionCode BackupRestore::BackupUserFiles(sys::Service *ownerService, std::filesystem::path &path)
{
    assert(ownerService != nullptr);
    LOG_INFO("BackupUserFiles: backup started...");
    LOG_INFO("Backup started...");

    if (BackupRestore::RemoveBackupDir(path) == false) {
        return false;
        return CompletionCode::FSError;
    }

    if (BackupRestore::CreateBackupDir(path) == false) {
        return false;
        return CompletionCode::FSError;
    }

    LOG_INFO("BackupUserFiles: database backup started...");

    LOG_DEBUG("Database backup started...");
    if (DBServiceAPI::DBBackup(ownerService, path) == false) {
        LOG_ERROR("BackupUserFiles: database backup failed, quitting...");
        LOG_ERROR("Database backup failed, quitting...");
        BackupRestore::RemoveBackupDir(path);
        return false;
        return CompletionCode::DBError;
    }

    if (WriteBackupInfo(ownerService, path) == false) {
        LOG_ERROR("Failed to write backup info");
        LOG_ERROR("Failed to save backup info");
        BackupRestore::RemoveBackupDir(path);
        return false;
        return CompletionCode::CopyError;
    }

    LOG_DEBUG("Packing files");
    if (BackupRestore::PackUserFiles(path) == false) {
        LOG_ERROR("Failed pack backup");
        BackupRestore::RemoveBackupDir(path);
        return CompletionCode::PackError;
    }

    LOG_INFO("BackupUserFiles: packing files");
    return BackupRestore::PackUserFiles(path);
    return CompletionCode::Success;
}

bool BackupRestore::WriteBackupInfo(sys::Service *ownerService, const std::filesystem::path &path)
{
    LOG_INFO("Writing backup info");

    if (std::filesystem::is_directory(path)) {
        try {
            copyFile(purefs::dir::getRootDiskPath() / purefs::file::boot_json, path / bkp::backupInfo);

            LOG_DEBUG("%s copied to %s",
                      (purefs::dir::getRootDiskPath() / purefs::file::boot_json).c_str(),
                      (path / bkp::backupInfo).c_str());
    if (!std::filesystem::is_directory(path)) {
        LOG_ERROR("%s is not a directory", path.c_str());
        return false;
    }

            return true;
        }
        catch (std::filesystem::filesystem_error &e) {
            LOG_ERROR("failed to copy %s->%s error:\"%s\"",
                      (purefs::dir::getRootDiskPath() / purefs::file::boot_json).c_str(),
                      (path / bkp::backupInfo).c_str(),
                      e.what());
    const auto version_json = purefs::dir::getCurrentOSPath() / purefs::file::version_json;

            return false;
        }
    }
    else {
        LOG_ERROR("%s is not a directory", path.c_str());
    std::error_code errorCode;
    if (!std::filesystem::copy_file(version_json, path / bkp::backupInfo, errorCode)) {
        LOG_ERROR("Failed to copy %s -> %s, error: %d",
                  (version_json).c_str(),
                  (path / bkp::backupInfo).c_str(),
                  errorCode.value());
        return false;
    }

    LOG_DEBUG("%s copied to %s", (version_json).c_str(), (path / bkp::backupInfo).c_str());
    return true;
}

bool BackupRestore::RestoreUserFiles(sys::Service *ownerService, const std::filesystem::path &path)
BackupRestore::CompletionCode BackupRestore::RestoreUserFiles(sys::Service *ownerService,
                                                              const std::filesystem::path &path)
{
    assert(ownerService != nullptr);
    LOG_INFO("RestoreUserFiles: restore started");

    LOG_INFO("Restore started");

    if (BackupRestore::UnpackBackupFile(path) == false) {
        return false;
        LOG_ERROR("Can't unpack user files");
        return CompletionCode::UnpackError;
    }

    if (sys::SystemManagerCommon::Restore(ownerService) == false) {
        LOG_ERROR("Can't enter update system state");
        return false;
    if (BackupRestore::CanRestoreFromBackup(TempPathForBackupFile(path)) == false) {
        LOG_ERROR("Can't restore user files");
        return CompletionCode::FSError;
    }
    else {
        LOG_INFO("entered update state");

        if (BackupRestore::ReplaceUserFiles(path) == false) {
            LOG_ERROR("can't restore user files");
            return false;
        }
    if (sys::SystemManagerCommon::Restore(ownerService) == false) {
        LOG_ERROR("Can't enter system restore state");
        return CompletionCode::OtherError;
    }

    // BackupRestore::RemoveBackupDir(path);
    LOG_INFO("Entered restore state");

    return true;
    if (BackupRestore::ReplaceUserFiles(path) == false) {
        LOG_ERROR("Can't restore user files");
        return CompletionCode::CopyError;
    }

    return CompletionCode::Success;
}

bool BackupRestore::RemoveBackupDir(std::filesystem::path &path)
bool BackupRestore::RemoveBackupDir(const std::filesystem::path &path)
{
    /* prepare directories */
    if (std::filesystem::is_directory(path)) {
        LOG_INFO("RemoveBackupDir: removing backup directory %s...", path.c_str());
        LOG_INFO("Removing backup directory %s...", path.c_str());
        std::error_code errorCode;

        try {
            std::filesystem::remove_all(path);
        }
        catch (const std::filesystem::filesystem_error &e) {
            LOG_ERROR("RemoveBackupDir: removing backup directory %s failed, error: %s.", path.c_str(), e.what());
        if (!std::filesystem::remove_all(path, errorCode)) {
            LOG_ERROR("Removing backup directory %s failed, error: %d.", path.c_str(), errorCode.value());
            return false;
        }
    }


@@ 178,32 149,14 @@ bool BackupRestore::RemoveBackupDir(std::filesystem::path &path)
    return true;
}

bool BackupRestore::CreateBackupDir(std::filesystem::path &path)
bool BackupRestore::CreateBackupDir(const std::filesystem::path &path)
{
    LOG_INFO("CreateBackupDir: creating backup directory %s...", path.c_str());
    LOG_INFO("Creating backup directory %s...", path.c_str());
    std::error_code errorCode;

    if (!std::filesystem::is_directory(purefs::dir::getBackupOSPath())) {
        if (!std::filesystem::create_directory(purefs::dir::getBackupOSPath(), errorCode)) {
            LOG_ERROR("CreateBackupDir: creating backup directory %s failed. \"%s\"",
                      purefs::dir::getBackupOSPath().c_str(),
                      errorCode.message().c_str());
            return false;
        }
    }

    if (!std::filesystem::is_directory(purefs::dir::getTemporaryPath())) {
        if (!std::filesystem::create_directory(purefs::dir::getTemporaryPath(), errorCode)) {
            LOG_ERROR("CreateBackupDir: creating backup directory %s failed. \"%s\"",
                      purefs::dir::getTemporaryPath().c_str(),
                      errorCode.message().c_str());
            return false;
        }
    }

    if (!std::filesystem::is_directory(path)) {
        if (!std::filesystem::create_directory(path, errorCode)) {
            LOG_ERROR("CreateBackupDir: creating backup directory failed.");
    if (!std::filesystem::exists(path)) {
        if (!std::filesystem::create_directories(path, errorCode)) {
            LOG_ERROR("Failed to create directory: %s, error: %d", path.c_str(), errorCode.value());
            return false;
        }
    }


@@ 211,23 164,22 @@ bool BackupRestore::CreateBackupDir(std::filesystem::path &path)
    return true;
}

bool BackupRestore::PackUserFiles(std::filesystem::path &path)
bool BackupRestore::PackUserFiles(const std::filesystem::path &path)
{
    if (std::filesystem::is_empty(path)) {
        LOG_ERROR("backup dir is empty, nothing to backup, quitting...");
        LOG_ERROR("Backup dir is empty, nothing to backup, quitting...");
        BackupRestore::RemoveBackupDir(path);
        return false;
    }

    std::filesystem::path tarFilePath =
        (purefs::dir::getBackupOSPath() / path.filename()).replace_extension(purefs::extension::tar);
    std::filesystem::path tarFilePath = (purefs::dir::getBackupOSPath() / path.filename());
    mtar_t tarFile;

    LOG_INFO("opening tar file...");
    LOG_INFO("Opening tar %s file...", tarFilePath.c_str());

    int ret = mtar_open(&tarFile, tarFilePath.c_str(), "w");
    if (ret != MTAR_ESUCCESS) {
        LOG_ERROR("opening tar file failed, quitting...");
        LOG_ERROR("Opening tar file failed, quitting...");
        BackupRestore::RemoveBackupDir(path);
        return false;
    }


@@ 236,38 188,38 @@ bool BackupRestore::PackUserFiles(std::filesystem::path &path)
    constexpr size_t streamBufferSize = 64 * 1024;
    auto streamBuffer                 = std::make_unique<char[]>(streamBufferSize);
    setvbuf(tarFile.stream, streamBuffer.get(), _IOFBF, streamBufferSize);
    std::error_code e;
    std::error_code errorCode;

    for (auto &direntry : std::filesystem::directory_iterator(path)) {
        if (!isValidDirentry(direntry)) {
            continue;
        }

        LOG_INFO("archiving file ...");
        LOG_INFO("Archiving file ...");
        auto *file = std::fopen(direntry.path().string().c_str(), "r");

        if (file == nullptr) {
            LOG_ERROR("archiving file failed, cannot open file, quitting...");
            LOG_ERROR("Archiving file failed, cannot open file, quitting...");
            mtar_close(&tarFile);
            BackupRestore::RemoveBackupDir(path);
            return false;
        }

        LOG_DEBUG("writting tar header ...");
        LOG_DEBUG("Writting tar header ...");

        if (mtar_write_file_header(&tarFile,
                                   direntry.path().filename().c_str(),
                                   static_cast<unsigned>(std::filesystem::file_size(direntry))) != MTAR_ESUCCESS) {
            LOG_ERROR("writing tar header failed");
            LOG_ERROR("Writing tar header failed");
            std::fclose(file);
            mtar_close(&tarFile);
            BackupRestore::RemoveBackupDir(path);
            return false;
        }

        uintmax_t filesize = std::filesystem::file_size(direntry.path(), e);
        if (e) {
            LOG_ERROR("failed to get size for file");
        uintmax_t filesize = std::filesystem::file_size(direntry.path(), errorCode);
        if (errorCode) {
            LOG_ERROR("Failed to get size for file %s, error: %d", direntry.path().c_str(), errorCode.value());
            BackupRestore::RemoveBackupDir(path);
            return false;
        }


@@ 282,19 234,19 @@ bool BackupRestore::PackUserFiles(std::filesystem::path &path)
                readsize = purefs::buffer::tar_buf;
            }

            LOG_DEBUG("reading file ...");
            LOG_DEBUG("Reading file ...");

            if (std::fread(buffer.get(), 1, readsize, file) != readsize) {
                LOG_ERROR("reading file failed, quitting...");
                LOG_ERROR("Reading file failed, quitting...");
                std::fclose(file);
                mtar_close(&tarFile);
                BackupRestore::RemoveBackupDir(path);
                return false;
            }

            LOG_DEBUG("writting into backup...");
            LOG_DEBUG("Writting into backup...");
            if (mtar_write_data(&tarFile, buffer.get(), readsize) != MTAR_ESUCCESS) {
                LOG_ERROR("PackUserFiles: writting into backup failed, quitting...");
                LOG_ERROR("Writting into backup failed, quitting...");
                std::fclose(file);
                mtar_close(&tarFile);
                BackupRestore::RemoveBackupDir(path);


@@ 302,35 254,35 @@ bool BackupRestore::PackUserFiles(std::filesystem::path &path)
            }
        }

        LOG_INFO("closing file...");
        LOG_INFO("Closing file...");
        if (std::fclose(file) != 0) {
            LOG_ERROR("PackUserFiles: closing file failed, quitting...");
            LOG_ERROR("Closing file failed, quitting...");
            mtar_close(&tarFile);
            BackupRestore::RemoveBackupDir(path);
            return false;
        }

        LOG_INFO("deleting file ...");
        LOG_INFO("Deleting file ...");

        if (std::remove(direntry.path().c_str()) != 0) {
            LOG_ERROR("PackUserFiles: deleting file failed, quitting...");
        if (!std::filesystem::remove(direntry.path(), errorCode)) {
            LOG_ERROR("Deleting file failed, error: %d, quitting...", errorCode.value());
            mtar_close(&tarFile);
            BackupRestore::RemoveBackupDir(path);
            return false;
        }
    }

    LOG_INFO("finalizing tar file...");
    LOG_INFO("Finalizing tar file...");
    if (mtar_finalize(&tarFile) != MTAR_ESUCCESS) {
        LOG_ERROR("PackUserFiles: finalizing tar file failed, quitting....");
        LOG_ERROR("Finalizing tar file failed, quitting....");
        mtar_close(&tarFile);
        BackupRestore::RemoveBackupDir(path);
        return false;
    }

    LOG_INFO("closing tar file...");
    LOG_INFO("Closing tar file...");
    if (mtar_close(&tarFile) != MTAR_ESUCCESS) {
        LOG_ERROR("PackUserFiles: closing tar file failed, quitting...");
        LOG_ERROR("Closing tar file failed, quitting...");
        BackupRestore::RemoveBackupDir(path);
        return false;
    }


@@ 338,29 290,42 @@ bool BackupRestore::PackUserFiles(std::filesystem::path &path)
    return true;
}

auto BackupRestore::TempPathForBackupFile(const std::filesystem::path &tarFilePath) -> std::filesystem::path const
{
    std::filesystem::path extractFolder;

    if (tarFilePath.has_extension()) {
        extractFolder = tarFilePath.stem();
    }
    else {
        extractFolder = tarFilePath.filename();
    }

    return purefs::dir::getTemporaryPath() / extractFolder;
}

bool BackupRestore::UnpackBackupFile(const std::filesystem::path &tarFilePath)
{
    mtar_t tarFile;
    mtar_header_t tarHeader;
    std::error_code e;
    std::error_code errorCode;

    auto extractDestination = purefs::dir::getTemporaryPath() / tarFilePath.stem();
    auto extractDestination = TempPathForBackupFile(tarFilePath);

    LOG_INFO("creating temporary directory");
    if (!std::filesystem::is_directory(extractDestination, e)) {
        std::filesystem::create_directory(extractDestination, e);
        if (e) {
            LOG_ERROR("Can't create temporary directory");
    LOG_INFO("Creating temporary directory");
    if (!std::filesystem::exists(extractDestination, errorCode)) {
        if (!std::filesystem::create_directories(extractDestination, errorCode)) {
            LOG_ERROR("Can't create temporary directory, error: %d", errorCode.value());
            return false;
        }
    }

    LOG_INFO("opening tar file ...");
    LOG_INFO("Opening tar file ...");

    int ret = mtar_open(&tarFile, tarFilePath.c_str(), "r");

    if (ret != MTAR_ESUCCESS) {
        LOG_ERROR("opening tar file failed, quitting...");
        LOG_ERROR("Opening tar file failed, quitting...");
        return false;
    }



@@ 371,16 336,16 @@ bool BackupRestore::UnpackBackupFile(const std::filesystem::path &tarFilePath)

    do {
        ret = mtar_read_header(&tarFile, &tarHeader);
        LOG_DEBUG("reading tar header name...");
        LOG_DEBUG("Reading tar header name...");

        if ((tarHeader.type == MTAR_TREG) && (ret == MTAR_ESUCCESS)) {
            LOG_DEBUG("extracting tar file ...");
            LOG_DEBUG("Extracting tar file ...");

            std::filesystem::path extractedFile = extractDestination / tarHeader.name;
            auto *file                          = std::fopen(extractedFile.c_str(), "w");

            if (file == nullptr) {
                LOG_ERROR("can't open file for writing");
                LOG_ERROR("Can't open file for writing");
                mtar_close(&tarFile);
                return false;
            }


@@ 402,60 367,138 @@ bool BackupRestore::UnpackBackupFile(const std::filesystem::path &tarFilePath)
                }

                if (mtar_read_data(&tarFile, buffer.get(), readsize) != MTAR_ESUCCESS) {
                    LOG_ERROR("extracting file failed, quitting...");
                    LOG_ERROR("Extracting file failed, quitting...");
                    mtar_close(&tarFile);
                    std::fclose(file);
                    std::remove(extractedFile.c_str());
                    std::filesystem::remove(extractedFile.c_str());
                    return false;
                }

                if (std::fwrite(buffer.get(), readsize, 1, file) != readsize) {
                if (std::fwrite(buffer.get(), 1, readsize, file) != readsize) {
                    LOG_ERROR("writting file failed, quitting...");
                    mtar_close(&tarFile);
                    std::fclose(file);
                    std::remove(extractedFile.c_str());
                    std::filesystem::remove(extractedFile.c_str());
                    return false;
                }
            }

            LOG_INFO("extracting file succeeded");
            LOG_INFO("Extracting file succeeded");
            std::fclose(file);
        }
        else {
            LOG_DEBUG("found header %d, skipping", tarHeader.type);
            LOG_DEBUG("Found header %d, skipping", tarHeader.type);
        }

        ret = mtar_next(&tarFile);
        LOG_DEBUG("reading tar next status %s", mtar_strerror(ret));
        LOG_DEBUG("Reading tar next status %s", mtar_strerror(ret));
    } while (ret == MTAR_ESUCCESS);

    LOG_DEBUG("cleanup");
    LOG_DEBUG("Cleanup");
    mtar_close(&tarFile);
    std::remove(tarFilePath.c_str());
    errorCode.clear();
    std::filesystem::remove(tarFilePath.c_str(), errorCode);

    if (errorCode) {
        LOG_WARN("Can't cleanup temporary dir, error: %d", errorCode.value());
    }

    return true;
}

std::string BackupRestore::ReadFileAsString(const std::filesystem::path &fileToRead)
{
    const auto file = std::ifstream(fileToRead);

    if (!file.is_open()) {
        LOG_ERROR("Can't open %s file", fileToRead.string().c_str());
        return {};
    }

    std::ostringstream fileContents;
    fileContents << file.rdbuf();

    return fileContents.str();
}

    if (e) {
        LOG_WARN("can't cleanup temporary dir");
using namespace sdesktop::endpoints;

std::string BackupRestore::ReadVersionFromJsonFile(const std::filesystem::path &jsonFilePath)
{
    const auto jsonFile = ReadFileAsString(jsonFilePath);

    if (jsonFile.empty()) {
        LOG_ERROR("Can't read data from %s file", jsonFilePath.c_str());
        return {};
    }

    std::string err;
    const auto jsonFile_Root = json11::Json::parse(jsonFile, err);
    if (jsonFile_Root.is_null()) {
        LOG_ERROR("Can't parse %s file: %s", jsonFilePath.c_str(), err.c_str());
        return {};
    }

    auto bootVersionObject = jsonFile_Root[json::boot].object_items();

    if (!bootVersionObject.count(json::version)) {
        LOG_ERROR("No OS version in %s file", jsonFilePath.c_str());
        return {};
    }

    return (bootVersionObject[json::version]).string_value();
}

bool BackupRestore::CheckBackupVersion(const std::filesystem::path &extractedBackup)
{
    const auto backupVersion = ReadVersionFromJsonFile(extractedBackup / bkp::backupInfo);

    if (backupVersion.empty()) {
        LOG_ERROR("Backup OS version empty");
        return false;
    }

    const auto currentVersion = ReadVersionFromJsonFile(purefs::dir::getCurrentOSPath() / purefs::file::version_json);

    if (currentVersion.empty()) {
        LOG_ERROR("Current OS version empty");
        return false;
    }

    auto const currentVersionTokens = utils::split(currentVersion, ".");
    auto const backupVersionTokens  = utils::split(backupVersion, ".");

    if (currentVersionTokens[0] < backupVersionTokens[0] || //  major version
        currentVersionTokens[1] < backupVersionTokens[1]) { //  minor version
        LOG_ERROR(
            "Current OS version %s older than backup version %s", (currentVersion).c_str(), (backupVersion).c_str());
        return false;
    }

    return true;
}

bool BackupRestore::CanRestoreFromBackup(const std::filesystem::path &extractedBackup)
{
    return CheckBackupVersion(extractedBackup);
}

bool BackupRestore::ReplaceUserFiles(const std::filesystem::path &path)
{
    /* replace existing files that have respective backup files existing */
    const auto tempDir = purefs::dir::getTemporaryPath() / path.stem();

    if (std::filesystem::is_directory(tempDir) && std::filesystem::is_empty(tempDir)) {
        LOG_INFO("dir empty, nothing to restore, quitting...");
        LOG_INFO("Dir empty, nothing to restore, quitting...");
        return false;
    }

    const std::filesystem::path userDir   = purefs::dir::getUserDiskPath();
    const std::filesystem::path backupDir = purefs::dir::getBackupOSPath();
    std::error_code e;
    std::error_code errorCode;

    for (auto &direntry : std::filesystem::directory_iterator(tempDir, e)) {
        if (e) {
    for (auto &direntry : std::filesystem::directory_iterator(tempDir, errorCode)) {
        if (errorCode) {
            LOG_INFO("Can't list contents of temp dir");
            return false;
        }


@@ 469,17 512,18 @@ bool BackupRestore::ReplaceUserFiles(const std::filesystem::path &path)
            continue;
        }

        LOG_INFO("restoring backup file ...");
        LOG_INFO("Restoring backup file ...");

        if (std::filesystem::remove(userDir / direntry.path().filename(), e)) {
            std::filesystem::rename(tempDir / direntry.path().filename(), userDir / direntry.path().filename(), e);
            if (e) {
                LOG_ERROR("can't rename file. Restore failed");
        if (std::filesystem::remove(userDir / direntry.path().filename(), errorCode)) {
            std::filesystem::rename(
                tempDir / direntry.path().filename(), userDir / direntry.path().filename(), errorCode);
            if (errorCode) {
                LOG_ERROR("Can't move file. Restore failed, error: %d", errorCode.value());
                return false;
            }
        }
        else {
            LOG_WARN("can't remove file");
            LOG_WARN("Can't remove file, error: %d", errorCode.value());
            // we should continue, there can be new files in the backup
        }
    }


@@ 490,14 534,14 @@ bool BackupRestore::ReplaceUserFiles(const std::filesystem::path &path)
json11::Json BackupRestore::GetBackupFiles()
{
    auto dirEntryVector = std::vector<std::string>();
    std::error_code e;
    for (const auto &p : std::filesystem::directory_iterator(purefs::dir::getBackupOSPath(), e)) {
        if (e) {
            LOG_ERROR("Can't get directory %s contents", purefs::dir::getBackupOSPath().c_str());
    std::error_code errorCode;
    for (const auto &p : std::filesystem::directory_iterator(purefs::dir::getBackupOSPath(), errorCode)) {
        if (errorCode) {
            LOG_ERROR("Can't get directory %s contents: %d", purefs::dir::getBackupOSPath().c_str(), errorCode.value());
            return json11::Json();
        }
        if (!p.is_directory() && p.path().extension() == purefs::extension::tar) {
            LOG_DEBUG("possible restore file");
        if (!p.is_directory()) {
            LOG_DEBUG("Possible restore file");
            dirEntryVector.push_back(p.path().filename());
        }
    }

M module-services/service-desktop/CMakeLists.txt => module-services/service-desktop/CMakeLists.txt +3 -0
@@ 7,6 7,7 @@ add_library(service-desktop STATIC)
target_sources(
        service-desktop
    PRIVATE
        BackupRestore.cpp
        DesktopEvent.cpp
        DeveloperModeMessage.cpp
        DesktopMessages.cpp


@@ 20,6 21,7 @@ target_sources(
        parser/ParserFSM.hpp
        parser/MessageHandler.hpp
    PUBLIC
        include/service-desktop/BackupRestore.hpp
        include/service-desktop/Constants.hpp
        include/service-desktop/DesktopEvent.hpp
        include/service-desktop/DesktopMessages.hpp


@@ 49,6 51,7 @@ target_link_libraries(service-desktop
        utils-bootconfig
        Microsoft.GSL::GSL
        json::json
        microtar::microtar
        $<$<STREQUAL:${PROJECT_TARGET},TARGET_RT1051>:usb_stack::usb_stack>
    PUBLIC
        module-cellular

M module-services/service-desktop/ServiceDesktop.cpp => module-services/service-desktop/ServiceDesktop.cpp +37 -20
@@ 6,9 6,9 @@
#include <service-desktop/ServiceDesktop.hpp>
#include <service-desktop/WorkerDesktop.hpp>
#include <service-cellular/CellularMessage.hpp>
#include <service-desktop/BackupRestore.hpp>
#include <endpoints/EndpointFactory.hpp>
#include <endpoints/bluetooth/BluetoothMessagesHandler.hpp>
#include <endpoints/backup/BackupRestore.hpp>

#include <Common/Query.hpp>
#include <MessageType.hpp>


@@ 115,13 115,19 @@ sys::ReturnCodes ServiceDesktop::InitHandler()
    connect(sdesktop::BackupMessage(), [&](sys::Message *msg) {
        sdesktop::BackupMessage *backupMessage = dynamic_cast<sdesktop::BackupMessage *>(msg);
        if (backupMessage != nullptr) {

            backupRestoreStatus.state = OperationState::Running;
            backupRestoreStatus.lastOperationResult =
            backupRestoreStatus.state = BackupRestore::OperationState::Running;
            backupRestoreStatus.completionCode =
                BackupRestore::BackupUserFiles(this, backupRestoreStatus.backupTempDir);
            backupRestoreStatus.location =
                (purefs::dir::getBackupOSPath() / backupRestoreStatus.task).replace_extension(purefs::extension::tar);
            backupRestoreStatus.state = OperationState::Stopped;
            backupRestoreStatus.location = backupRestoreStatus.taskId;

            if (backupRestoreStatus.completionCode == BackupRestore::CompletionCode::Success) {
                LOG_INFO("Backup finished");
                backupRestoreStatus.state = BackupRestore::OperationState::Finished;
            }
            else {
                LOG_ERROR("Backup failed");
                backupRestoreStatus.state = BackupRestore::OperationState::Error;
            }
        }
        return sys::MessageNone{};
    });


@@ 129,16 135,17 @@ sys::ReturnCodes ServiceDesktop::InitHandler()
    connect(sdesktop::RestoreMessage(), [&](sys::Message *msg) {
        sdesktop::RestoreMessage *restoreMessage = dynamic_cast<sdesktop::RestoreMessage *>(msg);
        if (restoreMessage != nullptr) {
            backupRestoreStatus.state = OperationState::Running;
            backupRestoreStatus.lastOperationResult =
                BackupRestore::RestoreUserFiles(this, backupRestoreStatus.location);
            backupRestoreStatus.state          = BackupRestore::OperationState::Running;
            backupRestoreStatus.completionCode = BackupRestore::RestoreUserFiles(this, backupRestoreStatus.location);

            backupRestoreStatus.state = OperationState::Stopped;
            if (backupRestoreStatus.lastOperationResult == true) {
            if (backupRestoreStatus.completionCode == BackupRestore::CompletionCode::Success) {
                LOG_DEBUG("Restore finished");
                backupRestoreStatus.state = BackupRestore::OperationState::Finished;
                sys::SystemManagerCommon::Reboot(this);
            }
            else {
                LOG_ERROR("Restore failed");
                backupRestoreStatus.state = BackupRestore::OperationState::Error;
            }
        }
        return sys::MessageNone{};


@@ 259,18 266,28 @@ sys::MessagePointer ServiceDesktop::DataReceivedHandler(sys::DataMessage *msg, s
    return std::make_shared<sys::ResponseMessage>();
}

std::string ServiceDesktop::prepareBackupFilename()
{
    std::array<char, 64> backupFileName;
    std::time_t now;
    std::time(&now);
    std::strftime(backupFileName.data(), backupFileName.size(), "%FT%OH%OM%OSZ", std::localtime(&now));

    return std::string(backupFileName.data());
}

void ServiceDesktop::prepareBackupData()
{
    backupRestoreStatus.operation     = ServiceDesktop::Operation::Backup;
    backupRestoreStatus.task          = std::to_string(static_cast<uint32_t>(std::time(nullptr)));
    backupRestoreStatus.state         = OperationState::Stopped;
    backupRestoreStatus.backupTempDir = purefs::dir::getTemporaryPath() / backupRestoreStatus.task;
    backupRestoreStatus.operation     = BackupRestore::Operation::Backup;
    backupRestoreStatus.taskId        = prepareBackupFilename();
    backupRestoreStatus.state         = BackupRestore::OperationState::Stopped;
    backupRestoreStatus.backupTempDir = purefs::dir::getTemporaryPath() / backupRestoreStatus.taskId;
}

void ServiceDesktop::prepareRestoreData(const std::filesystem::path &restoreLocation)
{
    backupRestoreStatus.operation = ServiceDesktop::Operation::Restore;
    backupRestoreStatus.location  = purefs::dir::getBackupOSPath() / restoreLocation;
    backupRestoreStatus.state     = OperationState::Stopped;
    backupRestoreStatus.task      = restoreLocation.filename();
    backupRestoreStatus.operation = BackupRestore::Operation::Restore;
    backupRestoreStatus.taskId    = restoreLocation.filename();
    backupRestoreStatus.state     = BackupRestore::OperationState::Stopped;
    backupRestoreStatus.location  = purefs::dir::getBackupOSPath() / backupRestoreStatus.taskId;
}

M module-services/service-desktop/endpoints/CMakeLists.txt => module-services/service-desktop/endpoints/CMakeLists.txt +5 -3
@@ 40,7 40,7 @@ target_sources(
        desktop-endpoints-common
    PRIVATE
        backup/BackupEndpoint.cpp
        backup/BackupRestore.cpp
        backup/BackupHelper.cpp
        bluetooth/BluetoothEndpoint.cpp
        bluetooth/BluetoothEventMessages.cpp
        bluetooth/BluetoothHelper.cpp


@@ 57,13 57,14 @@ target_sources(
        filesystem/FilesystemEndpoint.cpp
        nullEndpoint/NullEndpoint.cpp
        restore/RestoreEndpoint.cpp
        restore/RestoreHelper.cpp
        security/SecurityEndpoint.cpp
        security/SecurityEndpointHelper.cpp
        update/UpdateEndpoint.cpp
        update/UpdateHelper.cpp
    PUBLIC
        include/endpoints/backup/BackupEndpoint.hpp
        include/endpoints/backup/BackupRestore.hpp
        include/endpoints/backup/BackupHelper.hpp
        include/endpoints/bluetooth/BluetoothEndpoint.hpp
        include/endpoints/bluetooth/BluetoothEventMessages.hpp
        include/endpoints/bluetooth/BluetoothHelper.hpp


@@ 80,6 81,7 @@ target_sources(
        include/endpoints/filesystem/FilesystemEndpoint.hpp
        include/endpoints/nullEndpoint/NullEndpoint.hpp
        include/endpoints/restore/RestoreEndpoint.hpp
        include/endpoints/restore/RestoreHelper.hpp
        include/endpoints/security/SecurityEndpoint.hpp
        include/endpoints/security/SecurityEndpointHelper.hpp
        include/endpoints/update/UpdateEndpoint.hpp


@@ 97,8 99,8 @@ target_link_libraries(
    PUBLIC
        desktop-endpoint-base
    PRIVATE
        microtar
        base64::base64
        microtar::microtar
)

add_library(desktop-endpoints INTERFACE)

M module-services/service-desktop/endpoints/backup/BackupEndpoint.cpp => module-services/service-desktop/endpoints/backup/BackupEndpoint.cpp +11 -59
@@ 2,79 2,31 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <endpoints/backup/BackupEndpoint.hpp>

#include <endpoints/JsonKeyNames.hpp>
#include <endpoints/message/Sender.hpp>
#include <service-desktop/DesktopMessages.hpp>
#include <service-desktop/ServiceDesktop.hpp>

#include <json11.hpp>
#include <purefs/filesystem_paths.hpp>

#include <filesystem>
#include <memory>

namespace sdesktop::endpoints
{
    using sender::putToSendQueue;

    auto BackupEndpoint::handle(Context &context) -> void
    {
        switch (context.getMethod()) {
        case http::Method::get:
            request(context);
            break;
        case http::Method::post:
        case http::Method::put:
        case http::Method::del:
            context.setResponseStatus(http::Code::BadRequest);
            putToSendQueue(context.createSimpleResponse());
            break;
        }
    }
    auto BackupEndpoint::request(Context &context) -> sys::ReturnCodes
    {
        json11::Json responseBodyJson;
        auto owner = static_cast<ServiceDesktop *>(ownerServicePtr);

        if (context.getBody()[json::task].is_string()) {
            if (owner->getBackupRestoreStatus().task == context.getBody()[json::task].string_value()) {
                if (owner->getBackupRestoreStatus().state != ServiceDesktop::OperationState::Running) {
                    context.setResponseStatus(http::Code::SeeOther);
                }
        auto [sent, response] = helper->process(context.getMethod(), context);

                context.setResponseBody(owner->getBackupRestoreStatus());
            }
            else {
                context.setResponseStatus(http::Code::NotFound);
            }
        if (sent == sent::delayed) {
            LOG_DEBUG("There is no proper delayed serving mechanism - depend on invisible context caching");
        }
        else if (context.getBody()[json::request] == true) {
            if (owner->getBackupRestoreStatus().state == ServiceDesktop::OperationState::Running) {
                // a backup is already running, don't start a second task
        if (sent == sent::no) {
            if (not response) {
                LOG_ERROR("Response not sent & response not created : respond with error");
                context.setResponseStatus(http::Code::NotAcceptable);
            }
            else {
                // initialize new backup information
                owner->prepareBackupData();

                // start the backup process in the background
                ownerServicePtr->bus.sendUnicast(std::make_shared<sdesktop::BackupMessage>(),
                                                 service::name::service_desktop);

                // return new generated backup info
                context.setResponseBody(owner->getBackupRestoreStatus());
                context.setResponse(response.value());
            }

            sender::putToSendQueue(context.createSimpleResponse());
        }
        else {
            // unknown request for backup endpoint
            context.setResponseStatus(http::Code::BadRequest);
        if (sent == sent::yes and response) {
            LOG_ERROR("Response set when we already handled response in handler");
        }

        LOG_DEBUG("responding");
        putToSendQueue(context.createSimpleResponse());

        return sys::ReturnCodes::Success;
    }

} // namespace sdesktop::endpoints

A module-services/service-desktop/endpoints/backup/BackupHelper.cpp => module-services/service-desktop/endpoints/backup/BackupHelper.cpp +87 -0
@@ 0,0 1,87 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <endpoints/Context.hpp>
#include <endpoints/backup/BackupHelper.hpp>
#include <endpoints/JsonKeyNames.hpp>
#include <endpoints/message/Sender.hpp>
#include <service-desktop/DesktopMessages.hpp>
#include <service-desktop/ServiceDesktop.hpp>

#include <json11.hpp>
#include <purefs/filesystem_paths.hpp>

#include <filesystem>

namespace sdesktop::endpoints
{
    using sender::putToSendQueue;

    auto BackupHelper::processGet(Context &context) -> ProcessResult
    {
        return checkState(context);
    }

    auto BackupHelper::processPost(Context &context) -> ProcessResult
    {
        return executeRequest(context);
    }

    auto BackupHelper::executeRequest(Context &context) -> ProcessResult
    {
        auto ownerServicePtr = static_cast<ServiceDesktop *>(owner);

        if (ownerServicePtr->getBackupRestoreStatus().state == BackupRestore::OperationState::Running) {
            LOG_DEBUG("Backup already running");
            // a backup is already running, don't start a second task
            context.setResponseStatus(http::Code::NotAcceptable);
        }
        else {
            LOG_DEBUG("Starting backup");
            // initialize new backup information
            ownerServicePtr->prepareBackupData();

            // start the backup process in the background
            ownerServicePtr->bus.sendUnicast(std::make_shared<sdesktop::BackupMessage>(),
                                             service::name::service_desktop);

            // return new generated backup info
            context.setResponseBody(ownerServicePtr->getBackupRestoreStatus());
        }

        putToSendQueue(context.createSimpleResponse());

        return {sent::yes, std::nullopt};
    }

    auto BackupHelper::checkState(Context &context) -> ProcessResult
    {
        auto ownerServicePtr = static_cast<ServiceDesktop *>(owner);

        if (context.getBody()[json::taskId].is_string()) {
            if (ownerServicePtr->getBackupRestoreStatus().taskId == context.getBody()[json::taskId].string_value()) {
                if (ownerServicePtr->getBackupRestoreStatus().state == BackupRestore::OperationState::Finished) {
                    context.setResponseStatus(http::Code::SeeOther);
                }
                else {
                    context.setResponseStatus(http::Code::OK);
                }

                context.setResponseBody(ownerServicePtr->getBackupRestoreStatus());
            }
            else {
                context.setResponseStatus(http::Code::NotFound);
            }
        }
        else {
            LOG_DEBUG("Backup task not found");
            context.setResponseStatus(http::Code::BadRequest);
        }

        LOG_DEBUG("Responding");
        putToSendQueue(context.createSimpleResponse());

        return {sent::yes, std::nullopt};
    }

} // namespace sdesktop::endpoints

M module-services/service-desktop/endpoints/include/endpoints/JsonKeyNames.hpp => module-services/service-desktop/endpoints/include/endpoints/JsonKeyNames.hpp +4 -1
@@ 35,11 35,13 @@ namespace sdesktop::endpoints::json
    inline constexpr auto updateHistory       = "updateHistory";
    inline constexpr auto versionString       = "string";
    inline constexpr auto fileExists          = "fileExists";
    inline constexpr auto boot                = "boot";
    inline constexpr auto version             = "version";
    inline constexpr auto task                = "task";
    inline constexpr auto taskId              = "id";
    inline constexpr auto state               = "state";
    inline constexpr auto success             = "success";
    inline constexpr auto request             = "request";
    inline constexpr auto restore             = "restore";
    inline constexpr auto finished            = "finished";
    inline constexpr auto pending             = "pending";
    inline constexpr auto location            = "location";


@@ 48,6 50,7 @@ namespace sdesktop::endpoints::json
    inline constexpr auto caseColour          = "caseColour";
    inline constexpr auto fileList            = "fileList";
    inline constexpr auto files               = "files";
    inline constexpr auto backupLocation      = "backupLocation";

    namespace filesystem
    {

M module-services/service-desktop/endpoints/include/endpoints/backup/BackupEndpoint.hpp => module-services/service-desktop/endpoints/include/endpoints/backup/BackupEndpoint.hpp +7 -11
@@ 4,27 4,23 @@
#pragma once

#include <endpoints/Endpoint.hpp>

#include <system/Common.hpp>

#include <string>

namespace sys
{
    class Service;
} // namespace sys
#include "BackupHelper.hpp"

namespace sdesktop::endpoints
{
    class BackupEndpoint : public Endpoint
    {
      public:
        BackupEndpoint(sys::Service *ownerServicePtr) : Endpoint(ownerServicePtr)
        BackupEndpoint(sys::Service *ownerServicePtr)
            : Endpoint(ownerServicePtr), helper(std::make_unique<BackupHelper>(ownerServicePtr))
        {
            debugName = "BackupEndpoint";
        }

        auto handle(Context &context) -> void override;
        auto request(Context &context) -> sys::ReturnCodes;

      private:
        const std::unique_ptr<BackupHelper> helper;
    };

} // namespace sdesktop::endpoints

A module-services/service-desktop/endpoints/include/endpoints/backup/BackupHelper.hpp => module-services/service-desktop/endpoints/include/endpoints/backup/BackupHelper.hpp +23 -0
@@ 0,0 1,23 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <endpoints/BaseHelper.hpp>

namespace sdesktop::endpoints
{
    class BackupHelper : public BaseHelper
    {
      public:
        explicit BackupHelper(sys::Service *p) : BaseHelper(p)
        {}

        auto processPost(Context &context) -> ProcessResult final;
        auto processGet(Context &context) -> ProcessResult final;

      private:
        auto executeRequest(Context &context) -> ProcessResult;
        auto checkState(Context &context) -> ProcessResult;
    };
} // namespace sdesktop::endpoints

D module-services/service-desktop/endpoints/include/endpoints/backup/BackupRestore.hpp => module-services/service-desktop/endpoints/include/endpoints/backup/BackupRestore.hpp +0 -36
@@ 1,36 0,0 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <Service/Service.hpp>
#include <filesystem>
#include <json11.hpp>

namespace sys
{
    class Service;
} // namespace sys

namespace bkp
{
    inline constexpr auto backupInfo = "backup.json";
};

class BackupRestore
{
  public:
    BackupRestore(){};
    ~BackupRestore(){};
    static bool BackupUserFiles(sys::Service *ownerService, std::filesystem::path &path);
    static bool RestoreUserFiles(sys::Service *ownerService, const std::filesystem::path &path);
    static json11::Json GetBackupFiles();

  private:
    static bool RemoveBackupDir(std::filesystem::path &path);
    static bool CreateBackupDir(std::filesystem::path &path);
    static bool PackUserFiles(std::filesystem::path &path);
    static bool UnpackBackupFile(const std::filesystem::path &path);
    static bool ReplaceUserFiles(const std::filesystem::path &path);
    static bool WriteBackupInfo(sys::Service *ownerService, const std::filesystem::path &path);
};

M module-services/service-desktop/endpoints/include/endpoints/restore/RestoreEndpoint.hpp => module-services/service-desktop/endpoints/include/endpoints/restore/RestoreEndpoint.hpp +6 -2
@@ 3,6 3,7 @@

#pragma once

#include "RestoreHelper.hpp"
#include <endpoints/Endpoint.hpp>

namespace sdesktop::endpoints


@@ 11,12 12,15 @@ namespace sdesktop::endpoints
    class RestoreEndpoint : public Endpoint
    {
      public:
        explicit RestoreEndpoint(sys::Service *ownerServicePtr) : Endpoint(ownerServicePtr)
        explicit RestoreEndpoint(sys::Service *ownerServicePtr)
            : Endpoint(ownerServicePtr), helper(std::make_unique<RestoreHelper>(ownerServicePtr))
        {
            debugName = "RestoreEndpoint";
        }
        auto handle(Context &context) -> void override;
        auto request(Context &context) -> sys::ReturnCodes;

      private:
        const std::unique_ptr<RestoreHelper> helper;
    };

} // namespace sdesktop::endpoints

A module-services/service-desktop/endpoints/include/endpoints/restore/RestoreHelper.hpp => module-services/service-desktop/endpoints/include/endpoints/restore/RestoreHelper.hpp +23 -0
@@ 0,0 1,23 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <endpoints/BaseHelper.hpp>

namespace sdesktop::endpoints
{
    class RestoreHelper : public BaseHelper
    {
      public:
        explicit RestoreHelper(sys::Service *p) : BaseHelper(p)
        {}

        auto processPost(Context &context) -> ProcessResult final;
        auto processGet(Context &context) -> ProcessResult final;

      private:
        auto executeRequest(Context &context) -> ProcessResult;
        auto checkState(Context &context) -> ProcessResult;
    };
} // namespace sdesktop::endpoints

M module-services/service-desktop/endpoints/restore/RestoreEndpoint.cpp => module-services/service-desktop/endpoints/restore/RestoreEndpoint.cpp +11 -77
@@ 2,97 2,31 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <endpoints/restore/RestoreEndpoint.hpp>

#include <endpoints/JsonKeyNames.hpp>
#include <endpoints/message/Sender.hpp>
#include <service-desktop/DesktopMessages.hpp>
#include <service-desktop/ServiceDesktop.hpp>
#include <endpoints/backup/BackupRestore.hpp>
#include <purefs/filesystem_paths.hpp>

#include <memory>

namespace sdesktop::endpoints
{
    using sender::putToSendQueue;

    auto RestoreEndpoint::handle(Context &context) -> void
    {
        switch (context.getMethod()) {
        case http::Method::get:
            context.setResponseBody(BackupRestore::GetBackupFiles());
            break;
        case http::Method::post:
            request(context);
            break;
        case http::Method::put:
        case http::Method::del:
            context.setResponseStatus(http::Code::BadRequest);
            break;
        }

        putToSendQueue(context.createSimpleResponse());
    }

    auto RestoreEndpoint::request(Context &context) -> sys::ReturnCodes
    {
        json11::Json responseBodyJson;
        auto owner = static_cast<ServiceDesktop *>(ownerServicePtr);

        if (context.getBody()[json::task].is_string()) {
            if (owner->getBackupRestoreStatus().task == context.getBody()[json::task].string_value()) {
                if (owner->getBackupRestoreStatus().state == ServiceDesktop::OperationState::Running) {
                    LOG_WARN("looks like a previous job is running can't start a new one");
                    context.setResponseStatus(http::Code::SeeOther);
                }
        auto [sent, response] = helper->process(context.getMethod(), context);

                context.setResponseBody(owner->getBackupRestoreStatus());
            }
            else {
                context.setResponseStatus(http::Code::NotFound);
            }
        if (sent == sent::delayed) {
            LOG_DEBUG("There is no proper delayed serving mechanism - depend on invisible context caching");
        }
        else if (context.getBody()[json::request] == true) {
            if (owner->getBackupRestoreStatus().state == ServiceDesktop::OperationState::Running) {
                LOG_WARN("looks like a job is running, try again later");
        if (sent == sent::no) {
            if (not response) {
                LOG_ERROR("Response not sent & response not created : respond with error");
                context.setResponseStatus(http::Code::NotAcceptable);
            }
            else {
                const std::filesystem::path location(context.getBody()[json::location].string_value());
                if (location.empty()) {
                    LOG_ERROR("no location in request");
                    context.setResponseBody(json11::Json::object({{"msg", "no location passed"}}));
                    context.setResponseStatus(http::Code::NotAcceptable);

                    return sys::ReturnCodes::Failure;
                }
                if (!std::filesystem::exists(purefs::dir::getBackupOSPath() / location)) {
                    LOG_ERROR("file %s does not exist", (purefs::dir::getBackupOSPath() / location).c_str());
                    context.setResponseBody(json11::Json::object({{"msg", "passed location is not readable"}}));
                    context.setResponseStatus(http::Code::NotFound);

                    return sys::ReturnCodes::Failure;
                }
                // initialize new restore information
                owner->prepareRestoreData(location);

                // start the request process
                ownerServicePtr->bus.sendUnicast(std::make_shared<sdesktop::RestoreMessage>(),
                                                 service::name::service_desktop);

                // return new generated restore info
                context.setResponseBody(owner->getBackupRestoreStatus());
                context.setResponse(response.value());
            }

            sender::putToSendQueue(context.createSimpleResponse());
        }
        else {
            // unknown request for backup endpoint
            context.setResponseStatus(http::Code::BadRequest);
        if (sent == sent::yes and response) {
            LOG_ERROR("Response set when we already handled response in handler");
        }

        LOG_DEBUG("responding");
        putToSendQueue(context.createSimpleResponse());

        return sys::ReturnCodes::Success;
    }

} // namespace sdesktop::endpoints

A module-services/service-desktop/endpoints/restore/RestoreHelper.cpp => module-services/service-desktop/endpoints/restore/RestoreHelper.cpp +106 -0
@@ 0,0 1,106 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <endpoints/Context.hpp>
#include <endpoints/restore/RestoreHelper.hpp>
#include <endpoints/JsonKeyNames.hpp>
#include <endpoints/message/Sender.hpp>
#include <service-desktop/DesktopMessages.hpp>
#include <service-desktop/ServiceDesktop.hpp>
#include <json11.hpp>
#include <purefs/filesystem_paths.hpp>

#include <filesystem>

namespace sdesktop::endpoints
{
    using sender::putToSendQueue;

    auto RestoreHelper::processGet(Context &context) -> ProcessResult
    {
        return checkState(context);
    }

    auto RestoreHelper::processPost(Context &context) -> ProcessResult
    {
        return executeRequest(context);
    }

    auto RestoreHelper::checkState(Context &context) -> ProcessResult
    {
        auto ownerService = static_cast<ServiceDesktop *>(owner);

        if (context.getBody()[json::taskId].is_string()) {
            if (ownerService->getBackupRestoreStatus().taskId == context.getBody()[json::taskId].string_value()) {
                context.setResponseStatus(http::Code::OK);
                context.setResponseBody(ownerService->getBackupRestoreStatus());
            }
            else {
                return {sent::no, ResponseContext{.status = http::Code::NotFound}};
            }
        }
        else if (context.getBody()[json::request].is_string()) {
            const std::string request(context.getBody()[json::request].string_value());

            if (request.compare(json::fileList)) {

                return {sent::no, ResponseContext{.status = http::Code::BadRequest}};
            }
            auto filesList = json11::Json::object{{json::files, BackupRestore::GetBackupFiles()}};
            context.setResponseBody(filesList);
        }

        LOG_DEBUG("Responding");
        putToSendQueue(context.createSimpleResponse());

        return {sent::yes, std::nullopt};
    }

    auto RestoreHelper::executeRequest(Context &context) -> ProcessResult
    {
        auto ownerService = static_cast<ServiceDesktop *>(owner);

        if (context.getBody()[json::restore].is_string()) {
            if (ownerService->getBackupRestoreStatus().state == BackupRestore::OperationState::Running) {
                LOG_WARN("Restore is running, try again later");

                return {sent::no, ResponseContext{.status = http::Code::NotAcceptable}};
            }
            else {
                const std::filesystem::path restorePoint(context.getBody()[json::restore].string_value());
                if (restorePoint.empty()) {
                    LOG_ERROR("no restorePoint in request");
                    context.setResponseBody(json11::Json::object({{json::reason, "No restore point passed"}}));

                    return {sent::no, ResponseContext{.status = http::Code::NotAcceptable}};
                }
                if (!std::filesystem::exists(purefs::dir::getBackupOSPath() / restorePoint)) {
                    LOG_ERROR("Restore point %s does not exist", (restorePoint).c_str());
                    context.setResponseBody(json11::Json::object({{json::reason, "Invalid restore point"}}));

                    return {sent::no, ResponseContext{.status = http::Code::NotFound}};
                }
                // initialize new restore information
                ownerService->prepareRestoreData(restorePoint);

                // start the request process
                ownerService->bus.sendUnicast(std::make_shared<sdesktop::RestoreMessage>(),
                                              service::name::service_desktop);

                // return new generated restore info
                context.setResponseBody(ownerService->getBackupRestoreStatus());
            }
        }
        else {
            // unknown request for backup endpoint

            return {sent::no, ResponseContext{.status = http::Code::BadRequest}};
        }

        LOG_DEBUG("Responding");
        putToSendQueue(context.createSimpleResponse());

        return {sent::yes, std::nullopt};
    }

} // namespace sdesktop::endpoints

A module-services/service-desktop/include/service-desktop/BackupRestore.hpp => module-services/service-desktop/include/service-desktop/BackupRestore.hpp +120 -0
@@ 0,0 1,120 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <Service/Service.hpp>
#include <endpoints/JsonKeyNames.hpp>
#include <json11.hpp>
#include <filesystem>

namespace sys
{
    class Service;
} // namespace sys

class BackupRestore
{
  public:
    enum class Operation
    {
        Backup,
        Restore
    };

    enum class OperationState
    {
        Stopped,
        Running,
        Finished,
        Error
    };

    static const std::string opToString(const OperationState &op)
    {
        switch (op) {
        case OperationState::Stopped:
            return "stopped";
        case OperationState::Running:
            return "running";
        case OperationState::Finished:
            return "finished";
        case OperationState::Error:
            return "error";
        }
        return "unknown";
    }

    enum class CompletionCode
    {
        Success,
        VersionConflict,
        DBError,
        FSError,
        UnpackError,
        PackError,
        CopyError,
        OtherError
    };

    static const std::string completionCodeToString(const CompletionCode &code)
    {
        switch (code) {
        case CompletionCode::Success:
            return "Success";
        case CompletionCode::VersionConflict:
            return "Version conflict";
        case CompletionCode::DBError:
            return "DB operation error";
        case CompletionCode::FSError:
            return "FSError error";
        case CompletionCode::UnpackError:
            return "Backup unpack failed";
        case CompletionCode::PackError:
            return "Backup pack failed";
        case CompletionCode::CopyError:
            return "File copying failed";
        case CompletionCode::OtherError:
            return "Undetermined error";
        }
        return "unknown";
    }

    struct OperationStatus
    {
        std::filesystem::path backupTempDir;
        std::filesystem::path location;
        CompletionCode completionCode = CompletionCode::Success;
        std::string taskId;
        OperationState state = OperationState::Stopped;
        Operation operation  = Operation::Backup;
        json11::Json to_json() const
        {
            auto response = json11::Json::object{{sdesktop::endpoints::json::taskId, taskId},
                                                 {sdesktop::endpoints::json::state, opToString(state)}};

            if (state == OperationState::Error) {
                response[sdesktop::endpoints::json::reason] = completionCodeToString(completionCode);
            }

            return response;
        }
    };

    static CompletionCode BackupUserFiles(sys::Service *ownerService, std::filesystem::path &path);
    static CompletionCode RestoreUserFiles(sys::Service *ownerService, const std::filesystem::path &path);
    static json11::Json GetBackupFiles();

  private:
    static bool RemoveBackupDir(const std::filesystem::path &path);
    static bool CreateBackupDir(const std::filesystem::path &path);
    static bool PackUserFiles(const std::filesystem::path &path);
    static bool UnpackBackupFile(const std::filesystem::path &path);
    static std::string ReadVersionFromJsonFile(const std::filesystem::path &jsonFilePath);
    static bool CheckBackupVersion(const std::filesystem::path &extractedBackup);
    static bool CanRestoreFromBackup(const std::filesystem::path &path);
    static auto TempPathForBackupFile(const std::filesystem::path &tarFilePath) -> std::filesystem::path const;
    static bool ReplaceUserFiles(const std::filesystem::path &path);
    static bool WriteBackupInfo(sys::Service *ownerService, const std::filesystem::path &path);
    static std::string ReadFileAsString(const std::filesystem::path &fileToRead);
};

M module-services/service-desktop/include/service-desktop/ServiceDesktop.hpp => module-services/service-desktop/include/service-desktop/ServiceDesktop.hpp +5 -43
@@ 11,9 11,9 @@
#include "Timers/TimerHandle.hpp"
#include "Constants.hpp"
#include "USBSecurityModel.hpp"

#include <service-desktop/BackupRestore.hpp>
#include <service-db/DBServiceName.hpp>
#include <endpoints/JsonKeyNames.hpp>

#include <bsp/usb/usb.hpp>

#include <filesystem>


@@ 51,46 51,7 @@ class ServiceDesktop : public sys::Service
    ServiceDesktop();
    ~ServiceDesktop() override;

    enum class Operation
    {
        Backup,
        Restore
    };
    enum class OperationState
    {
        Stopped,
        Running,
        Error
    };

    static const std::string opToString(const OperationState &op)
    {
        switch (op) {
        case OperationState::Stopped:
            return "stopped";
        case OperationState::Running:
            return "running";
        case OperationState::Error:
            return "error";
        default:
            return "unkown";
        }
    }
    struct BackupRestoreStatus
    {
        std::filesystem::path backupTempDir;
        std::filesystem::path location;
        bool lastOperationResult = false;
        std::string task;
        OperationState state = OperationState::Stopped;
        Operation operation  = Operation::Backup;
        json11::Json to_json() const
        {
            return json11::Json::object{{sdesktop::endpoints::json::task, task},
                                        {sdesktop::endpoints::json::state, opToString(state)},
                                        {sdesktop::endpoints::json::location, location.string()}};
        }
    } backupRestoreStatus;
    BackupRestore::OperationStatus backupRestoreStatus;

    sys::ReturnCodes InitHandler() override;
    sys::ReturnCodes DeinitHandler() override;


@@ 100,9 61,10 @@ class ServiceDesktop : public sys::Service

    std::unique_ptr<WorkerDesktop> desktopWorker;

    std::string prepareBackupFilename();
    void prepareBackupData();
    void prepareRestoreData(const std::filesystem::path &restoreLocation);
    const BackupRestoreStatus getBackupRestoreStatus()
    const BackupRestore::OperationStatus getBackupRestoreStatus()
    {
        return backupRestoreStatus;
    }

M module-vfs/paths/include/purefs/filesystem_paths.hpp => module-vfs/paths/include/purefs/filesystem_paths.hpp +1 -0
@@ 28,6 28,7 @@ namespace purefs
    {
        constexpr inline auto boot_json = ".boot.json";
        constexpr inline auto boot_bin  = "boot.bin";
        constexpr inline auto version_json = "version.json";
    } // namespace file

    namespace extension

M products/PurePhone/services/db/ServiceDB.cpp => products/PurePhone/services/db/ServiceDB.cpp +6 -0
@@ 293,6 293,12 @@ bool ServiceDB::StoreIntoBackup(const std::filesystem::path &backupPath)
        return false;
    }

    if (notificationsDB->storeIntoFile(backupPath / std::filesystem::path(notificationsDB->getName()).filename()) ==
        false) {
        LOG_ERROR("notificationsDB backup failed");
        return false;
    }

    for (auto &db : databaseAgents) {
        if (db.get() && db.get()->getAgentName() == "settingsAgent") {


M products/PurePhone/services/desktop/endpoints/deviceInfo/DeviceInfoEndpoint.cpp => products/PurePhone/services/desktop/endpoints/deviceInfo/DeviceInfoEndpoint.cpp +2 -1
@@ 160,7 160,8 @@ namespace sdesktop::endpoints
             {json::currentRTCTime, std::to_string(static_cast<uint32_t>(std::time(nullptr)))},
             {json::version, std::string(VERSION)},
             {json::serialNumber, getSerialNumber()},
             {json::caseColour, getCaseColour()}}));
             {json::caseColour, getCaseColour()},
             {json::backupLocation, purefs::dir::getBackupOSPath().string()}}));

        context.setResponseStatus(http::Code::OK);
        return true;

M test/get_os_log.py => test/get_os_log.py +4 -3
@@ 3,7 3,7 @@
# For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

import sys
import os.path
import os
import atexit

from harness.harness import Harness


@@ 38,11 38,12 @@ def main():
            f'Please pass log storage directory as the parameter: \'python {sys.argv[0]} <log dir>\' ')
        raise TestError(Error.OTHER_ERROR)

    log_save_dir = str(sys.argv[1])
    log_save_dir = os.path.join(os.path.abspath(str(sys.argv[1])), '')

    if (not os.path.exists(log_save_dir)):
        print(f'Log storage directory {log_save_dir} not found')
        raise TestError(Error.OTHER_ERROR)
        os.makedirs(log_save_dir, exist_ok = True)
        # raise TestError(Error.OTHER_ERROR)

    harness = Harness.from_detect()


M test/harness => test/harness +1 -1
@@ 1,1 1,1 @@
Subproject commit 8a33fa2634c6e24b9099e23404ab5104678ac426
Subproject commit 8aa7fe8ad3276583688f6ae6a7370f7e686e1810

M test/pytest/service-desktop/test_backup.py => test/pytest/service-desktop/test_backup.py +53 -53
@@ 2,67 2,67 @@
# For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
import pytest
import time
import os
from harness import log
from harness.interface.defs import status

from harness.utils import Timeout
from harness.interface.error import Error, TestError
from harness.api.filesystem import get_log_file_with_path
from harness.api.device_info import GetDeviceInfo
from harness.api.backup import BackupInit, BackupGetState

@pytest.mark.service_desktop_test
@pytest.mark.rt1051
@pytest.mark.usefixtures("phone_unlocked")
@pytest.mark.backup
def test_backup(harness):
    body = { "request": True }
    log.debug("backup testing");
    
    # this requests a backup start
    response = harness.endpoint_request("backup", "get", body)
    assert response["body"] != ""
    assert response["body"]["task"] != ""
    task = response["body"]["task"]
    assert response["status"] == status["OK"]
    
    log.debug("Requesting backup");

    resp = BackupInit().run(harness)

    assert resp.taskId != ""
    taskId = resp.taskId
    # in response we get a task ID and status 200
    log.debug("backup started, waiting for results")
    
    log.debug("Backup started, waiting for results")

    time.sleep(1)  # wait for the endpoint to be ready

    # start polling for backup status and wait for it to end
    i = 0
    while True:
        i = i+1
        time.sleep(1)  # wait for the endpoint to be ready
    try:
        with Timeout.limit(seconds=30):
            while True:
                i = i+1

                # now that we know the task ID we can poll for it's status
                # body = { "id": response.taskId }
                resp = BackupGetState(taskId).run(harness)

                # Backup is still running
                if resp.state == "running":
                    log.debug("Backup is running...")

                # Backup has stopped, should be OK and finished, status is 303
                # and redirects to a location as per the documentation
                elif resp.state == "finished":
                    log.debug("Backup ended, checking results")

                    resp = GetDeviceInfo().run(harness)

                    bkpPath = resp.diag_info["backupLocation"]

                    # Retrieving backup file from phone
                    p = bkpPath + "/" + taskId
                    log.debug(f'Backup file path: {p}')
                    get_log_file_with_path(harness, p, "./")
                    break

                # Backup ended with error
                elif resp.state == "error":
                    log.debug(f'Backup failed: {resp.reason}')
                    raise TestError(Error.TEST_FAILED)

        # now that we know the task ID we can poll for it's status
        body = { "request": True, "task": task }
        response = harness.endpoint_request("backup", "get", body)
        
        # backup is still running
        if response["body"]["state"] == "running":
            assert response["status"] == 200
            log.debug("backup is running...")
            
        # backup has stopped, should be OK and finished, status is 303
        # and redirects to a location as per the documentation
        if response["body"]["state"] == "stopped":
            log.debug("backup ended, check results")
            assert response["status"] == 303
            assert response["body"]["location"] != ""
            
            # see if the location is a valid backup path, extensions is .tar
            # and starts with a /
            p = response["body"]["location"]
            p_split = os.path.splitext(p)
            assert p_split[1] == ".tar"
            assert p_split[0][0] == "/"
            
            break
            
        # wait for a moment
        log.debug("sleeping for 1 second")
        time.sleep(1)
        
        # max 30 second timeout
        if i > 30:
            # this is bas, and the test fails here
            log.error("backup timeout reached")
            assert False
            break
                # wait for a moment
                log.debug("Sleeping 1s")
                time.sleep(1)
    except Timeout as e:
        log.error("Backup timeout reached")
        raise TestError(Error.OtherError)

M test/pytest/service-desktop/test_device_info.py => test/pytest/service-desktop/test_device_info.py +1 -0
@@ 28,4 28,5 @@ def test_device_info(harness):
    assert ret.diag_info["version"] is not None
    assert ret.diag_info["serialNumber"] is not None
    assert ret.diag_info["caseColour"] is not None
    assert ret.diag_info["backupLocation"] is not None


M test/pytest/service-desktop/test_restore.py => test/pytest/service-desktop/test_restore.py +117 -29
@@ 2,39 2,127 @@
# For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
import pytest
import time
from harness.interface.defs import status
from harness.interface.error import Error, TestError
from harness.utils import Timeout
from harness import log
from harness.harness import Harness
from harness.api.filesystem import put_file
from harness.api.device_info import GetDeviceInfo
from harness.api.restore import RestoreInit, RestoreGetBackupList, RestoreGetState

def put_backup_file(harness, backupFile: str = ""):
    if (backupFile == ""):
        log.info("Enter path to the backup file to upload to phone:")
        backupFile = input("> ")

@pytest.mark.service_desktop_test
@pytest.mark.rt1051
@pytest.mark.usefixtures("phone_unlocked")
@pytest.mark.restore
def test_restore(harness):
    if (backupFile != ""):
        backupPath = GetDeviceInfo().run(harness).diag_info["backupLocation"]
        log.debug(f'Sending backup file {backupFile} to {backupPath}')
        put_file(harness, backupFile, backupPath)
    else:
        log.debug("No backup file name provided")
        assert False

def list_restore_points(harness):
    # this requests the list of available files
    body = { "request": True }
    response = harness.endpoint_request("restore", "get", body)
    
    # should be ok 200
    assert response["status"] == status["OK"]
    log.debug("check if body is an array")
    
    resp = RestoreGetBackupList().run(harness)

    backupList = resp.files

    # body should be an array of files
    assert isinstance(response["body"], list) == True
    
    assert isinstance(backupList, list) == True

    # chose the first entry for test if array is > 0
    if len(response["body"]) > 0:
        restore_task = response["body"][0]
        log.debug("there are possible backup files on target, test restore %s" % (restore_task))
        # this starts a restore process with a file as parameter
        body = { "request":True, "location": restore_task }
        response = harness.endpoint_request("restore", "post", body)
        
        # we can't really test for results here, as the phone should reset
        # in case the restore process lasts longer we should be able to poll
        # but only on rt1051
        assert response["body"]["location"] != ""
        assert response["body"]["state"] != ""
    if len(backupList) == 0:
        return False, []
    else:
        log.error("no possisble backup files on phone, run backup first")
        assert False
        return True, backupList

def select_restore_point(backupList):
    availableBackups = len(backupList)

    if availableBackups > 1:
        log.info("List of restore points:")
        idx = 1
        for restore_point in backupList:
            log.info(f'{idx}: {restore_point} ')
            idx += 1

        log.info('0: Cancel')
        log.info("Select a restore point:")
        fileIdx = int(input())

        if (fileIdx == 0):
            log.debug("Test canceled by user")
            assert False
    else:
        log.info("Selecting first available backup")
        fileIdx = availableBackups

    return backupList[fileIdx - 1]

def run_restore(harness, backupList):
    restore_point = select_restore_point(backupList)
    log.debug(f"Running restore for {restore_point}")

    # this starts a restore process with a file as parameter
    resp = RestoreInit(restore_point).run(harness)

    log.debug("Restore started, waiting for results")
    time.sleep(1)  # wait for the endpoint to be ready

    # start polling for Restore status and wait for it to end
    i = 0
    try:
        with Timeout.limit(seconds=30):
            while True:
                i = i+1

                # now that we know the task ID we can poll for it's status
                resp = RestoreGetState(restore_point).run(harness)

                # Restore is still running
                if resp.state == "running":
                    log.debug("Restore is running...")

                # Restore has stopped, should be OK and finished, status is 303
                # and redirects to a location as per the documentation
                elif resp.state == "finished":
                    log.debug("Restore ended, check results")
                    break

                # Restore has stopped, should be OK and finished, status is 303
                # and redirects to a location as per the documentation
                elif resp.state == "error":
                    log.debug(f'Restore failed: {resp.reason}')
                    raise TestError(Error.TEST_FAILED)

                # wait for a moment
                log.debug("Sleeping 1s")
                time.sleep(1)
    except Timeout as e:
        log.error("Restore timeout reached")

@pytest.mark.service_desktop_test
@pytest.mark.rt1051
@pytest.mark.usefixtures("phone_unlocked")
@pytest.mark.restore
def test_restore(harness: Harness):

    try:
        ret, list = list_restore_points(harness)
        if ret == False:
            log.error("No backup files on phone, run backup first")
            # put_backup_file(harness)
            assert False

        ret, list = list_restore_points(harness)
        if ret == True:
            run_restore(harness, list)
        else:
            log.error("No backup files on phone")
            raise TestError(Error.OTHER_ERROR)

    except TestError as err:
        log.debug(err)
        log.info("Restore ended, phone is restarting")

M third-party/microtar/CMakeLists.txt => third-party/microtar/CMakeLists.txt +3 -0
@@ 1,7 1,10 @@
add_library(microtar)
add_library(microtar::microtar ALIAS microtar)
target_sources(microtar
   PRIVATE
      ${CMAKE_CURRENT_SOURCE_DIR}/src/src/microtar.cpp

   PUBLIC
      ${CMAKE_CURRENT_SOURCE_DIR}/src/src/microtar.hpp
)


M tools/generate_image.sh => tools/generate_image.sh +2 -2
@@ 4,7 4,7 @@

usage() {
cat << ==usage
Usage: $(basename $0) image_path image_partitions build_dir version.json_file [boot.bin_file] [updater.bin_file]
Usage: $(basename $0) image_path image_partitions build_dir [version.json_file] [boot.bin_file] [updater.bin_file]
    image_path        - Destination image path name e.g., PurePhone.img
    image_partitions  - Path to image_partitions.map product-specific file
    sysroot           - product's system root e.g., build-rt1051-RelWithDebInfo/sysroot


@@ 14,7 14,7 @@ Usage: $(basename $0) image_path image_partitions build_dir version.json_file [b
==usage
}

if [[ ( $# -ne 4 ) && ( $# -ne 6 ) ]]; then
if [[ ( $# -ne 3 ) && ( $# -ne 6 ) ]]; then
	echo "Error! Invalid argument count"
	usage
	exit -1