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