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/mac/launchd.h"
21 #include "chrome/common/service_process_util.h"
22 #include "components/version_info/version_info.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 const char info_plist_format
[] =
53 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
54 "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" "
55 "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
56 "<plist version=\"1.0\">\n"
58 " <key>CFBundleDevelopmentRegion</key>\n"
59 " <string>English</string>\n"
60 " <key>CFBundleExecutable</key>\n"
61 " <string>%s</string>\n"
62 " <key>CFBundleIdentifier</key>\n"
63 " <string>com.test.%s</string>\n"
64 " <key>CFBundleInfoDictionaryVersion</key>\n"
65 " <string>6.0</string>\n"
66 " <key>CFBundleShortVersionString</key>\n"
67 " <string>%s</string>\n"
68 " <key>CFBundleVersion</key>\n"
69 " <string>1</string>\n"
72 std::string info_plist_data
=
73 base::StringPrintf(info_plist_format
,
76 version_info::GetVersionNumber().c_str());
77 len
= info_plist_data
.length();
78 if (base::WriteFile(info_plist
, info_plist_data
.c_str(), len
) != len
) {
81 const UInt8
* bundle_root_path
=
82 reinterpret_cast<const UInt8
*>(bundle_root
->value().c_str());
83 base::ScopedCFTypeRef
<CFURLRef
> url(
84 CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault
,
86 bundle_root
->value().length(),
88 base::ScopedCFTypeRef
<CFBundleRef
> bundle(
89 CFBundleCreate(kCFAllocatorDefault
, url
));
93 MockLaunchd::MockLaunchd(const base::FilePath
& file
,
94 base::MessageLoop
* loop
,
98 pipe_name_(GetServiceProcessChannel().name
),
100 create_socket_(create_socket
),
101 as_service_(as_service
),
102 restart_called_(false),
103 remove_called_(false),
104 checkin_called_(false),
105 write_called_(false),
106 delete_called_(false) {
109 MockLaunchd::~MockLaunchd() {
112 CFDictionaryRef
MockLaunchd::CopyJobDictionary(CFStringRef label
) {
114 scoped_ptr
<MultiProcessLock
> running_lock(
115 TakeNamedLock(pipe_name_
, false));
116 if (running_lock
.get())
120 CFStringRef program
= CFSTR(LAUNCH_JOBKEY_PROGRAM
);
121 CFStringRef program_pid
= CFSTR(LAUNCH_JOBKEY_PID
);
122 const void *keys
[] = { program
, program_pid
};
123 base::ScopedCFTypeRef
<CFStringRef
> path(
124 base::SysUTF8ToCFStringRef(file_
.value()));
125 int process_id
= base::GetCurrentProcId();
126 base::ScopedCFTypeRef
<CFNumberRef
> pid(
127 CFNumberCreate(NULL
, kCFNumberIntType
, &process_id
));
128 const void *values
[] = { path
, pid
};
129 static_assert(arraysize(keys
) == arraysize(values
),
130 "keys must have the same number of elements as values");
131 return CFDictionaryCreate(kCFAllocatorDefault
,
135 &kCFTypeDictionaryKeyCallBacks
,
136 &kCFTypeDictionaryValueCallBacks
);
139 CFDictionaryRef
MockLaunchd::CopyDictionaryByCheckingIn(CFErrorRef
* error
) {
140 checkin_called_
= true;
141 CFStringRef program
= CFSTR(LAUNCH_JOBKEY_PROGRAM
);
142 CFStringRef program_args
= CFSTR(LAUNCH_JOBKEY_PROGRAMARGUMENTS
);
143 base::ScopedCFTypeRef
<CFStringRef
> path(
144 base::SysUTF8ToCFStringRef(file_
.value()));
145 const void *array_values
[] = { path
.get() };
146 base::ScopedCFTypeRef
<CFArrayRef
> args(CFArrayCreate(
147 kCFAllocatorDefault
, array_values
, 1, &kCFTypeArrayCallBacks
));
149 if (!create_socket_
) {
150 const void *keys
[] = { program
, program_args
};
151 const void *values
[] = { path
, args
};
152 static_assert(arraysize(keys
) == arraysize(values
),
153 "keys must have the same number of elements as values");
154 return CFDictionaryCreate(kCFAllocatorDefault
,
158 &kCFTypeDictionaryKeyCallBacks
,
159 &kCFTypeDictionaryValueCallBacks
);
162 CFStringRef socket_key
= CFSTR(LAUNCH_JOBKEY_SOCKETS
);
164 EXPECT_TRUE(as_service_
);
166 // Create unix_addr structure.
167 struct sockaddr_un unix_addr
= {0};
168 unix_addr
.sun_family
= AF_UNIX
;
170 base::strlcpy(unix_addr
.sun_path
, pipe_name_
.c_str(), kMaxPipeNameLength
);
171 DCHECK_EQ(pipe_name_
.length(), path_len
);
172 unix_addr
.sun_len
= SUN_LEN(&unix_addr
);
174 CFSocketSignature signature
;
175 signature
.protocolFamily
= PF_UNIX
;
176 signature
.socketType
= SOCK_STREAM
;
177 signature
.protocol
= 0;
178 size_t unix_addr_len
= offsetof(struct sockaddr_un
,
179 sun_path
) + path_len
+ 1;
180 base::ScopedCFTypeRef
<CFDataRef
> address(
181 CFDataCreate(NULL
, reinterpret_cast<UInt8
*>(&unix_addr
), unix_addr_len
));
182 signature
.address
= address
;
185 CFSocketCreateWithSocketSignature(NULL
, &signature
, 0, NULL
, NULL
);
187 local_pipe
= CFSocketGetNative(socket
);
188 EXPECT_NE(-1, local_pipe
);
189 if (local_pipe
== -1) {
191 *error
= CFErrorCreate(kCFAllocatorDefault
, kCFErrorDomainPOSIX
,
197 base::ScopedCFTypeRef
<CFNumberRef
> socket_fd(
198 CFNumberCreate(NULL
, kCFNumberIntType
, &local_pipe
));
199 const void *socket_array_values
[] = { socket_fd
};
200 base::ScopedCFTypeRef
<CFArrayRef
> sockets(CFArrayCreate(
201 kCFAllocatorDefault
, socket_array_values
, 1, &kCFTypeArrayCallBacks
));
202 CFStringRef socket_dict_key
= CFSTR("ServiceProcessSocket");
203 const void *socket_keys
[] = { socket_dict_key
};
204 const void *socket_values
[] = { sockets
};
205 static_assert(arraysize(socket_keys
) == arraysize(socket_values
),
206 "socket_keys must have the same number of elements "
208 base::ScopedCFTypeRef
<CFDictionaryRef
> socket_dict(
209 CFDictionaryCreate(kCFAllocatorDefault
,
212 arraysize(socket_keys
),
213 &kCFTypeDictionaryKeyCallBacks
,
214 &kCFTypeDictionaryValueCallBacks
));
215 const void *keys
[] = { program
, program_args
, socket_key
};
216 const void *values
[] = { path
, args
, socket_dict
};
217 static_assert(arraysize(keys
) == arraysize(values
),
218 "keys must have the same number of elements as values");
219 return CFDictionaryCreate(kCFAllocatorDefault
,
223 &kCFTypeDictionaryKeyCallBacks
,
224 &kCFTypeDictionaryValueCallBacks
);
227 bool MockLaunchd::RemoveJob(CFStringRef label
, CFErrorRef
* error
) {
228 remove_called_
= true;
229 message_loop_
->PostTask(FROM_HERE
, base::MessageLoop::QuitClosure());
233 bool MockLaunchd::RestartJob(Domain domain
,
236 CFStringRef session_type
) {
237 restart_called_
= true;
238 message_loop_
->PostTask(FROM_HERE
, base::MessageLoop::QuitClosure());
242 CFMutableDictionaryRef
MockLaunchd::CreatePlistFromFile(
246 base::ScopedCFTypeRef
<CFDictionaryRef
> dict(CopyDictionaryByCheckingIn(NULL
));
247 return CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, dict
);
250 bool MockLaunchd::WritePlistToFile(Domain domain
,
253 CFDictionaryRef dict
) {
254 write_called_
= true;
258 bool MockLaunchd::DeletePlist(Domain domain
,
261 delete_called_
= true;
265 void MockLaunchd::SignalReady() {
266 ASSERT_TRUE(as_service_
);
267 running_lock_
.reset(TakeNamedLock(pipe_name_
, true));