1/*
2 * Copyright (C) 2010 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 "RunLoop.h"
30#import "WebProcess.h"
31#import "WebKitSystemInterface.h"
32#import <crt_externs.h>
33#import <mach-o/dyld.h>
34#import <mach/machine.h>
35#import <runtime/InitializeThreading.h>
36#import <servers/bootstrap.h>
37#import <spawn.h>
38#import <sys/param.h>
39#import <sys/stat.h>
40#import <wtf/PassRefPtr.h>
41#import <wtf/RetainPtr.h>
42#import <wtf/Threading.h>
43#import <wtf/text/CString.h>
44#import <wtf/text/WTFString.h>
45
46using namespace WebCore;
47
48// FIXME: We should be doing this another way.
49extern "C" kern_return_t bootstrap_register2(mach_port_t, name_t, mach_port_t, uint64_t);
50
51namespace WebKit {
52
53static void setUpTerminationNotificationHandler(pid_t pid)
54{
55#if HAVE(DISPATCH_H)
56    dispatch_source_t processDiedSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, pid, DISPATCH_PROC_EXIT, dispatch_get_current_queue());
57    dispatch_source_set_event_handler(processDiedSource, ^{
58        int status;
59        waitpid(dispatch_source_get_handle(processDiedSource), &status, 0);
60        dispatch_source_cancel(processDiedSource);
61    });
62    dispatch_source_set_cancel_handler(processDiedSource, ^{
63        dispatch_release(processDiedSource);
64    });
65    dispatch_resume(processDiedSource);
66#endif
67}
68
69class EnvironmentVariables {
70    WTF_MAKE_NONCOPYABLE(EnvironmentVariables);
71
72public:
73    EnvironmentVariables()
74        : m_environmentPointer(*_NSGetEnviron())
75    {
76    }
77
78    ~EnvironmentVariables()
79    {
80        size_t size = m_allocatedStrings.size();
81        for (size_t i = 0; i < size; ++i)
82            fastFree(m_allocatedStrings[i]);
83    }
84
85    void set(const char* name, const char* value)
86    {
87        // Check if we need to copy the environment.
88        if (m_environmentPointer == *_NSGetEnviron())
89            copyEnvironmentVariables();
90
91        // Allocate a string for the name and value.
92        const char* nameAndValue = createStringForVariable(name, value);
93
94        for (size_t i = 0; i < m_environmentVariables.size() - 1; ++i) {
95            if (valueIfVariableHasName(m_environmentVariables[i], name)) {
96                // Just replace the environment variable.
97                m_environmentVariables[i] = const_cast<char*>(nameAndValue);
98                return;
99            }
100        }
101
102        // Append the new string.
103        ASSERT(!m_environmentVariables.last());
104        m_environmentVariables.last() = const_cast<char*>(nameAndValue);
105        m_environmentVariables.append(static_cast<char*>(0));
106
107        m_environmentPointer = m_environmentVariables.data();
108    }
109
110    const char* get(const char* name) const
111    {
112        for (size_t i = 0; m_environmentPointer[i]; ++i) {
113            if (const char* value = valueIfVariableHasName(m_environmentPointer[i], name))
114                return value;
115        }
116        return 0;
117    }
118
119    // Will append the value with the given separator if the environment variable already exists.
120    void appendValue(const char* name, const char* value, char separator)
121    {
122        const char* existingValue = get(name);
123        if (!existingValue) {
124            set(name, value);
125            return;
126        }
127
128        Vector<char, 128> newValue;
129        newValue.append(existingValue, strlen(existingValue));
130        newValue.append(separator);
131        newValue.append(value, strlen(value) + 1);
132
133        set(name, newValue.data());
134    }
135
136    char** environmentPointer() const { return m_environmentPointer; }
137
138private:
139    const char* valueIfVariableHasName(const char* environmentVariable, const char* name) const
140    {
141        // Find the environment variable name.
142        const char* equalsLocation = strchr(environmentVariable, '=');
143        ASSERT(equalsLocation);
144
145        size_t nameLength = equalsLocation - environmentVariable;
146        if (strlen(name) != nameLength)
147            return 0;
148        if (memcmp(environmentVariable, name, nameLength))
149            return 0;
150
151        return equalsLocation + 1;
152    }
153
154    const char* createStringForVariable(const char* name, const char* value)
155    {
156        int nameLength = strlen(name);
157        int valueLength = strlen(value);
158
159        // Allocate enough room to hold 'name=value' and the null character.
160        char* string = static_cast<char*>(fastMalloc(nameLength + 1 + valueLength + 1));
161        memcpy(string, name, nameLength);
162        string[nameLength] = '=';
163        memcpy(string + nameLength + 1, value, valueLength);
164        string[nameLength + 1 + valueLength] = '\0';
165
166        m_allocatedStrings.append(string);
167
168        return string;
169    }
170
171    void copyEnvironmentVariables()
172    {
173        for (size_t i = 0; (*_NSGetEnviron())[i]; i++)
174            m_environmentVariables.append((*_NSGetEnviron())[i]);
175
176        // Null-terminate the array.
177        m_environmentVariables.append(static_cast<char*>(0));
178
179        // Update the environment pointer.
180        m_environmentPointer = m_environmentVariables.data();
181    }
182
183    char** m_environmentPointer;
184    Vector<char*> m_environmentVariables;
185
186    // These allocated strings will be freed in the destructor.
187    Vector<char*> m_allocatedStrings;
188};
189
190void ProcessLauncher::launchProcess()
191{
192    // Create the listening port.
193    mach_port_t listeningPort;
194    mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &listeningPort);
195
196    // Insert a send right so we can send to it.
197    mach_port_insert_right(mach_task_self(), listeningPort, listeningPort, MACH_MSG_TYPE_MAKE_SEND);
198
199    NSBundle *webKit2Bundle = [NSBundle bundleWithIdentifier:@"com.apple.WebKit2"];
200    NSString *frameworksPath = [[webKit2Bundle bundlePath] stringByDeletingLastPathComponent];
201    const char* frameworkExecutablePath = [[webKit2Bundle executablePath] fileSystemRepresentation];
202
203    NSString *processPath;
204    if (m_launchOptions.processType == ProcessLauncher::PluginProcess)
205        processPath = [webKit2Bundle pathForAuxiliaryExecutable:@"PluginProcess.app"];
206    else
207        processPath = [webKit2Bundle pathForAuxiliaryExecutable:@"WebProcess.app"];
208
209    NSString *processAppExecutablePath = [[NSBundle bundleWithPath:processPath] executablePath];
210
211    RetainPtr<CFStringRef> cfLocalization(AdoptCF, WKCopyCFLocalizationPreferredName(NULL));
212    CString localization = String(cfLocalization.get()).utf8();
213
214    // Make a unique, per pid, per process launcher web process service name.
215    CString serviceName = String::format("com.apple.WebKit.WebProcess-%d-%p", getpid(), this).utf8();
216
217    const char* args[] = { [processAppExecutablePath fileSystemRepresentation], frameworkExecutablePath, "-type", processTypeAsString(m_launchOptions.processType), "-servicename", serviceName.data(), "-localization", localization.data(), 0 };
218
219    // Register ourselves.
220    kern_return_t kr = bootstrap_register2(bootstrap_port, const_cast<char*>(serviceName.data()), listeningPort, 0);
221    ASSERT_UNUSED(kr, kr == KERN_SUCCESS);
222
223    posix_spawnattr_t attr;
224    posix_spawnattr_init(&attr);
225
226    short flags = 0;
227
228    // We want our process to receive all signals.
229    sigset_t signalMaskSet;
230    sigemptyset(&signalMaskSet);
231
232    posix_spawnattr_setsigmask(&attr, &signalMaskSet);
233    flags |= POSIX_SPAWN_SETSIGMASK;
234
235    // Determine the architecture to use.
236    cpu_type_t architecture = m_launchOptions.architecture;
237    if (architecture == LaunchOptions::MatchCurrentArchitecture)
238        architecture = _NSGetMachExecuteHeader()->cputype;
239
240    cpu_type_t cpuTypes[] = { architecture };
241    size_t outCount = 0;
242    posix_spawnattr_setbinpref_np(&attr, 1, cpuTypes, &outCount);
243
244    // Start suspended so we can set up the termination notification handler.
245    flags |= POSIX_SPAWN_START_SUSPENDED;
246
247#ifndef BUILDING_ON_SNOW_LEOPARD
248    static const int allowExecutableHeapFlag = 0x2000;
249    if (m_launchOptions.executableHeap)
250        flags |= allowExecutableHeapFlag;
251#endif
252
253    posix_spawnattr_setflags(&attr, flags);
254
255    pid_t processIdentifier;
256
257    EnvironmentVariables environmentVariables;
258
259    // To make engineering builds work, if the path is outside of /System set up
260    // DYLD_FRAMEWORK_PATH to pick up other frameworks, but don't do it for the
261    // production configuration because it involves extra file system access.
262    if (![frameworksPath hasPrefix:@"/System/"])
263        environmentVariables.appendValue("DYLD_FRAMEWORK_PATH", [frameworksPath fileSystemRepresentation], ':');
264
265    if (m_launchOptions.processType == ProcessLauncher::PluginProcess) {
266        // We need to insert the plug-in process shim.
267        NSString *pluginProcessShimPathNSString = [[processAppExecutablePath stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"PluginProcessShim.dylib"];
268        const char* pluginProcessShimPath = [pluginProcessShimPathNSString fileSystemRepresentation];
269
270        // Make sure that the file exists.
271        struct stat statBuf;
272        if (stat(pluginProcessShimPath, &statBuf) == 0 && (statBuf.st_mode & S_IFMT) == S_IFREG)
273            environmentVariables.appendValue("DYLD_INSERT_LIBRARIES", pluginProcessShimPath, ':');
274    }
275
276    int result = posix_spawn(&processIdentifier, args[0], 0, &attr, const_cast<char**>(args), environmentVariables.environmentPointer());
277
278    posix_spawnattr_destroy(&attr);
279
280    if (!result) {
281        // Set up the termination notification handler and then ask the child process to continue.
282        setUpTerminationNotificationHandler(processIdentifier);
283        kill(processIdentifier, SIGCONT);
284    } else {
285        // We failed to launch. Release the send right.
286        mach_port_deallocate(mach_task_self(), listeningPort);
287
288        // And the receive right.
289        mach_port_mod_refs(mach_task_self(), listeningPort, MACH_PORT_RIGHT_RECEIVE, -1);
290
291        listeningPort = MACH_PORT_NULL;
292        processIdentifier = 0;
293    }
294
295    // We've finished launching the process, message back to the main run loop.
296    RunLoop::main()->scheduleWork(WorkItem::create(this, &ProcessLauncher::didFinishLaunchingProcess, processIdentifier, listeningPort));
297}
298
299void ProcessLauncher::terminateProcess()
300{
301    if (!m_processIdentifier)
302        return;
303
304    kill(m_processIdentifier, SIGKILL);
305}
306
307void ProcessLauncher::platformInvalidate()
308{
309}
310
311} // namespace WebKit
312