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/files/file_path.h"
20#include "base/files/file_util.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
34using content::BrowserThread;
35
36namespace {
37
38class 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(bool override_kill) {
164    scoped_ptr<TestableProcessSingleton> process_singleton(
165        CreateProcessSingleton());
166    CommandLine command_line(CommandLine::ForCurrentProcess()->GetProgram());
167    command_line.AppendArg("about:blank");
168    if (override_kill) {
169      process_singleton->OverrideCurrentPidForTesting(
170          base::GetCurrentProcId() + 1);
171      process_singleton->OverrideKillCallbackForTesting(
172          base::Bind(&ProcessSingletonPosixTest::KillCallback,
173                     base::Unretained(this)));
174    }
175
176    return process_singleton->NotifyOtherProcessWithTimeout(
177        command_line, kRetryAttempts, timeout(), true);
178  }
179
180  // A helper method to call ProcessSingleton::NotifyOtherProcessOrCreate().
181  ProcessSingleton::NotifyResult NotifyOtherProcessOrCreate(
182      const std::string& url) {
183    scoped_ptr<TestableProcessSingleton> process_singleton(
184        CreateProcessSingleton());
185    CommandLine command_line(CommandLine::ForCurrentProcess()->GetProgram());
186    command_line.AppendArg(url);
187    return process_singleton->NotifyOtherProcessWithTimeoutOrCreate(
188        command_line, kRetryAttempts, timeout());
189  }
190
191  void CheckNotified() {
192    ASSERT_TRUE(process_singleton_on_thread_ != NULL);
193    ASSERT_EQ(1u, process_singleton_on_thread_->callback_command_lines_.size());
194    bool found = false;
195    for (size_t i = 0;
196         i < process_singleton_on_thread_->callback_command_lines_[0].size();
197         ++i) {
198      if (process_singleton_on_thread_->callback_command_lines_[0][i] ==
199          "about:blank") {
200        found = true;
201        break;
202      }
203    }
204    ASSERT_TRUE(found);
205    ASSERT_EQ(0, kill_callbacks_);
206  }
207
208  void BlockWorkerThread() {
209    worker_thread_->message_loop()->PostTask(
210        FROM_HERE,
211        base::Bind(&ProcessSingletonPosixTest::BlockThread,
212                   base::Unretained(this)));
213  }
214
215  void UnblockWorkerThread() {
216    wait_event_.Signal();  // Unblock the worker thread for shutdown.
217    signal_event_.Wait();  // Ensure thread unblocks before continuing.
218  }
219
220  void BlockThread() {
221    wait_event_.Wait();
222    signal_event_.Signal();
223  }
224
225  base::FilePath user_data_path_;
226  base::FilePath lock_path_;
227  base::FilePath socket_path_;
228  base::FilePath cookie_path_;
229  int kill_callbacks_;
230
231 private:
232  static const int kRetryAttempts = 2;
233
234  base::TimeDelta timeout() const {
235    return TestTimeouts::tiny_timeout() * kRetryAttempts;
236  }
237
238  void CreateProcessSingletonInternal() {
239    ASSERT_TRUE(!process_singleton_on_thread_);
240    process_singleton_on_thread_ = CreateProcessSingleton();
241    ASSERT_EQ(ProcessSingleton::PROCESS_NONE,
242              process_singleton_on_thread_->NotifyOtherProcessOrCreate());
243  }
244
245  void DestructProcessSingleton() {
246    ASSERT_TRUE(process_singleton_on_thread_);
247    delete process_singleton_on_thread_;
248  }
249
250  void KillCallback(int pid) {
251    kill_callbacks_++;
252  }
253
254  content::TestBrowserThread io_thread_;
255  base::ScopedTempDir temp_dir_;
256  base::WaitableEvent wait_event_;
257  base::WaitableEvent signal_event_;
258
259  scoped_ptr<base::Thread> worker_thread_;
260  TestableProcessSingleton* process_singleton_on_thread_;
261};
262
263}  // namespace
264
265// Test if the socket file and symbol link created by ProcessSingletonPosix
266// are valid.
267// If this test flakes, use http://crbug.com/74554.
268TEST_F(ProcessSingletonPosixTest, CheckSocketFile) {
269  CreateProcessSingletonOnThread();
270  VerifyFiles();
271}
272
273// TODO(james.su@gmail.com): port following tests to Windows.
274// Test success case of NotifyOtherProcess().
275TEST_F(ProcessSingletonPosixTest, NotifyOtherProcessSuccess) {
276  CreateProcessSingletonOnThread();
277  EXPECT_EQ(ProcessSingleton::PROCESS_NOTIFIED, NotifyOtherProcess(true));
278  CheckNotified();
279}
280
281// Test failure case of NotifyOtherProcess().
282TEST_F(ProcessSingletonPosixTest, NotifyOtherProcessFailure) {
283  CreateProcessSingletonOnThread();
284
285  BlockWorkerThread();
286  EXPECT_EQ(ProcessSingleton::PROCESS_NONE, NotifyOtherProcess(true));
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.
293TEST_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, NotifyOtherProcess(false));
308  // If we've gotten to this point without killing ourself, the test succeeded.
309}
310
311// Test that we can still notify a process on the same host even after the
312// hostname changed.
313TEST_F(ProcessSingletonPosixTest, NotifyOtherProcessHostChanged) {
314  CreateProcessSingletonOnThread();
315  EXPECT_EQ(0, unlink(lock_path_.value().c_str()));
316  EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_.value().c_str()));
317
318  EXPECT_EQ(ProcessSingleton::PROCESS_NOTIFIED, NotifyOtherProcess(false));
319  CheckNotified();
320}
321
322// Test that we fail when lock says process is on another host and we can't
323// notify it over the socket.
324TEST_F(ProcessSingletonPosixTest, NotifyOtherProcessDifferingHost) {
325  CreateProcessSingletonOnThread();
326
327  BlockWorkerThread();
328
329  EXPECT_EQ(0, unlink(lock_path_.value().c_str()));
330  EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_.value().c_str()));
331
332  EXPECT_EQ(ProcessSingleton::PROFILE_IN_USE, NotifyOtherProcess(false));
333
334  ASSERT_EQ(0, unlink(lock_path_.value().c_str()));
335
336  UnblockWorkerThread();
337}
338
339// Test that we fail when lock says process is on another host and we can't
340// notify it over the socket.
341TEST_F(ProcessSingletonPosixTest, NotifyOtherProcessOrCreate_DifferingHost) {
342  CreateProcessSingletonOnThread();
343
344  BlockWorkerThread();
345
346  EXPECT_EQ(0, unlink(lock_path_.value().c_str()));
347  EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_.value().c_str()));
348
349  std::string url("about:blank");
350  EXPECT_EQ(ProcessSingleton::PROFILE_IN_USE, NotifyOtherProcessOrCreate(url));
351
352  ASSERT_EQ(0, unlink(lock_path_.value().c_str()));
353
354  UnblockWorkerThread();
355}
356
357// Test that Create fails when another browser is using the profile directory.
358TEST_F(ProcessSingletonPosixTest, CreateFailsWithExistingBrowser) {
359  CreateProcessSingletonOnThread();
360
361  scoped_ptr<TestableProcessSingleton> process_singleton(
362      CreateProcessSingleton());
363  process_singleton->OverrideCurrentPidForTesting(base::GetCurrentProcId() + 1);
364  EXPECT_FALSE(process_singleton->Create());
365}
366
367// Test that Create fails when another browser is using the profile directory
368// but with the old socket location.
369TEST_F(ProcessSingletonPosixTest, CreateChecksCompatibilitySocket) {
370  CreateProcessSingletonOnThread();
371  scoped_ptr<TestableProcessSingleton> process_singleton(
372      CreateProcessSingleton());
373  process_singleton->OverrideCurrentPidForTesting(base::GetCurrentProcId() + 1);
374
375  // Do some surgery so as to look like the old configuration.
376  char buf[PATH_MAX];
377  ssize_t len = readlink(socket_path_.value().c_str(), buf, sizeof(buf));
378  ASSERT_GT(len, 0);
379  base::FilePath socket_target_path = base::FilePath(std::string(buf, len));
380  ASSERT_EQ(0, unlink(socket_path_.value().c_str()));
381  ASSERT_EQ(0, rename(socket_target_path.value().c_str(),
382                      socket_path_.value().c_str()));
383  ASSERT_EQ(0, unlink(cookie_path_.value().c_str()));
384
385  EXPECT_FALSE(process_singleton->Create());
386}
387
388// Test that we fail when lock says process is on another host and we can't
389// notify it over the socket before of a bad cookie.
390TEST_F(ProcessSingletonPosixTest, NotifyOtherProcessOrCreate_BadCookie) {
391  CreateProcessSingletonOnThread();
392  // Change the cookie.
393  EXPECT_EQ(0, unlink(cookie_path_.value().c_str()));
394  EXPECT_EQ(0, symlink("INCORRECTCOOKIE", cookie_path_.value().c_str()));
395
396  // Also change the hostname, so the remote does not retry.
397  EXPECT_EQ(0, unlink(lock_path_.value().c_str()));
398  EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_.value().c_str()));
399
400  std::string url("about:blank");
401  EXPECT_EQ(ProcessSingleton::PROFILE_IN_USE, NotifyOtherProcessOrCreate(url));
402}
403
404#if defined(OS_MACOSX)
405// Test that if there is an existing lock file, and we could not flock()
406// it, then exit.
407TEST_F(ProcessSingletonPosixTest, CreateRespectsOldMacLock) {
408  scoped_ptr<TestableProcessSingleton> process_singleton(
409      CreateProcessSingleton());
410  base::ScopedFD lock_fd(HANDLE_EINTR(
411      open(lock_path_.value().c_str(), O_RDWR | O_CREAT | O_EXLOCK, 0644)));
412  ASSERT_TRUE(lock_fd.is_valid());
413  EXPECT_FALSE(process_singleton->Create());
414  base::File::Info info;
415  EXPECT_TRUE(base::GetFileInfo(lock_path_, &info));
416  EXPECT_FALSE(info.is_directory);
417  EXPECT_FALSE(info.is_symbolic_link);
418}
419
420// Test that if there is an existing lock file, and it's not locked, we replace
421// it.
422TEST_F(ProcessSingletonPosixTest, CreateReplacesOldMacLock) {
423  scoped_ptr<TestableProcessSingleton> process_singleton(
424      CreateProcessSingleton());
425  EXPECT_EQ(0, base::WriteFile(lock_path_, "", 0));
426  EXPECT_TRUE(process_singleton->Create());
427  VerifyFiles();
428}
429#endif  // defined(OS_MACOSX)
430