blob: d3dc2897849bebc76c064f9d0d2ea7d51c4399c9 [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 Han73bac242021-07-02 10:25:49 +090019use anyhow::{anyhow, Result};
Jooyung Han21e9b922021-06-26 04:14:16 +090020use microdroid_metadata::{ApexPayload, ApkPayload, Metadata};
21use microdroid_payload_config::ApexConfig;
Jooyung Han73bac242021-07-02 10:25:49 +090022use regex::Regex;
Jooyung Han21e9b922021-06-26 04:14:16 +090023use std::fs;
24use std::fs::OpenOptions;
Jooyung Han73bac242021-07-02 10:25:49 +090025use std::io::{BufRead, BufReader, Seek, SeekFrom, Write};
Jooyung Han21e9b922021-06-26 04:14:16 +090026use std::path::{Path, PathBuf};
27use std::process::Command;
28use vmconfig::{DiskImage, Partition};
29
Jooyung Han73bac242021-07-02 10:25:49 +090030/// Represents the list of APEXes
31#[derive(Debug)]
32pub struct ApexInfoList {
33 list: Vec<ApexInfo>,
34}
35
36#[derive(Debug)]
37struct ApexInfo {
38 name: String,
39 path: PathBuf,
40}
41
42impl ApexInfoList {
43 /// Loads ApexInfoList
44 pub fn load() -> Result<ApexInfoList> {
45 // TODO(b/191601801): look up /apex/apex-info-list.xml instead of apexservice
46 // Each APEX prints the line:
47 // Module: <...> Version: <...> VersionName: <...> Path: <...> IsActive: <...> IsFactory: <...>
48 // We only care about "Module:" and "Path:" tagged values for now.
49 let info_pattern = Regex::new(r"^Module: (?P<name>[^ ]*) .* Path: (?P<path>[^ ]*) .*$")?;
50 let output = Command::new("cmd")
51 .arg("-w")
52 .arg("apexservice")
53 .arg("getActivePackages")
54 .output()
55 .expect("failed to execute apexservice cmd");
56 let list = BufReader::new(output.stdout.as_slice())
57 .lines()
58 .map(|line| -> Result<ApexInfo> {
59 let line = line?;
60 let captures =
61 info_pattern.captures(&line).ok_or_else(|| anyhow!("can't parse: {}", line))?;
62 let name = captures.name("name").unwrap();
63 let path = captures.name("path").unwrap();
64 Ok(ApexInfo { name: name.as_str().to_owned(), path: path.as_str().into() })
65 })
66 .collect::<Result<Vec<ApexInfo>>>()?;
67 Ok(ApexInfoList { list })
68 }
69
70 fn get_path_for(&self, apex_name: &str) -> Result<PathBuf> {
71 Ok(self
72 .list
73 .iter()
74 .find(|apex| apex.name == apex_name)
75 .ok_or_else(|| anyhow!("{} not found.", apex_name))?
76 .path
77 .clone())
78 }
Jooyung Han21e9b922021-06-26 04:14:16 +090079}
80
81/// When passing a host APEX file as a block device in a payload disk image,
82/// the size of the original file needs to be stored in the last 4 bytes so that
83/// other programs (e.g. apexd) can read it as a zip.
Jooyung Han35edb8f2021-07-01 16:17:16 +090084/// Returns true always since the filler is created.
Jooyung Han21e9b922021-06-26 04:14:16 +090085fn make_size_filler(size: u64, filler_path: &Path) -> Result<bool> {
86 let partition_size = align_to_partition_size(size + 4);
87 let mut file = OpenOptions::new().create_new(true).write(true).open(filler_path)?;
88 file.set_len(partition_size - size)?;
89 file.seek(SeekFrom::End(-4))?;
90 file.write_all(&(size as i32).to_be_bytes())?;
91 Ok(true)
92}
93
94/// When passing a host APK file as a block device in a payload disk image and it is
95/// mounted via dm-verity, we need to make the device zero-padded up to 4K boundary.
Jooyung Han35edb8f2021-07-01 16:17:16 +090096/// Otherwise, integrity checks via hashtree will fail.
97/// Returns true if filler is created.
Jooyung Han21e9b922021-06-26 04:14:16 +090098fn make_zero_filler(size: u64, filler_path: &Path) -> Result<bool> {
99 let partition_size = align_to_partition_size(size);
100 if partition_size <= size {
101 return Ok(false);
102 }
103 let file = OpenOptions::new().create_new(true).write(true).open(filler_path)?;
104 file.set_len(partition_size - size)?;
105 Ok(true)
106}
107
108/// When passing a host idsig file as a block device, we don't need any filler because it is read
109/// in length-prefixed way.
Jooyung Han35edb8f2021-07-01 16:17:16 +0900110/// Returns false always because the filler is not created.
Jooyung Han21e9b922021-06-26 04:14:16 +0900111fn make_no_filler(_size: u64, _filler_path: &Path) -> Result<bool> {
112 Ok(false)
113}
114
115/// Creates a DiskImage with partitions:
116/// metadata: metadata
117/// microdroid-apex-0: [apex 0, size filler]
118/// microdroid-apex-1: [apex 1, size filler]
119/// ..
120/// microdroid-apk: [apk, zero filler]
121/// microdroid-apk-idsig: idsig
Jooyung Han73bac242021-07-02 10:25:49 +0900122pub fn make_payload_disk(
123 apex_info_list: &ApexInfoList,
Jooyung Han21e9b922021-06-26 04:14:16 +0900124 apk_file: PathBuf,
125 idsig_file: PathBuf,
126 config_path: &str,
127 apexes: &[ApexConfig],
128 temporary_directory: &Path,
129) -> Result<DiskImage> {
130 let metadata_path = temporary_directory.join("metadata");
131 let metadata = Metadata {
132 version: 1u32,
133 apexes: apexes
134 .iter()
Jooyung Han35edb8f2021-07-01 16:17:16 +0900135 .map(|apex| ApexPayload { name: apex.name.clone(), ..Default::default() })
Jooyung Han21e9b922021-06-26 04:14:16 +0900136 .collect(),
137 apk: Some(ApkPayload {
Jooyung Han35edb8f2021-07-01 16:17:16 +0900138 name: "apk".to_owned(),
139 payload_partition_name: "microdroid-apk".to_owned(),
140 idsig_partition_name: "microdroid-apk-idsig".to_owned(),
Jooyung Han21e9b922021-06-26 04:14:16 +0900141 ..Default::default()
142 })
143 .into(),
144 payload_config_path: format!("/mnt/apk/{}", config_path),
145 ..Default::default()
146 };
147 let mut metadata_file =
148 OpenOptions::new().create_new(true).read(true).write(true).open(&metadata_path)?;
149 microdroid_metadata::write_metadata(&metadata, &mut metadata_file)?;
150
151 // put metadata at the first partition
152 let mut partitions = vec![Partition {
Jooyung Han35edb8f2021-07-01 16:17:16 +0900153 label: "metadata".to_owned(),
Jooyung Han54f422f2021-07-01 00:37:19 +0900154 paths: vec![metadata_path],
Jooyung Han21e9b922021-06-26 04:14:16 +0900155 writable: false,
156 }];
157
158 let mut filler_count = 0;
Jooyung Han21e9b922021-06-26 04:14:16 +0900159 for (i, apex) in apexes.iter().enumerate() {
160 partitions.push(make_partition(
161 format!("microdroid-apex-{}", i),
Jooyung Han73bac242021-07-02 10:25:49 +0900162 apex_info_list.get_path_for(&apex.name)?,
Jooyung Han35edb8f2021-07-01 16:17:16 +0900163 temporary_directory,
164 &mut filler_count,
Jooyung Han21e9b922021-06-26 04:14:16 +0900165 &make_size_filler,
166 )?);
167 }
Jooyung Han21e9b922021-06-26 04:14:16 +0900168 partitions.push(make_partition(
Jooyung Han35edb8f2021-07-01 16:17:16 +0900169 "microdroid-apk".to_owned(),
170 apk_file,
171 temporary_directory,
172 &mut filler_count,
173 &make_zero_filler,
174 )?);
175 partitions.push(make_partition(
176 "microdroid-apk-idsig".to_owned(),
Jooyung Han21e9b922021-06-26 04:14:16 +0900177 idsig_file,
Jooyung Han35edb8f2021-07-01 16:17:16 +0900178 temporary_directory,
179 &mut filler_count,
Jooyung Han21e9b922021-06-26 04:14:16 +0900180 &make_no_filler,
181 )?);
182
183 Ok(DiskImage { image: None, partitions, writable: false })
184}
Jooyung Han35edb8f2021-07-01 16:17:16 +0900185
186fn make_partition(
187 label: String,
188 path: PathBuf,
189 temporary_directory: &Path,
190 filler_count: &mut u32,
191 make_filler: &dyn Fn(u64, &Path) -> Result<bool>,
192) -> Result<Partition> {
193 let filler_path = temporary_directory.join(format!("filler-{}", filler_count));
194 let size = fs::metadata(&path)?.len();
195
196 if make_filler(size, &filler_path)? {
197 *filler_count += 1;
198 Ok(Partition { label, paths: vec![path, filler_path], writable: false })
199 } else {
200 Ok(Partition { label, paths: vec![path], writable: false })
201 }
202}