926 lines
22 KiB
C++
926 lines
22 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "ZenServerState.h"
|
|
|
|
#include "HAL/FileManager.h"
|
|
#include "HAL/PlatformProcess.h"
|
|
#include "Logging/LogMacros.h"
|
|
#include "Misc/ScopeExit.h"
|
|
#include "Misc/Paths.h"
|
|
#include "Misc/PathViews.h"
|
|
#include "Serialization/CompactBinaryValidation.h"
|
|
|
|
#if PLATFORM_WINDOWS
|
|
# include "Windows/AllowWindowsPlatformTypes.h"
|
|
# include <shellapi.h>
|
|
# include <synchapi.h>
|
|
# include <Windows.h>
|
|
# include "Windows/HideWindowsPlatformTypes.h"
|
|
#endif
|
|
|
|
#if PLATFORM_UNIX || PLATFORM_MAC
|
|
# include <sys/file.h>
|
|
# include <sys/mman.h>
|
|
# include <sys/sem.h>
|
|
# include <sys/stat.h>
|
|
# include <sys/sysctl.h>
|
|
#endif
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogZenServiceState, Log, All);
|
|
|
|
#if PLATFORM_UNIX
|
|
static char
|
|
GetPidStatus(int Pid)
|
|
{
|
|
TAnsiStringBuilder<128> StatPath;
|
|
StatPath.Appendf("/proc/%d/stat", Pid);
|
|
FILE* StatFile = fopen(*StatPath, "r");
|
|
if (StatFile)
|
|
{
|
|
char Buffer[5120];
|
|
int Size = fread(Buffer, 1, 5120 - 1, StatFile);
|
|
fclose(StatFile);
|
|
if (Size > 0)
|
|
{
|
|
Buffer[Size] = 0;
|
|
char* ScanPtr = strrchr(Buffer, ')');
|
|
if (ScanPtr && ScanPtr[1] != '\0')
|
|
{
|
|
ScanPtr += 2;
|
|
char State = *ScanPtr;
|
|
return State;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static bool
|
|
IsZombieProcess(int pid)
|
|
{
|
|
char Status = GetPidStatus(pid);
|
|
if (Status == 'Z' || Status == 0)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#endif // ZEN_PLATFORM_LINUX
|
|
|
|
#if PLATFORM_MAC
|
|
static bool
|
|
IsZombieProcess(int pid)
|
|
{
|
|
struct kinfo_proc Info;
|
|
int Mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid };
|
|
size_t InfoSize = sizeof Info;
|
|
|
|
int Res = sysctl(Mib, 4, &Info, &InfoSize, NULL, 0);
|
|
if (Res != 0)
|
|
{
|
|
return false;
|
|
}
|
|
if (Info.kp_proc.p_stat == SZOMB)
|
|
{
|
|
// Zombie process
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
#endif // PLATFORM_MAC
|
|
|
|
// Native functions to interact with a process using a process id
|
|
// We don't use UE's own OpenProcess as they try to open processes with PROCESS_ALL_ACCESS
|
|
bool ZenServerState::IsProcessRunning(uint32 Pid)
|
|
{
|
|
if (Pid == 0)
|
|
{
|
|
return false;
|
|
}
|
|
#if PLATFORM_WINDOWS
|
|
HANDLE Handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, (DWORD)Pid);
|
|
|
|
if (!Handle)
|
|
{
|
|
DWORD Error = GetLastError();
|
|
|
|
if (Error == ERROR_INVALID_PARAMETER)
|
|
{
|
|
return false;
|
|
}
|
|
else if (Error == ERROR_ACCESS_DENIED)
|
|
{
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("No access to open running process %d: %d, assuming it is running"), Pid, Error);
|
|
return true;
|
|
}
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("Failed to open running process %d: %d, assuming it is not running"), Pid, Error);
|
|
return false;
|
|
}
|
|
ON_SCOPE_EXIT{ CloseHandle(Handle); };
|
|
|
|
DWORD ExitCode = 0;
|
|
if (GetExitCodeProcess(Handle, &ExitCode) == 0)
|
|
{
|
|
DWORD Error = GetLastError();
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("Failed to get running process exit code %d: %d, assuming it is still running"), Pid, Error);
|
|
return true;
|
|
}
|
|
else if (ExitCode == STILL_ACTIVE)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
|
|
#elif PLATFORM_UNIX || PLATFORM_MAC
|
|
int Res = kill(pid_t(Pid), 0);
|
|
if (Res == 0)
|
|
{
|
|
if (IsZombieProcess(Pid))
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
int Error = errno;
|
|
if (Error == EPERM)
|
|
{
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("No permission to signal running process %d: %d, assuming it is running"), Pid, Error);
|
|
return true;
|
|
}
|
|
else if (Error == ESRCH)
|
|
{
|
|
return false;
|
|
}
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("Failed to signal running process %d: %d, assuming it is running"), Pid, Error);
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
bool ZenServerState::Terminate(uint32 Pid)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
HANDLE Handle = OpenProcess(PROCESS_TERMINATE | SYNCHRONIZE, 0, (DWORD)Pid);
|
|
if (Handle == NULL)
|
|
{
|
|
DWORD Error = GetLastError();
|
|
|
|
if (Error != ERROR_INVALID_PARAMETER)
|
|
{
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("Failed to open running process for terminate %d: %d"), Pid, Error);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
ON_SCOPE_EXIT{ CloseHandle(Handle); };
|
|
|
|
BOOL bTerminated = TerminateProcess(Handle, 0);
|
|
if (!bTerminated)
|
|
{
|
|
DWORD Error = GetLastError();
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("Failed to terminate running process %d: %d"), Pid, Error);
|
|
return false;
|
|
}
|
|
DWORD WaitResult = WaitForSingleObject(Handle, 15000);
|
|
BOOL bSuccess = (WaitResult == WAIT_OBJECT_0) || (WaitResult == WAIT_ABANDONED_0);
|
|
if (!bSuccess)
|
|
{
|
|
if (WaitResult == WAIT_FAILED)
|
|
{
|
|
DWORD Error = GetLastError();
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("Failed to wait for terminated process %d: %d"), Pid, Error);
|
|
}
|
|
return false;
|
|
}
|
|
#elif PLATFORM_UNIX || PLATFORM_MAC
|
|
int Res = kill(pid_t(Pid), SIGKILL);
|
|
if (Res != 0)
|
|
{
|
|
int LastError = errno;
|
|
if (LastError != ESRCH)
|
|
{
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("Failed to terminate running process %d: %d"), Pid, LastError);
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
bool ZenServerState::FindRunningProcessId(const TCHAR* ExecutablePath, uint32* OutPid)
|
|
{
|
|
FString NormalizedExecutablePath(ExecutablePath);
|
|
FPaths::NormalizeFilename(NormalizedExecutablePath);
|
|
FPlatformProcess::FProcEnumerator ProcIter;
|
|
while (ProcIter.MoveNext())
|
|
{
|
|
FPlatformProcess::FProcEnumInfo ProcInfo = ProcIter.GetCurrent();
|
|
FString Candidate = ProcInfo.GetFullPath();
|
|
FPaths::NormalizeFilename(Candidate);
|
|
if (Candidate == NormalizedExecutablePath)
|
|
{
|
|
if (OutPid)
|
|
{
|
|
*OutPid = ProcInfo.GetPID();
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
ZenServerState::ZenServerState(bool ReadOnly)
|
|
: m_hMapFile(nullptr)
|
|
, m_Data(nullptr)
|
|
{
|
|
size_t MapSize = m_MaxEntryCount * sizeof(ZenServerEntry);
|
|
|
|
#if PLATFORM_WINDOWS
|
|
DWORD DesiredAccess = ReadOnly ? FILE_MAP_READ : (FILE_MAP_READ | FILE_MAP_WRITE);
|
|
HANDLE hMap = OpenFileMapping(DesiredAccess, 0, L"Global\\ZenMap");
|
|
if (hMap == NULL)
|
|
{
|
|
hMap = OpenFileMapping(DesiredAccess, 0, L"Local\\ZenMap");
|
|
}
|
|
|
|
if (hMap == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
void* pBuf = MapViewOfFile(hMap, // handle to map object
|
|
DesiredAccess, // read permission
|
|
0, // offset high
|
|
0, // offset low
|
|
MapSize);
|
|
|
|
if (pBuf == NULL)
|
|
{
|
|
CloseHandle(hMap);
|
|
return;
|
|
}
|
|
#elif PLATFORM_UNIX || PLATFORM_MAC
|
|
int OFlag = ReadOnly ? (O_RDONLY | O_CLOEXEC) : (O_RDWR | O_CREAT | O_CLOEXEC);
|
|
int Fd = shm_open("/UnrealEngineZen", OFlag, 0666);
|
|
if (Fd < 0)
|
|
{
|
|
return;
|
|
}
|
|
void* hMap = (void*)intptr_t(Fd);
|
|
|
|
int Prot = ReadOnly ? PROT_READ : (PROT_WRITE | PROT_READ);
|
|
void* pBuf = mmap(nullptr, MapSize, Prot, MAP_SHARED, Fd, 0);
|
|
if (pBuf == MAP_FAILED)
|
|
{
|
|
close(Fd);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
#if PLATFORM_WINDOWS || PLATFORM_UNIX || PLATFORM_MAC
|
|
m_hMapFile = hMap;
|
|
m_Data = reinterpret_cast<ZenServerEntry*>(pBuf);
|
|
#endif
|
|
m_IsReadOnly = ReadOnly;
|
|
}
|
|
|
|
ZenServerState::~ZenServerState()
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
if (m_Data)
|
|
{
|
|
UnmapViewOfFile(m_Data);
|
|
}
|
|
|
|
if (m_hMapFile)
|
|
{
|
|
CloseHandle(m_hMapFile);
|
|
}
|
|
#elif PLATFORM_UNIX || PLATFORM_MAC
|
|
if (m_Data != nullptr)
|
|
{
|
|
munmap((void*)m_Data, m_MaxEntryCount * sizeof(ZenServerEntry));
|
|
}
|
|
|
|
int Fd = int(intptr_t(m_hMapFile));
|
|
close(Fd);
|
|
#endif
|
|
m_hMapFile = nullptr;
|
|
m_Data = nullptr;
|
|
}
|
|
|
|
const ZenServerState::ZenServerEntry* ZenServerState::LookupByDesiredListenPortInternal(int Port) const
|
|
{
|
|
if (m_Data == nullptr)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
for (int i = 0; i < m_MaxEntryCount; ++i)
|
|
{
|
|
if (m_Data[i].DesiredListenPort.load(std::memory_order_relaxed) == Port)
|
|
{
|
|
const ZenServerState::ZenServerEntry* Entry = &m_Data[i];
|
|
if (IsProcessRunning((uint32)Entry->Pid.load(std::memory_order_relaxed)))
|
|
{
|
|
return Entry;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const ZenServerState::ZenServerEntry* ZenServerState::LookupByDesiredListenPort(int Port) const
|
|
{
|
|
return LookupByDesiredListenPortInternal(Port);
|
|
}
|
|
|
|
ZenServerState::ZenServerEntry* ZenServerState::LookupByDesiredListenPort(int Port)
|
|
{
|
|
check(!m_IsReadOnly);
|
|
return const_cast<ZenServerState::ZenServerEntry*>(LookupByDesiredListenPortInternal(Port));
|
|
}
|
|
|
|
const ZenServerState::ZenServerEntry* ZenServerState::LookupByEffectiveListenPortInternal(int Port) const
|
|
{
|
|
if (m_Data == nullptr)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
for (int i = 0; i < m_MaxEntryCount; ++i)
|
|
{
|
|
const ZenServerState::ZenServerEntry* Entry = &m_Data[i];
|
|
if (Entry->EffectiveListenPort.load(std::memory_order_relaxed) == Port)
|
|
{
|
|
if (IsProcessRunning((uint32)Entry->Pid.load(std::memory_order_relaxed)))
|
|
{
|
|
return Entry;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const ZenServerState::ZenServerEntry* ZenServerState::LookupByEffectiveListenPort(int Port) const
|
|
{
|
|
return LookupByEffectiveListenPortInternal(Port);
|
|
}
|
|
|
|
ZenServerState::ZenServerEntry* ZenServerState::LookupByEffectiveListenPort(int Port)
|
|
{
|
|
check(!m_IsReadOnly);
|
|
return const_cast<ZenServerState::ZenServerEntry*>(LookupByEffectiveListenPortInternal(Port));
|
|
}
|
|
|
|
const ZenServerState::ZenServerEntry* ZenServerState::LookupByPid(uint32 Pid) const
|
|
{
|
|
if (m_Data == nullptr)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
for (int i = 0; i < m_MaxEntryCount; ++i)
|
|
{
|
|
const ZenServerState::ZenServerEntry* Entry = &m_Data[i];
|
|
if (m_Data[i].Pid.load(std::memory_order_relaxed) == Pid)
|
|
{
|
|
if (IsProcessRunning(Pid))
|
|
{
|
|
return Entry;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool
|
|
ZenServerState::ZenServerEntry::AddSponsorProcess(uint32 PidToAdd)
|
|
{
|
|
uint32_t ServerPid = Pid.load();
|
|
|
|
// Sponsor processes are checked every second, so 2 second wait time should be enough
|
|
const FTimespan MaximumWaitForPickup = FTimespan::FromSeconds(2);
|
|
auto WaitForPickup = [&](uint32_t AddedSlotIndex) {
|
|
FDateTime StartedWaitingForPickup = FDateTime::UtcNow();
|
|
while (SponsorPids[AddedSlotIndex] == PidToAdd)
|
|
{
|
|
FTimespan WaitForPickup = FDateTime::UtcNow() - StartedWaitingForPickup;
|
|
if (WaitForPickup > MaximumWaitForPickup)
|
|
{
|
|
SponsorPids[AddedSlotIndex].compare_exchange_strong(PidToAdd, 0);
|
|
return false;
|
|
}
|
|
if (!IsProcessRunning(ServerPid))
|
|
{
|
|
SponsorPids[AddedSlotIndex].compare_exchange_strong(PidToAdd, 0);
|
|
return false;
|
|
}
|
|
FPlatformProcess::Sleep(0.1f);
|
|
}
|
|
return true;
|
|
};
|
|
for (uint32_t SponsorIndex = 0; SponsorIndex < 8; SponsorIndex++)
|
|
{
|
|
if (SponsorPids[SponsorIndex].load(std::memory_order_relaxed) == PidToAdd)
|
|
{
|
|
return WaitForPickup(SponsorIndex);
|
|
}
|
|
uint32_t Expected = 0;
|
|
if (SponsorPids[SponsorIndex].compare_exchange_strong(Expected, PidToAdd))
|
|
{
|
|
return WaitForPickup(SponsorIndex);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
ZenSharedEvent::ZenSharedEvent(FStringView EventName)
|
|
: m_EventName(EventName)
|
|
{
|
|
check(m_EventName.Len() > 0);
|
|
}
|
|
ZenSharedEvent::~ZenSharedEvent()
|
|
{
|
|
Close();
|
|
}
|
|
|
|
bool ZenSharedEvent::Create()
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
check(m_EventHandle == NULL);
|
|
FString FullEventName = GetFullEventName();
|
|
m_EventHandle = (void*)CreateEventW(nullptr, true, false, *FullEventName);
|
|
if (m_EventHandle == NULL)
|
|
{
|
|
DWORD LastError = GetLastError();
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("Failed creating named event '%s' (err: %d)"), *FullEventName, LastError);
|
|
return false;
|
|
}
|
|
return true;
|
|
#elif PLATFORM_UNIX || PLATFORM_MAC
|
|
check(m_Fd == -1);
|
|
check(m_Semaphore == -1);
|
|
FAnsiString EventPath = GetEventPath();
|
|
// Create a file to back the semaphore
|
|
m_Fd = open(*EventPath, O_RDWR | O_CREAT | O_CLOEXEC, 0666);
|
|
if (m_Fd < 0)
|
|
{
|
|
int LastError = errno;
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("Failed to create named event '%hs' (err: %d)"), *EventPath, LastError);
|
|
return false;
|
|
}
|
|
fchmod(m_Fd, 0666);
|
|
|
|
// Use the file path to generate an IPC key
|
|
key_t IpcKey = ftok(*EventPath, 1);
|
|
if (IpcKey < 0)
|
|
{
|
|
int LastError = errno;
|
|
close(m_Fd);
|
|
m_Fd = -1;
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("Failed to create an SysV IPC key for named event '%hs' (err: %d)"), *EventPath, LastError);
|
|
return false;
|
|
}
|
|
|
|
// Use the key to create/open the semaphore
|
|
m_Semaphore = semget(IpcKey, 1, 0600 | IPC_CREAT);
|
|
if (m_Semaphore < 0)
|
|
{
|
|
int LastError = errno;
|
|
close(m_Fd);
|
|
m_Fd = -1;
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("Failed creating an SysV semaphore for named event '%hs' (err: %d)"), *EventPath, LastError);
|
|
return false;
|
|
}
|
|
|
|
// Atomically claim ownership of the semaphore's key. The owner initializes
|
|
// 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(m_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(m_Semaphore, 0, SETVAL, 1);
|
|
}
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
bool ZenSharedEvent::Exists() const
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
FString FullEventName = GetFullEventName();
|
|
HANDLE EventHandle = OpenEventW(READ_CONTROL, false, *FullEventName);
|
|
if (EventHandle == NULL)
|
|
{
|
|
DWORD LastError = GetLastError();
|
|
if (LastError != ERROR_FILE_NOT_FOUND)
|
|
{
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("Failed checking existance of named event '%s' (err: %d)"), *FullEventName, LastError);
|
|
}
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
CloseHandle(EventHandle);
|
|
return false;
|
|
}
|
|
#elif PLATFORM_UNIX || PLATFORM_MAC
|
|
FAnsiString EventPath = GetEventPath();
|
|
key_t IpcKey = ftok(*EventPath, 1);
|
|
if (IpcKey < 0)
|
|
{
|
|
int LastError = errno;
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("Failed to create an SysV IPC key for named event '%hs' (err: %d)"), *EventPath, LastError);
|
|
return false;
|
|
}
|
|
int Semaphore = semget(IpcKey, 1, 0400);
|
|
if (Semaphore < 0)
|
|
{
|
|
int LastError = errno;
|
|
if (LastError != ENOENT)
|
|
{
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("Failed checking named event '%hs' (err: %d)"), *EventPath, LastError);
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
bool ZenSharedEvent::Open()
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
check(m_EventHandle == NULL);
|
|
FString FullEventName = GetFullEventName();
|
|
m_EventHandle = (void*)OpenEventW(EVENT_MODIFY_STATE, false, *FullEventName);
|
|
if (m_EventHandle == NULL)
|
|
{
|
|
DWORD LastError = GetLastError();
|
|
if (LastError != ERROR_FILE_NOT_FOUND)
|
|
{
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("Failed opening named event '%s' (err: %d)"), *FullEventName, LastError);
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
#elif PLATFORM_UNIX || PLATFORM_MAC
|
|
check(m_Fd == -1);
|
|
check(m_Semaphore == -1);
|
|
FAnsiString EventPath = GetEventPath();
|
|
key_t IpcKey = ftok(*EventPath, 1);
|
|
if (IpcKey < 0)
|
|
{
|
|
int LastError = errno;
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("Failed to create an SysV IPC key for named event '%hs' (err: %d)"), *EventPath, LastError);
|
|
return false;
|
|
}
|
|
m_Semaphore = semget(IpcKey, 1, 0600);
|
|
if (m_Semaphore < 0)
|
|
{
|
|
int LastError = errno;
|
|
if (LastError != ENOENT)
|
|
{
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("Failed opening named event '%hs' (err: %d)"), *EventPath, LastError);
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
bool ZenSharedEvent::Wait(int TimeoutMs)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
check(m_EventHandle != NULL);
|
|
|
|
const DWORD Timeout = (TimeoutMs < 0) ? INFINITE : TimeoutMs;
|
|
|
|
DWORD Result = WaitForSingleObject((HANDLE)m_EventHandle, Timeout);
|
|
|
|
if (Result == WAIT_FAILED)
|
|
{
|
|
DWORD LastError = GetLastError();
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("Failed waiting for named event '%s' (err: %d)"), *m_EventName, LastError);
|
|
return false;
|
|
}
|
|
|
|
return (Result == WAIT_OBJECT_0) || (Result == WAIT_ABANDONED_0);
|
|
|
|
#elif PLATFORM_UNIX || PLATFORM_MAC
|
|
check(m_Semaphore != -1);
|
|
|
|
int Result;
|
|
struct sembuf SemOp = {};
|
|
|
|
if (TimeoutMs < 0)
|
|
{
|
|
Result = semop(m_Semaphore, &SemOp, 1);
|
|
if (Result != 0)
|
|
{
|
|
int LastError = errno;
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("Failed waiting for named event '%s' (err: %d)"), *m_EventName, LastError);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
#if PLATFORM_UNIX
|
|
const int TimeoutSec = TimeoutMs / 1000;
|
|
struct timespec TimeoutValue = {
|
|
.tv_sec = TimeoutSec,
|
|
.tv_nsec = (TimeoutMs - (TimeoutSec * 1000)) * 1000000,
|
|
};
|
|
Result = semtimedop(m_Semaphore, &SemOp, 1, &TimeoutValue);
|
|
if (Result == 0)
|
|
{
|
|
return true;
|
|
}
|
|
int LastError = errno;
|
|
if (LastError != EAGAIN)
|
|
{
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("Failed waiting for named event '%s' (err: %d)"), *m_EventName, LastError);
|
|
}
|
|
return false;
|
|
#elif PLATFORM_MAC
|
|
const unsigned int SleepTimeMs = 10u;
|
|
SemOp.sem_flg = IPC_NOWAIT;
|
|
do
|
|
{
|
|
Result = semop(m_Semaphore, &SemOp, 1);
|
|
if (Result == 0)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
int LastError = errno;
|
|
if (errno != EAGAIN)
|
|
{
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("Failed waiting for named event '%s' (err: %d)"), *m_EventName, LastError);
|
|
break;
|
|
}
|
|
}
|
|
usleep(SleepTimeMs * 1000u);
|
|
TimeoutMs -= SleepTimeMs;
|
|
} while (TimeoutMs > 0);
|
|
return false;
|
|
#endif // PLATFORM_MAC
|
|
#endif
|
|
}
|
|
|
|
bool ZenSharedEvent::Set()
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
check(m_EventHandle != nullptr);
|
|
if (SetEvent((HANDLE)m_EventHandle))
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
DWORD LastError = GetLastError();
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("Failed signalling named event '%s' (err: %d)"), *m_EventName, LastError);
|
|
return false;
|
|
}
|
|
#elif PLATFORM_UNIX || PLATFORM_MAC
|
|
check(m_Semaphore != -1);
|
|
if (semctl(m_Semaphore, 0, SETVAL, 0) != -1)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
int LastError = errno;
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("Failed signalling named event '%s' (err: %d)"), *m_EventName, LastError);
|
|
return false;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void ZenSharedEvent::Close()
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
if (m_EventHandle != NULL)
|
|
{
|
|
CloseHandle((HANDLE)m_EventHandle);
|
|
}
|
|
m_EventHandle = NULL;
|
|
#elif PLATFORM_UNIX || PLATFORM_MAC
|
|
if (m_Fd != -1)
|
|
{
|
|
if (flock(m_Fd, LOCK_EX | LOCK_NB) == 0)
|
|
{
|
|
unlink(*m_EventPath);
|
|
|
|
flock(m_Fd, LOCK_UN | LOCK_NB);
|
|
close(m_Fd);
|
|
|
|
semctl(m_Semaphore, 0, IPC_RMID);
|
|
}
|
|
}
|
|
m_Fd = -1;
|
|
m_Semaphore = -1;
|
|
#endif
|
|
}
|
|
|
|
#if PLATFORM_WINDOWS
|
|
|
|
FString ZenSharedEvent::GetFullEventName() const
|
|
{
|
|
TStringBuilder<128> Name;
|
|
Name << "Local\\";
|
|
Name << m_EventName;
|
|
return *Name;
|
|
}
|
|
|
|
#elif PLATFORM_UNIX || PLATFORM_MAC
|
|
|
|
FAnsiString ZenSharedEvent::GetEventPath() const
|
|
{
|
|
TAnsiStringBuilder<128> Name;
|
|
Name << "/tmp/";
|
|
Name << m_EventName;
|
|
return *Name;
|
|
}
|
|
#endif
|
|
|
|
FString ZenSharedEvent::GetShutdownEventName(uint16 EffectiveListenPort)
|
|
{
|
|
return *WriteToWideString<64>(WIDETEXT("Zen_"), EffectiveListenPort, WIDETEXT("_Shutdown"));
|
|
}
|
|
|
|
FString ZenSharedEvent::GetStartupEventName()
|
|
{
|
|
return (*WriteToWideString<64>(WIDETEXT("Zen_"), FPlatformProcess::GetCurrentProcessId(), WIDETEXT("_Startup")));
|
|
}
|
|
|
|
|
|
|
|
static ZenLockFileData ReadLockData(FUniqueBuffer&& FileBytes)
|
|
{
|
|
if (ValidateCompactBinary(FileBytes, ECbValidateMode::Default) == ECbValidateError::None)
|
|
{
|
|
FCbObject LockObject(FileBytes.MoveToShared());
|
|
|
|
int32 ProcessId = LockObject["pid"].AsInt32();
|
|
FCbObjectId SessionId = LockObject["session_id"].AsObjectId();
|
|
int32 EffectivePort = LockObject["port"].AsInt32();
|
|
bool IsReady = LockObject["ready"].AsBool();
|
|
FUtf8StringView DataDir = LockObject["data"].AsString();
|
|
FUtf8StringView ExecutablePath = LockObject["executable"].AsString();
|
|
|
|
bool IsValid = ProcessId > 0 && EffectivePort > 0 && EffectivePort <= 0xffff;
|
|
|
|
return ZenLockFileData{
|
|
.ProcessId = static_cast<uint32>(ProcessId),
|
|
.SessionId = SessionId,
|
|
.EffectivePort = static_cast<uint16>(EffectivePort),
|
|
.IsReady = IsReady,
|
|
.DataDir = FString(DataDir),
|
|
.ExecutablePath = FString(ExecutablePath),
|
|
.IsValid = IsValid };
|
|
}
|
|
return ZenLockFileData{};
|
|
}
|
|
|
|
bool
|
|
ZenLockFileData::IsLockFileLocked(const TCHAR* FileName, bool bAttemptCleanUp)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
if (bAttemptCleanUp)
|
|
{
|
|
IFileManager::Get().Delete(FileName, false, false, true);
|
|
}
|
|
return IFileManager::Get().FileExists(FileName);
|
|
#elif PLATFORM_UNIX || PLATFORM_MAC
|
|
TAnsiStringBuilder<256> LockFilePath;
|
|
LockFilePath << FileName;
|
|
|
|
if (bAttemptCleanUp)
|
|
{
|
|
int32 Fd = open(LockFilePath.ToString(), O_RDONLY | O_CLOEXEC);
|
|
if (Fd < 0)
|
|
{
|
|
int LastError = errno;
|
|
if (LastError == ENOENT)
|
|
{
|
|
return false;
|
|
}
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("Failed opening lock file '%hs' (err: %d)"), *LockFilePath, LastError);
|
|
return true;
|
|
}
|
|
|
|
int32 LockRet = flock(Fd, LOCK_EX | LOCK_NB);
|
|
if (LockRet < 0)
|
|
{
|
|
int LastError = errno;
|
|
close(Fd);
|
|
if (LastError != EWOULDBLOCK)
|
|
{
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("Failed locking lock file '%hs' (err: %d)"), *LockFilePath, LastError);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
unlink(LockFilePath.ToString());
|
|
|
|
flock(Fd, LOCK_UN);
|
|
close(Fd);
|
|
}
|
|
|
|
struct stat Stat;
|
|
int Res = stat(LockFilePath.ToString(), &Stat);
|
|
if (Res == 0)
|
|
{
|
|
return true;
|
|
}
|
|
int LastError = errno;
|
|
if (LastError == ENOENT)
|
|
{
|
|
return false;
|
|
}
|
|
UE_LOG(LogZenServiceState, Warning, TEXT("Failed checking stat of '%hs' (err: %d)"), *LockFilePath, LastError);
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
ZenLockFileData
|
|
ZenLockFileData::ReadCbLockFile(const TCHAR* FileName)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
// Windows specific lock reading path
|
|
// Uses share flags that are unique to windows to allow us to read file contents while the file may be open for write AND delete by another process (zenserver).
|
|
|
|
uint32 Access = GENERIC_READ;
|
|
uint32 WinFlags = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
|
|
uint32 Create = OPEN_EXISTING;
|
|
|
|
TStringBuilder<MAX_PATH> FullFileNameBuilder;
|
|
FPathViews::ToAbsolutePath(FileName, FullFileNameBuilder);
|
|
for (TCHAR& Char : MakeArrayView(FullFileNameBuilder))
|
|
{
|
|
if (Char == TEXT('/'))
|
|
{
|
|
Char = TEXT('\\');
|
|
}
|
|
}
|
|
if (FullFileNameBuilder.Len() >= MAX_PATH)
|
|
{
|
|
FullFileNameBuilder.Prepend(TEXTVIEW("\\\\?\\"));
|
|
}
|
|
|
|
HANDLE Handle = CreateFileW(*FullFileNameBuilder, Access, WinFlags, NULL, Create, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
if (Handle != INVALID_HANDLE_VALUE)
|
|
{
|
|
ON_SCOPE_EXIT{ CloseHandle(Handle); };
|
|
LARGE_INTEGER LI;
|
|
if (GetFileSizeEx(Handle, &LI))
|
|
{
|
|
checkf(LI.QuadPart == LI.u.LowPart, TEXT("Lock file exceeds supported 2GB limit."));
|
|
int32 FileSize32 = LI.u.LowPart;
|
|
if (FileSize32 != 0)
|
|
{
|
|
FUniqueBuffer FileBytes = FUniqueBuffer::Alloc(FileSize32);
|
|
DWORD ReadBytes = 0;
|
|
if (ReadFile(Handle, FileBytes.GetData(), FileSize32, &ReadBytes, NULL) && (ReadBytes == FileSize32))
|
|
{
|
|
return ReadLockData(std::move(FileBytes));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return {};
|
|
#elif PLATFORM_UNIX || PLATFORM_MAC
|
|
TAnsiStringBuilder<256> LockFilePath;
|
|
LockFilePath << FileName;
|
|
int32 Fd = open(LockFilePath.ToString(), O_RDONLY | O_CLOEXEC);
|
|
if (Fd != -1)
|
|
{
|
|
ON_SCOPE_EXIT{ close(Fd); };
|
|
struct stat Stat;
|
|
int Res = fstat(Fd, &Stat);
|
|
if (Res == 0)
|
|
{
|
|
uint64 FileSize = uint64(Stat.st_size);
|
|
checkf(FileSize < 2u * 1024u * 1024u * 1024u, TEXT("Lock file exceeds supported 2GB limit."));
|
|
if (FileSize != 0)
|
|
{
|
|
FUniqueBuffer FileBytes = FUniqueBuffer::Alloc(FileSize);
|
|
if (read(Fd, FileBytes.GetData(), FileSize) == FileSize)
|
|
{
|
|
return ReadLockData(std::move(FileBytes));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return {};
|
|
#endif
|
|
}
|