/*** Copyright (C) 2021-2022 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: AuProcess.Win32.cpp Date: 2022-1-29 Author: Reece ***/ #include #include "AuProcess.Win32.hpp" #include #include #include namespace Aurora::Processes { static HANDLE gLeaderJob = INVALID_HANDLE_VALUE; void InitWin32() { JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli {}; gLeaderJob = CreateJobObject(NULL, NULL); if (!gLeaderJob) { SysPushErrorArg("CreateJobObject error"); return; } jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; if (!SetInformationJobObject(gLeaderJob, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli))) { SysPushErrorGen("SetInformationJobObject error"); } } void DeinitWin32() { AuWin32CloseHandle(gLeaderJob); } void AssignJobWorker(HANDLE handle) { if (gLeaderJob) { if (!AssignProcessToJobObject(gLeaderJob, handle)) { SysPushErrorGen("Could not AssignProcessToObject"); } } } static bool HasWin32ProcessExited(HANDLE handle) { DWORD exitCode; if (!GetExitCodeProcess(handle, &exitCode)) { return true; } return exitCode != STILL_ACTIVE; } struct Hack { HANDLE base; AuUInt32 count; }; static BOOL CALLBACK TermWinHandleWin32Thread(HWND handle, LPARAM a) { DWORD windowpid; auto context = reinterpret_cast(a); auto pid = GetProcessId(context->base); GetWindowThreadProcessId(handle, &windowpid); if (pid && pid == windowpid) { SendMessageA(handle, WM_CLOSE, 0, 0); context->count++; } return true; } static bool SendExtermianteWinloopCloseMessages(HANDLE handle) { THREADENTRY32 te{}; HANDLE h{}; Hack hello; hello.count = 0; hello.base = handle; te.dwSize = sizeof(te); te.th32OwnerProcessID = GetProcessId(handle); if (!te.th32OwnerProcessID) { return false; } h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, GetProcessId(handle)); if (h == INVALID_HANDLE_VALUE) { return false; } if (Thread32First(h, &te)) { do { if (te.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(te.th32OwnerProcessID)) { EnumThreadWindows(te.th32ThreadID, TermWinHandleWin32Thread, reinterpret_cast(&hello)); } te.dwSize = sizeof(te); } while (Thread32Next(h, &te)); } CloseHandle(h); if (!hello.count) { return false; } return true; } static bool SendControlCEquiv(HANDLE handle) { auto cwnd = GetConsoleWindow(); if (!(AttachConsole(GetProcessId(handle)))) { return false; } if (cwnd == GetConsoleWindow()) { FreeConsole(); return false; } if (SetConsoleCtrlHandler({}, true)) { GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); SysAssert(FreeConsole(), "Couldn't pop off zombie process console"); // other people use 2k ms, their code is over a decade or so old tho // this is evil; however, since we are calling free, the os should be // smart enough to not terminate us now. id be nice if msft made that // defined behaviour. for now, we have to sleep to make sure we dont // eat items in the kernels/conhosts/somethings pending work queue Sleep(750); } else { SysAssert(FreeConsole(), "Couldn't pop off zombie process console"); return false; } if (!SetConsoleCtrlHandler({}, false)) { SysPushErrorHAL("Couldn't swap control+c handler -> wont accept control termination on exit"); } return true; } static bool Wait2500OrUntilClose(HANDLE handle) { return Threading::YieldPoll(true, 2500, [=]() { return !HasWin32ProcessExited(handle); }); } bool SendExitSignal(HANDLE handle) { if (handle == INVALID_HANDLE_VALUE) { return false; } if (!(SendControlCEquiv(handle) // send GenerateConsoleCtrlEvent || SendExtermianteWinloopCloseMessages(handle))) // send WM_CLOSE { return false; } return Wait2500OrUntilClose(handle); } }