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