1 /*
2 * Copyright (C) 2014 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 #import "LTRelayController.h"
27
28 #import "CoreSimulatorSPI.h"
29 #import "LTPipeRelay.h"
30 #import <AppKit/AppKit.h>
31 #import <crt_externs.h>
32
33 @interface LTRelayController ()
34 @property (readonly, strong) dispatch_source_t standardInputDispatchSource;
35 @property (readonly, strong) NSFileHandle *standardOutput;
36 @property (readonly, strong) NSFileHandle *standardError;
37 @property (readonly, strong) NSString *ipcIdentifier;
38 @property (readonly, strong) NSString *appBundleIdentifier;
39 @property (readonly, strong) NSString *appPath;
40 @property (readonly, strong) NSUUID *deviceUDID;
41 @property (readonly, strong) NSArray *dumpToolArguments;
42 @property (readonly, strong) NSString *productDir;
43 @property (strong) SimDevice *device;
44 @property (strong) id<LTRelay> relay;
45 @property pid_t pid;
46 @property (strong) dispatch_source_t appDispatchSource;
47 @end
48
49 @implementation LTRelayController
50
51 - (id)initWithDevice:(SimDevice *)device productDir:(NSString *)productDir appPath:(NSString *)appPath deviceUDID:(NSUUID *)udid dumpToolArguments:(NSArray *)arguments
52 {
53 if ((self = [super init])) {
54 _device = device;
55 _productDir = productDir;
56 _appPath = appPath;
57 _appBundleIdentifier = [[NSBundle bundleWithPath:appPath] bundleIdentifier];
58 _deviceUDID = udid;
59 _dumpToolArguments = arguments;
60 _standardInputDispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, STDIN_FILENO, 0, dispatch_get_main_queue());
61 _standardOutput = [NSFileHandle fileHandleWithStandardOutput];
62 _standardError = [NSFileHandle fileHandleWithStandardError];
63
64 dispatch_source_set_event_handler(_standardInputDispatchSource, ^{
65 uint8_t buffer[1024];
66 ssize_t len = read(STDIN_FILENO, buffer, sizeof(buffer));
67 if (len > 0) {
68 @try {
69 [[[self relay] outputStream] write:buffer maxLength:len];
70 } @catch (NSException *e) {
71 // Broken pipe - the dump tool crashed. Time to die.
72 [self didCrashWithMessage:nil];
73 }
74 } else {
75 // EOF Received on the relay's standard input.
76 [self finish];
77 }
78 });
79
80 _relay = [[LTPipeRelay alloc] initWithPrefix:[@"/tmp" stringByAppendingPathComponent:self.ipcIdentifier]];
81 [_relay setRelayDelegate:self];
82 }
83 return self;
84 }
85
86 - (NSString *)ipcIdentifier
87 {
88 return [NSString stringWithFormat:@"%@-%@", self.appBundleIdentifier, self.deviceUDID.UUIDString];
89 }
90
91 - (NSString *)processName
92 {
93 return [self.appBundleIdentifier componentsSeparatedByString:@"."].lastObject;
94 }
95
96 - (void)didReceiveStdoutData:(NSData *)data
97 {
98 @try {
99 [[self standardOutput] writeData:data];
100 } @catch (NSException *exception) {
101 // NSFileHandleOperationException
102 // Broken pipe - the test harness stopped listening to us,
103 // probably because we timed out or a run was canceled.
104 exit(EXIT_FAILURE);
105 }
106 }
107
108 - (void)didReceiveStderrData:(NSData *)data
109 {
110 @try {
111 [[self standardError] writeData:data];
112 } @catch (NSException *exception) {
113 // NSFileHandleOperationException
114 // Broken pipe - the test harness stopped listening to us,
115 // probably because we timed out or a run was canceled.
116 exit(EXIT_FAILURE);
117 }
118 }
119
120 - (void)didDisconnect
121 {
122 dispatch_suspend([self standardInputDispatchSource]);
123 }
124
125 - (void)didConnect
126 {
127 dispatch_resume([self standardInputDispatchSource]);
128 }
129
130 - (void)didCrashWithMessage:(NSString *)message
131 {
132 [[self relay] disconnect];
133 NSString *crashMessage = [NSString stringWithFormat:@"\n#CRASHED - %@ (pid %d)\n", [self processName], [self pid]];
134
135 if (message)
136 crashMessage = [crashMessage stringByAppendingFormat:@"%@\n", message];
137
138 [[self standardError] writeData:[crashMessage dataUsingEncoding:NSUTF8StringEncoding]];
139 [[self standardError] closeFile];
140 [[self standardOutput] closeFile];
141 exit(EXIT_FAILURE);
142 }
143
144 - (void)installApp
145 {
146 NSDictionary *installOptions = @{
147 (NSString *)kCFBundleIdentifierKey: self.appBundleIdentifier,
148 };
149
150 NSError *error = nil;
151 BOOL installed = [self.device installApplication:[NSURL fileURLWithPath:self.appPath] withOptions:installOptions error:&error];
152 if (!installed) {
153 NSLog(@"Couldn't install %@: %@", self.appPath, error.description);
154 exit(EXIT_FAILURE);
155 }
156 }
157
158 - (void)killApp
159 {
160 dispatch_source_t dispatch = [self appDispatchSource];
161
162 if (dispatch)
163 dispatch_source_cancel(dispatch);
164
165 if ([self pid] > 1) {
166 kill(self.pid, SIGKILL);
167 [self setPid:-1];
168 }
169 [self setAppDispatchSource:nil];
170 }
171
172 - (NSDictionary *)_environmentVariables
173 {
174 static NSDictionary *environmentVariables;
175 static dispatch_once_t once;
176
177 dispatch_once(&once, ^{
178 NSString *productDirectory = [self productDir];
179
180 NSMutableDictionary *dictionary = [@{
181 @"IPC_IDENTIFIER": self.ipcIdentifier,
182 @"DYLD_FRAMEWORK_PATH": productDirectory,
183 @"__XPC_DYLD_FRAMEWORK_PATH": productDirectory,
184 @"DYLD_LIBRARY_PATH": productDirectory,
185 @"__XPC_DYLD_LIBRARY_PATH": productDirectory,
186 } mutableCopy];
187
188 for (NSString *keyName in @[@"DYLD_INSERT_LIBRARIES", @"MallocStackLogging", @"LOCAL_RESOURCE_ROOT", @"DUMPRENDERTREE_TEMP"]) {
189 const char* value = getenv([keyName UTF8String]);
190 if (value && strlen(value)) {
191 NSString *nsValue = [NSString stringWithUTF8String:value];
192 [dictionary setObject:nsValue forKey:keyName];
193 [dictionary setObject:nsValue forKey:[@"__XPC_" stringByAppendingString:keyName]];
194 }
195 }
196
197 for (char** envp = *_NSGetEnviron(); *envp; envp++) {
198 const char* env = *envp;
199 if (!strncmp("JSC_", env, 4) || !strncmp("__XPC_JSC_", env, 10)) {
200 const char* equal = strchr(env, '=');
201 if (!equal) {
202 NSLog(@"Missing '=' in env var '%s'", env);
203 continue;
204 }
205
206 static const size_t maxKeyLength = 256;
207 size_t keyLength = equal - env;
208 if (keyLength >= maxKeyLength) {
209 NSLog(@"Env var '%s' is too long", env);
210 continue;
211 }
212
213 char key[maxKeyLength];
214 strncpy(key, env, keyLength);
215 key[keyLength] = '\0';
216 const char* value = equal + 1;
217
218 NSString *nsKey = [NSString stringWithUTF8String:key];
219 NSString *nsValue = [NSString stringWithUTF8String:value];
220 [dictionary setObject:nsValue forKey:nsKey];
221 }
222 }
223
224 environmentVariables = [dictionary copy];
225 });
226
227 return environmentVariables;
228 }
229
230 - (void)launchApp
231 {
232 NSDictionary *launchOptions = @{
233 kSimDeviceLaunchApplicationEnvironment: [self _environmentVariables],
234 kSimDeviceLaunchApplicationArguments: [self dumpToolArguments],
235 };
236
237 NSError *error;
238 pid_t pid = [self.device launchApplicationWithID:self.appBundleIdentifier options:launchOptions error:&error];
239
240 if (pid < 0) {
241 NSLog(@"Couldn't launch %@: %@", self.appBundleIdentifier, error.description);
242 exit(EXIT_FAILURE);
243 }
244
245 [self setPid:pid];
246
247 dispatch_queue_t queue = dispatch_get_main_queue();
248 dispatch_source_t simulatorAppExitDispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, [self pid], DISPATCH_PROC_EXIT, queue);
249 [self setAppDispatchSource:simulatorAppExitDispatchSource];
250 dispatch_source_set_event_handler(simulatorAppExitDispatchSource, ^{
251 dispatch_source_cancel(simulatorAppExitDispatchSource);
252 [self didCrashWithMessage:nil];
253 });
254 dispatch_resume(simulatorAppExitDispatchSource);
255 }
256
257 - (void)start
258 {
259 [self installApp];
260 [[self relay] setup];
261 [self launchApp];
262 [[self relay] connect];
263 }
264
265 - (void)finish
266 {
267 [[self relay] disconnect];
268 [self killApp];
269 exit(EXIT_SUCCESS);
270 }
271
272 @end