1// Copyright (c) 2011 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
9#if !defined(OS_MACOSX)
10#include "base/at_exit.h"
11#include "base/command_line.h"
12#include "base/memory/scoped_ptr.h"
13#include "base/process_util.h"
14#include "base/string_util.h"
15#include "base/test/multiprocess_test.h"
16#include "base/test/test_timeouts.h"
17#include "base/threading/thread.h"
18#include "base/utf_string_conversions.h"
19#include "chrome/common/chrome_switches.h"
20#include "chrome/common/chrome_version_info.h"
21#include "testing/multiprocess_func_list.h"
22
23#if defined(OS_WIN)
24#include "base/win/win_util.h"
25#endif
26
27#if defined(OS_LINUX)
28#include <glib.h>
29#include "chrome/common/auto_start_linux.h"
30#endif
31
32namespace {
33
34bool g_good_shutdown = false;
35
36void ShutdownTask(MessageLoop* loop) {
37  // Quit the main message loop.
38  ASSERT_FALSE(g_good_shutdown);
39  g_good_shutdown = true;
40  loop->PostTask(FROM_HERE, new MessageLoop::QuitTask());
41}
42
43}  // namespace
44
45TEST(ServiceProcessUtilTest, ScopedVersionedName) {
46  std::string test_str = "test";
47  std::string scoped_name = GetServiceProcessScopedVersionedName(test_str);
48  chrome::VersionInfo version_info;
49  DCHECK(version_info.is_valid());
50  EXPECT_TRUE(EndsWith(scoped_name, test_str, true));
51  EXPECT_NE(std::string::npos, scoped_name.find(version_info.Version()));
52}
53
54class ServiceProcessStateTest : public base::MultiProcessTest {
55 public:
56  ServiceProcessStateTest();
57  ~ServiceProcessStateTest();
58  virtual void SetUp();
59  base::MessageLoopProxy* IOMessageLoopProxy() {
60    return io_thread_.message_loop_proxy();
61  }
62  void LaunchAndWait(const std::string& name);
63
64 private:
65  // This is used to release the ServiceProcessState singleton after each test.
66  base::ShadowingAtExitManager at_exit_manager_;
67  base::Thread io_thread_;
68};
69
70ServiceProcessStateTest::ServiceProcessStateTest()
71    : io_thread_("ServiceProcessStateTestThread") {
72}
73
74ServiceProcessStateTest::~ServiceProcessStateTest() {
75}
76
77void ServiceProcessStateTest::SetUp() {
78  base::Thread::Options options(MessageLoop::TYPE_IO, 0);
79  ASSERT_TRUE(io_thread_.StartWithOptions(options));
80}
81
82void ServiceProcessStateTest::LaunchAndWait(const std::string& name) {
83  base::ProcessHandle handle = SpawnChild(name, false);
84  ASSERT_TRUE(handle);
85  int exit_code = 0;
86  ASSERT_TRUE(base::WaitForExitCode(handle, &exit_code));
87  ASSERT_EQ(exit_code, 0);
88}
89
90TEST_F(ServiceProcessStateTest, Singleton) {
91  ServiceProcessState state;
92  ASSERT_TRUE(state.Initialize());
93  LaunchAndWait("ServiceProcessStateTestSingleton");
94}
95
96TEST_F(ServiceProcessStateTest, ReadyState) {
97  ASSERT_FALSE(CheckServiceProcessReady());
98  ServiceProcessState state;
99  ASSERT_TRUE(state.Initialize());
100  ASSERT_TRUE(state.SignalReady(IOMessageLoopProxy(), NULL));
101  LaunchAndWait("ServiceProcessStateTestReadyTrue");
102  state.SignalStopped();
103  LaunchAndWait("ServiceProcessStateTestReadyFalse");
104}
105
106TEST_F(ServiceProcessStateTest, AutoRun) {
107  ServiceProcessState state;
108  ASSERT_TRUE(state.AddToAutoRun());
109  scoped_ptr<CommandLine> autorun_command_line;
110#if defined(OS_WIN)
111  std::string value_name = GetServiceProcessScopedName("_service_run");
112  string16 value;
113  EXPECT_TRUE(base::win::ReadCommandFromAutoRun(HKEY_CURRENT_USER,
114                                                UTF8ToWide(value_name),
115                                                &value));
116  autorun_command_line.reset(new CommandLine(CommandLine::FromString(value)));
117#elif defined(OS_LINUX)
118#if defined(GOOGLE_CHROME_BUILD)
119  std::string base_desktop_name = "google-chrome-service.desktop";
120#else  // CHROMIUM_BUILD
121  std::string base_desktop_name = "chromium-service.desktop";
122#endif
123  std::string exec_value;
124  EXPECT_TRUE(AutoStart::GetAutostartFileValue(
125      GetServiceProcessScopedName(base_desktop_name), "Exec", &exec_value));
126  GError *error = NULL;
127  gchar **argv = NULL;
128  gint argc = 0;
129  if (g_shell_parse_argv(exec_value.c_str(), &argc, &argv, &error)) {
130    autorun_command_line.reset(new CommandLine(argc, argv));
131    g_strfreev(argv);
132  } else {
133    ADD_FAILURE();
134    g_error_free(error);
135  }
136#endif  // defined(OS_WIN)
137  if (autorun_command_line.get()) {
138    EXPECT_EQ(autorun_command_line->GetSwitchValueASCII(switches::kProcessType),
139              std::string(switches::kServiceProcess));
140  }
141  ASSERT_TRUE(state.RemoveFromAutoRun());
142#if defined(OS_WIN)
143  EXPECT_FALSE(base::win::ReadCommandFromAutoRun(HKEY_CURRENT_USER,
144                                                 UTF8ToWide(value_name),
145                                                 &value));
146#elif defined(OS_LINUX)
147  EXPECT_FALSE(AutoStart::GetAutostartFileValue(
148      GetServiceProcessScopedName(base_desktop_name), "Exec", &exec_value));
149#endif  // defined(OS_WIN)
150}
151
152TEST_F(ServiceProcessStateTest, SharedMem) {
153  std::string version;
154  base::ProcessId pid;
155#if defined(OS_WIN)
156  // On Posix, named shared memory uses a file on disk. This file
157  // could be lying around from previous crashes which could cause
158  // GetServiceProcessPid to lie. On Windows, we use a named event so we
159  // don't have this issue. Until we have a more stable shared memory
160  // implementation on Posix, this check will only execute on Windows.
161  ASSERT_FALSE(GetServiceProcessData(&version, &pid));
162#endif  // defined(OS_WIN)
163  ServiceProcessState state;
164  ASSERT_TRUE(state.Initialize());
165  ASSERT_TRUE(GetServiceProcessData(&version, &pid));
166  ASSERT_EQ(base::GetCurrentProcId(), pid);
167}
168
169TEST_F(ServiceProcessStateTest, ForceShutdown) {
170  base::ProcessHandle handle = SpawnChild("ServiceProcessStateTestShutdown",
171                                          true);
172  ASSERT_TRUE(handle);
173  for (int i = 0; !CheckServiceProcessReady() && i < 10; ++i) {
174    base::PlatformThread::Sleep(TestTimeouts::tiny_timeout_ms());
175  }
176  ASSERT_TRUE(CheckServiceProcessReady());
177  std::string version;
178  base::ProcessId pid;
179  ASSERT_TRUE(GetServiceProcessData(&version, &pid));
180  ASSERT_TRUE(ForceServiceProcessShutdown(version, pid));
181  int exit_code = 0;
182  ASSERT_TRUE(base::WaitForExitCodeWithTimeout(handle,
183      &exit_code, TestTimeouts::action_max_timeout_ms()));
184  base::CloseProcessHandle(handle);
185  ASSERT_EQ(exit_code, 0);
186}
187
188MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestSingleton) {
189  ServiceProcessState state;
190  EXPECT_FALSE(state.Initialize());
191  return 0;
192}
193
194MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestReadyTrue) {
195  EXPECT_TRUE(CheckServiceProcessReady());
196  return 0;
197}
198
199MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestReadyFalse) {
200  EXPECT_FALSE(CheckServiceProcessReady());
201  return 0;
202}
203
204MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestShutdown) {
205  MessageLoop message_loop;
206  message_loop.set_thread_name("ServiceProcessStateTestShutdownMainThread");
207  base::Thread io_thread_("ServiceProcessStateTestShutdownIOThread");
208  base::Thread::Options options(MessageLoop::TYPE_IO, 0);
209  EXPECT_TRUE(io_thread_.StartWithOptions(options));
210  ServiceProcessState state;
211  EXPECT_TRUE(state.Initialize());
212  EXPECT_TRUE(state.SignalReady(io_thread_.message_loop_proxy(),
213                                NewRunnableFunction(&ShutdownTask,
214                                                    MessageLoop::current())));
215  message_loop.PostDelayedTask(FROM_HERE,
216                               new MessageLoop::QuitTask(),
217                               TestTimeouts::action_max_timeout_ms());
218  EXPECT_FALSE(g_good_shutdown);
219  message_loop.Run();
220  EXPECT_TRUE(g_good_shutdown);
221  return 0;
222}
223
224#else  // !OS_MACOSX
225
226#include <CoreFoundation/CoreFoundation.h>
227
228#include <launch.h>
229#include <sys/stat.h>
230
231#include "base/file_path.h"
232#include "base/file_util.h"
233#include "base/mac/mac_util.h"
234#include "base/mac/scoped_cftyperef.h"
235#include "base/memory/scoped_temp_dir.h"
236#include "base/message_loop.h"
237#include "base/stringprintf.h"
238#include "base/sys_string_conversions.h"
239#include "base/test/test_timeouts.h"
240#include "base/threading/thread.h"
241#include "chrome/common/launchd_mac.h"
242#include "testing/gtest/include/gtest/gtest.h"
243
244// TODO(dmaclach): Write this in terms of a real mock.
245// http://crbug.com/76923
246class MockLaunchd : public Launchd {
247 public:
248  MockLaunchd(const FilePath& file, MessageLoop* loop)
249      : file_(file),
250        message_loop_(loop),
251        restart_called_(false),
252        remove_called_(false),
253        checkin_called_(false),
254        write_called_(false),
255        delete_called_(false) {
256  }
257  virtual ~MockLaunchd() { }
258
259  virtual CFDictionaryRef CopyExports() OVERRIDE {
260    ADD_FAILURE();
261    return NULL;
262  }
263
264  virtual CFDictionaryRef CopyJobDictionary(CFStringRef label) OVERRIDE {
265    ADD_FAILURE();
266    return NULL;
267  }
268
269  virtual CFDictionaryRef CopyDictionaryByCheckingIn(CFErrorRef* error)
270      OVERRIDE {
271    checkin_called_ = true;
272    CFStringRef program = CFSTR(LAUNCH_JOBKEY_PROGRAM);
273    CFStringRef program_args = CFSTR(LAUNCH_JOBKEY_PROGRAMARGUMENTS);
274    const void *keys[] = { program, program_args };
275    base::mac::ScopedCFTypeRef<CFStringRef> path(
276        base::SysUTF8ToCFStringRef(file_.value()));
277    const void *array_values[] = { path.get() };
278    base::mac::ScopedCFTypeRef<CFArrayRef> args(
279        CFArrayCreate(kCFAllocatorDefault,
280                      array_values,
281                      1,
282                      &kCFTypeArrayCallBacks));
283    const void *values[] = { path, args };
284    return CFDictionaryCreate(kCFAllocatorDefault,
285                              keys,
286                              values,
287                              arraysize(keys),
288                              &kCFTypeDictionaryKeyCallBacks,
289                              &kCFTypeDictionaryValueCallBacks);
290  }
291
292  virtual bool RemoveJob(CFStringRef label, CFErrorRef* error) OVERRIDE {
293    remove_called_ = true;
294    message_loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask);
295    return true;
296  }
297
298  virtual bool RestartJob(Domain domain,
299                          Type type,
300                          CFStringRef name,
301                          CFStringRef session_type) OVERRIDE {
302    restart_called_ = true;
303    message_loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask);
304    return true;
305  }
306
307  virtual CFMutableDictionaryRef CreatePlistFromFile(
308      Domain domain,
309      Type type,
310      CFStringRef name) OVERRIDE {
311    base::mac::ScopedCFTypeRef<CFDictionaryRef> dict(
312        CopyDictionaryByCheckingIn(NULL));
313    return CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, dict);
314  }
315
316  virtual bool WritePlistToFile(Domain domain,
317                                Type type,
318                                CFStringRef name,
319                                CFDictionaryRef dict) OVERRIDE {
320    write_called_ = true;
321    return true;
322  }
323
324  virtual bool DeletePlist(Domain domain,
325                           Type type,
326                           CFStringRef name) OVERRIDE {
327    delete_called_ = true;
328    return true;
329  }
330
331  bool restart_called() const { return restart_called_; }
332  bool remove_called() const { return remove_called_; }
333  bool checkin_called() const { return checkin_called_; }
334  bool write_called() const { return write_called_; }
335  bool delete_called() const { return delete_called_; }
336
337 private:
338  FilePath file_;
339  MessageLoop* message_loop_;
340  bool restart_called_;
341  bool remove_called_;
342  bool checkin_called_;
343  bool write_called_;
344  bool delete_called_;
345};
346
347class ServiceProcessStateFileManipulationTest : public ::testing::Test {
348 protected:
349  ServiceProcessStateFileManipulationTest()
350      : io_thread_("ServiceProcessStateFileManipulationTest_IO") {
351  }
352  virtual ~ServiceProcessStateFileManipulationTest() { }
353
354  virtual void SetUp() {
355    base::Thread::Options options;
356    options.message_loop_type = MessageLoop::TYPE_IO;
357    ASSERT_TRUE(io_thread_.StartWithOptions(options));
358    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
359    ASSERT_TRUE(MakeABundle(GetTempDirPath(),
360                            "Test",
361                            &bundle_path_,
362                            &executable_path_));
363    mock_launchd_.reset(new MockLaunchd(executable_path_, &loop_));
364    scoped_launchd_instance_.reset(
365        new Launchd::ScopedInstance(mock_launchd_.get()));
366    ASSERT_TRUE(service_process_state_.Initialize());
367    ASSERT_TRUE(service_process_state_.SignalReady(
368        io_thread_.message_loop_proxy(),
369        NULL));
370    loop_.PostDelayedTask(FROM_HERE,
371                          new MessageLoop::QuitTask,
372                          TestTimeouts::action_max_timeout_ms());
373  }
374
375  bool MakeABundle(const FilePath& dst,
376                   const std::string& name,
377                   FilePath* bundle_root,
378                   FilePath* executable) {
379    *bundle_root = dst.Append(name + std::string(".app"));
380    FilePath contents = bundle_root->AppendASCII("Contents");
381    FilePath mac_os = contents.AppendASCII("MacOS");
382    *executable = mac_os.Append(name);
383    FilePath info_plist = contents.Append("Info.plist");
384
385    if (!file_util::CreateDirectory(mac_os)) {
386      return false;
387    }
388    const char *data = "#! testbundle\n";
389    int len = strlen(data);
390    if (file_util::WriteFile(*executable, data, len) != len) {
391      return false;
392    }
393    if (chmod(executable->value().c_str(), 0555) != 0) {
394      return false;
395    }
396
397    const char* info_plist_format =
398      "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
399      "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
400      "<plist version=\"1.0\">\n"
401      "<dict>\n"
402      "  <key>CFBundleDevelopmentRegion</key>\n"
403      "  <string>English</string>\n"
404      "  <key>CFBundleIdentifier</key>\n"
405      "  <string>com.test.%s</string>\n"
406      "  <key>CFBundleInfoDictionaryVersion</key>\n"
407      "  <string>6.0</string>\n"
408      "  <key>CFBundleExecutable</key>\n"
409      "  <string>%s</string>\n"
410      "  <key>CFBundleVersion</key>\n"
411      "  <string>1</string>\n"
412      "</dict>\n"
413      "</plist>\n";
414    std::string info_plist_data = base::StringPrintf(info_plist_format,
415                                                     name.c_str(),
416                                                     name.c_str());
417    len = info_plist_data.length();
418    if (file_util::WriteFile(info_plist, info_plist_data.c_str(), len) != len) {
419      return false;
420    }
421    const UInt8* bundle_root_path =
422        reinterpret_cast<const UInt8*>(bundle_root->value().c_str());
423    base::mac::ScopedCFTypeRef<CFURLRef> url(
424      CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
425                                              bundle_root_path,
426                                              bundle_root->value().length(),
427                                              true));
428    base::mac::ScopedCFTypeRef<CFBundleRef> bundle(
429        CFBundleCreate(kCFAllocatorDefault, url));
430    return bundle.get();
431  }
432
433  const MockLaunchd* mock_launchd() const { return mock_launchd_.get(); }
434  const FilePath& executable_path() const { return executable_path_; }
435  const FilePath& bundle_path() const { return bundle_path_; }
436  const FilePath& GetTempDirPath() const { return temp_dir_.path(); }
437
438  base::MessageLoopProxy* GetIOMessageLoopProxy() {
439    return io_thread_.message_loop_proxy().get();
440  }
441  void Run() { loop_.Run(); }
442
443 private:
444  ScopedTempDir temp_dir_;
445  MessageLoopForUI loop_;
446  base::Thread io_thread_;
447  FilePath executable_path_, bundle_path_;
448  scoped_ptr<MockLaunchd> mock_launchd_;
449  scoped_ptr<Launchd::ScopedInstance> scoped_launchd_instance_;
450  ServiceProcessState service_process_state_;
451};
452
453void DeleteFunc(const FilePath& file) {
454  EXPECT_TRUE(file_util::Delete(file, true));
455}
456
457void MoveFunc(const FilePath& from, const FilePath& to) {
458  EXPECT_TRUE(file_util::Move(from, to));
459}
460
461void ChangeAttr(const FilePath& from, int mode) {
462  EXPECT_EQ(chmod(from.value().c_str(), mode), 0);
463}
464
465class ScopedAttributesRestorer {
466 public:
467  ScopedAttributesRestorer(const FilePath& path, int mode)
468      : path_(path), mode_(mode) {
469  }
470  ~ScopedAttributesRestorer() {
471    ChangeAttr(path_, mode_);
472  }
473 private:
474  FilePath path_;
475  int mode_;
476};
477
478void TrashFunc(const FilePath& src) {
479  FSRef path_ref;
480  FSRef new_path_ref;
481  EXPECT_TRUE(base::mac::FSRefFromPath(src.value(), &path_ref));
482  OSStatus status = FSMoveObjectToTrashSync(&path_ref,
483                                            &new_path_ref,
484                                            kFSFileOperationDefaultOptions);
485  EXPECT_EQ(status, noErr)  << "FSMoveObjectToTrashSync " << status;
486}
487
488TEST_F(ServiceProcessStateFileManipulationTest, DeleteFile) {
489  GetIOMessageLoopProxy()->PostTask(
490      FROM_HERE,
491      NewRunnableFunction(&DeleteFunc, executable_path()));
492  Run();
493  ASSERT_TRUE(mock_launchd()->remove_called());
494  ASSERT_TRUE(mock_launchd()->delete_called());
495}
496
497TEST_F(ServiceProcessStateFileManipulationTest, DeleteBundle) {
498  GetIOMessageLoopProxy()->PostTask(
499      FROM_HERE,
500      NewRunnableFunction(&DeleteFunc, bundle_path()));
501  Run();
502  ASSERT_TRUE(mock_launchd()->remove_called());
503  ASSERT_TRUE(mock_launchd()->delete_called());
504}
505
506TEST_F(ServiceProcessStateFileManipulationTest, MoveBundle) {
507  FilePath new_loc = GetTempDirPath().AppendASCII("MoveBundle");
508  GetIOMessageLoopProxy()->PostTask(
509      FROM_HERE,
510      NewRunnableFunction(&MoveFunc, bundle_path(), new_loc));
511  Run();
512  ASSERT_TRUE(mock_launchd()->restart_called());
513  ASSERT_TRUE(mock_launchd()->write_called());
514}
515
516TEST_F(ServiceProcessStateFileManipulationTest, MoveFile) {
517  FilePath new_loc = GetTempDirPath().AppendASCII("MoveFile");
518  GetIOMessageLoopProxy()->PostTask(
519      FROM_HERE,
520      NewRunnableFunction(&MoveFunc, executable_path(), new_loc));
521  Run();
522  ASSERT_TRUE(mock_launchd()->remove_called());
523  ASSERT_TRUE(mock_launchd()->delete_called());
524}
525
526TEST_F(ServiceProcessStateFileManipulationTest, TrashBundle) {
527  FSRef bundle_ref;
528  ASSERT_TRUE(base::mac::FSRefFromPath(bundle_path().value(), &bundle_ref));
529  GetIOMessageLoopProxy()->PostTask(
530      FROM_HERE,
531      NewRunnableFunction(&TrashFunc, bundle_path()));
532  Run();
533  ASSERT_TRUE(mock_launchd()->remove_called());
534  ASSERT_TRUE(mock_launchd()->delete_called());
535  std::string path(base::mac::PathFromFSRef(bundle_ref));
536  FilePath file_path(path);
537  ASSERT_TRUE(file_util::Delete(file_path, true));
538}
539
540TEST_F(ServiceProcessStateFileManipulationTest, ChangeAttr) {
541  ScopedAttributesRestorer restorer(bundle_path(), 0777);
542  GetIOMessageLoopProxy()->PostTask(
543      FROM_HERE,
544      NewRunnableFunction(&ChangeAttr, bundle_path(), 0222));
545  Run();
546  ASSERT_TRUE(mock_launchd()->remove_called());
547  ASSERT_TRUE(mock_launchd()->delete_called());
548}
549
550#endif  // !OS_MACOSX
551