Merge "vmbase_example: Condition compilation for aarch64" into main
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/InstalledImage.kt b/android/TerminalApp/java/com/android/virtualization/terminal/InstalledImage.kt
index a4663c8..086ff3d 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/InstalledImage.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/InstalledImage.kt
@@ -25,9 +25,6 @@
 import java.io.FileReader
 import java.io.IOException
 import java.io.RandomAccessFile
-import java.lang.IllegalArgumentException
-import java.lang.NumberFormatException
-import java.lang.RuntimeException
 import java.nio.file.Files
 import java.nio.file.Path
 import java.nio.file.StandardCopyOption
@@ -91,6 +88,13 @@
     }
 
     @Throws(IOException::class)
+    fun getPhysicalSize(): Long {
+        val stat = RandomAccessFile(rootPartition.toFile(), "rw").use { raf -> Os.fstat(raf.fd) }
+        // The unit of st_blocks is 512 byte in Android.
+        return 512L * stat.st_blocks
+    }
+
+    @Throws(IOException::class)
     fun getSmallestSizePossible(): Long {
         runE2fsck(rootPartition)
         val p: String = rootPartition.toAbsolutePath().toString()
@@ -129,6 +133,17 @@
         return getSize()
     }
 
+    @Throws(IOException::class)
+    fun truncate(size: Long) {
+        try {
+            RandomAccessFile(rootPartition.toFile(), "rw").use { raf -> Os.ftruncate(raf.fd, size) }
+            Log.d(TAG, "Truncated space to: $size bytes")
+        } catch (e: ErrnoException) {
+            Log.e(TAG, "Failed to allocate space", e)
+            throw IOException("Failed to allocate space", e)
+        }
+    }
+
     companion object {
         private const val INSTALL_DIRNAME = "linux"
         private const val ROOTFS_FILENAME = "root_part"
@@ -148,10 +163,9 @@
         @Throws(IOException::class)
         private fun allocateSpace(path: Path, sizeInBytes: Long) {
             try {
-                val raf = RandomAccessFile(path.toFile(), "rw")
-                val fd = raf.fd
-                Os.posix_fallocate(fd, 0, sizeInBytes)
-                raf.close()
+                RandomAccessFile(path.toFile(), "rw").use { raf ->
+                    Os.posix_fallocate(raf.fd, 0, sizeInBytes)
+                }
                 Log.d(TAG, "Allocated space to: $sizeInBytes bytes")
             } catch (e: ErrnoException) {
                 Log.e(TAG, "Failed to allocate space", e)
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.kt
index e4eaecb..d85242b 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.kt
@@ -49,7 +49,7 @@
 import androidx.viewpager2.widget.ViewPager2
 import com.android.internal.annotations.VisibleForTesting
 import com.android.microdroid.test.common.DeviceProperties
-import com.android.system.virtualmachine.flags.Flags.terminalGuiSupport
+import com.android.system.virtualmachine.flags.Flags
 import com.android.virtualization.terminal.VmLauncherService.VmLauncherServiceCallback
 import com.google.android.material.tabs.TabLayout
 import com.google.android.material.tabs.TabLayoutMediator
@@ -125,9 +125,9 @@
         }
 
         displayMenu?.also {
-            it.visibility = if (terminalGuiSupport()) View.VISIBLE else View.GONE
+            it.visibility = if (Flags.terminalGuiSupport()) View.VISIBLE else View.GONE
             it.setEnabled(false)
-            if (terminalGuiSupport()) {
+            if (Flags.terminalGuiSupport()) {
                 it.setOnClickListener {
                     val intent = Intent(this, DisplayActivity::class.java)
                     intent.flags =
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt
index 067d540..f426ce6 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt
@@ -31,6 +31,7 @@
 import android.os.Parcel
 import android.os.Parcelable
 import android.os.ResultReceiver
+import android.os.StatFs
 import android.os.SystemProperties
 import android.system.virtualmachine.VirtualMachine
 import android.system.virtualmachine.VirtualMachineCustomImageConfig
@@ -139,6 +140,30 @@
         return START_NOT_STICKY
     }
 
+    private fun truncateDiskIfNecessary(image: InstalledImage) {
+        val curSize = image.getSize()
+        val physicalSize = image.getPhysicalSize()
+
+        // Change the rootfs disk's apparent size to GUEST_SPARSE_DISK_SIZE_PERCENTAGE of the total
+        // disk size.
+        // Note that the physical size is not changed.
+        val statFs = StatFs(filesDir.absolutePath)
+        val hostSize = statFs.totalBytes
+        val expectedSize = hostSize * GUEST_SPARSE_DISK_SIZE_PERCENTAGE / 100
+        Log.d(
+            TAG,
+            "rootfs apparent size=$curSize, physical size=$physicalSize, expectedSize=$expectedSize",
+        )
+
+        if (curSize != expectedSize) {
+            try {
+                image.truncate(expectedSize)
+            } catch (e: IOException) {
+                throw RuntimeException("Failed to truncate a disk", e)
+            }
+        }
+    }
+
     @WorkerThread
     private fun doStart(
         notification: Notification,
@@ -150,7 +175,15 @@
         val json = ConfigJson.from(this, image.configPath)
         val configBuilder = json.toConfigBuilder(this)
         val customImageConfigBuilder = json.toCustomImageConfigBuilder(this)
-        image.resize(diskSize)
+
+        if (Flags.terminalStorageBalloon()) {
+            // When storage ballooning flag is enabled, convert rootfs disk into a sparse file.
+            truncateDiskIfNecessary(image)
+        } else {
+            // Note: this doesn't always do the resizing. If the current image size is the same as
+            // the requested size which is rounded up to the page alignment, resizing is not done.
+            image.resize(diskSize)
+        }
 
         customImageConfigBuilder.setAudioConfig(
             AudioConfig.Builder().setUseSpeaker(true).setUseMicrophone(true).build()
@@ -445,6 +478,8 @@
         private const val KEY_TERMINAL_IPADDRESS = "address"
         private const val KEY_TERMINAL_PORT = "port"
 
+        private const val GUEST_SPARSE_DISK_SIZE_PERCENTAGE = 95
+
         private val VM_BOOT_TIMEOUT_SECONDS: Int =
             {
                 val deviceName = SystemProperties.get("ro.product.vendor.device", "")
diff --git a/android/virtmgr/src/aidl.rs b/android/virtmgr/src/aidl.rs
index 1e756eb..d7f68b8 100644
--- a/android/virtmgr/src/aidl.rs
+++ b/android/virtmgr/src/aidl.rs
@@ -798,9 +798,8 @@
                 .or_service_specific_exception(-1)?;
         }
 
-        // Check if files for payloads and bases are NOT coming from /vendor and /odm, as they may
-        // have unstable interfaces.
-        // TODO(b/316431494): remove once Treble interfaces are stabilized.
+        // Check if files for payloads and bases are on the same side of the Treble boundary as the
+        // calling process, as they may have unstable interfaces.
         check_partitions_for_files(config, calling_partition).or_service_specific_exception(-1)?;
 
         let zero_filler_path = temporary_directory.join("zero.img");
diff --git a/guest/pvmfw/README.md b/guest/pvmfw/README.md
index 652ca90..c7f3dd6 100644
--- a/guest/pvmfw/README.md
+++ b/guest/pvmfw/README.md
@@ -147,6 +147,10 @@
 |  offset = (FOURTH - HEAD)     |
 |  size = (FOURTH_END - FOURTH) |
 +-------------------------------+
+|           [Entry 4]           | <-- Entry 4 is present since version 1.3
+|  offset = (FIFTH - HEAD)      |
+|  size = (FIFTH_END - FIFTH)   |
++-------------------------------+
 |              ...              |
 +-------------------------------+
 |           [Entry n]           |
@@ -168,7 +172,11 @@
 | {Fourth blob: VM reference DT}|
 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ <-- FOURTH_END
 | (Padding to 8-byte alignment) |
-+===============================+
++===============================+ <-- FIFTH
+| {Fifth blob: Reserved Memory} |
++~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ <-- FIFTH_END
+| (Padding to 8-byte alignment) |
++===============================+ <-- FIFTH
 |              ...              |
 +===============================+ <-- TAIL
 ```
@@ -238,6 +246,31 @@
 [secretkeeper_key]: https://android.googlesource.com/platform/system/secretkeeper/+/refs/heads/main/README.md#secretkeeper-public-key
 [vendor_hashtree_digest]: ../../build/microdroid/README.md#verification-of-vendor-image
 
+#### Version 1.3 {#pvmfw-data-v1-3}
+
+In version 1.3, a fifth blob is added.
+
+- entry 4, if present, contains potentially confidential data to be passed to
+  specific guests identified from their VM name. If the data is confidential,
+  this feature should only be used with guests using a fixed rollback
+  protection mechanism to prevent rollback attacks from a malicious host. Data
+  is passed as a reserved-memory region through the device tree with the
+  provided properties at an address which is implementation defined. Multiple
+  regions may be passed to the same guest. The format is as follows.
+
+  ```rust
+  #[repr(C)]
+  struct ReservedMemConfigEntry<const N: usize> {
+    /// The number of headers contained in this blob.
+    count: u32,
+    /// The [reserved memory headers](src/reserved_mem.rs) describing the passed data.
+    headers: [RMemHeader; N]
+    /// The actual data being passed. The reserved memory headers point to
+    /// offsets within this array.
+    data: [u8],
+  }
+  ```
+
 #### Virtual Platform DICE Chain Handover
 
 The format of the DICE chain entry mentioned above, compatible with the
diff --git a/libs/dice/open_dice/src/error.rs b/libs/dice/open_dice/src/error.rs
index c9eb5cc..87d463e 100644
--- a/libs/dice/open_dice/src/error.rs
+++ b/libs/dice/open_dice/src/error.rs
@@ -33,6 +33,8 @@
     UnsupportedKeyAlgorithm(coset::iana::Algorithm),
     /// A failed fallible allocation. Used in no_std environments.
     MemoryAllocationError,
+    /// DICE chain not found in artifacts.
+    DiceChainNotFound,
 }
 
 /// This makes `DiceError` accepted by anyhow.
@@ -51,6 +53,7 @@
                 write!(f, "Unsupported key algorithm: {algorithm:?}")
             }
             Self::MemoryAllocationError => write!(f, "Memory allocation failed"),
+            Self::DiceChainNotFound => write!(f, "DICE chain not found in artifacts"),
         }
     }
 }
diff --git a/libs/dice/open_dice/src/retry.rs b/libs/dice/open_dice/src/retry.rs
index d793218..2b7b740 100644
--- a/libs/dice/open_dice/src/retry.rs
+++ b/libs/dice/open_dice/src/retry.rs
@@ -17,7 +17,7 @@
 //! of this buffer may fail and callers will see Error::MemoryAllocationError.
 //! When running with std, allocation may fail.
 
-use crate::bcc::{bcc_format_config_descriptor, bcc_main_flow, DiceConfigValues};
+use crate::bcc::{bcc_format_config_descriptor, bcc_main_flow, BccHandover, DiceConfigValues};
 use crate::dice::{
     dice_main_flow, Cdi, CdiValues, DiceArtifacts, InputValues, CDI_SIZE, PRIVATE_KEY_SEED_SIZE,
     PRIVATE_KEY_SIZE,
@@ -60,6 +60,20 @@
     }
 }
 
+impl TryFrom<BccHandover<'_>> for OwnedDiceArtifacts {
+    type Error = DiceError;
+
+    fn try_from(artifacts: BccHandover<'_>) -> Result<Self> {
+        let cdi_attest = artifacts.cdi_attest().to_vec().try_into().unwrap();
+        let cdi_seal = artifacts.cdi_seal().to_vec().try_into().unwrap();
+        let bcc = artifacts
+            .bcc()
+            .map(|bcc_slice| bcc_slice.to_vec())
+            .ok_or(DiceError::DiceChainNotFound)?;
+        Ok(OwnedDiceArtifacts { cdi_values: CdiValues { cdi_attest, cdi_seal }, bcc })
+    }
+}
+
 /// Retries the given function with bigger measured buffer size.
 fn retry_with_measured_buffer<F>(mut f: F) -> Result<Vec<u8>>
 where
diff --git a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java
index b8b4ace..eba6fcc 100644
--- a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java
+++ b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java
@@ -1981,6 +1981,12 @@
     @SystemApi
     @SuppressLint("UnflaggedApi") // already existing functionality exposed, users should flag
     public interface VsockConnectionProvider {
+        /**
+         * Returns a connection, either from {@link #connectVsock} or from
+         * the VM owner which would call {@link #connectVsock} on your behalf.
+         *
+         * <p>Each call should return a new connection.
+         */
         @NonNull
         @SuppressLint("UnflaggedApi") // already existing functionality exposed, users should flag
         public ParcelFileDescriptor addConnection() throws VirtualMachineException;
@@ -2042,8 +2048,21 @@
     /**
      * Convert existing vsock connection to a binder connection.
      *
+     * <p>See {@linkplain #connectToVsockServer} for details. This method allows
+     * you to create the connects independently from upgrading them to the
+     * binder connection. Specifically:
+     *
      * <p>connectToVsockServer = connectToVsock + binderFromPreconnectedClient
      *
+     * <p>This method is useful if you want to pass the vsock connection to
+     * another process before establishing the RPC binder connection, so that
+     * you can create a direct connection.
+     *
+     * @args
+     *     provider: a provider that provides the vsock connection. This
+     *              provider should return connections from
+     *              {@link #connectVsock}, from the VM owner.
+     *
      * @hide
      */
     @SystemApi