// Copyright Epic Games, Inc. All Rights Reserved. #include #include #include #include #include #include #if ZEN_PLATFORM_LINUX # if !defined(_GNU_SOURCE) # define _GNU_SOURCE // for semtimedop() # endif #endif #if ZEN_PLATFORM_WINDOWS # include # include # include #else # include # include # include # include # include # include # include # include # include # include # include # include #endif #include ZEN_THIRD_PARTY_INCLUDES_START #include ZEN_THIRD_PARTY_INCLUDES_END namespace zen { #if ZEN_PLATFORM_WINDOWS // The information on how to set the thread name comes from // a MSDN article: http://msdn2.microsoft.com/en-us/library/xcb2z8hs.aspx const DWORD kVCThreadNameException = 0x406D1388; typedef struct tagTHREADNAME_INFO { DWORD dwType; // Must be 0x1000. LPCSTR szName; // Pointer to name (in user addr space). DWORD dwThreadID; // Thread ID (-1=caller thread). DWORD dwFlags; // Reserved for future use, must be zero. } THREADNAME_INFO; // The SetThreadDescription API was brought in version 1607 of Windows 10. typedef HRESULT(WINAPI* SetThreadDescription)(HANDLE hThread, PCWSTR lpThreadDescription); // This function has try handling, so it is separated out of its caller. void SetNameInternal(DWORD thread_id, const char* name) { THREADNAME_INFO info; info.dwType = 0x1000; info.szName = name; info.dwThreadID = thread_id; info.dwFlags = 0; __try { RaiseException(kVCThreadNameException, 0, sizeof(info) / sizeof(DWORD), reinterpret_cast(&info)); } __except (EXCEPTION_CONTINUE_EXECUTION) { } } #endif #if ZEN_PLATFORM_LINUX const bool bNoZombieChildren = []() { // When a child process exits it is put into a zombie state until the parent // collects its result. This doesn't fit the Windows-like model that Zen uses // where there is a less strict familial model and no zombification. Ignoring // SIGCHLD siganals removes the need to call wait() on zombies. Another option // would be for the child to call setsid() but that would detatch the child // from the terminal. struct sigaction Action = {}; sigemptyset(&Action.sa_mask); Action.sa_handler = SIG_IGN; sigaction(SIGCHLD, &Action, nullptr); return true; }(); #endif void SetCurrentThreadName([[maybe_unused]] std::string_view ThreadName) { #if ZEN_PLATFORM_WINDOWS // The SetThreadDescription API works even if no debugger is attached. static auto SetThreadDescriptionFunc = reinterpret_cast(::GetProcAddress(::GetModuleHandle(L"Kernel32.dll"), "SetThreadDescription")); if (SetThreadDescriptionFunc) { SetThreadDescriptionFunc(::GetCurrentThread(), Utf8ToWide(ThreadName).c_str()); } // The debugger needs to be around to catch the name in the exception. If // there isn't a debugger, we are just needlessly throwing an exception. if (!::IsDebuggerPresent()) return; std::string ThreadNameZ{ThreadName}; SetNameInternal(GetCurrentThreadId(), ThreadNameZ.c_str()); #else std::string ThreadNameZ{ThreadName}; # if ZEN_PLATFORM_MAC pthread_setname_np(ThreadNameZ.c_str()); # else pthread_setname_np(pthread_self(), ThreadNameZ.c_str()); # endif #endif } // namespace zen void RwLock::AcquireShared() { m_Mutex.lock_shared(); } void RwLock::ReleaseShared() { m_Mutex.unlock_shared(); } void RwLock::AcquireExclusive() { m_Mutex.lock(); } void RwLock::ReleaseExclusive() { m_Mutex.unlock(); } ////////////////////////////////////////////////////////////////////////// #if !ZEN_PLATFORM_WINDOWS struct EventInner { std::mutex Mutex; std::condition_variable CondVar; bool volatile bSet = false; }; #endif // !ZEN_PLATFORM_WINDOWS Event::Event() { bool bManualReset = true; bool bInitialState = false; #if ZEN_PLATFORM_WINDOWS m_EventHandle = CreateEvent(nullptr, bManualReset, bInitialState, nullptr); #else ZEN_UNUSED(bManualReset); auto* Inner = new EventInner(); Inner->bSet = bInitialState; m_EventHandle = Inner; #endif } Event::~Event() { Close(); } void Event::Set() { #if ZEN_PLATFORM_WINDOWS SetEvent(m_EventHandle); #else auto* Inner = (EventInner*)m_EventHandle; { std::unique_lock Lock(Inner->Mutex); Inner->bSet = true; } Inner->CondVar.notify_all(); #endif } void Event::Reset() { #if ZEN_PLATFORM_WINDOWS ResetEvent(m_EventHandle); #else auto* Inner = (EventInner*)m_EventHandle; { std::unique_lock Lock(Inner->Mutex); Inner->bSet = false; } #endif } void Event::Close() { #if ZEN_PLATFORM_WINDOWS CloseHandle(m_EventHandle); #else auto* Inner = (EventInner*)m_EventHandle; delete Inner; #endif m_EventHandle = nullptr; } bool Event::Wait(int TimeoutMs) { #if ZEN_PLATFORM_WINDOWS using namespace std::literals; const DWORD Timeout = (TimeoutMs < 0) ? INFINITE : TimeoutMs; DWORD Result = WaitForSingleObject(m_EventHandle, Timeout); if (Result == WAIT_FAILED) { zen::ThrowLastError("Event wait failed"sv); } return (Result == WAIT_OBJECT_0); #else auto* Inner = (EventInner*)m_EventHandle; if (TimeoutMs >= 0) { std::unique_lock Lock(Inner->Mutex); if (Inner->bSet) { return true; } return Inner->CondVar.wait_for(Lock, std::chrono::milliseconds(TimeoutMs), [&] { return Inner->bSet; }); } std::unique_lock Lock(Inner->Mutex); if (!Inner->bSet) { Inner->CondVar.wait(Lock, [&] { return Inner->bSet; }); } return true; #endif } ////////////////////////////////////////////////////////////////////////// NamedEvent::NamedEvent(std::string_view EventName) { #if ZEN_PLATFORM_WINDOWS using namespace std::literals; ExtendableStringBuilder<64> Name; Name << "Local\\"sv; Name << EventName; m_EventHandle = CreateEventA(nullptr, true, false, Name.c_str()); #elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC // Create a file to back the semaphore ExtendableStringBuilder<64> EventPath; EventPath << "/tmp/" << EventName; int Fd = open(EventPath.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0666); if (Fd < 0) { ThrowLastError(fmt::format("Failed to create '{}' for named event", EventPath)); } fchmod(Fd, 0666); // Use the file path to generate an IPC key key_t IpcKey = ftok(EventPath.c_str(), 1); if (IpcKey < 0) { close(Fd); ThrowLastError("Failed to create an SysV IPC key"); } // Use the key to create/open the semaphore int Sem = semget(IpcKey, 1, 0600 | IPC_CREAT); if (Sem < 0) { close(Fd); ThrowLastError("Failed creating an SysV semaphore"); } // Atomically claim ownership of the semaphore's key. The owner initialises // the semaphore to 1 so we can use the wait-for-zero op as that does not // modify the semaphore's value on a successful wait. int LockResult = flock(Fd, LOCK_EX | LOCK_NB); if (LockResult == 0) { // This isn't thread safe really. Another thread could open the same // semaphore and successfully wait on it in the period of time where // this comment is but before the semaphore's initialised. semctl(Sem, 0, SETVAL, 1); } // Pack into the handle static_assert(sizeof(Sem) + sizeof(Fd) <= sizeof(void*), "Semaphore packing assumptions not met"); intptr_t Packed; Packed = intptr_t(Sem) << 32; Packed |= intptr_t(Fd) & 0xffff'ffff; m_EventHandle = (void*)Packed; #endif } NamedEvent::~NamedEvent() { Close(); } void NamedEvent::Close() { if (m_EventHandle == nullptr) { return; } #if ZEN_PLATFORM_WINDOWS CloseHandle(m_EventHandle); #elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC int Fd = int(intptr_t(m_EventHandle) & 0xffff'ffff); if (flock(Fd, LOCK_EX | LOCK_NB) == 0) { std::filesystem::path Name = PathFromHandle((void*)(intptr_t(Fd))); unlink(Name.c_str()); flock(Fd, LOCK_UN | LOCK_NB); close(Fd); int Sem = int(intptr_t(m_EventHandle) >> 32); semctl(Sem, 0, IPC_RMID); } #endif m_EventHandle = nullptr; } void NamedEvent::Set() { #if ZEN_PLATFORM_WINDOWS SetEvent(m_EventHandle); #elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC int Sem = int(intptr_t(m_EventHandle) >> 32); semctl(Sem, 0, SETVAL, 0); #endif } bool NamedEvent::Wait(int TimeoutMs) { #if ZEN_PLATFORM_WINDOWS const DWORD Timeout = (TimeoutMs < 0) ? INFINITE : TimeoutMs; DWORD Result = WaitForSingleObject(m_EventHandle, Timeout); if (Result == WAIT_FAILED) { using namespace std::literals; zen::ThrowLastError("Event wait failed"sv); } return (Result == WAIT_OBJECT_0); #elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC int Sem = int(intptr_t(m_EventHandle) >> 32); int Result; struct sembuf SemOp = {}; if (TimeoutMs < 0) { Result = semop(Sem, &SemOp, 1); return Result == 0; } # if defined(_GNU_SOURCE) struct timespec TimeoutValue = { .tv_sec = TimeoutMs >> 10, .tv_nsec = (TimeoutMs & 0x3ff) << 20, }; Result = semtimedop(Sem, &SemOp, 1, &TimeoutValue); # else const int SleepTimeMs = 10; SemOp.sem_flg = IPC_NOWAIT; do { Result = semop(Sem, &SemOp, 1); if (Result == 0 || errno != EAGAIN) { break; } Sleep(SleepTimeMs); TimeoutMs -= SleepTimeMs; } while (TimeoutMs > 0); # endif // _GNU_SOURCE return Result == 0; #endif } ////////////////////////////////////////////////////////////////////////// NamedMutex::~NamedMutex() { #if ZEN_PLATFORM_WINDOWS if (m_MutexHandle) { CloseHandle(m_MutexHandle); } #elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC int Inner = int(intptr_t(m_MutexHandle)); flock(Inner, LOCK_UN); close(Inner); #endif } bool NamedMutex::Create(std::string_view MutexName) { #if ZEN_PLATFORM_WINDOWS ZEN_ASSERT(m_MutexHandle == nullptr); using namespace std::literals; ExtendableStringBuilder<64> Name; Name << "Global\\"sv; Name << MutexName; m_MutexHandle = CreateMutexA(nullptr, /* InitialOwner */ TRUE, Name.c_str()); return !!m_MutexHandle; #elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC ExtendableStringBuilder<64> Name; Name << "/tmp/" << MutexName; int Inner = open(Name.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0666); if (Inner < 0) { return false; } fchmod(Inner, 0666); if (flock(Inner, LOCK_EX) != 0) { close(Inner); Inner = 0; return false; } m_MutexHandle = (void*)(intptr_t(Inner)); return true; #endif // ZEN_PLATFORM_WINDOWS } bool NamedMutex::Exists(std::string_view MutexName) { #if ZEN_PLATFORM_WINDOWS using namespace std::literals; ExtendableStringBuilder<64> Name; Name << "Global\\"sv; Name << MutexName; void* MutexHandle = OpenMutexA(SYNCHRONIZE, /* InheritHandle */ FALSE, Name.c_str()); if (MutexHandle == nullptr) { return false; } CloseHandle(MutexHandle); return true; #elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC ExtendableStringBuilder<64> Name; Name << "/tmp/" << MutexName; bool bExists = false; int Fd = open(Name.c_str(), O_RDWR | O_CLOEXEC); if (Fd >= 0) { if (flock(Fd, LOCK_EX | LOCK_NB) == 0) { flock(Fd, LOCK_UN | LOCK_NB); } else { bExists = true; } close(Fd); } return bExists; #endif // ZEN_PLATFORM_WINDOWS } ////////////////////////////////////////////////////////////////////////// ProcessHandle::ProcessHandle() = default; #if ZEN_PLATFORM_WINDOWS void ProcessHandle::Initialize(void* ProcessHandle) { ZEN_ASSERT(m_ProcessHandle == nullptr); if (ProcessHandle == INVALID_HANDLE_VALUE) { ProcessHandle = nullptr; } // TODO: perform some debug verification here to verify it's a valid handle? m_ProcessHandle = ProcessHandle; m_Pid = GetProcessId(m_ProcessHandle); } #endif // ZEN_PLATFORM_WINDOWS ProcessHandle::~ProcessHandle() { Reset(); } void ProcessHandle::Initialize(int Pid) { ZEN_ASSERT(m_ProcessHandle == nullptr); #if ZEN_PLATFORM_WINDOWS m_ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Pid); #elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC if (Pid > 0) { m_ProcessHandle = (void*)(intptr_t(Pid)); } #endif if (!m_ProcessHandle) { ThrowLastError(fmt::format("ProcessHandle::Initialize(pid: {}) failed", Pid)); } m_Pid = Pid; } bool ProcessHandle::IsRunning() const { bool bActive = false; #if ZEN_PLATFORM_WINDOWS DWORD ExitCode = 0; GetExitCodeProcess(m_ProcessHandle, &ExitCode); bActive = (ExitCode == STILL_ACTIVE); #elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC bActive = (kill(pid_t(m_Pid), 0) == 0); #endif return bActive; } bool ProcessHandle::IsValid() const { return (m_ProcessHandle != nullptr); } void ProcessHandle::Terminate(int ExitCode) { if (!IsRunning()) { return; } bool bSuccess = false; #if ZEN_PLATFORM_WINDOWS TerminateProcess(m_ProcessHandle, ExitCode); DWORD WaitResult = WaitForSingleObject(m_ProcessHandle, INFINITE); bSuccess = (WaitResult != WAIT_OBJECT_0); #elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC ZEN_UNUSED(ExitCode); bSuccess = (kill(m_Pid, SIGKILL) == 0); #endif if (!bSuccess) { // What might go wrong here, and what is meaningful to act on? } } void ProcessHandle::Reset() { if (IsValid()) { #if ZEN_PLATFORM_WINDOWS CloseHandle(m_ProcessHandle); #endif m_ProcessHandle = nullptr; m_Pid = 0; } } bool ProcessHandle::Wait(int TimeoutMs) { using namespace std::literals; #if ZEN_PLATFORM_WINDOWS const DWORD Timeout = (TimeoutMs < 0) ? INFINITE : TimeoutMs; const DWORD WaitResult = WaitForSingleObject(m_ProcessHandle, Timeout); switch (WaitResult) { case WAIT_OBJECT_0: return true; case WAIT_TIMEOUT: return false; case WAIT_FAILED: break; } #elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC const int SleepMs = 20; timespec SleepTime = {0, SleepMs * 1000 * 1000}; for (int i = 0;; i += SleepMs) { # if ZEN_PLATFORM_MAC int WaitState = 0; waitpid(m_Pid, &WaitState, WNOHANG | WCONTINUED | WUNTRACED); # endif if (kill(m_Pid, 0) < 0) { if (zen::GetLastError() == ESRCH) { return true; } break; } if (TimeoutMs >= 0 && i >= TimeoutMs) { return false; } nanosleep(&SleepTime, nullptr); } #endif // What might go wrong here, and what is meaningful to act on? ThrowLastError("Process::Wait failed"sv); } ////////////////////////////////////////////////////////////////////////// #if !ZEN_PLATFORM_WINDOWS || ZEN_WITH_TESTS static void BuildArgV(std::vector& Out, char* CommandLine) { char* Cursor = CommandLine; while (true) { // Skip leading whitespace for (; *Cursor == ' '; ++Cursor) ; // Check for nullp terminator if (*Cursor == '\0') { break; } Out.push_back(Cursor); // Extract word int QuoteCount = 0; do { QuoteCount += (*Cursor == '\"'); if (*Cursor == ' ' && !(QuoteCount & 1)) { break; } ++Cursor; } while (*Cursor != '\0'); if (*Cursor == '\0') { break; } *Cursor = '\0'; ++Cursor; } } #endif // !WINDOWS || TESTS #if ZEN_PLATFORM_WINDOWS static CreateProcResult CreateProcNormal(const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options) { PROCESS_INFORMATION ProcessInfo{}; STARTUPINFO StartupInfo{.cb = sizeof(STARTUPINFO)}; const bool InheritHandles = false; void* Environment = nullptr; LPSECURITY_ATTRIBUTES ProcessAttributes = nullptr; LPSECURITY_ATTRIBUTES ThreadAttributes = nullptr; DWORD CreationFlags = 0; if (Options.Flags & CreateProcOptions::Flag_NewConsole) { CreationFlags |= CREATE_NEW_CONSOLE; } const wchar_t* WorkingDir = nullptr; if (Options.WorkingDirectory != nullptr) { WorkingDir = Options.WorkingDirectory->c_str(); } ExtendableWideStringBuilder<256> CommandLineZ; CommandLineZ << CommandLine; BOOL Success = CreateProcessW(Executable.c_str(), CommandLineZ.Data(), ProcessAttributes, ThreadAttributes, InheritHandles, CreationFlags, Environment, WorkingDir, &StartupInfo, &ProcessInfo); if (!Success) { return nullptr; } CloseHandle(ProcessInfo.hThread); return ProcessInfo.hProcess; } static CreateProcResult CreateProcUnelevated(const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options) { /* Launches a binary with the shell as its parent. The shell (such as Explorer) should be an unelevated process. */ // No sense in using this route if we are not elevated in the first place if (IsUserAnAdmin() == FALSE) { return CreateProcNormal(Executable, CommandLine, Options); } // Get the users' shell process and open it for process creation HWND ShellWnd = GetShellWindow(); if (ShellWnd == nullptr) { return nullptr; } DWORD ShellPid; GetWindowThreadProcessId(ShellWnd, &ShellPid); HANDLE Process = OpenProcess(PROCESS_CREATE_PROCESS, FALSE, ShellPid); if (Process == nullptr) { return nullptr; } auto $0 = MakeGuard([&] { CloseHandle(Process); }); // Creating a process as a child of another process is done by setting a // thread-attribute list on the startup info passed to CreateProcess() SIZE_T AttrListSize; InitializeProcThreadAttributeList(nullptr, 1, 0, &AttrListSize); auto AttrList = (PPROC_THREAD_ATTRIBUTE_LIST)malloc(AttrListSize); auto $1 = MakeGuard([&] { free(AttrList); }); if (!InitializeProcThreadAttributeList(AttrList, 1, 0, &AttrListSize)) { return nullptr; } BOOL bOk = UpdateProcThreadAttribute(AttrList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, (HANDLE*)&Process, sizeof(Process), nullptr, nullptr); if (!bOk) { return nullptr; } // By this point we know we are an elevated process. It is not allowed to // create a process as a child of another unelevated process that share our // elevated console window if we have one. So we'll need to create a new one. uint32_t CreateProcFlags = EXTENDED_STARTUPINFO_PRESENT; if (GetConsoleWindow() != nullptr) { CreateProcFlags |= CREATE_NEW_CONSOLE; } else { CreateProcFlags |= DETACHED_PROCESS; } // Everything is set up now so we can proceed and launch the process STARTUPINFOEXW StartupInfo = { .StartupInfo = {.cb = sizeof(STARTUPINFOEXW)}, .lpAttributeList = AttrList, }; PROCESS_INFORMATION ProcessInfo = {}; if (Options.Flags & CreateProcOptions::Flag_NewConsole) { CreateProcFlags |= CREATE_NEW_CONSOLE; } ExtendableWideStringBuilder<256> CommandLineZ; CommandLineZ << CommandLine; bOk = CreateProcessW(Executable.c_str(), CommandLineZ.Data(), nullptr, nullptr, FALSE, CreateProcFlags, nullptr, nullptr, &StartupInfo.StartupInfo, &ProcessInfo); if (bOk == FALSE) { return nullptr; } CloseHandle(ProcessInfo.hThread); return ProcessInfo.hProcess; } static CreateProcResult CreateProcElevated(const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options) { ExtendableWideStringBuilder<256> CommandLineZ; CommandLineZ << CommandLine; SHELLEXECUTEINFO ShellExecuteInfo; ZeroMemory(&ShellExecuteInfo, sizeof(ShellExecuteInfo)); ShellExecuteInfo.cbSize = sizeof(ShellExecuteInfo); ShellExecuteInfo.fMask = SEE_MASK_UNICODE | SEE_MASK_NOCLOSEPROCESS; ShellExecuteInfo.lpFile = Executable.c_str(); ShellExecuteInfo.lpVerb = TEXT("runas"); ShellExecuteInfo.nShow = SW_SHOW; ShellExecuteInfo.lpParameters = CommandLineZ.c_str(); if (Options.WorkingDirectory != nullptr) { ShellExecuteInfo.lpDirectory = Options.WorkingDirectory->c_str(); } if (::ShellExecuteEx(&ShellExecuteInfo)) { return ShellExecuteInfo.hProcess; } return nullptr; } #endif // ZEN_PLATFORM_WINDOWS CreateProcResult CreateProc(const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options) { #if ZEN_PLATFORM_WINDOWS if (Options.Flags & CreateProcOptions::Flag_Unelevated) { return CreateProcUnelevated(Executable, CommandLine, Options); } if (Options.Flags & CreateProcOptions::Flag_Elevated) { return CreateProcElevated(Executable, CommandLine, Options); } return CreateProcNormal(Executable, CommandLine, Options); #else std::vector ArgV; std::string CommandLineZ(CommandLine); BuildArgV(ArgV, CommandLineZ.data()); ArgV.push_back(nullptr); int ChildPid = fork(); if (ChildPid < 0) { ThrowLastError("Failed to fork a new child process"); } else if (ChildPid == 0) { if (Options.WorkingDirectory != nullptr) { int Result = chdir(Options.WorkingDirectory->c_str()); ZEN_UNUSED(Result); } if (execv(Executable.c_str(), ArgV.data()) < 0) { ThrowLastError("Failed to exec() a new process image"); } } return ChildPid; #endif } ////////////////////////////////////////////////////////////////////////// ProcessMonitor::ProcessMonitor() { } ProcessMonitor::~ProcessMonitor() { RwLock::ExclusiveLockScope _(m_Lock); for (HandleType& Proc : m_ProcessHandles) { #if ZEN_PLATFORM_WINDOWS CloseHandle(Proc); #endif Proc = 0; } } bool ProcessMonitor::IsRunning() { RwLock::ExclusiveLockScope _(m_Lock); bool FoundOne = false; for (HandleType& Proc : m_ProcessHandles) { bool ProcIsActive; #if ZEN_PLATFORM_WINDOWS DWORD ExitCode = 0; GetExitCodeProcess(Proc, &ExitCode); ProcIsActive = (ExitCode == STILL_ACTIVE); if (!ProcIsActive) { CloseHandle(Proc); } #else int Pid = int(intptr_t(Proc)); ProcIsActive = IsProcessRunning(Pid); #endif if (!ProcIsActive) { Proc = 0; } // Still alive FoundOne |= ProcIsActive; } std::erase_if(m_ProcessHandles, [](HandleType Handle) { return Handle == 0; }); return FoundOne; } void ProcessMonitor::AddPid(int Pid) { HandleType ProcessHandle; #if ZEN_PLATFORM_WINDOWS ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Pid); #else ProcessHandle = HandleType(intptr_t(Pid)); #endif if (ProcessHandle) { RwLock::ExclusiveLockScope _(m_Lock); m_ProcessHandles.push_back(ProcessHandle); } } bool ProcessMonitor::IsActive() const { RwLock::SharedLockScope _(m_Lock); return m_ProcessHandles.empty() == false; } ////////////////////////////////////////////////////////////////////////// bool IsProcessRunning(int pid) { // This function is arguably not super useful, a pid can be re-used // by the OS so holding on to a pid and polling it over some time // period will not necessarily tell you what you probably want to know. #if ZEN_PLATFORM_WINDOWS HANDLE hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); if (!hProc) { DWORD Error = zen::GetLastError(); if (Error == ERROR_INVALID_PARAMETER) { return false; } ThrowSystemError(Error, fmt::format("failed to open process with pid {}", pid)); } CloseHandle(hProc); return true; #elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC return (kill(pid_t(pid), 0) == 0); #endif } int GetCurrentProcessId() { #if ZEN_PLATFORM_WINDOWS return ::GetCurrentProcessId(); #else return int(getpid()); #endif } int GetCurrentThreadId() { #if ZEN_PLATFORM_WINDOWS return ::GetCurrentThreadId(); #elif ZEN_PLATFORM_LINUX return int(gettid()); #elif ZEN_PLATFORM_MAC return int(pthread_mach_thread_np(pthread_self())); #endif } void Sleep(int ms) { #if ZEN_PLATFORM_WINDOWS ::Sleep(ms); #else usleep(ms * 1000U); #endif } ////////////////////////////////////////////////////////////////////////// // // Testing related code follows... // #if ZEN_WITH_TESTS void thread_forcelink() { } TEST_CASE("Thread") { int Pid = GetCurrentProcessId(); CHECK(Pid > 0); CHECK(IsProcessRunning(Pid)); CHECK_FALSE(GetCurrentThreadId() == 0); } TEST_CASE("BuildArgV") { const char* Words[] = {"one", "two", "three", "four", "five"}; struct { int WordCount; const char* Input; } Cases[] = { {0, ""}, {0, " "}, {1, "one"}, {1, " one"}, {1, "one "}, {2, "one two"}, {2, " one two"}, {2, "one two "}, {2, " one two"}, {2, "one two "}, {2, "one two "}, {3, "one two three"}, {3, "\"one\" two \"three\""}, {5, "one two three four five"}, }; for (const auto& Case : Cases) { std::vector OutArgs; StringBuilder<64> Mutable; Mutable << Case.Input; BuildArgV(OutArgs, Mutable.Data()); CHECK_EQ(OutArgs.size(), Case.WordCount); for (int i = 0, n = int(OutArgs.size()); i < n; ++i) { const char* Truth = Words[i]; size_t TruthLen = strlen(Truth); const char* Candidate = OutArgs[i]; bool bQuoted = (Candidate[0] == '\"'); Candidate += bQuoted; CHECK(strncmp(Truth, Candidate, TruthLen) == 0); if (bQuoted) { CHECK_EQ(Candidate[TruthLen], '\"'); } } } } TEST_CASE("NamedEvent") { std::string Name = "zencore_test_event"; NamedEvent TestEvent(Name); // Timeout test for (uint32_t i = 0; i < 8; ++i) { bool bEventSet = TestEvent.Wait(100); CHECK(!bEventSet); } // Thread check std::thread Waiter = std::thread([Name]() { NamedEvent ReadyEvent(Name + "_ready"); ReadyEvent.Set(); NamedEvent TestEvent(Name); TestEvent.Wait(1000); }); NamedEvent ReadyEvent(Name + "_ready"); ReadyEvent.Wait(); zen::Sleep(500); TestEvent.Set(); Waiter.join(); // Manual reset property for (uint32_t i = 0; i < 8; ++i) { bool bEventSet = TestEvent.Wait(100); CHECK(bEventSet); } } TEST_CASE("NamedMutex") { static const char* Name = "zen_test_mutex"; CHECK(!NamedMutex::Exists(Name)); { NamedMutex TestMutex; CHECK(TestMutex.Create(Name)); CHECK(NamedMutex::Exists(Name)); } CHECK(!NamedMutex::Exists(Name)); } #endif // ZEN_WITH_TESTS } // namespace zen