Home | History | Annotate | Download | only in mac
      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