Dusk Audio Framework
Loading...
Searching...
No Matches
ChildProcess.hpp
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
17#pragma once
18
19#include "Sleep.hpp"
20#include "Time.hpp"
21
22#ifdef DAF_OS_WINDOWS
23# include <string>
24# include <winsock2.h>
25# include <windows.h>
26#else
27# include <cerrno>
28# include <ctime>
29# include <signal.h>
30# include <sys/wait.h>
31#endif
32
33#if defined(DAF_OS_LINUX)
34# include <sys/prctl.h>
35#elif defined(DAF_OS_MAC)
36# include <dispatch/dispatch.h>
37#endif
38
40
41// -----------------------------------------------------------------------------------------------------------
42
43class ChildProcess
44{
45 #ifdef DAF_OS_WINDOWS
46 PROCESS_INFORMATION pinfo;
47 #else
48 pid_t pid;
49 #endif
50
51 #ifdef DAF_OS_MAC
52 static void _proc_exit_handler(void*) { ::kill(::getpid(), SIGTERM); }
53 #endif
54
55public:
56 ChildProcess()
57 #ifdef DAF_OS_WINDOWS
58 : pinfo(CPP_AGGREGATE_INIT(PROCESS_INFORMATION){ INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, 0, 0 })
59 #else
60 : pid(-1)
61 #endif
62 {
63 }
64
65 ~ChildProcess()
66 {
67 stop();
68 }
69
70 #ifdef DAF_OS_WINDOWS
71 bool start(const char* const args[], const WCHAR* const envp = nullptr)
72 #else
73 bool start(const char* const args[], char* const* const envp = nullptr)
74 #endif
75 {
76 #ifdef DAF_OS_WINDOWS
77 std::string cmd;
78
79 for (uint i = 0; args[i] != nullptr; ++i)
80 {
81 if (i != 0)
82 cmd += " ";
83
84 if (args[i][0] != '"' && std::strchr(args[i], ' ') != nullptr)
85 {
86 cmd += "\"";
87 cmd += args[i];
88 cmd += "\"";
89 }
90 else
91 {
92 cmd += args[i];
93 }
94 }
95
96 wchar_t wcmd[PATH_MAX];
97 if (MultiByteToWideChar(CP_UTF8, 0, cmd.data(), -1, wcmd, PATH_MAX) <= 0)
98 return false;
99
100 STARTUPINFOW si = {};
101 si.cb = sizeof(si);
102
103 d_stdout("will start process with args '%s'", cmd.data());
104
105 return CreateProcessW(nullptr, // lpApplicationName
106 wcmd, // lpCommandLine
107 nullptr, // lpProcessAttributes
108 nullptr, // lpThreadAttributes
109 TRUE, // bInheritHandles
110 CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, // dwCreationFlags
111 const_cast<LPWSTR>(envp), // lpEnvironment
112 nullptr, // lpCurrentDirectory
113 &si, // lpStartupInfo
114 &pinfo) != FALSE;
115 #else
116 const pid_t ret = pid = vfork();
117
118 switch (ret)
119 {
120 // child process
121 case 0:
122 #if defined(DAF_OS_LINUX)
123 ::prctl(PR_SET_PDEATHSIG, SIGTERM);
124 #elif defined(DAF_OS_MAC)
125 if (const dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC,
126 ::getppid(),
127 DISPATCH_PROC_EXIT,
128 nullptr))
129 {
130 dispatch_source_set_event_handler_f(source, _proc_exit_handler);
131 dispatch_resume(source);
132 }
133 #endif
134
135 if (envp != nullptr)
136 execve(args[0], const_cast<char* const*>(args), envp);
137 else
138 execvp(args[0], const_cast<char* const*>(args));
139
140 d_stderr2("exec failed: %d:%s", errno, std::strerror(errno));
141 _exit(1);
142 break;
143
144 // error
145 case -1:
146 d_stderr2("vfork() failed: %d:%s", errno, std::strerror(errno));
147 break;
148 }
149
150 return ret > 0;
151 #endif
152 }
153
154 void stop(const uint32_t timeoutInMilliseconds = 2000)
155 {
156 const uint32_t timeout = d_gettime_ms() + timeoutInMilliseconds;
157 bool sendTerminate = true;
158
159 #ifdef DAF_OS_WINDOWS
160 if (pinfo.hProcess == INVALID_HANDLE_VALUE)
161 return;
162
163 const PROCESS_INFORMATION opinfo = pinfo;
164 pinfo = (PROCESS_INFORMATION){ INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, 0, 0 };
165
166 for (DWORD exitCode;;)
167 {
168 if (GetExitCodeProcess(opinfo.hProcess, &exitCode) == FALSE ||
169 exitCode != STILL_ACTIVE ||
170 WaitForSingleObject(opinfo.hProcess, 0) != WAIT_TIMEOUT)
171 {
172 CloseHandle(opinfo.hThread);
173 CloseHandle(opinfo.hProcess);
174 return;
175 }
176
177 if (sendTerminate)
178 {
179 sendTerminate = false;
180 TerminateProcess(opinfo.hProcess, ERROR_BROKEN_PIPE);
181 }
182
183 if (d_gettime_ms() < timeout)
184 {
185 d_msleep(5);
186 continue;
187 }
188 d_stderr("ChildProcess::stop() - timed out");
189 TerminateProcess(opinfo.hProcess, 9);
190 d_msleep(5);
191 CloseHandle(opinfo.hThread);
192 CloseHandle(opinfo.hProcess);
193 break;
194 }
195 #else
196 if (pid <= 0)
197 return;
198
199 const pid_t opid = pid;
200 pid = -1;
201
202 for (pid_t ret;;)
203 {
204 try {
205 ret = ::waitpid(opid, nullptr, WNOHANG);
206 } DAF_SAFE_EXCEPTION_BREAK("waitpid");
207
208 switch (ret)
209 {
210 case -1:
211 if (errno == ECHILD)
212 {
213 // success, child doesn't exist
214 return;
215 }
216 else
217 {
218 d_stderr("ChildProcess::stop() - waitpid failed: %d:%s", errno, std::strerror(errno));
219 return;
220 }
221 break;
222
223 case 0:
224 if (sendTerminate)
225 {
226 sendTerminate = false;
227 kill(opid, SIGTERM);
228 }
229 if (d_gettime_ms() < timeout)
230 {
231 d_msleep(5);
232 continue;
233 }
234
235 d_stderr("ChildProcess::stop() - timed out");
236 kill(opid, SIGKILL);
237 waitpid(opid, nullptr, WNOHANG);
238 break;
239
240 default:
241 if (ret == opid)
242 {
243 // success
244 return;
245 }
246 else
247 {
248 d_stderr("ChildProcess::stop() - got wrong pid %i (requested was %i)", int(ret), int(opid));
249 return;
250 }
251 }
252
253 break;
254 }
255 #endif
256 }
257
258 bool isRunning()
259 {
260 #ifdef DAF_OS_WINDOWS
261 if (pinfo.hProcess == INVALID_HANDLE_VALUE)
262 return false;
263
264 DWORD exitCode;
265 if (GetExitCodeProcess(pinfo.hProcess, &exitCode) == FALSE ||
266 exitCode != STILL_ACTIVE ||
267 WaitForSingleObject(pinfo.hProcess, 0) != WAIT_TIMEOUT)
268 {
269 const PROCESS_INFORMATION opinfo = pinfo;
270 pinfo = (PROCESS_INFORMATION){ INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, 0, 0 };
271 CloseHandle(opinfo.hThread);
272 CloseHandle(opinfo.hProcess);
273 return false;
274 }
275
276 return true;
277 #else
278 if (pid <= 0)
279 return false;
280
281 const pid_t ret = ::waitpid(pid, nullptr, WNOHANG);
282
283 if (ret == pid || (ret == -1 && errno == ECHILD))
284 {
285 pid = 0;
286 return false;
287 }
288
289 return true;
290 #endif
291 }
292
293 #ifndef DAF_OS_WINDOWS
294 void signal(const int sig)
295 {
296 if (pid > 0)
297 kill(pid, sig);
298 }
299 #endif
300
301 void terminate()
302 {
303 #ifdef DAF_OS_WINDOWS
304 if (pinfo.hProcess != INVALID_HANDLE_VALUE)
305 TerminateProcess(pinfo.hProcess, 15);
306 #else
307 if (pid > 0)
308 kill(pid, SIGTERM);
309 #endif
310 }
311
312 DAF_DECLARE_NON_COPYABLE(ChildProcess)
313};
314
315// -----------------------------------------------------------------------------------------------------------
316
#define END_NAMESPACE_DAF
Definition DafInfo.hpp:996
#define START_NAMESPACE_DAF
Definition DafInfo.hpp:990