/*
* Copyright 2011-2013 Arx Libertatis Team (see the AUTHORS file)
*
* This file is part of Arx Libertatis.
*
* Arx Libertatis is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Arx Libertatis is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Arx Libertatis. If not, see .
*/
#include "crashreporter/ErrorReport.h"
#ifdef ARX_HAVE_WINAPI
// Win32
#include
#include
#else
#include
#endif
#ifdef ARX_HAVE_GETRUSAGE
#include
#include
#endif
#if defined(ARX_HAVE_PRCTL)
#include
#ifndef PR_SET_PTRACER
#define PR_SET_PTRACER 0x59616d61
#endif
#endif
#ifdef ARX_HAVE_SYSCONF
#include
#endif
#ifdef ARX_HAVE_CRASHHANDLER_POSIX
#include
#endif
#ifdef ARX_HAVE_SETENV
#include
#endif
// Qt
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// Boost
#include
#include "Configure.h"
#include "core/Version.h"
#include "io/fs/Filesystem.h"
#include "io/fs/FileStream.h"
#include "crashreporter/Win32Utilities.h"
#include "crashreporter/tbg/TBG.h"
#include "platform/Architecture.h"
#include "platform/OS.h"
ErrorReport::ErrorReport(const QString& sharedMemoryName)
: m_RunningTimeSec(0)
, m_ProcessMemoryUsage(0)
, m_SharedMemoryName(sharedMemoryName)
, m_pCrashInfo()
, m_Username("CrashBot")
, m_Password("WbAtVjS9")
{
#if defined(ARX_HAVE_PRCTL)
// Allow debuggers to be attached to this process, for development purpose...
prctl(PR_SET_PTRACER, 1, 0, 0, 0);
#endif
}
bool ErrorReport::Initialize()
{
// Create a shared memory object.
m_SharedMemory = boost::interprocess::shared_memory_object(boost::interprocess::open_only, m_SharedMemoryName.toStdString().c_str(), boost::interprocess::read_write);
// Map the whole shared memory in this process
m_MemoryMappedRegion = boost::interprocess::mapped_region(m_SharedMemory, boost::interprocess::read_write);
// Our SharedCrashInfo will be stored in this shared memory.
m_pCrashInfo = (CrashInfo*)m_MemoryMappedRegion.get_address();
if(m_MemoryMappedRegion.get_size() != sizeof(CrashInfo))
{
m_DetailedError = "The size of the memory mapped region does not match the size of the CrashInfo structure.";
return false;
}
bool bMiscCrashInfo = GetMiscCrashInfo();
if(!bMiscCrashInfo)
return false;
// Add attached files from the report
for(int i = 0; i < m_pCrashInfo->nbFilesAttached; i++)
AddFile(m_pCrashInfo->attachedFiles[i]);
m_ReportFolder = fs::path(m_pCrashInfo->crashReportFolder) / fs::path(m_CrashDateTime.toString("yyyy.MM.dd hh.mm.ss").toUtf8());
if(!fs::create_directories(m_ReportFolder))
{
m_DetailedError = QString("Unable to create directory (%1) to store the crash report files.").arg(m_ReportFolder.string().c_str());
return false;
}
return true;
}
bool ErrorReport::GetCrashDump(const fs::path & fileName) {
#ifdef ARX_HAVE_WINAPI
bool bHaveDump = false;
if(fs::exists(m_pCrashInfo->miniDumpTmpFile))
{
fs::path fullPath = m_ReportFolder / fileName;
if(fs::rename(m_pCrashInfo->miniDumpTmpFile, fullPath))
{
AddFile(fullPath);
bHaveDump = true;
}
}
return bHaveDump;
#else // !ARX_HAVE_WINAPI
ARX_UNUSED(fileName);
// TODO: Write core dump to
// fs::path fullPath = m_ReportFolder / fileName;
return getCrashDescription();
#endif // !ARX_HAVE_WINAPI
}
#ifndef ARX_HAVE_WINAPI
void getProcessSatus(QString filename, u64 & rss, u64 & startTicks) {
rss = startTicks = 0;
QFile file(filename);
if(!file.open(QIODevice::ReadOnly)) {
return;
}
QByteArray stat = file.readAll();
file.close();
QList stat_fields = stat.split(' ');
const int rss_index = 23;
if(rss_index < stat_fields.size()) {
rss = stat_fields[rss_index].toULongLong();
}
const int starttime_index = 21;
if(starttime_index < stat_fields.size()) {
startTicks = stat_fields[starttime_index].toULongLong();
}
}
void getResourceUsage(int pid, quint64 & memoryUsage, double & runningTimeSec) {
memoryUsage = 0;
runningTimeSec = 0.0;
#if defined(ARX_HAVE_GETRUSAGE) && ARX_PLATFORM != ARX_PLATFORM_MACOSX
{
struct rusage usage;
if(getrusage(pid, &usage) == 0) {
memoryUsage = usage.ru_maxrss * 1024;
}
}
#endif
#if defined(ARX_HAVE_SYSCONF) && (defined(_SC_PAGESIZE) || defined(_SC_CLK_TCK))
u64 rss, startTicks, endTicks, dummy;
getProcessSatus(QString("/proc/%1/stat").arg(pid), rss, startTicks);
// Get the rss memory usage from /proc/pid/stat
#ifdef _SC_PAGESIZE
if(rss != 0) {
long pagesize = sysconf(_SC_PAGESIZE);
if(pagesize > 0) {
memoryUsage = rss * pagesize;
}
}
#endif
#ifdef _SC_CLK_TCK
if(startTicks == 0) {
return;
}
pid_t child = fork();
if(!child) {
while(1) {
// wait
}
}
getProcessSatus(QString("/proc/%1/stat").arg(child), dummy, endTicks);
kill(child, SIGTERM);
if(startTicks != 0 && endTicks != 0) {
u64 ticksPerSecond = sysconf(_SC_CLK_TCK);
if(ticksPerSecond > 0) {
runningTimeSec = double(endTicks - startTicks) / double(ticksPerSecond);
}
}
#endif
#endif
}
#endif // !defined(ARX_HAVE_WINAPI)
bool ErrorReport::getCrashDescription() {
#ifdef ARX_HAVE_WINAPI
return true;
#else // !defined(ARX_HAVE_WINAPI)
switch(m_pCrashInfo->signal) {
case SIGILL: {
m_ReportDescription = "Illegal instruction";
switch(m_pCrashInfo->code) {
#ifdef ILL_ILLOPC
case ILL_ILLOPC: m_ReportDescription += ": illegal opcode"; break;
#endif
#ifdef ILL_ILLOPN
case ILL_ILLOPN: m_ReportDescription += ": illegal operand"; break;
#endif
#ifdef ILL_ADR
case ILL_ADR: m_ReportDescription += ": illegal addressing mode"; break;
#endif
#ifdef ILL_ILLTRP
case ILL_ILLTRP: m_ReportDescription += ": illegal trap"; break;
#endif
#ifdef ILL_PRVOPC
case ILL_PRVOPC: m_ReportDescription += ": privileged opcode"; break;
#endif
#ifdef ILL_PRVREG
case ILL_PRVREG: m_ReportDescription += ": privileged register"; break;
#endif
#ifdef ILL_COPROC
case ILL_COPROC: m_ReportDescription += ": coprocessor error"; break;
#endif
#ifdef ILL_BADSTK
case ILL_BADSTK: m_ReportDescription += ": internal stack error"; break;
#endif
default: break;
}
break;
}
case SIGABRT: {
m_ReportDescription = "Abnormal termination";
break;
}
case SIGBUS: {
m_ReportDescription = "Bus error";
switch(m_pCrashInfo->code) {
#ifdef BUS_ADRALN
case BUS_ADRALN: m_ReportDescription += ": invalid address alignment"; break;
#endif
#ifdef BUS_ADRERR
case BUS_ADRERR: m_ReportDescription += ": non-existent physical address"; break;
#endif
#ifdef BUS_OBJERR
case BUS_OBJERR: m_ReportDescription += ": object specific hardware error"; break;
#endif
default: break;
}
break;
}
case SIGFPE: {
m_ReportDescription = "Floating-point error";
switch(m_pCrashInfo->code) {
#ifdef FPE_INTDIV
case FPE_INTDIV: m_ReportDescription += ": integer division by zero"; break;
#endif
#ifdef FPE_INTOVF
case FPE_INTOVF: m_ReportDescription += ": integer overflow"; break;
#endif
#ifdef FPE_FLTDIV
case FPE_FLTDIV: m_ReportDescription += ": floating point division by zero"; break;
#endif
#ifdef FPE_FLTOVF
case FPE_FLTOVF: m_ReportDescription += ": floating point overflow"; break;
#endif
#ifdef FPE_FLTUND
case FPE_FLTUND: m_ReportDescription += ": floating point underflow"; break;
#endif
#ifdef FPE_FLTRES
case FPE_FLTRES: m_ReportDescription += ": floating point inexact result"; break;
#endif
#ifdef FPE_FLTINV
case FPE_FLTINV: m_ReportDescription += ": invalid floating point operation"; break;
#endif
#ifdef FPE_FLTSUB
case FPE_FLTSUB: m_ReportDescription += ": subscript out of range"; break;
#endif
default: break;
}
break;
}
case SIGSEGV: {
m_ReportDescription = "Illegal storage access";
switch(m_pCrashInfo->code) {
#ifdef SEGV_MAPERR
case SEGV_MAPERR: {
m_ReportDescription += ": address not mapped to object";
break;
}
#endif
#ifdef SEGV_ACCERR
case SEGV_ACCERR: {
m_ReportDescription += ": invalid permissions for mapped object";
break;
}
#endif
default: break;
}
break;
}
default: {
m_ReportDescription = QString("Received signal %1").arg(m_pCrashInfo->signal);
break;
}
}
m_ReportDescription += "\n\n";
m_ReportDescriptionText = m_ReportDescription;
#if defined(ARX_HAVE_FORK) && defined(ARX_HAVE_EXECLP) && defined(ARX_HAVE_DUP2)
fs::path tracePath = m_ReportFolder / "gdbtrace.txt";
// Fork so we retain control after launching GDB.
int childPID = fork();
if(childPID) {
// Wait for GDB to exit.
waitpid(childPID, NULL, 0);
} else {
// Redirect output to a file
int fd = open(tracePath.string().c_str(), O_WRONLY|O_CREAT, 0666);
dup2(fd, 1);
close(fd);
// Prepare pid argument for GDB.
char pid_buf[30];
memset(&pid_buf, 0, sizeof(pid_buf));
sprintf(pid_buf, "%d", m_pCrashInfo->processId);
// Turn off localization for the backtrace output
#ifdef ARX_HAVE_SETENV
setenv("LANG", "C", 1);
setenv("LC_ALL", "C", 1);
#endif
// Try to execute gdb to get a very detailed stack trace.
execlp("gdb", "gdb", "--batch", "-n", "-ex", "thread", "-ex", "set confirm off", "-ex", "set print frame-arguments all", "-ex", "set print static-members off", "-ex", "info threads", "-ex", "thread apply all bt full", m_pCrashInfo->execFullName, pid_buf, NULL);
// GDB failed to start.
exit(1);
}
#endif // defined(ARX_HAVE_EXECLP) && defined(ARX_HAVE_DUP2)
bool bWroteDump = fs::exists(tracePath) && fs::file_size(tracePath) > 0;
if(!bWroteDump) {
m_DetailedError = "GDB is probably not installed on your machine. Please install it in order to obtain valuable crash reports in the future.";
return false;
}
#ifdef ARX_HAVE_BACKTRACE
boost::crc_32_type callstackCRC32;
for(size_t i = 0; i < ARRAY_SIZE(m_pCrashInfo->backtrace); i++) {
if(m_pCrashInfo->backtrace[i] == 0) {
break;
}
callstackCRC32.process_bytes(&m_pCrashInfo->backtrace[i], sizeof(m_pCrashInfo->backtrace[i]));
}
u32 callstackCrc = callstackCRC32.checksum();
m_ReportUniqueID = QString("[%1]").arg(QString::number(callstackCrc, 16).toUpper());
#endif // ARX_HAVE_BACKTRACE
QFile traceFile(tracePath.string().c_str());
traceFile.open(QIODevice::ReadOnly);
if(!traceFile.isOpen()) {
m_DetailedError = "Unable to read GDB output from the disk.";
return false;
}
QString traceStr = traceFile.readAll();
traceFile.close();
QString callstackTop = "Unknown";
// The useful stack frames are found below ""
int posStart = traceStr.indexOf("");
int posEnd = -1;
if(posStart != -1) {
posStart = traceStr.indexOf("#", posStart);
if(posStart != -1)
posEnd = traceStr.indexOf("\n", posStart);
}
// Capture the entry where the crash occured and format it
if(posStart != -1 && posEnd != -1) {
callstackTop = traceStr.mid(posStart, posEnd - posStart);
// Remove "#N 0x???????? in "
posEnd = callstackTop.indexOf(" in ");
if(posEnd != -1) {
posEnd += 4;
callstackTop.remove(0, posEnd);
}
// Remove params
posStart = callstackTop.indexOf("(");
posEnd = callstackTop.indexOf(")", posStart);
if(posStart != -1 && posEnd != -1) {
posStart++;
callstackTop.remove(posStart, posEnd - posStart);
}
// Trim filenames
posStart = callstackTop.lastIndexOf(") at");
posEnd = callstackTop.lastIndexOf("/");
if(posStart != -1 && posEnd != -1) {
posStart += 2;
posEnd++;
callstackTop.remove(posStart, posEnd - posStart);
}
}
m_ReportDescription += "\nGDB stack trace:\n";
m_ReportDescription += "\n";
m_ReportDescriptionText += "\nGDB stack trace:\n";
m_ReportDescriptionText += "\n";
m_ReportDescriptionText += traceStr;
m_ReportTitle = QString("%1 %2").arg(m_ReportUniqueID, callstackTop.trimmed());
#endif // !defined(ARX_HAVE_WINAPI)
return true;
}
bool ErrorReport::GetMiscCrashInfo() {
// Get crash time
m_CrashDateTime = QDateTime::currentDateTime();
m_ProcessArchitecture = ARX_ARCH_NAME;
m_OSName = QString::fromUtf8(platform::getOSName().c_str());
m_OSArchitecture = QString::fromUtf8(platform::getOSArchitecture().c_str());
m_OSDistribution = QString::fromUtf8(platform::getOSDistribution().c_str());
#ifdef ARX_HAVE_WINAPI
// Open parent process handle
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, m_pCrashInfo->processId);
if(hProcess != NULL)
{
// Get memory usage info
PROCESS_MEMORY_COUNTERS meminfo;
BOOL bGetMemInfo = GetProcessMemoryInfo(hProcess, &meminfo, sizeof(PROCESS_MEMORY_COUNTERS));
if(bGetMemInfo)
m_ProcessMemoryUsage = meminfo.WorkingSetSize;
// Determine the period of time the process is working.
FILETIME CreationTime, ExitTime, KernelTime, UserTime;
BOOL bGetTimes = GetProcessTimes(hProcess, &CreationTime, &ExitTime, &KernelTime, &UserTime);
if(bGetTimes)
{
SYSTEMTIME AppStartTime;
FileTimeToSystemTime(&CreationTime, &AppStartTime);
SYSTEMTIME CurTime;
GetSystemTime(&CurTime);
ULONG64 uCurTime = ConvertSystemTimeToULONG64(CurTime);
ULONG64 uStartTime = ConvertSystemTimeToULONG64(AppStartTime);
// Check that the application works for at least one minute before crash.
// This might help to avoid cyclic error report generation when the applciation
// crashes on startup.
m_RunningTimeSec = (double)(uCurTime-uStartTime)*10E-08;
}
}
else
{
m_DetailedError = QString("Unable to obtain an handle to the crashed process (Error %1).").arg(QString::number(GetLastError()));
return false;
}
if(m_pCrashInfo->exceptionCode != 0)
{
QString exceptionStr = GetExceptionString(m_pCrashInfo->exceptionCode).c_str();
if(!exceptionStr.isEmpty())
{
m_ReportDescription += "\nException code:\n ";
m_ReportDescription += exceptionStr;
m_ReportDescription += "\n";
}
}
std::string callStack, callstackTop;
u32 callstackCrc;
bool bCallstack = GetCallStackInfo(hProcess, m_pCrashInfo->threadHandle, &m_pCrashInfo->contextRecord, callStack, callstackTop, callstackCrc);
if(!bCallstack)
{
m_DetailedError = "A failure occured when obtaining information regarding the callstack.";
return false;
}
m_ReportUniqueID = QString("[%1]").arg(QString::number(callstackCrc, 16).toUpper());
m_ReportDescription = m_pCrashInfo->detailedCrashInfo;
m_ReportDescription += "\nCallstack:\n";
m_ReportDescription += callStack.c_str();
m_ReportTitle = QString("%1 %2").arg(m_ReportUniqueID, callstackTop.c_str());
QString registers(GetRegisters(&m_pCrashInfo->contextRecord).c_str());
if(!registers.isEmpty())
{
m_ReportDescription += "\nRegisters:\n";
m_ReportDescription += registers;
}
CloseHandle(hProcess);
m_ReportDescriptionText = m_ReportDescription;
#else // !ARX_HAVE_WINAPI
getResourceUsage(m_pCrashInfo->processId, m_ProcessMemoryUsage, m_RunningTimeSec);
#endif // !ARX_HAVE_WINAPI
return true;
}
bool ErrorReport::WriteReport(const fs::path & fileName) {
fs::path fullPath = m_ReportFolder / fileName;
QFile file(fullPath.string().c_str());
if(!file.open(QIODevice::WriteOnly)) {
m_DetailedError = "Unable to open report manifest for writing.";
return false;
}
QXmlStreamWriter xml;
xml.setDevice(&file);
xml.setAutoFormatting(true);
xml.writeStartDocument();
xml.writeStartElement("CrashReport");
xml.writeComment("Information related to the crashed process");
xml.writeStartElement("Process");
xml.writeTextElement("Path", m_pCrashInfo->executablePath);
xml.writeTextElement("Version", m_pCrashInfo->executableVersion);
if(m_ProcessMemoryUsage != 0) {
xml.writeTextElement("MemoryUsage", QString::number(m_ProcessMemoryUsage));
}
xml.writeTextElement("Architecture", m_ProcessArchitecture);
if(m_RunningTimeSec > 0) {
xml.writeTextElement("RunningTime", QString::number(m_RunningTimeSec));
}
xml.writeTextElement("CrashDateTime", m_CrashDateTime.toString("dd.MM.yyyy hh:mm:ss"));
xml.writeEndElement();
xml.writeComment("Information related to the OS");
xml.writeStartElement("OS");
if(!m_OSName.isEmpty()) {
xml.writeTextElement("Name", m_OSName);
}
if(!m_OSArchitecture.isEmpty()) {
xml.writeTextElement("Architecture", m_OSArchitecture);
}
if(!m_OSDistribution.isEmpty()) {
xml.writeTextElement("Distribution", m_OSDistribution);
}
xml.writeEndElement();
xml.writeComment("List of files generated by the crash reporter");
xml.writeComment("Note that some of these files could have been manually excluded from the report");
xml.writeStartElement("Files");
for(FileList::const_iterator it = m_AttachedFiles.begin();
it != m_AttachedFiles.end(); ++it) {
xml.writeTextElement("File", it->path.string().c_str());
}
xml.writeEndElement();
xml.writeComment("Variables attached by the crashed process");
xml.writeStartElement("Variables");
for(int i = 0; i < m_pCrashInfo->nbVariables; ++i)
{
xml.writeStartElement("Variable");
xml.writeAttribute("Name", m_pCrashInfo->variables[i].name);
xml.writeAttribute("Value", m_pCrashInfo->variables[i].value);
xml.writeEndElement();
}
xml.writeEndElement();
xml.writeEndElement();
xml.writeEndDocument();
file.close();
AddFile(fullPath);
return true;
}
bool ErrorReport::GenerateReport(ErrorReport::IProgressNotifier* pProgressNotifier)
{
ErrorReport* report = this;
BOOST_SCOPE_EXIT((report))
{
// Allow the crashed process to exit
report->ReleaseApplicationLock();
} BOOST_SCOPE_EXIT_END
pProgressNotifier->taskStarted("Generating crash report", 3);
// Initialize shared memory
pProgressNotifier->taskStepStarted("Connecting to crashed application");
bool bInit = Initialize();
pProgressNotifier->taskStepEnded();
if(!bInit)
{
pProgressNotifier->setError("Could not generate the crash dump.");
pProgressNotifier->setDetailedError(m_DetailedError);
return false;
}
if(m_pCrashInfo->architecture != ARX_ARCH) {
pProgressNotifier->setError("Architecture mismatch between the crashed process and the crash reporter.");
pProgressNotifier->setDetailedError(m_DetailedError);
return false;
}
// Generate minidump
pProgressNotifier->taskStepStarted("Generating crash dump");
bool bCrashDump = GetCrashDump("crash.dmp");
pProgressNotifier->taskStepEnded();
if(!bCrashDump)
{
pProgressNotifier->setError("Could not generate the crash dump.");
pProgressNotifier->setDetailedError(m_DetailedError);
return false;
}
// Generate manifest
pProgressNotifier->taskStepStarted("Generating report manifest");
bool bCrashXml = WriteReport("crash.xml");
pProgressNotifier->taskStepEnded();
if(!bCrashXml)
{
pProgressNotifier->setError("Could not generate the manifest.");
pProgressNotifier->setDetailedError(m_DetailedError);
return false;
}
return true;
}
bool ErrorReport::SendReport(ErrorReport::IProgressNotifier* pProgressNotifier)
{
int nbFilesToSend = 0;
for(FileList::const_iterator it = m_AttachedFiles.begin(); it != m_AttachedFiles.end(); ++it)
{
if(it->attachToReport)
nbFilesToSend++;
}
pProgressNotifier->taskStarted("Sending crash report", 3 + nbFilesToSend);
// This must be called before creating our QNetworkAccessManager
AddSSLCertificate();
TBG::Server server("https://bugs.arx-libertatis.org");
// Login to TBG server
pProgressNotifier->taskStepStarted("Connecting to the bug tracker");
bool bLoggedIn = server.login(m_Username, m_Password);
pProgressNotifier->taskStepEnded();
if(!bLoggedIn)
{
pProgressNotifier->setError("Could not connect to the bug tracker");
pProgressNotifier->setDetailedError(server.getErrorString());
return false;
}
// Look for existing issue
int issue_id = -1;
pProgressNotifier->taskStepStarted("Searching for existing issue");
bool bSearchSuccessful = m_ReportUniqueID.isEmpty() || server.findIssue(m_ReportUniqueID, issue_id);
pProgressNotifier->taskStepEnded();
if(!bSearchSuccessful)
{
pProgressNotifier->setError("Failure occured when searching issue on the bug tracker");
pProgressNotifier->setDetailedError(server.getErrorString());
return false;
}
// Create new issue if no match was found
if(issue_id == -1)
{
pProgressNotifier->taskStepStarted("Creating new issue");
bool bCreatedIssue = server.createCrashReport(m_ReportTitle, m_ReportDescription, m_ReproSteps, tbg_version_id, issue_id);
if(!bCreatedIssue)
{
pProgressNotifier->taskStepEnded();
pProgressNotifier->setError("Could not create a new issue on the bug tracker");
pProgressNotifier->setDetailedError(server.getErrorString());
return false;
}
// Set OS
#if ARX_PLATFORM == ARX_PLATFORM_WIN32
int os_id = TBG::Server::OS_Windows;
#elif ARX_PLATFORM == ARX_PLATFORM_LINUX
int os_id = TBG::Server::OS_Linux;
#elif ARX_PLATFORM == ARX_PLATFORM_MACOSX
int os_id = TBG::Server::OS_MacOSX;
#elif ARX_PLATFORM == ARX_PLATFORM_BSD
int os_id = TBG::Server::OS_FreeBSD;
#else
int os_id = TBG::Server::OS_Other;
#endif
server.setOperatingSystem(issue_id, os_id);
// Set Architecture
int arch_id;
if(m_ProcessArchitecture == ARX_ARCH_NAME_X86_64)
arch_id = TBG::Server::Arch_Amd64;
else if(m_ProcessArchitecture == ARX_ARCH_NAME_X86)
arch_id = TBG::Server::Arch_x86;
else
arch_id = TBG::Server::Arch_Other;
server.setArchitecture(issue_id, arch_id);
pProgressNotifier->taskStepEnded();
}
else
{
if(!m_ReproSteps.isEmpty())
{
pProgressNotifier->taskStepStarted("Duplicate issue found, adding information");
bool bCommentAdded = server.addComment(issue_id, m_ReproSteps);
pProgressNotifier->taskStepEnded();
if(!bCommentAdded)
{
pProgressNotifier->setError("Failure occured when trying to add information to an existing issue");
pProgressNotifier->setDetailedError(server.getErrorString());
return false;
}
}
}
// Send files
for(FileList::const_iterator it = m_AttachedFiles.begin(); it != m_AttachedFiles.end(); ++it)
{
// Ignore files that were removed by the user.
if(!it->attachToReport)
continue;
// One more check to verify that the file still exists.
if(!fs::exists(it->path))
continue;
pProgressNotifier->taskStepStarted(QString("Sending file \"%1\"").arg(it->path.filename().c_str()));
bool bAttached = server.attachFile(issue_id, it->path.string().c_str(), it->path.filename().c_str(), m_SharedMemoryName);
pProgressNotifier->taskStepEnded();
if(!bAttached)
{
pProgressNotifier->setError(QString("Could not send file \"%1\"").arg(it->path.filename().c_str()));
pProgressNotifier->setDetailedError(server.getErrorString());
return false;
}
}
m_IssueLink = server.getUrl().toString();
return true;
}
void ErrorReport::ReleaseApplicationLock() {
#if ARX_PLATFORM == ARX_PLATFORM_WIN32
m_pCrashInfo->exitLock.post();
#else
// Kill the original, busy-waiting process.
kill(m_pCrashInfo->processId, SIGKILL);
#endif
}
void ErrorReport::AddSSLCertificate() {
QFile file(":/startcom.cer", 0);
if(file.open(QIODevice::ReadOnly)) {
QSslCertificate certificate(file.readAll(), QSsl::Der);
if(certificate.isNull()) {
return;
}
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
if(!certificate.isValid()) {
return;
}
#else
if(certificate.isBlacklisted()) {
return;
}
#endif
QSslSocket::addDefaultCaCertificate(certificate);
}
}
void ErrorReport::AddFile(const fs::path& fileName)
{
// Do not include files that can't be found, and empty files...
if(fs::exists(fileName) && fs::file_size(fileName) != 0)
m_AttachedFiles.push_back(File(fileName, true));
}
ErrorReport::FileList& ErrorReport::GetAttachedFiles()
{
return m_AttachedFiles;
}
const QString& ErrorReport::GetErrorDescription() const
{
return m_ReportDescriptionText;
}
const QString& ErrorReport::GetIssueLink() const
{
return m_IssueLink;
}
void ErrorReport::SetLoginInfo(const QString& username, const QString& password)
{
m_Username = username;
m_Password = password;
}
void ErrorReport::SetReproSteps(const QString& reproSteps)
{
m_ReproSteps = reproSteps;
}