Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / content / public / android / java / src / org / chromium / content / browser / ChildProcessLauncher.java
1 // Copyright 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.
4
5 package org.chromium.content.browser;
6
7 import android.content.Context;
8 import android.graphics.SurfaceTexture;
9 import android.util.Log;
10 import android.util.Pair;
11 import android.view.Surface;
12
13 import com.google.common.annotations.VisibleForTesting;
14
15 import org.chromium.base.CalledByNative;
16 import org.chromium.base.JNINamespace;
17 import org.chromium.base.ThreadUtils;
18 import org.chromium.base.TraceEvent;
19 import org.chromium.base.library_loader.Linker;
20 import org.chromium.content.app.ChildProcessService;
21 import org.chromium.content.app.ChromiumLinkerParams;
22 import org.chromium.content.app.PrivilegedProcessService;
23 import org.chromium.content.app.SandboxedProcessService;
24 import org.chromium.content.common.IChildProcessCallback;
25
26 import java.util.ArrayList;
27 import java.util.Map;
28 import java.util.concurrent.ConcurrentHashMap;
29
30 /**
31  * This class provides the method to start/stop ChildProcess called by native.
32  */
33 @JNINamespace("content")
34 public class ChildProcessLauncher {
35     private static final String TAG = "ChildProcessLauncher";
36
37     static final int CALLBACK_FOR_UNKNOWN_PROCESS = 0;
38     static final int CALLBACK_FOR_GPU_PROCESS = 1;
39     static final int CALLBACK_FOR_RENDERER_PROCESS = 2;
40
41     private static final String SWITCH_PROCESS_TYPE = "type";
42     private static final String SWITCH_PPAPI_BROKER_PROCESS = "ppapi-broker";
43     private static final String SWITCH_RENDERER_PROCESS = "renderer";
44     private static final String SWITCH_GPU_PROCESS = "gpu-process";
45
46     // The upper limit on the number of simultaneous sandboxed and privileged child service process
47     // instances supported. Each limit must not exceed total number of SandboxedProcessServiceX
48     // classes and PrivilegedProcessServiceX classes declared in this package and defined as
49     // services in the embedding application's manifest file.
50     // (See {@link ChildProcessService} for more details on defining the services.)
51     /* package */ static final int MAX_REGISTERED_SANDBOXED_SERVICES = 13;
52     /* package */ static final int MAX_REGISTERED_PRIVILEGED_SERVICES = 3;
53
54     private static class ChildConnectionAllocator {
55         // Connections to services. Indices of the array correspond to the service numbers.
56         private final ChildProcessConnection[] mChildProcessConnections;
57
58         // The list of free (not bound) service indices. When looking for a free service, the first
59         // index in that list should be used. When a service is unbound, its index is added to the
60         // end of the list. This is so that we avoid immediately reusing the freed service (see
61         // http://crbug.com/164069): the framework might keep a service process alive when it's been
62         // unbound for a short time. If a new connection to the same service is bound at that point,
63         // the process is reused and bad things happen (mostly static variables are set when we
64         // don't expect them to).
65         // SHOULD BE ACCESSED WITH mConnectionLock.
66         private final ArrayList<Integer> mFreeConnectionIndices;
67         private final Object mConnectionLock = new Object();
68
69         private Class<? extends ChildProcessService> mChildClass;
70         private final boolean mInSandbox;
71
72         public ChildConnectionAllocator(boolean inSandbox) {
73             int numChildServices = inSandbox ?
74                     MAX_REGISTERED_SANDBOXED_SERVICES : MAX_REGISTERED_PRIVILEGED_SERVICES;
75             mChildProcessConnections = new ChildProcessConnectionImpl[numChildServices];
76             mFreeConnectionIndices = new ArrayList<Integer>(numChildServices);
77             for (int i = 0; i < numChildServices; i++) {
78                 mFreeConnectionIndices.add(i);
79             }
80             setServiceClass(inSandbox ?
81                     SandboxedProcessService.class : PrivilegedProcessService.class);
82             mInSandbox = inSandbox;
83         }
84
85         public void setServiceClass(Class<? extends ChildProcessService> childClass) {
86             mChildClass = childClass;
87         }
88
89         public ChildProcessConnection allocate(
90                 Context context, ChildProcessConnection.DeathCallback deathCallback,
91                 ChromiumLinkerParams chromiumLinkerParams) {
92             synchronized (mConnectionLock) {
93                 if (mFreeConnectionIndices.isEmpty()) {
94                     Log.w(TAG, "Ran out of service.");
95                     return null;
96                 }
97                 int slot = mFreeConnectionIndices.remove(0);
98                 assert mChildProcessConnections[slot] == null;
99                 mChildProcessConnections[slot] = new ChildProcessConnectionImpl(context, slot,
100                         mInSandbox, deathCallback, mChildClass, chromiumLinkerParams);
101                 return mChildProcessConnections[slot];
102             }
103         }
104
105         public void free(ChildProcessConnection connection) {
106             synchronized (mConnectionLock) {
107                 int slot = connection.getServiceNumber();
108                 if (mChildProcessConnections[slot] != connection) {
109                     int occupier = mChildProcessConnections[slot] == null ?
110                             -1 : mChildProcessConnections[slot].getServiceNumber();
111                     Log.e(TAG, "Unable to find connection to free in slot: " + slot +
112                             " already occupied by service: " + occupier);
113                     assert false;
114                 } else {
115                     mChildProcessConnections[slot] = null;
116                     assert !mFreeConnectionIndices.contains(slot);
117                     mFreeConnectionIndices.add(slot);
118                 }
119             }
120         }
121
122         /** @return the count of connections managed by the allocator */
123         @VisibleForTesting
124         int allocatedConnectionsCountForTesting() {
125             return mChildProcessConnections.length - mFreeConnectionIndices.size();
126         }
127     }
128
129     // Service class for child process. As the default value it uses SandboxedProcessService0 and
130     // PrivilegedProcessService0.
131     private static final ChildConnectionAllocator sSandboxedChildConnectionAllocator =
132             new ChildConnectionAllocator(true);
133     private static final ChildConnectionAllocator sPrivilegedChildConnectionAllocator =
134             new ChildConnectionAllocator(false);
135
136     private static boolean sConnectionAllocated = false;
137
138     /**
139      * Sets service class for sandboxed service and privileged service.
140      */
141     public static void setChildProcessClass(
142             Class<? extends SandboxedProcessService> sandboxedServiceClass,
143             Class<? extends PrivilegedProcessService> privilegedServiceClass) {
144         // We should guarantee this is called before allocating connection.
145         assert !sConnectionAllocated;
146         sSandboxedChildConnectionAllocator.setServiceClass(sandboxedServiceClass);
147         sPrivilegedChildConnectionAllocator.setServiceClass(privilegedServiceClass);
148     }
149
150     private static ChildConnectionAllocator getConnectionAllocator(boolean inSandbox) {
151         return inSandbox ?
152                 sSandboxedChildConnectionAllocator : sPrivilegedChildConnectionAllocator;
153     }
154
155     private static ChildProcessConnection allocateConnection(Context context,
156             boolean inSandbox, ChromiumLinkerParams chromiumLinkerParams) {
157         ChildProcessConnection.DeathCallback deathCallback =
158             new ChildProcessConnection.DeathCallback() {
159                 @Override
160                 public void onChildProcessDied(ChildProcessConnection connection) {
161                     if (connection.getPid() != 0) {
162                         stop(connection.getPid());
163                     } else {
164                         freeConnection(connection);
165                     }
166                 }
167             };
168         sConnectionAllocated = true;
169         return getConnectionAllocator(inSandbox).allocate(context, deathCallback,
170                 chromiumLinkerParams);
171     }
172
173     private static boolean sLinkerInitialized = false;
174     private static long sLinkerLoadAddress = 0;
175
176     private static ChromiumLinkerParams getLinkerParamsForNewConnection() {
177         if (!sLinkerInitialized) {
178             if (Linker.isUsed()) {
179                 sLinkerLoadAddress = Linker.getBaseLoadAddress();
180                 if (sLinkerLoadAddress == 0) {
181                     Log.i(TAG, "Shared RELRO support disabled!");
182                 }
183             }
184             sLinkerInitialized = true;
185         }
186
187         if (sLinkerLoadAddress == 0)
188             return null;
189
190         // Always wait for the shared RELROs in service processes.
191         final boolean waitForSharedRelros = true;
192         return new ChromiumLinkerParams(sLinkerLoadAddress,
193                                 waitForSharedRelros,
194                                 Linker.getTestRunnerClassName());
195     }
196
197     private static ChildProcessConnection allocateBoundConnection(Context context,
198             String[] commandLine, boolean inSandbox) {
199         ChromiumLinkerParams chromiumLinkerParams = getLinkerParamsForNewConnection();
200         ChildProcessConnection connection =
201                 allocateConnection(context, inSandbox, chromiumLinkerParams);
202         if (connection != null) {
203             connection.start(commandLine);
204         }
205         return connection;
206     }
207
208     private static void freeConnection(ChildProcessConnection connection) {
209         getConnectionAllocator(connection.isInSandbox()).free(connection);
210     }
211
212     // Represents an invalid process handle; same as base/process/process.h kNullProcessHandle.
213     private static final int NULL_PROCESS_HANDLE = 0;
214
215     // Map from pid to ChildService connection.
216     private static Map<Integer, ChildProcessConnection> sServiceMap =
217             new ConcurrentHashMap<Integer, ChildProcessConnection>();
218
219     // A pre-allocated and pre-bound connection ready for connection setup, or null.
220     private static ChildProcessConnection sSpareSandboxedConnection = null;
221
222     // Manages oom bindings used to bind chind services.
223     private static BindingManager sBindingManager = BindingManagerImpl.createBindingManager();
224
225     // Map from surface id to Surface.
226     private static Map<Integer, Surface> sViewSurfaceMap =
227             new ConcurrentHashMap<Integer, Surface>();
228
229     // Map from surface texture id to Surface.
230     private static Map<Pair<Integer, Integer>, Surface> sSurfaceTextureSurfaceMap =
231             new ConcurrentHashMap<Pair<Integer, Integer>, Surface>();
232
233     @VisibleForTesting
234     public static void setBindingManagerForTesting(BindingManager manager) {
235         sBindingManager = manager;
236     }
237
238     /** @return true iff the child process is protected from out-of-memory killing */
239     @CalledByNative
240     private static boolean isOomProtected(int pid) {
241         return sBindingManager.isOomProtected(pid);
242     }
243
244     @CalledByNative
245     private static void registerViewSurface(int surfaceId, Surface surface) {
246         sViewSurfaceMap.put(surfaceId, surface);
247     }
248
249     @CalledByNative
250     private static void unregisterViewSurface(int surfaceId) {
251         sViewSurfaceMap.remove(surfaceId);
252     }
253
254     @CalledByNative
255     private static void registerSurfaceTexture(
256             int surfaceTextureId, int childProcessId, SurfaceTexture surfaceTexture) {
257         Pair<Integer, Integer> key = new Pair<Integer, Integer>(surfaceTextureId, childProcessId);
258         sSurfaceTextureSurfaceMap.put(key, new Surface(surfaceTexture));
259     }
260
261     @CalledByNative
262     private static void unregisterSurfaceTexture(int surfaceTextureId, int childProcessId) {
263         Pair<Integer, Integer> key = new Pair<Integer, Integer>(surfaceTextureId, childProcessId);
264         sSurfaceTextureSurfaceMap.remove(key);
265     }
266
267     /**
268      * Sets the visibility of the child process when it changes or when it is determined for the
269      * first time.
270      */
271     @CalledByNative
272     public static void setInForeground(int pid, boolean inForeground) {
273         sBindingManager.setInForeground(pid, inForeground);
274     }
275
276     /**
277      * Called when the embedding application is sent to background.
278      */
279     public static void onSentToBackground() {
280         sBindingManager.onSentToBackground();
281     }
282
283     /**
284      * Called when the embedding application is brought to foreground.
285      */
286     public static void onBroughtToForeground() {
287         sBindingManager.onBroughtToForeground();
288     }
289
290     /**
291      * Should be called early in startup so the work needed to spawn the child process can be done
292      * in parallel to other startup work. Must not be called on the UI thread. Spare connection is
293      * created in sandboxed child process.
294      * @param context the application context used for the connection.
295      */
296     public static void warmUp(Context context) {
297         synchronized (ChildProcessLauncher.class) {
298             assert !ThreadUtils.runningOnUiThread();
299             if (sSpareSandboxedConnection == null) {
300                 sSpareSandboxedConnection = allocateBoundConnection(context, null, true);
301             }
302         }
303     }
304
305     private static String getSwitchValue(final String[] commandLine, String switchKey) {
306         if (commandLine == null || switchKey == null) {
307             return null;
308         }
309         // This format should be matched with the one defined in command_line.h.
310         final String switchKeyPrefix = "--" + switchKey + "=";
311         for (String command : commandLine) {
312             if (command != null && command.startsWith(switchKeyPrefix)) {
313                 return command.substring(switchKeyPrefix.length());
314             }
315         }
316         return null;
317     }
318
319     /**
320      * Spawns and connects to a child process. May be called on any thread. It will not block, but
321      * will instead callback to {@link #nativeOnChildProcessStarted} when the connection is
322      * established. Note this callback will not necessarily be from the same thread (currently it
323      * always comes from the main thread).
324      *
325      * @param context Context used to obtain the application context.
326      * @param commandLine The child process command line argv.
327      * @param fileIds The ID that should be used when mapping files in the created process.
328      * @param fileFds The file descriptors that should be mapped in the created process.
329      * @param fileAutoClose Whether the file descriptors should be closed once they were passed to
330      * the created process.
331      * @param clientContext Arbitrary parameter used by the client to distinguish this connection.
332      */
333     @CalledByNative
334     static void start(
335             Context context,
336             final String[] commandLine,
337             int childProcessId,
338             int[] fileIds,
339             int[] fileFds,
340             boolean[] fileAutoClose,
341             long clientContext) {
342         TraceEvent.begin();
343         assert fileIds.length == fileFds.length && fileFds.length == fileAutoClose.length;
344         FileDescriptorInfo[] filesToBeMapped = new FileDescriptorInfo[fileFds.length];
345         for (int i = 0; i < fileFds.length; i++) {
346             filesToBeMapped[i] =
347                     new FileDescriptorInfo(fileIds[i], fileFds[i], fileAutoClose[i]);
348         }
349         assert clientContext != 0;
350
351         int callbackType = CALLBACK_FOR_UNKNOWN_PROCESS;
352         boolean inSandbox = true;
353         String processType = getSwitchValue(commandLine, SWITCH_PROCESS_TYPE);
354         if (SWITCH_RENDERER_PROCESS.equals(processType)) {
355             callbackType = CALLBACK_FOR_RENDERER_PROCESS;
356         } else if (SWITCH_GPU_PROCESS.equals(processType)) {
357             callbackType = CALLBACK_FOR_GPU_PROCESS;
358         } else if (SWITCH_PPAPI_BROKER_PROCESS.equals(processType)) {
359             inSandbox = false;
360         }
361
362         ChildProcessConnection allocatedConnection = null;
363         synchronized (ChildProcessLauncher.class) {
364             if (inSandbox) {
365                 allocatedConnection = sSpareSandboxedConnection;
366                 sSpareSandboxedConnection = null;
367             }
368         }
369         if (allocatedConnection == null) {
370             allocatedConnection = allocateBoundConnection(context, commandLine, inSandbox);
371             if (allocatedConnection == null) {
372                 // Notify the native code so it can free the heap allocated callback.
373                 nativeOnChildProcessStarted(clientContext, 0);
374                 Log.e(TAG, "Allocation of new service failed.");
375                 TraceEvent.end();
376                 return;
377             }
378         }
379
380         Log.d(TAG, "Setting up connection to process: slot=" +
381                 allocatedConnection.getServiceNumber());
382         triggerConnectionSetup(allocatedConnection, commandLine, childProcessId, filesToBeMapped,
383                 callbackType, clientContext);
384         TraceEvent.end();
385     }
386
387     @VisibleForTesting
388     static void triggerConnectionSetup(
389             final ChildProcessConnection connection,
390             String[] commandLine,
391             int childProcessId,
392             FileDescriptorInfo[] filesToBeMapped,
393             int callbackType,
394             final long clientContext) {
395         ChildProcessConnection.ConnectionCallback connectionCallback =
396                 new ChildProcessConnection.ConnectionCallback() {
397                     @Override
398                     public void onConnected(int pid) {
399                         Log.d(TAG, "on connect callback, pid=" + pid + " context=" + clientContext);
400                         if (pid != NULL_PROCESS_HANDLE) {
401                             sBindingManager.addNewConnection(pid, connection);
402                             sServiceMap.put(pid, connection);
403                         }
404                         // If the connection fails and pid == 0, the Java-side cleanup was already
405                         // handled by DeathCallback. We still have to call back to native for
406                         // cleanup there.
407                         if (clientContext != 0) {  // Will be 0 in Java instrumentation tests.
408                             nativeOnChildProcessStarted(clientContext, pid);
409                         }
410                     }
411                 };
412
413         // TODO(sievers): Revisit this as it doesn't correctly handle the utility process
414         // assert callbackType != CALLBACK_FOR_UNKNOWN_PROCESS;
415
416         connection.setupConnection(commandLine,
417                                    filesToBeMapped,
418                                    createCallback(childProcessId, callbackType),
419                                    connectionCallback,
420                                    Linker.getSharedRelros());
421     }
422
423     /**
424      * Terminates a child process. This may be called from any thread.
425      *
426      * @param pid The pid (process handle) of the service connection obtained from {@link #start}.
427      */
428     @CalledByNative
429     static void stop(int pid) {
430         Log.d(TAG, "stopping child connection: pid=" + pid);
431         ChildProcessConnection connection = sServiceMap.remove(pid);
432         if (connection == null) {
433             logPidWarning(pid, "Tried to stop non-existent connection");
434             return;
435         }
436         sBindingManager.clearConnection(pid);
437         connection.stop();
438         freeConnection(connection);
439     }
440
441     /**
442      * This implementation is used to receive callbacks from the remote service.
443      */
444     private static IChildProcessCallback createCallback(
445             final int childProcessId, final int callbackType) {
446         return new IChildProcessCallback.Stub() {
447             /**
448              * This is called by the remote service regularly to tell us about new values. Note that
449              * IPC calls are dispatched through a thread pool running in each process, so the code
450              * executing here will NOT be running in our main thread -- so, to update the UI, we
451              * need to use a Handler.
452              */
453             @Override
454             public void establishSurfacePeer(
455                     int pid, Surface surface, int primaryID, int secondaryID) {
456                 // Do not allow a malicious renderer to connect to a producer. This is only used
457                 // from stream textures managed by the GPU process.
458                 if (callbackType != CALLBACK_FOR_GPU_PROCESS) {
459                     Log.e(TAG, "Illegal callback for non-GPU process.");
460                     return;
461                 }
462
463                 nativeEstablishSurfacePeer(pid, surface, primaryID, secondaryID);
464             }
465
466             @Override
467             public Surface getViewSurface(int surfaceId) {
468                 // Do not allow a malicious renderer to get to our view surface.
469                 if (callbackType != CALLBACK_FOR_GPU_PROCESS) {
470                     Log.e(TAG, "Illegal callback for non-GPU process.");
471                     return null;
472                 }
473
474                 Surface surface = sViewSurfaceMap.get(surfaceId);
475                 if (surface == null) {
476                     Log.e(TAG, "Invalid surfaceId.");
477                     return null;
478                 }
479                 return surface;
480             }
481
482             @Override
483             public Surface getSurfaceTextureSurface(int primaryId, int secondaryId) {
484                 if (callbackType != CALLBACK_FOR_RENDERER_PROCESS) {
485                     Log.e(TAG, "Illegal callback for non-renderer process.");
486                     return null;
487                 }
488
489                 if (secondaryId != childProcessId) {
490                     Log.e(TAG, "Illegal secondaryId for renderer process.");
491                     return null;
492                 }
493
494                 Pair<Integer, Integer> key = new Pair<Integer, Integer>(primaryId, secondaryId);
495                 // Note: This removes the surface and passes the ownership to the caller.
496                 Surface surface = sSurfaceTextureSurfaceMap.remove(key);
497                 if (surface == null) {
498                     Log.e(TAG, "Invalid Id for surface texture.");
499                     return null;
500                 }
501                 return surface;
502             }
503         };
504     }
505
506      static void logPidWarning(int pid, String message) {
507         // This class is effectively a no-op in single process mode, so don't log warnings there.
508         if (pid > 0 && !nativeIsSingleProcess()) {
509             Log.w(TAG, message + ", pid=" + pid);
510         }
511     }
512
513     @VisibleForTesting
514     static ChildProcessConnection allocateBoundConnectionForTesting(Context context) {
515         return allocateBoundConnection(context, null, true);
516     }
517
518     /** @return the count of sandboxed connections managed by the allocator */
519     @VisibleForTesting
520     static int allocatedConnectionsCountForTesting() {
521         return sSandboxedChildConnectionAllocator.allocatedConnectionsCountForTesting();
522     }
523
524     /** @return the count of services set up and working */
525     @VisibleForTesting
526     static int connectedServicesCountForTesting() {
527         return sServiceMap.size();
528     }
529
530     private static native void nativeOnChildProcessStarted(long clientContext, int pid);
531     private static native void nativeEstablishSurfacePeer(
532             int pid, Surface surface, int primaryID, int secondaryID);
533     private static native boolean nativeIsSingleProcess();
534 }