Call odrefresh in VM from composd
composd needs to prepare the directory, run a fd_server, etc. then
request to run odrefresh in the VM.
`FdServerConfig` and `FdServer` are introduced to make starting a
fd_server from composd easier.
Also, add a testing command in composd_cmd.
Bug: 205750213
Test: atest ComposHostTestCases
Test: With some local hacks in ART, with SELinux disabled in the VM,
odrefresh completed with exit code 80 and output files that
look normal (at least sizes are).
Change-Id: I52c9d1ad369eea6d423831adb42087a3bcf30d66
diff --git a/compos/composd/Android.bp b/compos/composd/Android.bp
index 2ab12e3..735b9a5 100644
--- a/compos/composd/Android.bp
+++ b/compos/composd/Android.bp
@@ -21,6 +21,7 @@
"libminijail_rust",
"libnix",
"libnum_traits",
+ "liblibc",
"liblog_rust",
"librustutils",
"libshared_child",
diff --git a/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl b/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
index 6b3a6bb..6f4476c 100644
--- a/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
+++ b/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
@@ -21,12 +21,25 @@
interface IIsolatedCompilationService {
/**
* Run "odrefresh --dalvik-cache=pending-test --force-compile" in a test instance of CompOS.
+ *
* 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.
*/
ICompilationTask startTestCompile(ICompilationTaskCallback callback);
+
+ /**
+ * Run odrefresh in a test instance of CompOS until completed or failed.
+ *
+ * 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.
+ *
+ * TODO(205750213): Change the API to async.
+ */
+ byte startTestOdrefresh();
}
diff --git a/compos/composd/src/composd_main.rs b/compos/composd/src/composd_main.rs
index 57f64a5..67b5974 100644
--- a/compos/composd/src/composd_main.rs
+++ b/compos/composd/src/composd_main.rs
@@ -35,8 +35,10 @@
use std::sync::Arc;
fn try_main() -> Result<()> {
+ let debuggable = env!("TARGET_BUILD_VARIANT") != "user";
+ let log_level = if debuggable { log::Level::Debug } else { log::Level::Info };
android_logger::init_once(
- android_logger::Config::default().with_tag("composd").with_min_level(log::Level::Info),
+ android_logger::Config::default().with_tag("composd").with_min_level(log_level),
);
ProcessState::start_thread_pool();
diff --git a/compos/composd/src/fd_server_helper.rs b/compos/composd/src/fd_server_helper.rs
index 2dab9a3..24dc9e7 100644
--- a/compos/composd/src/fd_server_helper.rs
+++ b/compos/composd/src/fd_server_helper.rs
@@ -15,46 +15,92 @@
*/
//! A helper library to start a fd_server.
-//!
-//! TODO(205750213): Make it easy to spawn a fd_server.
use anyhow::{Context, Result};
-use log::debug;
+use log::{debug, warn};
use minijail::Minijail;
use nix::fcntl::OFlag;
use nix::unistd::pipe2;
use std::fs::File;
use std::io::Read;
-use std::os::unix::io::{AsRawFd, FromRawFd};
+use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::path::Path;
const FD_SERVER_BIN: &str = "/apex/com.android.virt/bin/fd_server";
-#[allow(dead_code)]
-fn spawn_fd_server(input_fds: &[i32], output_fds: &[i32], ready_file: File) -> Result<Minijail> {
- let mut inheritable_fds = Vec::new();
- let mut args = vec![FD_SERVER_BIN.to_string()];
- for fd in input_fds {
- args.push("--ro-fds".to_string());
- args.push(fd.to_string());
- inheritable_fds.push(*fd);
- }
- for fd in output_fds {
- args.push("--rw-fds".to_string());
- args.push(fd.to_string());
- inheritable_fds.push(*fd);
- }
- let ready_fd = ready_file.as_raw_fd();
- args.push("--ready-fd".to_string());
- args.push(ready_fd.to_string());
- inheritable_fds.push(ready_fd);
-
- let jail = Minijail::new()?;
- let _pid = jail.run(Path::new(FD_SERVER_BIN), &inheritable_fds, &args)?;
- Ok(jail)
+/// Config for starting a `FdServer`
+#[derive(Default)]
+pub struct FdServerConfig {
+ /// List of file FDs exposed for read-only operations.
+ pub ro_file_fds: Vec<RawFd>,
+ /// List of file FDs exposed for read-write operations.
+ pub rw_file_fds: Vec<RawFd>,
+ /// List of directory FDs exposed for read-only operations.
+ pub ro_dir_fds: Vec<RawFd>,
+ /// List of directory FDs exposed for read-write operations.
+ pub rw_dir_fds: Vec<RawFd>,
}
-#[allow(dead_code)]
+impl FdServerConfig {
+ /// Creates a `FdServer` based on the current config.
+ pub fn into_fd_server(self) -> Result<FdServer> {
+ let (ready_read_fd, ready_write_fd) = create_pipe()?;
+ let fd_server_jail = self.do_spawn_fd_server(ready_write_fd)?;
+ wait_for_fd_server_ready(ready_read_fd)?;
+ Ok(FdServer { jailed_process: fd_server_jail })
+ }
+
+ fn do_spawn_fd_server(self, ready_file: File) -> Result<Minijail> {
+ let mut inheritable_fds = Vec::new();
+ let mut args = vec![FD_SERVER_BIN.to_string()];
+ for fd in self.ro_file_fds {
+ args.push("--ro-fds".to_string());
+ args.push(fd.to_string());
+ inheritable_fds.push(fd);
+ }
+ for fd in self.rw_file_fds {
+ args.push("--rw-fds".to_string());
+ args.push(fd.to_string());
+ inheritable_fds.push(fd);
+ }
+ for fd in self.ro_dir_fds {
+ args.push("--ro-dirs".to_string());
+ args.push(fd.to_string());
+ inheritable_fds.push(fd);
+ }
+ for fd in self.rw_dir_fds {
+ args.push("--rw-dirs".to_string());
+ args.push(fd.to_string());
+ inheritable_fds.push(fd);
+ }
+ let ready_fd = ready_file.as_raw_fd();
+ args.push("--ready-fd".to_string());
+ args.push(ready_fd.to_string());
+ inheritable_fds.push(ready_fd);
+
+ debug!("Spawn fd_server {:?} (inheriting FDs: {:?})", args, inheritable_fds);
+ let jail = Minijail::new()?;
+ let _pid = jail.run(Path::new(FD_SERVER_BIN), &inheritable_fds, &args)?;
+ Ok(jail)
+ }
+}
+
+/// `FdServer` represents a running `fd_server` process. The process lifetime is associated with
+/// the instance lifetime.
+pub struct FdServer {
+ jailed_process: Minijail,
+}
+
+impl Drop for FdServer {
+ fn drop(&mut self) {
+ if let Err(e) = self.jailed_process.kill() {
+ if !matches!(e, minijail::Error::Killed(_)) {
+ warn!("Failed to kill fd_server: {}", e);
+ }
+ }
+ }
+}
+
fn create_pipe() -> Result<(File, File)> {
let (raw_read, raw_write) = pipe2(OFlag::O_CLOEXEC)?;
// SAFETY: We are the sole owners of these fds as they were just created.
@@ -63,7 +109,6 @@
Ok((read_fd, write_fd))
}
-#[allow(dead_code)]
fn wait_for_fd_server_ready(mut ready_fd: File) -> Result<()> {
let mut buffer = [0];
// When fd_server is ready it closes its end of the pipe. And if it exits, the pipe is also
diff --git a/compos/composd/src/service.rs b/compos/composd/src/service.rs
index d9963d1..3738e18 100644
--- a/compos/composd/src/service.rs
+++ b/compos/composd/src/service.rs
@@ -18,7 +18,9 @@
//! desired.
use crate::compilation_task::CompilationTask;
+use crate::fd_server_helper::FdServerConfig;
use crate::instance_manager::InstanceManager;
+use crate::instance_starter::CompOsInstance;
use crate::util::to_binder_result;
use android_system_composd::aidl::android::system::composd::{
ICompilationTask::{BnCompilationTask, ICompilationTask},
@@ -29,7 +31,12 @@
self, BinderFeatures, ExceptionCode, Interface, Status, Strong, ThreadState,
};
use anyhow::{Context, Result};
-use rustutils::users::{AID_ROOT, AID_SYSTEM};
+use compos_common::COMPOS_DATA_ROOT;
+use rustutils::{system_properties, users::AID_ROOT, users::AID_SYSTEM};
+use std::fs::{create_dir, File, OpenOptions};
+use std::os::unix::fs::OpenOptionsExt;
+use std::os::unix::io::AsRawFd;
+use std::path::{Path, PathBuf};
use std::sync::Arc;
pub struct IsolatedCompilationService {
@@ -50,13 +57,14 @@
&self,
callback: &Strong<dyn ICompilationTaskCallback>,
) -> binder::Result<Strong<dyn ICompilationTask>> {
- let calling_uid = ThreadState::get_calling_uid();
- // This should only be called by system server, or root while testing
- if calling_uid != AID_SYSTEM && calling_uid != AID_ROOT {
- return Err(Status::new_exception(ExceptionCode::SECURITY, None));
- }
+ check_test_permissions()?;
to_binder_result(self.do_start_test_compile(callback))
}
+
+ fn startTestOdrefresh(&self) -> binder::Result<i8> {
+ check_test_permissions()?;
+ to_binder_result(self.do_odrefresh_for_test())
+ }
}
impl IsolatedCompilationService {
@@ -70,4 +78,62 @@
Ok(BnCompilationTask::new_binder(task, BinderFeatures::default()))
}
+
+ fn do_odrefresh_for_test(&self) -> Result<i8> {
+ let mut staging_dir_path = PathBuf::from(COMPOS_DATA_ROOT);
+ staging_dir_path.push("test-artifacts");
+ to_binder_result(create_dir(&staging_dir_path))?;
+
+ let compos = self
+ .instance_manager
+ .start_test_instance()
+ .context("Starting CompOS for odrefresh test")?;
+ self.do_odrefresh(compos, &staging_dir_path)
+ }
+
+ fn do_odrefresh(&self, compos: Arc<CompOsInstance>, staging_dir_path: &Path) -> Result<i8> {
+ let output_dir = open_dir_path(staging_dir_path)?;
+ let system_dir = open_dir_path(Path::new("/system"))?;
+
+ // Spawn a fd_server to serve the FDs.
+ let fd_server_config = FdServerConfig {
+ ro_dir_fds: vec![system_dir.as_raw_fd()],
+ rw_dir_fds: vec![output_dir.as_raw_fd()],
+ ..Default::default()
+ };
+ let fd_server_raii = fd_server_config.into_fd_server()?;
+
+ let zygote_arch = system_properties::read("ro.zygote")?;
+ let result = compos.get_service().odrefresh(
+ system_dir.as_raw_fd(),
+ output_dir.as_raw_fd(),
+ &zygote_arch,
+ );
+ drop(fd_server_raii);
+ Ok(result?.exitCode)
+ }
+}
+
+fn check_test_permissions() -> binder::Result<()> {
+ let calling_uid = ThreadState::get_calling_uid();
+ // This should only be called by system server, or root while testing
+ if calling_uid != AID_SYSTEM && calling_uid != AID_ROOT {
+ Err(Status::new_exception(ExceptionCode::SECURITY, None))
+ } else {
+ Ok(())
+ }
+}
+
+/// Returns an owned FD of the directory path. It currently returns a `File` as a FD owner, but
+/// it's better to use `std::os::unix::io::OwnedFd` once/if it becomes standard.
+fn open_dir_path(path: &Path) -> Result<File> {
+ OpenOptions::new()
+ .custom_flags(libc::O_PATH | libc::O_DIRECTORY)
+ // The custom flags above is not taken into consideration by the unix implementation of
+ // OpenOptions for flag validation. So even though the man page of open(2) says that
+ // most flags include access mode are ignored, we still need to set a "valid" mode to
+ // make the library happy. The value does not appear to matter elsewhere in the library.
+ .read(true)
+ .open(path)
+ .with_context(|| format!("Failed to open {} directory as path fd", path.display()))
}
diff --git a/compos/composd/src/util.rs b/compos/composd/src/util.rs
index 091fb15..54d7751 100644
--- a/compos/composd/src/util.rs
+++ b/compos/composd/src/util.rs
@@ -18,8 +18,9 @@
use anyhow::Result;
use binder_common::new_binder_service_specific_error;
use log::error;
+use std::fmt::Debug;
-pub fn to_binder_result<T>(result: Result<T>) -> BinderResult<T> {
+pub fn to_binder_result<T, E: Debug>(result: Result<T, E>) -> BinderResult<T> {
result.map_err(|e| {
let message = format!("{:?}", e);
error!("Returning binder error: {}", &message);
diff --git a/compos/composd_cmd/composd_cmd.rs b/compos/composd_cmd/composd_cmd.rs
index 0422b44..e591794 100644
--- a/compos/composd_cmd/composd_cmd.rs
+++ b/compos/composd_cmd/composd_cmd.rs
@@ -37,7 +37,7 @@
.index(1)
.takes_value(true)
.required(true)
- .possible_values(&["forced-compile-test"]),
+ .possible_values(&["forced-compile-test", "forced-odrefresh"]),
);
let args = app.get_matches();
let command = args.value_of("command").unwrap();
@@ -46,6 +46,7 @@
match command {
"forced-compile-test" => run_forced_compile_for_test()?,
+ "forced-odrefresh" => run_forced_odrefresh_for_test()?,
_ => panic!("Unexpected command {}", command),
}
@@ -135,3 +136,11 @@
}
}
}
+
+fn run_forced_odrefresh_for_test() -> Result<()> {
+ let service = wait_for_interface::<dyn IIsolatedCompilationService>("android.system.composd")
+ .context("Failed to connect to composd service")?;
+ let compilation_result = service.startTestOdrefresh().context("Compilation failed")?;
+ println!("odrefresh exit code: {:?}", compilation_result);
+ Ok(())
+}