blob: 343f3cf558c1861b6566da0845752bc8a80260da [file] [log] [blame]
Jooyung Han21e9b922021-06-26 04:14:16 +09001// 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
Jaewan Kim61f86142023-03-28 15:12:52 +090017use crate::debug_config::DebugConfig;
Andrew Walbrancc0db522021-07-12 17:03:42 +000018use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
Alan Stokes0d1ef782022-09-27 13:46:35 +010019 DiskImage::DiskImage,
20 Partition::Partition,
21 VirtualMachineAppConfig::DebugLevel::DebugLevel,
22 VirtualMachineAppConfig::{Payload::Payload, VirtualMachineAppConfig},
Andrew Walbrancc0db522021-07-12 17:03:42 +000023 VirtualMachineRawConfig::VirtualMachineRawConfig,
24};
Inseob Kima5a262f2021-11-17 19:41:03 +090025use anyhow::{anyhow, bail, Context, Result};
Alan Stokes0e82b502022-08-08 14:44:48 +010026use binder::{wait_for_interface, ParcelFileDescriptor};
Alan Stokesbf20c6a2022-01-04 12:30:50 +000027use log::{info, warn};
Alan Stokes0d1ef782022-09-27 13:46:35 +010028use microdroid_metadata::{ApexPayload, ApkPayload, Metadata, PayloadConfig, PayloadMetadata};
Jooyung Han5dc42172021-10-05 16:43:47 +090029use microdroid_payload_config::{ApexConfig, VmPayloadConfig};
Jooyung Han9900f3d2021-07-06 10:27:54 +090030use once_cell::sync::OnceCell;
Jooyung Han743e0d62022-11-07 20:57:48 +090031use packagemanager_aidl::aidl::android::content::pm::{
32 IPackageManagerNative::IPackageManagerNative, StagedApexInfo::StagedApexInfo,
33};
Alan Stokesbf20c6a2022-01-04 12:30:50 +000034use regex::Regex;
Jooyung Han44b02ab2021-07-16 03:19:13 +090035use serde::Deserialize;
36use serde_xml_rs::from_reader;
Alan Stokesbf20c6a2022-01-04 12:30:50 +000037use std::collections::HashSet;
Andrew Walbran40be9d52022-01-19 14:32:53 +000038use std::fs::{metadata, File, OpenOptions};
Jooyung Han21e9b922021-06-26 04:14:16 +090039use std::path::{Path, PathBuf};
Alan Stokesbf20c6a2022-01-04 12:30:50 +000040use std::process::Command;
Andrew Walbran40be9d52022-01-19 14:32:53 +000041use std::time::SystemTime;
Andrew Walbrancc0db522021-07-12 17:03:42 +000042use vmconfig::open_parcel_file;
43
44/// The list of APEXes which microdroid requires.
45// TODO(b/192200378) move this to microdroid.json?
Inseob Kimb4868e62021-11-09 17:28:23 +090046const MICRODROID_REQUIRED_APEXES: [&str; 1] = ["com.android.os.statsd"];
47const MICRODROID_REQUIRED_APEXES_DEBUG: [&str; 1] = ["com.android.adbd"];
Jooyung Han21e9b922021-06-26 04:14:16 +090048
Jooyung Han44b02ab2021-07-16 03:19:13 +090049const APEX_INFO_LIST_PATH: &str = "/apex/apex-info-list.xml";
50
Jooyung Han5dc42172021-10-05 16:43:47 +090051const PACKAGE_MANAGER_NATIVE_SERVICE: &str = "package_native";
52
Jooyung Han73bac242021-07-02 10:25:49 +090053/// Represents the list of APEXes
Jooyung Han743e0d62022-11-07 20:57:48 +090054#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
Jooyung Han9900f3d2021-07-06 10:27:54 +090055struct ApexInfoList {
Jooyung Han44b02ab2021-07-16 03:19:13 +090056 #[serde(rename = "apex-info")]
Jooyung Han73bac242021-07-02 10:25:49 +090057 list: Vec<ApexInfo>,
58}
59
Jooyung Han5ce867a2022-01-28 03:18:38 +090060#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
Jooyung Han73bac242021-07-02 10:25:49 +090061struct ApexInfo {
Jooyung Han44b02ab2021-07-16 03:19:13 +090062 #[serde(rename = "moduleName")]
Jooyung Han73bac242021-07-02 10:25:49 +090063 name: String,
Jooyung Hancaa995c2022-11-08 16:35:50 +090064 #[serde(rename = "versionCode")]
65 version: u64,
Jooyung Han44b02ab2021-07-16 03:19:13 +090066 #[serde(rename = "modulePath")]
Jooyung Han73bac242021-07-02 10:25:49 +090067 path: PathBuf,
Jooyung Han9d7cd7d2021-10-12 17:44:14 +090068
69 #[serde(default)]
Alan Stokes46ac3862021-12-21 15:31:47 +000070 has_classpath_jar: bool,
Andrew Walbran40be9d52022-01-19 14:32:53 +000071
72 // The field claims to be milliseconds but is actually seconds.
73 #[serde(rename = "lastUpdateMillis")]
74 last_update_seconds: u64,
Jiyong Parkd6502352022-01-27 01:07:30 +090075
76 #[serde(rename = "isFactory")]
77 is_factory: bool,
Jooyung Hanec788042022-01-27 22:28:37 +090078
79 #[serde(rename = "isActive")]
80 is_active: bool,
Jooyung Han5ce867a2022-01-28 03:18:38 +090081
82 #[serde(rename = "provideSharedApexLibs")]
83 provide_shared_apex_libs: bool,
Nikita Ioffed4551e12023-07-14 16:01:03 +010084
85 #[serde(rename = "preinstalledModulePath")]
86 preinstalled_path: PathBuf,
Jooyung Han73bac242021-07-02 10:25:49 +090087}
88
89impl ApexInfoList {
90 /// Loads ApexInfoList
Jooyung Han9900f3d2021-07-06 10:27:54 +090091 fn load() -> Result<&'static ApexInfoList> {
92 static INSTANCE: OnceCell<ApexInfoList> = OnceCell::new();
93 INSTANCE.get_or_try_init(|| {
Jooyung Han44b02ab2021-07-16 03:19:13 +090094 let apex_info_list = File::open(APEX_INFO_LIST_PATH)
95 .context(format!("Failed to open {}", APEX_INFO_LIST_PATH))?;
Jooyung Han9d7cd7d2021-10-12 17:44:14 +090096 let mut apex_info_list: ApexInfoList = from_reader(apex_info_list)
Jooyung Han44b02ab2021-07-16 03:19:13 +090097 .context(format!("Failed to parse {}", APEX_INFO_LIST_PATH))?;
Jooyung Han9d7cd7d2021-10-12 17:44:14 +090098
Alan Stokesbf20c6a2022-01-04 12:30:50 +000099 // For active APEXes, we run derive_classpath and parse its output to see if it
100 // contributes to the classpath(s). (This allows us to handle any new classpath env
101 // vars seamlessly.)
102 let classpath_vars = run_derive_classpath()?;
103 let classpath_apexes = find_apex_names_in_classpath(&classpath_vars)?;
104
Jooyung Han9d7cd7d2021-10-12 17:44:14 +0900105 for apex_info in apex_info_list.list.iter_mut() {
Alan Stokesbf20c6a2022-01-04 12:30:50 +0000106 apex_info.has_classpath_jar = classpath_apexes.contains(&apex_info.name);
Jooyung Han9d7cd7d2021-10-12 17:44:14 +0900107 }
Alan Stokesbf20c6a2022-01-04 12:30:50 +0000108
Jooyung Han44b02ab2021-07-16 03:19:13 +0900109 Ok(apex_info_list)
Jooyung Han9900f3d2021-07-06 10:27:54 +0900110 })
Jooyung Han73bac242021-07-02 10:25:49 +0900111 }
Jooyung Han743e0d62022-11-07 20:57:48 +0900112
113 // Override apex info with the staged one
114 fn override_staged_apex(&mut self, staged_apex_info: &StagedApexInfo) -> Result<()> {
115 let mut need_to_add: Option<ApexInfo> = None;
116 for apex_info in self.list.iter_mut() {
117 if staged_apex_info.moduleName == apex_info.name {
118 if apex_info.is_active && apex_info.is_factory {
119 // Copy the entry to the end as factory/non-active after the loop
120 // to keep the factory version. Typically this step is unncessary,
121 // but some apexes (like sharedlibs) need to be kept even if it's inactive.
122 need_to_add.replace(ApexInfo { is_active: false, ..apex_info.clone() });
123 // And make this one as non-factory. Note that this one is still active
124 // and overridden right below.
125 apex_info.is_factory = false;
126 }
127 // Active one is overridden with the staged one.
128 if apex_info.is_active {
Jooyung Hancaa995c2022-11-08 16:35:50 +0900129 apex_info.version = staged_apex_info.versionCode as u64;
Jooyung Han743e0d62022-11-07 20:57:48 +0900130 apex_info.path = PathBuf::from(&staged_apex_info.diskImagePath);
131 apex_info.has_classpath_jar = staged_apex_info.hasClassPathJars;
132 apex_info.last_update_seconds = last_updated(&apex_info.path)?;
133 }
134 }
135 }
136 if let Some(info) = need_to_add {
137 self.list.push(info);
138 }
139 Ok(())
140 }
141}
142
143fn last_updated<P: AsRef<Path>>(path: P) -> Result<u64> {
144 let metadata = metadata(path)?;
145 Ok(metadata.modified()?.duration_since(SystemTime::UNIX_EPOCH)?.as_secs())
Jooyung Hanec788042022-01-27 22:28:37 +0900146}
Jooyung Han73bac242021-07-02 10:25:49 +0900147
Jooyung Hanec788042022-01-27 22:28:37 +0900148impl ApexInfo {
149 fn matches(&self, apex_config: &ApexConfig) -> bool {
150 // Match with pseudo name "{CLASSPATH}" which represents APEXes contributing
151 // to any derive_classpath environment variable
152 if apex_config.name == "{CLASSPATH}" && self.has_classpath_jar {
153 return true;
154 }
155 if apex_config.name == self.name {
156 return true;
157 }
158 false
Jooyung Han73bac242021-07-02 10:25:49 +0900159 }
Jooyung Han21e9b922021-06-26 04:14:16 +0900160}
161
Jooyung Han5dc42172021-10-05 16:43:47 +0900162struct PackageManager {
Jooyung Han5dc42172021-10-05 16:43:47 +0900163 apex_info_list: &'static ApexInfoList,
164}
165
166impl PackageManager {
167 fn new() -> Result<Self> {
Jooyung Han5dc42172021-10-05 16:43:47 +0900168 let apex_info_list = ApexInfoList::load()?;
Jooyung Han53cf7992021-10-18 19:38:41 +0900169 Ok(Self { apex_info_list })
Jooyung Han5dc42172021-10-05 16:43:47 +0900170 }
171
Jooyung Han9d7cd7d2021-10-12 17:44:14 +0900172 fn get_apex_list(&self, prefer_staged: bool) -> Result<ApexInfoList> {
Jooyung Han53cf7992021-10-18 19:38:41 +0900173 // get the list of active apexes
Jooyung Han9d7cd7d2021-10-12 17:44:14 +0900174 let mut list = self.apex_info_list.clone();
Jooyung Han53cf7992021-10-18 19:38:41 +0900175 // When prefer_staged, we override ApexInfo by consulting "package_native"
Jooyung Han5dc42172021-10-05 16:43:47 +0900176 if prefer_staged {
Jooyung Han53cf7992021-10-18 19:38:41 +0900177 let pm =
178 wait_for_interface::<dyn IPackageManagerNative>(PACKAGE_MANAGER_NATIVE_SERVICE)
179 .context("Failed to get service when prefer_staged is set.")?;
Alan Stokes70ccf162022-07-08 11:05:03 +0100180 let staged =
181 pm.getStagedApexModuleNames().context("getStagedApexModuleNames failed")?;
Jooyung Han743e0d62022-11-07 20:57:48 +0900182 for name in staged {
183 if let Some(staged_apex_info) =
184 pm.getStagedApexInfo(&name).context("getStagedApexInfo failed")?
185 {
186 list.override_staged_apex(&staged_apex_info)?;
Jooyung Han9d7cd7d2021-10-12 17:44:14 +0900187 }
Jooyung Han5dc42172021-10-05 16:43:47 +0900188 }
189 }
Jooyung Han9d7cd7d2021-10-12 17:44:14 +0900190 Ok(list)
Jooyung Han5dc42172021-10-05 16:43:47 +0900191 }
192}
193
Andrew Walbrancc0db522021-07-12 17:03:42 +0000194fn make_metadata_file(
Alan Stokes0d1ef782022-09-27 13:46:35 +0100195 app_config: &VirtualMachineAppConfig,
Jooyung Hanec788042022-01-27 22:28:37 +0900196 apex_infos: &[&ApexInfo],
Jooyung Han21e9b922021-06-26 04:14:16 +0900197 temporary_directory: &Path,
Andrew Walbrancc0db522021-07-12 17:03:42 +0000198) -> Result<ParcelFileDescriptor> {
Alan Stokes0d1ef782022-09-27 13:46:35 +0100199 let payload_metadata = match &app_config.payload {
Ludovic Barman93ee3082023-06-20 12:18:43 +0000200 Payload::PayloadConfig(payload_config) => PayloadMetadata::Config(PayloadConfig {
Alan Stokes8f12f2b2023-01-09 09:19:20 +0000201 payload_binary_name: payload_config.payloadBinaryName.clone(),
Alan Stokes0d1ef782022-09-27 13:46:35 +0100202 ..Default::default()
203 }),
204 Payload::ConfigPath(config_path) => {
Ludovic Barman93ee3082023-06-20 12:18:43 +0000205 PayloadMetadata::ConfigPath(format!("/mnt/apk/{}", config_path))
Alan Stokes0d1ef782022-09-27 13:46:35 +0100206 }
207 };
208
Jooyung Han21e9b922021-06-26 04:14:16 +0900209 let metadata = Metadata {
Andrew Walbrancc0db522021-07-12 17:03:42 +0000210 version: 1,
Jooyung Hanec788042022-01-27 22:28:37 +0900211 apexes: apex_infos
Jooyung Han21e9b922021-06-26 04:14:16 +0900212 .iter()
Jooyung Han19c1d6c2021-08-06 14:08:16 +0900213 .enumerate()
Jooyung Hanec788042022-01-27 22:28:37 +0900214 .map(|(i, apex_info)| {
Andrew Walbran40be9d52022-01-19 14:32:53 +0000215 Ok(ApexPayload {
Jooyung Hanec788042022-01-27 22:28:37 +0900216 name: apex_info.name.clone(),
Andrew Walbran40be9d52022-01-19 14:32:53 +0000217 partition_name: format!("microdroid-apex-{}", i),
Jiyong Parkd6502352022-01-27 01:07:30 +0900218 last_update_seconds: apex_info.last_update_seconds,
219 is_factory: apex_info.is_factory,
Andrew Walbran40be9d52022-01-19 14:32:53 +0000220 ..Default::default()
221 })
Jooyung Han19c1d6c2021-08-06 14:08:16 +0900222 })
Andrew Walbran40be9d52022-01-19 14:32:53 +0000223 .collect::<Result<_>>()?,
Jooyung Han21e9b922021-06-26 04:14:16 +0900224 apk: Some(ApkPayload {
Jooyung Han35edb8f2021-07-01 16:17:16 +0900225 name: "apk".to_owned(),
226 payload_partition_name: "microdroid-apk".to_owned(),
227 idsig_partition_name: "microdroid-apk-idsig".to_owned(),
Jooyung Han21e9b922021-06-26 04:14:16 +0900228 ..Default::default()
229 })
230 .into(),
Alan Stokes0d1ef782022-09-27 13:46:35 +0100231 payload: Some(payload_metadata),
Jooyung Han21e9b922021-06-26 04:14:16 +0900232 ..Default::default()
233 };
Andrew Walbrancc0db522021-07-12 17:03:42 +0000234
235 // Write metadata to file.
Alan Stokes0d1ef782022-09-27 13:46:35 +0100236 let metadata_path = temporary_directory.join("metadata");
Andrew Walbrancc0db522021-07-12 17:03:42 +0000237 let mut metadata_file = OpenOptions::new()
238 .create_new(true)
239 .read(true)
240 .write(true)
241 .open(&metadata_path)
242 .with_context(|| format!("Failed to open metadata file {:?}", metadata_path))?;
Jooyung Han21e9b922021-06-26 04:14:16 +0900243 microdroid_metadata::write_metadata(&metadata, &mut metadata_file)?;
244
Andrew Walbrancc0db522021-07-12 17:03:42 +0000245 // Re-open the metadata file as read-only.
246 open_parcel_file(&metadata_path, false)
247}
248
249/// Creates a DiskImage with partitions:
Alan Stokes53cc5ca2022-08-30 14:28:19 +0100250/// payload-metadata: metadata
Andrew Walbrancc0db522021-07-12 17:03:42 +0000251/// microdroid-apex-0: apex 0
252/// microdroid-apex-1: apex 1
253/// ..
254/// microdroid-apk: apk
255/// microdroid-apk-idsig: idsig
Inseob Kima5a262f2021-11-17 19:41:03 +0900256/// extra-apk-0: additional apk 0
257/// extra-idsig-0: additional idsig 0
258/// extra-apk-1: additional apk 1
259/// extra-idsig-1: additional idsig 1
260/// ..
Andrew Walbrancc0db522021-07-12 17:03:42 +0000261fn make_payload_disk(
Inseob Kima5a262f2021-11-17 19:41:03 +0900262 app_config: &VirtualMachineAppConfig,
Jaewan Kim61f86142023-03-28 15:12:52 +0900263 debug_config: &DebugConfig,
Andrew Walbrancc0db522021-07-12 17:03:42 +0000264 apk_file: File,
265 idsig_file: File,
Jooyung Han9d7cd7d2021-10-12 17:44:14 +0900266 vm_payload_config: &VmPayloadConfig,
Andrew Walbrancc0db522021-07-12 17:03:42 +0000267 temporary_directory: &Path,
268) -> Result<DiskImage> {
Inseob Kima5a262f2021-11-17 19:41:03 +0900269 if vm_payload_config.extra_apks.len() != app_config.extraIdsigs.len() {
270 bail!(
271 "payload config has {} apks, but app config has {} idsigs",
272 vm_payload_config.extra_apks.len(),
273 app_config.extraIdsigs.len()
274 );
275 }
276
Jooyung Han9d7cd7d2021-10-12 17:44:14 +0900277 let pm = PackageManager::new()?;
278 let apex_list = pm.get_apex_list(vm_payload_config.prefer_staged)?;
279
Jooyung Hanec788042022-01-27 22:28:37 +0900280 // collect APEXes from config
Nikita Ioffed4551e12023-07-14 16:01:03 +0100281 let mut apex_infos = collect_apex_infos(&apex_list, &vm_payload_config.apexes, debug_config)?;
Jooyung Hancaa995c2022-11-08 16:35:50 +0900282
283 // Pass sorted list of apexes. Sorting key shouldn't use `path` because it will change after
284 // reboot with prefer_staged. `last_update_seconds` is added to distinguish "samegrade"
285 // update.
286 apex_infos.sort_by_key(|info| (&info.name, &info.version, &info.last_update_seconds));
Jooyung Hanec788042022-01-27 22:28:37 +0900287 info!("Microdroid payload APEXes: {:?}", apex_infos.iter().map(|ai| &ai.name));
Jooyung Han9d7cd7d2021-10-12 17:44:14 +0900288
Alan Stokes0d1ef782022-09-27 13:46:35 +0100289 let metadata_file = make_metadata_file(app_config, &apex_infos, temporary_directory)?;
Jooyung Han21e9b922021-06-26 04:14:16 +0900290 // put metadata at the first partition
291 let mut partitions = vec![Partition {
Jooyung Han14e5a8e2021-07-06 20:48:38 +0900292 label: "payload-metadata".to_owned(),
Jooyung Han631d5882021-07-29 06:34:05 +0900293 image: Some(metadata_file),
Jooyung Han21e9b922021-06-26 04:14:16 +0900294 writable: false,
295 }];
296
Jooyung Hanec788042022-01-27 22:28:37 +0900297 for (i, apex_info) in apex_infos.iter().enumerate() {
298 let apex_file = open_parcel_file(&apex_info.path, false)?;
Jooyung Han95884632021-07-06 22:27:54 +0900299 partitions.push(Partition {
300 label: format!("microdroid-apex-{}", i),
Jooyung Han631d5882021-07-29 06:34:05 +0900301 image: Some(apex_file),
Jooyung Han95884632021-07-06 22:27:54 +0900302 writable: false,
303 });
Jooyung Han21e9b922021-06-26 04:14:16 +0900304 }
Jooyung Han95884632021-07-06 22:27:54 +0900305 partitions.push(Partition {
306 label: "microdroid-apk".to_owned(),
Jooyung Han631d5882021-07-29 06:34:05 +0900307 image: Some(ParcelFileDescriptor::new(apk_file)),
Jooyung Han95884632021-07-06 22:27:54 +0900308 writable: false,
309 });
310 partitions.push(Partition {
311 label: "microdroid-apk-idsig".to_owned(),
Jooyung Han631d5882021-07-29 06:34:05 +0900312 image: Some(ParcelFileDescriptor::new(idsig_file)),
Jooyung Han95884632021-07-06 22:27:54 +0900313 writable: false,
314 });
Jooyung Han21e9b922021-06-26 04:14:16 +0900315
Inseob Kima5a262f2021-11-17 19:41:03 +0900316 // we've already checked that extra_apks and extraIdsigs are in the same size.
317 let extra_apks = &vm_payload_config.extra_apks;
318 let extra_idsigs = &app_config.extraIdsigs;
319 for (i, (extra_apk, extra_idsig)) in extra_apks.iter().zip(extra_idsigs.iter()).enumerate() {
320 partitions.push(Partition {
321 label: format!("extra-apk-{}", i),
Victor Hsieh14497f02022-09-21 10:10:16 -0700322 image: Some(ParcelFileDescriptor::new(
323 File::open(PathBuf::from(&extra_apk.path)).with_context(|| {
324 format!("Failed to open the extra apk #{} {}", i, extra_apk.path)
325 })?,
326 )),
Inseob Kima5a262f2021-11-17 19:41:03 +0900327 writable: false,
328 });
329
330 partitions.push(Partition {
331 label: format!("extra-idsig-{}", i),
Victor Hsieh14497f02022-09-21 10:10:16 -0700332 image: Some(ParcelFileDescriptor::new(
333 extra_idsig
334 .as_ref()
335 .try_clone()
336 .with_context(|| format!("Failed to clone the extra idsig #{}", i))?,
337 )),
Inseob Kima5a262f2021-11-17 19:41:03 +0900338 writable: false,
339 });
340 }
341
Jooyung Han21e9b922021-06-26 04:14:16 +0900342 Ok(DiskImage { image: None, partitions, writable: false })
343}
Andrew Walbrancc0db522021-07-12 17:03:42 +0000344
Alan Stokesbf20c6a2022-01-04 12:30:50 +0000345fn run_derive_classpath() -> Result<String> {
346 let result = Command::new("/apex/com.android.sdkext/bin/derive_classpath")
347 .arg("/proc/self/fd/1")
348 .output()
349 .context("Failed to run derive_classpath")?;
350
351 if !result.status.success() {
352 bail!("derive_classpath returned {}", result.status);
353 }
354
355 String::from_utf8(result.stdout).context("Converting derive_classpath output")
356}
357
358fn find_apex_names_in_classpath(classpath_vars: &str) -> Result<HashSet<String>> {
359 // Each line should be in the format "export <var name> <paths>", where <paths> is a
360 // colon-separated list of paths to JARs. We don't care about the var names, and we're only
361 // interested in paths that look like "/apex/<apex name>/<anything>" so we know which APEXes
362 // contribute to at least one var.
363 let mut apexes = HashSet::new();
364
365 let pattern = Regex::new(r"^export [^ ]+ ([^ ]+)$").context("Failed to construct Regex")?;
366 for line in classpath_vars.lines() {
367 if let Some(captures) = pattern.captures(line) {
368 if let Some(paths) = captures.get(1) {
369 apexes.extend(paths.as_str().split(':').filter_map(|path| {
370 let path = path.strip_prefix("/apex/")?;
371 Some(path[..path.find('/')?].to_owned())
372 }));
373 continue;
374 }
375 }
376 warn!("Malformed line from derive_classpath: {}", line);
377 }
378
379 Ok(apexes)
Jooyung Han5e0f2062021-10-12 14:00:46 +0900380}
381
Nikita Ioffed4551e12023-07-14 16:01:03 +0100382fn check_apexes_are_from_allowed_partitions(requested_apexes: &Vec<&ApexInfo>) -> Result<()> {
383 const ALLOWED_PARTITIONS: [&str; 2] = ["/system", "/system_ext"];
384 for apex in requested_apexes {
385 if !ALLOWED_PARTITIONS.iter().any(|p| apex.preinstalled_path.starts_with(p)) {
386 bail!("Non-system APEX {} is not supported in Microdroid", apex.name);
387 }
388 }
389 Ok(())
390}
391
Jooyung Hanec788042022-01-27 22:28:37 +0900392// Collect ApexInfos from VM config
393fn collect_apex_infos<'a>(
394 apex_list: &'a ApexInfoList,
395 apex_configs: &[ApexConfig],
Jaewan Kim61f86142023-03-28 15:12:52 +0900396 debug_config: &DebugConfig,
Nikita Ioffed4551e12023-07-14 16:01:03 +0100397) -> Result<Vec<&'a ApexInfo>> {
Jooyung Hanec788042022-01-27 22:28:37 +0900398 let mut additional_apexes: Vec<&str> = MICRODROID_REQUIRED_APEXES.to_vec();
Jaewan Kim61f86142023-03-28 15:12:52 +0900399 if debug_config.should_include_debug_apexes() {
Jooyung Hanec788042022-01-27 22:28:37 +0900400 additional_apexes.extend(MICRODROID_REQUIRED_APEXES_DEBUG.to_vec());
Inseob Kimb4868e62021-11-09 17:28:23 +0900401 }
Jooyung Hanec788042022-01-27 22:28:37 +0900402
Nikita Ioffed4551e12023-07-14 16:01:03 +0100403 let apex_infos = apex_list
Jooyung Hanec788042022-01-27 22:28:37 +0900404 .list
405 .iter()
406 .filter(|ai| {
407 apex_configs.iter().any(|cfg| ai.matches(cfg) && ai.is_active)
408 || additional_apexes.iter().any(|name| name == &ai.name && ai.is_active)
Jooyung Han5ce867a2022-01-28 03:18:38 +0900409 || ai.provide_shared_apex_libs
Jooyung Hanec788042022-01-27 22:28:37 +0900410 })
Nikita Ioffed4551e12023-07-14 16:01:03 +0100411 .collect();
412
413 check_apexes_are_from_allowed_partitions(&apex_infos)?;
414 Ok(apex_infos)
Jooyung Han5e0f2062021-10-12 14:00:46 +0900415}
416
Nikita Ioffe5dfddf22023-06-29 16:11:26 +0100417pub fn add_microdroid_vendor_image(vendor_image: File, vm_config: &mut VirtualMachineRawConfig) {
418 vm_config.disks.push(DiskImage {
419 image: None,
420 writable: false,
421 partitions: vec![Partition {
422 label: "microdroid-vendor".to_owned(),
423 image: Some(ParcelFileDescriptor::new(vendor_image)),
424 writable: false,
425 }],
426 })
427}
428
Shikha Panwar22e70452022-10-10 18:32:55 +0000429pub fn add_microdroid_system_images(
Andrew Walbrancc0db522021-07-12 17:03:42 +0000430 config: &VirtualMachineAppConfig,
Jiyong Park8d081812021-07-23 17:45:04 +0900431 instance_file: File,
Shikha Panwar22e70452022-10-10 18:32:55 +0000432 storage_image: Option<File>,
Andrew Walbrancc0db522021-07-12 17:03:42 +0000433 vm_config: &mut VirtualMachineRawConfig,
434) -> Result<()> {
Shikha Panwared8ace42022-09-28 12:52:16 +0000435 let debug_suffix = match config.debugLevel {
436 DebugLevel::NONE => "normal",
Seungjae Yooe85831e2022-12-12 09:34:58 +0900437 DebugLevel::FULL => "debuggable",
Shikha Panwared8ace42022-09-28 12:52:16 +0000438 _ => return Err(anyhow!("unsupported debug level: {:?}", config.debugLevel)),
439 };
440 let initrd = format!("/apex/com.android.virt/etc/microdroid_initrd_{}.img", debug_suffix);
441 vm_config.initrd = Some(open_parcel_file(Path::new(&initrd), false)?);
442
Shikha Panwar22e70452022-10-10 18:32:55 +0000443 let mut writable_partitions = vec![Partition {
Shikha Panwared8ace42022-09-28 12:52:16 +0000444 label: "vm-instance".to_owned(),
445 image: Some(ParcelFileDescriptor::new(instance_file)),
446 writable: true,
Shikha Panwar22e70452022-10-10 18:32:55 +0000447 }];
448
449 if let Some(file) = storage_image {
450 writable_partitions.push(Partition {
Shikha Panwar566c9672022-11-15 14:39:58 +0000451 label: "encryptedstore".to_owned(),
Shikha Panwar22e70452022-10-10 18:32:55 +0000452 image: Some(ParcelFileDescriptor::new(file)),
453 writable: true,
454 });
455 }
456
457 vm_config.disks.push(DiskImage {
458 image: None,
459 partitions: writable_partitions,
460 writable: true,
461 });
462
463 Ok(())
464}
465
466pub fn add_microdroid_payload_images(
467 config: &VirtualMachineAppConfig,
Jaewan Kim61f86142023-03-28 15:12:52 +0900468 debug_config: &DebugConfig,
Shikha Panwar22e70452022-10-10 18:32:55 +0000469 temporary_directory: &Path,
470 apk_file: File,
471 idsig_file: File,
472 vm_payload_config: &VmPayloadConfig,
473 vm_config: &mut VirtualMachineRawConfig,
474) -> Result<()> {
Andrew Walbrancc0db522021-07-12 17:03:42 +0000475 vm_config.disks.push(make_payload_disk(
Inseob Kima5a262f2021-11-17 19:41:03 +0900476 config,
Jaewan Kim61f86142023-03-28 15:12:52 +0900477 debug_config,
Andrew Walbrancc0db522021-07-12 17:03:42 +0000478 apk_file,
479 idsig_file,
Jooyung Han9d7cd7d2021-10-12 17:44:14 +0900480 vm_payload_config,
Andrew Walbrancc0db522021-07-12 17:03:42 +0000481 temporary_directory,
482 )?);
483
Andrew Walbrancc0db522021-07-12 17:03:42 +0000484 Ok(())
485}
Jooyung Han5e0f2062021-10-12 14:00:46 +0900486
487#[cfg(test)]
488mod tests {
489 use super::*;
Jooyung Han743e0d62022-11-07 20:57:48 +0900490 use tempfile::NamedTempFile;
Andrew Walbranc1a5f5a2022-01-19 13:38:13 +0000491
Jooyung Han5e0f2062021-10-12 14:00:46 +0900492 #[test]
Alan Stokesbf20c6a2022-01-04 12:30:50 +0000493 fn test_find_apex_names_in_classpath() {
494 let vars = r#"
495export FOO /apex/unterminated
496export BAR /apex/valid.apex/something
497wrong
498export EMPTY
499export OTHER /foo/bar:/baz:/apex/second.valid.apex/:gibberish:"#;
500 let expected = vec!["valid.apex", "second.valid.apex"];
501 let expected: HashSet<_> = expected.into_iter().map(ToString::to_string).collect();
502
503 assert_eq!(find_apex_names_in_classpath(vars).unwrap(), expected);
Jooyung Han5e0f2062021-10-12 14:00:46 +0900504 }
Andrew Walbranc1a5f5a2022-01-19 13:38:13 +0000505
506 #[test]
Nikita Ioffed4551e12023-07-14 16:01:03 +0100507 fn test_collect_apexes() -> Result<()> {
Jooyung Hanec788042022-01-27 22:28:37 +0900508 let apex_info_list = ApexInfoList {
Andrew Walbranc1a5f5a2022-01-19 13:38:13 +0000509 list: vec![
510 ApexInfo {
Jooyung Hanec788042022-01-27 22:28:37 +0900511 // 0
512 name: "com.android.adbd".to_string(),
513 path: PathBuf::from("adbd"),
Nikita Ioffed4551e12023-07-14 16:01:03 +0100514 preinstalled_path: PathBuf::from("/system/adbd"),
Andrew Walbranc1a5f5a2022-01-19 13:38:13 +0000515 has_classpath_jar: false,
Andrew Walbran40be9d52022-01-19 14:32:53 +0000516 last_update_seconds: 12345678,
Jiyong Parkd6502352022-01-27 01:07:30 +0900517 is_factory: true,
Jooyung Hanec788042022-01-27 22:28:37 +0900518 is_active: true,
Jooyung Han5ce867a2022-01-28 03:18:38 +0900519 ..Default::default()
Andrew Walbranc1a5f5a2022-01-19 13:38:13 +0000520 },
521 ApexInfo {
Jooyung Hanec788042022-01-27 22:28:37 +0900522 // 1
523 name: "com.android.os.statsd".to_string(),
524 path: PathBuf::from("statsd"),
Nikita Ioffed4551e12023-07-14 16:01:03 +0100525 preinstalled_path: PathBuf::from("/system/statsd"),
Jooyung Hanec788042022-01-27 22:28:37 +0900526 has_classpath_jar: false,
527 last_update_seconds: 12345678,
528 is_factory: true,
529 is_active: false,
Jooyung Han5ce867a2022-01-28 03:18:38 +0900530 ..Default::default()
Jooyung Hanec788042022-01-27 22:28:37 +0900531 },
532 ApexInfo {
533 // 2
534 name: "com.android.os.statsd".to_string(),
535 path: PathBuf::from("statsd/updated"),
Nikita Ioffed4551e12023-07-14 16:01:03 +0100536 preinstalled_path: PathBuf::from("/system/statsd"),
Jooyung Hanec788042022-01-27 22:28:37 +0900537 has_classpath_jar: false,
538 last_update_seconds: 12345678 + 1,
539 is_factory: false,
540 is_active: true,
Jooyung Han5ce867a2022-01-28 03:18:38 +0900541 ..Default::default()
Jooyung Hanec788042022-01-27 22:28:37 +0900542 },
543 ApexInfo {
544 // 3
545 name: "no_classpath".to_string(),
546 path: PathBuf::from("no_classpath"),
547 has_classpath_jar: false,
548 last_update_seconds: 12345678,
549 is_factory: true,
550 is_active: true,
Jooyung Han5ce867a2022-01-28 03:18:38 +0900551 ..Default::default()
Jooyung Hanec788042022-01-27 22:28:37 +0900552 },
553 ApexInfo {
554 // 4
Andrew Walbranc1a5f5a2022-01-19 13:38:13 +0000555 name: "has_classpath".to_string(),
Jooyung Hanec788042022-01-27 22:28:37 +0900556 path: PathBuf::from("has_classpath"),
Andrew Walbranc1a5f5a2022-01-19 13:38:13 +0000557 has_classpath_jar: true,
Andrew Walbran40be9d52022-01-19 14:32:53 +0000558 last_update_seconds: 87654321,
Jooyung Hanec788042022-01-27 22:28:37 +0900559 is_factory: true,
560 is_active: false,
Jooyung Han5ce867a2022-01-28 03:18:38 +0900561 ..Default::default()
Jooyung Hanec788042022-01-27 22:28:37 +0900562 },
563 ApexInfo {
564 // 5
565 name: "has_classpath".to_string(),
566 path: PathBuf::from("has_classpath/updated"),
Nikita Ioffed4551e12023-07-14 16:01:03 +0100567 preinstalled_path: PathBuf::from("/system/has_classpath"),
Jooyung Hanec788042022-01-27 22:28:37 +0900568 has_classpath_jar: true,
569 last_update_seconds: 87654321 + 1,
Jiyong Parkd6502352022-01-27 01:07:30 +0900570 is_factory: false,
Jooyung Hanec788042022-01-27 22:28:37 +0900571 is_active: true,
Jooyung Han5ce867a2022-01-28 03:18:38 +0900572 ..Default::default()
Jooyung Hanec788042022-01-27 22:28:37 +0900573 },
574 ApexInfo {
575 // 6
576 name: "apex-foo".to_string(),
577 path: PathBuf::from("apex-foo"),
Nikita Ioffed4551e12023-07-14 16:01:03 +0100578 preinstalled_path: PathBuf::from("/system/apex-foo"),
Jooyung Hanec788042022-01-27 22:28:37 +0900579 has_classpath_jar: false,
580 last_update_seconds: 87654321,
581 is_factory: true,
582 is_active: false,
Jooyung Han5ce867a2022-01-28 03:18:38 +0900583 ..Default::default()
Jooyung Hanec788042022-01-27 22:28:37 +0900584 },
585 ApexInfo {
586 // 7
587 name: "apex-foo".to_string(),
588 path: PathBuf::from("apex-foo/updated"),
Nikita Ioffed4551e12023-07-14 16:01:03 +0100589 preinstalled_path: PathBuf::from("/system/apex-foo"),
Jooyung Hanec788042022-01-27 22:28:37 +0900590 has_classpath_jar: false,
591 last_update_seconds: 87654321 + 1,
592 is_factory: false,
593 is_active: true,
Jooyung Han5ce867a2022-01-28 03:18:38 +0900594 ..Default::default()
595 },
596 ApexInfo {
597 // 8
598 name: "sharedlibs".to_string(),
599 path: PathBuf::from("apex-foo"),
Nikita Ioffed4551e12023-07-14 16:01:03 +0100600 preinstalled_path: PathBuf::from("/system/apex-foo"),
Jooyung Han5ce867a2022-01-28 03:18:38 +0900601 last_update_seconds: 87654321,
602 is_factory: true,
603 provide_shared_apex_libs: true,
604 ..Default::default()
605 },
606 ApexInfo {
607 // 9
608 name: "sharedlibs".to_string(),
609 path: PathBuf::from("apex-foo/updated"),
Nikita Ioffed4551e12023-07-14 16:01:03 +0100610 preinstalled_path: PathBuf::from("/system/apex-foo"),
Jooyung Han5ce867a2022-01-28 03:18:38 +0900611 last_update_seconds: 87654321 + 1,
612 is_active: true,
613 provide_shared_apex_libs: true,
614 ..Default::default()
Andrew Walbranc1a5f5a2022-01-19 13:38:13 +0000615 },
616 ],
617 };
Jooyung Hanec788042022-01-27 22:28:37 +0900618 let apex_configs = vec![
619 ApexConfig { name: "apex-foo".to_string() },
Andrew Walbranc1a5f5a2022-01-19 13:38:13 +0000620 ApexConfig { name: "{CLASSPATH}".to_string() },
621 ];
622 assert_eq!(
Nikita Ioffed4551e12023-07-14 16:01:03 +0100623 collect_apex_infos(
624 &apex_info_list,
625 &apex_configs,
626 &DebugConfig::new(DebugLevel::FULL)
627 )?,
Andrew Walbranc1a5f5a2022-01-19 13:38:13 +0000628 vec![
Jooyung Han5ce867a2022-01-28 03:18:38 +0900629 // Pass active/required APEXes
Jooyung Hanec788042022-01-27 22:28:37 +0900630 &apex_info_list.list[0],
631 &apex_info_list.list[2],
Jooyung Han5ce867a2022-01-28 03:18:38 +0900632 // Pass active APEXes specified in the config
Jooyung Hanec788042022-01-27 22:28:37 +0900633 &apex_info_list.list[5],
634 &apex_info_list.list[7],
Jooyung Han5ce867a2022-01-28 03:18:38 +0900635 // Pass both preinstalled(inactive) and updated(active) for "sharedlibs" APEXes
636 &apex_info_list.list[8],
637 &apex_info_list.list[9],
Andrew Walbranc1a5f5a2022-01-19 13:38:13 +0000638 ]
639 );
Nikita Ioffed4551e12023-07-14 16:01:03 +0100640 Ok(())
641 }
642
643 #[test]
644 fn test_check_allowed_partitions_vendor_not_allowed() -> Result<()> {
645 let apex_info_list = ApexInfoList {
646 list: vec![ApexInfo {
647 name: "apex-vendor".to_string(),
648 path: PathBuf::from("apex-vendor"),
649 preinstalled_path: PathBuf::from("/vendor/apex-vendor"),
650 is_active: true,
651 ..Default::default()
652 }],
653 };
654 let apex_configs = vec![ApexConfig { name: "apex-vendor".to_string() }];
655
656 let ret =
657 collect_apex_infos(&apex_info_list, &apex_configs, &DebugConfig::new(DebugLevel::NONE));
658 assert!(ret
659 .is_err_and(|ret| ret.to_string()
660 == "Non-system APEX apex-vendor is not supported in Microdroid"));
661
662 Ok(())
663 }
664
665 #[test]
666 fn test_check_allowed_partitions_system_ext_allowed() -> Result<()> {
667 let apex_info_list = ApexInfoList {
668 list: vec![ApexInfo {
669 name: "apex-system_ext".to_string(),
670 path: PathBuf::from("apex-system_ext"),
671 preinstalled_path: PathBuf::from("/system_ext/apex-system_ext"),
672 is_active: true,
673 ..Default::default()
674 }],
675 };
676
677 let apex_configs = vec![ApexConfig { name: "apex-system_ext".to_string() }];
678
679 assert_eq!(
680 collect_apex_infos(
681 &apex_info_list,
682 &apex_configs,
683 &DebugConfig::new(DebugLevel::NONE)
684 )?,
685 vec![&apex_info_list.list[0]]
686 );
687
688 Ok(())
Andrew Walbranc1a5f5a2022-01-19 13:38:13 +0000689 }
Jooyung Han743e0d62022-11-07 20:57:48 +0900690
691 #[test]
692 fn test_prefer_staged_apex_with_factory_active_apex() {
693 let single_apex = ApexInfo {
694 name: "foo".to_string(),
Jooyung Hancaa995c2022-11-08 16:35:50 +0900695 version: 1,
Jooyung Han743e0d62022-11-07 20:57:48 +0900696 path: PathBuf::from("foo.apex"),
697 is_factory: true,
698 is_active: true,
699 ..Default::default()
700 };
701 let mut apex_info_list = ApexInfoList { list: vec![single_apex.clone()] };
702
703 let staged = NamedTempFile::new().unwrap();
704 apex_info_list
705 .override_staged_apex(&StagedApexInfo {
706 moduleName: "foo".to_string(),
Jooyung Hancaa995c2022-11-08 16:35:50 +0900707 versionCode: 2,
Jooyung Han743e0d62022-11-07 20:57:48 +0900708 diskImagePath: staged.path().to_string_lossy().to_string(),
709 ..Default::default()
710 })
711 .expect("should be ok");
712
713 assert_eq!(
714 apex_info_list,
715 ApexInfoList {
716 list: vec![
717 ApexInfo {
Jooyung Hancaa995c2022-11-08 16:35:50 +0900718 version: 2,
Jooyung Han743e0d62022-11-07 20:57:48 +0900719 is_factory: false,
720 path: staged.path().to_owned(),
721 last_update_seconds: last_updated(staged.path()).unwrap(),
722 ..single_apex.clone()
723 },
724 ApexInfo { is_active: false, ..single_apex },
725 ],
726 }
727 );
728 }
729
730 #[test]
731 fn test_prefer_staged_apex_with_factory_and_inactive_apex() {
732 let factory_apex = ApexInfo {
733 name: "foo".to_string(),
Jooyung Hancaa995c2022-11-08 16:35:50 +0900734 version: 1,
Jooyung Han743e0d62022-11-07 20:57:48 +0900735 path: PathBuf::from("foo.apex"),
736 is_factory: true,
737 ..Default::default()
738 };
739 let active_apex = ApexInfo {
740 name: "foo".to_string(),
Jooyung Hancaa995c2022-11-08 16:35:50 +0900741 version: 2,
Jooyung Han743e0d62022-11-07 20:57:48 +0900742 path: PathBuf::from("foo.downloaded.apex"),
743 is_active: true,
744 ..Default::default()
745 };
746 let mut apex_info_list =
747 ApexInfoList { list: vec![factory_apex.clone(), active_apex.clone()] };
748
749 let staged = NamedTempFile::new().unwrap();
750 apex_info_list
751 .override_staged_apex(&StagedApexInfo {
752 moduleName: "foo".to_string(),
Jooyung Hancaa995c2022-11-08 16:35:50 +0900753 versionCode: 3,
Jooyung Han743e0d62022-11-07 20:57:48 +0900754 diskImagePath: staged.path().to_string_lossy().to_string(),
755 ..Default::default()
756 })
757 .expect("should be ok");
758
759 assert_eq!(
760 apex_info_list,
761 ApexInfoList {
762 list: vec![
763 // factory apex isn't touched
764 factory_apex,
765 // update active one
766 ApexInfo {
Jooyung Hancaa995c2022-11-08 16:35:50 +0900767 version: 3,
Jooyung Han743e0d62022-11-07 20:57:48 +0900768 path: staged.path().to_owned(),
769 last_update_seconds: last_updated(staged.path()).unwrap(),
770 ..active_apex
771 },
772 ],
773 }
774 );
775 }
Jooyung Han5e0f2062021-10-12 14:00:46 +0900776}