diff options
Diffstat (limited to 'zencore/thread.cpp')
| -rw-r--r-- | zencore/thread.cpp | 1212 |
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 |