| OLD | NEW |
| (Empty) |
| 1 // Copyright 2015 The Crashpad Authors. All rights reserved. | |
| 2 // | |
| 3 // Licensed under the Apache License, Version 2.0 (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 | |
| 6 // | |
| 7 // http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 // | |
| 9 // Unless required by applicable law or agreed to in writing, software | |
| 10 // distributed under the License is distributed on an "AS IS" BASIS, | |
| 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 12 // See the License for the specific language governing permissions and | |
| 13 // limitations under the License. | |
| 14 | |
| 15 #include "handler/mac/crash_report_upload_thread.h" | |
| 16 | |
| 17 #include <errno.h> | |
| 18 #include <time.h> | |
| 19 | |
| 20 #include <map> | |
| 21 #include <vector> | |
| 22 | |
| 23 #include "base/logging.h" | |
| 24 #include "base/memory/scoped_ptr.h" | |
| 25 #include "client/settings.h" | |
| 26 #include "snapshot/minidump/process_snapshot_minidump.h" | |
| 27 #include "snapshot/module_snapshot.h" | |
| 28 #include "util/file/file_reader.h" | |
| 29 #include "util/misc/uuid.h" | |
| 30 #include "util/net/http_body.h" | |
| 31 #include "util/net/http_multipart_builder.h" | |
| 32 #include "util/net/http_transport.h" | |
| 33 #include "util/stdlib/map_insert.h" | |
| 34 | |
| 35 namespace crashpad { | |
| 36 | |
| 37 namespace { | |
| 38 | |
| 39 void InsertOrReplaceMapEntry(std::map<std::string, std::string>* map, | |
| 40 const std::string& key, | |
| 41 const std::string& value) { | |
| 42 std::string old_value; | |
| 43 if (!MapInsertOrReplace(map, key, value, &old_value)) { | |
| 44 LOG(WARNING) << "duplicate key " << key << ", discarding value " | |
| 45 << old_value; | |
| 46 } | |
| 47 } | |
| 48 | |
| 49 // Given a minidump file readable by |minidump_file_reader|, returns a map of | |
| 50 // key-value pairs to use as HTTP form parameters for upload to a Breakpad | |
| 51 // server. The map is built by combining the process simple annotations map with | |
| 52 // each module’s simple annotations map. In the case of duplicate keys, the map | |
| 53 // will retain the first value found for any key, and will log a warning about | |
| 54 // discarded values. Each module’s annotations vector is also examined and built | |
| 55 // into a single string value, with distinct elements separated by newlines, and | |
| 56 // stored at the key named “list_annotations”, which supersedes any other key | |
| 57 // found by that name. The client ID stored in the minidump is converted to | |
| 58 // a string and stored at the key named “guid”, which supersedes any other key | |
| 59 // found by that name. | |
| 60 // | |
| 61 // In the event of an error reading the minidump file, a message will be logged. | |
| 62 std::map<std::string, std::string> BreakpadHTTPFormParametersFromMinidump( | |
| 63 FileReader* minidump_file_reader) { | |
| 64 ProcessSnapshotMinidump minidump_process_snapshot; | |
| 65 if (!minidump_process_snapshot.Initialize(minidump_file_reader)) { | |
| 66 return std::map<std::string, std::string>(); | |
| 67 } | |
| 68 | |
| 69 std::map<std::string, std::string> parameters = | |
| 70 minidump_process_snapshot.AnnotationsSimpleMap(); | |
| 71 | |
| 72 std::string list_annotations; | |
| 73 for (const ModuleSnapshot* module : minidump_process_snapshot.Modules()) { | |
| 74 for (const auto& kv : module->AnnotationsSimpleMap()) { | |
| 75 if (!parameters.insert(kv).second) { | |
| 76 LOG(WARNING) << "duplicate key " << kv.first << ", discarding value " | |
| 77 << kv.second; | |
| 78 } | |
| 79 } | |
| 80 | |
| 81 for (std::string annotation : module->AnnotationsVector()) { | |
| 82 list_annotations.append(annotation); | |
| 83 list_annotations.append("\n"); | |
| 84 } | |
| 85 } | |
| 86 | |
| 87 if (!list_annotations.empty()) { | |
| 88 // Remove the final newline character. | |
| 89 list_annotations.resize(list_annotations.size() - 1); | |
| 90 | |
| 91 InsertOrReplaceMapEntry(¶meters, "list_annotations", list_annotations); | |
| 92 } | |
| 93 | |
| 94 UUID client_id; | |
| 95 minidump_process_snapshot.ClientID(&client_id); | |
| 96 InsertOrReplaceMapEntry(¶meters, "guid", client_id.ToString()); | |
| 97 | |
| 98 return parameters; | |
| 99 } | |
| 100 | |
| 101 // Calls CrashReportDatabase::RecordUploadAttempt() with |successful| set to | |
| 102 // false upon destruction unless disarmed by calling Fire() or Disarm(). Fire() | |
| 103 // triggers an immediate call. Armed upon construction. | |
| 104 class CallRecordUploadAttempt { | |
| 105 public: | |
| 106 CallRecordUploadAttempt(CrashReportDatabase* database, | |
| 107 const CrashReportDatabase::Report* report) | |
| 108 : database_(database), | |
| 109 report_(report) { | |
| 110 } | |
| 111 | |
| 112 ~CallRecordUploadAttempt() { | |
| 113 Fire(); | |
| 114 } | |
| 115 | |
| 116 void Fire() { | |
| 117 if (report_) { | |
| 118 database_->RecordUploadAttempt(report_, false, std::string()); | |
| 119 } | |
| 120 | |
| 121 Disarm(); | |
| 122 } | |
| 123 | |
| 124 void Disarm() { | |
| 125 report_ = nullptr; | |
| 126 } | |
| 127 | |
| 128 private: | |
| 129 CrashReportDatabase* database_; // weak | |
| 130 const CrashReportDatabase::Report* report_; // weak | |
| 131 | |
| 132 DISALLOW_COPY_AND_ASSIGN(CallRecordUploadAttempt); | |
| 133 }; | |
| 134 | |
| 135 } // namespace | |
| 136 | |
| 137 CrashReportUploadThread::CrashReportUploadThread(CrashReportDatabase* database, | |
| 138 const std::string& url) | |
| 139 : url_(url), | |
| 140 database_(database), | |
| 141 semaphore_(0), | |
| 142 thread_(0), | |
| 143 running_(false) { | |
| 144 } | |
| 145 | |
| 146 CrashReportUploadThread::~CrashReportUploadThread() { | |
| 147 DCHECK(!running_); | |
| 148 DCHECK(!thread_); | |
| 149 } | |
| 150 | |
| 151 void CrashReportUploadThread::Start() { | |
| 152 DCHECK(!running_); | |
| 153 DCHECK(!thread_); | |
| 154 | |
| 155 running_ = true; | |
| 156 if ((errno = pthread_create(&thread_, nullptr, RunThreadMain, this)) != 0) { | |
| 157 PLOG(ERROR) << "pthread_create"; | |
| 158 DCHECK(false); | |
| 159 running_ = false; | |
| 160 } | |
| 161 } | |
| 162 | |
| 163 void CrashReportUploadThread::Stop() { | |
| 164 DCHECK(running_); | |
| 165 DCHECK(thread_); | |
| 166 | |
| 167 if (!running_) { | |
| 168 return; | |
| 169 } | |
| 170 | |
| 171 running_ = false; | |
| 172 semaphore_.Signal(); | |
| 173 | |
| 174 if ((errno = pthread_join(thread_, nullptr)) != 0) { | |
| 175 PLOG(ERROR) << "pthread_join"; | |
| 176 DCHECK(false); | |
| 177 } | |
| 178 | |
| 179 thread_ = 0; | |
| 180 } | |
| 181 | |
| 182 void CrashReportUploadThread::ReportPending() { | |
| 183 semaphore_.Signal(); | |
| 184 } | |
| 185 | |
| 186 void CrashReportUploadThread::ThreadMain() { | |
| 187 while (running_) { | |
| 188 ProcessPendingReports(); | |
| 189 | |
| 190 // Check for pending reports every 15 minutes, even in the absence of a | |
| 191 // signal from the handler thread. This allows for failed uploads to be | |
| 192 // retried periodically, and for pending reports written by other processes | |
| 193 // to be recognized. | |
| 194 semaphore_.TimedWait(15 * 60); | |
| 195 } | |
| 196 } | |
| 197 | |
| 198 void CrashReportUploadThread::ProcessPendingReports() { | |
| 199 std::vector<CrashReportDatabase::Report> reports; | |
| 200 if (database_->GetPendingReports(&reports) != CrashReportDatabase::kNoError) { | |
| 201 // The database is sick. It might be prudent to stop trying to poke it from | |
| 202 // this thread by abandoning the thread altogether. On the other hand, if | |
| 203 // the problem is transient, it might be possible to talk to it again on the | |
| 204 // next pass. For now, take the latter approach. | |
| 205 return; | |
| 206 } | |
| 207 | |
| 208 for (const CrashReportDatabase::Report& report : reports) { | |
| 209 ProcessPendingReport(report); | |
| 210 | |
| 211 // Respect Stop() being called after at least one attempt to process a | |
| 212 // report. | |
| 213 if (!running_) { | |
| 214 return; | |
| 215 } | |
| 216 } | |
| 217 } | |
| 218 | |
| 219 void CrashReportUploadThread::ProcessPendingReport( | |
| 220 const CrashReportDatabase::Report& report) { | |
| 221 Settings* const settings = database_->GetSettings(); | |
| 222 | |
| 223 bool uploads_enabled; | |
| 224 if (!settings->GetUploadsEnabled(&uploads_enabled) || | |
| 225 !uploads_enabled || | |
| 226 url_.empty()) { | |
| 227 // If the upload-enabled state can’t be determined, uploads are disabled, or | |
| 228 // there’s no URL to upload to, don’t attempt to upload the new report. | |
| 229 database_->SkipReportUpload(report.uuid); | |
| 230 return; | |
| 231 } | |
| 232 | |
| 233 // This currently implements very simplistic rate-limiting, compatible with | |
| 234 // the Breakpad client, where the strategy is to permit one upload attempt per | |
| 235 // hour, and retire reports that would exceed this limit or for which the | |
| 236 // upload fails on the first attempt. | |
| 237 // | |
| 238 // TODO(mark): Provide a proper rate-limiting strategy and allow for failed | |
| 239 // upload attempts to be retried. | |
| 240 time_t last_upload_attempt_time; | |
| 241 if (settings->GetLastUploadAttemptTime(&last_upload_attempt_time)) { | |
| 242 time_t now = time(nullptr); | |
| 243 if (now >= last_upload_attempt_time) { | |
| 244 // If the most recent upload attempt occurred within the past hour, don’t | |
| 245 // attempt to upload the new report. If it happened longer ago, attempt to | |
| 246 // upload the report. | |
| 247 const int kUploadAttemptIntervalSeconds = 60 * 60; // 1 hour | |
| 248 if (now - last_upload_attempt_time < kUploadAttemptIntervalSeconds) { | |
| 249 database_->SkipReportUpload(report.uuid); | |
| 250 return; | |
| 251 } | |
| 252 } else { | |
| 253 // The most recent upload attempt purportedly occurred in the future. If | |
| 254 // it “happened” at least one day in the future, assume that the last | |
| 255 // upload attempt time is bogus, and attempt to upload the report. If the | |
| 256 // most recent upload time is in the future but within one day, accept it | |
| 257 // and don’t attempt to upload the report. | |
| 258 const int kBackwardsClockTolerance = 60 * 60 * 24; // 1 day | |
| 259 if (last_upload_attempt_time - now < kBackwardsClockTolerance) { | |
| 260 database_->SkipReportUpload(report.uuid); | |
| 261 return; | |
| 262 } | |
| 263 } | |
| 264 } | |
| 265 | |
| 266 const CrashReportDatabase::Report* upload_report; | |
| 267 CrashReportDatabase::OperationStatus status = | |
| 268 database_->GetReportForUploading(report.uuid, &upload_report); | |
| 269 switch (status) { | |
| 270 case CrashReportDatabase::kNoError: | |
| 271 break; | |
| 272 | |
| 273 case CrashReportDatabase::kBusyError: | |
| 274 return; | |
| 275 | |
| 276 case CrashReportDatabase::kReportNotFound: | |
| 277 case CrashReportDatabase::kFileSystemError: | |
| 278 case CrashReportDatabase::kDatabaseError: | |
| 279 // In these cases, SkipReportUpload() might not work either, but it’s best | |
| 280 // to at least try to get the report out of the way. | |
| 281 database_->SkipReportUpload(report.uuid); | |
| 282 return; | |
| 283 } | |
| 284 | |
| 285 CallRecordUploadAttempt call_record_upload_attempt(database_, upload_report); | |
| 286 | |
| 287 std::string response_body; | |
| 288 UploadResult upload_result = UploadReport(upload_report, &response_body); | |
| 289 switch (upload_result) { | |
| 290 case UploadResult::kSuccess: | |
| 291 call_record_upload_attempt.Disarm(); | |
| 292 database_->RecordUploadAttempt(upload_report, true, response_body); | |
| 293 break; | |
| 294 case UploadResult::kPermanentFailure: | |
| 295 case UploadResult::kRetry: | |
| 296 call_record_upload_attempt.Fire(); | |
| 297 | |
| 298 // TODO(mark): Deal with retries properly: don’t call SkipReportUplaod() | |
| 299 // if the result was kRetry and the report hasn’t already been retried | |
| 300 // too many times. | |
| 301 database_->SkipReportUpload(report.uuid); | |
| 302 break; | |
| 303 } | |
| 304 } | |
| 305 | |
| 306 CrashReportUploadThread::UploadResult CrashReportUploadThread::UploadReport( | |
| 307 const CrashReportDatabase::Report* report, | |
| 308 std::string* response_body) { | |
| 309 std::map<std::string, std::string> parameters; | |
| 310 | |
| 311 { | |
| 312 FileReader minidump_file_reader; | |
| 313 if (!minidump_file_reader.Open(report->file_path)) { | |
| 314 // If the minidump file can’t be opened, all hope is lost. | |
| 315 return UploadResult::kPermanentFailure; | |
| 316 } | |
| 317 | |
| 318 // If the minidump file could be opened, ignore any errors that might occur | |
| 319 // when attempting to interpret it. This may result in its being uploaded | |
| 320 // with few or no parameters, but as long as there’s a dump file, the server | |
| 321 // can decide what to do with it. | |
| 322 parameters = BreakpadHTTPFormParametersFromMinidump(&minidump_file_reader); | |
| 323 } | |
| 324 | |
| 325 HTTPMultipartBuilder http_multipart_builder; | |
| 326 | |
| 327 const char kMinidumpKey[] = "upload_file_minidump"; | |
| 328 | |
| 329 for (const auto& kv : parameters) { | |
| 330 if (kv.first == kMinidumpKey) { | |
| 331 LOG(WARNING) << "reserved key " << kv.first << ", discarding value " | |
| 332 << kv.second; | |
| 333 } else { | |
| 334 http_multipart_builder.SetFormData(kv.first, kv.second); | |
| 335 } | |
| 336 } | |
| 337 | |
| 338 http_multipart_builder.SetFileAttachment(kMinidumpKey, | |
| 339 report->file_path.BaseName().value(), | |
| 340 report->file_path, | |
| 341 "application/octet-stream"); | |
| 342 | |
| 343 scoped_ptr<HTTPTransport> http_transport(HTTPTransport::Create()); | |
| 344 http_transport->SetURL(url_); | |
| 345 HTTPHeaders::value_type content_type = | |
| 346 http_multipart_builder.GetContentType(); | |
| 347 http_transport->SetHeader(content_type.first, content_type.second); | |
| 348 http_transport->SetBodyStream(http_multipart_builder.GetBodyStream().Pass()); | |
| 349 // TODO(mark): The timeout should be configurable by the client. | |
| 350 http_transport->SetTimeout(60.0); // 1 minute. | |
| 351 | |
| 352 if (!http_transport->ExecuteSynchronously(response_body)) { | |
| 353 return UploadResult::kRetry; | |
| 354 } | |
| 355 | |
| 356 return UploadResult::kSuccess; | |
| 357 } | |
| 358 | |
| 359 // static | |
| 360 void* CrashReportUploadThread::RunThreadMain(void* arg) { | |
| 361 CrashReportUploadThread* self = static_cast<CrashReportUploadThread*>(arg); | |
| 362 self->ThreadMain(); | |
| 363 return nullptr; | |
| 364 } | |
| 365 | |
| 366 } // namespace crashpad | |
| OLD | NEW |