OLD | NEW |
1 // Copyright 2015 The Crashpad Authors. All rights reserved. | 1 // Copyright 2015 The Crashpad Authors. All rights reserved. |
2 // | 2 // |
3 // Licensed under the Apache License, Version 2.0 (the "License"); | 3 // Licensed under the Apache License, Version 2.0 (the "License"); |
4 // you may not use this file except in compliance with the License. | 4 // you may not use this file except in compliance with the License. |
5 // You may obtain a copy of the License at | 5 // You may obtain a copy of the License at |
6 // | 6 // |
7 // http://www.apache.org/licenses/LICENSE-2.0 | 7 // http://www.apache.org/licenses/LICENSE-2.0 |
8 // | 8 // |
9 // Unless required by applicable law or agreed to in writing, software | 9 // Unless required by applicable law or agreed to in writing, software |
10 // distributed under the License is distributed on an "AS IS" BASIS, | 10 // distributed under the License is distributed on an "AS IS" BASIS, |
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 // See the License for the specific language governing permissions and | 12 // See the License for the specific language governing permissions and |
13 // limitations under the License. | 13 // limitations under the License. |
14 | 14 |
15 #include "handler/mac/crash_report_upload_thread.h" | 15 #include "handler/mac/crash_report_upload_thread.h" |
16 | 16 |
17 #include <errno.h> | 17 #include <errno.h> |
18 | 18 |
| 19 #include <map> |
19 #include <vector> | 20 #include <vector> |
| 21 #include <utility> |
20 | 22 |
21 #include "base/logging.h" | 23 #include "base/logging.h" |
| 24 #include "base/memory/scoped_ptr.h" |
| 25 #include "snapshot/minidump/process_snapshot_minidump.h" |
| 26 #include "snapshot/module_snapshot.h" |
| 27 #include "util/file/file_reader.h" |
| 28 #include "util/net/http_body.h" |
| 29 #include "util/net/http_multipart_builder.h" |
| 30 #include "util/net/http_transport.h" |
22 | 31 |
23 namespace crashpad { | 32 namespace crashpad { |
24 | 33 |
25 CrashReportUploadThread::CrashReportUploadThread(CrashReportDatabase* database) | 34 namespace { |
26 : database_(database), | 35 |
| 36 // Given a minidump file readable by |minidump_file_reader|, returns a map of |
| 37 // key-value pairs to use as HTTP form parameters for upload to a Breakpad |
| 38 // server. The map is built by combining the process simple annotations map with |
| 39 // each module’s simple annotations map. In the case of duplicate keys, the map |
| 40 // will retain the first value found for any key, and will log a warning about |
| 41 // discarded values. Each module’s annotations vector is also examined and built |
| 42 // into a single string value, with distinct elements separated by newlines, and |
| 43 // stored at the key named “list_annotations”, which supersedes any other key |
| 44 // found by that name. |
| 45 // |
| 46 // In the event of an error reading the minidump file, a message will be logged. |
| 47 std::map<std::string, std::string> BreakpadHTTPFormParametersFromMinidump( |
| 48 FileReader* minidump_file_reader) { |
| 49 ProcessSnapshotMinidump minidump_process_snapshot; |
| 50 if (!minidump_process_snapshot.Initialize(minidump_file_reader)) { |
| 51 return std::map<std::string, std::string>(); |
| 52 } |
| 53 |
| 54 std::map<std::string, std::string> parameters = |
| 55 minidump_process_snapshot.AnnotationsSimpleMap(); |
| 56 |
| 57 std::string list_annotations; |
| 58 for (const ModuleSnapshot* module : minidump_process_snapshot.Modules()) { |
| 59 for (const auto& kv : module->AnnotationsSimpleMap()) { |
| 60 if (parameters.find(kv.first) != parameters.end()) { |
| 61 LOG(WARNING) << "duplicate key " << kv.first << ", discarding value " |
| 62 << kv.second; |
| 63 } else { |
| 64 parameters.insert(kv); |
| 65 } |
| 66 } |
| 67 |
| 68 for (std::string annotation : module->AnnotationsVector()) { |
| 69 list_annotations.append(annotation); |
| 70 list_annotations.append("\n"); |
| 71 } |
| 72 } |
| 73 |
| 74 if (!list_annotations.empty()) { |
| 75 // Remove the final newline character. |
| 76 list_annotations.pop_back(); |
| 77 |
| 78 const char kListAnnotationsKey[] = "list_annotations"; |
| 79 auto it = parameters.find(kListAnnotationsKey); |
| 80 if (it != parameters.end()) { |
| 81 LOG(WARNING) << "duplicate key " << kListAnnotationsKey |
| 82 << ", discarding value " << it->second; |
| 83 it->second = list_annotations; |
| 84 } else { |
| 85 parameters.insert(std::make_pair(kListAnnotationsKey, list_annotations)); |
| 86 } |
| 87 } |
| 88 |
| 89 return parameters; |
| 90 } |
| 91 |
| 92 // Calls CrashReportDatabase::RecordUploadAttempt() with |successful| set to |
| 93 // false upon destruction unless disarmed by calling Fire() or Disarm(). Fire() |
| 94 // triggers an immediate call. Armed upon construction. |
| 95 class CallRecordUploadAttempt { |
| 96 public: |
| 97 CallRecordUploadAttempt(CrashReportDatabase* database, |
| 98 const CrashReportDatabase::Report* report) |
| 99 : database_(database), |
| 100 report_(report) { |
| 101 } |
| 102 |
| 103 ~CallRecordUploadAttempt() { |
| 104 Fire(); |
| 105 } |
| 106 |
| 107 void Fire() { |
| 108 if (report_) { |
| 109 database_->RecordUploadAttempt(report_, false, std::string()); |
| 110 } |
| 111 |
| 112 Disarm(); |
| 113 } |
| 114 |
| 115 void Disarm() { |
| 116 report_ = nullptr; |
| 117 } |
| 118 |
| 119 private: |
| 120 CrashReportDatabase* database_; // weak |
| 121 const CrashReportDatabase::Report* report_; // weak |
| 122 |
| 123 DISALLOW_COPY_AND_ASSIGN(CallRecordUploadAttempt); |
| 124 }; |
| 125 |
| 126 } // namespace |
| 127 |
| 128 CrashReportUploadThread::CrashReportUploadThread(CrashReportDatabase* database, |
| 129 const std::string& url) |
| 130 : url_(url), |
| 131 database_(database), |
27 semaphore_(0), | 132 semaphore_(0), |
28 thread_(0), | 133 thread_(0), |
29 running_(false) { | 134 running_(false) { |
30 } | 135 } |
31 | 136 |
32 CrashReportUploadThread::~CrashReportUploadThread() { | 137 CrashReportUploadThread::~CrashReportUploadThread() { |
33 DCHECK(!running_); | 138 DCHECK(!running_); |
34 DCHECK(!thread_); | 139 DCHECK(!thread_); |
35 } | 140 } |
36 | 141 |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
97 // Respect Stop() being called after at least one attempt to process a | 202 // Respect Stop() being called after at least one attempt to process a |
98 // report. | 203 // report. |
99 if (!running_) { | 204 if (!running_) { |
100 return; | 205 return; |
101 } | 206 } |
102 } | 207 } |
103 } | 208 } |
104 | 209 |
105 void CrashReportUploadThread::ProcessPendingReport( | 210 void CrashReportUploadThread::ProcessPendingReport( |
106 const CrashReportDatabase::Report& report) { | 211 const CrashReportDatabase::Report& report) { |
107 // TODO(mark): Actually upload the report, if uploads are enabled. | 212 // TODO(mark): Allow uploads to be disabled. |
108 database_->SkipReportUpload(report.uuid); | 213 // TODO(mark): Rate-limit uploads. |
| 214 |
| 215 const CrashReportDatabase::Report* upload_report; |
| 216 CrashReportDatabase::OperationStatus status = |
| 217 database_->GetReportForUploading(report.uuid, &upload_report); |
| 218 switch (status) { |
| 219 case CrashReportDatabase::kNoError: |
| 220 break; |
| 221 |
| 222 case CrashReportDatabase::kBusyError: |
| 223 return; |
| 224 |
| 225 case CrashReportDatabase::kReportNotFound: |
| 226 case CrashReportDatabase::kFileSystemError: |
| 227 case CrashReportDatabase::kDatabaseError: |
| 228 // In these cases, SkipReportUpload() might not work either, but it’s best |
| 229 // to at least try to get the report out of the way. |
| 230 database_->SkipReportUpload(report.uuid); |
| 231 return; |
| 232 } |
| 233 |
| 234 CallRecordUploadAttempt call_record_upload_attempt(database_, upload_report); |
| 235 |
| 236 std::string response_body; |
| 237 UploadResult upload_result = UploadReport(upload_report, &response_body); |
| 238 switch (upload_result) { |
| 239 case UploadResult::kSuccess: |
| 240 call_record_upload_attempt.Disarm(); |
| 241 database_->RecordUploadAttempt(upload_report, true, response_body); |
| 242 break; |
| 243 case UploadResult::kPermanentFailure: |
| 244 case UploadResult::kRetry: |
| 245 call_record_upload_attempt.Fire(); |
| 246 |
| 247 // TODO(mark): Deal with retries properly: don’t call SkipReportUplaod() |
| 248 // if the result was kRetry and the report hasn’t already been retried |
| 249 // too many times. |
| 250 database_->SkipReportUpload(report.uuid); |
| 251 break; |
| 252 } |
| 253 } |
| 254 |
| 255 CrashReportUploadThread::UploadResult CrashReportUploadThread::UploadReport( |
| 256 const CrashReportDatabase::Report* report, |
| 257 std::string* response_body) { |
| 258 std::map<std::string, std::string> parameters; |
| 259 |
| 260 { |
| 261 FileReader minidump_file_reader; |
| 262 if (!minidump_file_reader.Open(report->file_path)) { |
| 263 // If the minidump file can’t be opened, all hope is lost. |
| 264 return UploadResult::kPermanentFailure; |
| 265 } |
| 266 |
| 267 // If the minidump file could be opened, ignore any errors that might occur |
| 268 // when attempting to interpret it. This may result in its being uploaded |
| 269 // with few or no parameters, but as long as there’s a dump file, the server |
| 270 // can decide what to do with it. |
| 271 parameters = BreakpadHTTPFormParametersFromMinidump(&minidump_file_reader); |
| 272 } |
| 273 |
| 274 HTTPMultipartBuilder http_multipart_builder; |
| 275 |
| 276 const char kMinidumpKey[] = "upload_file_minidump"; |
| 277 |
| 278 for (const auto& kv : parameters) { |
| 279 if (kv.first == kMinidumpKey) { |
| 280 LOG(WARNING) << "reserved key " << kv.first << ", discarding value " |
| 281 << kv.second; |
| 282 } else { |
| 283 http_multipart_builder.SetFormData(kv.first, kv.second); |
| 284 } |
| 285 } |
| 286 |
| 287 http_multipart_builder.SetFileAttachment(kMinidumpKey, |
| 288 report->file_path.BaseName().value(), |
| 289 report->file_path, |
| 290 "application/octet-stream"); |
| 291 |
| 292 // TODO(mark): There should be a timeout option for upload. |
| 293 scoped_ptr<HTTPTransport> http_transport(HTTPTransport::Create()); |
| 294 http_transport->SetURL(url_); |
| 295 HTTPHeaders::value_type content_type = |
| 296 http_multipart_builder.GetContentType(); |
| 297 http_transport->SetHeader(content_type.first, content_type.second); |
| 298 http_transport->SetBodyStream(http_multipart_builder.GetBodyStream().Pass()); |
| 299 |
| 300 if (!http_transport->ExecuteSynchronously(response_body)) { |
| 301 return UploadResult::kRetry; |
| 302 } |
| 303 |
| 304 return UploadResult::kSuccess; |
109 } | 305 } |
110 | 306 |
111 // static | 307 // static |
112 void* CrashReportUploadThread::RunThreadMain(void* arg) { | 308 void* CrashReportUploadThread::RunThreadMain(void* arg) { |
113 CrashReportUploadThread* self = static_cast<CrashReportUploadThread*>(arg); | 309 CrashReportUploadThread* self = static_cast<CrashReportUploadThread*>(arg); |
114 self->ThreadMain(); | 310 self->ThreadMain(); |
115 return nullptr; | 311 return nullptr; |
116 } | 312 } |
117 | 313 |
118 } // namespace crashpad | 314 } // namespace crashpad |
OLD | NEW |