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 //! Functions for creating a composite disk image.
16
17 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::Partition::Partition;
18 use anyhow::{bail, Context, Error};
19 use disk::{create_composite_disk, ImagePartitionType, PartitionInfo};
20 use std::fs::{File, OpenOptions};
21 use std::io::ErrorKind;
22 use std::os::unix::fs::FileExt;
23 use std::os::unix::io::AsRawFd;
24 use std::path::{Path, PathBuf};
25 use zerocopy::AsBytes;
26 use zerocopy::FromBytes;
27 use zerocopy::FromZeroes;
28
29 use uuid::Uuid;
30
31 /// Constructs a composite disk image for the given list of partitions, and opens it ready to use.
32 ///
33 /// Returns the composite disk image file, and a list of files whose file descriptors must be passed
34 /// to any process which wants to use it. This is necessary because the composite image contains
35 /// paths of the form `/proc/self/fd/N` for the partition images.
make_composite_image( partitions: &[Partition], zero_filler_path: &Path, output_path: &Path, header_path: &Path, footer_path: &Path, ) -> Result<(File, Vec<File>), Error>36 pub fn make_composite_image(
37 partitions: &[Partition],
38 zero_filler_path: &Path,
39 output_path: &Path,
40 header_path: &Path,
41 footer_path: &Path,
42 ) -> Result<(File, Vec<File>), Error> {
43 let (partitions, mut files) = convert_partitions(partitions)?;
44
45 let mut composite_image = OpenOptions::new()
46 .create_new(true)
47 .read(true)
48 .write(true)
49 .open(output_path)
50 .with_context(|| format!("Failed to create composite image {:?}", output_path))?;
51 let mut header_file =
52 OpenOptions::new().create_new(true).read(true).write(true).open(header_path).with_context(
53 || format!("Failed to create composite image header {:?}", header_path),
54 )?;
55 let mut footer_file =
56 OpenOptions::new().create_new(true).read(true).write(true).open(footer_path).with_context(
57 || format!("Failed to create composite image header {:?}", footer_path),
58 )?;
59 let zero_filler_file = File::open(zero_filler_path).with_context(|| {
60 format!("Failed to open composite image zero filler {:?}", zero_filler_path)
61 })?;
62
63 create_composite_disk(
64 &partitions,
65 &fd_path_for_file(&zero_filler_file),
66 &fd_path_for_file(&header_file),
67 &mut header_file,
68 &fd_path_for_file(&footer_file),
69 &mut footer_file,
70 &mut composite_image,
71 )?;
72
73 // Re-open the composite image as read-only.
74 let composite_image = File::open(output_path)
75 .with_context(|| format!("Failed to open composite image {:?}", output_path))?;
76
77 files.push(header_file);
78 files.push(footer_file);
79 files.push(zero_filler_file);
80
81 Ok((composite_image, files))
82 }
83
84 /// Given the AIDL config containing a list of partitions, with a [`ParcelFileDescriptor`] for each
85 /// partition, returns the corresponding list of PartitionInfo and the list of files whose file
86 /// descriptors must be passed to any process using the composite image.
convert_partitions(partitions: &[Partition]) -> Result<(Vec<PartitionInfo>, Vec<File>), Error>87 fn convert_partitions(partitions: &[Partition]) -> Result<(Vec<PartitionInfo>, Vec<File>), Error> {
88 // File descriptors to pass to child process.
89 let mut files = vec![];
90
91 let partitions = partitions
92 .iter()
93 .map(|partition| {
94 // TODO(b/187187765): This shouldn't be an Option.
95 let file = partition
96 .image
97 .as_ref()
98 .context("Invalid partition image file descriptor")?
99 .as_ref()
100 .try_clone()
101 .context("Failed to clone partition image file descriptor")?
102 .into();
103 let path = fd_path_for_file(&file);
104 let size = get_partition_size(&file)?;
105 files.push(file);
106
107 Ok(PartitionInfo {
108 label: partition.label.to_owned(),
109 path,
110 partition_type: ImagePartitionType::LinuxFilesystem,
111 writable: partition.writable,
112 size,
113 part_guid: partition.guid.as_deref().map(Uuid::parse_str).transpose()?,
114 })
115 })
116 .collect::<Result<_, Error>>()?;
117
118 Ok((partitions, files))
119 }
120
fd_path_for_file(file: &File) -> PathBuf121 fn fd_path_for_file(file: &File) -> PathBuf {
122 let fd = file.as_raw_fd();
123 format!("/proc/self/fd/{}", fd).into()
124 }
125
126 /// Find the size of the partition image in the given file by parsing the header.
127 ///
128 /// This will work for raw and Android sparse images. QCOW2 and composite images aren't supported.
get_partition_size(file: &File) -> Result<u64, Error>129 fn get_partition_size(file: &File) -> Result<u64, Error> {
130 match detect_image_type(file).context("failed to detect partition image type")? {
131 ImageType::Raw => Ok(file.metadata().context("failed to get metadata")?.len()),
132 ImageType::AndroidSparse => {
133 // Source: system/core/libsparse/sparse_format.h
134 #[repr(C)]
135 #[derive(Clone, Copy, Debug, AsBytes, FromZeroes, FromBytes)]
136 struct SparseHeader {
137 magic: u32,
138 major_version: u16,
139 minor_version: u16,
140 file_hdr_sz: u16,
141 chunk_hdr_size: u16,
142 blk_sz: u32,
143 total_blks: u32,
144 total_chunks: u32,
145 image_checksum: u32,
146 }
147 let mut header = SparseHeader::new_zeroed();
148 file.read_exact_at(header.as_bytes_mut(), 0)
149 .context("failed to read android sparse header")?;
150 let len = u64::from(header.total_blks)
151 .checked_mul(header.blk_sz.into())
152 .context("android sparse image len too big")?;
153 Ok(len)
154 }
155 t => bail!("unsupported partition image type: {t:?}"),
156 }
157 }
158
159 /// Image file types we can detect.
160 #[derive(Debug, PartialEq, Eq)]
161 enum ImageType {
162 Raw,
163 Qcow2,
164 CompositeDisk,
165 AndroidSparse,
166 }
167
168 /// Detect image type by looking for magic bytes.
detect_image_type(file: &File) -> std::io::Result<ImageType>169 fn detect_image_type(file: &File) -> std::io::Result<ImageType> {
170 const CDISK_MAGIC: &str = "composite_disk\x1d";
171 const QCOW_MAGIC: u32 = 0x5146_49fb;
172 const SPARSE_HEADER_MAGIC: u32 = 0xed26ff3a;
173
174 let mut magic4 = [0u8; 4];
175 match file.read_exact_at(&mut magic4[..], 0) {
176 Ok(()) => {}
177 Err(e) if e.kind() == ErrorKind::UnexpectedEof => return Ok(ImageType::Raw),
178 Err(e) => return Err(e),
179 }
180 if magic4 == QCOW_MAGIC.to_be_bytes() {
181 return Ok(ImageType::Qcow2);
182 }
183 if magic4 == SPARSE_HEADER_MAGIC.to_le_bytes() {
184 return Ok(ImageType::AndroidSparse);
185 }
186
187 let mut buf = [0u8; CDISK_MAGIC.len()];
188 match file.read_exact_at(buf.as_bytes_mut(), 0) {
189 Ok(()) => {}
190 Err(e) if e.kind() == ErrorKind::UnexpectedEof => return Ok(ImageType::Raw),
191 Err(e) => return Err(e),
192 }
193 if buf == CDISK_MAGIC.as_bytes() {
194 return Ok(ImageType::CompositeDisk);
195 }
196
197 Ok(ImageType::Raw)
198 }
199