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