Index: handler/mac/crash_report_upload_thread.cc |
diff --git a/handler/mac/crash_report_upload_thread.cc b/handler/mac/crash_report_upload_thread.cc |
index 55e2d291dfba647a6e623a32ff258ab6c672d711..a5ae5d05d7444637293db6814c1b58794a7e7c54 100644 |
--- a/handler/mac/crash_report_upload_thread.cc |
+++ b/handler/mac/crash_report_upload_thread.cc |
@@ -16,14 +16,119 @@ |
#include <errno.h> |
+#include <map> |
#include <vector> |
+#include <utility> |
#include "base/logging.h" |
+#include "base/memory/scoped_ptr.h" |
+#include "snapshot/minidump/process_snapshot_minidump.h" |
+#include "snapshot/module_snapshot.h" |
+#include "util/file/file_reader.h" |
+#include "util/net/http_body.h" |
+#include "util/net/http_multipart_builder.h" |
+#include "util/net/http_transport.h" |
namespace crashpad { |
-CrashReportUploadThread::CrashReportUploadThread(CrashReportDatabase* database) |
- : database_(database), |
+namespace { |
+ |
+// Given a minidump file readable by |minidump_file_reader|, returns a map of |
+// key-value pairs to use as HTTP form parameters for upload to a Breakpad |
+// server. The map is built by combining the process simple annotations map with |
+// each module’s simple annotations map. In the case of duplicate keys, the map |
+// will retain the first value found for any key, and will log a warning about |
+// discarded values. Each module’s annotations vector is also examined and built |
+// into a single string value, with distinct elements separated by newlines, and |
+// stored at the key named “list_annotations”, which supersedes any other key |
+// found by that name. |
+// |
+// In the event of an error reading the minidump file, a message will be logged. |
+std::map<std::string, std::string> BreakpadHTTPFormParametersFromMinidump( |
+ FileReader* minidump_file_reader) { |
+ ProcessSnapshotMinidump minidump_process_snapshot; |
+ if (!minidump_process_snapshot.Initialize(minidump_file_reader)) { |
+ return std::map<std::string, std::string>(); |
+ } |
+ |
+ std::map<std::string, std::string> parameters = |
+ minidump_process_snapshot.AnnotationsSimpleMap(); |
+ |
+ std::string list_annotations; |
+ for (const ModuleSnapshot* module : minidump_process_snapshot.Modules()) { |
+ for (const auto& kv : module->AnnotationsSimpleMap()) { |
+ if (parameters.find(kv.first) != parameters.end()) { |
+ LOG(WARNING) << "duplicate key " << kv.first << ", discarding value " |
+ << kv.second; |
+ } else { |
+ parameters.insert(kv); |
+ } |
+ } |
+ |
+ for (std::string annotation : module->AnnotationsVector()) { |
+ list_annotations.append(annotation); |
+ list_annotations.append("\n"); |
+ } |
+ } |
+ |
+ if (!list_annotations.empty()) { |
+ // Remove the final newline character. |
+ list_annotations.pop_back(); |
+ |
+ const char kListAnnotationsKey[] = "list_annotations"; |
+ auto it = parameters.find(kListAnnotationsKey); |
+ if (it != parameters.end()) { |
+ LOG(WARNING) << "duplicate key " << kListAnnotationsKey |
+ << ", discarding value " << it->second; |
+ it->second = list_annotations; |
+ } else { |
+ parameters.insert(std::make_pair(kListAnnotationsKey, list_annotations)); |
+ } |
+ } |
+ |
+ return parameters; |
+} |
+ |
+// Calls CrashReportDatabase::RecordUploadAttempt() with |successful| set to |
+// false upon destruction unless disarmed by calling Fire() or Disarm(). Fire() |
+// triggers an immediate call. Armed upon construction. |
+class CallRecordUploadAttempt { |
+ public: |
+ CallRecordUploadAttempt(CrashReportDatabase* database, |
+ const CrashReportDatabase::Report* report) |
+ : database_(database), |
+ report_(report) { |
+ } |
+ |
+ ~CallRecordUploadAttempt() { |
+ Fire(); |
+ } |
+ |
+ void Fire() { |
+ if (report_) { |
+ database_->RecordUploadAttempt(report_, false, std::string()); |
+ } |
+ |
+ Disarm(); |
+ } |
+ |
+ void Disarm() { |
+ report_ = nullptr; |
+ } |
+ |
+ private: |
+ CrashReportDatabase* database_; // weak |
+ const CrashReportDatabase::Report* report_; // weak |
+ |
+ DISALLOW_COPY_AND_ASSIGN(CallRecordUploadAttempt); |
+}; |
+ |
+} // namespace |
+ |
+CrashReportUploadThread::CrashReportUploadThread(CrashReportDatabase* database, |
+ const std::string& url) |
+ : url_(url), |
+ database_(database), |
semaphore_(0), |
thread_(0), |
running_(false) { |
@@ -104,8 +209,99 @@ void CrashReportUploadThread::ProcessPendingReports() { |
void CrashReportUploadThread::ProcessPendingReport( |
const CrashReportDatabase::Report& report) { |
- // TODO(mark): Actually upload the report, if uploads are enabled. |
- database_->SkipReportUpload(report.uuid); |
+ // TODO(mark): Allow uploads to be disabled. |
+ // TODO(mark): Rate-limit uploads. |
+ |
+ const CrashReportDatabase::Report* upload_report; |
+ CrashReportDatabase::OperationStatus status = |
+ database_->GetReportForUploading(report.uuid, &upload_report); |
+ switch (status) { |
+ case CrashReportDatabase::kNoError: |
+ break; |
+ |
+ case CrashReportDatabase::kBusyError: |
+ return; |
+ |
+ case CrashReportDatabase::kReportNotFound: |
+ case CrashReportDatabase::kFileSystemError: |
+ case CrashReportDatabase::kDatabaseError: |
+ // In these cases, SkipReportUpload() might not work either, but it’s best |
+ // to at least try to get the report out of the way. |
+ database_->SkipReportUpload(report.uuid); |
+ return; |
+ } |
+ |
+ CallRecordUploadAttempt call_record_upload_attempt(database_, upload_report); |
+ |
+ std::string response_body; |
+ UploadResult upload_result = UploadReport(upload_report, &response_body); |
+ switch (upload_result) { |
+ case UploadResult::kSuccess: |
+ call_record_upload_attempt.Disarm(); |
+ database_->RecordUploadAttempt(upload_report, true, response_body); |
+ break; |
+ case UploadResult::kPermanentFailure: |
+ case UploadResult::kRetry: |
+ call_record_upload_attempt.Fire(); |
+ |
+ // TODO(mark): Deal with retries properly: don’t call SkipReportUplaod() |
+ // if the result was kRetry and the report hasn’t already been retried |
+ // too many times. |
+ database_->SkipReportUpload(report.uuid); |
+ break; |
+ } |
+} |
+ |
+CrashReportUploadThread::UploadResult CrashReportUploadThread::UploadReport( |
+ const CrashReportDatabase::Report* report, |
+ std::string* response_body) { |
+ std::map<std::string, std::string> parameters; |
+ |
+ { |
+ FileReader minidump_file_reader; |
+ if (!minidump_file_reader.Open(report->file_path)) { |
+ // If the minidump file can’t be opened, all hope is lost. |
+ return UploadResult::kPermanentFailure; |
+ } |
+ |
+ // If the minidump file could be opened, ignore any errors that might occur |
+ // when attempting to interpret it. This may result in its being uploaded |
+ // with few or no parameters, but as long as there’s a dump file, the server |
+ // can decide what to do with it. |
+ parameters = BreakpadHTTPFormParametersFromMinidump(&minidump_file_reader); |
+ } |
+ |
+ HTTPMultipartBuilder http_multipart_builder; |
+ |
+ const char kMinidumpKey[] = "upload_file_minidump"; |
+ |
+ for (const auto& kv : parameters) { |
+ if (kv.first == kMinidumpKey) { |
+ LOG(WARNING) << "reserved key " << kv.first << ", discarding value " |
+ << kv.second; |
+ } else { |
+ http_multipart_builder.SetFormData(kv.first, kv.second); |
+ } |
+ } |
+ |
+ http_multipart_builder.SetFileAttachment(kMinidumpKey, |
+ report->file_path.BaseName().value(), |
+ report->file_path, |
+ "application/octet-stream"); |
+ |
+ // TODO(mark): There should be a timeout option for upload. |
+ scoped_ptr<HTTPTransport> http_transport(HTTPTransport::Create()); |
+ http_transport->SetURL(url_); |
+ HTTPHeaders::value_type content_type = |
+ http_multipart_builder.GetContentType(); |
+ http_transport->SetHeader(content_type.first, content_type.second); |
+ http_transport->SetBodyStream(http_multipart_builder.GetBodyStream().Pass()); |
+ |
+ if (!http_transport->ExecuteSynchronously(response_body)) { |
+ return UploadResult::kRetry; |
+ } |
+ |
+ return UploadResult::kSuccess; |
} |
// static |