• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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