1/*
2 * Copyright (C) 2010 Apple Inc. All rights reserved.
3 * Portions Copyright (c) 2010 Motorola Mobility, Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY MOTOROLA INC. AND ITS CONTRIBUTORS ``AS IS''
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MOTOROLA INC. OR ITS CONTRIBUTORS
18 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24 * THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "ProcessLauncher.h"
29
30#include "BubblewrapLauncher.h"
31#include "Connection.h"
32#include "FlatpakLauncher.h"
33#include "ProcessExecutablePath.h"
34#include <errno.h>
35#include <fcntl.h>
36#include <glib.h>
37#include <wtf/FileSystem.h>
38#include <wtf/RunLoop.h>
39#include <wtf/UniStdExtras.h>
40#include <wtf/glib/GLibUtilities.h>
41#include <wtf/glib/GUniquePtr.h>
42#include <wtf/text/CString.h>
43#include <wtf/text/WTFString.h>
44
45namespace WebKit {
46
47static void childSetupFunction(gpointer userData)
48{
49 int socket = GPOINTER_TO_INT(userData);
50 close(socket);
51}
52
53#if OS(LINUX)
54static bool isInsideFlatpak()
55{
56 static int ret = -1;
57 if (ret != -1)
58 return ret;
59
60 GUniquePtr<GKeyFile> infoFile(g_key_file_new());
61 if (!g_key_file_load_from_file(infoFile.get(), "/.flatpak-info", G_KEY_FILE_NONE, nullptr)) {
62 ret = false;
63 return ret;
64 }
65
66 // If we are in a `flatpak build` session we cannot launch ourselves since we aren't installed.
67 ret = !g_key_file_get_boolean(infoFile.get(), "Instance", "build", nullptr);
68 return ret;
69}
70#endif
71
72void ProcessLauncher::launchProcess()
73{
74 IPC::Connection::SocketPair socketPair = IPC::Connection::createPlatformConnection(IPC::Connection::ConnectionOptions::SetCloexecOnServer);
75
76 String executablePath;
77 CString realExecutablePath;
78#if ENABLE(NETSCAPE_PLUGIN_API)
79 String pluginPath;
80 CString realPluginPath;
81#endif
82 switch (m_launchOptions.processType) {
83 case ProcessLauncher::ProcessType::Web:
84 executablePath = executablePathOfWebProcess();
85 break;
86#if ENABLE(NETSCAPE_PLUGIN_API)
87 case ProcessLauncher::ProcessType::Plugin64:
88 case ProcessLauncher::ProcessType::Plugin32:
89 executablePath = executablePathOfPluginProcess();
90#if ENABLE(PLUGIN_PROCESS_GTK2)
91 if (m_launchOptions.extraInitializationData.contains("requires-gtk2"))
92 executablePath.append('2');
93#endif
94 pluginPath = m_launchOptions.extraInitializationData.get("plugin-path");
95 realPluginPath = FileSystem::fileSystemRepresentation(pluginPath);
96 break;
97#endif
98 case ProcessLauncher::ProcessType::Network:
99 executablePath = executablePathOfNetworkProcess();
100 break;
101 default:
102 ASSERT_NOT_REACHED();
103 return;
104 }
105
106 realExecutablePath = FileSystem::fileSystemRepresentation(executablePath);
107 GUniquePtr<gchar> processIdentifier(g_strdup_printf("%" PRIu64, m_launchOptions.processIdentifier.toUInt64()));
108 GUniquePtr<gchar> webkitSocket(g_strdup_printf("%d", socketPair.client));
109 unsigned nargs = 5; // size of the argv array for g_spawn_async()
110
111#if ENABLE(DEVELOPER_MODE)
112 Vector<CString> prefixArgs;
113 if (!m_launchOptions.processCmdPrefix.isNull()) {
114 for (auto& arg : m_launchOptions.processCmdPrefix.split(' '))
115 prefixArgs.append(arg.utf8());
116 nargs += prefixArgs.size();
117 }
118#endif
119
120 char** argv = g_newa(char*, nargs);
121 unsigned i = 0;
122#if ENABLE(DEVELOPER_MODE)
123 // If there's a prefix command, put it before the rest of the args.
124 for (auto& arg : prefixArgs)
125 argv[i++] = const_cast<char*>(arg.data());
126#endif
127 argv[i++] = const_cast<char*>(realExecutablePath.data());
128 argv[i++] = processIdentifier.get();
129 argv[i++] = webkitSocket.get();
130#if ENABLE(NETSCAPE_PLUGIN_API)
131 argv[i++] = const_cast<char*>(realPluginPath.data());
132#else
133 argv[i++] = nullptr;
134#endif
135 argv[i++] = nullptr;
136
137 GRefPtr<GSubprocessLauncher> launcher = adoptGRef(g_subprocess_launcher_new(G_SUBPROCESS_FLAGS_INHERIT_FDS));
138 g_subprocess_launcher_set_child_setup(launcher.get(), childSetupFunction, GINT_TO_POINTER(socketPair.server), nullptr);
139 g_subprocess_launcher_take_fd(launcher.get(), socketPair.client, socketPair.client);
140
141 GUniqueOutPtr<GError> error;
142 GRefPtr<GSubprocess> process;
143#if OS(LINUX)
144 const char* sandboxEnv = g_getenv("WEBKIT_FORCE_SANDBOX");
145 bool sandboxEnabled = m_launchOptions.extraInitializationData.get("enable-sandbox") == "true";
146
147 if (sandboxEnv)
148 sandboxEnabled = !strcmp(sandboxEnv, "1");
149
150 if (sandboxEnabled && isInsideFlatpak())
151 process = flatpakSpawn(launcher.get(), m_launchOptions, argv, socketPair.client, &error.outPtr());
152#if ENABLE(BUBBLEWRAP_SANDBOX)
153 else if (sandboxEnabled)
154 process = bubblewrapSpawn(launcher.get(), m_launchOptions, argv, &error.outPtr());
155#endif
156 else
157#endif
158 process = adoptGRef(g_subprocess_launcher_spawnv(launcher.get(), argv, &error.outPtr()));
159
160 if (!process.get())
161 g_error("Unable to fork a new child process: %s", error->message);
162
163 const char* processIdStr = g_subprocess_get_identifier(process.get());
164 m_processIdentifier = g_ascii_strtoll(processIdStr, nullptr, 0);
165 RELEASE_ASSERT(m_processIdentifier);
166
167 // Don't expose the parent socket to potential future children.
168 if (!setCloseOnExec(socketPair.client))
169 RELEASE_ASSERT_NOT_REACHED();
170
171 // We've finished launching the process, message back to the main run loop.
172 RunLoop::main().dispatch([protectedThis = makeRef(*this), this, serverSocket = socketPair.server] {
173 didFinishLaunchingProcess(m_processIdentifier, serverSocket);
174 });
175}
176
177void ProcessLauncher::terminateProcess()
178{
179 if (m_isLaunching) {
180 invalidate();
181 return;
182 }
183
184 if (!m_processIdentifier)
185 return;
186
187 kill(m_processIdentifier, SIGKILL);
188 m_processIdentifier = 0;
189}
190
191void ProcessLauncher::platformInvalidate()
192{
193}
194
195} // namespace WebKit
196