[Session restore] Rename group name Enabled to Restore.
[chromium-blink-merge.git] / components / crash / tools / crash_service.cc
blob23d5ec66bc1de198d557638717aa6183c93974a2
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 "components/crash/tools/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/files/file_util.h"
15 #include "base/logging.h"
16 #include "base/win/windows_version.h"
17 #include "breakpad/src/client/windows/crash_generation/client_info.h"
18 #include "breakpad/src/client/windows/crash_generation/crash_generation_server.h"
19 #include "breakpad/src/client/windows/sender/crash_report_sender.h"
21 namespace breakpad {
23 namespace {
25 const wchar_t kTestPipeName[] = L"\\\\.\\pipe\\ChromeCrashServices";
27 const wchar_t kCrashReportURL[] = L"https://clients2.google.com/cr/report";
28 const wchar_t kCheckPointFile[] = L"crash_checkpoint.txt";
30 typedef std::map<std::wstring, std::wstring> CrashMap;
32 bool CustomInfoToMap(const google_breakpad::ClientInfo* client_info,
33 const std::wstring& reporter_tag, CrashMap* map) {
34 google_breakpad::CustomClientInfo info = client_info->GetCustomInfo();
36 for (uintptr_t i = 0; i < info.count; ++i) {
37 (*map)[info.entries[i].name] = info.entries[i].value;
40 (*map)[L"rept"] = reporter_tag;
42 return !map->empty();
45 bool WriteCustomInfoToFile(const std::wstring& dump_path, const CrashMap& map) {
46 std::wstring file_path(dump_path);
47 size_t last_dot = file_path.rfind(L'.');
48 if (last_dot == std::wstring::npos)
49 return false;
50 file_path.resize(last_dot);
51 file_path += L".txt";
53 std::wofstream file(file_path.c_str(),
54 std::ios_base::out | std::ios_base::app | std::ios::binary);
55 if (!file.is_open())
56 return false;
58 CrashMap::const_iterator pos;
59 for (pos = map.begin(); pos != map.end(); ++pos) {
60 std::wstring line = pos->first;
61 line += L':';
62 line += pos->second;
63 line += L'\n';
64 file.write(line.c_str(), static_cast<std::streamsize>(line.length()));
66 return true;
69 // The window procedure task is to handle when a) the user logs off.
70 // b) the system shuts down or c) when the user closes the window.
71 LRESULT __stdcall CrashSvcWndProc(HWND hwnd, UINT message,
72 WPARAM wparam, LPARAM lparam) {
73 switch (message) {
74 case WM_CLOSE:
75 case WM_ENDSESSION:
76 case WM_DESTROY:
77 PostQuitMessage(0);
78 break;
79 default:
80 return DefWindowProc(hwnd, message, wparam, lparam);
82 return 0;
85 // This is the main and only application window.
86 HWND g_top_window = NULL;
88 bool CreateTopWindow(HINSTANCE instance, bool visible) {
89 WNDCLASSEXW wcx = {0};
90 wcx.cbSize = sizeof(wcx);
91 wcx.style = CS_HREDRAW | CS_VREDRAW;
92 wcx.lpfnWndProc = CrashSvcWndProc;
93 wcx.hInstance = instance;
94 wcx.lpszClassName = L"crash_svc_class";
95 ::RegisterClassExW(&wcx);
96 DWORD style = visible ? WS_POPUPWINDOW | WS_VISIBLE : WS_OVERLAPPED;
98 // The window size is zero but being a popup window still shows in the
99 // task bar and can be closed using the system menu or using task manager.
100 HWND window = CreateWindowExW(0, wcx.lpszClassName, L"crash service", style,
101 CW_USEDEFAULT, CW_USEDEFAULT, 0, 0,
102 NULL, NULL, instance, NULL);
103 if (!window)
104 return false;
106 ::UpdateWindow(window);
107 VLOG(1) << "window handle is " << window;
108 g_top_window = window;
109 return true;
112 // Simple helper class to keep the process alive until the current request
113 // finishes.
114 class ProcessingLock {
115 public:
116 ProcessingLock() {
117 ::InterlockedIncrement(&op_count_);
119 ~ProcessingLock() {
120 ::InterlockedDecrement(&op_count_);
122 static bool IsWorking() {
123 return (op_count_ != 0);
125 private:
126 static volatile LONG op_count_;
129 volatile LONG ProcessingLock::op_count_ = 0;
131 // This structure contains the information that the worker thread needs to
132 // send a crash dump to the server.
133 struct DumpJobInfo {
134 DWORD pid;
135 CrashService* self;
136 CrashMap map;
137 std::wstring dump_path;
139 DumpJobInfo(DWORD process_id, CrashService* service,
140 const CrashMap& crash_map, const std::wstring& path)
141 : pid(process_id), self(service), map(crash_map), dump_path(path) {
145 } // namespace
147 // Command line switches:
148 const char CrashService::kMaxReports[] = "max-reports";
149 const char CrashService::kNoWindow[] = "no-window";
150 const char CrashService::kReporterTag[] = "reporter";
151 const char CrashService::kDumpsDir[] = "dumps-dir";
152 const char CrashService::kPipeName[] = "pipe-name";
154 CrashService::CrashService()
155 : sender_(NULL),
156 dumper_(NULL),
157 requests_handled_(0),
158 requests_sent_(0),
159 clients_connected_(0),
160 clients_terminated_(0) {
163 CrashService::~CrashService() {
164 base::AutoLock lock(sending_);
165 delete dumper_;
166 delete sender_;
169 bool CrashService::Initialize(const base::FilePath& operating_dir,
170 const base::FilePath& dumps_path) {
171 using google_breakpad::CrashReportSender;
172 using google_breakpad::CrashGenerationServer;
174 std::wstring pipe_name = kTestPipeName;
175 int max_reports = -1;
177 // The checkpoint file allows CrashReportSender to enforce the the maximum
178 // reports per day quota. Does not seem to serve any other purpose.
179 base::FilePath checkpoint_path = operating_dir.Append(kCheckPointFile);
181 base::CommandLine& cmd_line = *base::CommandLine::ForCurrentProcess();
183 base::FilePath dumps_path_to_use = dumps_path;
185 if (cmd_line.HasSwitch(kDumpsDir)) {
186 dumps_path_to_use =
187 base::FilePath(cmd_line.GetSwitchValueNative(kDumpsDir));
190 // We can override the send reports quota with a command line switch.
191 if (cmd_line.HasSwitch(kMaxReports))
192 max_reports = _wtoi(cmd_line.GetSwitchValueNative(kMaxReports).c_str());
194 // Allow the global pipe name to be overridden for better testability.
195 if (cmd_line.HasSwitch(kPipeName))
196 pipe_name = cmd_line.GetSwitchValueNative(kPipeName);
198 #ifdef _WIN64
199 pipe_name += L"-x64";
200 #endif
202 if (max_reports > 0) {
203 // Create the http sender object.
204 sender_ = new CrashReportSender(checkpoint_path.value());
205 sender_->set_max_reports_per_day(max_reports);
208 SECURITY_ATTRIBUTES security_attributes = {0};
209 SECURITY_ATTRIBUTES* security_attributes_actual = NULL;
211 if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
212 SECURITY_DESCRIPTOR* security_descriptor =
213 reinterpret_cast<SECURITY_DESCRIPTOR*>(
214 GetSecurityDescriptorForLowIntegrity());
215 DCHECK(security_descriptor != NULL);
217 security_attributes.nLength = sizeof(security_attributes);
218 security_attributes.lpSecurityDescriptor = security_descriptor;
219 security_attributes.bInheritHandle = FALSE;
221 security_attributes_actual = &security_attributes;
224 // Create the OOP crash generator object.
225 dumper_ = new CrashGenerationServer(pipe_name, security_attributes_actual,
226 &CrashService::OnClientConnected, this,
227 &CrashService::OnClientDumpRequest, this,
228 &CrashService::OnClientExited, this,
229 NULL, NULL,
230 true, &dumps_path_to_use.value());
232 if (!dumper_) {
233 LOG(ERROR) << "could not create dumper";
234 if (security_attributes.lpSecurityDescriptor)
235 LocalFree(security_attributes.lpSecurityDescriptor);
236 return false;
239 if (!CreateTopWindow(::GetModuleHandleW(NULL),
240 !cmd_line.HasSwitch(kNoWindow))) {
241 LOG(ERROR) << "could not create window";
242 if (security_attributes.lpSecurityDescriptor)
243 LocalFree(security_attributes.lpSecurityDescriptor);
244 return false;
247 reporter_tag_ = L"crash svc";
248 if (cmd_line.HasSwitch(kReporterTag))
249 reporter_tag_ = cmd_line.GetSwitchValueNative(kReporterTag);
251 // Log basic information.
252 VLOG(1) << "pipe name is " << pipe_name
253 << "\ndumps at " << dumps_path_to_use.value();
255 if (sender_) {
256 VLOG(1) << "checkpoint is " << checkpoint_path.value()
257 << "\nserver is " << kCrashReportURL
258 << "\nmaximum " << sender_->max_reports_per_day() << " reports/day"
259 << "\nreporter is " << reporter_tag_;
261 // Start servicing clients.
262 if (!dumper_->Start()) {
263 LOG(ERROR) << "could not start dumper";
264 if (security_attributes.lpSecurityDescriptor)
265 LocalFree(security_attributes.lpSecurityDescriptor);
266 return false;
269 if (security_attributes.lpSecurityDescriptor)
270 LocalFree(security_attributes.lpSecurityDescriptor);
272 // This is throwaway code. We don't need to sync with the browser process
273 // once Google Update is updated to a version supporting OOP crash handling.
274 // Create or open an event to signal the browser process that the crash
275 // service is initialized.
276 HANDLE running_event =
277 ::CreateEventW(NULL, TRUE, TRUE, L"g_chrome_crash_svc");
278 // If the browser already had the event open, the CreateEvent call did not
279 // signal it. We need to do it manually.
280 ::SetEvent(running_event);
282 return true;
285 void CrashService::OnClientConnected(void* context,
286 const google_breakpad::ClientInfo* client_info) {
287 ProcessingLock lock;
288 VLOG(1) << "client start. pid = " << client_info->pid();
289 CrashService* self = static_cast<CrashService*>(context);
290 ::InterlockedIncrement(&self->clients_connected_);
293 void CrashService::OnClientExited(void* context,
294 const google_breakpad::ClientInfo* client_info) {
295 ProcessingLock lock;
296 VLOG(1) << "client end. pid = " << client_info->pid();
297 CrashService* self = static_cast<CrashService*>(context);
298 ::InterlockedIncrement(&self->clients_terminated_);
300 if (!self->sender_)
301 return;
303 // When we are instructed to send reports we need to exit if there are
304 // no more clients to service. The next client that runs will start us.
305 // Only chrome.exe starts crash_service with a non-zero max_reports.
306 if (self->clients_connected_ > self->clients_terminated_)
307 return;
308 if (self->sender_->max_reports_per_day() > 0) {
309 // Wait for the other thread to send crashes, if applicable. The sender
310 // thread takes the sending_ lock, so the sleep is just to give it a
311 // chance to start.
312 ::Sleep(1000);
313 base::AutoLock lock(self->sending_);
314 // Some people can restart chrome very fast, check again if we have
315 // a new client before exiting for real.
316 if (self->clients_connected_ == self->clients_terminated_) {
317 VLOG(1) << "zero clients. exiting";
318 ::PostMessage(g_top_window, WM_CLOSE, 0, 0);
323 void CrashService::OnClientDumpRequest(void* context,
324 const google_breakpad::ClientInfo* client_info,
325 const std::wstring* file_path) {
326 ProcessingLock lock;
328 if (!file_path) {
329 LOG(ERROR) << "dump with no file path";
330 return;
332 if (!client_info) {
333 LOG(ERROR) << "dump with no client info";
334 return;
337 CrashService* self = static_cast<CrashService*>(context);
338 if (!self) {
339 LOG(ERROR) << "dump with no context";
340 return;
343 CrashMap map;
344 CustomInfoToMap(client_info, self->reporter_tag_, &map);
346 // Move dump file to the directory under client breakpad dump location.
347 base::FilePath dump_location = base::FilePath(*file_path);
348 CrashMap::const_iterator it = map.find(L"breakpad-dump-location");
349 if (it != map.end()) {
350 base::FilePath alternate_dump_location = base::FilePath(it->second);
351 base::CreateDirectoryW(alternate_dump_location);
352 alternate_dump_location = alternate_dump_location.Append(
353 dump_location.BaseName());
354 base::Move(dump_location, alternate_dump_location);
355 dump_location = alternate_dump_location;
358 DWORD pid = client_info->pid();
359 VLOG(1) << "dump for pid = " << pid << " is " << dump_location.value();
361 if (!WriteCustomInfoToFile(dump_location.value(), map)) {
362 LOG(ERROR) << "could not write custom info file";
365 if (!self->sender_)
366 return;
368 // Send the crash dump using a worker thread. This operation has retry
369 // logic in case there is no internet connection at the time.
370 DumpJobInfo* dump_job = new DumpJobInfo(pid, self, map,
371 dump_location.value());
372 if (!::QueueUserWorkItem(&CrashService::AsyncSendDump,
373 dump_job, WT_EXECUTELONGFUNCTION)) {
374 LOG(ERROR) << "could not queue job";
378 // We are going to try sending the report several times. If we can't send,
379 // we sleep from one minute to several hours depending on the retry round.
380 unsigned long CrashService::AsyncSendDump(void* context) {
381 if (!context)
382 return 0;
384 DumpJobInfo* info = static_cast<DumpJobInfo*>(context);
386 std::wstring report_id = L"<unsent>";
388 const DWORD kOneMinute = 60*1000;
389 const DWORD kOneHour = 60*kOneMinute;
391 const DWORD kSleepSchedule[] = {
392 24*kOneHour,
393 8*kOneHour,
394 4*kOneHour,
395 kOneHour,
396 15*kOneMinute,
399 int retry_round = arraysize(kSleepSchedule) - 1;
401 do {
402 ::Sleep(kSleepSchedule[retry_round]);
404 // Take the server lock while sending. This also prevent early
405 // termination of the service object.
406 base::AutoLock lock(info->self->sending_);
407 VLOG(1) << "trying to send report for pid = " << info->pid;
408 google_breakpad::ReportResult send_result
409 = info->self->sender_->SendCrashReport(kCrashReportURL, info->map,
410 info->dump_path, &report_id);
411 switch (send_result) {
412 case google_breakpad::RESULT_FAILED:
413 report_id = L"<network issue>";
414 break;
415 case google_breakpad::RESULT_REJECTED:
416 report_id = L"<rejected>";
417 ++info->self->requests_handled_;
418 retry_round = 0;
419 break;
420 case google_breakpad::RESULT_SUCCEEDED:
421 ++info->self->requests_sent_;
422 ++info->self->requests_handled_;
423 retry_round = 0;
424 break;
425 case google_breakpad::RESULT_THROTTLED:
426 report_id = L"<throttled>";
427 break;
428 default:
429 report_id = L"<unknown>";
430 break;
434 VLOG(1) << "dump for pid =" << info->pid << " crash2 id =" << report_id;
435 --retry_round;
436 } while (retry_round >= 0);
438 if (!::DeleteFileW(info->dump_path.c_str()))
439 LOG(WARNING) << "could not delete " << info->dump_path;
441 delete info;
442 return 0;
445 int CrashService::ProcessingLoop() {
446 MSG msg;
447 while (GetMessage(&msg, NULL, 0, 0)) {
448 TranslateMessage(&msg);
449 DispatchMessage(&msg);
452 VLOG(1) << "session ending..";
453 while (ProcessingLock::IsWorking()) {
454 ::Sleep(50);
457 VLOG(1) << "clients connected :" << clients_connected_
458 << "\nclients terminated :" << clients_terminated_
459 << "\ndumps serviced :" << requests_handled_
460 << "\ndumps reported :" << requests_sent_;
462 return static_cast<int>(msg.wParam);
465 PSECURITY_DESCRIPTOR CrashService::GetSecurityDescriptorForLowIntegrity() {
466 // Build the SDDL string for the label.
467 std::wstring sddl = L"S:(ML;;NW;;;S-1-16-4096)";
469 PSECURITY_DESCRIPTOR sec_desc = NULL;
471 PACL sacl = NULL;
472 BOOL sacl_present = FALSE;
473 BOOL sacl_defaulted = FALSE;
475 if (::ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl.c_str(),
476 SDDL_REVISION,
477 &sec_desc, NULL)) {
478 if (::GetSecurityDescriptorSacl(sec_desc, &sacl_present, &sacl,
479 &sacl_defaulted)) {
480 return sec_desc;
484 return NULL;
487 } // namespace breakpad