Upstream version 10.38.222.0
[platform/framework/web/crosswalk.git] / src / components / breakpad / tools / crash_service.cc
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.
4
5 #include "components/breakpad/tools/crash_service.h"
6
7 #include <windows.h>
8
9 #include <sddl.h>
10 #include <fstream>
11 #include <map>
12
13 #include "base/command_line.h"
14 #include "base/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"
20
21 namespace breakpad {
22
23 namespace {
24
25 const wchar_t kTestPipeName[] = L"\\\\.\\pipe\\ChromeCrashServices";
26
27 const wchar_t kCrashReportURL[] = L"https://clients2.google.com/cr/report";
28 const wchar_t kCheckPointFile[] = L"crash_checkpoint.txt";
29
30 typedef std::map<std::wstring, std::wstring> CrashMap;
31
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();
35
36   for (uintptr_t i = 0; i < info.count; ++i) {
37     (*map)[info.entries[i].name] = info.entries[i].value;
38   }
39
40   (*map)[L"rept"] = reporter_tag;
41
42   return !map->empty();
43 }
44
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";
52
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;
57
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()));
65   }
66   return true;
67 }
68
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);
81   }
82   return 0;
83 }
84
85 // This is the main and only application window.
86 HWND g_top_window = NULL;
87
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   ATOM atom = ::RegisterClassExW(&wcx);
96   DWORD style = visible ? WS_POPUPWINDOW | WS_VISIBLE : WS_OVERLAPPED;
97
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;
105
106   ::UpdateWindow(window);
107   VLOG(1) << "window handle is " << window;
108   g_top_window = window;
109   return true;
110 }
111
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_);
118   }
119   ~ProcessingLock() {
120     ::InterlockedDecrement(&op_count_);
121   }
122   static bool IsWorking() {
123     return (op_count_ != 0);
124   }
125  private:
126   static volatile LONG op_count_;
127 };
128
129 volatile LONG ProcessingLock::op_count_ = 0;
130
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;
138
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) {
142   }
143 };
144
145 }  // namespace
146
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";
153
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) {
161 }
162
163 CrashService::~CrashService() {
164   base::AutoLock lock(sending_);
165   delete dumper_;
166   delete sender_;
167 }
168
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;
173
174   std::wstring pipe_name = kTestPipeName;
175   int max_reports = -1;
176
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);
180
181   CommandLine& cmd_line = *CommandLine::ForCurrentProcess();
182
183   base::FilePath dumps_path_to_use = dumps_path;
184
185   if (cmd_line.HasSwitch(kDumpsDir)) {
186     dumps_path_to_use =
187         base::FilePath(cmd_line.GetSwitchValueNative(kDumpsDir));
188   }
189
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());
193
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);
197
198 #ifdef _WIN64
199   pipe_name += L"-x64";
200 #endif
201
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);
206   }
207
208   SECURITY_ATTRIBUTES security_attributes = {0};
209   SECURITY_ATTRIBUTES* security_attributes_actual = NULL;
210
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);
216
217     security_attributes.nLength = sizeof(security_attributes);
218     security_attributes.lpSecurityDescriptor = security_descriptor;
219     security_attributes.bInheritHandle = FALSE;
220
221     security_attributes_actual = &security_attributes;
222   }
223
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());
231
232   if (!dumper_) {
233     LOG(ERROR) << "could not create dumper";
234     if (security_attributes.lpSecurityDescriptor)
235       LocalFree(security_attributes.lpSecurityDescriptor);
236     return false;
237   }
238
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;
245   }
246
247   reporter_tag_ = L"crash svc";
248   if (cmd_line.HasSwitch(kReporterTag))
249     reporter_tag_ = cmd_line.GetSwitchValueNative(kReporterTag);
250
251   // Log basic information.
252   VLOG(1) << "pipe name is " << pipe_name
253           << "\ndumps at " << dumps_path_to_use.value();
254
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_;
260   }
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;
267   }
268
269   if (security_attributes.lpSecurityDescriptor)
270     LocalFree(security_attributes.lpSecurityDescriptor);
271
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);
281
282   return true;
283 }
284
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_);
291 }
292
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_);
299
300   if (!self->sender_)
301     return;
302
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);
319     }
320   }
321 }
322
323 void CrashService::OnClientDumpRequest(void* context,
324     const google_breakpad::ClientInfo* client_info,
325     const std::wstring* file_path) {
326   ProcessingLock lock;
327
328   if (!file_path) {
329     LOG(ERROR) << "dump with no file path";
330     return;
331   }
332   if (!client_info) {
333     LOG(ERROR) << "dump with no client info";
334     return;
335   }
336
337   CrashService* self = static_cast<CrashService*>(context);
338   if (!self) {
339     LOG(ERROR) << "dump with no context";
340     return;
341   }
342
343   CrashMap map;
344   CustomInfoToMap(client_info, self->reporter_tag_, &map);
345
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;
356   }
357
358   DWORD pid = client_info->pid();
359   VLOG(1) << "dump for pid = " << pid << " is " << dump_location.value();
360
361   if (!WriteCustomInfoToFile(dump_location.value(), map)) {
362     LOG(ERROR) << "could not write custom info file";
363   }
364
365   if (!self->sender_)
366     return;
367
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";
375   }
376 }
377
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;
383
384   DumpJobInfo* info = static_cast<DumpJobInfo*>(context);
385
386   std::wstring report_id = L"<unsent>";
387
388   const DWORD kOneMinute = 60*1000;
389   const DWORD kOneHour = 60*kOneMinute;
390
391   const DWORD kSleepSchedule[] = {
392       24*kOneHour,
393       8*kOneHour,
394       4*kOneHour,
395       kOneHour,
396       15*kOneMinute,
397       0};
398
399   int retry_round = arraysize(kSleepSchedule) - 1;
400
401   do {
402     ::Sleep(kSleepSchedule[retry_round]);
403     {
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;
431       };
432     }
433
434     VLOG(1) << "dump for pid =" << info->pid << " crash2 id =" << report_id;
435     --retry_round;
436   } while (retry_round >= 0);
437
438   if (!::DeleteFileW(info->dump_path.c_str()))
439     LOG(WARNING) << "could not delete " << info->dump_path;
440
441   delete info;
442   return 0;
443 }
444
445 int CrashService::ProcessingLoop() {
446   MSG msg;
447   while (GetMessage(&msg, NULL, 0, 0)) {
448     TranslateMessage(&msg);
449     DispatchMessage(&msg);
450   }
451
452   VLOG(1) << "session ending..";
453   while (ProcessingLock::IsWorking()) {
454     ::Sleep(50);
455   }
456
457   VLOG(1) << "clients connected :" << clients_connected_
458           << "\nclients terminated :" << clients_terminated_
459           << "\ndumps serviced :" << requests_handled_
460           << "\ndumps reported :" << requests_sent_;
461
462   return static_cast<int>(msg.wParam);
463 }
464
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)";
468
469   DWORD error = ERROR_SUCCESS;
470   PSECURITY_DESCRIPTOR sec_desc = NULL;
471
472   PACL sacl = NULL;
473   BOOL sacl_present = FALSE;
474   BOOL sacl_defaulted = FALSE;
475
476   if (::ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl.c_str(),
477                                                              SDDL_REVISION,
478                                                              &sec_desc, NULL)) {
479     if (::GetSecurityDescriptorSacl(sec_desc, &sacl_present, &sacl,
480                                     &sacl_defaulted)) {
481       return sec_desc;
482     }
483   }
484
485   return NULL;
486 }
487
488 }  // namespace breakpad