Add test APIs for Microdroid GKI

As we'll run MicrodroidTests on all supported GKIs, this adds test APIs
to get a list of available OSes and to run a specific microdroid GKI.
MicrodroidTests will use the API to retrieve a list of available GKIs
and run tests with such GKIs.

Bug: 302465542
Test: atest MicrodroidTests
Change-Id: I35bb602975776396445f96154e7be3891580e91d
diff --git a/virtualizationmanager/Android.bp b/virtualizationmanager/Android.bp
index 60c94fc..f58e999 100644
--- a/virtualizationmanager/Android.bp
+++ b/virtualizationmanager/Android.bp
@@ -38,6 +38,7 @@
         "libclap",
         "libcommand_fds",
         "libdisk",
+        "libglob",
         "libhex",
         "libhypervisor_props",
         "liblazy_static",
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 12b8f88..2603e77 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -69,12 +69,12 @@
     IntoBinderResult,
 };
 use disk::QcowFile;
+use glob::glob;
 use lazy_static::lazy_static;
 use libfdt::Fdt;
 use log::{debug, error, info, warn};
 use microdroid_payload_config::{OsConfig, Task, TaskType, VmPayloadConfig};
 use nix::unistd::pipe;
-use regex::Regex;
 use rpcbinder::RpcServer;
 use rustutils::system_properties;
 use semver::VersionReq;
@@ -83,6 +83,7 @@
 use std::ffi::{CStr, CString};
 use std::fs::{canonicalize, read_dir, remove_file, File, OpenOptions};
 use std::io::{BufRead, BufReader, Error, ErrorKind, Write};
+use std::iter;
 use std::num::{NonZeroU16, NonZeroU32};
 use std::os::unix::io::{FromRawFd, IntoRawFd};
 use std::os::unix::raw::pid_t;
@@ -126,8 +127,8 @@
     pub static ref GLOBAL_SERVICE: Strong<dyn IVirtualizationServiceInternal> =
         wait_for_interface(BINDER_SERVICE_IDENTIFIER)
             .expect("Could not connect to VirtualizationServiceInternal");
-    static ref MICRODROID_GKI_OS_NAME_PATTERN: Regex =
-        Regex::new(r"^microdroid_gki-android\d+-\d+\.\d+$").expect("Failed to construct Regex");
+    static ref SUPPORTED_OS_NAMES: HashSet<String> =
+        get_supported_os_names().expect("Failed to get list of supported os names");
 }
 
 fn create_or_update_idsig_file(
@@ -289,6 +290,11 @@
         GLOBAL_SERVICE.getAssignableDevices()
     }
 
+    /// Get a list of supported OSes.
+    fn getSupportedOSList(&self) -> binder::Result<Vec<String>> {
+        Ok(Vec::from_iter(SUPPORTED_OS_NAMES.iter().cloned()))
+    }
+
     /// Returns whether given feature is enabled
     fn isFeatureEnabled(&self, feature: &str) -> binder::Result<bool> {
         check_manage_access()?;
@@ -728,14 +734,32 @@
     }
 }
 
-fn is_valid_os(os_name: &str) -> bool {
-    if os_name == MICRODROID_OS_NAME {
-        true
-    } else if cfg!(vendor_modules) && MICRODROID_GKI_OS_NAME_PATTERN.is_match(os_name) {
-        PathBuf::from(format!("/apex/com.android.virt/etc/{}.json", os_name)).exists()
-    } else {
-        false
+fn extract_os_name_from_config_path(config: &Path) -> Option<String> {
+    if config.extension()?.to_str()? != "json" {
+        return None;
     }
+
+    Some(config.with_extension("").file_name()?.to_str()?.to_owned())
+}
+
+fn extract_os_names_from_configs(config_glob_pattern: &str) -> Result<HashSet<String>> {
+    let configs = glob(config_glob_pattern)?.collect::<Result<Vec<_>, _>>()?;
+    let os_names =
+        configs.iter().filter_map(|x| extract_os_name_from_config_path(x)).collect::<HashSet<_>>();
+
+    Ok(os_names)
+}
+
+fn get_supported_os_names() -> Result<HashSet<String>> {
+    if !cfg!(vendor_modules) {
+        return Ok(iter::once(MICRODROID_OS_NAME.to_owned()).collect());
+    }
+
+    extract_os_names_from_configs("/apex/com.android.virt/etc/microdroid*.json")
+}
+
+fn is_valid_os(os_name: &str) -> bool {
+    SUPPORTED_OS_NAMES.contains(os_name)
 }
 
 fn load_app_config(
@@ -1593,6 +1617,72 @@
         tmp_dir.close()?;
         Ok(())
     }
+
+    fn test_extract_os_name_from_config_path(
+        path: &Path,
+        expected_result: Option<&str>,
+    ) -> Result<()> {
+        let result = extract_os_name_from_config_path(path);
+        if result.as_deref() != expected_result {
+            bail!("Expected {:?} but was {:?}", expected_result, &result)
+        }
+        Ok(())
+    }
+
+    #[test]
+    fn test_extract_os_name_from_microdroid_config() -> Result<()> {
+        test_extract_os_name_from_config_path(
+            Path::new("/apex/com.android.virt/etc/microdroid.json"),
+            Some("microdroid"),
+        )
+    }
+
+    #[test]
+    fn test_extract_os_name_from_microdroid_gki_config() -> Result<()> {
+        test_extract_os_name_from_config_path(
+            Path::new("/apex/com.android.virt/etc/microdroid_gki-android14-6.1.json"),
+            Some("microdroid_gki-android14-6.1"),
+        )
+    }
+
+    #[test]
+    fn test_extract_os_name_from_invalid_path() -> Result<()> {
+        test_extract_os_name_from_config_path(
+            Path::new("/apex/com.android.virt/etc/microdroid.img"),
+            None,
+        )
+    }
+
+    #[test]
+    fn test_extract_os_name_from_configs() -> Result<()> {
+        let tmp_dir = tempfile::TempDir::new()?;
+        let tmp_dir_path = tmp_dir.path().to_owned();
+
+        let mut os_names: HashSet<String> = HashSet::new();
+        os_names.insert("microdroid".to_owned());
+        os_names.insert("microdroid_gki-android14-6.1".to_owned());
+        os_names.insert("microdroid_gki-android15-6.1".to_owned());
+
+        // config files
+        for os_name in &os_names {
+            std::fs::write(tmp_dir_path.join(os_name.to_owned() + ".json"), b"")?;
+        }
+
+        // fake files not related to configs
+        std::fs::write(tmp_dir_path.join("microdroid_super.img"), b"")?;
+        std::fs::write(tmp_dir_path.join("microdroid_foobar.apk"), b"")?;
+
+        let glob_pattern = match tmp_dir_path.join("microdroid*.json").to_str() {
+            Some(s) => s.to_owned(),
+            None => bail!("tmp_dir_path {:?} is not UTF-8", tmp_dir_path),
+        };
+
+        let result = extract_os_names_from_configs(&glob_pattern)?;
+        if result != os_names {
+            bail!("Expected {:?} but was {:?}", os_names, result);
+        }
+        Ok(())
+    }
 }
 
 struct SecretkeeperProxy(Strong<dyn ISecretkeeper>);