Merge changes I2614e5df,I25f8db10,I97947c38,Ic4eebf81,I1404cab0, ...
* changes:
pvmfw: Add support for appended configuration data
pvmfw: Add MemoryTracker & MemorySlices
pvmfw: Support managing page tables dynamically
libfdt: Rename Fdt::bytes to Fdt::buffer
libfdt: Map FDT_ERR_NOTFOUND to Option::None
libfdt: Extend Rust wrapper with write functions
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 8376c1f..dd81738 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -30,6 +30,11 @@
"name": "AVFHostTestCases"
}
],
+ "postsubmit": [
+ {
+ "name": "CtsMicrodroidDisabledTestCases"
+ }
+ ],
"imports": [
{
"path": "packages/modules/Virtualization/apkdmverity"
diff --git a/compos/compos_key_helper/Android.bp b/compos/compos_key_helper/Android.bp
index cffa1e3..f8dc783 100644
--- a/compos/compos_key_helper/Android.bp
+++ b/compos/compos_key_helper/Android.bp
@@ -29,7 +29,7 @@
"libcompos_key",
],
shared_libs: [
- "libvm_payload",
+ "libvm_payload#current",
"libbinder_ndk",
],
}
diff --git a/compos/src/compsvc_main.rs b/compos/src/compsvc_main.rs
index 206dd4b..77e2daa 100644
--- a/compos/src/compsvc_main.rs
+++ b/compos/src/compsvc_main.rs
@@ -23,11 +23,13 @@
mod fsverity;
use anyhow::Result;
+use binder::unstable_api::AsNative;
use compos_common::COMPOS_VSOCK_PORT;
use log::{debug, error};
-use rpcbinder::RpcServer;
+use std::os::raw::c_void;
use std::panic;
-use vm_payload_bindgen::AVmPayload_notifyPayloadReady;
+use std::ptr;
+use vm_payload_bindgen::{AIBinder, AVmPayload_notifyPayloadReady, AVmPayload_runVsockRpcServer};
fn main() {
if let Err(e) = try_main() {
@@ -46,10 +48,20 @@
}));
debug!("compsvc is starting as a rpc service.");
- let service = compsvc::new_binder()?.as_binder();
- let server = RpcServer::new_vsock(service, COMPOS_VSOCK_PORT)?;
- // SAFETY: Invokes a method from the bindgen library `vm_payload_bindgen`.
- unsafe { AVmPayload_notifyPayloadReady() };
- server.join();
+ let param = ptr::null_mut();
+ let mut service = compsvc::new_binder()?.as_binder();
+ unsafe {
+ // SAFETY: We hold a strong pointer, so the raw pointer remains valid. The bindgen AIBinder
+ // is the same type as sys::AIBinder.
+ let service = service.as_native_mut() as *mut AIBinder;
+ // SAFETY: It is safe for on_ready to be invoked at any time, with any parameter.
+ AVmPayload_runVsockRpcServer(service, COMPOS_VSOCK_PORT, Some(on_ready), param);
+ }
Ok(())
}
+
+extern "C" fn on_ready(_param: *mut c_void) {
+ // SAFETY: Invokes a method from the bindgen library `vm_payload_bindgen` which is safe to
+ // call at any time.
+ unsafe { AVmPayload_notifyPayloadReady() };
+}
diff --git a/demo/java/com/android/microdroid/demo/MainActivity.java b/demo/java/com/android/microdroid/demo/MainActivity.java
index 8e870ea..77f2ee7 100644
--- a/demo/java/com/android/microdroid/demo/MainActivity.java
+++ b/demo/java/com/android/microdroid/demo/MainActivity.java
@@ -256,7 +256,8 @@
builder.setDebugLevel(VirtualMachineConfig.DEBUG_LEVEL_FULL);
}
VirtualMachineConfig config = builder.build();
- VirtualMachineManager vmm = VirtualMachineManager.getInstance(getApplication());
+ VirtualMachineManager vmm =
+ getApplication().getSystemService(VirtualMachineManager.class);
mVirtualMachine = vmm.getOrCreate(VM_NAME, config);
try {
mVirtualMachine.setConfig(config);
diff --git a/javalib/api/module-lib-current.txt b/javalib/api/module-lib-current.txt
index d802177..4d59764 100644
--- a/javalib/api/module-lib-current.txt
+++ b/javalib/api/module-lib-current.txt
@@ -1 +1,9 @@
// Signature format: 2.0
+package android.system.virtualmachine {
+
+ public class VirtualizationFrameworkInitializer {
+ method public static void registerServiceWrappers();
+ }
+
+}
+
diff --git a/javalib/api/system-current.txt b/javalib/api/system-current.txt
index ea2d23e..16995c5 100644
--- a/javalib/api/system-current.txt
+++ b/javalib/api/system-current.txt
@@ -3,7 +3,7 @@
public class VirtualMachine implements java.lang.AutoCloseable {
method public void clearCallback();
- method public void close() throws android.system.virtualmachine.VirtualMachineException;
+ method public void close();
method @NonNull public android.os.IBinder connectToVsockServer(int) throws android.system.virtualmachine.VirtualMachineException;
method @NonNull public android.os.ParcelFileDescriptor connectVsock(int) throws android.system.virtualmachine.VirtualMachineException;
method public int getCid() throws android.system.virtualmachine.VirtualMachineException;
@@ -98,7 +98,6 @@
method public void delete(@NonNull String) throws android.system.virtualmachine.VirtualMachineException;
method @Nullable public android.system.virtualmachine.VirtualMachine get(@NonNull String) throws android.system.virtualmachine.VirtualMachineException;
method public int getCapabilities();
- method @NonNull public static android.system.virtualmachine.VirtualMachineManager getInstance(@NonNull android.content.Context);
method @NonNull public android.system.virtualmachine.VirtualMachine getOrCreate(@NonNull String, @NonNull android.system.virtualmachine.VirtualMachineConfig) throws android.system.virtualmachine.VirtualMachineException;
method @NonNull public android.system.virtualmachine.VirtualMachine importFromDescriptor(@NonNull String, @NonNull android.system.virtualmachine.VirtualMachineDescriptor) throws android.system.virtualmachine.VirtualMachineException;
field public static final int CAPABILITY_NON_PROTECTED_VM = 2; // 0x2
diff --git a/javalib/jarjar-rules.txt b/javalib/jarjar-rules.txt
index dd8ad2d..726f9aa 100644
--- a/javalib/jarjar-rules.txt
+++ b/javalib/jarjar-rules.txt
@@ -1,9 +1,10 @@
# Rules for the android.system.virtualmachine java_sdk_library.
-# This is the root of the API, everything we care about should be
-# reachable from here.
-# (This gets rid of all the android.sysprop classes we don't use.)
+# Keep the API surface, most of it is accessible from VirtualMachineManager
keep android.system.virtualmachine.VirtualMachineManager
+# VirtualizationModuleFrameworkInitializer is not accessible from
+# VirtualMachineManager, we need to explicitly keep it.
+keep android.system.virtualmachine.VirtualizationFrameworkInitializer
# We statically link PlatformProperties, rename to avoid clashes.
rule android.sysprop.** com.android.system.virtualmachine.sysprop.@1
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index 193d213..dec873f 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -65,6 +65,7 @@
import android.system.virtualizationservice.VirtualMachineAppConfig;
import android.system.virtualizationservice.VirtualMachineState;
import android.util.JsonReader;
+import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -105,6 +106,8 @@
*/
@SystemApi
public class VirtualMachine implements AutoCloseable {
+ private static final String TAG = "VirtualMachine";
+
/** Name of the directory under the files directory where all VMs created for the app exist. */
private static final String VM_DIR = "vm";
@@ -754,7 +757,8 @@
* computer; the machine halts immediately. Software running on the virtual machine is not
* notified of the event. A stopped virtual machine can be re-started by calling {@link #run()}.
*
- * @throws VirtualMachineException if the virtual machine could not be stopped.
+ * @throws VirtualMachineException if the virtual machine is not running or could not be
+ * stopped.
* @hide
*/
@SystemApi
@@ -775,15 +779,31 @@
}
/**
- * Stops this virtual machine. See {@link #stop()}.
+ * Stops this virtual machine, if it is running.
*
- * @throws VirtualMachineException if the virtual machine could not be stopped.
+ * @see #stop()
* @hide
*/
@SystemApi
@Override
- public void close() throws VirtualMachineException {
- stop();
+ public void close() {
+ synchronized (mLock) {
+ if (mVirtualMachine == null) {
+ return;
+ }
+ try {
+ if (stateToStatus(mVirtualMachine.getState()) == STATUS_RUNNING) {
+ mVirtualMachine.stop();
+ mVirtualMachine = null;
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } catch (ServiceSpecificException e) {
+ // Deliberately ignored; this almost certainly means the VM exited just as
+ // we tried to stop it.
+ Log.i(TAG, "Ignoring error on close()", e);
+ }
+ }
}
private static void deleteRecursively(File dir) throws IOException {
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index a520ab4..c179498 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -21,10 +21,11 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
-import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.sysprop.HypervisorProperties;
import android.util.ArrayMap;
@@ -34,7 +35,6 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.Map;
-import java.util.WeakHashMap;
/**
* Manages {@link VirtualMachine virtual machine} instances created by an app. Each instance is
@@ -46,9 +46,13 @@
*
* <p>The app can then start, stop and otherwise interact with the VM.
*
+ * <p>An instance of {@link VirtualMachineManager} can be obtained by calling {@link
+ * Context#getSystemService(Class)}.
+ *
* @hide
*/
@SystemApi
+@RequiresFeature(PackageManager.FEATURE_VIRTUALIZATION_FRAMEWORK)
public class VirtualMachineManager {
/**
* A lock used to synchronize the creation of virtual machines. It protects {@link #mVmsByName},
@@ -59,14 +63,11 @@
@NonNull private final Context mContext;
- private VirtualMachineManager(@NonNull Context context) {
- mContext = context;
+ /** @hide */
+ public VirtualMachineManager(@NonNull Context context) {
+ mContext = requireNonNull(context);
}
- @GuardedBy("sInstances")
- private static final Map<Context, WeakReference<VirtualMachineManager>> sInstances =
- new WeakHashMap<>();
-
@GuardedBy("sCreateLock")
private final Map<String, WeakReference<VirtualMachine>> mVmsByName = new ArrayMap<>();
@@ -93,27 +94,6 @@
public static final int CAPABILITY_NON_PROTECTED_VM = 2;
/**
- * Returns the per-context instance.
- *
- * @hide
- */
- @SystemApi
- @NonNull
- @SuppressLint("ManagerLookup") // TODO(b/249093790): remove
- public static VirtualMachineManager getInstance(@NonNull Context context) {
- requireNonNull(context, "context must not be null");
- synchronized (sInstances) {
- VirtualMachineManager vmm =
- sInstances.containsKey(context) ? sInstances.get(context).get() : null;
- if (vmm == null) {
- vmm = new VirtualMachineManager(context);
- sInstances.put(context, new WeakReference<>(vmm));
- }
- return vmm;
- }
- }
-
- /**
* Returns a set of flags indicating what this implementation of virtualization is capable of.
*
* @see #CAPABILITY_PROTECTED_VM
diff --git a/javalib/src/android/system/virtualmachine/VirtualizationFrameworkInitializer.java b/javalib/src/android/system/virtualmachine/VirtualizationFrameworkInitializer.java
new file mode 100644
index 0000000..30ac425
--- /dev/null
+++ b/javalib/src/android/system/virtualmachine/VirtualizationFrameworkInitializer.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.system.virtualmachine;
+
+import static android.content.pm.PackageManager.FEATURE_VIRTUALIZATION_FRAMEWORK;
+
+import android.annotation.SystemApi;
+import android.app.SystemServiceRegistry;
+import android.content.Context;
+
+/**
+ * Holds initialization code for virtualization module
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class VirtualizationFrameworkInitializer {
+
+ private VirtualizationFrameworkInitializer() {}
+
+ /**
+ * Called by the static initializer in the {@link SystemServiceRegistry}, and registers {@link
+ * VirtualMachineManager} to the {@link Context}. so that it's accessible from {@link
+ * Context#getSystemService(String)}.
+ */
+ public static void registerServiceWrappers() {
+ // Note: it's important that the getPackageManager().hasSystemFeature() check is executed
+ // in the lambda, and not directly in the registerServiceWrappers method. The
+ // registerServiceWrappers is called during Zygote static initialization, and at that
+ // point the PackageManager is not available yet.
+ //
+ // On the other hand, the lambda is executed after the app calls Context.getSystemService
+ // (VirtualMachineManager.class), at which point the PackageManager is available. The
+ // result of the lambda is cached on per-context basis.
+ SystemServiceRegistry.registerContextAwareService(
+ Context.VIRTUALIZATION_SERVICE,
+ VirtualMachineManager.class,
+ ctx ->
+ ctx.getPackageManager().hasSystemFeature(FEATURE_VIRTUALIZATION_FRAMEWORK)
+ ? new VirtualMachineManager(ctx)
+ : null);
+ }
+}
diff --git a/microdroid/init.rc b/microdroid/init.rc
index 94ef940..7d04557 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -74,6 +74,12 @@
# some services can be started.
trigger late-fs
+ # Wait for microdroid_manager to finish setting up sysprops from the payload config.
+ # Some further actions in the boot sequence might depend on the sysprops from the payloag,
+ # e.g. microdroid.config.enable_authfs configures whether to run authfs_service after
+ # /data is mounted.
+ wait_for_prop microdroid_manager.config_done 1
+
trigger post-fs-data
# Load persist properties and override properties (if enabled) from /data.
@@ -132,6 +138,13 @@
mkdir /data/local 0751 root root
mkdir /data/local/tmp 0771 shell shell
+on post-fs-data && property:microdroid_manager.authfs.enabled=1
+ start authfs_service
+
+on boot
+ # Mark boot completed. This will notify microdroid_manager to run payload.
+ setprop dev.bootcomplete 1
+
service tombstone_transmit /system/bin/tombstone_transmit.microdroid -cid 2 -port 2000 -remove_tombstones_after_transmitting
user system
group system
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index a706dbe..17532d7 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -417,9 +417,10 @@
mount_extra_apks(&config)?;
// Wait until apex config is done. (e.g. linker configuration for apexes)
- // TODO(jooyung): wait until sys.boot_completed?
wait_for_apex_config_done()?;
+ setup_config_sysprops(&config)?;
+
// Start tombstone_transmit if enabled
if config.export_tombstones {
control_service("start", "tombstone_transmit")?;
@@ -427,11 +428,6 @@
control_service("stop", "tombstoned")?;
}
- // Start authfs if enabled
- if config.enable_authfs {
- control_service("start", "authfs_service")?;
- }
-
// Wait until zipfuse has mounted the APK so we can access the payload
wait_for_property_true(APK_MOUNT_DONE_PROP).context("Failed waiting for APK mount done")?;
@@ -442,7 +438,8 @@
ensure!(exitcode.success(), "Unable to prepare encrypted storage. Exitcode={}", exitcode);
}
- system_properties::write("dev.bootcomplete", "1").context("set dev.bootcomplete")?;
+ wait_for_property_true("dev.bootcomplete").context("failed waiting for dev.bootcomplete")?;
+ info!("boot completed, time to run payload");
exec_task(task, service).context("Failed to run payload")
}
@@ -682,6 +679,16 @@
Ok(())
}
+fn setup_config_sysprops(config: &VmPayloadConfig) -> Result<()> {
+ if config.enable_authfs {
+ system_properties::write("microdroid_manager.authfs.enabled", "1")
+ .context("failed to write microdroid_manager.authfs.enabled")?;
+ }
+ system_properties::write("microdroid_manager.config_done", "1")
+ .context("failed to write microdroid_manager.config_done")?;
+ Ok(())
+}
+
// Waits until linker config is generated
fn wait_for_apex_config_done() -> Result<()> {
wait_for_property_true(APEX_CONFIG_DONE_PROP).context("Failed waiting for apex config done")
diff --git a/tests/benchmark/Android.bp b/tests/benchmark/Android.bp
index 10cdac5..9d2b6c7 100644
--- a/tests/benchmark/Android.bp
+++ b/tests/benchmark/Android.bp
@@ -38,6 +38,6 @@
"libbase",
"libbinder_ndk",
"liblog",
- "libvm_payload",
+ "libvm_payload#current",
],
}
diff --git a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
index 28852e8..14a0e39 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -88,16 +88,17 @@
private boolean canBootMicrodroidWithMemory(int mem)
throws VirtualMachineException, InterruptedException, IOException {
- VirtualMachineConfig normalConfig = mInner.newVmConfigBuilder()
- .setPayloadBinaryPath("MicrodroidIdleNativeLib.so")
- .setDebugLevel(DEBUG_LEVEL_NONE)
- .setMemoryMib(mem)
- .build();
+ VirtualMachineConfig normalConfig =
+ newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidIdleNativeLib.so")
+ .setDebugLevel(DEBUG_LEVEL_NONE)
+ .setMemoryMib(mem)
+ .build();
// returns true if succeeded at least once.
final int trialCount = 5;
for (int i = 0; i < trialCount; i++) {
- mInner.forceCreateNewVirtualMachine("test_vm_minimum_memory", normalConfig);
+ forceCreateNewVirtualMachine("test_vm_minimum_memory", normalConfig);
if (tryBootVm(TAG, "test_vm_minimum_memory").payloadStarted) return true;
}
@@ -147,12 +148,13 @@
for (int i = 0; i < trialCount; i++) {
// To grab boot events from log, set debug mode to FULL
- VirtualMachineConfig normalConfig = mInner.newVmConfigBuilder()
- .setPayloadBinaryPath("MicrodroidIdleNativeLib.so")
- .setDebugLevel(DEBUG_LEVEL_FULL)
- .setMemoryMib(256)
- .build();
- mInner.forceCreateNewVirtualMachine("test_vm_boot_time", normalConfig);
+ VirtualMachineConfig normalConfig =
+ newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidIdleNativeLib.so")
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .setMemoryMib(256)
+ .build();
+ forceCreateNewVirtualMachine("test_vm_boot_time", normalConfig);
BootResult result = tryBootVm(TAG, "test_vm_boot_time");
assertThat(result.payloadStarted).isTrue();
@@ -195,17 +197,17 @@
@Test
public void testVsockTransferFromHostToVM() throws Exception {
- VirtualMachineConfig config = mInner.newVmConfigBuilder()
- .setPayloadConfigPath("assets/vm_config_io.json")
- .setDebugLevel(DEBUG_LEVEL_FULL)
- .build();
+ VirtualMachineConfig config =
+ newVmConfigBuilder()
+ .setPayloadConfigPath("assets/vm_config_io.json")
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .build();
List<Double> transferRates = new ArrayList<>(IO_TEST_TRIAL_COUNT);
for (int i = 0; i < IO_TEST_TRIAL_COUNT; ++i) {
int port = (mProtectedVm ? 5666 : 6666) + i;
String vmName = "test_vm_io_" + i;
- mInner.forceCreateNewVirtualMachine(vmName, config);
- VirtualMachine vm = mInner.getVirtualMachineManager().get(vmName);
+ VirtualMachine vm = forceCreateNewVirtualMachine(vmName, config);
BenchmarkVmListener.create(new VsockListener(transferRates, port)).runToFinish(TAG, vm);
}
reportMetrics(transferRates, "vsock/transfer_host_to_vm", "mb_per_sec");
@@ -222,10 +224,11 @@
}
private void testVirtioBlkReadRate(boolean isRand) throws Exception {
- VirtualMachineConfig config = mInner.newVmConfigBuilder()
- .setPayloadConfigPath("assets/vm_config_io.json")
- .setDebugLevel(DEBUG_LEVEL_FULL)
- .build();
+ VirtualMachineConfig config =
+ newVmConfigBuilder()
+ .setPayloadConfigPath("assets/vm_config_io.json")
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .build();
List<Double> readRates = new ArrayList<>(IO_TEST_TRIAL_COUNT);
for (int i = 0; i < IO_TEST_TRIAL_COUNT + 1; ++i) {
@@ -236,8 +239,7 @@
readRates.clear();
}
String vmName = "test_vm_io_" + i;
- mInner.forceCreateNewVirtualMachine(vmName, config);
- VirtualMachine vm = mInner.getVirtualMachineManager().get(vmName);
+ VirtualMachine vm = forceCreateNewVirtualMachine(vmName, config);
BenchmarkVmListener.create(new VirtioBlkListener(readRates, isRand))
.runToFinish(TAG, vm);
}
@@ -280,13 +282,13 @@
@Test
public void testMemoryUsage() throws Exception {
final String vmName = "test_vm_mem_usage";
- VirtualMachineConfig config = mInner.newVmConfigBuilder()
- .setPayloadConfigPath("assets/vm_config_io.json")
- .setDebugLevel(DEBUG_LEVEL_NONE)
- .setMemoryMib(256)
- .build();
- mInner.forceCreateNewVirtualMachine(vmName, config);
- VirtualMachine vm = mInner.getVirtualMachineManager().get(vmName);
+ VirtualMachineConfig config =
+ newVmConfigBuilder()
+ .setPayloadConfigPath("assets/vm_config_io.json")
+ .setDebugLevel(DEBUG_LEVEL_NONE)
+ .setMemoryMib(256)
+ .build();
+ VirtualMachine vm = forceCreateNewVirtualMachine(vmName, config);
MemoryUsageListener listener = new MemoryUsageListener(this::executeCommand);
BenchmarkVmListener.create(listener).runToFinish(TAG, vm);
diff --git a/tests/benchmark/src/native/benchmarkbinary.cpp b/tests/benchmark/src/native/benchmarkbinary.cpp
index 66b41a1..70ec7db 100644
--- a/tests/benchmark/src/native/benchmarkbinary.cpp
+++ b/tests/benchmark/src/native/benchmarkbinary.cpp
@@ -161,10 +161,8 @@
Result<void> run_io_benchmark_tests() {
auto test_service = ndk::SharedRefBase::make<IOBenchmarkService>();
auto callback = []([[maybe_unused]] void* param) { AVmPayload_notifyPayloadReady(); };
- if (!AVmPayload_runVsockRpcServer(test_service->asBinder().get(), test_service->SERVICE_PORT,
- callback, nullptr)) {
- return Error() << "RPC Server failed to run";
- }
+ AVmPayload_runVsockRpcServer(test_service->asBinder().get(), test_service->SERVICE_PORT,
+ callback, nullptr);
return {};
}
} // Anonymous namespace
diff --git a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index 24e2049..c266b96 100644
--- a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -73,59 +73,44 @@
permission);
}
- // TODO(b/220920264): remove Inner class; this is a hack to hide virt APEX types
- protected static class Inner {
- private final boolean mProtectedVm;
- private final Context mContext;
- private final VirtualMachineManager mVmm;
-
- public Inner(Context context, boolean protectedVm, VirtualMachineManager vmm) {
- mProtectedVm = protectedVm;
- mVmm = vmm;
- mContext = context;
- }
-
- public VirtualMachineManager getVirtualMachineManager() {
- return mVmm;
- }
-
- public Context getContext() {
- return mContext;
- }
-
- public VirtualMachineConfig.Builder newVmConfigBuilder() {
- return new VirtualMachineConfig.Builder(mContext).setProtectedVm(mProtectedVm);
- }
-
- /**
- * Creates a new virtual machine, potentially removing an existing virtual machine with
- * given name.
- */
- public VirtualMachine forceCreateNewVirtualMachine(String name, VirtualMachineConfig config)
- throws VirtualMachineException {
- VirtualMachine existingVm = mVmm.get(name);
- if (existingVm != null) {
- mVmm.delete(name);
- }
- return mVmm.create(name, config);
- }
- }
-
- protected Inner mInner;
+ private Context mCtx;
+ private boolean mProtectedVm;
protected Context getContext() {
- return mInner.getContext();
+ return mCtx;
+ }
+
+ public VirtualMachineManager getVirtualMachineManager() {
+ return mCtx.getSystemService(VirtualMachineManager.class);
+ }
+
+ public VirtualMachineConfig.Builder newVmConfigBuilder() {
+ return new VirtualMachineConfig.Builder(mCtx).setProtectedVm(mProtectedVm);
+ }
+
+ /**
+ * Creates a new virtual machine, potentially removing an existing virtual machine with given
+ * name.
+ */
+ public VirtualMachine forceCreateNewVirtualMachine(String name, VirtualMachineConfig config)
+ throws VirtualMachineException {
+ final VirtualMachineManager vmm = getVirtualMachineManager();
+ VirtualMachine existingVm = vmm.get(name);
+ if (existingVm != null) {
+ vmm.delete(name);
+ }
+ return vmm.create(name, config);
}
public void prepareTestSetup(boolean protectedVm) {
- Context ctx = ApplicationProvider.getApplicationContext();
+ mCtx = ApplicationProvider.getApplicationContext();
assume().withMessage("Device doesn't support AVF")
- .that(ctx.getPackageManager().hasSystemFeature(FEATURE_VIRTUALIZATION_FRAMEWORK))
+ .that(mCtx.getPackageManager().hasSystemFeature(FEATURE_VIRTUALIZATION_FRAMEWORK))
.isTrue();
- mInner = new Inner(ctx, protectedVm, VirtualMachineManager.getInstance(ctx));
+ mProtectedVm = protectedVm;
- int capabilities = mInner.getVirtualMachineManager().getCapabilities();
+ int capabilities = getVirtualMachineManager().getCapabilities();
if (protectedVm) {
assume().withMessage("Skip where protected VMs aren't supported")
.that(capabilities & VirtualMachineManager.CAPABILITY_PROTECTED_VM)
@@ -314,7 +299,7 @@
public BootResult tryBootVm(String logTag, String vmName)
throws VirtualMachineException, InterruptedException {
- VirtualMachine vm = mInner.getVirtualMachineManager().get(vmName);
+ VirtualMachine vm = getVirtualMachineManager().get(vmName);
final CompletableFuture<Boolean> payloadStarted = new CompletableFuture<>();
final CompletableFuture<Integer> deathReason = new CompletableFuture<>();
final CompletableFuture<Long> endTime = new CompletableFuture<>();
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 795d7b3..11b3e84 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -146,23 +146,16 @@
.collect(toList())));
FileUtil.writeToFile(config.toString(), configFile);
- File mkPayload = findTestFile("mk_payload");
RunUtil runUtil = new RunUtil();
- // Set the parent dir on the PATH (e.g. <workdir>/bin)
- String separator = System.getProperty("path.separator");
- String path = mkPayload.getParentFile().getPath() + separator + System.getenv("PATH");
- runUtil.setEnvVariable("PATH", path);
-
- List<String> command = new ArrayList<>();
- command.add("mk_payload");
- command.add("--metadata-only");
- command.add(configFile.toString());
- command.add(payloadMetadata.toString());
-
- CommandResult result =
- runUtil.runTimedCmd(
- // mk_payload should run fast enough
- 5 * 1000, "/bin/bash", "-c", String.join(" ", command));
+ String command =
+ String.join(
+ " ",
+ findTestFile("mk_payload").getAbsolutePath(),
+ "--metadata-only",
+ configFile.getAbsolutePath(),
+ payloadMetadata.getAbsolutePath());
+ // mk_payload should run fast enough
+ CommandResult result = runUtil.runTimedCmd(5000, "/bin/bash", "-c", command);
String out = result.getStdout();
String err = result.getStderr();
assertWithMessage(
@@ -186,7 +179,7 @@
runUtil.setEnvVariable("PATH", path);
List<String> command = new ArrayList<>();
- command.add("sign_virt_apex");
+ command.add(signVirtApex.getAbsolutePath());
keyOverrides.forEach(
(filename, keyFile) ->
command.add("--key_override " + filename + "=" + keyFile.getPath()));
@@ -211,18 +204,11 @@
long timeoutMillis, Callable<T> callable, org.hamcrest.Matcher<T> matcher)
throws Exception {
long start = System.currentTimeMillis();
- while (true) {
- try {
- assertThat(callable.call(), matcher);
- return;
- } catch (Throwable e) {
- if (System.currentTimeMillis() - start < timeoutMillis) {
- Thread.sleep(500);
- } else {
- throw e;
- }
- }
+ while ((System.currentTimeMillis() - start < timeoutMillis)
+ && !matcher.matches(callable.call())) {
+ Thread.sleep(500);
}
+ assertThat(callable.call(), matcher);
}
static class ActiveApexInfo {
diff --git a/tests/no_avf/Android.bp b/tests/no_avf/Android.bp
new file mode 100644
index 0000000..fd0d5e2
--- /dev/null
+++ b/tests/no_avf/Android.bp
@@ -0,0 +1,21 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "CtsMicrodroidDisabledTestCases",
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ srcs: ["src/**/*.java"],
+ static_libs: [
+ "androidx.test.runner",
+ "androidx.test.ext.junit",
+ "compatibility-common-util-devicesidelib",
+ "truth-prebuilt",
+ ],
+ sdk_version: "test_current",
+ compile_multilib: "both",
+ min_sdk_version: "UpsideDownCake",
+}
diff --git a/tests/no_avf/AndroidManifest.xml b/tests/no_avf/AndroidManifest.xml
new file mode 100644
index 0000000..4a1304e
--- /dev/null
+++ b/tests/no_avf/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.nomicrodroid.test">
+ <application />
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.nomicrodroid.test"
+ android:label="CTS test for devices without Microdroid support" />
+</manifest>
diff --git a/tests/no_avf/AndroidTest.xml b/tests/no_avf/AndroidTest.xml
new file mode 100644
index 0000000..1e93887
--- /dev/null
+++ b/tests/no_avf/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Microdroid device-side tests.">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="security" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="test-file-name" value="CtsMicrodroidDisabledTestCases.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.nomicrodroid.test" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/tests/no_avf/README.md b/tests/no_avf/README.md
new file mode 100644
index 0000000..b96dc97
--- /dev/null
+++ b/tests/no_avf/README.md
@@ -0,0 +1 @@
+CTS tests for devices that don't support AVF.
\ No newline at end of file
diff --git a/tests/no_avf/src/com/android/nomicrodroid/test/NoMicrodroidTest.java b/tests/no_avf/src/com/android/nomicrodroid/test/NoMicrodroidTest.java
new file mode 100644
index 0000000..0982e35
--- /dev/null
+++ b/tests/no_avf/src/com/android/nomicrodroid/test/NoMicrodroidTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.nomicrodroid.test;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.system.virtualmachine.VirtualMachineManager;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.CddTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests to validate that devices without support for AVF (Android Virtualization Framework) are set
+ * up correctly.
+ */
+@RunWith(JUnit4.class)
+public class NoMicrodroidTest {
+
+ @Before
+ public void setUp() {
+ final PackageManager pm =
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+ assume().withMessage("Device supports AVF")
+ .that(pm.hasSystemFeature(PackageManager.FEATURE_VIRTUALIZATION_FRAMEWORK))
+ .isFalse();
+ }
+
+ @CddTest(requirements = {"9.17/C-1-1"})
+ @Test
+ public void testVirtualMachineManagerLookupReturnsNull() {
+ final Context ctx = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ assertThat(ctx.getSystemService(VirtualMachineManager.class)).isNull();
+ }
+}
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index df7c6c0..4dc9489 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -39,7 +39,7 @@
shared_libs: [
"libbinder_ndk",
"MicrodroidTestNativeLibSub",
- "libvm_payload",
+ "libvm_payload#current",
],
static_libs: [
"com.android.microdroid.testservice-ndk",
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index 71a9e3b..1f5bae9 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -15,6 +15,9 @@
*/
package com.android.microdroid.test;
+import static android.system.virtualmachine.VirtualMachine.STATUS_DELETED;
+import static android.system.virtualmachine.VirtualMachine.STATUS_RUNNING;
+import static android.system.virtualmachine.VirtualMachine.STATUS_STOPPED;
import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_APP_ONLY;
import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_FULL;
import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_NONE;
@@ -102,18 +105,16 @@
private static final int MIN_MEM_X86_64 = 196;
@Test
- @CddTest(requirements = {
- "9.17/C-1-1",
- "9.17/C-2-1"
- })
- public void connectToVmService() throws Exception {
+ @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+ public void createAndConnectToVm() throws Exception {
assumeSupportedKernel();
- VirtualMachineConfig config = mInner.newVmConfigBuilder()
- .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
- .setMemoryMib(minMemoryRequired())
- .build();
- VirtualMachine vm = mInner.forceCreateNewVirtualMachine("test_vm", config);
+ VirtualMachineConfig config =
+ newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+ .setMemoryMib(minMemoryRequired())
+ .build();
+ VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
TestResults testResults = runVmTestService(vm);
assertThat(testResults.mException).isNull();
@@ -135,18 +136,51 @@
revokePermission(VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION);
- VirtualMachineConfig config = mInner.newVmConfigBuilder()
- .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
- .setMemoryMib(minMemoryRequired())
- .build();
+ VirtualMachineConfig config =
+ newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+ .setMemoryMib(minMemoryRequired())
+ .build();
- SecurityException e = assertThrows(SecurityException.class,
- () -> mInner.forceCreateNewVirtualMachine("test_vm_requires_permission", config));
+ SecurityException e =
+ assertThrows(
+ SecurityException.class,
+ () -> forceCreateNewVirtualMachine("test_vm_requires_permission", config));
assertThat(e).hasMessageThat()
.contains("android.permission.MANAGE_VIRTUAL_MACHINE permission");
}
@Test
+ @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+ public void autoCloseVm() throws Exception {
+ assumeSupportedKernel();
+
+ VirtualMachineConfig config =
+ newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+ .setMemoryMib(minMemoryRequired())
+ .build();
+
+ try (VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config)) {
+ assertThat(vm.getStatus()).isEqualTo(STATUS_STOPPED);
+ // close() implicitly called on stopped VM.
+ }
+
+ try (VirtualMachine vm = getVirtualMachineManager().get("test_vm")) {
+ vm.run();
+ assertThat(vm.getStatus()).isEqualTo(STATUS_RUNNING);
+ // close() implicitly called on running VM.
+ }
+
+ try (VirtualMachine vm = getVirtualMachineManager().get("test_vm")) {
+ assertThat(vm.getStatus()).isEqualTo(STATUS_STOPPED);
+ getVirtualMachineManager().delete("test_vm");
+ assertThat(vm.getStatus()).isEqualTo(STATUS_DELETED);
+ // close() implicitly called on deleted VM.
+ }
+ }
+
+ @Test
@CddTest(requirements = {
"9.17/C-1-1",
"9.17/C-1-2",
@@ -155,13 +189,14 @@
public void createVmWithConfigRequiresPermission() throws Exception {
assumeSupportedKernel();
- VirtualMachineConfig config = mInner.newVmConfigBuilder()
- .setPayloadConfigPath("assets/vm_config.json")
- .setMemoryMib(minMemoryRequired())
- .build();
+ VirtualMachineConfig config =
+ newVmConfigBuilder()
+ .setPayloadConfigPath("assets/vm_config.json")
+ .setMemoryMib(minMemoryRequired())
+ .build();
- VirtualMachine vm = mInner.forceCreateNewVirtualMachine(
- "test_vm_config_requires_permission", config);
+ VirtualMachine vm =
+ forceCreateNewVirtualMachine("test_vm_config_requires_permission", config);
SecurityException e = assertThrows(SecurityException.class, () -> runVmTestService(vm));
assertThat(e).hasMessageThat()
@@ -175,14 +210,14 @@
public void deleteVm() throws Exception {
assumeSupportedKernel();
- VirtualMachineConfig config = mInner.newVmConfigBuilder()
- .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
- .setMemoryMib(minMemoryRequired())
- .build();
+ VirtualMachineConfig config =
+ newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+ .setMemoryMib(minMemoryRequired())
+ .build();
- VirtualMachine vm = mInner.forceCreateNewVirtualMachine("test_vm_delete",
- config);
- VirtualMachineManager vmm = mInner.getVirtualMachineManager();
+ VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_delete", config);
+ VirtualMachineManager vmm = getVirtualMachineManager();
vmm.delete("test_vm_delete");
// VM should no longer exist
@@ -202,14 +237,14 @@
public void validApkPathIsAccepted() throws Exception {
assumeSupportedKernel();
- VirtualMachineConfig config = mInner.newVmConfigBuilder()
- .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
- .setApkPath(getContext().getPackageCodePath())
- .setMemoryMib(minMemoryRequired())
- .build();
+ VirtualMachineConfig config =
+ newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+ .setApkPath(getContext().getPackageCodePath())
+ .setMemoryMib(minMemoryRequired())
+ .build();
- VirtualMachine vm = mInner.forceCreateNewVirtualMachine(
- "test_vm_explicit_apk_path", config);
+ VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_explicit_apk_path", config);
TestResults testResults = runVmTestService(vm);
assertThat(testResults.mException).isNull();
@@ -222,10 +257,11 @@
public void invalidApkPathIsRejected() {
assumeSupportedKernel();
- VirtualMachineConfig.Builder builder = mInner.newVmConfigBuilder()
- .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
- .setApkPath("relative/path/to.apk")
- .setMemoryMib(minMemoryRequired());
+ VirtualMachineConfig.Builder builder =
+ newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+ .setApkPath("relative/path/to.apk")
+ .setMemoryMib(minMemoryRequired());
assertThrows(IllegalArgumentException.class, () -> builder.build());
}
@@ -238,11 +274,12 @@
assumeSupportedKernel();
grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
- VirtualMachineConfig config = mInner.newVmConfigBuilder()
- .setPayloadConfigPath("assets/vm_config_extra_apk.json")
- .setMemoryMib(minMemoryRequired())
- .build();
- VirtualMachine vm = mInner.forceCreateNewVirtualMachine("test_vm_extra_apk", config);
+ VirtualMachineConfig config =
+ newVmConfigBuilder()
+ .setPayloadConfigPath("assets/vm_config_extra_apk.json")
+ .setMemoryMib(minMemoryRequired())
+ .build();
+ VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_extra_apk", config);
TestResults testResults = runVmTestService(vm);
assertThat(testResults.mExtraApkTestProp).isEqualTo("PASS");
@@ -251,12 +288,13 @@
@Test
public void bootFailsWhenLowMem() throws Exception {
for (int memMib : new int[]{ 10, 20, 40 }) {
- VirtualMachineConfig lowMemConfig = mInner.newVmConfigBuilder()
- .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
- .setMemoryMib(memMib)
- .setDebugLevel(DEBUG_LEVEL_NONE)
- .build();
- VirtualMachine vm = mInner.forceCreateNewVirtualMachine("low_mem", lowMemConfig);
+ VirtualMachineConfig lowMemConfig =
+ newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+ .setMemoryMib(memMib)
+ .setDebugLevel(DEBUG_LEVEL_NONE)
+ .build();
+ VirtualMachine vm = forceCreateNewVirtualMachine("low_mem", lowMemConfig);
final CompletableFuture<Boolean> onPayloadReadyExecuted = new CompletableFuture<>();
final CompletableFuture<Boolean> onStoppedExecuted = new CompletableFuture<>();
VmEventListener listener =
@@ -298,11 +336,11 @@
assumeSupportedKernel();
VirtualMachineConfig.Builder builder =
- mInner.newVmConfigBuilder()
+ newVmConfigBuilder()
.setPayloadBinaryPath("MicrodroidTestNativeLib.so")
.setDebugLevel(fromLevel);
VirtualMachineConfig normalConfig = builder.build();
- mInner.forceCreateNewVirtualMachine("test_vm", normalConfig);
+ forceCreateNewVirtualMachine("test_vm", normalConfig);
assertThat(tryBootVm(TAG, "test_vm").payloadStarted).isTrue();
// Try to run the VM again with the previous instance.img
@@ -311,7 +349,7 @@
File vmInstance = getVmFile("test_vm", "instance.img");
File vmInstanceBackup = File.createTempFile("instance", ".img");
Files.copy(vmInstance.toPath(), vmInstanceBackup.toPath(), REPLACE_EXISTING);
- mInner.forceCreateNewVirtualMachine("test_vm", normalConfig);
+ forceCreateNewVirtualMachine("test_vm", normalConfig);
Files.copy(vmInstanceBackup.toPath(), vmInstance.toPath(), REPLACE_EXISTING);
assertThat(tryBootVm(TAG, "test_vm").payloadStarted).isTrue();
@@ -320,7 +358,7 @@
// For testing, we do that by creating a new VM with debug level, and copy the old instance
// image to the new VM instance image.
VirtualMachineConfig debugConfig = builder.setDebugLevel(toLevel).build();
- mInner.forceCreateNewVirtualMachine("test_vm", debugConfig);
+ forceCreateNewVirtualMachine("test_vm", debugConfig);
Files.copy(vmInstanceBackup.toPath(), vmInstance.toPath(), REPLACE_EXISTING);
assertThat(tryBootVm(TAG, "test_vm").payloadStarted).isFalse();
}
@@ -331,7 +369,7 @@
}
private VmCdis launchVmAndGetCdis(String instanceName) throws Exception {
- VirtualMachine vm = mInner.getVirtualMachineManager().get(instanceName);
+ VirtualMachine vm = getVirtualMachineManager().get(instanceName);
final VmCdis vmCdis = new VmCdis();
final CompletableFuture<Exception> exception = new CompletableFuture<>();
VmEventListener listener =
@@ -367,12 +405,13 @@
assumeSupportedKernel();
grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
- VirtualMachineConfig normalConfig = mInner.newVmConfigBuilder()
- .setPayloadConfigPath("assets/vm_config.json")
- .setDebugLevel(DEBUG_LEVEL_FULL)
- .build();
- mInner.forceCreateNewVirtualMachine("test_vm_a", normalConfig);
- mInner.forceCreateNewVirtualMachine("test_vm_b", normalConfig);
+ VirtualMachineConfig normalConfig =
+ newVmConfigBuilder()
+ .setPayloadConfigPath("assets/vm_config.json")
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .build();
+ forceCreateNewVirtualMachine("test_vm_a", normalConfig);
+ forceCreateNewVirtualMachine("test_vm_b", normalConfig);
VmCdis vm_a_cdis = launchVmAndGetCdis("test_vm_a");
VmCdis vm_b_cdis = launchVmAndGetCdis("test_vm_b");
assertThat(vm_a_cdis.cdiAttest).isNotNull();
@@ -390,13 +429,15 @@
})
public void sameInstanceKeepsSameCdis() throws Exception {
assumeSupportedKernel();
+ assume().withMessage("Skip on CF. Too Slow. b/257270529").that(isCuttlefish()).isFalse();
grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
- VirtualMachineConfig normalConfig = mInner.newVmConfigBuilder()
- .setPayloadConfigPath("assets/vm_config.json")
- .setDebugLevel(DEBUG_LEVEL_FULL)
- .build();
- mInner.forceCreateNewVirtualMachine("test_vm", normalConfig);
+ VirtualMachineConfig normalConfig =
+ newVmConfigBuilder()
+ .setPayloadConfigPath("assets/vm_config.json")
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .build();
+ forceCreateNewVirtualMachine("test_vm", normalConfig);
VmCdis first_boot_cdis = launchVmAndGetCdis("test_vm");
VmCdis second_boot_cdis = launchVmAndGetCdis("test_vm");
@@ -415,11 +456,12 @@
assumeSupportedKernel();
grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
- VirtualMachineConfig normalConfig = mInner.newVmConfigBuilder()
- .setPayloadConfigPath("assets/vm_config.json")
- .setDebugLevel(DEBUG_LEVEL_FULL)
- .build();
- VirtualMachine vm = mInner.forceCreateNewVirtualMachine("bcc_vm", normalConfig);
+ VirtualMachineConfig normalConfig =
+ newVmConfigBuilder()
+ .setPayloadConfigPath("assets/vm_config.json")
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .build();
+ VirtualMachine vm = forceCreateNewVirtualMachine("bcc_vm", normalConfig);
final CompletableFuture<byte[]> bcc = new CompletableFuture<>();
final CompletableFuture<Exception> exception = new CompletableFuture<>();
VmEventListener listener =
@@ -463,11 +505,12 @@
public void accessToCdisIsRestricted() throws Exception {
assumeSupportedKernel();
- VirtualMachineConfig config = mInner.newVmConfigBuilder()
- .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
- .setDebugLevel(DEBUG_LEVEL_FULL)
- .build();
- mInner.forceCreateNewVirtualMachine("test_vm", config);
+ VirtualMachineConfig config =
+ newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .build();
+ forceCreateNewVirtualMachine("test_vm", config);
assertThrows(Exception.class, () -> launchVmAndGetCdis("test_vm"));
}
@@ -509,12 +552,13 @@
}
private RandomAccessFile prepareInstanceImage(String vmName) throws Exception {
- VirtualMachineConfig config = mInner.newVmConfigBuilder()
- .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
- .setDebugLevel(DEBUG_LEVEL_FULL)
- .build();
+ VirtualMachineConfig config =
+ newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .build();
- mInner.forceCreateNewVirtualMachine(vmName, config);
+ forceCreateNewVirtualMachine(vmName, config);
assertThat(tryBootVm(TAG, vmName).payloadStarted).isTrue();
File instanceImgPath = getVmFile(vmName, "instance.img");
return new RandomAccessFile(instanceImgPath, "rw");
@@ -569,11 +613,12 @@
@Test
public void bootFailsWhenConfigIsInvalid() throws Exception {
grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
- VirtualMachineConfig normalConfig = mInner.newVmConfigBuilder()
- .setPayloadConfigPath("assets/vm_config_no_task.json")
- .setDebugLevel(DEBUG_LEVEL_FULL)
- .build();
- mInner.forceCreateNewVirtualMachine("test_vm_invalid_config", normalConfig);
+ VirtualMachineConfig normalConfig =
+ newVmConfigBuilder()
+ .setPayloadConfigPath("assets/vm_config_no_task.json")
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .build();
+ forceCreateNewVirtualMachine("test_vm_invalid_config", normalConfig);
BootResult bootResult = tryBootVm(TAG, "test_vm_invalid_config");
assertThat(bootResult.payloadStarted).isFalse();
@@ -583,10 +628,10 @@
@Test
public void bootFailsWhenBinaryPathIsInvalid() throws Exception {
- VirtualMachineConfig.Builder builder = mInner.newVmConfigBuilder()
- .setPayloadBinaryPath("DoesNotExist.so");
+ VirtualMachineConfig.Builder builder =
+ newVmConfigBuilder().setPayloadBinaryPath("DoesNotExist.so");
VirtualMachineConfig normalConfig = builder.setDebugLevel(DEBUG_LEVEL_FULL).build();
- mInner.forceCreateNewVirtualMachine("test_vm_invalid_binary_path", normalConfig);
+ forceCreateNewVirtualMachine("test_vm_invalid_binary_path", normalConfig);
BootResult bootResult = tryBootVm(TAG, "test_vm_invalid_binary_path");
assertThat(bootResult.payloadStarted).isFalse();
@@ -596,17 +641,18 @@
@Test
public void sameInstancesShareTheSameVmObject() throws Exception {
- VirtualMachineConfig config = mInner.newVmConfigBuilder()
- .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
- .setDebugLevel(DEBUG_LEVEL_NONE)
- .build();
+ VirtualMachineConfig config =
+ newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+ .setDebugLevel(DEBUG_LEVEL_NONE)
+ .build();
- VirtualMachine vm = mInner.forceCreateNewVirtualMachine("test_vm", config);
- VirtualMachine vm2 = mInner.getVirtualMachineManager().get("test_vm");
+ VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
+ VirtualMachine vm2 = getVirtualMachineManager().get("test_vm");
assertThat(vm).isEqualTo(vm2);
- VirtualMachine newVm = mInner.forceCreateNewVirtualMachine("test_vm", config);
- VirtualMachine newVm2 = mInner.getVirtualMachineManager().get("test_vm");
+ VirtualMachine newVm = forceCreateNewVirtualMachine("test_vm", config);
+ VirtualMachine newVm2 = getVirtualMachineManager().get("test_vm");
assertThat(newVm).isEqualTo(newVm2);
assertThat(vm).isNotEqualTo(newVm);
@@ -618,17 +664,17 @@
// Arrange
grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
VirtualMachineConfig config =
- mInner.newVmConfigBuilder()
+ newVmConfigBuilder()
.setPayloadConfigPath("assets/vm_config.json")
.setDebugLevel(DEBUG_LEVEL_FULL)
.build();
String vmNameOrig = "test_vm_orig";
String vmNameImport = "test_vm_import";
- VirtualMachine vmOrig = mInner.forceCreateNewVirtualMachine(vmNameOrig, config);
+ VirtualMachine vmOrig = forceCreateNewVirtualMachine(vmNameOrig, config);
VmCdis origCdis = launchVmAndGetCdis(vmNameOrig);
assertThat(origCdis.instanceSecret).isNotNull();
VirtualMachineDescriptor descriptor = vmOrig.toDescriptor();
- VirtualMachineManager vmm = mInner.getVirtualMachineManager();
+ VirtualMachineManager vmm = getVirtualMachineManager();
if (vmm.get(vmNameImport) != null) {
vmm.delete(vmNameImport);
}
@@ -646,19 +692,19 @@
public void importedVmIsEqualToTheOriginalVm() throws Exception {
// Arrange
VirtualMachineConfig config =
- mInner.newVmConfigBuilder()
+ newVmConfigBuilder()
.setPayloadBinaryPath("MicrodroidTestNativeLib.so")
.setDebugLevel(DEBUG_LEVEL_NONE)
.build();
String vmNameOrig = "test_vm_orig";
String vmNameImport = "test_vm_import";
- VirtualMachine vmOrig = mInner.forceCreateNewVirtualMachine(vmNameOrig, config);
+ VirtualMachine vmOrig = forceCreateNewVirtualMachine(vmNameOrig, config);
// Run something to make the instance.img different with the initialized one.
TestResults origTestResults = runVmTestService(vmOrig);
assertThat(origTestResults.mException).isNull();
assertThat(origTestResults.mAddInteger).isEqualTo(123 + 456);
VirtualMachineDescriptor descriptor = vmOrig.toDescriptor();
- VirtualMachineManager vmm = mInner.getVirtualMachineManager();
+ VirtualMachineManager vmm = getVirtualMachineManager();
if (vmm.get(vmNameImport) != null) {
vmm.delete(vmNameImport);
}
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index 5c217ff..c0a8c0e 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -119,10 +119,8 @@
auto testService = ndk::SharedRefBase::make<TestService>();
auto callback = []([[maybe_unused]] void* param) { AVmPayload_notifyPayloadReady(); };
- if (!AVmPayload_runVsockRpcServer(testService->asBinder().get(), testService->SERVICE_PORT,
- callback, nullptr)) {
- return Error() << "RPC Server failed to run";
- }
+ AVmPayload_runVsockRpcServer(testService->asBinder().get(), testService->SERVICE_PORT, callback,
+ nullptr);
return {};
}
diff --git a/vm_payload/Android.bp b/vm_payload/Android.bp
index 6be6f22..967d1cf 100644
--- a/vm_payload/Android.bp
+++ b/vm_payload/Android.bp
@@ -2,9 +2,11 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-rust_ffi_shared {
- name: "libvm_payload",
+// The Rust implementation of the C API.
+rust_ffi_static {
+ name: "libvm_payload_impl",
crate_name: "vm_payload",
+ visibility: ["//visibility:private"],
srcs: ["src/*.rs"],
include_dirs: ["include"],
prefer_rlib: true,
@@ -19,9 +21,6 @@
"librpcbinder_rs",
"libvsock",
],
- apex_available: [
- "com.android.compos",
- ],
// The sanitize section below fixes the fuzzer build in b/256166339.
// TODO(b/250854486): Remove the sanitize section once the bug is fixed.
sanitize: {
@@ -29,6 +28,8 @@
},
}
+// Rust wrappers round the C API for Rust clients.
+// (Yes, this involves going Rust -> C -> Rust.)
rust_bindgen {
name: "libvm_payload_bindgen",
wrapper_src: "include-restricted/vm_payload_restricted.h",
@@ -37,16 +38,38 @@
apex_available: ["com.android.compos"],
visibility: ["//packages/modules/Virtualization/compos"],
shared_libs: [
- "libvm_payload",
+ "libvm_payload#current",
],
}
+// Shared library for clients to link against.
+cc_library_shared {
+ name: "libvm_payload",
+ shared_libs: [
+ "libbinder_ndk",
+ "libbinder_rpc_unstable",
+ "liblog",
+ ],
+ whole_static_libs: ["libvm_payload_impl"],
+ export_static_lib_headers: ["libvm_payload_impl"],
+ installable: false,
+ version_script: "libvm_payload.map.txt",
+ stubs: {
+ symbol_file: "libvm_payload.map.txt",
+ // Implementation is available inside a Microdroid VM.
+ implementation_installable: false,
+ },
+}
+
+// Just the headers. Mostly useful for clients that only want the
+// declaration of AVmPayload_main().
cc_library_headers {
name: "vm_payload_headers",
apex_available: ["com.android.compos"],
export_include_dirs: ["include"],
}
+// Restricted headers for use by internal clients & associated tests.
cc_library_headers {
name: "vm_payload_restricted_headers",
header_libs: ["vm_payload_headers"],
diff --git a/vm_payload/include/vm_payload.h b/vm_payload/include/vm_payload.h
index 0ad4c64..7c224f6 100644
--- a/vm_payload/include/vm_payload.h
+++ b/vm_payload/include/vm_payload.h
@@ -18,6 +18,7 @@
#include <stdbool.h>
#include <stddef.h>
+#include <stdnoreturn.h>
#include <sys/cdefs.h>
#include "vm_main.h"
@@ -46,18 +47,17 @@
* called to allow appropriate action to be taken - e.g. to notify clients that they may now
* attempt to connect with `AVmPayload_notifyPayloadReady`.
*
- * The current thread is joined to the binder thread pool to handle incoming messages.
+ * Note that this function does not return. The calling thread joins the binder
+ * thread pool to handle incoming messages.
*
* \param service the service to bind to the given port.
* \param port vsock port.
* \param on_ready the callback to execute once the server is ready for connections. The callback
* will be called at most once.
* \param param param for the `on_ready` callback.
- *
- * \return true if the server has shutdown normally, false if it failed in some way.
*/
-bool AVmPayload_runVsockRpcServer(AIBinder *service, unsigned int port,
- void (*on_ready)(void *param), void *param);
+noreturn void AVmPayload_runVsockRpcServer(AIBinder *service, unsigned int port,
+ void (*on_ready)(void *param), void *param);
/**
* Get a secret that is uniquely bound to this VM instance. The secrets are
diff --git a/vm_payload/libvm_payload.map.txt b/vm_payload/libvm_payload.map.txt
new file mode 100644
index 0000000..a2402d1
--- /dev/null
+++ b/vm_payload/libvm_payload.map.txt
@@ -0,0 +1,12 @@
+LIBVM_PAYLOAD {
+ global:
+ AVmPayload_notifyPayloadReady; # systemapi
+ AVmPayload_runVsockRpcServer; # systemapi
+ AVmPayload_getVmInstanceSecret; # systemapi
+ AVmPayload_getDiceAttestationChain; # systemapi
+ AVmPayload_getDiceAttestationCdi; # systemapi
+ AVmPayload_getApkContentsPath; # systemapi
+ AVmPayload_getEncryptedStoragePath; # systemapi
+ local:
+ *;
+};
diff --git a/vm_payload/src/api.rs b/vm_payload/src/api.rs
index febc2be..a79c0bb 100644
--- a/vm_payload/src/api.rs
+++ b/vm_payload/src/api.rs
@@ -14,13 +14,17 @@
//! This module handles the interaction with virtual machine payload service.
+// We're implementing unsafe functions, but we still want warnings on unsafe usage within them.
+#![warn(unsafe_op_in_unsafe_fn)]
+
use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
IVmPayloadService, VM_PAYLOAD_SERVICE_SOCKET_NAME, VM_APK_CONTENTS_PATH};
-use anyhow::{ensure, Context, Result};
+use anyhow::{ensure, bail, Context, Result};
use binder::{Strong, unstable_api::{AIBinder, new_spibinder}};
use lazy_static::lazy_static;
use log::{error, info, Level};
use rpcbinder::{get_unix_domain_rpc_interface, RpcServer};
+use std::convert::Infallible;
use std::ffi::CString;
use std::fmt::Debug;
use std::os::raw::{c_char, c_void};
@@ -96,44 +100,55 @@
/// called to allow appropriate action to be taken - e.g. to notify clients that they may now
/// attempt to connect.
///
-/// The current thread is joined to the binder thread pool to handle incoming messages.
+/// The current thread joins the binder thread pool to handle incoming messages.
+/// This function never returns.
///
-/// Returns true if the server has shutdown normally, false if it failed in some way.
+/// Panics on error (including unexpected server exit).
///
/// # Safety
///
-/// The `on_ready` callback is only called inside `run_vsock_rpc_server`, within the lifetime of
-/// `ReadyNotifier` (the last parameter of `run_vsock_rpc_server`). If `on_ready` is called with
-/// wrong param, the callback execution could go wrong.
+/// If present, the `on_ready` callback must be a valid function pointer, which will be called at
+/// most once, while this function is executing, with the `param` parameter.
#[no_mangle]
pub unsafe extern "C" fn AVmPayload_runVsockRpcServer(
service: *mut AIBinder,
port: u32,
on_ready: Option<unsafe extern "C" fn(param: *mut c_void)>,
param: *mut c_void,
-) -> bool {
+) -> Infallible {
initialize_logging();
+ // SAFETY: try_run_vsock_server has the same requirements as this function
+ unwrap_or_abort(unsafe { try_run_vsock_server(service, port, on_ready, param) })
+}
+
+/// # Safety: Same as `AVmPayload_runVsockRpcServer`.
+unsafe fn try_run_vsock_server(
+ service: *mut AIBinder,
+ port: u32,
+ on_ready: Option<unsafe extern "C" fn(param: *mut c_void)>,
+ param: *mut c_void,
+) -> Result<Infallible> {
// SAFETY: AIBinder returned has correct reference count, and the ownership can
// safely be taken by new_spibinder.
- let service = new_spibinder(service);
+ let service = unsafe { new_spibinder(service) };
if let Some(service) = service {
match RpcServer::new_vsock(service, port) {
Ok(server) => {
if let Some(on_ready) = on_ready {
- on_ready(param);
+ // SAFETY: We're calling the callback with the parameter specified within the
+ // allowed lifetime.
+ unsafe { on_ready(param) };
}
server.join();
- true
+ bail!("RpcServer unexpectedly terminated");
}
Err(err) => {
- error!("Failed to start RpcServer: {:?}", err);
- false
+ bail!("Failed to start RpcServer: {:?}", err);
}
}
} else {
- error!("Failed to convert the given service from AIBinder to SpIBinder.");
- false
+ bail!("Failed to convert the given service from AIBinder to SpIBinder.");
}
}
@@ -157,9 +172,15 @@
) {
initialize_logging();
- let identifier = std::slice::from_raw_parts(identifier, identifier_size);
+ // SAFETY: See the requirements on `identifier` above.
+ let identifier = unsafe { std::slice::from_raw_parts(identifier, identifier_size) };
let vm_secret = unwrap_or_abort(try_get_vm_instance_secret(identifier, size));
- ptr::copy_nonoverlapping(vm_secret.as_ptr(), secret, size);
+
+ // SAFETY: See the requirements on `secret` above; `vm_secret` is known to have length `size`,
+ // and cannot overlap `secret` because we just allocated it.
+ unsafe {
+ ptr::copy_nonoverlapping(vm_secret.as_ptr(), secret, size);
+ }
}
fn try_get_vm_instance_secret(identifier: &[u8], size: usize) -> Result<Vec<u8>> {
@@ -190,7 +211,9 @@
initialize_logging();
let chain = unwrap_or_abort(try_get_dice_attestation_chain());
- ptr::copy_nonoverlapping(chain.as_ptr(), data, std::cmp::min(chain.len(), size));
+ // SAFETY: See the requirements on `data` above. The number of bytes copied doesn't exceed
+ // the length of either buffer, and `chain` cannot overlap `data` because we just allocated it.
+ unsafe { ptr::copy_nonoverlapping(chain.as_ptr(), data, std::cmp::min(chain.len(), size)) };
chain.len()
}
@@ -213,7 +236,9 @@
initialize_logging();
let cdi = unwrap_or_abort(try_get_dice_attestation_cdi());
- ptr::copy_nonoverlapping(cdi.as_ptr(), data, std::cmp::min(cdi.len(), size));
+ // SAFETY: See the requirements on `data` above. The number of bytes copied doesn't exceed
+ // the length of either buffer, and `cdi` cannot overlap `data` because we just allocated it.
+ unsafe { ptr::copy_nonoverlapping(cdi.as_ptr(), data, std::cmp::min(cdi.len(), size)) };
cdi.len()
}