crash_report_upload_thread.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  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. #include "handler/crash_report_upload_thread.h"
  15. #include <errno.h>
  16. #include <time.h>
  17. #include <algorithm>
  18. #include <map>
  19. #include <memory>
  20. #include <vector>
  21. #include "base/logging.h"
  22. #include "base/strings/stringprintf.h"
  23. #include "base/strings/utf_string_conversions.h"
  24. #include "build/build_config.h"
  25. #include "client/settings.h"
  26. #include "handler/minidump_to_upload_parameters.h"
  27. #include "snapshot/minidump/process_snapshot_minidump.h"
  28. #include "snapshot/module_snapshot.h"
  29. #include "util/file/file_reader.h"
  30. #include "util/misc/metrics.h"
  31. #include "util/misc/uuid.h"
  32. #include "util/net/http_body.h"
  33. #include "util/net/http_multipart_builder.h"
  34. #include "util/net/http_transport.h"
  35. #include "util/net/url.h"
  36. #include "util/stdlib/map_insert.h"
  37. #if defined(OS_MACOSX)
  38. #include "handler/mac/file_limit_annotation.h"
  39. #endif // OS_MACOSX
  40. namespace crashpad {
  41. namespace {
  42. // Calls CrashReportDatabase::RecordUploadAttempt() with |successful| set to
  43. // false upon destruction unless disarmed by calling Fire() or Disarm(). Fire()
  44. // triggers an immediate call. Armed upon construction.
  45. class CallRecordUploadAttempt {
  46. public:
  47. CallRecordUploadAttempt(CrashReportDatabase* database,
  48. const CrashReportDatabase::Report* report)
  49. : database_(database),
  50. report_(report) {
  51. }
  52. ~CallRecordUploadAttempt() {
  53. Fire();
  54. }
  55. void Fire() {
  56. if (report_) {
  57. database_->RecordUploadAttempt(report_, false, std::string());
  58. }
  59. Disarm();
  60. }
  61. void Disarm() {
  62. report_ = nullptr;
  63. }
  64. private:
  65. CrashReportDatabase* database_; // weak
  66. const CrashReportDatabase::Report* report_; // weak
  67. DISALLOW_COPY_AND_ASSIGN(CallRecordUploadAttempt);
  68. };
  69. } // namespace
  70. CrashReportUploadThread::CrashReportUploadThread(CrashReportDatabase* database,
  71. const std::string& url,
  72. const Options& options)
  73. : options_(options),
  74. url_(url),
  75. // When watching for pending reports, check every 15 minutes, even in the
  76. // absence of a signal from the handler thread. This allows for failed
  77. // uploads to be retried periodically, and for pending reports written by
  78. // other processes to be recognized.
  79. thread_(options.watch_pending_reports ? 15 * 60.0
  80. : WorkerThread::kIndefiniteWait,
  81. this),
  82. known_pending_report_uuids_(),
  83. database_(database) {}
  84. CrashReportUploadThread::~CrashReportUploadThread() {
  85. }
  86. void CrashReportUploadThread::Start() {
  87. thread_.Start(
  88. options_.watch_pending_reports ? 0.0 : WorkerThread::kIndefiniteWait);
  89. }
  90. void CrashReportUploadThread::Stop() {
  91. thread_.Stop();
  92. }
  93. void CrashReportUploadThread::ReportPending(const UUID& report_uuid) {
  94. known_pending_report_uuids_.PushBack(report_uuid);
  95. thread_.DoWorkNow();
  96. }
  97. void CrashReportUploadThread::ProcessPendingReports() {
  98. std::vector<UUID> known_report_uuids = known_pending_report_uuids_.Drain();
  99. for (const UUID& report_uuid : known_report_uuids) {
  100. CrashReportDatabase::Report report;
  101. if (database_->LookUpCrashReport(report_uuid, &report) !=
  102. CrashReportDatabase::kNoError) {
  103. continue;
  104. }
  105. ProcessPendingReport(report);
  106. // Respect Stop() being called after at least one attempt to process a
  107. // report.
  108. if (!thread_.is_running()) {
  109. return;
  110. }
  111. }
  112. // Known pending reports are always processed (above). The rest of this
  113. // function is concerned with scanning for pending reports not already known
  114. // to this thread.
  115. if (!options_.watch_pending_reports) {
  116. return;
  117. }
  118. std::vector<CrashReportDatabase::Report> reports;
  119. if (database_->GetPendingReports(&reports) != CrashReportDatabase::kNoError) {
  120. // The database is sick. It might be prudent to stop trying to poke it from
  121. // this thread by abandoning the thread altogether. On the other hand, if
  122. // the problem is transient, it might be possible to talk to it again on the
  123. // next pass. For now, take the latter approach.
  124. return;
  125. }
  126. for (const CrashReportDatabase::Report& report : reports) {
  127. if (std::find(known_report_uuids.begin(),
  128. known_report_uuids.end(),
  129. report.uuid) != known_report_uuids.end()) {
  130. // An attempt to process the report already occurred above. The report is
  131. // still pending, so upload must have failed. Don’t retry it immediately,
  132. // it can wait until at least the next pass through this method.
  133. continue;
  134. }
  135. ProcessPendingReport(report);
  136. // Respect Stop() being called after at least one attempt to process a
  137. // report.
  138. if (!thread_.is_running()) {
  139. return;
  140. }
  141. }
  142. }
  143. void CrashReportUploadThread::ProcessPendingReport(
  144. const CrashReportDatabase::Report& report) {
  145. #if defined(OS_MACOSX)
  146. RecordFileLimitAnnotation();
  147. #endif // OS_MACOSX
  148. Settings* const settings = database_->GetSettings();
  149. bool uploads_enabled;
  150. if (url_.empty() ||
  151. (!report.upload_explicitly_requested &&
  152. (!settings->GetUploadsEnabled(&uploads_enabled) || !uploads_enabled))) {
  153. // Don’t attempt an upload if there’s no URL to upload to. Allow upload if
  154. // it has been explicitly requested by the user, otherwise, respect the
  155. // upload-enabled state stored in the database’s settings.
  156. database_->SkipReportUpload(report.uuid,
  157. Metrics::CrashSkippedReason::kUploadsDisabled);
  158. return;
  159. }
  160. // This currently implements very simplistic rate-limiting, compatible with
  161. // the Breakpad client, where the strategy is to permit one upload attempt per
  162. // hour, and retire reports that would exceed this limit or for which the
  163. // upload fails on the first attempt.
  164. //
  165. // If upload was requested explicitly (i.e. by user action), we do not
  166. // throttle the upload.
  167. //
  168. // TODO(mark): Provide a proper rate-limiting strategy and allow for failed
  169. // upload attempts to be retried.
  170. if (!report.upload_explicitly_requested && options_.rate_limit) {
  171. time_t last_upload_attempt_time;
  172. if (settings->GetLastUploadAttemptTime(&last_upload_attempt_time)) {
  173. time_t now = time(nullptr);
  174. if (now >= last_upload_attempt_time) {
  175. // If the most recent upload attempt occurred within the past hour,
  176. // don’t attempt to upload the new report. If it happened longer ago,
  177. // attempt to upload the report.
  178. constexpr int kUploadAttemptIntervalSeconds = 60 * 60; // 1 hour
  179. if (now - last_upload_attempt_time < kUploadAttemptIntervalSeconds) {
  180. database_->SkipReportUpload(
  181. report.uuid, Metrics::CrashSkippedReason::kUploadThrottled);
  182. return;
  183. }
  184. } else {
  185. // The most recent upload attempt purportedly occurred in the future. If
  186. // it “happened” at least one day in the future, assume that the last
  187. // upload attempt time is bogus, and attempt to upload the report. If
  188. // the most recent upload time is in the future but within one day,
  189. // accept it and don’t attempt to upload the report.
  190. constexpr int kBackwardsClockTolerance = 60 * 60 * 24; // 1 day
  191. if (last_upload_attempt_time - now < kBackwardsClockTolerance) {
  192. database_->SkipReportUpload(
  193. report.uuid, Metrics::CrashSkippedReason::kUnexpectedTime);
  194. return;
  195. }
  196. }
  197. }
  198. }
  199. const CrashReportDatabase::Report* upload_report;
  200. CrashReportDatabase::OperationStatus status =
  201. database_->GetReportForUploading(report.uuid, &upload_report);
  202. switch (status) {
  203. case CrashReportDatabase::kNoError:
  204. break;
  205. case CrashReportDatabase::kBusyError:
  206. case CrashReportDatabase::kReportNotFound:
  207. // Someone else may have gotten to it first. If they’re working on it now,
  208. // this will be kBusyError. If they’ve already finished with it, it’ll be
  209. // kReportNotFound.
  210. return;
  211. case CrashReportDatabase::kFileSystemError:
  212. case CrashReportDatabase::kDatabaseError:
  213. // In these cases, SkipReportUpload() might not work either, but it’s best
  214. // to at least try to get the report out of the way.
  215. database_->SkipReportUpload(report.uuid,
  216. Metrics::CrashSkippedReason::kDatabaseError);
  217. return;
  218. case CrashReportDatabase::kCannotRequestUpload:
  219. NOTREACHED();
  220. return;
  221. }
  222. CallRecordUploadAttempt call_record_upload_attempt(database_, upload_report);
  223. std::string response_body;
  224. UploadResult upload_result = UploadReport(upload_report, &response_body);
  225. switch (upload_result) {
  226. case UploadResult::kSuccess:
  227. call_record_upload_attempt.Disarm();
  228. database_->RecordUploadAttempt(upload_report, true, response_body);
  229. break;
  230. case UploadResult::kPermanentFailure:
  231. case UploadResult::kRetry:
  232. call_record_upload_attempt.Fire();
  233. // TODO(mark): Deal with retries properly: don’t call SkipReportUplaod()
  234. // if the result was kRetry and the report hasn’t already been retried
  235. // too many times.
  236. database_->SkipReportUpload(report.uuid,
  237. Metrics::CrashSkippedReason::kUploadFailed);
  238. break;
  239. }
  240. }
  241. CrashReportUploadThread::UploadResult CrashReportUploadThread::UploadReport(
  242. const CrashReportDatabase::Report* report,
  243. std::string* response_body) {
  244. std::map<std::string, std::string> parameters;
  245. FileReader minidump_file_reader;
  246. if (!minidump_file_reader.Open(report->file_path)) {
  247. // If the minidump file can’t be opened, all hope is lost.
  248. return UploadResult::kPermanentFailure;
  249. }
  250. FileOffset start_offset = minidump_file_reader.SeekGet();
  251. if (start_offset < 0) {
  252. return UploadResult::kPermanentFailure;
  253. }
  254. // Ignore any errors that might occur when attempting to interpret the
  255. // minidump file. This may result in its being uploaded with few or no
  256. // parameters, but as long as there’s a dump file, the server can decide what
  257. // to do with it.
  258. ProcessSnapshotMinidump minidump_process_snapshot;
  259. if (minidump_process_snapshot.Initialize(&minidump_file_reader)) {
  260. parameters =
  261. BreakpadHTTPFormParametersFromMinidump(&minidump_process_snapshot);
  262. }
  263. if (!minidump_file_reader.SeekSet(start_offset)) {
  264. return UploadResult::kPermanentFailure;
  265. }
  266. HTTPMultipartBuilder http_multipart_builder;
  267. http_multipart_builder.SetGzipEnabled(options_.upload_gzip);
  268. static constexpr char kMinidumpKey[] = "upload_file_minidump";
  269. for (const auto& kv : parameters) {
  270. if (kv.first == kMinidumpKey) {
  271. LOG(WARNING) << "reserved key " << kv.first << ", discarding value "
  272. << kv.second;
  273. } else {
  274. http_multipart_builder.SetFormData(kv.first, kv.second);
  275. }
  276. }
  277. http_multipart_builder.SetFileAttachment(
  278. kMinidumpKey,
  279. #if defined(OS_WIN)
  280. base::UTF16ToUTF8(report->file_path.BaseName().value()),
  281. #else
  282. report->file_path.BaseName().value(),
  283. #endif
  284. &minidump_file_reader,
  285. "application/octet-stream");
  286. std::unique_ptr<HTTPTransport> http_transport(HTTPTransport::Create());
  287. HTTPHeaders content_headers;
  288. http_multipart_builder.PopulateContentHeaders(&content_headers);
  289. for (const auto& content_header : content_headers) {
  290. http_transport->SetHeader(content_header.first, content_header.second);
  291. }
  292. http_transport->SetBodyStream(http_multipart_builder.GetBodyStream());
  293. // TODO(mark): The timeout should be configurable by the client.
  294. http_transport->SetTimeout(60.0); // 1 minute.
  295. std::string url = url_;
  296. if (options_.identify_client_via_url) {
  297. // Add parameters to the URL which identify the client to the server.
  298. static constexpr struct {
  299. const char* key;
  300. const char* url_field_name;
  301. } kURLParameterMappings[] = {
  302. {"prod", "product"},
  303. {"ver", "version"},
  304. {"guid", "guid"},
  305. };
  306. for (const auto& parameter_mapping : kURLParameterMappings) {
  307. const auto it = parameters.find(parameter_mapping.key);
  308. if (it != parameters.end()) {
  309. url.append(
  310. base::StringPrintf("%c%s=%s",
  311. url.find('?') == std::string::npos ? '?' : '&',
  312. parameter_mapping.url_field_name,
  313. URLEncode(it->second).c_str()));
  314. }
  315. }
  316. }
  317. http_transport->SetURL(url);
  318. if (!http_transport->ExecuteSynchronously(response_body)) {
  319. return UploadResult::kRetry;
  320. }
  321. return UploadResult::kSuccess;
  322. }
  323. void CrashReportUploadThread::DoWork(const WorkerThread* thread) {
  324. ProcessPendingReports();
  325. }
  326. } // namespace crashpad