| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/common/mac/mock_launchd.h" |
| |
| #include <CoreFoundation/CoreFoundation.h> |
| #include <sys/socket.h> |
| #include <sys/un.h> |
| |
| #include "base/basictypes.h" |
| #include "base/file_util.h" |
| #include "base/files/file_path.h" |
| #include "base/mac/foundation_util.h" |
| #include "base/mac/scoped_cftyperef.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "chrome/common/chrome_version_info.h" |
| #include "chrome/common/mac/launchd.h" |
| #include "chrome/common/service_process_util.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| static sockaddr_un* throwaway_sockaddr_un; |
| static const size_t kMaxPipeNameLength = |
| sizeof(throwaway_sockaddr_un->sun_path); |
| |
| // static |
| bool MockLaunchd::MakeABundle(const base::FilePath& dst, |
| const std::string& name, |
| base::FilePath* bundle_root, |
| base::FilePath* executable) { |
| *bundle_root = dst.Append(name + std::string(".app")); |
| base::FilePath contents = bundle_root->AppendASCII("Contents"); |
| base::FilePath mac_os = contents.AppendASCII("MacOS"); |
| *executable = mac_os.Append(name); |
| base::FilePath info_plist = contents.Append("Info.plist"); |
| |
| if (!base::CreateDirectory(mac_os)) { |
| return false; |
| } |
| const char *data = "#! testbundle\n"; |
| int len = strlen(data); |
| if (file_util::WriteFile(*executable, data, len) != len) { |
| return false; |
| } |
| if (chmod(executable->value().c_str(), 0555) != 0) { |
| return false; |
| } |
| |
| chrome::VersionInfo version_info; |
| |
| const char* info_plist_format = |
| "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" |
| "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" " |
| "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" |
| "<plist version=\"1.0\">\n" |
| "<dict>\n" |
| " <key>CFBundleDevelopmentRegion</key>\n" |
| " <string>English</string>\n" |
| " <key>CFBundleExecutable</key>\n" |
| " <string>%s</string>\n" |
| " <key>CFBundleIdentifier</key>\n" |
| " <string>com.test.%s</string>\n" |
| " <key>CFBundleInfoDictionaryVersion</key>\n" |
| " <string>6.0</string>\n" |
| " <key>CFBundleShortVersionString</key>\n" |
| " <string>%s</string>\n" |
| " <key>CFBundleVersion</key>\n" |
| " <string>1</string>\n" |
| "</dict>\n" |
| "</plist>\n"; |
| std::string info_plist_data = |
| base::StringPrintf(info_plist_format, |
| name.c_str(), |
| name.c_str(), |
| version_info.Version().c_str()); |
| len = info_plist_data.length(); |
| if (file_util::WriteFile(info_plist, info_plist_data.c_str(), len) != len) { |
| return false; |
| } |
| const UInt8* bundle_root_path = |
| reinterpret_cast<const UInt8*>(bundle_root->value().c_str()); |
| base::ScopedCFTypeRef<CFURLRef> url( |
| CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, |
| bundle_root_path, |
| bundle_root->value().length(), |
| true)); |
| base::ScopedCFTypeRef<CFBundleRef> bundle( |
| CFBundleCreate(kCFAllocatorDefault, url)); |
| return bundle.get(); |
| } |
| |
| MockLaunchd::MockLaunchd(const base::FilePath& file, |
| base::MessageLoop* loop, |
| bool create_socket, |
| bool as_service) |
| : file_(file), |
| message_loop_(loop), |
| create_socket_(create_socket), |
| as_service_(as_service), |
| restart_called_(false), |
| remove_called_(false), |
| checkin_called_(false), |
| write_called_(false), |
| delete_called_(false) { |
| std::string pipe_suffix("_SOCKET"); |
| base::FilePath socket_path = file_; |
| while (socket_path.value().length() + pipe_suffix.length() > |
| kMaxPipeNameLength - 2) { |
| socket_path = socket_path.DirName(); |
| } |
| pipe_name_ = socket_path.value() + pipe_suffix; |
| } |
| |
| MockLaunchd::~MockLaunchd() { |
| } |
| |
| CFDictionaryRef MockLaunchd::CopyExports() { |
| if (!create_socket_) { |
| ADD_FAILURE(); |
| return NULL; |
| } |
| |
| CFStringRef env_var = |
| base::mac::NSToCFCast(GetServiceProcessLaunchDSocketEnvVar()); |
| base::ScopedCFTypeRef<CFStringRef> socket_path(CFStringCreateWithCString( |
| kCFAllocatorDefault, pipe_name_.c_str(), kCFStringEncodingUTF8)); |
| const void *keys[] = { env_var }; |
| const void *values[] = { socket_path }; |
| COMPILE_ASSERT(arraysize(keys) == arraysize(values), array_sizes_must_match); |
| return CFDictionaryCreate(kCFAllocatorDefault, |
| keys, |
| values, |
| arraysize(keys), |
| &kCFTypeDictionaryKeyCallBacks, |
| &kCFTypeDictionaryValueCallBacks); |
| } |
| |
| CFDictionaryRef MockLaunchd::CopyJobDictionary(CFStringRef label) { |
| if (!as_service_) { |
| scoped_ptr<MultiProcessLock> running_lock( |
| TakeNamedLock(pipe_name_, false)); |
| if (running_lock.get()) |
| return NULL; |
| } |
| |
| CFStringRef program = CFSTR(LAUNCH_JOBKEY_PROGRAM); |
| CFStringRef program_pid = CFSTR(LAUNCH_JOBKEY_PID); |
| const void *keys[] = { program, program_pid }; |
| base::ScopedCFTypeRef<CFStringRef> path( |
| base::SysUTF8ToCFStringRef(file_.value())); |
| int process_id = base::GetCurrentProcId(); |
| base::ScopedCFTypeRef<CFNumberRef> pid( |
| CFNumberCreate(NULL, kCFNumberIntType, &process_id)); |
| const void *values[] = { path, pid }; |
| COMPILE_ASSERT(arraysize(keys) == arraysize(values), array_sizes_must_match); |
| return CFDictionaryCreate(kCFAllocatorDefault, |
| keys, |
| values, |
| arraysize(keys), |
| &kCFTypeDictionaryKeyCallBacks, |
| &kCFTypeDictionaryValueCallBacks); |
| } |
| |
| CFDictionaryRef MockLaunchd::CopyDictionaryByCheckingIn(CFErrorRef* error) { |
| checkin_called_ = true; |
| CFStringRef program = CFSTR(LAUNCH_JOBKEY_PROGRAM); |
| CFStringRef program_args = CFSTR(LAUNCH_JOBKEY_PROGRAMARGUMENTS); |
| base::ScopedCFTypeRef<CFStringRef> path( |
| base::SysUTF8ToCFStringRef(file_.value())); |
| const void *array_values[] = { path.get() }; |
| base::ScopedCFTypeRef<CFArrayRef> args(CFArrayCreate( |
| kCFAllocatorDefault, array_values, 1, &kCFTypeArrayCallBacks)); |
| |
| if (!create_socket_) { |
| const void *keys[] = { program, program_args }; |
| const void *values[] = { path, args }; |
| COMPILE_ASSERT(arraysize(keys) == arraysize(values), |
| array_sizes_must_match); |
| return CFDictionaryCreate(kCFAllocatorDefault, |
| keys, |
| values, |
| arraysize(keys), |
| &kCFTypeDictionaryKeyCallBacks, |
| &kCFTypeDictionaryValueCallBacks); |
| } |
| |
| CFStringRef socket_key = CFSTR(LAUNCH_JOBKEY_SOCKETS); |
| int local_pipe = -1; |
| EXPECT_TRUE(as_service_); |
| |
| // Create unix_addr structure. |
| struct sockaddr_un unix_addr = {0}; |
| unix_addr.sun_family = AF_UNIX; |
| size_t path_len = |
| base::strlcpy(unix_addr.sun_path, pipe_name_.c_str(), kMaxPipeNameLength); |
| DCHECK_EQ(pipe_name_.length(), path_len); |
| unix_addr.sun_len = SUN_LEN(&unix_addr); |
| |
| CFSocketSignature signature; |
| signature.protocolFamily = PF_UNIX; |
| signature.socketType = SOCK_STREAM; |
| signature.protocol = 0; |
| size_t unix_addr_len = offsetof(struct sockaddr_un, |
| sun_path) + path_len + 1; |
| base::ScopedCFTypeRef<CFDataRef> address( |
| CFDataCreate(NULL, reinterpret_cast<UInt8*>(&unix_addr), unix_addr_len)); |
| signature.address = address; |
| |
| CFSocketRef socket = |
| CFSocketCreateWithSocketSignature(NULL, &signature, 0, NULL, NULL); |
| |
| local_pipe = CFSocketGetNative(socket); |
| EXPECT_NE(-1, local_pipe); |
| if (local_pipe == -1) { |
| if (error) { |
| *error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainPOSIX, |
| errno, NULL); |
| } |
| return NULL; |
| } |
| |
| base::ScopedCFTypeRef<CFNumberRef> socket_fd( |
| CFNumberCreate(NULL, kCFNumberIntType, &local_pipe)); |
| const void *socket_array_values[] = { socket_fd }; |
| base::ScopedCFTypeRef<CFArrayRef> sockets(CFArrayCreate( |
| kCFAllocatorDefault, socket_array_values, 1, &kCFTypeArrayCallBacks)); |
| CFStringRef socket_dict_key = CFSTR("ServiceProcessSocket"); |
| const void *socket_keys[] = { socket_dict_key }; |
| const void *socket_values[] = { sockets }; |
| COMPILE_ASSERT(arraysize(socket_keys) == arraysize(socket_values), |
| socket_array_sizes_must_match); |
| base::ScopedCFTypeRef<CFDictionaryRef> socket_dict( |
| CFDictionaryCreate(kCFAllocatorDefault, |
| socket_keys, |
| socket_values, |
| arraysize(socket_keys), |
| &kCFTypeDictionaryKeyCallBacks, |
| &kCFTypeDictionaryValueCallBacks)); |
| const void *keys[] = { program, program_args, socket_key }; |
| const void *values[] = { path, args, socket_dict }; |
| COMPILE_ASSERT(arraysize(keys) == arraysize(values), array_sizes_must_match); |
| return CFDictionaryCreate(kCFAllocatorDefault, |
| keys, |
| values, |
| arraysize(keys), |
| &kCFTypeDictionaryKeyCallBacks, |
| &kCFTypeDictionaryValueCallBacks); |
| } |
| |
| bool MockLaunchd::RemoveJob(CFStringRef label, CFErrorRef* error) { |
| remove_called_ = true; |
| message_loop_->PostTask(FROM_HERE, base::MessageLoop::QuitClosure()); |
| return true; |
| } |
| |
| bool MockLaunchd::RestartJob(Domain domain, |
| Type type, |
| CFStringRef name, |
| CFStringRef session_type) { |
| restart_called_ = true; |
| message_loop_->PostTask(FROM_HERE, base::MessageLoop::QuitClosure()); |
| return true; |
| } |
| |
| CFMutableDictionaryRef MockLaunchd::CreatePlistFromFile( |
| Domain domain, |
| Type type, |
| CFStringRef name) { |
| base::ScopedCFTypeRef<CFDictionaryRef> dict(CopyDictionaryByCheckingIn(NULL)); |
| return CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, dict); |
| } |
| |
| bool MockLaunchd::WritePlistToFile(Domain domain, |
| Type type, |
| CFStringRef name, |
| CFDictionaryRef dict) { |
| write_called_ = true; |
| return true; |
| } |
| |
| bool MockLaunchd::DeletePlist(Domain domain, |
| Type type, |
| CFStringRef name) { |
| delete_called_ = true; |
| return true; |
| } |
| |
| void MockLaunchd::SignalReady() { |
| ASSERT_TRUE(as_service_); |
| running_lock_.reset(TakeNamedLock(pipe_name_, true)); |
| } |