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 "chrome/browser/process_singleton.h"
6
7 #include <shellapi.h>
8
9 #include "base/base_paths.h"
10 #include "base/bind.h"
11 #include "base/command_line.h"
12 #include "base/files/file_path.h"
13 #include "base/path_service.h"
14 #include "base/process/kill.h"
15 #include "base/process/process_info.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/time/time.h"
20 #include "base/win/metro.h"
21 #include "base/win/registry.h"
22 #include "base/win/scoped_handle.h"
23 #include "base/win/windows_version.h"
24 #include "chrome/browser/browser_process.h"
25 #include "chrome/browser/browser_process_platform_part.h"
26 #include "chrome/browser/chrome_process_finder_win.h"
27 #include "chrome/browser/metro_utils/metro_chrome_win.h"
28 #include "chrome/browser/shell_integration.h"
29 #include "chrome/browser/ui/simple_message_box.h"
30 #include "chrome/common/chrome_constants.h"
31 #include "chrome/common/chrome_paths.h"
32 #include "chrome/common/chrome_paths_internal.h"
33 #include "chrome/common/chrome_switches.h"
34 #include "chrome/grit/chromium_strings.h"
35 #include "chrome/installer/util/wmi.h"
36 #include "content/public/common/result_codes.h"
37 #include "net/base/escape.h"
38 #include "ui/base/l10n/l10n_util.h"
39 #include "ui/gfx/win/hwnd_util.h"
40
41 namespace {
42
43 const char kLockfile[] = "lockfile";
44
45 const int kMetroChromeActivationTimeoutMs = 3000;
46
47 // A helper class that acquires the given |mutex| while the AutoLockMutex is in
48 // scope.
49 class AutoLockMutex {
50 public:
AutoLockMutex(HANDLE mutex)51 explicit AutoLockMutex(HANDLE mutex) : mutex_(mutex) {
52 DWORD result = ::WaitForSingleObject(mutex_, INFINITE);
53 DPCHECK(result == WAIT_OBJECT_0) << "Result = " << result;
54 }
55
~AutoLockMutex()56 ~AutoLockMutex() {
57 BOOL released = ::ReleaseMutex(mutex_);
58 DPCHECK(released);
59 }
60
61 private:
62 HANDLE mutex_;
63 DISALLOW_COPY_AND_ASSIGN(AutoLockMutex);
64 };
65
66 // A helper class that releases the given |mutex| while the AutoUnlockMutex is
67 // in scope and immediately re-acquires it when going out of scope.
68 class AutoUnlockMutex {
69 public:
AutoUnlockMutex(HANDLE mutex)70 explicit AutoUnlockMutex(HANDLE mutex) : mutex_(mutex) {
71 BOOL released = ::ReleaseMutex(mutex_);
72 DPCHECK(released);
73 }
74
~AutoUnlockMutex()75 ~AutoUnlockMutex() {
76 DWORD result = ::WaitForSingleObject(mutex_, INFINITE);
77 DPCHECK(result == WAIT_OBJECT_0) << "Result = " << result;
78 }
79
80 private:
81 HANDLE mutex_;
82 DISALLOW_COPY_AND_ASSIGN(AutoUnlockMutex);
83 };
84
85 // Checks the visibility of the enumerated window and signals once a visible
86 // window has been found.
BrowserWindowEnumeration(HWND window,LPARAM param)87 BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) {
88 bool* result = reinterpret_cast<bool*>(param);
89 *result = ::IsWindowVisible(window) != 0;
90 // Stops enumeration if a visible window has been found.
91 return !*result;
92 }
93
ParseCommandLine(const COPYDATASTRUCT * cds,CommandLine * parsed_command_line,base::FilePath * current_directory)94 bool ParseCommandLine(const COPYDATASTRUCT* cds,
95 CommandLine* parsed_command_line,
96 base::FilePath* current_directory) {
97 // We should have enough room for the shortest command (min_message_size)
98 // and also be a multiple of wchar_t bytes. The shortest command
99 // possible is L"START\0\0" (empty current directory and command line).
100 static const int min_message_size = 7;
101 if (cds->cbData < min_message_size * sizeof(wchar_t) ||
102 cds->cbData % sizeof(wchar_t) != 0) {
103 LOG(WARNING) << "Invalid WM_COPYDATA, length = " << cds->cbData;
104 return false;
105 }
106
107 // We split the string into 4 parts on NULLs.
108 DCHECK(cds->lpData);
109 const std::wstring msg(static_cast<wchar_t*>(cds->lpData),
110 cds->cbData / sizeof(wchar_t));
111 const std::wstring::size_type first_null = msg.find_first_of(L'\0');
112 if (first_null == 0 || first_null == std::wstring::npos) {
113 // no NULL byte, don't know what to do
114 LOG(WARNING) << "Invalid WM_COPYDATA, length = " << msg.length() <<
115 ", first null = " << first_null;
116 return false;
117 }
118
119 // Decode the command, which is everything until the first NULL.
120 if (msg.substr(0, first_null) == L"START") {
121 // Another instance is starting parse the command line & do what it would
122 // have done.
123 VLOG(1) << "Handling STARTUP request from another process";
124 const std::wstring::size_type second_null =
125 msg.find_first_of(L'\0', first_null + 1);
126 if (second_null == std::wstring::npos ||
127 first_null == msg.length() - 1 || second_null == msg.length()) {
128 LOG(WARNING) << "Invalid format for start command, we need a string in 4 "
129 "parts separated by NULLs";
130 return false;
131 }
132
133 // Get current directory.
134 *current_directory = base::FilePath(msg.substr(first_null + 1,
135 second_null - first_null));
136
137 const std::wstring::size_type third_null =
138 msg.find_first_of(L'\0', second_null + 1);
139 if (third_null == std::wstring::npos ||
140 third_null == msg.length()) {
141 LOG(WARNING) << "Invalid format for start command, we need a string in 4 "
142 "parts separated by NULLs";
143 }
144
145 // Get command line.
146 const std::wstring cmd_line =
147 msg.substr(second_null + 1, third_null - second_null);
148 *parsed_command_line = CommandLine::FromString(cmd_line);
149 return true;
150 }
151 return false;
152 }
153
ProcessLaunchNotification(const ProcessSingleton::NotificationCallback & notification_callback,UINT message,WPARAM wparam,LPARAM lparam,LRESULT * result)154 bool ProcessLaunchNotification(
155 const ProcessSingleton::NotificationCallback& notification_callback,
156 UINT message,
157 WPARAM wparam,
158 LPARAM lparam,
159 LRESULT* result) {
160 if (message != WM_COPYDATA)
161 return false;
162
163 // Handle the WM_COPYDATA message from another process.
164 HWND hwnd = reinterpret_cast<HWND>(wparam);
165 const COPYDATASTRUCT* cds = reinterpret_cast<COPYDATASTRUCT*>(lparam);
166
167 CommandLine parsed_command_line(CommandLine::NO_PROGRAM);
168 base::FilePath current_directory;
169 if (!ParseCommandLine(cds, &parsed_command_line, ¤t_directory)) {
170 *result = TRUE;
171 return true;
172 }
173
174 *result = notification_callback.Run(parsed_command_line, current_directory) ?
175 TRUE : FALSE;
176 return true;
177 }
178
179 // Returns true if Chrome needs to be relaunched into Windows 8 immersive mode.
180 // Following conditions apply:-
181 // 1. Windows 8 or greater.
182 // 2. Not in Windows 8 immersive mode.
183 // 3. Chrome is default browser.
184 // 4. Process integrity level is not high.
185 // 5. The profile data directory is the default directory.
186 // 6. Last used mode was immersive/machine is a tablet.
187 // TODO(ananta)
188 // Move this function to a common place as the Windows 8 delegate_execute
189 // handler can possibly use this.
ShouldLaunchInWindows8ImmersiveMode(const base::FilePath & user_data_dir)190 bool ShouldLaunchInWindows8ImmersiveMode(const base::FilePath& user_data_dir) {
191 #if defined(USE_AURA)
192 // Returning false from this function doesn't mean we don't launch immersive
193 // mode in Aura. This function is specifically called in case when we need
194 // to relaunch desktop launched chrome into immersive mode through 'relaunch'
195 // menu. In case of Aura, we will use delegate_execute to do the relaunch.
196 return false;
197 #else
198 if (base::win::GetVersion() < base::win::VERSION_WIN8)
199 return false;
200
201 if (base::win::IsProcessImmersive(base::GetCurrentProcessHandle()))
202 return false;
203
204 if (ShellIntegration::GetDefaultBrowser() != ShellIntegration::IS_DEFAULT)
205 return false;
206
207 base::IntegrityLevel integrity_level = base::INTEGRITY_UNKNOWN;
208 base::GetProcessIntegrityLevel(base::GetCurrentProcessHandle(),
209 &integrity_level);
210 if (integrity_level == base::HIGH_INTEGRITY)
211 return false;
212
213 base::FilePath default_user_data_dir;
214 if (!chrome::GetDefaultUserDataDirectory(&default_user_data_dir))
215 return false;
216
217 if (default_user_data_dir != user_data_dir)
218 return false;
219
220 base::win::RegKey reg_key;
221 DWORD reg_value = 0;
222 if (reg_key.Create(HKEY_CURRENT_USER, chrome::kMetroRegistryPath,
223 KEY_READ) == ERROR_SUCCESS &&
224 reg_key.ReadValueDW(chrome::kLaunchModeValue,
225 ®_value) == ERROR_SUCCESS) {
226 return reg_value == 1;
227 }
228 return false;
229 #endif
230 }
231
232 } // namespace
233
234 // Microsoft's Softricity virtualization breaks the sandbox processes.
235 // So, if we detect the Softricity DLL we use WMI Win32_Process.Create to
236 // break out of the virtualization environment.
237 // http://code.google.com/p/chromium/issues/detail?id=43650
EscapeVirtualization(const base::FilePath & user_data_dir)238 bool ProcessSingleton::EscapeVirtualization(
239 const base::FilePath& user_data_dir) {
240 if (::GetModuleHandle(L"sftldr_wow64.dll") ||
241 ::GetModuleHandle(L"sftldr.dll")) {
242 int process_id;
243 if (!installer::WMIProcess::Launch(::GetCommandLineW(), &process_id))
244 return false;
245 is_virtualized_ = true;
246 // The new window was spawned from WMI, and won't be in the foreground.
247 // So, first we sleep while the new chrome.exe instance starts (because
248 // WaitForInputIdle doesn't work here). Then we poll for up to two more
249 // seconds and make the window foreground if we find it (or we give up).
250 HWND hwnd = 0;
251 ::Sleep(90);
252 for (int tries = 200; tries; --tries) {
253 hwnd = chrome::FindRunningChromeWindow(user_data_dir);
254 if (hwnd) {
255 ::SetForegroundWindow(hwnd);
256 break;
257 }
258 ::Sleep(10);
259 }
260 return true;
261 }
262 return false;
263 }
264
ProcessSingleton(const base::FilePath & user_data_dir,const NotificationCallback & notification_callback)265 ProcessSingleton::ProcessSingleton(
266 const base::FilePath& user_data_dir,
267 const NotificationCallback& notification_callback)
268 : notification_callback_(notification_callback),
269 is_virtualized_(false), lock_file_(INVALID_HANDLE_VALUE),
270 user_data_dir_(user_data_dir) {
271 }
272
~ProcessSingleton()273 ProcessSingleton::~ProcessSingleton() {
274 if (lock_file_ != INVALID_HANDLE_VALUE)
275 ::CloseHandle(lock_file_);
276 }
277
278 // Code roughly based on Mozilla.
NotifyOtherProcess()279 ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() {
280 if (is_virtualized_)
281 return PROCESS_NOTIFIED; // We already spawned the process in this case.
282 if (lock_file_ == INVALID_HANDLE_VALUE && !remote_window_) {
283 return LOCK_ERROR;
284 } else if (!remote_window_) {
285 return PROCESS_NONE;
286 }
287
288 switch (chrome::AttemptToNotifyRunningChrome(remote_window_, false)) {
289 case chrome::NOTIFY_SUCCESS:
290 return PROCESS_NOTIFIED;
291 case chrome::NOTIFY_FAILED:
292 remote_window_ = NULL;
293 return PROCESS_NONE;
294 case chrome::NOTIFY_WINDOW_HUNG:
295 remote_window_ = NULL;
296 break;
297 }
298
299 DWORD process_id = 0;
300 DWORD thread_id = ::GetWindowThreadProcessId(remote_window_, &process_id);
301 if (!thread_id || !process_id) {
302 remote_window_ = NULL;
303 return PROCESS_NONE;
304 }
305
306 // The window is hung. Scan for every window to find a visible one.
307 bool visible_window = false;
308 ::EnumThreadWindows(thread_id,
309 &BrowserWindowEnumeration,
310 reinterpret_cast<LPARAM>(&visible_window));
311
312 // If there is a visible browser window, ask the user before killing it.
313 if (visible_window &&
314 chrome::ShowMessageBox(
315 NULL,
316 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME),
317 l10n_util::GetStringUTF16(IDS_BROWSER_HUNGBROWSER_MESSAGE),
318 chrome::MESSAGE_BOX_TYPE_QUESTION) == chrome::MESSAGE_BOX_RESULT_NO) {
319 // The user denied. Quit silently.
320 return PROCESS_NOTIFIED;
321 }
322
323 // Time to take action. Kill the browser process.
324 base::KillProcessById(process_id, content::RESULT_CODE_HUNG, true);
325 remote_window_ = NULL;
326 return PROCESS_NONE;
327 }
328
329 ProcessSingleton::NotifyResult
NotifyOtherProcessOrCreate()330 ProcessSingleton::NotifyOtherProcessOrCreate() {
331 ProcessSingleton::NotifyResult result = PROCESS_NONE;
332 if (!Create()) {
333 result = NotifyOtherProcess();
334 if (result == PROCESS_NONE)
335 result = PROFILE_IN_USE;
336 } else {
337 g_browser_process->platform_part()->PlatformSpecificCommandLineProcessing(
338 *CommandLine::ForCurrentProcess());
339 }
340 return result;
341 }
342
343 // Look for a Chrome instance that uses the same profile directory. If there
344 // isn't one, create a message window with its title set to the profile
345 // directory path.
Create()346 bool ProcessSingleton::Create() {
347 static const wchar_t kMutexName[] = L"Local\\ChromeProcessSingletonStartup!";
348 static const wchar_t kMetroActivationEventName[] =
349 L"Local\\ChromeProcessSingletonStartupMetroActivation!";
350
351 remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_);
352 if (!remote_window_ && !EscapeVirtualization(user_data_dir_)) {
353 // Make sure we will be the one and only process creating the window.
354 // We use a named Mutex since we are protecting against multi-process
355 // access. As documented, it's clearer to NOT request ownership on creation
356 // since it isn't guaranteed we will get it. It is better to create it
357 // without ownership and explicitly get the ownership afterward.
358 base::win::ScopedHandle only_me(::CreateMutex(NULL, FALSE, kMutexName));
359 if (!only_me.IsValid()) {
360 DPLOG(FATAL) << "CreateMutex failed";
361 return false;
362 }
363
364 AutoLockMutex auto_lock_only_me(only_me.Get());
365
366 // We now own the mutex so we are the only process that can create the
367 // window at this time, but we must still check if someone created it
368 // between the time where we looked for it above and the time the mutex
369 // was given to us.
370 remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_);
371
372
373 // In Win8+, a new Chrome process launched in Desktop mode may need to be
374 // transmuted into Metro Chrome (see ShouldLaunchInWindows8ImmersiveMode for
375 // heuristics). To accomplish this, the current Chrome activates Metro
376 // Chrome, releases the startup mutex, and waits for metro Chrome to take
377 // the singleton. From that point onward, the command line for this Chrome
378 // process will be sent to Metro Chrome by the usual channels.
379 if (!remote_window_ && base::win::GetVersion() >= base::win::VERSION_WIN8 &&
380 !base::win::IsMetroProcess()) {
381 // |metro_activation_event| is created right before activating a Metro
382 // Chrome (note that there can only be one Metro Chrome process; by OS
383 // design); all following Desktop processes will then wait for this event
384 // to be signaled by Metro Chrome which will do so as soon as it grabs
385 // this singleton (should any of the waiting processes timeout waiting for
386 // the signal they will try to grab the singleton for themselves which
387 // will result in a forced Desktop Chrome launch in the worst case).
388 base::win::ScopedHandle metro_activation_event(
389 ::OpenEvent(SYNCHRONIZE, FALSE, kMetroActivationEventName));
390 if (!metro_activation_event.IsValid() &&
391 ShouldLaunchInWindows8ImmersiveMode(user_data_dir_)) {
392 // No Metro activation is under way, but the desire is to launch in
393 // Metro mode: activate and rendez-vous with the activated process.
394 metro_activation_event.Set(
395 ::CreateEvent(NULL, TRUE, FALSE, kMetroActivationEventName));
396 if (!chrome::ActivateMetroChrome()) {
397 // Failed to launch immersive Chrome, default to launching on Desktop.
398 LOG(ERROR) << "Failed to launch immersive chrome";
399 metro_activation_event.Close();
400 }
401 }
402
403 if (metro_activation_event.IsValid()) {
404 // Release |only_me| (to let Metro Chrome grab this singleton) and wait
405 // until the event is signaled (i.e. Metro Chrome was successfully
406 // activated). Ignore timeout waiting for |metro_activation_event|.
407 {
408 AutoUnlockMutex auto_unlock_only_me(only_me.Get());
409
410 DWORD result = ::WaitForSingleObject(metro_activation_event.Get(),
411 kMetroChromeActivationTimeoutMs);
412 DPCHECK(result == WAIT_OBJECT_0 || result == WAIT_TIMEOUT)
413 << "Result = " << result;
414 }
415
416 // Check if this singleton was successfully grabbed by another process
417 // (hopefully Metro Chrome). Failing to do so, this process will grab
418 // the singleton and launch in Desktop mode.
419 remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_);
420 }
421 }
422
423 if (!remote_window_) {
424 // We have to make sure there is no Chrome instance running on another
425 // machine that uses the same profile.
426 base::FilePath lock_file_path = user_data_dir_.AppendASCII(kLockfile);
427 lock_file_ = ::CreateFile(lock_file_path.value().c_str(),
428 GENERIC_WRITE,
429 FILE_SHARE_READ,
430 NULL,
431 CREATE_ALWAYS,
432 FILE_ATTRIBUTE_NORMAL |
433 FILE_FLAG_DELETE_ON_CLOSE,
434 NULL);
435 DWORD error = ::GetLastError();
436 LOG_IF(WARNING, lock_file_ != INVALID_HANDLE_VALUE &&
437 error == ERROR_ALREADY_EXISTS) << "Lock file exists but is writable.";
438 LOG_IF(ERROR, lock_file_ == INVALID_HANDLE_VALUE)
439 << "Lock file can not be created! Error code: " << error;
440
441 if (lock_file_ != INVALID_HANDLE_VALUE) {
442 // Set the window's title to the path of our user data directory so
443 // other Chrome instances can decide if they should forward to us.
444 bool result = window_.CreateNamed(
445 base::Bind(&ProcessLaunchNotification, notification_callback_),
446 user_data_dir_.value());
447 CHECK(result && window_.hwnd());
448 }
449
450 if (base::win::GetVersion() >= base::win::VERSION_WIN8) {
451 // Make sure no one is still waiting on Metro activation whether it
452 // succeeded (i.e., this is the Metro process) or failed.
453 base::win::ScopedHandle metro_activation_event(
454 ::OpenEvent(EVENT_MODIFY_STATE, FALSE, kMetroActivationEventName));
455 if (metro_activation_event.IsValid())
456 ::SetEvent(metro_activation_event.Get());
457 }
458 }
459 }
460
461 return window_.hwnd() != NULL;
462 }
463
Cleanup()464 void ProcessSingleton::Cleanup() {
465 }
466