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