diff --git a/rx/include/rx/Process.hpp b/rx/include/rx/Process.hpp index 8a135a199..888844f4c 100644 --- a/rx/include/rx/Process.hpp +++ b/rx/include/rx/Process.hpp @@ -1,6 +1,8 @@ #pragma once #include +#include +#include namespace rx { using ProcessId = std::uint32_t; @@ -27,4 +29,21 @@ ProcessId getCurrentProcessId(); ThreadId getCurrentThreadId(); ProcessHandle getCurrentProcessHandle(); ThreadHandle getCurrentThreadHandle(); + +// Spawn a new process with the given executable and arguments +// Returns the process ID on success, or -1 on failure +ProcessId spawn(const std::string &executable, + const std::vector &args = {}); + +// Wait for a process to exit and return its exit code +// Returns true if the process exited normally, false otherwise +bool waitProcess(ProcessId pid, int *exitCode = nullptr); + +// Suspend a process +// Returns true on success, false otherwise +bool suspendProcess(ProcessId pid); + +// Resume a suspended process +// Returns true on success, false otherwise +bool resumeProcess(ProcessId pid); } // namespace rx diff --git a/rx/src/Process.cpp b/rx/src/Process.cpp index ac4319ce0..4aee33196 100644 --- a/rx/src/Process.cpp +++ b/rx/src/Process.cpp @@ -1,9 +1,13 @@ #include +#include +#include + #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #else +#include #include #endif @@ -48,3 +52,174 @@ rx::ThreadHandle rx::getCurrentThreadHandle() { return getCurrentThreadId(); #endif } + +rx::ProcessId rx::spawn(const std::string &executable, + const std::vector &args) { +#ifdef _WIN32 + // Build command line + std::string cmdLine = "\"" + executable + "\""; + for (const auto &arg : args) { + cmdLine += " \"" + arg + "\""; + } + + // Convert to wide string + int wideSize = + ::MultiByteToWideChar(CP_UTF8, 0, cmdLine.c_str(), -1, nullptr, 0); + if (wideSize <= 0) { + return static_cast(-1); + } + + std::vector wideCmdLine(wideSize); + ::MultiByteToWideChar(CP_UTF8, 0, cmdLine.c_str(), -1, wideCmdLine.data(), + wideSize); + + STARTUPINFOW si{}; + si.cb = sizeof(si); + PROCESS_INFORMATION pi{}; + + if (!::CreateProcessW(nullptr, wideCmdLine.data(), nullptr, nullptr, FALSE, 0, + nullptr, nullptr, &si, &pi)) { + return static_cast(-1); + } + + ProcessId pid = ::GetProcessId(pi.hProcess); + ::CloseHandle(pi.hThread); + ::CloseHandle(pi.hProcess); + + return pid; +#else + // fork + execv + pid_t pid = ::fork(); + if (pid < 0) { + return static_cast(-1); + } + + if (pid == 0) { + // child process + std::vector argv; + argv.reserve(args.size() + 2); + + argv.push_back(const_cast(executable.c_str())); + + // Add arguments + for (const auto &arg : args) { + argv.push_back(const_cast(arg.c_str())); + } + + argv.push_back(nullptr); + + ::execv(executable.c_str(), argv.data()); + // If execv returns, it failed + ::_exit(127); + } + + // parent process + return static_cast(pid); +#endif +} + +bool rx::waitProcess(ProcessId pid, int *exitCode) { +#ifdef _WIN32 + HANDLE hProcess = ::OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, + FALSE, static_cast(pid)); + if (!hProcess) { + return false; + } + + DWORD result = ::WaitForSingleObject(hProcess, INFINITE); + if (result != WAIT_OBJECT_0) { + ::CloseHandle(hProcess); + return false; + } + + if (exitCode) { + DWORD code = 0; + if (::GetExitCodeProcess(hProcess, &code)) { + *exitCode = static_cast(code); + } else { + *exitCode = -1; + } + } + + ::CloseHandle(hProcess); + return true; +#else + int status = 0; + pid_t result = ::waitpid(static_cast(pid), &status, 0); + if (result == -1) { + return false; + } + + if (exitCode) { + if (WIFEXITED(status)) { + *exitCode = WEXITSTATUS(status); + } else if (WIFSIGNALED(status)) { + *exitCode = 128 + WTERMSIG(status); + } else { + *exitCode = -1; + } + } + + return WIFEXITED(status) || WIFSIGNALED(status); +#endif +} + +bool rx::suspendProcess(ProcessId pid) { +#ifdef _WIN32 + // Use NtSuspendProcess from ntdll.dll + using NtSuspendProcessFn = long(__stdcall *)(HANDLE); + static NtSuspendProcessFn ntSuspend = [] { + if (auto mod = ::GetModuleHandleW(L"ntdll.dll")) { + return reinterpret_cast( + ::GetProcAddress(mod, "NtSuspendProcess")); + } + return static_cast(nullptr); + }(); + + if (!ntSuspend) { + return false; + } + + HANDLE hProcess = + ::OpenProcess(PROCESS_SUSPEND_RESUME, FALSE, static_cast(pid)); + if (!hProcess) { + return false; + } + + long status = ntSuspend(hProcess); + ::CloseHandle(hProcess); + return status >= 0; +#else + return ::kill(static_cast(pid), SIGSTOP) == 0; +#endif +} + +bool rx::resumeProcess(ProcessId pid) { +#ifdef _WIN32 + // Use NtResumeProcess from ntdll.dll + using NtResumeProcessFn = long(__stdcall *)(HANDLE); + static NtResumeProcessFn ntResume = [] { + if (auto mod = ::GetModuleHandleW(L"ntdll.dll")) { + return reinterpret_cast( + ::GetProcAddress(mod, "NtResumeProcess")); + } + return static_cast(nullptr); + }(); + + if (!ntResume) { + return false; + } + + HANDLE hProcess = + ::OpenProcess(PROCESS_SUSPEND_RESUME, FALSE, static_cast(pid)); + if (!hProcess) { + return false; + } + + long status = ntResume(hProcess); + ::CloseHandle(hProcess); + return status >= 0; +#else + return ::kill(static_cast(pid), SIGCONT) == 0; +#endif +}