1 // Copyright 2019 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 use std::cmp::min;
6 use std::fmt::Debug;
7 use std::fs::File;
8 use std::io::{self, Read, Seek, SeekFrom};
9 use std::path::Path;
10 use std::sync::Arc;
11
12 use async_trait::async_trait;
13 use base::{
14 get_filesystem_type, info, AsRawDescriptors, FileAllocate, FileReadWriteAtVolatile, FileSetLen,
15 FileSync, PunchHole, WriteZeroesAt,
16 };
17 use cros_async::Executor;
18 use remain::sorted;
19 use thiserror::Error as ThisError;
20 use vm_memory::GuestMemory;
21
22 mod qcow;
23 pub use qcow::{QcowFile, QCOW_MAGIC};
24
25 #[cfg(feature = "composite-disk")]
26 mod composite;
27 #[cfg(feature = "composite-disk")]
28 use composite::{CompositeDiskFile, CDISK_MAGIC, CDISK_MAGIC_LEN};
29 #[cfg(feature = "composite-disk")]
30 mod gpt;
31 #[cfg(feature = "composite-disk")]
32 pub use composite::{
33 create_composite_disk, create_zero_filler, Error as CompositeError, ImagePartitionType,
34 PartitionInfo,
35 };
36 #[cfg(feature = "composite-disk")]
37 pub use gpt::Error as GptError;
38
39 mod android_sparse;
40 use android_sparse::{AndroidSparse, SPARSE_HEADER_MAGIC};
41
42 /// Nesting depth limit for disk formats that can open other disk files.
43 pub const MAX_NESTING_DEPTH: u32 = 10;
44
45 #[sorted]
46 #[derive(ThisError, Debug)]
47 pub enum Error {
48 #[error("failed to create block device: {0}")]
49 BlockDeviceNew(base::Error),
50 #[error("requested file conversion not supported")]
51 ConversionNotSupported,
52 #[error("failure in android sparse disk: {0}")]
53 CreateAndroidSparseDisk(android_sparse::Error),
54 #[cfg(feature = "composite-disk")]
55 #[error("failure in composite disk: {0}")]
56 CreateCompositeDisk(composite::Error),
57 #[error("failure creating single file disk: {0}")]
58 CreateSingleFileDisk(cros_async::AsyncError),
59 #[error("failure with fallocate: {0}")]
60 Fallocate(cros_async::AsyncError),
61 #[error("failure with fsync: {0}")]
62 Fsync(cros_async::AsyncError),
63 #[error("checking host fs type: {0}")]
64 HostFsType(base::Error),
65 #[error("maximum disk nesting depth exceeded")]
66 MaxNestingDepthExceeded,
67 #[error("failure in qcow: {0}")]
68 QcowError(qcow::Error),
69 #[error("failed to read data: {0}")]
70 ReadingData(io::Error),
71 #[error("failed to read header: {0}")]
72 ReadingHeader(io::Error),
73 #[error("failed to read to memory: {0}")]
74 ReadToMem(cros_async::AsyncError),
75 #[error("failed to seek file: {0}")]
76 SeekingFile(io::Error),
77 #[error("failed to set file size: {0}")]
78 SettingFileSize(io::Error),
79 #[error("unknown disk type")]
80 UnknownType,
81 #[error("failed to write from memory: {0}")]
82 WriteFromMem(cros_async::AsyncError),
83 #[error("failed to write from vec: {0}")]
84 WriteFromVec(cros_async::AsyncError),
85 #[error("failed to write data: {0}")]
86 WritingData(io::Error),
87 }
88
89 pub type Result<T> = std::result::Result<T, Error>;
90
91 /// A trait for getting the length of a disk image or raw block device.
92 pub trait DiskGetLen {
93 /// Get the current length of the disk in bytes.
get_len(&self) -> io::Result<u64>94 fn get_len(&self) -> io::Result<u64>;
95 }
96
97 impl DiskGetLen for File {
get_len(&self) -> io::Result<u64>98 fn get_len(&self) -> io::Result<u64> {
99 let mut s = self;
100 let orig_seek = s.seek(SeekFrom::Current(0))?;
101 let end = s.seek(SeekFrom::End(0))? as u64;
102 s.seek(SeekFrom::Start(orig_seek))?;
103 Ok(end)
104 }
105 }
106
107 /// The prerequisites necessary to support a block device.
108 #[rustfmt::skip] // rustfmt won't wrap the long list of trait bounds.
109 pub trait DiskFile:
110 FileSetLen
111 + DiskGetLen
112 + FileSync
113 + FileReadWriteAtVolatile
114 + PunchHole
115 + WriteZeroesAt
116 + FileAllocate
117 + Send
118 + AsRawDescriptors
119 + Debug
120 {
121 }
122 impl<
123 D: FileSetLen
124 + DiskGetLen
125 + FileSync
126 + PunchHole
127 + FileReadWriteAtVolatile
128 + WriteZeroesAt
129 + FileAllocate
130 + Send
131 + AsRawDescriptors
132 + Debug,
133 > DiskFile for D
134 {
135 }
136
137 /// A `DiskFile` that can be converted for asychronous access.
138 pub trait ToAsyncDisk: DiskFile {
139 /// Convert a boxed self in to a box-wrapped implementaiton of AsyncDisk.
140 /// Used to convert a standard disk image to an async disk image. This conversion and the
141 /// inverse are needed so that the `Send` DiskImage can be given to the block thread where it is
142 /// converted to a non-`Send` AsyncDisk. The AsyncDisk can then be converted back and returned
143 /// to the main device thread if the block device is destroyed or reset.
to_async_disk(self: Box<Self>, ex: &Executor) -> Result<Box<dyn AsyncDisk>>144 fn to_async_disk(self: Box<Self>, ex: &Executor) -> Result<Box<dyn AsyncDisk>>;
145 }
146
147 impl ToAsyncDisk for File {
to_async_disk(self: Box<Self>, ex: &Executor) -> Result<Box<dyn AsyncDisk>>148 fn to_async_disk(self: Box<Self>, ex: &Executor) -> Result<Box<dyn AsyncDisk>> {
149 Ok(Box::new(SingleFileDisk::new(*self, ex)?))
150 }
151 }
152
153 /// The variants of image files on the host that can be used as virtual disks.
154 #[derive(Debug, PartialEq, Eq)]
155 pub enum ImageType {
156 Raw,
157 Qcow2,
158 CompositeDisk,
159 AndroidSparse,
160 }
161
log_host_fs_type(file: &File) -> Result<()>162 fn log_host_fs_type(file: &File) -> Result<()> {
163 let fstype = get_filesystem_type(file).map_err(Error::HostFsType)?;
164 info!("Disk image file is hosted on file system type {:x}", fstype);
165 Ok(())
166 }
167
168 /// Detect the type of an image file by checking for a valid header of the supported formats.
detect_image_type(file: &File) -> Result<ImageType>169 pub fn detect_image_type(file: &File) -> Result<ImageType> {
170 let mut f = file;
171 let disk_size = f.get_len().map_err(Error::SeekingFile)?;
172 let orig_seek = f.seek(SeekFrom::Current(0)).map_err(Error::SeekingFile)?;
173 f.seek(SeekFrom::Start(0)).map_err(Error::SeekingFile)?;
174
175 info!("disk size {}, ", disk_size);
176 log_host_fs_type(f)?;
177 // Try to read the disk in a nicely-aligned block size unless the whole file is smaller.
178 const MAGIC_BLOCK_SIZE: usize = 4096;
179 #[repr(align(4096))]
180 struct BlockAlignedBuffer {
181 data: [u8; MAGIC_BLOCK_SIZE],
182 }
183 let mut magic = BlockAlignedBuffer {
184 data: [0u8; MAGIC_BLOCK_SIZE],
185 };
186 let magic_read_len = if disk_size > MAGIC_BLOCK_SIZE as u64 {
187 MAGIC_BLOCK_SIZE
188 } else {
189 // This cast is safe since we know disk_size is less than MAGIC_BLOCK_SIZE (4096) and
190 // therefore is representable in usize.
191 disk_size as usize
192 };
193
194 f.read_exact(&mut magic.data[0..magic_read_len])
195 .map_err(Error::ReadingHeader)?;
196 f.seek(SeekFrom::Start(orig_seek))
197 .map_err(Error::SeekingFile)?;
198
199 #[cfg(feature = "composite-disk")]
200 if let Some(cdisk_magic) = magic.data.get(0..CDISK_MAGIC_LEN) {
201 if cdisk_magic == CDISK_MAGIC.as_bytes() {
202 return Ok(ImageType::CompositeDisk);
203 }
204 }
205
206 if let Some(magic4) = magic.data.get(0..4) {
207 if magic4 == QCOW_MAGIC.to_be_bytes() {
208 return Ok(ImageType::Qcow2);
209 } else if magic4 == SPARSE_HEADER_MAGIC.to_le_bytes() {
210 return Ok(ImageType::AndroidSparse);
211 }
212 }
213
214 Ok(ImageType::Raw)
215 }
216
217 /// Check if the image file type can be used for async disk access.
async_ok(raw_image: &File) -> Result<bool>218 pub fn async_ok(raw_image: &File) -> Result<bool> {
219 let image_type = detect_image_type(raw_image)?;
220 Ok(match image_type {
221 ImageType::Raw => true,
222 ImageType::Qcow2 | ImageType::AndroidSparse | ImageType::CompositeDisk => false,
223 })
224 }
225
226 /// Inspect the image file type and create an appropriate disk file to match it.
create_async_disk_file(raw_image: File) -> Result<Box<dyn ToAsyncDisk>>227 pub fn create_async_disk_file(raw_image: File) -> Result<Box<dyn ToAsyncDisk>> {
228 let image_type = detect_image_type(&raw_image)?;
229 Ok(match image_type {
230 ImageType::Raw => Box::new(raw_image) as Box<dyn ToAsyncDisk>,
231 ImageType::Qcow2 | ImageType::AndroidSparse | ImageType::CompositeDisk => {
232 return Err(Error::UnknownType)
233 }
234 })
235 }
236
237 /// Inspect the image file type and create an appropriate disk file to match it.
create_disk_file( raw_image: File, mut max_nesting_depth: u32, #[allow(unused_variables)] image_path: &Path, ) -> Result<Box<dyn DiskFile>>238 pub fn create_disk_file(
239 raw_image: File,
240 mut max_nesting_depth: u32,
241 // image_path is only used if the composite-disk feature is enabled.
242 #[allow(unused_variables)] image_path: &Path,
243 ) -> Result<Box<dyn DiskFile>> {
244 if max_nesting_depth == 0 {
245 return Err(Error::MaxNestingDepthExceeded);
246 }
247 max_nesting_depth -= 1;
248
249 let image_type = detect_image_type(&raw_image)?;
250 Ok(match image_type {
251 ImageType::Raw => Box::new(raw_image) as Box<dyn DiskFile>,
252 ImageType::Qcow2 => {
253 Box::new(QcowFile::from(raw_image, max_nesting_depth).map_err(Error::QcowError)?)
254 as Box<dyn DiskFile>
255 }
256 #[cfg(feature = "composite-disk")]
257 ImageType::CompositeDisk => {
258 // Valid composite disk header present
259 Box::new(
260 CompositeDiskFile::from_file(raw_image, max_nesting_depth, image_path)
261 .map_err(Error::CreateCompositeDisk)?,
262 ) as Box<dyn DiskFile>
263 }
264 #[cfg(not(feature = "composite-disk"))]
265 ImageType::CompositeDisk => return Err(Error::UnknownType),
266 ImageType::AndroidSparse => {
267 Box::new(AndroidSparse::from_file(raw_image).map_err(Error::CreateAndroidSparseDisk)?)
268 as Box<dyn DiskFile>
269 }
270 })
271 }
272
273 /// An asynchronously accessible disk.
274 #[async_trait(?Send)]
275 pub trait AsyncDisk: DiskGetLen + FileSetLen + FileAllocate {
276 /// Returns the inner file consuming self.
into_inner(self: Box<Self>) -> Box<dyn ToAsyncDisk>277 fn into_inner(self: Box<Self>) -> Box<dyn ToAsyncDisk>;
278
279 /// Asynchronously fsyncs any completed operations to the disk.
fsync(&self) -> Result<()>280 async fn fsync(&self) -> Result<()>;
281
282 /// Reads from the file at 'file_offset' in to memory `mem` at `mem_offsets`.
283 /// `mem_offsets` is similar to an iovec except relative to the start of `mem`.
read_to_mem<'a>( &self, file_offset: u64, mem: Arc<GuestMemory>, mem_offsets: &'a [cros_async::MemRegion], ) -> Result<usize>284 async fn read_to_mem<'a>(
285 &self,
286 file_offset: u64,
287 mem: Arc<GuestMemory>,
288 mem_offsets: &'a [cros_async::MemRegion],
289 ) -> Result<usize>;
290
291 /// Writes to the file at 'file_offset' from memory `mem` at `mem_offsets`.
write_from_mem<'a>( &self, file_offset: u64, mem: Arc<GuestMemory>, mem_offsets: &'a [cros_async::MemRegion], ) -> Result<usize>292 async fn write_from_mem<'a>(
293 &self,
294 file_offset: u64,
295 mem: Arc<GuestMemory>,
296 mem_offsets: &'a [cros_async::MemRegion],
297 ) -> Result<usize>;
298
299 /// Replaces a range of bytes with a hole.
punch_hole(&self, file_offset: u64, length: u64) -> Result<()>300 async fn punch_hole(&self, file_offset: u64, length: u64) -> Result<()>;
301
302 /// Writes up to `length` bytes of zeroes to the stream, returning how many bytes were written.
write_zeroes_at(&self, file_offset: u64, length: u64) -> Result<()>303 async fn write_zeroes_at(&self, file_offset: u64, length: u64) -> Result<()>;
304 }
305
306 use cros_async::IoSourceExt;
307
308 /// A disk backed by a single file that implements `AsyncDisk` for access.
309 pub struct SingleFileDisk {
310 inner: Box<dyn IoSourceExt<File>>,
311 }
312
313 impl SingleFileDisk {
new(disk: File, ex: &Executor) -> Result<Self>314 pub fn new(disk: File, ex: &Executor) -> Result<Self> {
315 ex.async_from(disk)
316 .map_err(Error::CreateSingleFileDisk)
317 .map(|inner| SingleFileDisk { inner })
318 }
319 }
320
321 impl DiskGetLen for SingleFileDisk {
get_len(&self) -> io::Result<u64>322 fn get_len(&self) -> io::Result<u64> {
323 self.inner.as_source().get_len()
324 }
325 }
326
327 impl FileSetLen for SingleFileDisk {
set_len(&self, len: u64) -> io::Result<()>328 fn set_len(&self, len: u64) -> io::Result<()> {
329 self.inner.as_source().set_len(len)
330 }
331 }
332
333 impl FileAllocate for SingleFileDisk {
allocate(&mut self, offset: u64, len: u64) -> io::Result<()>334 fn allocate(&mut self, offset: u64, len: u64) -> io::Result<()> {
335 self.inner.as_source_mut().allocate(offset, len)
336 }
337 }
338
339 #[async_trait(?Send)]
340 impl AsyncDisk for SingleFileDisk {
into_inner(self: Box<Self>) -> Box<dyn ToAsyncDisk>341 fn into_inner(self: Box<Self>) -> Box<dyn ToAsyncDisk> {
342 Box::new(self.inner.into_source())
343 }
344
fsync(&self) -> Result<()>345 async fn fsync(&self) -> Result<()> {
346 self.inner.fsync().await.map_err(Error::Fsync)
347 }
348
read_to_mem<'a>( &self, file_offset: u64, mem: Arc<GuestMemory>, mem_offsets: &'a [cros_async::MemRegion], ) -> Result<usize>349 async fn read_to_mem<'a>(
350 &self,
351 file_offset: u64,
352 mem: Arc<GuestMemory>,
353 mem_offsets: &'a [cros_async::MemRegion],
354 ) -> Result<usize> {
355 self.inner
356 .read_to_mem(Some(file_offset), mem, mem_offsets)
357 .await
358 .map_err(Error::ReadToMem)
359 }
360
write_from_mem<'a>( &self, file_offset: u64, mem: Arc<GuestMemory>, mem_offsets: &'a [cros_async::MemRegion], ) -> Result<usize>361 async fn write_from_mem<'a>(
362 &self,
363 file_offset: u64,
364 mem: Arc<GuestMemory>,
365 mem_offsets: &'a [cros_async::MemRegion],
366 ) -> Result<usize> {
367 self.inner
368 .write_from_mem(Some(file_offset), mem, mem_offsets)
369 .await
370 .map_err(Error::WriteFromMem)
371 }
372
punch_hole(&self, file_offset: u64, length: u64) -> Result<()>373 async fn punch_hole(&self, file_offset: u64, length: u64) -> Result<()> {
374 self.inner
375 .fallocate(
376 file_offset,
377 length,
378 (libc::FALLOC_FL_PUNCH_HOLE | libc::FALLOC_FL_KEEP_SIZE) as u32,
379 )
380 .await
381 .map_err(Error::Fallocate)
382 }
383
write_zeroes_at(&self, file_offset: u64, length: u64) -> Result<()>384 async fn write_zeroes_at(&self, file_offset: u64, length: u64) -> Result<()> {
385 if self
386 .inner
387 .fallocate(
388 file_offset,
389 length,
390 (libc::FALLOC_FL_ZERO_RANGE | libc::FALLOC_FL_KEEP_SIZE) as u32,
391 )
392 .await
393 .is_ok()
394 {
395 return Ok(());
396 }
397
398 // Fall back to writing zeros if fallocate doesn't work.
399 let buf_size = min(length, 0x10000);
400 let mut nwritten = 0;
401 while nwritten < length {
402 let remaining = length - nwritten;
403 let write_size = min(remaining, buf_size) as usize;
404 let buf = vec![0u8; write_size];
405 nwritten += self
406 .inner
407 .write_from_vec(Some(file_offset + nwritten as u64), buf)
408 .await
409 .map(|(n, _)| n as u64)
410 .map_err(Error::WriteFromVec)?;
411 }
412 Ok(())
413 }
414 }
415
416 #[cfg(test)]
417 mod tests {
418 use super::*;
419
420 use std::fs::{File, OpenOptions};
421 use std::io::Write;
422
423 use cros_async::{Executor, MemRegion};
424 use vm_memory::{GuestAddress, GuestMemory};
425
426 #[test]
read_async()427 fn read_async() {
428 async fn read_zeros_async(ex: &Executor) {
429 let guest_mem = Arc::new(GuestMemory::new(&[(GuestAddress(0), 4096)]).unwrap());
430 let f = File::open("/dev/zero").unwrap();
431 let async_file = SingleFileDisk::new(f, ex).unwrap();
432 let result = async_file
433 .read_to_mem(
434 0,
435 Arc::clone(&guest_mem),
436 &[MemRegion { offset: 0, len: 48 }],
437 )
438 .await;
439 assert_eq!(48, result.unwrap());
440 }
441
442 let ex = Executor::new().unwrap();
443 ex.run_until(read_zeros_async(&ex)).unwrap();
444 }
445
446 #[test]
write_async()447 fn write_async() {
448 async fn write_zeros_async(ex: &Executor) {
449 let guest_mem = Arc::new(GuestMemory::new(&[(GuestAddress(0), 4096)]).unwrap());
450 let f = OpenOptions::new().write(true).open("/dev/null").unwrap();
451 let async_file = SingleFileDisk::new(f, ex).unwrap();
452 let result = async_file
453 .write_from_mem(
454 0,
455 Arc::clone(&guest_mem),
456 &[MemRegion { offset: 0, len: 48 }],
457 )
458 .await;
459 assert_eq!(48, result.unwrap());
460 }
461
462 let ex = Executor::new().unwrap();
463 ex.run_until(write_zeros_async(&ex)).unwrap();
464 }
465
466 #[test]
detect_image_type_raw()467 fn detect_image_type_raw() {
468 let mut t = tempfile::tempfile().unwrap();
469 // Fill the first block of the file with "random" data.
470 let buf = "ABCD".as_bytes().repeat(1024);
471 t.write_all(&buf).unwrap();
472 let image_type = detect_image_type(&t).expect("failed to detect image type");
473 assert_eq!(image_type, ImageType::Raw);
474 }
475
476 #[test]
detect_image_type_qcow2()477 fn detect_image_type_qcow2() {
478 let mut t = tempfile::tempfile().unwrap();
479 // Write the qcow2 magic signature. The rest of the header is not filled in, so if
480 // detect_image_type is ever updated to validate more of the header, this test would need
481 // to be updated.
482 let buf: &[u8] = &[0x51, 0x46, 0x49, 0xfb];
483 t.write_all(buf).unwrap();
484 let image_type = detect_image_type(&t).expect("failed to detect image type");
485 assert_eq!(image_type, ImageType::Qcow2);
486 }
487
488 #[test]
detect_image_type_android_sparse()489 fn detect_image_type_android_sparse() {
490 let mut t = tempfile::tempfile().unwrap();
491 // Write the Android sparse magic signature. The rest of the header is not filled in, so if
492 // detect_image_type is ever updated to validate more of the header, this test would need
493 // to be updated.
494 let buf: &[u8] = &[0x3a, 0xff, 0x26, 0xed];
495 t.write_all(buf).unwrap();
496 let image_type = detect_image_type(&t).expect("failed to detect image type");
497 assert_eq!(image_type, ImageType::AndroidSparse);
498 }
499
500 #[test]
501 #[cfg(feature = "composite-disk")]
detect_image_type_composite()502 fn detect_image_type_composite() {
503 let mut t = tempfile::tempfile().unwrap();
504 // Write the composite disk magic signature. The rest of the header is not filled in, so if
505 // detect_image_type is ever updated to validate more of the header, this test would need
506 // to be updated.
507 let buf = "composite_disk\x1d".as_bytes();
508 t.write_all(buf).unwrap();
509 let image_type = detect_image_type(&t).expect("failed to detect image type");
510 assert_eq!(image_type, ImageType::CompositeDisk);
511 }
512
513 #[test]
detect_image_type_small_file()514 fn detect_image_type_small_file() {
515 let mut t = tempfile::tempfile().unwrap();
516 // Write a file smaller than the four-byte qcow2/sparse magic to ensure the small file logic
517 // works correctly and handles it as a raw file.
518 let buf: &[u8] = &[0xAA, 0xBB];
519 t.write_all(buf).unwrap();
520 let image_type = detect_image_type(&t).expect("failed to detect image type");
521 assert_eq!(image_type, ImageType::Raw);
522 }
523 }
524