process_singleton_win.cc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  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. #include "chrome/browser/process_singleton.h"
  5. #include <shellapi.h>
  6. #include "base/base_paths.h"
  7. #include "base/bind.h"
  8. #include "base/command_line.h"
  9. #include "base/files/file_path.h"
  10. #include "base/files/file_util.h"
  11. #include "base/process/process.h"
  12. #include "base/process/process_info.h"
  13. #include "base/strings/string_number_conversions.h"
  14. #include "base/strings/stringprintf.h"
  15. #include "base/strings/utf_string_conversions.h"
  16. #include "base/time/time.h"
  17. #include "base/win/registry.h"
  18. #include "base/win/scoped_handle.h"
  19. #include "base/win/windows_version.h"
  20. #include "chrome/browser/chrome_process_finder_win.h"
  21. #include "content/public/common/result_codes.h"
  22. #include "net/base/escape.h"
  23. #include "ui/base/l10n/l10n_util.h"
  24. #include "ui/gfx/win/hwnd_util.h"
  25. namespace {
  26. const char kLockfile[] = "lockfile";
  27. // A helper class that acquires the given |mutex| while the AutoLockMutex is in
  28. // scope.
  29. class AutoLockMutex {
  30. public:
  31. explicit AutoLockMutex(HANDLE mutex) : mutex_(mutex) {
  32. DWORD result = ::WaitForSingleObject(mutex_, INFINITE);
  33. DPCHECK(result == WAIT_OBJECT_0) << "Result = " << result;
  34. }
  35. ~AutoLockMutex() {
  36. BOOL released = ::ReleaseMutex(mutex_);
  37. DPCHECK(released);
  38. }
  39. private:
  40. HANDLE mutex_;
  41. DISALLOW_COPY_AND_ASSIGN(AutoLockMutex);
  42. };
  43. // A helper class that releases the given |mutex| while the AutoUnlockMutex is
  44. // in scope and immediately re-acquires it when going out of scope.
  45. class AutoUnlockMutex {
  46. public:
  47. explicit AutoUnlockMutex(HANDLE mutex) : mutex_(mutex) {
  48. BOOL released = ::ReleaseMutex(mutex_);
  49. DPCHECK(released);
  50. }
  51. ~AutoUnlockMutex() {
  52. DWORD result = ::WaitForSingleObject(mutex_, INFINITE);
  53. DPCHECK(result == WAIT_OBJECT_0) << "Result = " << result;
  54. }
  55. private:
  56. HANDLE mutex_;
  57. DISALLOW_COPY_AND_ASSIGN(AutoUnlockMutex);
  58. };
  59. // Checks the visibility of the enumerated window and signals once a visible
  60. // window has been found.
  61. BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) {
  62. bool* result = reinterpret_cast<bool*>(param);
  63. *result = ::IsWindowVisible(window) != 0;
  64. // Stops enumeration if a visible window has been found.
  65. return !*result;
  66. }
  67. // Convert Command line string to argv.
  68. base::CommandLine::StringVector CommandLineStringToArgv(
  69. const std::wstring& command_line_string) {
  70. int num_args = 0;
  71. wchar_t** args = NULL;
  72. args = ::CommandLineToArgvW(command_line_string.c_str(), &num_args);
  73. base::CommandLine::StringVector argv;
  74. for (int i = 0; i < num_args; ++i)
  75. argv.push_back(std::wstring(args[i]));
  76. LocalFree(args);
  77. return argv;
  78. }
  79. bool ParseCommandLine(const COPYDATASTRUCT* cds,
  80. base::CommandLine::StringVector* parsed_command_line,
  81. base::FilePath* current_directory) {
  82. // We should have enough room for the shortest command (min_message_size)
  83. // and also be a multiple of wchar_t bytes. The shortest command
  84. // possible is L"START\0\0" (empty current directory and command line).
  85. static const int min_message_size = 7;
  86. if (cds->cbData < min_message_size * sizeof(wchar_t) ||
  87. cds->cbData % sizeof(wchar_t) != 0) {
  88. LOG(WARNING) << "Invalid WM_COPYDATA, length = " << cds->cbData;
  89. return false;
  90. }
  91. // We split the string into 4 parts on NULLs.
  92. DCHECK(cds->lpData);
  93. const std::wstring msg(static_cast<wchar_t*>(cds->lpData),
  94. cds->cbData / sizeof(wchar_t));
  95. const std::wstring::size_type first_null = msg.find_first_of(L'\0');
  96. if (first_null == 0 || first_null == std::wstring::npos) {
  97. // no NULL byte, don't know what to do
  98. LOG(WARNING) << "Invalid WM_COPYDATA, length = " << msg.length()
  99. << ", first null = " << first_null;
  100. return false;
  101. }
  102. // Decode the command, which is everything until the first NULL.
  103. if (msg.substr(0, first_null) == L"START") {
  104. // Another instance is starting parse the command line & do what it would
  105. // have done.
  106. VLOG(1) << "Handling STARTUP request from another process";
  107. const std::wstring::size_type second_null =
  108. msg.find_first_of(L'\0', first_null + 1);
  109. if (second_null == std::wstring::npos || first_null == msg.length() - 1 ||
  110. second_null == msg.length()) {
  111. LOG(WARNING) << "Invalid format for start command, we need a string in 4 "
  112. "parts separated by NULLs";
  113. return false;
  114. }
  115. // Get current directory.
  116. *current_directory =
  117. base::FilePath(msg.substr(first_null + 1, second_null - first_null));
  118. const std::wstring::size_type third_null =
  119. msg.find_first_of(L'\0', second_null + 1);
  120. if (third_null == std::wstring::npos || third_null == msg.length()) {
  121. LOG(WARNING) << "Invalid format for start command, we need a string in 4 "
  122. "parts separated by NULLs";
  123. }
  124. // Get command line.
  125. const std::wstring cmd_line =
  126. msg.substr(second_null + 1, third_null - second_null);
  127. *parsed_command_line = CommandLineStringToArgv(cmd_line);
  128. return true;
  129. }
  130. return false;
  131. }
  132. bool ProcessLaunchNotification(
  133. const ProcessSingleton::NotificationCallback& notification_callback,
  134. UINT message,
  135. WPARAM wparam,
  136. LPARAM lparam,
  137. LRESULT* result) {
  138. if (message != WM_COPYDATA)
  139. return false;
  140. // Handle the WM_COPYDATA message from another process.
  141. const COPYDATASTRUCT* cds = reinterpret_cast<COPYDATASTRUCT*>(lparam);
  142. base::CommandLine::StringVector parsed_command_line;
  143. base::FilePath current_directory;
  144. if (!ParseCommandLine(cds, &parsed_command_line, &current_directory)) {
  145. *result = TRUE;
  146. return true;
  147. }
  148. *result = notification_callback.Run(parsed_command_line, current_directory)
  149. ? TRUE
  150. : FALSE;
  151. return true;
  152. }
  153. bool TerminateAppWithError() {
  154. // TODO: This is called when the secondary process can't ping the primary
  155. // process. Need to find out what to do here.
  156. return false;
  157. }
  158. } // namespace
  159. ProcessSingleton::ProcessSingleton(
  160. const base::FilePath& user_data_dir,
  161. const NotificationCallback& notification_callback)
  162. : notification_callback_(notification_callback),
  163. is_virtualized_(false),
  164. lock_file_(INVALID_HANDLE_VALUE),
  165. user_data_dir_(user_data_dir),
  166. should_kill_remote_process_callback_(base::Bind(&TerminateAppWithError)) {
  167. // The user_data_dir may have not been created yet.
  168. base::CreateDirectoryAndGetError(user_data_dir, nullptr);
  169. }
  170. ProcessSingleton::~ProcessSingleton() {
  171. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  172. if (lock_file_ != INVALID_HANDLE_VALUE)
  173. ::CloseHandle(lock_file_);
  174. }
  175. // Code roughly based on Mozilla.
  176. ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() {
  177. if (is_virtualized_)
  178. return PROCESS_NOTIFIED; // We already spawned the process in this case.
  179. if (lock_file_ == INVALID_HANDLE_VALUE && !remote_window_) {
  180. return LOCK_ERROR;
  181. } else if (!remote_window_) {
  182. return PROCESS_NONE;
  183. }
  184. switch (chrome::AttemptToNotifyRunningChrome(remote_window_, false)) {
  185. case chrome::NOTIFY_SUCCESS:
  186. return PROCESS_NOTIFIED;
  187. case chrome::NOTIFY_FAILED:
  188. remote_window_ = NULL;
  189. return PROCESS_NONE;
  190. case chrome::NOTIFY_WINDOW_HUNG:
  191. // Fall through and potentially terminate the hung browser.
  192. break;
  193. }
  194. DWORD process_id = 0;
  195. DWORD thread_id = ::GetWindowThreadProcessId(remote_window_, &process_id);
  196. if (!thread_id || !process_id) {
  197. remote_window_ = NULL;
  198. return PROCESS_NONE;
  199. }
  200. base::Process process = base::Process::Open(process_id);
  201. // The window is hung. Scan for every window to find a visible one.
  202. bool visible_window = false;
  203. ::EnumThreadWindows(thread_id, &BrowserWindowEnumeration,
  204. reinterpret_cast<LPARAM>(&visible_window));
  205. // If there is a visible browser window, ask the user before killing it.
  206. if (visible_window && !should_kill_remote_process_callback_.Run()) {
  207. // The user denied. Quit silently.
  208. return PROCESS_NOTIFIED;
  209. }
  210. // Time to take action. Kill the browser process.
  211. process.Terminate(content::RESULT_CODE_HUNG, true);
  212. remote_window_ = NULL;
  213. return PROCESS_NONE;
  214. }
  215. ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessOrCreate() {
  216. ProcessSingleton::NotifyResult result = PROCESS_NONE;
  217. if (!Create()) {
  218. result = NotifyOtherProcess();
  219. if (result == PROCESS_NONE)
  220. result = PROFILE_IN_USE;
  221. }
  222. return result;
  223. }
  224. void ProcessSingleton::StartListeningOnSocket() {}
  225. void ProcessSingleton::OnBrowserReady() {}
  226. // Look for a Chrome instance that uses the same profile directory. If there
  227. // isn't one, create a message window with its title set to the profile
  228. // directory path.
  229. bool ProcessSingleton::Create() {
  230. static const wchar_t kMutexName[] = L"Local\\AtomProcessSingletonStartup!";
  231. remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_);
  232. if (!remote_window_) {
  233. // Make sure we will be the one and only process creating the window.
  234. // We use a named Mutex since we are protecting against multi-process
  235. // access. As documented, it's clearer to NOT request ownership on creation
  236. // since it isn't guaranteed we will get it. It is better to create it
  237. // without ownership and explicitly get the ownership afterward.
  238. base::win::ScopedHandle only_me(::CreateMutex(NULL, FALSE, kMutexName));
  239. if (!only_me.IsValid()) {
  240. DPLOG(FATAL) << "CreateMutex failed";
  241. return false;
  242. }
  243. AutoLockMutex auto_lock_only_me(only_me.Get());
  244. // We now own the mutex so we are the only process that can create the
  245. // window at this time, but we must still check if someone created it
  246. // between the time where we looked for it above and the time the mutex
  247. // was given to us.
  248. remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_);
  249. if (!remote_window_) {
  250. // We have to make sure there is no Chrome instance running on another
  251. // machine that uses the same profile.
  252. base::FilePath lock_file_path = user_data_dir_.AppendASCII(kLockfile);
  253. lock_file_ =
  254. ::CreateFile(lock_file_path.value().c_str(), GENERIC_WRITE,
  255. FILE_SHARE_READ, NULL, CREATE_ALWAYS,
  256. FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);
  257. DWORD error = ::GetLastError();
  258. LOG_IF(WARNING, lock_file_ != INVALID_HANDLE_VALUE &&
  259. error == ERROR_ALREADY_EXISTS)
  260. << "Lock file exists but is writable.";
  261. LOG_IF(ERROR, lock_file_ == INVALID_HANDLE_VALUE)
  262. << "Lock file can not be created! Error code: " << error;
  263. if (lock_file_ != INVALID_HANDLE_VALUE) {
  264. // Set the window's title to the path of our user data directory so
  265. // other Chrome instances can decide if they should forward to us.
  266. bool result = window_.CreateNamed(
  267. base::Bind(&ProcessLaunchNotification, notification_callback_),
  268. user_data_dir_.value());
  269. // NB: Ensure that if the primary app gets started as elevated
  270. // admin inadvertently, secondary windows running not as elevated
  271. // will still be able to send messages
  272. ::ChangeWindowMessageFilterEx(window_.hwnd(), WM_COPYDATA, MSGFLT_ALLOW,
  273. NULL);
  274. CHECK(result && window_.hwnd());
  275. }
  276. }
  277. }
  278. return window_.hwnd() != NULL;
  279. }
  280. void ProcessSingleton::Cleanup() {}
  281. void ProcessSingleton::OverrideShouldKillRemoteProcessCallbackForTesting(
  282. const ShouldKillRemoteProcessCallback& display_dialog_callback) {
  283. should_kill_remote_process_callback_ = display_dialog_callback;
  284. }