Merge pull request #126 from Mauler125/backtrace

Implement crash reporting system (Backtrace)
This commit is contained in:
Kawe Mazidjatari 2024-09-13 20:26:48 +02:00 committed by GitHub
commit ae60220a00
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 310 additions and 59 deletions

View File

@ -53,7 +53,7 @@ typedef float float32;
typedef double float64; typedef double float64;
typedef float32 f32; typedef float32 f32;
typedef float32 f64; typedef float64 f64;
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// 8-bit <--> 64-bit wide boolean type // 8-bit <--> 64-bit wide boolean type
typedef int8 b8; typedef int8 b8;

View File

@ -7,6 +7,7 @@
#include "tier0/basetypes.h" #include "tier0/basetypes.h"
#include "tier0/crashhandler.h" #include "tier0/crashhandler.h"
#include "tier0/commandline.h" #include "tier0/commandline.h"
#include "tier2/crashreporter.h"
/*****************************************************************************/ /*****************************************************************************/
#ifndef DEDICATED #ifndef DEDICATED
#include "windows/id3dx.h" #include "windows/id3dx.h"
@ -38,12 +39,10 @@ static HMODULE s_hModuleHandle = NULL;
// UTILITY // UTILITY
//############################################################################# //#############################################################################
void Crash_Callback() void Crash_Callback(const CCrashHandler* handler)
{ {
// Shutdown SpdLog to flush all buffers. CrashReporter_SubmitToCollector(handler);
SpdLog_Shutdown(); SpdLog_Shutdown(); // Shutdown SpdLog to flush all buffers.
// TODO[ AMOS ]: This is where we want to call backtrace from.
} }
void Show_Emblem() void Show_Emblem()

View File

@ -401,7 +401,7 @@ void QuerySystemInfo()
Msg(eDLL_T::NONE, "%-25s: '%s'\n","CPU model identifier", pi.m_szProcessorBrand); Msg(eDLL_T::NONE, "%-25s: '%s'\n","CPU model identifier", pi.m_szProcessorBrand);
Msg(eDLL_T::NONE, "%-25s: '%s'\n","CPU vendor tag", pi.m_szProcessorID); Msg(eDLL_T::NONE, "%-25s: '%s'\n","CPU vendor tag", pi.m_szProcessorID);
Msg(eDLL_T::NONE, "%-25s: '%12hhu' ('%2hhu' %s)\n", "CPU core count", pi.m_nPhysicalProcessors, pi.m_nLogicalProcessors, "logical"); Msg(eDLL_T::NONE, "%-25s: '%12hhu' ('%2hhu' %s)\n", "CPU core count", pi.m_nPhysicalProcessors, pi.m_nLogicalProcessors, "logical");
Msg(eDLL_T::NONE, "%-25s: '%12lld' ('%6.1f' %s)\n", "CPU core speed", pi.m_Speed, float(pi.m_Speed / 1000000), "MHz"); Msg(eDLL_T::NONE, "%-25s: '%12lld' ('%.1lf' %s)\n", "CPU core speed", pi.m_Speed, f64(pi.m_Speed / 1000000.0), "MHz");
Msg(eDLL_T::NONE, "%-20s%s: '%12lu' ('0x%-8X')\n", "L1 cache", "(KiB)", pi.m_nL1CacheSizeKb, pi.m_nL1CacheDesc); Msg(eDLL_T::NONE, "%-20s%s: '%12lu' ('0x%-8X')\n", "L1 cache", "(KiB)", pi.m_nL1CacheSizeKb, pi.m_nL1CacheDesc);
Msg(eDLL_T::NONE, "%-20s%s: '%12lu' ('0x%-8X')\n", "L2 cache", "(KiB)", pi.m_nL2CacheSizeKb, pi.m_nL2CacheDesc); Msg(eDLL_T::NONE, "%-20s%s: '%12lu' ('0x%-8X')\n", "L2 cache", "(KiB)", pi.m_nL2CacheSizeKb, pi.m_nL2CacheDesc);
Msg(eDLL_T::NONE, "%-20s%s: '%12lu' ('0x%-8X')\n", "L3 cache", "(KiB)", pi.m_nL3CacheSizeKb, pi.m_nL3CacheDesc); Msg(eDLL_T::NONE, "%-20s%s: '%12lu' ('0x%-8X')\n", "L3 cache", "(KiB)", pi.m_nL3CacheSizeKb, pi.m_nL3CacheDesc);

View File

@ -4,6 +4,26 @@
#define CRASHMESSAGE_MSG_EXECUTABLE "bin\\crashmsg.exe" #define CRASHMESSAGE_MSG_EXECUTABLE "bin\\crashmsg.exe"
class CCrashHandler;
typedef void (*CrashCallback_t)(const CCrashHandler* handler);
struct CrashHardWareInfo_s
{
CrashHardWareInfo_s()
{
displayDevice = { sizeof(displayDevice), {0} };
memoryStatus = { sizeof(memoryStatus) , {0} };
availDiskSpace = 0;
totalDiskSpace = 0;
}
DISPLAY_DEVICE displayDevice;
MEMORYSTATUSEX memoryStatus;
size_t totalDiskSpace;
size_t availDiskSpace;
};
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Purpose: // Purpose:
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -29,6 +49,12 @@ public:
inline bool IsValid() const { return m_hExceptionHandler != nullptr; }; inline bool IsValid() const { return m_hExceptionHandler != nullptr; };
inline bool CrashingModuleNameKnown() const { return m_CrashingModule.Length() > 0; }
inline const char* GetCrashingModuleName() const { return m_CrashingModule.String(); }
inline const EXCEPTION_POINTERS* GetExceptionPointers() const { return m_pExceptionPointers; }
inline const CrashHardWareInfo_s& GetHardwareInfo() const { return m_HardWareInfo; }
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
// Formatters: // Formatters:
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
@ -46,13 +72,13 @@ public:
const char* ExceptionToString(const DWORD nExceptionCode) const; const char* ExceptionToString(const DWORD nExceptionCode) const;
void SetExceptionPointers(EXCEPTION_POINTERS* const pExceptionPointers) { m_pExceptionPointers = pExceptionPointers; } void SetExceptionPointers(EXCEPTION_POINTERS* const pExceptionPointers) { m_pExceptionPointers = pExceptionPointers; }
void SetCrashCallback(PVOID pCrashCallback) { m_pCrashCallback = pCrashCallback; } void SetCrashCallback(CrashCallback_t pCrashCallback) { m_pCrashCallback = pCrashCallback; }
void CaptureCallStack(); void CaptureCallStack();
void WriteFile(); void WriteFile();
void CreateMessageProcess(); void CreateMessageProcess();
void CrashCallback(); void CrashCallback() const;
private: private:
@ -83,7 +109,7 @@ private:
HMODULE m_ppModuleHandles[MAX_MODULE_HANDLES]; HMODULE m_ppModuleHandles[MAX_MODULE_HANDLES];
// Custom crash callback that's called after the logs have been written. // Custom crash callback that's called after the logs have been written.
PVOID m_pCrashCallback; CrashCallback_t m_pCrashCallback;
// Current exception handler, only kept here for tracking as we need the // Current exception handler, only kept here for tracking as we need the
// handle if we want to remove this handler. // handle if we want to remove this handler.
@ -109,6 +135,9 @@ private:
// Set when crashmsg.exe is created to prevent recursive messages. // Set when crashmsg.exe is created to prevent recursive messages.
bool m_bMessageCreated; bool m_bMessageCreated;
// Cached hardware info this application crashed on.
CrashHardWareInfo_s m_HardWareInfo;
mutable RTL_SRWLOCK m_Lock; mutable RTL_SRWLOCK m_Lock;
}; };

View File

@ -94,9 +94,17 @@ string Format(const char* szFormat, ...);
template <typename Iter, typename Compare> template <typename Iter, typename Compare>
Iter ExtremeElementABS(Iter first, Iter last, Compare compare) Iter ExtremeElementABS(Iter first, Iter last, Compare compare)
{ {
auto abs_compare = [compare](LONG a, LONG b) using ValueType = typename std::iterator_traits<Iter>::value_type;
auto abs_compare = [compare](ValueType a, ValueType b)
{ {
return compare(abs(a), abs(b)); if constexpr (std::is_signed_v<ValueType>)
{
return compare(abs(a), abs(b));
}
else
{
return compare(a, b);
}
}; };
return std::min_element(first, last, abs_compare); return std::min_element(first, last, abs_compare);

View File

@ -0,0 +1,11 @@
//=============================================================================//
//
// Purpose: Post-mortem crash reporter
//
//=============================================================================//
#ifndef TIER2_CRASHREPORTER_H
#define TIER2_CRASHREPORTER_H
extern void CrashReporter_SubmitToCollector(const CCrashHandler* const handler);
#endif // TIER2_CRASHREPORTER_H

View File

@ -12,21 +12,24 @@ struct CURLProgress
CURL* curl; CURL* curl;
const char* name; const char* name;
void* cust; // custom pointer to anything. void* cust; // custom pointer to anything, todo(amos): rename to 'user'.
size_t size; size_t size;
}; };
struct CURLParams struct CURLParams
{ {
CURLParams() CURLParams()
: writeFunction(nullptr) : readFunction(nullptr)
, writeFunction(nullptr)
, statusFunction(nullptr) , statusFunction(nullptr)
, timeout(0) , timeout(0)
, verifyPeer(false) , verifyPeer(false)
, followRedirect(false) , followRedirect(false)
, verbose(false) , verbose(false)
, failOnError(true)
{} {}
void* readFunction;
void* writeFunction; void* writeFunction;
void* statusFunction; void* statusFunction;
@ -34,11 +37,17 @@ struct CURLParams
bool verifyPeer; bool verifyPeer;
bool followRedirect; bool followRedirect;
bool verbose; bool verbose;
bool failOnError;
}; };
size_t CURLReadFileCallback(void* data, const size_t size, const size_t nmemb, FILE* stream);
size_t CURLWriteFileCallback(void* data, const size_t size, const size_t nmemb, FILE* stream);
size_t CURLWriteStringCallback(char* contents, const size_t size, const size_t nmemb, string* userp); size_t CURLWriteStringCallback(char* contents, const size_t size, const size_t nmemb, string* userp);
size_t CURLWriteFileCallback(void* data, const size_t size, const size_t nmemb, FILE* userp);
curl_slist* CURLSlistAppend(curl_slist* slist, const char* string);
bool CURLUploadFile(const char* remote, const char* filePath, const char* options,
void* customPointer, const bool usePost, const curl_slist* slist, const CURLParams& params);
bool CURLDownloadFile(const char* remote, const char* savePath, const char* fileName, bool CURLDownloadFile(const char* remote, const char* savePath, const char* fileName,
const char* options, curl_off_t dataSize, void* customPointer, const CURLParams& params); const char* options, curl_off_t dataSize, void* customPointer, const CURLParams& params);

View File

@ -171,9 +171,10 @@ void CCrashHandler::FormatSystemInfo()
m_Buffer.AppendFormat("\tcpu_model = \"%s\"\n", pi.m_szProcessorBrand); m_Buffer.AppendFormat("\tcpu_model = \"%s\"\n", pi.m_szProcessorBrand);
m_Buffer.AppendFormat("\tcpu_speed = %010lld // clock cycles\n", pi.m_Speed); m_Buffer.AppendFormat("\tcpu_speed = %010lld // clock cycles\n", pi.m_Speed);
DISPLAY_DEVICE& dd = m_HardWareInfo.displayDevice;
for (DWORD i = 0; ; i++) for (DWORD i = 0; ; i++)
{ {
DISPLAY_DEVICE dd = { sizeof(dd), {0} };
const BOOL f = EnumDisplayDevices(NULL, i, &dd, EDD_GET_DEVICE_INTERFACE_NAME); const BOOL f = EnumDisplayDevices(NULL, i, &dd, EDD_GET_DEVICE_INTERFACE_NAME);
if (!f) if (!f)
@ -185,16 +186,28 @@ void CCrashHandler::FormatSystemInfo()
{ {
m_Buffer.AppendFormat("\tgpu_model = \"%s\"\n", dd.DeviceString); m_Buffer.AppendFormat("\tgpu_model = \"%s\"\n", dd.DeviceString);
m_Buffer.AppendFormat("\tgpu_flags = 0x%08X // primary device\n", dd.StateFlags); m_Buffer.AppendFormat("\tgpu_flags = 0x%08X // primary device\n", dd.StateFlags);
break;
} }
} }
MEMORYSTATUSEX statex{}; MEMORYSTATUSEX& statex = m_HardWareInfo.memoryStatus;
statex.dwLength = sizeof(statex);
if (GlobalMemoryStatusEx(&statex)) if (GlobalMemoryStatusEx(&statex))
{ {
m_Buffer.AppendFormat("\tram_total = [%010d, %010d] // physical/virtual (MiB)\n", (statex.ullTotalPhys / 1024) / 1024, (statex.ullTotalVirtual / 1024) / 1024); m_Buffer.AppendFormat("\tram_total = [%.2lf, %.2lf] // physical/virtual (MiB)\n", (f64)(statex.ullTotalPhys / (1024.0 * 1024.0)), (f64)(statex.ullTotalVirtual / (1024.0 * 1024.0)));
m_Buffer.AppendFormat("\tram_avail = [%010d, %010d] // physical/virtual (MiB)\n", (statex.ullAvailPhys / 1024) / 1024, (statex.ullAvailVirtual / 1024) / 1024); m_Buffer.AppendFormat("\tram_avail = [%.2lf, %.2lf] // physical/virtual (MiB)\n", (f64)(statex.ullAvailPhys / (1024.0 * 1024.0)), (f64)(statex.ullAvailVirtual / (1024.0 * 1024.0)));
}
DWORD sectorsPerCluster, bytesPerSector, freeClusters, totalClusters;
if (GetDiskFreeSpaceA(NULL, &sectorsPerCluster, &bytesPerSector, &freeClusters, &totalClusters))
{
m_HardWareInfo.totalDiskSpace = (u64)totalClusters * sectorsPerCluster * bytesPerSector;
m_HardWareInfo.availDiskSpace = (u64)freeClusters * sectorsPerCluster * bytesPerSector;
m_Buffer.AppendFormat("\tdsk_total = %.2lf // (MiB)\n", (f64)(m_HardWareInfo.totalDiskSpace / (1024.0 * 1024.0)));
m_Buffer.AppendFormat("\tdsk_avail = %.2lf // (MiB)\n", (f64)(m_HardWareInfo.availDiskSpace / (1024.0 * 1024.0)));
} }
m_Buffer.Append("}\n"); m_Buffer.Append("}\n");
@ -366,7 +379,7 @@ void CCrashHandler::FormatFPU(const char* const pszRegister, const M128A* const
*reinterpret_cast<const FLOAT*>(&nVec[2]), *reinterpret_cast<const FLOAT*>(&nVec[2]),
*reinterpret_cast<const FLOAT*>(&nVec[3])); *reinterpret_cast<const FLOAT*>(&nVec[3]));
const LONG nHighest = abs(LONG(*MaxElementABS(std::begin(nVec), std::end(nVec)))); const DWORD nHighest = *MaxElementABS(std::begin(nVec), std::end(nVec));
if (nHighest >= 1000000) if (nHighest >= 1000000)
{ {
@ -531,11 +544,11 @@ void CCrashHandler::CreateMessageProcess()
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Purpose: calls the crash callback // Purpose: calls the crash callback
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void CCrashHandler::CrashCallback() void CCrashHandler::CrashCallback() const
{ {
if (m_pCrashCallback) if (m_pCrashCallback)
{ {
((void(*)(void))m_pCrashCallback)(); m_pCrashCallback(this);
} }
} }
@ -548,11 +561,9 @@ long __stdcall BottomLevelExceptionFilter(EXCEPTION_POINTERS* const pExceptionIn
{ {
g_CrashHandler.Start(); g_CrashHandler.Start();
// If the exception couldn't be handled, run the crash callback and // If the exception couldn't be handled, terminate the process
// terminate the process
if (g_CrashHandler.GetExit()) if (g_CrashHandler.GetExit())
{ {
g_CrashHandler.CrashCallback();
ExitProcess(EXIT_FAILURE); ExitProcess(EXIT_FAILURE);
} }
@ -590,6 +601,9 @@ long __stdcall BottomLevelExceptionFilter(EXCEPTION_POINTERS* const pExceptionIn
// Display the message to the user. // Display the message to the user.
g_CrashHandler.CreateMessageProcess(); g_CrashHandler.CreateMessageProcess();
// Run the crash callback
g_CrashHandler.CrashCallback();
// End it here, the next recursive call terminates the process. // End it here, the next recursive call terminates the process.
g_CrashHandler.End(); g_CrashHandler.End();
@ -625,7 +639,6 @@ void CCrashHandler::Reset()
m_Buffer.Clear(); m_Buffer.Clear();
m_CrashingModule.Clear(); m_CrashingModule.Clear();
m_MessageCmdLine.Clear(); m_MessageCmdLine.Clear();
m_nCrashMsgFlags = 0; m_nCrashMsgFlags = 0;
} }

View File

@ -4,6 +4,7 @@ add_module( "lib" "tier2" "vpc" ${FOLDER_CONTEXT} TRUE TRUE )
start_sources() start_sources()
add_sources( SOURCE_GROUP "Utility" add_sources( SOURCE_GROUP "Utility"
"crashreporter.cpp"
"cryptutils.cpp" "cryptutils.cpp"
"curlutils.cpp" "curlutils.cpp"
"fileutils.cpp" "fileutils.cpp"

View File

@ -0,0 +1,61 @@
//=============================================================================//
//
// Purpose: Post-mortem crash reporter
//
//=============================================================================//
#include "tier0/crashhandler.h"
#include "tier0/cpu.h"
#include "tier2/curlutils.h"
#include "tier2/crashreporter.h"
static ConVar backtrace_enabled("backtrace_enabled", "1", FCVAR_RELEASE, "Whether to report fatal errors to the collection server");
static ConVar backtrace_hostname("backtrace_hostname", "submit.backtrace.io", FCVAR_RELEASE, "Holds the error collection server hostname");
static ConVar backtrace_universe("backtrace_universe", "r5reloaded", FCVAR_RELEASE, "Holds the error collection server hosted instance");
static ConVar backtrace_token("backtrace_token", "f178fd48d89c8fec7f8b6404ae6dae591c330fd3e2599cab888788033944ec98", FCVAR_RELEASE, "Holds the error collection server submission token");
static inline string CrashReporter_FormatAttributes(const CCrashHandler* const handler)
{
const CPUInformation& pi = GetCPUInformation();
const CrashHardWareInfo_s& hi = handler->GetHardwareInfo();
const char* const format = "uuid=%s&" "build_id=%lld&"
"cpu_model=%s&" "cpu_speed=%lf GHz&" "gpu_model=%s&" "gpu_flags=%lu&"
"ram_phys_total=%.2lf MiB&" "ram_phys_avail=%.2lf MiB&"
"ram_virt_total=%.2lf MiB&" "ram_virt_avail=%.2lf MiB&"
"disk_total=%.2lf MiB&" "disk_avail=%.2lf MiB";
const DISPLAY_DEVICE& dd = hi.displayDevice;
const MEMORYSTATUSEX& ms = hi.memoryStatus;
return Format(format, g_LogSessionUUID.c_str(), g_SDKDll.GetNTHeaders()->FileHeader.TimeDateStamp,
pi.m_szProcessorBrand, (f64)(pi.m_Speed / 1000000000.0), dd.DeviceString, dd.StateFlags,
(f64)(ms.ullTotalPhys / (1024.0*1024.0)), (f64)(ms.ullAvailPhys / (1024.0*1024.0)),
(f64)(ms.ullTotalVirtual / (1024.0*1024.0)), (f64)(ms.ullAvailVirtual / (1024.0*1024.0)),
(f64)(hi.totalDiskSpace / (1024.0*1024.0)), (f64)(hi.availDiskSpace / (1024.0*1024.0)));
}
void CrashReporter_SubmitToCollector(const CCrashHandler* const handler)
{
if (!backtrace_enabled.GetBool())
return;
curl_slist* slist = nullptr;
slist = CURLSlistAppend(slist, "Expect:");
if (!slist)
return; // failure.
const string attributes = CrashReporter_FormatAttributes(handler);
const string hostName = Format("%s%s/%s/%s/%s&%s",
"https://", backtrace_hostname.GetString(), backtrace_universe.GetString(), backtrace_token.GetString(), "minidump", attributes.c_str());
const string miniDumpPath = Format("%s/%s.dmp", g_LogSessionDirectory.c_str(), "minidump");
CURLParams params;
params.readFunction = &CURLReadFileCallback;
params.writeFunction = &CURLWriteStringCallback;
params.timeout = curl_timeout.GetInt();
params.verifyPeer = ssl_verify_peer.GetBool();
params.verbose = curl_debug.GetBool();
CURLUploadFile(hostName.c_str(), miniDumpPath.c_str(), "rb", nullptr, true, slist, params);
}

View File

@ -6,40 +6,177 @@
#include "tier1/cvar.h" #include "tier1/cvar.h"
#include "tier2/curlutils.h" #include "tier2/curlutils.h"
size_t CURLReadFileCallback(void* data, const size_t size, const size_t nmemb, FILE* stream)
{
const size_t numBytesRead = fread(data, size, nmemb, stream);
return numBytesRead;
}
size_t CURLWriteFileCallback(void* data, const size_t size, const size_t nmemb, FILE* stream)
{
const size_t numBytesWritten = fwrite(data, size, nmemb, stream);
return numBytesWritten;
}
size_t CURLWriteStringCallback(char* data, const size_t size, const size_t nmemb, string* userp) size_t CURLWriteStringCallback(char* data, const size_t size, const size_t nmemb, string* userp)
{ {
userp->append(data, size * nmemb); userp->append(data, size * nmemb);
return size * nmemb; return size * nmemb;
} }
size_t CURLWriteFileCallback(void* data, const size_t size, const size_t nmemb, FILE* userp)
{
const size_t numBytesWritten = fwrite(data, size, nmemb, userp);
return numBytesWritten;
}
void CURLInitCommonOptions(CURL* curl, const char* remote, void CURLInitCommonOptions(CURL* curl, const char* remote,
const void* writeData, const CURLParams& params) const void* readData, const void* writeData,
const CURLParams& params, const CURLProgress* progressData)
{ {
curl_easy_setopt(curl, CURLOPT_TIMEOUT, params.timeout); curl_easy_setopt(curl, CURLOPT_TIMEOUT, params.timeout);
curl_easy_setopt(curl, CURLOPT_VERBOSE, params.verbose); curl_easy_setopt(curl, CURLOPT_VERBOSE, params.verbose);
curl_easy_setopt(curl, CURLOPT_FAILONERROR, params.failOnError);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, params.followRedirect); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, params.followRedirect);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, params.verifyPeer); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, params.verifyPeer);
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L); curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_ALL); curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_ALL);
curl_easy_setopt(curl, CURLOPT_URL, remote); curl_easy_setopt(curl, CURLOPT_URL, remote);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, writeData); curl_easy_setopt(curl, CURLOPT_WRITEDATA, writeData);
curl_easy_setopt(curl, CURLOPT_READDATA, readData);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "R5R HTTPS/1.0"); curl_easy_setopt(curl, CURLOPT_USERAGENT, "R5R HTTPS/1.0");
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, params.writeFunction); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, params.writeFunction);
curl_easy_setopt(curl, CURLOPT_READFUNCTION, params.readFunction);
if (params.statusFunction)
{
Assert(progressData);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0l);
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &progressData);
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, params.statusFunction);
}
} }
bool CURLDownloadFile(const char* remote, const char* savePath, const char* fileName, static CURL* EasyInit()
const char* options, curl_off_t dataSize, void* customPointer, const CURLParams& params)
{ {
CURL* curl = curl_easy_init(); CURL* curl = curl_easy_init();
if (!curl) if (!curl)
{ {
Error(eDLL_T::COMMON, NO_ERROR, "CURL: %s\n", "Easy init failed"); Error(eDLL_T::COMMON, NO_ERROR, "CURL: %s\n", "Easy init failed");
}
return curl;
}
static FILE* OpenFile(const char* filePath, const char* options)
{
FILE* file = fopen(filePath, options);
if (!file)
{
Error(eDLL_T::COMMON, NO_ERROR, "CURL: %s\n", "Open file failed");
}
return file;
}
static bool FileStat(const char* fileName, struct _stat64& stat)
{
if (_stat64(fileName, &stat) != 0)
{
Error(eDLL_T::COMMON, NO_ERROR, "CURL: %s\n", "File status query failed");
return false;
}
return true;
}
curl_slist* CURLSlistAppend(curl_slist* slist, const char* string)
{
slist = curl_slist_append(slist, string);
if (!slist)
{
Error(eDLL_T::COMMON, NO_ERROR, "CURL: %s\n", "Slist append failed");
}
return slist;
}
bool CURLUploadFile(const char* remote, const char* filePath,
const char* options, void* customPointer, const bool usePost,
const curl_slist* slist, const CURLParams& params)
{
CURL* curl = EasyInit();
if (!curl)
{
return false;
}
FILE* file = OpenFile(filePath, options);
if (!file)
{
return false;
}
struct _stat64 fileStatus;
if (!FileStat(filePath, fileStatus))
{
fclose(file);
return false;
}
CURLProgress progressData;
progressData.curl = curl;
progressData.name = filePath;
progressData.cust = customPointer;
progressData.size = fileStatus.st_size;
string response;
CURLInitCommonOptions(curl, remote, file, &response, params, &progressData);
if (slist)
{
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
}
const bool largeFile = fileStatus.st_size > INT_MAX;
CURLoption fileSizeOption;
if (usePost)
{
curl_easy_setopt(curl, CURLOPT_POST, 1L);
fileSizeOption = largeFile
? CURLOPT_POSTFIELDSIZE_LARGE
: CURLOPT_POSTFIELDSIZE;
}
else
{
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
fileSizeOption = largeFile
? CURLOPT_INFILESIZE_LARGE
: CURLOPT_INFILESIZE;
}
curl_easy_setopt(curl, fileSizeOption, (curl_off_t)fileStatus.st_size);
CURLcode res = curl_easy_perform(curl);
const bool success = res == CURLE_OK;
if (!success)
{
Error(eDLL_T::COMMON, NO_ERROR, "CURL: Upload of file '%s' failed; %s\n",
filePath, curl_easy_strerror(res));
}
curl_easy_cleanup(curl);
fclose(file);
return success;
}
bool CURLDownloadFile(const char* remote, const char* savePath, const char* fileName,
const char* options, curl_off_t dataSize, void* customPointer, const CURLParams& params)
{
CURL* curl = EasyInit();
if (!curl)
{
return false; return false;
} }
@ -48,12 +185,10 @@ bool CURLDownloadFile(const char* remote, const char* savePath, const char* file
AppendSlash(filePath); AppendSlash(filePath);
filePath.append(fileName); filePath.append(fileName);
FILE* file = fopen(filePath.c_str(), options); FILE* file = OpenFile(filePath.c_str(), options);
if (!file) if (!file)
{ {
Error(eDLL_T::COMMON, NO_ERROR, "CURL: %s\n", "Open file failed");
curl_easy_cleanup(curl); curl_easy_cleanup(curl);
return false; return false;
} }
@ -64,14 +199,7 @@ bool CURLDownloadFile(const char* remote, const char* savePath, const char* file
progressData.cust = customPointer; progressData.cust = customPointer;
progressData.size = dataSize; progressData.size = dataSize;
CURLInitCommonOptions(curl, remote, file, params); CURLInitCommonOptions(curl, remote, nullptr, file, params, &progressData);
if (params.statusFunction)
{
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0l);
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &progressData);
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, params.statusFunction);
}
CURLcode res = curl_easy_perform(curl); CURLcode res = curl_easy_perform(curl);
@ -95,33 +223,25 @@ bool CURLDownloadFile(const char* remote, const char* savePath, const char* file
CURL* CURLInitRequest(const char* remote, const char* request, CURL* CURLInitRequest(const char* remote, const char* request,
string& outResponse, curl_slist*& slist, const CURLParams& params) string& outResponse, curl_slist*& slist, const CURLParams& params)
{ {
std::function<void(const char*)> fnError = [&](const char* errorMsg) slist = CURLSlistAppend(slist, "Content-Type: application/json");
{
Error(eDLL_T::COMMON, NO_ERROR, "CURL: %s\n", errorMsg);
curl_slist_free_all(slist);
};
slist = curl_slist_append(slist, "Content-Type: application/json");
if (!slist) if (!slist)
{ {
fnError("Slist init failed");
return nullptr; return nullptr;
} }
CURL* curl = curl_easy_init(); CURL* curl = EasyInit();
if (!curl) if (!curl)
{ {
fnError("Easy init failed");
return nullptr; return nullptr;
} }
CURLInitCommonOptions(curl, remote, &outResponse, params); CURLInitCommonOptions(curl, remote, nullptr, &outResponse, params, nullptr);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
if (request) if (request)
{ {
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request);
curl_easy_setopt(curl, CURLOPT_POST, 1L); curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request);
} }
return curl; return curl;