Merge "Add ABL-compliant pvmfw pre-built"
diff --git a/authfs/fd_server/Android.bp b/authfs/fd_server/Android.bp
index 8ddbf69..327df0d 100644
--- a/authfs/fd_server/Android.bp
+++ b/authfs/fd_server/Android.bp
@@ -9,6 +9,7 @@
         "authfs_aidl_interface-rust",
         "libandroid_logger",
         "libanyhow",
+        "libbinder_common",
         "libbinder_rpc_unstable_bindgen",
         "libbinder_rs",
         "libclap",
diff --git a/authfs/fd_server/src/main.rs b/authfs/fd_server/src/main.rs
index 7e551a3..32ffc0a 100644
--- a/authfs/fd_server/src/main.rs
+++ b/authfs/fd_server/src/main.rs
@@ -30,9 +30,9 @@
 use std::cmp::min;
 use std::collections::BTreeMap;
 use std::convert::TryInto;
-use std::ffi::CString;
 use std::fs::File;
 use std::io;
+use std::os::raw;
 use std::os::unix::fs::FileExt;
 use std::os::unix::io::{AsRawFd, FromRawFd};
 
@@ -43,13 +43,10 @@
 use authfs_aidl_interface::binder::{
     BinderFeatures, ExceptionCode, Interface, Result as BinderResult, Status, StatusCode, Strong,
 };
+use binder_common::new_binder_exception;
 
 const RPC_SERVICE_PORT: u32 = 3264; // TODO: support dynamic port for multiple fd_server instances
 
-fn new_binder_exception<T: AsRef<str>>(exception: ExceptionCode, message: T) -> Status {
-    Status::new_exception(exception, CString::new(message.as_ref()).as_deref().ok())
-}
-
 fn validate_and_cast_offset(offset: i64) -> Result<u64, Status> {
     offset.try_into().map_err(|_| {
         new_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT, format!("Invalid offset: {}", offset))
@@ -296,7 +293,12 @@
     Ok((fd, FdConfig::ReadWrite(file)))
 }
 
-fn parse_args() -> Result<BTreeMap<i32, FdConfig>> {
+struct Args {
+    fd_pool: BTreeMap<i32, FdConfig>,
+    ready_fd: Option<File>,
+}
+
+fn parse_args() -> Result<Args> {
     #[rustfmt::skip]
     let matches = clap::App::new("fd_server")
         .arg(clap::Arg::with_name("ro-fds")
@@ -307,6 +309,9 @@
              .long("rw-fds")
              .multiple(true)
              .number_of_values(1))
+        .arg(clap::Arg::with_name("ready-fd")
+            .long("ready-fd")
+            .takes_value(true))
         .get_matches();
 
     let mut fd_pool = BTreeMap::new();
@@ -322,8 +327,13 @@
             fd_pool.insert(fd, config);
         }
     }
-
-    Ok(fd_pool)
+    let ready_fd = if let Some(arg) = matches.value_of("ready-fd") {
+        let fd = arg.parse::<i32>()?;
+        Some(fd_to_file(fd)?)
+    } else {
+        None
+    };
+    Ok(Args { fd_pool, ready_fd })
 }
 
 fn main() -> Result<()> {
@@ -331,16 +341,22 @@
         android_logger::Config::default().with_tag("fd_server").with_min_level(log::Level::Debug),
     );
 
-    let fd_pool = parse_args()?;
+    let args = parse_args()?;
 
-    let mut service = FdService::new_binder(fd_pool).as_binder();
+    let mut service = FdService::new_binder(args.fd_pool).as_binder();
+    let mut ready_notifier = ReadyNotifier(args.ready_fd);
+
     debug!("fd_server is starting as a rpc service.");
     // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
     // Plus the binder objects are threadsafe.
+    // RunRpcServerCallback does not retain a reference to ready_callback, and only ever
+    // calls it with the param we provide during the lifetime of ready_notifier.
     let retval = unsafe {
-        binder_rpc_unstable_bindgen::RunRpcServer(
+        binder_rpc_unstable_bindgen::RunRpcServerCallback(
             service.as_native_mut() as *mut binder_rpc_unstable_bindgen::AIBinder,
             RPC_SERVICE_PORT,
+            Some(ReadyNotifier::ready_callback),
+            ready_notifier.as_void_ptr(),
         )
     };
     if retval {
@@ -350,3 +366,25 @@
         bail!("Premature termination of RPC server");
     }
 }
+
+struct ReadyNotifier(Option<File>);
+
+impl ReadyNotifier {
+    fn notify(&mut self) {
+        debug!("fd_server is ready");
+        // Close the ready-fd if we were given one to signal our readiness.
+        drop(self.0.take());
+    }
+
+    fn as_void_ptr(&mut self) -> *mut raw::c_void {
+        self as *mut _ as *mut raw::c_void
+    }
+
+    unsafe extern "C" fn ready_callback(param: *mut raw::c_void) {
+        // SAFETY: This is only ever called by RunRpcServerCallback, within the lifetime of the
+        // ReadyNotifier, with param taking the value returned by as_void_ptr (so a properly aligned
+        // non-null pointer to an initialized instance).
+        let ready_notifier = param as *mut Self;
+        ready_notifier.as_mut().unwrap().notify()
+    }
+}
diff --git a/authfs/service/Android.bp b/authfs/service/Android.bp
index 943db35..6c32c67 100644
--- a/authfs/service/Android.bp
+++ b/authfs/service/Android.bp
@@ -12,6 +12,7 @@
         "authfs_aidl_interface-rust",
         "libandroid_logger",
         "libanyhow",
+        "libbinder_common",
         "libbinder_rs",
         "liblibc",
         "liblog_rust",
diff --git a/authfs/service/src/authfs.rs b/authfs/service/src/authfs.rs
index f41a3a6..6d87243 100644
--- a/authfs/service/src/authfs.rs
+++ b/authfs/service/src/authfs.rs
@@ -26,7 +26,6 @@
 use std::thread::sleep;
 use std::time::{Duration, Instant};
 
-use crate::common::new_binder_exception;
 use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFs::{BnAuthFs, IAuthFs};
 use authfs_aidl_interface::aidl::com::android::virt::fs::{
     AuthFsConfig::AuthFsConfig, InputFdAnnotation::InputFdAnnotation,
@@ -35,6 +34,7 @@
 use authfs_aidl_interface::binder::{
     self, BinderFeatures, ExceptionCode, Interface, ParcelFileDescriptor, Strong,
 };
+use binder_common::new_binder_exception;
 
 const AUTHFS_BIN: &str = "/system/bin/authfs";
 const AUTHFS_SETUP_POLL_INTERVAL_MS: Duration = Duration::from_millis(50);
@@ -82,8 +82,11 @@
             &config.outputFdAnnotations,
             debuggable,
         )?;
-        wait_until_authfs_ready(&mountpoint).map_err(|e| {
-            debug!("Wait for authfs: {:?}", child.wait());
+        wait_until_authfs_ready(&child, &mountpoint).map_err(|e| {
+            match child.wait() {
+                Ok(status) => debug!("Wait for authfs: {}", status),
+                Err(e) => warn!("Failed to wait for child: {}", e),
+            }
             e
         })?;
 
@@ -144,13 +147,18 @@
     SharedChild::spawn(&mut command).context("Spawn authfs")
 }
 
-fn wait_until_authfs_ready(mountpoint: &OsStr) -> Result<()> {
+fn wait_until_authfs_ready(child: &SharedChild, mountpoint: &OsStr) -> Result<()> {
     let start_time = Instant::now();
     loop {
         if is_fuse(mountpoint)? {
             break;
         }
+        if let Some(exit_status) = child.try_wait()? {
+            // If the child has exited, we will never become ready.
+            bail!("Child has exited: {}", exit_status);
+        }
         if start_time.elapsed() > AUTHFS_SETUP_TIMEOUT_SEC {
+            let _ = child.kill();
             bail!("Time out mounting authfs");
         }
         sleep(AUTHFS_SETUP_POLL_INTERVAL_MS);
diff --git a/authfs/service/src/main.rs b/authfs/service/src/main.rs
index e426734..890e108 100644
--- a/authfs/service/src/main.rs
+++ b/authfs/service/src/main.rs
@@ -21,7 +21,6 @@
 //! is able to retrieve "remote file descriptors".
 
 mod authfs;
-mod common;
 
 use anyhow::{bail, Context, Result};
 use log::*;
@@ -29,7 +28,6 @@
 use std::fs::{create_dir, read_dir, remove_dir_all, remove_file};
 use std::sync::atomic::{AtomicUsize, Ordering};
 
-use crate::common::new_binder_exception;
 use authfs_aidl_interface::aidl::com::android::virt::fs::AuthFsConfig::AuthFsConfig;
 use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFs::IAuthFs;
 use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFsService::{
@@ -38,6 +36,7 @@
 use authfs_aidl_interface::binder::{
     self, add_service, BinderFeatures, ExceptionCode, Interface, ProcessState, Strong,
 };
+use binder_common::new_binder_exception;
 
 const SERVICE_NAME: &str = "authfs_service";
 const SERVICE_ROOT: &str = "/data/misc/authfs";
@@ -60,7 +59,7 @@
         create_dir(&mountpoint).map_err(|e| {
             new_binder_exception(
                 ExceptionCode::SERVICE_SPECIFIC,
-                format!("Cannot create mount directory {:?}: {}", &mountpoint, e),
+                format!("Cannot create mount directory {:?}: {:?}", &mountpoint, e),
             )
         })?;
 
@@ -110,7 +109,7 @@
     Ok(())
 }
 
-fn main() -> Result<()> {
+fn try_main() -> Result<()> {
     let debuggable = env!("TARGET_BUILD_VARIANT") != "user";
     let log_level = if debuggable { log::Level::Trace } else { log::Level::Info };
     android_logger::init_once(
@@ -129,3 +128,10 @@
     ProcessState::join_thread_pool();
     bail!("Unexpected exit after join_thread_pool")
 }
+
+fn main() {
+    if let Err(e) = try_main() {
+        error!("failed with {:?}", e);
+        std::process::exit(1);
+    }
+}
diff --git a/authfs/src/main.rs b/authfs/src/main.rs
index c85d801..ecb0e68 100644
--- a/authfs/src/main.rs
+++ b/authfs/src/main.rs
@@ -28,6 +28,7 @@
 //! e.g. /mountpoint/42.
 
 use anyhow::{bail, Context, Result};
+use log::error;
 use std::collections::BTreeMap;
 use std::convert::TryInto;
 use std::fs::File;
@@ -325,7 +326,7 @@
     Ok(file_pool)
 }
 
-fn main() -> Result<()> {
+fn try_main() -> Result<()> {
     let args = Args::from_args();
 
     let log_level = if args.debug { log::Level::Debug } else { log::Level::Info };
@@ -337,3 +338,10 @@
     fusefs::loop_forever(file_pool, &args.mount_point, &args.extra_options)?;
     bail!("Unexpected exit after the handler loop")
 }
+
+fn main() {
+    if let Err(e) = try_main() {
+        error!("failed with {:?}", e);
+        std::process::exit(1);
+    }
+}
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
index 1b4fa4a..3d97ee7 100644
--- a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
+++ b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
@@ -44,6 +44,7 @@
 
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 @RootPermissionTest
 @RunWith(DeviceJUnit4ClassRunner.class)
@@ -67,6 +68,8 @@
     /** FUSE's magic from statfs(2) */
     private static final String FUSE_SUPER_MAGIC_HEX = "65735546";
 
+    private static final int VMADDR_CID_HOST = 2;
+
     private static CommandRunner sAndroid;
     private static String sCid;
     private static boolean sAssumptionFailed;
@@ -108,7 +111,8 @@
                         apkName,
                         packageName,
                         configPath,
-                        /* debug */ false);
+                        /* debug */ false,
+                        /* use default memoryMib */ 0);
         adbConnectToMicrodroid(androidDevice, sCid);
 
         // Root because authfs (started from shell in this test) currently require root to open
@@ -154,7 +158,8 @@
                 "--ro-fds 3:4:5 --ro-fds 6");
 
         runAuthFsOnMicrodroid(
-                "--remote-ro-file-unverified 10:6 --remote-ro-file 11:3:cert.der --cid 2");
+                "--remote-ro-file-unverified 10:6 --remote-ro-file 11:3:cert.der --cid "
+                        + VMADDR_CID_HOST);
 
         // Action
         String actualHashUnverified4m = computeFileHashOnMicrodroid(MOUNT_DIR + "/10");
@@ -178,7 +183,8 @@
                         + " 6<input.4k1 7<input.4k1.merkle_dump 8<input.4k1.fsv_sig",
                 "--ro-fds 3:4:5 --ro-fds 6:7:8");
         runAuthFsOnMicrodroid(
-                "--remote-ro-file 10:3:cert.der --remote-ro-file 11:6:cert.der --cid 2");
+                "--remote-ro-file 10:3:cert.der --remote-ro-file 11:6:cert.der --cid "
+                        + VMADDR_CID_HOST);
 
         // Action
         String actualHash4k = computeFileHashOnMicrodroid(MOUNT_DIR + "/10");
@@ -198,7 +204,7 @@
         // Setup
         runFdServerOnAndroid(
                 "3<input.4m 4<input.4m.merkle_dump.bad 5<input.4m.fsv_sig", "--ro-fds 3:4:5");
-        runAuthFsOnMicrodroid("--remote-ro-file 10:3:cert.der --cid 2");
+        runAuthFsOnMicrodroid("--remote-ro-file 10:3:cert.der --cid " + VMADDR_CID_HOST);
 
         // Verify
         assertFalse(copyFileOnMicrodroid(MOUNT_DIR + "/10", "/dev/null"));
@@ -209,7 +215,7 @@
             throws DeviceNotAvailableException, InterruptedException {
         // Setup
         runFdServerOnAndroid("3<>output", "--rw-fds 3");
-        runAuthFsOnMicrodroid("--remote-new-rw-file 20:3 --cid 2");
+        runAuthFsOnMicrodroid("--remote-new-rw-file 20:3 --cid " + VMADDR_CID_HOST);
 
         // Action
         String srcPath = "/system/bin/linker64";
@@ -227,7 +233,7 @@
             throws DeviceNotAvailableException, InterruptedException {
         // Setup
         runFdServerOnAndroid("3<>output", "--rw-fds 3");
-        runAuthFsOnMicrodroid("--remote-new-rw-file 20:3 --cid 2");
+        runAuthFsOnMicrodroid("--remote-new-rw-file 20:3 --cid " + VMADDR_CID_HOST);
 
         String srcPath = "/system/bin/linker64";
         String destPath = MOUNT_DIR + "/20";
@@ -258,7 +264,7 @@
     public void testFileResize() throws DeviceNotAvailableException, InterruptedException {
         // Setup
         runFdServerOnAndroid("3<>output", "--rw-fds 3");
-        runAuthFsOnMicrodroid("--remote-new-rw-file 20:3 --cid 2");
+        runAuthFsOnMicrodroid("--remote-new-rw-file 20:3 --cid " + VMADDR_CID_HOST);
         String outputPath = MOUNT_DIR + "/20";
         String backendPath = TEST_DIR + "/output";
 
@@ -337,11 +343,16 @@
     private void runAuthFsOnMicrodroid(String flags) {
         String cmd = AUTHFS_BIN + " " + MOUNT_DIR + " " + flags;
 
+        AtomicBoolean starting = new AtomicBoolean(true);
         mThreadPool.submit(
                 () -> {
-                    CLog.i("Starting authfs");
-                    CommandResult result = runOnMicrodroidForResult(cmd);
-                    CLog.w("authfs has stopped: " + result);
+                    // authfs may fail to start if fd_server is not yet listening on the vsock
+                    // ("Error: Invalid raw AIBinder"). Just restart if that happens.
+                    while (starting.get()) {
+                        CLog.i("Starting authfs");
+                        CommandResult result = runOnMicrodroidForResult(cmd);
+                        CLog.w("authfs has stopped: " + result);
+                    }
                 });
         try {
             PollingCheck.waitFor(
@@ -351,6 +362,8 @@
             // methods. waitFor throws Exception because the callback, Callable#call(), has a
             // signature to throw an Exception.
             throw new RuntimeException(e);
+        } finally {
+            starting.set(false);
         }
     }
 
diff --git a/binder_common/Android.bp b/binder_common/Android.bp
new file mode 100644
index 0000000..9e6d590
--- /dev/null
+++ b/binder_common/Android.bp
@@ -0,0 +1,18 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_library {
+    name: "libbinder_common",
+    crate_name: "binder_common",
+    srcs: ["lib.rs"],
+    edition: "2018",
+    rustlibs: [
+        "libbinder_rs",
+        "liblazy_static",
+    ],
+    apex_available: [
+        "com.android.compos",
+        "com.android.virt",
+    ],
+}
diff --git a/binder_common/lazy_service.rs b/binder_common/lazy_service.rs
new file mode 100644
index 0000000..a2b85db
--- /dev/null
+++ b/binder_common/lazy_service.rs
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+
+//! Rust API for lazy (aka dynamic) AIDL services.
+//! See https://source.android.com/devices/architecture/aidl/dynamic-aidl.
+
+use binder::public_api::force_lazy_services_persist;
+use lazy_static::lazy_static;
+use std::sync::Mutex;
+
+// TODO(b/200924402): Move this class to libbinder_rs once the infrastructure needed exists.
+
+/// An RAII object to ensure a server of lazy services is not killed. During the lifetime of any of
+/// these objects the service manager will not not kill the current process even if none of its
+/// lazy services are in use.
+#[must_use]
+#[derive(Debug)]
+pub struct LazyServiceGuard {
+    // Prevent construction outside this module.
+    _private: (),
+}
+
+lazy_static! {
+    // Count of how many LazyServiceGuard objects are in existence.
+    static ref GUARD_COUNT: Mutex<u64> = Mutex::new(0);
+}
+
+impl LazyServiceGuard {
+    /// Create a new LazyServiceGuard to prevent the service manager prematurely killing this
+    /// process.
+    pub fn new() -> Self {
+        let mut count = GUARD_COUNT.lock().unwrap();
+        *count += 1;
+        if *count == 1 {
+            // It's important that we make this call with the mutex held, to make sure
+            // that multiple calls (e.g. if the count goes 1 -> 0 -> 1) are correctly
+            // sequenced. (That also means we can't just use an AtomicU64.)
+            force_lazy_services_persist(true);
+        }
+        Self { _private: () }
+    }
+}
+
+impl Drop for LazyServiceGuard {
+    fn drop(&mut self) {
+        let mut count = GUARD_COUNT.lock().unwrap();
+        *count -= 1;
+        if *count == 0 {
+            force_lazy_services_persist(false);
+        }
+    }
+}
+
+impl Clone for LazyServiceGuard {
+    fn clone(&self) -> Self {
+        Self::new()
+    }
+}
+
+impl Default for LazyServiceGuard {
+    fn default() -> Self {
+        Self::new()
+    }
+}
diff --git a/binder_common/lib.rs b/binder_common/lib.rs
new file mode 100644
index 0000000..055688a
--- /dev/null
+++ b/binder_common/lib.rs
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+//! Common items useful for binder clients and/or servers.
+
+pub mod lazy_service;
+
+use binder::public_api::{ExceptionCode, Status};
+use std::ffi::CString;
+
+/// Constructs a new Binder error `Status` with the given `ExceptionCode` and message.
+pub fn new_binder_exception<T: AsRef<str>>(exception: ExceptionCode, message: T) -> Status {
+    match exception {
+        ExceptionCode::SERVICE_SPECIFIC => new_binder_service_specific_error(-1, message),
+        _ => Status::new_exception(exception, to_cstring(message).as_deref()),
+    }
+}
+
+/// Constructs a Binder `Status` representing a service-specific exception with the given code and
+/// message.
+pub fn new_binder_service_specific_error<T: AsRef<str>>(code: i32, message: T) -> Status {
+    Status::new_service_specific_error(code, to_cstring(message).as_deref())
+}
+
+fn to_cstring<T: AsRef<str>>(message: T) -> Option<CString> {
+    CString::new(message.as_ref()).ok()
+}
diff --git a/compos/Android.bp b/compos/Android.bp
index 2af19c8..b1e5f89 100644
--- a/compos/Android.bp
+++ b/compos/Android.bp
@@ -6,6 +6,7 @@
     name: "pvm_exec",
     srcs: ["src/pvm_exec.rs"],
     rustlibs: [
+        "android.system.composd-rust",
         "compos_aidl_interface-rust",
         "libandroid_logger",
         "libanyhow",
@@ -34,17 +35,21 @@
     rustlibs: [
         "android.hardware.security.keymint-V1-rust",
         "android.system.keystore2-V1-rust",
+        "android.system.virtualmachineservice-rust",
         "authfs_aidl_interface-rust",
         "compos_aidl_interface-rust",
         "libandroid_logger",
         "libanyhow",
+        "libbinder_common",
         "libbinder_rpc_unstable_bindgen",
         "libbinder_rs",
         "libclap",
         "libcompos_common",
+        "libenv_logger",
         "liblibc",
         "liblog_rust",
         "libminijail_rust",
+        "libnix",
         "libring",
         "libscopeguard",
     ],
diff --git a/compos/aidl/com/android/compos/ICompOsService.aidl b/compos/aidl/com/android/compos/ICompOsService.aidl
index 7904130..29c453b 100644
--- a/compos/aidl/com/android/compos/ICompOsService.aidl
+++ b/compos/aidl/com/android/compos/ICompOsService.aidl
@@ -43,7 +43,22 @@
      * @param fd_annotation Additional file descriptor information of the execution
      * @return a CompilationResult
      */
-    CompilationResult compile(in String[] args, in FdAnnotation fd_annotation);
+    CompilationResult compile_cmd(in String[] args, in FdAnnotation fd_annotation);
+
+    /**
+     * Runs dexopt compilation encoded in the marshaled dexopt arguments.
+     *
+     * To keep ART indepdendantly updatable, the compilation arguments are not stabilized. As a
+     * result, the arguments are marshaled into byte array.  Upon received, the service asks ART to
+     * return relevant information (since ART is able to unmarshal its own encoding), in order to
+     * set up the execution context (mainly file descriptors for compiler input and output) then
+     * invokes the compiler.
+     *
+     * @param marshaledArguments The marshaled dexopt arguments.
+     * @param fd_annotation Additional file descriptor information of the execution.
+     * @return exit code
+     */
+    byte compile(in byte[] marshaledArguments, in FdAnnotation fd_annotation);
 
     /**
      * Generate a new public/private key pair suitable for signing CompOs output files.
diff --git a/compos/apk/assets/vm_config.json b/compos/apk/assets/vm_config.json
index 9be60d0..9c71942 100644
--- a/compos/apk/assets/vm_config.json
+++ b/compos/apk/assets/vm_config.json
@@ -5,7 +5,10 @@
   },
   "task": {
     "type": "executable",
-    "command": "/apex/com.android.compos/bin/compsvc"
+    "command": "/apex/com.android.compos/bin/compsvc",
+    "args": [
+      "--log_to_stderr"
+    ]
   },
   "apexes": [
     {
diff --git a/compos/apk/assets/vm_test_config.json b/compos/apk/assets/vm_test_config.json
new file mode 100644
index 0000000..54a0aac
--- /dev/null
+++ b/compos/apk/assets/vm_test_config.json
@@ -0,0 +1,18 @@
+{
+    "version": 1,
+    "os": {
+        "name": "microdroid"
+    },
+    "task": {
+        "type": "executable",
+        "command": "/apex/com.android.compos/bin/compsvc"
+    },
+    "apexes": [
+        {
+            "name": "com.android.art"
+        },
+        {
+            "name": "com.android.compos"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 03cc331..5f14005 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -34,8 +34,9 @@
     FromIBinder,
 };
 use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
-use log::warn;
+use log::{info, warn};
 use std::fs::File;
+use std::io::{BufRead, BufReader};
 use std::os::raw;
 use std::os::unix::io::IntoRawFd;
 use std::path::Path;
@@ -45,16 +46,21 @@
 
 /// This owns an instance of the CompOS VM.
 pub struct VmInstance {
-    #[allow(dead_code)] // Keeps the vm alive even if we don`t touch it
+    #[allow(dead_code)] // Keeps the VM alive even if we don`t touch it
     vm: Strong<dyn IVirtualMachine>,
     cid: i32,
 }
 
 impl VmInstance {
+    /// Return a new connection to the Virtualization Service binder interface. This will start the
+    /// service if necessary.
+    pub fn connect_to_virtualization_service() -> Result<Strong<dyn IVirtualizationService>> {
+        wait_for_interface::<dyn IVirtualizationService>("android.system.virtualizationservice")
+            .context("Failed to find VirtualizationService")
+    }
+
     /// Start a new CompOS VM instance using the specified instance image file.
-    pub fn start(instance_image: &Path) -> Result<VmInstance> {
-        let instance_image =
-            File::open(instance_image).context("Failed to open instance image file")?;
+    pub fn start(instance_image: File) -> Result<VmInstance> {
         let instance_fd = ParcelFileDescriptor::new(instance_image);
 
         let apex_dir = Path::new(COMPOS_APEX_ROOT);
@@ -80,10 +86,7 @@
             ..Default::default()
         });
 
-        let service = wait_for_interface::<dyn IVirtualizationService>(
-            "android.system.virtualizationservice",
-        )
-        .context("Failed to find VirtualizationService")?;
+        let service = Self::connect_to_virtualization_service()?;
 
         let vm = service.createVm(&config, Some(&log_fd)).context("Failed to create VM")?;
         let vm_state = Arc::new(VmStateMonitor::default());
@@ -103,10 +106,7 @@
 
         vm.start()?;
 
-        let cid = vm_state.wait_for_start()?;
-
-        // TODO: Use onPayloadReady to avoid this
-        thread::sleep(Duration::from_secs(3));
+        let cid = vm_state.wait_until_ready()?;
 
         Ok(VmInstance { vm, cid })
     }
@@ -114,26 +114,17 @@
     /// Create and return an RPC Binder connection to the Comp OS service in the VM.
     pub fn get_service(&self) -> Result<Strong<dyn ICompOsService>> {
         let mut vsock_factory = VsockFactory::new(&*self.vm);
-        let param = vsock_factory.as_void_ptr();
 
-        let ibinder = unsafe {
-            // SAFETY: AIBinder returned by RpcPreconnectedClient has correct reference count, and
-            // the ownership can be safely taken by new_spibinder.
-            // RpcPreconnectedClient does not take ownership of param, only passing it to
-            // request_fd.
-            let binder = binder_rpc_unstable_bindgen::RpcPreconnectedClient(
-                Some(VsockFactory::request_fd),
-                param,
-            ) as *mut AIBinder;
-            new_spibinder(binder)
-        }
-        .ok_or_else(|| anyhow!("Failed to connect to CompOS service"))?;
+        let ibinder = vsock_factory
+            .connect_rpc_client()
+            .ok_or_else(|| anyhow!("Failed to connect to CompOS service"))?;
 
         FromIBinder::try_from(ibinder).context("Connecting to CompOS service")
     }
 
     /// Return the CID of the VM.
     pub fn cid(&self) -> i32 {
+        // TODO: Do we actually need/use this?
         self.cid
     }
 }
@@ -147,6 +138,21 @@
         Self { vm }
     }
 
+    fn connect_rpc_client(&mut self) -> Option<binder::SpIBinder> {
+        let param = self.as_void_ptr();
+
+        unsafe {
+            // SAFETY: AIBinder returned by RpcPreconnectedClient has correct reference count, and
+            // the ownership can be safely taken by new_spibinder.
+            // RpcPreconnectedClient does not take ownership of param, only passing it to
+            // request_fd.
+            let binder =
+                binder_rpc_unstable_bindgen::RpcPreconnectedClient(Some(Self::request_fd), param)
+                    as *mut AIBinder;
+            new_spibinder(binder)
+        }
+    }
+
     fn as_void_ptr(&mut self) -> *mut raw::c_void {
         self as *mut _ as *mut raw::c_void
     }
@@ -168,8 +174,8 @@
         // SAFETY: This is only ever called by RpcPreconnectedClient, within the lifetime of the
         // VsockFactory, with param taking the value returned by as_void_ptr (so a properly aligned
         // non-null pointer to an initialized instance).
-        let holder = param as *mut Self;
-        holder.as_ref().unwrap().new_vsock_fd()
+        let vsock_factory = param as *mut Self;
+        vsock_factory.as_ref().unwrap().new_vsock_fd()
     }
 }
 
@@ -206,7 +212,7 @@
         self.state_ready.notify_all();
     }
 
-    fn set_started(&self, cid: i32) {
+    fn set_ready(&self, cid: i32) {
         let mut state = self.mutex.lock().unwrap();
         if state.has_died {
             return;
@@ -216,10 +222,10 @@
         self.state_ready.notify_all();
     }
 
-    fn wait_for_start(&self) -> Result<i32> {
+    fn wait_until_ready(&self) -> Result<i32> {
         let (state, result) = self
             .state_ready
-            .wait_timeout_while(self.mutex.lock().unwrap(), Duration::from_secs(10), |state| {
+            .wait_timeout_while(self.mutex.lock().unwrap(), Duration::from_secs(20), |state| {
                 state.cid.is_none() && !state.has_died
             })
             .unwrap();
@@ -245,16 +251,19 @@
     fn onPayloadStarted(
         &self,
         cid: i32,
-        _stream: Option<&binder::parcel::ParcelFileDescriptor>,
+        stream: Option<&ParcelFileDescriptor>,
     ) -> BinderResult<()> {
-        self.0.set_started(cid);
-        // TODO: Use the stream?
+        if let Some(pfd) = stream {
+            if let Err(e) = start_logging(pfd) {
+                warn!("Can't log vm output: {}", e);
+            };
+        }
         log::info!("VM payload started, cid = {}", cid);
         Ok(())
     }
 
     fn onPayloadReady(&self, cid: i32) -> BinderResult<()> {
-        // TODO: Use this to trigger vsock connection
+        self.0.set_ready(cid);
         log::info!("VM payload ready, cid = {}", cid);
         Ok(())
     }
@@ -267,3 +276,19 @@
         Ok(())
     }
 }
+
+fn start_logging(pfd: &ParcelFileDescriptor) -> Result<()> {
+    let reader = BufReader::new(pfd.as_ref().try_clone().context("Cloning fd failed")?);
+    thread::spawn(move || {
+        for line in reader.lines() {
+            match line {
+                Ok(line) => info!("VM: {}", line),
+                Err(e) => {
+                    warn!("Reading VM output failed: {}", e);
+                    break;
+                }
+            }
+        }
+    });
+    Ok(())
+}
diff --git a/compos/common/lib.rs b/compos/common/lib.rs
index 6bea62c..104b8e5 100644
--- a/compos/common/lib.rs
+++ b/compos/common/lib.rs
@@ -18,6 +18,9 @@
 
 pub mod compos_client;
 
+/// Special CID indicating "any".
+pub const VMADDR_CID_ANY: u32 = -1i32 as u32;
+
 /// VSock port that the CompOS server listens on for RPC binder connections. This should be out of
 /// future port range (if happens) that microdroid may reserve for system components.
 pub const COMPOS_VSOCK_PORT: u32 = 6432;
diff --git a/compos/compos_key_cmd/compos_key_cmd.cpp b/compos/compos_key_cmd/compos_key_cmd.cpp
index ff53548..e168648 100644
--- a/compos/compos_key_cmd/compos_key_cmd.cpp
+++ b/compos/compos_key_cmd/compos_key_cmd.cpp
@@ -31,6 +31,7 @@
 #include <openssl/mem.h>
 #include <openssl/sha.h>
 #include <openssl/x509.h>
+#include <stdio.h>
 #include <unistd.h>
 
 #include <binder_rpc_unstable.hpp>
@@ -41,6 +42,7 @@
 #include <mutex>
 #include <string>
 #include <string_view>
+#include <thread>
 
 #include "compos_signature.pb.h"
 
@@ -56,6 +58,7 @@
 using aidl::com::android::compos::ICompOsService;
 using android::base::ErrnoError;
 using android::base::Error;
+using android::base::Fdopen;
 using android::base::Result;
 using android::base::unique_fd;
 using compos::proto::Signature;
@@ -94,22 +97,45 @@
 }
 
 namespace {
+
+void copyToLog(unique_fd&& fd) {
+    FILE* source = Fdopen(std::move(fd), "r");
+    size_t size = 0;
+    char* line = nullptr;
+
+    LOG(INFO) << "Started logging VM output";
+
+    for (;;) {
+        ssize_t len = getline(&line, &size, source);
+        if (len < 0) {
+            LOG(INFO) << "VM logging ended: " << ErrnoError().str();
+            break;
+        }
+        LOG(DEBUG) << "VM: " << std::string_view(line, len);
+    }
+    free(line);
+}
+
 class Callback : public BnVirtualMachineCallback {
 public:
-    ::ndk::ScopedAStatus onPayloadStarted(
-            int32_t in_cid, const ::ndk::ScopedFileDescriptor& /*in_stream*/) override {
-        // TODO: Consider copying stdout somewhere useful?
+    ::ndk::ScopedAStatus onPayloadStarted(int32_t in_cid,
+                                          const ::ndk::ScopedFileDescriptor& stream) override {
         LOG(INFO) << "Payload started! cid = " << in_cid;
-        {
-            std::unique_lock lock(mMutex);
-            mStarted = true;
-        }
-        mCv.notify_all();
+
+        unique_fd stream_fd(dup(stream.get()));
+        std::thread logger([fd = std::move(stream_fd)]() mutable { copyToLog(std::move(fd)); });
+        logger.detach();
+
         return ScopedAStatus::ok();
     }
 
     ::ndk::ScopedAStatus onPayloadReady(int32_t in_cid) override {
         LOG(INFO) << "Payload is ready! cid = " << in_cid;
+        {
+            std::unique_lock lock(mMutex);
+            mReady = true;
+        }
+        mCv.notify_all();
         return ScopedAStatus::ok();
     }
 
@@ -128,16 +154,16 @@
         return ScopedAStatus::ok();
     }
 
-    bool waitForStarted() {
+    bool waitUntilReady() {
         std::unique_lock lock(mMutex);
-        return mCv.wait_for(lock, std::chrono::seconds(10), [this] { return mStarted || mDied; }) &&
+        return mCv.wait_for(lock, std::chrono::seconds(20), [this] { return mReady || mDied; }) &&
                 !mDied;
     }
 
 private:
     std::mutex mMutex;
     std::condition_variable mCv;
-    bool mStarted;
+    bool mReady;
     bool mDied;
 };
 
@@ -237,14 +263,10 @@
         }
         LOG(INFO) << "Started VM";
 
-        if (!mCallback->waitForStarted()) {
+        if (!mCallback->waitUntilReady()) {
             return Error() << "VM Payload failed to start";
         }
 
-        // TODO(b/194677789): Implement a polling loop or find a more reliable
-        // way to detect when the service is listening.
-        sleep(3);
-
         return cid;
     }
 
@@ -255,6 +277,7 @@
     std::shared_ptr<Callback> mCallback;
     std::shared_ptr<IVirtualMachine> mVm;
 };
+
 } // namespace
 
 static Result<std::vector<uint8_t>> extractRsaPublicKey(
diff --git a/compos/composd/Android.bp b/compos/composd/Android.bp
index 5c968b8..9887483 100644
--- a/compos/composd/Android.bp
+++ b/compos/composd/Android.bp
@@ -9,11 +9,14 @@
     prefer_rlib: true,
     rustlibs: [
         "android.system.composd-rust",
+        "android.system.virtualizationservice-rust",
         "compos_aidl_interface-rust",
         "libandroid_logger",
         "libanyhow",
+        "libbinder_common",
         "libbinder_rs",
         "libcompos_common",
+        "libcomposd_native_rust",
         "libnum_traits",
         "liblog_rust",
     ],
diff --git a/compos/composd/aidl/Android.bp b/compos/composd/aidl/Android.bp
index 90c0de0..0352001 100644
--- a/compos/composd/aidl/Android.bp
+++ b/compos/composd/aidl/Android.bp
@@ -5,6 +5,7 @@
 aidl_interface {
     name: "android.system.composd",
     srcs: ["android/system/composd/*.aidl"],
+    imports: ["compos_aidl_interface"],
     // TODO: Make this stable when the APEX becomes updatable.
     unstable: true,
     backend: {
diff --git a/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl b/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
index 9240bc6..a1bb92c 100644
--- a/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
+++ b/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
@@ -15,7 +15,28 @@
  */
 package android.system.composd;
 
+import com.android.compos.CompilationResult;
+import com.android.compos.FdAnnotation;
+
 interface IIsolatedCompilationService {
-    /// Run "odrefresh --force-compile" in CompOS
+    /** Run "odrefresh --force-compile" in CompOS. */
     void runForcedCompile();
+
+    /**
+     * Run dex2oat in the currently running instance of the CompOS VM. This is a simple proxy
+     * to ICompOsService#compile_cmd.
+     *
+     * This method can only be called from odrefresh. If there is no currently running instance
+     * an error is returned.
+     */
+    CompilationResult compile_cmd(in String[] args, in FdAnnotation fd_annotation);
+
+    /**
+     * Run dex2oat in the currently running instance of the CompOS VM. This is a simple proxy
+     * to ICompOsService#compile.
+     *
+     * This method can only be called from libcompos_client. If there is no currently running
+     * instance an error is returned.
+     */
+    byte compile(in byte[] marshaledArguments, in FdAnnotation fd_annotation);
 }
diff --git a/compos/composd/native/Android.bp b/compos/composd/native/Android.bp
new file mode 100644
index 0000000..ad0afd9
--- /dev/null
+++ b/compos/composd/native/Android.bp
@@ -0,0 +1,42 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_library {
+    name: "libcomposd_native_rust",
+    crate_name: "composd_native",
+    srcs: ["lib.rs"],
+    rustlibs: [
+        "libcxx",
+    ],
+    static_libs: [
+        "libcomposd_native_cpp",
+    ],
+    shared_libs: ["libcrypto"],
+    apex_available: ["com.android.compos"],
+}
+
+cc_library_static {
+    name: "libcomposd_native_cpp",
+    srcs: ["composd_native.cpp"],
+    shared_libs: ["libcrypto"],
+    generated_headers: ["composd_native_header"],
+    generated_sources: ["composd_native_code"],
+    apex_available: ["com.android.compos"],
+}
+
+genrule {
+    name: "composd_native_code",
+    tools: ["cxxbridge"],
+    cmd: "$(location cxxbridge) $(in) >> $(out)",
+    srcs: ["lib.rs"],
+    out: ["composd_native_cxx_generated.cc"],
+}
+
+genrule {
+    name: "composd_native_header",
+    tools: ["cxxbridge"],
+    cmd: "$(location cxxbridge) $(in) --header >> $(out)",
+    srcs: ["lib.rs"],
+    out: ["lib.rs.h"],
+}
diff --git a/compos/composd/native/composd_native.cpp b/compos/composd/native/composd_native.cpp
new file mode 100644
index 0000000..ebed816
--- /dev/null
+++ b/compos/composd/native/composd_native.cpp
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+#include "composd_native.h"
+
+#include <openssl/evp.h>
+#include <openssl/mem.h>
+#include <openssl/sha.h>
+#include <openssl/x509.h>
+
+#include <algorithm>
+#include <iterator>
+
+using rust::Slice;
+using rust::String;
+
+namespace {
+KeyResult make_error(const char* message) {
+    return KeyResult{{}, message};
+}
+} // namespace
+
+KeyResult extract_rsa_public_key(rust::Slice<const uint8_t> der_certificate) {
+    auto data = der_certificate.data();
+    bssl::UniquePtr<X509> x509(d2i_X509(nullptr, &data, der_certificate.size()));
+    if (!x509) {
+        return make_error("Failed to parse certificate");
+    }
+    if (data != der_certificate.data() + der_certificate.size()) {
+        return make_error("Certificate has unexpected trailing data");
+    }
+
+    bssl::UniquePtr<EVP_PKEY> pkey(X509_get_pubkey(x509.get()));
+    if (EVP_PKEY_base_id(pkey.get()) != EVP_PKEY_RSA) {
+        return make_error("Subject key is not RSA");
+    }
+    RSA* rsa = EVP_PKEY_get0_RSA(pkey.get());
+    if (!rsa) {
+        return make_error("Failed to extract RSA key");
+    }
+
+    uint8_t* out = nullptr;
+    int size = i2d_RSAPublicKey(rsa, &out);
+    if (size < 0 || !out) {
+        return make_error("Failed to convert to RSAPublicKey");
+    }
+    bssl::UniquePtr<uint8_t> buffer(out);
+
+    KeyResult result;
+    result.key.reserve(size);
+    std::copy(out, out + size, std::back_inserter(result.key));
+    return result;
+}
diff --git a/authfs/service/src/common.rs b/compos/composd/native/composd_native.h
similarity index 65%
rename from authfs/service/src/common.rs
rename to compos/composd/native/composd_native.h
index 00efe9e..112ef73 100644
--- a/authfs/service/src/common.rs
+++ b/compos/composd/native/composd_native.h
@@ -14,11 +14,8 @@
  * limitations under the License.
  */
 
-use std::ffi::CString;
+#pragma once
 
-use authfs_aidl_interface::binder::{ExceptionCode, Status};
+#include "lib.rs.h"
 
-/// Helper function to create a binder exception.
-pub fn new_binder_exception<T: AsRef<str>>(exception: ExceptionCode, message: T) -> Status {
-    Status::new_exception(exception, CString::new(message.as_ref()).as_deref().ok())
-}
+KeyResult extract_rsa_public_key(rust::Slice<const uint8_t> der_certificate);
diff --git a/compos/composd/native/lib.rs b/compos/composd/native/lib.rs
new file mode 100644
index 0000000..ace9600
--- /dev/null
+++ b/compos/composd/native/lib.rs
@@ -0,0 +1,38 @@
+// 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.
+
+//! Bindings native helpers for composd.
+
+pub use ffi::*;
+
+#[cxx::bridge]
+mod ffi {
+    /// Contains either a key or a reason why the key could not be extracted.
+    struct KeyResult {
+        /// The extracted key. If empty, the attempt to extract the key failed.
+        key: Vec<u8>,
+        /// A description of what went wrong if the attempt failed.
+        error: String,
+    }
+
+    unsafe extern "C++" {
+        include!("composd_native.h");
+
+        // SAFETY: The C++ implementation manages its own memory, and does not retain or abuse
+        // the der_certificate reference. cxx handles the mapping of the return value.
+
+        /// Parse the supplied DER X.509 certificate and extract the subject's RsaPublicKey.
+        fn extract_rsa_public_key(der_certificate: &[u8]) -> KeyResult;
+    }
+}
diff --git a/compos/composd/src/compos_instance.rs b/compos/composd/src/compos_instance.rs
deleted file mode 100644
index e30a8b3..0000000
--- a/compos/composd/src/compos_instance.rs
+++ /dev/null
@@ -1,52 +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.
- */
-
-//! Starts and manages instances of the CompOS VM.
-
-use anyhow::{Context, Result};
-use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
-use compos_aidl_interface::binder::Strong;
-use compos_common::compos_client::VmInstance;
-use compos_common::{COMPOS_DATA_ROOT, CURRENT_DIR, INSTANCE_IMAGE_FILE, PRIVATE_KEY_BLOB_FILE};
-use std::fs;
-use std::path::PathBuf;
-
-#[allow(dead_code)]
-pub struct CompOsInstance {
-    instance: VmInstance,
-    service: Strong<dyn ICompOsService>,
-}
-
-impl CompOsInstance {
-    pub fn start_current_instance() -> Result<CompOsInstance> {
-        let instance_image: PathBuf =
-            [COMPOS_DATA_ROOT, CURRENT_DIR, INSTANCE_IMAGE_FILE].iter().collect();
-
-        let instance = VmInstance::start(&instance_image).context("Starting VM")?;
-        let service = instance.get_service().context("Connecting to CompOS")?;
-
-        let key_blob: PathBuf =
-            [COMPOS_DATA_ROOT, CURRENT_DIR, PRIVATE_KEY_BLOB_FILE].iter().collect();
-        let key_blob = fs::read(key_blob).context("Reading private key")?;
-        service.initializeSigningKey(&key_blob).context("Loading key")?;
-
-        Ok(CompOsInstance { instance, service })
-    }
-
-    pub fn cid(&self) -> i32 {
-        self.instance.cid()
-    }
-}
diff --git a/compos/composd/src/composd_main.rs b/compos/composd/src/composd_main.rs
index 33da889..60aeb39 100644
--- a/compos/composd/src/composd_main.rs
+++ b/compos/composd/src/composd_main.rs
@@ -18,12 +18,15 @@
 //! responsible for managing the lifecycle of the CompOS VM instances, providing key management for
 //! them, and orchestrating trusted compilation.
 
-mod compos_instance;
+mod instance_manager;
+mod instance_starter;
 mod odrefresh;
 mod service;
 
+use crate::instance_manager::InstanceManager;
 use android_system_composd::binder::{register_lazy_service, ProcessState};
 use anyhow::{Context, Result};
+use compos_common::compos_client::VmInstance;
 use log::{error, info};
 
 fn try_main() -> Result<()> {
@@ -33,8 +36,10 @@
 
     ProcessState::start_thread_pool();
 
-    let service = service::new_binder();
-    register_lazy_service("android.system.composd", service.as_binder())
+    let virtualization_service = VmInstance::connect_to_virtualization_service()?;
+    let instance_manager = InstanceManager::new(virtualization_service);
+    let composd_service = service::new_binder(instance_manager);
+    register_lazy_service("android.system.composd", composd_service.as_binder())
         .context("Registering service")?;
 
     info!("Registered service, joining threadpool");
@@ -46,7 +51,7 @@
 
 fn main() {
     if let Err(e) = try_main() {
-        error!("{}", e);
+        error!("{:?}", e);
         std::process::exit(1)
     }
 }
diff --git a/compos/composd/src/instance_manager.rs b/compos/composd/src/instance_manager.rs
new file mode 100644
index 0000000..6b36ed8
--- /dev/null
+++ b/compos/composd/src/instance_manager.rs
@@ -0,0 +1,132 @@
+/*
+ * 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.
+ */
+
+//! Manages running instances of the CompOS VM. At most one instance should be running at
+//! a time, started on demand.
+
+use crate::instance_starter::{CompOsInstance, InstanceStarter};
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice;
+use anyhow::{bail, Context, Result};
+use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
+use compos_aidl_interface::binder::Strong;
+use compos_common::CURRENT_DIR;
+use std::sync::{Arc, Mutex, Weak};
+use virtualizationservice::IVirtualizationService::IVirtualizationService;
+
+pub struct InstanceManager {
+    service: Strong<dyn IVirtualizationService>,
+    state: Mutex<State>,
+}
+
+impl InstanceManager {
+    pub fn new(service: Strong<dyn IVirtualizationService>) -> Self {
+        Self { service, state: Default::default() }
+    }
+
+    pub fn get_running_service(&self) -> Result<Strong<dyn ICompOsService>> {
+        let mut state = self.state.lock().unwrap();
+        let instance = state.get_running_instance().context("No running instance")?;
+        Ok(instance.get_service())
+    }
+
+    pub fn start_current_instance(&self) -> Result<Arc<CompOsInstance>> {
+        let mut state = self.state.lock().unwrap();
+        state.mark_starting()?;
+        // Don't hold the lock while we start the instance to avoid blocking other callers.
+        drop(state);
+
+        let instance = self.try_start_current_instance();
+
+        let mut state = self.state.lock().unwrap();
+        if let Ok(ref instance) = instance {
+            state.mark_started(instance)?;
+        } else {
+            state.mark_stopped();
+        }
+        instance
+    }
+
+    fn try_start_current_instance(&self) -> Result<Arc<CompOsInstance>> {
+        let instance_starter = InstanceStarter::new(CURRENT_DIR);
+        let compos_instance = instance_starter.create_or_start_instance(&*self.service)?;
+
+        Ok(Arc::new(compos_instance))
+    }
+}
+
+// Ensures we only run one instance at a time.
+// Valid states:
+// Starting: is_starting is true, running_instance is None.
+// Started: is_starting is false, running_instance is Some(x) and there is a strong ref to x.
+// Stopped: is_starting is false and running_instance is None or a weak ref to a dropped instance.
+// The panic calls here should never happen, unless the code above in InstanceManager is buggy.
+// In particular nothing the client does should be able to trigger them.
+#[derive(Default)]
+struct State {
+    running_instance: Option<Weak<CompOsInstance>>,
+    is_starting: bool,
+}
+
+impl State {
+    // Move to Starting iff we are Stopped.
+    fn mark_starting(&mut self) -> Result<()> {
+        if self.is_starting {
+            bail!("An instance is already starting");
+        }
+        if let Some(weak) = &self.running_instance {
+            if weak.strong_count() != 0 {
+                bail!("An instance is already running");
+            }
+        }
+        self.running_instance = None;
+        self.is_starting = true;
+        Ok(())
+    }
+
+    // Move from Starting to Stopped.
+    fn mark_stopped(&mut self) {
+        if !self.is_starting || self.running_instance.is_some() {
+            panic!("Tried to mark stopped when not starting");
+        }
+        self.is_starting = false;
+    }
+
+    // Move from Starting to Started.
+    fn mark_started(&mut self, instance: &Arc<CompOsInstance>) -> Result<()> {
+        if !self.is_starting {
+            panic!("Tried to mark started when not starting")
+        }
+        if self.running_instance.is_some() {
+            panic!("Attempted to mark started when already started");
+        }
+        self.is_starting = false;
+        self.running_instance = Some(Arc::downgrade(instance));
+        Ok(())
+    }
+
+    // Return the running instance if we are in the Started state.
+    fn get_running_instance(&mut self) -> Option<Arc<CompOsInstance>> {
+        if self.is_starting {
+            return None;
+        }
+        let instance = self.running_instance.as_ref()?.upgrade();
+        if instance.is_none() {
+            // No point keeping an orphaned weak reference
+            self.running_instance = None;
+        }
+        instance
+    }
+}
diff --git a/compos/composd/src/instance_starter.rs b/compos/composd/src/instance_starter.rs
new file mode 100644
index 0000000..1751d35
--- /dev/null
+++ b/compos/composd/src/instance_starter.rs
@@ -0,0 +1,185 @@
+/*
+ * 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.
+ */
+
+//! Responsible for validating and starting an existing instance of the CompOS VM, or creating and
+//! starting a new instance if necessary.
+
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
+    IVirtualizationService::IVirtualizationService, PartitionType::PartitionType,
+};
+use anyhow::{bail, Context, Result};
+use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
+use compos_aidl_interface::binder::{ParcelFileDescriptor, Strong};
+use compos_common::compos_client::VmInstance;
+use compos_common::{
+    COMPOS_DATA_ROOT, INSTANCE_IMAGE_FILE, PRIVATE_KEY_BLOB_FILE, PUBLIC_KEY_FILE,
+};
+use log::{info, warn};
+use std::fs;
+use std::path::{Path, PathBuf};
+
+pub struct CompOsInstance {
+    #[allow(dead_code)] // Keeps VirtualizationService & the VM alive
+    vm_instance: VmInstance,
+    service: Strong<dyn ICompOsService>,
+}
+
+impl CompOsInstance {
+    pub fn get_service(&self) -> Strong<dyn ICompOsService> {
+        self.service.clone()
+    }
+}
+
+pub struct InstanceStarter {
+    instance_name: String,
+    instance_root: PathBuf,
+    instance_image: PathBuf,
+    key_blob: PathBuf,
+    public_key: PathBuf,
+}
+
+impl InstanceStarter {
+    pub fn new(instance_name: &str) -> Self {
+        let instance_root = Path::new(COMPOS_DATA_ROOT).join(instance_name);
+        let instant_root_path = instance_root.as_path();
+        let instance_image = instant_root_path.join(INSTANCE_IMAGE_FILE);
+        let key_blob = instant_root_path.join(PRIVATE_KEY_BLOB_FILE);
+        let public_key = instant_root_path.join(PUBLIC_KEY_FILE);
+        Self {
+            instance_name: instance_name.to_owned(),
+            instance_root,
+            instance_image,
+            key_blob,
+            public_key,
+        }
+    }
+
+    pub fn create_or_start_instance(
+        &self,
+        service: &dyn IVirtualizationService,
+    ) -> Result<CompOsInstance> {
+        let compos_instance = self.start_existing_instance();
+        match compos_instance {
+            Ok(_) => return compos_instance,
+            Err(e) => warn!("Failed to start: {}", e),
+        }
+
+        self.start_new_instance(service)
+    }
+
+    fn start_existing_instance(&self) -> Result<CompOsInstance> {
+        // No point even trying if the files we need aren't there.
+        self.check_files_exist()?;
+
+        info!("Starting {} CompOs instance", self.instance_name);
+
+        let key_blob = fs::read(&self.key_blob).context("Reading private key blob")?;
+        let public_key = fs::read(&self.public_key).context("Reading public key")?;
+
+        let compos_instance = self.start_vm()?;
+        let service = &compos_instance.service;
+
+        if !service.verifySigningKey(&key_blob, &public_key).context("Verifying key pair")? {
+            bail!("Key pair invalid");
+        }
+
+        // If we get this far then the instance image is valid in the current context (e.g. the
+        // current set of APEXes) and the key blob can be successfully decrypted by the VM. So the
+        // files have not been tampered with and we're good to go.
+
+        service.initializeSigningKey(&key_blob).context("Loading signing key")?;
+
+        Ok(compos_instance)
+    }
+
+    fn start_new_instance(
+        &self,
+        virtualization_service: &dyn IVirtualizationService,
+    ) -> Result<CompOsInstance> {
+        info!("Creating {} CompOs instance", self.instance_name);
+
+        // Ignore failure here - the directory may already exist.
+        let _ = fs::create_dir(&self.instance_root);
+
+        self.create_instance_image(virtualization_service)?;
+
+        let compos_instance = self.start_vm()?;
+        let service = &compos_instance.service;
+
+        let key_data = service.generateSigningKey().context("Generating signing key")?;
+        fs::write(&self.key_blob, &key_data.keyBlob).context("Writing key blob")?;
+
+        let key_result = composd_native::extract_rsa_public_key(&key_data.certificate);
+        let rsa_public_key = key_result.key;
+        if rsa_public_key.is_empty() {
+            bail!("Failed to extract public key from certificate: {}", key_result.error);
+        }
+        fs::write(&self.public_key, &rsa_public_key).context("Writing public key")?;
+
+        // We don't need to verify the key, since we just generated it and have it in memory.
+
+        service.initializeSigningKey(&key_data.keyBlob).context("Loading signing key")?;
+
+        Ok(compos_instance)
+    }
+
+    fn start_vm(&self) -> Result<CompOsInstance> {
+        let instance_image = fs::OpenOptions::new()
+            .read(true)
+            .write(true)
+            .open(&self.instance_image)
+            .context("Failed to open instance image")?;
+        let vm_instance = VmInstance::start(instance_image).context("Starting VM")?;
+        let service = vm_instance.get_service().context("Connecting to CompOS")?;
+        Ok(CompOsInstance { vm_instance, service })
+    }
+
+    fn create_instance_image(
+        &self,
+        virtualization_service: &dyn IVirtualizationService,
+    ) -> Result<()> {
+        let instance_image = fs::OpenOptions::new()
+            .create(true)
+            .read(true)
+            .write(true)
+            .open(&self.instance_image)
+            .context("Creating instance image file")?;
+        let instance_image = ParcelFileDescriptor::new(instance_image);
+        // TODO: Where does this number come from?
+        let size = 10 * 1024 * 1024;
+        virtualization_service
+            .initializeWritablePartition(&instance_image, size, PartitionType::ANDROID_VM_INSTANCE)
+            .context("Writing instance image file")?;
+        Ok(())
+    }
+
+    fn check_files_exist(&self) -> Result<()> {
+        if !self.instance_root.is_dir() {
+            bail!("Directory {} not found", self.instance_root.display())
+        };
+        Self::check_file_exists(&self.instance_image)?;
+        Self::check_file_exists(&self.key_blob)?;
+        Self::check_file_exists(&self.public_key)?;
+        Ok(())
+    }
+
+    fn check_file_exists(file: &Path) -> Result<()> {
+        if !file.is_file() {
+            bail!("File {} not found", file.display())
+        };
+        Ok(())
+    }
+}
diff --git a/compos/composd/src/odrefresh.rs b/compos/composd/src/odrefresh.rs
index c0042f0..54da231 100644
--- a/compos/composd/src/odrefresh.rs
+++ b/compos/composd/src/odrefresh.rs
@@ -17,6 +17,7 @@
 //! Handle the details of executing odrefresh to generate compiled artifacts.
 
 use anyhow::{bail, Context, Result};
+use compos_common::VMADDR_CID_ANY;
 use num_derive::FromPrimitive;
 use num_traits::FromPrimitive;
 use std::process::Command;
@@ -36,10 +37,10 @@
     CleanupFailed = EX_MAX + 4,
 }
 
-pub fn run_forced_compile(cid: i32) -> Result<ExitCode> {
+pub fn run_forced_compile() -> Result<ExitCode> {
     // We don`t need to capture stdout/stderr - odrefresh writes to the log
     let mut odrefresh = Command::new(ODREFRESH_BIN)
-        .arg(format!("--use-compilation-os={}", cid))
+        .arg(format!("--use-compilation-os={}", VMADDR_CID_ANY))
         .arg("--force-compile")
         .spawn()
         .context("Running odrefresh")?;
diff --git a/compos/composd/src/service.rs b/compos/composd/src/service.rs
index 7fc9ab0..be9c30c 100644
--- a/compos/composd/src/service.rs
+++ b/compos/composd/src/service.rs
@@ -17,20 +17,25 @@
 //! Implementation of IIsolatedCompilationService, called from system server when compilation is
 //! desired.
 
-use crate::compos_instance::CompOsInstance;
+use crate::instance_manager::InstanceManager;
 use crate::odrefresh;
 use android_system_composd::aidl::android::system::composd::IIsolatedCompilationService::{
     BnIsolatedCompilationService, IIsolatedCompilationService,
 };
-use android_system_composd::binder::{self, BinderFeatures, Interface, Status, Strong};
+use android_system_composd::binder::{self, BinderFeatures, Interface, Strong};
 use anyhow::{bail, 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};
-use std::ffi::CString;
 
-pub struct IsolatedCompilationService {}
+pub struct IsolatedCompilationService {
+    instance_manager: InstanceManager,
+}
 
-pub fn new_binder() -> Strong<dyn IIsolatedCompilationService> {
-    let service = IsolatedCompilationService {};
+pub fn new_binder(instance_manager: InstanceManager) -> Strong<dyn IIsolatedCompilationService> {
+    let service = IsolatedCompilationService { instance_manager };
     BnIsolatedCompilationService::new_binder(service, BinderFeatures::default())
 }
 
@@ -38,14 +43,29 @@
 
 impl IIsolatedCompilationService for IsolatedCompilationService {
     fn runForcedCompile(&self) -> binder::Result<()> {
+        // TODO - check caller is system or shell/root?
         to_binder_result(self.do_run_forced_compile())
     }
+
+    fn compile_cmd(
+        &self,
+        args: &[String],
+        fd_annotation: &FdAnnotation,
+    ) -> binder::Result<CompilationResult> {
+        // TODO - check caller is odrefresh
+        to_binder_result(self.do_compile(args, fd_annotation))
+    }
+
+    fn compile(&self, _marshaled: &[u8], _fd_annotation: &FdAnnotation) -> binder::Result<i8> {
+        Err(new_binder_service_specific_error(-1, "Not yet implemented"))
+    }
 }
 
 fn to_binder_result<T>(result: Result<T>) -> binder::Result<T> {
     result.map_err(|e| {
-        error!("Returning binder error: {:#}", e);
-        Status::new_service_specific_error(-1, CString::new(format!("{:#}", e)).ok().as_deref())
+        let message = format!("{:?}", e);
+        error!("Returning binder error: {}", &message);
+        new_binder_service_specific_error(-1, message)
     })
 }
 
@@ -53,16 +73,26 @@
     fn do_run_forced_compile(&self) -> Result<()> {
         info!("runForcedCompile");
 
-        // TODO: Create instance if need be, handle instance failure, prevent
-        // multiple instances running
-        let comp_os = CompOsInstance::start_current_instance().context("Starting CompOS")?;
+        let comp_os = self.instance_manager.start_current_instance().context("Starting CompOS")?;
 
-        let exit_code = odrefresh::run_forced_compile(comp_os.cid())?;
+        let exit_code = odrefresh::run_forced_compile()?;
 
         if exit_code != odrefresh::ExitCode::CompilationSuccess {
             bail!("Unexpected odrefresh result: {:?}", exit_code);
         }
 
+        // The instance is needed until odrefresh is finished
+        drop(comp_os);
+
         Ok(())
     }
+
+    fn do_compile(
+        &self,
+        args: &[String],
+        fd_annotation: &FdAnnotation,
+    ) -> Result<CompilationResult> {
+        let compos = self.instance_manager.get_running_service()?;
+        compos.compile_cmd(args, fd_annotation).context("Compiling")
+    }
 }
diff --git a/compos/src/compilation.rs b/compos/src/compilation.rs
index fec82a6..1499d4b 100644
--- a/compos/src/compilation.rs
+++ b/compos/src/compilation.rs
@@ -52,7 +52,7 @@
 
 /// Runs the compiler with given flags with file descriptors described in `fd_annotation` retrieved
 /// via `authfs_service`. Returns exit code of the compiler process.
-pub fn compile(
+pub fn compile_cmd(
     compiler_path: &Path,
     compiler_args: &[String],
     authfs_service: Strong<dyn IAuthFsService>,
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 55d9d64..08f3521 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -19,13 +19,13 @@
 //! actual compiler.
 
 use anyhow::Result;
+use binder_common::new_binder_exception;
 use log::warn;
 use std::default::Default;
-use std::ffi::CString;
 use std::path::PathBuf;
 use std::sync::{Arc, RwLock};
 
-use crate::compilation::{compile, CompilerOutput};
+use crate::compilation::{compile_cmd, CompilerOutput};
 use crate::compos_key_service::CompOsKeyService;
 use crate::fsverity;
 use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFsService::IAuthFsService;
@@ -36,7 +36,7 @@
     ICompOsService::{BnCompOsService, ICompOsService},
 };
 use compos_aidl_interface::binder::{
-    BinderFeatures, ExceptionCode, Interface, Result as BinderResult, Status, Strong,
+    BinderFeatures, ExceptionCode, Interface, Result as BinderResult, Strong,
 };
 
 const AUTHFS_SERVICE_NAME: &str = "authfs_service";
@@ -85,14 +85,14 @@
         }
     }
 
-    fn compile(
+    fn compile_cmd(
         &self,
         args: &[String],
         fd_annotation: &FdAnnotation,
     ) -> BinderResult<CompilationResult> {
         let authfs_service = get_authfs_service()?;
         let output =
-            compile(&self.dex2oat_path, args, authfs_service, fd_annotation).map_err(|e| {
+            compile_cmd(&self.dex2oat_path, args, authfs_service, fd_annotation).map_err(|e| {
                 new_binder_exception(
                     ExceptionCode::SERVICE_SPECIFIC,
                     format!("Compilation failed: {}", e),
@@ -124,6 +124,10 @@
         }
     }
 
+    fn compile(&self, _marshaled: &[u8], _fd_annotation: &FdAnnotation) -> BinderResult<i8> {
+        Err(new_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION, "Not yet implemented"))
+    }
+
     fn generateSigningKey(&self) -> BinderResult<CompOsKeyData> {
         self.key_service
             .do_generate()
@@ -154,7 +158,3 @@
 fn get_authfs_service() -> BinderResult<Strong<dyn IAuthFsService>> {
     Ok(authfs_aidl_interface::binder::get_interface(AUTHFS_SERVICE_NAME)?)
 }
-
-fn new_binder_exception<T: AsRef<str>>(exception: ExceptionCode, message: T) -> Status {
-    Status::new_exception(exception, CString::new(message.as_ref()).as_deref().ok())
-}
diff --git a/compos/src/compsvc_main.rs b/compos/src/compsvc_main.rs
index 9855b53..d0c920a 100644
--- a/compos/src/compsvc_main.rs
+++ b/compos/src/compsvc_main.rs
@@ -22,24 +22,61 @@
 mod fsverity;
 mod signer;
 
-use anyhow::{bail, Result};
-use binder::unstable_api::AsNative;
+use android_system_virtualmachineservice::{
+    aidl::android::system::virtualmachineservice::IVirtualMachineService::{
+        IVirtualMachineService, VM_BINDER_SERVICE_PORT,
+    },
+    binder::Strong,
+};
+use anyhow::{anyhow, bail, Context, Result};
+use binder::{
+    unstable_api::{new_spibinder, AIBinder, AsNative},
+    FromIBinder,
+};
 use compos_common::COMPOS_VSOCK_PORT;
-use log::debug;
+use log::{debug, error};
+use nix::ioctl_read_bad;
+use std::fs::OpenOptions;
+use std::os::raw;
+use std::os::unix::io::AsRawFd;
 
-fn main() -> Result<()> {
-    android_logger::init_once(
-        android_logger::Config::default().with_tag("compsvc").with_min_level(log::Level::Debug),
-    );
+/// The CID representing the host VM
+const VMADDR_CID_HOST: u32 = 2;
+
+fn main() {
+    if let Err(e) = try_main() {
+        error!("failed with {:?}", e);
+        std::process::exit(1);
+    }
+}
+
+fn try_main() -> Result<()> {
+    let args = clap::App::new("compsvc")
+        .arg(clap::Arg::with_name("log_to_stderr").long("log_to_stderr"))
+        .get_matches();
+    if args.is_present("log_to_stderr") {
+        env_logger::builder().filter_level(log::LevelFilter::Debug).init();
+    } else {
+        android_logger::init_once(
+            android_logger::Config::default().with_tag("compsvc").with_min_level(log::Level::Debug),
+        );
+    }
 
     let mut service = compsvc::new_binder()?.as_binder();
     debug!("compsvc is starting as a rpc service.");
+
+    let mut ready_notifier = ReadyNotifier::new()?;
+
     // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
     // Plus the binder objects are threadsafe.
+    // RunRpcServerCallback does not retain a reference to ready_callback, and only ever
+    // calls it with the param we provide during the lifetime of ready_notifier.
     let retval = unsafe {
-        binder_rpc_unstable_bindgen::RunRpcServer(
+        binder_rpc_unstable_bindgen::RunRpcServerCallback(
             service.as_native_mut() as *mut binder_rpc_unstable_bindgen::AIBinder,
             COMPOS_VSOCK_PORT,
+            Some(ReadyNotifier::ready_callback),
+            ready_notifier.as_void_ptr(),
         )
     };
     if retval {
@@ -49,3 +86,69 @@
         bail!("Premature termination of RPC server");
     }
 }
+
+struct ReadyNotifier {
+    vm_service: Strong<dyn IVirtualMachineService>,
+    local_cid: u32,
+}
+
+impl ReadyNotifier {
+    fn new() -> Result<Self> {
+        Ok(Self { vm_service: Self::get_vm_service()?, local_cid: Self::get_local_cid()? })
+    }
+
+    fn notify(&self) {
+        if let Err(e) = self.vm_service.notifyPayloadReady(self.local_cid as i32) {
+            error!("Unable to notify ready: {}", e);
+        }
+    }
+
+    fn as_void_ptr(&mut self) -> *mut raw::c_void {
+        self as *mut _ as *mut raw::c_void
+    }
+
+    unsafe extern "C" fn ready_callback(param: *mut raw::c_void) {
+        // SAFETY: This is only ever called by RunRpcServerCallback, within the lifetime of the
+        // ReadyNotifier, with param taking the value returned by as_void_ptr (so a properly aligned
+        // non-null pointer to an initialized instance).
+        let ready_notifier = param as *mut Self;
+        ready_notifier.as_ref().unwrap().notify()
+    }
+
+    fn get_vm_service() -> Result<Strong<dyn IVirtualMachineService>> {
+        // SAFETY: AIBinder returned by RpcClient has correct reference count, and the ownership
+        // can be safely taken by new_spibinder.
+        let ibinder = unsafe {
+            new_spibinder(binder_rpc_unstable_bindgen::RpcClient(
+                VMADDR_CID_HOST,
+                VM_BINDER_SERVICE_PORT as u32,
+            ) as *mut AIBinder)
+        }
+        .ok_or_else(|| anyhow!("Failed to connect to IVirtualMachineService"))?;
+
+        FromIBinder::try_from(ibinder).context("Connecting to IVirtualMachineService")
+    }
+
+    // TODO(b/199259751): remove this after VS can check the peer addresses of binder clients
+    fn get_local_cid() -> Result<u32> {
+        let f = OpenOptions::new()
+            .read(true)
+            .write(false)
+            .open("/dev/vsock")
+            .context("Failed to open /dev/vsock")?;
+        let mut cid = 0;
+        // SAFETY: the kernel only modifies the given u32 integer.
+        unsafe { vm_sockets_get_local_cid(f.as_raw_fd(), &mut cid) }
+            .context("Failed to get local CID")?;
+        Ok(cid)
+    }
+}
+
+// TODO(b/199259751): remove this after VS can check the peer addresses of binder clients
+const IOCTL_VM_SOCKETS_GET_LOCAL_CID: usize = 0x7b9;
+ioctl_read_bad!(
+    /// Gets local cid from /dev/vsock
+    vm_sockets_get_local_cid,
+    IOCTL_VM_SOCKETS_GET_LOCAL_CID,
+    u32
+);
diff --git a/compos/src/pvm_exec.rs b/compos/src/pvm_exec.rs
index b6fc729..fd5ffaf 100644
--- a/compos/src/pvm_exec.rs
+++ b/compos/src/pvm_exec.rs
@@ -32,19 +32,31 @@
 use clap::{value_t, App, Arg};
 use log::{debug, error, warn};
 use minijail::Minijail;
-use nix::fcntl::{fcntl, FcntlArg::F_GETFD};
-use std::os::unix::io::RawFd;
+use nix::fcntl::{fcntl, FcntlArg::F_GETFD, OFlag};
+use nix::unistd::pipe2;
+use std::fs::File;
+use std::io::Read;
+use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
 use std::path::Path;
 use std::process::exit;
 
+use android_system_composd::{
+    aidl::android::system::composd::IIsolatedCompilationService::IIsolatedCompilationService,
+    binder::wait_for_interface,
+};
 use compos_aidl_interface::aidl::com::android::compos::{
     FdAnnotation::FdAnnotation, ICompOsService::ICompOsService,
 };
 use compos_aidl_interface::binder::Strong;
-use compos_common::COMPOS_VSOCK_PORT;
+use compos_common::{COMPOS_VSOCK_PORT, VMADDR_CID_ANY};
 
 const FD_SERVER_BIN: &str = "/apex/com.android.virt/bin/fd_server";
 
+fn get_composd() -> Result<Strong<dyn IIsolatedCompilationService>> {
+    wait_for_interface::<dyn IIsolatedCompilationService>("android.system.composd")
+        .context("Failed to find IIsolatedCompilationService")
+}
+
 fn get_rpc_binder(cid: u32) -> Result<Strong<dyn ICompOsService>> {
     // SAFETY: AIBinder returned by RpcClient has correct reference count, and the ownership can be
     // safely taken by new_spibinder.
@@ -60,7 +72,11 @@
     }
 }
 
-fn spawn_fd_server(fd_annotation: &FdAnnotation, debuggable: bool) -> Result<Minijail> {
+fn spawn_fd_server(
+    fd_annotation: &FdAnnotation,
+    ready_file: File,
+    debuggable: bool,
+) -> Result<Minijail> {
     let mut inheritable_fds = if debuggable {
         vec![1, 2] // inherit/redirect stdout/stderr for debugging
     } else {
@@ -78,6 +94,10 @@
         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)?;
@@ -144,18 +164,31 @@
     Ok(Config { args, fd_annotation: FdAnnotation { input_fds, output_fds }, cid, debuggable })
 }
 
-fn main() -> Result<()> {
-    let debuggable = env!("TARGET_BUILD_VARIANT") != "user";
-    let log_level = if debuggable { log::Level::Trace } else { log::Level::Info };
-    android_logger::init_once(
-        android_logger::Config::default().with_tag("pvm_exec").with_min_level(log_level),
-    );
+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.
+    let read_fd = unsafe { File::from_raw_fd(raw_read) };
+    let write_fd = unsafe { File::from_raw_fd(raw_write) };
+    Ok((read_fd, write_fd))
+}
 
+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
+    // closed. Either way this read will return 0 bytes at that point, and there's no point waiting
+    // any longer.
+    let _ = ready_fd.read(&mut buffer).context("Waiting for fd_server to be ready")?;
+    debug!("fd_server is ready");
+    Ok(())
+}
+
+fn try_main() -> Result<()> {
     // 1. Parse the command line arguments for collect execution data.
     let Config { args, fd_annotation, cid, debuggable } = parse_args()?;
 
     // 2. Spawn and configure a fd_server to serve remote read/write requests.
-    let fd_server_jail = spawn_fd_server(&fd_annotation, debuggable)?;
+    let (ready_read_fd, ready_write_fd) = create_pipe()?;
+    let fd_server_jail = spawn_fd_server(&fd_annotation, ready_write_fd, debuggable)?;
     let fd_server_lifetime = scopeguard::guard(fd_server_jail, |fd_server_jail| {
         if let Err(e) = fd_server_jail.kill() {
             if !matches!(e, minijail::Error::Killed(_)) {
@@ -165,8 +198,18 @@
     });
 
     // 3. Send the command line args to the remote to execute.
-    let service = get_rpc_binder(cid)?;
-    let result = service.compile(&args, &fd_annotation).context("Binder call failed")?;
+    let result = if cid == VMADDR_CID_ANY {
+        // Sentinel value that indicates we should use composd
+        let composd = get_composd()?;
+        wait_for_fd_server_ready(ready_read_fd)?;
+        composd.compile_cmd(&args, &fd_annotation)
+    } else {
+        // Call directly into the VM
+        let compos_vm = get_rpc_binder(cid)?;
+        wait_for_fd_server_ready(ready_read_fd)?;
+        compos_vm.compile_cmd(&args, &fd_annotation)
+    };
+    let result = result.context("Binder call failed")?;
 
     // TODO: store/use the signature
     debug!(
@@ -179,9 +222,24 @@
     // Be explicit about the lifetime, which should last at least until the task is finished.
     drop(fd_server_lifetime);
 
-    if result.exitCode > 0 {
+    if result.exitCode != 0 {
         error!("remote execution failed with exit code {}", result.exitCode);
         exit(result.exitCode as i32);
     }
     Ok(())
 }
+
+fn main() {
+    let debuggable = env!("TARGET_BUILD_VARIANT") != "user";
+    let log_level = if debuggable { log::Level::Trace } else { log::Level::Info };
+    android_logger::init_once(
+        android_logger::Config::default().with_tag("pvm_exec").with_min_level(log_level),
+    );
+
+    // Make sure we log and indicate failure if we were unable to run the command and get its exit
+    // code.
+    if let Err(e) = try_main() {
+        error!("{:?}", e);
+        std::process::exit(-1)
+    }
+}
diff --git a/compos/tests/java/android/compos/test/ComposKeyTestCase.java b/compos/tests/java/android/compos/test/ComposKeyTestCase.java
index 64fd969..d9f7065 100644
--- a/compos/tests/java/android/compos/test/ComposKeyTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposKeyTestCase.java
@@ -158,8 +158,9 @@
                         getBuild(),
                         apkName,
                         packageName,
-                        "assets/vm_config.json",
-                        /* debug */ false);
+                        "assets/vm_test_config.json",
+                        /* debug */ false,
+                        /* use default memoryMib */ 0);
         adbConnectToMicrodroid(getDevice(), mCid);
     }
 
diff --git a/compos/tests/java/android/compos/test/ComposTestCase.java b/compos/tests/java/android/compos/test/ComposTestCase.java
index 8409f44..40f95c3 100644
--- a/compos/tests/java/android/compos/test/ComposTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposTestCase.java
@@ -149,8 +149,9 @@
                         getBuild(),
                         apkName,
                         packageName,
-                        "assets/vm_config.json",
-                        /* debug */ false);
+                        "assets/vm_test_config.json",
+                        /* debug */ false,
+                        /* Use default memory */ 0);
         adbConnectToMicrodroid(getDevice(), mCid);
     }
 
diff --git a/compos/verify_key/verify_key.rs b/compos/verify_key/verify_key.rs
index 8439b97..0cc6473 100644
--- a/compos/verify_key/verify_key.rs
+++ b/compos/verify_key/verify_key.rs
@@ -87,8 +87,9 @@
 
     let blob = read_small_file(blob).context("Failed to read key blob")?;
     let public_key = read_small_file(public_key).context("Failed to read public key")?;
+    let instance_image = File::open(instance_image).context("Failed to open instance image")?;
 
-    let vm_instance = VmInstance::start(&instance_image)?;
+    let vm_instance = VmInstance::start(instance_image)?;
     let service = vm_instance.get_service()?;
 
     let result = service.verifySigningKey(&blob, &public_key).context("Verifying signing key")?;
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index e4334cb..b61ae18 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -102,7 +102,6 @@
         common: {
             deps: [
                 // non-updatable & mandatory apexes
-                "com.android.i18n",
                 "com.android.runtime",
 
                 "microdroid_plat_sepolicy.cil",
@@ -173,7 +172,6 @@
     deps: [
         "android.hardware.security.keymint-service.microdroid",
         "microdroid_fstab",
-        "microdroid_precompiled_sepolicy",
         "microdroid_precompiled_sepolicy.plat_sepolicy_and_mapping.sha256",
         "microdroid_vendor_manifest",
         "microdroid_vendor_compatibility_matrix",
@@ -184,6 +182,7 @@
                 "microdroid_vendor_sepolicy.cil",
                 "microdroid_plat_pub_versioned.cil",
                 "microdroid_plat_sepolicy_vers.txt",
+                "microdroid_precompiled_sepolicy",
             ],
         },
     },
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 9c1792d..dc72c95 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -90,7 +90,7 @@
 
 fn main() {
     if let Err(e) = try_main() {
-        error!("failed with {}", e);
+        error!("failed with {:?}", e);
         std::process::exit(1);
     }
 }
@@ -246,7 +246,8 @@
     info!("notifying payload started");
     service.notifyPayloadStarted(local_cid as i32)?;
 
-    if let Some(code) = child.wait()?.code() {
+    let exit_status = child.wait()?;
+    if let Some(code) = exit_status.code() {
         info!("notifying payload finished");
         service.notifyPayloadFinished(local_cid as i32, code)?;
 
@@ -256,7 +257,7 @@
             error!("task exited with exit code: {}", code);
         }
     } else {
-        error!("task terminated by signal");
+        error!("task terminated: {}", exit_status);
     }
     Ok(())
 }
diff --git a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
index 4c8f5eb..24a955b 100644
--- a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
+++ b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
@@ -167,7 +167,8 @@
             String apkName,
             String packageName,
             String configPath,
-            boolean debug)
+            boolean debug,
+            int memoryMib)
             throws DeviceNotAvailableException {
         CommandRunner android = new CommandRunner(androidDevice);
 
@@ -198,6 +199,7 @@
                         "run-app",
                         "--daemonize",
                         "--log " + logPath,
+                        "--mem " + memoryMib,
                         debugFlag,
                         apkPath,
                         outApkIdsigPath,
diff --git a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
index a7b855a..6548428 100644
--- a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
@@ -18,7 +18,10 @@
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
+import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.After;
@@ -31,6 +34,22 @@
     private static final String APK_NAME = "MicrodroidTestApp.apk";
     private static final String PACKAGE_NAME = "com.android.microdroid.test";
 
+    private static final int MIN_MEM_ARM64 = 125;
+    private static final int MIN_MEM_X86_64 = 270;
+
+    private int minMemorySize() throws DeviceNotAvailableException {
+        CommandRunner android = new CommandRunner(getDevice());
+        String abi = android.run("getprop", "ro.product.cpu.abi");
+        assertTrue(abi != null && !abi.isEmpty());
+        if (abi.startsWith("arm64")) {
+            return MIN_MEM_ARM64;
+        } else if (abi.startsWith("x86_64")) {
+            return MIN_MEM_X86_64;
+        }
+        fail("Unsupported ABI: " + abi);
+        return 0;
+    }
+
     @Test
     public void testMicrodroidBoots() throws Exception {
         final String configPath = "assets/vm_config.json"; // path inside the APK
@@ -41,7 +60,8 @@
                         APK_NAME,
                         PACKAGE_NAME,
                         configPath,
-                        /* debug */ false);
+                        /* debug */ false,
+                        minMemorySize());
         adbConnectToMicrodroid(getDevice(), cid);
 
         // Wait until logd-init starts. The service is one of the last services that are started in
@@ -89,7 +109,14 @@
         final String configPath = "assets/vm_config.json"; // path inside the APK
         final boolean debug = true;
         final String cid =
-                startMicrodroid(getDevice(), getBuild(), APK_NAME, PACKAGE_NAME, configPath, debug);
+                startMicrodroid(
+                        getDevice(),
+                        getBuild(),
+                        APK_NAME,
+                        PACKAGE_NAME,
+                        configPath,
+                        debug,
+                        minMemorySize());
         adbConnectToMicrodroid(getDevice(), cid);
 
         assertThat(runOnMicrodroid("getenforce"), is("Permissive"));
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index 54b32ec..443436d 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -25,6 +25,7 @@
         "android.os.permissions_aidl-rust",
         "libandroid_logger",
         "libanyhow",
+        "libbinder_common",
         "libbinder_rpc_unstable_bindgen",
         "libbinder_rs",
         "libcommand_fds",
diff --git a/virtualizationservice/aidl/Android.bp b/virtualizationservice/aidl/Android.bp
index 571cc5d..30a4b03 100644
--- a/virtualizationservice/aidl/Android.bp
+++ b/virtualizationservice/aidl/Android.bp
@@ -39,7 +39,10 @@
     backend: {
         rust: {
             enabled: true,
-            apex_available: ["com.android.virt"],
+            apex_available: [
+                "com.android.virt",
+                "com.android.compos",
+            ],
         },
     },
 }
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 6679da6..a072060 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -19,12 +19,11 @@
 use crate::payload::add_microdroid_images;
 use crate::{Cid, FIRST_GUEST_CID, SYSPROP_LAST_CID};
 
+use ::binder::unstable_api::AsNative;
 use android_os_permissions_aidl::aidl::android::os::IPermissionController;
-use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualMachine::{
-    BnVirtualMachine, IVirtualMachine,
-};
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
     DiskImage::DiskImage,
+    IVirtualMachine::{BnVirtualMachine, IVirtualMachine},
     IVirtualMachineCallback::IVirtualMachineCallback,
     IVirtualizationService::IVirtualizationService,
     PartitionType::PartitionType,
@@ -35,21 +34,24 @@
     VirtualMachineState::VirtualMachineState,
 };
 use android_system_virtualizationservice::binder::{
-    self, force_lazy_services_persist, BinderFeatures, ExceptionCode, Interface, ParcelFileDescriptor, Status, Strong, ThreadState,
+    self, BinderFeatures, ExceptionCode, Interface, ParcelFileDescriptor, Status, Strong,
+    ThreadState,
 };
-use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
-    VM_BINDER_SERVICE_PORT, VM_STREAM_SERVICE_PORT, BnVirtualMachineService, IVirtualMachineService,
+use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::{
+    IVirtualMachineService::{
+        BnVirtualMachineService, IVirtualMachineService, VM_BINDER_SERVICE_PORT,
+        VM_STREAM_SERVICE_PORT,
+    },
 };
 use anyhow::{anyhow, bail, Context, Result};
-use ::binder::unstable_api::AsNative;
+use binder_common::{lazy_service::LazyServiceGuard, new_binder_exception};
 use disk::QcowFile;
-use idsig::{V4Signature, HashAlgorithm};
-use log::{debug, error, warn, info};
+use idsig::{HashAlgorithm, V4Signature};
+use log::{debug, error, info, warn};
 use microdroid_payload_config::VmPayloadConfig;
 use rustutils::system_properties;
 use std::convert::TryInto;
-use std::ffi::CString;
-use std::fs::{File, OpenOptions, create_dir};
+use std::fs::{create_dir, File, OpenOptions};
 use std::io::{Error, ErrorKind, Write};
 use std::num::NonZeroU32;
 use std::os::unix::io::{FromRawFd, IntoRawFd};
@@ -557,11 +559,13 @@
 #[derive(Debug)]
 struct VirtualMachine {
     instance: Arc<VmInstance>,
+    /// Keeps our service process running as long as this VM instance exists.
+    lazy_service_guard: LazyServiceGuard,
 }
 
 impl VirtualMachine {
     fn create(instance: Arc<VmInstance>) -> Strong<dyn IVirtualMachine> {
-        let binder = VirtualMachine { instance };
+        let binder = VirtualMachine { instance, lazy_service_guard: Default::default() };
         BnVirtualMachine::new_binder(binder, BinderFeatures::default())
     }
 }
@@ -714,19 +718,12 @@
     /// Store a strong VM reference.
     fn debug_hold_vm(&mut self, vm: Strong<dyn IVirtualMachine>) {
         self.debug_held_vms.push(vm);
-        // Make sure our process is not shut down while we hold the VM reference
-        // on behalf of the caller.
-        force_lazy_services_persist(true);
     }
 
     /// Retrieve and remove a strong VM reference.
     fn debug_drop_vm(&mut self, cid: i32) -> Option<Strong<dyn IVirtualMachine>> {
         let pos = self.debug_held_vms.iter().position(|vm| vm.getCid() == Ok(cid))?;
         let vm = self.debug_held_vms.swap_remove(pos);
-        if self.debug_held_vms.is_empty() {
-            // Once we no longer hold any VM references it is ok for our process to be shut down.
-            force_lazy_services_persist(false);
-        }
         Some(vm)
     }
 }
@@ -795,11 +792,6 @@
     ParcelFileDescriptor::new(f)
 }
 
-/// Constructs a new Binder error `Status` with the given `ExceptionCode` and message.
-fn new_binder_exception<T: AsRef<str>>(exception: ExceptionCode, message: T) -> Status {
-    Status::new_exception(exception, CString::new(message.as_ref()).ok().as_deref())
-}
-
 /// Simple utility for referencing Borrowed or Owned. Similar to std::borrow::Cow, but
 /// it doesn't require that T implements Clone.
 enum BorrowedOrOwned<'a, T> {
diff --git a/vm/src/main.rs b/vm/src/main.rs
index fe47d2c..062773b 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -62,6 +62,11 @@
         /// Whether to run VM in debug mode.
         #[structopt(short, long)]
         debug: bool,
+
+        /// Memory size (in MiB) of the VM. If unspecified, defaults to the value of `memory_mib`
+        /// in the VM config file.
+        #[structopt(short, long)]
+        mem: Option<u32>,
     },
     /// Run a virtual machine
     Run {
@@ -118,7 +123,7 @@
         .context("Failed to find VirtualizationService")?;
 
     match opt {
-        Opt::RunApp { apk, idsig, instance, config_path, daemonize, log, debug } => {
+        Opt::RunApp { apk, idsig, instance, config_path, daemonize, log, debug, mem } => {
             command_run_app(
                 service,
                 &apk,
@@ -128,10 +133,11 @@
                 daemonize,
                 log.as_deref(),
                 debug,
+                mem,
             )
         }
         Opt::Run { config, daemonize, log } => {
-            command_run(service, &config, daemonize, log.as_deref())
+            command_run(service, &config, daemonize, log.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 0d34a97..42da6a3 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -49,6 +49,7 @@
     daemonize: bool,
     log_path: Option<&Path>,
     debug: bool,
+    mem: Option<u32>,
 ) -> Result<(), Error> {
     let apk_file = File::open(apk).context("Failed to open APK file")?;
     let idsig_file = File::create(idsig).context("Failed to create idsig file")?;
@@ -76,8 +77,7 @@
         instanceImage: open_parcel_file(instance, true /* writable */)?.into(),
         configPath: config_path.to_owned(),
         debug,
-        // Use the default.
-        memoryMib: 0,
+        memoryMib: mem.unwrap_or(0) as i32, // 0 means use the VM default
     });
     run(service, &config, &format!("{:?}!{:?}", apk, config_path), daemonize, log_path)
 }
@@ -88,10 +88,14 @@
     config_path: &Path,
     daemonize: bool,
     log_path: Option<&Path>,
+    mem: Option<u32>,
 ) -> Result<(), Error> {
     let config_file = File::open(config_path).context("Failed to open config file")?;
-    let config =
+    let mut config =
         VmConfig::load(&config_file).context("Failed to parse config file")?.to_parcelable()?;
+    if let Some(mem) = mem {
+        config.memoryMib = mem as i32;
+    }
     run(
         service,
         &VirtualMachineConfig::RawConfig(config),