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.
5 #include "chrome/common/mac/mock_launchd.h"
7 #include <CoreFoundation/CoreFoundation.h>
8 #include <sys/socket.h>
11 #include "base/basictypes.h"
12 #include "base/files/file_path.h"
13 #include "base/files/file_util.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"
25 static sockaddr_un
* throwaway_sockaddr_un
;
26 static const size_t kMaxPipeNameLength
=
27 sizeof(throwaway_sockaddr_un
->sun_path
);
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");
40 if (!base::CreateDirectory(mac_os
)) {
43 const char *data
= "#! testbundle\n";
44 int len
= strlen(data
);
45 if (base::WriteFile(*executable
, data
, len
) != len
) {
48 if (chmod(executable
->value().c_str(), 0555) != 0) {
52 chrome::VersionInfo version_info
;
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"
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"
74 std::string info_plist_data
=
75 base::StringPrintf(info_plist_format
,
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
) {
83 const UInt8
* bundle_root_path
=
84 reinterpret_cast<const UInt8
*>(bundle_root
->value().c_str());
85 base::ScopedCFTypeRef
<CFURLRef
> url(
86 CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault
,
88 bundle_root
->value().length(),
90 base::ScopedCFTypeRef
<CFBundleRef
> bundle(
91 CFBundleCreate(kCFAllocatorDefault
, url
));
95 MockLaunchd::MockLaunchd(const base::FilePath
& file
,
96 base::MessageLoop
* 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();
114 pipe_name_
= socket_path
.value() + pipe_suffix
;
117 MockLaunchd::~MockLaunchd() {
120 CFDictionaryRef
MockLaunchd::CopyExports() {
121 if (!create_socket_
) {
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
,
137 &kCFTypeDictionaryKeyCallBacks
,
138 &kCFTypeDictionaryValueCallBacks
);
141 CFDictionaryRef
MockLaunchd::CopyJobDictionary(CFStringRef label
) {
143 scoped_ptr
<MultiProcessLock
> running_lock(
144 TakeNamedLock(pipe_name_
, false));
145 if (running_lock
.get())
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
,
163 &kCFTypeDictionaryKeyCallBacks
,
164 &kCFTypeDictionaryValueCallBacks
);
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
));
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
,
186 &kCFTypeDictionaryKeyCallBacks
,
187 &kCFTypeDictionaryValueCallBacks
);
190 CFStringRef socket_key
= CFSTR(LAUNCH_JOBKEY_SOCKETS
);
192 EXPECT_TRUE(as_service_
);
194 // Create unix_addr structure.
195 struct sockaddr_un unix_addr
= {0};
196 unix_addr
.sun_family
= AF_UNIX
;
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
);
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
;
213 CFSocketCreateWithSocketSignature(NULL
, &signature
, 0, NULL
, NULL
);
215 local_pipe
= CFSocketGetNative(socket
);
216 EXPECT_NE(-1, local_pipe
);
217 if (local_pipe
== -1) {
219 *error
= CFErrorCreate(kCFAllocatorDefault
, kCFErrorDomainPOSIX
,
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
,
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
,
249 &kCFTypeDictionaryKeyCallBacks
,
250 &kCFTypeDictionaryValueCallBacks
);
253 bool MockLaunchd::RemoveJob(CFStringRef label
, CFErrorRef
* error
) {
254 remove_called_
= true;
255 message_loop_
->PostTask(FROM_HERE
, base::MessageLoop::QuitClosure());
259 bool MockLaunchd::RestartJob(Domain domain
,
262 CFStringRef session_type
) {
263 restart_called_
= true;
264 message_loop_
->PostTask(FROM_HERE
, base::MessageLoop::QuitClosure());
268 CFMutableDictionaryRef
MockLaunchd::CreatePlistFromFile(
272 base::ScopedCFTypeRef
<CFDictionaryRef
> dict(CopyDictionaryByCheckingIn(NULL
));
273 return CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, dict
);
276 bool MockLaunchd::WritePlistToFile(Domain domain
,
279 CFDictionaryRef dict
) {
280 write_called_
= true;
284 bool MockLaunchd::DeletePlist(Domain domain
,
287 delete_called_
= true;
291 void MockLaunchd::SignalReady() {
292 ASSERT_TRUE(as_service_
);
293 running_lock_
.reset(TakeNamedLock(pipe_name_
, true));