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
25static sockaddr_un* throwaway_sockaddr_un;
26static const size_t kMaxPipeNameLength =
27    sizeof(throwaway_sockaddr_un->sun_path);
28
29// static
30bool 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 (!file_util::CreateDirectory(mac_os)) {
41    return false;
42  }
43  const char *data = "#! testbundle\n";
44  int len = strlen(data);
45  if (file_util::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 (file_util::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
95MockLaunchd::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
117MockLaunchd::~MockLaunchd() {
118}
119
120CFDictionaryRef 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
141CFDictionaryRef 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
167CFDictionaryRef 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
253bool MockLaunchd::RemoveJob(CFStringRef label, CFErrorRef* error) {
254  remove_called_ = true;
255  message_loop_->PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
256  return true;
257}
258
259bool 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
268CFMutableDictionaryRef 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
276bool MockLaunchd::WritePlistToFile(Domain domain,
277                                   Type type,
278                                   CFStringRef name,
279                                   CFDictionaryRef dict) {
280  write_called_ = true;
281  return true;
282}
283
284bool MockLaunchd::DeletePlist(Domain domain,
285                              Type type,
286                              CFStringRef name) {
287  delete_called_ = true;
288  return true;
289}
290
291void MockLaunchd::SignalReady() {
292  ASSERT_TRUE(as_service_);
293  running_lock_.reset(TakeNamedLock(pipe_name_, true));
294}
295