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