#include "module_control.h" #include "sdv_core.h" #include #include "../../global/exec_dir_helper.h" #include "toml_parser/parser_toml.h" #include "toml_parser//parser_node_toml.h" /// @cond DOXYGEN_IGNORE #ifdef _WIN32 #include #elif defined __unix__ #include #include #else #error OS is not supported! #endif /// @endcond CModuleControl::CModuleControl() {} CModuleControl::~CModuleControl() { try { UnloadAll(std::vector()); } catch (const sdv::XSysExcept&) { } catch (const std::exception&) { } } bool CModuleControl::AddModuleSearchDir(const sdv::u8string& ssDir) { // Add initial paths if not done so already AddCurrentPath(); std::unique_lock lock(m_mtxModules); std::filesystem::path pathDir(ssDir.c_str()); // Relative paths are always relative to the executable if (pathDir.is_relative()) pathDir = GetExecDirectory() / ssDir.c_str(); // Remove any indirections pathDir = pathDir.lexically_normal(); // If the current path is not a directory, it cannot be added if (!std::filesystem::is_directory(pathDir)) return false; // Check whether the path is already in the list if (std::find(m_lstSearchPaths.begin(), m_lstSearchPaths.end(), pathDir) != m_lstSearchPaths.end()) return true; // This is not an error // Add the path m_lstSearchPaths.push_back(pathDir); return true; } sdv::sequence CModuleControl::GetModuleSearchDirs() const { std::unique_lock lock(m_mtxModules); sdv::sequence seqSearchDirs; for (const std::filesystem::path& rpathSearchDir : m_lstSearchPaths) seqSearchDirs.push_back(rpathSearchDir.generic_u8string()); return seqSearchDirs; } sdv::sequence CModuleControl::GetModuleList() const { std::unique_lock lock(m_mtxModules); sdv::sequence seqModuleInfos; // Build the module information sequence for (const std::shared_ptr& ptrModule : m_lstModules) { if (!ptrModule) continue; seqModuleInfos.push_back(ptrModule->GetModuleInfo()); } return seqModuleInfos; } sdv::sequence CModuleControl::GetClassList(/*in*/ sdv::core::TModuleID tModuleID) const { // Find the module instance std::unique_lock lock(m_mtxModules); sdv::sequence seqClassInfos; auto itModule = std::find_if(m_lstModules.begin(), m_lstModules.end(), [&](const std::shared_ptr& ptrModule) { return ptrModule && ptrModule->GetModuleID() == tModuleID; }); if (itModule == m_lstModules.end()) return seqClassInfos; // Cannot find the module. // Request the class infos from the module instance. std::vector vecClasses = (*itModule)->GetAvailableClasses(); for (const std::string& rssClass : vecClasses) { auto optClassInfo = (*itModule)->GetClassInfo(rssClass); if (!optClassInfo) continue; seqClassInfos.push_back(*optClassInfo); } return seqClassInfos; } sdv::core::TModuleID CModuleControl::Load(const sdv::u8string& ssModulePath) { if(ssModulePath.empty()) return 0; // Add initial paths if not done so already AddCurrentPath(); // Core services bypass. std::unique_lock lock(m_mtxModules); std::filesystem::path pathModule; if (ssModulePath == "core_services.sdv") pathModule = static_cast(ssModulePath); else { if (GetAppControl().IsMainApplication() || GetAppControl().IsIsolatedApplication()) { // Check the installation for the module. pathModule = GetAppConfig().FindInstalledModule(static_cast(ssModulePath)); } else { try { // Get the search paths if the module path is relative std::list lstSearchPaths; std::filesystem::path pathSupplied(static_cast(ssModulePath)); if (pathSupplied.is_relative()) lstSearchPaths = m_lstSearchPaths; // Add an empty path to allow the OS to search when our own search paths could not find the module. lstSearchPaths.push_back(std::filesystem::path()); // Run through the search paths and try to find the module. for (const std::filesystem::path& rpathDirectory : lstSearchPaths) { // Compose the path std::filesystem::path pathModuleTemp; if (rpathDirectory.is_absolute()) pathModuleTemp = (rpathDirectory / pathSupplied).lexically_normal(); else { if (rpathDirectory.empty()) pathModuleTemp = pathSupplied.lexically_normal(); else pathModuleTemp = (GetExecDirectory() / rpathDirectory / pathSupplied).lexically_normal(); } if (std::filesystem::exists(pathModuleTemp)) { pathModule = pathModuleTemp; break; } } } catch (const std::exception& re) { SDV_LOG_ERROR("Supplied path to module load is not valid \"", ssModulePath, "\": ", re.what()); } } } // Check for an exisiting module. If existing, return the existing module. for (const std::shared_ptr& rptrModule : m_lstModules) { if (rptrModule && rptrModule->GetModulePath() == pathModule) return rptrModule->GetModuleID(); } // Create a new instance (even if the module could not be found). std::shared_ptr ptrModule = std::make_shared(static_cast(ssModulePath), pathModule); m_lstModules.push_back(ptrModule); m_setConfigModules.insert(ptrModule->GetModuleID()); if (!ptrModule->IsValid()) { SDV_LOG_ERROR("The module was not found \"", ssModulePath, "\""); return 0; // Do not return the module ID of not loadable modules. } return ptrModule->GetModuleID(); } bool CModuleControl::Unload(sdv::core::TModuleID tModuleID) { // Cannot unload the core services if (tModuleID == sdv::core::tCoreLibModule) return true; return ContextUnload(tModuleID, false); } bool CModuleControl::HasActiveObjects(sdv::core::TModuleID tModuleID) const { // Find the module instance std::unique_lock lock(m_mtxModules); auto itModule = std::find_if(m_lstModules.begin(), m_lstModules.end(), [&](const std::shared_ptr& ptrModule) { return ptrModule && ptrModule->GetModuleID() == tModuleID; }); if (itModule == m_lstModules.end()) return false; // Cannot find the module; no active objects known. // Redirect the call to the module instance. return (*itModule)->HasActiveObjects(); } std::shared_ptr CModuleControl::FindModuleByClass(const std::string& rssClass) { // Find the module instance std::unique_lock lock(m_mtxModules); for (const std::shared_ptr& ptrModule : m_lstModules) { if (!ptrModule) continue; std::vector vecClasses = ptrModule->GetAvailableClasses(); for (const std::string& rssClassLocal : vecClasses) { if (rssClassLocal == rssClass) return ptrModule; auto optClassInfo = ptrModule->GetClassInfo(rssClass); if (!optClassInfo) continue; if (std::find(optClassInfo->seqClassAliases.begin(), optClassInfo->seqClassAliases.end(), rssClass) != optClassInfo->seqClassAliases.end()) return ptrModule; } } // For main and isolated applications, check whether the module is in one of the installation manifests. auto optManifest = GetAppConfig().FindInstalledComponent(rssClass); if (!optManifest) return nullptr; auto ssManifest = GetAppConfig().FindInstalledModuleManifest(optManifest->pathRelModule); if (ssManifest.empty()) return nullptr; lock.unlock(); // Load the module return GetModule(ContextLoad(optManifest->pathRelModule, ssManifest)); } std::shared_ptr CModuleControl::GetModule(sdv::core::TModuleID tModuleID) const { // Find the module instance std::unique_lock lock(m_mtxModules); auto itModule = std::find_if(m_lstModules.begin(), m_lstModules.end(), [&](const std::shared_ptr& ptrModule) { return ptrModule && ptrModule->GetModuleID() == tModuleID; }); if (itModule == m_lstModules.end()) return nullptr; // Cannot find the module. return *itModule; } void CModuleControl::UnloadAll(const std::vector& rvecIgnoreModules) { // Force unloading all modules in reverse order of their loading std::unique_lock lock(m_mtxModules); std::list> lstCopy = m_lstModules; lock.unlock(); while (lstCopy.size()) { std::shared_ptr ptrModule = std::move(lstCopy.back()); lstCopy.pop_back(); if (!ptrModule) continue; // On the ignore list? if (std::find(rvecIgnoreModules.begin(), rvecIgnoreModules.end(), ptrModule->GetModuleID()) != rvecIgnoreModules.end()) continue; // Unload... -> call the module control function to remove it from the list. ContextUnload(ptrModule->GetModuleID(), true); } } void CModuleControl::ResetConfigBaseline() { std::unique_lock lock(m_mtxModules); m_setConfigModules.clear(); } std::string CModuleControl::SaveConfig(const std::set& rsetIgnoreModule) { std::stringstream sstream; std::unique_lock lock(m_mtxModules); // Add all the loaded modules for (const std::shared_ptr& rptrModule : m_lstModules) { if (m_setConfigModules.find(rptrModule->GetModuleID()) != m_setConfigModules.end() && rsetIgnoreModule.find(rptrModule->GetModuleConfigPath()) != rsetIgnoreModule.end()) { sstream << std::endl; sstream << "[[Module]]" << std::endl; sstream << "Path = \"" << rptrModule->GetModuleConfigPath().generic_u8string() << "\"" << std::endl; } } return sstream.str(); } sdv::core::TModuleID CModuleControl::ContextLoad(const std::filesystem::path& rpathModule, const std::string& rssManifest) { if (GetAppControl().IsMaintenanceApplication()) return 0; // Not allowed // Run through the manifest and check for complex services, applications and utilities. // TODO EVE: Temporary suppression of cppcheck warning. // cppcheck-suppress variableScope [[maybe_unused]] bool bIsolationNeeded = false; try { size_t nIndex = 0; size_t nUnsupportedObjectCount = 0; toml_parser::CParser parser(rssManifest); do { std::shared_ptr ptrComponentType = parser.Root().Direct(std::string("Component[") + std::to_string(nIndex++) + "].Type"); if (!ptrComponentType) break; std::string ssType = static_cast(ptrComponentType->GetValue()); if (ssType == "Device") continue; // Okay if (ssType == "BasicService") continue; // Okay if (ssType == "Proxy") continue; // Okay if (ssType == "Stub") continue; // Okay if (ssType == "ComplexService" || ssType == "Application" || ssType == "Utility") { bIsolationNeeded = true; continue; // Okay } // Not okay nUnsupportedObjectCount++; } while (true); if (nIndex == nUnsupportedObjectCount) return 0; // No object to load. } catch (const sdv::toml::XTOMLParseException& /*rexcept*/) { return 0; } // TODO: If the module to load needs isolation... // If this is a main application: isolate modules that should be isolated // If this is an isolated application: check for another loaded module that needs isolation already. If this do not allow // this module to load. // Load the module return Load(rpathModule.generic_u8string()); } bool CModuleControl::ContextUnload(sdv::core::TModuleID tModuleID, bool bForce) { // Find the module instance std::unique_lock lock(m_mtxModules); auto itModule = std::find_if(m_lstModules.begin(), m_lstModules.end(), [&](const std::shared_ptr& ptrModule) { return ptrModule && ptrModule->GetModuleID() == tModuleID; }); if (itModule == m_lstModules.end()) return true; // Cannot find the module to unload. This is not an error! m_setConfigModules.erase(tModuleID); // Remove from config if part of it. // Check whether it is possible to unload. bool bSuccess = (*itModule)->Unload(bForce); if (bSuccess) m_lstModules.erase(itModule); return bSuccess; } std::shared_ptr CModuleControl::FindModuleByPath(const std::filesystem::path& rpathModule) const { // Find the module instance std::unique_lock lock(m_mtxModules); auto itModule = std::find_if(m_lstModules.begin(), m_lstModules.end(), [&](const std::shared_ptr& ptrModule) { return ptrModule && ptrModule->GetModulePath() == rpathModule; }); if (itModule == m_lstModules.end()) return nullptr; // Cannot find the module return *itModule; } void CModuleControl::AddCurrentPath() { std::unique_lock lock(m_mtxModules); // Do this only once. if (!m_lstSearchPaths.empty()) return; // Add the core directory std::filesystem::path pathCoreDir = GetCoreDirectory().lexically_normal(); m_lstSearchPaths.push_back(pathCoreDir); // Add the exe dir std::filesystem::path pathExeDir = GetExecDirectory().lexically_normal(); if (pathExeDir != pathCoreDir) m_lstSearchPaths.push_back(pathExeDir); } #ifndef DO_NOT_INCLUDE_IN_UNIT_TEST CModuleControl& CModuleControlService::GetModuleControl() { return ::GetModuleControl(); } bool CModuleControlService::EnableModuleControlAccess() { return GetAppControl().IsStandaloneApplication() || GetAppControl().IsEssentialApplication(); } #endif