1 /* 2 * Copyright (C) 2021 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 android.system.virtualmachine; 18 19 import static android.os.ParcelFileDescriptor.AutoCloseInputStream; 20 import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; 21 import static android.os.ParcelFileDescriptor.MODE_READ_WRITE; 22 import static android.system.virtualmachine.VirtualMachineCallback.ERROR_PAYLOAD_CHANGED; 23 import static android.system.virtualmachine.VirtualMachineCallback.ERROR_PAYLOAD_INVALID_CONFIG; 24 import static android.system.virtualmachine.VirtualMachineCallback.ERROR_PAYLOAD_VERIFICATION_FAILED; 25 import static android.system.virtualmachine.VirtualMachineCallback.ERROR_UNKNOWN; 26 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_CRASH; 27 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_HANGUP; 28 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_INFRASTRUCTURE_ERROR; 29 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_KILLED; 30 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE; 31 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG; 32 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_PAYLOAD_HAS_CHANGED; 33 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_PAYLOAD_VERIFICATION_FAILED; 34 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_UNKNOWN_RUNTIME_ERROR; 35 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED; 36 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_PVM_FIRMWARE_PUBLIC_KEY_MISMATCH; 37 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_REBOOT; 38 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_SHUTDOWN; 39 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_START_FAILED; 40 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_UNKNOWN; 41 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_VIRTUALIZATION_SERVICE_DIED; 42 43 import static java.util.Objects.requireNonNull; 44 45 import android.annotation.CallbackExecutor; 46 import android.annotation.IntDef; 47 import android.annotation.IntRange; 48 import android.annotation.NonNull; 49 import android.annotation.Nullable; 50 import android.annotation.RequiresPermission; 51 import android.annotation.SuppressLint; 52 import android.annotation.SystemApi; 53 import android.annotation.TestApi; 54 import android.annotation.WorkerThread; 55 import android.content.ComponentCallbacks2; 56 import android.content.Context; 57 import android.content.res.Configuration; 58 import android.os.Binder; 59 import android.os.IBinder; 60 import android.os.ParcelFileDescriptor; 61 import android.os.RemoteException; 62 import android.os.ServiceSpecificException; 63 import android.system.virtualizationcommon.DeathReason; 64 import android.system.virtualizationcommon.ErrorCode; 65 import android.system.virtualizationservice.IVirtualMachine; 66 import android.system.virtualizationservice.IVirtualMachineCallback; 67 import android.system.virtualizationservice.IVirtualizationService; 68 import android.system.virtualizationservice.MemoryTrimLevel; 69 import android.system.virtualizationservice.PartitionType; 70 import android.system.virtualizationservice.VirtualMachineAppConfig; 71 import android.system.virtualizationservice.VirtualMachineState; 72 import android.util.JsonReader; 73 import android.util.Log; 74 75 import com.android.internal.annotations.GuardedBy; 76 77 import java.io.File; 78 import java.io.FileInputStream; 79 import java.io.FileNotFoundException; 80 import java.io.FileOutputStream; 81 import java.io.IOException; 82 import java.io.InputStream; 83 import java.io.InputStreamReader; 84 import java.lang.annotation.Retention; 85 import java.lang.annotation.RetentionPolicy; 86 import java.nio.channels.FileChannel; 87 import java.nio.file.FileAlreadyExistsException; 88 import java.nio.file.FileVisitResult; 89 import java.nio.file.Files; 90 import java.nio.file.Path; 91 import java.nio.file.SimpleFileVisitor; 92 import java.nio.file.attribute.BasicFileAttributes; 93 import java.util.ArrayList; 94 import java.util.Collections; 95 import java.util.List; 96 import java.util.concurrent.Executor; 97 import java.util.concurrent.atomic.AtomicBoolean; 98 import java.util.function.Consumer; 99 import java.util.zip.ZipFile; 100 101 /** 102 * Represents an VM instance, with its own configuration and state. Instances are persistent and are 103 * created or retrieved via {@link VirtualMachineManager}. 104 * 105 * <p>The {@link #run} method actually starts up the VM and allows the payload code to execute. It 106 * will continue until it exits or {@link #stop} is called. Updates on the state of the VM can be 107 * received using {@link #setCallback}. The app can communicate with the VM using {@link 108 * #connectToVsockServer} or {@link #connectVsock}. 109 * 110 * <p>The payload code running inside the VM has access to a set of native APIs; see the <a 111 * href="https://cs.android.com/android/platform/superproject/+/master:packages/modules/Virtualization/vm_payload/README.md">README 112 * file</a> for details. 113 * 114 * <p>Each VM has a unique secret, computed from the APK that contains the code running in it, the 115 * VM configuration, and a random per-instance salt. The secret can be accessed by the payload code 116 * running inside the VM (using {@code AVmPayload_getVmInstanceSecret}) but is not made available 117 * outside it. 118 * 119 * @hide 120 */ 121 @SystemApi 122 public class VirtualMachine implements AutoCloseable { 123 /** The permission needed to create or run a virtual machine. */ 124 public static final String MANAGE_VIRTUAL_MACHINE_PERMISSION = 125 "android.permission.MANAGE_VIRTUAL_MACHINE"; 126 127 /** 128 * The permission needed to create a virtual machine with more advanced configuration options. 129 */ 130 public static final String USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION = 131 "android.permission.USE_CUSTOM_VIRTUAL_MACHINE"; 132 133 /** 134 * The lowest port number that can be used to communicate with the virtual machine payload. 135 * 136 * @see #connectToVsockServer 137 * @see #connectVsock 138 */ 139 @SuppressLint("MinMaxConstant") // Won't change: see man 7 vsock. 140 public static final long MIN_VSOCK_PORT = 1024; 141 142 /** 143 * The highest port number that can be used to communicate with the virtual machine payload. 144 * 145 * @see #connectToVsockServer 146 * @see #connectVsock 147 */ 148 @SuppressLint("MinMaxConstant") // Won't change: see man 7 vsock. 149 public static final long MAX_VSOCK_PORT = (1L << 32) - 1; 150 151 /** 152 * Status of a virtual machine 153 * 154 * @hide 155 */ 156 @Retention(RetentionPolicy.SOURCE) 157 @IntDef(prefix = "STATUS_", value = { 158 STATUS_STOPPED, 159 STATUS_RUNNING, 160 STATUS_DELETED 161 }) 162 public @interface Status {} 163 164 /** The virtual machine has just been created, or {@link #stop} was called on it. */ 165 public static final int STATUS_STOPPED = 0; 166 167 /** The virtual machine is running. */ 168 public static final int STATUS_RUNNING = 1; 169 170 /** 171 * The virtual machine has been deleted. This is an irreversible state. Once a virtual machine 172 * is deleted all its secrets are permanently lost, and it cannot be run. A new virtual machine 173 * with the same name and config may be created, with new and different secrets. 174 */ 175 public static final int STATUS_DELETED = 2; 176 177 private static final String TAG = "VirtualMachine"; 178 179 /** Name of the directory under the files directory where all VMs created for the app exist. */ 180 private static final String VM_DIR = "vm"; 181 182 /** Name of the persisted config file for a VM. */ 183 private static final String CONFIG_FILE = "config.xml"; 184 185 /** Name of the instance image file for a VM. (Not implemented) */ 186 private static final String INSTANCE_IMAGE_FILE = "instance.img"; 187 188 /** Name of the idsig file for a VM */ 189 private static final String IDSIG_FILE = "idsig"; 190 191 /** Name of the idsig files for extra APKs. */ 192 private static final String EXTRA_IDSIG_FILE_PREFIX = "extra_idsig_"; 193 194 /** Size of the instance image. 10 MB. */ 195 private static final long INSTANCE_FILE_SIZE = 10 * 1024 * 1024; 196 197 /** Name of the file backing the encrypted storage */ 198 private static final String ENCRYPTED_STORE_FILE = "storage.img"; 199 200 /** The package which owns this VM. */ 201 @NonNull private final String mPackageName; 202 203 /** Name of this VM within the package. The name should be unique in the package. */ 204 @NonNull private final String mName; 205 206 /** 207 * Path to the directory containing all the files related to this VM. 208 */ 209 @NonNull private final File mVmRootPath; 210 211 /** 212 * Path to the config file for this VM. The config file is where the configuration is persisted. 213 */ 214 @NonNull private final File mConfigFilePath; 215 216 /** Path to the instance image file for this VM. */ 217 @NonNull private final File mInstanceFilePath; 218 219 /** Path to the idsig file for this VM. */ 220 @NonNull private final File mIdsigFilePath; 221 222 /** File that backs the encrypted storage - Will be null if not enabled. */ 223 @Nullable private final File mEncryptedStoreFilePath; 224 225 /** 226 * Unmodifiable list of extra apks. Apks are specified by the vm config, and corresponding 227 * idsigs are to be generated. 228 */ 229 @NonNull private final List<ExtraApkSpec> mExtraApks; 230 231 private class MemoryManagementCallbacks implements ComponentCallbacks2 { 232 @Override onConfigurationChanged(@onNull Configuration newConfig)233 public void onConfigurationChanged(@NonNull Configuration newConfig) {} 234 235 @Override onLowMemory()236 public void onLowMemory() {} 237 238 @Override onTrimMemory(int level)239 public void onTrimMemory(int level) { 240 @MemoryTrimLevel int vmTrimLevel; 241 242 switch (level) { 243 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL: 244 vmTrimLevel = MemoryTrimLevel.TRIM_MEMORY_RUNNING_CRITICAL; 245 break; 246 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW: 247 vmTrimLevel = MemoryTrimLevel.TRIM_MEMORY_RUNNING_LOW; 248 break; 249 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE: 250 vmTrimLevel = MemoryTrimLevel.TRIM_MEMORY_RUNNING_MODERATE; 251 break; 252 case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND: 253 case ComponentCallbacks2.TRIM_MEMORY_MODERATE: 254 case ComponentCallbacks2.TRIM_MEMORY_COMPLETE: 255 /* Release as much memory as we can. The app is on the LMKD LRU kill list. */ 256 vmTrimLevel = MemoryTrimLevel.TRIM_MEMORY_RUNNING_CRITICAL; 257 break; 258 default: 259 /* Treat unrecognised messages as generic low-memory warnings. */ 260 vmTrimLevel = MemoryTrimLevel.TRIM_MEMORY_RUNNING_LOW; 261 break; 262 } 263 264 synchronized (mLock) { 265 try { 266 if (mVirtualMachine != null) { 267 mVirtualMachine.onTrimMemory(vmTrimLevel); 268 } 269 } catch (Exception e) { 270 /* Caller doesn't want our exceptions. Log them instead. */ 271 Log.w(TAG, "TrimMemory failed: ", e); 272 } 273 } 274 } 275 } 276 277 /** Running instance of virtmgr that hosts VirtualizationService for this VM. */ 278 @NonNull private final VirtualizationService mVirtualizationService; 279 280 @NonNull private final MemoryManagementCallbacks mMemoryManagementCallbacks; 281 282 @NonNull private final Context mContext; 283 284 // A note on lock ordering: 285 // You can take mLock while holding VirtualMachineManager.sCreateLock, but not vice versa. 286 // We never take any other lock while holding mCallbackLock; therefore you can 287 // take mCallbackLock while holding any other lock. 288 289 /** Lock protecting our mutable state (other than callbacks). */ 290 private final Object mLock = new Object(); 291 292 /** Lock protecting callbacks. */ 293 private final Object mCallbackLock = new Object(); 294 295 private final boolean mVmOutputCaptured; 296 297 /** The configuration that is currently associated with this VM. */ 298 @GuardedBy("mLock") 299 @NonNull 300 private VirtualMachineConfig mConfig; 301 302 /** Handle to the "running" VM. */ 303 @GuardedBy("mLock") 304 @Nullable 305 private IVirtualMachine mVirtualMachine; 306 307 @GuardedBy("mLock") 308 @Nullable 309 private ParcelFileDescriptor mConsoleReader; 310 311 @GuardedBy("mLock") 312 @Nullable 313 private ParcelFileDescriptor mConsoleWriter; 314 315 @GuardedBy("mLock") 316 @Nullable 317 private ParcelFileDescriptor mLogReader; 318 319 @GuardedBy("mLock") 320 @Nullable 321 private ParcelFileDescriptor mLogWriter; 322 323 @GuardedBy("mLock") 324 private boolean mWasDeleted = false; 325 326 /** The registered callback */ 327 @GuardedBy("mCallbackLock") 328 @Nullable 329 private VirtualMachineCallback mCallback; 330 331 /** The executor on which the callback will be executed */ 332 @GuardedBy("mCallbackLock") 333 @Nullable 334 private Executor mCallbackExecutor; 335 336 private static class ExtraApkSpec { 337 public final File apk; 338 public final File idsig; 339 ExtraApkSpec(File apk, File idsig)340 ExtraApkSpec(File apk, File idsig) { 341 this.apk = apk; 342 this.idsig = idsig; 343 } 344 } 345 346 static { 347 System.loadLibrary("virtualmachine_jni"); 348 } 349 VirtualMachine( @onNull Context context, @NonNull String name, @NonNull VirtualMachineConfig config, @NonNull VirtualizationService service)350 private VirtualMachine( 351 @NonNull Context context, 352 @NonNull String name, 353 @NonNull VirtualMachineConfig config, 354 @NonNull VirtualizationService service) 355 throws VirtualMachineException { 356 mPackageName = context.getPackageName(); 357 mName = requireNonNull(name, "Name must not be null"); 358 mConfig = requireNonNull(config, "Config must not be null"); 359 mVirtualizationService = service; 360 361 File thisVmDir = getVmDir(context, mName); 362 mVmRootPath = thisVmDir; 363 mConfigFilePath = new File(thisVmDir, CONFIG_FILE); 364 mInstanceFilePath = new File(thisVmDir, INSTANCE_IMAGE_FILE); 365 mIdsigFilePath = new File(thisVmDir, IDSIG_FILE); 366 mExtraApks = setupExtraApks(context, config, thisVmDir); 367 mMemoryManagementCallbacks = new MemoryManagementCallbacks(); 368 mContext = context; 369 mEncryptedStoreFilePath = 370 (config.isEncryptedStorageEnabled()) 371 ? new File(thisVmDir, ENCRYPTED_STORE_FILE) 372 : null; 373 374 mVmOutputCaptured = config.isVmOutputCaptured(); 375 } 376 377 /** 378 * Creates a virtual machine from an {@link VirtualMachineDescriptor} object and associates it 379 * with the given name. 380 * 381 * <p>The new virtual machine will be in the same state as the descriptor indicates. 382 * 383 * <p>Once a virtual machine is imported it is persisted until it is deleted by calling {@link 384 * #delete}. The imported virtual machine is in {@link #STATUS_STOPPED} state. To run the VM, 385 * call {@link #run}. 386 */ 387 @GuardedBy("VirtualMachineManager.sCreateLock") 388 @NonNull fromDescriptor( @onNull Context context, @NonNull String name, @NonNull VirtualMachineDescriptor vmDescriptor)389 static VirtualMachine fromDescriptor( 390 @NonNull Context context, 391 @NonNull String name, 392 @NonNull VirtualMachineDescriptor vmDescriptor) 393 throws VirtualMachineException { 394 File vmDir = createVmDir(context, name); 395 try { 396 VirtualMachine vm; 397 try (vmDescriptor) { 398 VirtualMachineConfig config = VirtualMachineConfig.from(vmDescriptor.getConfigFd()); 399 vm = new VirtualMachine(context, name, config, VirtualizationService.getInstance()); 400 config.serialize(vm.mConfigFilePath); 401 try { 402 vm.mInstanceFilePath.createNewFile(); 403 } catch (IOException e) { 404 throw new VirtualMachineException("failed to create instance image", e); 405 } 406 vm.importInstanceFrom(vmDescriptor.getInstanceImgFd()); 407 408 if (vmDescriptor.getEncryptedStoreFd() != null) { 409 try { 410 vm.mEncryptedStoreFilePath.createNewFile(); 411 } catch (IOException e) { 412 throw new VirtualMachineException( 413 "failed to create encrypted storage image", e); 414 } 415 vm.importEncryptedStoreFrom(vmDescriptor.getEncryptedStoreFd()); 416 } 417 } 418 return vm; 419 } catch (VirtualMachineException | RuntimeException e) { 420 // If anything goes wrong, delete any files created so far and the VM's directory 421 try { 422 deleteRecursively(vmDir); 423 } catch (IOException innerException) { 424 e.addSuppressed(innerException); 425 } 426 throw e; 427 } 428 } 429 430 /** 431 * Creates a virtual machine with the given name and config. Once a virtual machine is created 432 * it is persisted until it is deleted by calling {@link #delete}. The created virtual machine 433 * is in {@link #STATUS_STOPPED} state. To run the VM, call {@link #run}. 434 */ 435 @GuardedBy("VirtualMachineManager.sCreateLock") 436 @NonNull create( @onNull Context context, @NonNull String name, @NonNull VirtualMachineConfig config)437 static VirtualMachine create( 438 @NonNull Context context, @NonNull String name, @NonNull VirtualMachineConfig config) 439 throws VirtualMachineException { 440 File vmDir = createVmDir(context, name); 441 442 try { 443 VirtualMachine vm = 444 new VirtualMachine(context, name, config, VirtualizationService.getInstance()); 445 config.serialize(vm.mConfigFilePath); 446 try { 447 vm.mInstanceFilePath.createNewFile(); 448 } catch (IOException e) { 449 throw new VirtualMachineException("failed to create instance image", e); 450 } 451 if (config.isEncryptedStorageEnabled()) { 452 try { 453 vm.mEncryptedStoreFilePath.createNewFile(); 454 } catch (IOException e) { 455 throw new VirtualMachineException( 456 "failed to create encrypted storage image", e); 457 } 458 } 459 460 IVirtualizationService service = vm.mVirtualizationService.getBinder(); 461 462 try { 463 service.initializeWritablePartition( 464 ParcelFileDescriptor.open(vm.mInstanceFilePath, MODE_READ_WRITE), 465 INSTANCE_FILE_SIZE, 466 PartitionType.ANDROID_VM_INSTANCE); 467 } catch (FileNotFoundException e) { 468 throw new VirtualMachineException("instance image missing", e); 469 } catch (RemoteException e) { 470 throw e.rethrowAsRuntimeException(); 471 } catch (ServiceSpecificException | IllegalArgumentException e) { 472 throw new VirtualMachineException("failed to create instance partition", e); 473 } 474 475 if (config.isEncryptedStorageEnabled()) { 476 try { 477 service.initializeWritablePartition( 478 ParcelFileDescriptor.open(vm.mEncryptedStoreFilePath, MODE_READ_WRITE), 479 config.getEncryptedStorageBytes(), 480 PartitionType.ENCRYPTEDSTORE); 481 } catch (FileNotFoundException e) { 482 throw new VirtualMachineException("encrypted storage image missing", e); 483 } catch (RemoteException e) { 484 throw e.rethrowAsRuntimeException(); 485 } catch (ServiceSpecificException | IllegalArgumentException e) { 486 throw new VirtualMachineException( 487 "failed to create encrypted storage partition", e); 488 } 489 } 490 return vm; 491 } catch (VirtualMachineException | RuntimeException e) { 492 // If anything goes wrong, delete any files created so far and the VM's directory 493 try { 494 deleteRecursively(vmDir); 495 } catch (IOException innerException) { 496 e.addSuppressed(innerException); 497 } 498 throw e; 499 } 500 } 501 502 /** Loads a virtual machine that is already created before. */ 503 @GuardedBy("VirtualMachineManager.sCreateLock") 504 @Nullable load(@onNull Context context, @NonNull String name)505 static VirtualMachine load(@NonNull Context context, @NonNull String name) 506 throws VirtualMachineException { 507 File thisVmDir = getVmDir(context, name); 508 if (!thisVmDir.exists()) { 509 // The VM doesn't exist. 510 return null; 511 } 512 File configFilePath = new File(thisVmDir, CONFIG_FILE); 513 VirtualMachineConfig config = VirtualMachineConfig.from(configFilePath); 514 VirtualMachine vm = 515 new VirtualMachine(context, name, config, VirtualizationService.getInstance()); 516 517 if (!vm.mInstanceFilePath.exists()) { 518 throw new VirtualMachineException("instance image missing"); 519 } 520 if (config.isEncryptedStorageEnabled() && !vm.mEncryptedStoreFilePath.exists()) { 521 throw new VirtualMachineException("Storage image missing"); 522 } 523 return vm; 524 } 525 526 @GuardedBy("VirtualMachineManager.sCreateLock") delete(Context context, String name)527 void delete(Context context, String name) throws VirtualMachineException { 528 synchronized (mLock) { 529 checkStopped(); 530 // Once we explicitly delete a VM it must remain permanently in the deleted state; 531 // if a new VM is created with the same name (and files) that's unrelated. 532 mWasDeleted = true; 533 } 534 deleteVmDirectory(context, name); 535 } 536 deleteVmDirectory(Context context, String name)537 static void deleteVmDirectory(Context context, String name) throws VirtualMachineException { 538 try { 539 deleteRecursively(getVmDir(context, name)); 540 } catch (IOException e) { 541 throw new VirtualMachineException(e); 542 } 543 } 544 545 @GuardedBy("VirtualMachineManager.sCreateLock") 546 @NonNull createVmDir(@onNull Context context, @NonNull String name)547 private static File createVmDir(@NonNull Context context, @NonNull String name) 548 throws VirtualMachineException { 549 File vmDir = getVmDir(context, name); 550 try { 551 // We don't need to undo this even if VM creation fails. 552 Files.createDirectories(vmDir.getParentFile().toPath()); 553 554 // The checking of the existence of this directory and the creation of it is done 555 // atomically. If the directory already exists (i.e. the VM with the same name was 556 // already created), FileAlreadyExistsException is thrown. 557 Files.createDirectory(vmDir.toPath()); 558 } catch (FileAlreadyExistsException e) { 559 throw new VirtualMachineException("virtual machine already exists", e); 560 } catch (IOException e) { 561 throw new VirtualMachineException("failed to create directory for VM", e); 562 } 563 return vmDir; 564 } 565 566 @NonNull getVmDir(@onNull Context context, @NonNull String name)567 private static File getVmDir(@NonNull Context context, @NonNull String name) { 568 if (name.contains(File.separator) || name.equals(".") || name.equals("..")) { 569 throw new IllegalArgumentException("Invalid VM name: " + name); 570 } 571 File vmRoot = new File(context.getDataDir(), VM_DIR); 572 return new File(vmRoot, name); 573 } 574 575 /** 576 * Returns the name of this virtual machine. The name is unique in the package and can't be 577 * changed. 578 * 579 * @hide 580 */ 581 @SystemApi 582 @NonNull getName()583 public String getName() { 584 return mName; 585 } 586 587 /** 588 * Returns the currently selected config of this virtual machine. There can be multiple virtual 589 * machines sharing the same config. Even in that case, the virtual machines are completely 590 * isolated from each other; they have different secrets. It is also possible that a virtual 591 * machine can change its config, which can be done by calling {@link #setConfig}. 592 * 593 * <p>NOTE: This method may block and should not be called on the main thread. 594 * 595 * @hide 596 */ 597 @SystemApi 598 @WorkerThread 599 @NonNull getConfig()600 public VirtualMachineConfig getConfig() { 601 synchronized (mLock) { 602 return mConfig; 603 } 604 } 605 606 /** 607 * Returns the current status of this virtual machine. 608 * 609 * <p>NOTE: This method may block and should not be called on the main thread. 610 * 611 * @hide 612 */ 613 @SystemApi 614 @WorkerThread 615 @Status getStatus()616 public int getStatus() { 617 IVirtualMachine virtualMachine; 618 synchronized (mLock) { 619 if (mWasDeleted) { 620 return STATUS_DELETED; 621 } 622 virtualMachine = mVirtualMachine; 623 } 624 625 int status; 626 if (virtualMachine == null) { 627 status = STATUS_STOPPED; 628 } else { 629 try { 630 status = stateToStatus(virtualMachine.getState()); 631 } catch (RemoteException e) { 632 throw e.rethrowAsRuntimeException(); 633 } 634 } 635 if (status == STATUS_STOPPED && !mVmRootPath.exists()) { 636 // A VM can quite happily keep running if its backing files have been deleted. 637 // But once it stops, it's gone forever. 638 synchronized (mLock) { 639 dropVm(); 640 } 641 return STATUS_DELETED; 642 } 643 return status; 644 } 645 stateToStatus(@irtualMachineState int state)646 private int stateToStatus(@VirtualMachineState int state) { 647 switch (state) { 648 case VirtualMachineState.STARTING: 649 case VirtualMachineState.STARTED: 650 case VirtualMachineState.READY: 651 case VirtualMachineState.FINISHED: 652 return STATUS_RUNNING; 653 case VirtualMachineState.NOT_STARTED: 654 case VirtualMachineState.DEAD: 655 default: 656 return STATUS_STOPPED; 657 } 658 } 659 660 // Throw an appropriate exception if we have a running VM, or the VM has been deleted. 661 @GuardedBy("mLock") checkStopped()662 private void checkStopped() throws VirtualMachineException { 663 if (mWasDeleted || !mVmRootPath.exists()) { 664 throw new VirtualMachineException("VM has been deleted"); 665 } 666 if (mVirtualMachine == null) { 667 return; 668 } 669 try { 670 if (stateToStatus(mVirtualMachine.getState()) != STATUS_STOPPED) { 671 throw new VirtualMachineException("VM is not in stopped state"); 672 } 673 } catch (RemoteException e) { 674 throw e.rethrowAsRuntimeException(); 675 } 676 // It's stopped, but we still have a reference to it - we can fix that. 677 dropVm(); 678 } 679 680 /** 681 * This should only be called when we know our VM has stopped; we no longer need to hold a 682 * reference to it (this allows resources to be GC'd) and we no longer need to be informed of 683 * memory pressure. 684 */ 685 @GuardedBy("mLock") dropVm()686 private void dropVm() { 687 mContext.unregisterComponentCallbacks(mMemoryManagementCallbacks); 688 mVirtualMachine = null; 689 } 690 691 /** If we have an IVirtualMachine in the running state return it, otherwise throw. */ 692 @GuardedBy("mLock") getRunningVm()693 private IVirtualMachine getRunningVm() throws VirtualMachineException { 694 try { 695 if (mVirtualMachine != null 696 && stateToStatus(mVirtualMachine.getState()) == STATUS_RUNNING) { 697 return mVirtualMachine; 698 } else { 699 if (mWasDeleted || !mVmRootPath.exists()) { 700 throw new VirtualMachineException("VM has been deleted"); 701 } else { 702 throw new VirtualMachineException("VM is not in running state"); 703 } 704 } 705 } catch (RemoteException e) { 706 throw e.rethrowAsRuntimeException(); 707 } 708 } 709 710 /** 711 * Registers the callback object to get events from the virtual machine. If a callback was 712 * already registered, it is replaced with the new one. 713 * 714 * @hide 715 */ 716 @SystemApi setCallback( @onNull @allbackExecutor Executor executor, @NonNull VirtualMachineCallback callback)717 public void setCallback( 718 @NonNull @CallbackExecutor Executor executor, 719 @NonNull VirtualMachineCallback callback) { 720 synchronized (mCallbackLock) { 721 mCallback = callback; 722 mCallbackExecutor = executor; 723 } 724 } 725 726 /** 727 * Clears the currently registered callback. 728 * 729 * @hide 730 */ 731 @SystemApi clearCallback()732 public void clearCallback() { 733 synchronized (mCallbackLock) { 734 mCallback = null; 735 mCallbackExecutor = null; 736 } 737 } 738 739 /** Executes a callback on the callback executor. */ executeCallback(Consumer<VirtualMachineCallback> fn)740 private void executeCallback(Consumer<VirtualMachineCallback> fn) { 741 final VirtualMachineCallback callback; 742 final Executor executor; 743 synchronized (mCallbackLock) { 744 callback = mCallback; 745 executor = mCallbackExecutor; 746 } 747 if (callback == null || executor == null) { 748 return; 749 } 750 final long restoreToken = Binder.clearCallingIdentity(); 751 try { 752 executor.execute(() -> fn.accept(callback)); 753 } finally { 754 Binder.restoreCallingIdentity(restoreToken); 755 } 756 } 757 758 /** 759 * Runs this virtual machine. The returning of this method however doesn't mean that the VM has 760 * actually started running or the OS has booted there. Such events can be notified by 761 * registering a callback using {@link #setCallback} before calling {@code run()}. 762 * 763 * <p>NOTE: This method may block and should not be called on the main thread. 764 * 765 * @throws VirtualMachineException if the virtual machine is not stopped or could not be 766 * started. 767 * @hide 768 */ 769 @SystemApi 770 @WorkerThread 771 @RequiresPermission(MANAGE_VIRTUAL_MACHINE_PERMISSION) run()772 public void run() throws VirtualMachineException { 773 synchronized (mLock) { 774 checkStopped(); 775 776 try { 777 mIdsigFilePath.createNewFile(); 778 for (ExtraApkSpec extraApk : mExtraApks) { 779 extraApk.idsig.createNewFile(); 780 } 781 } catch (IOException e) { 782 // If the file already exists, exception is not thrown. 783 throw new VirtualMachineException("Failed to create APK signature file", e); 784 } 785 786 IVirtualizationService service = mVirtualizationService.getBinder(); 787 788 try { 789 if (mVmOutputCaptured) { 790 createVmPipes(); 791 } 792 793 VirtualMachineAppConfig appConfig = 794 getConfig().toVsConfig(mContext.getPackageManager()); 795 appConfig.name = mName; 796 797 try { 798 createIdSigs(service, appConfig); 799 } catch (FileNotFoundException e) { 800 throw new VirtualMachineException("Failed to generate APK signature", e); 801 } 802 803 android.system.virtualizationservice.VirtualMachineConfig vmConfigParcel = 804 android.system.virtualizationservice.VirtualMachineConfig.appConfig( 805 appConfig); 806 807 mVirtualMachine = service.createVm(vmConfigParcel, mConsoleWriter, mLogWriter); 808 mVirtualMachine.registerCallback(new CallbackTranslator(service)); 809 mContext.registerComponentCallbacks(mMemoryManagementCallbacks); 810 mVirtualMachine.start(); 811 } catch (IllegalStateException | ServiceSpecificException e) { 812 throw new VirtualMachineException(e); 813 } catch (RemoteException e) { 814 throw e.rethrowAsRuntimeException(); 815 } 816 } 817 } 818 createIdSigs(IVirtualizationService service, VirtualMachineAppConfig appConfig)819 private void createIdSigs(IVirtualizationService service, VirtualMachineAppConfig appConfig) 820 throws RemoteException, FileNotFoundException { 821 // Fill the idsig file by hashing the apk 822 service.createOrUpdateIdsigFile( 823 appConfig.apk, ParcelFileDescriptor.open(mIdsigFilePath, MODE_READ_WRITE)); 824 825 for (ExtraApkSpec extraApk : mExtraApks) { 826 service.createOrUpdateIdsigFile( 827 ParcelFileDescriptor.open(extraApk.apk, MODE_READ_ONLY), 828 ParcelFileDescriptor.open(extraApk.idsig, MODE_READ_WRITE)); 829 } 830 831 // Re-open idsig files in read-only mode 832 appConfig.idsig = ParcelFileDescriptor.open(mIdsigFilePath, MODE_READ_ONLY); 833 appConfig.instanceImage = ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_WRITE); 834 if (mEncryptedStoreFilePath != null) { 835 appConfig.encryptedStorageImage = 836 ParcelFileDescriptor.open(mEncryptedStoreFilePath, MODE_READ_WRITE); 837 } 838 List<ParcelFileDescriptor> extraIdsigs = new ArrayList<>(); 839 for (ExtraApkSpec extraApk : mExtraApks) { 840 extraIdsigs.add(ParcelFileDescriptor.open(extraApk.idsig, MODE_READ_ONLY)); 841 } 842 appConfig.extraIdsigs = extraIdsigs; 843 } 844 845 @GuardedBy("mLock") createVmPipes()846 private void createVmPipes() throws VirtualMachineException { 847 try { 848 if (mConsoleReader == null || mConsoleWriter == null) { 849 ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); 850 mConsoleReader = pipe[0]; 851 mConsoleWriter = pipe[1]; 852 } 853 854 if (mLogReader == null || mLogWriter == null) { 855 ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); 856 mLogReader = pipe[0]; 857 mLogWriter = pipe[1]; 858 } 859 } catch (IOException e) { 860 throw new VirtualMachineException("Failed to create stream for VM", e); 861 } 862 } 863 864 /** 865 * Returns the stream object representing the console output from the virtual machine. The 866 * console output is only available if the {@link VirtualMachineConfig} specifies that it should 867 * be {@linkplain VirtualMachineConfig#isVmOutputCaptured captured}. 868 * 869 * <p>If you turn on output capture, you must consume data from {@code getConsoleOutput} - 870 * because otherwise the code in the VM may get blocked when the pipe buffer fills up. 871 * 872 * <p>NOTE: This method may block and should not be called on the main thread. 873 * 874 * @throws VirtualMachineException if the stream could not be created, or capturing is turned 875 * off. 876 * @hide 877 */ 878 @SystemApi 879 @WorkerThread 880 @NonNull getConsoleOutput()881 public InputStream getConsoleOutput() throws VirtualMachineException { 882 if (!mVmOutputCaptured) { 883 throw new VirtualMachineException("Capturing vm outputs is turned off"); 884 } 885 synchronized (mLock) { 886 createVmPipes(); 887 return new FileInputStream(mConsoleReader.getFileDescriptor()); 888 } 889 } 890 891 /** 892 * Returns the stream object representing the log output from the virtual machine. The log 893 * output is only available if the VirtualMachineConfig specifies that it should be {@linkplain 894 * VirtualMachineConfig#isVmOutputCaptured captured}. 895 * 896 * <p>If you turn on output capture, you must consume data from {@code getLogOutput} - because 897 * otherwise the code in the VM may get blocked when the pipe buffer fills up. 898 * 899 * <p>NOTE: This method may block and should not be called on the main thread. 900 * 901 * @throws VirtualMachineException if the stream could not be created, or capturing is turned 902 * off. 903 * @hide 904 */ 905 @SystemApi 906 @WorkerThread 907 @NonNull getLogOutput()908 public InputStream getLogOutput() throws VirtualMachineException { 909 if (!mVmOutputCaptured) { 910 throw new VirtualMachineException("Capturing vm outputs is turned off"); 911 } 912 synchronized (mLock) { 913 createVmPipes(); 914 return new FileInputStream(mLogReader.getFileDescriptor()); 915 } 916 } 917 918 /** 919 * Stops this virtual machine. Stopping a virtual machine is like pulling the plug on a real 920 * computer; the machine halts immediately. Software running on the virtual machine is not 921 * notified of the event. Writes to {@linkplain 922 * VirtualMachineConfig.Builder#setEncryptedStorageBytes encrypted storage} might not be 923 * persisted, and the instance might be left in an inconsistent state. 924 * 925 * <p>For a graceful shutdown, you could request the payload to call {@code exit()}, e.g. via a 926 * {@linkplain #connectToVsockServer binder request}, and wait for {@link 927 * VirtualMachineCallback#onPayloadFinished} to be called. 928 * 929 * <p>A stopped virtual machine can be re-started by calling {@link #run()}. 930 * 931 * <p>NOTE: This method may block and should not be called on the main thread. 932 * 933 * @throws VirtualMachineException if the virtual machine is not running or could not be 934 * stopped. 935 * @hide 936 */ 937 @SystemApi 938 @WorkerThread stop()939 public void stop() throws VirtualMachineException { 940 synchronized (mLock) { 941 if (mVirtualMachine == null) { 942 throw new VirtualMachineException("VM is not running"); 943 } 944 try { 945 mVirtualMachine.stop(); 946 dropVm(); 947 } catch (RemoteException e) { 948 throw e.rethrowAsRuntimeException(); 949 } catch (ServiceSpecificException e) { 950 throw new VirtualMachineException(e); 951 } 952 } 953 } 954 955 /** 956 * Stops this virtual machine, if it is running. 957 * 958 * <p>NOTE: This method may block and should not be called on the main thread. 959 * 960 * @see #stop() 961 * @hide 962 */ 963 @SystemApi 964 @WorkerThread 965 @Override close()966 public void close() { 967 synchronized (mLock) { 968 if (mVirtualMachine == null) { 969 return; 970 } 971 try { 972 if (stateToStatus(mVirtualMachine.getState()) == STATUS_RUNNING) { 973 mVirtualMachine.stop(); 974 dropVm(); 975 } 976 } catch (RemoteException e) { 977 throw e.rethrowAsRuntimeException(); 978 } catch (ServiceSpecificException e) { 979 // Deliberately ignored; this almost certainly means the VM exited just as 980 // we tried to stop it. 981 Log.i(TAG, "Ignoring error on close()", e); 982 } 983 } 984 } 985 deleteRecursively(File dir)986 private static void deleteRecursively(File dir) throws IOException { 987 // Note: This doesn't follow symlinks, which is important. Instead they are just deleted 988 // (and Files.delete deletes the link not the target). 989 Files.walkFileTree(dir.toPath(), new SimpleFileVisitor<>() { 990 @Override 991 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 992 throws IOException { 993 Files.delete(file); 994 return FileVisitResult.CONTINUE; 995 } 996 997 @Override 998 public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException { 999 // Directory is deleted after we've visited (deleted) all its contents, so it 1000 // should be empty by now. 1001 Files.delete(dir); 1002 return FileVisitResult.CONTINUE; 1003 } 1004 }); 1005 } 1006 1007 /** 1008 * Changes the config of this virtual machine to a new one. This can be used to adjust things 1009 * like the number of CPU and size of the RAM, depending on the situation (e.g. the size of the 1010 * application to run on the virtual machine, etc.) 1011 * 1012 * <p>The new config must be {@linkplain VirtualMachineConfig#isCompatibleWith compatible with} 1013 * the existing config. 1014 * 1015 * <p>NOTE: This method may block and should not be called on the main thread. 1016 * 1017 * @return the old config 1018 * @throws VirtualMachineException if the virtual machine is not stopped, or the new config is 1019 * incompatible. 1020 * @hide 1021 */ 1022 @SystemApi 1023 @WorkerThread 1024 @NonNull setConfig(@onNull VirtualMachineConfig newConfig)1025 public VirtualMachineConfig setConfig(@NonNull VirtualMachineConfig newConfig) 1026 throws VirtualMachineException { 1027 synchronized (mLock) { 1028 VirtualMachineConfig oldConfig = mConfig; 1029 if (!oldConfig.isCompatibleWith(newConfig)) { 1030 throw new VirtualMachineException("incompatible config"); 1031 } 1032 checkStopped(); 1033 1034 if (oldConfig != newConfig) { 1035 // Delete any existing file before recreating; that ensures any 1036 // VirtualMachineDescriptor that refers to the old file does not see the new config. 1037 mConfigFilePath.delete(); 1038 newConfig.serialize(mConfigFilePath); 1039 mConfig = newConfig; 1040 } 1041 return oldConfig; 1042 } 1043 } 1044 1045 @Nullable nativeConnectToVsockServer(IBinder vmBinder, int port)1046 private static native IBinder nativeConnectToVsockServer(IBinder vmBinder, int port); 1047 1048 /** 1049 * Connect to a VM's binder service via vsock and return the root IBinder object. Guest VMs are 1050 * expected to set up vsock servers in their payload. After the host app receives the {@link 1051 * VirtualMachineCallback#onPayloadReady}, it can use this method to establish a connection to 1052 * the guest VM. 1053 * 1054 * <p>NOTE: This method may block and should not be called on the main thread. 1055 * 1056 * @throws VirtualMachineException if the virtual machine is not running or the connection 1057 * failed. 1058 * @hide 1059 */ 1060 @SystemApi 1061 @WorkerThread 1062 @NonNull connectToVsockServer( @ntRangefrom = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port)1063 public IBinder connectToVsockServer( 1064 @IntRange(from = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port) 1065 throws VirtualMachineException { 1066 1067 synchronized (mLock) { 1068 IBinder iBinder = 1069 nativeConnectToVsockServer(getRunningVm().asBinder(), validatePort(port)); 1070 if (iBinder == null) { 1071 throw new VirtualMachineException("Failed to connect to vsock server"); 1072 } 1073 return iBinder; 1074 } 1075 } 1076 1077 /** 1078 * Opens a vsock connection to the VM on the given port. 1079 * 1080 * <p>The caller is responsible for closing the returned {@code ParcelFileDescriptor}. 1081 * 1082 * <p>NOTE: This method may block and should not be called on the main thread. 1083 * 1084 * @throws VirtualMachineException if connecting fails. 1085 * @hide 1086 */ 1087 @SystemApi 1088 @WorkerThread 1089 @NonNull connectVsock( @ntRangefrom = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port)1090 public ParcelFileDescriptor connectVsock( 1091 @IntRange(from = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port) 1092 throws VirtualMachineException { 1093 synchronized (mLock) { 1094 try { 1095 return getRunningVm().connectVsock(validatePort(port)); 1096 } catch (RemoteException e) { 1097 throw e.rethrowAsRuntimeException(); 1098 } catch (ServiceSpecificException e) { 1099 throw new VirtualMachineException(e); 1100 } 1101 } 1102 } 1103 validatePort(long port)1104 private int validatePort(long port) { 1105 // Ports below 1024 are "privileged" (payload code can't bind to these), and port numbers 1106 // are 32-bit unsigned numbers at the OS level, even though we pass them as 32-bit signed 1107 // numbers internally. 1108 if (port < MIN_VSOCK_PORT || port > MAX_VSOCK_PORT) { 1109 throw new IllegalArgumentException("Bad port " + port); 1110 } 1111 return (int) port; 1112 } 1113 1114 /** 1115 * Returns the root directory where all files related to this {@link VirtualMachine} (e.g. 1116 * {@code instance.img}, {@code apk.idsig}, etc) are stored. 1117 * 1118 * @hide 1119 */ 1120 @TestApi 1121 @NonNull getRootDir()1122 public File getRootDir() { 1123 return mVmRootPath; 1124 } 1125 1126 /** 1127 * Captures the current state of the VM in a {@link VirtualMachineDescriptor} instance. The VM 1128 * needs to be stopped to avoid inconsistency in its state representation. 1129 * 1130 * <p>The state of the VM is not actually copied until {@link 1131 * VirtualMachineManager#importFromDescriptor} is called. It is recommended that the VM not be 1132 * started until that operation is complete. 1133 * 1134 * <p>NOTE: This method may block and should not be called on the main thread. 1135 * 1136 * @return a {@link VirtualMachineDescriptor} instance that represents the VM's state. 1137 * @throws VirtualMachineException if the virtual machine is not stopped, or the state could not 1138 * be captured. 1139 * @hide 1140 */ 1141 @SystemApi 1142 @WorkerThread 1143 @NonNull toDescriptor()1144 public VirtualMachineDescriptor toDescriptor() throws VirtualMachineException { 1145 synchronized (mLock) { 1146 checkStopped(); 1147 try { 1148 return new VirtualMachineDescriptor( 1149 ParcelFileDescriptor.open(mConfigFilePath, MODE_READ_ONLY), 1150 ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_ONLY), 1151 mEncryptedStoreFilePath != null 1152 ? ParcelFileDescriptor.open(mEncryptedStoreFilePath, MODE_READ_ONLY) 1153 : null); 1154 } catch (IOException e) { 1155 throw new VirtualMachineException(e); 1156 } 1157 } 1158 } 1159 1160 @Override toString()1161 public String toString() { 1162 VirtualMachineConfig config = getConfig(); 1163 String payloadConfigPath = config.getPayloadConfigPath(); 1164 String payloadBinaryName = config.getPayloadBinaryName(); 1165 1166 StringBuilder result = new StringBuilder(); 1167 result.append("VirtualMachine(") 1168 .append("name:") 1169 .append(getName()) 1170 .append(", "); 1171 if (payloadBinaryName != null) { 1172 result.append("payload:").append(payloadBinaryName).append(", "); 1173 } 1174 if (payloadConfigPath != null) { 1175 result.append("config:") 1176 .append(payloadConfigPath) 1177 .append(", "); 1178 } 1179 result.append("package: ") 1180 .append(mPackageName) 1181 .append(")"); 1182 return result.toString(); 1183 } 1184 parseExtraApkListFromPayloadConfig(JsonReader reader)1185 private static List<String> parseExtraApkListFromPayloadConfig(JsonReader reader) 1186 throws VirtualMachineException { 1187 /* 1188 * JSON schema from packages/modules/Virtualization/microdroid/payload/config/src/lib.rs: 1189 * 1190 * <p>{ "extra_apks": [ { "path": "/system/app/foo.apk", }, ... ], ... } 1191 */ 1192 try { 1193 List<String> apks = new ArrayList<>(); 1194 1195 reader.beginObject(); 1196 while (reader.hasNext()) { 1197 if (reader.nextName().equals("extra_apks")) { 1198 reader.beginArray(); 1199 while (reader.hasNext()) { 1200 reader.beginObject(); 1201 String name = reader.nextName(); 1202 if (name.equals("path")) { 1203 apks.add(reader.nextString()); 1204 } else { 1205 reader.skipValue(); 1206 } 1207 reader.endObject(); 1208 } 1209 reader.endArray(); 1210 } else { 1211 reader.skipValue(); 1212 } 1213 } 1214 reader.endObject(); 1215 return apks; 1216 } catch (IOException e) { 1217 throw new VirtualMachineException(e); 1218 } 1219 } 1220 1221 /** 1222 * Reads the payload config inside the application, parses extra APK information, and then 1223 * creates corresponding idsig file paths. 1224 */ setupExtraApks( @onNull Context context, @NonNull VirtualMachineConfig config, @NonNull File vmDir)1225 private static List<ExtraApkSpec> setupExtraApks( 1226 @NonNull Context context, @NonNull VirtualMachineConfig config, @NonNull File vmDir) 1227 throws VirtualMachineException { 1228 String configPath = config.getPayloadConfigPath(); 1229 if (configPath == null) { 1230 return Collections.emptyList(); 1231 } 1232 try (ZipFile zipFile = new ZipFile(context.getPackageCodePath())) { 1233 InputStream inputStream = 1234 zipFile.getInputStream(zipFile.getEntry(configPath)); 1235 List<String> apkList = 1236 parseExtraApkListFromPayloadConfig( 1237 new JsonReader(new InputStreamReader(inputStream))); 1238 1239 List<ExtraApkSpec> extraApks = new ArrayList<>(); 1240 for (int i = 0; i < apkList.size(); ++i) { 1241 extraApks.add( 1242 new ExtraApkSpec( 1243 new File(apkList.get(i)), 1244 new File(vmDir, EXTRA_IDSIG_FILE_PREFIX + i))); 1245 } 1246 1247 return Collections.unmodifiableList(extraApks); 1248 } catch (IOException e) { 1249 throw new VirtualMachineException("Couldn't parse extra apks from the vm config", e); 1250 } 1251 } 1252 importInstanceFrom(@onNull ParcelFileDescriptor instanceFd)1253 private void importInstanceFrom(@NonNull ParcelFileDescriptor instanceFd) 1254 throws VirtualMachineException { 1255 try (FileChannel instance = new FileOutputStream(mInstanceFilePath).getChannel(); 1256 FileChannel instanceInput = new AutoCloseInputStream(instanceFd).getChannel()) { 1257 instance.transferFrom(instanceInput, /*position=*/ 0, instanceInput.size()); 1258 } catch (IOException e) { 1259 throw new VirtualMachineException("failed to transfer instance image", e); 1260 } 1261 } 1262 importEncryptedStoreFrom(@onNull ParcelFileDescriptor encryptedStoreFd)1263 private void importEncryptedStoreFrom(@NonNull ParcelFileDescriptor encryptedStoreFd) 1264 throws VirtualMachineException { 1265 try (FileChannel storeOutput = new FileOutputStream(mEncryptedStoreFilePath).getChannel(); 1266 FileChannel storeInput = new AutoCloseInputStream(encryptedStoreFd).getChannel()) { 1267 storeOutput.transferFrom(storeInput, /*position=*/ 0, storeInput.size()); 1268 } catch (IOException e) { 1269 throw new VirtualMachineException("failed to transfer encryptedstore image", e); 1270 } 1271 } 1272 1273 /** Map the raw AIDL (& binder) callbacks to what the client expects. */ 1274 private class CallbackTranslator extends IVirtualMachineCallback.Stub { 1275 private final IVirtualizationService mService; 1276 private final DeathRecipient mDeathRecipient; 1277 1278 // The VM should only be observed to die once 1279 private final AtomicBoolean mOnDiedCalled = new AtomicBoolean(false); 1280 CallbackTranslator(IVirtualizationService service)1281 public CallbackTranslator(IVirtualizationService service) throws RemoteException { 1282 this.mService = service; 1283 this.mDeathRecipient = () -> reportStopped(STOP_REASON_VIRTUALIZATION_SERVICE_DIED); 1284 service.asBinder().linkToDeath(mDeathRecipient, 0); 1285 } 1286 1287 @Override onPayloadStarted(int cid)1288 public void onPayloadStarted(int cid) { 1289 executeCallback((cb) -> cb.onPayloadStarted(VirtualMachine.this)); 1290 } 1291 1292 @Override onPayloadReady(int cid)1293 public void onPayloadReady(int cid) { 1294 executeCallback((cb) -> cb.onPayloadReady(VirtualMachine.this)); 1295 } 1296 1297 @Override onPayloadFinished(int cid, int exitCode)1298 public void onPayloadFinished(int cid, int exitCode) { 1299 executeCallback((cb) -> cb.onPayloadFinished(VirtualMachine.this, exitCode)); 1300 } 1301 1302 @Override onError(int cid, int errorCode, String message)1303 public void onError(int cid, int errorCode, String message) { 1304 int translatedError = getTranslatedError(errorCode); 1305 executeCallback((cb) -> cb.onError(VirtualMachine.this, translatedError, message)); 1306 } 1307 1308 @Override onDied(int cid, int reason)1309 public void onDied(int cid, int reason) { 1310 int translatedReason = getTranslatedReason(reason); 1311 reportStopped(translatedReason); 1312 mService.asBinder().unlinkToDeath(mDeathRecipient, 0); 1313 } 1314 reportStopped(@irtualMachineCallback.StopReason int reason)1315 private void reportStopped(@VirtualMachineCallback.StopReason int reason) { 1316 if (mOnDiedCalled.compareAndSet(false, true)) { 1317 executeCallback((cb) -> cb.onStopped(VirtualMachine.this, reason)); 1318 } 1319 } 1320 1321 @VirtualMachineCallback.ErrorCode getTranslatedError(int reason)1322 private int getTranslatedError(int reason) { 1323 switch (reason) { 1324 case ErrorCode.PAYLOAD_VERIFICATION_FAILED: 1325 return ERROR_PAYLOAD_VERIFICATION_FAILED; 1326 case ErrorCode.PAYLOAD_CHANGED: 1327 return ERROR_PAYLOAD_CHANGED; 1328 case ErrorCode.PAYLOAD_CONFIG_INVALID: 1329 return ERROR_PAYLOAD_INVALID_CONFIG; 1330 default: 1331 return ERROR_UNKNOWN; 1332 } 1333 } 1334 1335 @VirtualMachineCallback.StopReason getTranslatedReason(int reason)1336 private int getTranslatedReason(int reason) { 1337 switch (reason) { 1338 case DeathReason.INFRASTRUCTURE_ERROR: 1339 return STOP_REASON_INFRASTRUCTURE_ERROR; 1340 case DeathReason.KILLED: 1341 return STOP_REASON_KILLED; 1342 case DeathReason.SHUTDOWN: 1343 return STOP_REASON_SHUTDOWN; 1344 case DeathReason.START_FAILED: 1345 return STOP_REASON_START_FAILED; 1346 case DeathReason.REBOOT: 1347 return STOP_REASON_REBOOT; 1348 case DeathReason.CRASH: 1349 return STOP_REASON_CRASH; 1350 case DeathReason.PVM_FIRMWARE_PUBLIC_KEY_MISMATCH: 1351 return STOP_REASON_PVM_FIRMWARE_PUBLIC_KEY_MISMATCH; 1352 case DeathReason.PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED: 1353 return STOP_REASON_PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED; 1354 case DeathReason.MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE: 1355 return STOP_REASON_MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE; 1356 case DeathReason.MICRODROID_PAYLOAD_HAS_CHANGED: 1357 return STOP_REASON_MICRODROID_PAYLOAD_HAS_CHANGED; 1358 case DeathReason.MICRODROID_PAYLOAD_VERIFICATION_FAILED: 1359 return STOP_REASON_MICRODROID_PAYLOAD_VERIFICATION_FAILED; 1360 case DeathReason.MICRODROID_INVALID_PAYLOAD_CONFIG: 1361 return STOP_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG; 1362 case DeathReason.MICRODROID_UNKNOWN_RUNTIME_ERROR: 1363 return STOP_REASON_MICRODROID_UNKNOWN_RUNTIME_ERROR; 1364 case DeathReason.HANGUP: 1365 return STOP_REASON_HANGUP; 1366 default: 1367 return STOP_REASON_UNKNOWN; 1368 } 1369 } 1370 } 1371 } 1372