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
,
100 pipe_name_(GetServiceProcessChannel().name
),
102 create_socket_(create_socket
),
103 as_service_(as_service
),
104 restart_called_(false),
105 remove_called_(false),
106 checkin_called_(false),
107 write_called_(false),
108 delete_called_(false) {
111 MockLaunchd::~MockLaunchd() {
114 CFDictionaryRef
MockLaunchd::CopyJobDictionary(CFStringRef label
) {
116 scoped_ptr
<MultiProcessLock
> running_lock(
117 TakeNamedLock(pipe_name_
, false));
118 if (running_lock
.get())
122 CFStringRef program
= CFSTR(LAUNCH_JOBKEY_PROGRAM
);
123 CFStringRef program_pid
= CFSTR(LAUNCH_JOBKEY_PID
);
124 const void *keys
[] = { program
, program_pid
};
125 base::ScopedCFTypeRef
<CFStringRef
> path(
126 base::SysUTF8ToCFStringRef(file_
.value()));
127 int process_id
= base::GetCurrentProcId();
128 base::ScopedCFTypeRef
<CFNumberRef
> pid(
129 CFNumberCreate(NULL
, kCFNumberIntType
, &process_id
));
130 const void *values
[] = { path
, pid
};
131 static_assert(arraysize(keys
) == arraysize(values
),
132 "keys must have the same number of elements as values");
133 return CFDictionaryCreate(kCFAllocatorDefault
,
137 &kCFTypeDictionaryKeyCallBacks
,
138 &kCFTypeDictionaryValueCallBacks
);
141 CFDictionaryRef
MockLaunchd::CopyDictionaryByCheckingIn(CFErrorRef
* error
) {
142 checkin_called_
= true;
143 CFStringRef program
= CFSTR(LAUNCH_JOBKEY_PROGRAM
);
144 CFStringRef program_args
= CFSTR(LAUNCH_JOBKEY_PROGRAMARGUMENTS
);
145 base::ScopedCFTypeRef
<CFStringRef
> path(
146 base::SysUTF8ToCFStringRef(file_
.value()));
147 const void *array_values
[] = { path
.get() };
148 base::ScopedCFTypeRef
<CFArrayRef
> args(CFArrayCreate(
149 kCFAllocatorDefault
, array_values
, 1, &kCFTypeArrayCallBacks
));
151 if (!create_socket_
) {
152 const void *keys
[] = { program
, program_args
};
153 const void *values
[] = { path
, args
};
154 static_assert(arraysize(keys
) == arraysize(values
),
155 "keys must have the same number of elements as values");
156 return CFDictionaryCreate(kCFAllocatorDefault
,
160 &kCFTypeDictionaryKeyCallBacks
,
161 &kCFTypeDictionaryValueCallBacks
);
164 CFStringRef socket_key
= CFSTR(LAUNCH_JOBKEY_SOCKETS
);
166 EXPECT_TRUE(as_service_
);
168 // Create unix_addr structure.
169 struct sockaddr_un unix_addr
= {0};
170 unix_addr
.sun_family
= AF_UNIX
;
172 base::strlcpy(unix_addr
.sun_path
, pipe_name_
.c_str(), kMaxPipeNameLength
);
173 DCHECK_EQ(pipe_name_
.length(), path_len
);
174 unix_addr
.sun_len
= SUN_LEN(&unix_addr
);
176 CFSocketSignature signature
;
177 signature
.protocolFamily
= PF_UNIX
;
178 signature
.socketType
= SOCK_STREAM
;
179 signature
.protocol
= 0;
180 size_t unix_addr_len
= offsetof(struct sockaddr_un
,
181 sun_path
) + path_len
+ 1;
182 base::ScopedCFTypeRef
<CFDataRef
> address(
183 CFDataCreate(NULL
, reinterpret_cast<UInt8
*>(&unix_addr
), unix_addr_len
));
184 signature
.address
= address
;
187 CFSocketCreateWithSocketSignature(NULL
, &signature
, 0, NULL
, NULL
);
189 local_pipe
= CFSocketGetNative(socket
);
190 EXPECT_NE(-1, local_pipe
);
191 if (local_pipe
== -1) {
193 *error
= CFErrorCreate(kCFAllocatorDefault
, kCFErrorDomainPOSIX
,
199 base::ScopedCFTypeRef
<CFNumberRef
> socket_fd(
200 CFNumberCreate(NULL
, kCFNumberIntType
, &local_pipe
));
201 const void *socket_array_values
[] = { socket_fd
};
202 base::ScopedCFTypeRef
<CFArrayRef
> sockets(CFArrayCreate(
203 kCFAllocatorDefault
, socket_array_values
, 1, &kCFTypeArrayCallBacks
));
204 CFStringRef socket_dict_key
= CFSTR("ServiceProcessSocket");
205 const void *socket_keys
[] = { socket_dict_key
};
206 const void *socket_values
[] = { sockets
};
207 static_assert(arraysize(socket_keys
) == arraysize(socket_values
),
208 "socket_keys must have the same number of elements "
210 base::ScopedCFTypeRef
<CFDictionaryRef
> socket_dict(
211 CFDictionaryCreate(kCFAllocatorDefault
,
214 arraysize(socket_keys
),
215 &kCFTypeDictionaryKeyCallBacks
,
216 &kCFTypeDictionaryValueCallBacks
));
217 const void *keys
[] = { program
, program_args
, socket_key
};
218 const void *values
[] = { path
, args
, socket_dict
};
219 static_assert(arraysize(keys
) == arraysize(values
),
220 "keys must have the same number of elements as values");
221 return CFDictionaryCreate(kCFAllocatorDefault
,
225 &kCFTypeDictionaryKeyCallBacks
,
226 &kCFTypeDictionaryValueCallBacks
);
229 bool MockLaunchd::RemoveJob(CFStringRef label
, CFErrorRef
* error
) {
230 remove_called_
= true;
231 message_loop_
->PostTask(FROM_HERE
, base::MessageLoop::QuitClosure());
235 bool MockLaunchd::RestartJob(Domain domain
,
238 CFStringRef session_type
) {
239 restart_called_
= true;
240 message_loop_
->PostTask(FROM_HERE
, base::MessageLoop::QuitClosure());
244 CFMutableDictionaryRef
MockLaunchd::CreatePlistFromFile(
248 base::ScopedCFTypeRef
<CFDictionaryRef
> dict(CopyDictionaryByCheckingIn(NULL
));
249 return CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, dict
);
252 bool MockLaunchd::WritePlistToFile(Domain domain
,
255 CFDictionaryRef dict
) {
256 write_called_
= true;
260 bool MockLaunchd::DeletePlist(Domain domain
,
263 delete_called_
= true;
267 void MockLaunchd::SignalReady() {
268 ASSERT_TRUE(as_service_
);
269 running_lock_
.reset(TakeNamedLock(pipe_name_
, true));