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