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