Files
openvehicle-api/sdv_services/core/app_control.cpp
2026-03-27 14:12:49 +01:00

617 lines
22 KiB
C++

/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "app_control.h"
#include "module_control.h"
#include "repository.h"
#include "../../global/base64.h"
#include "../../global/tracefifo/trace_fifo.cpp"
#include "toml_parser/parser_toml.h"
#include "local_shutdown_request.h"
#include "app_settings.h"
#include "logger_control.h"
#include "app_config.h"
/**
* @brief Specific exit handler helping to shut down after startup. In case the shutdown wasn't explicitly executed.
*/
void ExitHandler()
{
// Check if the system was properly shut down
switch (GetAppControl().GetOperationState())
{
case sdv::app::EAppOperationState::shutting_down:
case sdv::app::EAppOperationState::not_started:
break;
default:
GetAppControl().Shutdown(true);
break;
}
}
CAppControl& GetAppControl()
{
static CAppControl app_control;
return app_control;
}
bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfaceAccess* pEventHandler)
{
m_pEvent = pEventHandler ? pEventHandler->GetInterface<sdv::app::IAppEvent>() : nullptr;
// Intercept the logging...
std::stringstream sstreamCOUT, sstreamCLOG, sstreamCERR;
std::streambuf* pstreambufCOUT = std::cout.rdbuf(sstreamCOUT.rdbuf());
std::streambuf* pstreambufCLOG = std::clog.rdbuf(sstreamCLOG.rdbuf());
std::streambuf* pstreambufCERR = std::cerr.rdbuf(sstreamCERR.rdbuf());
// Write initial startup message
auto now = std::chrono::system_clock::now();
auto in_time_t = std::chrono::system_clock::to_time_t(now);
SDV_LOG_INFO("SDV Application start at ", std::put_time(std::localtime(&in_time_t), "%Y-%m-%d %H:%M:%S %Z"));
BroadcastOperationState(sdv::app::EAppOperationState::initializing);
// Process the application config.
bool bRet = GetAppSettings().ProcessAppStartupConfig(ssConfig);
// Undo logging interception
sstreamCOUT.rdbuf()->pubsync();
sstreamCLOG.rdbuf()->pubsync();
sstreamCERR.rdbuf()->pubsync();
std::cout.rdbuf(pstreambufCOUT);
std::clog.rdbuf(pstreambufCLOG);
std::cerr.rdbuf(pstreambufCERR);
// Process config error...
if (!bRet)
{
std::cout << sstreamCOUT.str();
std::clog << sstreamCLOG.str();
std::cerr << sstreamCERR.str();
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Cannot continue!" << std::endl;
Shutdown(true);
return false;
}
// Open the stream buffer and attach the streams if the application control is initialized as main app.
if (GetAppSettings().IsMainApplication())
{
m_fifoTraceStreamBuffer.SetInstanceID(GetAppSettings().GetInstanceID());
m_fifoTraceStreamBuffer.Open(1000);
std::cout << "**********************************************" << std::endl;
}
// Stream the starting logging to the output streams...
std::cout << sstreamCOUT.str();
std::clog << sstreamCLOG.str();
std::cerr << sstreamCERR.str();
// Check for a correctly opened stream buffer
if (GetAppSettings().IsMainApplication() && !m_fifoTraceStreamBuffer.IsOpened())
{
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Log streaming could not be initialized; cannot continue!" << std::endl;
Shutdown(true);
return false;
}
// Allow only one instance if running as main application
if (GetAppSettings().IsMainApplication())
{
m_pathLockFile = GetExecDirectory() / ("sdv_core_" + std::to_string(GetAppSettings().GetInstanceID()) + ".lock");
#ifdef _WIN32
// Checkf or the existence of a lock file. If existing, do not continue.
if (std::filesystem::exists(m_pathLockFile)) return false;
// Create temp file
m_pLockFile = _wfopen(m_pathLockFile.native().c_str(), L"a+TD");
#elif defined __unix__
// Create a lock file if not existing.
m_pLockFile = fopen(m_pathLockFile.native().c_str(), "a+");
if (!m_pLockFile || lockf(fileno(m_pLockFile), F_TLOCK, 0) != 0)
{
// File exists already or could not be opened.
// No continuation possible.
if (m_pLockFile) fclose(m_pLockFile);
m_pLockFile = nullptr;
return false;
}
#else
#error OS is not supported!
#endif
}
std::string ssErrorString;
auto fnLoadModule = [&ssErrorString](const sdv::u8string& rssModule) -> bool
{
bool bLocalRet = GetModuleControl().Load(rssModule);
if (!bLocalRet) ssErrorString = std::string("Failed to load module '") + rssModule + "'.";
return bLocalRet;
};
auto fnCreateObject = [&ssErrorString](const sdv::u8string& rssClass, const sdv::u8string& rssObject, const sdv::u8string& rssConfig) -> bool
{
bool bLocalRet = GetRepository().CreateObject2(rssClass, rssObject, rssConfig);
if (!bLocalRet)
{
ssErrorString = std::string("Failed to instantiate a new object from class '") + rssClass + "'";
if (!rssObject.empty())
ssErrorString += std::string(" with name '") + rssObject + "'.";
}
return bLocalRet;
};
// Load the core services
if (!fnLoadModule("core_services.sdv"))
{
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Failed to load the Core Services. Cannot continue!" << std::endl;
Shutdown(true);
return false;
}
// Load the logger module if one is specified
if (!GetAppSettings().GetLoggerModulePath().empty())
{
m_tLoggerModuleID = fnLoadModule(GetAppSettings().GetLoggerModulePath().u8string());
if (!m_tLoggerModuleID)
{
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Failed to load the custom logger. Cannot continue!" << std::endl;
Shutdown(true);
return false;
}
}
// Start the logger and assign it to the logger control.
fnCreateObject(GetAppSettings().GetLoggerClass(), GetAppSettings().GetLoggerClass(), "");
sdv::IInterfaceAccess* pLoggerObj = GetRepository().GetObject(GetAppSettings().GetLoggerClass());
if (!pLoggerObj)
{
GetRepository().DestroyObject2(GetAppSettings().GetLoggerClass());
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Failed to start the logger. Cannot continue!" << std::endl;
Shutdown(true);
return false;
}
sdv::core::ILoggerConfig* pLoggerConfig = pLoggerObj->GetInterface<sdv::core::ILoggerConfig>();
sdv::core::ILogger* pLogger = pLoggerObj->GetInterface<sdv::core::ILogger>();
if (!pLoggerConfig || !pLogger)
{
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Failed to start the logger. Cannot continue!" << std::endl;
Shutdown(true);
return false;
}
if (!GetAppSettings().GetLoggerProgramTag().empty())
pLoggerConfig->SetProgramTag(GetAppSettings().GetLoggerProgramTag());
pLoggerConfig->SetLogFilter(GetAppSettings().GetLoggerSeverityFilter(), GetAppSettings().GetConsoleSeverityFilter());
GetLoggerControl().SetLogger(pLogger);
// Create the core service objects
bRet = fnCreateObject("AppControlService", "AppControlService", "");
bRet = bRet && fnCreateObject("RepositoryService", "RepositoryService", "");
bRet = bRet && fnCreateObject("ModuleControlService", "ModuleControlService", "");
bRet = bRet && fnCreateObject("ConfigService", "ConfigService", "");
if (!bRet)
{
if (!GetAppSettings().IsConsoleSilent())
{
std::cerr << "ERROR: Failed to start the Core Services. Cannot continue!" << std::endl;
if (!ssErrorString.empty())
std::cerr << "REASON: " << ssErrorString << std::endl;
}
Shutdown(true);
return false;
}
// Load specific services
bool bLoadRPCClient = false, bLoadRPCServer = false;
switch (GetAppSettings().GetContextType())
{
case sdv::app::EAppContext::standalone:
break;
case sdv::app::EAppContext::external:
bLoadRPCClient = true;
break;
case sdv::app::EAppContext::isolated:
bLoadRPCClient = true;
break;
case sdv::app::EAppContext::main:
bLoadRPCClient = true;
bLoadRPCServer = true;
break;
case sdv::app::EAppContext::essential:
//bLoadRPCClient = true;
break;
case sdv::app::EAppContext::maintenance:
bLoadRPCClient = true;
break;
default:
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Invalid Run-As mode. Cannot continue!" << std::endl;
Shutdown(true);
return false;
}
// Load the application settings.
if (!GetAppSettings().LoadSettingsFile())
{
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Failed to load application settings file. Cannot continue!" << std::endl;
Shutdown(true);
return false;
}
// Load installation manifests. For standalone applications, only the core manifest is loaded.
if (!GetAppConfig().LoadInstallationManifests())
{
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Failed to load installation manifests. Cannot continue!" << std::endl;
Shutdown(true);
return false;
}
// Load process control
if (bRet) bRet = fnLoadModule("process_control.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("ProcessControlService", "", "");
// Load RPC components
if (bRet && bLoadRPCServer)
{
// Load hardware identification
bRet = fnLoadModule("hardware_ident.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("HardwareIdentificationService", "", "");
// Load shared memory channel
if (bRet) bRet = fnLoadModule("ipc_shared_mem.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("DefaultSharedMemoryChannelControl", "", "");
// Load IPC service
if (bRet) bRet = fnLoadModule("ipc_com.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("CommunicationControl", "", "");
// Load IPC service and create listener local connections
if (bRet) bRet = fnLoadModule("ipc_listener.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("ConnectionListenerService", "", R"code([Listener]
Type = "Local"
)code");
// Load IPC service
if (bRet) bRet = fnLoadModule("ipc_connect.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("ConnectionService", "", "");
// Load proxy/stub for core interfaces
if (bRet) bRet = fnLoadModule("core_ps.sdv") ? true : false;
// // Start the listener
// if (bRet) bRet = fnLoadModule("ipc_listener.sdv") ? true : false;
// if (bRet) bRet = GetRepository().CreateObject("ConnectionListenerService", "ConnectionListenerService", R"code([Listener]
//Type="local"
//Instance=)code" + std::to_string(GetInstanceID()));
if (!bRet)
{
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Failed to load IPC server components. Cannot continue!" << std::endl;
if (!ssErrorString.empty())
std::cerr << "REASON: " << ssErrorString << std::endl;
Shutdown(true);
return false;
}
}
else if (bRet && bLoadRPCClient)
{
// Interpret the connect string...
// Load hardware identification
bRet = fnLoadModule("hardware_ident.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("HardwareIdentificationService", "", "");
// Load shared memory channel
if (bRet) bRet = fnLoadModule("ipc_shared_mem.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("DefaultSharedMemoryChannelControl", "", "");
// Load IPC service
if (bRet) bRet = fnLoadModule("ipc_com.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("CommunicationControl", "", "");
// Load IPC service
if (bRet) bRet = fnLoadModule("ipc_connect.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("ConnectionService", "", "");
// Load proxy/stub for core interfaces
if (bRet) bRet = fnLoadModule("core_ps.sdv") ? true : false;
if (!bRet)
{
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Failed to load IPC client components. Cannot continue!" << std::endl;
Shutdown(true);
return false;
}
}
if (!bRet)
{
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Failed to start core components. Cannot continue!" << std::endl;
if (!ssErrorString.empty())
std::cerr << "REASON: " << ssErrorString << std::endl;
Shutdown(true);
return false;
}
// Register the exit handler to force a proper shutdown in case the application exits without a call to shutdown.
std::atexit(ExitHandler);
BroadcastOperationState(sdv::app::EAppOperationState::initialized);
SetConfigMode();
if (GetAppSettings().IsMainApplication())
{
// Load system configs - they need to be present completely
for (const std::filesystem::path& rpathConfig : GetAppSettings().GetSystemConfigPaths())
{
sdv::core::EConfigProcessResult eResult = GetAppConfig().LoadConfig(rpathConfig.generic_u8string());
if (eResult != sdv::core::EConfigProcessResult::successful)
{
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Cannot load or partly load the system configuration: " <<
rpathConfig.generic_u8string() << std::endl;
Shutdown(true);
return false;
}
}
// The system configs should not be stored once more.
GetAppConfig().ResetConfigBaseline();
// Load the application config - they can also be partly there
if (!GetAppSettings().GetUserConfigPath().empty())
{
sdv::core::EConfigProcessResult eResult =
GetAppConfig().LoadConfig(GetAppSettings().GetUserConfigPath().generic_u8string());
if (eResult == sdv::core::EConfigProcessResult::failed)
{
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Cannot load application configuration: " <<
GetAppSettings().GetUserConfigPath().generic_u8string() << std::endl;
Shutdown(true);
return false;
}
else if (eResult != sdv::core::EConfigProcessResult::partially_successful)
{
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "WARNING: Partially could not load application configuration: " <<
GetAppSettings().GetUserConfigPath().generic_u8string() << std::endl;
}
m_bAutoSaveConfig = true;
}
}
SetRunningMode();
return true;
}
void CAppControl::RunLoop()
{
// Only allowed to run during configuration and running mode.
switch (m_eState)
{
case sdv::app::EAppOperationState::configuring :
case sdv::app::EAppOperationState::running :
break;
default:
throw sdv::XInvalidState();
}
// Treat local running differently from main and isolated app running.
// Note... Maintenance apps should not use the loop
bool bLocal = true;
switch (GetAppSettings().GetContextType())
{
case sdv::app::EAppContext::main:
case sdv::app::EAppContext::isolated:
bLocal = false;
break;
case sdv::app::EAppContext::maintenance:
throw sdv::XAccessDenied();
break;
default:
bLocal = true;
break;
}
CShutdownRequestListener listener;
if (bLocal)
{
listener = std::move(CShutdownRequestListener(GetAppSettings().GetInstanceID()));
if (!listener.IsValid())
throw sdv::XAccessDenied(); // Another instance is already running.
}
if (GetAppSettings().IsConsoleVerbose())
std::cout << "Entering loop" << std::endl;
m_bRunLoop = true;
while (m_bRunLoop)
{
// Check and wait 2 milliseconds.
if (bLocal)
m_bRunLoop = !listener.HasTriggered(2);
else
std::this_thread::sleep_for(std::chrono::milliseconds(2));
// Inform about running.
if (m_pEvent)
{
sdv::app::SAppEvent sEvent{};
sEvent.uiEventID = sdv::app::EVENT_RUNNING_LOOP;
sEvent.uiInfo = 0u;
m_pEvent->ProcessEvent(sEvent);
}
}
if (GetAppSettings().IsConsoleVerbose())
std::cout << "Leaving loop" << std::endl;
}
void CAppControl::Shutdown(/*in*/ bool bForce)
{
if (m_eState == sdv::app::EAppOperationState::not_started) return;
// Shutdown the running loop
if (m_bRunLoop)
{
m_bRunLoop = false;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
BroadcastOperationState(sdv::app::EAppOperationState::shutting_down);
// Set the config state for all objects
GetRepository().SetConfigMode();
// Disable automatic configuration saving.
m_bAutoSaveConfig = false;
// Update the application settings file
GetAppSettings().SaveSettingsFile();
// Destroy all objects... this should also remove any registered services, except the custom logger.
GetRepository().DestroyAllObjects(std::vector<std::string>({GetAppSettings().GetLoggerClass()}), bForce);
// Unload all modules... this should destroy all running services, except the custom logger
GetModuleControl().UnloadAll(std::vector<sdv::core::TModuleID>({ m_tLoggerModuleID }));
// Remove all installation manifests
GetAppConfig().UnloadInstallatonManifests();
// End logging
auto now = std::chrono::system_clock::now();
auto in_time_t = std::chrono::system_clock::to_time_t(now);
if (bForce) SDV_LOG_INFO("Forced shutdown of application.");
SDV_LOG_INFO("SDV Application end at ", std::put_time(std::localtime(&in_time_t), "%Y-%m-%d %H:%M:%S %Z"));
GetLoggerControl().SetLogger(nullptr);
// Unload all modules... this should also destroy the custom logger.
GetModuleControl().UnloadAll(std::vector<sdv::core::TModuleID>());
// Release the lock on the instance (only for main application)
if (m_pLockFile)
{
#ifdef __unix__
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-result"
#endif
lockf(fileno(m_pLockFile), F_ULOCK, 0);
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
#endif
fclose(m_pLockFile);
m_pLockFile = nullptr;
try
{
if (std::filesystem::exists(m_pathLockFile))
std::filesystem::remove(m_pathLockFile);
} catch (const std::filesystem::filesystem_error&)
{}
m_pathLockFile.clear();
}
// End trace streaming
if (GetAppSettings().IsMainApplication())
{
std::cout << "**********************************************" << std::endl;
// Shutdown log streaming
m_fifoTraceStreamBuffer.Close();
}
BroadcastOperationState(sdv::app::EAppOperationState::not_started);
m_pEvent = nullptr;
m_tLoggerModuleID = 0;
m_bAutoSaveConfig = false;
m_bEnableAutoSave = false;
GetAppSettings().Reset();
}
void CAppControl::RequestShutdown()
{
SDV_LOG_INFO("Shutdown request received.");
m_bRunLoop = false;
}
sdv::app::EAppOperationState CAppControl::GetOperationState() const
{
return m_eState;
}
void CAppControl::SetConfigMode()
{
GetRepository().SetConfigMode();
if (m_eState == sdv::app::EAppOperationState::running || m_eState == sdv::app::EAppOperationState::initialized)
BroadcastOperationState(sdv::app::EAppOperationState::configuring);
}
void CAppControl::SetRunningMode()
{
GetRepository().SetRunningMode();
if (m_eState == sdv::app::EAppOperationState::configuring || m_eState == sdv::app::EAppOperationState::initialized)
BroadcastOperationState(sdv::app::EAppOperationState::running);
}
void CAppControl::DisableAutoConfigUpdate()
{
m_bEnableAutoSave = false;
}
void CAppControl::EnableAutoConfigUpdate()
{
m_bEnableAutoSave = true;
}
void CAppControl::TriggerConfigUpdate()
{
if (!m_bAutoSaveConfig || !m_bEnableAutoSave) return;
if (GetAppSettings().GetUserConfigPath().empty())
return;
if (!GetAppConfig().SaveConfig(GetAppSettings().GetUserConfigPath().generic_u8string()))
SDV_LOG_ERROR("Failed to automatically save the configuration ", GetAppSettings().GetUserConfigPath().generic_u8string());
}
void CAppControl::BroadcastOperationState(sdv::app::EAppOperationState eState)
{
m_eState = eState;
if (m_pEvent)
{
sdv::app::SAppEvent sEvent{};
sEvent.uiEventID = sdv::app::EVENT_OPERATION_STATE_CHANGED;
sEvent.uiInfo = static_cast<uint32_t>(eState);
m_pEvent->ProcessEvent(sEvent);
}
}
bool CAppControlService::EnableAppShutdownRequestAccess() const
{
return ::GetAppSettings().IsMainApplication() || ::GetAppSettings().IsIsolatedApplication();
}