1#include <Foundation/FoundationInternal.h>
2PL_FOUNDATION_INTERNAL_HEADER
4#include <Foundation/Logging/Log.h>
5#include <Foundation/System/Process.h>
10 HANDLE m_pipeRead =
nullptr;
11 HANDLE m_pipeWrite =
nullptr;
12 std::thread m_readThread;
13 std::atomic<bool> m_running =
false;
15 bool IsRunning()
const
22 SECURITY_ATTRIBUTES saAttr;
25 saAttr.nLength =
sizeof(SECURITY_ATTRIBUTES);
26 saAttr.bInheritHandle = TRUE;
27 saAttr.lpSecurityDescriptor =
nullptr;
30 if (!CreatePipe(&m_pipeRead, &m_pipeWrite, &saAttr, 0))
34 if (!SetHandleInformation(m_pipeRead, HANDLE_FLAG_INHERIT, 0))
42 CloseHandle(m_pipeWrite);
43 m_pipeWrite =
nullptr;
45 if (m_readThread.joinable())
49 CloseHandle(m_pipeRead);
66 while (szStart < szEnd)
72 ReportString(func, tmp);
80 m_readThread = std::thread([&]()
84 constexpr int BUFSIZE = 512;
89 bool res = ReadFile(m_pipeRead, chBuf, BUFSIZE, &bytesRead,
nullptr);
90 if (!res || bytesRead == 0)
94 ReportString(ref_onStdOut, overflowBuffer);
99 const char* szCurrentPos = chBuf;
100 const char* szEndPos = chBuf + bytesRead;
102 while (szCurrentPos < szEndPos)
110 ReportString(ref_onStdOut, szCurrentPos, szFound + 1);
116 while (szCurrentPos < szFound + 1)
118 overflowBuffer.
PushBack(*szCurrentPos);
122 ReportString(ref_onStdOut, overflowBuffer);
124 overflowBuffer.
Clear();
127 szCurrentPos = szFound + 1;
133 while (szCurrentPos < szEndPos)
135 overflowBuffer.
PushBack(*szCurrentPos);
150 plOsProcessHandle m_ProcessHandle =
nullptr;
151 plOsProcessHandle m_MainThreadHandle =
nullptr;
152 plOsProcessID m_ProcessID = 0;
160 if (m_MainThreadHandle !=
nullptr)
162 CloseHandle(m_MainThreadHandle);
163 m_MainThreadHandle =
nullptr;
166 if (m_ProcessHandle !=
nullptr)
168 CloseHandle(m_ProcessHandle);
169 m_ProcessHandle =
nullptr;
172 m_pipeStdOut.Close();
173 m_pipeStdErr.Close();
177plProcess::plProcess()
182plProcess::~plProcess()
184 if (GetState() == plProcessState::Running)
186 plLog::Dev(
"Process still running - terminating '{}'", m_sProcess);
188 Terminate().IgnoreResult();
196plOsProcessHandle plProcess::GetProcessHandle()
const
198 return m_pImpl->m_ProcessHandle;
201plOsProcessID plProcess::GetProcessID()
const
203 return m_pImpl->m_ProcessID;
206plOsProcessID plProcess::GetCurrentProcessID()
208 const plOsProcessID processID = GetCurrentProcessId();
215static BOOL CreateProcessWithExplicitHandles(LPCWSTR pApplicationName, LPWSTR pCommandLine, LPSECURITY_ATTRIBUTES pProcessAttributes,
216 LPSECURITY_ATTRIBUTES pThreadAttributes, BOOL inheritHandles, DWORD uiCreationFlags, LPVOID pEnvironment, LPCWSTR pCurrentDirectory,
217 LPSTARTUPINFOW pStartupInfo, LPPROCESS_INFORMATION pProcessInformation,
219 DWORD uiHandlesToInherit, HANDLE* pHandlesToInherit)
222 BOOL fInitialized = FALSE;
224 LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList =
nullptr;
225 fSuccess = uiHandlesToInherit < 0xFFFFFFFF /
sizeof(HANDLE) && pStartupInfo->cb ==
sizeof(*pStartupInfo);
228 SetLastError(ERROR_INVALID_PARAMETER);
231 if (uiHandlesToInherit > 0)
235 fSuccess = InitializeProcThreadAttributeList(
nullptr, 1, 0, &size) || GetLastError() == ERROR_INSUFFICIENT_BUFFER;
239 lpAttributeList =
reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST
>(HeapAlloc(GetProcessHeap(), 0, size));
240 fSuccess = lpAttributeList !=
nullptr;
244 fSuccess = InitializeProcThreadAttributeList(lpAttributeList, 1, 0, &size);
249 fSuccess = UpdateProcThreadAttribute(
250 lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, pHandlesToInherit, uiHandlesToInherit *
sizeof(HANDLE),
nullptr,
nullptr);
257 ZeroMemory(&info,
sizeof(info));
258 info.StartupInfo = *pStartupInfo;
259 info.StartupInfo.cb =
sizeof(info);
260 info.lpAttributeList = lpAttributeList;
263 fSuccess = CreateProcessW(pApplicationName, pCommandLine, pProcessAttributes, pThreadAttributes, inheritHandles,
264 uiCreationFlags | EXTENDED_STARTUPINFO_PRESENT, pEnvironment, pCurrentDirectory, &info.StartupInfo, pProcessInformation);
267 DeleteProcThreadAttributeList(lpAttributeList);
269 HeapFree(GetProcessHeap(), 0, lpAttributeList);
275 PL_ASSERT_DEV(m_pImpl->m_ProcessHandle ==
nullptr,
"Cannot reuse an instance of plProcess");
276 PL_ASSERT_DEV(m_pImpl->m_ProcessID == 0,
"Cannot reuse an instance of plProcess");
282 m_sProcess = sProcess;
283 m_OnStdOut = opt.m_onStdOut;
284 m_OnStdError = opt.m_onStdError;
289 si.dwFlags = STARTF_FORCEOFFFEEDBACK;
294 HANDLE HandlesToInherit[2];
295 plUInt32 uiNumHandlesToInherit = 0;
297 if (m_OnStdOut.IsValid())
299 m_pImpl->m_pipeStdOut.Create();
300 si.hStdOutput = m_pImpl->m_pipeStdOut.m_pipeWrite;
301 si.dwFlags |= STARTF_USESTDHANDLES;
302 HandlesToInherit[uiNumHandlesToInherit++] = m_pImpl->m_pipeStdOut.m_pipeWrite;
304 if (m_OnStdError.IsValid())
306 m_pImpl->m_pipeStdErr.Create();
307 si.hStdError = m_pImpl->m_pipeStdErr.m_pipeWrite;
308 si.dwFlags |= STARTF_USESTDHANDLES;
309 HandlesToInherit[uiNumHandlesToInherit++] = m_pImpl->m_pipeStdErr.m_pipeWrite;
317 PROCESS_INFORMATION pi;
322 BuildFullCommandLineString(opt, sProcess, sCmdLine);
324 DWORD dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT;
326 if (opt.m_bHideConsoleWindow)
328 dwCreationFlags |= CREATE_NO_WINDOW;
331 if (launchFlags.
IsSet(plProcessLaunchFlags::Suspended))
333 dwCreationFlags |= CREATE_SUSPENDED;
338 if (!CreateProcessWithExplicitHandles(
nullptr,
const_cast<wchar_t*
>(
plStringWChar(sCmdLine).GetData()),
341 uiNumHandlesToInherit > 0 ? TRUE : FALSE,
344 opt.m_sWorkingDirectory.IsEmpty() ? nullptr :
plStringWChar(opt.m_sWorkingDirectory).GetData(),
347 uiNumHandlesToInherit,
351 m_pImpl->m_pipeStdOut.Close();
352 m_pImpl->m_pipeStdErr.Close();
356 m_pImpl->m_pipeStdOut.StartRead(m_OnStdOut);
357 m_pImpl->m_pipeStdErr.StartRead(m_OnStdError);
359 m_pImpl->m_ProcessHandle = pi.hProcess;
360 m_pImpl->m_ProcessID = pi.dwProcessId;
362 if (launchFlags.
IsSet(plProcessLaunchFlags::Suspended))
365 m_pImpl->m_MainThreadHandle = pi.hThread;
369 CloseHandle(pi.hThread);
372 if (launchFlags.
IsSet(plProcessLaunchFlags::Detached))
380plResult plProcess::ResumeSuspended()
382 if (m_pImpl->m_ProcessHandle ==
nullptr || m_pImpl->m_MainThreadHandle ==
nullptr)
385 const DWORD prevSuspendCount = ResumeThread(m_pImpl->m_MainThreadHandle);
386 if (prevSuspendCount != 1)
387 plLog::Warning(
"plProcess::ResumeSuspended: Unexpected ResumeThread result ({})", plUInt64(prevSuspendCount));
390 if (!CloseHandle(m_pImpl->m_MainThreadHandle))
391 plLog::Warning(
"plProcess::ResumeSuspended: Failed to close handle");
392 m_pImpl->m_MainThreadHandle =
nullptr;
399 PL_ASSERT_DEV(m_pImpl->m_ProcessHandle !=
nullptr,
"Launch a process before waiting on it");
400 PL_ASSERT_DEV(m_pImpl->m_ProcessID != 0,
"Launch a process before waiting on it");
402 DWORD dwTimeout = INFINITE;
407 dwTimeout = INFINITE;
409 const DWORD res = WaitForSingleObject(m_pImpl->m_ProcessHandle, dwTimeout);
411 if (res == WAIT_TIMEOUT)
417 if (res == WAIT_FAILED)
425 m_pImpl->m_pipeStdOut.Close();
426 m_pImpl->m_pipeStdErr.Close();
428 GetExitCodeProcess(m_pImpl->m_ProcessHandle,
reinterpret_cast<DWORD*
>(&m_iExitCode));
433plResult plProcess::Execute(
const plProcessOptions& opt, plInt32* out_pExitCode )
437 PL_SUCCEED_OR_RETURN(proc.Launch(opt));
438 PL_SUCCEED_OR_RETURN(proc.WaitToFinish());
440 if (out_pExitCode !=
nullptr)
442 *out_pExitCode = proc.GetExitCode();
450 PL_ASSERT_DEV(m_pImpl->m_ProcessHandle !=
nullptr,
"Launch a process before terminating it");
451 PL_ASSERT_DEV(m_pImpl->m_ProcessID != 0,
"Launch a process before terminating it");
453 if (TerminateProcess(m_pImpl->m_ProcessHandle, 0xFFFFFFFF) == FALSE)
455 const DWORD err = GetLastError();
456 if (err == ERROR_ACCESS_DENIED)
463 PL_SUCCEED_OR_RETURN(WaitToFinish());
468plProcessState plProcess::GetState()
const
470 if (m_pImpl->m_ProcessHandle == 0)
471 return plProcessState::NotStarted;
474 if (GetExitCodeProcess(m_pImpl->m_ProcessHandle, &exitCode) == FALSE)
480 return plProcessState::Finished;
483 if (exitCode == STILL_ACTIVE)
484 return plProcessState::Running;
486 if (m_ProcessExited.IsZero())
492 if (m_pImpl->m_pipeStdOut.IsRunning() || m_pImpl->m_pipeStdErr.IsRunning())
496 return plProcessState::Running;
499 m_pImpl->m_pipeStdOut.Close();
500 m_pImpl->m_pipeStdErr.Close();
503 m_iExitCode = (plInt32)exitCode;
504 return plProcessState::Finished;
507void plProcess::Detach()
513 m_iExitCode = -0xFFFF;
void PushBack(const T &value)
Pushes value at the end of the array.
Definition ArrayBase_inl.h:333
T * GetData()
Returns a pointer to the array data, or nullptr if the array is empty.
Definition ArrayBase_inl.h:423
void Clear()
Clears the array.
Definition ArrayBase_inl.h:184
bool IsEmpty() const
Returns true, if the array does not contain any elements.
Definition ArrayBase_inl.h:178
plUInt32 GetCount() const
Returns the number of active elements in the array.
Definition ArrayBase_inl.h:172
A hybrid array uses in-place storage to handle the first few elements without any allocation....
Definition HybridArray.h:12
static void Dev(plLogInterface *pInterface, const plFormatString &string)
Status information that is nice to have during development.
static void Error(plLogInterface *pInterface, const plFormatString &string)
An error that needs to be fixed as soon as possible.
Definition Log.cpp:375
static void Warning(plLogInterface *pInterface, const plFormatString &string)
A potential problem or a performance warning. Might be possible to ignore it.
Definition Log.cpp:391
static void ZeroFill(T *pDestination, size_t uiCount=1)
Zeros every byte in the provided memory buffer.
plStringBuilder is a class that is meant for creating and modifying strings.
Definition StringBuilder.h:35
void MakeCleanPath()
Removes "../" where possible, replaces all path separators with /, removes double slashes.
Definition StringBuilder.cpp:751
plUInt32 ReplaceAll(plStringView sSearchFor, plStringView sReplacement)
Replaces all occurrences of szSearchFor by szReplacement. Returns the number of replacements.
Definition StringBuilder.cpp:537
static const char * FindSubString(const char *szSource, const char *szStringToFind, const char *pSourceEnd=plUnicodeUtils::GetMaxStringEnd< char >(), const char *szStringToFindEnd=plUnicodeUtils::GetMaxStringEnd< char >())
Searches for the first occurrence of szStringToFind in szSource.
Definition StringUtils.cpp:550
plStringView represent a read-only sub-string of a larger string, as it can store a dedicated string ...
Definition StringView.h:34
A very simple string class that should only be used to temporarily convert text to the OSes native wc...
Definition StringConversion.h:20
static bool RepairNonUtf8Text(const char *pStartData, const char *pEndData, Container &out_result)
Checks an array of char's, whether it is a valid Utf8 string. If not, it repairs the string,...
Definition StringBuilder_inl.h:213
Converts a windows HRESULT into an error code and a human-readable error message. Pass in GetLastErro...
Definition FormatStringArgs.h:140
Wraps a string that may contain sensitive information, such as user file paths.
Definition FormatStringArgs.h:177
The plBitflags class allows you to work with type-safe bitflags.
Definition Bitflags.h:82
PL_ALWAYS_INLINE bool IsSet(Enum flag) const
Checks if certain flags are set within the bitfield.
Definition Bitflags.h:127
A generic delegate class which supports static functions and member functions.
Definition Delegate.h:76
Definition Process_win.h:9
Definition Process_posix.h:152
Default enum for returning failure or success, instead of using a bool.
Definition Types.h:54
The time class encapsulates a double value storing the time in seconds.
Definition Time.h:12
PL_ALWAYS_INLINE constexpr bool IsPositive() const
Checks for a positive time value. This does not include zero.
Definition Time.h:56
PL_ALWAYS_INLINE static constexpr plTime MakeFromSeconds(double fSeconds)
Creates an instance of plTime that was initialized from seconds.
Definition Time.h:30
static plTime Now()
Gets the current time.
Definition Time_Posix.h:12
constexpr double GetMilliseconds() const
Returns the milliseconds value.
Definition Time_inl.h:25