aboutsummaryrefslogtreecommitdiff
path: root/zencore/thread.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'zencore/thread.cpp')
-rw-r--r--zencore/thread.cpp1212
1 files changed, 0 insertions, 1212 deletions
diff --git a/zencore/thread.cpp b/zencore/thread.cpp
deleted file mode 100644
index 1597a7dd9..000000000
--- a/zencore/thread.cpp
+++ /dev/null
@@ -1,1212 +0,0 @@
-// Copyright Epic Games, Inc. All Rights Reserved.
-
-#include <zencore/thread.h>
-
-#include <zencore/except.h>
-#include <zencore/filesystem.h>
-#include <zencore/scopeguard.h>
-#include <zencore/string.h>
-#include <zencore/testing.h>
-
-#if ZEN_PLATFORM_LINUX
-# if !defined(_GNU_SOURCE)
-# define _GNU_SOURCE // for semtimedop()
-# endif
-#endif
-
-#if ZEN_PLATFORM_WINDOWS
-# include <shellapi.h>
-# include <Shlobj.h>
-# include <zencore/windows.h>
-#else
-# include <chrono>
-# include <condition_variable>
-# include <mutex>
-
-# include <fcntl.h>
-# include <pthread.h>
-# include <signal.h>
-# include <sys/file.h>
-# include <sys/sem.h>
-# include <sys/stat.h>
-# include <sys/syscall.h>
-# include <sys/wait.h>
-# include <time.h>
-# include <unistd.h>
-#endif
-
-#include <thread>
-
-ZEN_THIRD_PARTY_INCLUDES_START
-#include <fmt/format.h>
-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<DWORD_PTR*>(&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<SetThreadDescription>(::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<char*>& 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<char*> 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(syscall(SYS_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<char*> 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