Initial work to make VM config file optional

Modify VirtualMachineAppConfig to allow configPath to be omitted;
instead a subset of the available config options can be specified
directly.

Modify VS to map the config options to the Metadata proto file.

At this stage nothing ever sets the payload config directly, and
Microdroid would reject it if it received it, but this at least
establishes the plumbing.

Bug: 243513572
Test: atest MicrodroidTests
Change-Id: I596384abf6e3bbbccd8934ba28fa3a85faa51b56
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index b35f8da..22418b9 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -29,9 +29,10 @@
     IVirtualizationService::IVirtualizationService,
     Partition::Partition,
     PartitionType::PartitionType,
-    VirtualMachineAppConfig::VirtualMachineAppConfig,
+    VirtualMachineAppConfig::{VirtualMachineAppConfig, Payload::Payload},
     VirtualMachineConfig::VirtualMachineConfig,
     VirtualMachineDebugInfo::VirtualMachineDebugInfo,
+    VirtualMachinePayloadConfig::VirtualMachinePayloadConfig,
     VirtualMachineRawConfig::VirtualMachineRawConfig,
     VirtualMachineState::VirtualMachineState,
 };
@@ -49,7 +50,7 @@
 use disk::QcowFile;
 use apkverify::{HashAlgorithm, V4Signature};
 use log::{debug, error, info, warn};
-use microdroid_payload_config::VmPayloadConfig;
+use microdroid_payload_config::{VmPayloadConfig, OsConfig, Task, TaskType};
 use rustutils::system_properties;
 use semver::VersionReq;
 use std::convert::TryInto;
@@ -85,6 +86,8 @@
 
 const CHUNK_RECV_MAX_LEN: usize = 1024;
 
+const MICRODROID_OS_NAME: &str = "microdroid";
+
 /// Implementation of `IVirtualizationService`, the entry point of the AIDL service.
 #[derive(Debug, Default)]
 pub struct VirtualizationService {
@@ -376,15 +379,10 @@
         let config = match config {
             VirtualMachineConfig::AppConfig(config) => BorrowedOrOwned::Owned(
                 load_app_config(config, &temporary_directory).map_err(|e| {
-                    error!("Failed to load app config from {}: {:?}", &config.configPath, e);
                     *is_protected = config.protectedVm;
-                    Status::new_service_specific_error_str(
-                        -1,
-                        Some(format!(
-                            "Failed to load app config from {}: {:?}",
-                            &config.configPath, e
-                        )),
-                    )
+                    let message = format!("Failed to load app config: {:?}", e);
+                    error!("{}", message);
+                    Status::new_service_specific_error_str(-1, Some(message))
                 })?,
             ),
             VirtualMachineConfig::RawConfig(config) => BorrowedOrOwned::Borrowed(config),
@@ -604,16 +602,18 @@
     let apk_file = clone_file(config.apk.as_ref().unwrap())?;
     let idsig_file = clone_file(config.idsig.as_ref().unwrap())?;
     let instance_file = clone_file(config.instanceImage.as_ref().unwrap())?;
-    let config_path = &config.configPath;
 
-    let mut apk_zip = ZipArchive::new(&apk_file)?;
-    let config_file = apk_zip.by_name(config_path)?;
-    let vm_payload_config: VmPayloadConfig = serde_json::from_reader(config_file)?;
+    let vm_payload_config = match &config.payload {
+        Payload::ConfigPath(config_path) => {
+            load_vm_payload_config_from_file(&apk_file, config_path.as_str())
+                .with_context(|| format!("Couldn't read config from {}", config_path))?
+        }
+        Payload::PayloadConfig(payload_config) => create_vm_payload_config(payload_config),
+    };
 
-    let os_name = &vm_payload_config.os.name;
-
-    // For now, the only supported "os" value is "microdroid"
-    if os_name != "microdroid" {
+    // For now, the only supported OS is Microdroid
+    let os_name = vm_payload_config.os.name.as_str();
+    if os_name != MICRODROID_OS_NAME {
         bail!("Unknown OS \"{}\"", os_name);
     }
 
@@ -633,21 +633,45 @@
     vm_config.taskProfiles = config.taskProfiles.clone();
 
     // Microdroid requires an additional init ramdisk & payload disk image
-    if os_name == "microdroid" {
-        add_microdroid_images(
-            config,
-            temporary_directory,
-            apk_file,
-            idsig_file,
-            instance_file,
-            &vm_payload_config,
-            &mut vm_config,
-        )?;
-    }
+    add_microdroid_images(
+        config,
+        temporary_directory,
+        apk_file,
+        idsig_file,
+        instance_file,
+        &vm_payload_config,
+        &mut vm_config,
+    )?;
 
     Ok(vm_config)
 }
 
+fn load_vm_payload_config_from_file(apk_file: &File, config_path: &str) -> Result<VmPayloadConfig> {
+    let mut apk_zip = ZipArchive::new(apk_file)?;
+    let config_file = apk_zip.by_name(config_path)?;
+    Ok(serde_json::from_reader(config_file)?)
+}
+
+fn create_vm_payload_config(payload_config: &VirtualMachinePayloadConfig) -> VmPayloadConfig {
+    // There isn't an actual config file. Construct a synthetic VmPayloadConfig from the explicit
+    // parameters we've been given. Microdroid will do something equivalent inside the VM using the
+    // payload config that we send it via the metadata file.
+    let task = Task {
+        type_: TaskType::MicrodroidLauncher,
+        command: payload_config.payloadPath.clone(),
+        args: payload_config.args.clone(),
+    };
+    VmPayloadConfig {
+        os: OsConfig { name: MICRODROID_OS_NAME.to_owned() },
+        task: Some(task),
+        apexes: vec![],
+        extra_apks: vec![],
+        prefer_staged: false,
+        export_tombstones: payload_config.exportTombstones,
+        enable_authfs: false,
+    }
+}
+
 /// Generates a unique filename to use for a composite disk image.
 fn make_composite_image_filenames(
     temporary_directory: &Path,
diff --git a/virtualizationservice/src/atom.rs b/virtualizationservice/src/atom.rs
index 3b29d19..eabb4cc 100644
--- a/virtualizationservice/src/atom.rs
+++ b/virtualizationservice/src/atom.rs
@@ -16,23 +16,47 @@
 
 use crate::aidl::clone_file;
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
-    DeathReason::DeathReason, IVirtualMachine::IVirtualMachine,
-    VirtualMachineAppConfig::VirtualMachineAppConfig, VirtualMachineConfig::VirtualMachineConfig,
+    DeathReason::DeathReason,
+    IVirtualMachine::IVirtualMachine,
+    VirtualMachineAppConfig::{Payload::Payload, VirtualMachineAppConfig},
+    VirtualMachineConfig::VirtualMachineConfig,
 };
 use android_system_virtualizationservice::binder::{Status, Strong};
 use anyhow::{anyhow, Result};
-use binder::ThreadState;
+use binder::{ParcelFileDescriptor, ThreadState};
 use log::{trace, warn};
 use microdroid_payload_config::VmPayloadConfig;
 use statslog_virtualization_rust::{vm_booted, vm_creation_requested, vm_exited};
 use std::time::{Duration, SystemTime};
 use zip::ZipArchive;
 
-fn get_vm_payload_config(config: &VirtualMachineAppConfig) -> Result<VmPayloadConfig> {
-    let apk = config.apk.as_ref().ok_or_else(|| anyhow!("APK is none"))?;
+fn get_apex_list(config: &VirtualMachineAppConfig) -> String {
+    match &config.payload {
+        Payload::PayloadConfig(_) => String::new(),
+        Payload::ConfigPath(config_path) => {
+            let vm_payload_config = get_vm_payload_config(&config.apk, config_path);
+            if let Ok(vm_payload_config) = vm_payload_config {
+                vm_payload_config
+                    .apexes
+                    .iter()
+                    .map(|x| x.name.clone())
+                    .collect::<Vec<String>>()
+                    .join(":")
+            } else {
+                "INFO: Can't get VmPayloadConfig".to_owned()
+            }
+        }
+    }
+}
+
+fn get_vm_payload_config(
+    apk_fd: &Option<ParcelFileDescriptor>,
+    config_path: &str,
+) -> Result<VmPayloadConfig> {
+    let apk = apk_fd.as_ref().ok_or_else(|| anyhow!("APK is none"))?;
     let apk_file = clone_file(apk)?;
     let mut apk_zip = ZipArchive::new(&apk_file)?;
-    let config_file = apk_zip.by_name(&config.configPath)?;
+    let config_file = apk_zip.by_name(config_path)?;
     let vm_payload_config: VmPayloadConfig = serde_json::from_reader(config_file)?;
     Ok(vm_payload_config)
 }
@@ -63,38 +87,22 @@
         }
     }
 
-    let vm_identifier;
-    let config_type;
-    let num_cpus;
-    let memory_mib;
-    let apexes;
-    match config {
-        VirtualMachineConfig::AppConfig(config) => {
-            vm_identifier = &config.name;
-            config_type = vm_creation_requested::ConfigType::VirtualMachineAppConfig;
-            num_cpus = config.numCpus;
-            memory_mib = config.memoryMib;
-
-            let vm_payload_config = get_vm_payload_config(config);
-            if let Ok(vm_payload_config) = vm_payload_config {
-                apexes = vm_payload_config
-                    .apexes
-                    .iter()
-                    .map(|x| x.name.clone())
-                    .collect::<Vec<String>>()
-                    .join(":");
-            } else {
-                apexes = "INFO: Can't get VmPayloadConfig".into();
-            }
-        }
-        VirtualMachineConfig::RawConfig(config) => {
-            vm_identifier = &config.name;
-            config_type = vm_creation_requested::ConfigType::VirtualMachineRawConfig;
-            num_cpus = config.numCpus;
-            memory_mib = config.memoryMib;
-            apexes = String::new();
-        }
-    }
+    let (vm_identifier, config_type, num_cpus, memory_mib, apexes) = match config {
+        VirtualMachineConfig::AppConfig(config) => (
+            &config.name,
+            vm_creation_requested::ConfigType::VirtualMachineAppConfig,
+            config.numCpus,
+            config.memoryMib,
+            get_apex_list(config),
+        ),
+        VirtualMachineConfig::RawConfig(config) => (
+            &config.name,
+            vm_creation_requested::ConfigType::VirtualMachineRawConfig,
+            config.numCpus,
+            config.memoryMib,
+            String::new(),
+        ),
+    };
 
     let vm_creation_requested = vm_creation_requested::VmCreationRequested {
         uid: ThreadState::get_calling_uid() as i32,
diff --git a/virtualizationservice/src/payload.rs b/virtualizationservice/src/payload.rs
index 06b9716..3efd7ac 100644
--- a/virtualizationservice/src/payload.rs
+++ b/virtualizationservice/src/payload.rs
@@ -15,14 +15,16 @@
 //! Payload disk image
 
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
-    DiskImage::DiskImage, Partition::Partition, VirtualMachineAppConfig::DebugLevel::DebugLevel,
-    VirtualMachineAppConfig::VirtualMachineAppConfig,
+    DiskImage::DiskImage,
+    Partition::Partition,
+    VirtualMachineAppConfig::DebugLevel::DebugLevel,
+    VirtualMachineAppConfig::{Payload::Payload, VirtualMachineAppConfig},
     VirtualMachineRawConfig::VirtualMachineRawConfig,
 };
 use anyhow::{anyhow, bail, Context, Result};
 use binder::{wait_for_interface, ParcelFileDescriptor};
 use log::{info, warn};
-use microdroid_metadata::{ApexPayload, ApkPayload, Metadata};
+use microdroid_metadata::{ApexPayload, ApkPayload, Metadata, PayloadConfig, PayloadMetadata};
 use microdroid_payload_config::{ApexConfig, VmPayloadConfig};
 use once_cell::sync::OnceCell;
 use packagemanager_aidl::aidl::android::content::pm::IPackageManagerNative::IPackageManagerNative;
@@ -156,11 +158,22 @@
 }
 
 fn make_metadata_file(
-    config_path: &str,
+    app_config: &VirtualMachineAppConfig,
     apex_infos: &[&ApexInfo],
     temporary_directory: &Path,
 ) -> Result<ParcelFileDescriptor> {
-    let metadata_path = temporary_directory.join("metadata");
+    let payload_metadata = match &app_config.payload {
+        Payload::PayloadConfig(payload_config) => PayloadMetadata::config(PayloadConfig {
+            payload_binary_path: payload_config.payloadPath.clone(),
+            export_tombstones: payload_config.exportTombstones,
+            args: payload_config.args.clone().into(),
+            ..Default::default()
+        }),
+        Payload::ConfigPath(config_path) => {
+            PayloadMetadata::config_path(format!("/mnt/apk/{}", config_path))
+        }
+    };
+
     let metadata = Metadata {
         version: 1,
         apexes: apex_infos
@@ -183,11 +196,12 @@
             ..Default::default()
         })
         .into(),
-        payload_config_path: format!("/mnt/apk/{}", config_path),
+        payload: Some(payload_metadata),
         ..Default::default()
     };
 
     // Write metadata to file.
+    let metadata_path = temporary_directory.join("metadata");
     let mut metadata_file = OpenOptions::new()
         .create_new(true)
         .read(true)
@@ -235,8 +249,7 @@
         collect_apex_infos(&apex_list, &vm_payload_config.apexes, app_config.debugLevel);
     info!("Microdroid payload APEXes: {:?}", apex_infos.iter().map(|ai| &ai.name));
 
-    let metadata_file =
-        make_metadata_file(&app_config.configPath, &apex_infos, temporary_directory)?;
+    let metadata_file = make_metadata_file(app_config, &apex_infos, temporary_directory)?;
     // put metadata at the first partition
     let mut partitions = vec![Partition {
         label: "payload-metadata".to_owned(),