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: 51 explicit AutoLockMutex(HANDLE mutex) : mutex_(mutex) { 52 DWORD result = ::WaitForSingleObject(mutex_, INFINITE); 53 DPCHECK(result == WAIT_OBJECT_0) << "Result = " << result; 54 } 55 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: 70 explicit AutoUnlockMutex(HANDLE mutex) : mutex_(mutex) { 71 BOOL released = ::ReleaseMutex(mutex_); 72 DPCHECK(released); 73 } 74 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. 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 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 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. 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 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 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 273 ProcessSingleton::~ProcessSingleton() { 274 if (lock_file_ != INVALID_HANDLE_VALUE) 275 ::CloseHandle(lock_file_); 276 } 277 278 // Code roughly based on Mozilla. 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 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. 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 464 void ProcessSingleton::Cleanup() { 465 } 466