Add a parameter for extra apks to payload config

Users can pass extra apks and corresponding idsigs to the VM, by setting
extra_apks property to the payload config.

Bug: 205224817
Test: add extra_apks, vm run-app, see /dev/block/by-name/extra-apk-0 and
/dev/block/by-name/extra-idsig-0

Change-Id: I7908788a163d6ff7b90bb008fc326eb23d1611bb
diff --git a/vm/Android.bp b/vm/Android.bp
index 734f2d3..2d22562 100644
--- a/vm/Android.bp
+++ b/vm/Android.bp
@@ -7,16 +7,19 @@
     crate_name: "vm",
     srcs: ["src/main.rs"],
     edition: "2018",
+    prefer_rlib: true,
     rustlibs: [
         "android.system.virtualizationservice-rust",
         "libanyhow",
         "libenv_logger",
         "liblibc",
         "liblog_rust",
+        "libmicrodroid_payload_config",
         "libserde_json",
         "libserde",
         "libstructopt",
         "libvmconfig",
+        "libzip",
     ],
     apex_available: [
         "com.android.virt",
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 87bcda7..d53305b 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -33,6 +33,9 @@
 const VIRTUALIZATION_SERVICE_BINDER_SERVICE_IDENTIFIER: &str =
     "android.system.virtualizationservice";
 
+#[derive(Debug)]
+struct Idsigs(Vec<PathBuf>);
+
 #[derive(StructOpt)]
 #[structopt(no_version, global_settings = &[AppSettings::DisableVersion])]
 enum Opt {
@@ -73,6 +76,10 @@
         /// in the VM config file.
         #[structopt(short, long)]
         mem: Option<u32>,
+
+        /// Paths to extra idsig files.
+        #[structopt(long)]
+        extra_idsigs: Vec<PathBuf>,
     },
     /// Run a virtual machine
     Run {
@@ -138,20 +145,30 @@
         .context("Failed to find VirtualizationService")?;
 
     match opt {
-        Opt::RunApp { apk, idsig, instance, config_path, daemonize, console, log, debug, mem } => {
-            command_run_app(
-                service,
-                &apk,
-                &idsig,
-                &instance,
-                &config_path,
-                daemonize,
-                console.as_deref(),
-                log.as_deref(),
-                debug,
-                mem,
-            )
-        }
+        Opt::RunApp {
+            apk,
+            idsig,
+            instance,
+            config_path,
+            daemonize,
+            console,
+            log,
+            debug,
+            mem,
+            extra_idsigs,
+        } => command_run_app(
+            service,
+            &apk,
+            &idsig,
+            &instance,
+            &config_path,
+            daemonize,
+            console.as_deref(),
+            log.as_deref(),
+            debug,
+            mem,
+            &extra_idsigs,
+        ),
         Opt::Run { config, daemonize, console } => {
             command_run(service, &config, daemonize, console.as_deref(), /* mem */ None)
         }
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 15775cb..1cd51a1 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -28,12 +28,14 @@
     BinderFeatures, DeathRecipient, IBinder, ParcelFileDescriptor, Strong,
 };
 use android_system_virtualizationservice::binder::{Interface, Result as BinderResult};
-use anyhow::{Context, Error};
+use anyhow::{bail, Context, Error};
+use microdroid_payload_config::VmPayloadConfig;
 use std::fs::File;
 use std::io::{self, BufRead, BufReader};
 use std::os::unix::io::{AsRawFd, FromRawFd};
-use std::path::Path;
+use std::path::{Path, PathBuf};
 use vmconfig::{open_parcel_file, VmConfig};
+use zip::ZipArchive;
 
 /// Run a VM from the given APK, idsig, and config.
 #[allow(clippy::too_many_arguments)]
@@ -48,7 +50,23 @@
     log_path: Option<&Path>,
     debug_level: DebugLevel,
     mem: Option<u32>,
+    extra_idsigs: &[PathBuf],
 ) -> Result<(), Error> {
+    let extra_apks = parse_extra_apk_list(apk, config_path)?;
+    if extra_apks.len() != extra_idsigs.len() {
+        bail!(
+            "Found {} extra apks, but there are {} extra idsigs",
+            extra_apks.len(),
+            extra_idsigs.len()
+        )
+    }
+
+    for i in 0..extra_apks.len() {
+        let extra_apk_fd = ParcelFileDescriptor::new(File::open(&extra_apks[i])?);
+        let extra_idsig_fd = ParcelFileDescriptor::new(File::create(&extra_idsigs[i])?);
+        service.createOrUpdateIdsigFile(&extra_apk_fd, &extra_idsig_fd)?;
+    }
+
     let apk_file = File::open(apk).context("Failed to open APK file")?;
     let idsig_file = File::create(idsig).context("Failed to create idsig file")?;
 
@@ -69,9 +87,13 @@
         )?;
     }
 
+    let extra_idsig_files: Result<Vec<File>, _> = extra_idsigs.iter().map(File::open).collect();
+    let extra_idsig_fds = extra_idsig_files?.into_iter().map(ParcelFileDescriptor::new).collect();
+
     let config = VirtualMachineConfig::AppConfig(VirtualMachineAppConfig {
         apk: apk_fd.into(),
         idsig: idsig_fd.into(),
+        extraIdsigs: extra_idsig_fds,
         instanceImage: open_parcel_file(instance, true /* writable */)?.into(),
         configPath: config_path.to_owned(),
         debugLevel: debug_level,
@@ -204,6 +226,13 @@
     Ok(death_recipient)
 }
 
+fn parse_extra_apk_list(apk: &Path, config_path: &str) -> Result<Vec<String>, Error> {
+    let mut archive = ZipArchive::new(File::open(apk)?)?;
+    let config_file = archive.by_name(config_path)?;
+    let config: VmPayloadConfig = serde_json::from_reader(config_file)?;
+    Ok(config.extra_apks.into_iter().map(|x| x.path).collect())
+}
+
 #[derive(Debug)]
 struct VirtualMachineCallback {
     dead: AtomicFlag,