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/mac/mock_launchd.h" 6 7 #include <CoreFoundation/CoreFoundation.h> 8 #include <sys/socket.h> 9 #include <sys/un.h> 10 11 #include "base/basictypes.h" 12 #include "base/file_util.h" 13 #include "base/files/file_path.h" 14 #include "base/mac/foundation_util.h" 15 #include "base/mac/scoped_cftyperef.h" 16 #include "base/message_loop/message_loop.h" 17 #include "base/strings/string_util.h" 18 #include "base/strings/stringprintf.h" 19 #include "base/strings/sys_string_conversions.h" 20 #include "chrome/common/chrome_version_info.h" 21 #include "chrome/common/mac/launchd.h" 22 #include "chrome/common/service_process_util.h" 23 #include "testing/gtest/include/gtest/gtest.h" 24 25 static sockaddr_un* throwaway_sockaddr_un; 26 static const size_t kMaxPipeNameLength = 27 sizeof(throwaway_sockaddr_un->sun_path); 28 29 // static 30 bool MockLaunchd::MakeABundle(const base::FilePath& dst, 31 const std::string& name, 32 base::FilePath* bundle_root, 33 base::FilePath* executable) { 34 *bundle_root = dst.Append(name + std::string(".app")); 35 base::FilePath contents = bundle_root->AppendASCII("Contents"); 36 base::FilePath mac_os = contents.AppendASCII("MacOS"); 37 *executable = mac_os.Append(name); 38 base::FilePath info_plist = contents.Append("Info.plist"); 39 40 if (!base::CreateDirectory(mac_os)) { 41 return false; 42 } 43 const char *data = "#! testbundle\n"; 44 int len = strlen(data); 45 if (base::WriteFile(*executable, data, len) != len) { 46 return false; 47 } 48 if (chmod(executable->value().c_str(), 0555) != 0) { 49 return false; 50 } 51 52 chrome::VersionInfo version_info; 53 54 const char* info_plist_format = 55 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" 56 "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" " 57 "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" 58 "<plist version=\"1.0\">\n" 59 "<dict>\n" 60 " <key>CFBundleDevelopmentRegion</key>\n" 61 " <string>English</string>\n" 62 " <key>CFBundleExecutable</key>\n" 63 " <string>%s</string>\n" 64 " <key>CFBundleIdentifier</key>\n" 65 " <string>com.test.%s</string>\n" 66 " <key>CFBundleInfoDictionaryVersion</key>\n" 67 " <string>6.0</string>\n" 68 " <key>CFBundleShortVersionString</key>\n" 69 " <string>%s</string>\n" 70 " <key>CFBundleVersion</key>\n" 71 " <string>1</string>\n" 72 "</dict>\n" 73 "</plist>\n"; 74 std::string info_plist_data = 75 base::StringPrintf(info_plist_format, 76 name.c_str(), 77 name.c_str(), 78 version_info.Version().c_str()); 79 len = info_plist_data.length(); 80 if (base::WriteFile(info_plist, info_plist_data.c_str(), len) != len) { 81 return false; 82 } 83 const UInt8* bundle_root_path = 84 reinterpret_cast<const UInt8*>(bundle_root->value().c_str()); 85 base::ScopedCFTypeRef<CFURLRef> url( 86 CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, 87 bundle_root_path, 88 bundle_root->value().length(), 89 true)); 90 base::ScopedCFTypeRef<CFBundleRef> bundle( 91 CFBundleCreate(kCFAllocatorDefault, url)); 92 return bundle.get(); 93 } 94 95 MockLaunchd::MockLaunchd(const base::FilePath& file, 96 base::MessageLoop* loop, 97 bool create_socket, 98 bool as_service) 99 : file_(file), 100 message_loop_(loop), 101 create_socket_(create_socket), 102 as_service_(as_service), 103 restart_called_(false), 104 remove_called_(false), 105 checkin_called_(false), 106 write_called_(false), 107 delete_called_(false) { 108 std::string pipe_suffix("_SOCKET"); 109 base::FilePath socket_path = file_; 110 while (socket_path.value().length() + pipe_suffix.length() > 111 kMaxPipeNameLength - 2) { 112 socket_path = socket_path.DirName(); 113 } 114 pipe_name_ = socket_path.value() + pipe_suffix; 115 } 116 117 MockLaunchd::~MockLaunchd() { 118 } 119 120 CFDictionaryRef MockLaunchd::CopyExports() { 121 if (!create_socket_) { 122 ADD_FAILURE(); 123 return NULL; 124 } 125 126 CFStringRef env_var = 127 base::mac::NSToCFCast(GetServiceProcessLaunchDSocketEnvVar()); 128 base::ScopedCFTypeRef<CFStringRef> socket_path(CFStringCreateWithCString( 129 kCFAllocatorDefault, pipe_name_.c_str(), kCFStringEncodingUTF8)); 130 const void *keys[] = { env_var }; 131 const void *values[] = { socket_path }; 132 COMPILE_ASSERT(arraysize(keys) == arraysize(values), array_sizes_must_match); 133 return CFDictionaryCreate(kCFAllocatorDefault, 134 keys, 135 values, 136 arraysize(keys), 137 &kCFTypeDictionaryKeyCallBacks, 138 &kCFTypeDictionaryValueCallBacks); 139 } 140 141 CFDictionaryRef MockLaunchd::CopyJobDictionary(CFStringRef label) { 142 if (!as_service_) { 143 scoped_ptr<MultiProcessLock> running_lock( 144 TakeNamedLock(pipe_name_, false)); 145 if (running_lock.get()) 146 return NULL; 147 } 148 149 CFStringRef program = CFSTR(LAUNCH_JOBKEY_PROGRAM); 150 CFStringRef program_pid = CFSTR(LAUNCH_JOBKEY_PID); 151 const void *keys[] = { program, program_pid }; 152 base::ScopedCFTypeRef<CFStringRef> path( 153 base::SysUTF8ToCFStringRef(file_.value())); 154 int process_id = base::GetCurrentProcId(); 155 base::ScopedCFTypeRef<CFNumberRef> pid( 156 CFNumberCreate(NULL, kCFNumberIntType, &process_id)); 157 const void *values[] = { path, pid }; 158 COMPILE_ASSERT(arraysize(keys) == arraysize(values), array_sizes_must_match); 159 return CFDictionaryCreate(kCFAllocatorDefault, 160 keys, 161 values, 162 arraysize(keys), 163 &kCFTypeDictionaryKeyCallBacks, 164 &kCFTypeDictionaryValueCallBacks); 165 } 166 167 CFDictionaryRef MockLaunchd::CopyDictionaryByCheckingIn(CFErrorRef* error) { 168 checkin_called_ = true; 169 CFStringRef program = CFSTR(LAUNCH_JOBKEY_PROGRAM); 170 CFStringRef program_args = CFSTR(LAUNCH_JOBKEY_PROGRAMARGUMENTS); 171 base::ScopedCFTypeRef<CFStringRef> path( 172 base::SysUTF8ToCFStringRef(file_.value())); 173 const void *array_values[] = { path.get() }; 174 base::ScopedCFTypeRef<CFArrayRef> args(CFArrayCreate( 175 kCFAllocatorDefault, array_values, 1, &kCFTypeArrayCallBacks)); 176 177 if (!create_socket_) { 178 const void *keys[] = { program, program_args }; 179 const void *values[] = { path, args }; 180 COMPILE_ASSERT(arraysize(keys) == arraysize(values), 181 array_sizes_must_match); 182 return CFDictionaryCreate(kCFAllocatorDefault, 183 keys, 184 values, 185 arraysize(keys), 186 &kCFTypeDictionaryKeyCallBacks, 187 &kCFTypeDictionaryValueCallBacks); 188 } 189 190 CFStringRef socket_key = CFSTR(LAUNCH_JOBKEY_SOCKETS); 191 int local_pipe = -1; 192 EXPECT_TRUE(as_service_); 193 194 // Create unix_addr structure. 195 struct sockaddr_un unix_addr = {0}; 196 unix_addr.sun_family = AF_UNIX; 197 size_t path_len = 198 base::strlcpy(unix_addr.sun_path, pipe_name_.c_str(), kMaxPipeNameLength); 199 DCHECK_EQ(pipe_name_.length(), path_len); 200 unix_addr.sun_len = SUN_LEN(&unix_addr); 201 202 CFSocketSignature signature; 203 signature.protocolFamily = PF_UNIX; 204 signature.socketType = SOCK_STREAM; 205 signature.protocol = 0; 206 size_t unix_addr_len = offsetof(struct sockaddr_un, 207 sun_path) + path_len + 1; 208 base::ScopedCFTypeRef<CFDataRef> address( 209 CFDataCreate(NULL, reinterpret_cast<UInt8*>(&unix_addr), unix_addr_len)); 210 signature.address = address; 211 212 CFSocketRef socket = 213 CFSocketCreateWithSocketSignature(NULL, &signature, 0, NULL, NULL); 214 215 local_pipe = CFSocketGetNative(socket); 216 EXPECT_NE(-1, local_pipe); 217 if (local_pipe == -1) { 218 if (error) { 219 *error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainPOSIX, 220 errno, NULL); 221 } 222 return NULL; 223 } 224 225 base::ScopedCFTypeRef<CFNumberRef> socket_fd( 226 CFNumberCreate(NULL, kCFNumberIntType, &local_pipe)); 227 const void *socket_array_values[] = { socket_fd }; 228 base::ScopedCFTypeRef<CFArrayRef> sockets(CFArrayCreate( 229 kCFAllocatorDefault, socket_array_values, 1, &kCFTypeArrayCallBacks)); 230 CFStringRef socket_dict_key = CFSTR("ServiceProcessSocket"); 231 const void *socket_keys[] = { socket_dict_key }; 232 const void *socket_values[] = { sockets }; 233 COMPILE_ASSERT(arraysize(socket_keys) == arraysize(socket_values), 234 socket_array_sizes_must_match); 235 base::ScopedCFTypeRef<CFDictionaryRef> socket_dict( 236 CFDictionaryCreate(kCFAllocatorDefault, 237 socket_keys, 238 socket_values, 239 arraysize(socket_keys), 240 &kCFTypeDictionaryKeyCallBacks, 241 &kCFTypeDictionaryValueCallBacks)); 242 const void *keys[] = { program, program_args, socket_key }; 243 const void *values[] = { path, args, socket_dict }; 244 COMPILE_ASSERT(arraysize(keys) == arraysize(values), array_sizes_must_match); 245 return CFDictionaryCreate(kCFAllocatorDefault, 246 keys, 247 values, 248 arraysize(keys), 249 &kCFTypeDictionaryKeyCallBacks, 250 &kCFTypeDictionaryValueCallBacks); 251 } 252 253 bool MockLaunchd::RemoveJob(CFStringRef label, CFErrorRef* error) { 254 remove_called_ = true; 255 message_loop_->PostTask(FROM_HERE, base::MessageLoop::QuitClosure()); 256 return true; 257 } 258 259 bool MockLaunchd::RestartJob(Domain domain, 260 Type type, 261 CFStringRef name, 262 CFStringRef session_type) { 263 restart_called_ = true; 264 message_loop_->PostTask(FROM_HERE, base::MessageLoop::QuitClosure()); 265 return true; 266 } 267 268 CFMutableDictionaryRef MockLaunchd::CreatePlistFromFile( 269 Domain domain, 270 Type type, 271 CFStringRef name) { 272 base::ScopedCFTypeRef<CFDictionaryRef> dict(CopyDictionaryByCheckingIn(NULL)); 273 return CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, dict); 274 } 275 276 bool MockLaunchd::WritePlistToFile(Domain domain, 277 Type type, 278 CFStringRef name, 279 CFDictionaryRef dict) { 280 write_called_ = true; 281 return true; 282 } 283 284 bool MockLaunchd::DeletePlist(Domain domain, 285 Type type, 286 CFStringRef name) { 287 delete_called_ = true; 288 return true; 289 } 290 291 void MockLaunchd::SignalReady() { 292 ASSERT_TRUE(as_service_); 293 running_lock_.reset(TakeNamedLock(pipe_name_, true)); 294 } 295