// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include "BackupRestore.hpp" #include "vfs.hpp" #include "microtar/src/microtar.hpp" #include "api/DBServiceAPI.hpp" #include "service-db/includes/DBServiceName.hpp" #include "SystemManager/SystemManager.hpp" static const long unsigned int empty_dirlist_size = 2; static const auto backup_file_name = "backup.tar"; void BackupRestore::BackupUserFiles(sys::Service *ownerService) { assert(ownerService != nullptr); LOG_INFO("BackupUserFiles: backup started..."); if (BackupRestore::RemoveBackupDir() == false) { return; } if (BackupRestore::CreateBackupDir() == false) { return; } std::string backupPathDB = PATH_BACKUP; backupPathDB += "/"; LOG_INFO("BackupUserFiles: database backup started..."); if (DBServiceAPI::DBBackup(ownerService, backupPathDB) == false) { LOG_ERROR("BackupUserFiles: database backup failed, quitting..."); BackupRestore::RemoveBackupDir(); return; } BackupRestore::PackUserFiles(); LOG_INFO("BackupUserFiles: backup finished"); } void BackupRestore::RestoreUserFiles(sys::Service *ownerService) { assert(ownerService != nullptr); LOG_INFO("RestoreUserFiles: restore started..."); if (BackupRestore::UnpackBackupFile() == false) { return; } /* close user files to be restored */ LOG_INFO("RestoreUserFiles: closing ServiceDB..."); std::string dbServiceName = service::name::db; sys::SystemManager::DestroyService(dbServiceName, ownerService); BackupRestore::ReplaceUserFiles(); BackupRestore::RemoveBackupDir(); LOG_INFO("RestoreUserFiles: restoring finished, rebooting..."); sys::SystemManager::Reboot(ownerService); } bool BackupRestore::RemoveBackupDir() { /* prepare directories */ if (vfs.isDir(purefs::dir::os_backup.c_str())) { LOG_INFO("RemoveBackupDir: removing backup directory %s...", purefs::dir::os_backup.c_str()); if (vfs.deltree(purefs::dir::os_backup.c_str()) != 0) { LOG_ERROR("RemoveBackupDir: removing backup directory %s failed, error: %s.", purefs::dir::os_backup.c_str(), vfs.lastErrnoToStr().c_str()); return false; } } return true; } bool BackupRestore::CreateBackupDir() { LOG_INFO("CreateBackupDir: creating backup directory %s...", purefs::dir::os_backup.c_str()); if (vfs.isDir(purefs::dir::os_backup.c_str()) == false) { if (vfs.mkdir(purefs::dir::os_backup.c_str()) != 0) { LOG_ERROR("CreateBackupDir: creating backup directory %s failed, error: %s.", purefs::dir::os_backup.c_str(), vfs.lastErrnoToStr().c_str()); return false; } } return true; } bool BackupRestore::PackUserFiles() { std::string backupPathDB = PATH_BACKUP; backupPathDB += "/"; std::vector dirlist = vfs.listdir(purefs::dir::os_backup.c_str(), "", true); if (dirlist.size() <= empty_dirlist_size) { /* vfs.listdir also lists two extra directories ".." and "..." by default, they shall be ignored */ LOG_ERROR("PackUserFiles: backup dir %s is empty, nothing to backup, quitting...", purefs::dir::os_backup.c_str()); BackupRestore::RemoveBackupDir(); return false; } LOG_INFO("PackUserFiles: backup dir %s listed with %lu files.", purefs::dir::os_backup.c_str(), dirlist.size() - empty_dirlist_size); std::string tarFilePath = purefs::dir::os_backup; tarFilePath += "/"; tarFilePath += backup_file_name; mtar_t tarFile; LOG_INFO("PackUserFiles: 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)); BackupRestore::RemoveBackupDir(); return false; } std::unique_ptr buffer(new unsigned char[purefs::buffer::tar_buf]); for (auto &direntry : dirlist) { if ((direntry.fileName.compare(".") != 0) && (direntry.fileName.compare("..") != 0) && (direntry.fileName.compare("...") != 0)) { LOG_INFO("PackUserFiles: archiving file %s...", (backupPathDB + direntry.fileName).c_str()); vfs::FILE *file = vfs.fopen((backupPathDB + direntry.fileName).c_str(), "r"); if (file == nullptr) { LOG_ERROR("PackUserFiles: archiving file %s failed, cannot open file, quitting...", direntry.fileName.c_str()); mtar_close(&tarFile); BackupRestore::RemoveBackupDir(); return false; } LOG_INFO("PackUserFiles: writting tar header for %s...", direntry.fileName.c_str()); if (mtar_write_file_header(&tarFile, direntry.fileName.c_str(), static_cast(direntry.fileSize)) != MTAR_ESUCCESS) { LOG_ERROR("PackUserFiles: writing tar header for %s failed", direntry.fileName.c_str()); vfs.fclose(file); mtar_close(&tarFile); BackupRestore::RemoveBackupDir(); return false; } uint32_t loopcount = (vfs.filelength(file) / purefs::buffer::tar_buf) + 1u; uint32_t readsize = 0u; for (uint32_t i = 0u; i < loopcount; i++) { if (i + 1u == loopcount) { readsize = vfs.filelength(file) % purefs::buffer::tar_buf; } else { readsize = purefs::buffer::tar_buf; } LOG_INFO("PackUserFiles: reading file %s...", direntry.fileName.c_str()); if (vfs.fread(buffer.get(), 1, readsize, file) != readsize) { LOG_ERROR("PackUserFiles: reading file %s failed, quitting...", direntry.fileName.c_str()); vfs.fclose(file); mtar_close(&tarFile); BackupRestore::RemoveBackupDir(); return false; } LOG_INFO("PackUserFiles: writting %s into backup...", direntry.fileName.c_str()); if (mtar_write_data(&tarFile, buffer.get(), readsize) != MTAR_ESUCCESS) { LOG_ERROR("PackUserFiles: writting %s into backup failed, quitting...", direntry.fileName.c_str()); vfs.fclose(file); mtar_close(&tarFile); BackupRestore::RemoveBackupDir(); return false; } } LOG_INFO("PackUserFiles: closing file %s...", (backupPathDB + direntry.fileName).c_str()); if (vfs.fclose(file) != 0) { LOG_ERROR("PackUserFiles: closing file %s failed, quitting...", (backupPathDB + direntry.fileName).c_str()); mtar_close(&tarFile); BackupRestore::RemoveBackupDir(); return false; } LOG_INFO("PackUserFiles: deleting file %s...", (backupPathDB + direntry.fileName).c_str()); if (vfs.remove((backupPathDB + direntry.fileName).c_str()) != 0) { LOG_ERROR("PackUserFiles: deleting file %s failed, quitting...", (backupPathDB + direntry.fileName).c_str()); mtar_close(&tarFile); BackupRestore::RemoveBackupDir(); return false; } } } LOG_INFO("PackUserFiles: 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); BackupRestore::RemoveBackupDir(); return false; } LOG_INFO("PackUserFiles: 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(); return false; } return true; } bool BackupRestore::UnpackBackupFile() { std::string tarFilePath = purefs::dir::os_backup; tarFilePath += "/"; tarFilePath += backup_file_name; mtar_t tarFile; mtar_header_t tarHeader; LOG_INFO("UnpackBackupFile: 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)); return false; } std::unique_ptr buffer(new unsigned char[purefs::buffer::tar_buf]); do { ret = mtar_read_header(&tarFile, &tarHeader); LOG_INFO("UnpackBackupFile: reading tar header name %s...", tarHeader.name); if ((tarHeader.type == MTAR_TREG) && (ret == MTAR_ESUCCESS)) { LOG_INFO("UnpackBackupFile: extracting file %s...", tarHeader.name); std::string restoreFilePath = purefs::dir::os_backup; restoreFilePath += "/"; restoreFilePath += tarHeader.name; vfs::FILE *file = vfs.fopen(restoreFilePath.c_str(), "w"); if (file == nullptr) { LOG_ERROR("UnpackBackupFile: extracting file %s failed, quitting...", tarHeader.name); mtar_close(&tarFile); return false; } 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("UnpackBackupFile: extracting file %s failed, quitting...", tarHeader.name); mtar_close(&tarFile); vfs.fclose(file); vfs.remove(restoreFilePath.c_str()); return false; } if (vfs.fwrite(buffer.get(), 1, readsize, file) != readsize) { LOG_ERROR("UnpackBackupFile: writting file %s failed, quitting...", restoreFilePath.c_str()); mtar_close(&tarFile); vfs.fclose(file); vfs.remove(restoreFilePath.c_str()); return false; } } LOG_INFO("UnpackBackupFile: extracting file %s succeeded", tarHeader.name); vfs.fclose(file); } else { LOG_INFO("UnpackBackupFile: found header %d, skipping", tarHeader.type); } ret = mtar_next(&tarFile); LOG_INFO("UnpackBackupFile: reading tar next status %s", mtar_strerror(ret)); } while (ret == MTAR_ESUCCESS); LOG_INFO("UnpackBackupFile: cleaning directory from tar file..."); mtar_close(&tarFile); vfs.remove(tarFilePath.c_str()); return true; } bool BackupRestore::ReplaceUserFiles() { /* replace existing files that have respective backup files existing */ std::vector dirlist = vfs.listdir(purefs::dir::os_backup.c_str()); if (dirlist.size() <= empty_dirlist_size) { LOG_INFO("ReplaceUserFiles: dir emtpy, nothing to restore, quitting..."); return false; } LOG_INFO("ReplaceUserFiles: dir listed with %lu files", dirlist.size() - empty_dirlist_size); std::string userFilePath = purefs::dir::user_disk; userFilePath += "/"; std::string backupFilePath = purefs::dir::os_backup; backupFilePath += "/"; for (auto &direntry : dirlist) { if ((direntry.fileName.compare(".") != 0) && (direntry.fileName.compare("..") != 0) && (direntry.fileName.compare("...") != 0)) { LOG_INFO("ReplaceUserFiles: restoring backup file %s...", direntry.fileName.c_str()); if (vfs.fileExists((userFilePath + direntry.fileName).c_str())) { if (vfs.remove((userFilePath + direntry.fileName).c_str()) == 0) { if (vfs.rename((backupFilePath + direntry.fileName).c_str(), (userFilePath + direntry.fileName).c_str()) == 0) { LOG_INFO("ReplaceUserFiles: restoring backup file %s succeeded", direntry.fileName.c_str()); } else { LOG_ERROR("ReplaceUserFiles: restoring backup file %s failed on error %s", direntry.fileName.c_str(), vfs.lastErrnoToStr().c_str()); } } else { LOG_ERROR("ReplaceUserFiles: restoring backup file %s failed on error %s", direntry.fileName.c_str(), vfs.lastErrnoToStr().c_str()); } } else { if (vfs.rename((backupFilePath + direntry.fileName).c_str(), (userFilePath + direntry.fileName).c_str()) == 0) { LOG_INFO("ReplaceUserFiles: restoring backup file %s succeeded", direntry.fileName.c_str()); } else { LOG_ERROR("ReplaceUserFiles: restoring backup file %s failed on error %s", direntry.fileName.c_str(), vfs.lastErrnoToStr().c_str()); } } } } return true; }