blob: 62a469f380bbf538e6f73fd0c443713c20de69e9 [file] [log] [blame]
[email protected]e3ce40a2012-01-31 03:03:031// Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]fc14cef2009-01-27 22:17:292// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
[email protected]7c47ae3e2009-02-18 00:34:215#include "chrome/browser/process_singleton.h"
[email protected]fc14cef2009-01-27 22:17:296
7#include "base/base_paths.h"
8#include "base/command_line.h"
[email protected]f805fe82010-08-03 22:47:109#include "base/file_path.h"
[email protected]9e9b6e8e2009-12-02 08:45:0110#include "base/path_service.h"
[email protected]fc14cef2009-01-27 22:17:2911#include "base/process_util.h"
[email protected]f394bc62012-08-22 21:42:2912#include "base/stringprintf.h"
[email protected]0f26d7b2011-01-05 19:10:4413#include "base/utf_string_conversions.h"
[email protected]f394bc62012-08-22 21:42:2914#include "base/win/metro.h"
[email protected]b90d7e802011-01-09 16:32:2015#include "base/win/scoped_handle.h"
[email protected]ecb924c2011-03-17 00:34:0916#include "base/win/wrapped_window_proc.h"
[email protected]b50892c5f2012-05-13 07:34:1417#include "chrome/browser/ui/simple_message_box.h"
[email protected]fc14cef2009-01-27 22:17:2918#include "chrome/common/chrome_constants.h"
[email protected]0a194552011-09-14 17:53:3519#include "chrome/installer/util/wmi.h"
[email protected]b39ef1cb2011-10-25 04:46:5520#include "content/public/common/result_codes.h"
[email protected]34ac8f32009-02-22 23:03:2721#include "grit/chromium_strings.h"
22#include "grit/generated_resources.h"
[email protected]f394bc62012-08-22 21:42:2923#include "net/base/escape.h"
[email protected]c051a1b2011-01-21 23:30:1724#include "ui/base/l10n/l10n_util.h"
[email protected]e7661062011-01-19 22:16:5325#include "ui/base/win/hwnd_util.h"
[email protected]fc14cef2009-01-27 22:17:2926
27namespace {
28
[email protected]2b7ff5b92012-07-17 22:45:1829const char kLockfile[] = "lockfile";
30
[email protected]f394bc62012-08-22 21:42:2931const char kSearchUrl[] =
32 "http://www.google.com/search?q=%s&sourceid=chrome&ie=UTF-8";
33
[email protected]f891fb32009-04-08 00:20:3234// Checks the visibility of the enumerated window and signals once a visible
[email protected]fc14cef2009-01-27 22:17:2935// window has been found.
36BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) {
37 bool* result = reinterpret_cast<bool*>(param);
38 *result = IsWindowVisible(window) != 0;
39 // Stops enumeration if a visible window has been found.
40 return !*result;
41}
42
[email protected]e3ce40a2012-01-31 03:03:0343// This function thunks to the object's version of the windowproc, taking in
44// consideration that there are several messages being dispatched before
45// WM_NCCREATE which we let windows handle.
46LRESULT CALLBACK ThunkWndProc(HWND hwnd, UINT message,
47 WPARAM wparam, LPARAM lparam) {
48 ProcessSingleton* singleton =
49 reinterpret_cast<ProcessSingleton*>(ui::GetWindowUserData(hwnd));
50 if (message == WM_NCCREATE) {
51 CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lparam);
52 singleton = reinterpret_cast<ProcessSingleton*>(cs->lpCreateParams);
53 CHECK(singleton);
54 ui::SetWindowUserData(hwnd, singleton);
55 } else if (!singleton) {
56 return ::DefWindowProc(hwnd, message, wparam, lparam);
57 }
58 return singleton->WndProc(hwnd, message, wparam, lparam);
59}
60
[email protected]edf04b512012-02-23 09:47:4361bool ParseCommandLine(const COPYDATASTRUCT* cds,
62 CommandLine* parsed_command_line,
63 FilePath* current_directory) {
64 // We should have enough room for the shortest command (min_message_size)
65 // and also be a multiple of wchar_t bytes. The shortest command
66 // possible is L"START\0\0" (empty current directory and command line).
67 static const int min_message_size = 7;
68 if (cds->cbData < min_message_size * sizeof(wchar_t) ||
69 cds->cbData % sizeof(wchar_t) != 0) {
70 LOG(WARNING) << "Invalid WM_COPYDATA, length = " << cds->cbData;
71 return false;
72 }
73
74 // We split the string into 4 parts on NULLs.
75 DCHECK(cds->lpData);
76 const std::wstring msg(static_cast<wchar_t*>(cds->lpData),
77 cds->cbData / sizeof(wchar_t));
78 const std::wstring::size_type first_null = msg.find_first_of(L'\0');
79 if (first_null == 0 || first_null == std::wstring::npos) {
80 // no NULL byte, don't know what to do
81 LOG(WARNING) << "Invalid WM_COPYDATA, length = " << msg.length() <<
82 ", first null = " << first_null;
83 return false;
84 }
85
86 // Decode the command, which is everything until the first NULL.
87 if (msg.substr(0, first_null) == L"START") {
88 // Another instance is starting parse the command line & do what it would
89 // have done.
90 VLOG(1) << "Handling STARTUP request from another process";
91 const std::wstring::size_type second_null =
92 msg.find_first_of(L'\0', first_null + 1);
93 if (second_null == std::wstring::npos ||
94 first_null == msg.length() - 1 || second_null == msg.length()) {
95 LOG(WARNING) << "Invalid format for start command, we need a string in 4 "
96 "parts separated by NULLs";
97 return false;
98 }
99
100 // Get current directory.
101 *current_directory = FilePath(msg.substr(first_null + 1,
102 second_null - first_null));
103
104 const std::wstring::size_type third_null =
105 msg.find_first_of(L'\0', second_null + 1);
106 if (third_null == std::wstring::npos ||
107 third_null == msg.length()) {
108 LOG(WARNING) << "Invalid format for start command, we need a string in 4 "
109 "parts separated by NULLs";
110 }
111
112 // Get command line.
113 const std::wstring cmd_line =
114 msg.substr(second_null + 1, third_null - second_null);
115 *parsed_command_line = CommandLine::FromString(cmd_line);
116 return true;
117 }
118 return false;
119}
120
[email protected]fc14cef2009-01-27 22:17:29121} // namespace
122
[email protected]0a194552011-09-14 17:53:35123// Microsoft's Softricity virtualization breaks the sandbox processes.
124// So, if we detect the Softricity DLL we use WMI Win32_Process.Create to
125// break out of the virtualization environment.
126// http://code.google.com/p/chromium/issues/detail?id=43650
127bool ProcessSingleton::EscapeVirtualization(const FilePath& user_data_dir) {
128 if (::GetModuleHandle(L"sftldr_wow64.dll") ||
129 ::GetModuleHandle(L"sftldr.dll")) {
130 int process_id;
131 if (!installer::WMIProcess::Launch(GetCommandLineW(), &process_id))
132 return false;
133 is_virtualized_ = true;
134 // The new window was spawned from WMI, and won't be in the foreground.
135 // So, first we sleep while the new chrome.exe instance starts (because
136 // WaitForInputIdle doesn't work here). Then we poll for up to two more
137 // seconds and make the window foreground if we find it (or we give up).
138 HWND hwnd = 0;
139 ::Sleep(90);
140 for (int tries = 200; tries; --tries) {
141 hwnd = FindWindowEx(HWND_MESSAGE, NULL, chrome::kMessageWindowClass,
142 user_data_dir.value().c_str());
143 if (hwnd) {
144 ::SetForegroundWindow(hwnd);
145 break;
146 }
147 ::Sleep(10);
148 }
149 return true;
150 }
151 return false;
152}
153
[email protected]f891fb32009-04-08 00:20:32154// Look for a Chrome instance that uses the same profile directory.
[email protected]9a182832012-02-10 18:45:58155// If there isn't one, create a message window with its title set to
156// the profile directory path.
[email protected]7c47ae3e2009-02-18 00:34:21157ProcessSingleton::ProcessSingleton(const FilePath& user_data_dir)
[email protected]0a194552011-09-14 17:53:35158 : window_(NULL), locked_(false), foreground_window_(NULL),
[email protected]2b7ff5b92012-07-17 22:45:18159 is_virtualized_(false), lock_file_(INVALID_HANDLE_VALUE) {
[email protected]bbef41f02010-03-04 16:16:19160 remote_window_ = FindWindowEx(HWND_MESSAGE, NULL,
161 chrome::kMessageWindowClass,
[email protected]8a205c02011-02-04 20:41:33162 user_data_dir.value().c_str());
[email protected]0a194552011-09-14 17:53:35163 if (!remote_window_ && !EscapeVirtualization(user_data_dir)) {
[email protected]bbef41f02010-03-04 16:16:19164 // Make sure we will be the one and only process creating the window.
165 // We use a named Mutex since we are protecting against multi-process
166 // access. As documented, it's clearer to NOT request ownership on creation
167 // since it isn't guaranteed we will get it. It is better to create it
168 // without ownership and explicitly get the ownership afterward.
[email protected]e132804b2010-12-22 12:48:25169 std::wstring mutex_name(L"Local\\ChromeProcessSingletonStartup!");
[email protected]b90d7e802011-01-09 16:32:20170 base::win::ScopedHandle only_me(
171 CreateMutex(NULL, FALSE, mutex_name.c_str()));
[email protected]bbef41f02010-03-04 16:16:19172 DCHECK(only_me.Get() != NULL) << "GetLastError = " << GetLastError();
173
174 // This is how we acquire the mutex (as opposed to the initial ownership).
175 DWORD result = WaitForSingleObject(only_me, INFINITE);
176 DCHECK(result == WAIT_OBJECT_0) << "Result = " << result <<
177 "GetLastError = " << GetLastError();
178
179 // We now own the mutex so we are the only process that can create the
180 // window at this time, but we must still check if someone created it
181 // between the time where we looked for it above and the time the mutex
182 // was given to us.
183 remote_window_ = FindWindowEx(HWND_MESSAGE, NULL,
184 chrome::kMessageWindowClass,
[email protected]8a205c02011-02-04 20:41:33185 user_data_dir.value().c_str());
[email protected]9a182832012-02-10 18:45:58186 if (!remote_window_) {
[email protected]2b7ff5b92012-07-17 22:45:18187 // We have to make sure there is no Chrome instance running on another
188 // machine that uses the same profile.
189 FilePath lock_file_path = user_data_dir.AppendASCII(kLockfile);
190 lock_file_ = CreateFile(lock_file_path.value().c_str(),
191 GENERIC_WRITE,
192 FILE_SHARE_READ,
193 NULL,
194 CREATE_ALWAYS,
195 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE,
196 NULL);
197 DWORD error = GetLastError();
198 LOG_IF(WARNING, lock_file_ != INVALID_HANDLE_VALUE &&
199 error == ERROR_ALREADY_EXISTS) << "Lock file exists but is writable.";
200 LOG_IF(ERROR, lock_file_ == INVALID_HANDLE_VALUE)
201 << "Lock file can not be created! Error code: " << error;
[email protected]9a182832012-02-10 18:45:58202
[email protected]2b7ff5b92012-07-17 22:45:18203 if (lock_file_ != INVALID_HANDLE_VALUE) {
204 HINSTANCE hinst = base::GetModuleFromAddress(&ThunkWndProc);
[email protected]9a182832012-02-10 18:45:58205
[email protected]2b7ff5b92012-07-17 22:45:18206 WNDCLASSEX wc = {0};
207 wc.cbSize = sizeof(wc);
208 wc.lpfnWndProc = base::win::WrappedWindowProc<ThunkWndProc>;
209 wc.hInstance = hinst;
210 wc.lpszClassName = chrome::kMessageWindowClass;
211 ATOM clazz = ::RegisterClassEx(&wc);
212 DCHECK(clazz);
213
214 // Set the window's title to the path of our user data directory so
215 // other Chrome instances can decide if they should forward to us.
216 window_ = ::CreateWindow(MAKEINTATOM(clazz),
217 user_data_dir.value().c_str(),
218 0, 0, 0, 0, 0, HWND_MESSAGE, 0, hinst, this);
219 CHECK(window_);
220 }
[email protected]9a182832012-02-10 18:45:58221 }
[email protected]bbef41f02010-03-04 16:16:19222 BOOL success = ReleaseMutex(only_me);
223 DCHECK(success) << "GetLastError = " << GetLastError();
224 }
[email protected]fc14cef2009-01-27 22:17:29225}
226
[email protected]7c47ae3e2009-02-18 00:34:21227ProcessSingleton::~ProcessSingleton() {
[email protected]a9b36c92012-06-18 08:47:57228 // We need to unregister the window as late as possible so that we can detect
229 // another instance of chrome running. Otherwise we may end up writing out
230 // data while a new chrome is starting up.
231 if (window_) {
232 ::DestroyWindow(window_);
233 ::UnregisterClass(chrome::kMessageWindowClass,
234 base::GetModuleFromAddress(&ThunkWndProc));
235 }
[email protected]2b7ff5b92012-07-17 22:45:18236 if (lock_file_ != INVALID_HANDLE_VALUE)
237 CloseHandle(lock_file_);
[email protected]fc14cef2009-01-27 22:17:29238}
239
[email protected]9f20a6d02009-08-21 01:18:37240ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() {
[email protected]0a194552011-09-14 17:53:35241 if (is_virtualized_)
242 return PROCESS_NOTIFIED; // We already spawned the process in this case.
[email protected]2b7ff5b92012-07-17 22:45:18243 if (lock_file_ == INVALID_HANDLE_VALUE && !remote_window_)
244 return LOCK_ERROR;
[email protected]0a194552011-09-14 17:53:35245 else if (!remote_window_)
[email protected]9f20a6d02009-08-21 01:18:37246 return PROCESS_NONE;
[email protected]fc14cef2009-01-27 22:17:29247
[email protected]f394bc62012-08-22 21:42:29248 DWORD process_id = 0;
249 DWORD thread_id = GetWindowThreadProcessId(remote_window_, &process_id);
250 // It is possible that the process owning this window may have died by now.
251 if (!thread_id || !process_id) {
252 remote_window_ = NULL;
253 return PROCESS_NONE;
254 }
255
256 if (base::win::IsMetroProcess()) {
257 // Interesting corner case. We are launched as a metro process but we
258 // found another chrome running. Since metro enforces single instance then
259 // the other chrome must be desktop chrome and this must be a search charm
260 // activation. This scenario is unique; other cases should be properly
261 // handled by the delegate_execute which will not activate a second chrome.
262 string16 terms;
263 base::win::MetroLaunchType launch = base::win::GetMetroLaunchParams(&terms);
264 if (launch != base::win::METRO_SEARCH) {
265 LOG(WARNING) << "In metro mode, but and launch is " << launch;
266 } else {
267 std::string query = net::EscapeQueryParamValue(UTF16ToUTF8(terms), true);
268 std::string url = base::StringPrintf(kSearchUrl, query.c_str());
269 SHELLEXECUTEINFOA sei = { sizeof(sei) };
270 sei.fMask = SEE_MASK_FLAG_LOG_USAGE;
271 sei.nShow = SW_SHOWNORMAL;
272 sei.lpFile = url.c_str();
273 OutputDebugStringA(sei.lpFile);
274 sei.lpDirectory = "";
275 ::ShellExecuteExA(&sei);
276 }
277 return PROCESS_NOTIFIED;
278 }
279 // Non-metro mode, send our command line to the other chrome message window.
[email protected]fc14cef2009-01-27 22:17:29280 // format is "START\0<<<current directory>>>\0<<<commandline>>>".
281 std::wstring to_send(L"START\0", 6); // want the NULL in the string.
[email protected]b9696482010-11-30 23:56:18282 FilePath cur_dir;
[email protected]fc14cef2009-01-27 22:17:29283 if (!PathService::Get(base::DIR_CURRENT, &cur_dir))
[email protected]9f20a6d02009-08-21 01:18:37284 return PROCESS_NONE;
[email protected]b9696482010-11-30 23:56:18285 to_send.append(cur_dir.value());
[email protected]fc14cef2009-01-27 22:17:29286 to_send.append(L"\0", 1); // Null separator.
287 to_send.append(GetCommandLineW());
288 to_send.append(L"\0", 1); // Null separator.
289
290 // Allow the current running browser window making itself the foreground
291 // window (otherwise it will just flash in the taskbar).
[email protected]fc14cef2009-01-27 22:17:29292 AllowSetForegroundWindow(process_id);
293
[email protected]fc14cef2009-01-27 22:17:29294 COPYDATASTRUCT cds;
295 cds.dwData = 0;
296 cds.cbData = static_cast<DWORD>((to_send.length() + 1) * sizeof(wchar_t));
297 cds.lpData = const_cast<wchar_t*>(to_send.c_str());
298 DWORD_PTR result = 0;
299 if (SendMessageTimeout(remote_window_,
300 WM_COPYDATA,
301 NULL,
302 reinterpret_cast<LPARAM>(&cds),
303 SMTO_ABORTIFHUNG,
[email protected]8b08cbd2009-08-04 05:34:19304 kTimeoutInSeconds * 1000,
[email protected]fc14cef2009-01-27 22:17:29305 &result)) {
[email protected]0815b6d2009-02-11 00:39:37306 // It is possible that the process owning this window may have died by now.
307 if (!result) {
308 remote_window_ = NULL;
[email protected]9f20a6d02009-08-21 01:18:37309 return PROCESS_NONE;
[email protected]0815b6d2009-02-11 00:39:37310 }
[email protected]9f20a6d02009-08-21 01:18:37311 return PROCESS_NOTIFIED;
[email protected]fc14cef2009-01-27 22:17:29312 }
313
[email protected]0815b6d2009-02-11 00:39:37314 // It is possible that the process owning this window may have died by now.
315 if (!IsWindow(remote_window_)) {
316 remote_window_ = NULL;
[email protected]9f20a6d02009-08-21 01:18:37317 return PROCESS_NONE;
[email protected]0815b6d2009-02-11 00:39:37318 }
319
[email protected]fc14cef2009-01-27 22:17:29320 // The window is hung. Scan for every window to find a visible one.
321 bool visible_window = false;
322 EnumThreadWindows(thread_id,
323 &BrowserWindowEnumeration,
324 reinterpret_cast<LPARAM>(&visible_window));
325
326 // If there is a visible browser window, ask the user before killing it.
[email protected]d33220292012-07-04 01:41:27327 if (visible_window && chrome::ShowMessageBox(NULL,
[email protected]5da155e2012-05-26 16:31:16328 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME),
329 l10n_util::GetStringUTF16(IDS_BROWSER_HUNGBROWSER_MESSAGE),
[email protected]d33220292012-07-04 01:41:27330 chrome::MESSAGE_BOX_TYPE_QUESTION) == chrome::MESSAGE_BOX_RESULT_NO) {
[email protected]5da155e2012-05-26 16:31:16331 // The user denied. Quit silently.
332 return PROCESS_NOTIFIED;
[email protected]fc14cef2009-01-27 22:17:29333 }
334
335 // Time to take action. Kill the browser process.
[email protected]1fcfb202011-07-19 19:53:14336 base::KillProcessById(process_id, content::RESULT_CODE_HUNG, true);
[email protected]fc14cef2009-01-27 22:17:29337 remote_window_ = NULL;
[email protected]9f20a6d02009-08-21 01:18:37338 return PROCESS_NONE;
[email protected]fc14cef2009-01-27 22:17:29339}
340
[email protected]5d364542012-04-05 07:15:39341ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessOrCreate(
342 const NotificationCallback& notification_callback) {
[email protected]4a44bc32010-05-28 22:22:44343 NotifyResult result = NotifyOtherProcess();
344 if (result != PROCESS_NONE)
345 return result;
[email protected]5d364542012-04-05 07:15:39346 return Create(notification_callback) ? PROCESS_NONE : PROFILE_IN_USE;
[email protected]4a44bc32010-05-28 22:22:44347}
348
[email protected]9a182832012-02-10 18:45:58349// On Windows, there is no need to call Create() since the message
350// window is created in the constructor but to avoid having more
351// platform specific code in browser_main.cc we tolerate calls to
[email protected]5d364542012-04-05 07:15:39352// Create().
353bool ProcessSingleton::Create(
354 const NotificationCallback& notification_callback) {
[email protected]fc14cef2009-01-27 22:17:29355 DCHECK(!remote_window_);
[email protected]5d364542012-04-05 07:15:39356 DCHECK(notification_callback_.is_null());
357
358 if (window_ != NULL)
359 notification_callback_ = notification_callback;
360
[email protected]9a182832012-02-10 18:45:58361 return window_ != NULL;
[email protected]fc14cef2009-01-27 22:17:29362}
363
[email protected]9f20a6d02009-08-21 01:18:37364void ProcessSingleton::Cleanup() {
[email protected]9f20a6d02009-08-21 01:18:37365}
366
[email protected]7c47ae3e2009-02-18 00:34:21367LRESULT ProcessSingleton::OnCopyData(HWND hwnd, const COPYDATASTRUCT* cds) {
[email protected]175a7a22009-05-03 15:57:53368 // If locked, it means we are not ready to process this message because
[email protected]afd20c022010-06-10 00:48:20369 // we are probably in a first run critical phase.
[email protected]175a7a22009-05-03 15:57:53370 if (locked_) {
[email protected]95259c62011-10-25 23:23:53371#if defined(USE_AURA)
372 NOTIMPLEMENTED();
373#else
[email protected]175a7a22009-05-03 15:57:53374 // Attempt to place ourselves in the foreground / flash the task bar.
[email protected]edf04b512012-02-23 09:47:43375 if (foreground_window_ != NULL && IsWindow(foreground_window_)) {
[email protected]175a7a22009-05-03 15:57:53376 SetForegroundWindow(foreground_window_);
[email protected]edf04b512012-02-23 09:47:43377 } else {
378 // Read the command line and store it. It will be replayed when the
379 // ProcessSingleton becomes unlocked.
380 CommandLine parsed_command_line(CommandLine::NO_PROGRAM);
381 FilePath current_directory;
382 if (ParseCommandLine(cds, &parsed_command_line, &current_directory))
383 saved_startup_messages_.push_back(
384 std::make_pair(parsed_command_line.argv(), current_directory));
385 }
[email protected]95259c62011-10-25 23:23:53386#endif
[email protected]175a7a22009-05-03 15:57:53387 return TRUE;
388 }
389
[email protected]edf04b512012-02-23 09:47:43390 CommandLine parsed_command_line(CommandLine::NO_PROGRAM);
391 FilePath current_directory;
392 if (!ParseCommandLine(cds, &parsed_command_line, &current_directory))
[email protected]fc14cef2009-01-27 22:17:29393 return TRUE;
[email protected]5d364542012-04-05 07:15:39394 return notification_callback_.Run(parsed_command_line, current_directory) ?
395 TRUE : FALSE;
[email protected]fc14cef2009-01-27 22:17:29396}
397
[email protected]e3ce40a2012-01-31 03:03:03398LRESULT ProcessSingleton::WndProc(HWND hwnd, UINT message,
399 WPARAM wparam, LPARAM lparam) {
[email protected]fc14cef2009-01-27 22:17:29400 switch (message) {
401 case WM_COPYDATA:
402 return OnCopyData(reinterpret_cast<HWND>(wparam),
403 reinterpret_cast<COPYDATASTRUCT*>(lparam));
404 default:
405 break;
406 }
407
408 return ::DefWindowProc(hwnd, message, wparam, lparam);
409}