/*** Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: Process.NT.cpp Date: 2021-6-12 Author: Reece ***/ #include #include "Processes.hpp" #include "Process.Win32.hpp" #include #if defined(AURORA_PLATFORM_WIN32) #include "Process.Win32.hpp" #endif namespace Aurora::Processes { class ProcessImpl : public IProcess { public: ProcessImpl(const StartupParmaters ¶ms); ~ProcessImpl(); bool TermWinEnumProcesses(); bool TryKill() override; bool HasExited(); AuUInt GetProcessId() override; bool Terminate() override; AuSPtr AsWaitable() override; AuSInt GetExitCode() override; void ShutdownPipes(); bool Read(bool error, void *buffer, AuUInt32 &len) override; bool Read(void *buffer, AuUInt32 &len, bool errorStream, bool nonblock) override; bool Write(const void *buffer, AuUInt32 len) override; bool Start() override; bool Init(); private: HANDLE pipeStdOutRead_ {INVALID_HANDLE_VALUE}; HANDLE pipeStdOutWrite_ {INVALID_HANDLE_VALUE}; HANDLE pipeStdErrRead_ {INVALID_HANDLE_VALUE}; HANDLE pipeStdErrWrite_ {INVALID_HANDLE_VALUE}; HANDLE pipeStdInRead_ {INVALID_HANDLE_VALUE}; HANDLE pipeStdInWrite_ {INVALID_HANDLE_VALUE}; StartupParmaters startup_; ESpawnType type_; AuList cargs_; AuString windowsCli_; AuThreads::ThreadUnique_t thread_; HANDLE process_ {INVALID_HANDLE_VALUE}; HANDLE hthread_ {INVALID_HANDLE_VALUE}; AuSInt exitCode_; }; ProcessImpl::ProcessImpl(const StartupParmaters ¶ms) : startup_(params) { startup_.args.insert(startup_.args.begin(), startup_.process); // ehhhh https://github.com/tritao/WindowsSDK/blob/07983c7ba4f6861d15e23f195744c60c0c249ce0/SDKs/SourceDir/Windows%20Kits/10/Source/10.0.17763.0/ucrt/exec/cenvarg.cpp#L23 for (const auto &arg : this->startup_.args) { this->cargs_.push_back(arg.c_str()); this->windowsCli_ += arg + " "; } this->cargs_.push_back(nullptr); this->windowsCli_.resize(this->windowsCli_.size() - 1); this->type_ = params.type; } ProcessImpl::~ProcessImpl() { if (this->type_ == ESpawnType::eSpawnChildProcessWorker) { TryKill(); Terminate(); } if (this->thread_) { this->thread_.reset(); } AuWin32CloseHandle(this->process_); AuWin32CloseHandle(this->hthread_); ShutdownPipes(); } AuUInt ProcessImpl::GetProcessId() { if (this->process_ == INVALID_HANDLE_VALUE) { return {}; } return ::GetProcessId(this->process_); } bool ProcessImpl::TermWinEnumProcesses() { #if defined(AURORA_PLATFORM_WIN32) return SendExitSignal(this->process_); #else return false; #endif } bool ProcessImpl::TryKill() { return TermWinEnumProcesses(); } bool ProcessImpl::HasExited() { DWORD a; if (!GetExitCodeProcess(this->process_, &a)) return true; return a != STILL_ACTIVE; } bool ProcessImpl::Terminate() { if (this->process_ != INVALID_HANDLE_VALUE) { return false; } return TerminateProcess(this->process_, 0) || HasExited(); } AuSPtr ProcessImpl::AsWaitable() { if (!this->thread_) return nullptr; return this->thread_->AsWaitable(); } AuSInt ProcessImpl::GetExitCode() { return this->exitCode_; } void ProcessImpl::ShutdownPipes() { AuWin32CloseHandle(this->pipeStdOutRead_); AuWin32CloseHandle(this->pipeStdOutWrite_); AuWin32CloseHandle(this->pipeStdErrRead_); AuWin32CloseHandle(this->pipeStdErrWrite_); AuWin32CloseHandle(this->pipeStdInRead_); AuWin32CloseHandle(this->pipeStdInWrite_); } bool ProcessImpl::Read(bool error, void *buffer, AuUInt32 &len) { return Read(buffer, len, error, false); } bool ProcessImpl::Read(void *buffer, AuUInt32 &len, bool errorStream, bool nonblock) { DWORD size = AuExchange(len, 0); auto handle = errorStream ? pipeStdErrRead_ : pipeStdOutRead_; if (handle == INVALID_HANDLE_VALUE) { return false; } if (nonblock) { DWORD avail {}; if (!PeekNamedPipe(handle, NULL, NULL, NULL, &avail, NULL)) { return false; } if (!avail) { return true; } size = AuMin(size, avail); } auto ret = ReadFile(handle, buffer, size, &size, NULL); len = size; return ret; } bool ProcessImpl::Write(const void *buffer, AuUInt32 len) { DWORD size = len; if (pipeStdInWrite_ == INVALID_HANDLE_VALUE) return false; return WriteFile(pipeStdInWrite_, buffer, size, &size, NULL) && size == len; } bool ProcessImpl::Init() { SECURITY_ATTRIBUTES saAttr {}; saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; if (this->type_ == ESpawnType::eSpawnAtomicOvermap) { return true; } if (this->process_ != INVALID_HANDLE_VALUE) { return false; } this->exitCode_ = 0x10110100; if (this->startup_.fwdOut) { if (!CreatePipe(&pipeStdOutRead_, &pipeStdOutWrite_, &saAttr, 0)) { return false; } if (!SetHandleInformation(pipeStdOutRead_, HANDLE_FLAG_INHERIT, 0)) { return false; } } if (this->startup_.fwdErr) { if (!CreatePipe(&pipeStdErrRead_, &pipeStdErrWrite_, &saAttr, 0)) { return false; } if (!SetHandleInformation(pipeStdErrRead_, HANDLE_FLAG_INHERIT, 0)) { return false; } } if (this->startup_.fwdIn) { if (!CreatePipe(&pipeStdInRead_, &pipeStdInWrite_, &saAttr, 0)) { return false; } if (!SetHandleInformation(pipeStdInWrite_, HANDLE_FLAG_INHERIT, 0)) { return false; } } if (this->startup_.noShowConsole) { HANDLE nulFile = INVALID_HANDLE_VALUE; // I dont want to move the other SetHandleInformations down below the if block above is, just to find out double SetHandleInfo with the same input params results in a return false condition // This will do for.now #define CHK_NUL \ if (nulFile == INVALID_HANDLE_VALUE) \ { \ nulFile = CreateFileA("nul:", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); \ SetHandleInformation(nulFile, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); \ } if (this->pipeStdInRead_ == INVALID_HANDLE_VALUE) { CHK_NUL; this->pipeStdInRead_ = nulFile; } if (this->pipeStdErrWrite_ == INVALID_HANDLE_VALUE) { CHK_NUL; this->pipeStdErrWrite_ = nulFile; } if (this->pipeStdOutWrite_ == INVALID_HANDLE_VALUE) { CHK_NUL; this->pipeStdOutWrite_ = nulFile; } } return true; } bool ProcessImpl::Start() { if (this->process_ != INVALID_HANDLE_VALUE) { return false; } if (this->type_ == ESpawnType::eSpawnAtomicOvermap) { _spawnv(_P_OVERLAY, this->startup_.process.c_str(), this->cargs_.data()); SysPushErrorGen("_spawnv didn't overwrite the process map, given {} ({})", this->startup_.process, this->windowsCli_); return false; } PROCESS_INFORMATION processInfo = { 0 }; { STARTUPINFOW startupInfo = { 0 }; startupInfo.cb = sizeof(startupInfo); bool inheritHandles = this->startup_.fwdIn || this->startup_.fwdErr || this->startup_.fwdOut || this->startup_.noShowConsole; startupInfo.hStdInput = pipeStdInRead_; startupInfo.hStdError = pipeStdErrWrite_; startupInfo.hStdOutput = pipeStdOutWrite_; startupInfo.dwFlags |= (inheritHandles ? STARTF_USESTDHANDLES : 0); auto result = CreateProcessW(Locale::ConvertFromUTF8(this->startup_.process).c_str(), Locale::ConvertFromUTF8(this->windowsCli_).data(), NULL, NULL, inheritHandles, this->startup_.noShowConsole ? CREATE_NO_WINDOW : NULL, // yea we can keep CREATE_NO_WINDOW on for non-console apps. its legal -> https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags NULL, NULL, &startupInfo, &processInfo); if (!result) { SysPushErrorGen("CreateProcess failed"); return false; } this->process_ = processInfo.hProcess; this->hthread_ = processInfo.hThread; AuWin32CloseHandle(this->pipeStdOutRead_); AuWin32CloseHandle(this->pipeStdErrRead_); AuWin32CloseHandle(this->pipeStdInWrite_); if (this->type_ == ESpawnType::eSpawnChildProcessWorker) { #if defined(AURORA_PLATFORM_WIN32) AssignJobWorker(processInfo.hProcess); #endif } } // TODO: delegate to a singular worker thread auto a = [=]() { WaitForSingleObject(processInfo.hProcess, INFINITE); DWORD exitCode; auto result = GetExitCodeProcess(processInfo.hProcess, &exitCode); this->exitCode_ = exitCode; }; this->thread_ = AuThreads::ThreadUnique(AuThreads::ThreadInfo( AuMakeShared(AuThreads::IThreadVectorsFunctional::OnEntry_t(std::bind(a)), AuThreads::IThreadVectorsFunctional::OnExit_t{}) )); if (!this->thread_) { return false; } this->thread_->Run(); return true; } AUKN_SYM IProcess *SpawnNew(const StartupParmaters ¶ms) { try { auto hi = _new ProcessImpl(params); if (!hi) return {}; if (!hi->Init()) { delete hi; return {}; } return hi; } catch (...) { return {}; } } AUKN_SYM void SpawnRelease(IProcess *process) { SafeDelete(process); } }