Merge "pvmfw: Expect an appended BCC"
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index a6b1f95..43c89d4 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -4,10 +4,15 @@
bpfmt = true
clang_format = true
jsonlint = true
+google_java_format = true
pylint3 = true
rustfmt = true
xmllint = true
+[Tool Paths]
+google-java-format = ${REPO_ROOT}/prebuilts/tools/common/google-java-format/google-java-format
+google-java-format-diff = ${REPO_ROOT}/prebuilts/tools/common/google-java-format/google-java-format-diff.py
+
[Builtin Hooks Options]
clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
rustfmt = --config-path=rustfmt.toml
diff --git a/authfs/tests/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java b/authfs/tests/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java
index 428c816..8cee496 100644
--- a/authfs/tests/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java
+++ b/authfs/tests/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java
@@ -20,6 +20,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
@@ -28,11 +29,11 @@
import com.android.fs.common.AuthFsTestRule;
import com.android.microdroid.test.common.MetricsProcessor;
+import com.android.microdroid.test.host.MicrodroidHostTestCaseBase;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.metrics.proto.MetricMeasurement.DataType;
import com.android.tradefed.metrics.proto.MetricMeasurement.Measurements;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import org.junit.After;
import org.junit.AfterClass;
@@ -52,7 +53,7 @@
@RootPermissionTest
@RunWith(DeviceJUnit4Parameterized.class)
@UseParametersRunnerFactory(DeviceJUnit4ClassRunnerWithParameters.RunnerFactory.class)
-public class AuthFsBenchmarks extends BaseHostJUnit4Test {
+public class AuthFsBenchmarks extends MicrodroidHostTestCaseBase {
private static final int TRIAL_COUNT = 5;
/** Name of the measure_io binary on host. */
@@ -82,6 +83,7 @@
AuthFsTestRule.setUpAndroid(getTestInformation());
mAuthFsTestRule.setUpTest();
assumeTrue(AuthFsTestRule.getDevice().supportsMicrodroid(mProtectedVm));
+ assumeFalse("Skip on CF; protected VM not supported", isCuttlefish());
String metricsPrefix =
MetricsProcessor.getMetricPrefix(
getDevice().getProperty("debug.hypervisor.metrics_tag"));
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/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/java/com/android/microdroid/demo/MainActivity.java b/demo/java/com/android/microdroid/demo/MainActivity.java
index df6f44e..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));
}
@@ -278,8 +276,8 @@
mVirtualMachine.setCallback(Executors.newSingleThreadExecutor(), callback);
mStatus.postValue(mVirtualMachine.getStatus());
- InputStream console = mVirtualMachine.getConsoleOutputStream();
- InputStream log = mVirtualMachine.getLogOutputStream();
+ InputStream console = mVirtualMachine.getConsoleOutput();
+ InputStream log = mVirtualMachine.getLogOutput();
mExecutorService.execute(new Reader("console", mConsoleOutput, console));
mExecutorService.execute(new Reader("log", mLogOutput, log));
} catch (VirtualMachineException e) {
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/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index 214e7e6..d1742b2 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -643,9 +643,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 +661,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 +683,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) {
@@ -722,7 +730,7 @@
* @hide
*/
@NonNull
- public InputStream getConsoleOutputStream() throws VirtualMachineException {
+ public InputStream getConsoleOutput() throws VirtualMachineException {
synchronized (mLock) {
createVmPipes();
return new FileInputStream(mConsoleReader.getFileDescriptor());
@@ -736,7 +744,7 @@
* @hide
*/
@NonNull
- public InputStream getLogOutputStream() throws VirtualMachineException {
+ public InputStream getLogOutput() throws VirtualMachineException {
synchronized (mLock) {
createVmPipes();
return new FileInputStream(mLogReader.getFileDescriptor());
@@ -895,22 +903,22 @@
}
/**
- * 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.
*/
@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);
}
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 90b09c8..b814367 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -381,7 +381,7 @@
* @hide
*/
public Builder(@NonNull Context context) {
- mContext = requireNonNull(context);
+ mContext = requireNonNull(context, "context must not be null");
mDebugLevel = DEBUG_LEVEL_NONE;
mNumCpus = 1;
}
diff --git a/javalib/src/android/system/virtualmachine/ParcelVirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
similarity index 74%
rename from javalib/src/android/system/virtualmachine/ParcelVirtualMachine.java
rename to javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
index 808f30a..70532fc 100644
--- a/javalib/src/android/system/virtualmachine/ParcelVirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
@@ -26,15 +26,15 @@
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 {
+public final class VirtualMachineDescriptor implements Parcelable {
private final @NonNull ParcelFileDescriptor mConfigFd;
private final @NonNull ParcelFileDescriptor mInstanceImgFd;
// TODO(b/243129654): Add trusted storage fd once it is available.
@@ -50,14 +50,14 @@
mInstanceImgFd.writeToParcel(out, flags);
}
- public static final Parcelable.Creator<ParcelVirtualMachine> CREATOR =
- new Parcelable.Creator<ParcelVirtualMachine>() {
- public ParcelVirtualMachine createFromParcel(Parcel in) {
- return new ParcelVirtualMachine(in);
+ public static final Parcelable.Creator<VirtualMachineDescriptor> CREATOR =
+ new Parcelable.Creator<VirtualMachineDescriptor>() {
+ 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];
}
};
@@ -79,13 +79,13 @@
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/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/microdroid_manager.rc b/microdroid_manager/microdroid_manager.rc
index cfa70bd..c41ee38 100644
--- a/microdroid_manager/microdroid_manager.rc
+++ b/microdroid_manager/microdroid_manager.rc
@@ -1,6 +1,9 @@
service microdroid_manager /system/bin/microdroid_manager
disabled
+ # print android log to kmsg
file /dev/kmsg w
+ # redirect stdout/stderr to kmsg_debug
+ stdio_to_kmsg
setenv RUST_LOG info
# TODO(jooyung) remove this when microdroid_manager becomes a daemon
oneshot
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 4b4f996..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};
@@ -39,6 +39,7 @@
use log::{error, info};
use microdroid_metadata::{write_metadata, Metadata, PayloadMetadata};
use microdroid_payload_config::{OsConfig, Task, TaskType, VmPayloadConfig};
+use nix::sys::signal::Signal;
use openssl::sha::Sha512;
use payload::{get_apex_data_from_payload, load_metadata, to_metadata};
use rand::Fill;
@@ -47,14 +48,14 @@
use rustutils::system_properties::PropertyWatcher;
use std::borrow::Cow::{Borrowed, Owned};
use std::convert::TryInto;
-use std::fs::{self, create_dir, File, OpenOptions};
+use std::env;
+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";
@@ -152,6 +153,11 @@
}
fn main() -> Result<()> {
+ // If debuggable, print full backtrace to console log with stdio_to_kmsg
+ if system_properties::read_bool(APP_DEBUGGABLE_PROP, true)? {
+ env::set_var("RUST_BACKTRACE", "full");
+ }
+
scopeguard::defer! {
info!("Shutting down...");
if let Err(e) = system_properties::write("sys.powerctl", "shutdown") {
@@ -724,16 +730,6 @@
/// 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)?;
-
- info!("notifying payload started");
- service.notifyPayloadStarted()?;
-
- let exit_status = command.spawn()?.wait()?;
- exit_status.code().ok_or_else(|| anyhow!("Failed to get exit_code from the paylaod."))
-}
-
-fn build_command(task: &Task) -> Result<Command> {
let mut command = match task.type_ {
TaskType::Executable => Command::new(&task.command),
TaskType::MicrodroidLauncher => {
@@ -743,28 +739,21 @@
}
};
- 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());
- }
- }
+ info!("notifying payload started");
+ service.notifyPayloadStarted()?;
- Ok(command)
+ let exit_status = command.spawn()?.wait()?;
+ match exit_status.code() {
+ Some(exit_code) => Ok(exit_code),
+ None => Err(match exit_status.signal() {
+ Some(signal) => anyhow!(
+ "Payload exited due to signal: {} ({})",
+ signal,
+ Signal::try_from(signal).map_or("unknown", |s| s.as_str())
+ ),
+ None => anyhow!("Payload has neither exit code nor signal"),
+ }),
+ }
}
fn find_library_path(name: &str) -> Result<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/tests/benchmark/src/native/benchmarkbinary.cpp b/tests/benchmark/src/native/benchmarkbinary.cpp
index 6321c25..70c6884 100644
--- a/tests/benchmark/src/native/benchmarkbinary.cpp
+++ b/tests/benchmark/src/native/benchmarkbinary.cpp
@@ -96,7 +96,7 @@
const int64_t block_count = fileSizeBytes / 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{}()};
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 ede838b..1e57ff8 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
@@ -202,8 +202,8 @@
throws VirtualMachineException, InterruptedException {
vm.setCallback(mExecutorService, this);
vm.run();
- logVmOutputAndMonitorBootEvents(logTag, vm.getConsoleOutputStream(), "Console");
- logVmOutput(logTag, vm.getLogOutputStream(), "Log");
+ logVmOutputAndMonitorBootEvents(logTag, vm.getConsoleOutput(), "Console");
+ logVmOutput(logTag, vm.getLogOutput(), "Log");
mExecutorService.awaitTermination(300, TimeUnit.SECONDS);
}
@@ -232,7 +232,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 +330,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/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index f9de77e..a836559 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -465,7 +465,9 @@
// check until microdroid is shut down
CommandRunner android = new CommandRunner(getDevice());
- android.runWithTimeout(15000, "logcat", "-m", "1", "-e", "'crosvm has exited normally'");
+ // TODO: improve crosvm exit check. b/258848245
+ android.runWithTimeout(15000, "logcat", "-m", "1", "-e",
+ "'virtualizationservice::crosvm.*exited with status exit status: 0'");
// Check that tombstone is received (from host logcat)
String result =
runOnHost(
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..492eb33 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;
@@ -600,7 +600,7 @@
}
@Test
- public void vmConvertsToValidParcelVm() throws Exception {
+ public void vmConvertsToValidDescriptor() throws Exception {
// Arrange
VirtualMachineConfig config =
mInner.newVmConfigBuilder()
@@ -611,11 +611,11 @@
VirtualMachine vm = mInner.forceCreateNewVirtualMachine(vmName, config);
// Action
- ParcelVirtualMachine parcelVm = vm.toParcelVirtualMachine();
+ VirtualMachineDescriptor descriptor = vm.toDescriptor();
// Asserts
- assertFileContentsAreEqual(parcelVm.getConfigFd(), vmName, "config.xml");
- assertFileContentsAreEqual(parcelVm.getInstanceImgFd(), vmName, "instance.img");
+ assertFileContentsAreEqual(descriptor.getConfigFd(), vmName, "config.xml");
+ assertFileContentsAreEqual(descriptor.getInstanceImgFd(), vmName, "instance.img");
}
private void assertFileContentsAreEqual(
@@ -671,8 +671,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 +696,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/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/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 bc697e3..340fc68 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)
@@ -854,11 +821,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 +860,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 +1048,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_start_timestamp.lock().unwrap();
write_vm_booted_stats(vm.requester_uid as i32, &vm.name, *vm_start_timestamp);
@@ -1140,6 +1115,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/crosvm.rs b/virtualizationservice/src/crosvm.rs
index 1b8061e..29040b7 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -35,7 +35,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;
@@ -190,8 +189,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 timestamp when the VM is started.
@@ -223,7 +220,6 @@
requester_uid,
requester_debug_pid,
callbacks: Default::default(),
- stream: Mutex::new(None),
vm_service: Mutex::new(None),
vm_start_timestamp: Mutex::new(None),
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(())
}