Precommit (#1)

* first commit

* cleanup
This commit is contained in:
tompzf
2025-11-04 13:28:06 +01:00
committed by GitHub
parent dba45dc636
commit 6ed4b1534e
898 changed files with 256340 additions and 0 deletions

View 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)

View 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));
}
}

View 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