// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include "UpdatePureOS.hpp" #if defined(TARGET_RT1051) #include "board/cross/eMMC/eMMC.hpp" #endif #include #include #include #include FileInfo::FileInfo(mtar_header_t &h, unsigned long crc32) : fileSize(h.size), fileCRC32(crc32) { if (h.name[0] == '.' && h.name[1] == '/') { // microtar relative paths, strip the leading ./away fileName = std::string(h.name).substr(2); } else { fileName = std::string(h.name); } } json11::Json FileInfo::to_json() const { return json11::Json::object{ {"name", fileName}, {"size", std::to_string(fileSize)}, {"crc32", std::to_string(fileCRC32)}}; } UpdatePureOS::UpdatePureOS(ServiceDesktop *ownerService) : owner(ownerService) { status = updateos::UpdateState::Initial; } updateos::UpdateError UpdatePureOS::setUpdateFile(fs::path updateFileToUse) { updateFile = purefs::dir::os_updates / updateFileToUse; if (vfs.fileExists(updateFile.c_str())) { versioInformation = UpdatePureOS::getVersionInfoFromFile(updateFile); if (mtar_open(&updateTar, updateFile.c_str(), "r") == MTAR_ESUCCESS) { totalBytes = vfs.filelength(updateTar.stream); } else { informError("UpdatePureOS::setUpdateFile can't open TAR file %s", updateFile.c_str()); return updateos::UpdateError::CantOpenUpdateFile; } } else { informError("UpdatePureOS::setUpdateFile %s does not exist", updateFile.c_str()); return updateos::UpdateError::CantOpenUpdateFile; } status = updateos::UpdateState::UpdateFileSet; return updateos::UpdateError::NoError; } updateos::UpdateError UpdatePureOS::runUpdate() { informDebug("Prepraring temp dir"); updateos::UpdateError err = prepareTempDirForUpdate(); if (err != updateos::UpdateError::NoError) { informError("runUpdate can't prepare temp directory for update"); return err; } informDebug("Unpacking update"); if ((err = unpackUpdate()) == updateos::UpdateError::NoError) { informUpdate("Unpacked"); } else { informError("%s can't be unpacked", updateFile.c_str()); return err; } if ((err = verifyChecksums()) == updateos::UpdateError::NoError) { informUpdate("Verify checksums"); } else { informError("Checksum verification failed"); return err; } if ((err = verifyVersion()) == updateos::UpdateError::NoError) { informUpdate("Verify version"); } else { informError("Can't verify version"); return err; } if ((err = updateBootloader()) == updateos::UpdateError::NoError) { informUpdate("Update bootloader"); } else { informError("Failed to update the bootloader"); return err; } if ((err = prepareRoot()) == updateos::UpdateError::NoError) { informUpdate("Ready for reset"); } else { informError("Can't prepare root dir for reset"); } if ((err = cleanupAfterUpdate()) != updateos::UpdateError::NoError) { informError("runUpdate cleanupAfterUpdate failed, resetting anyway"); } // reboot always sys::SystemManager::Reboot(owner); return err; } updateos::UpdateError UpdatePureOS::unpackUpdate() { mtar_header_t tarHeader; filesInUpdatePackage.clear(); status = updateos::UpdateState::ExtractingFiles; while ((mtar_read_header(&updateTar, &tarHeader)) != MTAR_ENULLRECORD) { unsigned long fileCRC32 = 0; if (tarHeader.type == MTAR_TDIR) { fs::path tmpPath = getUpdateTmpChild(tarHeader.name); if (vfs.mkdir(tmpPath.c_str()) != 0) { informError("unpackUpdate failed to create %s when extracting update tar", tmpPath.c_str()); return (updateos::UpdateError::CantCreateExtractedFile); } } else { if (unpackFileToTemp(tarHeader, &fileCRC32) == false) { informError("unpackUpdate failed to extract update file %s", tarHeader.name); return (updateos::UpdateError::CantCreateExtractedFile); } filesInUpdatePackage.emplace_back(FileInfo(tarHeader, fileCRC32)); } mtar_next(&updateTar); } return updateos::UpdateError::NoError; } updateos::UpdateError UpdatePureOS::verifyChecksums() { status = updateos::UpdateState::ChecksumVerification; std::unique_ptr lineBuff( new char[purefs::buffer::tar_buf]); // max line should be freertos max path + checksum, so this is enough fs::path checksumsFile = getUpdateTmpChild(updateos::file::checksums); vfs::FILE *fpChecksums = vfs.fopen(checksumsFile.c_str(), "r"); if (fpChecksums == nullptr) { informError("verifyChecksums can't open checksums file %s", checksumsFile.c_str()); return updateos::UpdateError::CantOpenChecksumsFile; } while (!vfs.eof(fpChecksums)) { char *line = vfs.fgets(lineBuff.get(), purefs::buffer::tar_buf, fpChecksums); std::string filePath; unsigned long fileCRC32; if (lineBuff[0] == ';' || line == nullptr) continue; getChecksumInfo(line, filePath, &fileCRC32); unsigned long computedCRC32 = getExtractedFileCRC32(filePath); if (computedCRC32 != fileCRC32) { informError("verifyChecksums %s crc32 match FAIL %lX != %lX", filePath.c_str(), fileCRC32, computedCRC32); vfs.fclose(fpChecksums); return updateos::UpdateError::VerifyChecksumsFailure; } } vfs.fclose(fpChecksums); return updateos::UpdateError::NoError; } updateos::UpdateError UpdatePureOS::verifyVersion() { status = updateos::UpdateState::VersionVerificiation; if (!vfs.fileExists(getUpdateTmpChild(updateos::file::version).c_str())) { informError("verifyVersion %s does not exist", getUpdateTmpChild(updateos::file::version).c_str()); return updateos::UpdateError::VerifyVersionFailure; } std::string versionJsonString = vfs.loadFileAsString(getUpdateTmpChild(updateos::file::version)); std::string parserError; json11::Json updateVersionInformation = json11::Json::parse(versionJsonString, parserError); if (parserError != "") { informUpdate("verifyVersion parse json error: %s", parserError.c_str()); return updateos::UpdateError::VerifyVersionFailure; } else { } return updateos::UpdateError::NoError; } updateos::UpdateError UpdatePureOS::updateBootloader() { informDebug("updateBootloader noError"); return updateos::UpdateError::NoError; } unsigned long UpdatePureOS::getExtractedFileCRC32(const std::string &filePath) { for (auto file : filesInUpdatePackage) { if (file.fileName == filePath) { return file.fileCRC32; } } return 0; } void UpdatePureOS::getChecksumInfo(const std::string &infoLine, std::string &filePath, unsigned long *fileCRC32Long) { std::size_t lastSpacePos = infoLine.find_last_of(' '); if (lastSpacePos > 0) { filePath = infoLine.substr(0, lastSpacePos); const std::string fileCRC32Str = infoLine.substr(lastSpacePos + 1, purefs::buffer::crc_char_size - 1); if (fileCRC32Long != nullptr) { *fileCRC32Long = strtoull(fileCRC32Str.c_str(), nullptr, purefs::buffer::crc_radix); informDebug("getChecksumInfo filePath: %s fileCRC32Str: %s fileCRC32Long: %lu fileCRC32Hex: %lX", filePath.c_str(), fileCRC32Str.c_str(), *fileCRC32Long, *fileCRC32Long); } } } updateos::UpdateError UpdatePureOS::prepareRoot() { informDebug("prepareRoot()"); int ret; // basic needed dirs informDebug("prepareRoot mkdir: %s", purefs::dir::os_previous.c_str()); vfs.mkdir(purefs::dir::os_previous.c_str()); informDebug("prepareRoot mkdir: %s", purefs::dir::os_current.c_str()); vfs.mkdir(purefs::dir::os_current.c_str()); informDebug("prepareRoot mkdir: %s", purefs::dir::user_disk.c_str()); vfs.mkdir(purefs::dir::user_disk.c_str()); // remove the previous OS version from partition informDebug("prepareRoot deltree: %s", purefs::dir::os_previous.c_str()); ret = vfs.deltree(purefs::dir::os_previous.c_str()); if (ret != 0) { informError("prepareRoot ff_deltree on %s caused an error %s", purefs::dir::os_previous.c_str(), vfs.lastErrnoToStr().c_str()); } if (vfs.isDir(purefs::dir::os_previous.c_str())) { informError("prepareRoot %s still exists, we can't continue", purefs::dir::os_previous.c_str()); return updateos::UpdateError::CantDeletePreviousOS; } // rename the current OS to previous on partition informDebug("prepareRoot rename: %s->%s", purefs::dir::os_current.c_str(), purefs::dir::os_previous.c_str()); ret = vfs.rename(purefs::dir::os_current.c_str(), purefs::dir::os_previous.c_str()); if (ret != 0) { informError("prepareRoot can't rename %s -> %s error %s", purefs::dir::os_current.c_str(), purefs::dir::os_previous.c_str(), vfs.lastErrnoToStr().c_str()); return updateos::UpdateError::CantRenameCurrentToPrevious; } // rename the temp directory to current (extracted update) informDebug("prepareRoot rename: %s->%s", updateTempDirectory.c_str(), purefs::dir::os_current.c_str()); ret = vfs.rename(updateTempDirectory.c_str(), purefs::dir::os_current.c_str()); if (ret != 0) { informError("prepareRoot can't rename %s -> %s error %s", updateTempDirectory.c_str(), purefs::dir::os_current.c_str(), vfs.lastErrnoToStr().c_str()); return updateos::UpdateError::CantRenameTempToCurrent; } // move the contents of /sys/current/user if it exists to /user ret = static_cast(updateUserData()); return updateBootJSON(); } updateos::UpdateError UpdatePureOS::updateBootJSON() { unsigned long bootJSONAbsoulteCRC = 0; fs::path bootJSONAbsoulte = purefs::dir::eMMC_disk / purefs::file::boot_json; informDebug("updateBootJSON %s", bootJSONAbsoulte.c_str()); vfs::FILE *fp = vfs.fopen(bootJSONAbsoulte.c_str(), "r"); if (fp != nullptr) { vfs.computeCRC32(fp, &bootJSONAbsoulteCRC); bootJSONAbsoulte += purefs::extension::crc32; vfs::FILE *fpCRC = vfs.fopen(bootJSONAbsoulte.c_str(), "w"); if (fpCRC != nullptr) { std::array crcBuf; snprintf(crcBuf.data(), crcBuf.size(), "%lX", bootJSONAbsoulteCRC); vfs.fwrite(crcBuf.data(), 1, purefs::buffer::crc_char_size, fpCRC); vfs.fclose(fpCRC); } else { return updateos::UpdateError::CantUpdateCRC32JSON; } vfs.fclose(fp); } else { return updateos::UpdateError::CantUpdateCRC32JSON; } informDebug("updateBootJSON no error"); return updateos::UpdateError::NoError; } bool UpdatePureOS::unpackFileToTemp(mtar_header_t &h, unsigned long *crc32) { std::unique_ptr readBuf(new unsigned char[purefs::buffer::tar_buf]); const fs::path fullPath = getUpdateTmpChild(h.name); uint32_t blocksToRead = (h.size / purefs::buffer::tar_buf) + 1; uint32_t sizeToRead = purefs::buffer::tar_buf; fileExtracted = h.name; fileExtractedSize = h.size; informUpdate("Unpack %s", fullPath.filename().c_str()); if (crc32 != nullptr) { *crc32 = 0; } else { return false; } int errCode = MTAR_ESUCCESS; vfs::FILE *fp = vfs.fopen(fullPath.c_str(), "w+"); if (fp == nullptr) { informError("unpackFileToTemp %s can't open for writing", fullPath.c_str()); return false; } for (uint32_t i = 0; i < blocksToRead; i++) { if (i + 1 == blocksToRead) { sizeToRead = h.size % purefs::buffer::tar_buf; } else { sizeToRead = purefs::buffer::tar_buf; } if (sizeToRead == 0) break; if ((errCode = mtar_read_data(&updateTar, readBuf.get(), sizeToRead)) != MTAR_ESUCCESS) { informError("unpackFileToTemp mtar_read_data failed, errCode=%d", errCode); return false; } const uint32_t dataWritten = vfs.fwrite(readBuf.get(), 1, sizeToRead, fp); if (dataWritten != sizeToRead) { informError( "unpackFileToTemp %s can't write to file error: %s", fullPath.c_str(), vfs.lastErrnoToStr().c_str()); vfs.fclose(fp); return false; } currentExtractedBytes += dataWritten; *crc32 = Crc32_ComputeBuf(*crc32, readBuf.get(), sizeToRead); } vfs.fclose(fp); return true; } updateos::UpdateError UpdatePureOS::cleanupAfterUpdate() { if (vfs.isDir(updateTempDirectory.c_str()) && vfs.deltree(updateTempDirectory.c_str())) { informError("ff_deltree failed on %s", updateTempDirectory.c_str()); return updateos::UpdateError::CantRemoveUniqueTmpDir; } mtar_close(&updateTar); if (vfs.remove(updateFile.c_str())) { informError("Failed to delete %s", updateFile.c_str()); return updateos::UpdateError::CantRemoveUpdateFile; } status = updateos::UpdateState::ReadyForReset; return updateos::UpdateError::NoError; } const fs::path UpdatePureOS::getUpdateTmpChild(const fs::path &childPath) { return updateTempDirectory / childPath; } updateos::UpdateError UpdatePureOS::prepareTempDirForUpdate() { status = updateos::UpdateState::CreatingDirectories; updateTempDirectory = purefs::dir::tmp / vfs::generateRandomId(updateos::prefix_len); informDebug("Temp dir for update %s", updateTempDirectory.c_str()); if (vfs.isDir(purefs::dir::os_updates.c_str()) == false) { if (vfs.mkdir(purefs::dir::os_updates.c_str()) != 0) { informError("%s can't create it %s", purefs::dir::os_updates.c_str(), vfs.lastErrnoToStr().c_str()); return updateos::UpdateError::CantCreateUpdatesDir; } else { informDebug("prepareTempDirForUpdate %s created", purefs::dir::os_updates.c_str()); } } else { informDebug("prepareTempDirForUpdate %s exists", purefs::dir::os_updates.c_str()); } if (vfs.isDir(purefs::dir::tmp.c_str()) == false) { informDebug("prepareTempDirForUpdate %s is not a directory", purefs::dir::tmp.c_str()); if (vfs.mkdir(purefs::dir::tmp.c_str()) != 0) { informError("%s can't create it %s", purefs::dir::tmp.c_str(), vfs.lastErrnoToStr().c_str()); return updateos::UpdateError::CantCreateTempDir; } else { informDebug("prepareTempDirForUpdate %s created", purefs::dir::tmp.c_str()); } } else { informDebug("prepareTempDirForUpdate %s exists", purefs::dir::tmp.c_str()); } if (vfs.isDir(updateTempDirectory.c_str())) { informDebug("prepareTempDirForUpdate %s exists already, try to remove it", updateTempDirectory.c_str()); if (vfs.deltree(updateTempDirectory.c_str()) != 0) { informError("prepareTempDirForUpdate can't remove %s", updateTempDirectory.c_str()); return updateos::UpdateError::CantRemoveUniqueTmpDir; } else { informDebug("prepareTempDirForUpdate %s removed", updateTempDirectory.c_str()); } } informDebug("prepareTempDirForUpdate trying to create %s as tempDir", updateTempDirectory.c_str()); if (vfs.mkdir(updateTempDirectory.c_str()) != 0) { informError("prepareTempDirForUpdate failed to create: %s error: %s", updateTempDirectory.c_str(), vfs.lastErrnoToStr().c_str()); return updateos::UpdateError::CantCreateUniqueTmpDir; } return updateos::UpdateError::NoError; } updateos::BootloaderUpdateError UpdatePureOS::writeBootloader(fs::path bootloaderFile) { status = updateos::UpdateState::UpdatingBootloader; #if defined(TARGET_Linux) return updateos::BootloaderUpdateError::NoError; #else if (vfs.fileExists(bootloaderFile.c_str()) == false) { informError("[Bootloader Update] File %s doesn't exist!\n", bootloaderFile.c_str()); return updateos::BootloaderUpdateError::NoBootloaderFile; } auto fileHandler = vfs.fopen(bootloaderFile.c_str(), "r"); if (fileHandler == nullptr) { informError("[Bootloader Update] Failed to open file %s\n", bootloaderFile.c_str()); return updateos::BootloaderUpdateError::CantOpenBootloaderFile; } unsigned long fileLen = vfs.filelength(fileHandler); auto fileBuf = std::make_unique(fileLen); if (fileBuf == nullptr) { informError("[Bootloader Update] Failed to allocate buffer\n"); return updateos::BootloaderUpdateError::CantAllocateBuffer; } auto filesLoaded = vfs.fread(fileBuf.get(), fileLen, 1, fileHandler); if (filesLoaded == 0) { informError("[Bootloader Update] Failed to load file %s\n", bootloaderFile.c_str()); return updateos::BootloaderUpdateError::CantOpenBootloaderFile; } informUpdate("[Bootloader Update] File size: %lu B, Writing...", fileLen); bsp::eMMC emmc; emmc.Init(); emmc.SwitchPartition(bsp::eMMC::Partition::Boot1); emmc.WriteBlocks(fileBuf.get(), 0, std::ceil(fileLen / FSL_SDMMC_DEFAULT_BLOCK_SIZE)); informUpdate("[Bootloader Update] DONE!\n"); emmc.SwitchPartition(bsp::eMMC::Partition::UserArea); return updateos::BootloaderUpdateError::NoError; #endif } const json11::Json UpdatePureOS::getVersionInfoFromFile(const fs::path &updateFile) { if (vfs.fileExists(updateFile.c_str())) { mtar_t tar; mtar_header_t h; if (mtar_open(&tar, updateFile.c_str(), "r") == MTAR_EOPENFAIL) { LOG_INFO("UpdatePureOS::getVersionInfoFromFile %s can't open", updateFile.c_str()); return json11::Json(); } std::unique_ptr versionFilename(new char[purefs::buffer::crc_buf]); sprintf(versionFilename.get(), "./%s", updateos::file::version.c_str()); if (mtar_find(&tar, versionFilename.get(), &h) == MTAR_ENOTFOUND) { LOG_INFO("UpdatePureOS::getVersionInfoFromFile can't find %s in %s", updateos::file::version.c_str(), updateFile.c_str()); mtar_close(&tar); return json11::Json(); } /* this file should never be larger then purefs::buffer::tar_buf */ std::unique_ptr readBuf(new char[purefs::buffer::tar_buf]); if (mtar_read_data(&tar, readBuf.get(), h.size) != MTAR_ESUCCESS) { LOG_INFO("UpdatePureOS::getVersionInfoFromFile can't read %s in %s", updateos::file::version.c_str(), updateFile.c_str()); mtar_close(&tar); return json11::Json(); } mtar_close(&tar); std::string parserError; std::string dataPackage = std::string(static_cast(readBuf.get()), h.size); json11::Json versionInfo = json11::Json::parse(dataPackage, parserError); if (parserError != "") { LOG_INFO("UpdatePureOS::getVersionInfoFromFile can't parse %s as JSON error: \"%s\"", updateos::file::version.c_str(), parserError.c_str()); return json11::Json(); } return versionInfo; } else { LOG_INFO("UpdatePureOS::getVersionInfoFromFile %s does not exist", updateFile.c_str()); } return json11::Json(); } bool UpdatePureOS::isUpgradeToCurrent(const std::string &versionToCompare) { return true; } const fs::path UpdatePureOS::checkForUpdate() { std::vector fileList = vfs.listdir(purefs::dir::os_updates.c_str(), updateos::extension::update, true); for (auto &file : fileList) { json11::Json versionInfo = UpdatePureOS::getVersionInfoFromFile(purefs::dir::os_updates / file.fileName); if (versionInfo.is_null()) continue; if (versionInfo[purefs::json::os_version][purefs::json::version_string].is_string()) { if (UpdatePureOS::isUpgradeToCurrent( versionInfo[purefs::json::os_version][purefs::json::version_string].string_value())) { return purefs::dir::os_updates / file.fileName; } } } return fs::path(); } updateos::UpdateError UpdatePureOS::updateUserData() { return updateos::UpdateError::NoError; } void UpdatePureOS::informError(const char *format, ...) { va_list argptr; std::unique_ptr readBuf(new char[purefs::buffer::tar_buf]); va_start(argptr, format); vsnprintf(readBuf.get(), purefs::buffer::tar_buf, format, argptr); va_end(argptr); LOG_ERROR("UPDATE_ERRROR %s", readBuf.get()); auto msgToSend = std::make_shared(updateos::UpdateMessageType::UpdateError); messageText = std::string(readBuf.get()); msgToSend->updateStats = (updateos::UpdateStats)(*this); sys::Bus::SendUnicast(msgToSend, app::name_desktop, owner); } void UpdatePureOS::informDebug(const char *format, ...) { va_list argptr; std::unique_ptr readBuf(new char[purefs::buffer::tar_buf]); va_start(argptr, format); vsnprintf(readBuf.get(), purefs::buffer::tar_buf, format, argptr); va_end(argptr); LOG_DEBUG("UPDATE_DEBUG %s", readBuf.get()); } void UpdatePureOS::informUpdate(const char *format, ...) { va_list argptr; std::unique_ptr readBuf(new char[purefs::buffer::tar_buf]); va_start(argptr, format); vsnprintf(readBuf.get(), purefs::buffer::tar_buf, format, argptr); va_end(argptr); LOG_INFO("UPDATE_INFO %s", readBuf.get()); auto msgToSend = std::make_shared(updateos::UpdateMessageType::UpdateInform); messageText = std::string(readBuf.get()); msgToSend->updateStats = (updateos::UpdateStats)(*this); sys::Bus::SendUnicast(msgToSend, app::name_desktop, owner); }