Merge "Collect cpu/mem stats via VmExited atom"
diff --git a/apex/Android.bp b/apex/Android.bp
index e0ca9bf..2d6c757 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -15,20 +15,55 @@
"microdroid_vendor_boot",
]
-apex {
- name: "com.android.virt",
+soong_config_module_type {
+ name: "virt_apex",
+ module_type: "apex",
+ config_namespace: "ANDROID",
+ bool_variables: ["avf_enabled"],
+ properties: ["defaults"],
+}
+virt_apex {
+ name: "com.android.virt",
+ soong_config_variables: {
+ avf_enabled: {
+ defaults: ["com.android.virt_avf_enabled"],
+ conditions_default: {
+ defaults: ["com.android.virt_avf_disabled"],
+ },
+ },
+ },
+}
+
+apex_defaults {
+ name: "com.android.virt_common",
// TODO(jiyong): make it updatable
updatable: false,
future_updatable: true,
platform_apis: true,
- system_ext_specific: true,
-
manifest: "manifest.json",
key: "com.android.virt.key",
certificate: ":com.android.virt.certificate",
+
+ apps: [
+ "android.system.virtualmachine.res",
+ ],
+
+ file_contexts: ":com.android.virt-file_contexts",
+ canned_fs_config: "canned_fs_config",
+
+ bootclasspath_fragments: [
+ "com.android.virt-bootclasspath-fragment",
+ ],
+}
+
+apex_defaults {
+ name: "com.android.virt_avf_enabled",
+
+ defaults: ["com.android.virt_common"],
+
custom_sign_tool: "sign_virt_apex",
// crosvm and virtualizationservice are only enabled for 64-bit targets on device
@@ -52,17 +87,12 @@
"fd_server",
"vm",
],
- java_libs: [
- "android.system.virtualmachine",
- ],
jni_libs: [
"libvirtualmachine_jni",
],
- apps: [
- "android.system.virtualmachine.res",
- ],
prebuilts: [
"com.android.virt.init.rc",
+ "features_com.android.virt.xml",
"microdroid_initrd_app_debuggable",
"microdroid_initrd_full_debuggable",
"microdroid_initrd_normal",
@@ -71,13 +101,17 @@
"microdroid_bootloader.avbpubkey",
"microdroid_kernel",
],
- file_contexts: ":com.android.virt-file_contexts",
- canned_fs_config: "canned_fs_config",
host_required: [
"vm_shell",
],
}
+apex_defaults {
+ name: "com.android.virt_avf_disabled",
+
+ defaults: ["com.android.virt_common"],
+}
+
apex_key {
name: "com.android.virt.key",
public_key: "com.android.virt.avbpubkey",
@@ -174,3 +208,43 @@
},
},
}
+
+// Encapsulate the contributions made by the com.android.virt to the bootclasspath.
+bootclasspath_fragment {
+ name: "com.android.virt-bootclasspath-fragment",
+ contents: ["framework-virtualization"],
+ apex_available: ["com.android.virt"],
+
+ // The bootclasspath_fragments that provide APIs on which this depends.
+ fragments: [
+ {
+ apex: "com.android.art",
+ module: "art-bootclasspath-fragment",
+ },
+ ],
+
+ // Additional stubs libraries that this fragment's contents use which are
+ // not provided by another bootclasspath_fragment.
+ additional_stubs: [
+ "android-non-updatable",
+ ],
+
+ hidden_api: {
+
+ // This module does not contain any split packages.
+ split_packages: [],
+
+ // The following packages and all their subpackages currently only
+ // contain classes from this bootclasspath_fragment. Listing a package
+ // here won't prevent other bootclasspath modules from adding classes in
+ // any of those packages but it will prevent them from adding those
+ // classes into an API surface, e.g. public, system, etc.. Doing so will
+ // result in a build failure due to inconsistent flags.
+ package_prefixes: [
+ "android.system.virtualmachine",
+ "android.system.virtualizationservice",
+ // android.sysprop.*, renamed by jarjar
+ "com.android.system.virtualmachine.sysprop",
+ ],
+ },
+}
diff --git a/apex/permissions/Android.bp b/apex/permissions/Android.bp
new file mode 100644
index 0000000..0c925ce
--- /dev/null
+++ b/apex/permissions/Android.bp
@@ -0,0 +1,24 @@
+//
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+prebuilt_etc {
+ name: "features_com.android.virt.xml",
+ sub_dir: "permissions",
+ src: "features_com.android.virt.xml",
+}
diff --git a/apex/permissions/features_com.android.virt.xml b/apex/permissions/features_com.android.virt.xml
new file mode 100644
index 0000000..d2b32e6
--- /dev/null
+++ b/apex/permissions/features_com.android.virt.xml
@@ -0,0 +1,19 @@
+<?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.
+ -->
+<permissions>
+ <feature name="android.software.virtualization_framework" />
+</permissions>
diff --git a/apex/product_packages.mk b/apex/product_packages.mk
index 5bc0044..4293c80 100644
--- a/apex/product_packages.mk
+++ b/apex/product_packages.mk
@@ -21,7 +21,6 @@
PRODUCT_PACKAGES += \
com.android.compos \
- com.android.virt \
# TODO(b/207336449): Figure out how to get these off /system
PRODUCT_ARTIFACT_PATH_REQUIREMENT_ALLOWED_LIST := \
@@ -33,3 +32,5 @@
PRODUCT_SYSTEM_EXT_PROPERTIES := ro.config.isolated_compilation_enabled=true
PRODUCT_FSVERITY_GENERATE_METADATA := true
+
+PRODUCT_AVF_ENABLED := true
diff --git a/apkdmverity/src/main.rs b/apkdmverity/src/main.rs
index a69b583..6e12e38 100644
--- a/apkdmverity/src/main.rs
+++ b/apkdmverity/src/main.rs
@@ -162,7 +162,7 @@
}
fn create_block_aligned_file(path: &Path, data: &[u8]) {
- let mut f = File::create(&path).unwrap();
+ let mut f = File::create(path).unwrap();
f.write_all(data).unwrap();
// Add padding so that the size of the file is multiple of 4096.
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 68e1948..02459b2 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -240,15 +240,17 @@
struct Callback {}
impl vmclient::VmCallback for Callback {
- fn on_payload_started(&self, cid: i32, stream: Option<&File>) {
- if let Some(file) = stream {
- if let Err(e) = start_logging(file) {
- warn!("Can't log vm output: {}", e);
- };
- }
+ fn on_payload_started(&self, cid: i32) {
log::info!("VM payload started, cid = {}", cid);
}
+ fn on_payload_stdio(&self, cid: i32, stream: &File) {
+ if let Err(e) = start_logging(stream) {
+ log::warn!("Can't log vm output: {}", e);
+ };
+ log::info!("VM payload forwarded its stdio, cid = {}", cid);
+ }
+
fn on_payload_ready(&self, cid: i32) {
log::info!("VM payload ready, cid = {}", cid);
}
diff --git a/compos/src/artifact_signer.rs b/compos/src/artifact_signer.rs
index e51b8dd..d3843fc 100644
--- a/compos/src/artifact_signer.rs
+++ b/compos/src/artifact_signer.rs
@@ -46,7 +46,7 @@
pub fn add_artifact(&mut self, path: &Path) -> Result<()> {
// The path we store is where the file will be when it is verified, not where it is now.
let suffix = path
- .strip_prefix(&self.base_directory)
+ .strip_prefix(self.base_directory)
.context("Artifacts must be under base directory")?;
let target_path = Path::new(TARGET_DIRECTORY).join(suffix);
let target_path = target_path.to_str().ok_or_else(|| anyhow!("Invalid path"))?;
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 4330bbf..0e8b9f5 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -144,7 +144,7 @@
fn add_artifacts(target_dir: &Path, artifact_signer: &mut ArtifactSigner) -> Result<()> {
for entry in
- read_dir(&target_dir).with_context(|| format!("Traversing {}", target_dir.display()))?
+ read_dir(target_dir).with_context(|| format!("Traversing {}", target_dir.display()))?
{
let entry = entry?;
let file_type = entry.file_type()?;
diff --git a/compos/src/compsvc_main.rs b/compos/src/compsvc_main.rs
index a4e3903..c280956 100644
--- a/compos/src/compsvc_main.rs
+++ b/compos/src/compsvc_main.rs
@@ -24,10 +24,10 @@
use anyhow::{bail, Result};
use compos_common::COMPOS_VSOCK_PORT;
-use log::{debug, error};
+use log::{debug, error, warn};
use rpcbinder::run_vsock_rpc_server;
use std::panic;
-use vm_payload_bindgen::AVmPayload_notifyPayloadReady;
+use vm_payload_bindgen::{AVmPayload_notifyPayloadReady, AVmPayload_setupStdioProxy};
fn main() {
if let Err(e) = try_main() {
@@ -44,6 +44,10 @@
panic::set_hook(Box::new(|panic_info| {
error!("{}", panic_info);
}));
+ // Redirect stdio to the host.
+ if !unsafe { AVmPayload_setupStdioProxy() } {
+ warn!("Failed to setup stdio proxy");
+ }
let service = compsvc::new_binder()?.as_binder();
debug!("compsvc is starting as a rpc service.");
diff --git a/demo/Android.bp b/demo/Android.bp
index 8613166..5241e25 100644
--- a/demo/Android.bp
+++ b/demo/Android.bp
@@ -13,7 +13,7 @@
"com.google.android.material_material",
],
libs: [
- "android.system.virtualmachine",
+ "framework-virtualization",
],
jni_libs: ["MicrodroidTestNativeLib"],
platform_apis: true,
diff --git a/demo/AndroidManifest.xml b/demo/AndroidManifest.xml
index 6669adb..17a7680 100644
--- a/demo/AndroidManifest.xml
+++ b/demo/AndroidManifest.xml
@@ -4,11 +4,11 @@
<uses-permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE" />
<uses-sdk android:minSdkVersion="33" android:targetSdkVersion="33"/>
+ <uses-feature android:name="android.software.virtualization_framework" android:required="true" />
<application
android:label="MicrodroidDemo"
android:theme="@style/Theme.MicrodroidDemo"
android:testOnly="true">
- <uses-library android:name="android.system.virtualmachine" android:required="true" />
<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/demo/java/com/android/microdroid/demo/MainActivity.java b/demo/java/com/android/microdroid/demo/MainActivity.java
index b5ae3d5..ebc2bb3 100644
--- a/demo/java/com/android/microdroid/demo/MainActivity.java
+++ b/demo/java/com/android/microdroid/demo/MainActivity.java
@@ -169,13 +169,11 @@
private final ExecutorService mService = mExecutorService;
@Override
- public void onPayloadStarted(VirtualMachine vm,
- ParcelFileDescriptor stream) {
- if (stream == null) {
- mPayloadOutput.postValue("(no output available)");
- return;
- }
+ public void onPayloadStarted(VirtualMachine vm) {}
+ @Override
+ public void onPayloadStdio(VirtualMachine vm, ParcelFileDescriptor stream) {
+ mPayloadOutput.postValue("(Payload connected standard output...)");
InputStream input = new FileInputStream(stream.getFileDescriptor());
mService.execute(new Reader("payload", mPayloadOutput, input));
}
diff --git a/docs/getting_started/index.md b/docs/getting_started/index.md
index 245aba6..5f552f9 100644
--- a/docs/getting_started/index.md
+++ b/docs/getting_started/index.md
@@ -133,7 +133,8 @@
--debug full \
/data/local/tmp/virt/MicrodroidDemoApp.apk \
/data/local/tmp/virt/MicrodroidDemoApp.apk.idsig \
- /data/local/tmp/virt/instance.img assets/vm_config.json
+ /data/local/tmp/virt/instance.img \
+ --payload-path MicrodroidTestNativeLib.so
```
## Building and updating CrosVM and VirtualizationService {#building-and-updating}
diff --git a/javalib/Android.bp b/javalib/Android.bp
index cb03fa1..04ed273 100644
--- a/javalib/Android.bp
+++ b/javalib/Android.bp
@@ -11,10 +11,20 @@
}
java_sdk_library {
- name: "android.system.virtualmachine",
- installable: true,
+ name: "framework-virtualization",
+ installable: false,
compile_dex: true,
+ // TODO(b/243512044): introduce non-updatable-framework-module-defaults
+
+ defaults: ["framework-module-defaults"],
+
+ shared_library: false,
+
+ default_to_stubs: false,
+
+ dist_group: "android",
+
jarjar_rules: "jarjar-rules.txt",
srcs: ["src/**/*.java"],
@@ -25,6 +35,7 @@
],
apex_available: ["com.android.virt"],
+
permitted_packages: [
"android.system.virtualmachine",
"android.system.virtualizationservice",
@@ -38,6 +49,38 @@
"-Xep:GuardedBy:ERROR",
],
},
+
+ public: {
+ enabled: true,
+ sdk_version: "module_current",
+ },
+
+ system: {
+ enabled: true,
+ sdk_version: "module_current",
+ },
+
+ module_lib: {
+ enabled: true,
+ sdk_version: "module_current",
+ },
+
+ test: {
+ enabled: true,
+ sdk_version: "module_current",
+ },
+
+ sdk_version: "core_platform",
+ platform_apis: true,
+ impl_only_libs: [
+ "framework",
+ ],
+ impl_library_visibility: [
+ "//frameworks/base",
+ ],
+
+ // Temporary workaround, will be removed in a follow-up child cl.
+ unsafe_ignore_missing_latest_api: true,
}
prebuilt_apis {
diff --git a/javalib/api/module-lib-current.txt b/javalib/api/module-lib-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/javalib/api/module-lib-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/javalib/api/module-lib-removed.txt b/javalib/api/module-lib-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/javalib/api/module-lib-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index 7c826b6..c200d00 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -16,6 +16,7 @@
package android.system.virtualmachine;
+import static android.os.ParcelFileDescriptor.AutoCloseInputStream;
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
import static android.system.virtualmachine.VirtualMachineCallback.ERROR_PAYLOAD_CHANGED;
@@ -76,6 +77,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
+import java.nio.channels.FileChannel;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
@@ -285,10 +287,45 @@
return sInstances.computeIfAbsent(context, unused -> new HashMap<>());
}
+ /**
+ * Builds a virtual machine from an {@link VirtualMachineDescriptor} object and associates it
+ * with the given name.
+ *
+ * <p>The new virtual machine will be in the same state as the descriptor indicates.
+ *
+ * <p>Once a virtual machine is imported it is persisted until it is deleted by calling {@link
+ * #delete}. The imported virtual machine is in {@link #STATUS_STOPPED} state. To run the VM,
+ * call {@link #run}.
+ */
+ @GuardedBy("sCreateLock")
@NonNull
- private static File getVmDir(Context context, String name) {
- File vmRoot = new File(context.getDataDir(), VM_DIR);
- return new File(vmRoot, name);
+ static VirtualMachine fromDescriptor(
+ @NonNull Context context,
+ @NonNull String name,
+ @NonNull VirtualMachineDescriptor vmDescriptor)
+ throws VirtualMachineException {
+ VirtualMachineConfig config = VirtualMachineConfig.from(vmDescriptor.getConfigFd());
+ File vmDir = createVmDir(context, name);
+ try {
+ VirtualMachine vm = new VirtualMachine(context, name, config);
+ config.serialize(vm.mConfigFilePath);
+ try {
+ vm.mInstanceFilePath.createNewFile();
+ } catch (IOException e) {
+ throw new VirtualMachineException("failed to create instance image", e);
+ }
+ vm.importInstanceFrom(vmDescriptor.getInstanceImgFd());
+ getInstancesMap(context).put(name, new WeakReference<>(vm));
+ return vm;
+ } catch (VirtualMachineException | RuntimeException e) {
+ // If anything goes wrong, delete any files created so far and the VM's directory
+ try {
+ deleteRecursively(vmDir);
+ } catch (IOException innerException) {
+ e.addSuppressed(innerException);
+ }
+ throw e;
+ }
}
/**
@@ -301,31 +338,11 @@
static VirtualMachine create(
@NonNull Context context, @NonNull String name, @NonNull VirtualMachineConfig config)
throws VirtualMachineException {
- File vmDir = getVmDir(context, name);
-
- try {
- // We don't need to undo this even if VM creation fails.
- Files.createDirectories(vmDir.getParentFile().toPath());
-
- // The checking of the existence of this directory and the creation of it is done
- // atomically. If the directory already exists (i.e. the VM with the same name was
- // already created), FileAlreadyExistsException is thrown.
- Files.createDirectory(vmDir.toPath());
- } catch (FileAlreadyExistsException e) {
- throw new VirtualMachineException("virtual machine already exists", e);
- } catch (IOException e) {
- throw new VirtualMachineException("failed to create directory for VM", e);
- }
+ File vmDir = createVmDir(context, name);
try {
VirtualMachine vm = new VirtualMachine(context, name, config);
-
- try (FileOutputStream output = new FileOutputStream(vm.mConfigFilePath)) {
- config.serialize(output);
- } catch (IOException e) {
- throw new VirtualMachineException("failed to write VM config", e);
- }
-
+ config.serialize(vm.mConfigFilePath);
try {
vm.mInstanceFilePath.createNewFile();
} catch (IOException e) {
@@ -374,13 +391,7 @@
return null;
}
File configFilePath = new File(thisVmDir, CONFIG_FILE);
- VirtualMachineConfig config;
- try (FileInputStream input = new FileInputStream(configFilePath)) {
- config = VirtualMachineConfig.from(input);
- } catch (IOException e) {
- throw new VirtualMachineException("Failed to read config file", e);
- }
-
+ VirtualMachineConfig config = VirtualMachineConfig.from(configFilePath);
Map<String, WeakReference<VirtualMachine>> instancesMap = getInstancesMap(context);
VirtualMachine vm = null;
@@ -425,6 +436,33 @@
if (instancesMap != null) instancesMap.remove(name);
}
+ @GuardedBy("sCreateLock")
+ @NonNull
+ private static File createVmDir(@NonNull Context context, @NonNull String name)
+ throws VirtualMachineException {
+ File vmDir = getVmDir(context, name);
+ try {
+ // We don't need to undo this even if VM creation fails.
+ Files.createDirectories(vmDir.getParentFile().toPath());
+
+ // The checking of the existence of this directory and the creation of it is done
+ // atomically. If the directory already exists (i.e. the VM with the same name was
+ // already created), FileAlreadyExistsException is thrown.
+ Files.createDirectory(vmDir.toPath());
+ } catch (FileAlreadyExistsException e) {
+ throw new VirtualMachineException("virtual machine already exists", e);
+ } catch (IOException e) {
+ throw new VirtualMachineException("failed to create directory for VM", e);
+ }
+ return vmDir;
+ }
+
+ @NonNull
+ private static File getVmDir(Context context, String name) {
+ File vmRoot = new File(context.getDataDir(), VM_DIR);
+ return new File(vmRoot, name);
+ }
+
/**
* Returns the name of this virtual machine. The name is unique in the package and can't be
* changed.
@@ -643,9 +681,14 @@
mVirtualMachine.registerCallback(
new IVirtualMachineCallback.Stub() {
@Override
- public void onPayloadStarted(int cid, ParcelFileDescriptor stream) {
+ public void onPayloadStarted(int cid) {
+ executeCallback((cb) -> cb.onPayloadStarted(VirtualMachine.this));
+ }
+
+ @Override
+ public void onPayloadStdio(int cid, ParcelFileDescriptor stream) {
executeCallback(
- (cb) -> cb.onPayloadStarted(VirtualMachine.this, stream));
+ (cb) -> cb.onPayloadStdio(VirtualMachine.this, stream));
}
@Override
@@ -656,16 +699,20 @@
@Override
public void onPayloadFinished(int cid, int exitCode) {
executeCallback(
- (cb) -> cb.onPayloadFinished(VirtualMachine.this,
- exitCode));
+ (cb) ->
+ cb.onPayloadFinished(
+ VirtualMachine.this, exitCode));
}
@Override
public void onError(int cid, int errorCode, String message) {
int translatedError = getTranslatedError(errorCode);
executeCallback(
- (cb) -> cb.onError(VirtualMachine.this, translatedError,
- message));
+ (cb) ->
+ cb.onError(
+ VirtualMachine.this,
+ translatedError,
+ message));
}
@Override
@@ -674,18 +721,17 @@
int translatedReason = getTranslatedReason(reason);
if (onDiedCalled.compareAndSet(false, true)) {
executeCallback(
- (cb) -> cb.onStopped(VirtualMachine.this,
- translatedReason));
+ (cb) ->
+ cb.onStopped(
+ VirtualMachine.this, translatedReason));
}
}
@Override
public void onRamdump(int cid, ParcelFileDescriptor ramdump) {
- executeCallback(
- (cb) -> cb.onRamdump(VirtualMachine.this, ramdump));
+ executeCallback((cb) -> cb.onRamdump(VirtualMachine.this, ramdump));
}
- }
- );
+ });
service.asBinder().linkToDeath(deathRecipient, 0);
mVirtualMachine.start();
} catch (IOException | IllegalStateException | ServiceSpecificException e) {
@@ -838,14 +884,7 @@
throw new VirtualMachineException("incompatible config");
}
checkStopped();
-
- try {
- FileOutputStream output = new FileOutputStream(mConfigFilePath);
- newConfig.serialize(output);
- output.close();
- } catch (IOException e) {
- throw new VirtualMachineException("Failed to persist config", e);
- }
+ newConfig.serialize(mConfigFilePath);
mConfig = newConfig;
return oldConfig;
}
@@ -895,22 +934,23 @@
}
/**
- * Captures the current state of the VM in a {@link ParcelVirtualMachine} instance.
- * The VM needs to be stopped to avoid inconsistency in its state representation.
+ * Captures the current state of the VM in a {@link VirtualMachineDescriptor} instance. The VM
+ * needs to be stopped to avoid inconsistency in its state representation.
*
- * @return a {@link ParcelVirtualMachine} instance that represents the VM's state.
+ * @return a {@link VirtualMachineDescriptor} instance that represents the VM's state.
* @throws VirtualMachineException if the virtual machine is not stopped, or the state could not
* be captured.
+ * @hide
*/
@NonNull
- public ParcelVirtualMachine toParcelVirtualMachine() throws VirtualMachineException {
+ public VirtualMachineDescriptor toDescriptor() throws VirtualMachineException {
synchronized (mLock) {
checkStopped();
}
try {
- return new ParcelVirtualMachine(
- ParcelFileDescriptor.open(mConfigFilePath, MODE_READ_ONLY),
- ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_ONLY));
+ return new VirtualMachineDescriptor(
+ ParcelFileDescriptor.open(mConfigFilePath, MODE_READ_ONLY),
+ ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_ONLY));
} catch (IOException e) {
throw new VirtualMachineException(e);
}
@@ -1065,4 +1105,14 @@
throw new VirtualMachineException("Couldn't parse extra apks from the vm config", e);
}
}
+
+ private void importInstanceFrom(@NonNull ParcelFileDescriptor instanceFd)
+ throws VirtualMachineException {
+ try (FileChannel instance = new FileOutputStream(mInstanceFilePath).getChannel();
+ FileChannel instanceInput = new AutoCloseInputStream(instanceFd).getChannel()) {
+ instance.transferFrom(instanceInput, /*position=*/ 0, instanceInput.size());
+ } catch (IOException e) {
+ throw new VirtualMachineException("failed to transfer instance image", e);
+ }
+ }
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
index bb6b2b8..26b8ba2 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
@@ -18,7 +18,6 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.os.ParcelFileDescriptor;
@@ -135,11 +134,11 @@
/** The VM killed due to hangup */
int STOP_REASON_HANGUP = 16;
- /**
- * Called when the payload starts in the VM. The stream, if non-null, provides access
- * to the stdin/stdout of the VM payload.
- */
- void onPayloadStarted(@NonNull VirtualMachine vm, @Nullable ParcelFileDescriptor stream);
+ /** Called when the payload starts in the VM. */
+ void onPayloadStarted(@NonNull VirtualMachine vm);
+
+ /** Called when the payload creates a standard input/output stream. */
+ void onPayloadStdio(@NonNull VirtualMachine vm, @NonNull ParcelFileDescriptor stream);
/**
* Called when the payload in the VM is ready to serve. See
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index b814367..a660306 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -16,6 +16,7 @@
package android.system.virtualmachine;
+import static android.os.ParcelFileDescriptor.AutoCloseInputStream;
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
import static java.util.Objects.requireNonNull;
@@ -33,7 +34,9 @@
import android.system.virtualizationservice.VirtualMachinePayloadConfig;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -181,9 +184,30 @@
mNumCpus = numCpus;
}
+ /** Loads a config from a file. */
+ @NonNull
+ static VirtualMachineConfig from(@NonNull File file) throws VirtualMachineException {
+ try (FileInputStream input = new FileInputStream(file)) {
+ return fromInputStream(input);
+ } catch (IOException e) {
+ throw new VirtualMachineException("Failed to read VM config from file", e);
+ }
+ }
+
+ /** Loads a config from a {@link ParcelFileDescriptor}. */
+ @NonNull
+ static VirtualMachineConfig from(@NonNull ParcelFileDescriptor fd)
+ throws VirtualMachineException {
+ try (AutoCloseInputStream input = new AutoCloseInputStream(fd)) {
+ return fromInputStream(input);
+ } catch (IOException e) {
+ throw new VirtualMachineException("failed to read VM config from file descriptor", e);
+ }
+ }
+
/** Loads a config from a stream, for example a file. */
@NonNull
- static VirtualMachineConfig from(@NonNull InputStream input)
+ private static VirtualMachineConfig fromInputStream(@NonNull InputStream input)
throws IOException, VirtualMachineException {
PersistableBundle b = PersistableBundle.readFromStream(input);
int version = b.getInt(KEY_VERSION);
@@ -215,8 +239,17 @@
protectedVm, memoryMib, numCpus);
}
+ /** Persists this config to a file. */
+ void serialize(@NonNull File file) throws VirtualMachineException {
+ try (FileOutputStream output = new FileOutputStream(file)) {
+ serializeOutputStream(output);
+ } catch (IOException e) {
+ throw new VirtualMachineException("failed to write VM config", e);
+ }
+ }
+
/** Persists this config to a stream, for example a file. */
- void serialize(@NonNull OutputStream output) throws IOException {
+ private void serializeOutputStream(@NonNull OutputStream output) throws IOException {
PersistableBundle b = new PersistableBundle();
b.putInt(KEY_VERSION, VERSION);
b.putString(KEY_APKPATH, mApkPath);
diff --git a/javalib/src/android/system/virtualmachine/ParcelVirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
similarity index 61%
rename from javalib/src/android/system/virtualmachine/ParcelVirtualMachine.java
rename to javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
index 808f30a..b51cbce 100644
--- a/javalib/src/android/system/virtualmachine/ParcelVirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
@@ -23,20 +23,18 @@
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
-import com.android.internal.annotations.VisibleForTesting;
-
/**
- * A parcelable that captures the state of a Virtual Machine.
+ * A VM descriptor that captures the state of a Virtual Machine.
*
* <p>You can capture the current state of VM by creating an instance of this class with {@link
- * VirtualMachine#toParcelVirtualMachine()}, optionally pass it to another App, and then build an
- * identical VM with the parcel received.
+ * VirtualMachine#toDescriptor()}, optionally pass it to another App, and then build an identical VM
+ * with the descriptor received.
*
* @hide
*/
-public final class ParcelVirtualMachine implements Parcelable {
- private final @NonNull ParcelFileDescriptor mConfigFd;
- private final @NonNull ParcelFileDescriptor mInstanceImgFd;
+public final class VirtualMachineDescriptor implements Parcelable {
+ @NonNull private final ParcelFileDescriptor mConfigFd;
+ @NonNull private final ParcelFileDescriptor mInstanceImgFd;
// TODO(b/243129654): Add trusted storage fd once it is available.
@Override
@@ -45,47 +43,46 @@
}
@Override
- public void writeToParcel(Parcel out, int flags) {
+ public void writeToParcel(@NonNull Parcel out, int flags) {
mConfigFd.writeToParcel(out, flags);
mInstanceImgFd.writeToParcel(out, flags);
}
- public static final Parcelable.Creator<ParcelVirtualMachine> CREATOR =
- new Parcelable.Creator<ParcelVirtualMachine>() {
- public ParcelVirtualMachine createFromParcel(Parcel in) {
- return new ParcelVirtualMachine(in);
+ @NonNull
+ public static final Parcelable.Creator<VirtualMachineDescriptor> CREATOR =
+ new Parcelable.Creator<>() {
+ public VirtualMachineDescriptor createFromParcel(Parcel in) {
+ return new VirtualMachineDescriptor(in);
}
- public ParcelVirtualMachine[] newArray(int size) {
- return new ParcelVirtualMachine[size];
+ public VirtualMachineDescriptor[] newArray(int size) {
+ return new VirtualMachineDescriptor[size];
}
};
/**
* @return File descriptor of the VM configuration file config.xml.
- * @hide
*/
- @VisibleForTesting
- public @NonNull ParcelFileDescriptor getConfigFd() {
+ @NonNull
+ ParcelFileDescriptor getConfigFd() {
return mConfigFd;
}
/**
* @return File descriptor of the instance.img of the VM.
- * @hide
*/
- @VisibleForTesting
- public @NonNull ParcelFileDescriptor getInstanceImgFd() {
+ @NonNull
+ ParcelFileDescriptor getInstanceImgFd() {
return mInstanceImgFd;
}
- ParcelVirtualMachine(
+ VirtualMachineDescriptor(
@NonNull ParcelFileDescriptor configFd, @NonNull ParcelFileDescriptor instanceImgFd) {
mConfigFd = configFd;
mInstanceImgFd = instanceImgFd;
}
- private ParcelVirtualMachine(Parcel in) {
+ private VirtualMachineDescriptor(Parcel in) {
mConfigFd = requireNonNull(in.readFileDescriptor());
mInstanceImgFd = requireNonNull(in.readFileDescriptor());
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index 34b9fd9..c357f50 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -142,6 +142,24 @@
}
/**
+ * Imports a virtual machine from an {@link VirtualMachineDescriptor} object and associates it
+ * with the given name.
+ *
+ * <p>The new virtual machine will be in the same state as the descriptor indicates.
+ *
+ * @throws VirtualMachineException if the VM cannot be imported.
+ * @hide
+ */
+ @NonNull
+ public VirtualMachine importFromDescriptor(
+ @NonNull String name, @NonNull VirtualMachineDescriptor vmDescriptor)
+ throws VirtualMachineException {
+ synchronized (VirtualMachine.sCreateLock) {
+ return VirtualMachine.fromDescriptor(mContext, name, vmDescriptor);
+ }
+ }
+
+ /**
* Returns an existing {@link VirtualMachine} with the given name. Returns null if there is no
* such virtual machine.
*
diff --git a/libs/devicemapper/src/lib.rs b/libs/devicemapper/src/lib.rs
index b9fb5c3..ebe71e4 100644
--- a/libs/devicemapper/src/lib.rs
+++ b/libs/devicemapper/src/lib.rs
@@ -212,7 +212,7 @@
dm_dev_suspend(self, &mut data).context("failed to activate")?;
// Step 4: wait unti the device is created and return the device path
- let path = Path::new(MAPPER_DEV_ROOT).join(&name);
+ let path = Path::new(MAPPER_DEV_ROOT).join(name);
wait_for_path(&path)?;
Ok(path)
}
@@ -250,13 +250,13 @@
}
fn write_to_dev(path: &Path, data: &[u8]) {
- let mut f = OpenOptions::new().read(true).write(true).open(&path).unwrap();
+ let mut f = OpenOptions::new().read(true).write(true).open(path).unwrap();
f.write_all(data).unwrap();
}
fn delete_device(dm: &DeviceMapper, name: &str) -> Result<()> {
dm.delete_device_deferred(name)?;
- wait_for_path_disappears(Path::new(MAPPER_DEV_ROOT).join(&name))?;
+ wait_for_path_disappears(Path::new(MAPPER_DEV_ROOT).join(name))?;
Ok(())
}
diff --git a/microdroid/README.md b/microdroid/README.md
index 2519416..41278a5 100644
--- a/microdroid/README.md
+++ b/microdroid/README.md
@@ -141,7 +141,7 @@
PATH_TO_YOUR_APP \
$TEST_ROOT/MyApp.apk.idsig \
$TEST_ROOT/instance.img \
-assets/VM_CONFIG_FILE
+--config-path assets/VM_CONFIG_FILE
```
The last command lets you know the CID assigned to the VM. The console output
diff --git a/microdroid/vm_payload/Android.bp b/microdroid/vm_payload/Android.bp
index e153f92..dd2a937 100644
--- a/microdroid/vm_payload/Android.bp
+++ b/microdroid/vm_payload/Android.bp
@@ -14,6 +14,7 @@
"libanyhow",
"libbinder_rs",
"liblazy_static",
+ "liblibc",
"liblog_rust",
"librpcbinder_rs",
],
diff --git a/microdroid/vm_payload/include/vm_payload.h b/microdroid/vm_payload/include/vm_payload.h
index 82dbd6d..d5853a1 100644
--- a/microdroid/vm_payload/include/vm_payload.h
+++ b/microdroid/vm_payload/include/vm_payload.h
@@ -80,4 +80,13 @@
*/
const char *AVmPayload_getApkContentsPath(void);
+/**
+ * Initiates a socket connection with the host and duplicates stdin, stdout and
+ * stderr file descriptors to the socket.
+ *
+ * \return true on success and false on failure. If unsuccessful, the stdio FDs
+ * may be in an inconsistent state.
+ */
+bool AVmPayload_setupStdioProxy();
+
__END_DECLS
diff --git a/microdroid/vm_payload/src/lib.rs b/microdroid/vm_payload/src/lib.rs
index be6cf93..65b59bf 100644
--- a/microdroid/vm_payload/src/lib.rs
+++ b/microdroid/vm_payload/src/lib.rs
@@ -18,5 +18,5 @@
pub use vm_payload_service::{
AVmPayload_getDiceAttestationCdi, AVmPayload_getDiceAttestationChain,
- AVmPayload_getVmInstanceSecret, AVmPayload_notifyPayloadReady,
+ AVmPayload_getVmInstanceSecret, AVmPayload_notifyPayloadReady, AVmPayload_setupStdioProxy,
};
diff --git a/microdroid/vm_payload/src/vm_payload_service.rs b/microdroid/vm_payload/src/vm_payload_service.rs
index 098d246..e89f730 100644
--- a/microdroid/vm_payload/src/vm_payload_service.rs
+++ b/microdroid/vm_payload/src/vm_payload_service.rs
@@ -21,8 +21,11 @@
use lazy_static::lazy_static;
use log::{error, info, Level};
use rpcbinder::{get_unix_domain_rpc_interface, run_vsock_rpc_server};
+use std::io;
use std::ffi::CString;
+use std::fs::File;
use std::os::raw::{c_char, c_void};
+use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd};
lazy_static! {
static ref VM_APK_CONTENTS_PATH_C: CString =
@@ -202,6 +205,36 @@
get_vm_payload_service()?.getDiceAttestationCdi().context("Cannot get attestation CDI")
}
+/// Creates a socket connection with the host and duplicates standard I/O
+/// file descriptors of the payload to that socket. Then notifies the host.
+#[no_mangle]
+pub extern "C" fn AVmPayload_setupStdioProxy() -> bool {
+ if let Err(e) = try_setup_stdio_proxy() {
+ error!("{:?}", e);
+ false
+ } else {
+ info!("Successfully set up stdio proxy to the host");
+ true
+ }
+}
+
+fn dup2(old_fd: &File, new_fd: BorrowedFd) -> Result<(), io::Error> {
+ // SAFETY - ownership does not change, only modifies the underlying raw FDs.
+ match unsafe { libc::dup2(old_fd.as_raw_fd(), new_fd.as_raw_fd()) } {
+ -1 => Err(io::Error::last_os_error()),
+ _ => Ok(()),
+ }
+}
+
+fn try_setup_stdio_proxy() -> Result<()> {
+ let fd =
+ get_vm_payload_service()?.setupStdioProxy().context("Could not connect a host socket")?;
+ dup2(fd.as_ref(), io::stdin().as_fd()).context("Failed to dup stdin")?;
+ dup2(fd.as_ref(), io::stdout().as_fd()).context("Failed to dup stdout")?;
+ dup2(fd.as_ref(), io::stderr().as_fd()).context("Failed to dup stderr")?;
+ Ok(())
+}
+
fn get_vm_payload_service() -> Result<Strong<dyn IVmPayloadService>> {
get_unix_domain_rpc_interface(VM_PAYLOAD_SERVICE_SOCKET_NAME)
.context(format!("Failed to connect to service: {}", VM_PAYLOAD_SERVICE_SOCKET_NAME))
diff --git a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
index f8e7d34..1141965 100644
--- a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
+++ b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
@@ -16,6 +16,8 @@
package android.system.virtualization.payload;
+import android.os.ParcelFileDescriptor;
+
/**
* This interface regroups the tasks that payloads delegate to
* Microdroid Manager for execution.
@@ -61,4 +63,16 @@
* @throws SecurityException if the use of test APIs is not permitted.
*/
byte[] getDiceAttestationCdi();
+
+ /**
+ * Sets up a standard I/O proxy to the host.
+ *
+ * Creates a socket with the host and notifies its listeners that the stdio
+ * proxy is ready.
+ *
+ * Temporarily uses a random free port allocated by the OS.
+ * @return a file descriptor that the payload should dup() its standard I/O
+ * file descriptors to.
+ */
+ ParcelFileDescriptor setupStdioProxy();
}
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index c18dd26..762a149 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -26,7 +26,7 @@
use crate::vm_payload_service::register_vm_payload_service;
use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::ErrorCode::ErrorCode;
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
- IVirtualMachineService, VM_BINDER_SERVICE_PORT, VM_STREAM_SERVICE_PORT,
+ IVirtualMachineService, VM_BINDER_SERVICE_PORT,
};
use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::VM_APK_CONTENTS_PATH;
use anyhow::{anyhow, bail, ensure, Context, Error, Result};
@@ -49,15 +49,13 @@
use std::borrow::Cow::{Borrowed, Owned};
use std::convert::TryInto;
use std::env;
-use std::fs::{self, create_dir, File, OpenOptions};
+use std::fs::{self, create_dir, OpenOptions};
use std::io::Write;
-use std::os::unix::io::{FromRawFd, IntoRawFd};
use std::os::unix::process::ExitStatusExt;
use std::path::Path;
use std::process::{Child, Command, Stdio};
use std::str;
use std::time::{Duration, SystemTime};
-use vsock::VsockStream;
const WAIT_TIMEOUT: Duration = Duration::from_secs(10);
const MAIN_APK_PATH: &str = "/dev/block/by-name/microdroid-apk";
@@ -732,7 +730,14 @@
/// virtualizationservice in the host side.
fn exec_task(task: &Task, service: &Strong<dyn IVirtualMachineService>) -> Result<i32> {
info!("executing main task {:?}...", task);
- let mut command = build_command(task)?;
+ let mut command = match task.type_ {
+ TaskType::Executable => Command::new(&task.command),
+ TaskType::MicrodroidLauncher => {
+ let mut command = Command::new("/system/bin/microdroid_launcher");
+ command.arg(find_library_path(&task.command)?);
+ command
+ }
+ };
info!("notifying payload started");
service.notifyPayloadStarted()?;
@@ -751,40 +756,6 @@
}
}
-fn build_command(task: &Task) -> Result<Command> {
- let mut command = match task.type_ {
- TaskType::Executable => Command::new(&task.command),
- TaskType::MicrodroidLauncher => {
- let mut command = Command::new("/system/bin/microdroid_launcher");
- command.arg(find_library_path(&task.command)?);
- command
- }
- };
-
- match VsockStream::connect_with_cid_port(VMADDR_CID_HOST, VM_STREAM_SERVICE_PORT as u32) {
- Ok(stream) => {
- // SAFETY: the ownership of the underlying file descriptor is transferred from stream
- // to the file object, and then into the Command object. When the command is finished,
- // the file descriptor is closed.
- let file = unsafe { File::from_raw_fd(stream.into_raw_fd()) };
- command
- .stdin(Stdio::from(file.try_clone()?))
- .stdout(Stdio::from(file.try_clone()?))
- .stderr(Stdio::from(file));
- }
- Err(e) => {
- error!("failed to connect to virtualization service: {}", e);
- // Don't fail hard here. Even if we failed to connect to the virtualizationservice,
- // we keep executing the task. This can happen if the owner of the VM doesn't register
- // callback to accept the stream. Use /dev/null as the stream so that the task can
- // make progress without waiting for someone to consume the output.
- command.stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());
- }
- }
-
- Ok(command)
-}
-
fn find_library_path(name: &str) -> Result<String> {
let mut watcher = PropertyWatcher::new("ro.product.cpu.abilist")?;
let value = watcher.read(|_name, value| Ok(value.trim().to_string()))?;
diff --git a/microdroid_manager/src/vm_payload_service.rs b/microdroid_manager/src/vm_payload_service.rs
index fcfc79d..249a2d8 100644
--- a/microdroid_manager/src/vm_payload_service.rs
+++ b/microdroid_manager/src/vm_payload_service.rs
@@ -18,15 +18,18 @@
use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
BnVmPayloadService, IVmPayloadService, VM_PAYLOAD_SERVICE_SOCKET_NAME};
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
-use anyhow::{bail, Result};
-use binder::{Interface, BinderFeatures, ExceptionCode, Status, Strong};
+use anyhow::{bail, Context, Result};
+use binder::{Interface, BinderFeatures, ExceptionCode, ParcelFileDescriptor, Status, Strong};
use log::{error, info};
use openssl::hkdf::hkdf;
use openssl::md::Md;
use rpcbinder::run_init_unix_domain_rpc_server;
+use std::fs::File;
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
+use std::os::unix::io::{FromRawFd, IntoRawFd};
+use vsock::VsockListener;
/// Implementation of `IVmPayloadService`.
struct VmPayloadService {
@@ -67,6 +70,16 @@
self.check_restricted_apis_allowed()?;
Ok(self.dice.cdi_attest.to_vec())
}
+
+ fn setupStdioProxy(&self) -> binder::Result<ParcelFileDescriptor> {
+ let f = self.setup_payload_stdio_proxy().map_err(|e| {
+ Status::new_service_specific_error_str(
+ -1,
+ Some(format!("Failed to create stdio proxy: {:?}", e)),
+ )
+ })?;
+ Ok(ParcelFileDescriptor::new(f))
+ }
}
impl Interface for VmPayloadService {}
@@ -89,6 +102,22 @@
Err(Status::new_exception_str(ExceptionCode::SECURITY, Some("Use of restricted APIs")))
}
}
+
+ fn setup_payload_stdio_proxy(&self) -> Result<File> {
+ // Instead of a predefined port in the host, we open up a port in the guest and have
+ // the host connect to it. This makes it possible to have per-app instances of VS.
+ const ANY_PORT: u32 = 0;
+ let listener = VsockListener::bind_with_cid_port(libc::VMADDR_CID_HOST, ANY_PORT)
+ .context("Failed to create vsock listener")?;
+ let addr = listener.local_addr().context("Failed to resolve listener port")?;
+ self.virtual_machine_service
+ .connectPayloadStdioProxy(addr.port() as i32)
+ .context("Failed to connect to the host")?;
+ let (stream, _) =
+ listener.accept().context("Failed to accept vsock connection from the host")?;
+ // SAFETY: ownership is transferred from stream to the new File
+ Ok(unsafe { File::from_raw_fd(stream.into_raw_fd()) })
+ }
}
/// Registers the `IVmPayloadService` service.
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index 71bac72..77de696 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -8,6 +8,9 @@
defaults: ["vmbase_ffi_defaults"],
srcs: ["src/main.rs"],
edition: "2021",
+ features: [
+ "legacy",
+ ],
rustlibs: [
"libbuddy_system_allocator",
"liblog_rust_nostd",
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index dc2087d..c0ad878 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -19,11 +19,15 @@
use crate::mmio_guard;
use core::arch::asm;
use core::slice;
-use log::{debug, error, LevelFilter};
-use vmbase::{console, logger, main, power::reboot};
+use log::debug;
+use log::error;
+use log::LevelFilter;
+use vmbase::{console, layout, logger, main, power::reboot};
#[derive(Debug, Clone)]
enum RebootReason {
+ /// A malformed BCC was received.
+ InvalidBcc,
/// An unexpected internal error happened.
InternalError,
}
@@ -80,8 +84,17 @@
RebootReason::InternalError
})?;
+ // SAFETY - We only get the appended payload from here, once. It is mapped and the linker
+ // script prevents it from overlapping with other objects.
+ let bcc = as_bcc(unsafe { get_appended_data_slice() }).ok_or_else(|| {
+ error!("Invalid BCC");
+ RebootReason::InvalidBcc
+ })?;
+
// This wrapper allows main() to be blissfully ignorant of platform details.
- crate::main(fdt, payload);
+ crate::main(fdt, payload, bcc);
+
+ // TODO: Overwrite BCC before jumping to payload to avoid leaking our sealing key.
mmio_guard::unmap(console::BASE_ADDRESS).map_err(|e| {
error!("Failed to unshare the UART: {e}");
@@ -147,3 +160,22 @@
);
};
}
+
+unsafe fn get_appended_data_slice() -> &'static mut [u8] {
+ let base = helpers::align_up(layout::binary_end(), helpers::SIZE_4KB).unwrap();
+ // pvmfw is contained in a 2MiB region so the payload can't be larger than the 2MiB alignment.
+ let size = helpers::align_up(base, helpers::SIZE_2MB).unwrap() - base;
+
+ slice::from_raw_parts_mut(base as *mut u8, size)
+}
+
+fn as_bcc(data: &mut [u8]) -> Option<&mut [u8]> {
+ const BCC_SIZE: usize = helpers::SIZE_4KB;
+
+ if cfg!(feature = "legacy") {
+ // TODO(b/256148034): return None if BccHandoverParse(bcc) != kDiceResultOk.
+ Some(&mut data[..BCC_SIZE])
+ } else {
+ None
+ }
+}
diff --git a/pvmfw/src/helpers.rs b/pvmfw/src/helpers.rs
index adfc189..59cf9f3 100644
--- a/pvmfw/src/helpers.rs
+++ b/pvmfw/src/helpers.rs
@@ -17,12 +17,25 @@
pub const SIZE_4KB: usize = 4 << 10;
pub const SIZE_2MB: usize = 2 << 20;
-/// Computes the address of the page containing a given address.
-pub const fn page_of(addr: usize, page_size: usize) -> usize {
- addr & !(page_size - 1)
+/// Computes the largest multiple of the provided alignment smaller or equal to the address.
+///
+/// Note: the result is undefined if alignment isn't a power of two.
+pub const fn unchecked_align_down(addr: usize, alignment: usize) -> usize {
+ addr & !(alignment - 1)
+}
+
+/// Safe wrapper around unchecked_align_up() that validates its assumptions and doesn't wrap.
+pub const fn align_up(addr: usize, alignment: usize) -> Option<usize> {
+ if !alignment.is_power_of_two() {
+ None
+ } else if let Some(s) = addr.checked_add(alignment - 1) {
+ Some(unchecked_align_down(s, alignment))
+ } else {
+ None
+ }
}
/// Computes the address of the 4KiB page containing a given address.
pub const fn page_4kb_of(addr: usize) -> usize {
- page_of(addr, SIZE_4KB)
+ unchecked_align_down(addr, SIZE_4KB)
}
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index c0bb263..8178d0b 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -29,7 +29,7 @@
use avb::PUBLIC_KEY;
use log::{debug, info};
-fn main(fdt: &mut [u8], payload: &[u8]) {
+fn main(fdt: &mut [u8], payload: &[u8], bcc: &[u8]) {
info!("pVM firmware");
debug!(
"fdt_address={:#018x}, payload_start={:#018x}, payload_size={:#018x}",
@@ -37,6 +37,7 @@
payload.as_ptr() as usize,
payload.len(),
);
+ debug!("BCC: {:?} ({:#x} bytes)", bcc.as_ptr(), bcc.len());
debug!("AVB public key: addr={:?}, size={:#x} ({1})", PUBLIC_KEY.as_ptr(), PUBLIC_KEY.len());
info!("Starting payload...");
}
diff --git a/tests/aidl/com/android/microdroid/testservice/IBenchmarkService.aidl b/tests/aidl/com/android/microdroid/testservice/IBenchmarkService.aidl
index 260f804..16e4893 100644
--- a/tests/aidl/com/android/microdroid/testservice/IBenchmarkService.aidl
+++ b/tests/aidl/com/android/microdroid/testservice/IBenchmarkService.aidl
@@ -25,7 +25,7 @@
*
* @return The read rate in MB/s.
*/
- double measureReadRate(String filename, long fileSizeBytes, boolean isRand);
+ double measureReadRate(String filename, boolean isRand);
/** Returns an entry from /proc/meminfo. */
long getMemInfoEntry(String name);
diff --git a/tests/benchmark/Android.bp b/tests/benchmark/Android.bp
index e6f39f8..bccea6b 100644
--- a/tests/benchmark/Android.bp
+++ b/tests/benchmark/Android.bp
@@ -16,7 +16,7 @@
"com.android.microdroid.testservice-java",
"truth-prebuilt",
],
- libs: ["android.system.virtualmachine"],
+ libs: ["framework-virtualization"],
jni_libs: [
"MicrodroidBenchmarkNativeLib",
"MicrodroidIdleNativeLib",
@@ -28,20 +28,11 @@
}
cc_library_shared {
- name: "MicrodroidIdleNativeLib",
- srcs: ["src/native/idlebinary.cpp"],
- header_libs: ["vm_payload_headers"],
- shared_libs: [
- "libbase",
- ],
-}
-
-cc_library_shared {
name: "MicrodroidBenchmarkNativeLib",
- srcs: ["src/native/benchmarkbinary.cpp"],
+ srcs: ["src/native/*.cpp"],
+ local_include_dirs: ["src/native/include"],
static_libs: [
"com.android.microdroid.testservice-ndk",
- "libiobenchmark",
],
shared_libs: [
"libbase",
@@ -50,12 +41,3 @@
"libvm_payload",
],
}
-
-cc_library {
- name: "libiobenchmark",
- srcs: ["src/native/io_vsock.cpp"],
- export_include_dirs: ["src/native/include"],
- shared_libs: [
- "libbase",
- ],
-}
diff --git a/tests/benchmark/AndroidManifest.xml b/tests/benchmark/AndroidManifest.xml
index c39b91c..8a7366a 100644
--- a/tests/benchmark/AndroidManifest.xml
+++ b/tests/benchmark/AndroidManifest.xml
@@ -18,7 +18,6 @@
<uses-permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE" />
<uses-permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE" />
<application>
- <uses-library android:name="android.system.virtualmachine" android:required="false" />
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.microdroid.benchmark"
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 db74358..28852e8 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -257,18 +257,10 @@
private static class VirtioBlkListener implements BenchmarkVmListener.InnerListener {
private static final String FILENAME = APEX_ETC_FS + "microdroid_super.img";
- private final long mFileSizeBytes;
private final List<Double> mReadRates;
private final boolean mIsRand;
VirtioBlkListener(List<Double> readRates, boolean isRand) {
- File file = new File(FILENAME);
- try {
- mFileSizeBytes = Files.size(file.toPath());
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- assertThat(mFileSizeBytes).isGreaterThan((long) SIZE_MB);
mReadRates = readRates;
mIsRand = isRand;
}
@@ -276,7 +268,7 @@
@Override
public void onPayloadReady(VirtualMachine vm, IBenchmarkService benchmarkService)
throws RemoteException {
- double readRate = benchmarkService.measureReadRate(FILENAME, mFileSizeBytes, mIsRand);
+ double readRate = benchmarkService.measureReadRate(FILENAME, mIsRand);
mReadRates.add(readRate);
}
}
diff --git a/tests/benchmark/src/native/benchmarkbinary.cpp b/tests/benchmark/src/native/benchmarkbinary.cpp
index 6321c25..e43025c 100644
--- a/tests/benchmark/src/native/benchmarkbinary.cpp
+++ b/tests/benchmark/src/native/benchmarkbinary.cpp
@@ -23,6 +23,8 @@
#include <fcntl.h>
#include <linux/vm_sockets.h>
#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <vm_main.h>
@@ -56,9 +58,9 @@
class IOBenchmarkService : public aidl::com::android::microdroid::testservice::BnBenchmarkService {
public:
- ndk::ScopedAStatus measureReadRate(const std::string& filename, int64_t fileSizeBytes,
- bool isRand, double* out) override {
- auto res = measure_read_rate(filename, fileSizeBytes, isRand);
+ ndk::ScopedAStatus measureReadRate(const std::string& filename, bool isRand,
+ double* out) override {
+ auto res = measure_read_rate(filename, isRand);
if (res.ok()) {
*out = res.value();
}
@@ -90,13 +92,20 @@
}
private:
- /** Measures the read rate for reading the given file. */
- Result<double> measure_read_rate(const std::string& filename, int64_t fileSizeBytes,
- bool is_rand) {
- const int64_t block_count = fileSizeBytes / kBlockSizeBytes;
+ /**
+ * Measures the read rate for reading the given file.
+ * @return The read rate in MB/s.
+ */
+ Result<double> measure_read_rate(const std::string& filename, bool is_rand) {
+ struct stat file_stats;
+ if (stat(filename.c_str(), &file_stats) == -1) {
+ return Error() << "failed to get file stats";
+ }
+ const int64_t file_size_bytes = file_stats.st_size;
+ const int64_t block_count = file_size_bytes / kBlockSizeBytes;
std::vector<uint64_t> offsets(block_count);
for (auto i = 0; i < block_count; ++i) {
- offsets.push_back(i * kBlockSizeBytes);
+ offsets[i] = i * kBlockSizeBytes;
}
if (is_rand) {
std::mt19937 rd{std::random_device{}()};
@@ -118,8 +127,8 @@
}
}
double elapsed_seconds = ((double)clock() - start) / CLOCKS_PER_SEC;
- double read_rate = (double)fileSizeBytes / kNumBytesPerMB / elapsed_seconds;
- return {read_rate};
+ double file_size_mb = (double)file_size_bytes / kNumBytesPerMB;
+ return {file_size_mb / elapsed_seconds};
}
Result<size_t> read_meminfo_entry(const std::string& stat) {
diff --git a/tests/helper/Android.bp b/tests/helper/Android.bp
index 60d4be1..bd92020 100644
--- a/tests/helper/Android.bp
+++ b/tests/helper/Android.bp
@@ -24,5 +24,5 @@
"VirtualizationTestHelper",
"truth-prebuilt",
],
- libs: ["android.system.virtualmachine"],
+ libs: ["framework-virtualization"],
}
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 9fb7d91..d1e1f6c 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
@@ -15,9 +15,9 @@
*/
package com.android.microdroid.test.device;
-import static com.google.common.truth.TruthJUnit.assume;
+import static android.content.pm.PackageManager.FEATURE_VIRTUALIZATION_FRAMEWORK;
-import static org.junit.Assume.assumeNoException;
+import static com.google.common.truth.TruthJUnit.assume;
import android.app.Instrumentation;
import android.app.UiAutomation;
@@ -119,17 +119,12 @@
}
public void prepareTestSetup(boolean protectedVm) {
- // In case when the virt APEX doesn't exist on the device, classes in the
- // android.system.virtualmachine package can't be loaded. Therefore, before using the
- // classes, check the existence of a class in the package and skip this test if not exist.
- try {
- Class.forName("android.system.virtualmachine.VirtualMachineManager");
- } catch (ClassNotFoundException e) {
- assumeNoException(e);
- return;
- }
- Context context = ApplicationProvider.getApplicationContext();
- mInner = new Inner(context, protectedVm, VirtualMachineManager.getInstance(context));
+ Context ctx = ApplicationProvider.getApplicationContext();
+ assume().withMessage("Device doesn't support AVF")
+ .that(ctx.getPackageManager().hasSystemFeature(FEATURE_VIRTUALIZATION_FRAMEWORK))
+ .isTrue();
+
+ mInner = new Inner(ctx, protectedVm, VirtualMachineManager.getInstance(ctx));
int capabilities = mInner.getVirtualMachineManager().getCapabilities();
if (protectedVm) {
@@ -232,7 +227,10 @@
}
@Override
- public void onPayloadStarted(VirtualMachine vm, ParcelFileDescriptor stream) {}
+ public void onPayloadStarted(VirtualMachine vm) {}
+
+ @Override
+ public void onPayloadStdio(VirtualMachine vm, ParcelFileDescriptor stream) {}
@Override
public void onPayloadReady(VirtualMachine vm) {}
@@ -327,7 +325,7 @@
VmEventListener listener =
new VmEventListener() {
@Override
- public void onPayloadStarted(VirtualMachine vm, ParcelFileDescriptor stream) {
+ public void onPayloadStarted(VirtualMachine vm) {
endTime.complete(System.nanoTime());
payloadStarted.complete(true);
forceStop(vm);
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
index 43fe615..9bcd1d3 100644
--- a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
@@ -100,6 +100,9 @@
public static void testIfDeviceIsCapable(ITestDevice androidDevice) throws Exception {
assumeTrue("Need an actual TestDevice", androidDevice instanceof TestDevice);
TestDevice testDevice = (TestDevice) androidDevice;
+ assumeTrue(
+ "Requires VM support",
+ testDevice.hasFeature("android.software.virtualization_framework"));
assumeTrue("Requires VM support", testDevice.supportsMicrodroid());
}
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index 8972046..8d49721 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -19,7 +19,7 @@
"truth-prebuilt",
"compatibility-common-util-devicesidelib",
],
- libs: ["android.system.virtualmachine"],
+ libs: ["framework-virtualization"],
jni_libs: [
"MicrodroidTestNativeLib",
"MicrodroidIdleNativeLib",
@@ -55,3 +55,10 @@
srcs: ["src/native/testlib.cpp"],
stl: "libc++_static",
}
+
+cc_library_shared {
+ name: "MicrodroidIdleNativeLib",
+ srcs: ["src/native/idlebinary.cpp"],
+ header_libs: ["vm_payload_headers"],
+ stl: "libc++_static",
+}
diff --git a/tests/testapk/AndroidManifest.xml b/tests/testapk/AndroidManifest.xml
index ab22546..fefd20a 100644
--- a/tests/testapk/AndroidManifest.xml
+++ b/tests/testapk/AndroidManifest.xml
@@ -18,8 +18,8 @@
<uses-permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE" />
<uses-permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE" />
<uses-sdk android:minSdkVersion="33" android:targetSdkVersion="33" />
+ <uses-feature android:name="android.software.virtualization_framework" android:required="false" />
<application>
- <uses-library android:name="android.system.virtualmachine" android:required="false" />
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.microdroid.test"
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 cc623a8..5e86798 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -30,10 +30,10 @@
import android.os.ParcelFileDescriptor;
import android.os.ServiceSpecificException;
import android.os.SystemProperties;
-import android.system.virtualmachine.ParcelVirtualMachine;
import android.system.virtualmachine.VirtualMachine;
import android.system.virtualmachine.VirtualMachineCallback;
import android.system.virtualmachine.VirtualMachineConfig;
+import android.system.virtualmachine.VirtualMachineDescriptor;
import android.system.virtualmachine.VirtualMachineException;
import android.system.virtualmachine.VirtualMachineManager;
import android.util.Log;
@@ -61,6 +61,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Arrays;
import java.util.List;
import java.util.OptionalLong;
import java.util.UUID;
@@ -600,31 +601,46 @@
}
@Test
- public void vmConvertsToValidParcelVm() throws Exception {
+ public void importedVmIsEqualToTheOriginalVm() throws Exception {
// Arrange
VirtualMachineConfig config =
mInner.newVmConfigBuilder()
.setPayloadBinaryPath("MicrodroidTestNativeLib.so")
.setDebugLevel(DEBUG_LEVEL_NONE)
.build();
- String vmName = "test_vm";
- VirtualMachine vm = mInner.forceCreateNewVirtualMachine(vmName, config);
+ String vmNameOrig = "test_vm_orig", vmNameImport = "test_vm_import";
+ VirtualMachine vmOrig = mInner.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();
+ if (vmm.get(vmNameImport) != null) {
+ vmm.delete(vmNameImport);
+ }
// Action
- ParcelVirtualMachine parcelVm = vm.toParcelVirtualMachine();
+ VirtualMachine vmImport = vmm.importFromDescriptor(vmNameImport, descriptor);
// Asserts
- assertFileContentsAreEqual(parcelVm.getConfigFd(), vmName, "config.xml");
- assertFileContentsAreEqual(parcelVm.getInstanceImgFd(), vmName, "instance.img");
+ assertFileContentsAreEqualInTwoVms("config.xml", vmNameOrig, vmNameImport);
+ assertFileContentsAreEqualInTwoVms("instance.img", vmNameOrig, vmNameImport);
+ assertThat(vmImport).isNotEqualTo(vmOrig);
+ vmm.delete(vmNameOrig);
+ assertThat(vmImport).isEqualTo(vmm.get(vmNameImport));
+ TestResults testResults = runVmTestService(vmImport);
+ assertThat(testResults.mException).isNull();
+ assertThat(testResults.mAddInteger).isEqualTo(123 + 456);
}
- private void assertFileContentsAreEqual(
- ParcelFileDescriptor parcelFd, String vmName, String fileName) throws IOException {
- File file = getVmFile(vmName, fileName);
- // Use try-with-resources to close the files automatically after assert.
- try (FileInputStream input1 = new FileInputStream(parcelFd.getFileDescriptor());
- FileInputStream input2 = new FileInputStream(file)) {
- assertThat(input1.readAllBytes()).isEqualTo(input2.readAllBytes());
+ private void assertFileContentsAreEqualInTwoVms(String fileName, String vmName1, String vmName2)
+ throws IOException {
+ File file1 = getVmFile(vmName1, fileName);
+ File file2 = getVmFile(vmName2, fileName);
+ try (FileInputStream input1 = new FileInputStream(file1);
+ FileInputStream input2 = new FileInputStream(file2)) {
+ assertThat(Arrays.equals(input1.readAllBytes(), input2.readAllBytes())).isTrue();
}
}
@@ -671,8 +687,9 @@
new VmEventListener() {
private void testVMService(VirtualMachine vm) {
try {
- ITestService testService = ITestService.Stub.asInterface(
- vm.connectToVsockServer(ITestService.SERVICE_PORT));
+ ITestService testService =
+ ITestService.Stub.asInterface(
+ vm.connectToVsockServer(ITestService.SERVICE_PORT));
testResults.mAddInteger = testService.addInteger(123, 456);
testResults.mAppRunProp =
testService.readProperty("debug.microdroid.app.run");
@@ -695,11 +712,16 @@
}
@Override
- public void onPayloadStarted(VirtualMachine vm, ParcelFileDescriptor stream) {
+ public void onPayloadStarted(VirtualMachine vm) {
Log.i(TAG, "onPayloadStarted");
payloadStarted.complete(true);
- logVmOutput(TAG, new FileInputStream(stream.getFileDescriptor()),
- "Payload");
+ }
+
+ @Override
+ public void onPayloadStdio(VirtualMachine vm, ParcelFileDescriptor stream) {
+ Log.i(TAG, "onPayloadStdio");
+ logVmOutput(
+ TAG, new FileInputStream(stream.getFileDescriptor()), "Payload");
}
};
listener.runToFinish(TAG, vm);
diff --git a/tests/benchmark/src/native/idlebinary.cpp b/tests/testapk/src/native/idlebinary.cpp
similarity index 100%
rename from tests/benchmark/src/native/idlebinary.cpp
rename to tests/testapk/src/native/idlebinary.cpp
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index 48942dc..1b18ce9 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -158,6 +158,9 @@
} // Anonymous namespace
extern "C" int AVmPayload_main() {
+ // Forward standard I/O to the host.
+ AVmPayload_setupStdioProxy();
+
// disable buffering to communicate seamlessly
setvbuf(stdin, nullptr, _IONBF, 0);
setvbuf(stdout, nullptr, _IONBF, 0);
diff --git a/virtualizationservice/aidl/Android.bp b/virtualizationservice/aidl/Android.bp
index 4d5326a..da237f8 100644
--- a/virtualizationservice/aidl/Android.bp
+++ b/virtualizationservice/aidl/Android.bp
@@ -39,6 +39,9 @@
imports: ["android.system.virtualizationcommon"],
unstable: true,
backend: {
+ java: {
+ sdk_version: "module_current",
+ },
rust: {
enabled: true,
apex_available: [
@@ -55,6 +58,7 @@
unstable: true,
backend: {
java: {
+ sdk_version: "module_current",
apex_available: ["com.android.virt"],
},
ndk: {
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
index 8d6ed08..521cf12 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
@@ -24,13 +24,14 @@
*/
oneway interface IVirtualMachineCallback {
/**
- * Called when the payload starts in the VM. `stream` is the input/output port of the payload.
- *
- * <p>Note: when the virtual machine object is shared to multiple processes and they register
- * this callback to the same virtual machine object, the processes will compete to access the
- * same payload stream. Keep only one process to access the stream.
+ * Called when the payload starts in the VM.
*/
- void onPayloadStarted(int cid, in @nullable ParcelFileDescriptor stream);
+ void onPayloadStarted(int cid);
+
+ /**
+ * Called when the payload provides access to its standard input/output via a socket.
+ */
+ void onPayloadStdio(int cid, in ParcelFileDescriptor fd);
/**
* Called when the payload in the VM is ready to serve.
diff --git a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
index e8c1724..deee662 100644
--- a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
@@ -21,12 +21,6 @@
interface IVirtualMachineService {
/**
* Port number that VirtualMachineService listens on connections from the guest VMs for the
- * payload input and output.
- */
- const int VM_STREAM_SERVICE_PORT = 3000;
-
- /**
- * Port number that VirtualMachineService listens on connections from the guest VMs for the
* VirtualMachineService binder service.
*/
const int VM_BINDER_SERVICE_PORT = 5000;
@@ -53,7 +47,12 @@
void notifyPayloadFinished(int exitCode);
/**
- * Notifies that an error has occurred inside the VM..
+ * Notifies that an error has occurred inside the VM.
*/
void notifyError(ErrorCode errorCode, in String message);
+
+ /**
+ * Notifies that the guest has started a stdio proxy on the given port.
+ */
+ void connectPayloadStdioProxy(int port);
}
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 00a46bf..30b89da 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -38,7 +38,7 @@
};
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
BnVirtualMachineService, IVirtualMachineService, VM_BINDER_SERVICE_PORT,
- VM_STREAM_SERVICE_PORT, VM_TOMBSTONES_SERVICE_PORT,
+ VM_TOMBSTONES_SERVICE_PORT,
};
use anyhow::{anyhow, bail, Context, Result};
use apkverify::{HashAlgorithm, V4Signature};
@@ -301,12 +301,6 @@
pub fn init() -> VirtualizationService {
let service = VirtualizationService::default();
- // server for payload output
- let state = service.state.clone(); // reference to state (not the state itself) is copied
- std::thread::spawn(move || {
- handle_stream_connection_from_vm(state).unwrap();
- });
-
std::thread::spawn(|| {
if let Err(e) = handle_stream_connection_tombstoned() {
warn!("Error receiving tombstone from guest or writing them. Error: {:?}", e);
@@ -488,33 +482,6 @@
}
}
-/// Waits for incoming connections from VM. If a new connection is made, stores the stream in the
-/// corresponding `VmInstance`.
-fn handle_stream_connection_from_vm(state: Arc<Mutex<State>>) -> Result<()> {
- let listener =
- VsockListener::bind_with_cid_port(VMADDR_CID_HOST, VM_STREAM_SERVICE_PORT as u32)?;
- for stream in listener.incoming() {
- let stream = match stream {
- Err(e) => {
- warn!("invalid incoming connection: {:?}", e);
- continue;
- }
- Ok(s) => s,
- };
- if let Ok(addr) = stream.peer_addr() {
- let cid = addr.cid();
- let port = addr.port();
- info!("payload stream connected from cid={}, port={}", cid, port);
- if let Some(vm) = state.lock().unwrap().get_vm(cid) {
- *vm.stream.lock().unwrap() = Some(stream);
- } else {
- error!("connection from cid={} is not from a guest VM", cid);
- }
- }
- }
- Ok(())
-}
-
fn write_zero_filler(zero_filler_path: &Path) -> Result<()> {
let file = OpenOptions::new()
.create_new(true)
@@ -533,8 +500,7 @@
}
fn prepare_ramdump_file(ramdump_path: &Path) -> Result<File> {
- File::create(&ramdump_path)
- .context(format!("Failed to create ramdump file {:?}", &ramdump_path))
+ File::create(ramdump_path).context(format!("Failed to create ramdump file {:?}", &ramdump_path))
}
/// Given the configuration for a disk image, assembles the `DiskFile` to pass to crosvm.
@@ -854,11 +820,10 @@
impl VirtualMachineCallbacks {
/// Call all registered callbacks to notify that the payload has started.
- pub fn notify_payload_started(&self, cid: Cid, stream: Option<VsockStream>) {
+ pub fn notify_payload_started(&self, cid: Cid) {
let callbacks = &*self.0.lock().unwrap();
- let pfd = stream.map(vsock_stream_to_pfd);
for callback in callbacks {
- if let Err(e) = callback.onPayloadStarted(cid as i32, pfd.as_ref()) {
+ if let Err(e) = callback.onPayloadStarted(cid as i32) {
error!("Error notifying payload start event from VM CID {}: {:?}", cid, e);
}
}
@@ -894,6 +859,16 @@
}
}
+ /// Call all registered callbacks to notify that the payload has provided a standard I/O proxy.
+ pub fn notify_payload_stdio(&self, cid: Cid, fd: ParcelFileDescriptor) {
+ let callbacks = &*self.0.lock().unwrap();
+ for callback in callbacks {
+ if let Err(e) = callback.onPayloadStdio(cid as i32, &fd) {
+ error!("Error notifying payload stdio event from VM CID {}: {:?}", cid, e);
+ }
+ }
+ }
+
/// Call all registered callbacks to say that the VM has died.
pub fn callback_on_died(&self, cid: Cid, reason: DeathReason) {
let callbacks = &*self.0.lock().unwrap();
@@ -1072,8 +1047,7 @@
vm.update_payload_state(PayloadState::Started).map_err(|e| {
Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some(e.to_string()))
})?;
- let stream = vm.stream.lock().unwrap().take();
- vm.callbacks.notify_payload_started(cid, stream);
+ vm.callbacks.notify_payload_started(cid);
let vm_start_timestamp = vm.vm_metric.lock().unwrap().start_timestamp;
write_vm_booted_stats(vm.requester_uid as i32, &vm.name, vm_start_timestamp);
@@ -1140,6 +1114,27 @@
))
}
}
+
+ fn connectPayloadStdioProxy(&self, port: i32) -> binder::Result<()> {
+ let cid = self.cid;
+ if let Some(vm) = self.state.lock().unwrap().get_vm(cid) {
+ info!("VM with CID {} started a stdio proxy", cid);
+ let stream = VsockStream::connect_with_cid_port(cid, port as u32).map_err(|e| {
+ Status::new_service_specific_error_str(
+ -1,
+ Some(format!("Failed to connect to guest stdio proxy: {:?}", e)),
+ )
+ })?;
+ vm.callbacks.notify_payload_stdio(cid, vsock_stream_to_pfd(stream));
+ Ok(())
+ } else {
+ error!("connectPayloadStdioProxy is called from an unknown CID {}", cid);
+ Err(Status::new_service_specific_error_str(
+ -1,
+ Some(format!("cannot find a VM with CID {}", cid)),
+ ))
+ }
+ }
}
impl VirtualMachineService {
diff --git a/virtualizationservice/src/composite.rs b/virtualizationservice/src/composite.rs
index c9a68ac..fe17ff4 100644
--- a/virtualizationservice/src/composite.rs
+++ b/virtualizationservice/src/composite.rs
@@ -51,7 +51,7 @@
OpenOptions::new().create_new(true).read(true).write(true).open(footer_path).with_context(
|| format!("Failed to create composite image header {:?}", footer_path),
)?;
- let zero_filler_file = File::open(&zero_filler_path).with_context(|| {
+ let zero_filler_file = File::open(zero_filler_path).with_context(|| {
format!("Failed to open composite image zero filler {:?}", zero_filler_path)
})?;
@@ -66,7 +66,7 @@
)?;
// Re-open the composite image as read-only.
- let composite_image = File::open(&output_path)
+ let composite_image = File::open(output_path)
.with_context(|| format!("Failed to open composite image {:?}", output_path))?;
files.push(header_file);
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index 2ada1ec..db6da43 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -37,7 +37,6 @@
use std::sync::{Arc, Condvar, Mutex};
use std::time::{Duration, SystemTime};
use std::thread;
-use vsock::VsockStream;
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::DeathReason::DeathReason;
use binder::Strong;
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
@@ -218,8 +217,6 @@
pub requester_debug_pid: i32,
/// Callbacks to clients of the VM.
pub callbacks: VirtualMachineCallbacks,
- /// Input/output stream of the payload run in the VM.
- pub stream: Mutex<Option<VsockStream>>,
/// VirtualMachineService binder object for the VM.
pub vm_service: Mutex<Option<Strong<dyn IVirtualMachineService>>>,
/// Recorded metrics of VM such as timestamp or cpu / memory usage.
@@ -251,7 +248,6 @@
requester_uid,
requester_debug_pid,
callbacks: Default::default(),
- stream: Mutex::new(None),
vm_service: Mutex::new(None),
vm_metric: Mutex::new(Default::default()),
payload_state: Mutex::new(PayloadState::Starting),
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 3b887d3..89d56d4 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -48,8 +48,13 @@
instance: PathBuf,
/// Path to VM config JSON within APK (e.g. assets/vm_config.json)
+ #[clap(long)]
config_path: Option<String>,
+ /// Path to VM payload binary within APK (e.g. MicrodroidTestNativeLib.so)
+ #[clap(long)]
+ payload_path: Option<String>,
+
/// Name of VM
#[clap(long)]
name: Option<String>,
@@ -201,6 +206,7 @@
storage,
storage_size,
config_path,
+ payload_path,
daemonize,
console,
log,
@@ -219,7 +225,8 @@
&instance,
storage.as_deref(),
storage_size,
- config_path.as_deref().unwrap_or(""),
+ config_path,
+ payload_path,
daemonize,
console.as_deref(),
log.as_deref(),
diff --git a/vm/src/run.rs b/vm/src/run.rs
index de8f1c0..7cd5a19 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -20,6 +20,7 @@
PartitionType::PartitionType,
VirtualMachineAppConfig::{DebugLevel::DebugLevel, Payload::Payload, VirtualMachineAppConfig},
VirtualMachineConfig::VirtualMachineConfig,
+ VirtualMachinePayloadConfig::VirtualMachinePayloadConfig,
VirtualMachineState::VirtualMachineState,
};
use anyhow::{bail, Context, Error};
@@ -43,7 +44,8 @@
instance: &Path,
storage: Option<&Path>,
storage_size: Option<u64>,
- config_path: &str,
+ config_path: Option<String>,
+ payload_path: Option<String>,
daemonize: bool,
console_path: Option<&Path>,
log_path: Option<&Path>,
@@ -57,7 +59,11 @@
) -> Result<(), Error> {
let apk_file = File::open(apk).context("Failed to open APK file")?;
- let extra_apks = parse_extra_apk_list(apk, config_path)?;
+ let extra_apks = match config_path.as_deref() {
+ Some(path) => parse_extra_apk_list(apk, path)?,
+ None => vec![],
+ };
+
if extra_apks.len() != extra_idsigs.len() {
bail!(
"Found {} extra apks, but there are {} extra idsigs",
@@ -108,6 +114,19 @@
let extra_idsig_files: Result<Vec<File>, _> = extra_idsigs.iter().map(File::open).collect();
let extra_idsig_fds = extra_idsig_files?.into_iter().map(ParcelFileDescriptor::new).collect();
+ let payload = if let Some(config_path) = config_path {
+ if payload_path.is_some() {
+ bail!("Only one of --config-path or --payload-path can be defined")
+ }
+ Payload::ConfigPath(config_path)
+ } else if let Some(payload_path) = payload_path {
+ Payload::PayloadConfig(VirtualMachinePayloadConfig { payloadPath: payload_path })
+ } else {
+ bail!("Either --config-path or --payload-path must be defined")
+ };
+
+ let payload_config_str = format!("{:?}!{:?}", apk, payload);
+
let config = VirtualMachineConfig::AppConfig(VirtualMachineAppConfig {
name: name.unwrap_or_else(|| String::from("VmRunApp")),
apk: apk_fd.into(),
@@ -115,22 +134,14 @@
extraIdsigs: extra_idsig_fds,
instanceImage: open_parcel_file(instance, true /* writable */)?.into(),
encryptedStorageImage: storage,
- payload: Payload::ConfigPath(config_path.to_owned()),
+ payload,
debugLevel: debug_level,
protectedVm: protected,
memoryMib: mem.unwrap_or(0) as i32, // 0 means use the VM default
numCpus: cpus.unwrap_or(1) as i32,
taskProfiles: task_profiles,
});
- run(
- service,
- &config,
- &format!("{:?}!{:?}", apk, config_path),
- daemonize,
- console_path,
- log_path,
- ramdump_path,
- )
+ run(service, &config, &payload_config_str, daemonize, console_path, log_path, ramdump_path)
}
/// Run a VM from the given configuration file.
@@ -187,7 +198,7 @@
fn run(
service: &dyn IVirtualizationService,
config: &VirtualMachineConfig,
- config_path: &str,
+ payload_config: &str,
daemonize: bool,
console_path: Option<&Path>,
log_path: Option<&Path>,
@@ -221,7 +232,7 @@
println!(
"Created VM from {} with CID {}, state is {}.",
- config_path,
+ payload_config,
vm.cid(),
state_to_str(vm.state()?)
);
@@ -265,19 +276,22 @@
struct Callback {}
impl vmclient::VmCallback for Callback {
- fn on_payload_started(&self, _cid: i32, stream: Option<&File>) {
+ fn on_payload_started(&self, _cid: i32) {
+ eprintln!("payload started");
+ }
+
+ fn on_payload_stdio(&self, _cid: i32, stream: &File) {
+ eprintln!("connecting payload stdio...");
// Show the output of the payload
- if let Some(stream) = stream {
- let mut reader = BufReader::new(stream.try_clone().unwrap());
- std::thread::spawn(move || loop {
- let mut s = String::new();
- match reader.read_line(&mut s) {
- Ok(0) => break,
- Ok(_) => print!("{}", s),
- Err(e) => eprintln!("error reading from virtual machine: {}", e),
- };
- });
- }
+ let mut reader = BufReader::new(stream.try_clone().unwrap());
+ std::thread::spawn(move || loop {
+ let mut s = String::new();
+ match reader.read_line(&mut s) {
+ Ok(0) => break,
+ Ok(_) => print!("{}", s),
+ Err(e) => eprintln!("error reading from virtual machine: {}", e),
+ };
+ });
}
fn on_payload_ready(&self, _cid: i32) {
diff --git a/vmclient/src/lib.rs b/vmclient/src/lib.rs
index e6f32b4..1dd553c 100644
--- a/vmclient/src/lib.rs
+++ b/vmclient/src/lib.rs
@@ -74,12 +74,15 @@
pub trait VmCallback {
/// Called when the payload has been started within the VM. If present, `stream` is connected
/// to the stdin/stdout of the payload.
- fn on_payload_started(&self, cid: i32, stream: Option<&File>) {}
+ fn on_payload_started(&self, cid: i32) {}
/// Callend when the payload has notified Virtualization Service that it is ready to serve
/// clients.
fn on_payload_ready(&self, cid: i32) {}
+ /// Called by the payload to forward its standard I/O streams to the host.
+ fn on_payload_stdio(&self, cid: i32, fd: &File);
+
/// Called when the payload has exited in the VM. `exit_code` is the exit code of the payload
/// process.
fn on_payload_finished(&self, cid: i32, exit_code: i32) {}
@@ -269,14 +272,17 @@
impl Interface for VirtualMachineCallback {}
impl IVirtualMachineCallback for VirtualMachineCallback {
- fn onPayloadStarted(
- &self,
- cid: i32,
- stream: Option<&ParcelFileDescriptor>,
- ) -> BinderResult<()> {
+ fn onPayloadStarted(&self, cid: i32) -> BinderResult<()> {
self.state.notify_state(VirtualMachineState::STARTED);
if let Some(ref callback) = self.client_callback {
- callback.on_payload_started(cid, stream.map(ParcelFileDescriptor::as_ref));
+ callback.on_payload_started(cid);
+ }
+ Ok(())
+ }
+
+ fn onPayloadStdio(&self, cid: i32, stream: &ParcelFileDescriptor) -> BinderResult<()> {
+ if let Some(ref callback) = self.client_callback {
+ callback.on_payload_stdio(cid, stream.as_ref());
}
Ok(())
}