Respond with QuotaExceededError when IndexedDB has no disk space on open.
[chromium-blink-merge.git] / chrome / tools / crash_service / crash_service.cc
blob63a674b4763269c7a75d957a9103755766237691
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/tools/crash_service/crash_service.h"
7 #include <windows.h>
9 #include <sddl.h>
10 #include <fstream>
11 #include <map>
13 #include "base/command_line.h"
14 #include "base/file_util.h"
15 #include "base/logging.h"
16 #include "base/path_service.h"
17 #include "base/win/windows_version.h"
18 #include "breakpad/src/client/windows/crash_generation/client_info.h"
19 #include "breakpad/src/client/windows/crash_generation/crash_generation_server.h"
20 #include "breakpad/src/client/windows/sender/crash_report_sender.h"
21 #include "chrome/common/chrome_constants.h"
22 #include "chrome/common/chrome_paths.h"
24 namespace {
26 const wchar_t kTestPipeName[] = L"\\\\.\\pipe\\ChromeCrashServices";
28 const wchar_t kCrashReportURL[] = L"https://clients2.google.com/cr/report";
29 const wchar_t kCheckPointFile[] = L"crash_checkpoint.txt";
31 typedef std::map<std::wstring, std::wstring> CrashMap;
33 bool CustomInfoToMap(const google_breakpad::ClientInfo* client_info,
34 const std::wstring& reporter_tag, CrashMap* map) {
35 google_breakpad::CustomClientInfo info = client_info->GetCustomInfo();
37 for (uintptr_t i = 0; i < info.count; ++i) {
38 (*map)[info.entries[i].name] = info.entries[i].value;
41 (*map)[L"rept"] = reporter_tag;
43 return !map->empty();
46 bool WriteCustomInfoToFile(const std::wstring& dump_path, const CrashMap& map) {
47 std::wstring file_path(dump_path);
48 size_t last_dot = file_path.rfind(L'.');
49 if (last_dot == std::wstring::npos)
50 return false;
51 file_path.resize(last_dot);
52 file_path += L".txt";
54 std::wofstream file(file_path.c_str(),
55 std::ios_base::out | std::ios_base::app | std::ios::binary);
56 if (!file.is_open())
57 return false;
59 CrashMap::const_iterator pos;
60 for (pos = map.begin(); pos != map.end(); ++pos) {
61 std::wstring line = pos->first;
62 line += L':';
63 line += pos->second;
64 line += L'\n';
65 file.write(line.c_str(), static_cast<std::streamsize>(line.length()));
67 return true;
70 // The window procedure task is to handle when a) the user logs off.
71 // b) the system shuts down or c) when the user closes the window.
72 LRESULT __stdcall CrashSvcWndProc(HWND hwnd, UINT message,
73 WPARAM wparam, LPARAM lparam) {
74 switch (message) {
75 case WM_CLOSE:
76 case WM_ENDSESSION:
77 case WM_DESTROY:
78 PostQuitMessage(0);
79 break;
80 default:
81 return DefWindowProc(hwnd, message, wparam, lparam);
83 return 0;
86 // This is the main and only application window.
87 HWND g_top_window = NULL;
89 bool CreateTopWindow(HINSTANCE instance, bool visible) {
90 WNDCLASSEXW wcx = {0};
91 wcx.cbSize = sizeof(wcx);
92 wcx.style = CS_HREDRAW | CS_VREDRAW;
93 wcx.lpfnWndProc = CrashSvcWndProc;
94 wcx.hInstance = instance;
95 wcx.lpszClassName = L"crash_svc_class";
96 ATOM atom = ::RegisterClassExW(&wcx);
97 DWORD style = visible ? WS_POPUPWINDOW | WS_VISIBLE : WS_OVERLAPPED;
99 // The window size is zero but being a popup window still shows in the
100 // task bar and can be closed using the system menu or using task manager.
101 HWND window = CreateWindowExW(0, wcx.lpszClassName, L"crash service", style,
102 CW_USEDEFAULT, CW_USEDEFAULT, 0, 0,
103 NULL, NULL, instance, NULL);
104 if (!window)
105 return false;
107 ::UpdateWindow(window);
108 VLOG(1) << "window handle is " << window;
109 g_top_window = window;
110 return true;
113 // Simple helper class to keep the process alive until the current request
114 // finishes.
115 class ProcessingLock {
116 public:
117 ProcessingLock() {
118 ::InterlockedIncrement(&op_count_);
120 ~ProcessingLock() {
121 ::InterlockedDecrement(&op_count_);
123 static bool IsWorking() {
124 return (op_count_ != 0);
126 private:
127 static volatile LONG op_count_;
130 volatile LONG ProcessingLock::op_count_ = 0;
132 // This structure contains the information that the worker thread needs to
133 // send a crash dump to the server.
134 struct DumpJobInfo {
135 DWORD pid;
136 CrashService* self;
137 CrashMap map;
138 std::wstring dump_path;
140 DumpJobInfo(DWORD process_id, CrashService* service,
141 const CrashMap& crash_map, const std::wstring& path)
142 : pid(process_id), self(service), map(crash_map), dump_path(path) {
146 } // namespace
148 // Command line switches:
149 const char CrashService::kMaxReports[] = "max-reports";
150 const char CrashService::kNoWindow[] = "no-window";
151 const char CrashService::kReporterTag[] = "reporter";
152 const char CrashService::kDumpsDir[] = "dumps-dir";
153 const char CrashService::kPipeName[] = "pipe-name";
155 CrashService::CrashService(const std::wstring& report_dir)
156 : report_path_(report_dir),
157 sender_(NULL),
158 dumper_(NULL),
159 requests_handled_(0),
160 requests_sent_(0),
161 clients_connected_(0),
162 clients_terminated_(0) {
163 chrome::RegisterPathProvider();
166 CrashService::~CrashService() {
167 base::AutoLock lock(sending_);
168 delete dumper_;
169 delete sender_;
173 bool CrashService::Initialize(const std::wstring& command_line) {
174 using google_breakpad::CrashReportSender;
175 using google_breakpad::CrashGenerationServer;
177 std::wstring pipe_name = kTestPipeName;
178 int max_reports = -1;
180 // The checkpoint file allows CrashReportSender to enforce the the maximum
181 // reports per day quota. Does not seem to serve any other purpose.
182 base::FilePath checkpoint_path = report_path_.Append(kCheckPointFile);
184 // The dumps path is typically : '<user profile>\Local settings\
185 // Application data\Goggle\Chrome\Crash Reports' and the report path is
186 // Application data\Google\Chrome\Reported Crashes.txt
187 base::FilePath user_data_dir;
188 if (!PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)) {
189 LOG(ERROR) << "could not get DIR_USER_DATA";
190 return false;
192 report_path_ = user_data_dir.Append(chrome::kCrashReportLog);
194 CommandLine cmd_line = CommandLine::FromString(command_line);
196 base::FilePath dumps_path;
197 if (cmd_line.HasSwitch(kDumpsDir)) {
198 dumps_path = base::FilePath(cmd_line.GetSwitchValueNative(kDumpsDir));
199 } else {
200 if (!PathService::Get(chrome::DIR_CRASH_DUMPS, &dumps_path)) {
201 LOG(ERROR) << "could not get DIR_CRASH_DUMPS";
202 return false;
206 // We can override the send reports quota with a command line switch.
207 if (cmd_line.HasSwitch(kMaxReports))
208 max_reports = _wtoi(cmd_line.GetSwitchValueNative(kMaxReports).c_str());
210 // Allow the global pipe name to be overridden for better testability.
211 if (cmd_line.HasSwitch(kPipeName))
212 pipe_name = cmd_line.GetSwitchValueNative(kPipeName);
214 #ifdef _WIN64
215 pipe_name += L"-x64";
216 #endif
218 if (max_reports > 0) {
219 // Create the http sender object.
220 sender_ = new CrashReportSender(checkpoint_path.value());
221 if (!sender_) {
222 LOG(ERROR) << "could not create sender";
223 return false;
225 sender_->set_max_reports_per_day(max_reports);
228 SECURITY_ATTRIBUTES security_attributes = {0};
229 SECURITY_ATTRIBUTES* security_attributes_actual = NULL;
231 if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
232 SECURITY_DESCRIPTOR* security_descriptor =
233 reinterpret_cast<SECURITY_DESCRIPTOR*>(
234 GetSecurityDescriptorForLowIntegrity());
235 DCHECK(security_descriptor != NULL);
237 security_attributes.nLength = sizeof(security_attributes);
238 security_attributes.lpSecurityDescriptor = security_descriptor;
239 security_attributes.bInheritHandle = FALSE;
241 security_attributes_actual = &security_attributes;
244 // Create the OOP crash generator object.
245 dumper_ = new CrashGenerationServer(pipe_name, security_attributes_actual,
246 &CrashService::OnClientConnected, this,
247 &CrashService::OnClientDumpRequest, this,
248 &CrashService::OnClientExited, this,
249 NULL, NULL,
250 true, &dumps_path.value());
252 if (!dumper_) {
253 LOG(ERROR) << "could not create dumper";
254 if (security_attributes.lpSecurityDescriptor)
255 LocalFree(security_attributes.lpSecurityDescriptor);
256 return false;
259 if (!CreateTopWindow(::GetModuleHandleW(NULL),
260 !cmd_line.HasSwitch(kNoWindow))) {
261 LOG(ERROR) << "could not create window";
262 if (security_attributes.lpSecurityDescriptor)
263 LocalFree(security_attributes.lpSecurityDescriptor);
264 return false;
267 reporter_tag_ = L"crash svc";
268 if (cmd_line.HasSwitch(kReporterTag))
269 reporter_tag_ = cmd_line.GetSwitchValueNative(kReporterTag);
271 // Log basic information.
272 VLOG(1) << "pipe name is " << pipe_name
273 << "\ndumps at " << dumps_path.value()
274 << "\nreports at " << report_path_.value();
276 if (sender_) {
277 VLOG(1) << "checkpoint is " << checkpoint_path.value()
278 << "\nserver is " << kCrashReportURL
279 << "\nmaximum " << sender_->max_reports_per_day() << " reports/day"
280 << "\nreporter is " << reporter_tag_;
282 // Start servicing clients.
283 if (!dumper_->Start()) {
284 LOG(ERROR) << "could not start dumper";
285 if (security_attributes.lpSecurityDescriptor)
286 LocalFree(security_attributes.lpSecurityDescriptor);
287 return false;
290 if (security_attributes.lpSecurityDescriptor)
291 LocalFree(security_attributes.lpSecurityDescriptor);
293 // This is throwaway code. We don't need to sync with the browser process
294 // once Google Update is updated to a version supporting OOP crash handling.
295 // Create or open an event to signal the browser process that the crash
296 // service is initialized.
297 HANDLE running_event =
298 ::CreateEventW(NULL, TRUE, TRUE, L"g_chrome_crash_svc");
299 // If the browser already had the event open, the CreateEvent call did not
300 // signal it. We need to do it manually.
301 ::SetEvent(running_event);
303 return true;
306 void CrashService::OnClientConnected(void* context,
307 const google_breakpad::ClientInfo* client_info) {
308 ProcessingLock lock;
309 VLOG(1) << "client start. pid = " << client_info->pid();
310 CrashService* self = static_cast<CrashService*>(context);
311 ::InterlockedIncrement(&self->clients_connected_);
314 void CrashService::OnClientExited(void* context,
315 const google_breakpad::ClientInfo* client_info) {
316 ProcessingLock lock;
317 VLOG(1) << "client end. pid = " << client_info->pid();
318 CrashService* self = static_cast<CrashService*>(context);
319 ::InterlockedIncrement(&self->clients_terminated_);
321 if (!self->sender_)
322 return;
324 // When we are instructed to send reports we need to exit if there are
325 // no more clients to service. The next client that runs will start us.
326 // Only chrome.exe starts crash_service with a non-zero max_reports.
327 if (self->clients_connected_ > self->clients_terminated_)
328 return;
329 if (self->sender_->max_reports_per_day() > 0) {
330 // Wait for the other thread to send crashes, if applicable. The sender
331 // thread takes the sending_ lock, so the sleep is just to give it a
332 // chance to start.
333 ::Sleep(1000);
334 base::AutoLock lock(self->sending_);
335 // Some people can restart chrome very fast, check again if we have
336 // a new client before exiting for real.
337 if (self->clients_connected_ == self->clients_terminated_) {
338 VLOG(1) << "zero clients. exiting";
339 ::PostMessage(g_top_window, WM_CLOSE, 0, 0);
344 void CrashService::OnClientDumpRequest(void* context,
345 const google_breakpad::ClientInfo* client_info,
346 const std::wstring* file_path) {
347 ProcessingLock lock;
349 if (!file_path) {
350 LOG(ERROR) << "dump with no file path";
351 return;
353 if (!client_info) {
354 LOG(ERROR) << "dump with no client info";
355 return;
358 CrashService* self = static_cast<CrashService*>(context);
359 if (!self) {
360 LOG(ERROR) << "dump with no context";
361 return;
364 CrashMap map;
365 CustomInfoToMap(client_info, self->reporter_tag_, &map);
367 // Move dump file to the directory under client breakpad dump location.
368 base::FilePath dump_location = base::FilePath(*file_path);
369 CrashMap::const_iterator it = map.find(L"breakpad-dump-location");
370 if (it != map.end()) {
371 base::FilePath alternate_dump_location = base::FilePath(it->second);
372 file_util::CreateDirectoryW(alternate_dump_location);
373 alternate_dump_location = alternate_dump_location.Append(
374 dump_location.BaseName());
375 base::Move(dump_location, alternate_dump_location);
376 dump_location = alternate_dump_location;
379 DWORD pid = client_info->pid();
380 VLOG(1) << "dump for pid = " << pid << " is " << dump_location.value();
382 if (!WriteCustomInfoToFile(dump_location.value(), map)) {
383 LOG(ERROR) << "could not write custom info file";
386 if (!self->sender_)
387 return;
389 // Send the crash dump using a worker thread. This operation has retry
390 // logic in case there is no internet connection at the time.
391 DumpJobInfo* dump_job = new DumpJobInfo(pid, self, map,
392 dump_location.value());
393 if (!::QueueUserWorkItem(&CrashService::AsyncSendDump,
394 dump_job, WT_EXECUTELONGFUNCTION)) {
395 LOG(ERROR) << "could not queue job";
399 // We are going to try sending the report several times. If we can't send,
400 // we sleep from one minute to several hours depending on the retry round.
401 unsigned long CrashService::AsyncSendDump(void* context) {
402 if (!context)
403 return 0;
405 DumpJobInfo* info = static_cast<DumpJobInfo*>(context);
407 std::wstring report_id = L"<unsent>";
409 const DWORD kOneMinute = 60*1000;
410 const DWORD kOneHour = 60*kOneMinute;
412 const DWORD kSleepSchedule[] = {
413 24*kOneHour,
414 8*kOneHour,
415 4*kOneHour,
416 kOneHour,
417 15*kOneMinute,
420 int retry_round = arraysize(kSleepSchedule) - 1;
422 do {
423 ::Sleep(kSleepSchedule[retry_round]);
425 // Take the server lock while sending. This also prevent early
426 // termination of the service object.
427 base::AutoLock lock(info->self->sending_);
428 VLOG(1) << "trying to send report for pid = " << info->pid;
429 google_breakpad::ReportResult send_result
430 = info->self->sender_->SendCrashReport(kCrashReportURL, info->map,
431 info->dump_path, &report_id);
432 switch (send_result) {
433 case google_breakpad::RESULT_FAILED:
434 report_id = L"<network issue>";
435 break;
436 case google_breakpad::RESULT_REJECTED:
437 report_id = L"<rejected>";
438 ++info->self->requests_handled_;
439 retry_round = 0;
440 break;
441 case google_breakpad::RESULT_SUCCEEDED:
442 ++info->self->requests_sent_;
443 ++info->self->requests_handled_;
444 retry_round = 0;
445 break;
446 case google_breakpad::RESULT_THROTTLED:
447 report_id = L"<throttled>";
448 break;
449 default:
450 report_id = L"<unknown>";
451 break;
455 VLOG(1) << "dump for pid =" << info->pid << " crash2 id =" << report_id;
456 --retry_round;
457 } while (retry_round >= 0);
459 if (!::DeleteFileW(info->dump_path.c_str()))
460 LOG(WARNING) << "could not delete " << info->dump_path;
462 delete info;
463 return 0;
466 int CrashService::ProcessingLoop() {
467 MSG msg;
468 while (GetMessage(&msg, NULL, 0, 0)) {
469 TranslateMessage(&msg);
470 DispatchMessage(&msg);
473 VLOG(1) << "session ending..";
474 while (ProcessingLock::IsWorking()) {
475 ::Sleep(50);
478 VLOG(1) << "clients connected :" << clients_connected_
479 << "\nclients terminated :" << clients_terminated_
480 << "\ndumps serviced :" << requests_handled_
481 << "\ndumps reported :" << requests_sent_;
483 return static_cast<int>(msg.wParam);
486 PSECURITY_DESCRIPTOR CrashService::GetSecurityDescriptorForLowIntegrity() {
487 // Build the SDDL string for the label.
488 std::wstring sddl = L"S:(ML;;NW;;;S-1-16-4096)";
490 DWORD error = ERROR_SUCCESS;
491 PSECURITY_DESCRIPTOR sec_desc = NULL;
493 PACL sacl = NULL;
494 BOOL sacl_present = FALSE;
495 BOOL sacl_defaulted = FALSE;
497 if (::ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl.c_str(),
498 SDDL_REVISION,
499 &sec_desc, NULL)) {
500 if (::GetSecurityDescriptorSacl(sec_desc, &sacl_present, &sacl,
501 &sacl_defaulted)) {
502 return sec_desc;
506 return NULL;