1 : // Copyright 2014 Google Inc. 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 "syzygy/kasko/reporter.h"
16 :
17 : #include <stdint.h>
18 :
19 : #include <map>
20 : #include <memory>
21 : #include <string>
22 :
23 : #include "base/bind.h"
24 : #include "base/bind_helpers.h"
25 : #include "base/callback.h"
26 : #include "base/logging.h"
27 : #include "base/files/file_util.h"
28 : #include "base/memory/ptr_util.h"
29 : #include "base/process/process.h"
30 : #include "base/strings/utf_string_conversions.h"
31 : #include "base/threading/platform_thread.h"
32 : #include "base/time/time.h"
33 : #include "syzygy/kasko/http_agent_impl.h"
34 : #include "syzygy/kasko/minidump.h"
35 : #include "syzygy/kasko/minidump_request.h"
36 : #include "syzygy/kasko/service.h"
37 : #include "syzygy/kasko/upload.h"
38 : #include "syzygy/kasko/upload_thread.h"
39 : #include "syzygy/kasko/version.h"
40 : #include "syzygy/kasko/waitable_timer.h"
41 : #include "syzygy/kasko/waitable_timer_impl.h"
42 :
43 m : namespace kasko {
44 :
45 m : namespace {
46 :
47 : // The RPC protocol used for receiving dump requests.
48 m : const base::char16* const kRpcProtocol = L"ncalrpc";
49 :
50 : // The subdirectory where minidumps are generated.
51 m : const base::char16* const kTemporarySubdir = L"Temporary";
52 :
53 : // Moves |minidump_path| and |crash_keys_path| to |permanent_failure_directory|.
54 : // The destination filenames have the filename from |minidump_path| and the
55 : // extensions Reporter::kPermanentFailureMinidumpExtension and
56 : // Reporter::kPermanentFailureCrashKeysExtension.
57 m : void HandlePermanentFailure(const base::FilePath& permanent_failure_directory,
58 m : const base::FilePath& minidump_path,
59 m : const base::FilePath& crash_keys_path) {
60 m : base::FilePath minidump_target = permanent_failure_directory.Append(
61 m : minidump_path.BaseName().ReplaceExtension(
62 m : Reporter::kPermanentFailureMinidumpExtension));
63 :
64 : // Note that we take the filename from the minidump file, in order to
65 : // guarantee that the two files have matching names.
66 m : base::FilePath crash_keys_target = permanent_failure_directory.Append(
67 m : minidump_path.BaseName().ReplaceExtension(
68 m : Reporter::kPermanentFailureCrashKeysExtension));
69 :
70 m : if (!base::CreateDirectory(permanent_failure_directory)) {
71 m : LOG(ERROR) << "Failed to create directory at "
72 m : << permanent_failure_directory.value();
73 m : } else if (!base::Move(minidump_path, minidump_target)) {
74 m : LOG(ERROR) << "Failed to move " << minidump_path.value() << " to "
75 m : << minidump_target.value();
76 m : } else if (!base::Move(crash_keys_path, crash_keys_target)) {
77 m : LOG(ERROR) << "Failed to move " << crash_keys_path.value() << " to "
78 m : << crash_keys_target.value();
79 m : }
80 m : }
81 :
82 m : void GenerateReport(const base::FilePath& temporary_directory,
83 m : ReportRepository* report_repository,
84 m : base::ProcessHandle client_process,
85 m : base::PlatformThreadId thread_id,
86 m : const MinidumpRequest& request) {
87 m : if (!base::CreateDirectory(temporary_directory)) {
88 m : LOG(ERROR) << "Failed to create dump destination directory: "
89 m : << temporary_directory.value();
90 m : return;
91 m : }
92 :
93 m : base::FilePath dump_file;
94 m : if (!base::CreateTemporaryFileInDir(temporary_directory, &dump_file)) {
95 m : LOG(ERROR) << "Failed to create a temporary dump file.";
96 m : return;
97 m : }
98 :
99 m : if (!GenerateMinidump(dump_file, client_process, thread_id, request)) {
100 m : LOG(ERROR) << "Minidump generation failed.";
101 m : base::DeleteFile(dump_file, false);
102 m : return;
103 m : }
104 :
105 m : std::map<base::string16, base::string16> crash_keys;
106 m : for (auto& crash_key : request.crash_keys) {
107 m : crash_keys[crash_key.first] = crash_key.second;
108 m : }
109 :
110 m : crash_keys[Reporter::kKaskoGeneratedByVersion] =
111 m : base::ASCIIToUTF16(KASKO_VERSION_STRING);
112 :
113 m : report_repository->StoreReport(dump_file, crash_keys);
114 m : }
115 :
116 : // Implements kasko::Service to capture minidumps and store them in a
117 : // ReportRepository.
118 m : class ServiceImpl : public Service {
119 m : public:
120 m : ServiceImpl(const base::FilePath& temporary_directory,
121 m : ReportRepository* report_repository,
122 m : UploadThread* upload_thread)
123 m : : temporary_directory_(temporary_directory),
124 m : report_repository_(report_repository),
125 m : upload_thread_(upload_thread) {}
126 :
127 m : ~ServiceImpl() override {}
128 :
129 : // Service implementation.
130 m : void SendDiagnosticReport(base::ProcessId client_process_id,
131 m : base::PlatformThreadId thread_id,
132 m : const MinidumpRequest& request) override {
133 m : base::win::ScopedHandle client_process(
134 m : ::OpenProcess(GetRequiredAccessForMinidumpType(request.type), FALSE,
135 m : client_process_id));
136 m : if (client_process.IsValid()) {
137 m : GenerateReport(temporary_directory_, report_repository_,
138 m : client_process.Get(), thread_id, request);
139 m : }
140 m : upload_thread_->UploadOneNowAsync();
141 m : }
142 :
143 m : private:
144 m : base::FilePath temporary_directory_;
145 m : ReportRepository* report_repository_;
146 m : UploadThread* upload_thread_;
147 :
148 m : DISALLOW_COPY_AND_ASSIGN(ServiceImpl);
149 m : };
150 :
151 m : } // namespace
152 :
153 m : const base::char16* const Reporter::kPermanentFailureCrashKeysExtension =
154 m : L".kys";
155 m : const base::char16* const Reporter::kPermanentFailureMinidumpExtension =
156 m : L".dmp";
157 m : const base::char16* const Reporter::kMinidumpUploadFilePart =
158 m : L"upload_file_minidump";
159 m : const base::char16* const Reporter::kKaskoGeneratedByVersion =
160 m : L"kasko-generated-by-version";
161 m : const base::char16* const Reporter::kKaskoUploadedByVersion =
162 m : L"kasko-uploaded-by-version";
163 :
164 : // static
165 m : std::unique_ptr<Reporter> Reporter::Create(
166 m : const base::string16& endpoint_name,
167 m : const base::string16& url,
168 m : const base::FilePath& data_directory,
169 m : const base::FilePath& permanent_failure_directory,
170 m : const base::TimeDelta& upload_interval,
171 m : const base::TimeDelta& retry_interval,
172 m : const OnUploadCallback& on_upload_callback) {
173 m : std::unique_ptr<WaitableTimer> waitable_timer(
174 m : WaitableTimerImpl::Create(upload_interval));
175 m : if (!waitable_timer) {
176 m : LOG(ERROR) << "Failed to create a timer for the upload process.";
177 m : return std::unique_ptr<Reporter>();
178 m : }
179 :
180 m : std::unique_ptr<ReportRepository> report_repository(new ReportRepository(
181 m : data_directory, retry_interval, base::Bind(&base::Time::Now),
182 m : base::Bind(&UploadCrashReport, on_upload_callback, url),
183 m : base::Bind(&HandlePermanentFailure, permanent_failure_directory)));
184 :
185 : // It's safe to pass an Unretained reference to |report_repository| because
186 : // the Reporter instance will shut down |upload_thread| before destroying
187 : // |report_repository|.
188 m : std::unique_ptr<UploadThread> upload_thread = UploadThread::Create(
189 m : data_directory, std::move(waitable_timer),
190 m : base::Bind(base::IgnoreResult(&ReportRepository::UploadPendingReport),
191 m : base::Unretained(report_repository.get())));
192 :
193 m : if (!upload_thread) {
194 m : LOG(ERROR) << "Failed to initialize background upload process.";
195 m : return std::unique_ptr<Reporter>();
196 m : }
197 :
198 m : std::unique_ptr<Reporter> instance(
199 m : new Reporter(std::move(report_repository), std::move(upload_thread),
200 m : endpoint_name, data_directory.Append(kTemporarySubdir)));
201 m : if (!instance->service_bridge_.Run()) {
202 m : LOG(ERROR) << "Failed to start the Kasko RPC service using protocol "
203 m : << kRpcProtocol << " and endpoint name " << endpoint_name << ".";
204 m : return std::unique_ptr<Reporter>();
205 m : }
206 :
207 m : instance->upload_thread_->Start();
208 :
209 m : return std::move(instance);
210 m : }
211 :
212 m : Reporter::~Reporter() {}
213 :
214 m : void Reporter::SendReportForProcess(base::ProcessHandle process_handle,
215 m : base::PlatformThreadId thread_id,
216 m : MinidumpRequest request) {
217 m : GenerateReport(temporary_minidump_directory_, report_repository_.get(),
218 m : process_handle, thread_id, request);
219 m : upload_thread_->UploadOneNowAsync();
220 m : }
221 :
222 : // static
223 m : void Reporter::Shutdown(std::unique_ptr<Reporter> instance) {
224 m : instance->upload_thread_->Stop(); // Non-blocking.
225 m : instance->service_bridge_.Stop(); // Blocking.
226 m : instance->upload_thread_->Join(); // Blocking.
227 m : }
228 :
229 : // static
230 m : bool Reporter::UploadCrashReport(
231 m : const Reporter::OnUploadCallback& on_upload_callback,
232 m : const base::string16& upload_url,
233 m : const base::FilePath& minidump_path,
234 m : const std::map<base::string16, base::string16>& crash_keys) {
235 m : std::string dump_contents;
236 m : if (!base::ReadFileToString(minidump_path, &dump_contents)) {
237 m : LOG(ERROR) << "Failed to read the minidump file at "
238 m : << minidump_path.value();
239 m : return false;
240 m : }
241 :
242 m : HttpAgentImpl http_agent(
243 m : L"Kasko", base::ASCIIToUTF16(KASKO_VERSION_STRING));
244 m : base::string16 remote_dump_id;
245 m : uint16_t response_code = 0;
246 m : std::map<base::string16, base::string16> augmented_crash_keys(crash_keys);
247 m : augmented_crash_keys[Reporter::kKaskoUploadedByVersion] =
248 m : base::ASCIIToUTF16(KASKO_VERSION_STRING);
249 m : if (!SendHttpUpload(&http_agent, upload_url, augmented_crash_keys,
250 m : dump_contents, Reporter::kMinidumpUploadFilePart,
251 m : &remote_dump_id, &response_code)) {
252 m : LOG(ERROR) << "Failed to upload the minidump file to " << upload_url;
253 m : return false;
254 m : } else if (!on_upload_callback.is_null()) {
255 m : on_upload_callback.Run(remote_dump_id, minidump_path, crash_keys);
256 m : }
257 :
258 m : return true;
259 m : }
260 :
261 m : Reporter::Reporter(std::unique_ptr<ReportRepository> report_repository,
262 m : std::unique_ptr<UploadThread> upload_thread,
263 m : const base::string16& endpoint_name,
264 m : const base::FilePath& temporary_minidump_directory)
265 m : : report_repository_(std::move(report_repository)),
266 m : upload_thread_(std::move(upload_thread)),
267 m : temporary_minidump_directory_(temporary_minidump_directory),
268 m : service_bridge_(
269 m : kRpcProtocol,
270 m : endpoint_name,
271 m : base::WrapUnique(new ServiceImpl(temporary_minidump_directory_,
272 m : report_repository_.get(),
273 m : upload_thread_.get()))) {
274 m : }
275 :
276 m : } // namespace kasko
|