2015-02-12 15:03:59 -05:00
|
|
|
|
// Copyright 2015 The Crashpad Authors. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
|
//
|
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
//
|
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
2015-08-18 15:34:10 -07:00
|
|
|
|
#include "handler/crash_report_upload_thread.h"
|
2015-02-12 15:03:59 -05:00
|
|
|
|
|
|
|
|
|
#include <errno.h>
|
2015-03-10 15:31:25 -04:00
|
|
|
|
#include <time.h>
|
2015-02-12 15:03:59 -05:00
|
|
|
|
|
2017-04-14 14:50:11 -04:00
|
|
|
|
#include <algorithm>
|
2015-03-05 15:40:47 -05:00
|
|
|
|
#include <map>
|
2016-04-25 12:13:07 -07:00
|
|
|
|
#include <memory>
|
2015-02-12 15:03:59 -05:00
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
|
|
#include "base/logging.h"
|
2017-09-28 09:53:39 -07:00
|
|
|
|
#include "base/strings/stringprintf.h"
|
2015-08-18 15:34:10 -07:00
|
|
|
|
#include "base/strings/utf_string_conversions.h"
|
|
|
|
|
#include "build/build_config.h"
|
2015-03-10 15:31:25 -04:00
|
|
|
|
#include "client/settings.h"
|
2017-11-02 11:55:44 -04:00
|
|
|
|
#include "handler/minidump_to_upload_parameters.h"
|
2015-03-05 15:40:47 -05:00
|
|
|
|
#include "snapshot/minidump/process_snapshot_minidump.h"
|
|
|
|
|
#include "snapshot/module_snapshot.h"
|
|
|
|
|
#include "util/file/file_reader.h"
|
2016-09-26 15:06:19 -07:00
|
|
|
|
#include "util/misc/metrics.h"
|
2017-09-28 09:53:39 -07:00
|
|
|
|
#include "util/misc/uuid.h"
|
2015-03-05 15:40:47 -05:00
|
|
|
|
#include "util/net/http_body.h"
|
|
|
|
|
#include "util/net/http_multipart_builder.h"
|
|
|
|
|
#include "util/net/http_transport.h"
|
2017-09-28 09:53:39 -07:00
|
|
|
|
#include "util/net/url.h"
|
2015-03-31 14:29:32 -04:00
|
|
|
|
#include "util/stdlib/map_insert.h"
|
2015-02-12 15:03:59 -05:00
|
|
|
|
|
2017-04-18 11:50:39 -04:00
|
|
|
|
#if defined(OS_MACOSX)
|
|
|
|
|
#include "handler/mac/file_limit_annotation.h"
|
|
|
|
|
#endif // OS_MACOSX
|
|
|
|
|
|
2015-02-12 15:03:59 -05:00
|
|
|
|
namespace crashpad {
|
|
|
|
|
|
2015-03-05 15:40:47 -05:00
|
|
|
|
CrashReportUploadThread::CrashReportUploadThread(CrashReportDatabase* database,
|
2016-01-06 09:59:54 -08:00
|
|
|
|
const std::string& url,
|
2017-09-28 09:53:39 -07:00
|
|
|
|
const Options& options)
|
|
|
|
|
: options_(options),
|
|
|
|
|
url_(url),
|
2017-04-14 14:50:11 -04:00
|
|
|
|
// When watching for pending reports, check every 15 minutes, even in the
|
|
|
|
|
// absence of a signal from the handler thread. This allows for failed
|
|
|
|
|
// uploads to be retried periodically, and for pending reports written by
|
|
|
|
|
// other processes to be recognized.
|
2017-09-28 09:53:39 -07:00
|
|
|
|
thread_(options.watch_pending_reports ? 15 * 60.0
|
|
|
|
|
: WorkerThread::kIndefiniteWait,
|
2017-04-14 14:50:11 -04:00
|
|
|
|
this),
|
|
|
|
|
known_pending_report_uuids_(),
|
2018-02-20 10:55:17 -08:00
|
|
|
|
database_(database) {
|
|
|
|
|
DCHECK(!url_.empty());
|
|
|
|
|
}
|
2015-02-12 15:03:59 -05:00
|
|
|
|
|
|
|
|
|
CrashReportUploadThread::~CrashReportUploadThread() {
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-20 10:55:17 -08:00
|
|
|
|
void CrashReportUploadThread::ReportPending(const UUID& report_uuid) {
|
|
|
|
|
known_pending_report_uuids_.PushBack(report_uuid);
|
|
|
|
|
thread_.DoWorkNow();
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-12 15:03:59 -05:00
|
|
|
|
void CrashReportUploadThread::Start() {
|
2017-09-28 09:53:39 -07:00
|
|
|
|
thread_.Start(
|
|
|
|
|
options_.watch_pending_reports ? 0.0 : WorkerThread::kIndefiniteWait);
|
2015-02-12 15:03:59 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CrashReportUploadThread::Stop() {
|
2016-01-04 17:10:58 -05:00
|
|
|
|
thread_.Stop();
|
2015-02-12 15:03:59 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CrashReportUploadThread::ProcessPendingReports() {
|
2017-04-14 14:50:11 -04:00
|
|
|
|
std::vector<UUID> known_report_uuids = known_pending_report_uuids_.Drain();
|
|
|
|
|
for (const UUID& report_uuid : known_report_uuids) {
|
|
|
|
|
CrashReportDatabase::Report report;
|
|
|
|
|
if (database_->LookUpCrashReport(report_uuid, &report) !=
|
|
|
|
|
CrashReportDatabase::kNoError) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ProcessPendingReport(report);
|
|
|
|
|
|
|
|
|
|
// Respect Stop() being called after at least one attempt to process a
|
|
|
|
|
// report.
|
|
|
|
|
if (!thread_.is_running()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Known pending reports are always processed (above). The rest of this
|
|
|
|
|
// function is concerned with scanning for pending reports not already known
|
|
|
|
|
// to this thread.
|
2017-09-28 09:53:39 -07:00
|
|
|
|
if (!options_.watch_pending_reports) {
|
2017-04-14 14:50:11 -04:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-06 18:43:28 -05:00
|
|
|
|
std::vector<CrashReportDatabase::Report> reports;
|
2015-02-12 15:03:59 -05:00
|
|
|
|
if (database_->GetPendingReports(&reports) != CrashReportDatabase::kNoError) {
|
|
|
|
|
// The database is sick. It might be prudent to stop trying to poke it from
|
|
|
|
|
// this thread by abandoning the thread altogether. On the other hand, if
|
|
|
|
|
// the problem is transient, it might be possible to talk to it again on the
|
|
|
|
|
// next pass. For now, take the latter approach.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const CrashReportDatabase::Report& report : reports) {
|
2017-04-14 14:50:11 -04:00
|
|
|
|
if (std::find(known_report_uuids.begin(),
|
|
|
|
|
known_report_uuids.end(),
|
|
|
|
|
report.uuid) != known_report_uuids.end()) {
|
|
|
|
|
// An attempt to process the report already occurred above. The report is
|
|
|
|
|
// still pending, so upload must have failed. Don’t retry it immediately,
|
|
|
|
|
// it can wait until at least the next pass through this method.
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-12 15:03:59 -05:00
|
|
|
|
ProcessPendingReport(report);
|
|
|
|
|
|
|
|
|
|
// Respect Stop() being called after at least one attempt to process a
|
|
|
|
|
// report.
|
2016-01-04 17:10:58 -05:00
|
|
|
|
if (!thread_.is_running()) {
|
2015-02-12 15:03:59 -05:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CrashReportUploadThread::ProcessPendingReport(
|
|
|
|
|
const CrashReportDatabase::Report& report) {
|
2017-04-18 11:50:39 -04:00
|
|
|
|
#if defined(OS_MACOSX)
|
|
|
|
|
RecordFileLimitAnnotation();
|
|
|
|
|
#endif // OS_MACOSX
|
|
|
|
|
|
2015-03-10 15:31:25 -04:00
|
|
|
|
Settings* const settings = database_->GetSettings();
|
|
|
|
|
|
|
|
|
|
bool uploads_enabled;
|
2018-02-20 10:55:17 -08:00
|
|
|
|
if (!report.upload_explicitly_requested &&
|
|
|
|
|
(!settings->GetUploadsEnabled(&uploads_enabled) || !uploads_enabled)) {
|
2016-08-24 15:37:46 -04:00
|
|
|
|
// Don’t attempt an upload if there’s no URL to upload to. Allow upload if
|
|
|
|
|
// it has been explicitly requested by the user, otherwise, respect the
|
|
|
|
|
// upload-enabled state stored in the database’s settings.
|
2016-09-26 15:06:19 -07:00
|
|
|
|
database_->SkipReportUpload(report.uuid,
|
|
|
|
|
Metrics::CrashSkippedReason::kUploadsDisabled);
|
2015-03-10 15:31:25 -04:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This currently implements very simplistic rate-limiting, compatible with
|
|
|
|
|
// the Breakpad client, where the strategy is to permit one upload attempt per
|
|
|
|
|
// hour, and retire reports that would exceed this limit or for which the
|
|
|
|
|
// upload fails on the first attempt.
|
|
|
|
|
//
|
2016-10-31 14:16:12 -07:00
|
|
|
|
// If upload was requested explicitly (i.e. by user action), we do not
|
|
|
|
|
// throttle the upload.
|
|
|
|
|
//
|
2015-03-10 15:31:25 -04:00
|
|
|
|
// TODO(mark): Provide a proper rate-limiting strategy and allow for failed
|
|
|
|
|
// upload attempts to be retried.
|
2017-09-28 09:53:39 -07:00
|
|
|
|
if (!report.upload_explicitly_requested && options_.rate_limit) {
|
2016-01-06 09:59:54 -08:00
|
|
|
|
time_t last_upload_attempt_time;
|
|
|
|
|
if (settings->GetLastUploadAttemptTime(&last_upload_attempt_time)) {
|
|
|
|
|
time_t now = time(nullptr);
|
|
|
|
|
if (now >= last_upload_attempt_time) {
|
|
|
|
|
// If the most recent upload attempt occurred within the past hour,
|
|
|
|
|
// don’t attempt to upload the new report. If it happened longer ago,
|
|
|
|
|
// attempt to upload the report.
|
2017-07-25 19:15:48 -04:00
|
|
|
|
constexpr int kUploadAttemptIntervalSeconds = 60 * 60; // 1 hour
|
2016-01-06 09:59:54 -08:00
|
|
|
|
if (now - last_upload_attempt_time < kUploadAttemptIntervalSeconds) {
|
2016-09-26 15:06:19 -07:00
|
|
|
|
database_->SkipReportUpload(
|
|
|
|
|
report.uuid, Metrics::CrashSkippedReason::kUploadThrottled);
|
2016-01-06 09:59:54 -08:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// The most recent upload attempt purportedly occurred in the future. If
|
|
|
|
|
// it “happened” at least one day in the future, assume that the last
|
|
|
|
|
// upload attempt time is bogus, and attempt to upload the report. If
|
|
|
|
|
// the most recent upload time is in the future but within one day,
|
|
|
|
|
// accept it and don’t attempt to upload the report.
|
2017-07-25 19:15:48 -04:00
|
|
|
|
constexpr int kBackwardsClockTolerance = 60 * 60 * 24; // 1 day
|
2016-01-06 09:59:54 -08:00
|
|
|
|
if (last_upload_attempt_time - now < kBackwardsClockTolerance) {
|
2016-09-26 15:06:19 -07:00
|
|
|
|
database_->SkipReportUpload(
|
|
|
|
|
report.uuid, Metrics::CrashSkippedReason::kUnexpectedTime);
|
2016-01-06 09:59:54 -08:00
|
|
|
|
return;
|
|
|
|
|
}
|
2015-03-10 15:31:25 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-03-05 15:40:47 -05:00
|
|
|
|
|
2018-02-15 08:17:12 -08:00
|
|
|
|
std::unique_ptr<const CrashReportDatabase::UploadReport> upload_report;
|
2015-03-05 15:40:47 -05:00
|
|
|
|
CrashReportDatabase::OperationStatus status =
|
|
|
|
|
database_->GetReportForUploading(report.uuid, &upload_report);
|
|
|
|
|
switch (status) {
|
|
|
|
|
case CrashReportDatabase::kNoError:
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case CrashReportDatabase::kBusyError:
|
2017-04-13 10:12:28 -04:00
|
|
|
|
case CrashReportDatabase::kReportNotFound:
|
|
|
|
|
// Someone else may have gotten to it first. If they’re working on it now,
|
|
|
|
|
// this will be kBusyError. If they’ve already finished with it, it’ll be
|
|
|
|
|
// kReportNotFound.
|
2015-03-05 15:40:47 -05:00
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
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.
|
2016-09-26 15:06:19 -07:00
|
|
|
|
database_->SkipReportUpload(report.uuid,
|
|
|
|
|
Metrics::CrashSkippedReason::kDatabaseError);
|
2015-03-05 15:40:47 -05:00
|
|
|
|
return;
|
2016-08-24 15:37:46 -04:00
|
|
|
|
|
|
|
|
|
case CrashReportDatabase::kCannotRequestUpload:
|
|
|
|
|
NOTREACHED();
|
|
|
|
|
return;
|
2015-03-05 15:40:47 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string response_body;
|
2018-02-15 08:17:12 -08:00
|
|
|
|
UploadResult upload_result =
|
|
|
|
|
UploadReport(upload_report.get(), &response_body);
|
2015-03-05 15:40:47 -05:00
|
|
|
|
switch (upload_result) {
|
|
|
|
|
case UploadResult::kSuccess:
|
2018-02-15 08:17:12 -08:00
|
|
|
|
database_->RecordUploadComplete(std::move(upload_report), response_body);
|
2015-03-05 15:40:47 -05:00
|
|
|
|
break;
|
|
|
|
|
case UploadResult::kPermanentFailure:
|
2018-07-24 13:09:38 -07:00
|
|
|
|
upload_report.reset();
|
|
|
|
|
database_->SkipReportUpload(
|
|
|
|
|
report.uuid, Metrics::CrashSkippedReason::kPrepareForUploadFailed);
|
|
|
|
|
break;
|
2015-03-05 15:40:47 -05:00
|
|
|
|
case UploadResult::kRetry:
|
2018-02-15 08:17:12 -08:00
|
|
|
|
upload_report.reset();
|
2015-03-05 15:40:47 -05:00
|
|
|
|
|
|
|
|
|
// 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.
|
2016-09-26 15:06:19 -07:00
|
|
|
|
database_->SkipReportUpload(report.uuid,
|
|
|
|
|
Metrics::CrashSkippedReason::kUploadFailed);
|
2015-03-05 15:40:47 -05:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CrashReportUploadThread::UploadResult CrashReportUploadThread::UploadReport(
|
2018-02-15 08:17:12 -08:00
|
|
|
|
const CrashReportDatabase::UploadReport* report,
|
2015-03-05 15:40:47 -05:00
|
|
|
|
std::string* response_body) {
|
|
|
|
|
std::map<std::string, std::string> parameters;
|
|
|
|
|
|
2018-02-15 08:17:12 -08:00
|
|
|
|
FileReader* reader = report->Reader();
|
|
|
|
|
FileOffset start_offset = reader->SeekGet();
|
2017-10-17 08:50:58 -07:00
|
|
|
|
if (start_offset < 0) {
|
|
|
|
|
return UploadResult::kPermanentFailure;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ignore any errors that might occur when attempting to interpret the
|
|
|
|
|
// minidump file. 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.
|
2017-11-02 11:55:44 -04:00
|
|
|
|
ProcessSnapshotMinidump minidump_process_snapshot;
|
2018-02-15 08:17:12 -08:00
|
|
|
|
if (minidump_process_snapshot.Initialize(reader)) {
|
2017-11-02 11:55:44 -04:00
|
|
|
|
parameters =
|
|
|
|
|
BreakpadHTTPFormParametersFromMinidump(&minidump_process_snapshot);
|
|
|
|
|
}
|
2015-03-05 15:40:47 -05:00
|
|
|
|
|
2018-02-15 08:17:12 -08:00
|
|
|
|
if (!reader->SeekSet(start_offset)) {
|
2017-10-17 08:50:58 -07:00
|
|
|
|
return UploadResult::kPermanentFailure;
|
2015-03-05 15:40:47 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HTTPMultipartBuilder http_multipart_builder;
|
2017-09-28 09:53:39 -07:00
|
|
|
|
http_multipart_builder.SetGzipEnabled(options_.upload_gzip);
|
2015-03-05 15:40:47 -05:00
|
|
|
|
|
2017-07-25 13:34:04 -04:00
|
|
|
|
static constexpr char kMinidumpKey[] = "upload_file_minidump";
|
2015-03-05 15:40:47 -05:00
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-18 15:53:54 -07:00
|
|
|
|
for (const auto& it : report->GetAttachments()) {
|
|
|
|
|
http_multipart_builder.SetFileAttachment(
|
|
|
|
|
it.first, it.first, it.second, "application/octet-stream");
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-15 08:17:12 -08:00
|
|
|
|
http_multipart_builder.SetFileAttachment(kMinidumpKey,
|
|
|
|
|
report->uuid.ToString() + ".dmp",
|
|
|
|
|
reader,
|
|
|
|
|
"application/octet-stream");
|
2015-03-05 15:40:47 -05:00
|
|
|
|
|
2016-04-25 12:13:07 -07:00
|
|
|
|
std::unique_ptr<HTTPTransport> http_transport(HTTPTransport::Create());
|
2017-02-15 19:54:19 -05:00
|
|
|
|
HTTPHeaders content_headers;
|
|
|
|
|
http_multipart_builder.PopulateContentHeaders(&content_headers);
|
|
|
|
|
for (const auto& content_header : content_headers) {
|
|
|
|
|
http_transport->SetHeader(content_header.first, content_header.second);
|
|
|
|
|
}
|
2015-11-30 14:20:54 -08:00
|
|
|
|
http_transport->SetBodyStream(http_multipart_builder.GetBodyStream());
|
2015-03-12 18:51:00 -04:00
|
|
|
|
// TODO(mark): The timeout should be configurable by the client.
|
|
|
|
|
http_transport->SetTimeout(60.0); // 1 minute.
|
2015-03-05 15:40:47 -05:00
|
|
|
|
|
2017-09-28 09:53:39 -07:00
|
|
|
|
std::string url = url_;
|
|
|
|
|
if (options_.identify_client_via_url) {
|
|
|
|
|
// Add parameters to the URL which identify the client to the server.
|
|
|
|
|
static constexpr struct {
|
|
|
|
|
const char* key;
|
|
|
|
|
const char* url_field_name;
|
|
|
|
|
} kURLParameterMappings[] = {
|
|
|
|
|
{"prod", "product"},
|
|
|
|
|
{"ver", "version"},
|
|
|
|
|
{"guid", "guid"},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (const auto& parameter_mapping : kURLParameterMappings) {
|
|
|
|
|
const auto it = parameters.find(parameter_mapping.key);
|
|
|
|
|
if (it != parameters.end()) {
|
|
|
|
|
url.append(
|
|
|
|
|
base::StringPrintf("%c%s=%s",
|
|
|
|
|
url.find('?') == std::string::npos ? '?' : '&',
|
|
|
|
|
parameter_mapping.url_field_name,
|
|
|
|
|
URLEncode(it->second).c_str()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
http_transport->SetURL(url);
|
|
|
|
|
|
2015-03-05 15:40:47 -05:00
|
|
|
|
if (!http_transport->ExecuteSynchronously(response_body)) {
|
|
|
|
|
return UploadResult::kRetry;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return UploadResult::kSuccess;
|
2015-02-12 15:03:59 -05:00
|
|
|
|
}
|
|
|
|
|
|
2016-01-04 17:10:58 -05:00
|
|
|
|
void CrashReportUploadThread::DoWork(const WorkerThread* thread) {
|
|
|
|
|
ProcessPendingReports();
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-12 15:03:59 -05:00
|
|
|
|
} // namespace crashpad
|