Sign artifacts after compilation

If compilation succeeds, pass over all the artifacts we generated and
produce an OdsignInfo file with all the names and digests and a
signature of it using the CompOS key.

Various refactorings along the way to make it eaiser:
- Extract the odrefresh ExitCode enum for reuse
- Extracted a Signer, distinct from an ArtifactSigner
- Defined OdrefreshContext to stop the linter complaining about a
  function with >7 arguments.

Also added more debugging to help fix my various silly mistakes.

Bug: 161471326
Test: composd_cmd forced-odrefresh -> signature file produced
Change-Id: Ia6a6abd9e28158f4767e7752727635087f9bb566
diff --git a/compos/src/compilation.rs b/compos/src/compilation.rs
index f8a66c2..cf6f30a 100644
--- a/compos/src/compilation.rs
+++ b/compos/src/compilation.rs
@@ -18,10 +18,12 @@
 use log::{debug, error, info};
 use minijail::{self, Minijail};
 use std::env;
-use std::fs::File;
+use std::fs::{read_dir, File};
 use std::os::unix::io::{AsRawFd, RawFd};
 use std::path::{Path, PathBuf};
 
+use crate::artifact_signer::ArtifactSigner;
+use crate::compos_key_service::Signer;
 use crate::fsverity;
 use authfs_aidl_interface::aidl::com::android::virt::fs::{
     AuthFsConfig::{
@@ -34,6 +36,7 @@
 };
 use authfs_aidl_interface::binder::{ParcelFileDescriptor, Strong};
 use compos_aidl_interface::aidl::com::android::compos::FdAnnotation::FdAnnotation;
+use compos_common::odrefresh::ExitCode;
 
 const FD_SERVER_PORT: i32 = 3264; // TODO: support dynamic port
 
@@ -58,28 +61,51 @@
     image: ParcelFileDescriptor,
 }
 
-pub fn odrefresh(
-    odrefresh_path: &Path,
-    target_dir_name: &str,
+pub struct OdrefreshContext<'a> {
     system_dir_fd: i32,
     output_dir_fd: i32,
     staging_dir_fd: i32,
-    zygote_arch: &str,
+    target_dir_name: &'a str,
+    zygote_arch: &'a str,
+}
+
+impl<'a> OdrefreshContext<'a> {
+    pub fn new(
+        system_dir_fd: i32,
+        output_dir_fd: i32,
+        staging_dir_fd: i32,
+        target_dir_name: &'a str,
+        zygote_arch: &'a str,
+    ) -> Result<Self> {
+        if system_dir_fd < 0 || output_dir_fd < 0 || staging_dir_fd < 0 {
+            bail!("The remote FDs are expected to be non-negative");
+        }
+        if zygote_arch != "zygote64" && zygote_arch != "zygote64_32" {
+            bail!("Invalid zygote arch");
+        }
+        Ok(Self { system_dir_fd, output_dir_fd, staging_dir_fd, target_dir_name, zygote_arch })
+    }
+}
+
+pub fn odrefresh(
+    odrefresh_path: &Path,
+    context: OdrefreshContext,
     authfs_service: Strong<dyn IAuthFsService>,
-) -> Result<i8> {
+    signer: Signer,
+) -> Result<ExitCode> {
     // 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,
+            fd: context.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 },
-            OutputDirFdAnnotation { fd: staging_dir_fd },
+            OutputDirFdAnnotation { fd: context.output_dir_fd },
+            OutputDirFdAnnotation { fd: context.staging_dir_fd },
         ],
         ..Default::default()
     };
@@ -87,21 +113,21 @@
     let mountpoint = PathBuf::from(authfs.getMountPoint()?);
 
     let mut android_root = mountpoint.clone();
-    android_root.push(system_dir_fd.to_string());
+    android_root.push(context.system_dir_fd.to_string());
     android_root.push("system");
     env::set_var("ANDROID_ROOT", &android_root);
+    debug!("ANDROID_ROOT={:?}", &android_root);
 
-    let mut art_apex_data = mountpoint.clone();
-    art_apex_data.push(output_dir_fd.to_string());
+    let art_apex_data = mountpoint.join(context.output_dir_fd.to_string());
     env::set_var("ART_APEX_DATA", &art_apex_data);
+    debug!("ART_APEX_DATA={:?}", &art_apex_data);
 
-    let mut staging_dir = mountpoint;
-    staging_dir.push(staging_dir_fd.to_string());
+    let staging_dir = mountpoint.join(context.staging_dir_fd.to_string());
 
     let args = vec![
         "odrefresh".to_string(),
-        format!("--zygote-arch={}", zygote_arch),
-        format!("--dalvik-cache={}", target_dir_name),
+        format!("--zygote-arch={}", context.zygote_arch),
+        format!("--dalvik-cache={}", context.target_dir_name),
         "--no-refresh".to_string(),
         format!("--staging-dir={}", staging_dir.display()),
         "--force-compile".to_string(),
@@ -109,17 +135,47 @@
     debug!("Running odrefresh with args: {:?}", &args);
     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(0i8),
-        Err(minijail::Error::ReturnCode(exit_code)) => {
-            info!("odrefresh exited with exit code {}", exit_code);
-            Ok(exit_code as i8)
-        }
+    let exit_code = match jail.wait() {
+        Ok(_) => Result::<u8>::Ok(0),
+        Err(minijail::Error::ReturnCode(exit_code)) => Ok(exit_code),
         Err(e) => {
             bail!("Unexpected minijail error: {}", e)
         }
+    }?;
+
+    let exit_code = ExitCode::from_i32(exit_code.into())
+        .ok_or_else(|| anyhow!("Unexpected odrefresh exit code: {}", exit_code))?;
+    info!("odrefresh exited with {:?}", exit_code);
+
+    if exit_code == ExitCode::CompilationSuccess {
+        // authfs only shows us the files we created, so it's ok to just sign everything under
+        // the target directory.
+        let target_dir = art_apex_data.join(context.target_dir_name);
+        let mut artifact_signer = ArtifactSigner::new(&target_dir);
+        add_artifacts(&target_dir, &mut artifact_signer)?;
+
+        artifact_signer.write_info_and_signature(signer, &target_dir.join("compos.info"))?;
     }
+
+    Ok(exit_code)
+}
+
+fn add_artifacts(target_dir: &Path, artifact_signer: &mut ArtifactSigner) -> Result<()> {
+    for entry in
+        read_dir(&target_dir).with_context(|| format!("Traversing {}", target_dir.display()))?
+    {
+        let entry = entry?;
+        let file_type = entry.file_type()?;
+        if file_type.is_dir() {
+            add_artifacts(&entry.path(), artifact_signer)?;
+        } else if file_type.is_file() {
+            artifact_signer.add_artifact(&entry.path())?;
+        } else {
+            // authfs shouldn't create anything else, but just in case
+            bail!("Unexpected file type in artifacts: {:?}", entry);
+        }
+    }
+    Ok(())
 }
 
 /// Runs the compiler with given flags with file descriptors described in `fd_annotation` retrieved