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 23 import static java.util.Objects.requireNonNull; 24 25 import android.annotation.FlaggedApi; 26 import android.annotation.IntDef; 27 import android.annotation.IntRange; 28 import android.annotation.NonNull; 29 import android.annotation.Nullable; 30 import android.annotation.RequiresPermission; 31 import android.annotation.StringDef; 32 import android.annotation.SystemApi; 33 import android.annotation.TestApi; 34 import android.content.Context; 35 import android.content.pm.ApplicationInfo; 36 import android.content.pm.PackageManager; 37 import android.net.LocalSocket; 38 import android.net.LocalSocketAddress; 39 import android.os.Build; 40 import android.os.ParcelFileDescriptor; 41 import android.os.PersistableBundle; 42 import android.sysprop.HypervisorProperties; 43 import android.system.virtualizationservice.AssignedDevices; 44 import android.system.virtualizationservice.CpuOptions; 45 import android.system.virtualizationservice.CustomMemoryBackingFile; 46 import android.system.virtualizationservice.DiskImage; 47 import android.system.virtualizationservice.Partition; 48 import android.system.virtualizationservice.SharedPath; 49 import android.system.virtualizationservice.UsbConfig; 50 import android.system.virtualizationservice.VirtualMachineAppConfig; 51 import android.system.virtualizationservice.VirtualMachinePayloadConfig; 52 import android.system.virtualizationservice.VirtualMachineRawConfig; 53 import android.text.TextUtils; 54 import android.util.Log; 55 56 import com.android.system.virtualmachine.flags.Flags; 57 58 import java.io.File; 59 import java.io.FileInputStream; 60 import java.io.FileNotFoundException; 61 import java.io.FileOutputStream; 62 import java.io.IOException; 63 import java.io.InputStream; 64 import java.io.OutputStream; 65 import java.lang.annotation.Retention; 66 import java.lang.annotation.RetentionPolicy; 67 import java.nio.file.Files; 68 import java.nio.file.Path; 69 import java.util.ArrayList; 70 import java.util.Arrays; 71 import java.util.Collections; 72 import java.util.List; 73 import java.util.Objects; 74 import java.util.Optional; 75 import java.util.zip.ZipFile; 76 77 /** 78 * Represents a configuration of a virtual machine. A configuration consists of hardware 79 * configurations like the number of CPUs and the size of RAM, and software configurations like the 80 * payload to run on the virtual machine. 81 * 82 * @hide 83 */ 84 @SystemApi 85 public final class VirtualMachineConfig { 86 private static final String TAG = "VirtualMachineConfig"; 87 88 private static String[] EMPTY_STRING_ARRAY = {}; 89 private static final String U_BOOT_PREBUILT_PATH_ARM = "/apex/com.android.virt/etc/u-boot.bin"; 90 private static final String U_BOOT_PREBUILT_PATH_X86 = "/apex/com.android.virt/etc/u-boot.rom"; 91 92 // These define the schema of the config file persisted on disk. 93 // Please bump up the version number when adding a new key. 94 private static final int VERSION = 10; 95 private static final String KEY_VERSION = "version"; 96 private static final String KEY_PACKAGENAME = "packageName"; 97 private static final String KEY_APKPATH = "apkPath"; 98 private static final String KEY_PAYLOADCONFIGPATH = "payloadConfigPath"; 99 private static final String KEY_CUSTOMIMAGECONFIG = "customImageConfig"; 100 private static final String KEY_PAYLOADBINARYNAME = "payloadBinaryPath"; 101 private static final String KEY_DEBUGLEVEL = "debugLevel"; 102 private static final String KEY_PROTECTED_VM = "protectedVm"; 103 private static final String KEY_MEMORY_BYTES = "memoryBytes"; 104 private static final String KEY_CPU_TOPOLOGY = "cpuTopology"; 105 private static final String KEY_CONSOLE_INPUT_DEVICE = "consoleInputDevice"; 106 private static final String KEY_ENCRYPTED_STORAGE_BYTES = "encryptedStorageBytes"; 107 private static final String KEY_VM_OUTPUT_CAPTURED = "vmOutputCaptured"; 108 private static final String KEY_VM_CONSOLE_INPUT_SUPPORTED = "vmConsoleInputSupported"; 109 private static final String KEY_CONNECT_VM_CONSOLE = "connectVmConsole"; 110 private static final String KEY_VENDOR_DISK_IMAGE_PATH = "vendorDiskImagePath"; 111 private static final String KEY_OS = "os"; 112 private static final String KEY_EXTRA_APKS = "extraApks"; 113 private static final String KEY_SHOULD_BOOST_UCLAMP = "shouldBoostUclamp"; 114 private static final String KEY_SHOULD_USE_HUGEPAGES = "shouldUseHugepages"; 115 116 /** @hide */ 117 @Retention(RetentionPolicy.SOURCE) 118 @IntDef( 119 prefix = "DEBUG_LEVEL_", 120 value = {DEBUG_LEVEL_NONE, DEBUG_LEVEL_FULL}) 121 public @interface DebugLevel {} 122 123 /** 124 * Not debuggable at all. No log is exported from the VM. Debugger can't be attached to the app 125 * process running in the VM. This is the default level. 126 * 127 * @hide 128 */ 129 @SystemApi public static final int DEBUG_LEVEL_NONE = 0; 130 131 /** 132 * Fully debuggable. All logs (both logcat and kernel message) are exported. All processes 133 * running in the VM can be attached to the debugger. Rooting is possible. 134 * 135 * @hide 136 */ 137 @SystemApi public static final int DEBUG_LEVEL_FULL = 1; 138 139 /** @hide */ 140 @Retention(RetentionPolicy.SOURCE) 141 @IntDef( 142 prefix = "CPU_TOPOLOGY_", 143 value = { 144 CPU_TOPOLOGY_ONE_CPU, 145 CPU_TOPOLOGY_MATCH_HOST, 146 }) 147 public @interface CpuTopology {} 148 149 /** 150 * Run VM with 1 vCPU. This is the default option, usually the fastest to boot and consuming the 151 * least amount of resources. Typically the best option for small or ephemeral workloads. 152 * 153 * @hide 154 */ 155 @SystemApi public static final int CPU_TOPOLOGY_ONE_CPU = 0; 156 157 /** 158 * Run VM with vCPU topology matching the physical CPU topology of the host. Usually takes 159 * longer to boot and consumes more resources compared to a single vCPU. Typically a good option 160 * for long-running workloads that benefit from parallel execution. 161 * 162 * @hide 163 */ 164 @SystemApi public static final int CPU_TOPOLOGY_MATCH_HOST = 1; 165 166 /** Name of a package whose primary APK contains the VM payload. */ 167 @Nullable private final String mPackageName; 168 169 /** Absolute path to the APK file containing the VM payload. */ 170 @Nullable private final String mApkPath; 171 172 private final List<String> mExtraApks; 173 174 @DebugLevel private final int mDebugLevel; 175 176 /** Whether to run the VM in protected mode, so the host can't access its memory. */ 177 private final boolean mProtectedVm; 178 179 /** 180 * The amount of RAM to give the VM, in bytes. If this is 0 or negative the default will be 181 * used. 182 */ 183 private final long mMemoryBytes; 184 185 /** CPU topology configuration of the VM. */ 186 @CpuTopology private final int mCpuTopology; 187 188 /** The serial device for VM console input. */ 189 @Nullable private final String mConsoleInputDevice; 190 191 /** Path within the APK to the payload config file that defines software aspects of the VM. */ 192 @Nullable private final String mPayloadConfigPath; 193 194 /** Name of the payload binary file within the APK that will be executed within the VM. */ 195 @Nullable private final String mPayloadBinaryName; 196 197 /** The custom image config file to launch the custom VM. */ 198 @Nullable private final VirtualMachineCustomImageConfig mCustomImageConfig; 199 200 /** The size of storage in bytes. 0 indicates that encryptedStorage is not required */ 201 private final long mEncryptedStorageBytes; 202 203 /** Whether the app can read console and log output. */ 204 private final boolean mVmOutputCaptured; 205 206 /** Whether the app can write console input to the VM */ 207 private final boolean mVmConsoleInputSupported; 208 209 /** Whether to connect the VM console to a host console. */ 210 private final boolean mConnectVmConsole; 211 212 @Nullable private final File mVendorDiskImage; 213 214 /** OS name of the VM using payload binaries. */ 215 @NonNull @OsName private final String mOs; 216 217 private final boolean mShouldBoostUclamp; 218 219 private final boolean mShouldUseHugepages; 220 221 @Retention(RetentionPolicy.SOURCE) 222 @StringDef( 223 prefix = "MICRODROID", 224 value = {MICRODROID}) 225 private @interface OsName {} 226 227 /** 228 * OS name of microdroid using microdroid kernel. 229 * 230 * @see Builder#setOs 231 * @hide 232 */ 233 @TestApi 234 @OsName 235 public static final String MICRODROID = "microdroid"; 236 VirtualMachineConfig( @ullable String packageName, @Nullable String apkPath, List<String> extraApks, @Nullable String payloadConfigPath, @Nullable String payloadBinaryName, @Nullable VirtualMachineCustomImageConfig customImageConfig, @DebugLevel int debugLevel, boolean protectedVm, long memoryBytes, @CpuTopology int cpuTopology, @Nullable String consoleInputDevice, long encryptedStorageBytes, boolean vmOutputCaptured, boolean vmConsoleInputSupported, boolean connectVmConsole, @Nullable File vendorDiskImage, @NonNull @OsName String os, boolean shouldBoostUclamp, boolean shouldUseHugepages)237 private VirtualMachineConfig( 238 @Nullable String packageName, 239 @Nullable String apkPath, 240 List<String> extraApks, 241 @Nullable String payloadConfigPath, 242 @Nullable String payloadBinaryName, 243 @Nullable VirtualMachineCustomImageConfig customImageConfig, 244 @DebugLevel int debugLevel, 245 boolean protectedVm, 246 long memoryBytes, 247 @CpuTopology int cpuTopology, 248 @Nullable String consoleInputDevice, 249 long encryptedStorageBytes, 250 boolean vmOutputCaptured, 251 boolean vmConsoleInputSupported, 252 boolean connectVmConsole, 253 @Nullable File vendorDiskImage, 254 @NonNull @OsName String os, 255 boolean shouldBoostUclamp, 256 boolean shouldUseHugepages) { 257 // This is only called from Builder.build(); the builder handles parameter validation. 258 mPackageName = packageName; 259 mApkPath = apkPath; 260 mExtraApks = 261 extraApks.isEmpty() 262 ? Collections.emptyList() 263 : Collections.unmodifiableList( 264 Arrays.asList(extraApks.toArray(new String[0]))); 265 mPayloadConfigPath = payloadConfigPath; 266 mPayloadBinaryName = payloadBinaryName; 267 mCustomImageConfig = customImageConfig; 268 mDebugLevel = debugLevel; 269 mProtectedVm = protectedVm; 270 mMemoryBytes = memoryBytes; 271 mCpuTopology = cpuTopology; 272 mConsoleInputDevice = consoleInputDevice; 273 mEncryptedStorageBytes = encryptedStorageBytes; 274 mVmOutputCaptured = vmOutputCaptured; 275 mVmConsoleInputSupported = vmConsoleInputSupported; 276 mConnectVmConsole = connectVmConsole; 277 mVendorDiskImage = vendorDiskImage; 278 mOs = os; 279 mShouldBoostUclamp = shouldBoostUclamp; 280 mShouldUseHugepages = shouldUseHugepages; 281 } 282 283 /** Loads a config from a file. */ 284 @NonNull from(@onNull File file)285 static VirtualMachineConfig from(@NonNull File file) throws VirtualMachineException { 286 try (FileInputStream input = new FileInputStream(file)) { 287 return fromInputStream(input); 288 } catch (IOException e) { 289 throw new VirtualMachineException("Failed to read VM config from file", e); 290 } 291 } 292 293 /** Loads a config from a {@link ParcelFileDescriptor}. */ 294 @NonNull from(@onNull ParcelFileDescriptor fd)295 static VirtualMachineConfig from(@NonNull ParcelFileDescriptor fd) 296 throws VirtualMachineException { 297 try (AutoCloseInputStream input = new AutoCloseInputStream(fd)) { 298 return fromInputStream(input); 299 } catch (IOException e) { 300 throw new VirtualMachineException("failed to read VM config from file descriptor", e); 301 } 302 } 303 304 /** Loads a config from a stream, for example a file. */ 305 @NonNull fromInputStream(@onNull InputStream input)306 private static VirtualMachineConfig fromInputStream(@NonNull InputStream input) 307 throws IOException, VirtualMachineException { 308 PersistableBundle b = PersistableBundle.readFromStream(input); 309 try { 310 return fromPersistableBundle(b); 311 } catch (NullPointerException | IllegalArgumentException | IllegalStateException e) { 312 throw new VirtualMachineException("Persisted VM config is invalid", e); 313 } 314 } 315 316 @NonNull fromPersistableBundle(PersistableBundle b)317 private static VirtualMachineConfig fromPersistableBundle(PersistableBundle b) { 318 int version = b.getInt(KEY_VERSION); 319 if (version > VERSION) { 320 throw new IllegalArgumentException( 321 "Version " + version + " too high; current is " + VERSION); 322 } 323 324 String packageName = b.getString(KEY_PACKAGENAME); 325 Builder builder = new Builder(packageName); 326 327 String apkPath = b.getString(KEY_APKPATH); 328 if (apkPath != null) { 329 builder.setApkPath(apkPath); 330 } 331 332 String payloadConfigPath = b.getString(KEY_PAYLOADCONFIGPATH); 333 String payloadBinaryName = b.getString(KEY_PAYLOADBINARYNAME); 334 PersistableBundle customImageConfigBundle = b.getPersistableBundle(KEY_CUSTOMIMAGECONFIG); 335 if (customImageConfigBundle != null) { 336 builder.setCustomImageConfig( 337 VirtualMachineCustomImageConfig.from(customImageConfigBundle)); 338 } else if (payloadConfigPath != null) { 339 builder.setPayloadConfigPath(payloadConfigPath); 340 } else { 341 builder.setPayloadBinaryName(payloadBinaryName); 342 } 343 344 @DebugLevel int debugLevel = b.getInt(KEY_DEBUGLEVEL); 345 if (debugLevel != DEBUG_LEVEL_NONE && debugLevel != DEBUG_LEVEL_FULL) { 346 throw new IllegalArgumentException("Invalid debugLevel: " + debugLevel); 347 } 348 builder.setDebugLevel(debugLevel); 349 builder.setProtectedVm(b.getBoolean(KEY_PROTECTED_VM)); 350 long memoryBytes = b.getLong(KEY_MEMORY_BYTES); 351 if (memoryBytes != 0) { 352 builder.setMemoryBytes(memoryBytes); 353 } 354 builder.setCpuTopology(b.getInt(KEY_CPU_TOPOLOGY)); 355 String consoleInputDevice = b.getString(KEY_CONSOLE_INPUT_DEVICE); 356 if (consoleInputDevice != null) { 357 builder.setConsoleInputDevice(consoleInputDevice); 358 } 359 long encryptedStorageBytes = b.getLong(KEY_ENCRYPTED_STORAGE_BYTES); 360 if (encryptedStorageBytes != 0) { 361 builder.setEncryptedStorageBytes(encryptedStorageBytes); 362 } 363 builder.setVmOutputCaptured(b.getBoolean(KEY_VM_OUTPUT_CAPTURED)); 364 builder.setVmConsoleInputSupported(b.getBoolean(KEY_VM_CONSOLE_INPUT_SUPPORTED)); 365 builder.setConnectVmConsole(b.getBoolean(KEY_CONNECT_VM_CONSOLE)); 366 367 String vendorDiskImagePath = b.getString(KEY_VENDOR_DISK_IMAGE_PATH); 368 if (vendorDiskImagePath != null) { 369 builder.setVendorDiskImage(new File(vendorDiskImagePath)); 370 } 371 372 builder.setOs(b.getString(KEY_OS)); 373 374 String[] extraApks = b.getStringArray(KEY_EXTRA_APKS); 375 if (extraApks != null) { 376 for (String extraApk : extraApks) { 377 builder.addExtraApk(extraApk); 378 } 379 } 380 381 builder.setShouldBoostUclamp(b.getBoolean(KEY_SHOULD_BOOST_UCLAMP)); 382 builder.setShouldUseHugepages(b.getBoolean(KEY_SHOULD_USE_HUGEPAGES)); 383 384 return builder.build(); 385 } 386 387 /** Persists this config to a file. */ serialize(@onNull File file)388 void serialize(@NonNull File file) throws VirtualMachineException { 389 try (FileOutputStream output = new FileOutputStream(file)) { 390 serializeOutputStream(output); 391 } catch (IOException e) { 392 throw new VirtualMachineException("failed to write VM config", e); 393 } 394 } 395 396 /** Persists this config to a stream, for example a file. */ serializeOutputStream(@onNull OutputStream output)397 private void serializeOutputStream(@NonNull OutputStream output) throws IOException { 398 PersistableBundle b = new PersistableBundle(); 399 b.putInt(KEY_VERSION, VERSION); 400 if (mPackageName != null) { 401 b.putString(KEY_PACKAGENAME, mPackageName); 402 } 403 if (mApkPath != null) { 404 b.putString(KEY_APKPATH, mApkPath); 405 } 406 b.putString(KEY_PAYLOADCONFIGPATH, mPayloadConfigPath); 407 b.putString(KEY_PAYLOADBINARYNAME, mPayloadBinaryName); 408 if (mCustomImageConfig != null) { 409 b.putPersistableBundle(KEY_CUSTOMIMAGECONFIG, mCustomImageConfig.toPersistableBundle()); 410 } 411 b.putInt(KEY_DEBUGLEVEL, mDebugLevel); 412 b.putBoolean(KEY_PROTECTED_VM, mProtectedVm); 413 b.putInt(KEY_CPU_TOPOLOGY, mCpuTopology); 414 if (mConsoleInputDevice != null) { 415 b.putString(KEY_CONSOLE_INPUT_DEVICE, mConsoleInputDevice); 416 } 417 if (mMemoryBytes > 0) { 418 b.putLong(KEY_MEMORY_BYTES, mMemoryBytes); 419 } 420 if (mEncryptedStorageBytes > 0) { 421 b.putLong(KEY_ENCRYPTED_STORAGE_BYTES, mEncryptedStorageBytes); 422 } 423 b.putBoolean(KEY_VM_OUTPUT_CAPTURED, mVmOutputCaptured); 424 b.putBoolean(KEY_VM_CONSOLE_INPUT_SUPPORTED, mVmConsoleInputSupported); 425 b.putBoolean(KEY_CONNECT_VM_CONSOLE, mConnectVmConsole); 426 if (mVendorDiskImage != null) { 427 b.putString(KEY_VENDOR_DISK_IMAGE_PATH, mVendorDiskImage.getAbsolutePath()); 428 } 429 b.putString(KEY_OS, mOs); 430 if (!mExtraApks.isEmpty()) { 431 String[] extraApks = mExtraApks.toArray(new String[0]); 432 b.putStringArray(KEY_EXTRA_APKS, extraApks); 433 } 434 b.putBoolean(KEY_SHOULD_BOOST_UCLAMP, mShouldBoostUclamp); 435 b.putBoolean(KEY_SHOULD_USE_HUGEPAGES, mShouldUseHugepages); 436 b.writeToStream(output); 437 } 438 439 /** 440 * Returns the absolute path of the APK which should contain the binary payload that will 441 * execute within the VM. Returns null if no specific path has been set. 442 * 443 * @hide 444 */ 445 @SystemApi 446 @Nullable getApkPath()447 public String getApkPath() { 448 return mApkPath; 449 } 450 451 /** 452 * Returns the package names of any extra APKs that have been requested for the VM. They are 453 * returned in the order in which they were added via {@link Builder#addExtraApk}. 454 * 455 * @hide 456 */ 457 @TestApi 458 @NonNull getExtraApks()459 public List<String> getExtraApks() { 460 return mExtraApks; 461 } 462 463 /** 464 * Returns the path within the APK to the payload config file that defines software aspects of 465 * the VM. 466 * 467 * @hide 468 */ 469 @TestApi 470 @Nullable getPayloadConfigPath()471 public String getPayloadConfigPath() { 472 return mPayloadConfigPath; 473 } 474 475 /** 476 * Returns the custom image config to launch the custom VM. 477 * 478 * @hide 479 */ 480 @Nullable getCustomImageConfig()481 public VirtualMachineCustomImageConfig getCustomImageConfig() { 482 return mCustomImageConfig; 483 } 484 485 /** 486 * Returns the name of the payload binary file, in the {@code lib/<ABI>} directory of the APK, 487 * that will be executed within the VM. 488 * 489 * @hide 490 */ 491 @SystemApi 492 @Nullable getPayloadBinaryName()493 public String getPayloadBinaryName() { 494 return mPayloadBinaryName; 495 } 496 497 /** 498 * Returns the debug level for the VM. 499 * 500 * @hide 501 */ 502 @SystemApi 503 @DebugLevel getDebugLevel()504 public int getDebugLevel() { 505 return mDebugLevel; 506 } 507 508 /** 509 * Returns whether the VM's memory will be protected from the host. 510 * 511 * @hide 512 */ 513 @SystemApi isProtectedVm()514 public boolean isProtectedVm() { 515 return mProtectedVm; 516 } 517 518 /** 519 * Returns the amount of RAM that will be made available to the VM, or 0 if the default size 520 * will be used. 521 * 522 * @hide 523 */ 524 @SystemApi 525 @IntRange(from = 0) getMemoryBytes()526 public long getMemoryBytes() { 527 return mMemoryBytes; 528 } 529 530 /** 531 * Returns the CPU topology configuration of the VM. 532 * 533 * @hide 534 */ 535 @SystemApi 536 @CpuTopology getCpuTopology()537 public int getCpuTopology() { 538 return mCpuTopology; 539 } 540 541 /** 542 * Returns whether encrypted storage is enabled or not. 543 * 544 * @hide 545 */ 546 @SystemApi isEncryptedStorageEnabled()547 public boolean isEncryptedStorageEnabled() { 548 return mEncryptedStorageBytes > 0; 549 } 550 551 /** 552 * Returns the size of encrypted storage (in bytes) available in the VM, or 0 if encrypted 553 * storage is not enabled 554 * 555 * @hide 556 */ 557 @SystemApi 558 @IntRange(from = 0) getEncryptedStorageBytes()559 public long getEncryptedStorageBytes() { 560 return mEncryptedStorageBytes; 561 } 562 563 /** 564 * Returns whether the app can read the VM console or log output. If not, the VM output is 565 * automatically forwarded to the host logcat. 566 * 567 * @see Builder#setVmOutputCaptured 568 * @hide 569 */ 570 @SystemApi isVmOutputCaptured()571 public boolean isVmOutputCaptured() { 572 return mVmOutputCaptured; 573 } 574 575 /** 576 * Returns whether the app can write to the VM console. 577 * 578 * @see Builder#setVmConsoleInputSupported 579 * @hide 580 */ 581 @TestApi isVmConsoleInputSupported()582 public boolean isVmConsoleInputSupported() { 583 return mVmConsoleInputSupported; 584 } 585 586 /** 587 * Returns whether to connect the VM console to a host console. 588 * 589 * @see Builder#setConnectVmConsole 590 * @hide 591 */ isConnectVmConsole()592 public boolean isConnectVmConsole() { 593 return mConnectVmConsole; 594 } 595 596 /** 597 * Returns the OS of the VM. 598 * 599 * @see Builder#setOs 600 * @hide 601 */ 602 @TestApi 603 @NonNull 604 @OsName getOs()605 public String getOs() { 606 return mOs; 607 } 608 609 /** 610 * Returns whether this VM enabled the hint to use transparent huge pages. 611 * 612 * @see Builder#setShouldUseHugepages 613 * @hide 614 */ 615 @SystemApi 616 @FlaggedApi(Flags.FLAG_PROMOTE_SET_SHOULD_USE_HUGEPAGES_TO_SYSTEM_API) shouldUseHugepages()617 public boolean shouldUseHugepages() { 618 return mShouldUseHugepages; 619 } 620 621 /** 622 * Tests if this config is compatible with other config. Being compatible means that the configs 623 * can be interchangeably used for the same virtual machine; they do not change the VM identity 624 * or secrets. Such changes include varying the number of CPUs or the size of the RAM. Changes 625 * that would alter the identity of the VM (e.g. using a different payload or changing the debug 626 * mode) are considered incompatible. 627 * 628 * @see VirtualMachine#setConfig 629 * @hide 630 */ 631 @SystemApi isCompatibleWith(@onNull VirtualMachineConfig other)632 public boolean isCompatibleWith(@NonNull VirtualMachineConfig other) { 633 if (this == other) { 634 return true; 635 } 636 return this.mDebugLevel == other.mDebugLevel 637 && this.mProtectedVm == other.mProtectedVm 638 && this.mVmOutputCaptured == other.mVmOutputCaptured 639 && this.mVmConsoleInputSupported == other.mVmConsoleInputSupported 640 && this.mConnectVmConsole == other.mConnectVmConsole 641 && (this.mVendorDiskImage == null) == (other.mVendorDiskImage == null) 642 && Objects.equals(this.mConsoleInputDevice, other.mConsoleInputDevice) 643 && Objects.equals(this.mPayloadConfigPath, other.mPayloadConfigPath) 644 && Objects.equals(this.mPayloadBinaryName, other.mPayloadBinaryName) 645 && Objects.equals(this.mPackageName, other.mPackageName) 646 && Objects.equals(this.mOs, other.mOs) 647 && Objects.equals(this.mExtraApks, other.mExtraApks); 648 } 649 openOrNull(File file, int mode)650 private ParcelFileDescriptor openOrNull(File file, int mode) { 651 try { 652 return ParcelFileDescriptor.open(file, mode); 653 } catch (FileNotFoundException e) { 654 Log.d(TAG, "cannot open", e); 655 return null; 656 } 657 } 658 startCrosvmVirtiofs( String sharedPath, int host_uid, int guest_uid, int guest_gid, String tagName, int mask, String socketPath)659 private void startCrosvmVirtiofs( 660 String sharedPath, 661 int host_uid, 662 int guest_uid, 663 int guest_gid, 664 String tagName, 665 int mask, 666 String socketPath) 667 throws IOException { 668 String ugidMapValue = 669 String.format("%d %d %d %d %d /", guest_uid, guest_gid, host_uid, host_uid, mask); 670 String cfgArg = String.format("ugid_map='%s'", ugidMapValue); 671 ProcessBuilder pb = 672 new ProcessBuilder( 673 "/apex/com.android.virt/bin/crosvm", 674 "device", 675 "fs", 676 "--socket=" + socketPath, 677 "--tag=" + tagName, 678 "--shared-dir=" + sharedPath, 679 "--cfg", 680 cfgArg, 681 "--disable-sandbox", 682 "--skip-pivot-root=true"); 683 684 pb.start(); 685 } 686 toVsRawConfig()687 VirtualMachineRawConfig toVsRawConfig() throws IllegalStateException, IOException { 688 VirtualMachineRawConfig config = new VirtualMachineRawConfig(); 689 VirtualMachineCustomImageConfig customImageConfig = getCustomImageConfig(); 690 requireNonNull(customImageConfig); 691 config.name = Optional.ofNullable(customImageConfig.getName()).orElse(""); 692 config.instanceId = new byte[64]; 693 config.kernel = 694 Optional.ofNullable(customImageConfig.getKernelPath()) 695 .map( 696 (path) -> { 697 try { 698 return ParcelFileDescriptor.open( 699 new File(path), MODE_READ_ONLY); 700 } catch (FileNotFoundException e) { 701 throw new RuntimeException(e); 702 } 703 }) 704 .orElse(null); 705 706 config.initrd = 707 Optional.ofNullable(customImageConfig.getInitrdPath()) 708 .map((path) -> openOrNull(new File(path), MODE_READ_ONLY)) 709 .orElse(null); 710 config.bootloader = 711 Optional.ofNullable(customImageConfig.getBootloaderPath()) 712 .map((path) -> openOrNull(new File(path), MODE_READ_ONLY)) 713 .orElse(null); 714 715 if (config.kernel == null && config.bootloader == null) { 716 if (Arrays.stream(Build.SUPPORTED_ABIS).anyMatch("x86_64"::equals)) { 717 config.bootloader = openOrNull(new File(U_BOOT_PREBUILT_PATH_X86), MODE_READ_ONLY); 718 } else { 719 config.bootloader = openOrNull(new File(U_BOOT_PREBUILT_PATH_ARM), MODE_READ_ONLY); 720 } 721 } 722 723 config.params = 724 Optional.ofNullable(customImageConfig.getParams()) 725 .map((params) -> TextUtils.join(" ", params)) 726 .orElse(""); 727 config.disks = 728 new DiskImage 729 [Optional.ofNullable(customImageConfig.getDisks()) 730 .map(arr -> arr.length) 731 .orElse(0)]; 732 for (int i = 0; i < config.disks.length; i++) { 733 config.disks[i] = new DiskImage(); 734 config.disks[i].writable = customImageConfig.getDisks()[i].isWritable(); 735 String diskImagePath = customImageConfig.getDisks()[i].getImagePath(); 736 if (diskImagePath != null) { 737 config.disks[i].image = 738 ParcelFileDescriptor.open( 739 new File(diskImagePath), 740 config.disks[i].writable ? MODE_READ_WRITE : MODE_READ_ONLY); 741 } 742 743 List<Partition> partitions = new ArrayList<>(); 744 for (VirtualMachineCustomImageConfig.Partition p : 745 customImageConfig.getDisks()[i].getPartitions()) { 746 Partition part = new Partition(); 747 part.label = p.name; 748 part.image = 749 ParcelFileDescriptor.open( 750 new File(p.imagePath), 751 p.writable ? MODE_READ_WRITE : MODE_READ_ONLY); 752 part.writable = p.writable; 753 part.guid = TextUtils.isEmpty(p.guid) ? null : p.guid; 754 partitions.add(part); 755 } 756 config.disks[i].partitions = partitions.toArray(new Partition[0]); 757 } 758 759 config.sharedPaths = 760 new SharedPath 761 [Optional.ofNullable(customImageConfig.getSharedPaths()) 762 .map(arr -> arr.length) 763 .orElse(0)]; 764 for (int i = 0; i < config.sharedPaths.length; i++) { 765 config.sharedPaths[i] = customImageConfig.getSharedPaths()[i].toParcelable(); 766 if (config.sharedPaths[i].appDomain) { 767 try { 768 String socketPath = customImageConfig.getSharedPaths()[i].getSocketPath(); 769 startCrosvmVirtiofs( 770 config.sharedPaths[i].sharedPath, 771 config.sharedPaths[i].hostUid, 772 config.sharedPaths[i].guestUid, 773 config.sharedPaths[i].guestGid, 774 config.sharedPaths[i].tag, 775 config.sharedPaths[i].mask, 776 socketPath); 777 long startTime = System.currentTimeMillis(); 778 long deadline = startTime + 5000; 779 // TODO: use socketpair instead of crosvm creating the named sockets. 780 while (!Files.exists(Path.of(socketPath)) 781 && System.currentTimeMillis() < deadline) { 782 Thread.sleep(200); 783 } 784 if (!Files.exists(Path.of(socketPath))) { 785 throw new IOException("Timeout waiting for socket: " + socketPath); 786 } 787 LocalSocket socket = new LocalSocket(); 788 socket.connect( 789 new LocalSocketAddress( 790 socketPath, LocalSocketAddress.Namespace.FILESYSTEM)); 791 config.sharedPaths[i].socketFd = 792 ParcelFileDescriptor.dup(socket.getFileDescriptor()); 793 } catch (IOException | InterruptedException e) { 794 Log.e(TAG, "startCrosvmVirtiofs failed", e); 795 throw new RuntimeException(e); 796 } 797 } 798 } 799 800 config.displayConfig = 801 Optional.ofNullable(customImageConfig.getDisplayConfig()) 802 .map(dc -> dc.toParcelable()) 803 .orElse(null); 804 config.gpuConfig = 805 Optional.ofNullable(customImageConfig.getGpuConfig()) 806 .map(dc -> dc.toParcelable()) 807 .orElse(null); 808 config.protectedVm = this.mProtectedVm; 809 config.memoryMib = bytesToMebiBytes(mMemoryBytes); 810 switch (this.mCpuTopology) { 811 case CPU_TOPOLOGY_MATCH_HOST: 812 config.cpuOptions = new CpuOptions(); 813 config.cpuOptions.cpuTopology = CpuOptions.CpuTopology.matchHost(true); 814 break; 815 default: 816 config.cpuOptions = new CpuOptions(); 817 config.cpuOptions.cpuTopology = CpuOptions.CpuTopology.cpuCount(1); 818 break; 819 } 820 config.consoleInputDevice = mConsoleInputDevice; 821 config.devices = AssignedDevices.devices(EMPTY_STRING_ARRAY); 822 config.platformVersion = "~1.0"; 823 config.audioConfig = 824 Optional.ofNullable(customImageConfig.getAudioConfig()) 825 .map(ac -> ac.toParcelable()) 826 .orElse(null); 827 config.balloon = customImageConfig.useAutoMemoryBalloon(); 828 config.usbConfig = 829 Optional.ofNullable(customImageConfig.getUsbConfig()) 830 .map( 831 uc -> { 832 UsbConfig usbConfig = new UsbConfig(); 833 usbConfig.controller = uc.getUsbController(); 834 return usbConfig; 835 }) 836 .orElse(null); 837 config.teeServices = EMPTY_STRING_ARRAY; 838 config.customMemoryBackingFiles = new CustomMemoryBackingFile[0]; 839 return config; 840 } 841 842 /** 843 * Converts this config object into the parcelable type used when creating a VM via the 844 * virtualization service. Notice that the files are not passed as paths, but as file 845 * descriptors because the service doesn't accept paths as it might not have permission to open 846 * app-owned files and that could be abused to run a VM with software that the calling 847 * application doesn't own. 848 */ toVsConfig(@onNull PackageManager packageManager)849 VirtualMachineAppConfig toVsConfig(@NonNull PackageManager packageManager) 850 throws VirtualMachineException { 851 VirtualMachineAppConfig vsConfig = new VirtualMachineAppConfig(); 852 853 String apkPath = (mApkPath != null) ? mApkPath : findPayloadApk(packageManager); 854 855 try { 856 vsConfig.apk = ParcelFileDescriptor.open(new File(apkPath), MODE_READ_ONLY); 857 } catch (FileNotFoundException e) { 858 throw new VirtualMachineException("Failed to open APK", e); 859 } 860 if (mPayloadBinaryName != null) { 861 VirtualMachinePayloadConfig payloadConfig = new VirtualMachinePayloadConfig(); 862 payloadConfig.payloadBinaryName = mPayloadBinaryName; 863 payloadConfig.extraApks = Collections.emptyList(); 864 vsConfig.payload = VirtualMachineAppConfig.Payload.payloadConfig(payloadConfig); 865 } else { 866 vsConfig.payload = VirtualMachineAppConfig.Payload.configPath(mPayloadConfigPath); 867 } 868 vsConfig.osName = mOs; 869 switch (mDebugLevel) { 870 case DEBUG_LEVEL_FULL: 871 vsConfig.debugLevel = VirtualMachineAppConfig.DebugLevel.FULL; 872 break; 873 default: 874 vsConfig.debugLevel = VirtualMachineAppConfig.DebugLevel.NONE; 875 break; 876 } 877 vsConfig.protectedVm = mProtectedVm; 878 vsConfig.memoryMib = bytesToMebiBytes(mMemoryBytes); 879 switch (mCpuTopology) { 880 case CPU_TOPOLOGY_MATCH_HOST: 881 vsConfig.cpuOptions = new CpuOptions(); 882 vsConfig.cpuOptions.cpuTopology = CpuOptions.CpuTopology.matchHost(true); 883 break; 884 default: 885 vsConfig.cpuOptions = new CpuOptions(); 886 vsConfig.cpuOptions.cpuTopology = CpuOptions.CpuTopology.cpuCount(1); 887 break; 888 } 889 890 if (mVendorDiskImage != null) { 891 VirtualMachineAppConfig.CustomConfig customConfig = 892 new VirtualMachineAppConfig.CustomConfig(); 893 customConfig.devices = EMPTY_STRING_ARRAY; 894 customConfig.extraKernelCmdlineParams = EMPTY_STRING_ARRAY; 895 customConfig.teeServices = EMPTY_STRING_ARRAY; 896 try { 897 customConfig.vendorImage = 898 ParcelFileDescriptor.open(mVendorDiskImage, MODE_READ_ONLY); 899 } catch (FileNotFoundException e) { 900 throw new VirtualMachineException( 901 "Failed to open vendor disk image " + mVendorDiskImage.getAbsolutePath(), 902 e); 903 } 904 vsConfig.customConfig = customConfig; 905 } 906 907 vsConfig.boostUclamp = mShouldBoostUclamp; 908 vsConfig.hugePages = mShouldUseHugepages; 909 910 return vsConfig; 911 } 912 findPayloadApk(PackageManager packageManager)913 private String findPayloadApk(PackageManager packageManager) throws VirtualMachineException { 914 ApplicationInfo appInfo; 915 try { 916 appInfo = 917 packageManager.getApplicationInfo( 918 mPackageName, PackageManager.ApplicationInfoFlags.of(0)); 919 } catch (PackageManager.NameNotFoundException e) { 920 throw new VirtualMachineException("Package not found", e); 921 } 922 923 String[] splitApkPaths = appInfo.splitSourceDirs; 924 String[] abis = Build.SUPPORTED_64_BIT_ABIS; 925 926 // If there are split APKs, and we know the payload binary name, see if we can find a 927 // split APK containing the binary. 928 if (mPayloadBinaryName != null && splitApkPaths != null && abis.length != 0) { 929 String[] libraryNames = new String[abis.length]; 930 for (int i = 0; i < abis.length; i++) { 931 libraryNames[i] = "lib/" + abis[i] + "/" + mPayloadBinaryName; 932 } 933 934 for (String path : splitApkPaths) { 935 try (ZipFile zip = new ZipFile(path)) { 936 for (String name : libraryNames) { 937 if (zip.getEntry(name) != null) { 938 Log.i(TAG, "Found payload in " + path); 939 return path; 940 } 941 } 942 } catch (IOException e) { 943 Log.w(TAG, "Failed to scan split APK: " + path, e); 944 } 945 } 946 } 947 948 // This really is the path to the APK, not a directory. 949 return appInfo.sourceDir; 950 } 951 bytesToMebiBytes(long mMemoryBytes)952 private int bytesToMebiBytes(long mMemoryBytes) { 953 long oneMebi = 1024 * 1024; 954 // We can't express requests for more than 2 exabytes, but then they're not going to succeed 955 // anyway. 956 if (mMemoryBytes > (Integer.MAX_VALUE - 1) * oneMebi) { 957 return Integer.MAX_VALUE; 958 } 959 return (int) ((mMemoryBytes + oneMebi - 1) / oneMebi); 960 } 961 962 /** 963 * A builder used to create a {@link VirtualMachineConfig}. 964 * 965 * @hide 966 */ 967 @SystemApi 968 public static final class Builder { 969 @OsName private final String DEFAULT_OS = MICRODROID; 970 971 @Nullable private final String mPackageName; 972 @Nullable private String mApkPath; 973 private final List<String> mExtraApks = new ArrayList<>(); 974 @Nullable private String mPayloadConfigPath; 975 @Nullable private VirtualMachineCustomImageConfig mCustomImageConfig; 976 @Nullable private String mPayloadBinaryName; 977 @DebugLevel private int mDebugLevel = DEBUG_LEVEL_NONE; 978 private boolean mProtectedVm; 979 private boolean mProtectedVmSet; 980 private long mMemoryBytes; 981 @CpuTopology private int mCpuTopology = CPU_TOPOLOGY_ONE_CPU; 982 @Nullable private String mConsoleInputDevice; 983 private long mEncryptedStorageBytes; 984 private boolean mVmOutputCaptured = false; 985 private boolean mVmConsoleInputSupported = false; 986 private boolean mConnectVmConsole = false; 987 @Nullable private File mVendorDiskImage; 988 @NonNull @OsName private String mOs = DEFAULT_OS; 989 private boolean mShouldBoostUclamp = false; 990 private boolean mShouldUseHugepages = false; 991 992 /** 993 * Creates a builder for the given context. 994 * 995 * @hide 996 */ 997 @SystemApi Builder(@onNull Context context)998 public Builder(@NonNull Context context) { 999 mPackageName = requireNonNull(context, "context must not be null").getPackageName(); 1000 } 1001 1002 /** 1003 * Creates a builder for a specific package. If packageName is null, {@link #setApkPath} 1004 * must be called to specify the APK containing the payload. 1005 */ Builder(@ullable String packageName)1006 private Builder(@Nullable String packageName) { 1007 mPackageName = packageName; 1008 } 1009 1010 /** 1011 * Builds an immutable {@link VirtualMachineConfig} 1012 * 1013 * @hide 1014 */ 1015 @SystemApi 1016 @NonNull build()1017 public VirtualMachineConfig build() { 1018 String apkPath = null; 1019 String packageName = null; 1020 1021 if (mApkPath != null) { 1022 apkPath = mApkPath; 1023 } else if (mPackageName != null) { 1024 packageName = mPackageName; 1025 } else { 1026 // This should never happen, unless we're deserializing a bad config 1027 throw new IllegalStateException("apkPath or packageName must be specified"); 1028 } 1029 if (mCustomImageConfig != null) { 1030 if (mPayloadBinaryName != null || mPayloadConfigPath != null) { 1031 throw new IllegalStateException( 1032 "setCustomImageConfig and (setPayloadBinaryName or" 1033 + " setPayloadConfigPath) may not both be called"); 1034 } 1035 } else if (mPayloadBinaryName == null) { 1036 if (mPayloadConfigPath == null) { 1037 throw new IllegalStateException("setPayloadBinaryName must be called"); 1038 } 1039 if (!mExtraApks.isEmpty()) { 1040 throw new IllegalStateException( 1041 "setPayloadConfigPath and addExtraApk may not both be called"); 1042 } 1043 } else { 1044 if (mPayloadConfigPath != null) { 1045 throw new IllegalStateException( 1046 "setPayloadBinaryName and setPayloadConfigPath may not both be called"); 1047 } 1048 } 1049 1050 if (!mProtectedVmSet) { 1051 throw new IllegalStateException("setProtectedVm must be called explicitly"); 1052 } 1053 1054 if (mVmOutputCaptured && mDebugLevel != DEBUG_LEVEL_FULL) { 1055 throw new IllegalStateException("debug level must be FULL to capture output"); 1056 } 1057 1058 if (mVmConsoleInputSupported && mDebugLevel != DEBUG_LEVEL_FULL) { 1059 throw new IllegalStateException("debug level must be FULL to use console input"); 1060 } 1061 1062 if (mConnectVmConsole && mDebugLevel != DEBUG_LEVEL_FULL) { 1063 throw new IllegalStateException( 1064 "debug level must be FULL to connect to the console"); 1065 } 1066 1067 return new VirtualMachineConfig( 1068 packageName, 1069 apkPath, 1070 mExtraApks, 1071 mPayloadConfigPath, 1072 mPayloadBinaryName, 1073 mCustomImageConfig, 1074 mDebugLevel, 1075 mProtectedVm, 1076 mMemoryBytes, 1077 mCpuTopology, 1078 mConsoleInputDevice, 1079 mEncryptedStorageBytes, 1080 mVmOutputCaptured, 1081 mVmConsoleInputSupported, 1082 mConnectVmConsole, 1083 mVendorDiskImage, 1084 mOs, 1085 mShouldBoostUclamp, 1086 mShouldUseHugepages); 1087 } 1088 1089 /** 1090 * Sets the absolute path of the APK containing the binary payload that will execute within 1091 * the VM. If not set explicitly, defaults to the split APK containing the payload, if there 1092 * is one, and otherwise the primary APK of the context. 1093 * 1094 * @hide 1095 */ 1096 @SystemApi 1097 @NonNull setApkPath(@onNull String apkPath)1098 public Builder setApkPath(@NonNull String apkPath) { 1099 requireNonNull(apkPath, "apkPath must not be null"); 1100 if (!apkPath.startsWith("/")) { 1101 throw new IllegalArgumentException("APK path must be an absolute path"); 1102 } 1103 mApkPath = apkPath; 1104 return this; 1105 } 1106 1107 /** 1108 * Specify the package name of an extra APK to be included in the VM. Each extra APK is 1109 * mounted, in unzipped form, inside the VM, allowing access to the code and/or data within 1110 * it. The VM entry point must be in the main APK. 1111 * 1112 * @hide 1113 */ 1114 @TestApi 1115 @NonNull addExtraApk(@onNull String packageName)1116 public Builder addExtraApk(@NonNull String packageName) { 1117 mExtraApks.add(requireNonNull(packageName, "extra APK package name must not be null")); 1118 return this; 1119 } 1120 1121 /** 1122 * Sets the path within the APK to the payload config file that defines software aspects of 1123 * the VM. The file is a JSON file; see 1124 * packages/modules/Virtualization/libs/libmicrodroid_payload_metadata/config/src/lib.rs for 1125 * the format. 1126 * 1127 * @hide 1128 */ 1129 @RequiresPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION) 1130 @TestApi 1131 @NonNull setPayloadConfigPath(@onNull String payloadConfigPath)1132 public Builder setPayloadConfigPath(@NonNull String payloadConfigPath) { 1133 mPayloadConfigPath = 1134 requireNonNull(payloadConfigPath, "payloadConfigPath must not be null"); 1135 return this; 1136 } 1137 1138 /** 1139 * Sets the custom config file to launch the custom VM. 1140 * 1141 * @hide 1142 */ 1143 @RequiresPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION) 1144 @NonNull setCustomImageConfig( @onNull VirtualMachineCustomImageConfig customImageConfig)1145 public Builder setCustomImageConfig( 1146 @NonNull VirtualMachineCustomImageConfig customImageConfig) { 1147 this.mCustomImageConfig = customImageConfig; 1148 return this; 1149 } 1150 1151 /** 1152 * Sets the name of the payload binary file that will be executed within the VM, e.g. 1153 * "payload.so". The file must reside in the {@code lib/<ABI>} directory of the APK. 1154 * 1155 * <p>Note that VMs only support 64-bit code, even if the owning app is running as a 32-bit 1156 * process. 1157 * 1158 * @hide 1159 */ 1160 @SystemApi 1161 @NonNull setPayloadBinaryName(@onNull String payloadBinaryName)1162 public Builder setPayloadBinaryName(@NonNull String payloadBinaryName) { 1163 requireNonNull(payloadBinaryName, "payloadBinaryName must not be null"); 1164 if (payloadBinaryName.contains(File.separator)) { 1165 throw new IllegalArgumentException( 1166 "Invalid binary file name: " + payloadBinaryName); 1167 } 1168 mPayloadBinaryName = payloadBinaryName; 1169 return this; 1170 } 1171 1172 /** 1173 * Sets the debug level. Defaults to {@link #DEBUG_LEVEL_NONE}. 1174 * 1175 * <p>If {@link #DEBUG_LEVEL_FULL} is set then logs from inside the VM are exported to the 1176 * host and adb connections from the host are possible. This is convenient for debugging but 1177 * may compromise the integrity of the VM - including bypassing the protections offered by a 1178 * {@linkplain #setProtectedVm protected VM}. 1179 * 1180 * <p>Note that it isn't possible to {@linkplain #isCompatibleWith change} the debug level 1181 * of a VM instance; debug and non-debug VMs always have different secrets. 1182 * 1183 * @hide 1184 */ 1185 @SystemApi 1186 @NonNull setDebugLevel(@ebugLevel int debugLevel)1187 public Builder setDebugLevel(@DebugLevel int debugLevel) { 1188 if (debugLevel != DEBUG_LEVEL_NONE && debugLevel != DEBUG_LEVEL_FULL) { 1189 throw new IllegalArgumentException("Invalid debugLevel: " + debugLevel); 1190 } 1191 mDebugLevel = debugLevel; 1192 return this; 1193 } 1194 1195 /** 1196 * Sets whether to protect the VM memory from the host. No default is provided, this must be 1197 * set explicitly. 1198 * 1199 * <p>Note that if debugging is {@linkplain #setDebugLevel enabled} for a protected VM, the 1200 * VM is not truly protected - direct memory access by the host is prevented, but e.g. the 1201 * debugger can be used to access the VM's internals. 1202 * 1203 * <p>It isn't possible to {@linkplain #isCompatibleWith change} the protected status of a 1204 * VM instance; protected and non-protected VMs always have different secrets. 1205 * 1206 * @see VirtualMachineManager#getCapabilities 1207 * @hide 1208 */ 1209 @SystemApi 1210 @NonNull setProtectedVm(boolean protectedVm)1211 public Builder setProtectedVm(boolean protectedVm) { 1212 if (protectedVm) { 1213 if (!HypervisorProperties.hypervisor_protected_vm_supported().orElse(false)) { 1214 throw new UnsupportedOperationException( 1215 "Protected VMs are not supported on this device."); 1216 } 1217 } else { 1218 if (!HypervisorProperties.hypervisor_vm_supported().orElse(false)) { 1219 throw new UnsupportedOperationException( 1220 "Non-protected VMs are not supported on this device."); 1221 } 1222 } 1223 mProtectedVm = protectedVm; 1224 mProtectedVmSet = true; 1225 return this; 1226 } 1227 1228 /** 1229 * Sets the amount of RAM to give the VM, in bytes. If not explicitly set then a default 1230 * size will be used. 1231 * 1232 * @hide 1233 */ 1234 @SystemApi 1235 @NonNull setMemoryBytes(@ntRangefrom = 1) long memoryBytes)1236 public Builder setMemoryBytes(@IntRange(from = 1) long memoryBytes) { 1237 if (memoryBytes <= 0) { 1238 throw new IllegalArgumentException("Memory size must be positive"); 1239 } 1240 mMemoryBytes = memoryBytes; 1241 return this; 1242 } 1243 1244 /** 1245 * Sets the CPU topology configuration of the VM. Defaults to {@link #CPU_TOPOLOGY_ONE_CPU}. 1246 * 1247 * <p>This determines how many virtual CPUs will be created, and their performance and 1248 * scheduling characteristics, such as affinity masks. Topology also has an effect on memory 1249 * usage as each vCPU requires additional memory to keep its state. 1250 * 1251 * @hide 1252 */ 1253 @SystemApi 1254 @NonNull setCpuTopology(@puTopology int cpuTopology)1255 public Builder setCpuTopology(@CpuTopology int cpuTopology) { 1256 if (cpuTopology != CPU_TOPOLOGY_ONE_CPU && cpuTopology != CPU_TOPOLOGY_MATCH_HOST) { 1257 throw new IllegalArgumentException("Invalid cpuTopology: " + cpuTopology); 1258 } 1259 mCpuTopology = cpuTopology; 1260 return this; 1261 } 1262 1263 /** 1264 * Sets the serial device for VM console input. 1265 * 1266 * @see android.system.virtualizationservice.ConsoleInputDevice 1267 * @hide 1268 */ setConsoleInputDevice(@ullable String consoleInputDevice)1269 public Builder setConsoleInputDevice(@Nullable String consoleInputDevice) { 1270 mConsoleInputDevice = consoleInputDevice; 1271 return this; 1272 } 1273 1274 /** 1275 * Sets the size (in bytes) of encrypted storage available to the VM. If not set, no 1276 * encrypted storage is provided. 1277 * 1278 * <p>The storage is encrypted with a key deterministically derived from the VM identity 1279 * 1280 * <p>The encrypted storage is persistent across VM reboots as well as device reboots. The 1281 * backing file (containing encrypted data) is stored in the app's private data directory. 1282 * 1283 * <p>Note - There is no integrity guarantee or rollback protection on the storage in case 1284 * the encrypted data is modified. 1285 * 1286 * <p>Deleting the VM will delete the encrypted data - there is no way to recover that data. 1287 * 1288 * @hide 1289 */ 1290 @SystemApi 1291 @NonNull setEncryptedStorageBytes(@ntRangefrom = 1) long encryptedStorageBytes)1292 public Builder setEncryptedStorageBytes(@IntRange(from = 1) long encryptedStorageBytes) { 1293 if (encryptedStorageBytes <= 0) { 1294 throw new IllegalArgumentException("Encrypted Storage size must be positive"); 1295 } 1296 mEncryptedStorageBytes = encryptedStorageBytes; 1297 return this; 1298 } 1299 1300 /** 1301 * Sets whether to allow the app to read the VM outputs (console / log). Default is {@code 1302 * false}. 1303 * 1304 * <p>By default, console and log outputs of a {@linkplain #setDebugLevel debuggable} VM are 1305 * automatically forwarded to the host logcat. Setting this as {@code true} will allow the 1306 * app to directly read {@linkplain VirtualMachine#getConsoleOutput console output} and 1307 * {@linkplain VirtualMachine#getLogOutput log output}, instead of forwarding them to the 1308 * host logcat. 1309 * 1310 * <p>If you turn on output capture, you must consume data from {@link 1311 * VirtualMachine#getConsoleOutput} and {@link VirtualMachine#getLogOutput} - because 1312 * otherwise the code in the VM may get blocked when the pipe buffer fills up. 1313 * 1314 * <p>The {@linkplain #setDebugLevel debug level} must be {@link #DEBUG_LEVEL_FULL} to be 1315 * set as true. 1316 * 1317 * @hide 1318 */ 1319 @SystemApi 1320 @NonNull setVmOutputCaptured(boolean captured)1321 public Builder setVmOutputCaptured(boolean captured) { 1322 mVmOutputCaptured = captured; 1323 return this; 1324 } 1325 1326 /** 1327 * Sets whether to allow the app to write to the VM console. Default is {@code false}. 1328 * 1329 * <p>Setting this as {@code true} will allow the app to directly write into {@linkplain 1330 * VirtualMachine#getConsoleInput console input}. 1331 * 1332 * <p>The {@linkplain #setDebugLevel debug level} must be {@link #DEBUG_LEVEL_FULL} to be 1333 * set as true. 1334 * 1335 * @hide 1336 */ 1337 @TestApi 1338 @NonNull setVmConsoleInputSupported(boolean supported)1339 public Builder setVmConsoleInputSupported(boolean supported) { 1340 mVmConsoleInputSupported = supported; 1341 return this; 1342 } 1343 1344 /** 1345 * Sets whether to connect the VM console to a host console. Default is {@code false}. 1346 * 1347 * <p>Setting this as {@code true} will allow the shell to directly communicate with the VM 1348 * console through the connected host console. 1349 * 1350 * <p>The {@linkplain #setDebugLevel debug level} must be {@link #DEBUG_LEVEL_FULL} to be 1351 * set as true. 1352 * 1353 * @hide 1354 */ 1355 @NonNull setConnectVmConsole(boolean supported)1356 public Builder setConnectVmConsole(boolean supported) { 1357 mConnectVmConsole = supported; 1358 return this; 1359 } 1360 1361 /** 1362 * Sets the path to the disk image with vendor-specific modules. 1363 * 1364 * @hide 1365 */ 1366 @TestApi 1367 @RequiresPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION) 1368 @NonNull setVendorDiskImage(@onNull File vendorDiskImage)1369 public Builder setVendorDiskImage(@NonNull File vendorDiskImage) { 1370 mVendorDiskImage = 1371 requireNonNull(vendorDiskImage, "vendor disk image must not be null"); 1372 return this; 1373 } 1374 1375 /** 1376 * Sets an OS for the VM. Defaults to {@code "microdroid"}. 1377 * 1378 * <p>See {@link VirtualMachineManager#getSupportedOSList} for available OS names. 1379 * 1380 * @hide 1381 */ 1382 @TestApi 1383 @RequiresPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION) 1384 @NonNull setOs(@onNull @sName String os)1385 public Builder setOs(@NonNull @OsName String os) { 1386 mOs = requireNonNull(os, "os must not be null"); 1387 return this; 1388 } 1389 1390 /** @hide */ setShouldBoostUclamp(boolean shouldBoostUclamp)1391 public Builder setShouldBoostUclamp(boolean shouldBoostUclamp) { 1392 mShouldBoostUclamp = shouldBoostUclamp; 1393 return this; 1394 } 1395 1396 /** 1397 * Hints whether the VM should make use of the transparent huge pages feature. 1398 * 1399 * <p>Note: this API just provides a hint, whether the VM will actually use transparent huge 1400 * pages additionally depends on the following: 1401 * 1402 * <ul> 1403 * <li>{@code /sys/kernel/mm/transparent_hugepages/shmem_enabled} should be configured 1404 * with the value {@code 'advise'}. 1405 * <li>Android host kernel version should be at least {@code android15-5.15} 1406 * </ul> 1407 * 1408 * @see https://docs.kernel.org/admin-guide/mm/transhuge.html 1409 * @see 1410 * https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Virtualization/docs/hugepages.md 1411 * @hide 1412 */ 1413 @SystemApi 1414 @FlaggedApi(Flags.FLAG_PROMOTE_SET_SHOULD_USE_HUGEPAGES_TO_SYSTEM_API) 1415 @NonNull setShouldUseHugepages(boolean shouldUseHugepages)1416 public Builder setShouldUseHugepages(boolean shouldUseHugepages) { 1417 mShouldUseHugepages = shouldUseHugepages; 1418 return this; 1419 } 1420 } 1421 } 1422