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); | |
Robert Sesek
2015/03/05 00:14:42
Why switch from const* to const& ?
Mark Mentovai
2015/03/05 19:41:31
Robert Sesek wrote:
| |
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 // TODO(mark): Deal with retries properly. | |
Robert Sesek
2015/03/05 00:14:42
Intention here will be RecordUploadAttempt(…, fals
Mark Mentovai
2015/03/05 19:41:31
Robert Sesek wrote:
| |
247 database_->SkipReportUpload(report.uuid); | |
248 break; | |
249 } | |
250 } | |
251 | |
252 CrashReportUploadThread::UploadResult CrashReportUploadThread::UploadReport( | |
253 const CrashReportDatabase::Report& report, | |
254 std::string* response_body) { | |
255 std::map<std::string, std::string> parameters; | |
256 | |
257 { | |
258 FileReader minidump_file_reader; | |
259 if (!minidump_file_reader.Open(report.file_path)) { | |
260 // If the minidump file can’t be opened, all hope is lost. | |
261 return UploadResult::kPermanentFailure; | |
262 } | |
263 | |
264 // If the minidump file could be opened, ignore any errors that might occur | |
265 // when attempting to interpret it. This may result in its being uploaded | |
266 // with few or no parameters, but as long as there’s a dump file, the server | |
267 // can decide what to do with it. | |
268 parameters = BreakpadHTTPFormParametersFromMinidump(&minidump_file_reader); | |
269 } | |
270 | |
271 HTTPMultipartBuilder http_multipart_builder; | |
272 | |
273 const char kMinidumpKey[] = "upload_file_minidump"; | |
274 | |
275 for (const auto& kv : parameters) { | |
276 if (kv.first == kMinidumpKey) { | |
277 LOG(WARNING) << "reserved key " << kv.first << ", discarding value " | |
278 << kv.second; | |
279 } else { | |
280 http_multipart_builder.SetFormData(kv.first, kv.second); | |
281 } | |
282 } | |
283 | |
284 http_multipart_builder.SetFileAttachment(kMinidumpKey, | |
285 report.file_path.BaseName().value(), | |
286 report.file_path, | |
287 "application/octet-stream"); | |
288 | |
289 // TODO(mark): There should be a timeout option for upload. | |
290 scoped_ptr<HTTPTransport> http_transport(HTTPTransport::Create()); | |
291 http_transport->SetURL(url_); | |
292 HTTPHeaders::value_type content_type = | |
293 http_multipart_builder.GetContentType(); | |
294 http_transport->SetHeader(content_type.first, content_type.second); | |
295 http_transport->SetBodyStream(http_multipart_builder.GetBodyStream().Pass()); | |
296 | |
297 if (!http_transport->ExecuteSynchronously(response_body)) { | |
298 return UploadResult::kRetry; | |
299 } | |
300 | |
301 return UploadResult::kSuccess; | |
109 } | 302 } |
110 | 303 |
111 // static | 304 // static |
112 void* CrashReportUploadThread::RunThreadMain(void* arg) { | 305 void* CrashReportUploadThread::RunThreadMain(void* arg) { |
113 CrashReportUploadThread* self = static_cast<CrashReportUploadThread*>(arg); | 306 CrashReportUploadThread* self = static_cast<CrashReportUploadThread*>(arg); |
114 self->ThreadMain(); | 307 self->ThreadMain(); |
115 return nullptr; | 308 return nullptr; |
116 } | 309 } |
117 | 310 |
118 } // namespace crashpad | 311 } // namespace crashpad |
OLD | NEW |