Collection of DPF-based plugins for packaging
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

277 lines
7.1KB

  1. // SPDX-FileCopyrightText: 2023-2024 MOD Audio UG
  2. // SPDX-License-Identifier: AGPL-3.0-or-later
  3. #pragma once
  4. #include "Sleep.hpp"
  5. #include "Time.hpp"
  6. #ifdef DISTRHO_OS_WINDOWS
  7. # include <string>
  8. # include <winsock2.h>
  9. # include <windows.h>
  10. #else
  11. # include <cerrno>
  12. # include <ctime>
  13. # include <signal.h>
  14. # include <sys/wait.h>
  15. #endif
  16. START_NAMESPACE_DISTRHO
  17. // -----------------------------------------------------------------------------------------------------------
  18. class ChildProcess
  19. {
  20. #ifdef _WIN32
  21. PROCESS_INFORMATION pinfo = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, 0, 0 };
  22. #else
  23. pid_t pid = -1;
  24. #endif
  25. public:
  26. ChildProcess()
  27. {
  28. }
  29. ~ChildProcess()
  30. {
  31. stop();
  32. }
  33. #ifdef _WIN32
  34. bool start(const char* const args[], const WCHAR* const envp)
  35. #else
  36. bool start(const char* const args[], char* const* const envp = nullptr)
  37. #endif
  38. {
  39. #ifdef _WIN32
  40. std::string cmd;
  41. for (uint i = 0; args[i] != nullptr; ++i)
  42. {
  43. if (i != 0)
  44. cmd += " ";
  45. if (args[i][0] != '"' && std::strchr(args[i], ' ') != nullptr)
  46. {
  47. cmd += "\"";
  48. cmd += args[i];
  49. cmd += "\"";
  50. }
  51. else
  52. {
  53. cmd += args[i];
  54. }
  55. }
  56. wchar_t wcmd[PATH_MAX];
  57. if (MultiByteToWideChar(CP_UTF8, 0, cmd.data(), -1, wcmd, PATH_MAX) <= 0)
  58. return false;
  59. STARTUPINFOW si = {};
  60. si.cb = sizeof(si);
  61. d_stdout("will start process with args '%s'", cmd.data());
  62. return CreateProcessW(nullptr, // lpApplicationName
  63. wcmd, // lpCommandLine
  64. nullptr, // lpProcessAttributes
  65. nullptr, // lpThreadAttributes
  66. TRUE, // bInheritHandles
  67. CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, // dwCreationFlags
  68. const_cast<LPWSTR>(envp), // lpEnvironment
  69. nullptr, // lpCurrentDirectory
  70. &si, // lpStartupInfo
  71. &pinfo) != FALSE;
  72. #else
  73. const pid_t ret = pid = vfork();
  74. switch (ret)
  75. {
  76. // child process
  77. case 0:
  78. if (envp != nullptr)
  79. execve(args[0], const_cast<char* const*>(args), envp);
  80. else
  81. execvp(args[0], const_cast<char* const*>(args));
  82. d_stderr2("exec failed: %d:%s", errno, std::strerror(errno));
  83. _exit(1);
  84. break;
  85. // error
  86. case -1:
  87. d_stderr2("vfork() failed: %d:%s", errno, std::strerror(errno));
  88. break;
  89. }
  90. return ret > 0;
  91. #endif
  92. }
  93. void stop(const uint32_t timeoutInMilliseconds = 2000)
  94. {
  95. const uint32_t timeout = d_gettime_ms() + timeoutInMilliseconds;
  96. bool sendTerminate = true;
  97. #ifdef _WIN32
  98. if (pinfo.hProcess == INVALID_HANDLE_VALUE)
  99. return;
  100. const PROCESS_INFORMATION opinfo = pinfo;
  101. pinfo = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, 0, 0 };
  102. for (DWORD exitCode;;)
  103. {
  104. if (GetExitCodeProcess(opinfo.hProcess, &exitCode) == FALSE ||
  105. exitCode != STILL_ACTIVE ||
  106. WaitForSingleObject(opinfo.hProcess, 0) != WAIT_TIMEOUT)
  107. {
  108. CloseHandle(opinfo.hThread);
  109. CloseHandle(opinfo.hProcess);
  110. return;
  111. }
  112. if (sendTerminate)
  113. {
  114. sendTerminate = false;
  115. TerminateProcess(opinfo.hProcess, ERROR_BROKEN_PIPE);
  116. }
  117. if (d_gettime_ms() < timeout)
  118. {
  119. d_msleep(5);
  120. continue;
  121. }
  122. d_stderr("ChildProcess::stop() - timed out");
  123. TerminateProcess(opinfo.hProcess, 9);
  124. d_msleep(5);
  125. CloseHandle(opinfo.hThread);
  126. CloseHandle(opinfo.hProcess);
  127. break;
  128. }
  129. #else
  130. if (pid <= 0)
  131. return;
  132. const pid_t opid = pid;
  133. pid = -1;
  134. for (pid_t ret;;)
  135. {
  136. try {
  137. ret = ::waitpid(opid, nullptr, WNOHANG);
  138. } DISTRHO_SAFE_EXCEPTION_BREAK("waitpid");
  139. switch (ret)
  140. {
  141. case -1:
  142. if (errno == ECHILD)
  143. {
  144. // success, child doesn't exist
  145. return;
  146. }
  147. else
  148. {
  149. d_stderr("ChildProcess::stop() - waitpid failed: %d:%s", errno, std::strerror(errno));
  150. return;
  151. }
  152. break;
  153. case 0:
  154. if (sendTerminate)
  155. {
  156. sendTerminate = false;
  157. kill(opid, SIGTERM);
  158. }
  159. if (d_gettime_ms() < timeout)
  160. {
  161. d_msleep(5);
  162. continue;
  163. }
  164. d_stderr("ChildProcess::stop() - timed out");
  165. kill(opid, SIGKILL);
  166. waitpid(opid, nullptr, WNOHANG);
  167. break;
  168. default:
  169. if (ret == opid)
  170. {
  171. // success
  172. return;
  173. }
  174. else
  175. {
  176. d_stderr("ChildProcess::stop() - got wrong pid %i (requested was %i)", int(ret), int(opid));
  177. return;
  178. }
  179. }
  180. break;
  181. }
  182. #endif
  183. }
  184. bool isRunning()
  185. {
  186. #ifdef _WIN32
  187. if (pinfo.hProcess == INVALID_HANDLE_VALUE)
  188. return false;
  189. DWORD exitCode;
  190. if (GetExitCodeProcess(pinfo.hProcess, &exitCode) == FALSE ||
  191. exitCode != STILL_ACTIVE ||
  192. WaitForSingleObject(pinfo.hProcess, 0) != WAIT_TIMEOUT)
  193. {
  194. const PROCESS_INFORMATION opinfo = pinfo;
  195. pinfo = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, 0, 0 };
  196. CloseHandle(opinfo.hThread);
  197. CloseHandle(opinfo.hProcess);
  198. return false;
  199. }
  200. return true;
  201. #else
  202. if (pid <= 0)
  203. return false;
  204. const pid_t ret = ::waitpid(pid, nullptr, WNOHANG);
  205. if (ret == pid || (ret == -1 && errno == ECHILD))
  206. {
  207. pid = 0;
  208. return false;
  209. }
  210. return true;
  211. #endif
  212. }
  213. #ifndef _WIN32
  214. void signal(const int sig)
  215. {
  216. if (pid > 0)
  217. kill(pid, sig);
  218. }
  219. #endif
  220. void terminate()
  221. {
  222. #ifdef _WIN32
  223. if (pinfo.hProcess != INVALID_HANDLE_VALUE)
  224. TerminateProcess(pinfo.hProcess, 15);
  225. #else
  226. if (pid > 0)
  227. kill(pid, SIGTERM);
  228. #endif
  229. }
  230. DISTRHO_DECLARE_NON_COPYABLE(ChildProcess)
  231. };
  232. // -----------------------------------------------------------------------------------------------------------
  233. END_NAMESPACE_DISTRHO