compsvc: run odrefresh in the VM

On request, compsvc is responsible to prepare an authfs mountpoint and
specify expected environment variables and command line flags to
odrefresh.

Bug: 205750213
Test: atest ComposHostTestCases # no regression
Change-Id: Ib3c5e5f85e7c76616f1a0937bd43466078598701
diff --git a/authfs/aidl/com/android/virt/fs/IAuthFs.aidl b/authfs/aidl/com/android/virt/fs/IAuthFs.aidl
index 064b6f3..f7b2c8d 100644
--- a/authfs/aidl/com/android/virt/fs/IAuthFs.aidl
+++ b/authfs/aidl/com/android/virt/fs/IAuthFs.aidl
@@ -22,4 +22,7 @@
 interface IAuthFs {
     /** Returns a file descriptor given the name of a remote file descriptor. */
     ParcelFileDescriptor openFile(int remoteFdName, boolean writable);
+
+    /** Returns the mount path of the current IAuthFs instance. */
+    String getMountPoint();
 }
diff --git a/authfs/service/src/authfs.rs b/authfs/service/src/authfs.rs
index 2d4f707..e1d820a 100644
--- a/authfs/service/src/authfs.rs
+++ b/authfs/service/src/authfs.rs
@@ -67,6 +67,14 @@
         })?;
         Ok(ParcelFileDescriptor::new(file))
     }
+
+    fn getMountPoint(&self) -> binder::Result<String> {
+        if let Some(s) = self.mountpoint.to_str() {
+            Ok(s.to_string())
+        } else {
+            Err(new_binder_exception(ExceptionCode::SERVICE_SPECIFIC, "Bad string encoding"))
+        }
+    }
 }
 
 impl AuthFs {
diff --git a/compos/aidl/com/android/compos/ICompOsService.aidl b/compos/aidl/com/android/compos/ICompOsService.aidl
index db746fd..7af2ada 100644
--- a/compos/aidl/com/android/compos/ICompOsService.aidl
+++ b/compos/aidl/com/android/compos/ICompOsService.aidl
@@ -42,6 +42,19 @@
             String bootClasspath, String dex2oatBootClasspath, String systemServerClassPath);
 
     /**
+     * Run odrefresh in the VM context.
+     *
+     * The execution is based on the VM's APEX mounts, files on Android's /system (by accessing
+     * through systemDirFd over AuthFS), and *CLASSPATH derived in the VM, to generate the same
+     * odrefresh output aritfacts to the output directory (through outputDirFd).
+     *
+     * The caller/Android is allowed to specify the zygote arch (ro.zygote).
+     *
+     * @return a CompilationResult
+     */
+    CompilationResult odrefresh(int systemDirFd, int outputDirFd, String zygoteArch);
+
+    /**
      * Run dex2oat command with provided args, in a context that may be specified in FdAnnotation,
      * e.g. with file descriptors pre-opened. The service is responsible to decide what executables
      * it may run.
diff --git a/compos/src/compilation.rs b/compos/src/compilation.rs
index 72dca14..b726a1e 100644
--- a/compos/src/compilation.rs
+++ b/compos/src/compilation.rs
@@ -17,14 +17,17 @@
 use anyhow::{anyhow, bail, Context, Result};
 use log::error;
 use minijail::{self, Minijail};
-use std::fs::File;
+use std::env;
+use std::fs::{create_dir, File};
 use std::os::unix::io::{AsRawFd, RawFd};
-use std::path::Path;
+use std::path::{Path, PathBuf};
 
 use crate::fsverity;
 use authfs_aidl_interface::aidl::com::android::virt::fs::{
     AuthFsConfig::{
-        AuthFsConfig, InputFdAnnotation::InputFdAnnotation, OutputFdAnnotation::OutputFdAnnotation,
+        AuthFsConfig, InputDirFdAnnotation::InputDirFdAnnotation,
+        InputFdAnnotation::InputFdAnnotation, OutputDirFdAnnotation::OutputDirFdAnnotation,
+        OutputFdAnnotation::OutputFdAnnotation,
     },
     IAuthFs::IAuthFs,
     IAuthFsService::IAuthFsService,
@@ -32,6 +35,8 @@
 use authfs_aidl_interface::binder::{ParcelFileDescriptor, Strong};
 use compos_aidl_interface::aidl::com::android::compos::FdAnnotation::FdAnnotation;
 
+const FD_SERVER_PORT: i32 = 3264; // TODO: support dynamic port
+
 /// 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;
@@ -53,6 +58,60 @@
     image: ParcelFileDescriptor,
 }
 
+pub fn odrefresh(
+    odrefresh_path: &Path,
+    system_dir_fd: i32,
+    output_dir_fd: i32,
+    zygote_arch: &str,
+    authfs_service: Strong<dyn IAuthFsService>,
+) -> Result<CompilerOutput> {
+    // Mount authfs (via authfs_service). The authfs instance unmounts once the `authfs` variable
+    // is out of scope.
+    let authfs_config = AuthFsConfig {
+        port: FD_SERVER_PORT,
+        inputDirFdAnnotations: vec![InputDirFdAnnotation {
+            fd: system_dir_fd,
+            // TODO(206869687): Replace /dev/null with the real path when possible.
+            manifestPath: "/dev/null".to_string(),
+            prefix: "/system".to_string(),
+        }],
+        outputDirFdAnnotations: vec![OutputDirFdAnnotation { fd: output_dir_fd }],
+        ..Default::default()
+    };
+    let authfs = authfs_service.mount(&authfs_config)?;
+    let mountpoint = PathBuf::from(authfs.getMountPoint()?);
+
+    let mut android_root = mountpoint.clone();
+    android_root.push(system_dir_fd.to_string());
+    android_root.push("system");
+    env::set_var("ANDROID_ROOT", &android_root);
+
+    let mut staging_dir = mountpoint;
+    staging_dir.push(output_dir_fd.to_string());
+    staging_dir.push("staging");
+    create_dir(&staging_dir).context("Create staging directory")?;
+
+    let args = vec![
+        "odrefresh".to_string(),
+        format!("--zygote-arch={}", zygote_arch),
+        format!("--staging-dir={}", staging_dir.display()),
+        "--force-compile".to_string(),
+    ];
+    let jail = spawn_jailed_task(odrefresh_path, &args, Vec::new() /* fd_mapping */)
+        .context("Spawn odrefresh")?;
+    match jail.wait() {
+        // TODO(161471326): On success, sign all files in the output directory.
+        Ok(()) => Ok(CompilerOutput::ExitCode(0)),
+        Err(minijail::Error::ReturnCode(exit_code)) => {
+            error!("dex2oat failed with exit code {}", exit_code);
+            Ok(CompilerOutput::ExitCode(exit_code as i8))
+        }
+        Err(e) => {
+            bail!("Unexpected minijail error: {}", e)
+        }
+    }
+}
+
 /// 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_cmd(
@@ -140,7 +199,7 @@
 
 fn build_authfs_config(fd_annotation: &FdAnnotation) -> AuthFsConfig {
     AuthFsConfig {
-        port: 3264, // TODO: support dynamic port
+        port: FD_SERVER_PORT,
         inputFdAnnotations: fd_annotation
             .input_fds
             .iter()
@@ -151,8 +210,7 @@
             .iter()
             .map(|fd| OutputFdAnnotation { fd: *fd })
             .collect(),
-        inputDirFdAnnotations: vec![],
-        outputDirFdAnnotations: vec![],
+        ..Default::default()
     }
 }
 
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 9d2f1dc..0a15876 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -26,7 +26,7 @@
 use std::path::PathBuf;
 use std::sync::{Arc, RwLock};
 
-use crate::compilation::{compile_cmd, CompilerOutput};
+use crate::compilation::{compile_cmd, odrefresh, CompilerOutput};
 use crate::compos_key_service::CompOsKeyService;
 use crate::fsverity;
 use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFsService::IAuthFsService;
@@ -42,11 +42,13 @@
 
 const AUTHFS_SERVICE_NAME: &str = "authfs_service";
 const DEX2OAT_PATH: &str = "/apex/com.android.art/bin/dex2oat64";
+const ODREFRESH_PATH: &str = "/apex/com.android.art/bin/odrefresh";
 
 /// Constructs a binder object that implements ICompOsService.
 pub fn new_binder() -> Result<Strong<dyn ICompOsService>> {
     let service = CompOsService {
         dex2oat_path: PathBuf::from(DEX2OAT_PATH),
+        odrefresh_path: PathBuf::from(ODREFRESH_PATH),
         key_service: CompOsKeyService::new()?,
         key_blob: Arc::new(RwLock::new(Vec::new())),
     };
@@ -55,6 +57,7 @@
 
 struct CompOsService {
     dex2oat_path: PathBuf,
+    odrefresh_path: PathBuf,
     key_service: CompOsKeyService,
     key_blob: Arc<RwLock<Vec<u8>>>,
 }
@@ -99,6 +102,48 @@
         Ok(())
     }
 
+    fn odrefresh(
+        &self,
+        system_dir_fd: i32,
+        output_dir_fd: i32,
+        zygote_arch: &str,
+    ) -> BinderResult<CompilationResult> {
+        if system_dir_fd < 0 || output_dir_fd < 0 {
+            return Err(new_binder_exception(
+                ExceptionCode::ILLEGAL_ARGUMENT,
+                "The remote FDs are expected to be non-negative",
+            ));
+        }
+        if zygote_arch != "zygote64" && zygote_arch != "zygote64_32" {
+            return Err(new_binder_exception(
+                ExceptionCode::ILLEGAL_ARGUMENT,
+                "Invalid zygote arch",
+            ));
+        }
+
+        let authfs_service = get_authfs_service()?;
+        let output = odrefresh(
+            &self.odrefresh_path,
+            system_dir_fd,
+            output_dir_fd,
+            zygote_arch,
+            authfs_service,
+        )
+        .map_err(|e| {
+            warn!("odrefresh failed: {}", e);
+            new_binder_exception(
+                ExceptionCode::SERVICE_SPECIFIC,
+                format!("odrefresh failed: {}", e),
+            )
+        })?;
+        match output {
+            CompilerOutput::ExitCode(exit_code) => {
+                Ok(CompilationResult { exitCode: exit_code, ..Default::default() })
+            }
+            _ => Err(new_binder_exception(ExceptionCode::SERVICE_SPECIFIC, "odrefresh failed")),
+        }
+    }
+
     fn compile_cmd(
         &self,
         args: &[String],