DISTRHO Plugin Framework
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.

318 lines
8.9KB

  1. /*
  2. * DISTRHO Plugin Framework (DPF)
  3. * Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * Permission to use, copy, modify, and/or distribute this software for any purpose with
  6. * or without fee is hereby granted, provided that the above copyright notice and this
  7. * permission notice appear in all copies.
  8. *
  9. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
  10. * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
  11. * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
  12. * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
  13. * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  14. * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. */
  16. #pragma once
  17. #include "Sleep.hpp"
  18. #include "Time.hpp"
  19. #ifdef DISTRHO_OS_WINDOWS
  20. # include <string>
  21. # include <winsock2.h>
  22. # include <windows.h>
  23. #else
  24. # include <cerrno>
  25. # include <ctime>
  26. # include <signal.h>
  27. # include <sys/wait.h>
  28. #endif
  29. #if defined(DISTRHO_OS_LINUX)
  30. # include <sys/prctl.h>
  31. #elif defined(DISTRHO_OS_MAC)
  32. # include <dispatch/dispatch.h>
  33. #endif
  34. START_NAMESPACE_DISTRHO
  35. // -----------------------------------------------------------------------------------------------------------
  36. class ChildProcess
  37. {
  38. #ifdef DISTRHO_OS_WINDOWS
  39. PROCESS_INFORMATION pinfo;
  40. #else
  41. pid_t pid;
  42. #endif
  43. #ifdef DISTRHO_OS_MAC
  44. static void _proc_exit_handler(void*) { ::kill(::getpid(), SIGTERM); }
  45. #endif
  46. public:
  47. ChildProcess()
  48. #ifdef DISTRHO_OS_WINDOWS
  49. : pinfo(CPP_AGGREGATE_INIT(PROCESS_INFORMATION){ INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, 0, 0 })
  50. #else
  51. : pid(-1)
  52. #endif
  53. {
  54. }
  55. ~ChildProcess()
  56. {
  57. stop();
  58. }
  59. #ifdef DISTRHO_OS_WINDOWS
  60. bool start(const char* const args[], const WCHAR* const envp = nullptr)
  61. #else
  62. bool start(const char* const args[], char* const* const envp = nullptr)
  63. #endif
  64. {
  65. #ifdef DISTRHO_OS_WINDOWS
  66. std::string cmd;
  67. for (uint i = 0; args[i] != nullptr; ++i)
  68. {
  69. if (i != 0)
  70. cmd += " ";
  71. if (args[i][0] != '"' && std::strchr(args[i], ' ') != nullptr)
  72. {
  73. cmd += "\"";
  74. cmd += args[i];
  75. cmd += "\"";
  76. }
  77. else
  78. {
  79. cmd += args[i];
  80. }
  81. }
  82. wchar_t wcmd[PATH_MAX];
  83. if (MultiByteToWideChar(CP_UTF8, 0, cmd.data(), -1, wcmd, PATH_MAX) <= 0)
  84. return false;
  85. STARTUPINFOW si = {};
  86. si.cb = sizeof(si);
  87. d_stdout("will start process with args '%s'", cmd.data());
  88. return CreateProcessW(nullptr, // lpApplicationName
  89. wcmd, // lpCommandLine
  90. nullptr, // lpProcessAttributes
  91. nullptr, // lpThreadAttributes
  92. TRUE, // bInheritHandles
  93. CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, // dwCreationFlags
  94. const_cast<LPWSTR>(envp), // lpEnvironment
  95. nullptr, // lpCurrentDirectory
  96. &si, // lpStartupInfo
  97. &pinfo) != FALSE;
  98. #else
  99. const pid_t ret = pid = vfork();
  100. switch (ret)
  101. {
  102. // child process
  103. case 0:
  104. #if defined(DISTRHO_OS_LINUX)
  105. ::prctl(PR_SET_PDEATHSIG, SIGTERM);
  106. #elif defined(DISTRHO_OS_MAC)
  107. if (const dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC,
  108. ::getppid(),
  109. DISPATCH_PROC_EXIT,
  110. nullptr))
  111. {
  112. dispatch_source_set_event_handler_f(source, _proc_exit_handler);
  113. dispatch_resume(source);
  114. }
  115. #endif
  116. if (envp != nullptr)
  117. execve(args[0], const_cast<char* const*>(args), envp);
  118. else
  119. execvp(args[0], const_cast<char* const*>(args));
  120. d_stderr2("exec failed: %d:%s", errno, std::strerror(errno));
  121. _exit(1);
  122. break;
  123. // error
  124. case -1:
  125. d_stderr2("vfork() failed: %d:%s", errno, std::strerror(errno));
  126. break;
  127. }
  128. return ret > 0;
  129. #endif
  130. }
  131. void stop(const uint32_t timeoutInMilliseconds = 2000)
  132. {
  133. const uint32_t timeout = d_gettime_ms() + timeoutInMilliseconds;
  134. bool sendTerminate = true;
  135. #ifdef DISTRHO_OS_WINDOWS
  136. if (pinfo.hProcess == INVALID_HANDLE_VALUE)
  137. return;
  138. const PROCESS_INFORMATION opinfo = pinfo;
  139. pinfo = (PROCESS_INFORMATION){ INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, 0, 0 };
  140. for (DWORD exitCode;;)
  141. {
  142. if (GetExitCodeProcess(opinfo.hProcess, &exitCode) == FALSE ||
  143. exitCode != STILL_ACTIVE ||
  144. WaitForSingleObject(opinfo.hProcess, 0) != WAIT_TIMEOUT)
  145. {
  146. CloseHandle(opinfo.hThread);
  147. CloseHandle(opinfo.hProcess);
  148. return;
  149. }
  150. if (sendTerminate)
  151. {
  152. sendTerminate = false;
  153. TerminateProcess(opinfo.hProcess, ERROR_BROKEN_PIPE);
  154. }
  155. if (d_gettime_ms() < timeout)
  156. {
  157. d_msleep(5);
  158. continue;
  159. }
  160. d_stderr("ChildProcess::stop() - timed out");
  161. TerminateProcess(opinfo.hProcess, 9);
  162. d_msleep(5);
  163. CloseHandle(opinfo.hThread);
  164. CloseHandle(opinfo.hProcess);
  165. break;
  166. }
  167. #else
  168. if (pid <= 0)
  169. return;
  170. const pid_t opid = pid;
  171. pid = -1;
  172. for (pid_t ret;;)
  173. {
  174. try {
  175. ret = ::waitpid(opid, nullptr, WNOHANG);
  176. } DISTRHO_SAFE_EXCEPTION_BREAK("waitpid");
  177. switch (ret)
  178. {
  179. case -1:
  180. if (errno == ECHILD)
  181. {
  182. // success, child doesn't exist
  183. return;
  184. }
  185. else
  186. {
  187. d_stderr("ChildProcess::stop() - waitpid failed: %d:%s", errno, std::strerror(errno));
  188. return;
  189. }
  190. break;
  191. case 0:
  192. if (sendTerminate)
  193. {
  194. sendTerminate = false;
  195. kill(opid, SIGTERM);
  196. }
  197. if (d_gettime_ms() < timeout)
  198. {
  199. d_msleep(5);
  200. continue;
  201. }
  202. d_stderr("ChildProcess::stop() - timed out");
  203. kill(opid, SIGKILL);
  204. waitpid(opid, nullptr, WNOHANG);
  205. break;
  206. default:
  207. if (ret == opid)
  208. {
  209. // success
  210. return;
  211. }
  212. else
  213. {
  214. d_stderr("ChildProcess::stop() - got wrong pid %i (requested was %i)", int(ret), int(opid));
  215. return;
  216. }
  217. }
  218. break;
  219. }
  220. #endif
  221. }
  222. bool isRunning()
  223. {
  224. #ifdef DISTRHO_OS_WINDOWS
  225. if (pinfo.hProcess == INVALID_HANDLE_VALUE)
  226. return false;
  227. DWORD exitCode;
  228. if (GetExitCodeProcess(pinfo.hProcess, &exitCode) == FALSE ||
  229. exitCode != STILL_ACTIVE ||
  230. WaitForSingleObject(pinfo.hProcess, 0) != WAIT_TIMEOUT)
  231. {
  232. const PROCESS_INFORMATION opinfo = pinfo;
  233. pinfo = (PROCESS_INFORMATION){ INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, 0, 0 };
  234. CloseHandle(opinfo.hThread);
  235. CloseHandle(opinfo.hProcess);
  236. return false;
  237. }
  238. return true;
  239. #else
  240. if (pid <= 0)
  241. return false;
  242. const pid_t ret = ::waitpid(pid, nullptr, WNOHANG);
  243. if (ret == pid || (ret == -1 && errno == ECHILD))
  244. {
  245. pid = 0;
  246. return false;
  247. }
  248. return true;
  249. #endif
  250. }
  251. #ifndef DISTRHO_OS_WINDOWS
  252. void signal(const int sig)
  253. {
  254. if (pid > 0)
  255. kill(pid, sig);
  256. }
  257. #endif
  258. void terminate()
  259. {
  260. #ifdef DISTRHO_OS_WINDOWS
  261. if (pinfo.hProcess != INVALID_HANDLE_VALUE)
  262. TerminateProcess(pinfo.hProcess, 15);
  263. #else
  264. if (pid > 0)
  265. kill(pid, SIGTERM);
  266. #endif
  267. }
  268. DISTRHO_DECLARE_NON_COPYABLE(ChildProcess)
  269. };
  270. // -----------------------------------------------------------------------------------------------------------
  271. END_NAMESPACE_DISTRHO