Home | History | Annotate | Download | only in common
      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 
     32 namespace {
     33 
     34 bool g_good_shutdown = false;
     35 
     36 void 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 
     45 TEST(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 
     54 class 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 
     70 ServiceProcessStateTest::ServiceProcessStateTest()
     71     : io_thread_("ServiceProcessStateTestThread") {
     72 }
     73 
     74 ServiceProcessStateTest::~ServiceProcessStateTest() {
     75 }
     76 
     77 void ServiceProcessStateTest::SetUp() {
     78   base::Thread::Options options(MessageLoop::TYPE_IO, 0);
     79   ASSERT_TRUE(io_thread_.StartWithOptions(options));
     80 }
     81 
     82 void 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 
     90 TEST_F(ServiceProcessStateTest, Singleton) {
     91   ServiceProcessState state;
     92   ASSERT_TRUE(state.Initialize());
     93   LaunchAndWait("ServiceProcessStateTestSingleton");
     94 }
     95 
     96 TEST_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 
    106 TEST_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 
    152 TEST_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 
    169 TEST_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 
    188 MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestSingleton) {
    189   ServiceProcessState state;
    190   EXPECT_FALSE(state.Initialize());
    191   return 0;
    192 }
    193 
    194 MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestReadyTrue) {
    195   EXPECT_TRUE(CheckServiceProcessReady());
    196   return 0;
    197 }
    198 
    199 MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestReadyFalse) {
    200   EXPECT_FALSE(CheckServiceProcessReady());
    201   return 0;
    202 }
    203 
    204 MULTIPROCESS_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
    246 class 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 
    347 class 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 
    453 void DeleteFunc(const FilePath& file) {
    454   EXPECT_TRUE(file_util::Delete(file, true));
    455 }
    456 
    457 void MoveFunc(const FilePath& from, const FilePath& to) {
    458   EXPECT_TRUE(file_util::Move(from, to));
    459 }
    460 
    461 void ChangeAttr(const FilePath& from, int mode) {
    462   EXPECT_EQ(chmod(from.value().c_str(), mode), 0);
    463 }
    464 
    465 class 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 
    478 void 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 
    488 TEST_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 
    497 TEST_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 
    506 TEST_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 
    516 TEST_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 
    526 TEST_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 
    540 TEST_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