1 // Copyright 2014 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.
5 #include "chrome/browser/process_singleton.h"
11 #include <sys/types.h>
20 #include "base/bind.h"
21 #include "base/command_line.h"
22 #include "base/files/file_path.h"
23 #include "base/files/file_util.h"
24 #include "base/files/scoped_temp_dir.h"
25 #include "base/location.h"
26 #include "base/posix/eintr_wrapper.h"
27 #include "base/single_thread_task_runner.h"
28 #include "base/strings/stringprintf.h"
29 #include "base/synchronization/waitable_event.h"
30 #include "base/test/metrics/histogram_tester.h"
31 #include "base/test/test_timeouts.h"
32 #include "base/test/thread_test_helper.h"
33 #include "base/threading/thread.h"
34 #include "build/build_config.h"
35 #include "chrome/common/chrome_constants.h"
36 #include "content/public/browser/browser_task_traits.h"
37 #include "content/public/browser/browser_thread.h"
38 #include "content/public/test/browser_task_environment.h"
39 #include "net/base/network_interfaces.h"
40 #include "testing/gtest/include/gtest/gtest.h"
44 class ProcessSingletonPosixTest : public testing::Test {
46 // A ProcessSingleton exposing some protected methods for testing.
47 class TestableProcessSingleton : public ProcessSingleton {
49 explicit TestableProcessSingleton(const base::FilePath& user_data_dir)
52 base::Bind(&TestableProcessSingleton::NotificationCallback,
53 base::Unretained(this))) {}
55 std::vector<base::CommandLine::StringVector> callback_command_lines_;
57 using ProcessSingleton::NotifyOtherProcessWithTimeout;
58 using ProcessSingleton::NotifyOtherProcessWithTimeoutOrCreate;
59 using ProcessSingleton::OverrideCurrentPidForTesting;
60 using ProcessSingleton::OverrideKillCallbackForTesting;
63 bool NotificationCallback(const base::CommandLine& command_line,
64 const base::FilePath& current_directory) {
65 callback_command_lines_.push_back(command_line.argv());
70 ProcessSingletonPosixTest()
72 task_environment_(content::BrowserTaskEnvironment::REAL_IO_THREAD),
73 wait_event_(base::WaitableEvent::ResetPolicy::MANUAL,
74 base::WaitableEvent::InitialState::NOT_SIGNALED),
75 signal_event_(base::WaitableEvent::ResetPolicy::MANUAL,
76 base::WaitableEvent::InitialState::NOT_SIGNALED),
77 process_singleton_on_thread_(nullptr) {}
79 void SetUp() override {
80 testing::Test::SetUp();
82 ProcessSingleton::DisablePromptForTesting();
83 ProcessSingleton::SkipIsChromeProcessCheckForTesting(false);
84 ProcessSingleton::SetUserOptedUnlockInUseProfileForTesting(false);
85 // Put the lock in a temporary directory. Doesn't need to be a
86 // full profile to test this code.
87 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
88 // Use a long directory name to ensure that the socket isn't opened through
90 user_data_path_ = temp_dir_.GetPath().Append(
91 std::string(sizeof(sockaddr_un::sun_path), 'a'));
92 ASSERT_TRUE(CreateDirectory(user_data_path_));
94 lock_path_ = user_data_path_.Append(chrome::kSingletonLockFilename);
95 socket_path_ = user_data_path_.Append(chrome::kSingletonSocketFilename);
96 cookie_path_ = user_data_path_.Append(chrome::kSingletonCookieFilename);
99 void TearDown() override {
100 scoped_refptr<base::ThreadTestHelper> io_helper(
101 new base::ThreadTestHelper(content::GetIOThreadTaskRunner({}).get()));
102 ASSERT_TRUE(io_helper->Run());
104 // Destruct the ProcessSingleton object before the IO thread so that its
105 // internals are destructed properly.
106 if (process_singleton_on_thread_) {
107 worker_thread_->task_runner()->PostTask(
109 base::BindOnce(&ProcessSingletonPosixTest::DestructProcessSingleton,
110 base::Unretained(this)));
112 scoped_refptr<base::ThreadTestHelper> helper(
113 new base::ThreadTestHelper(worker_thread_->task_runner().get()));
114 ASSERT_TRUE(helper->Run());
117 testing::Test::TearDown();
120 void CreateProcessSingletonOnThread() {
121 ASSERT_FALSE(worker_thread_.get());
122 worker_thread_.reset(new base::Thread("BlockingThread"));
123 worker_thread_->Start();
125 worker_thread_->task_runner()->PostTask(
128 &ProcessSingletonPosixTest::CreateProcessSingletonInternal,
129 base::Unretained(this)));
131 scoped_refptr<base::ThreadTestHelper> helper(
132 new base::ThreadTestHelper(worker_thread_->task_runner().get()));
133 ASSERT_TRUE(helper->Run());
136 TestableProcessSingleton* CreateProcessSingleton() {
137 return new TestableProcessSingleton(user_data_path_);
142 ASSERT_EQ(0, lstat(lock_path_.value().c_str(), &statbuf));
143 ASSERT_TRUE(S_ISLNK(statbuf.st_mode));
145 ssize_t len = readlink(lock_path_.value().c_str(), buf, PATH_MAX);
148 ASSERT_EQ(0, lstat(socket_path_.value().c_str(), &statbuf));
149 ASSERT_TRUE(S_ISLNK(statbuf.st_mode));
151 len = readlink(socket_path_.value().c_str(), buf, PATH_MAX);
153 base::FilePath socket_target_path = base::FilePath(std::string(buf, len));
155 ASSERT_EQ(0, lstat(socket_target_path.value().c_str(), &statbuf));
156 ASSERT_TRUE(S_ISSOCK(statbuf.st_mode));
158 len = readlink(cookie_path_.value().c_str(), buf, PATH_MAX);
160 std::string cookie(buf, len);
162 base::FilePath remote_cookie_path = socket_target_path.DirName().
163 Append(chrome::kSingletonCookieFilename);
164 len = readlink(remote_cookie_path.value().c_str(), buf, PATH_MAX);
166 EXPECT_EQ(cookie, std::string(buf, len));
169 ProcessSingleton::NotifyResult NotifyOtherProcess(bool override_kill) {
170 std::unique_ptr<TestableProcessSingleton> process_singleton(
171 CreateProcessSingleton());
172 base::CommandLine command_line(
173 base::CommandLine::ForCurrentProcess()->GetProgram());
174 command_line.AppendArg("about:blank");
176 process_singleton->OverrideCurrentPidForTesting(
177 base::GetCurrentProcId() + 1);
178 process_singleton->OverrideKillCallbackForTesting(
179 base::Bind(&ProcessSingletonPosixTest::KillCallback,
180 base::Unretained(this)));
183 return process_singleton->NotifyOtherProcessWithTimeout(
184 command_line, kRetryAttempts, timeout(), true);
187 // A helper method to call ProcessSingleton::NotifyOtherProcessOrCreate().
188 ProcessSingleton::NotifyResult NotifyOtherProcessOrCreate(
189 const std::string& url) {
190 std::unique_ptr<TestableProcessSingleton> process_singleton(
191 CreateProcessSingleton());
192 base::CommandLine command_line(
193 base::CommandLine::ForCurrentProcess()->GetProgram());
194 command_line.AppendArg(url);
195 return process_singleton->NotifyOtherProcessWithTimeoutOrCreate(
196 command_line, kRetryAttempts, timeout());
199 void CheckNotified() {
200 ASSERT_TRUE(process_singleton_on_thread_);
201 ASSERT_EQ(1u, process_singleton_on_thread_->callback_command_lines_.size());
204 i < process_singleton_on_thread_->callback_command_lines_[0].size();
206 if (process_singleton_on_thread_->callback_command_lines_[0][i] ==
213 ASSERT_EQ(0, kill_callbacks_);
216 void BlockWorkerThread() {
217 worker_thread_->task_runner()->PostTask(
218 FROM_HERE, base::BindOnce(&ProcessSingletonPosixTest::BlockThread,
219 base::Unretained(this)));
222 void UnblockWorkerThread() {
223 wait_event_.Signal(); // Unblock the worker thread for shutdown.
224 signal_event_.Wait(); // Ensure thread unblocks before continuing.
229 signal_event_.Signal();
232 base::FilePath user_data_path_;
233 base::FilePath lock_path_;
234 base::FilePath socket_path_;
235 base::FilePath cookie_path_;
239 static const int kRetryAttempts = 2;
241 base::TimeDelta timeout() const {
242 return TestTimeouts::tiny_timeout() * kRetryAttempts;
245 void CreateProcessSingletonInternal() {
246 ASSERT_TRUE(!process_singleton_on_thread_);
247 process_singleton_on_thread_ = CreateProcessSingleton();
248 ASSERT_EQ(ProcessSingleton::PROCESS_NONE,
249 process_singleton_on_thread_->NotifyOtherProcessOrCreate());
252 void DestructProcessSingleton() {
253 ASSERT_TRUE(process_singleton_on_thread_);
254 delete process_singleton_on_thread_;
257 void KillCallback(int pid) {
261 content::BrowserTaskEnvironment task_environment_;
262 base::ScopedTempDir temp_dir_;
263 base::WaitableEvent wait_event_;
264 base::WaitableEvent signal_event_;
266 std::unique_ptr<base::Thread> worker_thread_;
267 TestableProcessSingleton* process_singleton_on_thread_;
272 // Test if the socket file and symbol link created by ProcessSingletonPosix
274 // If this test flakes, use http://crbug.com/74554.
275 TEST_F(ProcessSingletonPosixTest, CheckSocketFile) {
276 CreateProcessSingletonOnThread();
280 // TODO(james.su@gmail.com): port following tests to Windows.
281 // Test success case of NotifyOtherProcess().
282 TEST_F(ProcessSingletonPosixTest, NotifyOtherProcessSuccess) {
283 CreateProcessSingletonOnThread();
284 EXPECT_EQ(ProcessSingleton::PROCESS_NOTIFIED, NotifyOtherProcess(true));
288 // Test failure case of NotifyOtherProcess().
289 TEST_F(ProcessSingletonPosixTest, NotifyOtherProcessFailure) {
290 base::HistogramTester histogram_tester;
291 CreateProcessSingletonOnThread();
294 EXPECT_EQ(ProcessSingleton::PROCESS_NONE, NotifyOtherProcess(true));
295 ASSERT_EQ(1, kill_callbacks_);
296 UnblockWorkerThread();
297 histogram_tester.ExpectUniqueSample(
298 "Chrome.ProcessSingleton.RemoteHungProcessTerminateReason",
299 ProcessSingleton::SOCKET_READ_FAILED, 1u);
302 // Test that we don't kill ourselves by accident if a lockfile with the same pid
304 TEST_F(ProcessSingletonPosixTest, NotifyOtherProcessNoSuicide) {
305 base::HistogramTester histogram_tester;
306 CreateProcessSingletonOnThread();
307 // Replace lockfile with one containing our own pid.
308 EXPECT_EQ(0, unlink(lock_path_.value().c_str()));
309 std::string symlink_content = base::StringPrintf(
311 net::GetHostName().c_str(),
313 base::GetCurrentProcId());
314 EXPECT_EQ(0, symlink(symlink_content.c_str(), lock_path_.value().c_str()));
316 // Remove socket so that we will not be able to notify the existing browser.
317 EXPECT_EQ(0, unlink(socket_path_.value().c_str()));
319 // Pretend we are browser process.
320 ProcessSingleton::SkipIsChromeProcessCheckForTesting(true);
322 EXPECT_EQ(ProcessSingleton::PROCESS_NONE, NotifyOtherProcess(false));
323 // If we've gotten to this point without killing ourself, the test succeeded.
324 histogram_tester.ExpectUniqueSample(
325 "Chrome.ProcessSingleton.RemoteProcessInteractionResult",
326 ProcessSingleton::SAME_BROWSER_INSTANCE, 1u);
329 // Test that we can still notify a process on the same host even after the
331 TEST_F(ProcessSingletonPosixTest, NotifyOtherProcessHostChanged) {
332 CreateProcessSingletonOnThread();
333 EXPECT_EQ(0, unlink(lock_path_.value().c_str()));
334 EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_.value().c_str()));
336 EXPECT_EQ(ProcessSingleton::PROCESS_NOTIFIED, NotifyOtherProcess(false));
340 // Test that we kill hung browser when lock says process is on another host and
341 // we can't notify it over the socket.
342 TEST_F(ProcessSingletonPosixTest, NotifyOtherProcessDifferingHost) {
343 base::HistogramTester histogram_tester;
344 CreateProcessSingletonOnThread();
348 EXPECT_EQ(0, unlink(lock_path_.value().c_str()));
349 EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_.value().c_str()));
351 EXPECT_EQ(ProcessSingleton::PROCESS_NONE, NotifyOtherProcess(true));
352 ASSERT_EQ(1, kill_callbacks_);
354 // lock_path_ should be unlinked in NotifyOtherProcess().
355 base::FilePath target_path;
356 EXPECT_FALSE(base::ReadSymbolicLink(lock_path_, &target_path));
358 UnblockWorkerThread();
360 histogram_tester.ExpectUniqueSample(
361 "Chrome.ProcessSingleton.RemoteHungProcessTerminateReason",
362 ProcessSingleton::SOCKET_READ_FAILED, 1u);
365 // Test that we'll start creating ProcessSingleton when we have old lock file
366 // that says process is on another host and there is browser with the same pid
367 // but with another user data dir. Also suppose that user opted to unlock
369 TEST_F(ProcessSingletonPosixTest,
370 NotifyOtherProcessDifferingHost_UnlockedProfileBeforeKill) {
371 base::HistogramTester histogram_tester;
372 CreateProcessSingletonOnThread();
376 EXPECT_EQ(0, unlink(lock_path_.value().c_str()));
377 EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_.value().c_str()));
379 // Remove socket so that we will not be able to notify the existing browser.
380 EXPECT_EQ(0, unlink(socket_path_.value().c_str()));
382 // Unlock profile that was locked by process on another host.
383 ProcessSingleton::SetUserOptedUnlockInUseProfileForTesting(true);
384 // Treat process with pid 1234 as browser with different user data dir.
385 ProcessSingleton::SkipIsChromeProcessCheckForTesting(true);
387 EXPECT_EQ(ProcessSingleton::PROCESS_NONE, NotifyOtherProcess(false));
389 // lock_path_ should be unlinked in NotifyOtherProcess().
390 base::FilePath target_path;
391 EXPECT_FALSE(base::ReadSymbolicLink(lock_path_, &target_path));
393 UnblockWorkerThread();
395 histogram_tester.ExpectUniqueSample(
396 "Chrome.ProcessSingleton.RemoteHungProcessTerminateReason",
397 ProcessSingleton::NOTIFY_ATTEMPTS_EXCEEDED, 1u);
398 histogram_tester.ExpectUniqueSample(
399 "Chrome.ProcessSingleton.RemoteProcessInteractionResult",
400 ProcessSingleton::PROFILE_UNLOCKED_BEFORE_KILL, 1u);
403 // Test that we unlock profile when lock says process is on another host and we
404 // can't notify it over the socket.
405 TEST_F(ProcessSingletonPosixTest, NotifyOtherProcessOrCreate_DifferingHost) {
406 base::HistogramTester histogram_tester;
407 CreateProcessSingletonOnThread();
411 EXPECT_EQ(0, unlink(lock_path_.value().c_str()));
412 EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_.value().c_str()));
414 // Remove socket so that we will not be able to notify the existing browser.
415 EXPECT_EQ(0, unlink(socket_path_.value().c_str()));
416 // Unlock profile that was locked by process on another host.
417 ProcessSingleton::SetUserOptedUnlockInUseProfileForTesting(true);
419 std::string url("about:blank");
420 EXPECT_EQ(ProcessSingleton::PROCESS_NONE, NotifyOtherProcessOrCreate(url));
422 ASSERT_EQ(0, unlink(lock_path_.value().c_str()));
424 UnblockWorkerThread();
426 histogram_tester.ExpectUniqueSample(
427 "Chrome.ProcessSingleton.RemoteProcessInteractionResult",
428 ProcessSingleton::PROFILE_UNLOCKED, 1u);
431 // Test that Create fails when another browser is using the profile directory.
432 TEST_F(ProcessSingletonPosixTest, CreateFailsWithExistingBrowser) {
433 CreateProcessSingletonOnThread();
435 std::unique_ptr<TestableProcessSingleton> process_singleton(
436 CreateProcessSingleton());
437 process_singleton->OverrideCurrentPidForTesting(base::GetCurrentProcId() + 1);
438 EXPECT_FALSE(process_singleton->Create());
441 // Test that Create fails when another browser is using the profile directory
442 // but with the old socket location.
443 TEST_F(ProcessSingletonPosixTest, CreateChecksCompatibilitySocket) {
444 CreateProcessSingletonOnThread();
445 std::unique_ptr<TestableProcessSingleton> process_singleton(
446 CreateProcessSingleton());
447 process_singleton->OverrideCurrentPidForTesting(base::GetCurrentProcId() + 1);
449 // Do some surgery so as to look like the old configuration.
451 ssize_t len = readlink(socket_path_.value().c_str(), buf, sizeof(buf));
453 base::FilePath socket_target_path = base::FilePath(std::string(buf, len));
454 ASSERT_EQ(0, unlink(socket_path_.value().c_str()));
455 ASSERT_EQ(0, rename(socket_target_path.value().c_str(),
456 socket_path_.value().c_str()));
457 ASSERT_EQ(0, unlink(cookie_path_.value().c_str()));
459 EXPECT_FALSE(process_singleton->Create());
462 // Test that we fail when lock says process is on another host and we can't
463 // notify it over the socket before of a bad cookie.
464 TEST_F(ProcessSingletonPosixTest, NotifyOtherProcessOrCreate_BadCookie) {
465 CreateProcessSingletonOnThread();
466 // Change the cookie.
467 EXPECT_EQ(0, unlink(cookie_path_.value().c_str()));
468 EXPECT_EQ(0, symlink("INCORRECTCOOKIE", cookie_path_.value().c_str()));
470 // Also change the hostname, so the remote does not retry.
471 EXPECT_EQ(0, unlink(lock_path_.value().c_str()));
472 EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_.value().c_str()));
474 std::string url("about:blank");
475 EXPECT_EQ(ProcessSingleton::PROFILE_IN_USE, NotifyOtherProcessOrCreate(url));
478 TEST_F(ProcessSingletonPosixTest, IgnoreSocketSymlinkWithTooLongTarget) {
479 base::HistogramTester histogram_tester;
480 CreateProcessSingletonOnThread();
481 // Change the symlink to one with a too-long target.
483 ssize_t len = readlink(socket_path_.value().c_str(), buf, PATH_MAX);
485 base::FilePath socket_target_path = base::FilePath(std::string(buf, len));
486 base::FilePath long_socket_target_path = socket_target_path.DirName().Append(
487 std::string(sizeof(sockaddr_un::sun_path), 'b'));
488 ASSERT_EQ(0, unlink(socket_path_.value().c_str()));
489 ASSERT_EQ(0, symlink(long_socket_target_path.value().c_str(),
490 socket_path_.value().c_str()));
492 // A new ProcessSingleton should ignore the invalid socket path target.
493 std::string url("about:blank");
494 EXPECT_EQ(ProcessSingleton::PROCESS_NONE, NotifyOtherProcessOrCreate(url));
496 // Lock file contains PID of unit_tests process. It is non browser process so
497 // we treat lock file as orphaned.
498 histogram_tester.ExpectUniqueSample(
499 "Chrome.ProcessSingleton.RemoteProcessInteractionResult",
500 ProcessSingleton::ORPHANED_LOCK_FILE, 1u);
503 #if defined(OS_MACOSX)
504 // Test that if there is an existing lock file, and we could not flock()
506 TEST_F(ProcessSingletonPosixTest, CreateRespectsOldMacLock) {
507 std::unique_ptr<TestableProcessSingleton> process_singleton(
508 CreateProcessSingleton());
509 base::ScopedFD lock_fd(HANDLE_EINTR(
510 open(lock_path_.value().c_str(), O_RDWR | O_CREAT | O_EXLOCK, 0644)));
511 ASSERT_TRUE(lock_fd.is_valid());
512 EXPECT_FALSE(process_singleton->Create());
513 base::File::Info info;
514 EXPECT_TRUE(base::GetFileInfo(lock_path_, &info));
515 EXPECT_FALSE(info.is_directory);
516 EXPECT_FALSE(info.is_symbolic_link);
519 // Test that if there is an existing lock file, and it's not locked, we replace
521 TEST_F(ProcessSingletonPosixTest, CreateReplacesOldMacLock) {
522 std::unique_ptr<TestableProcessSingleton> process_singleton(
523 CreateProcessSingleton());
524 EXPECT_EQ(0, base::WriteFile(lock_path_, "", 0));
525 EXPECT_TRUE(process_singleton->Create());
528 #endif // defined(OS_MACOSX)