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