Refactor platform process startup with CreateProcessSettings

This commit is contained in:
Wojtek Figat
2023-03-22 14:09:20 +01:00
parent 497aca829d
commit 3bbc7faf11
11 changed files with 351 additions and 281 deletions

View File

@@ -6,6 +6,7 @@
#include "Engine/Platform/Window.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/CreateWindowSettings.h"
#include "Engine/Platform/CreateProcessSettings.h"
#include "Engine/Platform/WindowsManager.h"
#include "Engine/Platform/MemoryStats.h"
#include "Engine/Platform/BatteryInfo.h"
@@ -23,6 +24,7 @@
#include <Psapi.h>
#include <objbase.h>
#include <cstdio>
#undef ShellExecute
#if CRASH_LOG_ENABLE
#include <dbghelp.h>
#endif
@@ -307,7 +309,7 @@ LONG CALLBACK SehExceptionHandler(EXCEPTION_POINTERS* ep)
// Log exception and return to the crash location when using debugger
if (Platform::IsDebuggerPresent())
{
{
LOG_STR(Error, errorMsg);
const String stackTrace = Platform::GetStackTrace(0, 60, ep);
LOG_STR(Error, stackTrace);
@@ -951,54 +953,12 @@ bool WindowsPlatform::SetEnvironmentVariable(const String& name, const String& v
return false;
}
int32 WindowsPlatform::StartProcess(const StringView& filename, const StringView& args, const StringView& workingDir, bool hiddenWindow, bool waitForEnd)
{
// Info
LOG(Info, "Command: {0} {1}", filename, args);
if (workingDir.HasChars())
{
LOG(Info, "Working directory: {0}", workingDir);
}
String filenameString(filename);
SHELLEXECUTEINFOW shExecInfo = { 0 };
shExecInfo.cbSize = sizeof(SHELLEXECUTEINFOW);
shExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
shExecInfo.lpFile = filenameString.GetText();
shExecInfo.lpParameters = args.HasChars() ? args.Get() : nullptr;
shExecInfo.lpDirectory = workingDir.HasChars() ? workingDir.Get() : nullptr;
shExecInfo.nShow = hiddenWindow ? SW_HIDE : SW_SHOW;
if (ShellExecuteExW(&shExecInfo) == FALSE)
{
LOG(Warning, "Cannot start process '{0}' with arguments '{2}'. Error code: {1:x}", filename, (int64)GetLastError(), args);
return 1;
}
int32 result = 0;
if (waitForEnd)
{
WaitForSingleObject(shExecInfo.hProcess, INFINITE);
DWORD exitCode;
if (GetExitCodeProcess(shExecInfo.hProcess, &exitCode) != 0)
result = exitCode;
CloseHandle(shExecInfo.hProcess);
}
return result;
}
int32 WindowsPlatform::RunProcess(const StringView& cmdLine, const StringView& workingDir, bool hiddenWindow)
{
return RunProcess(cmdLine, workingDir, Dictionary<String, String>(), hiddenWindow);
}
bool IsProcRunning(HANDLE handle)
{
return WaitForSingleObject(handle, 0) == WAIT_TIMEOUT;
}
void ReadPipe(HANDLE pipe, Array<char>& rawData, Array<Char>& logData, LogType logType)
void ReadPipe(HANDLE pipe, Array<char>& rawData, Array<Char>& logData, LogType logType, CreateProcessSettings& settings)
{
// Check if any data is ready to read
DWORD bytesAvailable = 0;
@@ -1022,162 +982,183 @@ void ReadPipe(HANDLE pipe, Array<char>& rawData, Array<Char>& logData, LogType l
logData.Resize(rawData.Count() + 1);
StringUtils::ConvertANSI2UTF16(rawData.Get(), logData.Get(), rawData.Count());
logData.Last() = '\0';
Log::Logger::Write(logType, StringView(logData.Get(), rawData.Count()));
if (settings.LogOutput)
Log::Logger::Write(logType, StringView(logData.Get(), rawData.Count()));
if (settings.SaveOutput)
settings.Output.Add(logData.Get(), rawData.Count());
}
}
}
int32 WindowsPlatform::RunProcess(const StringView& cmdLine, const StringView& workingDir, const Dictionary<String, String>& environment, bool hiddenWindow)
int32 WindowsPlatform::CreateProcess(CreateProcessSettings& settings)
{
const bool captureStdOut = true;
// Info
LOG(Info, "Command: {0}", cmdLine);
if (workingDir.HasChars())
LOG(Info, "Command: {0} {1}", settings.FileName, settings.Arguments);
if (settings.WorkingDirectory.HasChars())
{
LOG(Info, "Working directory: {0}", workingDir);
LOG(Info, "Working directory: {0}", settings.WorkingDirectory);
}
const bool captureStdOut = settings.LogOutput || settings.SaveOutput;
int32 result = -1;
STARTUPINFOEX startupInfoEx;
ZeroMemory(&startupInfoEx, sizeof(startupInfoEx));
startupInfoEx.StartupInfo.cb = sizeof(startupInfoEx);
if (hiddenWindow)
int32 result = 0;
if (settings.ShellExecute)
{
startupInfoEx.StartupInfo.dwFlags |= STARTF_USESHOWWINDOW;
startupInfoEx.StartupInfo.wShowWindow |= SW_HIDE | SW_SHOWNOACTIVATE;
}
DWORD dwCreationFlags = NORMAL_PRIORITY_CLASS | DETACHED_PROCESS;
if (hiddenWindow)
dwCreationFlags |= CREATE_NO_WINDOW;
Char* environmentStr = nullptr;
if (environment.HasItems())
{
dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
int32 totalLength = 1;
for (auto& e : environment)
totalLength += e.Key.Length() + e.Value.Length() + 2;
environmentStr = (Char*)Allocator::Allocate(totalLength * sizeof(Char));
Char* env = environmentStr;
for (auto& e : environment)
SHELLEXECUTEINFOW shExecInfo = { 0 };
shExecInfo.cbSize = sizeof(SHELLEXECUTEINFOW);
shExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
shExecInfo.lpFile = settings.FileName.GetText();
shExecInfo.lpParameters = settings.Arguments.HasChars() ? settings.Arguments.Get() : nullptr;
shExecInfo.lpDirectory = settings.WorkingDirectory.HasChars() ? settings.WorkingDirectory.Get() : nullptr;
shExecInfo.nShow = settings.HiddenWindow ? SW_HIDE : SW_SHOW;
if (ShellExecuteExW(&shExecInfo) == FALSE)
{
auto& key = e.Key;
auto& value = e.Value;
Platform::MemoryCopy(env, key.Get(), key.Length() * sizeof(Char));
env += key.Length();
*env++ = '=';
Platform::MemoryCopy(env, value.Get(), value.Length() * sizeof(Char));
env += value.Length();
*env++ = 0;
result = 1;
LOG(Warning, "Cannot start process. Error code: 0x{0:x}", (uint64)GetLastError());
}
*env++ = 0;
ASSERT((uint64)env - (uint64)environmentStr == (uint64)(totalLength * sizeof(Char)));
}
HANDLE stdOutRead = nullptr;
HANDLE stdErrRead = nullptr;
if (captureStdOut)
{
dwCreationFlags |= EXTENDED_STARTUPINFO_PRESENT;
startupInfoEx.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
SECURITY_ATTRIBUTES securityAttributes;
securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
securityAttributes.bInheritHandle = TRUE;
securityAttributes.lpSecurityDescriptor = nullptr;
if (!CreatePipe(&stdOutRead, &startupInfoEx.StartupInfo.hStdOutput, &securityAttributes, 0) ||
!CreatePipe(&stdErrRead, &startupInfoEx.StartupInfo.hStdError, &securityAttributes, 0))
else if (settings.WaitForEnd)
{
LOG(Warning, "CreatePipe failed");
return 1;
WaitForSingleObject(shExecInfo.hProcess, INFINITE);
DWORD exitCode;
if (GetExitCodeProcess(shExecInfo.hProcess, &exitCode) != 0)
result = exitCode;
CloseHandle(shExecInfo.hProcess);
}
}
else
{
result = -1;
const String cmdLine = settings.FileName + TEXT(" ") + settings.Arguments;
STARTUPINFOEX startupInfoEx;
ZeroMemory(&startupInfoEx, sizeof(startupInfoEx));
startupInfoEx.StartupInfo.cb = sizeof(startupInfoEx);
if (settings.HiddenWindow)
{
startupInfoEx.StartupInfo.dwFlags |= STARTF_USESHOWWINDOW;
startupInfoEx.StartupInfo.wShowWindow |= SW_HIDE | SW_SHOWNOACTIVATE;
}
SIZE_T bufferSize = 0;
if (!InitializeProcThreadAttributeList(nullptr, 1, 0, &bufferSize) && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
DWORD dwCreationFlags = NORMAL_PRIORITY_CLASS | DETACHED_PROCESS;
if (settings.HiddenWindow)
dwCreationFlags |= CREATE_NO_WINDOW;
Char* environmentStr = nullptr;
if (settings.Environment.HasItems())
{
startupInfoEx.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)Allocator::Allocate(bufferSize);
if (!InitializeProcThreadAttributeList(startupInfoEx.lpAttributeList, 1, 0, &bufferSize))
dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
int32 totalLength = 1;
for (auto& e : settings.Environment)
totalLength += e.Key.Length() + e.Value.Length() + 2;
environmentStr = (Char*)Allocator::Allocate(totalLength * sizeof(Char));
Char* env = environmentStr;
for (auto& e : settings.Environment)
{
LOG(Warning, "InitializeProcThreadAttributeList failed");
auto& key = e.Key;
auto& value = e.Value;
Platform::MemoryCopy(env, key.Get(), key.Length() * sizeof(Char));
env += key.Length();
*env++ = '=';
Platform::MemoryCopy(env, value.Get(), value.Length() * sizeof(Char));
env += value.Length();
*env++ = 0;
}
*env++ = 0;
ASSERT((uint64)env - (uint64)environmentStr == (uint64)(totalLength * sizeof(Char)));
}
HANDLE stdOutRead = nullptr;
HANDLE stdErrRead = nullptr;
if (captureStdOut)
{
dwCreationFlags |= EXTENDED_STARTUPINFO_PRESENT;
startupInfoEx.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
SECURITY_ATTRIBUTES securityAttributes;
securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
securityAttributes.bInheritHandle = TRUE;
securityAttributes.lpSecurityDescriptor = nullptr;
if (!CreatePipe(&stdOutRead, &startupInfoEx.StartupInfo.hStdOutput, &securityAttributes, 0) ||
!CreatePipe(&stdErrRead, &startupInfoEx.StartupInfo.hStdError, &securityAttributes, 0))
{
LOG(Warning, "CreatePipe failed");
return 1;
}
SIZE_T bufferSize = 0;
if (!InitializeProcThreadAttributeList(nullptr, 1, 0, &bufferSize) && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
startupInfoEx.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)Allocator::Allocate(bufferSize);
if (!InitializeProcThreadAttributeList(startupInfoEx.lpAttributeList, 1, 0, &bufferSize))
{
LOG(Warning, "InitializeProcThreadAttributeList failed");
goto ERROR_EXIT;
}
}
HANDLE inheritHandles[2] = { startupInfoEx.StartupInfo.hStdOutput, startupInfoEx.StartupInfo.hStdError };
if (!UpdateProcThreadAttribute(startupInfoEx.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, inheritHandles, sizeof(inheritHandles), nullptr, nullptr))
{
LOG(Warning, "UpdateProcThreadAttribute failed");
goto ERROR_EXIT;
}
}
HANDLE inheritHandles[2] = { startupInfoEx.StartupInfo.hStdOutput, startupInfoEx.StartupInfo.hStdError };
if (!UpdateProcThreadAttribute(startupInfoEx.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, inheritHandles, sizeof(inheritHandles), nullptr, nullptr))
// Create the process
PROCESS_INFORMATION procInfo;
if (!CreateProcessW(nullptr, const_cast<LPWSTR>(cmdLine.GetText()), nullptr, nullptr, TRUE, dwCreationFlags, (LPVOID)environmentStr, settings.WorkingDirectory.HasChars() ? settings.WorkingDirectory.Get() : nullptr, &startupInfoEx.StartupInfo, &procInfo))
{
LOG(Warning, "UpdateProcThreadAttribute failed");
LOG(Warning, "Cannot start process. Error code: 0x{0:x}", (uint64)GetLastError());
goto ERROR_EXIT;
}
}
// Create the process
PROCESS_INFORMATION procInfo;
if (!CreateProcessW(nullptr, const_cast<LPWSTR>(String(cmdLine).GetText()), nullptr, nullptr, TRUE, dwCreationFlags, (LPVOID)environmentStr, workingDir.HasChars() ? workingDir.Get() : nullptr, &startupInfoEx.StartupInfo, &procInfo))
{
LOG(Warning, "Cannot start process '{0}'. Error code: 0x{1:x}", cmdLine, static_cast<int64>(GetLastError()));
goto ERROR_EXIT;
}
if (stdOutRead != nullptr)
{
// Keep reading std output and std error streams until process is running
Array<char> rawData;
Array<Char> logData;
do
if (stdOutRead != nullptr)
{
ReadPipe(stdOutRead, rawData, logData, LogType::Info);
ReadPipe(stdErrRead, rawData, logData, LogType::Error);
Sleep(1);
} while (IsProcRunning(procInfo.hProcess));
ReadPipe(stdOutRead, rawData, logData, LogType::Info);
ReadPipe(stdErrRead, rawData, logData, LogType::Error);
}
else
{
WaitForSingleObject(procInfo.hProcess, INFINITE);
}
// Keep reading std output and std error streams until process is running
Array<char> rawData;
Array<Char> logData;
do
{
ReadPipe(stdOutRead, rawData, logData, LogType::Info, settings);
ReadPipe(stdErrRead, rawData, logData, LogType::Error, settings);
Sleep(1);
} while (IsProcRunning(procInfo.hProcess));
ReadPipe(stdOutRead, rawData, logData, LogType::Info, settings);
ReadPipe(stdErrRead, rawData, logData, LogType::Error, settings);
}
else
{
WaitForSingleObject(procInfo.hProcess, INFINITE);
}
DWORD exitCode;
if (GetExitCodeProcess(procInfo.hProcess, &exitCode) != 0)
result = exitCode;
DWORD exitCode;
if (GetExitCodeProcess(procInfo.hProcess, &exitCode) != 0)
result = exitCode;
CloseHandle(procInfo.hProcess);
CloseHandle(procInfo.hThread);
CloseHandle(procInfo.hProcess);
CloseHandle(procInfo.hThread);
// Cleanup
ERROR_EXIT:
if (startupInfoEx.StartupInfo.hStdOutput != nullptr)
{
CloseHandle(startupInfoEx.StartupInfo.hStdOutput);
// Cleanup
ERROR_EXIT:
if (startupInfoEx.StartupInfo.hStdOutput != nullptr)
CloseHandle(startupInfoEx.StartupInfo.hStdOutput);
if (startupInfoEx.StartupInfo.hStdError != nullptr)
CloseHandle(startupInfoEx.StartupInfo.hStdError);
if (stdOutRead != nullptr)
CloseHandle(stdOutRead);
if (stdErrRead != nullptr)
CloseHandle(stdErrRead);
if (startupInfoEx.lpAttributeList != nullptr)
{
DeleteProcThreadAttributeList(startupInfoEx.lpAttributeList);
Allocator::Free(startupInfoEx.lpAttributeList);
}
if (environmentStr)
Allocator::Free(environmentStr);
}
if (startupInfoEx.StartupInfo.hStdError != nullptr)
{
CloseHandle(startupInfoEx.StartupInfo.hStdError);
}
if (stdOutRead != nullptr)
{
CloseHandle(stdOutRead);
}
if (stdErrRead != nullptr)
{
CloseHandle(stdErrRead);
}
if (startupInfoEx.lpAttributeList != nullptr)
{
DeleteProcThreadAttributeList(startupInfoEx.lpAttributeList);
Allocator::Free(startupInfoEx.lpAttributeList);
}
if (environmentStr)
Allocator::Free(environmentStr);
return result;
}