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.

295 lines
7.9KB

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