/*** 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 "Process.NT.hpp" #include "Processes.hpp" #include #if defined(AURORA_PLATFORM_WIN32) #include "Process.Win32.hpp" #endif #include "ArgvQuote.hpp" #include namespace Aurora::Processes { ProcessImpl::ProcessImpl(const StartupParmaters ¶ms) : startup_(params) { this->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()); AuString escaped; ArgvQuote(arg, escaped, false); this->windowsCli_ += escaped + " "; } 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 HasExited() || TermWinEnumProcesses(); } bool ProcessImpl::HasExited() { DWORD exitCode; if (!GetExitCodeProcess(this->process_, &exitCode)) { return true; } return exitCode != 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(EStandardHandle stream, const AuMemoryViewStreamWrite &destination, bool nonblock) { DWORD size = destination.length; if (!EStandardHandleIsValid(stream) || (stream == EStandardHandle::eStdIn)) { SysPushErrorArg("Invalid Stream"); return {}; } auto handle = stream == EStandardHandle::eStdError ? this->pipeStdErrRead_ : this->pipeStdOutRead_; if (handle == INVALID_HANDLE_VALUE) { return false; } if (nonblock || !destination.ptr) { DWORD avail {}; if (!PeekNamedPipe(handle, NULL, NULL, NULL, &avail, NULL)) { return false; } if (!avail) { return true; } size = AuMin(size, avail); } if (!destination.ptr) { destination.outVariable = size; return true; } auto ret = ReadFile(handle, destination.ptr, size, &size, NULL); destination.outVariable = size; return ret; } bool ProcessImpl::Write(const AuMemoryViewStreamRead &in) { if (!in.ptr) { SysPushErrorArg("Missing pointer to stdin stream section"); return {}; } DWORD size = in.length; if (pipeStdInWrite_ == INVALID_HANDLE_VALUE) { SysPushErrorUninitialized(); return false; } return WriteFile(pipeStdInWrite_, in.ptr, size, &size, NULL) && size == in.length; } bool ProcessImpl::Init() { SECURITY_ATTRIBUTES saAttr {}; saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; if (this->type_ == ESpawnType::eSpawnOvermap) { 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; #define NEW_NULL_HANDLE \ { \ 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) { NEW_NULL_HANDLE; this->pipeStdInRead_ = nulFile; } if (this->pipeStdErrWrite_ == INVALID_HANDLE_VALUE) { NEW_NULL_HANDLE; this->pipeStdErrWrite_ = nulFile; } if (this->pipeStdOutWrite_ == INVALID_HANDLE_VALUE) { NEW_NULL_HANDLE; this->pipeStdOutWrite_ = nulFile; } } return true; } bool ProcessImpl::Start() { if (this->process_ != INVALID_HANDLE_VALUE) { return false; } if (this->type_ == ESpawnType::eSpawnOvermap) { _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(AuIOFS::NormalizePathRet(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) { DWORD wr = GetLastError(); SysPushErrorGen("CreateProcess failed"); return false; } this->process_ = processInfo.hProcess; this->hthread_ = processInfo.hThread; AuWin32CloseHandle(this->pipeStdOutWrite_); AuWin32CloseHandle(this->pipeStdErrWrite_); AuWin32CloseHandle(this->pipeStdInRead_); 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) { AuSafeDelete(process); } }