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