1 // Copyright 2021, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Payload disk image
16 
17 use crate::debug_config::DebugConfig;
18 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
19     DiskImage::DiskImage,
20     Partition::Partition,
21     VirtualMachineAppConfig::DebugLevel::DebugLevel,
22     VirtualMachineAppConfig::{Payload::Payload, VirtualMachineAppConfig},
23     VirtualMachineRawConfig::VirtualMachineRawConfig,
24 };
25 use anyhow::{anyhow, bail, Context, Result};
26 use binder::{wait_for_interface, ParcelFileDescriptor};
27 use log::{info, warn};
28 use microdroid_metadata::{ApexPayload, ApkPayload, Metadata, PayloadConfig, PayloadMetadata};
29 use microdroid_payload_config::{ApexConfig, VmPayloadConfig};
30 use once_cell::sync::OnceCell;
31 use packagemanager_aidl::aidl::android::content::pm::{
32     IPackageManagerNative::IPackageManagerNative, StagedApexInfo::StagedApexInfo,
33 };
34 use regex::Regex;
35 use serde::Deserialize;
36 use serde_xml_rs::from_reader;
37 use std::collections::HashSet;
38 use std::fs::{metadata, File, OpenOptions};
39 use std::path::{Path, PathBuf};
40 use std::process::Command;
41 use std::time::SystemTime;
42 use vmconfig::open_parcel_file;
43 
44 const APEX_INFO_LIST_PATH: &str = "/apex/apex-info-list.xml";
45 
46 const PACKAGE_MANAGER_NATIVE_SERVICE: &str = "package_native";
47 
48 /// Represents the list of APEXes
49 #[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
50 struct ApexInfoList {
51     #[serde(rename = "apex-info")]
52     list: Vec<ApexInfo>,
53 }
54 
55 #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
56 struct ApexInfo {
57     #[serde(rename = "moduleName")]
58     name: String,
59     #[serde(rename = "versionCode")]
60     version: u64,
61     #[serde(rename = "modulePath")]
62     path: PathBuf,
63 
64     #[serde(default)]
65     has_classpath_jar: bool,
66 
67     // The field claims to be milliseconds but is actually seconds.
68     #[serde(rename = "lastUpdateMillis")]
69     last_update_seconds: u64,
70 
71     #[serde(rename = "isFactory")]
72     is_factory: bool,
73 
74     #[serde(rename = "isActive")]
75     is_active: bool,
76 
77     #[serde(rename = "provideSharedApexLibs")]
78     provide_shared_apex_libs: bool,
79 
80     #[serde(rename = "preinstalledModulePath")]
81     preinstalled_path: PathBuf,
82 }
83 
84 impl ApexInfoList {
85     /// Loads ApexInfoList
load() -> Result<&'static ApexInfoList>86     fn load() -> Result<&'static ApexInfoList> {
87         static INSTANCE: OnceCell<ApexInfoList> = OnceCell::new();
88         INSTANCE.get_or_try_init(|| {
89             let apex_info_list = File::open(APEX_INFO_LIST_PATH)
90                 .context(format!("Failed to open {}", APEX_INFO_LIST_PATH))?;
91             let mut apex_info_list: ApexInfoList = from_reader(apex_info_list)
92                 .context(format!("Failed to parse {}", APEX_INFO_LIST_PATH))?;
93 
94             // For active APEXes, we run derive_classpath and parse its output to see if it
95             // contributes to the classpath(s). (This allows us to handle any new classpath env
96             // vars seamlessly.)
97             let classpath_vars = run_derive_classpath()?;
98             let classpath_apexes = find_apex_names_in_classpath(&classpath_vars)?;
99 
100             for apex_info in apex_info_list.list.iter_mut() {
101                 apex_info.has_classpath_jar = classpath_apexes.contains(&apex_info.name);
102             }
103 
104             Ok(apex_info_list)
105         })
106     }
107 
108     // Override apex info with the staged one
override_staged_apex(&mut self, staged_apex_info: &StagedApexInfo) -> Result<()>109     fn override_staged_apex(&mut self, staged_apex_info: &StagedApexInfo) -> Result<()> {
110         let mut need_to_add: Option<ApexInfo> = None;
111         for apex_info in self.list.iter_mut() {
112             if staged_apex_info.moduleName == apex_info.name {
113                 if apex_info.is_active && apex_info.is_factory {
114                     // Copy the entry to the end as factory/non-active after the loop
115                     // to keep the factory version. Typically this step is unncessary,
116                     // but some apexes (like sharedlibs) need to be kept even if it's inactive.
117                     need_to_add.replace(ApexInfo { is_active: false, ..apex_info.clone() });
118                     // And make this one as non-factory. Note that this one is still active
119                     // and overridden right below.
120                     apex_info.is_factory = false;
121                 }
122                 // Active one is overridden with the staged one.
123                 if apex_info.is_active {
124                     apex_info.version = staged_apex_info.versionCode as u64;
125                     apex_info.path = PathBuf::from(&staged_apex_info.diskImagePath);
126                     apex_info.has_classpath_jar = staged_apex_info.hasClassPathJars;
127                     apex_info.last_update_seconds = last_updated(&apex_info.path)?;
128                 }
129             }
130         }
131         if let Some(info) = need_to_add {
132             self.list.push(info);
133         }
134         Ok(())
135     }
136 }
137 
last_updated<P: AsRef<Path>>(path: P) -> Result<u64>138 fn last_updated<P: AsRef<Path>>(path: P) -> Result<u64> {
139     let metadata = metadata(path)?;
140     Ok(metadata.modified()?.duration_since(SystemTime::UNIX_EPOCH)?.as_secs())
141 }
142 
143 impl ApexInfo {
matches(&self, apex_config: &ApexConfig) -> bool144     fn matches(&self, apex_config: &ApexConfig) -> bool {
145         // Match with pseudo name "{CLASSPATH}" which represents APEXes contributing
146         // to any derive_classpath environment variable
147         if apex_config.name == "{CLASSPATH}" && self.has_classpath_jar {
148             return true;
149         }
150         if apex_config.name == self.name {
151             return true;
152         }
153         false
154     }
155 }
156 
157 struct PackageManager {
158     apex_info_list: &'static ApexInfoList,
159 }
160 
161 impl PackageManager {
new() -> Result<Self>162     fn new() -> Result<Self> {
163         let apex_info_list = ApexInfoList::load()?;
164         Ok(Self { apex_info_list })
165     }
166 
get_apex_list(&self, prefer_staged: bool) -> Result<ApexInfoList>167     fn get_apex_list(&self, prefer_staged: bool) -> Result<ApexInfoList> {
168         // get the list of active apexes
169         let mut list = self.apex_info_list.clone();
170         // When prefer_staged, we override ApexInfo by consulting "package_native"
171         if prefer_staged {
172             let pm =
173                 wait_for_interface::<dyn IPackageManagerNative>(PACKAGE_MANAGER_NATIVE_SERVICE)
174                     .context("Failed to get service when prefer_staged is set.")?;
175             let staged =
176                 pm.getStagedApexModuleNames().context("getStagedApexModuleNames failed")?;
177             for name in staged {
178                 if let Some(staged_apex_info) =
179                     pm.getStagedApexInfo(&name).context("getStagedApexInfo failed")?
180                 {
181                     list.override_staged_apex(&staged_apex_info)?;
182                 }
183             }
184         }
185         Ok(list)
186     }
187 }
188 
make_metadata_file( app_config: &VirtualMachineAppConfig, apex_infos: &[&ApexInfo], temporary_directory: &Path, ) -> Result<ParcelFileDescriptor>189 fn make_metadata_file(
190     app_config: &VirtualMachineAppConfig,
191     apex_infos: &[&ApexInfo],
192     temporary_directory: &Path,
193 ) -> Result<ParcelFileDescriptor> {
194     let payload_metadata = match &app_config.payload {
195         Payload::PayloadConfig(payload_config) => PayloadMetadata::Config(PayloadConfig {
196             payload_binary_name: payload_config.payloadBinaryName.clone(),
197             extra_apk_count: payload_config.extraApks.len().try_into()?,
198             special_fields: Default::default(),
199         }),
200         Payload::ConfigPath(config_path) => {
201             PayloadMetadata::ConfigPath(format!("/mnt/apk/{}", config_path))
202         }
203     };
204 
205     let metadata = Metadata {
206         version: 1,
207         apexes: apex_infos
208             .iter()
209             .enumerate()
210             .map(|(i, apex_info)| {
211                 Ok(ApexPayload {
212                     name: apex_info.name.clone(),
213                     partition_name: format!("microdroid-apex-{}", i),
214                     last_update_seconds: apex_info.last_update_seconds,
215                     is_factory: apex_info.is_factory,
216                     ..Default::default()
217                 })
218             })
219             .collect::<Result<_>>()?,
220         apk: Some(ApkPayload {
221             name: "apk".to_owned(),
222             payload_partition_name: "microdroid-apk".to_owned(),
223             idsig_partition_name: "microdroid-apk-idsig".to_owned(),
224             ..Default::default()
225         })
226         .into(),
227         payload: Some(payload_metadata),
228         ..Default::default()
229     };
230 
231     // Write metadata to file.
232     let metadata_path = temporary_directory.join("metadata");
233     let mut metadata_file = OpenOptions::new()
234         .create_new(true)
235         .read(true)
236         .write(true)
237         .open(&metadata_path)
238         .with_context(|| format!("Failed to open metadata file {:?}", metadata_path))?;
239     microdroid_metadata::write_metadata(&metadata, &mut metadata_file)?;
240 
241     // Re-open the metadata file as read-only.
242     open_parcel_file(&metadata_path, false)
243 }
244 
245 /// Creates a DiskImage with partitions:
246 ///   payload-metadata: metadata
247 ///   microdroid-apex-0: apex 0
248 ///   microdroid-apex-1: apex 1
249 ///   ..
250 ///   microdroid-apk: apk
251 ///   microdroid-apk-idsig: idsig
252 ///   extra-apk-0:   additional apk 0
253 ///   extra-idsig-0: additional idsig 0
254 ///   extra-apk-1:   additional apk 1
255 ///   extra-idsig-1: additional idsig 1
256 ///   ..
make_payload_disk( app_config: &VirtualMachineAppConfig, debug_config: &DebugConfig, apk_file: File, idsig_file: File, extra_apk_files: Vec<File>, vm_payload_config: &VmPayloadConfig, temporary_directory: &Path, ) -> Result<DiskImage>257 fn make_payload_disk(
258     app_config: &VirtualMachineAppConfig,
259     debug_config: &DebugConfig,
260     apk_file: File,
261     idsig_file: File,
262     extra_apk_files: Vec<File>,
263     vm_payload_config: &VmPayloadConfig,
264     temporary_directory: &Path,
265 ) -> Result<DiskImage> {
266     if extra_apk_files.len() != app_config.extraIdsigs.len() {
267         bail!(
268             "payload config has {} apks, but app config has {} idsigs",
269             vm_payload_config.extra_apks.len(),
270             app_config.extraIdsigs.len()
271         );
272     }
273 
274     let pm = PackageManager::new()?;
275     let apex_list = pm.get_apex_list(vm_payload_config.prefer_staged)?;
276 
277     // collect APEXes from config
278     let mut apex_infos = collect_apex_infos(&apex_list, &vm_payload_config.apexes, debug_config)?;
279 
280     // Pass sorted list of apexes. Sorting key shouldn't use `path` because it will change after
281     // reboot with prefer_staged. `last_update_seconds` is added to distinguish "samegrade"
282     // update.
283     apex_infos.sort_by_key(|info| (&info.name, &info.version, &info.last_update_seconds));
284     info!("Microdroid payload APEXes: {:?}", apex_infos.iter().map(|ai| &ai.name));
285 
286     let metadata_file = make_metadata_file(app_config, &apex_infos, temporary_directory)?;
287     // put metadata at the first partition
288     let mut partitions = vec![Partition {
289         label: "payload-metadata".to_owned(),
290         image: Some(metadata_file),
291         writable: false,
292     }];
293 
294     for (i, apex_info) in apex_infos.iter().enumerate() {
295         let apex_file = open_parcel_file(&apex_info.path, false)?;
296         partitions.push(Partition {
297             label: format!("microdroid-apex-{}", i),
298             image: Some(apex_file),
299             writable: false,
300         });
301     }
302     partitions.push(Partition {
303         label: "microdroid-apk".to_owned(),
304         image: Some(ParcelFileDescriptor::new(apk_file)),
305         writable: false,
306     });
307     partitions.push(Partition {
308         label: "microdroid-apk-idsig".to_owned(),
309         image: Some(ParcelFileDescriptor::new(idsig_file)),
310         writable: false,
311     });
312 
313     // we've already checked that extra_apks and extraIdsigs are in the same size.
314     let extra_idsigs = &app_config.extraIdsigs;
315     for (i, (extra_apk_file, extra_idsig)) in
316         extra_apk_files.into_iter().zip(extra_idsigs.iter()).enumerate()
317     {
318         partitions.push(Partition {
319             label: format!("extra-apk-{i}"),
320             image: Some(ParcelFileDescriptor::new(extra_apk_file)),
321             writable: false,
322         });
323 
324         partitions.push(Partition {
325             label: format!("extra-idsig-{i}"),
326             image: Some(ParcelFileDescriptor::new(
327                 extra_idsig
328                     .as_ref()
329                     .try_clone()
330                     .with_context(|| format!("Failed to clone the extra idsig #{i}"))?,
331             )),
332             writable: false,
333         });
334     }
335 
336     Ok(DiskImage { image: None, partitions, writable: false })
337 }
338 
run_derive_classpath() -> Result<String>339 fn run_derive_classpath() -> Result<String> {
340     let result = Command::new("/apex/com.android.sdkext/bin/derive_classpath")
341         .arg("/proc/self/fd/1")
342         .output()
343         .context("Failed to run derive_classpath")?;
344 
345     if !result.status.success() {
346         bail!("derive_classpath returned {}", result.status);
347     }
348 
349     String::from_utf8(result.stdout).context("Converting derive_classpath output")
350 }
351 
find_apex_names_in_classpath(classpath_vars: &str) -> Result<HashSet<String>>352 fn find_apex_names_in_classpath(classpath_vars: &str) -> Result<HashSet<String>> {
353     // Each line should be in the format "export <var name> <paths>", where <paths> is a
354     // colon-separated list of paths to JARs. We don't care about the var names, and we're only
355     // interested in paths that look like "/apex/<apex name>/<anything>" so we know which APEXes
356     // contribute to at least one var.
357     let mut apexes = HashSet::new();
358 
359     let pattern = Regex::new(r"^export [^ ]+ ([^ ]+)$").context("Failed to construct Regex")?;
360     for line in classpath_vars.lines() {
361         if let Some(captures) = pattern.captures(line) {
362             if let Some(paths) = captures.get(1) {
363                 apexes.extend(paths.as_str().split(':').filter_map(|path| {
364                     let path = path.strip_prefix("/apex/")?;
365                     Some(path[..path.find('/')?].to_owned())
366                 }));
367                 continue;
368             }
369         }
370         warn!("Malformed line from derive_classpath: {}", line);
371     }
372 
373     Ok(apexes)
374 }
375 
check_apexes_are_from_allowed_partitions(requested_apexes: &Vec<&ApexInfo>) -> Result<()>376 fn check_apexes_are_from_allowed_partitions(requested_apexes: &Vec<&ApexInfo>) -> Result<()> {
377     const ALLOWED_PARTITIONS: [&str; 2] = ["/system", "/system_ext"];
378     for apex in requested_apexes {
379         if !ALLOWED_PARTITIONS.iter().any(|p| apex.preinstalled_path.starts_with(p)) {
380             bail!("Non-system APEX {} is not supported in Microdroid", apex.name);
381         }
382     }
383     Ok(())
384 }
385 
386 // Collect ApexInfos from VM config
collect_apex_infos<'a>( apex_list: &'a ApexInfoList, apex_configs: &[ApexConfig], debug_config: &DebugConfig, ) -> Result<Vec<&'a ApexInfo>>387 fn collect_apex_infos<'a>(
388     apex_list: &'a ApexInfoList,
389     apex_configs: &[ApexConfig],
390     debug_config: &DebugConfig,
391 ) -> Result<Vec<&'a ApexInfo>> {
392     // APEXes which any Microdroid VM needs.
393     // TODO(b/192200378) move this to microdroid.json?
394     let required_apexes: &[_] =
395         if debug_config.should_include_debug_apexes() { &["com.android.adbd"] } else { &[] };
396 
397     let apex_infos = apex_list
398         .list
399         .iter()
400         .filter(|ai| {
401             apex_configs.iter().any(|cfg| ai.matches(cfg) && ai.is_active)
402                 || required_apexes.iter().any(|name| name == &ai.name && ai.is_active)
403                 || ai.provide_shared_apex_libs
404         })
405         .collect();
406 
407     check_apexes_are_from_allowed_partitions(&apex_infos)?;
408     Ok(apex_infos)
409 }
410 
add_microdroid_vendor_image(vendor_image: File, vm_config: &mut VirtualMachineRawConfig)411 pub fn add_microdroid_vendor_image(vendor_image: File, vm_config: &mut VirtualMachineRawConfig) {
412     vm_config.disks.push(DiskImage {
413         image: None,
414         writable: false,
415         partitions: vec![Partition {
416             label: "microdroid-vendor".to_owned(),
417             image: Some(ParcelFileDescriptor::new(vendor_image)),
418             writable: false,
419         }],
420     })
421 }
422 
add_microdroid_system_images( config: &VirtualMachineAppConfig, instance_file: File, storage_image: Option<File>, os_name: &str, vm_config: &mut VirtualMachineRawConfig, ) -> Result<()>423 pub fn add_microdroid_system_images(
424     config: &VirtualMachineAppConfig,
425     instance_file: File,
426     storage_image: Option<File>,
427     os_name: &str,
428     vm_config: &mut VirtualMachineRawConfig,
429 ) -> Result<()> {
430     let debug_suffix = match config.debugLevel {
431         DebugLevel::NONE => "normal",
432         DebugLevel::FULL => "debuggable",
433         _ => return Err(anyhow!("unsupported debug level: {:?}", config.debugLevel)),
434     };
435     let initrd = format!("/apex/com.android.virt/etc/{os_name}_initrd_{debug_suffix}.img");
436     vm_config.initrd = Some(open_parcel_file(Path::new(&initrd), false)?);
437 
438     let mut writable_partitions = vec![Partition {
439         label: "vm-instance".to_owned(),
440         image: Some(ParcelFileDescriptor::new(instance_file)),
441         writable: true,
442     }];
443 
444     if let Some(file) = storage_image {
445         writable_partitions.push(Partition {
446             label: "encryptedstore".to_owned(),
447             image: Some(ParcelFileDescriptor::new(file)),
448             writable: true,
449         });
450     }
451 
452     vm_config.disks.push(DiskImage {
453         image: None,
454         partitions: writable_partitions,
455         writable: true,
456     });
457 
458     Ok(())
459 }
460 
461 #[allow(clippy::too_many_arguments)] // TODO: Fewer arguments
add_microdroid_payload_images( config: &VirtualMachineAppConfig, debug_config: &DebugConfig, temporary_directory: &Path, apk_file: File, idsig_file: File, extra_apk_files: Vec<File>, vm_payload_config: &VmPayloadConfig, vm_config: &mut VirtualMachineRawConfig, ) -> Result<()>462 pub fn add_microdroid_payload_images(
463     config: &VirtualMachineAppConfig,
464     debug_config: &DebugConfig,
465     temporary_directory: &Path,
466     apk_file: File,
467     idsig_file: File,
468     extra_apk_files: Vec<File>,
469     vm_payload_config: &VmPayloadConfig,
470     vm_config: &mut VirtualMachineRawConfig,
471 ) -> Result<()> {
472     vm_config.disks.push(make_payload_disk(
473         config,
474         debug_config,
475         apk_file,
476         idsig_file,
477         extra_apk_files,
478         vm_payload_config,
479         temporary_directory,
480     )?);
481 
482     Ok(())
483 }
484 
485 #[cfg(test)]
486 mod tests {
487     use super::*;
488     use std::collections::HashMap;
489     use tempfile::NamedTempFile;
490 
491     #[test]
test_find_apex_names_in_classpath()492     fn test_find_apex_names_in_classpath() {
493         let vars = r#"
494 export FOO /apex/unterminated
495 export BAR /apex/valid.apex/something
496 wrong
497 export EMPTY
498 export OTHER /foo/bar:/baz:/apex/second.valid.apex/:gibberish:"#;
499         let expected = vec!["valid.apex", "second.valid.apex"];
500         let expected: HashSet<_> = expected.into_iter().map(ToString::to_string).collect();
501 
502         assert_eq!(find_apex_names_in_classpath(vars).unwrap(), expected);
503     }
504 
505     #[test]
test_collect_apexes() -> Result<()>506     fn test_collect_apexes() -> Result<()> {
507         let apex_infos_for_test = [
508             (
509                 "adbd",
510                 ApexInfo {
511                     name: "com.android.adbd".to_string(),
512                     path: PathBuf::from("adbd"),
513                     preinstalled_path: PathBuf::from("/system/adbd"),
514                     has_classpath_jar: false,
515                     last_update_seconds: 12345678,
516                     is_factory: true,
517                     is_active: false,
518                     ..Default::default()
519                 },
520             ),
521             (
522                 "adbd_updated",
523                 ApexInfo {
524                     name: "com.android.adbd".to_string(),
525                     path: PathBuf::from("adbd"),
526                     preinstalled_path: PathBuf::from("/system/adbd"),
527                     has_classpath_jar: false,
528                     last_update_seconds: 12345678 + 1,
529                     is_factory: false,
530                     is_active: true,
531                     ..Default::default()
532                 },
533             ),
534             (
535                 "no_classpath",
536                 ApexInfo {
537                     name: "no_classpath".to_string(),
538                     path: PathBuf::from("no_classpath"),
539                     has_classpath_jar: false,
540                     last_update_seconds: 12345678,
541                     is_factory: true,
542                     is_active: true,
543                     ..Default::default()
544                 },
545             ),
546             (
547                 "has_classpath",
548                 ApexInfo {
549                     name: "has_classpath".to_string(),
550                     path: PathBuf::from("has_classpath"),
551                     has_classpath_jar: true,
552                     last_update_seconds: 87654321,
553                     is_factory: true,
554                     is_active: false,
555                     ..Default::default()
556                 },
557             ),
558             (
559                 "has_classpath_updated",
560                 ApexInfo {
561                     name: "has_classpath".to_string(),
562                     path: PathBuf::from("has_classpath/updated"),
563                     preinstalled_path: PathBuf::from("/system/has_classpath"),
564                     has_classpath_jar: true,
565                     last_update_seconds: 87654321 + 1,
566                     is_factory: false,
567                     is_active: true,
568                     ..Default::default()
569                 },
570             ),
571             (
572                 "apex-foo",
573                 ApexInfo {
574                     name: "apex-foo".to_string(),
575                     path: PathBuf::from("apex-foo"),
576                     preinstalled_path: PathBuf::from("/system/apex-foo"),
577                     has_classpath_jar: false,
578                     last_update_seconds: 87654321,
579                     is_factory: true,
580                     is_active: false,
581                     ..Default::default()
582                 },
583             ),
584             (
585                 "apex-foo-updated",
586                 ApexInfo {
587                     name: "apex-foo".to_string(),
588                     path: PathBuf::from("apex-foo/updated"),
589                     preinstalled_path: PathBuf::from("/system/apex-foo"),
590                     has_classpath_jar: false,
591                     last_update_seconds: 87654321 + 1,
592                     is_factory: false,
593                     is_active: true,
594                     ..Default::default()
595                 },
596             ),
597             (
598                 "sharedlibs",
599                 ApexInfo {
600                     name: "sharedlibs".to_string(),
601                     path: PathBuf::from("apex-foo"),
602                     preinstalled_path: PathBuf::from("/system/apex-foo"),
603                     last_update_seconds: 87654321,
604                     is_factory: true,
605                     provide_shared_apex_libs: true,
606                     ..Default::default()
607                 },
608             ),
609             (
610                 "sharedlibs-updated",
611                 ApexInfo {
612                     name: "sharedlibs".to_string(),
613                     path: PathBuf::from("apex-foo/updated"),
614                     preinstalled_path: PathBuf::from("/system/apex-foo"),
615                     last_update_seconds: 87654321 + 1,
616                     is_active: true,
617                     provide_shared_apex_libs: true,
618                     ..Default::default()
619                 },
620             ),
621         ];
622         let apex_info_list = ApexInfoList {
623             list: apex_infos_for_test.iter().map(|(_, info)| info).cloned().collect(),
624         };
625         let apex_info_map = HashMap::from(apex_infos_for_test);
626         let apex_configs = vec![
627             ApexConfig { name: "apex-foo".to_string() },
628             ApexConfig { name: "{CLASSPATH}".to_string() },
629         ];
630         assert_eq!(
631             collect_apex_infos(
632                 &apex_info_list,
633                 &apex_configs,
634                 &DebugConfig::new_with_debug_level(DebugLevel::FULL)
635             )?,
636             vec![
637                 // Pass active/required APEXes
638                 &apex_info_map["adbd_updated"],
639                 // Pass active APEXes specified in the config
640                 &apex_info_map["has_classpath_updated"],
641                 &apex_info_map["apex-foo-updated"],
642                 // Pass both preinstalled(inactive) and updated(active) for "sharedlibs" APEXes
643                 &apex_info_map["sharedlibs"],
644                 &apex_info_map["sharedlibs-updated"],
645             ]
646         );
647         Ok(())
648     }
649 
650     #[test]
test_check_allowed_partitions_vendor_not_allowed() -> Result<()>651     fn test_check_allowed_partitions_vendor_not_allowed() -> Result<()> {
652         let apex_info_list = ApexInfoList {
653             list: vec![ApexInfo {
654                 name: "apex-vendor".to_string(),
655                 path: PathBuf::from("apex-vendor"),
656                 preinstalled_path: PathBuf::from("/vendor/apex-vendor"),
657                 is_active: true,
658                 ..Default::default()
659             }],
660         };
661         let apex_configs = vec![ApexConfig { name: "apex-vendor".to_string() }];
662 
663         let ret = collect_apex_infos(
664             &apex_info_list,
665             &apex_configs,
666             &DebugConfig::new_with_debug_level(DebugLevel::NONE),
667         );
668         assert!(ret
669             .is_err_and(|ret| ret.to_string()
670                 == "Non-system APEX apex-vendor is not supported in Microdroid"));
671 
672         Ok(())
673     }
674 
675     #[test]
test_check_allowed_partitions_system_ext_allowed() -> Result<()>676     fn test_check_allowed_partitions_system_ext_allowed() -> Result<()> {
677         let apex_info_list = ApexInfoList {
678             list: vec![ApexInfo {
679                 name: "apex-system_ext".to_string(),
680                 path: PathBuf::from("apex-system_ext"),
681                 preinstalled_path: PathBuf::from("/system_ext/apex-system_ext"),
682                 is_active: true,
683                 ..Default::default()
684             }],
685         };
686 
687         let apex_configs = vec![ApexConfig { name: "apex-system_ext".to_string() }];
688 
689         assert_eq!(
690             collect_apex_infos(
691                 &apex_info_list,
692                 &apex_configs,
693                 &DebugConfig::new_with_debug_level(DebugLevel::NONE)
694             )?,
695             vec![&apex_info_list.list[0]]
696         );
697 
698         Ok(())
699     }
700 
701     #[test]
test_prefer_staged_apex_with_factory_active_apex()702     fn test_prefer_staged_apex_with_factory_active_apex() {
703         let single_apex = ApexInfo {
704             name: "foo".to_string(),
705             version: 1,
706             path: PathBuf::from("foo.apex"),
707             is_factory: true,
708             is_active: true,
709             ..Default::default()
710         };
711         let mut apex_info_list = ApexInfoList { list: vec![single_apex.clone()] };
712 
713         let staged = NamedTempFile::new().unwrap();
714         apex_info_list
715             .override_staged_apex(&StagedApexInfo {
716                 moduleName: "foo".to_string(),
717                 versionCode: 2,
718                 diskImagePath: staged.path().to_string_lossy().to_string(),
719                 ..Default::default()
720             })
721             .expect("should be ok");
722 
723         assert_eq!(
724             apex_info_list,
725             ApexInfoList {
726                 list: vec![
727                     ApexInfo {
728                         version: 2,
729                         is_factory: false,
730                         path: staged.path().to_owned(),
731                         last_update_seconds: last_updated(staged.path()).unwrap(),
732                         ..single_apex.clone()
733                     },
734                     ApexInfo { is_active: false, ..single_apex },
735                 ],
736             }
737         );
738     }
739 
740     #[test]
test_prefer_staged_apex_with_factory_and_inactive_apex()741     fn test_prefer_staged_apex_with_factory_and_inactive_apex() {
742         let factory_apex = ApexInfo {
743             name: "foo".to_string(),
744             version: 1,
745             path: PathBuf::from("foo.apex"),
746             is_factory: true,
747             ..Default::default()
748         };
749         let active_apex = ApexInfo {
750             name: "foo".to_string(),
751             version: 2,
752             path: PathBuf::from("foo.downloaded.apex"),
753             is_active: true,
754             ..Default::default()
755         };
756         let mut apex_info_list =
757             ApexInfoList { list: vec![factory_apex.clone(), active_apex.clone()] };
758 
759         let staged = NamedTempFile::new().unwrap();
760         apex_info_list
761             .override_staged_apex(&StagedApexInfo {
762                 moduleName: "foo".to_string(),
763                 versionCode: 3,
764                 diskImagePath: staged.path().to_string_lossy().to_string(),
765                 ..Default::default()
766             })
767             .expect("should be ok");
768 
769         assert_eq!(
770             apex_info_list,
771             ApexInfoList {
772                 list: vec![
773                     // factory apex isn't touched
774                     factory_apex,
775                     // update active one
776                     ApexInfo {
777                         version: 3,
778                         path: staged.path().to_owned(),
779                         last_update_seconds: last_updated(staged.path()).unwrap(),
780                         ..active_apex
781                     },
782                 ],
783             }
784         );
785     }
786 }
787