Cleanup
[carla.git] / source / modules / water / threads / ChildProcess.cpp
blob28120abab35ecaedfbf77cf5755a229789612814
1 /*
2 ==============================================================================
4 This file is part of the Water library.
5 Copyright (c) 2016 ROLI Ltd.
6 Copyright (C) 2017-2024 Filipe Coelho <falktx@falktx.com>
8 Permission is granted to use this software under the terms of the ISC license
9 http://www.isc.org/downloads/software-support-policy/isc-license/
11 Permission to use, copy, modify, and/or distribute this software for any
12 purpose with or without fee is hereby granted, provided that the above
13 copyright notice and this permission notice appear in all copies.
15 THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD
16 TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
17 FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
18 OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
19 USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
20 TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
21 OF THIS SOFTWARE.
23 ==============================================================================
26 #include "ChildProcess.h"
27 #include "../files/File.h"
28 #include "../misc/Time.h"
30 #ifdef CARLA_OS_MAC
31 # include <crt_externs.h>
32 # include <spawn.h>
33 #endif
35 #ifndef CARLA_OS_WIN
36 # include <signal.h>
37 # include <sys/wait.h>
38 #endif
40 #include "CarlaProcessUtils.hpp"
42 namespace water {
44 #ifdef CARLA_OS_WIN
45 //=====================================================================================================================
46 class ChildProcess::ActiveProcess
48 public:
49 ActiveProcess (const String& command)
50 : ok (false)
52 STARTUPINFOA startupInfo;
53 carla_zeroStruct(startupInfo);
54 startupInfo.cb = sizeof (startupInfo);
56 ok = CreateProcessA (nullptr, const_cast<LPSTR>(command.toRawUTF8()),
57 nullptr, nullptr, TRUE, CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT,
58 nullptr, nullptr, &startupInfo, &processInfo) != FALSE;
61 ~ActiveProcess()
63 closeProcessInfo();
66 void closeProcessInfo() noexcept
68 if (ok)
70 ok = false;
71 CloseHandle (processInfo.hThread);
72 CloseHandle (processInfo.hProcess);
76 bool isRunning() const noexcept
78 return WaitForSingleObject (processInfo.hProcess, 0) != WAIT_OBJECT_0;
81 bool checkRunningAndUnsetPID() noexcept
83 if (isRunning())
84 return true;
86 ok = false;
87 CloseHandle (processInfo.hThread);
88 CloseHandle (processInfo.hProcess);
89 return false;
92 bool killProcess() const noexcept
94 return TerminateProcess (processInfo.hProcess, 0) != FALSE;
97 bool terminateProcess() const noexcept
99 return TerminateProcess (processInfo.hProcess, 0) != FALSE;
102 uint32 getExitCodeAndClearPID() noexcept
104 DWORD exitCode = 0;
105 GetExitCodeProcess (processInfo.hProcess, &exitCode);
106 closeProcessInfo();
107 return (uint32) exitCode;
110 int getPID() const noexcept
112 return 0;
115 bool ok;
117 private:
118 PROCESS_INFORMATION processInfo;
120 CARLA_DECLARE_NON_COPYABLE (ActiveProcess)
122 #else
123 class ChildProcess::ActiveProcess
125 public:
126 ActiveProcess (const StringArray& arguments, const Type type)
127 : childPID (0)
129 String exe (arguments[0].unquoted());
131 // Looks like you're trying to launch a non-existent exe or a folder (perhaps on OSX
132 // you're trying to launch the .app folder rather than the actual binary inside it?)
133 wassert (File::getCurrentWorkingDirectory().getChildFile (exe.toRawUTF8()).existsAsFile()
134 || ! exe.containsChar (CARLA_OS_SEP));
136 Array<char*> argv;
137 for (int i = 0; i < arguments.size(); ++i)
138 if (arguments[i].isNotEmpty())
139 argv.add (const_cast<char*> (arguments[i].toRawUTF8()));
141 argv.add (nullptr);
143 #ifdef CARLA_OS_MAC
144 cpu_type_t pref;
145 pid_t result = -1;
147 switch (type)
149 # ifdef __MAC_10_12
150 case TypeARM:
151 pref = CPU_TYPE_ARM64;
152 break;
153 # endif
154 case TypeIntel:
155 pref = CPU_TYPE_X86_64;
156 break;
157 default:
158 pref = CPU_TYPE_ANY;
159 break;
162 posix_spawnattr_t attr;
163 posix_spawnattr_init(&attr);
164 // posix_spawnattr_setflags(&attr, POSIX_SPAWN_USEVFORK);
165 CARLA_SAFE_ASSERT_RETURN(posix_spawnattr_setbinpref_np(&attr, 1, &pref, nullptr) == 0,);
166 char*** const environptr = _NSGetEnviron();
167 CARLA_SAFE_ASSERT_RETURN(posix_spawn(&result, exe.toRawUTF8(), nullptr, &attr,
168 argv.getRawDataPointer(), environptr != nullptr ? *environptr : nullptr) == 0,);
169 posix_spawnattr_destroy(&attr);
170 #else
171 const pid_t result = vfork();
172 #endif
174 if (result < 0)
176 // error
178 #ifndef CARLA_OS_MAC
179 else if (result == 0)
181 // child process
182 carla_terminateProcessOnParentExit(true);
184 if (execvp (exe.toRawUTF8(), argv.getRawDataPointer()))
185 _exit (-1);
187 #endif
188 else
190 // we're the parent process..
191 childPID = result;
194 #ifndef CARLA_OS_MAC
195 // unused
196 (void)type;
197 #endif
200 ~ActiveProcess()
202 CARLA_SAFE_ASSERT_INT(childPID == 0, childPID);
205 bool isRunning() const noexcept
207 if (childPID != 0)
209 int childState = 0;
210 const int pid = waitpid (childPID, &childState, WNOHANG|WUNTRACED);
211 return pid == 0 || ! (WIFEXITED (childState) || WIFSIGNALED (childState) || WIFSTOPPED (childState));
214 return false;
217 bool checkRunningAndUnsetPID() noexcept
219 if (childPID != 0)
221 int childState = 0;
222 const int pid = waitpid (childPID, &childState, WNOHANG|WUNTRACED);
223 if (pid == 0)
224 return true;
225 if ( ! (WIFEXITED (childState) || WIFSIGNALED (childState) || WIFSTOPPED (childState)))
226 return true;
228 childPID = 0;
229 return false;
232 return false;
235 bool killProcess() noexcept
237 if (::kill (childPID, SIGKILL) == 0)
239 childPID = 0;
240 return true;
243 return false;
246 bool terminateProcess() const noexcept
248 return ::kill (childPID, SIGTERM) == 0;
251 uint32 getExitCodeAndClearPID() noexcept
253 if (childPID != 0)
255 int childState = 0;
256 const int pid = waitpid (childPID, &childState, WNOHANG);
257 childPID = 0;
259 if (pid >= 0 && WIFEXITED (childState))
260 return WEXITSTATUS (childState);
263 return 0;
266 int getPID() const noexcept
268 return childPID;
271 int childPID;
273 private:
274 CARLA_DECLARE_NON_COPYABLE (ActiveProcess)
276 #endif
278 //=====================================================================================================================
280 ChildProcess::ChildProcess() {}
281 ChildProcess::~ChildProcess() {}
283 bool ChildProcess::isRunning() const
285 return activeProcess != nullptr && activeProcess->isRunning();
288 bool ChildProcess::kill()
290 return activeProcess == nullptr || activeProcess->killProcess();
293 bool ChildProcess::terminate()
295 return activeProcess == nullptr || activeProcess->terminateProcess();
298 uint32 ChildProcess::getExitCodeAndClearPID()
300 return activeProcess != nullptr ? activeProcess->getExitCodeAndClearPID() : 0;
303 bool ChildProcess::waitForProcessToFinish (const int timeoutMs)
305 const uint32 timeoutTime = Time::getMillisecondCounter() + (uint32) timeoutMs;
309 if (activeProcess == nullptr)
310 return true;
311 if (! activeProcess->checkRunningAndUnsetPID())
312 return true;
314 carla_msleep(5);
316 while (timeoutMs < 0 || Time::getMillisecondCounter() < timeoutTime);
318 return false;
321 uint32 ChildProcess::getPID() const noexcept
323 return activeProcess != nullptr ? activeProcess->getPID() : 0;
326 //=====================================================================================================================
328 #ifdef CARLA_OS_WIN
329 bool ChildProcess::start (const String& command, Type)
331 activeProcess = new ActiveProcess (command);
333 if (! activeProcess->ok)
334 activeProcess = nullptr;
336 return activeProcess != nullptr;
339 bool ChildProcess::start (const StringArray& args, const Type type)
341 String escaped;
343 for (int i = 0, size = args.size(); i < size; ++i)
345 String arg (args[i]);
347 // If there are spaces, surround it with quotes. If there are quotes,
348 // replace them with \" so that CommandLineToArgv will correctly parse them.
349 if (arg.containsAnyOf ("\" "))
350 arg = arg.replace ("\"", "\\\"").quoted();
352 escaped << arg;
354 if (i+1 < size)
355 escaped << ' ';
358 return start (escaped.trim(), type);
360 #else
361 bool ChildProcess::start (const String& command, const Type type)
363 return start (StringArray::fromTokens (command, true), type);
366 bool ChildProcess::start (const StringArray& args, const Type type)
368 if (args.size() == 0)
369 return false;
371 activeProcess = new ActiveProcess (args, type);
373 if (activeProcess->childPID == 0)
374 activeProcess = nullptr;
376 return activeProcess != nullptr;
378 #endif