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