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