Home | History | Annotate | Download | only in browser
      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