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 > 0 or indication of failed fork 542 * if < 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