Merge "apex: set custom_sign_tool"
diff --git a/authfs/fd_server/src/aidl.rs b/authfs/fd_server/src/aidl.rs
index ed3a0ea..b235025 100644
--- a/authfs/fd_server/src/aidl.rs
+++ b/authfs/fd_server/src/aidl.rs
@@ -83,8 +83,14 @@
BnVirtFdService::new_binder(FdService { fd_pool }, BinderFeatures::default())
}
- fn get_file_config(&self, id: i32) -> BinderResult<&FdConfig> {
- self.fd_pool.get(&id).ok_or_else(|| Status::from(ERROR_UNKNOWN_FD))
+ /// Handles the requesting file `id` with `handler` if it is in the FD pool. This function
+ /// returns whatever the handler returns.
+ fn handle_fd<F, R>(&self, id: i32, handler: F) -> BinderResult<R>
+ where
+ F: FnOnce(&FdConfig) -> BinderResult<R>,
+ {
+ let fd_config = self.fd_pool.get(&id).ok_or_else(|| Status::from(ERROR_UNKNOWN_FD))?;
+ handler(fd_config)
}
}
@@ -95,21 +101,21 @@
let size: usize = validate_and_cast_size(size)?;
let offset: u64 = validate_and_cast_offset(offset)?;
- match self.get_file_config(id)? {
+ self.handle_fd(id, |config| match config {
FdConfig::Readonly { file, .. } | FdConfig::ReadWrite(file) => {
read_into_buf(file, size, offset).map_err(|e| {
error!("readFile: read error: {}", e);
Status::from(ERROR_IO)
})
}
- }
+ })
}
fn readFsverityMerkleTree(&self, id: i32, offset: i64, size: i32) -> BinderResult<Vec<u8>> {
let size: usize = validate_and_cast_size(size)?;
let offset: u64 = validate_and_cast_offset(offset)?;
- match &self.get_file_config(id)? {
+ self.handle_fd(id, |config| match config {
FdConfig::Readonly { file, alt_merkle_tree, .. } => {
if let Some(tree_file) = &alt_merkle_tree {
read_into_buf(tree_file, size, offset).map_err(|e| {
@@ -134,11 +140,11 @@
// use.
Err(new_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION, "Unsupported"))
}
- }
+ })
}
fn readFsveritySignature(&self, id: i32) -> BinderResult<Vec<u8>> {
- match &self.get_file_config(id)? {
+ self.handle_fd(id, |config| match config {
FdConfig::Readonly { file, alt_signature, .. } => {
if let Some(sig_file) = &alt_signature {
// Supposedly big enough buffer size to store signature.
@@ -163,11 +169,11 @@
// There is no signature for a writable file.
Err(new_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION, "Unsupported"))
}
- }
+ })
}
fn writeFile(&self, id: i32, buf: &[u8], offset: i64) -> BinderResult<i32> {
- match &self.get_file_config(id)? {
+ self.handle_fd(id, |config| match config {
FdConfig::Readonly { .. } => Err(StatusCode::INVALID_OPERATION.into()),
FdConfig::ReadWrite(file) => {
let offset: u64 = offset.try_into().map_err(|_| {
@@ -185,11 +191,11 @@
Status::from(ERROR_IO)
})? as i32)
}
- }
+ })
}
fn resize(&self, id: i32, size: i64) -> BinderResult<()> {
- match &self.get_file_config(id)? {
+ self.handle_fd(id, |config| match config {
FdConfig::Readonly { .. } => Err(StatusCode::INVALID_OPERATION.into()),
FdConfig::ReadWrite(file) => {
if size < 0 {
@@ -203,11 +209,11 @@
Status::from(ERROR_IO)
})
}
- }
+ })
}
fn getFileSize(&self, id: i32) -> BinderResult<i64> {
- match &self.get_file_config(id)? {
+ self.handle_fd(id, |config| match config {
FdConfig::Readonly { file, .. } => {
let size = file
.metadata()
@@ -227,7 +233,7 @@
// for a writable file.
Err(new_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION, "Unsupported"))
}
- }
+ })
}
}
diff --git a/authfs/src/fusefs.rs b/authfs/src/fusefs.rs
index d54b5be..d985581 100644
--- a/authfs/src/fusefs.rs
+++ b/authfs/src/fusefs.rs
@@ -77,8 +77,15 @@
AuthFs { file_pool, max_write }
}
- fn get_file_config(&self, inode: &Inode) -> io::Result<&FileConfig> {
- self.file_pool.get(inode).ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))
+ /// Handles the file associated with `inode` if found. This function returns whatever the
+ /// handler returns.
+ fn handle_file<F, R>(&self, inode: &Inode, handler: F) -> io::Result<R>
+ where
+ F: FnOnce(&FileConfig) -> io::Result<R>,
+ {
+ let config =
+ self.file_pool.get(inode).ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))?;
+ handler(config)
}
}
@@ -197,15 +204,15 @@
// `forget` will decrease it). It is not necessary here since the files are configured to
// be static.
let inode = num.parse::<Inode>().map_err(|_| io::Error::from_raw_os_error(libc::ENOENT))?;
- let st = match self.get_file_config(&inode)? {
+ let st = self.handle_file(&inode, |config| match config {
FileConfig::UnverifiedReadonly { file_size, .. }
| FileConfig::VerifiedReadonly { file_size, .. } => {
- create_stat(inode, *file_size, FileMode::ReadOnly)?
+ create_stat(inode, *file_size, FileMode::ReadOnly)
}
FileConfig::VerifiedNew { editor } => {
- create_stat(inode, editor.size(), FileMode::ReadWrite)?
+ create_stat(inode, editor.size(), FileMode::ReadWrite)
}
- };
+ })?;
Ok(Entry {
inode,
generation: 0,
@@ -221,18 +228,20 @@
inode: Inode,
_handle: Option<Handle>,
) -> io::Result<(libc::stat64, Duration)> {
- Ok((
- match self.get_file_config(&inode)? {
- FileConfig::UnverifiedReadonly { file_size, .. }
- | FileConfig::VerifiedReadonly { file_size, .. } => {
- create_stat(inode, *file_size, FileMode::ReadOnly)?
- }
- FileConfig::VerifiedNew { editor } => {
- create_stat(inode, editor.size(), FileMode::ReadWrite)?
- }
- },
- DEFAULT_METADATA_TIMEOUT,
- ))
+ self.handle_file(&inode, |config| {
+ Ok((
+ match config {
+ FileConfig::UnverifiedReadonly { file_size, .. }
+ | FileConfig::VerifiedReadonly { file_size, .. } => {
+ create_stat(inode, *file_size, FileMode::ReadOnly)?
+ }
+ FileConfig::VerifiedNew { editor } => {
+ create_stat(inode, editor.size(), FileMode::ReadWrite)?
+ }
+ },
+ DEFAULT_METADATA_TIMEOUT,
+ ))
+ })
}
fn open(
@@ -243,18 +252,20 @@
) -> io::Result<(Option<Self::Handle>, fuse::sys::OpenOptions)> {
// Since file handle is not really used in later operations (which use Inode directly),
// return None as the handle.
- match self.get_file_config(&inode)? {
- FileConfig::VerifiedReadonly { .. } | FileConfig::UnverifiedReadonly { .. } => {
- check_access_mode(flags, libc::O_RDONLY)?;
+ self.handle_file(&inode, |config| {
+ match config {
+ FileConfig::VerifiedReadonly { .. } | FileConfig::UnverifiedReadonly { .. } => {
+ check_access_mode(flags, libc::O_RDONLY)?;
+ }
+ FileConfig::VerifiedNew { .. } => {
+ // No need to check access modes since all the modes are allowed to the
+ // read-writable file.
+ }
}
- FileConfig::VerifiedNew { .. } => {
- // No need to check access modes since all the modes are allowed to the
- // read-writable file.
- }
- }
- // Always cache the file content. There is currently no need to support direct I/O or avoid
- // the cache buffer. Memory mapping is only possible with cache enabled.
- Ok((None, fuse::sys::OpenOptions::KEEP_CACHE))
+ // Always cache the file content. There is currently no need to support direct I/O or avoid
+ // the cache buffer. Memory mapping is only possible with cache enabled.
+ Ok((None, fuse::sys::OpenOptions::KEEP_CACHE))
+ })
}
fn read<W: io::Write + ZeroCopyWriter>(
@@ -268,19 +279,21 @@
_lock_owner: Option<u64>,
_flags: u32,
) -> io::Result<usize> {
- match self.get_file_config(&inode)? {
- FileConfig::VerifiedReadonly { reader, file_size } => {
- read_chunks(w, reader, *file_size, offset, size)
+ self.handle_file(&inode, |config| {
+ match config {
+ FileConfig::VerifiedReadonly { reader, file_size } => {
+ read_chunks(w, reader, *file_size, offset, size)
+ }
+ FileConfig::UnverifiedReadonly { reader, file_size } => {
+ read_chunks(w, reader, *file_size, offset, size)
+ }
+ FileConfig::VerifiedNew { editor } => {
+ // Note that with FsOptions::WRITEBACK_CACHE, it's possible for the kernel to
+ // request a read even if the file is open with O_WRONLY.
+ read_chunks(w, editor, editor.size(), offset, size)
+ }
}
- FileConfig::UnverifiedReadonly { reader, file_size } => {
- read_chunks(w, reader, *file_size, offset, size)
- }
- FileConfig::VerifiedNew { editor } => {
- // Note that with FsOptions::WRITEBACK_CACHE, it's possible for the kernel to
- // request a read even if the file is open with O_WRONLY.
- read_chunks(w, editor, editor.size(), offset, size)
- }
- }
+ })
}
fn write<R: io::Read + ZeroCopyReader>(
@@ -295,14 +308,14 @@
_delayed_write: bool,
_flags: u32,
) -> io::Result<usize> {
- match self.get_file_config(&inode)? {
+ self.handle_file(&inode, |config| match config {
FileConfig::VerifiedNew { editor } => {
let mut buf = vec![0; size as usize];
r.read_exact(&mut buf)?;
editor.write_at(&buf, offset)
}
_ => Err(io::Error::from_raw_os_error(libc::EBADF)),
- }
+ })
}
fn setattr(
@@ -313,44 +326,52 @@
_handle: Option<Handle>,
valid: SetattrValid,
) -> io::Result<(libc::stat64, Duration)> {
- match self.get_file_config(&inode)? {
- FileConfig::VerifiedNew { editor } => {
- // Initialize the default stat.
- let mut new_attr = create_stat(inode, editor.size(), FileMode::ReadWrite)?;
- // `valid` indicates what fields in `attr` are valid. Update to return correctly.
- if valid.contains(SetattrValid::SIZE) {
- // st_size is i64, but the cast should be safe since kernel should not give a
- // negative size.
- debug_assert!(attr.st_size >= 0);
- new_attr.st_size = attr.st_size;
- editor.resize(attr.st_size as u64)?;
- }
+ self.handle_file(&inode, |config| {
+ match config {
+ FileConfig::VerifiedNew { editor } => {
+ // Initialize the default stat.
+ let mut new_attr = create_stat(inode, editor.size(), FileMode::ReadWrite)?;
+ // `valid` indicates what fields in `attr` are valid. Update to return correctly.
+ if valid.contains(SetattrValid::SIZE) {
+ // st_size is i64, but the cast should be safe since kernel should not give a
+ // negative size.
+ debug_assert!(attr.st_size >= 0);
+ new_attr.st_size = attr.st_size;
+ editor.resize(attr.st_size as u64)?;
+ }
- if valid.contains(SetattrValid::MODE) {
- warn!("Changing st_mode is not currently supported");
- return Err(io::Error::from_raw_os_error(libc::ENOSYS));
+ if valid.contains(SetattrValid::MODE) {
+ warn!("Changing st_mode is not currently supported");
+ return Err(io::Error::from_raw_os_error(libc::ENOSYS));
+ }
+ if valid.contains(SetattrValid::UID) {
+ warn!("Changing st_uid is not currently supported");
+ return Err(io::Error::from_raw_os_error(libc::ENOSYS));
+ }
+ if valid.contains(SetattrValid::GID) {
+ warn!("Changing st_gid is not currently supported");
+ return Err(io::Error::from_raw_os_error(libc::ENOSYS));
+ }
+ if valid.contains(SetattrValid::CTIME) {
+ debug!(
+ "Ignoring ctime change as authfs does not maintain timestamp currently"
+ );
+ }
+ if valid.intersects(SetattrValid::ATIME | SetattrValid::ATIME_NOW) {
+ debug!(
+ "Ignoring atime change as authfs does not maintain timestamp currently"
+ );
+ }
+ if valid.intersects(SetattrValid::MTIME | SetattrValid::MTIME_NOW) {
+ debug!(
+ "Ignoring mtime change as authfs does not maintain timestamp currently"
+ );
+ }
+ Ok((new_attr, DEFAULT_METADATA_TIMEOUT))
}
- if valid.contains(SetattrValid::UID) {
- warn!("Changing st_uid is not currently supported");
- return Err(io::Error::from_raw_os_error(libc::ENOSYS));
- }
- if valid.contains(SetattrValid::GID) {
- warn!("Changing st_gid is not currently supported");
- return Err(io::Error::from_raw_os_error(libc::ENOSYS));
- }
- if valid.contains(SetattrValid::CTIME) {
- debug!("Ignoring ctime change as authfs does not maintain timestamp currently");
- }
- if valid.intersects(SetattrValid::ATIME | SetattrValid::ATIME_NOW) {
- debug!("Ignoring atime change as authfs does not maintain timestamp currently");
- }
- if valid.intersects(SetattrValid::MTIME | SetattrValid::MTIME_NOW) {
- debug!("Ignoring mtime change as authfs does not maintain timestamp currently");
- }
- Ok((new_attr, DEFAULT_METADATA_TIMEOUT))
+ _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
}
- _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
- }
+ })
}
fn getxattr(
@@ -360,29 +381,31 @@
name: &CStr,
size: u32,
) -> io::Result<GetxattrReply> {
- match self.get_file_config(&inode)? {
- FileConfig::VerifiedNew { editor } => {
- // FUSE ioctl is limited, thus we can't implement fs-verity ioctls without a kernel
- // change (see b/196635431). Until it's possible, use xattr to expose what we need
- // as an authfs specific API.
- if name != CStr::from_bytes_with_nul(b"authfs.fsverity.digest\0").unwrap() {
- return Err(io::Error::from_raw_os_error(libc::ENODATA));
- }
+ self.handle_file(&inode, |config| {
+ match config {
+ FileConfig::VerifiedNew { editor } => {
+ // FUSE ioctl is limited, thus we can't implement fs-verity ioctls without a kernel
+ // change (see b/196635431). Until it's possible, use xattr to expose what we need
+ // as an authfs specific API.
+ if name != CStr::from_bytes_with_nul(b"authfs.fsverity.digest\0").unwrap() {
+ return Err(io::Error::from_raw_os_error(libc::ENODATA));
+ }
- if size == 0 {
- // Per protocol, when size is 0, return the value size.
- Ok(GetxattrReply::Count(editor.get_fsverity_digest_size() as u32))
- } else {
- let digest = editor.calculate_fsverity_digest()?;
- if digest.len() > size as usize {
- Err(io::Error::from_raw_os_error(libc::ERANGE))
+ if size == 0 {
+ // Per protocol, when size is 0, return the value size.
+ Ok(GetxattrReply::Count(editor.get_fsverity_digest_size() as u32))
} else {
- Ok(GetxattrReply::Value(digest.to_vec()))
+ let digest = editor.calculate_fsverity_digest()?;
+ if digest.len() > size as usize {
+ Err(io::Error::from_raw_os_error(libc::ERANGE))
+ } else {
+ Ok(GetxattrReply::Value(digest.to_vec()))
+ }
}
}
+ _ => Err(io::Error::from_raw_os_error(libc::ENODATA)),
}
- _ => Err(io::Error::from_raw_os_error(libc::ENODATA)),
- }
+ })
}
}
diff --git a/authfs/tests/Android.bp b/authfs/tests/Android.bp
index fd45e13..88c1ba6 100644
--- a/authfs/tests/Android.bp
+++ b/authfs/tests/Android.bp
@@ -29,10 +29,10 @@
rustlibs: [
"libandroid_logger",
"libanyhow",
+ "liblibc",
"libclap",
"libcommand_fds",
"liblog_rust",
- "libnix",
],
test_suites: ["general-tests"],
test_harness: false,
diff --git a/authfs/tests/open_then_run.rs b/authfs/tests/open_then_run.rs
index ba3ed38..3e6ae71 100644
--- a/authfs/tests/open_then_run.rs
+++ b/authfs/tests/open_then_run.rs
@@ -22,9 +22,8 @@
use clap::{App, Arg, Values};
use command_fds::{CommandFdExt, FdMapping};
use log::{debug, error};
-use nix::{dir::Dir, fcntl::OFlag, sys::stat::Mode};
use std::fs::{File, OpenOptions};
-use std::os::unix::io::{AsRawFd, RawFd};
+use std::os::unix::{fs::OpenOptionsExt, io::AsRawFd, io::RawFd};
use std::process::Command;
// `PseudoRawFd` is just an integer and not necessarily backed by a real FD. It is used to denote
@@ -32,31 +31,30 @@
// with this alias is to improve readability by distinguishing from actual RawFd.
type PseudoRawFd = RawFd;
-struct FileMapping<T: AsRawFd> {
- file: T,
+struct FileMapping {
+ file: File,
target_fd: PseudoRawFd,
}
-impl<T: AsRawFd> FileMapping<T> {
+impl FileMapping {
fn as_fd_mapping(&self) -> FdMapping {
FdMapping { parent_fd: self.file.as_raw_fd(), child_fd: self.target_fd }
}
}
struct Args {
- ro_files: Vec<FileMapping<File>>,
- rw_files: Vec<FileMapping<File>>,
- dir_files: Vec<FileMapping<Dir>>,
+ ro_files: Vec<FileMapping>,
+ rw_files: Vec<FileMapping>,
+ dir_files: Vec<FileMapping>,
cmdline_args: Vec<String>,
}
-fn parse_and_create_file_mapping<F, T>(
+fn parse_and_create_file_mapping<F>(
values: Option<Values<'_>>,
opener: F,
-) -> Result<Vec<FileMapping<T>>>
+) -> Result<Vec<FileMapping>>
where
- F: Fn(&str) -> Result<T>,
- T: AsRawFd,
+ F: Fn(&str) -> Result<File>,
{
if let Some(options) = values {
options
@@ -118,7 +116,13 @@
})?;
let dir_files = parse_and_create_file_mapping(matches.values_of("open-dir"), |path| {
- Dir::open(path, OFlag::O_DIRECTORY | OFlag::O_RDWR, Mode::S_IRWXU)
+ // The returned FD represents a path (that's supposed to be a directory), and is not really
+ // a file. It's better to use std::os::unix::io::OwnedFd but it's currently experimental.
+ // Ideally, all FDs opened by this program should be `OwnedFd` since we are only opening
+ // them for the provided program, and are not supposed to do anything else.
+ OpenOptions::new()
+ .custom_flags(libc::O_PATH | libc::O_DIRECTORY)
+ .open(path)
.with_context(|| format!("Open {} directory", path))
})?;
diff --git a/compos/common/Android.bp b/compos/common/Android.bp
index d8fec81..5893fd6 100644
--- a/compos/common/Android.bp
+++ b/compos/common/Android.bp
@@ -14,6 +14,7 @@
"libbinder_rpc_unstable_bindgen",
"libbinder_rs",
"liblog_rust",
+ "librustutils",
],
shared_libs: [
"libbinder_rpc_unstable",
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 6277a55..af504a1 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -16,6 +16,7 @@
//! Support for starting CompOS in a VM and connecting to the service
+use crate::timeouts::timeouts;
use crate::{COMPOS_APEX_ROOT, COMPOS_DATA_ROOT, COMPOS_VSOCK_PORT};
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
IVirtualMachine::IVirtualMachine,
@@ -42,7 +43,6 @@
use std::path::Path;
use std::sync::{Arc, Condvar, Mutex};
use std::thread;
-use std::time::Duration;
/// This owns an instance of the CompOS VM.
pub struct VmInstance {
@@ -85,6 +85,7 @@
.context("Failed to open config APK idsig file")?;
let idsig_fd = ParcelFileDescriptor::new(idsig_fd);
+ // Console output and the system log output from the VM are redirected to this file.
// TODO: Send this to stdout instead? Or specify None?
let log_fd = File::create(data_dir.join("vm.log")).context("Failed to create log file")?;
let log_fd = ParcelFileDescriptor::new(log_fd);
@@ -100,14 +101,18 @@
..Default::default()
});
- let vm = service.createVm(&config, Some(&log_fd)).context("Failed to create VM")?;
+ let vm = service
+ .createVm(&config, Some(&log_fd), Some(&log_fd))
+ .context("Failed to create VM")?;
let vm_state = Arc::new(VmStateMonitor::default());
let vm_state_clone = Arc::clone(&vm_state);
- vm.as_binder().link_to_death(&mut DeathRecipient::new(move || {
+ let mut death_recipient = DeathRecipient::new(move || {
vm_state_clone.set_died();
log::error!("VirtualizationService died");
- }))?;
+ });
+ // Note that dropping death_recipient cancels this, so we can't use a temporary here.
+ vm.as_binder().link_to_death(&mut death_recipient)?;
let vm_state_clone = Arc::clone(&vm_state);
let callback = BnVirtualMachineCallback::new_binder(
@@ -235,14 +240,13 @@
}
fn wait_until_ready(&self) -> Result<i32> {
- // 10s is long enough on real hardware, but it can take 90s when using nested
- // virtualization.
- // TODO(b/200924405): Reduce timeout/detect nested virtualization
let (state, result) = self
.state_ready
- .wait_timeout_while(self.mutex.lock().unwrap(), Duration::from_secs(120), |state| {
- state.cid.is_none() && !state.has_died
- })
+ .wait_timeout_while(
+ self.mutex.lock().unwrap(),
+ timeouts()?.vm_max_time_to_ready,
+ |state| state.cid.is_none() && !state.has_died,
+ )
.unwrap();
if result.timed_out() {
bail!("Timed out waiting for VM")
diff --git a/compos/common/lib.rs b/compos/common/lib.rs
index 0b84a28..4bfa81f 100644
--- a/compos/common/lib.rs
+++ b/compos/common/lib.rs
@@ -17,6 +17,7 @@
//! Common items used by CompOS server and/or clients
pub mod compos_client;
+pub mod timeouts;
/// Special CID indicating "any".
pub const VMADDR_CID_ANY: u32 = -1i32 as u32;
diff --git a/compos/common/timeouts.rs b/compos/common/timeouts.rs
new file mode 100644
index 0000000..42cfe69
--- /dev/null
+++ b/compos/common/timeouts.rs
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! Timeouts for common situations, with support for longer timeouts when using nested
+//! virtualization.
+
+use anyhow::Result;
+use rustutils::system_properties;
+use std::time::Duration;
+
+/// Holder for the various timeouts we use.
+#[derive(Debug, Copy, Clone)]
+pub struct Timeouts {
+ /// Total time that odrefresh may take to perform compilation
+ pub odrefresh_max_execution_time: Duration,
+ /// Time allowed for a single compilation step run by odrefresh
+ pub odrefresh_max_child_process_time: Duration,
+ /// Time allowed for the CompOS VM to start up and become ready.
+ pub vm_max_time_to_ready: Duration,
+}
+
+/// Whether the current platform requires extra time for operations inside a VM.
+pub fn need_extra_time() -> Result<bool> {
+ // Nested virtualization is slow. Check if we are running on vsoc as a proxy for this.
+ let value = system_properties::read("ro.build.product")?;
+ Ok(value == "vsoc_x86_64" || value == "vsoc_x86")
+}
+
+/// Return the timeouts that are appropriate on the current platform.
+pub fn timeouts() -> Result<&'static Timeouts> {
+ if need_extra_time()? {
+ Ok(&EXTENDED_TIMEOUTS)
+ } else {
+ Ok(&NORMAL_TIMEOUTS)
+ }
+}
+
+/// The timeouts that we use normally.
+pub const NORMAL_TIMEOUTS: Timeouts = Timeouts {
+ // Note: the source of truth for these odrefresh timeouts is art/odrefresh/odr_config.h.
+ odrefresh_max_execution_time: Duration::from_secs(300),
+ odrefresh_max_child_process_time: Duration::from_secs(90),
+ vm_max_time_to_ready: Duration::from_secs(10),
+};
+
+/// The timeouts that we use when need_extra_time() returns true.
+pub const EXTENDED_TIMEOUTS: Timeouts = Timeouts {
+ odrefresh_max_execution_time: Duration::from_secs(480),
+ odrefresh_max_child_process_time: Duration::from_secs(150),
+ vm_max_time_to_ready: Duration::from_secs(120),
+};
diff --git a/compos/compos_key_cmd/compos_key_cmd.cpp b/compos/compos_key_cmd/compos_key_cmd.cpp
index 7bf622d..2735f2e 100644
--- a/compos/compos_key_cmd/compos_key_cmd.cpp
+++ b/compos/compos_key_cmd/compos_key_cmd.cpp
@@ -197,6 +197,7 @@
return Error() << "Failed to connect to virtualization service.";
}
+ // Console output and the system log output from the VM are redirected to this file.
ScopedFileDescriptor logFd;
if (mLogFile.empty()) {
logFd.set(dup(STDOUT_FILENO));
@@ -239,7 +240,7 @@
appConfig.memoryMib = 0; // Use default
LOG(INFO) << "Starting VM";
- auto status = service->createVm(config, logFd, &mVm);
+ auto status = service->createVm(config, logFd, logFd, &mVm);
if (!status.isOk()) {
return Error() << status.getDescription();
}
diff --git a/compos/composd/Android.bp b/compos/composd/Android.bp
index 2a24b7a..ecfea61 100644
--- a/compos/composd/Android.bp
+++ b/compos/composd/Android.bp
@@ -19,7 +19,7 @@
"libcomposd_native_rust",
"libnum_traits",
"liblog_rust",
- "librustutils",
+ "libshared_child",
],
proc_macros: ["libnum_derive"],
apex_available: [
diff --git a/compos/composd/aidl/android/system/composd/ICompilationTask.aidl b/compos/composd/aidl/android/system/composd/ICompilationTask.aidl
new file mode 100644
index 0000000..ae03fcc
--- /dev/null
+++ b/compos/composd/aidl/android/system/composd/ICompilationTask.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.system.composd;
+
+/**
+ * Represents a compilation in process.
+ */
+interface ICompilationTask {
+ /**
+ * Attempt to cancel compilation. If successful compilation will end and no further success or
+ * failed callbacks will be received (although any in flight may still be delivered).
+ */
+ void cancel();
+}
diff --git a/compos/composd/aidl/android/system/composd/ICompilationTaskCallback.aidl b/compos/composd/aidl/android/system/composd/ICompilationTaskCallback.aidl
new file mode 100644
index 0000000..a9d41b8
--- /dev/null
+++ b/compos/composd/aidl/android/system/composd/ICompilationTaskCallback.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.system.composd;
+
+/**
+ * Interface to be implemented by clients of IIsolatedCompilationService to be notified when a
+ * requested compilation task completes.
+ */
+interface ICompilationTaskCallback {
+ /**
+ * Called if a compilation task has ended successfully, generating all the required artifacts.
+ */
+ void onSuccess();
+
+ /**
+ * Called if a compilation task has ended unsuccessfully.
+ */
+ void onFailure();
+}
diff --git a/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl b/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
index 3d0ad31..3d28894 100644
--- a/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
+++ b/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
@@ -15,6 +15,8 @@
*/
package android.system.composd;
+import android.system.composd.ICompilationTask;
+import android.system.composd.ICompilationTaskCallback;
import com.android.compos.CompilationResult;
import com.android.compos.FdAnnotation;
@@ -24,8 +26,11 @@
* This compiles BCP extensions and system server, even if the system artifacts are up to date,
* and writes the results to a test directory to avoid disrupting any real artifacts in
* existence.
+ * Compilation continues in the background, and success/failure is reported via the supplied
+ * callback, unless the returned ICompilationTask is cancelled. The caller should maintain
+ * a reference to the ICompilationTask until compilation completes or is cancelled.
*/
- void runForcedCompileForTest();
+ ICompilationTask startTestCompile(ICompilationTaskCallback callback);
/**
* Run dex2oat in the currently running instance of the CompOS VM. This is a simple proxy
diff --git a/compos/composd/src/compilation_task.rs b/compos/composd/src/compilation_task.rs
new file mode 100644
index 0000000..c4eed52
--- /dev/null
+++ b/compos/composd/src/compilation_task.rs
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use crate::instance_starter::CompOsInstance;
+use crate::odrefresh::{self, Odrefresh};
+use android_system_composd::aidl::android::system::composd::{
+ ICompilationTask::ICompilationTask, ICompilationTaskCallback::ICompilationTaskCallback,
+};
+use android_system_composd::binder::{Interface, Result as BinderResult, Strong};
+use anyhow::Result;
+use log::{error, warn};
+use std::sync::{Arc, Mutex};
+use std::thread;
+
+#[derive(Clone)]
+pub struct CompilationTask {
+ running_task: Arc<Mutex<Option<RunningTask>>>,
+}
+
+impl Interface for CompilationTask {}
+
+impl ICompilationTask for CompilationTask {
+ fn cancel(&self) -> BinderResult<()> {
+ let task = self.take();
+ if let Some(task) = task {
+ if let Err(e) = task.odrefresh.kill() {
+ warn!("Failed to kill running task: {:?}", e)
+ }
+ }
+ Ok(())
+ }
+}
+
+impl CompilationTask {
+ /// Return the current running task, if any, removing it from this CompilationTask.
+ /// Once removed, meaning the task has ended or been canceled, further calls will always return
+ /// None.
+ fn take(&self) -> Option<RunningTask> {
+ self.running_task.lock().unwrap().take()
+ }
+
+ pub fn start_test_compile(
+ comp_os: Arc<CompOsInstance>,
+ callback: &Strong<dyn ICompilationTaskCallback>,
+ ) -> Result<CompilationTask> {
+ let odrefresh = Odrefresh::spawn_forced_compile("test-artifacts")?;
+ let odrefresh = Arc::new(odrefresh);
+ let task =
+ RunningTask { odrefresh: odrefresh.clone(), comp_os, callback: callback.clone() };
+ let task = CompilationTask { running_task: Arc::new(Mutex::new(Some(task))) };
+
+ task.clone().start_waiting_thread(odrefresh);
+
+ Ok(task)
+ }
+
+ fn start_waiting_thread(self, odrefresh: Arc<Odrefresh>) {
+ thread::spawn(move || {
+ let exit_code = odrefresh.wait_for_exit();
+ let task = self.take();
+ // We don't do the callback if cancel has already happened.
+ if let Some(task) = task {
+ let result = match exit_code {
+ Ok(odrefresh::ExitCode::CompilationSuccess) => task.callback.onSuccess(),
+ Ok(exit_code) => {
+ error!("Unexpected odrefresh result: {:?}", exit_code);
+ task.callback.onFailure()
+ }
+ Err(e) => {
+ error!("Running odrefresh failed: {:?}", e);
+ task.callback.onFailure()
+ }
+ };
+ if let Err(e) = result {
+ warn!("Failed to deliver callback: {:?}", e);
+ }
+ }
+ });
+ }
+}
+
+struct RunningTask {
+ odrefresh: Arc<Odrefresh>,
+ callback: Strong<dyn ICompilationTaskCallback>,
+ #[allow(dead_code)] // Keeps the CompOS VM alive
+ comp_os: Arc<CompOsInstance>,
+}
diff --git a/compos/composd/src/composd_main.rs b/compos/composd/src/composd_main.rs
index 60aeb39..671ed16 100644
--- a/compos/composd/src/composd_main.rs
+++ b/compos/composd/src/composd_main.rs
@@ -18,10 +18,12 @@
//! responsible for managing the lifecycle of the CompOS VM instances, providing key management for
//! them, and orchestrating trusted compilation.
+mod compilation_task;
mod instance_manager;
mod instance_starter;
mod odrefresh;
mod service;
+mod util;
use crate::instance_manager::InstanceManager;
use android_system_composd::binder::{register_lazy_service, ProcessState};
diff --git a/compos/composd/src/instance_starter.rs b/compos/composd/src/instance_starter.rs
index 1a6e592..3959859 100644
--- a/compos/composd/src/instance_starter.rs
+++ b/compos/composd/src/instance_starter.rs
@@ -21,6 +21,7 @@
IVirtualizationService::IVirtualizationService, PartitionType::PartitionType,
};
use anyhow::{bail, Context, Result};
+use binder_common::lazy_service::LazyServiceGuard;
use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
use compos_aidl_interface::binder::{ParcelFileDescriptor, Strong};
use compos_common::compos_client::{VmInstance, VmParameters};
@@ -33,9 +34,11 @@
use std::path::{Path, PathBuf};
pub struct CompOsInstance {
+ service: Strong<dyn ICompOsService>,
#[allow(dead_code)] // Keeps VirtualizationService & the VM alive
vm_instance: VmInstance,
- service: Strong<dyn ICompOsService>,
+ #[allow(dead_code)] // Keeps composd process alive
+ lazy_service_guard: LazyServiceGuard,
}
impl CompOsInstance {
@@ -167,7 +170,7 @@
VmInstance::start(virtualization_service, instance_image, &self.vm_parameters)
.context("Starting VM")?;
let service = vm_instance.get_service().context("Connecting to CompOS")?;
- Ok(CompOsInstance { vm_instance, service })
+ Ok(CompOsInstance { vm_instance, service, lazy_service_guard: Default::default() })
}
fn create_instance_image(
diff --git a/compos/composd/src/odrefresh.rs b/compos/composd/src/odrefresh.rs
index 8c3febf..16dcb0f 100644
--- a/compos/composd/src/odrefresh.rs
+++ b/compos/composd/src/odrefresh.rs
@@ -17,10 +17,11 @@
//! Handle the details of executing odrefresh to generate compiled artifacts.
use anyhow::{bail, Context, Result};
+use compos_common::timeouts::{need_extra_time, EXTENDED_TIMEOUTS};
use compos_common::VMADDR_CID_ANY;
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
-use rustutils::system_properties;
+use shared_child::SharedChild;
use std::process::Command;
// TODO: What if this changes?
@@ -38,30 +39,44 @@
CleanupFailed = EX_MAX + 4,
}
-fn need_extra_time() -> Result<bool> {
- // Special case to add more time in nested VM
- let value = system_properties::read("ro.build.product")?;
- Ok(value == "vsoc_x86_64" || value == "vsoc_x86")
+pub struct Odrefresh {
+ child: SharedChild,
}
-pub fn run_forced_compile(target_dir: &str) -> Result<ExitCode> {
- // We don`t need to capture stdout/stderr - odrefresh writes to the log
- let mut cmdline = Command::new(ODREFRESH_BIN);
- if need_extra_time()? {
- cmdline.arg("--max-execution-seconds=480").arg("--max-child-process-seconds=150");
+impl Odrefresh {
+ pub fn spawn_forced_compile(target_dir: &str) -> Result<Self> {
+ // We don`t need to capture stdout/stderr - odrefresh writes to the log
+ let mut cmdline = Command::new(ODREFRESH_BIN);
+ if need_extra_time()? {
+ cmdline
+ .arg(format!(
+ "--max-execution-seconds={}",
+ EXTENDED_TIMEOUTS.odrefresh_max_execution_time.as_secs()
+ ))
+ .arg(format!(
+ "--max-child-process-seconds={}",
+ EXTENDED_TIMEOUTS.odrefresh_max_child_process_time.as_secs()
+ ));
+ }
+ cmdline
+ .arg(format!("--use-compilation-os={}", VMADDR_CID_ANY as i32))
+ .arg(format!("--dalvik-cache={}", target_dir))
+ .arg("--force-compile");
+ let child = SharedChild::spawn(&mut cmdline).context("Running odrefresh")?;
+ Ok(Odrefresh { child })
}
- cmdline
- .arg(format!("--use-compilation-os={}", VMADDR_CID_ANY as i32))
- .arg(format!("--dalvik-cache={}", target_dir))
- .arg("--force-compile");
- let mut odrefresh = cmdline.spawn().context("Running odrefresh")?;
- // TODO: timeout?
- let status = odrefresh.wait()?;
+ pub fn wait_for_exit(&self) -> Result<ExitCode> {
+ // No timeout here - but clients can kill the process, which will end the wait.
+ let status = self.child.wait()?;
+ if let Some(exit_code) = status.code().and_then(FromPrimitive::from_i32) {
+ Ok(exit_code)
+ } else {
+ bail!("odrefresh exited with {}", status)
+ }
+ }
- if let Some(exit_code) = status.code().and_then(FromPrimitive::from_i32) {
- Ok(exit_code)
- } else {
- bail!("odrefresh exited with {}", status)
+ pub fn kill(&self) -> Result<()> {
+ self.child.kill().context("Killing odrefresh process failed")
}
}
diff --git a/compos/composd/src/service.rs b/compos/composd/src/service.rs
index d3b73a1..351eae9 100644
--- a/compos/composd/src/service.rs
+++ b/compos/composd/src/service.rs
@@ -17,18 +17,20 @@
//! Implementation of IIsolatedCompilationService, called from system server when compilation is
//! desired.
+use crate::compilation_task::CompilationTask;
use crate::instance_manager::InstanceManager;
-use crate::odrefresh;
-use android_system_composd::aidl::android::system::composd::IIsolatedCompilationService::{
- BnIsolatedCompilationService, IIsolatedCompilationService,
+use crate::util::to_binder_result;
+use android_system_composd::aidl::android::system::composd::{
+ ICompilationTask::{BnCompilationTask, ICompilationTask},
+ ICompilationTaskCallback::ICompilationTaskCallback,
+ IIsolatedCompilationService::{BnIsolatedCompilationService, IIsolatedCompilationService},
};
use android_system_composd::binder::{self, BinderFeatures, Interface, Strong};
-use anyhow::{bail, Context, Result};
+use anyhow::{Context, Result};
use binder_common::new_binder_service_specific_error;
use compos_aidl_interface::aidl::com::android::compos::{
CompilationResult::CompilationResult, FdAnnotation::FdAnnotation,
};
-use log::{error, info};
pub struct IsolatedCompilationService {
instance_manager: InstanceManager,
@@ -42,9 +44,12 @@
impl Interface for IsolatedCompilationService {}
impl IIsolatedCompilationService for IsolatedCompilationService {
- fn runForcedCompileForTest(&self) -> binder::Result<()> {
+ fn startTestCompile(
+ &self,
+ callback: &Strong<dyn ICompilationTaskCallback>,
+ ) -> binder::Result<Strong<dyn ICompilationTask>> {
// TODO - check caller is system or shell/root?
- to_binder_result(self.do_run_forced_compile_for_test())
+ to_binder_result(self.do_start_test_compile(callback))
}
fn compile_cmd(
@@ -53,7 +58,7 @@
fd_annotation: &FdAnnotation,
) -> binder::Result<CompilationResult> {
// TODO - check caller is odrefresh
- to_binder_result(self.do_compile(args, fd_annotation))
+ to_binder_result(self.do_compile_cmd(args, fd_annotation))
}
fn compile(&self, _marshaled: &[u8], _fd_annotation: &FdAnnotation) -> binder::Result<i8> {
@@ -61,33 +66,19 @@
}
}
-fn to_binder_result<T>(result: Result<T>) -> binder::Result<T> {
- result.map_err(|e| {
- let message = format!("{:?}", e);
- error!("Returning binder error: {}", &message);
- new_binder_service_specific_error(-1, message)
- })
-}
-
impl IsolatedCompilationService {
- fn do_run_forced_compile_for_test(&self) -> Result<()> {
- info!("runForcedCompileForTest");
-
+ fn do_start_test_compile(
+ &self,
+ callback: &Strong<dyn ICompilationTaskCallback>,
+ ) -> Result<Strong<dyn ICompilationTask>> {
let comp_os = self.instance_manager.start_test_instance().context("Starting CompOS")?;
- let exit_code = odrefresh::run_forced_compile("test-artifacts")?;
+ let task = CompilationTask::start_test_compile(comp_os, callback)?;
- if exit_code != odrefresh::ExitCode::CompilationSuccess {
- bail!("Unexpected odrefresh result: {:?}", exit_code);
- }
-
- // The instance is needed until odrefresh is finished
- drop(comp_os);
-
- Ok(())
+ Ok(BnCompilationTask::new_binder(task, BinderFeatures::default()))
}
- fn do_compile(
+ fn do_compile_cmd(
&self,
args: &[String],
fd_annotation: &FdAnnotation,
diff --git a/compos/composd/src/util.rs b/compos/composd/src/util.rs
new file mode 100644
index 0000000..091fb15
--- /dev/null
+++ b/compos/composd/src/util.rs
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use android_system_composd::binder::Result as BinderResult;
+use anyhow::Result;
+use binder_common::new_binder_service_specific_error;
+use log::error;
+
+pub fn to_binder_result<T>(result: Result<T>) -> BinderResult<T> {
+ result.map_err(|e| {
+ let message = format!("{:?}", e);
+ error!("Returning binder error: {}", &message);
+ new_binder_service_specific_error(-1, message)
+ })
+}
diff --git a/compos/composd_cmd/Android.bp b/compos/composd_cmd/Android.bp
index 0081a0d..c230e13 100644
--- a/compos/composd_cmd/Android.bp
+++ b/compos/composd_cmd/Android.bp
@@ -11,6 +11,7 @@
"libanyhow",
"libbinder_rs",
"libclap",
+ "libcompos_common",
],
prefer_rlib: true,
apex_available: [
diff --git a/compos/composd_cmd/composd_cmd.rs b/compos/composd_cmd/composd_cmd.rs
index 04398c0..0422b44 100644
--- a/compos/composd_cmd/composd_cmd.rs
+++ b/compos/composd_cmd/composd_cmd.rs
@@ -17,10 +17,19 @@
//! Simple command-line tool to drive composd for testing and debugging.
use android_system_composd::{
- aidl::android::system::composd::IIsolatedCompilationService::IIsolatedCompilationService,
- binder::{wait_for_interface, ProcessState},
+ aidl::android::system::composd::{
+ ICompilationTaskCallback::{BnCompilationTaskCallback, ICompilationTaskCallback},
+ IIsolatedCompilationService::IIsolatedCompilationService,
+ },
+ binder::{
+ wait_for_interface, BinderFeatures, DeathRecipient, IBinder, Interface, ProcessState,
+ Result as BinderResult,
+ },
};
-use anyhow::{Context, Result};
+use anyhow::{bail, Context, Result};
+use compos_common::timeouts::timeouts;
+use std::sync::{Arc, Condvar, Mutex};
+use std::time::Duration;
fn main() -> Result<()> {
let app = clap::App::new("composd_cmd").arg(
@@ -35,11 +44,8 @@
ProcessState::start_thread_pool();
- let service = wait_for_interface::<dyn IIsolatedCompilationService>("android.system.composd")
- .context("Failed to connect to composd service")?;
-
match command {
- "forced-compile-test" => service.runForcedCompileForTest().context("Compilation failed")?,
+ "forced-compile-test" => run_forced_compile_for_test()?,
_ => panic!("Unexpected command {}", command),
}
@@ -47,3 +53,85 @@
Ok(())
}
+
+struct Callback(Arc<State>);
+
+#[derive(Default)]
+struct State {
+ mutex: Mutex<Option<Outcome>>,
+ completed: Condvar,
+}
+
+#[derive(Copy, Clone)]
+enum Outcome {
+ Succeeded,
+ Failed,
+}
+
+impl Interface for Callback {}
+
+impl ICompilationTaskCallback for Callback {
+ fn onSuccess(&self) -> BinderResult<()> {
+ self.0.set_outcome(Outcome::Succeeded);
+ Ok(())
+ }
+
+ fn onFailure(&self) -> BinderResult<()> {
+ self.0.set_outcome(Outcome::Failed);
+ Ok(())
+ }
+}
+
+impl State {
+ fn set_outcome(&self, outcome: Outcome) {
+ let mut guard = self.mutex.lock().unwrap();
+ *guard = Some(outcome);
+ drop(guard);
+ self.completed.notify_all();
+ }
+
+ fn wait(&self, duration: Duration) -> Result<Outcome> {
+ let (outcome, result) = self
+ .completed
+ .wait_timeout_while(self.mutex.lock().unwrap(), duration, |outcome| outcome.is_none())
+ .unwrap();
+ if result.timed_out() {
+ bail!("Timed out waiting for compilation")
+ }
+ Ok(outcome.unwrap())
+ }
+}
+
+fn run_forced_compile_for_test() -> Result<()> {
+ let service = wait_for_interface::<dyn IIsolatedCompilationService>("android.system.composd")
+ .context("Failed to connect to composd service")?;
+
+ let state = Arc::new(State::default());
+ let callback = Callback(state.clone());
+ let callback = BnCompilationTaskCallback::new_binder(callback, BinderFeatures::default());
+ let task = service.startTestCompile(&callback).context("Compilation failed")?;
+
+ // Make sure composd keeps going even if we don't hold a reference to its service.
+ drop(service);
+
+ let state_clone = state.clone();
+ let mut death_recipient = DeathRecipient::new(move || {
+ eprintln!("CompilationTask died");
+ state_clone.set_outcome(Outcome::Failed);
+ });
+ // Note that dropping death_recipient cancels this, so we can't use a temporary here.
+ task.as_binder().link_to_death(&mut death_recipient)?;
+
+ println!("Waiting");
+
+ match state.wait(timeouts()?.odrefresh_max_execution_time) {
+ Ok(Outcome::Succeeded) => Ok(()),
+ Ok(Outcome::Failed) => bail!("Compilation failed"),
+ Err(e) => {
+ if let Err(e) = task.cancel() {
+ eprintln!("Failed to cancel compilation: {:?}", e);
+ }
+ Err(e)
+ }
+ }
+}
diff --git a/demo/java/com/android/microdroid/demo/MainActivity.java b/demo/java/com/android/microdroid/demo/MainActivity.java
index bc87c3c..60e50bb 100644
--- a/demo/java/com/android/microdroid/demo/MainActivity.java
+++ b/demo/java/com/android/microdroid/demo/MainActivity.java
@@ -64,47 +64,14 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
- TextView consoleView = (TextView) findViewById(R.id.consoleOutput);
- TextView payloadView = (TextView) findViewById(R.id.payloadOutput);
Button runStopButton = (Button) findViewById(R.id.runStopButton);
- ScrollView scrollView = (ScrollView) findViewById(R.id.scrollConsoleOutput);
+ TextView consoleView = (TextView) findViewById(R.id.consoleOutput);
+ TextView logView = (TextView) findViewById(R.id.logOutput);
+ TextView payloadView = (TextView) findViewById(R.id.payloadOutput);
+ ScrollView scrollConsoleView = (ScrollView) findViewById(R.id.scrollConsoleOutput);
+ ScrollView scrollLogView = (ScrollView) findViewById(R.id.scrollLogOutput);
- // When the console output or payload output is updated, append the new line to the
- // corresponding text view.
VirtualMachineModel model = new ViewModelProvider(this).get(VirtualMachineModel.class);
- model.getConsoleOutput()
- .observeForever(
- new Observer<String>() {
- @Override
- public void onChanged(String line) {
- consoleView.append(line + "\n");
- scrollView.fullScroll(View.FOCUS_DOWN);
- }
- });
- model.getPayloadOutput()
- .observeForever(
- new Observer<String>() {
- @Override
- public void onChanged(String line) {
- payloadView.append(line + "\n");
- }
- });
-
- // When the VM status is updated, change the label of the button
- model.getStatus()
- .observeForever(
- new Observer<VirtualMachine.Status>() {
- @Override
- public void onChanged(VirtualMachine.Status status) {
- if (status == VirtualMachine.Status.RUNNING) {
- runStopButton.setText("Stop");
- consoleView.setText("");
- payloadView.setText("");
- } else {
- runStopButton.setText("Run");
- }
- }
- });
// When the button is clicked, run or stop the VM
runStopButton.setOnClickListener(
@@ -119,12 +86,86 @@
}
}
});
+
+ // When the VM status is updated, change the label of the button
+ model.getStatus()
+ .observeForever(
+ new Observer<VirtualMachine.Status>() {
+ @Override
+ public void onChanged(VirtualMachine.Status status) {
+ if (status == VirtualMachine.Status.RUNNING) {
+ runStopButton.setText("Stop");
+ // Clear the outputs from the previous run
+ consoleView.setText("");
+ logView.setText("");
+ payloadView.setText("");
+ } else {
+ runStopButton.setText("Run");
+ }
+ }
+ });
+
+ // When the console, log, or payload output is updated, append the new line to the
+ // corresponding text view.
+ model.getConsoleOutput()
+ .observeForever(
+ new Observer<String>() {
+ @Override
+ public void onChanged(String line) {
+ consoleView.append(line + "\n");
+ scrollConsoleView.fullScroll(View.FOCUS_DOWN);
+ }
+ });
+ model.getLogOutput()
+ .observeForever(
+ new Observer<String>() {
+ @Override
+ public void onChanged(String line) {
+ logView.append(line + "\n");
+ scrollLogView.fullScroll(View.FOCUS_DOWN);
+ }
+ });
+ model.getPayloadOutput()
+ .observeForever(
+ new Observer<String>() {
+ @Override
+ public void onChanged(String line) {
+ payloadView.append(line + "\n");
+ }
+ });
}
- /** Models a virtual machine and console output from it. */
+ /** Reads data from an input stream and posts it to the output data */
+ static class Reader implements Runnable {
+ private final String mName;
+ private final MutableLiveData<String> mOutput;
+ private final InputStream mStream;
+
+ Reader(String name, MutableLiveData<String> output, InputStream stream) {
+ mName = name;
+ mOutput = output;
+ mStream = stream;
+ }
+
+ @Override
+ public void run() {
+ try {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(mStream));
+ String line;
+ while ((line = reader.readLine()) != null && !Thread.interrupted()) {
+ mOutput.postValue(line);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Exception while posting " + mName + " output: " + e.getMessage());
+ }
+ }
+ }
+
+ /** Models a virtual machine and outputs from it. */
public static class VirtualMachineModel extends AndroidViewModel {
private VirtualMachine mVirtualMachine;
private final MutableLiveData<String> mConsoleOutput = new MutableLiveData<>();
+ private final MutableLiveData<String> mLogOutput = new MutableLiveData<>();
private final MutableLiveData<String> mPayloadOutput = new MutableLiveData<>();
private final MutableLiveData<VirtualMachine.Status> mStatus = new MutableLiveData<>();
private ExecutorService mExecutorService;
@@ -134,20 +175,11 @@
mStatus.setValue(VirtualMachine.Status.DELETED);
}
- private static void postOutput(MutableLiveData<String> output, InputStream stream)
- throws IOException {
- BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
- String line;
- while ((line = reader.readLine()) != null && !Thread.interrupted()) {
- output.postValue(line);
- }
- }
-
/** Runs a VM */
public void run(boolean debug) {
// Create a VM and run it.
// TODO(jiyong): remove the call to idsigPath
- mExecutorService = Executors.newFixedThreadPool(3);
+ mExecutorService = Executors.newFixedThreadPool(4);
VirtualMachineCallback callback =
new VirtualMachineCallback() {
@@ -162,23 +194,8 @@
return;
}
- mService.execute(
- new Runnable() {
- @Override
- public void run() {
- try {
- postOutput(
- mPayloadOutput,
- new FileInputStream(
- stream.getFileDescriptor()));
- } catch (IOException e) {
- Log.e(
- TAG,
- "IOException while reading payload: "
- + e.getMessage());
- }
- }
- });
+ InputStream input = new FileInputStream(stream.getFileDescriptor());
+ mService.execute(new Reader("payload", mPayloadOutput, input));
}
@Override
@@ -261,29 +278,23 @@
VirtualMachineConfig config = builder.build();
VirtualMachineManager vmm = VirtualMachineManager.getInstance(getApplication());
mVirtualMachine = vmm.getOrCreate("demo_vm", config);
+ try {
+ mVirtualMachine.setConfig(config);
+ } catch (VirtualMachineException e) {
+ mVirtualMachine.delete();
+ mVirtualMachine = vmm.create("demo_vm", config);
+ }
mVirtualMachine.run();
mVirtualMachine.setCallback(callback);
mStatus.postValue(mVirtualMachine.getStatus());
+
+ InputStream console = mVirtualMachine.getConsoleOutputStream();
+ InputStream log = mVirtualMachine.getLogOutputStream();
+ mExecutorService.execute(new Reader("console", mConsoleOutput, console));
+ mExecutorService.execute(new Reader("log", mLogOutput, log));
} catch (VirtualMachineException e) {
throw new RuntimeException(e);
}
-
- // Read console output from the VM in the background
- mExecutorService.execute(
- new Runnable() {
- @Override
- public void run() {
- try {
- postOutput(
- mConsoleOutput, mVirtualMachine.getConsoleOutputStream());
- } catch (IOException | VirtualMachineException e) {
- Log.e(
- TAG,
- "Exception while posting console output: "
- + e.getMessage());
- }
- }
- });
}
/** Stops the running VM */
@@ -303,6 +314,11 @@
return mConsoleOutput;
}
+ /** Returns the log output from the VM */
+ public LiveData<String> getLogOutput() {
+ return mLogOutput;
+ }
+
/** Returns the payload output from the VM */
public LiveData<String> getPayloadOutput() {
return mPayloadOutput;
diff --git a/demo/res/layout/activity_main.xml b/demo/res/layout/activity_main.xml
index e100027..f0e35d6 100644
--- a/demo/res/layout/activity_main.xml
+++ b/demo/res/layout/activity_main.xml
@@ -62,17 +62,50 @@
<ScrollView
android:id="@+id/scrollConsoleOutput"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="2">
- <TextView
- android:id="@+id/consoleOutput"
+ <HorizontalScrollView
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="#FFEB3B"
- android:fontFamily="monospace"
- android:textColor="#000000" />
+ android:layout_height="match_parent">
+
+ <TextView
+ android:id="@+id/consoleOutput"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="#FFEB3B"
+ android:fontFamily="monospace"
+ android:textSize="10sp"
+ android:textColor="#000000" />
+ </HorizontalScrollView>
+ </ScrollView>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:text="Log output:" />
+
+ <ScrollView
+ android:id="@+id/scrollLogOutput"
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:layout_weight="2">
+
+ <HorizontalScrollView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:id="@+id/logOutput"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="#FFEB3B"
+ android:fontFamily="monospace"
+ android:textSize="10sp"
+ android:textColor="#000000" />
+ </HorizontalScrollView>
</ScrollView>
</LinearLayout>
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index 2da7ecb..63c9288 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -113,6 +113,9 @@
private @Nullable ParcelFileDescriptor mConsoleReader;
private @Nullable ParcelFileDescriptor mConsoleWriter;
+ private @Nullable ParcelFileDescriptor mLogReader;
+ private @Nullable ParcelFileDescriptor mLogWriter;
+
private final ExecutorService mExecutorService = Executors.newCachedThreadPool();
static {
@@ -297,6 +300,12 @@
mConsoleWriter = pipe[1];
}
+ if (mLogReader == null && mLogWriter == null) {
+ ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+ mLogReader = pipe[0];
+ mLogWriter = pipe[1];
+ }
+
VirtualMachineAppConfig appConfig = getConfig().toParcel();
// Fill the idsig file by hashing the apk
@@ -310,7 +319,7 @@
android.system.virtualizationservice.VirtualMachineConfig vmConfigParcel =
android.system.virtualizationservice.VirtualMachineConfig.appConfig(appConfig);
- mVirtualMachine = service.createVm(vmConfigParcel, mConsoleWriter);
+ mVirtualMachine = service.createVm(vmConfigParcel, mConsoleWriter, mLogWriter);
mVirtualMachine.registerCallback(
new IVirtualMachineCallback.Stub() {
@Override
@@ -377,6 +386,14 @@
return new FileInputStream(mConsoleReader.getFileDescriptor());
}
+ /** Returns the stream object representing the log output from the virtual machine. */
+ public @NonNull InputStream getLogOutputStream() throws VirtualMachineException {
+ if (mLogReader == null) {
+ throw new VirtualMachineException("Log output not available");
+ }
+ return new FileInputStream(mLogReader.getFileDescriptor());
+ }
+
/**
* Stops this virtual machine. Stopping a virtual machine is like pulling the plug on a real
* computer; the machine halts immediately. Software running on the virtual machine is not
@@ -401,6 +418,7 @@
final File vmRootDir = mConfigFilePath.getParentFile();
mConfigFilePath.delete();
mInstanceFilePath.delete();
+ mIdsigFilePath.delete();
vmRootDir.delete();
}
diff --git a/microdroid/bootconfig.app_debuggable b/microdroid/bootconfig.app_debuggable
index f65d4cd..98d326a 100644
--- a/microdroid/bootconfig.app_debuggable
+++ b/microdroid/bootconfig.app_debuggable
@@ -8,3 +8,7 @@
# ADB is supported but rooting is prohibited.
androidboot.adb.enabled=1
+
+# logd is enabled
+# TODO(b/200914564) Filter only the log from the app
+androidboot.logd.enabled=1
diff --git a/microdroid/bootconfig.full_debuggable b/microdroid/bootconfig.full_debuggable
index 0d0457c..fd8a83e 100644
--- a/microdroid/bootconfig.full_debuggable
+++ b/microdroid/bootconfig.full_debuggable
@@ -9,3 +9,6 @@
# ro.adb.secure is still 0 (see build.prop) which means that adbd is started
# unrooted by default. To root, developer should explicitly execute `adb root`.
androidboot.adb.enabled=1
+
+# logd is enabled
+androidboot.logd.enabled=1
diff --git a/microdroid/bootconfig.normal b/microdroid/bootconfig.normal
index f7cdfc7..9cfb55a 100644
--- a/microdroid/bootconfig.normal
+++ b/microdroid/bootconfig.normal
@@ -6,3 +6,6 @@
# ADB is not enabled.
androidboot.adb.enabled=0
+
+# logd is not enabled
+androidboot.logd.enabled=0
diff --git a/microdroid/bootconfig.x86_64 b/microdroid/bootconfig.x86_64
index 20d64f7..2977ee3 100644
--- a/microdroid/bootconfig.x86_64
+++ b/microdroid/bootconfig.x86_64
@@ -1 +1 @@
-androidboot.boot_devices = pci0000:00/0000:00:02.0,pci0000:00/0000:00:03.0,pci0000:00/0000:00:04.0
+androidboot.boot_devices = pci0000:00/0000:00:03.0,pci0000:00/0000:00:04.0,pci0000:00/0000:00:05.0
diff --git a/microdroid/init.rc b/microdroid/init.rc
index 078b51d..ad551cc 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -74,9 +74,11 @@
chmod 0664 /dev/cpuset/background/tasks
chmod 0664 /dev/cpuset/system-background/tasks
+on init && property:ro.boot.logd.enabled=1
# Start logd before any other services run to ensure we capture all of their logs.
start logd
+on init
start servicemanager
# TODO(b/185767624): remove hidl after full keymint support
@@ -85,7 +87,7 @@
on init && property:ro.boot.adb.enabled=1
start adbd
-on load_persist_props_action
+on load_persist_props_action && property:ro.boot.logd.enabled=1
start logd
start logd-reinit
@@ -193,6 +195,11 @@
seclabel u:r:shell:s0
setenv HOSTNAME console
+service seriallogging /system/bin/logcat -b all -v threadtime -f /dev/hvc1 *:V
+ disabled
+ user logd
+ group root logd
+
on fs
write /dev/event-log-tags "# content owned by logd
"
diff --git a/microdroid/ueventd.rc b/microdroid/ueventd.rc
index 271e134..85f2f9d 100644
--- a/microdroid/ueventd.rc
+++ b/microdroid/ueventd.rc
@@ -24,3 +24,6 @@
# these should not be world writable
/dev/rtc0 0640 system system
/dev/tty0 0660 root system
+
+# Virtual console for logcat
+/dev/hvc1 0660 logd logd
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index ac62e58..f666294 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -49,6 +49,7 @@
const VMADDR_CID_HOST: u32 = 2;
const APEX_CONFIG_DONE_PROP: &str = "apex_config.done";
+const LOGD_ENABLED_PROP: &str = "ro.boot.logd.enabled";
fn get_vms_rpc_binder() -> Result<Strong<dyn IVirtualMachineService>> {
// SAFETY: AIBinder returned by RpcClient has correct reference count, and the ownership can be
@@ -68,7 +69,10 @@
fn main() {
if let Err(e) = try_main() {
- error!("failed with {:?}", e);
+ error!("Failed with {:?}. Shutting down...", e);
+ if let Err(e) = system_properties::write("sys.powerctl", "shutdown") {
+ error!("failed to shutdown {:?}", e);
+ }
std::process::exit(1);
}
}
@@ -223,6 +227,12 @@
info!("notifying payload started");
service.notifyPayloadStarted()?;
+ // Start logging if enabled
+ // TODO(b/200914564) set filterspec if debug_level is app_only
+ if system_properties::read(LOGD_ENABLED_PROP)? == "1" {
+ system_properties::write("ctl.start", "seriallogging")?;
+ }
+
let exit_status = command.spawn()?.wait()?;
if let Some(code) = exit_status.code() {
info!("notifying payload finished");
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index 0b0810f..493fc93 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -2,12 +2,12 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-android_test_helper_app {
+android_test {
name: "MicrodroidTestApp",
+ test_suites: ["device-tests"],
srcs: ["src/java/**/*.java"],
- libs: [
- "android.system.virtualmachine",
- ],
+ static_libs: ["androidx.test.runner"],
+ libs: ["android.system.virtualmachine"],
jni_libs: ["MicrodroidTestNativeLib"],
platform_apis: true,
use_embedded_native_libs: true,
diff --git a/tests/testapk/AndroidManifest.xml b/tests/testapk/AndroidManifest.xml
index 94f49dd..21abeb5 100644
--- a/tests/testapk/AndroidManifest.xml
+++ b/tests/testapk/AndroidManifest.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2021 The Android Open Source Project
+
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
@@ -14,13 +15,10 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.microdroid.test">
- <application android:label="Microdroid Test">
+ <application>
<uses-library android:name="android.system.virtualmachine" android:required="true" />
- <activity android:name="TestActivity" android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
</application>
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.microdroid.test"
+ android:label="Microdroid Test" />
</manifest>
diff --git a/tests/testapk/AndroidTest.xml b/tests/testapk/AndroidTest.xml
new file mode 100644
index 0000000..25b1001
--- /dev/null
+++ b/tests/testapk/AndroidTest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs sample instrumentation test.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="MicrodroidTestApp.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.microdroid.test" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
new file mode 100644
index 0000000..5e465d5
--- /dev/null
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.microdroid.test;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class MicrodroidTests {
+ @Test
+ public void testNothing() {
+ assertTrue(true);
+ }
+}
diff --git a/tests/testapk/src/java/com/android/microdroid/test/TestActivity.java b/tests/testapk/src/java/com/android/microdroid/test/TestActivity.java
deleted file mode 100644
index ad34ca4..0000000
--- a/tests/testapk/src/java/com/android/microdroid/test/TestActivity.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.microdroid.test;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.system.virtualmachine.VirtualMachine;
-import android.system.virtualmachine.VirtualMachineConfig;
-import android.system.virtualmachine.VirtualMachineException;
-import android.system.virtualmachine.VirtualMachineManager;
-
-public class TestActivity extends Activity {
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- VirtualMachine vm1 = createAndRunVirtualMachine("vm1");
- VirtualMachine vm2 = createAndRunVirtualMachine("vm2");
- }
-
- private VirtualMachine createAndRunVirtualMachine(String name) {
- VirtualMachine vm;
- try {
- VirtualMachineConfig config =
- new VirtualMachineConfig.Builder(this, "assets/vm_config.json")
- .build();
-
- VirtualMachineManager vmm = VirtualMachineManager.getInstance(this);
- vm = vmm.create(name, config);
- vm.run();
- } catch (VirtualMachineException e) {
- throw new RuntimeException(e);
- }
- return vm;
- }
-}
diff --git a/tests/vsock_test.cc b/tests/vsock_test.cc
index 480d05a..0b863a9 100644
--- a/tests/vsock_test.cc
+++ b/tests/vsock_test.cc
@@ -85,7 +85,7 @@
VirtualMachineConfig config(std::move(raw_config));
sp<IVirtualMachine> vm;
- status = virtualization_service->createVm(config, std::nullopt, &vm);
+ status = virtualization_service->createVm(config, std::nullopt, std::nullopt, &vm);
ASSERT_TRUE(status.isOk()) << "Error creating VM: " << status;
int32_t cid;
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
index 8be7331..e417ec4 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
@@ -23,10 +23,13 @@
interface IVirtualizationService {
/**
* Create the VM with the given config file, and return a handle to it ready to start it. If
- * `logFd` is provided then console logs from the VM will be sent to it.
+ * `consoleFd` is provided then console output from the VM will be sent to it. If `osLogFd` is
+ * provided then the OS-level logs will be sent to it. `osLogFd` is supported only when the OS
+ * running in the VM has the logging system. In case of Microdroid, the logging system is logd.
*/
- IVirtualMachine createVm(
- in VirtualMachineConfig config, in @nullable ParcelFileDescriptor logFd);
+ IVirtualMachine createVm(in VirtualMachineConfig config,
+ in @nullable ParcelFileDescriptor consoleFd,
+ in @nullable ParcelFileDescriptor osLogFd);
/**
* Initialise an empty partition image of the given size to be used as a writable partition.
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 2f901b4..5d64684 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -122,10 +122,12 @@
fn createVm(
&self,
config: &VirtualMachineConfig,
+ console_fd: Option<&ParcelFileDescriptor>,
log_fd: Option<&ParcelFileDescriptor>,
) -> binder::Result<Strong<dyn IVirtualMachine>> {
check_manage_access()?;
let state = &mut *self.state.lock().unwrap();
+ let mut console_fd = console_fd.map(clone_file).transpose()?;
let mut log_fd = log_fd.map(clone_file).transpose()?;
let requester_uid = ThreadState::get_calling_uid();
let requester_sid = get_calling_sid()?;
@@ -160,6 +162,9 @@
// doesn't understand the bootconfig parameters.
if let VirtualMachineConfig::AppConfig(config) = config {
if config.debugLevel != DebugLevel::FULL {
+ console_fd = None;
+ }
+ if config.debugLevel == DebugLevel::NONE {
log_fd = None;
}
}
@@ -212,6 +217,7 @@
params: config.params.to_owned(),
protected: config.protectedVm,
memory_mib: config.memoryMib.try_into().ok().and_then(NonZeroU32::new),
+ console_fd,
log_fd,
indirect_files,
};
@@ -250,7 +256,13 @@
)
})?;
let image = clone_file(image_fd)?;
-
+ // initialize the file. Any data in the file will be erased.
+ image.set_len(0).map_err(|e| {
+ new_binder_exception(
+ ExceptionCode::SERVICE_SPECIFIC,
+ format!("Failed to reset a file: {}", e),
+ )
+ })?;
let mut part = QcowFile::new(image, size).map_err(|e| {
new_binder_exception(
ExceptionCode::SERVICE_SPECIFIC,
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index 8a5a7dd..08be052 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -45,6 +45,7 @@
pub params: Option<String>,
pub protected: bool,
pub memory_mib: Option<NonZeroU32>,
+ pub console_fd: Option<File>,
pub log_fd: Option<File>,
pub indirect_files: Vec<File>,
}
@@ -180,8 +181,8 @@
/// `self.vm_state` to avoid holding the lock on `vm_state` while it is running.
fn monitor(&self, child: Arc<SharedChild>) {
match child.wait() {
- Err(e) => error!("Error waiting for crosvm instance to die: {}", e),
- Ok(status) => info!("crosvm exited with status {}", status),
+ Err(e) => error!("Error waiting for crosvm({}) instance to die: {}", child.id(), e),
+ Ok(status) => info!("crosvm({}) exited with status {}", child.id(), status),
}
let mut vm_state = self.vm_state.lock().unwrap();
@@ -219,9 +220,11 @@
pub fn kill(&self) {
let vm_state = &*self.vm_state.lock().unwrap();
if let VmState::Running { child } = vm_state {
+ let id = child.id();
+ debug!("Killing crosvm({})", id);
// TODO: Talk to crosvm to shutdown cleanly.
if let Err(e) = child.kill() {
- error!("Error killing crosvm instance: {}", e);
+ error!("Error killing crosvm({}) instance: {}", id, e);
}
}
}
@@ -243,28 +246,35 @@
command.arg("--mem").arg(memory_mib.to_string());
}
+ // Keep track of what file descriptors should be mapped to the crosvm process.
+ let mut preserved_fds = config.indirect_files.iter().map(|file| file.as_raw_fd()).collect();
+
// Setup the serial devices.
// 1. uart device: used as the output device by bootloaders and as early console by linux
// 2. virtio-console device: used as the console device
+ // 3. virtio-console device: used as the logcat output
//
- // When log_fd is not specified, the devices are attached to sink, which means what's written
- // there is discarded.
- //
+ // When [console|log]_fd is not specified, the devices are attached to sink, which means what's
+ // written there is discarded.
+ let mut format_serial_arg = |fd: &Option<File>| {
+ let path = fd.as_ref().map(|fd| add_preserved_fd(&mut preserved_fds, fd));
+ let type_arg = path.as_ref().map_or("type=sink", |_| "type=file");
+ let path_arg = path.as_ref().map_or(String::new(), |path| format!(",path={}", path));
+ format!("{}{}", type_arg, path_arg)
+ };
+ let console_arg = format_serial_arg(&config.console_fd);
+ let log_arg = format_serial_arg(&config.log_fd);
+
// Warning: Adding more serial devices requires you to shift the PCI device ID of the boot
// disks in bootconfig.x86_64. This is because x86 crosvm puts serial devices and the block
// devices in the same PCI bus and serial devices comes before the block devices. Arm crosvm
// doesn't have the issue.
- let backend = if let Some(log_fd) = config.log_fd {
- command.stdout(log_fd);
- "stdout"
- } else {
- "sink"
- };
- command.arg(format!("--serial=type={},hardware=serial", backend));
- command.arg(format!("--serial=type={},hardware=virtio-console", backend));
-
- // Keep track of what file descriptors should be mapped to the crosvm process.
- let mut preserved_fds = config.indirect_files.iter().map(|file| file.as_raw_fd()).collect();
+ // /dev/ttyS0
+ command.arg(format!("--serial={},hardware=serial", &console_arg));
+ // /dev/hvc0
+ command.arg(format!("--serial={},hardware=virtio-console,num=1", &console_arg));
+ // /dev/hvc1
+ command.arg(format!("--serial={},hardware=virtio-console,num=2", &log_arg));
if let Some(bootloader) = &config.bootloader {
command.arg("--bios").arg(add_preserved_fd(&mut preserved_fds, bootloader));
@@ -293,6 +303,7 @@
info!("Running {:?}", command);
let result = SharedChild::spawn(&mut command)?;
+ debug!("Spawned crosvm({}).", result.id());
Ok(result)
}
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 7e2a925..87bcda7 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -57,12 +57,16 @@
#[structopt(short, long)]
daemonize: bool,
+ /// Path to file for VM console output.
+ #[structopt(long)]
+ console: Option<PathBuf>,
+
/// Path to file for VM log output.
- #[structopt(short, long)]
+ #[structopt(long)]
log: Option<PathBuf>,
/// Debug level of the VM. Supported values: "none" (default), "app_only", and "full".
- #[structopt(short, long, default_value = "none", parse(try_from_str=parse_debug_level))]
+ #[structopt(long, default_value = "none", parse(try_from_str=parse_debug_level))]
debug: DebugLevel,
/// Memory size (in MiB) of the VM. If unspecified, defaults to the value of `memory_mib`
@@ -80,9 +84,9 @@
#[structopt(short, long)]
daemonize: bool,
- /// Path to file for VM log output.
- #[structopt(short, long)]
- log: Option<PathBuf>,
+ /// Path to file for VM console output.
+ #[structopt(long)]
+ console: Option<PathBuf>,
},
/// Stop a virtual machine running in the background
Stop {
@@ -134,7 +138,7 @@
.context("Failed to find VirtualizationService")?;
match opt {
- Opt::RunApp { apk, idsig, instance, config_path, daemonize, log, debug, mem } => {
+ Opt::RunApp { apk, idsig, instance, config_path, daemonize, console, log, debug, mem } => {
command_run_app(
service,
&apk,
@@ -142,13 +146,14 @@
&instance,
&config_path,
daemonize,
+ console.as_deref(),
log.as_deref(),
debug,
mem,
)
}
- Opt::Run { config, daemonize, log } => {
- command_run(service, &config, daemonize, log.as_deref(), /* mem */ None)
+ Opt::Run { config, daemonize, console } => {
+ command_run(service, &config, daemonize, console.as_deref(), /* mem */ None)
}
Opt::Stop { cid } => command_stop(service, cid),
Opt::List => command_list(service),
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 2d771fc..15775cb 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -44,6 +44,7 @@
instance: &Path,
config_path: &str,
daemonize: bool,
+ console_path: Option<&Path>,
log_path: Option<&Path>,
debug_level: DebugLevel,
mem: Option<u32>,
@@ -76,7 +77,14 @@
debugLevel: debug_level,
memoryMib: mem.unwrap_or(0) as i32, // 0 means use the VM default
});
- run(service, &config, &format!("{:?}!{:?}", apk, config_path), daemonize, log_path)
+ run(
+ service,
+ &config,
+ &format!("{:?}!{:?}", apk, config_path),
+ daemonize,
+ console_path,
+ log_path,
+ )
}
/// Run a VM from the given configuration file.
@@ -84,7 +92,7 @@
service: Strong<dyn IVirtualizationService>,
config_path: &Path,
daemonize: bool,
- log_path: Option<&Path>,
+ console_path: Option<&Path>,
mem: Option<u32>,
) -> Result<(), Error> {
let config_file = File::open(config_path).context("Failed to open config file")?;
@@ -98,7 +106,8 @@
&VirtualMachineConfig::RawConfig(config),
&format!("{:?}", config_path),
daemonize,
- log_path,
+ console_path,
+ None,
)
}
@@ -119,9 +128,20 @@
config: &VirtualMachineConfig,
config_path: &str,
daemonize: bool,
+ console_path: Option<&Path>,
log_path: Option<&Path>,
) -> Result<(), Error> {
- let stdout = if let Some(log_path) = log_path {
+ let console = if let Some(console_path) = console_path {
+ Some(ParcelFileDescriptor::new(
+ File::create(console_path)
+ .with_context(|| format!("Failed to open console file {:?}", console_path))?,
+ ))
+ } else if daemonize {
+ None
+ } else {
+ Some(ParcelFileDescriptor::new(duplicate_stdout()?))
+ };
+ let log = if let Some(log_path) = log_path {
Some(ParcelFileDescriptor::new(
File::create(log_path)
.with_context(|| format!("Failed to open log file {:?}", log_path))?,
@@ -131,7 +151,9 @@
} else {
Some(ParcelFileDescriptor::new(duplicate_stdout()?))
};
- let vm = service.createVm(config, stdout.as_ref()).context("Failed to create VM")?;
+
+ let vm =
+ service.createVm(config, console.as_ref(), log.as_ref()).context("Failed to create VM")?;
let cid = vm.getCid().context("Failed to get CID")?;
println!(