mirror of
https://github.com/eclipse-openvehicle-api/openvehicle-api.git
synced 2026-02-05 15:18:45 +00:00
19
sdv_services/process_control/CMakeLists.txt
Normal file
19
sdv_services/process_control/CMakeLists.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
# Define project
|
||||
project(process_control VERSION 1.0 LANGUAGES CXX)
|
||||
|
||||
# Define target
|
||||
add_library(process_control SHARED
|
||||
"process_control.h"
|
||||
"process_control.cpp")
|
||||
target_link_options(process_control PRIVATE)
|
||||
target_include_directories(process_control PRIVATE ./include/)
|
||||
target_link_libraries(process_control ${CMAKE_THREAD_LIBS_INIT})
|
||||
|
||||
set_target_properties(process_control PROPERTIES PREFIX "")
|
||||
set_target_properties(process_control PROPERTIES SUFFIX ".sdv")
|
||||
|
||||
# Build dependencies
|
||||
add_dependencies(process_control CompileCoreIDL)
|
||||
|
||||
# Appending the service in the service list
|
||||
set(SDV_Service_List ${SDV_Service_List} process_control PARENT_SCOPE)
|
||||
591
sdv_services/process_control/process_control.cpp
Normal file
591
sdv_services/process_control/process_control.cpp
Normal file
@@ -0,0 +1,591 @@
|
||||
#include "process_control.h"
|
||||
#include "../../global/base64.h"
|
||||
#include "../../global/exec_dir_helper.h"
|
||||
#include <support/local_service_access.h>
|
||||
#include <interfaces/app.h>
|
||||
|
||||
#ifdef __GNUC__
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
// Resolve conflict
|
||||
#pragma push_macro("interface")
|
||||
#undef interface
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <WinSock2.h>
|
||||
#include <Windows.h>
|
||||
#include <objbase.h>
|
||||
#include <wchar.h>
|
||||
|
||||
// Resolve conflict
|
||||
#pragma pop_macro("interface")
|
||||
#ifdef GetClassInfo
|
||||
#undef GetClassInfo
|
||||
#endif
|
||||
#elif defined __unix__
|
||||
//#include <spawn.h>
|
||||
#include <sys/wait.h>
|
||||
#include <signal.h>
|
||||
#include <spawn.h>
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
|
||||
#include "../../global/trace.h"
|
||||
|
||||
CProcessControl::~CProcessControl()
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
void CProcessControl::Initialize(const sdv::u8string& /*ssObjectConfig*/)
|
||||
{
|
||||
if (m_eObjectStatus != sdv::EObjectStatus::initialization_pending) return;
|
||||
|
||||
// Without monitor no trigger...
|
||||
m_threadMonitor = std::thread(&CProcessControl::MonitorThread, this);
|
||||
|
||||
m_eObjectStatus = sdv::EObjectStatus::initialized;
|
||||
}
|
||||
|
||||
sdv::EObjectStatus CProcessControl::GetStatus() const
|
||||
{
|
||||
return m_eObjectStatus;
|
||||
}
|
||||
|
||||
void CProcessControl::SetOperationMode(sdv::EOperationMode eMode)
|
||||
{
|
||||
switch (eMode)
|
||||
{
|
||||
case sdv::EOperationMode::configuring:
|
||||
if (m_eObjectStatus == sdv::EObjectStatus::running || m_eObjectStatus == sdv::EObjectStatus::initialized)
|
||||
m_eObjectStatus = sdv::EObjectStatus::configuring;
|
||||
break;
|
||||
case sdv::EOperationMode::running:
|
||||
if (m_eObjectStatus == sdv::EObjectStatus::configuring || m_eObjectStatus == sdv::EObjectStatus::initialized)
|
||||
m_eObjectStatus = sdv::EObjectStatus::running;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CProcessControl::Shutdown()
|
||||
{
|
||||
// TODO: Close process handles
|
||||
if (m_eObjectStatus == sdv::EObjectStatus::destruction_pending) return;
|
||||
m_eObjectStatus = sdv::EObjectStatus::shutdown_in_progress;
|
||||
|
||||
// Shutdown the monitor
|
||||
m_bShutdown = true;
|
||||
if (m_threadMonitor.joinable())
|
||||
m_threadMonitor.join();
|
||||
|
||||
m_eObjectStatus = sdv::EObjectStatus::destruction_pending;
|
||||
}
|
||||
|
||||
bool CProcessControl::AllowProcessControl() const
|
||||
{
|
||||
const sdv::app::IAppContext* pAppContext = sdv::core::GetCore<sdv::app::IAppContext>();
|
||||
return pAppContext && (pAppContext->GetContextType() == sdv::app::EAppContext::main ||
|
||||
pAppContext->GetContextType() == sdv::app::EAppContext::maintenance ||
|
||||
pAppContext->GetContextType() == sdv::app::EAppContext::essential);
|
||||
}
|
||||
|
||||
sdv::process::TProcessID CProcessControl::GetProcessID() const
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return static_cast<uint64_t>(GetCurrentProcessId());
|
||||
#elif defined __unix__
|
||||
return static_cast<uint64_t>(getpid());
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t CProcessControl::RegisterMonitor(/*in*/ sdv::process::TProcessID tProcessID, /*in*/ sdv::IInterfaceAccess* pMonitor)
|
||||
{
|
||||
if (!tProcessID || !pMonitor) return 0;
|
||||
sdv::process::IProcessLifetimeCallback* pCallback =
|
||||
sdv::TInterfaceAccessPtr(pMonitor).GetInterface<sdv::process::IProcessLifetimeCallback>();
|
||||
if (!pCallback) return 0;
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxProcesses);
|
||||
|
||||
SDV_LOG_TRACE(GetTimestamp(), "Registering... (PID#", tProcessID, ")");
|
||||
|
||||
// Find the process
|
||||
auto itProcess = m_mapProcesses.find(tProcessID);
|
||||
if (itProcess == m_mapProcesses.end())
|
||||
{
|
||||
#if _WIN32
|
||||
// Get the process handle.
|
||||
HANDLE hProcess = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION, FALSE, static_cast<DWORD>(tProcessID));
|
||||
if (!hProcess) return 0; // Cannot monitor
|
||||
#elif defined __unix__
|
||||
// Use "kill" to detect the existence of the process
|
||||
// Notice: this doesn't kill the process.
|
||||
if (kill(tProcessID, 0) != 0) return 0; // Cannot monitor
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
|
||||
// Create a new process structure
|
||||
auto ptrNewProcess = std::make_shared<SProcessHelper>();
|
||||
// Ignore cppcheck warning; normally the returned pointer should always have a value at this stage (otherwise an
|
||||
// exception was triggered).
|
||||
// cppcheck-suppress knownConditionTrueFalse
|
||||
if (!ptrNewProcess)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
CloseHandle(hProcess);
|
||||
#endif
|
||||
SDV_LOG_TRACE(GetTimestamp(), "Could not add monitor... (PID#", tProcessID, ")");
|
||||
return 0;
|
||||
}
|
||||
ptrNewProcess->tProcessID = tProcessID;
|
||||
#ifdef _WIN32
|
||||
ptrNewProcess->hProcess = hProcess;
|
||||
#endif
|
||||
auto prInsert = m_mapProcesses.insert(std::make_pair(tProcessID, ptrNewProcess));
|
||||
if (!prInsert.second)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
CloseHandle(hProcess);
|
||||
#endif
|
||||
SDV_LOG_TRACE(GetTimestamp(), "Could not add monitor... (PID#", tProcessID, ")");
|
||||
return 0; // Could not insert
|
||||
}
|
||||
SDV_LOG_TRACE(GetTimestamp(), "Monitor now added... (PID#", tProcessID, ")");
|
||||
itProcess = prInsert.first;
|
||||
}
|
||||
auto ptrProcess = itProcess->second;
|
||||
|
||||
// Create a new cookie
|
||||
uint32_t uiCookie = m_uiNextMonCookie++;
|
||||
|
||||
// Register the new monitor
|
||||
m_mapMonitors[uiCookie] = ptrProcess;
|
||||
lock.unlock();
|
||||
|
||||
// Add the monitor to the process as well
|
||||
std::unique_lock<std::mutex> lockProcess(ptrProcess->mtxProcess);
|
||||
ptrProcess->mapAssociatedMonitors[uiCookie] = pCallback;
|
||||
|
||||
// Monitor thread running?
|
||||
// Already terminated?
|
||||
if (!ptrProcess->bRunning)
|
||||
pCallback->ProcessTerminated(tProcessID, ptrProcess->iRetVal);
|
||||
|
||||
return uiCookie;
|
||||
}
|
||||
|
||||
void CProcessControl::UnregisterMonitor(/*in*/ uint32_t uiCookie)
|
||||
{
|
||||
if (!uiCookie) return;
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxProcesses);
|
||||
|
||||
// Find the monitor and remove the monitor
|
||||
auto itMonitor = m_mapMonitors.find(uiCookie);
|
||||
if (itMonitor == m_mapMonitors.end()) return;
|
||||
auto ptrProcess = itMonitor->second;
|
||||
m_mapMonitors.erase(itMonitor);
|
||||
|
||||
lock.unlock();
|
||||
|
||||
// Removing the monitor from the map might be problematic when the call is done in a callback. Simply update the pointer
|
||||
// instead (this will not affect the map order).
|
||||
std::unique_lock<std::mutex> lockProcess(ptrProcess->mtxProcess);
|
||||
ptrProcess->mapAssociatedMonitors.erase(uiCookie);
|
||||
}
|
||||
|
||||
bool CProcessControl::WaitForTerminate(/*in*/ sdv::process::TProcessID tProcessID, /*in*/ uint32_t uiWaitMs)
|
||||
{
|
||||
if (!tProcessID) return true; // Non-existent processes are terminated :-)
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxProcesses);
|
||||
|
||||
// Find the process
|
||||
auto itProcess = m_mapProcesses.find(tProcessID);
|
||||
if (itProcess == m_mapProcesses.end()) return true; // Non-existent processes are terminated :-)
|
||||
auto ptrProcess = itProcess->second;
|
||||
|
||||
lock.unlock();
|
||||
|
||||
// Already terminated?
|
||||
std::unique_lock<std::mutex> lockProcess(ptrProcess->mtxProcess);
|
||||
if (!ptrProcess->bRunning) return true;
|
||||
|
||||
// Wait for termination
|
||||
std::chrono::high_resolution_clock::time_point tpStart = std::chrono::high_resolution_clock::now();
|
||||
bool bTimeout = false;
|
||||
// False warning from cppcheck. bRunning is set by the monitor thread. Suppress warning.
|
||||
// cppcheck-suppress knownConditionTrueFalse
|
||||
while (ptrProcess->bRunning)
|
||||
{
|
||||
ptrProcess->cvWaitForProcess.wait_for(lockProcess, std::chrono::milliseconds(10));
|
||||
if (std::chrono::duration<double, std::milli>(std::chrono::high_resolution_clock::now() - tpStart).count() > static_cast<double>(uiWaitMs))
|
||||
{
|
||||
bTimeout = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return !bTimeout;
|
||||
}
|
||||
|
||||
sdv::process::TProcessID CProcessControl::Execute(/*in*/ const sdv::u8string& ssModule,
|
||||
/*in*/ const sdv::sequence<sdv::u8string>& seqArgs, /*in*/ sdv::process::EProcessRights eRights)
|
||||
{
|
||||
if (!AllowProcessControl()) return 0;
|
||||
if (ssModule.empty()) return 0;
|
||||
sdv::process::TProcessID tProcessID = 0;
|
||||
|
||||
// Update rights
|
||||
bool bReduceRights = false;
|
||||
const sdv::app::IAppContext* pAppContext = sdv::core::GetCore<sdv::app::IAppContext>();
|
||||
if (!pAppContext) return 0;
|
||||
|
||||
switch (eRights)
|
||||
{
|
||||
case sdv::process::EProcessRights::default_rights:
|
||||
// Default implementation would be reduced rights
|
||||
bReduceRights = pAppContext->GetContextType() == sdv::app::EAppContext::main;
|
||||
break;
|
||||
case sdv::process::EProcessRights::reduced_rights:
|
||||
bReduceRights = true;
|
||||
break;
|
||||
case sdv::process::EProcessRights::parent_rights:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Check for the existence of the module
|
||||
std::filesystem::path pathModule(static_cast<std::string>(ssModule));
|
||||
#ifdef _WIN32
|
||||
if (!pathModule.has_extension())
|
||||
pathModule.replace_extension(".exe");
|
||||
#endif
|
||||
if (pathModule.is_relative())
|
||||
{
|
||||
// Get the module serch dirs
|
||||
sdv::sequence<sdv::u8string> seqSearchDirs;
|
||||
const sdv::core::IModuleControlConfig* pModuleControlConfig = sdv::core::GetCore<sdv::core::IModuleControlConfig>();
|
||||
if (pModuleControlConfig)
|
||||
seqSearchDirs = pModuleControlConfig->GetModuleSearchDirs();
|
||||
|
||||
// Add the current directory as well.
|
||||
seqSearchDirs.insert(seqSearchDirs.begin(), ".");
|
||||
|
||||
// Now find the module
|
||||
bool bFound = false;
|
||||
for (const sdv::u8string& rssPath : seqSearchDirs)
|
||||
{
|
||||
std::filesystem::path pathModuleTemp = std::filesystem::path(static_cast<std::string>(rssPath)) / pathModule;
|
||||
if (std::filesystem::exists(pathModuleTemp))
|
||||
{
|
||||
pathModule = pathModuleTemp;
|
||||
bFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Found?
|
||||
if (!bFound) return 0;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
// The command line is one string. If containing spaces, include quotes
|
||||
std::wstringstream sstreamCommandline;
|
||||
sstreamCommandline << pathModule.native();
|
||||
for (auto& rssArg : seqArgs)
|
||||
{
|
||||
sstreamCommandline << L" ";
|
||||
bool bQuotes = rssArg.find(" ") != std::string::npos;
|
||||
if (bQuotes) sstreamCommandline << L'\"';
|
||||
sstreamCommandline << sdv::MakeWString(rssArg);
|
||||
if (bQuotes) sstreamCommandline << L'\"';
|
||||
}
|
||||
|
||||
// Command line string
|
||||
std::wstring ssCommandLine = sstreamCommandline.str();
|
||||
if (ssCommandLine.size() > 32768) return 0;
|
||||
ssCommandLine.reserve(32768);
|
||||
|
||||
/* First get a handle to the current process's primary token */
|
||||
HANDLE hProcessToken = 0;
|
||||
OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY, &hProcessToken);
|
||||
|
||||
// Update rights
|
||||
if (bReduceRights)
|
||||
{
|
||||
// Create a restricted token with all privileges removed.
|
||||
// NOTE: This doesn't restrict file system access. To do so, use a separate user (e.g. guest user).
|
||||
HANDLE hRestrictedToken = 0;
|
||||
CreateRestrictedToken(hProcessToken, DISABLE_MAX_PRIVILEGE, 0, 0, 0, 0, 0, 0, &hRestrictedToken);
|
||||
CloseHandle(hProcessToken);
|
||||
hProcessToken = hRestrictedToken;
|
||||
}
|
||||
|
||||
// Start the process
|
||||
STARTUPINFOW sStartInfo{};
|
||||
PROCESS_INFORMATION sProcessInfo{};
|
||||
bool bRes = CreateProcessAsUserW(hProcessToken, // Process token
|
||||
nullptr, // No module name (use command line)
|
||||
&ssCommandLine.front(), // Command line
|
||||
nullptr, // Process handle not inheritable
|
||||
nullptr, // Thread handle not inheritable
|
||||
FALSE, // Set handle inheritance to FALSE
|
||||
0, // No creation flags
|
||||
nullptr, // Use parent's environment block
|
||||
GetExecDirectory().native().c_str(), // Use parent's starting directory
|
||||
&sStartInfo, // Pointer to STARTUPINFO structure
|
||||
&sProcessInfo); // Pointer to PROCESS_INFORMATION structure
|
||||
if (!bRes) return 0;
|
||||
tProcessID = sProcessInfo.dwProcessId;
|
||||
CloseHandle(sProcessInfo.hThread);
|
||||
CloseHandle(hProcessToken);
|
||||
|
||||
#elif defined __unix__
|
||||
|
||||
// Create the argument list
|
||||
std::vector<char*> vecArgs;
|
||||
auto seqTempArgs = seqArgs;
|
||||
std::string ssModuleTemp = pathModule.native();
|
||||
seqTempArgs.insert(seqTempArgs.begin(), &ssModuleTemp.front());
|
||||
for (auto& rssArg : seqTempArgs)
|
||||
vecArgs.push_back(&rssArg.front());
|
||||
vecArgs.push_back(nullptr);
|
||||
|
||||
// Create environment variable list
|
||||
std::vector<char*> vecEnv;
|
||||
vecEnv.push_back(nullptr);
|
||||
|
||||
// TODO: Update rights and environment vars
|
||||
|
||||
// Fork the process
|
||||
int pid = vfork();
|
||||
switch (pid)
|
||||
{
|
||||
case -1: // Error called in parent process; cannot continue
|
||||
return 0;
|
||||
case 0: // Child process is executing with pid == 0
|
||||
{
|
||||
// Reduce rights
|
||||
if (bReduceRights)
|
||||
{
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-result"
|
||||
#endif
|
||||
setgid(getgid());
|
||||
setuid(getuid());
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
}
|
||||
|
||||
execve(pathModule.native().c_str(), &vecArgs.front(), &vecEnv.front());
|
||||
std::abort(); // Child process only comes here on error
|
||||
}
|
||||
default:
|
||||
// Parent process is executing with pid != 0
|
||||
tProcessID = static_cast<sdv::process::TProcessID>(pid);
|
||||
break;
|
||||
}
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
|
||||
// Create a new process structure
|
||||
auto ptrNewProcess = std::make_shared<SProcessHelper>();
|
||||
// Ignore cppcheck warning; normally the returned pointer should always have a value at this stage (otherwise an
|
||||
// exception was triggered).
|
||||
// cppcheck-suppress knownConditionTrueFalse
|
||||
if (!ptrNewProcess)
|
||||
{
|
||||
// TODO Terminate process and close handles...
|
||||
return 0;
|
||||
}
|
||||
ptrNewProcess->tProcessID = tProcessID;
|
||||
#ifdef _WIN32
|
||||
ptrNewProcess->hProcess = sProcessInfo.hProcess;
|
||||
#endif
|
||||
m_mapProcesses[tProcessID] = ptrNewProcess;
|
||||
|
||||
return tProcessID;
|
||||
}
|
||||
|
||||
bool CProcessControl::Terminate(/*in*/ sdv::process::TProcessID tProcessID)
|
||||
{
|
||||
if (!AllowProcessControl()) return false;
|
||||
|
||||
if (!tProcessID) return false;
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, static_cast<DWORD>(tProcessID));
|
||||
if (!hProcess) return 0; // Cannot terminate
|
||||
TerminateProcess(hProcess, static_cast<UINT>(-100));
|
||||
CloseHandle(hProcess);
|
||||
#elif defined __unix__
|
||||
// Send a signal (SIGUSR1) to the child process.
|
||||
if (kill(tProcessID, SIGUSR1) != 0) return false;
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CProcessControl::MonitorThread()
|
||||
{
|
||||
while (!m_bShutdown)
|
||||
{
|
||||
// Run through the list of processes and determine which process has been terminated.
|
||||
std::unique_lock<std::mutex> lock(m_mtxProcesses);
|
||||
std::vector<std::shared_ptr<SProcessHelper>> m_vecTerminatedProcesses;
|
||||
for (auto& vtProcess : m_mapProcesses)
|
||||
{
|
||||
// Update only once
|
||||
if (!vtProcess.second->bRunning) continue;
|
||||
|
||||
#ifdef _WIN32
|
||||
DWORD dwExitCode = 0;
|
||||
if (GetExitCodeProcess(vtProcess.second->hProcess, &dwExitCode) && dwExitCode != STILL_ACTIVE)
|
||||
// if (WaitForSingleObject(vtProcess.second->hProcess, 0) == WAIT_OBJECT_0)
|
||||
{
|
||||
// Get the exit code
|
||||
//DWORD dwExitCode = 0;
|
||||
//GetExitCodeProcess(vtProcess.second->hProcess, &dwExitCode);
|
||||
vtProcess.second->iRetVal = static_cast<int>(dwExitCode); // Do not convert to 64-bit!
|
||||
|
||||
// Close the process handle
|
||||
CloseHandle(vtProcess.second->hProcess);
|
||||
vtProcess.second->hProcess = 0;
|
||||
m_vecTerminatedProcesses.push_back(vtProcess.second);
|
||||
|
||||
TRACE(GetTimestamp(), "Adding process PID#", vtProcess.second->tProcessID, " to termination map with exit code ", vtProcess.second->iRetVal);
|
||||
}
|
||||
#elif defined __unix__
|
||||
// There are two ways of checking whether a process is still running. If the monitor process is the parent process, the
|
||||
// waitpid function should be used to request the process state. If the process was not created by the parent process,
|
||||
// the kill function returns information about the process.
|
||||
// After termination a process will stay dorment until the parent process has received the exit value of the process.
|
||||
// This is also the reason why the kill function is not working with the parent process, since it is only returning
|
||||
// a result after the process is not available at all any more (also not dorment).
|
||||
// Use a flag to indicate that the process is not a child process of the monitor process.
|
||||
if (!vtProcess.second->bNotAChild)
|
||||
{
|
||||
// Check with waitpid first
|
||||
int iStatus = 0;
|
||||
#ifdef WCONTINUED
|
||||
pid_t pid = waitpid(vtProcess.second->tProcessID, &iStatus, WCONTINUED | WNOHANG);
|
||||
#else
|
||||
pid_t pid = waitpid(vtProcess.second->tProcessID, &iStatus, WNOHANG);
|
||||
#endif
|
||||
switch (pid)
|
||||
{
|
||||
case -1:
|
||||
// Not a child process of the monitor process. Or a signal is returned to the calling process. Use kill instead.
|
||||
vtProcess.second->bNotAChild = true;
|
||||
SDV_LOG_TRACE(getpid(), " Process ", vtProcess.second->tProcessID, " is not child process...");
|
||||
break;
|
||||
case 0:
|
||||
// Process still running, no status available.
|
||||
break;
|
||||
default:
|
||||
if (WIFEXITED(iStatus))
|
||||
{
|
||||
m_vecTerminatedProcesses.push_back(vtProcess.second);
|
||||
vtProcess.second->iRetVal = static_cast<int8_t>(WEXITSTATUS(iStatus));
|
||||
SDV_LOG_TRACE(getpid(), " Normal exit detected for process ", vtProcess.second->tProcessID);
|
||||
}
|
||||
else if (WIFSIGNALED(iStatus))
|
||||
{
|
||||
m_vecTerminatedProcesses.push_back(vtProcess.second);
|
||||
// Note: The status is not reported by the process, since it was terminated without return value.
|
||||
//vtProcess.second->iRetVal = static_cast<int8_t>(WTERMSIG(iStatus));
|
||||
vtProcess.second->iRetVal = -100;
|
||||
SDV_LOG_TRACE(getpid(), " Signalled stop detected for process ", vtProcess.second->tProcessID);
|
||||
}
|
||||
else if (WIFSTOPPED(iStatus))
|
||||
{
|
||||
m_vecTerminatedProcesses.push_back(vtProcess.second);
|
||||
vtProcess.second->iRetVal = static_cast<int8_t>(WSTOPSIG(iStatus));
|
||||
SDV_LOG_TRACE(getpid(), " Terminate exit detected for process ", vtProcess.second->tProcessID);
|
||||
}
|
||||
SDV_LOG_TRACE(GetTimestamp(), "Exit detected with exit code ", vtProcess.second->iRetVal);
|
||||
}
|
||||
}
|
||||
|
||||
// For a process not being the child of the monitor process, check with the kill function.
|
||||
// Note: check for the bNotAChild flag once more, since it might be set by the waitpid analysis.
|
||||
if (vtProcess.second->bNotAChild)
|
||||
{
|
||||
// Use "kill" to detect the existence of the process
|
||||
// Notice: this doesn't kill the process.
|
||||
kill(vtProcess.first, 0);
|
||||
if (errno == ESRCH)
|
||||
{
|
||||
m_vecTerminatedProcesses.push_back(vtProcess.second);
|
||||
|
||||
SDV_LOG_TRACE(getpid(), " Non-child exit detected for process ", vtProcess.second->tProcessID);
|
||||
|
||||
// No exit code available...
|
||||
}
|
||||
}
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
}
|
||||
|
||||
// Allow changes
|
||||
lock.unlock();
|
||||
|
||||
// Is there a vector of terminated process IDs?
|
||||
for (auto ptrTerminatedProcess : m_vecTerminatedProcesses)
|
||||
{
|
||||
std::unique_lock<std::mutex> lockProcess(ptrTerminatedProcess->mtxProcess);
|
||||
|
||||
// Reset the running flag
|
||||
ptrTerminatedProcess->bRunning = false;
|
||||
|
||||
// Call the callback functions
|
||||
// Note: The unregister function might change the pointers in the map. Make a copy.
|
||||
auto mapAssociatedMonitorsCopy = ptrTerminatedProcess->mapAssociatedMonitors;
|
||||
lockProcess.unlock();
|
||||
for (auto& rvtMonitor : mapAssociatedMonitorsCopy)
|
||||
{
|
||||
// Check whether the monitor still exists
|
||||
lockProcess.lock();
|
||||
auto itMonitor = ptrTerminatedProcess->mapAssociatedMonitors.find(rvtMonitor.first);
|
||||
if (itMonitor == ptrTerminatedProcess->mapAssociatedMonitors.end() || !itMonitor->second)
|
||||
{
|
||||
// Monitor not available...
|
||||
lockProcess.unlock();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Allow updates to take place
|
||||
lockProcess.unlock();
|
||||
|
||||
// Call callback
|
||||
rvtMonitor.second->ProcessTerminated(ptrTerminatedProcess->tProcessID, ptrTerminatedProcess->iRetVal);
|
||||
}
|
||||
|
||||
// Inform all waiting processes
|
||||
ptrTerminatedProcess->cvWaitForProcess.notify_all();
|
||||
}
|
||||
|
||||
// Wait for 100ms until next check
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
}
|
||||
|
||||
176
sdv_services/process_control/process_control.h
Normal file
176
sdv_services/process_control/process_control.h
Normal file
@@ -0,0 +1,176 @@
|
||||
/**
|
||||
* @file process_control.h
|
||||
* @author Erik Verhoeven
|
||||
* @brief
|
||||
* @version 1.0
|
||||
* @date 2024-08-16
|
||||
*
|
||||
* @copyright Copyright ZF Friedrichshafen AG (c) 2024
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef PROCESS_CONTROL_H
|
||||
#define PROCESS_CONTROL_H
|
||||
|
||||
#include <interfaces/process.h>
|
||||
#include <support/component_impl.h>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <condition_variable>
|
||||
|
||||
/**
|
||||
* @brief Process control service class
|
||||
*/
|
||||
class CProcessControl : public sdv::CSdvObject, public sdv::IObjectControl, public sdv::process::IProcessInfo,
|
||||
public sdv::process::IProcessLifetime, public sdv::process::IProcessControl
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
CProcessControl() = default;
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
virtual ~CProcessControl() override;
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::IObjectControl)
|
||||
SDV_INTERFACE_ENTRY(sdv::process::IProcessInfo)
|
||||
SDV_INTERFACE_ENTRY(sdv::process::IProcessLifetime)
|
||||
SDV_INTERFACE_CHECK_CONDITION(AllowProcessControl())
|
||||
SDV_INTERFACE_ENTRY(sdv::process::IProcessControl)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
// Object declarations
|
||||
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
|
||||
DECLARE_OBJECT_CLASS_NAME("ProcessControlService")
|
||||
DECLARE_OBJECT_SINGLETON()
|
||||
|
||||
/**
|
||||
* @brief Initialize the object. Overload of sdv::IObjectControl::Initialize.
|
||||
* @param[in] ssObjectConfig Optional configuration string.
|
||||
*/
|
||||
void Initialize(const sdv::u8string& ssObjectConfig) override;
|
||||
|
||||
/**
|
||||
* @brief Get the current status of the object. Overload of sdv::IObjectControl::GetStatus.
|
||||
* @return Return the current status of the object.
|
||||
*/
|
||||
sdv::EObjectStatus GetStatus() const override;
|
||||
|
||||
/**
|
||||
* @brief Set the component operation mode. Overload of sdv::IObjectControl::SetOperationMode.
|
||||
* @param[in] eMode The operation mode, the component should run in.
|
||||
*/
|
||||
void SetOperationMode(sdv::EOperationMode eMode) override;
|
||||
|
||||
// Ignore cppcheck warning for not using dynamic binding when being called through the destructor.
|
||||
// cppcheck-suppress virtualCallInConstructor
|
||||
/**
|
||||
* @brief Shutdown called before the object is destroyed. Overload of sdv::IObjectControl::Shutdown.
|
||||
*/
|
||||
void Shutdown() override;
|
||||
|
||||
/**
|
||||
* @brief Check for the application context mode being main or standalone.
|
||||
* @return Returns 'true' when the process control is allowed; otherwise returns 'false'.
|
||||
*/
|
||||
bool AllowProcessControl() const;
|
||||
|
||||
/**
|
||||
* @brief Gets the process ID of that currently in. Overload of sdv::process::IProcessInfo::GetProcessID.
|
||||
* @return Return the process ID.
|
||||
*/
|
||||
sdv::process::TProcessID GetProcessID() const override;
|
||||
|
||||
/**
|
||||
* @brief Register a process lifetime monitor. Overload of sdv::process::IProcessLifetime::RegisterMonitor.
|
||||
* @param[in] tProcessID Process ID to monitor the lifetime for.
|
||||
* @param[in] pMonitor Pointer to the monitor interface. The monitor should expose the IProcessLifetimeCallback
|
||||
* interface.
|
||||
* @return Returns a non-zero cookie when successful; zero when not.
|
||||
*/
|
||||
virtual uint32_t RegisterMonitor(/*in*/ sdv::process::TProcessID tProcessID, /*in*/ sdv::IInterfaceAccess* pMonitor) override;
|
||||
|
||||
/**
|
||||
* @brief Unregistered a previously registered monitor. Overload of sdv::process::IProcessLifetime::UnregisterMonitor.
|
||||
* @param[in] uiCookie The cookie from the monitor registration.
|
||||
*/
|
||||
virtual void UnregisterMonitor(/*in*/ uint32_t uiCookie) override;
|
||||
|
||||
/**
|
||||
* @brief Wait for a process to finalize. Overload of sdv::process::IProcessLifetime::WaitForTerminate.
|
||||
* @param[in] tProcessID The process ID to wait for.
|
||||
* @param[in] uiWaitMs Maximum time to wait in ms. Could be 0xffffffff to wait indefintely.
|
||||
* @return Returns 'true' when the process was terminated (or isn't running), 'false' when still running and a timeout
|
||||
* has occurred.
|
||||
*/
|
||||
virtual bool WaitForTerminate(/*in*/ sdv::process::TProcessID tProcessID, /*in*/ uint32_t uiWaitMs) override;
|
||||
|
||||
/**
|
||||
* @brief Execute a process. Overload of sdv::process::IProcessControl::Execute.
|
||||
* @param[in] ssModule Module name of the process executable.
|
||||
* @param[in] seqArgs Instantiation arguments to supply to the process.
|
||||
* @param[in] eRights The process rights during instantiation.
|
||||
* @return Returns the process ID or 0 when process creation failed.
|
||||
*/
|
||||
virtual sdv::process::TProcessID Execute(/*in*/ const sdv::u8string& ssModule,
|
||||
/*in*/ const sdv::sequence<sdv::u8string>& seqArgs, /*in*/ sdv::process::EProcessRights eRights) override;
|
||||
|
||||
/**
|
||||
* @brief Terminate the process. Overload of sdv::process::IProcessControl::Terminate.
|
||||
* @attention Use this function as a last resort only. The process will be killed and anything unsaved will render invalid.
|
||||
* @param[in] tProcessID The process ID of the process to terminate.
|
||||
* @return Returns 'true' if termination was successful; returns 'false' if termination was not possible or not allowed.
|
||||
*/
|
||||
virtual bool Terminate(/*in*/ sdv::process::TProcessID tProcessID) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Monitor thread function.
|
||||
*/
|
||||
void MonitorThread();
|
||||
|
||||
sdv::EObjectStatus m_eObjectStatus = sdv::EObjectStatus::initialization_pending; ///< Object status.
|
||||
|
||||
std::mutex m_mtxProcessThreadShutdown; ///< Synchronize access
|
||||
#ifdef _WIN32
|
||||
std::map<sdv::process::TProcessID, std::pair<HANDLE,HANDLE>> m_mapProcessThreadShutdown; ///< Map with process IDs and event handles
|
||||
#elif __unix__
|
||||
std::set<sdv::process::TProcessID> m_setProcessThreadShutdown; ///< Set with process IDs
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Process helper structure
|
||||
*/
|
||||
struct SProcessHelper
|
||||
{
|
||||
sdv::process::TProcessID tProcessID = 0; ///< Process ID
|
||||
#ifdef _WIN32
|
||||
HANDLE hProcess = 0; ///< process handle
|
||||
#elif defined __unix__
|
||||
bool bNotAChild = false; ///< When set, the process is not a child of the monitor process.
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
bool bRunning = true; ///< Set when the process is running and not terminated yet.
|
||||
int64_t iRetVal = 0; ///< Process return value.
|
||||
std::map<uint32_t, sdv::process::IProcessLifetimeCallback*> mapAssociatedMonitors; ///< Map with associated monitors.
|
||||
std::mutex mtxProcess; ///< Mutex for process access.
|
||||
std::condition_variable cvWaitForProcess; ///< Condition variable to wait for process termination.
|
||||
};
|
||||
mutable std::mutex m_mtxProcesses; ///< Access control for monitor map.
|
||||
std::map<sdv::process::TProcessID, std::shared_ptr<SProcessHelper>> m_mapProcesses; ///< Monitor map
|
||||
uint32_t m_uiNextMonCookie = 1; ///< Next monitor cookie
|
||||
std::map<uint32_t, std::shared_ptr<SProcessHelper>> m_mapMonitors; ///< Map with monitors.
|
||||
bool m_bShutdown = false; ///< Set to shutdown the monitor thread.
|
||||
std::thread m_threadMonitor; ///< Monitor thread.
|
||||
};
|
||||
DEFINE_SDV_OBJECT(CProcessControl)
|
||||
|
||||
#endif // !define PROCESS_CONTROL_H
|
||||
Reference in New Issue
Block a user