[WTF] Add environment variable helpers
[WebKit-https.git] / Source / WebKit / UIProcess / Launcher / mac / ProcessLauncherMac.mm
1 /*
2  * Copyright (C) 2010-2018 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 "config.h"
27 #import "ProcessLauncher.h"
28
29 #import <crt_externs.h>
30 #import <mach-o/dyld.h>
31 #import <mach/mach_error.h>
32 #import <mach/machine.h>
33 #import <pal/spi/cocoa/ServersSPI.h>
34 #import <spawn.h>
35 #import <sys/param.h>
36 #import <sys/stat.h>
37 #import <wtf/Environment.h>
38 #import <wtf/MachSendRight.h>
39 #import <wtf/RunLoop.h>
40 #import <wtf/SoftLinking.h>
41 #import <wtf/Threading.h>
42 #import <wtf/spi/cf/CFBundleSPI.h>
43 #import <wtf/spi/darwin/XPCSPI.h>
44 #import <wtf/text/CString.h>
45 #import <wtf/text/WTFString.h>
46
47 #if PLATFORM(MAC)
48 #import "CodeSigning.h"
49 #endif
50
51 namespace WebKit {
52
53 static const char* serviceName(const ProcessLauncher::LaunchOptions& launchOptions)
54 {
55     switch (launchOptions.processType) {
56     case ProcessLauncher::ProcessType::Web:
57         return launchOptions.nonValidInjectedCodeAllowed ? "com.apple.WebKit.WebContent.Development" : "com.apple.WebKit.WebContent";
58     case ProcessLauncher::ProcessType::Network:
59         return "com.apple.WebKit.Networking";
60     case ProcessLauncher::ProcessType::NetworkDaemon:
61         ASSERT_NOT_REACHED();
62         return nullptr;
63 #if ENABLE(NETSCAPE_PLUGIN_API)
64     case ProcessLauncher::ProcessType::Plugin32:
65         return "com.apple.WebKit.Plugin.32";
66     case ProcessLauncher::ProcessType::Plugin64:
67         return "com.apple.WebKit.Plugin.64";
68 #endif
69     }
70 }
71
72 static bool shouldLeakBoost(const ProcessLauncher::LaunchOptions& launchOptions)
73 {
74 #if PLATFORM(IOS_FAMILY)
75     // On iOS, leak a boost onto all child processes
76     UNUSED_PARAM(launchOptions);
77     return true;
78 #else
79     // On Mac, leak a boost onto the NetworkProcess.
80     return launchOptions.processType == ProcessLauncher::ProcessType::Network;
81 #endif
82 }
83
84 static NSString *systemDirectoryPath()
85 {
86     static NSString *path = [^{
87 #if PLATFORM(IOS_FAMILY_SIMULATOR)
88         const char* simulatorRoot = Environment::getRaw("SIMULATOR_ROOT");
89         return simulatorRoot ? [NSString stringWithFormat:@"%s/System/", simulatorRoot] : @"/System/";
90 #else
91         return @"/System/";
92 #endif
93     }() copy];
94
95     return path;
96 }
97
98 void ProcessLauncher::launchProcess()
99 {
100     ASSERT(!m_xpcConnection);
101
102     if (m_launchOptions.processType == ProcessLauncher::ProcessType::NetworkDaemon)
103         m_xpcConnection = adoptOSObject(xpc_connection_create_mach_service("com.apple.WebKit.NetworkingDaemon", dispatch_get_main_queue(), 0));
104     else {
105         const char* name = m_launchOptions.customWebContentServiceBundleIdentifier.isNull() ? serviceName(m_launchOptions) : m_launchOptions.customWebContentServiceBundleIdentifier.data();
106         m_xpcConnection = adoptOSObject(xpc_connection_create(name, nullptr));
107
108         uuid_t uuid;
109         uuid_generate(uuid);
110         xpc_connection_set_oneshot_instance(m_xpcConnection.get(), uuid);
111     }
112
113     // Inherit UI process localization. It can be different from child process default localization:
114     // 1. When the application and system frameworks simply have different localized resources available, we should match the application.
115     // 1.1. An important case is WebKitTestRunner, where we should use English localizations for all system frameworks.
116     // 2. When AppleLanguages is passed as command line argument for UI process, or set in its preferences, we should respect it in child processes.
117     auto initializationMessage = adoptOSObject(xpc_dictionary_create(nullptr, nullptr, 0));
118     _CFBundleSetupXPCBootstrap(initializationMessage.get());
119 #if PLATFORM(IOS_FAMILY)
120     // Clients that set these environment variables explicitly do not have the values automatically forwarded by libxpc.
121     auto containerEnvironmentVariables = adoptOSObject(xpc_dictionary_create(nullptr, nullptr, 0));
122     if (const char* environmentHOME = Environment::getRaw("HOME"))
123         xpc_dictionary_set_string(containerEnvironmentVariables.get(), "HOME", environmentHOME);
124     if (const char* environmentCFFIXED_USER_HOME = Environment::getRaw("CFFIXED_USER_HOME"))
125         xpc_dictionary_set_string(containerEnvironmentVariables.get(), "CFFIXED_USER_HOME", environmentCFFIXED_USER_HOME);
126     if (const char* environmentTMPDIR = Environment::getRaw("TMPDIR"))
127         xpc_dictionary_set_string(containerEnvironmentVariables.get(), "TMPDIR", environmentTMPDIR);
128     xpc_dictionary_set_value(initializationMessage.get(), "ContainerEnvironmentVariables", containerEnvironmentVariables.get());
129 #endif
130
131     auto languagesIterator = m_launchOptions.extraInitializationData.find("OverrideLanguages");
132     if (languagesIterator != m_launchOptions.extraInitializationData.end()) {
133         auto languages = adoptOSObject(xpc_array_create(nullptr, 0));
134         for (auto& language : languagesIterator->value.split(','))
135             xpc_array_set_string(languages.get(), XPC_ARRAY_APPEND, language.utf8().data());
136         xpc_dictionary_set_value(initializationMessage.get(), "OverrideLanguages", languages.get());
137     }
138
139 #if PLATFORM(MAC)
140     xpc_dictionary_set_string(initializationMessage.get(), "WebKitBundleVersion", [[NSBundle bundleWithIdentifier:@"com.apple.WebKit"].infoDictionary[(__bridge NSString *)kCFBundleVersionKey] UTF8String]);
141 #endif
142
143     if (shouldLeakBoost(m_launchOptions)) {
144         auto preBootstrapMessage = adoptOSObject(xpc_dictionary_create(nullptr, nullptr, 0));
145         xpc_dictionary_set_string(preBootstrapMessage.get(), "message-name", "pre-bootstrap");
146         xpc_connection_send_message(m_xpcConnection.get(), preBootstrapMessage.get());
147     }
148     
149     // Create the listening port.
150     mach_port_t listeningPort = MACH_PORT_NULL;
151     auto kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &listeningPort);
152     if (kr != KERN_SUCCESS) {
153         LOG_ERROR("Could not allocate mach port, error %x: %s", kr, mach_error_string(kr));
154         CRASH();
155     }
156
157     // Insert a send right so we can send to it.
158     mach_port_insert_right(mach_task_self(), listeningPort, listeningPort, MACH_MSG_TYPE_MAKE_SEND);
159
160     mach_port_t previousNotificationPort = MACH_PORT_NULL;
161     auto mc = mach_port_request_notification(mach_task_self(), listeningPort, MACH_NOTIFY_NO_SENDERS, 0, listeningPort, MACH_MSG_TYPE_MAKE_SEND_ONCE, &previousNotificationPort);
162     ASSERT(!previousNotificationPort);
163     ASSERT(mc == KERN_SUCCESS);
164     if (mc != KERN_SUCCESS) {
165         // If mach_port_request_notification fails, 'previousNotificationPort' will be uninitialized.
166         LOG_ERROR("mach_port_request_notification failed: (%x) %s", mc, mach_error_string(mc));
167     }
168
169     String clientIdentifier;
170 #if PLATFORM(MAC)
171     clientIdentifier = codeSigningIdentifierForCurrentProcess();
172 #endif
173     if (clientIdentifier.isNull())
174         clientIdentifier = [[NSBundle mainBundle] bundleIdentifier];
175
176     auto bootstrapMessage = adoptOSObject(xpc_dictionary_create(nullptr, nullptr, 0));
177
178     xpc_dictionary_set_value(bootstrapMessage.get(), "initialization-message", initializationMessage.get());
179     
180     if (m_client && !m_client->isJITEnabled())
181         xpc_dictionary_set_bool(bootstrapMessage.get(), "disable-jit", true);
182
183     xpc_dictionary_set_string(bootstrapMessage.get(), "message-name", "bootstrap");
184
185     xpc_dictionary_set_mach_send(bootstrapMessage.get(), "server-port", listeningPort);
186
187     xpc_dictionary_set_string(bootstrapMessage.get(), "client-identifier", !clientIdentifier.isEmpty() ? clientIdentifier.utf8().data() : *_NSGetProgname());
188     xpc_dictionary_set_string(bootstrapMessage.get(), "process-identifier", String::number(m_launchOptions.processIdentifier.toUInt64()).utf8().data());
189     xpc_dictionary_set_string(bootstrapMessage.get(), "ui-process-name", [[[NSProcessInfo processInfo] processName] UTF8String]);
190
191     bool isWebKitDevelopmentBuild = ![[[[NSBundle bundleWithIdentifier:@"com.apple.WebKit"] bundlePath] stringByDeletingLastPathComponent] hasPrefix:systemDirectoryPath()];
192     if (isWebKitDevelopmentBuild) {
193         xpc_dictionary_set_fd(bootstrapMessage.get(), "stdout", STDOUT_FILENO);
194         xpc_dictionary_set_fd(bootstrapMessage.get(), "stderr", STDERR_FILENO);
195     }
196
197     auto extraInitializationData = adoptOSObject(xpc_dictionary_create(nullptr, nullptr, 0));
198
199     for (const auto& keyValuePair : m_launchOptions.extraInitializationData)
200         xpc_dictionary_set_string(extraInitializationData.get(), keyValuePair.key.utf8().data(), keyValuePair.value.utf8().data());
201
202     xpc_dictionary_set_value(bootstrapMessage.get(), "extra-initialization-data", extraInitializationData.get());
203
204     auto errorHandlerImpl = [weakProcessLauncher = makeWeakPtr(*this), listeningPort] (xpc_object_t event) {
205         ASSERT(!event || xpc_get_type(event) == XPC_TYPE_ERROR);
206
207         auto processLauncher = weakProcessLauncher.get();
208         if (!processLauncher)
209             return;
210
211         if (!processLauncher->isLaunching())
212             return;
213
214 #if !ASSERT_DISABLED
215         mach_port_urefs_t sendRightCount = 0;
216         mach_port_get_refs(mach_task_self(), listeningPort, MACH_PORT_RIGHT_SEND, &sendRightCount);
217         ASSERT(sendRightCount >= 1);
218 #endif
219
220         // We failed to launch. Release the send right.
221         deallocateSendRightSafely(listeningPort);
222
223         // And the receive right.
224         mach_port_mod_refs(mach_task_self(), listeningPort, MACH_PORT_RIGHT_RECEIVE, -1);
225
226         if (processLauncher->m_xpcConnection)
227             xpc_connection_cancel(processLauncher->m_xpcConnection.get());
228         processLauncher->m_xpcConnection = nullptr;
229
230         processLauncher->didFinishLaunchingProcess(0, IPC::Connection::Identifier());
231     };
232
233     auto errorHandler = [errorHandlerImpl = WTFMove(errorHandlerImpl)] (xpc_object_t event) mutable {
234         RunLoop::main().dispatch([errorHandlerImpl = WTFMove(errorHandlerImpl), event] {
235             errorHandlerImpl(event);
236         });
237     };
238
239     xpc_connection_set_event_handler(m_xpcConnection.get(), errorHandler);
240
241     xpc_connection_resume(m_xpcConnection.get());
242
243     if (UNLIKELY(m_launchOptions.shouldMakeProcessLaunchFailForTesting)) {
244         errorHandler(nullptr);
245         return;
246     }
247
248     ref();
249     xpc_connection_send_message_with_reply(m_xpcConnection.get(), bootstrapMessage.get(), dispatch_get_main_queue(), ^(xpc_object_t reply) {
250         // Errors are handled in the event handler.
251         // It is possible for this block to be called after the error event handler, in which case we're no longer
252         // launching and we already took care of cleaning things up.
253         if (isLaunching() && xpc_get_type(reply) != XPC_TYPE_ERROR) {
254             ASSERT(xpc_get_type(reply) == XPC_TYPE_DICTIONARY);
255             ASSERT(!strcmp(xpc_dictionary_get_string(reply, "message-name"), "process-finished-launching"));
256
257 #if !ASSERT_DISABLED
258             mach_port_urefs_t sendRightCount = 0;
259             mach_port_get_refs(mach_task_self(), listeningPort, MACH_PORT_RIGHT_SEND, &sendRightCount);
260             ASSERT(sendRightCount >= 1);
261 #endif
262
263             deallocateSendRightSafely(listeningPort);
264
265             if (!m_xpcConnection) {
266                 // The process was terminated.
267                 didFinishLaunchingProcess(0, IPC::Connection::Identifier());
268                 return;
269             }
270
271             // The process has finished launching, grab the pid from the connection.
272             pid_t processIdentifier = xpc_connection_get_pid(m_xpcConnection.get());
273
274             didFinishLaunchingProcess(processIdentifier, IPC::Connection::Identifier(listeningPort, m_xpcConnection));
275             m_xpcConnection = nullptr;
276         }
277
278         deref();
279     });
280 }
281
282 void ProcessLauncher::terminateProcess()
283 {
284     if (m_isLaunching) {
285         invalidate();
286         return;
287     }
288
289     if (!m_processIdentifier)
290         return;
291     
292     kill(m_processIdentifier, SIGKILL);
293     m_processIdentifier = 0;
294 }
295     
296 void ProcessLauncher::platformInvalidate()
297 {
298     if (!m_xpcConnection)
299         return;
300
301     xpc_connection_cancel(m_xpcConnection.get());
302     xpc_connection_kill(m_xpcConnection.get(), SIGKILL);
303     m_xpcConnection = nullptr;
304 }
305
306 } // namespace WebKit