Refactor compilation out of compsvc

This change makes compsvc always uses dex2oat and no longer accepts
generic executable.

To make the next step of merging ICompService and ICompOsKeyService easier,
the actual compilation setup is extracted from compsvc.rs into a new crate.

Also, remove debuggable flag since it doesn't seem quite useful anymore.

Bug: 194717985
Test: ComposHostTestCases
Change-Id: Ided0c07afb69cd518aed3473d50c5fb695386ca7
diff --git a/compos/apk/assets/vm_config.json b/compos/apk/assets/vm_config.json
index f9f1f90..3be8a8a 100644
--- a/compos/apk/assets/vm_config.json
+++ b/compos/apk/assets/vm_config.json
@@ -7,8 +7,7 @@
     "type": "executable",
     "command": "/apex/com.android.compos/bin/compsvc",
     "args": [
-      "--rpc-binder",
-      "/apex/com.android.art/bin/dex2oat64"
+      "--rpc-binder"
     ]
   },
   "apexes": [
diff --git a/compos/src/compilation.rs b/compos/src/compilation.rs
new file mode 100644
index 0000000..53302e8
--- /dev/null
+++ b/compos/src/compilation.rs
@@ -0,0 +1,121 @@
+/*
+ * 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.
+ */
+
+use anyhow::{bail, Context, Result};
+use log::error;
+use minijail::{self, Minijail};
+use std::os::unix::io::AsRawFd;
+use std::path::Path;
+
+use authfs_aidl_interface::aidl::com::android::virt::fs::{
+    AuthFsConfig::AuthFsConfig, IAuthFs::IAuthFs, IAuthFsService::IAuthFsService,
+    InputFdAnnotation::InputFdAnnotation, OutputFdAnnotation::OutputFdAnnotation,
+};
+use authfs_aidl_interface::binder::{ParcelFileDescriptor, Strong};
+use compos_aidl_interface::aidl::com::android::compos::Metadata::Metadata;
+
+/// The number that represents the file descriptor number expecting by the task. The number may be
+/// meaningless in the current process.
+pub type PseudoRawFd = i32;
+
+/// Runs the compiler with given flags with file descriptors described in `metadata` retrieved via
+/// `authfs_service`. Returns exit code of the compiler process.
+pub fn compile(
+    compiler_path: &Path,
+    compiler_args: &[String],
+    authfs_service: Strong<dyn IAuthFsService>,
+    metadata: &Metadata,
+) -> Result<i8> {
+    // Mount authfs (via authfs_service).
+    let authfs_config = build_authfs_config(metadata);
+    let authfs = authfs_service.mount(&authfs_config)?;
+
+    // The task expects to receive FD numbers that match its flags (e.g. --zip-fd=42) prepared
+    // on the host side. Since the local FD opened from authfs (e.g. /authfs/42) may not match
+    // the task's expectation, prepare a FD mapping and let minijail prepare the correct FD
+    // setup.
+    let fd_mapping =
+        open_authfs_files_for_fd_mapping(&authfs, &authfs_config).context("Open on authfs")?;
+
+    let jail =
+        spawn_jailed_task(compiler_path, compiler_args, fd_mapping).context("Spawn dex2oat")?;
+    let jail_result = jail.wait();
+
+    // Be explicit about the lifetime, which should last at least until the task is finished.
+    drop(authfs);
+
+    match jail_result {
+        Ok(()) => Ok(0), // TODO(b/161471326): Sign the output on succeed.
+        Err(minijail::Error::ReturnCode(exit_code)) => {
+            error!("Task failed with exit code {}", exit_code);
+            Ok(exit_code as i8)
+        }
+        Err(e) => {
+            bail!("Unexpected minijail error: {}", e)
+        }
+    }
+}
+
+fn build_authfs_config(metadata: &Metadata) -> AuthFsConfig {
+    AuthFsConfig {
+        port: 3264, // TODO: support dynamic port
+        inputFdAnnotations: metadata
+            .input_fd_annotations
+            .iter()
+            .map(|x| InputFdAnnotation { fd: x.fd, fileSize: x.file_size })
+            .collect(),
+        outputFdAnnotations: metadata
+            .output_fd_annotations
+            .iter()
+            .map(|x| OutputFdAnnotation { fd: x.fd })
+            .collect(),
+    }
+}
+
+fn open_authfs_files_for_fd_mapping(
+    authfs: &Strong<dyn IAuthFs>,
+    config: &AuthFsConfig,
+) -> Result<Vec<(ParcelFileDescriptor, PseudoRawFd)>> {
+    let mut fd_mapping = Vec::new();
+
+    let results: Result<Vec<_>> = config
+        .inputFdAnnotations
+        .iter()
+        .map(|annotation| Ok((authfs.openFile(annotation.fd, false)?, annotation.fd)))
+        .collect();
+    fd_mapping.append(&mut results?);
+
+    let results: Result<Vec<_>> = config
+        .outputFdAnnotations
+        .iter()
+        .map(|annotation| Ok((authfs.openFile(annotation.fd, true)?, annotation.fd)))
+        .collect();
+    fd_mapping.append(&mut results?);
+
+    Ok(fd_mapping)
+}
+
+fn spawn_jailed_task(
+    executable: &Path,
+    args: &[String],
+    fd_mapping: Vec<(ParcelFileDescriptor, PseudoRawFd)>,
+) -> Result<Minijail> {
+    // TODO(b/185175567): Run in a more restricted sandbox.
+    let jail = Minijail::new()?;
+    let preserve_fds: Vec<_> = fd_mapping.iter().map(|(f, id)| (f.as_raw_fd(), *id)).collect();
+    let _pid = jail.run_remap(executable, preserve_fds.as_slice(), args)?;
+    Ok(jail)
+}
diff --git a/compos/src/compos_key_main.rs b/compos/src/compos_key_main.rs
index 9d57e4d..ea5005d 100644
--- a/compos/src/compos_key_main.rs
+++ b/compos/src/compos_key_main.rs
@@ -15,6 +15,7 @@
 //! Run the CompOS key management service, either in the host using normal Binder or in the
 //! VM using RPC Binder.
 
+mod compilation;
 mod compos_key_service;
 mod compsvc;
 mod signer;
diff --git a/compos/src/compos_key_service.rs b/compos/src/compos_key_service.rs
index 779b798..35c04d7 100644
--- a/compos/src/compos_key_service.rs
+++ b/compos/src/compos_key_service.rs
@@ -122,12 +122,7 @@
     fn getCompService(&self, key_blob: &[u8]) -> binder::Result<Strong<dyn ICompService>> {
         let signer =
             Box::new(CompOsSigner { key_blob: key_blob.to_owned(), key_service: self.clone() });
-        let debuggable = true;
-        Ok(compsvc::new_binder(
-            "/apex/com.android.art/bin/dex2oat64".to_owned(),
-            debuggable,
-            Some(signer),
-        ))
+        Ok(compsvc::new_binder(Some(signer)))
     }
 }
 
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 14b520e..f22c785 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -14,52 +14,35 @@
  * limitations under the License.
  */
 
-//! compsvc is a service to run computational tasks in a PVM upon request. It is able to set up
+//! compsvc is a service to run compilation tasks in a PVM upon request. It is able to set up
 //! file descriptors backed by authfs (via authfs_service) and pass the file descriptors to the
-//! actual tasks.
+//! actual compiler.
 
-use anyhow::Result;
-use log::error;
-use minijail::{self, Minijail};
-use std::ffi::CString;
-use std::os::unix::io::AsRawFd;
-use std::path::{Path, PathBuf};
+use std::path::PathBuf;
 
+use crate::compilation::compile;
 use crate::signer::Signer;
-use authfs_aidl_interface::aidl::com::android::virt::fs::{
-    AuthFsConfig::AuthFsConfig, IAuthFs::IAuthFs, IAuthFsService::IAuthFsService,
-    InputFdAnnotation::InputFdAnnotation, OutputFdAnnotation::OutputFdAnnotation,
-};
-use authfs_aidl_interface::binder::ParcelFileDescriptor;
+use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFsService::IAuthFsService;
 use compos_aidl_interface::aidl::com::android::compos::ICompService::{
     BnCompService, ICompService,
 };
 use compos_aidl_interface::aidl::com::android::compos::Metadata::Metadata;
 use compos_aidl_interface::binder::{
-    BinderFeatures, ExceptionCode, Interface, Result as BinderResult, Status, StatusCode, Strong,
+    BinderFeatures, ExceptionCode, Interface, Result as BinderResult, Status, Strong,
 };
+use std::ffi::CString;
 
 const AUTHFS_SERVICE_NAME: &str = "authfs_service";
+const DEX2OAT_PATH: &str = "/apex/com.android.art/bin/dex2oat64";
 
-/// The number that represents the file descriptor number expecting by the task. The number may be
-/// meaningless in the current process.
-pub type PseudoRawFd = i32;
-
-/// Constructs a binder object that implements ICompService. task_bin is the path to the binary that will
-/// be run when execute() is called. If debuggable is true then stdout/stderr from the binary will be
-/// available for debugging.
-pub fn new_binder(
-    task_bin: String,
-    debuggable: bool,
-    signer: Option<Box<dyn Signer>>,
-) -> Strong<dyn ICompService> {
-    let service = CompService { task_bin: PathBuf::from(task_bin), debuggable, signer };
+/// Constructs a binder object that implements ICompService.
+pub fn new_binder(signer: Option<Box<dyn Signer>>) -> Strong<dyn ICompService> {
+    let service = CompService { dex2oat_path: PathBuf::from(DEX2OAT_PATH), signer };
     BnCompService::new_binder(service, BinderFeatures::default())
 }
 
 struct CompService {
-    task_bin: PathBuf,
-    debuggable: bool,
+    dex2oat_path: PathBuf,
     #[allow(dead_code)] // TODO: Make use of this
     signer: Option<Box<dyn Signer>>,
 }
@@ -68,45 +51,13 @@
 
 impl ICompService for CompService {
     fn execute(&self, args: &[String], metadata: &Metadata) -> BinderResult<i8> {
-        // Mount authfs (via authfs_service).
-        let authfs_config = build_authfs_config(metadata);
-        let authfs = get_authfs_service()?.mount(&authfs_config)?;
-
-        // The task expects to receive FD numbers that match its flags (e.g. --zip-fd=42) prepared
-        // on the host side. Since the local FD opened from authfs (e.g. /authfs/42) may not match
-        // the task's expectation, prepare a FD mapping and let minijail prepare the correct FD
-        // setup.
-        let fd_mapping =
-            open_authfs_files_for_fd_mapping(&authfs, &authfs_config).map_err(|e| {
-                new_binder_exception(
-                    ExceptionCode::SERVICE_SPECIFIC,
-                    format!("Failed to create FDs on authfs: {:?}", e),
-                )
-            })?;
-
-        let jail =
-            spawn_jailed_task(&self.task_bin, args, fd_mapping, self.debuggable).map_err(|e| {
-                new_binder_exception(
-                    ExceptionCode::SERVICE_SPECIFIC,
-                    format!("Failed to spawn the task: {:?}", e),
-                )
-            })?;
-        let jail_result = jail.wait();
-
-        // Be explicit about the lifetime, which should last at least until the task is finished.
-        drop(authfs);
-
-        match jail_result {
-            Ok(_) => Ok(0), // TODO(b/161471326): Sign the output on succeed.
-            Err(minijail::Error::ReturnCode(exit_code)) => {
-                error!("Task failed with exit code {}", exit_code);
-                Err(Status::from(StatusCode::FAILED_TRANSACTION))
-            }
-            Err(e) => {
-                error!("Unexpected minijail error: {}", e);
-                Err(Status::from(StatusCode::UNKNOWN_ERROR))
-            }
-        }
+        let authfs_service = get_authfs_service()?;
+        compile(&self.dex2oat_path, args, authfs_service, metadata).map_err(|e| {
+            new_binder_exception(
+                ExceptionCode::SERVICE_SPECIFIC,
+                format!("Compilation failed: {}", e),
+            )
+        })
     }
 }
 
@@ -114,67 +65,6 @@
     Ok(authfs_aidl_interface::binder::get_interface(AUTHFS_SERVICE_NAME)?)
 }
 
-fn build_authfs_config(metadata: &Metadata) -> AuthFsConfig {
-    AuthFsConfig {
-        port: 3264, // TODO: support dynamic port
-        inputFdAnnotations: metadata
-            .input_fd_annotations
-            .iter()
-            .map(|x| InputFdAnnotation { fd: x.fd, fileSize: x.file_size })
-            .collect(),
-        outputFdAnnotations: metadata
-            .output_fd_annotations
-            .iter()
-            .map(|x| OutputFdAnnotation { fd: x.fd })
-            .collect(),
-    }
-}
-
-fn open_authfs_files_for_fd_mapping(
-    authfs: &Strong<dyn IAuthFs>,
-    config: &AuthFsConfig,
-) -> Result<Vec<(ParcelFileDescriptor, PseudoRawFd)>> {
-    let mut fd_mapping = Vec::new();
-
-    let results: Result<Vec<_>> = config
-        .inputFdAnnotations
-        .iter()
-        .map(|annotation| Ok((authfs.openFile(annotation.fd, false)?, annotation.fd)))
-        .collect();
-    fd_mapping.append(&mut results?);
-
-    let results: Result<Vec<_>> = config
-        .outputFdAnnotations
-        .iter()
-        .map(|annotation| Ok((authfs.openFile(annotation.fd, true)?, annotation.fd)))
-        .collect();
-    fd_mapping.append(&mut results?);
-
-    Ok(fd_mapping)
-}
-
-fn spawn_jailed_task(
-    executable: &Path,
-    args: &[String],
-    fd_mapping: Vec<(ParcelFileDescriptor, PseudoRawFd)>,
-    debuggable: bool,
-) -> Result<Minijail> {
-    // TODO(b/185175567): Run in a more restricted sandbox.
-    let jail = Minijail::new()?;
-
-    let mut preserve_fds = if debuggable {
-        // Inherit/redirect stdout/stderr for debugging, assuming no conflict
-        vec![(1, 1), (2, 2)]
-    } else {
-        vec![]
-    };
-
-    preserve_fds.extend(fd_mapping.iter().map(|(f, id)| (f.as_raw_fd(), *id)));
-
-    let _pid = jail.run_remap(executable, preserve_fds.as_slice(), args)?;
-    Ok(jail)
-}
-
 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 9f12132..8f68949 100644
--- a/compos/src/compsvc_main.rs
+++ b/compos/src/compsvc_main.rs
@@ -16,11 +16,9 @@
 
 //! A tool to start a standalone compsvc server, either in the host using Binder or in a VM using
 //! RPC binder over vsock.
-//!
-//! Example:
-//! $ compsvc /system/bin/sleep
 
 mod common;
+mod compilation;
 mod compsvc;
 mod signer;
 
@@ -31,27 +29,17 @@
 use log::debug;
 
 struct Config {
-    task_bin: String,
     rpc_binder: bool,
-    debuggable: bool,
 }
 
 fn parse_args() -> Result<Config> {
     #[rustfmt::skip]
     let matches = clap::App::new("compsvc")
-        .arg(clap::Arg::with_name("debug")
-             .long("debug"))
-        .arg(clap::Arg::with_name("task_bin")
-             .required(true))
         .arg(clap::Arg::with_name("rpc_binder")
              .long("rpc-binder"))
         .get_matches();
 
-    Ok(Config {
-        task_bin: matches.value_of("task_bin").unwrap().to_string(),
-        rpc_binder: matches.is_present("rpc_binder"),
-        debuggable: matches.is_present("debug"),
-    })
+    Ok(Config { rpc_binder: matches.is_present("rpc_binder") })
 }
 
 fn main() -> Result<()> {
@@ -60,7 +48,7 @@
     );
 
     let config = parse_args()?;
-    let mut service = compsvc::new_binder(config.task_bin, config.debuggable, None).as_binder();
+    let mut service = compsvc::new_binder(None).as_binder();
     if config.rpc_binder {
         debug!("compsvc is starting as a rpc service.");
         // SAFETY: Service ownership is transferring to the server and won't be valid afterward.