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 <signal.h> 8 #include <sys/types.h> 9 #include <sys/wait.h> 10 #include <unistd.h> 11 12 #include <string> 13 #include <vector> 14 15 #include "base/bind.h" 16 #include "base/command_line.h" 17 #include "base/files/file_path.h" 18 #include "base/files/scoped_temp_dir.h" 19 #include "base/message_loop/message_loop.h" 20 #include "base/strings/stringprintf.h" 21 #include "base/synchronization/waitable_event.h" 22 #include "base/test/test_timeouts.h" 23 #include "base/test/thread_test_helper.h" 24 #include "base/threading/thread.h" 25 #include "chrome/common/chrome_constants.h" 26 #include "content/public/test/test_browser_thread.h" 27 #include "net/base/net_util.h" 28 #include "testing/gtest/include/gtest/gtest.h" 29 30 using content::BrowserThread; 31 32 namespace { 33 34 class ProcessSingletonLinuxTest : public testing::Test { 35 public: 36 // A ProcessSingleton exposing some protected methods for testing. 37 class TestableProcessSingleton : public ProcessSingleton { 38 public: 39 explicit TestableProcessSingleton(const base::FilePath& user_data_dir) 40 : ProcessSingleton( 41 user_data_dir, 42 base::Bind(&TestableProcessSingleton::NotificationCallback, 43 base::Unretained(this))) {} 44 45 46 std::vector<CommandLine::StringVector> callback_command_lines_; 47 48 using ProcessSingleton::NotifyOtherProcessWithTimeout; 49 using ProcessSingleton::NotifyOtherProcessWithTimeoutOrCreate; 50 using ProcessSingleton::OverrideCurrentPidForTesting; 51 using ProcessSingleton::OverrideKillCallbackForTesting; 52 53 private: 54 bool NotificationCallback(const CommandLine& command_line, 55 const base::FilePath& current_directory) { 56 callback_command_lines_.push_back(command_line.argv()); 57 return true; 58 } 59 }; 60 61 ProcessSingletonLinuxTest() 62 : kill_callbacks_(0), 63 io_thread_(BrowserThread::IO), 64 wait_event_(true, false), 65 signal_event_(true, false), 66 process_singleton_on_thread_(NULL) { 67 io_thread_.StartIOThread(); 68 } 69 70 virtual void SetUp() { 71 testing::Test::SetUp(); 72 73 ProcessSingleton::DisablePromptForTesting(); 74 // Put the lock in a temporary directory. Doesn't need to be a 75 // full profile to test this code. 76 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); 77 lock_path_ = temp_dir_.path().Append(chrome::kSingletonLockFilename); 78 socket_path_ = temp_dir_.path().Append(chrome::kSingletonSocketFilename); 79 cookie_path_ = temp_dir_.path().Append(chrome::kSingletonCookieFilename); 80 } 81 82 virtual void TearDown() { 83 scoped_refptr<base::ThreadTestHelper> io_helper(new base::ThreadTestHelper( 84 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO).get())); 85 ASSERT_TRUE(io_helper->Run()); 86 87 // Destruct the ProcessSingleton object before the IO thread so that its 88 // internals are destructed properly. 89 if (process_singleton_on_thread_) { 90 worker_thread_->message_loop()->PostTask( 91 FROM_HERE, 92 base::Bind(&ProcessSingletonLinuxTest::DestructProcessSingleton, 93 base::Unretained(this))); 94 95 scoped_refptr<base::ThreadTestHelper> helper(new base::ThreadTestHelper( 96 worker_thread_->message_loop_proxy().get())); 97 ASSERT_TRUE(helper->Run()); 98 } 99 100 io_thread_.Stop(); 101 testing::Test::TearDown(); 102 } 103 104 void CreateProcessSingletonOnThread() { 105 ASSERT_EQ(NULL, worker_thread_.get()); 106 worker_thread_.reset(new base::Thread("BlockingThread")); 107 worker_thread_->Start(); 108 109 worker_thread_->message_loop()->PostTask( 110 FROM_HERE, 111 base::Bind(&ProcessSingletonLinuxTest:: 112 CreateProcessSingletonInternal, 113 base::Unretained(this))); 114 115 scoped_refptr<base::ThreadTestHelper> helper( 116 new base::ThreadTestHelper(worker_thread_->message_loop_proxy().get())); 117 ASSERT_TRUE(helper->Run()); 118 } 119 120 TestableProcessSingleton* CreateProcessSingleton() { 121 return new TestableProcessSingleton(temp_dir_.path()); 122 } 123 124 ProcessSingleton::NotifyResult NotifyOtherProcess( 125 bool override_kill, 126 base::TimeDelta timeout) { 127 scoped_ptr<TestableProcessSingleton> process_singleton( 128 CreateProcessSingleton()); 129 CommandLine command_line(CommandLine::ForCurrentProcess()->GetProgram()); 130 command_line.AppendArg("about:blank"); 131 if (override_kill) { 132 process_singleton->OverrideCurrentPidForTesting( 133 base::GetCurrentProcId() + 1); 134 process_singleton->OverrideKillCallbackForTesting( 135 base::Bind(&ProcessSingletonLinuxTest::KillCallback, 136 base::Unretained(this))); 137 } 138 139 return process_singleton->NotifyOtherProcessWithTimeout( 140 command_line, timeout.InSeconds(), true); 141 } 142 143 // A helper method to call ProcessSingleton::NotifyOtherProcessOrCreate(). 144 ProcessSingleton::NotifyResult NotifyOtherProcessOrCreate( 145 const std::string& url, 146 base::TimeDelta timeout) { 147 scoped_ptr<TestableProcessSingleton> process_singleton( 148 CreateProcessSingleton()); 149 CommandLine command_line(CommandLine::ForCurrentProcess()->GetProgram()); 150 command_line.AppendArg(url); 151 return process_singleton->NotifyOtherProcessWithTimeoutOrCreate( 152 command_line, timeout.InSeconds()); 153 } 154 155 void CheckNotified() { 156 ASSERT_TRUE(process_singleton_on_thread_ != NULL); 157 ASSERT_EQ(1u, process_singleton_on_thread_->callback_command_lines_.size()); 158 bool found = false; 159 for (size_t i = 0; 160 i < process_singleton_on_thread_->callback_command_lines_[0].size(); 161 ++i) { 162 if (process_singleton_on_thread_->callback_command_lines_[0][i] == 163 "about:blank") { 164 found = true; 165 break; 166 } 167 } 168 ASSERT_TRUE(found); 169 ASSERT_EQ(0, kill_callbacks_); 170 } 171 172 void BlockWorkerThread() { 173 worker_thread_->message_loop()->PostTask( 174 FROM_HERE, 175 base::Bind(&ProcessSingletonLinuxTest::BlockThread, 176 base::Unretained(this))); 177 } 178 179 void UnblockWorkerThread() { 180 wait_event_.Signal(); // Unblock the worker thread for shutdown. 181 signal_event_.Wait(); // Ensure thread unblocks before continuing. 182 } 183 184 void BlockThread() { 185 wait_event_.Wait(); 186 signal_event_.Signal(); 187 } 188 189 base::FilePath lock_path_; 190 base::FilePath socket_path_; 191 base::FilePath cookie_path_; 192 int kill_callbacks_; 193 194 private: 195 void CreateProcessSingletonInternal() { 196 ASSERT_TRUE(!process_singleton_on_thread_); 197 process_singleton_on_thread_ = CreateProcessSingleton(); 198 ASSERT_EQ(ProcessSingleton::PROCESS_NONE, 199 process_singleton_on_thread_->NotifyOtherProcessOrCreate()); 200 } 201 202 void DestructProcessSingleton() { 203 ASSERT_TRUE(process_singleton_on_thread_); 204 delete process_singleton_on_thread_; 205 } 206 207 void KillCallback(int pid) { 208 kill_callbacks_++; 209 } 210 211 content::TestBrowserThread io_thread_; 212 base::ScopedTempDir temp_dir_; 213 base::WaitableEvent wait_event_; 214 base::WaitableEvent signal_event_; 215 216 scoped_ptr<base::Thread> worker_thread_; 217 TestableProcessSingleton* process_singleton_on_thread_; 218 }; 219 220 } // namespace 221 222 // Test if the socket file and symbol link created by ProcessSingletonLinux 223 // are valid. 224 // If this test flakes, use http://crbug.com/74554. 225 TEST_F(ProcessSingletonLinuxTest, CheckSocketFile) { 226 CreateProcessSingletonOnThread(); 227 struct stat statbuf; 228 ASSERT_EQ(0, lstat(lock_path_.value().c_str(), &statbuf)); 229 ASSERT_TRUE(S_ISLNK(statbuf.st_mode)); 230 char buf[PATH_MAX]; 231 ssize_t len = readlink(lock_path_.value().c_str(), buf, PATH_MAX); 232 ASSERT_GT(len, 0); 233 234 ASSERT_EQ(0, lstat(socket_path_.value().c_str(), &statbuf)); 235 ASSERT_TRUE(S_ISLNK(statbuf.st_mode)); 236 237 len = readlink(socket_path_.value().c_str(), buf, PATH_MAX); 238 ASSERT_GT(len, 0); 239 base::FilePath socket_target_path = base::FilePath(std::string(buf, len)); 240 241 ASSERT_EQ(0, lstat(socket_target_path.value().c_str(), &statbuf)); 242 ASSERT_TRUE(S_ISSOCK(statbuf.st_mode)); 243 244 len = readlink(cookie_path_.value().c_str(), buf, PATH_MAX); 245 ASSERT_GT(len, 0); 246 std::string cookie(buf, len); 247 248 base::FilePath remote_cookie_path = socket_target_path.DirName(). 249 Append(chrome::kSingletonCookieFilename); 250 len = readlink(remote_cookie_path.value().c_str(), buf, PATH_MAX); 251 ASSERT_GT(len, 0); 252 EXPECT_EQ(cookie, std::string(buf, len)); 253 } 254 255 // TODO(james.su (at) gmail.com): port following tests to Windows. 256 // Test success case of NotifyOtherProcess(). 257 TEST_F(ProcessSingletonLinuxTest, NotifyOtherProcessSuccess) { 258 CreateProcessSingletonOnThread(); 259 EXPECT_EQ(ProcessSingleton::PROCESS_NOTIFIED, 260 NotifyOtherProcess(true, TestTimeouts::action_timeout())); 261 CheckNotified(); 262 } 263 264 // Test failure case of NotifyOtherProcess(). 265 TEST_F(ProcessSingletonLinuxTest, NotifyOtherProcessFailure) { 266 CreateProcessSingletonOnThread(); 267 268 BlockWorkerThread(); 269 EXPECT_EQ(ProcessSingleton::PROCESS_NONE, 270 NotifyOtherProcess(true, TestTimeouts::action_timeout())); 271 272 ASSERT_EQ(1, kill_callbacks_); 273 UnblockWorkerThread(); 274 } 275 276 // Test that we don't kill ourselves by accident if a lockfile with the same pid 277 // happens to exist. 278 TEST_F(ProcessSingletonLinuxTest, NotifyOtherProcessNoSuicide) { 279 CreateProcessSingletonOnThread(); 280 // Replace lockfile with one containing our own pid. 281 EXPECT_EQ(0, unlink(lock_path_.value().c_str())); 282 std::string symlink_content = base::StringPrintf( 283 "%s%c%u", 284 net::GetHostName().c_str(), 285 '-', 286 base::GetCurrentProcId()); 287 EXPECT_EQ(0, symlink(symlink_content.c_str(), lock_path_.value().c_str())); 288 289 // Remove socket so that we will not be able to notify the existing browser. 290 EXPECT_EQ(0, unlink(socket_path_.value().c_str())); 291 292 EXPECT_EQ(ProcessSingleton::PROCESS_NONE, 293 NotifyOtherProcess(false, TestTimeouts::action_timeout())); 294 // If we've gotten to this point without killing ourself, the test succeeded. 295 } 296 297 // Test that we can still notify a process on the same host even after the 298 // hostname changed. 299 TEST_F(ProcessSingletonLinuxTest, NotifyOtherProcessHostChanged) { 300 CreateProcessSingletonOnThread(); 301 EXPECT_EQ(0, unlink(lock_path_.value().c_str())); 302 EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_.value().c_str())); 303 304 EXPECT_EQ(ProcessSingleton::PROCESS_NOTIFIED, 305 NotifyOtherProcess(false, TestTimeouts::action_timeout())); 306 CheckNotified(); 307 } 308 309 // Test that we fail when lock says process is on another host and we can't 310 // notify it over the socket. 311 TEST_F(ProcessSingletonLinuxTest, NotifyOtherProcessDifferingHost) { 312 CreateProcessSingletonOnThread(); 313 314 BlockWorkerThread(); 315 316 EXPECT_EQ(0, unlink(lock_path_.value().c_str())); 317 EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_.value().c_str())); 318 319 EXPECT_EQ(ProcessSingleton::PROFILE_IN_USE, 320 NotifyOtherProcess(false, TestTimeouts::action_timeout())); 321 322 ASSERT_EQ(0, unlink(lock_path_.value().c_str())); 323 324 UnblockWorkerThread(); 325 } 326 327 // Test that we fail when lock says process is on another host and we can't 328 // notify it over the socket. 329 TEST_F(ProcessSingletonLinuxTest, NotifyOtherProcessOrCreate_DifferingHost) { 330 CreateProcessSingletonOnThread(); 331 332 BlockWorkerThread(); 333 334 EXPECT_EQ(0, unlink(lock_path_.value().c_str())); 335 EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_.value().c_str())); 336 337 std::string url("about:blank"); 338 EXPECT_EQ(ProcessSingleton::PROFILE_IN_USE, 339 NotifyOtherProcessOrCreate(url, TestTimeouts::action_timeout())); 340 341 ASSERT_EQ(0, unlink(lock_path_.value().c_str())); 342 343 UnblockWorkerThread(); 344 } 345 346 // Test that Create fails when another browser is using the profile directory. 347 TEST_F(ProcessSingletonLinuxTest, CreateFailsWithExistingBrowser) { 348 CreateProcessSingletonOnThread(); 349 350 scoped_ptr<TestableProcessSingleton> process_singleton( 351 CreateProcessSingleton()); 352 process_singleton->OverrideCurrentPidForTesting(base::GetCurrentProcId() + 1); 353 EXPECT_FALSE(process_singleton->Create()); 354 } 355 356 // Test that Create fails when another browser is using the profile directory 357 // but with the old socket location. 358 TEST_F(ProcessSingletonLinuxTest, CreateChecksCompatibilitySocket) { 359 CreateProcessSingletonOnThread(); 360 scoped_ptr<TestableProcessSingleton> process_singleton( 361 CreateProcessSingleton()); 362 process_singleton->OverrideCurrentPidForTesting(base::GetCurrentProcId() + 1); 363 364 // Do some surgery so as to look like the old configuration. 365 char buf[PATH_MAX]; 366 ssize_t len = readlink(socket_path_.value().c_str(), buf, sizeof(buf)); 367 ASSERT_GT(len, 0); 368 base::FilePath socket_target_path = base::FilePath(std::string(buf, len)); 369 ASSERT_EQ(0, unlink(socket_path_.value().c_str())); 370 ASSERT_EQ(0, rename(socket_target_path.value().c_str(), 371 socket_path_.value().c_str())); 372 ASSERT_EQ(0, unlink(cookie_path_.value().c_str())); 373 374 EXPECT_FALSE(process_singleton->Create()); 375 } 376 377 // Test that we fail when lock says process is on another host and we can't 378 // notify it over the socket before of a bad cookie. 379 TEST_F(ProcessSingletonLinuxTest, NotifyOtherProcessOrCreate_BadCookie) { 380 CreateProcessSingletonOnThread(); 381 // Change the cookie. 382 EXPECT_EQ(0, unlink(cookie_path_.value().c_str())); 383 EXPECT_EQ(0, symlink("INCORRECTCOOKIE", cookie_path_.value().c_str())); 384 385 // Also change the hostname, so the remote does not retry. 386 EXPECT_EQ(0, unlink(lock_path_.value().c_str())); 387 EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_.value().c_str())); 388 389 std::string url("about:blank"); 390 EXPECT_EQ(ProcessSingleton::PROFILE_IN_USE, 391 NotifyOtherProcessOrCreate(url, TestTimeouts::action_timeout())); 392 } 393 394