From a0677af340a1fec8e8652b64fb97ca7f0902de12 Mon Sep 17 00:00:00 2001 From: Marek Niepieklo Date: Wed, 29 Sep 2021 12:32:16 +0200 Subject: [PATCH] [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 --- cmake/modules/DiskImage.cmake | 9 +- module-platform/CommonPlatform.cpp | 8 + .../service-db/ServiceDBCommon.cpp | 1 + .../service-desktop/BackupRestore.cpp | 550 ++++++++++++++++++ .../service-desktop/CMakeLists.txt | 3 + .../service-desktop/ServiceDesktop.cpp | 57 +- .../service-desktop/endpoints/CMakeLists.txt | 8 +- .../endpoints/backup/BackupEndpoint.cpp | 70 +-- .../endpoints/backup/BackupHelper.cpp | 87 +++ .../endpoints/backup/BackupRestore.cpp | 506 ---------------- .../include/endpoints/JsonKeyNames.hpp | 5 +- .../endpoints/backup/BackupEndpoint.hpp | 18 +- .../include/endpoints/backup/BackupHelper.hpp | 23 + .../endpoints/backup/BackupRestore.hpp | 36 -- .../endpoints/restore/RestoreEndpoint.hpp | 8 +- .../endpoints/restore/RestoreHelper.hpp | 23 + .../endpoints/restore/RestoreEndpoint.cpp | 88 +-- .../endpoints/restore/RestoreHelper.cpp | 106 ++++ .../include/service-desktop/BackupRestore.hpp | 120 ++++ .../service-desktop/ServiceDesktop.hpp | 48 +- .../paths/include/purefs/filesystem_paths.hpp | 1 + products/PurePhone/services/db/ServiceDB.cpp | 6 + .../deviceInfo/DeviceInfoEndpoint.cpp | 3 +- test/get_os_log.py | 7 +- test/harness | 2 +- test/pytest/service-desktop/test_backup.py | 106 ++-- .../service-desktop/test_device_info.py | 1 + test/pytest/service-desktop/test_restore.py | 146 ++++- third-party/microtar/CMakeLists.txt | 3 + tools/generate_image.sh | 4 +- 30 files changed, 1204 insertions(+), 849 deletions(-) create mode 100644 module-services/service-desktop/BackupRestore.cpp create mode 100644 module-services/service-desktop/endpoints/backup/BackupHelper.cpp delete mode 100644 module-services/service-desktop/endpoints/backup/BackupRestore.cpp create mode 100644 module-services/service-desktop/endpoints/include/endpoints/backup/BackupHelper.hpp delete mode 100644 module-services/service-desktop/endpoints/include/endpoints/backup/BackupRestore.hpp create mode 100644 module-services/service-desktop/endpoints/include/endpoints/restore/RestoreHelper.hpp create mode 100644 module-services/service-desktop/endpoints/restore/RestoreHelper.cpp create mode 100644 module-services/service-desktop/include/service-desktop/BackupRestore.hpp diff --git a/cmake/modules/DiskImage.cmake b/cmake/modules/DiskImage.cmake index d4a0444a51b53f8ba8592ca26b0642ace4f19cf4..6c2877ad35e822db18032c19e6cefc1348cba60c 100644 --- a/cmake/modules/DiskImage.cmake +++ b/cmake/modules/DiskImage.cmake @@ -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() diff --git a/module-platform/CommonPlatform.cpp b/module-platform/CommonPlatform.cpp index ebbd3c859070b87cd7348376419405aa88cac79f..9793b20a94cb0f563e61b4d7650b37f751170b62 100644 --- a/module-platform/CommonPlatform.cpp +++ b/module-platform/CommonPlatform.cpp @@ -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 diff --git a/module-services/service-db/ServiceDBCommon.cpp b/module-services/service-db/ServiceDBCommon.cpp index f83a1809751b78a7e7ef037e0dbb41930808f464..c5cb3605dc5291951179535bf667dd5bed23b518 100644 --- a/module-services/service-db/ServiceDBCommon.cpp +++ b/module-services/service-db/ServiceDBCommon.cpp @@ -60,6 +60,7 @@ sys::ReturnCodes ServiceDBCommon::InitHandler() sys::ReturnCodes ServiceDBCommon::DeinitHandler() { + Database::deinitialize(); return sys::ReturnCodes::Success; } diff --git a/module-services/service-desktop/BackupRestore.cpp b/module-services/service-desktop/BackupRestore.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2fb97630e3c7de3838c7d9cfb3883e005467b0c3 --- /dev/null +++ b/module-services/service-desktop/BackupRestore.cpp @@ -0,0 +1,550 @@ +// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. +// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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() != "..."; +} + +BackupRestore::CompletionCode BackupRestore::BackupUserFiles(sys::Service *ownerService, std::filesystem::path &path) +{ + assert(ownerService != nullptr); + LOG_INFO("Backup started..."); + + if (BackupRestore::RemoveBackupDir(path) == false) { + return CompletionCode::FSError; + } + + if (BackupRestore::CreateBackupDir(path) == false) { + return CompletionCode::FSError; + } + + LOG_DEBUG("Database backup started..."); + if (DBServiceAPI::DBBackup(ownerService, path) == false) { + LOG_ERROR("Database backup failed, quitting..."); + BackupRestore::RemoveBackupDir(path); + return CompletionCode::DBError; + } + + if (WriteBackupInfo(ownerService, path) == false) { + LOG_ERROR("Failed to save backup info"); + BackupRestore::RemoveBackupDir(path); + return CompletionCode::CopyError; + } + + LOG_DEBUG("Packing files"); + if (BackupRestore::PackUserFiles(path) == false) { + LOG_ERROR("Failed pack backup"); + BackupRestore::RemoveBackupDir(path); + return CompletionCode::PackError; + } + + 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)) { + LOG_ERROR("%s is not a directory", path.c_str()); + return false; + } + + const auto version_json = purefs::dir::getCurrentOSPath() / purefs::file::version_json; + + 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; +} + +BackupRestore::CompletionCode BackupRestore::RestoreUserFiles(sys::Service *ownerService, + const std::filesystem::path &path) +{ + assert(ownerService != nullptr); + + LOG_INFO("Restore started"); + + if (BackupRestore::UnpackBackupFile(path) == false) { + LOG_ERROR("Can't unpack user files"); + return CompletionCode::UnpackError; + } + + if (BackupRestore::CanRestoreFromBackup(TempPathForBackupFile(path)) == false) { + LOG_ERROR("Can't restore user files"); + return CompletionCode::FSError; + } + + if (sys::SystemManagerCommon::Restore(ownerService) == false) { + LOG_ERROR("Can't enter system restore state"); + return CompletionCode::OtherError; + } + + LOG_INFO("Entered restore state"); + + if (BackupRestore::ReplaceUserFiles(path) == false) { + LOG_ERROR("Can't restore user files"); + return CompletionCode::CopyError; + } + + return CompletionCode::Success; +} + +bool BackupRestore::RemoveBackupDir(const std::filesystem::path &path) +{ + /* prepare directories */ + if (std::filesystem::is_directory(path)) { + LOG_INFO("Removing backup directory %s...", path.c_str()); + std::error_code errorCode; + + if (!std::filesystem::remove_all(path, errorCode)) { + LOG_ERROR("Removing backup directory %s failed, error: %d.", path.c_str(), errorCode.value()); + return false; + } + } + + return true; +} + +bool BackupRestore::CreateBackupDir(const std::filesystem::path &path) +{ + LOG_INFO("Creating backup directory %s...", path.c_str()); + std::error_code errorCode; + + 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; + } + } + + return true; +} + +bool BackupRestore::PackUserFiles(const std::filesystem::path &path) +{ + if (std::filesystem::is_empty(path)) { + LOG_ERROR("Backup dir is empty, nothing to backup, quitting..."); + BackupRestore::RemoveBackupDir(path); + return false; + } + + std::filesystem::path tarFilePath = (purefs::dir::getBackupOSPath() / path.filename()); + mtar_t tarFile; + + 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..."); + BackupRestore::RemoveBackupDir(path); + return false; + } + + auto buffer = std::make_unique(purefs::buffer::tar_buf); + constexpr size_t streamBufferSize = 64 * 1024; + auto streamBuffer = std::make_unique(streamBufferSize); + setvbuf(tarFile.stream, streamBuffer.get(), _IOFBF, streamBufferSize); + std::error_code errorCode; + + for (auto &direntry : std::filesystem::directory_iterator(path)) { + if (!isValidDirentry(direntry)) { + continue; + } + + 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..."); + mtar_close(&tarFile); + BackupRestore::RemoveBackupDir(path); + return false; + } + + LOG_DEBUG("Writting tar header ..."); + + if (mtar_write_file_header(&tarFile, + direntry.path().filename().c_str(), + static_cast(std::filesystem::file_size(direntry))) != MTAR_ESUCCESS) { + 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(), 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; + } + uint32_t loopcount = (filesize / purefs::buffer::tar_buf) + 1u; + uint32_t readsize; + + 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("Reading file ..."); + + if (std::fread(buffer.get(), 1, readsize, file) != readsize) { + LOG_ERROR("Reading file failed, quitting..."); + std::fclose(file); + mtar_close(&tarFile); + BackupRestore::RemoveBackupDir(path); + return false; + } + + LOG_DEBUG("Writting into backup..."); + if (mtar_write_data(&tarFile, buffer.get(), readsize) != MTAR_ESUCCESS) { + LOG_ERROR("Writting into backup failed, quitting..."); + std::fclose(file); + mtar_close(&tarFile); + BackupRestore::RemoveBackupDir(path); + return false; + } + } + + LOG_INFO("Closing file..."); + if (std::fclose(file) != 0) { + LOG_ERROR("Closing file failed, quitting..."); + mtar_close(&tarFile); + BackupRestore::RemoveBackupDir(path); + return false; + } + + LOG_INFO("Deleting file ..."); + + 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..."); + if (mtar_finalize(&tarFile) != MTAR_ESUCCESS) { + LOG_ERROR("Finalizing tar file failed, quitting...."); + mtar_close(&tarFile); + BackupRestore::RemoveBackupDir(path); + return false; + } + + LOG_INFO("Closing tar file..."); + if (mtar_close(&tarFile) != MTAR_ESUCCESS) { + LOG_ERROR("Closing tar file failed, quitting..."); + BackupRestore::RemoveBackupDir(path); + return false; + } + + 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 errorCode; + + auto extractDestination = TempPathForBackupFile(tarFilePath); + + 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 ..."); + + int ret = mtar_open(&tarFile, tarFilePath.c_str(), "r"); + + if (ret != MTAR_ESUCCESS) { + LOG_ERROR("Opening tar file failed, quitting..."); + return false; + } + + auto buffer = std::make_unique(purefs::buffer::tar_buf); + constexpr size_t streamBufferSize = 64 * 1024; + auto streamBuffer = std::make_unique(streamBufferSize); + setvbuf(tarFile.stream, streamBuffer.get(), _IOFBF, streamBufferSize); + + do { + ret = mtar_read_header(&tarFile, &tarHeader); + LOG_DEBUG("Reading tar header name..."); + + if ((tarHeader.type == MTAR_TREG) && (ret == MTAR_ESUCCESS)) { + 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"); + mtar_close(&tarFile); + return false; + } + + constexpr size_t streamBufferSize = 16 * 1024; + auto streamBuffer = std::make_unique(streamBufferSize); + setvbuf(file, streamBuffer.get(), _IOFBF, streamBufferSize); + + uint32_t loopcount = (tarHeader.size / purefs::buffer::tar_buf) + 1u; + uint32_t readsize = 0u; + + for (uint32_t i = 0u; i < loopcount; i++) { + + if (i + 1u == loopcount) { + readsize = tarHeader.size % purefs::buffer::tar_buf; + } + else { + readsize = purefs::buffer::tar_buf; + } + + if (mtar_read_data(&tarFile, buffer.get(), readsize) != MTAR_ESUCCESS) { + LOG_ERROR("Extracting file failed, quitting..."); + mtar_close(&tarFile); + std::fclose(file); + std::filesystem::remove(extractedFile.c_str()); + return false; + } + + if (std::fwrite(buffer.get(), 1, readsize, file) != readsize) { + LOG_ERROR("writting file failed, quitting..."); + mtar_close(&tarFile); + std::fclose(file); + std::filesystem::remove(extractedFile.c_str()); + return false; + } + } + + LOG_INFO("Extracting file succeeded"); + std::fclose(file); + } + else { + LOG_DEBUG("Found header %d, skipping", tarHeader.type); + } + + ret = mtar_next(&tarFile); + LOG_DEBUG("Reading tar next status %s", mtar_strerror(ret)); + } while (ret == MTAR_ESUCCESS); + + LOG_DEBUG("Cleanup"); + mtar_close(&tarFile); + 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(); +} + +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..."); + return false; + } + + const std::filesystem::path userDir = purefs::dir::getUserDiskPath(); + const std::filesystem::path backupDir = purefs::dir::getBackupOSPath(); + std::error_code errorCode; + + for (auto &direntry : std::filesystem::directory_iterator(tempDir, errorCode)) { + if (errorCode) { + LOG_INFO("Can't list contents of temp dir"); + return false; + } + + if (!isValidDirentry(direntry)) { + continue; + } + + // dont restore the information file + if (direntry.path().filename() == bkp::backupInfo) { + continue; + } + + LOG_INFO("Restoring backup file ..."); + + 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, error: %d", errorCode.value()); + // we should continue, there can be new files in the backup + } + } + + return true; +} + +json11::Json BackupRestore::GetBackupFiles() +{ + auto dirEntryVector = std::vector(); + 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()) { + LOG_DEBUG("Possible restore file"); + dirEntryVector.push_back(p.path().filename()); + } + } + + return dirEntryVector; +} diff --git a/module-services/service-desktop/CMakeLists.txt b/module-services/service-desktop/CMakeLists.txt index 0001c211c11576862f68b54a225799842af12133..2cde45dd91f03b9c2d6b0b2983bfbee5d32435a3 100644 --- a/module-services/service-desktop/CMakeLists.txt +++ b/module-services/service-desktop/CMakeLists.txt @@ -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 $<$:usb_stack::usb_stack> PUBLIC module-cellular diff --git a/module-services/service-desktop/ServiceDesktop.cpp b/module-services/service-desktop/ServiceDesktop.cpp index 02bd1bb1d16098af8fd00c5c9646aae5fb4c50a4..70bb927be120e16d1525aec10375093f21e61b9f 100644 --- a/module-services/service-desktop/ServiceDesktop.cpp +++ b/module-services/service-desktop/ServiceDesktop.cpp @@ -6,9 +6,9 @@ #include #include #include +#include #include #include -#include #include #include @@ -115,13 +115,19 @@ sys::ReturnCodes ServiceDesktop::InitHandler() connect(sdesktop::BackupMessage(), [&](sys::Message *msg) { sdesktop::BackupMessage *backupMessage = dynamic_cast(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(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(); } +std::string ServiceDesktop::prepareBackupFilename() +{ + std::array 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(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; } diff --git a/module-services/service-desktop/endpoints/CMakeLists.txt b/module-services/service-desktop/endpoints/CMakeLists.txt index 51b7de4c04f06e1beb4fc6773e98bf065e94e83f..0137a68154a30ceafd1bae8e0b26aed1d4cd2393 100644 --- a/module-services/service-desktop/endpoints/CMakeLists.txt +++ b/module-services/service-desktop/endpoints/CMakeLists.txt @@ -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) diff --git a/module-services/service-desktop/endpoints/backup/BackupEndpoint.cpp b/module-services/service-desktop/endpoints/backup/BackupEndpoint.cpp index 94de4ead963b1bbb81fd05929d9500862ca9dd8d..83282f2fd06e46eb4c0f10e3595c5e47accb9268 100644 --- a/module-services/service-desktop/endpoints/backup/BackupEndpoint.cpp +++ b/module-services/service-desktop/endpoints/backup/BackupEndpoint.cpp @@ -2,79 +2,31 @@ // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include - -#include #include -#include -#include - -#include -#include - -#include -#include 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(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(), - 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 diff --git a/module-services/service-desktop/endpoints/backup/BackupHelper.cpp b/module-services/service-desktop/endpoints/backup/BackupHelper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3a5ccda12755805618e6af7a878e15c6e941cc02 --- /dev/null +++ b/module-services/service-desktop/endpoints/backup/BackupHelper.cpp @@ -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 +#include +#include +#include +#include +#include + +#include +#include + +#include + +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(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(), + 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(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 diff --git a/module-services/service-desktop/endpoints/backup/BackupRestore.cpp b/module-services/service-desktop/endpoints/backup/BackupRestore.cpp deleted file mode 100644 index 190ce004936a1ec6b3001e23c9f5c5779d4a5910..0000000000000000000000000000000000000000 --- a/module-services/service-desktop/endpoints/backup/BackupRestore.cpp +++ /dev/null @@ -1,506 +0,0 @@ -// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. -// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace sys -{ - class Service; -} // 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 -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 buffer = std::make_unique(purefs::buffer::tar_buf); - size_t bytes; - - constexpr size_t streamBufferSize = 64 * 1024; - auto streamBufferFrom = std::make_unique(streamBufferSize); - auto streamBufferTo = std::make_unique(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) -{ - assert(ownerService != nullptr); - LOG_INFO("BackupUserFiles: backup started..."); - - if (BackupRestore::RemoveBackupDir(path) == false) { - return false; - } - - if (BackupRestore::CreateBackupDir(path) == false) { - return false; - } - - LOG_INFO("BackupUserFiles: database backup started..."); - - if (DBServiceAPI::DBBackup(ownerService, path) == false) { - LOG_ERROR("BackupUserFiles: database backup failed, quitting..."); - BackupRestore::RemoveBackupDir(path); - return false; - } - - if (WriteBackupInfo(ownerService, path) == false) { - LOG_ERROR("Failed to write backup info"); - BackupRestore::RemoveBackupDir(path); - return false; - } - - LOG_INFO("BackupUserFiles: packing files"); - return BackupRestore::PackUserFiles(path); -} - -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()); - - 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()); - - return false; - } - } - else { - LOG_ERROR("%s is not a directory", path.c_str()); - return false; - } - - return true; -} - -bool BackupRestore::RestoreUserFiles(sys::Service *ownerService, const std::filesystem::path &path) -{ - assert(ownerService != nullptr); - LOG_INFO("RestoreUserFiles: restore started"); - - if (BackupRestore::UnpackBackupFile(path) == false) { - return false; - } - - if (sys::SystemManagerCommon::Restore(ownerService) == false) { - LOG_ERROR("Can't enter update system state"); - return false; - } - else { - LOG_INFO("entered update state"); - - if (BackupRestore::ReplaceUserFiles(path) == false) { - LOG_ERROR("can't restore user files"); - return false; - } - } - - // BackupRestore::RemoveBackupDir(path); - - return true; -} - -bool BackupRestore::RemoveBackupDir(std::filesystem::path &path) -{ - /* prepare directories */ - if (std::filesystem::is_directory(path)) { - LOG_INFO("RemoveBackupDir: removing backup directory %s...", path.c_str()); - - 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()); - return false; - } - } - - return true; -} - -bool BackupRestore::CreateBackupDir(std::filesystem::path &path) -{ - LOG_INFO("CreateBackupDir: 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."); - return false; - } - } - - return true; -} - -bool BackupRestore::PackUserFiles(std::filesystem::path &path) -{ - if (std::filesystem::is_empty(path)) { - 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); - mtar_t tarFile; - - LOG_INFO("opening tar file..."); - - int ret = mtar_open(&tarFile, tarFilePath.c_str(), "w"); - if (ret != MTAR_ESUCCESS) { - LOG_ERROR("opening tar file failed, quitting..."); - BackupRestore::RemoveBackupDir(path); - return false; - } - - auto buffer = std::make_unique(purefs::buffer::tar_buf); - constexpr size_t streamBufferSize = 64 * 1024; - auto streamBuffer = std::make_unique(streamBufferSize); - setvbuf(tarFile.stream, streamBuffer.get(), _IOFBF, streamBufferSize); - std::error_code e; - - for (auto &direntry : std::filesystem::directory_iterator(path)) { - if (!isValidDirentry(direntry)) { - continue; - } - - 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..."); - mtar_close(&tarFile); - BackupRestore::RemoveBackupDir(path); - return false; - } - - LOG_DEBUG("writting tar header ..."); - - if (mtar_write_file_header(&tarFile, - direntry.path().filename().c_str(), - static_cast(std::filesystem::file_size(direntry))) != MTAR_ESUCCESS) { - 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"); - BackupRestore::RemoveBackupDir(path); - return false; - } - uint32_t loopcount = (filesize / purefs::buffer::tar_buf) + 1u; - uint32_t readsize; - - 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("reading file ..."); - - if (std::fread(buffer.get(), 1, readsize, file) != readsize) { - LOG_ERROR("reading file failed, quitting..."); - std::fclose(file); - mtar_close(&tarFile); - BackupRestore::RemoveBackupDir(path); - return false; - } - - LOG_DEBUG("writting into backup..."); - if (mtar_write_data(&tarFile, buffer.get(), readsize) != MTAR_ESUCCESS) { - LOG_ERROR("PackUserFiles: writting into backup failed, quitting..."); - std::fclose(file); - mtar_close(&tarFile); - BackupRestore::RemoveBackupDir(path); - return false; - } - } - - LOG_INFO("closing file..."); - if (std::fclose(file) != 0) { - LOG_ERROR("PackUserFiles: closing file failed, quitting..."); - mtar_close(&tarFile); - BackupRestore::RemoveBackupDir(path); - return false; - } - - LOG_INFO("deleting file ..."); - - if (std::remove(direntry.path().c_str()) != 0) { - LOG_ERROR("PackUserFiles: deleting file failed, quitting..."); - mtar_close(&tarFile); - BackupRestore::RemoveBackupDir(path); - return false; - } - } - - LOG_INFO("finalizing tar file..."); - if (mtar_finalize(&tarFile) != MTAR_ESUCCESS) { - LOG_ERROR("PackUserFiles: finalizing tar file failed, quitting...."); - mtar_close(&tarFile); - BackupRestore::RemoveBackupDir(path); - return false; - } - - LOG_INFO("closing tar file..."); - if (mtar_close(&tarFile) != MTAR_ESUCCESS) { - LOG_ERROR("PackUserFiles: closing tar file failed, quitting..."); - BackupRestore::RemoveBackupDir(path); - return false; - } - - return true; -} - -bool BackupRestore::UnpackBackupFile(const std::filesystem::path &tarFilePath) -{ - mtar_t tarFile; - mtar_header_t tarHeader; - std::error_code e; - - auto extractDestination = purefs::dir::getTemporaryPath() / tarFilePath.stem(); - - 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"); - return false; - } - } - - 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..."); - return false; - } - - auto buffer = std::make_unique(purefs::buffer::tar_buf); - constexpr size_t streamBufferSize = 64 * 1024; - auto streamBuffer = std::make_unique(streamBufferSize); - setvbuf(tarFile.stream, streamBuffer.get(), _IOFBF, streamBufferSize); - - do { - ret = mtar_read_header(&tarFile, &tarHeader); - LOG_DEBUG("reading tar header name..."); - - if ((tarHeader.type == MTAR_TREG) && (ret == MTAR_ESUCCESS)) { - 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"); - mtar_close(&tarFile); - return false; - } - - constexpr size_t streamBufferSize = 16 * 1024; - auto streamBuffer = std::make_unique(streamBufferSize); - setvbuf(file, streamBuffer.get(), _IOFBF, streamBufferSize); - - uint32_t loopcount = (tarHeader.size / purefs::buffer::tar_buf) + 1u; - uint32_t readsize = 0u; - - for (uint32_t i = 0u; i < loopcount; i++) { - - if (i + 1u == loopcount) { - readsize = tarHeader.size % purefs::buffer::tar_buf; - } - else { - readsize = purefs::buffer::tar_buf; - } - - if (mtar_read_data(&tarFile, buffer.get(), readsize) != MTAR_ESUCCESS) { - LOG_ERROR("extracting file failed, quitting..."); - mtar_close(&tarFile); - std::fclose(file); - std::remove(extractedFile.c_str()); - return false; - } - - if (std::fwrite(buffer.get(), readsize, 1, file) != readsize) { - LOG_ERROR("writting file failed, quitting..."); - mtar_close(&tarFile); - std::fclose(file); - std::remove(extractedFile.c_str()); - return false; - } - } - - LOG_INFO("extracting file succeeded"); - std::fclose(file); - } - else { - LOG_DEBUG("found header %d, skipping", tarHeader.type); - } - - ret = mtar_next(&tarFile); - LOG_DEBUG("reading tar next status %s", mtar_strerror(ret)); - } while (ret == MTAR_ESUCCESS); - - LOG_DEBUG("cleanup"); - mtar_close(&tarFile); - std::remove(tarFilePath.c_str()); - - if (e) { - LOG_WARN("can't cleanup temporary dir"); - } - - return true; -} - -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..."); - return false; - } - - 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 temp dir"); - return false; - } - - if (!isValidDirentry(direntry)) { - continue; - } - - // dont restore the information file - if (direntry.path().filename() == bkp::backupInfo) { - continue; - } - - 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"); - return false; - } - } - else { - LOG_WARN("can't remove file"); - // we should continue, there can be new files in the backup - } - } - - return true; -} - -json11::Json BackupRestore::GetBackupFiles() -{ - auto dirEntryVector = std::vector(); - 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()); - return json11::Json(); - } - if (!p.is_directory() && p.path().extension() == purefs::extension::tar) { - LOG_DEBUG("possible restore file"); - dirEntryVector.push_back(p.path().filename()); - } - } - - return dirEntryVector; -} diff --git a/module-services/service-desktop/endpoints/include/endpoints/JsonKeyNames.hpp b/module-services/service-desktop/endpoints/include/endpoints/JsonKeyNames.hpp index 76501622c76c2360074105278f8440605ddac381..fa3ebb45be0caf0d80dac7a0aa40b3492d0ca459 100644 --- a/module-services/service-desktop/endpoints/include/endpoints/JsonKeyNames.hpp +++ b/module-services/service-desktop/endpoints/include/endpoints/JsonKeyNames.hpp @@ -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 { diff --git a/module-services/service-desktop/endpoints/include/endpoints/backup/BackupEndpoint.hpp b/module-services/service-desktop/endpoints/include/endpoints/backup/BackupEndpoint.hpp index 0b69f17c6abd3eee6c322b50e816672b9aba3087..0f8a2be42985cac7b6593fee3fc782af46ff22f4 100644 --- a/module-services/service-desktop/endpoints/include/endpoints/backup/BackupEndpoint.hpp +++ b/module-services/service-desktop/endpoints/include/endpoints/backup/BackupEndpoint.hpp @@ -4,27 +4,23 @@ #pragma once #include - -#include - -#include - -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(ownerServicePtr)) { debugName = "BackupEndpoint"; } + auto handle(Context &context) -> void override; - auto request(Context &context) -> sys::ReturnCodes; + + private: + const std::unique_ptr helper; }; } // namespace sdesktop::endpoints diff --git a/module-services/service-desktop/endpoints/include/endpoints/backup/BackupHelper.hpp b/module-services/service-desktop/endpoints/include/endpoints/backup/BackupHelper.hpp new file mode 100644 index 0000000000000000000000000000000000000000..a711d9ebf1c0092b12e21167bdb6c1db006d94b8 --- /dev/null +++ b/module-services/service-desktop/endpoints/include/endpoints/backup/BackupHelper.hpp @@ -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 + +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 diff --git a/module-services/service-desktop/endpoints/include/endpoints/backup/BackupRestore.hpp b/module-services/service-desktop/endpoints/include/endpoints/backup/BackupRestore.hpp deleted file mode 100644 index 7ccf00b0583366b296e7b5a07c82653284f7a006..0000000000000000000000000000000000000000 --- a/module-services/service-desktop/endpoints/include/endpoints/backup/BackupRestore.hpp +++ /dev/null @@ -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 -#include -#include - -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); -}; diff --git a/module-services/service-desktop/endpoints/include/endpoints/restore/RestoreEndpoint.hpp b/module-services/service-desktop/endpoints/include/endpoints/restore/RestoreEndpoint.hpp index f27d9409736cc60a1259ec229261f8ba9c07f0cc..184df631e7bf39b2eb2c1c9f6e2e94d13d4895a6 100644 --- a/module-services/service-desktop/endpoints/include/endpoints/restore/RestoreEndpoint.hpp +++ b/module-services/service-desktop/endpoints/include/endpoints/restore/RestoreEndpoint.hpp @@ -3,6 +3,7 @@ #pragma once +#include "RestoreHelper.hpp" #include 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(ownerServicePtr)) { debugName = "RestoreEndpoint"; } auto handle(Context &context) -> void override; - auto request(Context &context) -> sys::ReturnCodes; + + private: + const std::unique_ptr helper; }; } // namespace sdesktop::endpoints diff --git a/module-services/service-desktop/endpoints/include/endpoints/restore/RestoreHelper.hpp b/module-services/service-desktop/endpoints/include/endpoints/restore/RestoreHelper.hpp new file mode 100644 index 0000000000000000000000000000000000000000..539bcdc7830bd3f50aa42d5ffb4ee44cc7076866 --- /dev/null +++ b/module-services/service-desktop/endpoints/include/endpoints/restore/RestoreHelper.hpp @@ -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 + +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 diff --git a/module-services/service-desktop/endpoints/restore/RestoreEndpoint.cpp b/module-services/service-desktop/endpoints/restore/RestoreEndpoint.cpp index a9ef2bf7201d5f81ee13264485b2dc0b1adb7eee..e4db0fb18779e80c98080365b02cfea0c23c968b 100644 --- a/module-services/service-desktop/endpoints/restore/RestoreEndpoint.cpp +++ b/module-services/service-desktop/endpoints/restore/RestoreEndpoint.cpp @@ -2,97 +2,31 @@ // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include - -#include #include -#include -#include -#include -#include -#include 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(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(), - 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 diff --git a/module-services/service-desktop/endpoints/restore/RestoreHelper.cpp b/module-services/service-desktop/endpoints/restore/RestoreHelper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d61aca313f0a757f5ff7ded8eb0aac1893df6e36 --- /dev/null +++ b/module-services/service-desktop/endpoints/restore/RestoreHelper.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#include + +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(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(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(), + 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 diff --git a/module-services/service-desktop/include/service-desktop/BackupRestore.hpp b/module-services/service-desktop/include/service-desktop/BackupRestore.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6ea7dca448643e99c971652528b825ef4d4c03a0 --- /dev/null +++ b/module-services/service-desktop/include/service-desktop/BackupRestore.hpp @@ -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 +#include +#include +#include + +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); +}; diff --git a/module-services/service-desktop/include/service-desktop/ServiceDesktop.hpp b/module-services/service-desktop/include/service-desktop/ServiceDesktop.hpp index 2985bba19556e6e6e1d800b1fa039ccce84d61ca..aedef8c3afbecc3502ce36177eb4145ec510f294 100644 --- a/module-services/service-desktop/include/service-desktop/ServiceDesktop.hpp +++ b/module-services/service-desktop/include/service-desktop/ServiceDesktop.hpp @@ -11,9 +11,9 @@ #include "Timers/TimerHandle.hpp" #include "Constants.hpp" #include "USBSecurityModel.hpp" - +#include #include -#include + #include #include @@ -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 desktopWorker; + std::string prepareBackupFilename(); void prepareBackupData(); void prepareRestoreData(const std::filesystem::path &restoreLocation); - const BackupRestoreStatus getBackupRestoreStatus() + const BackupRestore::OperationStatus getBackupRestoreStatus() { return backupRestoreStatus; } diff --git a/module-vfs/paths/include/purefs/filesystem_paths.hpp b/module-vfs/paths/include/purefs/filesystem_paths.hpp index 5a4c91db63641e4ca07ce561a90d29c50260180c..c0c350d72241f072a68ae34fbe2d71542e37bb34 100644 --- a/module-vfs/paths/include/purefs/filesystem_paths.hpp +++ b/module-vfs/paths/include/purefs/filesystem_paths.hpp @@ -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 diff --git a/products/PurePhone/services/db/ServiceDB.cpp b/products/PurePhone/services/db/ServiceDB.cpp index 748ca82f2d8590a02e341f9e9289aad647d4d0ab..c7a75aac2033ce2af10dcd2d203afcc940544f32 100644 --- a/products/PurePhone/services/db/ServiceDB.cpp +++ b/products/PurePhone/services/db/ServiceDB.cpp @@ -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") { diff --git a/products/PurePhone/services/desktop/endpoints/deviceInfo/DeviceInfoEndpoint.cpp b/products/PurePhone/services/desktop/endpoints/deviceInfo/DeviceInfoEndpoint.cpp index 773b51c7562a73642cb6a188584b8cad879d601a..72c3660a77b25856e50f07d99ff2e695b07e66f5 100644 --- a/products/PurePhone/services/desktop/endpoints/deviceInfo/DeviceInfoEndpoint.cpp +++ b/products/PurePhone/services/desktop/endpoints/deviceInfo/DeviceInfoEndpoint.cpp @@ -160,7 +160,8 @@ namespace sdesktop::endpoints {json::currentRTCTime, std::to_string(static_cast(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; diff --git a/test/get_os_log.py b/test/get_os_log.py index 1d26c13b2eaa821676e078f43ef0dadf26cfa29e..15f3dfc2a28055205df2ed7654198c4527c822f7 100755 --- a/test/get_os_log.py +++ b/test/get_os_log.py @@ -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]} \' ') 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() diff --git a/test/harness b/test/harness index 8a33fa2634c6e24b9099e23404ab5104678ac426..8aa7fe8ad3276583688f6ae6a7370f7e686e1810 160000 --- a/test/harness +++ b/test/harness @@ -1 +1 @@ -Subproject commit 8a33fa2634c6e24b9099e23404ab5104678ac426 +Subproject commit 8aa7fe8ad3276583688f6ae6a7370f7e686e1810 diff --git a/test/pytest/service-desktop/test_backup.py b/test/pytest/service-desktop/test_backup.py index 09ae0aab1db516dc713bdd4bdb5438c1ee701660..b40132bda48230e9c38fbc8889a54fa61dde2946 100644 --- a/test/pytest/service-desktop/test_backup.py +++ b/test/pytest/service-desktop/test_backup.py @@ -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) diff --git a/test/pytest/service-desktop/test_device_info.py b/test/pytest/service-desktop/test_device_info.py index 1754bddf2bc8d6329258dafbc66bdd1b7aae3917..44f998ecffe15fbd53d169229e722ac7c6e1fed5 100644 --- a/test/pytest/service-desktop/test_device_info.py +++ b/test/pytest/service-desktop/test_device_info.py @@ -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 diff --git a/test/pytest/service-desktop/test_restore.py b/test/pytest/service-desktop/test_restore.py index 9bb9469a47908c72b7125c8895809075efa54bfc..f1c75d85693b1d344cde624c91f7ca8997519d7d 100644 --- a/test/pytest/service-desktop/test_restore.py +++ b/test/pytest/service-desktop/test_restore.py @@ -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") diff --git a/third-party/microtar/CMakeLists.txt b/third-party/microtar/CMakeLists.txt index cf5756be7757613cb24500535d0fd24e9d7bd4d9..e1f1b4d5a3f6802a2c29b21d6f80b9e49e8158d0 100644 --- a/third-party/microtar/CMakeLists.txt +++ b/third-party/microtar/CMakeLists.txt @@ -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 ) diff --git a/tools/generate_image.sh b/tools/generate_image.sh index a07bd70094b9f11ebab1d2abb90d88bfb10f7abf..b264eca5e6095f7dc5e55778b257cfd7693009df 100755 --- a/tools/generate_image.sh +++ b/tools/generate_image.sh @@ -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