~aleteoryx/muditaos

29bf1e40f1f0b372209712ed23dff304f841302b — Pawel.Paprocki 4 years ago 4bf9cb7
[EGD-5834] Fix restore API

Change restore API
backup/restore/update harness tests
M module-services/service-desktop/ServiceDesktop.cpp => module-services/service-desktop/ServiceDesktop.cpp +30 -8
@@ 111,10 111,13 @@ sys::ReturnCodes ServiceDesktop::InitHandler()
        sdesktop::BackupMessage *backupMessage = dynamic_cast<sdesktop::BackupMessage *>(msg);
        if (backupMessage != nullptr) {
            RemountFS();
            backupStatus.state = BackupRestore::BackupUserFiles(this, backupStatus.backupTempDir);
            backupStatus.location =
                (purefs::dir::getBackupOSPath() / backupStatus.task).replace_extension(purefs::extension::tar);
            desktopWorker->reinit(backupStatus.location.parent_path());

            backupRestoreStatus.state = OperationState::Running;
            backupRestoreStatus.lastOperationResult =
                BackupRestore::BackupUserFiles(this, backupRestoreStatus.backupTempDir);
            backupRestoreStatus.location =
                (purefs::dir::getBackupOSPath() / backupRestoreStatus.task).replace_extension(purefs::extension::tar);
            backupRestoreStatus.state = OperationState::Stopped;
        }
        return sys::MessageNone{};
    });


@@ 123,7 126,17 @@ sys::ReturnCodes ServiceDesktop::InitHandler()
        sdesktop::RestoreMessage *restoreMessage = dynamic_cast<sdesktop::RestoreMessage *>(msg);
        if (restoreMessage != nullptr) {
            RemountFS();
            BackupRestore::RestoreUserFiles(this);
            backupRestoreStatus.state = OperationState::Running;
            backupRestoreStatus.lastOperationResult =
                BackupRestore::RestoreUserFiles(this, backupRestoreStatus.location);

            backupRestoreStatus.state = OperationState::Stopped;
            if (backupRestoreStatus.lastOperationResult == true) {
                sys::SystemManager::Reboot(this);
            }
            else {
                LOG_ERROR("Restore failed");
            }
        }
        return sys::MessageNone{};
    });


@@ 322,9 335,10 @@ void ServiceDesktop::storeHistory(const std::string &historyValue)

void ServiceDesktop::prepareBackupData()
{
    backupStatus.task          = std::to_string(static_cast<uint32_t>(utils::time::getCurrentTimestamp().getTime()));
    backupStatus.state         = false;
    backupStatus.backupTempDir = purefs::dir::getTemporaryPath() / backupStatus.task;
    backupRestoreStatus.operation = ServiceDesktop::Operation::Backup;
    backupRestoreStatus.task      = std::to_string(static_cast<uint32_t>(utils::time::getCurrentTimestamp().getTime()));
    backupRestoreStatus.state     = OperationState::Stopped;
    backupRestoreStatus.backupTempDir = purefs::dir::getTemporaryPath() / backupRestoreStatus.task;
}

void ServiceDesktop::processUSBHandshake(sdesktop::usb::USBHandshake *msg)


@@ 341,3 355,11 @@ void ServiceDesktop::processUSBHandshake(sdesktop::usb::USBHandshake *msg)

    parserFSM::MessageHandler::putToSendQueue(responseContext.createSimpleResponse());
}

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();
}

M module-services/service-desktop/endpoints/backup/BackupEndpoint.cpp => module-services/service-desktop/endpoints/backup/BackupEndpoint.cpp +5 -5
@@ 37,19 37,19 @@ auto BackupEndpoint::request(Context &context) -> sys::ReturnCodes
    auto owner = static_cast<ServiceDesktop *>(ownerServicePtr);

    if (context.getBody()[json::task].is_string()) {
        if (owner->getBackupStatus().task == context.getBody()[json::task].string_value()) {
            if (owner->getBackupStatus().state == true) {
        if (owner->getBackupRestoreStatus().task == context.getBody()[json::task].string_value()) {
            if (owner->getBackupRestoreStatus().state != ServiceDesktop::OperationState::Running) {
                context.setResponseStatus(parserFSM::http::Code::SeeOther);
            }

            context.setResponseBody(owner->getBackupStatus());
            context.setResponseBody(owner->getBackupRestoreStatus());
        }
        else {
            context.setResponseStatus(parserFSM::http::Code::NotFound);
        }
    }
    else if (context.getBody()[json::request] == true) {
        if (owner->getBackupStatus().state == true) {
        if (owner->getBackupRestoreStatus().state == ServiceDesktop::OperationState::Running) {
            // a backup is already running, don't start a second task
            context.setResponseStatus(parserFSM::http::Code::NotAcceptable);
        }


@@ 62,7 62,7 @@ auto BackupEndpoint::request(Context &context) -> sys::ReturnCodes
                                             service::name::service_desktop);

            // return new generated backup info
            context.setResponseBody(owner->getBackupStatus());
            context.setResponseBody(owner->getBackupRestoreStatus());
        }
    }
    else {

M module-services/service-desktop/endpoints/backup/BackupRestore.cpp => module-services/service-desktop/endpoints/backup/BackupRestore.cpp +195 -170
@@ 26,7 26,10 @@ namespace sys
} // namespace sys

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


@@ 50,7 53,7 @@ static bool copyFile(const std::filesystem::path &from, const std::filesystem::p
        return false;
    }

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

    while ((bytes = std::fread(buffer.get(), 1, purefs::buffer::tar_buf, fromFp)) != 0) {


@@ 99,18 102,18 @@ bool BackupRestore::WriteBackupInfo(sys::Service *ownerService, const std::files

    if (std::filesystem::is_directory(path)) {
        try {
            copyFile(purefs::dir::getRootDiskPath() / purefs::file::boot_json, path / purefs::file::boot_json);
            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 / "backup.json").c_str());
                      (path / bkp::backupInfo).c_str());

            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 / "backup.json").c_str(),
                      (path / bkp::backupInfo).c_str(),
                      e.what());

            return false;


@@ 124,27 127,31 @@ bool BackupRestore::WriteBackupInfo(sys::Service *ownerService, const std::files
    return true;
}

void BackupRestore::RestoreUserFiles(sys::Service *ownerService)
bool BackupRestore::RestoreUserFiles(sys::Service *ownerService, const std::filesystem::path &path)
{
    assert(ownerService != nullptr);
    LOG_INFO("RestoreUserFiles: restore started from %s", path.c_str());

    LOG_INFO("RestoreUserFiles: restore started...");

    if (BackupRestore::UnpackBackupFile() == false) {
        return;
    if (BackupRestore::UnpackBackupFile(path) == false) {
        return false;
    }

    /* close user files to be restored */
    LOG_INFO("RestoreUserFiles: closing ServiceDB...");
    std::string dbServiceName = service::name::db;
    sys::SystemManager::DestroySystemService(dbServiceName, ownerService);
    if (sys::SystemManager::Restore(ownerService) == false) {
        LOG_ERROR("Can't enter update system state");
        return false;
    }
    else {
        LOG_INFO("entered update state");

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

    // BackupRestore::RemoveBackupDir(path);

    LOG_INFO("RestoreUserFiles: restoring finished, rebooting...");
    sys::SystemManager::Reboot(ownerService);
    return true;
}

bool BackupRestore::RemoveBackupDir(std::filesystem::path &path)


@@ 168,41 175,31 @@ bool BackupRestore::RemoveBackupDir(std::filesystem::path &path)
bool BackupRestore::CreateBackupDir(std::filesystem::path &path)
{
    LOG_INFO("CreateBackupDir: creating backup directory %s...", path.c_str());
    std::error_code e;

    struct statvfs stat;
    if (statvfs(path.c_str(), &stat)) {
        LOG_ERROR("statvdfs failed on %s", path.c_str());
        return false;
    }

    if (stat.f_flag & ST_RDONLY) {
        LOG_ERROR("%s is on a RO fs", path.c_str());
        return false;
    }
    std::error_code errorCode;

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

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

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


@@ 213,7 210,7 @@ bool BackupRestore::CreateBackupDir(std::filesystem::path &path)
bool BackupRestore::PackUserFiles(std::filesystem::path &path)
{
    if (std::filesystem::is_empty(path)) {
        LOG_ERROR("PackUserFiles: backup dir %s is empty, nothing to backup, quitting...", path.c_str());
        LOG_ERROR("backup dir %s is empty, nothing to backup, quitting...", path.c_str());
        BackupRestore::RemoveBackupDir(path);
        return false;
    }


@@ 222,103 219,102 @@ bool BackupRestore::PackUserFiles(std::filesystem::path &path)
        (purefs::dir::getBackupOSPath() / path.filename()).replace_extension(purefs::extension::tar);
    mtar_t tarFile;

    LOG_INFO("PackUserFiles: opening file %s...", tarFilePath.c_str());
    LOG_INFO("opening file %s...", tarFilePath.c_str());

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

    if (ret != MTAR_ESUCCESS) {
        LOG_ERROR(
            "PackUserFiles: opening file %s failed, error: %s, quitting...", tarFilePath.c_str(), mtar_strerror(ret));
        LOG_ERROR("opening file %s failed, error: %s, quitting...", tarFilePath.c_str(), mtar_strerror(ret));
        BackupRestore::RemoveBackupDir(path);
        return false;
    }
    std::unique_ptr<unsigned char[]> buffer(new unsigned char[purefs::buffer::tar_buf]);

    auto buffer = std::make_unique<unsigned char[]>(purefs::buffer::tar_buf);
    std::error_code e;

    for (auto &direntry : std::filesystem::directory_iterator(path)) {
        if ((direntry.path().string() != ".") && (direntry.path().string() != "..") &&
            (direntry.path().string() != "...")) {

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

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

            LOG_DEBUG("PackUserFiles: writting tar header for %s...", direntry.path().c_str());
        if (!isValidDirentry(direntry)) {
            continue;
        }

            if (mtar_write_file_header(&tarFile,
                                       direntry.path().filename().c_str(),
                                       static_cast<unsigned>(std::filesystem::file_size(direntry))) != MTAR_ESUCCESS) {
                LOG_ERROR("PackUserFiles: writing tar header for %s failed", direntry.path().filename().c_str());
                std::fclose(file);
                mtar_close(&tarFile);
                BackupRestore::RemoveBackupDir(path);
                return false;
            }
        LOG_INFO("archiving file %s...", direntry.path().c_str());
        auto *file = std::fopen(direntry.path().string().c_str(), "r");

            uintmax_t filesize = std::filesystem::file_size(direntry.path(), e);
            if (e) {
                LOG_ERROR("failed to get size for file: %s \"%s\"", path.c_str(), e.message().c_str());
                BackupRestore::RemoveBackupDir(path);
                return false;
            }
            uint32_t loopcount = (filesize / purefs::buffer::tar_buf) + 1u;
            uint32_t readsize  = 0u;
        if (file == nullptr) {
            LOG_ERROR("archiving file %s failed, cannot open file, quitting...", direntry.path().c_str());
            mtar_close(&tarFile);
            BackupRestore::RemoveBackupDir(path);
            return false;
        }

            for (uint32_t i = 0u; i < loopcount; i++) {
                if (i + 1u == loopcount) {
                    readsize = filesize % purefs::buffer::tar_buf;
                }
                else {
                    readsize = purefs::buffer::tar_buf;
                }
        LOG_DEBUG("writting tar header for %s...", direntry.path().c_str());

                LOG_DEBUG("PackUserFiles: reading file %s...", direntry.path().c_str());
        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 for %s failed", direntry.path().filename().c_str());
            std::fclose(file);
            mtar_close(&tarFile);
            BackupRestore::RemoveBackupDir(path);
            return false;
        }

                if (std::fread(buffer.get(), 1, readsize, file) != readsize) {
                    LOG_ERROR("PackUserFiles: reading file %s failed, quitting...", direntry.path().c_str());
                    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: %s \"%s\"", path.c_str(), e.message().c_str());
            BackupRestore::RemoveBackupDir(path);
            return false;
        }
        uint32_t loopcount = (filesize / purefs::buffer::tar_buf) + 1u;
        uint32_t readsize;

                LOG_DEBUG("PackUserFiles: writting %s into backup...", direntry.path().c_str());
                if (mtar_write_data(&tarFile, buffer.get(), readsize) != MTAR_ESUCCESS) {
                    LOG_ERROR("PackUserFiles: writting %s into backup failed, quitting...", direntry.path().c_str());
                    std::fclose(file);
                    mtar_close(&tarFile);
                    BackupRestore::RemoveBackupDir(path);
                    return false;
                }
        for (uint32_t i = 0u; i < loopcount; i++) {
            if (i + 1u == loopcount) {
                readsize = filesize % purefs::buffer::tar_buf;
            }
            else {
                readsize = purefs::buffer::tar_buf;
            }

            LOG_INFO("PackUserFiles: closing file %s...", direntry.path().c_str());
            if (std::fclose(file) != 0) {
                LOG_ERROR("PackUserFiles: closing file %s failed, quitting...", direntry.path().c_str());
            LOG_DEBUG("reading file %s...", direntry.path().c_str());

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

            LOG_INFO("PackUserFiles: deleting file %s...", direntry.path().c_str());

            if (std::remove(direntry.path().c_str()) != 0) {
                LOG_ERROR("PackUserFiles: deleting file %s failed, quitting...", direntry.path().c_str());
            LOG_DEBUG("writting %s into backup...", direntry.path().c_str());
            if (mtar_write_data(&tarFile, buffer.get(), readsize) != MTAR_ESUCCESS) {
                LOG_ERROR("PackUserFiles: writting %s into backup failed, quitting...", direntry.path().c_str());
                std::fclose(file);
                mtar_close(&tarFile);
                BackupRestore::RemoveBackupDir(path);
                return false;
            }
        }

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

        LOG_INFO("deleting file %s...", direntry.path().c_str());

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

    LOG_INFO("PackUserFiles: finalizing file %s...", tarFilePath.c_str());
    LOG_INFO("finalizing file %s...", tarFilePath.c_str());
    if (mtar_finalize(&tarFile) != MTAR_ESUCCESS) {
        LOG_ERROR("PackUserFiles: finalizing file %s failed, quitting....", tarFilePath.c_str());
        mtar_close(&tarFile);


@@ 326,7 322,7 @@ bool BackupRestore::PackUserFiles(std::filesystem::path &path)
        return false;
    }

    LOG_INFO("PackUserFiles: closing file %s...", tarFilePath.c_str());
    LOG_INFO("closing file %s...", tarFilePath.c_str());
    if (mtar_close(&tarFile) != MTAR_ESUCCESS) {
        LOG_ERROR("PackUserFiles: closing file %s failed, quitting...", tarFilePath.c_str());
        BackupRestore::RemoveBackupDir(path);


@@ 336,41 332,46 @@ bool BackupRestore::PackUserFiles(std::filesystem::path &path)
    return true;
}

bool BackupRestore::UnpackBackupFile()
bool BackupRestore::UnpackBackupFile(const std::filesystem::path &tarFilePath)
{
    std::string tarFilePath = purefs::dir::getBackupOSPath();
    tarFilePath += "/";

    mtar_t tarFile;
    mtar_header_t tarHeader;
    std::error_code e;

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

    LOG_INFO("creating temporary directory %s", extractDestination.c_str());
    if (!std::filesystem::is_directory(extractDestination, e)) {
        std::filesystem::create_directory(extractDestination, e);
        if (e) {
            LOG_ERROR("Can't create temporary directory %s \"%s\"", extractDestination.c_str(), e.message().c_str());
            return false;
        }
    }

    LOG_INFO("UnpackBackupFile: opening file %s...", tarFilePath.c_str());
    LOG_INFO("opening file %s...", tarFilePath.c_str());

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

    if (ret != MTAR_ESUCCESS) {
        LOG_ERROR("UnpackBackupFile: opening file %s failed, error: %s, quitting...",
                  tarFilePath.c_str(),
                  mtar_strerror(ret));
        LOG_ERROR("opening file %s failed, error: %s, quitting...", tarFilePath.c_str(), mtar_strerror(ret));
        return false;
    }

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

    do {
        ret = mtar_read_header(&tarFile, &tarHeader);
        LOG_INFO("UnpackBackupFile: reading tar header name %s...", tarHeader.name);
        LOG_DEBUG("reading tar header name %s...", tarHeader.name);

        if ((tarHeader.type == MTAR_TREG) && (ret == MTAR_ESUCCESS)) {
            LOG_INFO("UnpackBackupFile: extracting file %s...", tarHeader.name);
            LOG_DEBUG("extracting file %s...", tarHeader.name);

            std::string restoreFilePath = purefs::dir::getBackupOSPath();
            restoreFilePath += "/";
            restoreFilePath += tarHeader.name;
            auto *file = std::fopen(restoreFilePath.c_str(), "w");
            std::filesystem::path extractedFile = extractDestination / tarHeader.name;
            auto *file                          = std::fopen(extractedFile.c_str(), "w");

            if (file == nullptr) {
                LOG_ERROR("UnpackBackupFile: extracting file %s failed, quitting...", tarHeader.name);
                LOG_ERROR("can't open %s for writing", extractedFile.c_str());
                mtar_close(&tarFile);
                return false;
            }


@@ 388,90 389,114 @@ bool BackupRestore::UnpackBackupFile()
                }

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

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

            LOG_INFO("UnpackBackupFile: extracting file %s succeeded", tarHeader.name);
            LOG_INFO("extracting file %s succeeded", extractedFile.c_str());
            std::fclose(file);
        }
        else {
            LOG_INFO("UnpackBackupFile: found header %d, skipping", tarHeader.type);
            LOG_DEBUG("found header %d, skipping", tarHeader.type);
        }

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

    LOG_INFO("UnpackBackupFile: cleaning directory from tar file...");
    LOG_DEBUG("cleanup %s", tarFilePath.c_str());
    mtar_close(&tarFile);
    std::remove(tarFilePath.c_str());

    if (e) {
        LOG_WARN("can't cleanup temporary dir %s \"%s\"", extractDestination.c_str(), e.message().c_str());
    }

    return true;
}

bool BackupRestore::ReplaceUserFiles()
bool BackupRestore::ReplaceUserFiles(const std::filesystem::path &path)
{
    /* replace existing files that have respective backup files existing */
    const auto backupOSPath = purefs::dir::getBackupOSPath();
    if (std::filesystem::is_directory(backupOSPath) && std::filesystem::is_empty(backupOSPath)) {
        LOG_INFO("ReplaceUserFiles: dir emtpy, nothing to restore, quitting...");
    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...");
        return false;
    }
    std::string userFilePath = purefs::dir::getUserDiskPath();
    userFilePath += "/";

    std::string backupFilePath = purefs::dir::getBackupOSPath();
    backupFilePath += "/";

    for (auto &direntry : std::filesystem::directory_iterator(backupOSPath.c_str())) {
        if ((direntry.path().compare(".") != 0) && (direntry.path().compare("..") != 0) &&
            (direntry.path().compare("...") != 0)) {
            LOG_INFO("ReplaceUserFiles: restoring backup file %s...", direntry.path().c_str());

            if (std::filesystem::exists((userFilePath + direntry.path().string()))) {
                if (std::filesystem::remove((userFilePath + direntry.path().string()))) {
                    try {
                        std::filesystem::rename(backupFilePath + direntry.path().string(),
                                                userFilePath + direntry.path().string());
                        LOG_INFO("ReplaceUserFiles: restoring backup file %s succeeded", direntry.path().c_str());
                    }
                    catch (const std::exception &e) {
                        LOG_ERROR("ReplaceUserFiles: restoring backup file %s failed on error %s",
                                  direntry.path().c_str(),
                                  e.what());
                    }
                }
                else {
                    LOG_ERROR("ReplaceUserFiles: restoring backup file %s", direntry.path().c_str());
                }

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

    for (auto &direntry : std::filesystem::directory_iterator(tempDir, e)) {
        if (e) {
            LOG_INFO("Can't list contents of %s \"%s\"", tempDir.c_str(), e.message().c_str());
            return false;
        }

        if (!isValidDirentry(direntry)) {
            continue;
        }

        // dont restore the information file
        if (direntry.path().filename() == bkp::backupInfo) {
            continue;
        }

        LOG_INFO("restoring backup file %s...", direntry.path().c_str());

        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 %s->%s restore failed \"%s\"",
                          (tempDir / direntry.path().filename()).c_str(),
                          (userDir / direntry.path().filename()).c_str(),
                          e.message().c_str());
                return false;
            }
            else {
                try {
                    std::filesystem::rename(backupFilePath + direntry.path().string(),
                                            userFilePath + direntry.path().string());
                    LOG_INFO("ReplaceUserFiles: restoring backup file %s succeeded", direntry.path().c_str());
                }
                catch (const std::filesystem::filesystem_error &e) {
                    LOG_ERROR("ReplaceUserFiles: restoring backup file %s failed on error %s",
                              direntry.path().c_str(),
                              e.what());
                }
                LOG_INFO("restored %s->%s",
                         (tempDir / direntry.path().filename()).c_str(),
                         (userDir / direntry.path().filename()).c_str());
            }
        }
        else {
            LOG_WARN("can't remove %s \"%s\"", (userDir / direntry.path().filename()).c_str(), e.message().c_str());
            // we should continue, there can be new files in the backup
        }
    }

    return true;
}

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 \"%s\"", purefs::dir::getBackupOSPath().c_str(), e.message().c_str());
            return json11::Json();
        }
        if (!p.is_directory() && p.path().extension() == purefs::extension::tar) {
            LOG_DEBUG("possible restore file %s", p.path().filename().c_str());
            dirEntryVector.push_back(p.path().filename());
        }
    }

    return dirEntryVector;
}

M module-services/service-desktop/endpoints/backup/BackupRestore.hpp => module-services/service-desktop/endpoints/backup/BackupRestore.hpp +10 -3
@@ 5,25 5,32 @@

#include <Service/Service.hpp>
#include <filesystem>
#include <json/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 void RestoreUserFiles(sys::Service *ownerService);
    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();
    static bool ReplaceUserFiles();
    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/restore/RestoreEndpoint.cpp => module-services/service-desktop/endpoints/restore/RestoreEndpoint.cpp +67 -10
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "RestoreEndpoint.hpp"


@@ 8,6 8,7 @@
#include <parser/ParserUtils.hpp>
#include <service-desktop/DesktopMessages.hpp>
#include <service-desktop/ServiceDesktop.hpp>
#include <service-desktop/endpoints/backup/BackupRestore.hpp>

#include <json/json11.hpp>



@@ 17,23 18,79 @@ using namespace parserFSM;

auto RestoreEndpoint::handle(Context &context) -> void
{
    if (context.getMethod() == parserFSM::http::Method::post) {
    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;
    }

    MessageHandler::putToSendQueue(context.createSimpleResponse());
}

        if (context.getBody()[parserFSM::json::restoreRequest] == true) {
            auto msg = std::make_shared<sdesktop::RestoreMessage>();
            ownerServicePtr->bus.sendUnicast(msg, service::name::service_desktop);
auto RestoreEndpoint::request(Context &context) -> sys::ReturnCodes
{
    json11::Json responseBodyJson;
    auto owner = static_cast<ServiceDesktop *>(ownerServicePtr);

            context.setResponseBody(json11::Json::object({{parserFSM::json::restoreRequest, true}}));
    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(parserFSM::http::Code::SeeOther);
            }

            context.setResponseBody(owner->getBackupRestoreStatus());
        }
        else {
            context.setResponseBody(json11::Json::object({{parserFSM::json::restoreRequest, false}}));
            context.setResponseStatus(parserFSM::http::Code::NotFound);
        }
    }
    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");
            context.setResponseStatus(parserFSM::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(parserFSM::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(parserFSM::http::Code::NotFound);

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

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

        return;
            // return new generated restore info
            context.setResponseBody(owner->getBackupRestoreStatus());
        }
    }
    else {
        return;
        // unknown request for backup endpoint
        context.setResponseStatus(parserFSM::http::Code::BadRequest);
    }

    LOG_DEBUG("responding: %s", context.createSimpleResponse().c_str());
    MessageHandler::putToSendQueue(context.createSimpleResponse());

    return sys::ReturnCodes::Success;
}

M module-services/service-desktop/endpoints/restore/RestoreEndpoint.hpp => module-services/service-desktop/endpoints/restore/RestoreEndpoint.hpp +1 -0
@@ 25,4 25,5 @@ class RestoreEndpoint : public parserFSM::Endpoint
        debugName = "RestoreEndpoint";
    }
    auto handle(parserFSM::Context &context) -> void override;
    auto request(parserFSM::Context &context) -> sys::ReturnCodes;
};

M module-services/service-desktop/parser/ParserUtils.hpp => module-services/service-desktop/parser/ParserUtils.hpp +0 -1
@@ 94,7 94,6 @@ namespace parserFSM
        inline constexpr auto currentRTCTime   = "currentRTCTime";
        inline constexpr auto updateReady      = "updateReady";
        inline constexpr auto updateFileList   = "updateFileList";
        inline constexpr auto restoreRequest   = "restoreRequest";
        inline constexpr auto factoryRequest   = "factoryRequest";
        inline constexpr auto networkStatus    = "networkStatus";
        inline constexpr auto accessTechnology = "accessTechnology";

M module-services/service-desktop/service-desktop/ServiceDesktop.hpp => module-services/service-desktop/service-desktop/ServiceDesktop.hpp +36 -9
@@ 47,20 47,46 @@ class ServiceDesktop : public sys::Service
    ServiceDesktop();
    ~ServiceDesktop() override;

    struct BackupStatus
    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;
        bool state = false;
        OperationState state = OperationState::Stopped;
        Operation operation  = Operation::Backup;
        json11::Json to_json() const
        {
            return json11::Json::object{
                {parserFSM::json::task, task},
                {parserFSM::json::state, state ? parserFSM::json::finished : parserFSM::json::pending},
                {parserFSM::json::location, location.string()}};
            return json11::Json::object{{parserFSM::json::task, task},
                                        {parserFSM::json::state, opToString(state)},
                                        {parserFSM::json::location, location.string()}};
        }
    } backupStatus;
    } backupRestoreStatus;

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


@@ 73,9 99,10 @@ class ServiceDesktop : public sys::Service

    void storeHistory(const std::string &historyValue);
    void prepareBackupData();
    const BackupStatus getBackupStatus()
    void prepareRestoreData(const std::filesystem::path &restoreLocation);
    const BackupRestoreStatus getBackupRestoreStatus()
    {
        return backupStatus;
        return backupRestoreStatus;
    }
    const sdesktop::USBSecurityModel *getSecurity()
    {

M module-sys/SystemManager/SystemManager.cpp => module-sys/SystemManager/SystemManager.cpp +97 -25
@@ 46,6 46,34 @@ namespace sys
        constexpr std::chrono::milliseconds lowBatteryShutdownDelayTime{5000};
    } // namespace

    namespace state
    {
        namespace update
        {
            static const std::set<std::string> whitelist{service::name::service_desktop,
                                                         service::name::evt_manager,
                                                         service::name::gui,
                                                         service::name::db,
                                                         service::name::eink,
                                                         app::manager::ApplicationManager::ServiceName};
        }

        namespace restore
        {
            static const std::set<std::string> whitelist{service::name::service_desktop,
                                                         service::name::evt_manager,
                                                         service::name::gui,
                                                         service::name::eink,
                                                         app::manager::ApplicationManager::ServiceName};
        }

        static bool isOnWhitelist(const std::set<std::string> &list, const std::string &serviceName)
        {
            return list.find(serviceName) != list.end();
        }

    } // namespace state

    using namespace cpp_freertos;
    using namespace std;
    using namespace sys;


@@ 164,9 192,9 @@ namespace sys

    void SystemManager::StartSystem(InitFunction sysInit, InitFunction appSpaceInit)
    {
        powerManager  = std::make_unique<PowerManager>();
        cpuStatistics = std::make_unique<CpuStatistics>();
        deviceManager = std::make_unique<DeviceManager>();
        powerManager     = std::make_unique<PowerManager>();
        cpuStatistics    = std::make_unique<CpuStatistics>();
        deviceManager    = std::make_unique<DeviceManager>();
        phoneModeSubject = std::make_unique<phone_modes::Subject>(this);

        systemInit = std::move(sysInit);


@@ 180,7 208,13 @@ namespace sys
        cpuStatisticsTimer.start();
    }

    bool SystemManager::Update(Service *s, const std::string &updateOSVer, std::string &currentOSVer)
    bool SystemManager::CloseSystem(Service *s)
    {
        s->bus.sendUnicast(std::make_shared<SystemManagerCmd>(Code::CloseSystem), service::name::system_manager);
        return true;
    }

    bool SystemManager::Update(Service *s, const std::string &updateOSVer, const std::string &currentOSVer)
    {
        // set update OS version (and also current os version) in Settings
        storeOsVersion(s, updateOSVer, currentOSVer);


@@ 195,6 229,25 @@ namespace sys
        return true;
    }

    bool SystemManager::Restore(Service *s)
    {
        LOG_DEBUG("trying to enter restore state");
        auto ret = s->bus.sendUnicast(std::make_shared<SystemManagerCmd>(Code::Restore),
                                      service::name::system_manager,
                                      sys::constants::restoreTimeout);
        if (ret.first != ReturnCodes::Success) {
            LOG_WARN("Can't stop all services, %d ms wait time", sys::constants::restoreTimeout);
        }
        auto msgCloseApplications = std::make_shared<app::manager::UpdateInProgress>(service::name::system_manager);
        ret                       = s->bus.sendUnicast(std::move(msgCloseApplications),
                                 app::manager::ApplicationManager::ServiceName,
                                 sys::constants::restoreTimeout);
        if (ret.first != ReturnCodes::Success) {
            LOG_WARN("Can't stop all applications, %d ms wait time", sys::constants::restoreTimeout);
        }
        return true;
    }

    void SystemManager::storeOsVersion(Service *s, const std::string &updateOSVer, const std::string &currentOSVer)
    {
        // store OS version in Settings


@@ 411,6 464,9 @@ namespace sys
                case Code::Update:
                    UpdateSystemHandler();
                    break;
                case Code::Restore:
                    RestoreSystemHandler();
                    break;
                case Code::Reboot:
                    RebootHandler();
                    break;


@@ 418,6 474,7 @@ namespace sys
                    break;
                }
            }

            return MessageNone{};
        });



@@ 603,9 660,9 @@ namespace sys
        set(State::Shutdown);
    }

    void SystemManager::UpdateSystemHandler()
    void SystemManager::RestoreSystemHandler()
    {
        LOG_DEBUG("Starting system update procedure...");
        LOG_INFO("Entering restore system state");

        // We are going to remove services in reversed order of creation
        CriticalSection::Enter();


@@ 614,34 671,49 @@ namespace sys

        for (bool retry{};; retry = false) {
            for (auto &service : servicesList) {
                if (service->GetName() == service::name::evt_manager) {
                    LOG_DEBUG("Delay closing %s", service::name::evt_manager);
                    continue;
                }
                if (service->GetName() == service::name::service_desktop) {
                    LOG_DEBUG("Delay closing %s", service::name::service_desktop);
                if (sys::state::isOnWhitelist(sys::state::restore::whitelist, service->GetName())) {
                    continue;
                }

                if (service->GetName() == service::name::db) {
                    LOG_DEBUG("Delay closing %s", service::name::db);
                    continue;
                if (service->parent.empty()) {
                    LOG_DEBUG("destroy service: %s", service->GetName().c_str());
                    const auto ret = DestroySystemService(service->GetName(), this);
                    if (!ret) {
                        // no response to exit message,
                        LOG_FATAL("%s failed to respond to exit message", service->GetName().c_str());
                        kill(service);
                    }
                    else {
                        LOG_DEBUG("%s destroyed", service->GetName().c_str());
                    }
                    retry = true;
                    break;
                }
            }
            if (!retry) {
                break;
            }
        }

                if (service->GetName() == service::name::gui) {
                    LOG_DEBUG("Delay closing %s", service::name::gui);
                    continue;
                }
        LOG_INFO("entered restore state");
    }

                if (service->GetName() == service::name::eink) {
                    LOG_DEBUG("Delay closing %s", service::name::eink);
                    continue;
                }
    void SystemManager::UpdateSystemHandler()
    {
        LOG_DEBUG("Starting system update procedure...");

        // We are going to remove services in reversed order of creation
        CriticalSection::Enter();
        std::reverse(servicesList.begin(), servicesList.end());
        CriticalSection::Exit();

                if (service->GetName() == app::manager::ApplicationManager::ServiceName) {
                    LOG_DEBUG("Delay closing %s", app::manager::ApplicationManager::ServiceName);
        for (bool retry{};; retry = false) {
            for (auto &service : servicesList) {
                if (sys::state::isOnWhitelist(sys::state::update::whitelist, service->GetName())) {
                    LOG_DEBUG("Delay closing %s", service->GetName().c_str());
                    continue;
                }

                if (service->parent.empty()) {
                    const auto ret = DestroySystemService(service->GetName(), this);
                    if (!ret) {

M module-sys/SystemManager/SystemManager.hpp => module-sys/SystemManager/SystemManager.hpp +11 -2
@@ 36,6 36,7 @@ namespace sys
        using namespace std::chrono_literals;
        inline constexpr std::chrono::milliseconds timerInitInterval{30s};
        inline constexpr std::chrono::milliseconds timerPeriodInterval{100ms};
        inline constexpr auto restoreTimeout{5000};
    } // namespace constants

    class PhoneModeRequest; // Forward declaration


@@ 45,6 46,7 @@ namespace sys
    {
        CloseSystem,
        Update,
        Restore,
        Reboot,
        None,
    };


@@ 77,7 79,7 @@ namespace sys
            Suspend,
            Shutdown,
            ShutdownReady,
            Reboot,
            Reboot
        } state = State::Running;

        explicit SystemManager(std::vector<std::unique_ptr<BaseServiceCreator>> &&creators);


@@ 89,7 91,12 @@ namespace sys

        void StartSystem(InitFunction sysInit, InitFunction appSpaceInit);

        static bool Update(Service *s, const std::string &updateOSVer, std::string &currentOSVer);
        // Invoke system close procedure
        static bool CloseSystem(Service *s);

        static bool Update(Service *s, const std::string &updateOSVer, const std::string &currentOSVer);

        static bool Restore(Service *s);

        static bool Reboot(Service *s);



@@ 167,6 174,8 @@ namespace sys

        void UpdateSystemHandler();

        void RestoreSystemHandler();

        void RebootHandler();

        /// loop to handle prior to full system close

M test/pytest/conftest.py => test/pytest/conftest.py +4 -0
@@ 199,3 199,7 @@ def pytest_configure(config):
                            "usb_cdc_echo: mark test if it's intended for usb-cdc echo mode")
    config.addinivalue_line("markers",
                            "two_sim_cards: mark test in case when two sim cards are required")
    config.addinivalue_line("markers",
                            "backup: subset of backup user data tests")
    config.addinivalue_line("markers",
                            "restore: subset of restore user data tests")

M test/pytest/service-desktop/test_backup.py => test/pytest/service-desktop/test_backup.py +58 -3
@@ 1,12 1,67 @@
# Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
# 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

@pytest.mark.skip("rkubiak01 is fixing this issue")
@pytest.mark.service_desktop_test
@pytest.mark.rt1051
@pytest.mark.usefixtures("usb_unlocked")
@pytest.mark.backup
def test_backup(harness):
    body = { "request": True }
    ret = harness.endpoint_request("backup", "get", body)
    assert ret["status"] == status["BadRequest"]
    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"]
    
    # in response we get a task ID and status 200
    log.debug("backup started, waiting for results")
    
    # 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

        # 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

A test/pytest/service-desktop/test_restore.py => test/pytest/service-desktop/test_restore.py +39 -0
@@ 0,0 1,39 @@
# Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
# For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
import pytest
import time
from harness.interface.defs import status
from harness import log

@pytest.mark.service_desktop_test
@pytest.mark.rt1051
@pytest.mark.usefixtures("usb_unlocked")
@pytest.mark.restore
def test_restore(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")
    
    # body should be an array of files
    assert isinstance(response["body"], 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"] != ""
    else:
        log.error("no possisble backup files on phone, run backup first")
        assert False

M test/pytest/service-desktop/test_update.py => test/pytest/service-desktop/test_update.py +1 -2
@@ 1,11 1,10 @@
# Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
# Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
# For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
import pytest
from harness.interface.defs import status


@pytest.mark.service_desktop_test
@pytest.mark.rt1051
@pytest.mark.usefixtures("usb_unlocked")
@pytest.mark.rt1051
def test_update(harness):