blob: 1df537cd3a6bf0c8a038a11abbf839acd573be56 [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
17use crate::composite::align_to_partition_size;
18
Jooyung Han44b02ab2021-07-16 03:19:13 +090019use anyhow::{anyhow, Context, Result};
Jooyung Han21e9b922021-06-26 04:14:16 +090020use microdroid_metadata::{ApexPayload, ApkPayload, Metadata};
21use microdroid_payload_config::ApexConfig;
Jooyung Han9900f3d2021-07-06 10:27:54 +090022use once_cell::sync::OnceCell;
Jooyung Han44b02ab2021-07-16 03:19:13 +090023use serde::Deserialize;
24use serde_xml_rs::from_reader;
Jooyung Han21e9b922021-06-26 04:14:16 +090025use std::fs;
Jooyung Han44b02ab2021-07-16 03:19:13 +090026use std::fs::{File, OpenOptions};
27use std::io::{Seek, SeekFrom, Write};
Jooyung Han21e9b922021-06-26 04:14:16 +090028use std::path::{Path, PathBuf};
Jooyung Han21e9b922021-06-26 04:14:16 +090029use vmconfig::{DiskImage, Partition};
30
Jooyung Han44b02ab2021-07-16 03:19:13 +090031const APEX_INFO_LIST_PATH: &str = "/apex/apex-info-list.xml";
32
Jooyung Han73bac242021-07-02 10:25:49 +090033/// Represents the list of APEXes
Jooyung Han44b02ab2021-07-16 03:19:13 +090034#[derive(Debug, Deserialize)]
Jooyung Han9900f3d2021-07-06 10:27:54 +090035struct ApexInfoList {
Jooyung Han44b02ab2021-07-16 03:19:13 +090036 #[serde(rename = "apex-info")]
Jooyung Han73bac242021-07-02 10:25:49 +090037 list: Vec<ApexInfo>,
38}
39
Jooyung Han44b02ab2021-07-16 03:19:13 +090040#[derive(Debug, Deserialize)]
Jooyung Han73bac242021-07-02 10:25:49 +090041struct ApexInfo {
Jooyung Han44b02ab2021-07-16 03:19:13 +090042 #[serde(rename = "moduleName")]
Jooyung Han73bac242021-07-02 10:25:49 +090043 name: String,
Jooyung Han44b02ab2021-07-16 03:19:13 +090044 #[serde(rename = "modulePath")]
Jooyung Han73bac242021-07-02 10:25:49 +090045 path: PathBuf,
46}
47
48impl ApexInfoList {
49 /// Loads ApexInfoList
Jooyung Han9900f3d2021-07-06 10:27:54 +090050 fn load() -> Result<&'static ApexInfoList> {
51 static INSTANCE: OnceCell<ApexInfoList> = OnceCell::new();
52 INSTANCE.get_or_try_init(|| {
Jooyung Han44b02ab2021-07-16 03:19:13 +090053 let apex_info_list = File::open(APEX_INFO_LIST_PATH)
54 .context(format!("Failed to open {}", APEX_INFO_LIST_PATH))?;
55 let apex_info_list: ApexInfoList = from_reader(apex_info_list)
56 .context(format!("Failed to parse {}", APEX_INFO_LIST_PATH))?;
57 Ok(apex_info_list)
Jooyung Han9900f3d2021-07-06 10:27:54 +090058 })
Jooyung Han73bac242021-07-02 10:25:49 +090059 }
60
61 fn get_path_for(&self, apex_name: &str) -> Result<PathBuf> {
62 Ok(self
63 .list
64 .iter()
65 .find(|apex| apex.name == apex_name)
66 .ok_or_else(|| anyhow!("{} not found.", apex_name))?
67 .path
68 .clone())
69 }
Jooyung Han21e9b922021-06-26 04:14:16 +090070}
71
72/// When passing a host APEX file as a block device in a payload disk image,
73/// the size of the original file needs to be stored in the last 4 bytes so that
74/// other programs (e.g. apexd) can read it as a zip.
Jooyung Han35edb8f2021-07-01 16:17:16 +090075/// Returns true always since the filler is created.
Jooyung Han21e9b922021-06-26 04:14:16 +090076fn make_size_filler(size: u64, filler_path: &Path) -> Result<bool> {
77 let partition_size = align_to_partition_size(size + 4);
78 let mut file = OpenOptions::new().create_new(true).write(true).open(filler_path)?;
79 file.set_len(partition_size - size)?;
80 file.seek(SeekFrom::End(-4))?;
81 file.write_all(&(size as i32).to_be_bytes())?;
82 Ok(true)
83}
84
85/// When passing a host APK file as a block device in a payload disk image and it is
86/// mounted via dm-verity, we need to make the device zero-padded up to 4K boundary.
Jooyung Han35edb8f2021-07-01 16:17:16 +090087/// Otherwise, integrity checks via hashtree will fail.
88/// Returns true if filler is created.
Jooyung Han21e9b922021-06-26 04:14:16 +090089fn make_zero_filler(size: u64, filler_path: &Path) -> Result<bool> {
90 let partition_size = align_to_partition_size(size);
91 if partition_size <= size {
92 return Ok(false);
93 }
94 let file = OpenOptions::new().create_new(true).write(true).open(filler_path)?;
95 file.set_len(partition_size - size)?;
96 Ok(true)
97}
98
99/// When passing a host idsig file as a block device, we don't need any filler because it is read
100/// in length-prefixed way.
Jooyung Han35edb8f2021-07-01 16:17:16 +0900101/// Returns false always because the filler is not created.
Jooyung Han21e9b922021-06-26 04:14:16 +0900102fn make_no_filler(_size: u64, _filler_path: &Path) -> Result<bool> {
103 Ok(false)
104}
105
106/// Creates a DiskImage with partitions:
107/// metadata: metadata
108/// microdroid-apex-0: [apex 0, size filler]
109/// microdroid-apex-1: [apex 1, size filler]
110/// ..
111/// microdroid-apk: [apk, zero filler]
112/// microdroid-apk-idsig: idsig
Jooyung Han73bac242021-07-02 10:25:49 +0900113pub fn make_payload_disk(
Jooyung Han21e9b922021-06-26 04:14:16 +0900114 apk_file: PathBuf,
115 idsig_file: PathBuf,
116 config_path: &str,
117 apexes: &[ApexConfig],
118 temporary_directory: &Path,
119) -> Result<DiskImage> {
120 let metadata_path = temporary_directory.join("metadata");
121 let metadata = Metadata {
122 version: 1u32,
123 apexes: apexes
124 .iter()
Jooyung Han35edb8f2021-07-01 16:17:16 +0900125 .map(|apex| ApexPayload { name: apex.name.clone(), ..Default::default() })
Jooyung Han21e9b922021-06-26 04:14:16 +0900126 .collect(),
127 apk: Some(ApkPayload {
Jooyung Han35edb8f2021-07-01 16:17:16 +0900128 name: "apk".to_owned(),
129 payload_partition_name: "microdroid-apk".to_owned(),
130 idsig_partition_name: "microdroid-apk-idsig".to_owned(),
Jooyung Han21e9b922021-06-26 04:14:16 +0900131 ..Default::default()
132 })
133 .into(),
134 payload_config_path: format!("/mnt/apk/{}", config_path),
135 ..Default::default()
136 };
137 let mut metadata_file =
138 OpenOptions::new().create_new(true).read(true).write(true).open(&metadata_path)?;
139 microdroid_metadata::write_metadata(&metadata, &mut metadata_file)?;
140
141 // put metadata at the first partition
142 let mut partitions = vec![Partition {
Jooyung Han14e5a8e2021-07-06 20:48:38 +0900143 label: "payload-metadata".to_owned(),
Jooyung Han54f422f2021-07-01 00:37:19 +0900144 paths: vec![metadata_path],
Jooyung Han21e9b922021-06-26 04:14:16 +0900145 writable: false,
146 }];
147
Jooyung Han9900f3d2021-07-06 10:27:54 +0900148 let apex_info_list = ApexInfoList::load()?;
Jooyung Han21e9b922021-06-26 04:14:16 +0900149 let mut filler_count = 0;
Jooyung Han21e9b922021-06-26 04:14:16 +0900150 for (i, apex) in apexes.iter().enumerate() {
151 partitions.push(make_partition(
152 format!("microdroid-apex-{}", i),
Jooyung Han73bac242021-07-02 10:25:49 +0900153 apex_info_list.get_path_for(&apex.name)?,
Jooyung Han35edb8f2021-07-01 16:17:16 +0900154 temporary_directory,
155 &mut filler_count,
Jooyung Han21e9b922021-06-26 04:14:16 +0900156 &make_size_filler,
157 )?);
158 }
Jooyung Han21e9b922021-06-26 04:14:16 +0900159 partitions.push(make_partition(
Jooyung Han35edb8f2021-07-01 16:17:16 +0900160 "microdroid-apk".to_owned(),
161 apk_file,
162 temporary_directory,
163 &mut filler_count,
164 &make_zero_filler,
165 )?);
166 partitions.push(make_partition(
167 "microdroid-apk-idsig".to_owned(),
Jooyung Han21e9b922021-06-26 04:14:16 +0900168 idsig_file,
Jooyung Han35edb8f2021-07-01 16:17:16 +0900169 temporary_directory,
170 &mut filler_count,
Jooyung Han21e9b922021-06-26 04:14:16 +0900171 &make_no_filler,
172 )?);
173
174 Ok(DiskImage { image: None, partitions, writable: false })
175}
Jooyung Han35edb8f2021-07-01 16:17:16 +0900176
177fn make_partition(
178 label: String,
179 path: PathBuf,
180 temporary_directory: &Path,
181 filler_count: &mut u32,
182 make_filler: &dyn Fn(u64, &Path) -> Result<bool>,
183) -> Result<Partition> {
184 let filler_path = temporary_directory.join(format!("filler-{}", filler_count));
185 let size = fs::metadata(&path)?.len();
186
187 if make_filler(size, &filler_path)? {
188 *filler_count += 1;
189 Ok(Partition { label, paths: vec![path, filler_path], writable: false })
190 } else {
191 Ok(Partition { label, paths: vec![path], writable: false })
192 }
193}