Files
UnrealEngineUWP/Engine/Source/Developer/Zen/Private/ZenServerState.cpp
dan engelbrecht cb4a52a496 Allow zenserver to be installed using a "link" to the installed UE executables instead of copying to the %PROGRAMDATA% folder which may have restrictions preventing the zenserver executable from running.
Selecting "link" or "copy" is done automatically by checking if UE is running from an installed place or if it is a dev/p4 stream place, you can override this with the option -ZenAutoLaunchInstallMode.
The ZenAutoLaunchInstallMode defaults to "auto" where it tries to detect if it is an installed engine or not. "link" forces the use of a link file and "copy" forces the copy-install option which is the legacy behavior.

Additional fixes:
FMacSystemWideCriticalSection which could fail to aquire the critical section causing launch to fail  now uses flock in the same manner as FUnixSystemWideCriticalSection which resolves the issues
When checking if processes are running on Unix/Mac we now detect zombie processes properly

#jira UE-224517

#rb zack.neyland, Zousar.Shaker

[CL 36764363 by dan engelbrecht in 5.5 branch]
2024-10-01 20:41:28 -04:00

886 lines
21 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 + 1] = 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 err = errno;
if (err != ESRCH)
{
UE_LOG(LogZenServiceState, Warning, TEXT("Failed to terminate running process %d: %d"), Pid, err);
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)
{
for (std::atomic<uint32>& PidEntry : SponsorPids)
{
if (PidEntry.load(std::memory_order_relaxed) == 0)
{
uint32 Expected = 0;
if (PidEntry.compare_exchange_strong(Expected, PidToAdd))
{
// Success!
return true;
}
}
else if (PidEntry.load(std::memory_order_relaxed) == PidToAdd)
{
// Success, the because pid is already in the list
return true;
}
}
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 '{EventName}' (err: {Error})"), *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 '{EventName}' (err: {Error})"), *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 '{EventName}' (err: {Error})"), *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 '{EventName}' (err: {Error})"), *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 '{EventName}' (err: {Error})"), *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 '{EventName}' (err: {Error})"), *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 '{EventName}' (err: {Error})"), *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 '{EventName}' (err: {Error})"), *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 '{EventName}' (err: {Error})"), *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 '{EventName}' (err: {Error})"), *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 '{EventName}' (err: {Error})"), *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 '{EventName}' (err: {Error})"), *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 '{EventName}' (err: {Error})"), *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 '{EventName}' (err: {Error})"), *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 '{EventName}' (err: {Error})"), *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 '{EventName}' (err: {Error})"), *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();
FUtf8StringView DataDir = LockObject["data"].AsString();
int32 EffectivePort = LockObject["port"].AsInt32();
FCbObjectId SessionId = LockObject["session_id"].AsObjectId();
bool IsReady = LockObject["ready"].AsBool();
bool IsValid = ProcessId > 0 && EffectivePort > 0 && EffectivePort <= 0xffff;
return ZenLockFileData{ static_cast<uint32>(ProcessId), FString(DataDir), static_cast<uint16>(EffectivePort), SessionId, IsReady, IsValid };
}
return ZenLockFileData{ 0, {}, 0, {}, false };
}
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;
int32 Fd = open(LockFilePath.ToString(), O_RDONLY);
if (Fd < 0)
{
return false;
}
int32 LockRet = flock(Fd, LOCK_EX | LOCK_NB);
if (LockRet < 0)
{
close(Fd);
return errno == EWOULDBLOCK || errno == EAGAIN;
}
// Consider the lock file as orphaned if we we managed to claim the lock for
// it. Might as well delete it while we own it.
unlink(LockFilePath.ToString());
flock(Fd, LOCK_UN);
close(Fd);
return false;
#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.ToString(), 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;
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);
if (Fd < 0)
{
return {};
}
// If we can claim the lock then it's an orphaned lock file and should be
// ignored. Not ideal as there's a period of time when the lock can be
// held unncessarily.
int32 LockRet = flock(Fd, LOCK_EX | LOCK_NB);
if (LockRet >= 0)
{
unlink(LockFilePath.ToString());
flock(Fd, LOCK_UN);
close(Fd);
return {};
}
if (errno != EWOULDBLOCK && errno != EAGAIN)
{
close(Fd);
return {};
}
struct stat Stat;
fstat(Fd, &Stat);
uint64 FileSize = uint64(Stat.st_size);
FUniqueBuffer FileBytes = FUniqueBuffer::Alloc(FileSize);
if (read(Fd, FileBytes.GetData(), FileSize) == FileSize)
{
close(Fd);
return ReadLockData(std::move(FileBytes));
}
close(Fd);
return {};
#endif
}