Call odrefresh in VM from composd

composd needs to prepare the directory, run a fd_server, etc. then
request to run odrefresh in the VM.

`FdServerConfig` and `FdServer` are introduced to make starting a
fd_server from composd easier.

Also, add a testing command in composd_cmd.

Bug: 205750213
Test: atest ComposHostTestCases
Test: With some local hacks in ART, with SELinux disabled in the VM,
      odrefresh completed with exit code 80 and output files that
      look normal (at least sizes are).
Change-Id: I52c9d1ad369eea6d423831adb42087a3bcf30d66
diff --git a/compos/composd/src/service.rs b/compos/composd/src/service.rs
index d9963d1..3738e18 100644
--- a/compos/composd/src/service.rs
+++ b/compos/composd/src/service.rs
@@ -18,7 +18,9 @@
 //! desired.
 
 use crate::compilation_task::CompilationTask;
+use crate::fd_server_helper::FdServerConfig;
 use crate::instance_manager::InstanceManager;
+use crate::instance_starter::CompOsInstance;
 use crate::util::to_binder_result;
 use android_system_composd::aidl::android::system::composd::{
     ICompilationTask::{BnCompilationTask, ICompilationTask},
@@ -29,7 +31,12 @@
     self, BinderFeatures, ExceptionCode, Interface, Status, Strong, ThreadState,
 };
 use anyhow::{Context, Result};
-use rustutils::users::{AID_ROOT, AID_SYSTEM};
+use compos_common::COMPOS_DATA_ROOT;
+use rustutils::{system_properties, users::AID_ROOT, users::AID_SYSTEM};
+use std::fs::{create_dir, File, OpenOptions};
+use std::os::unix::fs::OpenOptionsExt;
+use std::os::unix::io::AsRawFd;
+use std::path::{Path, PathBuf};
 use std::sync::Arc;
 
 pub struct IsolatedCompilationService {
@@ -50,13 +57,14 @@
         &self,
         callback: &Strong<dyn ICompilationTaskCallback>,
     ) -> binder::Result<Strong<dyn ICompilationTask>> {
-        let calling_uid = ThreadState::get_calling_uid();
-        // This should only be called by system server, or root while testing
-        if calling_uid != AID_SYSTEM && calling_uid != AID_ROOT {
-            return Err(Status::new_exception(ExceptionCode::SECURITY, None));
-        }
+        check_test_permissions()?;
         to_binder_result(self.do_start_test_compile(callback))
     }
+
+    fn startTestOdrefresh(&self) -> binder::Result<i8> {
+        check_test_permissions()?;
+        to_binder_result(self.do_odrefresh_for_test())
+    }
 }
 
 impl IsolatedCompilationService {
@@ -70,4 +78,62 @@
 
         Ok(BnCompilationTask::new_binder(task, BinderFeatures::default()))
     }
+
+    fn do_odrefresh_for_test(&self) -> Result<i8> {
+        let mut staging_dir_path = PathBuf::from(COMPOS_DATA_ROOT);
+        staging_dir_path.push("test-artifacts");
+        to_binder_result(create_dir(&staging_dir_path))?;
+
+        let compos = self
+            .instance_manager
+            .start_test_instance()
+            .context("Starting CompOS for odrefresh test")?;
+        self.do_odrefresh(compos, &staging_dir_path)
+    }
+
+    fn do_odrefresh(&self, compos: Arc<CompOsInstance>, staging_dir_path: &Path) -> Result<i8> {
+        let output_dir = open_dir_path(staging_dir_path)?;
+        let system_dir = open_dir_path(Path::new("/system"))?;
+
+        // Spawn a fd_server to serve the FDs.
+        let fd_server_config = FdServerConfig {
+            ro_dir_fds: vec![system_dir.as_raw_fd()],
+            rw_dir_fds: vec![output_dir.as_raw_fd()],
+            ..Default::default()
+        };
+        let fd_server_raii = fd_server_config.into_fd_server()?;
+
+        let zygote_arch = system_properties::read("ro.zygote")?;
+        let result = compos.get_service().odrefresh(
+            system_dir.as_raw_fd(),
+            output_dir.as_raw_fd(),
+            &zygote_arch,
+        );
+        drop(fd_server_raii);
+        Ok(result?.exitCode)
+    }
+}
+
+fn check_test_permissions() -> binder::Result<()> {
+    let calling_uid = ThreadState::get_calling_uid();
+    // This should only be called by system server, or root while testing
+    if calling_uid != AID_SYSTEM && calling_uid != AID_ROOT {
+        Err(Status::new_exception(ExceptionCode::SECURITY, None))
+    } else {
+        Ok(())
+    }
+}
+
+/// Returns an owned FD of the directory path. It currently returns a `File` as a FD owner, but
+/// it's better to use `std::os::unix::io::OwnedFd` once/if it becomes standard.
+fn open_dir_path(path: &Path) -> Result<File> {
+    OpenOptions::new()
+        .custom_flags(libc::O_PATH | libc::O_DIRECTORY)
+        // The custom flags above is not taken into consideration by the unix implementation of
+        // OpenOptions for flag validation. So even though the man page of open(2) says that
+        // most flags include access mode are ignored, we still need to set a "valid" mode to
+        // make the library happy. The value does not appear to matter elsewhere in the library.
+        .read(true)
+        .open(path)
+        .with_context(|| format!("Failed to open {} directory as path fd", path.display()))
 }