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