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, &current_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                          &reg_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