blob: 18e7de51909e2bedd94bb7b507c44674f2c17dd1 [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 Han9900f3d2021-07-06 10:27:54 +090019use anyhow::{anyhow, bail, 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 Han73bac242021-07-02 10:25:49 +090023use regex::Regex;
Jooyung Han21e9b922021-06-26 04:14:16 +090024use std::fs;
25use std::fs::OpenOptions;
Jooyung Han73bac242021-07-02 10:25:49 +090026use std::io::{BufRead, BufReader, Seek, SeekFrom, Write};
Jooyung Han21e9b922021-06-26 04:14:16 +090027use std::path::{Path, PathBuf};
28use std::process::Command;
29use vmconfig::{DiskImage, Partition};
30
Jooyung Han73bac242021-07-02 10:25:49 +090031/// Represents the list of APEXes
32#[derive(Debug)]
Jooyung Han9900f3d2021-07-06 10:27:54 +090033struct ApexInfoList {
Jooyung Han73bac242021-07-02 10:25:49 +090034 list: Vec<ApexInfo>,
35}
36
37#[derive(Debug)]
38struct ApexInfo {
39 name: String,
40 path: PathBuf,
41}
42
43impl ApexInfoList {
44 /// Loads ApexInfoList
Jooyung Han9900f3d2021-07-06 10:27:54 +090045 fn load() -> Result<&'static ApexInfoList> {
46 static INSTANCE: OnceCell<ApexInfoList> = OnceCell::new();
47 INSTANCE.get_or_try_init(|| {
48 // TODO(b/191601801): look up /apex/apex-info-list.xml instead of apexservice
49 // Each APEX prints the line:
50 // Module: <...> Version: <...> VersionName: <...> Path: <...> IsActive: <...> IsFactory: <...>
51 // We only care about "Module:" and "Path:" tagged values for now.
52 let info_pattern =
53 Regex::new(r"^Module: (?P<name>[^ ]*) .* Path: (?P<path>[^ ]*) .*$")?;
54 let output = Command::new("cmd")
55 .arg("-w")
56 .arg("apexservice")
57 .arg("getActivePackages")
58 .output()
59 .expect("failed to execute apexservice cmd");
60 let list = BufReader::new(output.stdout.as_slice())
61 .lines()
62 .map(|line| -> Result<ApexInfo> {
63 let line = line?;
64 let captures = info_pattern
65 .captures(&line)
66 .ok_or_else(|| anyhow!("can't parse: {}", line))?;
67 let name = captures.name("name").unwrap();
68 let path = captures.name("path").unwrap();
69 Ok(ApexInfo { name: name.as_str().to_owned(), path: path.as_str().into() })
70 })
71 .collect::<Result<Vec<ApexInfo>>>()?;
72 if list.is_empty() {
73 bail!("failed to load apex info: empty");
74 }
75 Ok(ApexInfoList { list })
76 })
Jooyung Han73bac242021-07-02 10:25:49 +090077 }
78
79 fn get_path_for(&self, apex_name: &str) -> Result<PathBuf> {
80 Ok(self
81 .list
82 .iter()
83 .find(|apex| apex.name == apex_name)
84 .ok_or_else(|| anyhow!("{} not found.", apex_name))?
85 .path
86 .clone())
87 }
Jooyung Han21e9b922021-06-26 04:14:16 +090088}
89
90/// When passing a host APEX file as a block device in a payload disk image,
91/// the size of the original file needs to be stored in the last 4 bytes so that
92/// other programs (e.g. apexd) can read it as a zip.
Jooyung Han35edb8f2021-07-01 16:17:16 +090093/// Returns true always since the filler is created.
Jooyung Han21e9b922021-06-26 04:14:16 +090094fn make_size_filler(size: u64, filler_path: &Path) -> Result<bool> {
95 let partition_size = align_to_partition_size(size + 4);
96 let mut file = OpenOptions::new().create_new(true).write(true).open(filler_path)?;
97 file.set_len(partition_size - size)?;
98 file.seek(SeekFrom::End(-4))?;
99 file.write_all(&(size as i32).to_be_bytes())?;
100 Ok(true)
101}
102
103/// When passing a host APK file as a block device in a payload disk image and it is
104/// mounted via dm-verity, we need to make the device zero-padded up to 4K boundary.
Jooyung Han35edb8f2021-07-01 16:17:16 +0900105/// Otherwise, integrity checks via hashtree will fail.
106/// Returns true if filler is created.
Jooyung Han21e9b922021-06-26 04:14:16 +0900107fn make_zero_filler(size: u64, filler_path: &Path) -> Result<bool> {
108 let partition_size = align_to_partition_size(size);
109 if partition_size <= size {
110 return Ok(false);
111 }
112 let file = OpenOptions::new().create_new(true).write(true).open(filler_path)?;
113 file.set_len(partition_size - size)?;
114 Ok(true)
115}
116
117/// When passing a host idsig file as a block device, we don't need any filler because it is read
118/// in length-prefixed way.
Jooyung Han35edb8f2021-07-01 16:17:16 +0900119/// Returns false always because the filler is not created.
Jooyung Han21e9b922021-06-26 04:14:16 +0900120fn make_no_filler(_size: u64, _filler_path: &Path) -> Result<bool> {
121 Ok(false)
122}
123
124/// Creates a DiskImage with partitions:
125/// metadata: metadata
126/// microdroid-apex-0: [apex 0, size filler]
127/// microdroid-apex-1: [apex 1, size filler]
128/// ..
129/// microdroid-apk: [apk, zero filler]
130/// microdroid-apk-idsig: idsig
Jooyung Han73bac242021-07-02 10:25:49 +0900131pub fn make_payload_disk(
Jooyung Han21e9b922021-06-26 04:14:16 +0900132 apk_file: PathBuf,
133 idsig_file: PathBuf,
134 config_path: &str,
135 apexes: &[ApexConfig],
136 temporary_directory: &Path,
137) -> Result<DiskImage> {
138 let metadata_path = temporary_directory.join("metadata");
139 let metadata = Metadata {
140 version: 1u32,
141 apexes: apexes
142 .iter()
Jooyung Han35edb8f2021-07-01 16:17:16 +0900143 .map(|apex| ApexPayload { name: apex.name.clone(), ..Default::default() })
Jooyung Han21e9b922021-06-26 04:14:16 +0900144 .collect(),
145 apk: Some(ApkPayload {
Jooyung Han35edb8f2021-07-01 16:17:16 +0900146 name: "apk".to_owned(),
147 payload_partition_name: "microdroid-apk".to_owned(),
148 idsig_partition_name: "microdroid-apk-idsig".to_owned(),
Jooyung Han21e9b922021-06-26 04:14:16 +0900149 ..Default::default()
150 })
151 .into(),
152 payload_config_path: format!("/mnt/apk/{}", config_path),
153 ..Default::default()
154 };
155 let mut metadata_file =
156 OpenOptions::new().create_new(true).read(true).write(true).open(&metadata_path)?;
157 microdroid_metadata::write_metadata(&metadata, &mut metadata_file)?;
158
159 // put metadata at the first partition
160 let mut partitions = vec![Partition {
Jooyung Han35edb8f2021-07-01 16:17:16 +0900161 label: "metadata".to_owned(),
Jooyung Han54f422f2021-07-01 00:37:19 +0900162 paths: vec![metadata_path],
Jooyung Han21e9b922021-06-26 04:14:16 +0900163 writable: false,
164 }];
165
Jooyung Han9900f3d2021-07-06 10:27:54 +0900166 let apex_info_list = ApexInfoList::load()?;
Jooyung Han21e9b922021-06-26 04:14:16 +0900167 let mut filler_count = 0;
Jooyung Han21e9b922021-06-26 04:14:16 +0900168 for (i, apex) in apexes.iter().enumerate() {
169 partitions.push(make_partition(
170 format!("microdroid-apex-{}", i),
Jooyung Han73bac242021-07-02 10:25:49 +0900171 apex_info_list.get_path_for(&apex.name)?,
Jooyung Han35edb8f2021-07-01 16:17:16 +0900172 temporary_directory,
173 &mut filler_count,
Jooyung Han21e9b922021-06-26 04:14:16 +0900174 &make_size_filler,
175 )?);
176 }
Jooyung Han21e9b922021-06-26 04:14:16 +0900177 partitions.push(make_partition(
Jooyung Han35edb8f2021-07-01 16:17:16 +0900178 "microdroid-apk".to_owned(),
179 apk_file,
180 temporary_directory,
181 &mut filler_count,
182 &make_zero_filler,
183 )?);
184 partitions.push(make_partition(
185 "microdroid-apk-idsig".to_owned(),
Jooyung Han21e9b922021-06-26 04:14:16 +0900186 idsig_file,
Jooyung Han35edb8f2021-07-01 16:17:16 +0900187 temporary_directory,
188 &mut filler_count,
Jooyung Han21e9b922021-06-26 04:14:16 +0900189 &make_no_filler,
190 )?);
191
192 Ok(DiskImage { image: None, partitions, writable: false })
193}
Jooyung Han35edb8f2021-07-01 16:17:16 +0900194
195fn make_partition(
196 label: String,
197 path: PathBuf,
198 temporary_directory: &Path,
199 filler_count: &mut u32,
200 make_filler: &dyn Fn(u64, &Path) -> Result<bool>,
201) -> Result<Partition> {
202 let filler_path = temporary_directory.join(format!("filler-{}", filler_count));
203 let size = fs::metadata(&path)?.len();
204
205 if make_filler(size, &filler_path)? {
206 *filler_count += 1;
207 Ok(Partition { label, paths: vec![path, filler_path], writable: false })
208 } else {
209 Ok(Partition { label, paths: vec![path], writable: false })
210 }
211}