Files
UnrealEngine/Engine/Source/Programs/Shared/EpicGames.Perforce.Native/Client.cpp
2025-05-18 13:04:45 +08:00

803 lines
18 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "CoreTypes.h"
THIRD_PARTY_INCLUDES_START
#pragma warning(push)
#pragma warning(disable: 4244)
#pragma warning(disable: 4458)
#include "p4/clientapi.h"
#include "p4/strtable.h"
#include "p4/stdhdrs.h"
#include "p4/datetime.h"
#include "p4/i18napi.h"
#include "p4/charset.h"
#include "p4/md5.h"
#include "p4/p4libs.h"
#include "p4/signaler.h"
#include "ThirdParty/gzip.h"
#pragma warning(pop)
#include <signal.h>
#include <assert.h>
THIRD_PARTY_INCLUDES_END
#ifdef _MSC_VER
#define NATIVE_API __declspec(dllexport)
#define strcasecmp _stricmp
#else
#define NATIVE_API __attribute__((visibility("default")))
#endif
#pragma warning(disable:4100)
#pragma warning(disable:4458)
struct FSettings
{
const char* ServerAndPort;
const char* User;
const char* Password;
const char* Host;
const char* Client;
const char* AppName;
const char* AppVersion;
};
struct FReadBuffer
{
unsigned char* Data;
unsigned int Length;
unsigned int Count;
unsigned int MaxLength;
unsigned int MaxCount;
};
struct FWriteBuffer
{
unsigned char* Data;
unsigned int MaxLength;
unsigned int MaxCount;
};
typedef void (FOnBufferReadyFn)(const FReadBuffer* ReadBuffer, FWriteBuffer* WriteBuffer);
class FClientUser : public ClientUser
{
private:
unsigned char* Data;
unsigned int Count;
unsigned int Length;
unsigned int MaxCount;
unsigned int MaxLength;
FOnBufferReadyFn* OnBufferReady;
const char* InputBuffer = nullptr;
int InputLength = 0;
public:
const char* Func = nullptr;
const char* PromptResponse = nullptr;
bool InterceptIo = false;
FClientUser(FWriteBuffer* WriteBuffer, FOnBufferReadyFn* OnBufferReady)
{
SetWriteBuffer(WriteBuffer);
this->OnBufferReady = OnBufferReady;
}
virtual FileSys* File(FileSysType type) override;
virtual void InputData(StrBuf* strbuf, Error* e) override
{
if (InputBuffer != nullptr)
{
strbuf->Set(InputBuffer, InputLength);
}
else
{
ClientUser::InputData(strbuf, e);
}
}
virtual void Prompt(Error* err, StrBuf& rsp, int noEcho, Error* e) override
{
rsp.Set(PromptResponse);
}
virtual void Prompt(Error* err, StrBuf& rsp, int noEcho, int noOutput, Error* e) override
{
rsp.Set(PromptResponse);
}
virtual void Prompt(const StrPtr& msg, StrBuf& rsp, int noEcho, Error* e) override
{
rsp.Set(PromptResponse);
}
virtual void Prompt(const StrPtr& msg, StrBuf& rsp, int noEcho, int noOutput, Error* e) override
{
rsp.Set(PromptResponse);
}
void SetInputBuffer(const char* InInputBuffer, int InInputLength)
{
InputBuffer = InInputBuffer;
InputLength = InInputLength;
}
void SetWriteBuffer(FWriteBuffer* WriteBuffer)
{
this->Data = WriteBuffer->Data;
this->Length = 0;
this->Count = 0;
this->MaxLength = WriteBuffer->MaxLength;
this->MaxCount = (size_t)WriteBuffer->MaxCount;
}
void Flush()
{
// Writing an empty buffer indicates to the owning process that we need more data, so avoid that during a normal flush.
if (Length > 0)
{
FlushOrExpandBuffer();
}
}
void FlushOrExpandBuffer()
{
FReadBuffer ReadBuffer;
ReadBuffer.Data = Data;
ReadBuffer.Length = Length;
ReadBuffer.Count = Count;
ReadBuffer.MaxLength = MaxLength;
ReadBuffer.MaxCount = MaxCount;
FWriteBuffer WriteBuffer;
memset(&WriteBuffer, 0, sizeof(WriteBuffer));
(*OnBufferReady)(&ReadBuffer, &WriteBuffer);
SetWriteBuffer(&WriteBuffer);
}
virtual void HandleError(Error* err) override
{
while (!TryOutputError(err))
{
FlushOrExpandBuffer();
}
}
virtual void Message(Error* Err) override
{
while (!TryOutputError(Err))
{
FlushOrExpandBuffer();
}
}
bool TryOutputError(Error* Err)
{
static const char CodeKey[] = "code";
static const char SeverityKey[] = "severity";
static const char GenericKey[] = "generic";
static const char DataKey[] = "data";
StrBuf Message;
Err->Fmt(&Message, 0);
unsigned int MessageLen = Message.Length();
unsigned int RecordLen;
if (Err->GetSeverity() == E_INFO)
{
static const char InfoCode[] = "info";
RecordLen = 1 + MeasureStringField(CodeKey, InfoCode) + MeasureStringField(DataKey, MessageLen) + 1;
if (Length + RecordLen > MaxLength)
{
return false;
}
unsigned char* Pos = Data + Length;
*(Pos++) = '{';
Pos = WriteStringField(Pos, CodeKey, InfoCode);
Pos = WriteStringField(Pos, DataKey, Message.Text(), MessageLen);
*(Pos++) = '0';
assert(Pos == Data + Length + RecordLen);
}
else
{
static const char ErrorCode[] = "error";
RecordLen = 1 + MeasureStringField(CodeKey, ErrorCode) + MeasureIntField(SeverityKey) + MeasureIntField(GenericKey) + MeasureStringField(DataKey, MessageLen) + 1;
if (Length + RecordLen > MaxLength)
{
return false;
}
unsigned char* Pos = Data + Length;
*(Pos++) = '{';
Pos = WriteStringField(Pos, CodeKey, ErrorCode);
Pos = WriteIntField(Pos, SeverityKey, Err->GetSeverity());
Pos = WriteIntField(Pos, GenericKey, Err->GetGeneric());
Pos = WriteStringField(Pos, DataKey, Message.Text(), MessageLen);
*(Pos++) = '0';
assert(Pos == Data + Length + RecordLen);
}
Length += RecordLen;
return true;
}
virtual void OutputError(const char* errBuf) override
{
assert(false);
}
void OutputIo(int FileId, const char* Command, const void* Payload, int PayloadLen)
{
while (!TryOutputIo(FileId, Command, Payload, PayloadLen))
{
FlushOrExpandBuffer();
}
}
bool TryOutputIo(int FileId, const char* Command, const void* Payload, int PayloadLen)
{
static const char CodeKey[] = "code";
static const char FileKey[] = "file";
static const char CommandKey[] = "command";
static const char PayloadKey[] = "payload";
static const char IoCode[] = "io";
unsigned int CommandLen = (unsigned int)strlen(Command);
unsigned int RecordLen = (unsigned int)(1 + MeasureStringField(CodeKey, IoCode) + MeasureIntField(FileKey) + MeasureStringField(CommandKey, CommandLen) + MeasureStringField(PayloadKey, PayloadLen) + 1);
if (Length + RecordLen > MaxLength)
{
return false;
}
unsigned char* Pos = Data + Length;
*(Pos++) = '{';
Pos = WriteStringField(Pos, CodeKey, IoCode);
Pos = WriteIntField(Pos, FileKey, FileId);
Pos = WriteStringField(Pos, CommandKey, Command, CommandLen);
Pos = WriteStringField(Pos, PayloadKey, (const char*)Payload, PayloadLen);
*(Pos++) = '0';
assert(Pos == Data + Length + RecordLen);
Length += RecordLen;
return true;
}
virtual void OutputInfo(char InLevel, const char* InData) override
{
while (!TryOutputInfo(InLevel, InData))
{
FlushOrExpandBuffer();
}
}
bool TryOutputInfo(char InLevel, const char* InInfo)
{
static const char CodeKey[] = "code";
static const char LevelKey[] = "level";
static const char DataKey[] = "data";
static const char InfoCode[] = "info";
unsigned int InfoLen = (unsigned int)strlen(InInfo);
unsigned int RecordLen = 1 + MeasureStringField(CodeKey, InfoCode) + MeasureIntField(LevelKey) + MeasureStringField(DataKey, InfoLen) + 1;
if (Length + RecordLen > MaxLength)
{
return false;
}
unsigned char* Pos = Data + Length;
*(Pos++) = '{';
Pos = WriteStringField(Pos, CodeKey, InfoCode);
Pos = WriteIntField(Pos, LevelKey, InLevel);
Pos = WriteStringField(Pos, DataKey, InInfo, InfoLen);
*(Pos++) = '0';
assert(Pos == Data + Length + RecordLen);
Length += RecordLen;
return true;
}
virtual void OutputBinary(const char* InData, int InLength) override
{
assert(false);
}
virtual void OutputText(const char* InData, int InLength) override
{
assert(false);
}
virtual void OutputStat(StrDict* VarList) override
{
while (!TryWriteRecord(VarList))
{
FlushOrExpandBuffer();
}
if (++Count > MaxCount)
{
Flush();
}
}
template<int KeyLen>
static int MeasureStringField(const char(&Key)[KeyLen], int ValueLen)
{
return 10 + (KeyLen - 1) + ValueLen;
}
template<int KeyLen, int ValueLen>
static int MeasureStringField(const char(&Key)[KeyLen], const char(&Value)[ValueLen])
{
return MeasureStringField(Key, ValueLen - 1);
}
template<int KeyLen>
static int MeasureIntField(const char(&Key)[KeyLen])
{
return 10 + (KeyLen - 1);
}
template<int KeyLen>
static unsigned char* WriteStringField(unsigned char* Data, const char(&Key)[KeyLen], const char* Value, int ValueLen)
{
Data = WriteString(Data, Key);
return WriteString(Data, Value, ValueLen);
}
template<int KeyLen, int ValueLen>
static unsigned char* WriteStringField(unsigned char* Data, const char(&Key)[KeyLen], const char(&Value)[ValueLen])
{
Data = WriteString(Data, Key);
return WriteString(Data, Value);
}
template<int KeyLen>
static unsigned char* WriteIntField(unsigned char* Data, const char(&Key)[KeyLen], int Value)
{
Data = WriteString(Data, Key);
return WriteInt(Data, Value);
}
static unsigned char* WriteInt(unsigned char* Data, int Value)
{
*(Data++) = 'i';
return WriteIntValue(Data, Value);
}
static unsigned char* WriteIntValue(unsigned char* Data, int Value)
{
*(Data++) = Value & 0xff;
*(Data++) = (Value >> 8) & 0xff;
*(Data++) = (Value >> 16) & 0xff;
*(Data++) = (Value >> 24) & 0xff;
return Data;
}
static unsigned char* WriteString(unsigned char* Data, const char* Text, int Length)
{
*(Data++) = 's';
Data = WriteIntValue(Data, Length);
memcpy(Data, Text, Length);
return Data + Length;
}
template<int Length>
static unsigned char* WriteString(unsigned char* Data, const char(&Text)[Length])
{
return WriteString(Data, Text, Length - 1);
}
bool TryWriteRecord(StrDict* VarList)
{
return TryWriteRecord("stat", 4, VarList);
}
bool TryWriteRecord(const char* Code, unsigned int CodeLen, StrDict* VarList)
{
unsigned int Pos = Length;
// Record prefix: '{' (open record), string, 4 bytes, 'code', string ...
unsigned char Prefix[] = { '{', 's', 4, 0, 0, 0, 'c', 'o', 'd', 'e', 's' };
unsigned int PrefixLen = sizeof(Prefix) / sizeof(Prefix[0]);
if (Pos + PrefixLen + (unsigned int)sizeof(unsigned int) + CodeLen > MaxLength - 1)
{
return false;
}
memcpy(Data + Pos, Prefix, PrefixLen);
Pos += PrefixLen;
memcpy(Data + Pos, &CodeLen, sizeof(CodeLen));
Pos += sizeof(unsigned int);
memcpy(Data + Pos, Code, CodeLen);
Pos += CodeLen;
for (int Idx = 0;; Idx++)
{
StrRef Field;
StrRef Value;
if (VarList->GetVar(Idx, Field, Value) == 0)
{
break;
}
unsigned int FieldLength = Field.Length();
unsigned int ValueLength = Value.Length();
if (Pos + 1 + 4 + FieldLength + 1 + 4 + ValueLength > MaxLength - 1)
{
return false;
}
Data[Pos] = 's';
Pos++;
memcpy(Data + Pos, &FieldLength, sizeof(FieldLength));
Pos += sizeof(FieldLength);
memcpy(Data + Pos, Field.Text(), FieldLength);
Pos += FieldLength;
Data[Pos] = 's';
Pos++;
memcpy(Data + Pos, &ValueLength, sizeof(ValueLength));
Pos += sizeof(ValueLength);
memcpy(Data + Pos, Value.Text(), ValueLength);
Pos += ValueLength;
}
Data[Pos++] = '0';
Length = Pos;
return true;
}
};
class FFileSys : public FileSys
{
public:
static int NextFileId;
int FileId = -1;
const FileSysType Type;
class FClientUser& User;
FFileSys(FileSysType InType, FClientUser& InUser)
: Type(InType)
, User(InUser)
{
this->type = InType;
}
virtual void ChmodTimeHP(const DateTimeHighPrecision& /* modTime */, Error* /* e */) override {};
virtual void SetAttribute(FileSysAttr, Error*) override { };
virtual bool HasOnlyPerm(FilePerm perms) override { return false; }
virtual FD_PTR GetFd() override { return FD_ERR; }
virtual int GetOwner() override { return 0; }
virtual offL_t GetSize() override { return 0; }
virtual void Seek(offL_t offset, Error*) override { assert(false); }
virtual offL_t Tell() override { assert(false); return 0; }
virtual int StatAccessTime() override { assert(false); return 0; }
virtual void MakeLocalTemp(char* file) override { assert(false); }
virtual void SetDeleteOnClose() override { }
virtual void ClearDeleteOnClose() override {}
virtual StrArray* ScanDir(Error* e) override { return nullptr; }
virtual void MkDir(const StrPtr& p, Error* e) override { }
virtual void PurgeDir(const char* p, Error* e) override { }
virtual void RmDir(const StrPtr& p, Error* e) override { }
virtual int ReadLine(StrBuf* buf, Error* e) override { return 0; }
virtual void Open(FileOpenMode mode, Error* e) override
{
this->mode = mode;
int PathLen = path.Length() + 1;
int BufferLen = PathLen + (sizeof(int) * 3);
char* Buffer = new char[BufferLen];
char* BufferEnd = Buffer;
memcpy(BufferEnd, path.Text(), (size_t)PathLen);
BufferEnd += PathLen;
int TypeInt = (int)Type;
memcpy(BufferEnd, &TypeInt, sizeof(int));
BufferEnd += sizeof(int);
int ModeInt = (int)mode;
memcpy(BufferEnd, &ModeInt, sizeof(int));
BufferEnd += sizeof(int);
int PermsInt = (int)perms;
memcpy(BufferEnd, &PermsInt, sizeof(int));
BufferEnd += sizeof(int);
FileId = ++NextFileId;
User.OutputIo(FileId, "open", Buffer, BufferLen);
delete[] Buffer;
}
virtual void Write(const char* buf, int len, Error* e) override
{
User.OutputIo(FileId, "write", buf, len);
if (checksum && len > 0)
checksum->Update(StrRef(buf, len));
}
virtual int Read(char* buf, int len, Error* e) override
{
return 0;
}
virtual void Close(Error* e) override
{
if (FileId != -1)
{
User.OutputIo(FileId, "close", nullptr, 0);
FileId = -1;
}
}
virtual int Stat() override
{
return 0;
}
virtual int StatModTime() override
{
return 0;
}
virtual void StatModTimeHP(DateTimeHighPrecision* modTime) override
{
}
virtual void Truncate(Error* e) override
{
assert(false);
}
virtual void Truncate(offL_t offset, Error* e) override
{
assert(false);
}
virtual void Unlink(Error* e = 0) override
{
User.OutputIo(FileId, "unlink", path.Text(), path.Length());
}
virtual void Rename(FileSys* target, Error* e) override
{
assert(false);
}
virtual void Chmod(FilePerm perms, Error* e) override
{
assert(false);
}
virtual void ChmodTime(Error* e) override
{
assert(false);
}
};
class FUnzipFileSys : public FFileSys
{
public:
Gzip* GzipInst;
char* Buffer;
const size_t BufferSize;
FUnzipFileSys(FileSysType InType, FClientUser& InUser)
: FFileSys(InType, InUser)
, BufferSize(FFileSys::BufferSize())
{
GzipInst = nullptr;
Buffer = nullptr;
}
~FUnzipFileSys()
{
Cleanup();
delete GzipInst;
delete[] Buffer;
}
virtual void Open(FileOpenMode Mode, Error* Err) override
{
Buffer = new char[BufferSize];
GzipInst = new Gzip();
GzipInst->is = Buffer;
GzipInst->ie = Buffer;
GzipInst->os = Buffer;
GzipInst->oe = Buffer + BufferSize;
FFileSys::Open(Mode, Err);
}
virtual void Write(const char* WriteBuf, int WriteLen, Error* e) override
{
GzipInst->is = WriteBuf;
GzipInst->ie = WriteBuf + WriteLen;
for (;;)
{
if (GzipInst->OutputFull())
{
FFileSys::Write(Buffer, (int)(GzipInst->os - Buffer), e);
GzipInst->os = Buffer;
}
if (e->Test() || !GzipInst->Uncompress(e) || GzipInst->InputEmpty())
{
break;
}
}
}
virtual void Close(Error* e) override
{
if (GzipInst && mode == FOM_WRITE && GzipInst->os > Buffer)
{
FFileSys::Write(Buffer, (int)(GzipInst->os - Buffer), e);
GzipInst->os = Buffer;
}
FFileSys::Close(e);
}
};
int FFileSys::NextFileId = 100;
FileSys* FClientUser::File(FileSysType type)
{
if (InterceptIo)
{
if ((type & FST_C_MASK) == FST_C_GUNZIP)
{
return new FUnzipFileSys(type, *this);
}
else
{
return new FFileSys(type, *this);
}
}
else
{
return ClientUser::File(type);
}
}
class FClient
{
public:
ClientApi ClientApi;
FClientUser User;
char* AppVersion;
FClient(FWriteBuffer* WriteBuffer, FOnBufferReadyFn* OnBufferReady)
: User(WriteBuffer, OnBufferReady)
{
AppVersion = nullptr;
}
~FClient()
{
if (AppVersion != nullptr)
{
free(AppVersion);
}
}
};
struct FClientGlobalInit
{
FClientGlobalInit()
{
Error e;
P4Libraries::Initialize(P4LIBRARIES_INIT_ALL, &e);
signal(SIGINT, SIG_DFL); // unset the default set by global signaler in C++ so it does not exit
signaler.Disable(); // disable the global signaler memory tracking at runtime
}
};
extern "C" NATIVE_API FClient* Client_Create(const FSettings* Settings, FWriteBuffer* WriteBuffer, FOnBufferReadyFn* OnBufferReady)
{
static FClientGlobalInit GlobalInit;
FClient* Client = new FClient(WriteBuffer, OnBufferReady);
if (Settings != nullptr)
{
if (Settings->ServerAndPort != nullptr)
{
Client->ClientApi.SetPort(Settings->ServerAndPort);
}
if (Settings->User != nullptr)
{
Client->ClientApi.SetUser(Settings->User);
}
if (Settings->Password != nullptr)
{
Client->ClientApi.SetPassword(Settings->Password);
}
if (Settings->Host != nullptr)
{
Client->ClientApi.SetHost(Settings->Host);
}
if (Settings->Client != nullptr)
{
Client->ClientApi.SetClient(Settings->Client);
}
if (Settings->AppName != nullptr)
{
Client->ClientApi.SetProg(Settings->AppName);
}
if (Settings->AppVersion != nullptr)
{
Client->AppVersion = strdup(Settings->AppVersion);
}
}
Client->ClientApi.SetProtocol("tag", "");
Client->ClientApi.SetProtocol("unicode", "");
Error Err;
Client->ClientApi.Init(&Err);
Client->User.HandleError(&Err);
Client->User.Flush();
GlobalCharSet::Set(CharSetApi::UTF_8);
return Client;
}
extern "C" NATIVE_API void Client_Command(FClient* Client, const char* Func, int ArgCount, const char** Args, const char* InputData, int InputLength, const char* promptResponse, bool InterceptIo)
{
if (Client->AppVersion != nullptr)
{
Client->ClientApi.SetVersion(Client->AppVersion);
}
Client->User.InterceptIo = InterceptIo;
Client->User.Func = Func;
Client->User.SetInputBuffer(InputData, InputLength);
Client->User.PromptResponse = promptResponse;
Client->ClientApi.SetArgv(ArgCount, (char* const*)Args);
Client->ClientApi.Run(Func, &Client->User);
Client->User.Flush();
Client->User.PromptResponse = nullptr;
Client->User.SetInputBuffer(nullptr, 0);
Client->User.Func = nullptr;
Client->User.InterceptIo = false;
}
extern "C" NATIVE_API void Client_Destroy(FClient* Client)
{
Error Err;
Client->ClientApi.Final(&Err);
Client->User.HandleError(&Err);
delete Client;
}