// SPDX-FileCopyrightText: 2023-2024 MOD Audio UG // SPDX-License-Identifier: AGPL-3.0-or-later #pragma once #include "Sleep.hpp" #include "Time.hpp" #ifdef DISTRHO_OS_WINDOWS # include # include # include #else # include # include # include # include #endif START_NAMESPACE_DISTRHO // ----------------------------------------------------------------------------------------------------------- class ChildProcess { #ifdef _WIN32 PROCESS_INFORMATION pinfo = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, 0, 0 }; #else pid_t pid = -1; #endif public: ChildProcess() { } ~ChildProcess() { stop(); } #ifdef _WIN32 bool start(const char* const args[], const WCHAR* const envp) #else bool start(const char* const args[], char* const* const envp = nullptr) #endif { #ifdef _WIN32 std::string cmd; for (uint i = 0; args[i] != nullptr; ++i) { if (i != 0) cmd += " "; if (args[i][0] != '"' && std::strchr(args[i], ' ') != nullptr) { cmd += "\""; cmd += args[i]; cmd += "\""; } else { cmd += args[i]; } } wchar_t wcmd[PATH_MAX]; if (MultiByteToWideChar(CP_UTF8, 0, cmd.data(), -1, wcmd, PATH_MAX) <= 0) return false; STARTUPINFOW si = {}; si.cb = sizeof(si); d_stdout("will start process with args '%s'", cmd.data()); return CreateProcessW(nullptr, // lpApplicationName wcmd, // lpCommandLine nullptr, // lpProcessAttributes nullptr, // lpThreadAttributes TRUE, // bInheritHandles CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, // dwCreationFlags const_cast(envp), // lpEnvironment nullptr, // lpCurrentDirectory &si, // lpStartupInfo &pinfo) != FALSE; #else const pid_t ret = pid = vfork(); switch (ret) { // child process case 0: if (envp != nullptr) execve(args[0], const_cast(args), envp); else execvp(args[0], const_cast(args)); d_stderr2("exec failed: %d:%s", errno, std::strerror(errno)); _exit(1); break; // error case -1: d_stderr2("vfork() failed: %d:%s", errno, std::strerror(errno)); break; } return ret > 0; #endif } void stop(const uint32_t timeoutInMilliseconds = 2000) { const uint32_t timeout = d_gettime_ms() + timeoutInMilliseconds; bool sendTerminate = true; #ifdef _WIN32 if (pinfo.hProcess == INVALID_HANDLE_VALUE) return; const PROCESS_INFORMATION opinfo = pinfo; pinfo = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, 0, 0 }; for (DWORD exitCode;;) { if (GetExitCodeProcess(opinfo.hProcess, &exitCode) == FALSE || exitCode != STILL_ACTIVE || WaitForSingleObject(opinfo.hProcess, 0) != WAIT_TIMEOUT) { CloseHandle(opinfo.hThread); CloseHandle(opinfo.hProcess); return; } if (sendTerminate) { sendTerminate = false; TerminateProcess(opinfo.hProcess, ERROR_BROKEN_PIPE); } if (d_gettime_ms() < timeout) { d_msleep(5); continue; } d_stderr("ChildProcess::stop() - timed out"); TerminateProcess(opinfo.hProcess, 9); d_msleep(5); CloseHandle(opinfo.hThread); CloseHandle(opinfo.hProcess); break; } #else if (pid <= 0) return; const pid_t opid = pid; pid = -1; for (pid_t ret;;) { try { ret = ::waitpid(opid, nullptr, WNOHANG); } DISTRHO_SAFE_EXCEPTION_BREAK("waitpid"); switch (ret) { case -1: if (errno == ECHILD) { // success, child doesn't exist return; } else { d_stderr("ChildProcess::stop() - waitpid failed: %d:%s", errno, std::strerror(errno)); return; } break; case 0: if (sendTerminate) { sendTerminate = false; kill(opid, SIGTERM); } if (d_gettime_ms() < timeout) { d_msleep(5); continue; } d_stderr("ChildProcess::stop() - timed out"); kill(opid, SIGKILL); waitpid(opid, nullptr, WNOHANG); break; default: if (ret == opid) { // success return; } else { d_stderr("ChildProcess::stop() - got wrong pid %i (requested was %i)", int(ret), int(opid)); return; } } break; } #endif } bool isRunning() { #ifdef _WIN32 if (pinfo.hProcess == INVALID_HANDLE_VALUE) return false; DWORD exitCode; if (GetExitCodeProcess(pinfo.hProcess, &exitCode) == FALSE || exitCode != STILL_ACTIVE || WaitForSingleObject(pinfo.hProcess, 0) != WAIT_TIMEOUT) { const PROCESS_INFORMATION opinfo = pinfo; pinfo = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, 0, 0 }; CloseHandle(opinfo.hThread); CloseHandle(opinfo.hProcess); return false; } return true; #else if (pid <= 0) return false; const pid_t ret = ::waitpid(pid, nullptr, WNOHANG); if (ret == pid || (ret == -1 && errno == ECHILD)) { pid = 0; return false; } return true; #endif } #ifndef _WIN32 void signal(const int sig) { if (pid > 0) kill(pid, sig); } #endif void terminate() { #ifdef _WIN32 if (pinfo.hProcess != INVALID_HANDLE_VALUE) TerminateProcess(pinfo.hProcess, 15); #else if (pid > 0) kill(pid, SIGTERM); #endif } DISTRHO_DECLARE_NON_COPYABLE(ChildProcess) }; // ----------------------------------------------------------------------------------------------------------- END_NAMESPACE_DISTRHO