• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.os;
18 
19 import static android.system.OsConstants.F_SETFD;
20 import static android.system.OsConstants.O_CLOEXEC;
21 import static android.system.OsConstants.POLLIN;
22 
23 import static com.android.internal.os.ZygoteConnectionConstants.CONNECTION_TIMEOUT_MILLIS;
24 import static com.android.internal.os.ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS;
25 
26 import android.compat.annotation.UnsupportedAppUsage;
27 import android.content.pm.ApplicationInfo;
28 import android.net.Credentials;
29 import android.net.LocalSocket;
30 import android.os.Parcel;
31 import android.os.Process;
32 import android.os.Trace;
33 import android.system.ErrnoException;
34 import android.system.Os;
35 import android.system.StructPollfd;
36 import android.util.Log;
37 
38 import dalvik.system.VMRuntime;
39 import dalvik.system.ZygoteHooks;
40 
41 import libcore.io.IoUtils;
42 
43 import java.io.ByteArrayInputStream;
44 import java.io.DataInputStream;
45 import java.io.DataOutputStream;
46 import java.io.FileDescriptor;
47 import java.io.IOException;
48 import java.nio.charset.StandardCharsets;
49 import java.util.Base64;
50 import java.util.concurrent.TimeUnit;
51 
52 /**
53  * A connection that can make spawn requests.
54  */
55 class ZygoteConnection {
56     private static final String TAG = "Zygote";
57 
58     /**
59      * The command socket.
60      *
61      * mSocket is retained in the child process in "peer wait" mode, so
62      * that it closes when the child process terminates. In other cases,
63      * it is closed in the peer.
64      */
65     @UnsupportedAppUsage
66     private final LocalSocket mSocket;
67     @UnsupportedAppUsage
68     private final DataOutputStream mSocketOutStream;
69     @UnsupportedAppUsage
70     private final Credentials peer;
71     private final String abiList;
72     private boolean isEof;
73 
74     /**
75      * Constructs instance from connected socket.
76      *
77      * @param socket non-null; connected socket
78      * @param abiList non-null; a list of ABIs this zygote supports.
79      * @throws IOException If obtaining the peer credentials fails
80      */
ZygoteConnection(LocalSocket socket, String abiList)81     ZygoteConnection(LocalSocket socket, String abiList) throws IOException {
82         mSocket = socket;
83         this.abiList = abiList;
84 
85         mSocketOutStream = new DataOutputStream(socket.getOutputStream());
86 
87         mSocket.setSoTimeout(CONNECTION_TIMEOUT_MILLIS);
88 
89         try {
90             peer = mSocket.getPeerCredentials();
91         } catch (IOException ex) {
92             Log.e(TAG, "Cannot read peer credentials", ex);
93             throw ex;
94         }
95 
96         if (peer.getUid() != Process.SYSTEM_UID) {
97             throw new ZygoteSecurityException("Only system UID is allowed to connect to Zygote.");
98         }
99         isEof = false;
100     }
101 
102     /**
103      * Returns the file descriptor of the associated socket.
104      *
105      * @return null-ok; file descriptor
106      */
getFileDescriptor()107     FileDescriptor getFileDescriptor() {
108         return mSocket.getFileDescriptor();
109     }
110 
111     /**
112      * Reads a command from the command socket. If a child is successfully forked, a
113      * {@code Runnable} that calls the childs main method (or equivalent) is returned in the child
114      * process. {@code null} is always returned in the parent process (the zygote).
115      * If multipleOK is set, we may keep processing additional fork commands before returning.
116      *
117      * If the client closes the socket, an {@code EOF} condition is set, which callers can test
118      * for by calling {@code ZygoteConnection.isClosedByPeer}.
119      */
processCommand(ZygoteServer zygoteServer, boolean multipleOK)120     Runnable processCommand(ZygoteServer zygoteServer, boolean multipleOK) {
121         ZygoteArguments parsedArgs;
122 
123         try (ZygoteCommandBuffer argBuffer = new ZygoteCommandBuffer(mSocket)) {
124             while (true) {
125                 try {
126                     parsedArgs = ZygoteArguments.getInstance(argBuffer);
127                     // Keep argBuffer around, since we need it to fork.
128                 } catch (IOException ex) {
129                     throw new IllegalStateException("IOException on command socket", ex);
130                 }
131                 if (parsedArgs == null) {
132                     isEof = true;
133                     return null;
134                 }
135 
136                 int pid;
137                 FileDescriptor childPipeFd = null;
138                 FileDescriptor serverPipeFd = null;
139 
140                 if (parsedArgs.mBootCompleted) {
141                     handleBootCompleted();
142                     return null;
143                 }
144 
145                 if (parsedArgs.mAbiListQuery) {
146                     handleAbiListQuery();
147                     return null;
148                 }
149 
150                 if (parsedArgs.mPidQuery) {
151                     handlePidQuery();
152                     return null;
153                 }
154 
155                 if (parsedArgs.mUsapPoolStatusSpecified
156                         || parsedArgs.mApiDenylistExemptions != null
157                         || parsedArgs.mHiddenApiAccessLogSampleRate != -1
158                         || parsedArgs.mHiddenApiAccessStatslogSampleRate != -1) {
159                     // Handle these once we've released argBuffer, to avoid opening a second one.
160                     break;
161                 }
162 
163                 if (parsedArgs.mPreloadDefault) {
164                     handlePreload();
165                     return null;
166                 }
167 
168                 if (canPreloadApp() && parsedArgs.mPreloadApp != null) {
169                     byte[] rawParcelData = Base64.getDecoder().decode(parsedArgs.mPreloadApp);
170                     Parcel appInfoParcel = Parcel.obtain();
171                     appInfoParcel.unmarshall(rawParcelData, 0, rawParcelData.length);
172                     appInfoParcel.setDataPosition(0);
173                     ApplicationInfo appInfo =
174                             ApplicationInfo.CREATOR.createFromParcel(appInfoParcel);
175                     appInfoParcel.recycle();
176                     if (appInfo != null) {
177                         handlePreloadApp(appInfo);
178                     } else {
179                         throw new IllegalArgumentException("Failed to deserialize --preload-app");
180                     }
181                     return null;
182                 }
183 
184                 if (parsedArgs.mPermittedCapabilities != 0
185                         || parsedArgs.mEffectiveCapabilities != 0) {
186                     throw new ZygoteSecurityException("Client may not specify capabilities: "
187                             + "permitted=0x" + Long.toHexString(parsedArgs.mPermittedCapabilities)
188                             + ", effective=0x"
189                             + Long.toHexString(parsedArgs.mEffectiveCapabilities));
190                 }
191 
192                 Zygote.applyUidSecurityPolicy(parsedArgs, peer);
193                 Zygote.applyInvokeWithSecurityPolicy(parsedArgs, peer);
194 
195                 Zygote.applyDebuggerSystemProperty(parsedArgs);
196                 Zygote.applyInvokeWithSystemProperty(parsedArgs);
197 
198                 int[][] rlimits = null;
199 
200                 if (parsedArgs.mRLimits != null) {
201                     rlimits = parsedArgs.mRLimits.toArray(Zygote.INT_ARRAY_2D);
202                 }
203 
204                 int[] fdsToIgnore = null;
205 
206                 if (parsedArgs.mInvokeWith != null) {
207                     try {
208                         FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC);
209                         childPipeFd = pipeFds[1];
210                         serverPipeFd = pipeFds[0];
211                         Os.fcntlInt(childPipeFd, F_SETFD, 0);
212                         fdsToIgnore = new int[]{childPipeFd.getInt$(), serverPipeFd.getInt$()};
213                     } catch (ErrnoException errnoEx) {
214                         throw new IllegalStateException("Unable to set up pipe for invoke-with",
215                                 errnoEx);
216                     }
217                 }
218 
219                 /*
220                  * In order to avoid leaking descriptors to the Zygote child,
221                  * the native code must close the two Zygote socket descriptors
222                  * in the child process before it switches from Zygote-root to
223                  * the UID and privileges of the application being launched.
224                  *
225                  * In order to avoid "bad file descriptor" errors when the
226                  * two LocalSocket objects are closed, the Posix file
227                  * descriptors are released via a dup2() call which closes
228                  * the socket and substitutes an open descriptor to /dev/null.
229                  */
230 
231                 int [] fdsToClose = { -1, -1 };
232 
233                 FileDescriptor fd = mSocket.getFileDescriptor();
234 
235                 if (fd != null) {
236                     fdsToClose[0] = fd.getInt$();
237                 }
238 
239                 FileDescriptor zygoteFd = zygoteServer.getZygoteSocketFileDescriptor();
240 
241                 if (zygoteFd != null) {
242                     fdsToClose[1] = zygoteFd.getInt$();
243                 }
244 
245                 if (parsedArgs.mInvokeWith != null || parsedArgs.mStartChildZygote
246                         || !multipleOK || peer.getUid() != Process.SYSTEM_UID) {
247                     // Continue using old code for now. TODO: Handle these cases in the other path.
248                     pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid,
249                             parsedArgs.mGids, parsedArgs.mRuntimeFlags, rlimits,
250                             parsedArgs.mMountExternal, parsedArgs.mSeInfo, parsedArgs.mNiceName,
251                             fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
252                             parsedArgs.mInstructionSet, parsedArgs.mAppDataDir,
253                             parsedArgs.mIsTopApp, parsedArgs.mPkgDataInfoList,
254                             parsedArgs.mAllowlistedDataInfoList, parsedArgs.mBindMountAppDataDirs,
255                             parsedArgs.mBindMountAppStorageDirs,
256                             parsedArgs.mBindMountSyspropOverrides);
257 
258                     try {
259                         if (pid == 0) {
260                             // in child
261                             zygoteServer.setForkChild();
262 
263                             zygoteServer.closeServerSocket();
264                             IoUtils.closeQuietly(serverPipeFd);
265                             serverPipeFd = null;
266 
267                             return handleChildProc(parsedArgs, childPipeFd,
268                                     parsedArgs.mStartChildZygote);
269                         } else {
270                             // In the parent. A pid < 0 indicates a failure and will be handled in
271                             // handleParentProc.
272                             IoUtils.closeQuietly(childPipeFd);
273                             childPipeFd = null;
274                             handleParentProc(pid, serverPipeFd);
275                             return null;
276                         }
277                     } finally {
278                         IoUtils.closeQuietly(childPipeFd);
279                         IoUtils.closeQuietly(serverPipeFd);
280                     }
281                 } else {
282                     ZygoteHooks.preFork();
283                     Runnable result = Zygote.forkSimpleApps(argBuffer,
284                             zygoteServer.getZygoteSocketFileDescriptor(),
285                             peer.getUid(), Zygote.minChildUid(peer), parsedArgs.mNiceName);
286                     if (result == null) {
287                         // parent; we finished some number of forks. Result is Boolean.
288                         // We already did the equivalent of handleParentProc().
289                         ZygoteHooks.postForkCommon();
290                         // argBuffer contains a command not understood by forksimpleApps.
291                         continue;
292                     } else {
293                         // child; result is a Runnable.
294                         zygoteServer.setForkChild();
295                         return result;
296                     }
297                 }
298             }
299         }
300         // Handle anything that may need a ZygoteCommandBuffer after we've released ours.
301         if (parsedArgs.mUsapPoolStatusSpecified) {
302             return handleUsapPoolStatusChange(zygoteServer, parsedArgs.mUsapPoolEnabled);
303         }
304         if (parsedArgs.mApiDenylistExemptions != null) {
305             return handleApiDenylistExemptions(zygoteServer,
306                     parsedArgs.mApiDenylistExemptions);
307         }
308         if (parsedArgs.mHiddenApiAccessLogSampleRate != -1
309                 || parsedArgs.mHiddenApiAccessStatslogSampleRate != -1) {
310             return handleHiddenApiAccessLogSampleRate(zygoteServer,
311                     parsedArgs.mHiddenApiAccessLogSampleRate,
312                     parsedArgs.mHiddenApiAccessStatslogSampleRate);
313         }
314         throw new AssertionError("Shouldn't get here");
315     }
316 
handleAbiListQuery()317     private void handleAbiListQuery() {
318         try {
319             final byte[] abiListBytes = abiList.getBytes(StandardCharsets.US_ASCII);
320             mSocketOutStream.writeInt(abiListBytes.length);
321             mSocketOutStream.write(abiListBytes);
322         } catch (IOException ioe) {
323             throw new IllegalStateException("Error writing to command socket", ioe);
324         }
325     }
326 
handlePidQuery()327     private void handlePidQuery() {
328         try {
329             String pidString = String.valueOf(Process.myPid());
330             final byte[] pidStringBytes = pidString.getBytes(StandardCharsets.US_ASCII);
331             mSocketOutStream.writeInt(pidStringBytes.length);
332             mSocketOutStream.write(pidStringBytes);
333         } catch (IOException ioe) {
334             throw new IllegalStateException("Error writing to command socket", ioe);
335         }
336     }
337 
handleBootCompleted()338     private void handleBootCompleted() {
339         try {
340             mSocketOutStream.writeInt(0);
341         } catch (IOException ioe) {
342             throw new IllegalStateException("Error writing to command socket", ioe);
343         }
344 
345         VMRuntime.bootCompleted();
346     }
347 
348     /**
349      * Preloads resources if the zygote is in lazily preload mode. Writes the result of the
350      * preload operation; {@code 0} when a preload was initiated due to this request and {@code 1}
351      * if no preload was initiated. The latter implies that the zygote is not configured to load
352      * resources lazy or that the zygote has already handled a previous request to handlePreload.
353      */
handlePreload()354     private void handlePreload() {
355         try {
356             if (isPreloadComplete()) {
357                 mSocketOutStream.writeInt(1);
358             } else {
359                 preload();
360                 mSocketOutStream.writeInt(0);
361             }
362         } catch (IOException ioe) {
363             throw new IllegalStateException("Error writing to command socket", ioe);
364         }
365     }
366 
stateChangeWithUsapPoolReset(ZygoteServer zygoteServer, Runnable stateChangeCode)367     private Runnable stateChangeWithUsapPoolReset(ZygoteServer zygoteServer,
368             Runnable stateChangeCode) {
369         try {
370             if (zygoteServer.isUsapPoolEnabled()) {
371                 Log.i(TAG, "Emptying USAP Pool due to state change.");
372                 Zygote.emptyUsapPool();
373             }
374 
375             stateChangeCode.run();
376 
377             if (zygoteServer.isUsapPoolEnabled()) {
378                 Runnable fpResult =
379                         zygoteServer.fillUsapPool(
380                                 new int[]{mSocket.getFileDescriptor().getInt$()}, false);
381 
382                 if (fpResult != null) {
383                     zygoteServer.setForkChild();
384                     return fpResult;
385                 } else {
386                     Log.i(TAG, "Finished refilling USAP Pool after state change.");
387                 }
388             }
389 
390             mSocketOutStream.writeInt(0);
391 
392             return null;
393         } catch (IOException ioe) {
394             throw new IllegalStateException("Error writing to command socket", ioe);
395         }
396     }
397 
398     /**
399      * Makes the necessary changes to implement a new API deny list exemption policy, and then
400      * responds to the system server, letting it know that the task has been completed.
401      *
402      * This necessitates a change to the internal state of the Zygote.  As such, if the USAP
403      * pool is enabled all existing USAPs have an incorrect API deny list exemption list.  To
404      * properly handle this request the pool must be emptied and refilled.  This process can return
405      * a Runnable object that must be returned to ZygoteServer.runSelectLoop to be invoked.
406      *
407      * @param zygoteServer  The server object that received the request
408      * @param exemptions  The new exemption list.
409      * @return A Runnable object representing a new app in any USAPs spawned from here; the
410      *         zygote process will always receive a null value from this function.
411      */
handleApiDenylistExemptions(ZygoteServer zygoteServer, String[] exemptions)412     private Runnable handleApiDenylistExemptions(ZygoteServer zygoteServer, String[] exemptions) {
413         return stateChangeWithUsapPoolReset(zygoteServer,
414                 () -> ZygoteInit.setApiDenylistExemptions(exemptions));
415     }
416 
handleUsapPoolStatusChange(ZygoteServer zygoteServer, boolean newStatus)417     private Runnable handleUsapPoolStatusChange(ZygoteServer zygoteServer, boolean newStatus) {
418         try {
419             Runnable fpResult = zygoteServer.setUsapPoolStatus(newStatus, mSocket);
420 
421             if (fpResult == null) {
422                 mSocketOutStream.writeInt(0);
423             } else {
424                 zygoteServer.setForkChild();
425             }
426 
427             return fpResult;
428         } catch (IOException ioe) {
429             throw new IllegalStateException("Error writing to command socket", ioe);
430         }
431     }
432 
433     /**
434      * Changes the API access log sample rate for the Zygote and processes spawned from it.
435      *
436      * This necessitates a change to the internal state of the Zygote.  As such, if the USAP
437      * pool is enabled all existing USAPs have an incorrect API access log sample rate.  To
438      * properly handle this request the pool must be emptied and refilled.  This process can return
439      * a Runnable object that must be returned to ZygoteServer.runSelectLoop to be invoked.
440      *
441      * @param zygoteServer  The server object that received the request
442      * @param samplingRate  The new sample rate for regular logging
443      * @param statsdSamplingRate  The new sample rate for statslog logging
444      * @return A Runnable object representing a new app in any blastulas spawned from here; the
445      *         zygote process will always receive a null value from this function.
446      */
handleHiddenApiAccessLogSampleRate(ZygoteServer zygoteServer, int samplingRate, int statsdSamplingRate)447     private Runnable handleHiddenApiAccessLogSampleRate(ZygoteServer zygoteServer,
448             int samplingRate, int statsdSamplingRate) {
449         return stateChangeWithUsapPoolReset(zygoteServer, () -> {
450             int maxSamplingRate = Math.max(samplingRate, statsdSamplingRate);
451             ZygoteInit.setHiddenApiAccessLogSampleRate(maxSamplingRate);
452             StatsdHiddenApiUsageLogger.setHiddenApiAccessLogSampleRates(
453                     samplingRate, statsdSamplingRate);
454             ZygoteInit.setHiddenApiUsageLogger(StatsdHiddenApiUsageLogger.getInstance());
455         });
456     }
457 
preload()458     protected void preload() {
459         ZygoteInit.lazyPreload();
460     }
461 
isPreloadComplete()462     protected boolean isPreloadComplete() {
463         return ZygoteInit.isPreloadComplete();
464     }
465 
getSocketOutputStream()466     protected DataOutputStream getSocketOutputStream() {
467         return mSocketOutStream;
468     }
469 
canPreloadApp()470     protected boolean canPreloadApp() {
471         return false;
472     }
473 
handlePreloadApp(ApplicationInfo aInfo)474     protected void handlePreloadApp(ApplicationInfo aInfo) {
475         throw new RuntimeException("Zygote does not support app preloading");
476     }
477 
478     /**
479      * Closes socket associated with this connection.
480      */
481     @UnsupportedAppUsage
closeSocket()482     void closeSocket() {
483         try {
484             mSocket.close();
485         } catch (IOException ex) {
486             Log.e(TAG, "Exception while closing command "
487                     + "socket in parent", ex);
488         }
489     }
490 
isClosedByPeer()491     boolean isClosedByPeer() {
492         return isEof;
493     }
494 
495     /**
496      * Handles post-fork setup of child proc, closing sockets as appropriate,
497      * reopen stdio as appropriate, and ultimately throwing MethodAndArgsCaller
498      * if successful or returning if failed.
499      *
500      * @param parsedArgs non-null; zygote args
501      * @param pipeFd null-ok; pipe for communication back to Zygote.
502      * @param isZygote whether this new child process is itself a new Zygote.
503      */
handleChildProc(ZygoteArguments parsedArgs, FileDescriptor pipeFd, boolean isZygote)504     private Runnable handleChildProc(ZygoteArguments parsedArgs,
505             FileDescriptor pipeFd, boolean isZygote) {
506         /*
507          * By the time we get here, the native code has closed the two actual Zygote
508          * socket connections, and substituted /dev/null in their place.  The LocalSocket
509          * objects still need to be closed properly.
510          */
511 
512         closeSocket();
513 
514         Zygote.setAppProcessName(parsedArgs, TAG);
515 
516         // End of the postFork event.
517         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
518         if (parsedArgs.mInvokeWith != null) {
519             WrapperInit.execApplication(parsedArgs.mInvokeWith,
520                     parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion,
521                     VMRuntime.getCurrentInstructionSet(),
522                     pipeFd, parsedArgs.mRemainingArgs);
523 
524             // Should not get here.
525             throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned");
526         } else {
527             if (!isZygote) {
528                 return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
529                         parsedArgs.mDisabledCompatChanges,
530                         parsedArgs.mRemainingArgs, null /* classLoader */);
531             } else {
532                 return ZygoteInit.childZygoteInit(
533                         parsedArgs.mRemainingArgs  /* classLoader */);
534             }
535         }
536     }
537 
538     /**
539      * Handles post-fork cleanup of parent proc
540      *
541      * @param pid != 0; pid of child if &gt; 0 or indication of failed fork
542      * if &lt; 0;
543      * @param pipeFd null-ok; pipe for communication with child.
544      */
handleParentProc(int pid, FileDescriptor pipeFd)545     private void handleParentProc(int pid, FileDescriptor pipeFd) {
546         if (pid > 0) {
547             setChildPgid(pid);
548         }
549 
550         boolean usingWrapper = false;
551         if (pipeFd != null && pid > 0) {
552             int innerPid = -1;
553             try {
554                 // Do a busy loop here. We can't guarantee that a failure (and thus an exception
555                 // bail) happens in a timely manner.
556                 final int BYTES_REQUIRED = 4;  // Bytes in an int.
557 
558                 StructPollfd[] fds = new StructPollfd[] {
559                         new StructPollfd()
560                 };
561 
562                 byte[] data = new byte[BYTES_REQUIRED];
563 
564                 int remainingSleepTime = WRAPPED_PID_TIMEOUT_MILLIS;
565                 int dataIndex = 0;
566                 long startTime = System.nanoTime();
567 
568                 while (dataIndex < data.length && remainingSleepTime > 0) {
569                     fds[0].fd = pipeFd;
570                     fds[0].events = (short) POLLIN;
571                     fds[0].revents = 0;
572                     fds[0].userData = null;
573 
574                     int res = android.system.Os.poll(fds, remainingSleepTime);
575                     long endTime = System.nanoTime();
576                     int elapsedTimeMs =
577                             (int) TimeUnit.MILLISECONDS.convert(
578                                     endTime - startTime,
579                                     TimeUnit.NANOSECONDS);
580                     remainingSleepTime = WRAPPED_PID_TIMEOUT_MILLIS - elapsedTimeMs;
581 
582                     if (res > 0) {
583                         if ((fds[0].revents & POLLIN) != 0) {
584                             // Only read one byte, so as not to block. Really needed?
585                             int readBytes = android.system.Os.read(pipeFd, data, dataIndex, 1);
586                             if (readBytes < 0) {
587                                 throw new RuntimeException("Some error");
588                             }
589                             dataIndex += readBytes;
590                         } else {
591                             // Error case. revents should contain one of the error bits.
592                             break;
593                         }
594                     } else if (res == 0) {
595                         Log.w(TAG, "Timed out waiting for child.");
596                     }
597                 }
598 
599                 if (dataIndex == data.length) {
600                     DataInputStream is = new DataInputStream(new ByteArrayInputStream(data));
601                     innerPid = is.readInt();
602                 }
603 
604                 if (innerPid == -1) {
605                     Log.w(TAG, "Error reading pid from wrapped process, child may have died");
606                 }
607             } catch (Exception ex) {
608                 Log.w(TAG, "Error reading pid from wrapped process, child may have died", ex);
609             }
610 
611             // Ensure that the pid reported by the wrapped process is either the
612             // child process that we forked, or a descendant of it.
613             if (innerPid > 0) {
614                 int parentPid = innerPid;
615                 while (parentPid > 0 && parentPid != pid) {
616                     parentPid = Process.getParentPid(parentPid);
617                 }
618                 if (parentPid > 0) {
619                     Log.i(TAG, "Wrapped process has pid " + innerPid);
620                     pid = innerPid;
621                     usingWrapper = true;
622                 } else {
623                     Log.w(TAG, "Wrapped process reported a pid that is not a child of "
624                             + "the process that we forked: childPid=" + pid
625                             + " innerPid=" + innerPid);
626                 }
627             }
628         }
629 
630         try {
631             mSocketOutStream.writeInt(pid);
632             mSocketOutStream.writeBoolean(usingWrapper);
633         } catch (IOException ex) {
634             throw new IllegalStateException("Error writing to command socket", ex);
635         }
636     }
637 
setChildPgid(int pid)638     private void setChildPgid(int pid) {
639         // Try to move the new child into the peer's process group.
640         try {
641             Os.setpgid(pid, Os.getpgid(peer.getPid()));
642         } catch (ErrnoException ex) {
643             // This exception is expected in the case where
644             // the peer is not in our session
645             // TODO get rid of this log message in the case where
646             // getsid(0) != getsid(peer.getPid())
647             Log.i(TAG, "Zygote: setpgid failed. This is "
648                 + "normal if peer is not in our session");
649         }
650     }
651 }
652