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/common/service_process_util.h"
6
7#include "base/basictypes.h"
8#include "base/bind.h"
9#include "base/command_line.h"
10#include "base/files/file_path.h"
11#include "base/process/kill.h"
12#include "base/process/launch.h"
13#include "base/strings/string_split.h"
14
15#if !defined(OS_MACOSX)
16#include "base/at_exit.h"
17#include "base/memory/scoped_ptr.h"
18#include "base/strings/string_util.h"
19#include "base/strings/utf_string_conversions.h"
20#include "base/test/multiprocess_test.h"
21#include "base/test/test_timeouts.h"
22#include "base/threading/thread.h"
23#include "chrome/common/chrome_switches.h"
24#include "chrome/common/chrome_version_info.h"
25#include "testing/multiprocess_func_list.h"
26
27#if defined(OS_WIN)
28#include "base/win/win_util.h"
29#endif
30
31#if defined(OS_POSIX)
32#include "chrome/common/auto_start_linux.h"
33#endif
34
35#if defined(USE_AURA)
36// This test fails http://crbug.com/84854, and is very flaky on CrOS and
37// somewhat flaky on other Linux.
38#define MAYBE_ForceShutdown DISABLED_ForceShutdown
39#else
40#if defined(OS_LINUX) || defined(OS_WIN)
41#define MAYBE_ForceShutdown DISABLED_ForceShutdown
42#else
43#define MAYBE_ForceShutdown ForceShutdown
44#endif
45#endif
46
47namespace {
48
49bool g_good_shutdown = false;
50
51void ShutdownTask(base::MessageLoop* loop) {
52  // Quit the main message loop.
53  ASSERT_FALSE(g_good_shutdown);
54  g_good_shutdown = true;
55  loop->PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
56}
57
58}  // namespace
59
60TEST(ServiceProcessUtilTest, ScopedVersionedName) {
61  std::string test_str = "test";
62  std::string scoped_name = GetServiceProcessScopedVersionedName(test_str);
63  chrome::VersionInfo version_info;
64  DCHECK(version_info.is_valid());
65  EXPECT_TRUE(EndsWith(scoped_name, test_str, true));
66  EXPECT_NE(std::string::npos, scoped_name.find(version_info.Version()));
67}
68
69class ServiceProcessStateTest : public base::MultiProcessTest {
70 public:
71  ServiceProcessStateTest();
72  virtual ~ServiceProcessStateTest();
73  virtual void SetUp();
74  base::MessageLoopProxy* IOMessageLoopProxy() {
75    return io_thread_.message_loop_proxy().get();
76  }
77  void LaunchAndWait(const std::string& name);
78
79 private:
80  // This is used to release the ServiceProcessState singleton after each test.
81  base::ShadowingAtExitManager at_exit_manager_;
82  base::Thread io_thread_;
83};
84
85ServiceProcessStateTest::ServiceProcessStateTest()
86    : io_thread_("ServiceProcessStateTestThread") {
87}
88
89ServiceProcessStateTest::~ServiceProcessStateTest() {
90}
91
92void ServiceProcessStateTest::SetUp() {
93  base::Thread::Options options(base::MessageLoop::TYPE_IO, 0);
94  ASSERT_TRUE(io_thread_.StartWithOptions(options));
95}
96
97void ServiceProcessStateTest::LaunchAndWait(const std::string& name) {
98  base::ProcessHandle handle = SpawnChild(name);
99  ASSERT_TRUE(handle);
100  int exit_code = 0;
101  ASSERT_TRUE(base::WaitForExitCode(handle, &exit_code));
102  ASSERT_EQ(exit_code, 0);
103}
104
105TEST_F(ServiceProcessStateTest, Singleton) {
106  ServiceProcessState state;
107  ASSERT_TRUE(state.Initialize());
108  LaunchAndWait("ServiceProcessStateTestSingleton");
109}
110
111TEST_F(ServiceProcessStateTest, ReadyState) {
112  ASSERT_FALSE(CheckServiceProcessReady());
113  ServiceProcessState state;
114  ASSERT_TRUE(state.Initialize());
115  ASSERT_TRUE(state.SignalReady(IOMessageLoopProxy(), base::Closure()));
116  LaunchAndWait("ServiceProcessStateTestReadyTrue");
117  state.SignalStopped();
118  LaunchAndWait("ServiceProcessStateTestReadyFalse");
119}
120
121TEST_F(ServiceProcessStateTest, AutoRun) {
122  ServiceProcessState state;
123  ASSERT_TRUE(state.AddToAutoRun());
124  scoped_ptr<CommandLine> autorun_command_line;
125#if defined(OS_WIN)
126  std::string value_name = GetServiceProcessScopedName("_service_run");
127  base::string16 value;
128  EXPECT_TRUE(base::win::ReadCommandFromAutoRun(HKEY_CURRENT_USER,
129                                                base::UTF8ToWide(value_name),
130                                                &value));
131  autorun_command_line.reset(new CommandLine(CommandLine::FromString(value)));
132#elif defined(OS_POSIX) && !defined(OS_MACOSX)
133#if defined(GOOGLE_CHROME_BUILD)
134  std::string base_desktop_name = "google-chrome-service.desktop";
135#else  // CHROMIUM_BUILD
136  std::string base_desktop_name = "chromium-service.desktop";
137#endif
138  std::string exec_value;
139  EXPECT_TRUE(AutoStart::GetAutostartFileValue(
140      GetServiceProcessScopedName(base_desktop_name), "Exec", &exec_value));
141
142  // Make sure |exec_value| doesn't contain strings a shell would
143  // treat specially.
144  ASSERT_EQ(std::string::npos, exec_value.find('#'));
145  ASSERT_EQ(std::string::npos, exec_value.find('\n'));
146  ASSERT_EQ(std::string::npos, exec_value.find('"'));
147  ASSERT_EQ(std::string::npos, exec_value.find('\''));
148
149  CommandLine::StringVector argv;
150  base::SplitString(exec_value, ' ', &argv);
151  ASSERT_GE(argv.size(), 2U)
152      << "Expected at least one command-line option in: " << exec_value;
153  autorun_command_line.reset(new CommandLine(argv));
154#endif  // defined(OS_WIN)
155  if (autorun_command_line.get()) {
156    EXPECT_EQ(autorun_command_line->GetSwitchValueASCII(switches::kProcessType),
157              std::string(switches::kServiceProcess));
158  }
159  ASSERT_TRUE(state.RemoveFromAutoRun());
160#if defined(OS_WIN)
161  EXPECT_FALSE(base::win::ReadCommandFromAutoRun(HKEY_CURRENT_USER,
162                                                 base::UTF8ToWide(value_name),
163                                                 &value));
164#elif defined(OS_POSIX) && !defined(OS_MACOSX)
165  EXPECT_FALSE(AutoStart::GetAutostartFileValue(
166      GetServiceProcessScopedName(base_desktop_name), "Exec", &exec_value));
167#endif  // defined(OS_WIN)
168}
169
170TEST_F(ServiceProcessStateTest, SharedMem) {
171  std::string version;
172  base::ProcessId pid;
173#if defined(OS_WIN)
174  // On Posix, named shared memory uses a file on disk. This file
175  // could be lying around from previous crashes which could cause
176  // GetServiceProcessPid to lie. On Windows, we use a named event so we
177  // don't have this issue. Until we have a more stable shared memory
178  // implementation on Posix, this check will only execute on Windows.
179  ASSERT_FALSE(GetServiceProcessData(&version, &pid));
180#endif  // defined(OS_WIN)
181  ServiceProcessState state;
182  ASSERT_TRUE(state.Initialize());
183  ASSERT_TRUE(GetServiceProcessData(&version, &pid));
184  ASSERT_EQ(base::GetCurrentProcId(), pid);
185}
186
187TEST_F(ServiceProcessStateTest, MAYBE_ForceShutdown) {
188  base::ProcessHandle handle = SpawnChild("ServiceProcessStateTestShutdown");
189  ASSERT_TRUE(handle);
190  for (int i = 0; !CheckServiceProcessReady() && i < 10; ++i) {
191    base::PlatformThread::Sleep(TestTimeouts::tiny_timeout());
192  }
193  ASSERT_TRUE(CheckServiceProcessReady());
194  std::string version;
195  base::ProcessId pid;
196  ASSERT_TRUE(GetServiceProcessData(&version, &pid));
197  ASSERT_TRUE(ForceServiceProcessShutdown(version, pid));
198  int exit_code = 0;
199  ASSERT_TRUE(base::WaitForExitCodeWithTimeout(handle,
200      &exit_code, TestTimeouts::action_max_timeout()));
201  base::CloseProcessHandle(handle);
202  ASSERT_EQ(exit_code, 0);
203}
204
205MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestSingleton) {
206  ServiceProcessState state;
207  EXPECT_FALSE(state.Initialize());
208  return 0;
209}
210
211MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestReadyTrue) {
212  EXPECT_TRUE(CheckServiceProcessReady());
213  return 0;
214}
215
216MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestReadyFalse) {
217  EXPECT_FALSE(CheckServiceProcessReady());
218  return 0;
219}
220
221MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestShutdown) {
222  base::MessageLoop message_loop;
223  message_loop.set_thread_name("ServiceProcessStateTestShutdownMainThread");
224  base::Thread io_thread_("ServiceProcessStateTestShutdownIOThread");
225  base::Thread::Options options(base::MessageLoop::TYPE_IO, 0);
226  EXPECT_TRUE(io_thread_.StartWithOptions(options));
227  ServiceProcessState state;
228  EXPECT_TRUE(state.Initialize());
229  EXPECT_TRUE(state.SignalReady(
230      io_thread_.message_loop_proxy().get(),
231      base::Bind(&ShutdownTask, base::MessageLoop::current())));
232  message_loop.PostDelayedTask(FROM_HERE,
233                               base::MessageLoop::QuitClosure(),
234                               TestTimeouts::action_max_timeout());
235  EXPECT_FALSE(g_good_shutdown);
236  message_loop.Run();
237  EXPECT_TRUE(g_good_shutdown);
238  return 0;
239}
240
241#else  // !OS_MACOSX
242
243#include <CoreFoundation/CoreFoundation.h>
244
245#include "base/file_util.h"
246#include "base/files/file_path.h"
247#include "base/files/scoped_temp_dir.h"
248#include "base/mac/mac_util.h"
249#include "base/test/test_timeouts.h"
250#include "base/threading/thread.h"
251#include "chrome/common/mac/launchd.h"
252#include "chrome/common/mac/mock_launchd.h"
253#include "testing/gtest/include/gtest/gtest.h"
254
255class ServiceProcessStateFileManipulationTest : public ::testing::Test {
256 protected:
257  ServiceProcessStateFileManipulationTest()
258      : io_thread_("ServiceProcessStateFileManipulationTest_IO") {
259  }
260  virtual ~ServiceProcessStateFileManipulationTest() { }
261
262  virtual void SetUp() {
263    base::Thread::Options options;
264    options.message_loop_type = base::MessageLoop::TYPE_IO;
265    ASSERT_TRUE(io_thread_.StartWithOptions(options));
266    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
267    ASSERT_TRUE(MockLaunchd::MakeABundle(GetTempDirPath(),
268                                         "Test",
269                                         &bundle_path_,
270                                         &executable_path_));
271    mock_launchd_.reset(new MockLaunchd(executable_path_, &loop_,
272                                        false, false));
273    scoped_launchd_instance_.reset(
274        new Launchd::ScopedInstance(mock_launchd_.get()));
275    ASSERT_TRUE(service_process_state_.Initialize());
276    ASSERT_TRUE(service_process_state_.SignalReady(
277        io_thread_.message_loop_proxy().get(), base::Closure()));
278    loop_.PostDelayedTask(FROM_HERE,
279                          base::MessageLoop::QuitClosure(),
280                          TestTimeouts::action_max_timeout());
281  }
282
283  const MockLaunchd* mock_launchd() const { return mock_launchd_.get(); }
284  const base::FilePath& executable_path() const { return executable_path_; }
285  const base::FilePath& bundle_path() const { return bundle_path_; }
286  const base::FilePath& GetTempDirPath() const { return temp_dir_.path(); }
287
288  base::MessageLoopProxy* GetIOMessageLoopProxy() {
289    return io_thread_.message_loop_proxy().get();
290  }
291  void Run() { loop_.Run(); }
292
293 private:
294  base::ScopedTempDir temp_dir_;
295  base::MessageLoopForUI loop_;
296  base::Thread io_thread_;
297  base::FilePath executable_path_, bundle_path_;
298  scoped_ptr<MockLaunchd> mock_launchd_;
299  scoped_ptr<Launchd::ScopedInstance> scoped_launchd_instance_;
300  ServiceProcessState service_process_state_;
301};
302
303void DeleteFunc(const base::FilePath& file) {
304  EXPECT_TRUE(base::DeleteFile(file, true));
305}
306
307void MoveFunc(const base::FilePath& from, const base::FilePath& to) {
308  EXPECT_TRUE(base::Move(from, to));
309}
310
311void ChangeAttr(const base::FilePath& from, int mode) {
312  EXPECT_EQ(chmod(from.value().c_str(), mode), 0);
313}
314
315class ScopedAttributesRestorer {
316 public:
317  ScopedAttributesRestorer(const base::FilePath& path, int mode)
318      : path_(path), mode_(mode) {
319  }
320  ~ScopedAttributesRestorer() {
321    ChangeAttr(path_, mode_);
322  }
323 private:
324  base::FilePath path_;
325  int mode_;
326};
327
328void TrashFunc(const base::FilePath& src) {
329  FSRef path_ref;
330  FSRef new_path_ref;
331  EXPECT_TRUE(base::mac::FSRefFromPath(src.value(), &path_ref));
332  OSStatus status = FSMoveObjectToTrashSync(&path_ref,
333                                            &new_path_ref,
334                                            kFSFileOperationDefaultOptions);
335  EXPECT_EQ(status, noErr) << "FSMoveObjectToTrashSync " << status;
336}
337
338TEST_F(ServiceProcessStateFileManipulationTest, VerifyLaunchD) {
339  // There have been problems where launchd has gotten into a bad state, usually
340  // because something had deleted all the files in /tmp. launchd depends on
341  // a Unix Domain Socket that it creates at /tmp/launchd*/sock.
342  // The symptom of this problem is that the service process connect fails
343  // on Mac and "launch_msg(): Socket is not connected" appears.
344  // This test is designed to make sure that launchd is working.
345  // http://crbug/75518
346
347  CommandLine cl(base::FilePath("/bin/launchctl"));
348  cl.AppendArg("list");
349  cl.AppendArg("com.apple.launchctl.Aqua");
350
351  std::string output;
352  int exit_code = -1;
353  ASSERT_TRUE(base::GetAppOutputWithExitCode(cl, &output, &exit_code)
354              && exit_code == 0)
355      << " exit_code:" << exit_code << " " << output;
356}
357
358TEST_F(ServiceProcessStateFileManipulationTest, DeleteFile) {
359  GetIOMessageLoopProxy()->PostTask(
360      FROM_HERE,
361      base::Bind(&DeleteFunc, executable_path()));
362  Run();
363  ASSERT_TRUE(mock_launchd()->remove_called());
364  ASSERT_TRUE(mock_launchd()->delete_called());
365}
366
367TEST_F(ServiceProcessStateFileManipulationTest, DeleteBundle) {
368  GetIOMessageLoopProxy()->PostTask(
369      FROM_HERE,
370      base::Bind(&DeleteFunc, bundle_path()));
371  Run();
372  ASSERT_TRUE(mock_launchd()->remove_called());
373  ASSERT_TRUE(mock_launchd()->delete_called());
374}
375
376TEST_F(ServiceProcessStateFileManipulationTest, MoveBundle) {
377  base::FilePath new_loc = GetTempDirPath().AppendASCII("MoveBundle");
378  GetIOMessageLoopProxy()->PostTask(
379      FROM_HERE,
380      base::Bind(&MoveFunc, bundle_path(), new_loc));
381  Run();
382  ASSERT_TRUE(mock_launchd()->restart_called());
383  ASSERT_TRUE(mock_launchd()->write_called());
384}
385
386TEST_F(ServiceProcessStateFileManipulationTest, MoveFile) {
387  base::FilePath new_loc = GetTempDirPath().AppendASCII("MoveFile");
388  GetIOMessageLoopProxy()->PostTask(
389      FROM_HERE,
390      base::Bind(&MoveFunc, executable_path(), new_loc));
391  Run();
392  ASSERT_TRUE(mock_launchd()->remove_called());
393  ASSERT_TRUE(mock_launchd()->delete_called());
394}
395
396TEST_F(ServiceProcessStateFileManipulationTest, TrashBundle) {
397  FSRef bundle_ref;
398  ASSERT_TRUE(base::mac::FSRefFromPath(bundle_path().value(), &bundle_ref));
399  GetIOMessageLoopProxy()->PostTask(
400      FROM_HERE,
401      base::Bind(&TrashFunc, bundle_path()));
402  Run();
403  ASSERT_TRUE(mock_launchd()->remove_called());
404  ASSERT_TRUE(mock_launchd()->delete_called());
405  std::string path(base::mac::PathFromFSRef(bundle_ref));
406  base::FilePath file_path(path);
407  ASSERT_TRUE(base::DeleteFile(file_path, true));
408}
409
410TEST_F(ServiceProcessStateFileManipulationTest, ChangeAttr) {
411  ScopedAttributesRestorer restorer(bundle_path(), 0777);
412  GetIOMessageLoopProxy()->PostTask(
413      FROM_HERE,
414      base::Bind(&ChangeAttr, bundle_path(), 0222));
415  Run();
416  ASSERT_TRUE(mock_launchd()->remove_called());
417  ASSERT_TRUE(mock_launchd()->delete_called());
418}
419
420#endif  // !OS_MACOSX
421