• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 //! VM disk image file format I/O.
6 
7 use std::cmp::min;
8 use std::fmt::Debug;
9 use std::fs::File;
10 use std::io;
11 use std::io::Read;
12 use std::io::Seek;
13 use std::io::SeekFrom;
14 use std::path::Path;
15 use std::sync::Arc;
16 
17 use async_trait::async_trait;
18 use base::get_filesystem_type;
19 use base::info;
20 use base::AsRawDescriptors;
21 use base::FileAllocate;
22 use base::FileReadWriteAtVolatile;
23 use base::FileSetLen;
24 use base::PunchHole;
25 use cros_async::AllocateMode;
26 use cros_async::BackingMemory;
27 use cros_async::Executor;
28 use cros_async::IoSource;
29 use thiserror::Error as ThisError;
30 
31 mod asynchronous;
32 #[allow(unused)]
33 pub(crate) use asynchronous::AsyncDiskFileWrapper;
34 #[cfg(feature = "qcow")]
35 mod qcow;
36 #[cfg(feature = "qcow")]
37 pub use qcow::QcowFile;
38 #[cfg(feature = "qcow")]
39 pub use qcow::QCOW_MAGIC;
40 mod sys;
41 
42 #[cfg(feature = "composite-disk")]
43 mod composite;
44 #[cfg(feature = "composite-disk")]
45 use composite::CompositeDiskFile;
46 #[cfg(feature = "composite-disk")]
47 use composite::CDISK_MAGIC;
48 #[cfg(feature = "composite-disk")]
49 mod gpt;
50 #[cfg(feature = "composite-disk")]
51 pub use composite::create_composite_disk;
52 #[cfg(feature = "composite-disk")]
53 pub use composite::create_zero_filler;
54 #[cfg(feature = "composite-disk")]
55 pub use composite::Error as CompositeError;
56 #[cfg(feature = "composite-disk")]
57 pub use composite::ImagePartitionType;
58 #[cfg(feature = "composite-disk")]
59 pub use composite::PartitionInfo;
60 #[cfg(feature = "composite-disk")]
61 pub use gpt::Error as GptError;
62 
63 #[cfg(feature = "android-sparse")]
64 mod android_sparse;
65 #[cfg(feature = "android-sparse")]
66 use android_sparse::AndroidSparse;
67 #[cfg(feature = "android-sparse")]
68 use android_sparse::SPARSE_HEADER_MAGIC;
69 
70 /// Nesting depth limit for disk formats that can open other disk files.
71 pub const MAX_NESTING_DEPTH: u32 = 10;
72 
73 #[derive(ThisError, Debug)]
74 pub enum Error {
75     #[error("failed to create block device: {0}")]
76     BlockDeviceNew(base::Error),
77     #[error("requested file conversion not supported")]
78     ConversionNotSupported,
79     #[cfg(feature = "android-sparse")]
80     #[error("failure in android sparse disk: {0}")]
81     CreateAndroidSparseDisk(android_sparse::Error),
82     #[cfg(feature = "composite-disk")]
83     #[error("failure in composite disk: {0}")]
84     CreateCompositeDisk(composite::Error),
85     #[error("failure creating single file disk: {0}")]
86     CreateSingleFileDisk(cros_async::AsyncError),
87     #[error("failure with fallocate: {0}")]
88     Fallocate(cros_async::AsyncError),
89     #[error("failure with fsync: {0}")]
90     Fsync(cros_async::AsyncError),
91     #[error("failure with fsync: {0}")]
92     IoFsync(io::Error),
93     #[error("checking host fs type: {0}")]
94     HostFsType(base::Error),
95     #[error("maximum disk nesting depth exceeded")]
96     MaxNestingDepthExceeded,
97     #[error("failure to punch hole: {0}")]
98     PunchHole(io::Error),
99     #[cfg(feature = "qcow")]
100     #[error("failure in qcow: {0}")]
101     QcowError(qcow::Error),
102     #[error("failed to read data: {0}")]
103     ReadingData(io::Error),
104     #[error("failed to read header: {0}")]
105     ReadingHeader(io::Error),
106     #[error("failed to read to memory: {0}")]
107     ReadToMem(cros_async::AsyncError),
108     #[error("failed to seek file: {0}")]
109     SeekingFile(io::Error),
110     #[error("failed to set file size: {0}")]
111     SettingFileSize(io::Error),
112     #[error("unknown disk type")]
113     UnknownType,
114     #[error("failed to write from memory: {0}")]
115     WriteFromMem(cros_async::AsyncError),
116     #[error("failed to write from vec: {0}")]
117     WriteFromVec(cros_async::AsyncError),
118     #[error("failed to write zeroes: {0}")]
119     WriteZeroes(io::Error),
120     #[error("failed to write data: {0}")]
121     WritingData(io::Error),
122     #[error("failed to convert to async: {0}")]
123     ToAsync(cros_async::AsyncError),
124     #[cfg(windows)]
125     #[error("failed to set disk file sparse: {0}")]
126     SetSparseFailure(io::Error),
127     #[error("failure with guest memory access: {0}")]
128     GuestMemory(cros_async::mem::Error),
129     #[error("unsupported operation")]
130     UnsupportedOperation,
131 }
132 
133 pub type Result<T> = std::result::Result<T, Error>;
134 
135 /// A trait for getting the length of a disk image or raw block device.
136 pub trait DiskGetLen {
137     /// Get the current length of the disk in bytes.
get_len(&self) -> io::Result<u64>138     fn get_len(&self) -> io::Result<u64>;
139 }
140 
141 impl DiskGetLen for File {
get_len(&self) -> io::Result<u64>142     fn get_len(&self) -> io::Result<u64> {
143         let mut s = self;
144         let orig_seek = s.seek(SeekFrom::Current(0))?;
145         let end = s.seek(SeekFrom::End(0))? as u64;
146         s.seek(SeekFrom::Start(orig_seek))?;
147         Ok(end)
148     }
149 }
150 
151 pub trait PunchHoleMut {
152     /// Replace a range of bytes with a hole.
punch_hole_mut(&mut self, offset: u64, length: u64) -> io::Result<()>153     fn punch_hole_mut(&mut self, offset: u64, length: u64) -> io::Result<()>;
154 }
155 
156 impl<T: PunchHole> PunchHoleMut for T {
punch_hole_mut(&mut self, offset: u64, length: u64) -> io::Result<()>157     fn punch_hole_mut(&mut self, offset: u64, length: u64) -> io::Result<()> {
158         self.punch_hole(offset, length)
159     }
160 }
161 
162 /// The prerequisites necessary to support a block device.
163 pub trait DiskFile:
164     FileSetLen + DiskGetLen + FileReadWriteAtVolatile + ToAsyncDisk + Send + AsRawDescriptors + Debug
165 {
166     /// Creates a new DiskFile instance that shares the same underlying disk file image. IO
167     /// operations to a DiskFile should affect all DiskFile instances with the same underlying disk
168     /// file image.
169     ///
170     /// `try_clone()` returns [`io::ErrorKind::Unsupported`] Error if a DiskFile does not support
171     /// creating an instance with the same underlying disk file image.
try_clone(&self) -> io::Result<Box<dyn DiskFile>>172     fn try_clone(&self) -> io::Result<Box<dyn DiskFile>> {
173         Err(io::Error::new(
174             io::ErrorKind::Unsupported,
175             "unsupported operation",
176         ))
177     }
178 }
179 
180 /// A `DiskFile` that can be converted for asychronous access.
181 pub trait ToAsyncDisk: AsRawDescriptors + DiskGetLen + Send {
182     /// Convert a boxed self in to a box-wrapped implementaiton of AsyncDisk.
183     /// Used to convert a standard disk image to an async disk image. This conversion and the
184     /// inverse are needed so that the `Send` DiskImage can be given to the block thread where it is
185     /// converted to a non-`Send` AsyncDisk. The AsyncDisk can then be converted back and returned
186     /// 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>>187     fn to_async_disk(self: Box<Self>, ex: &Executor) -> Result<Box<dyn AsyncDisk>>;
188 }
189 
190 impl ToAsyncDisk for File {
to_async_disk(self: Box<Self>, ex: &Executor) -> Result<Box<dyn AsyncDisk>>191     fn to_async_disk(self: Box<Self>, ex: &Executor) -> Result<Box<dyn AsyncDisk>> {
192         Ok(Box::new(SingleFileDisk::new(*self, ex)?))
193     }
194 }
195 
196 /// The variants of image files on the host that can be used as virtual disks.
197 #[derive(Debug, PartialEq, Eq)]
198 pub enum ImageType {
199     Raw,
200     Qcow2,
201     CompositeDisk,
202     AndroidSparse,
203 }
204 
log_host_fs_type(file: &File) -> Result<()>205 fn log_host_fs_type(file: &File) -> Result<()> {
206     let fstype = get_filesystem_type(file).map_err(Error::HostFsType)?;
207     info!("Disk image file is hosted on file system type {:x}", fstype);
208     Ok(())
209 }
210 
211 /// Detect the type of an image file by checking for a valid header of the supported formats.
detect_image_type(file: &File) -> Result<ImageType>212 pub fn detect_image_type(file: &File) -> Result<ImageType> {
213     let mut f = file;
214     let disk_size = f.get_len().map_err(Error::SeekingFile)?;
215     let orig_seek = f.seek(SeekFrom::Current(0)).map_err(Error::SeekingFile)?;
216     f.seek(SeekFrom::Start(0)).map_err(Error::SeekingFile)?;
217 
218     info!("disk size {}, ", disk_size);
219     log_host_fs_type(f)?;
220     // Try to read the disk in a nicely-aligned block size unless the whole file is smaller.
221     const MAGIC_BLOCK_SIZE: usize = 4096;
222     #[repr(align(4096))]
223     struct BlockAlignedBuffer {
224         data: [u8; MAGIC_BLOCK_SIZE],
225     }
226     let mut magic = BlockAlignedBuffer {
227         data: [0u8; MAGIC_BLOCK_SIZE],
228     };
229     let magic_read_len = if disk_size > MAGIC_BLOCK_SIZE as u64 {
230         MAGIC_BLOCK_SIZE
231     } else {
232         // This cast is safe since we know disk_size is less than MAGIC_BLOCK_SIZE (4096) and
233         // therefore is representable in usize.
234         disk_size as usize
235     };
236 
237     f.read_exact(&mut magic.data[0..magic_read_len])
238         .map_err(Error::ReadingHeader)?;
239     f.seek(SeekFrom::Start(orig_seek))
240         .map_err(Error::SeekingFile)?;
241 
242     #[cfg(feature = "composite-disk")]
243     if let Some(cdisk_magic) = magic.data.get(0..CDISK_MAGIC.len()) {
244         if cdisk_magic == CDISK_MAGIC.as_bytes() {
245             return Ok(ImageType::CompositeDisk);
246         }
247     }
248 
249     #[allow(unused_variables)] // magic4 is only used with the qcow or android-sparse features.
250     if let Some(magic4) = magic.data.get(0..4) {
251         #[cfg(feature = "qcow")]
252         if magic4 == QCOW_MAGIC.to_be_bytes() {
253             return Ok(ImageType::Qcow2);
254         }
255         #[cfg(feature = "android-sparse")]
256         if magic4 == SPARSE_HEADER_MAGIC.to_le_bytes() {
257             return Ok(ImageType::AndroidSparse);
258         }
259     }
260 
261     Ok(ImageType::Raw)
262 }
263 
264 impl DiskFile for File {
try_clone(&self) -> io::Result<Box<dyn DiskFile>>265     fn try_clone(&self) -> io::Result<Box<dyn DiskFile>> {
266         Ok(Box::new(self.try_clone()?))
267     }
268 }
269 
270 /// Inspect the image file type and create an appropriate disk file to match it.
create_disk_file( raw_image: File, is_sparse_file: bool, #[allow(unused_variables)] mut max_nesting_depth: u32, #[allow(unused_variables)] image_path: &Path, ) -> Result<Box<dyn DiskFile>>271 pub fn create_disk_file(
272     raw_image: File,
273     is_sparse_file: bool,
274     // max_nesting_depth is only used if the composite-disk or qcow features are enabled.
275     #[allow(unused_variables)] mut max_nesting_depth: u32,
276     // image_path is only used if the composite-disk feature is enabled.
277     #[allow(unused_variables)] image_path: &Path,
278 ) -> Result<Box<dyn DiskFile>> {
279     if max_nesting_depth == 0 {
280         return Err(Error::MaxNestingDepthExceeded);
281     }
282     #[allow(unused_assignments)]
283     {
284         max_nesting_depth -= 1;
285     }
286 
287     let image_type = detect_image_type(&raw_image)?;
288     Ok(match image_type {
289         ImageType::Raw => {
290             sys::apply_raw_disk_file_options(&raw_image, is_sparse_file)?;
291             Box::new(raw_image) as Box<dyn DiskFile>
292         }
293         #[cfg(feature = "qcow")]
294         ImageType::Qcow2 => {
295             Box::new(QcowFile::from(raw_image, max_nesting_depth).map_err(Error::QcowError)?)
296                 as Box<dyn DiskFile>
297         }
298         #[cfg(feature = "composite-disk")]
299         ImageType::CompositeDisk => {
300             // Valid composite disk header present
301             Box::new(
302                 CompositeDiskFile::from_file(
303                     raw_image,
304                     is_sparse_file,
305                     max_nesting_depth,
306                     image_path,
307                 )
308                 .map_err(Error::CreateCompositeDisk)?,
309             ) as Box<dyn DiskFile>
310         }
311         #[cfg(feature = "android-sparse")]
312         ImageType::AndroidSparse => {
313             Box::new(AndroidSparse::from_file(raw_image).map_err(Error::CreateAndroidSparseDisk)?)
314                 as Box<dyn DiskFile>
315         }
316         #[allow(unreachable_patterns)]
317         _ => return Err(Error::UnknownType),
318     })
319 }
320 
321 /// An asynchronously accessible disk.
322 #[async_trait(?Send)]
323 pub trait AsyncDisk: DiskGetLen + FileSetLen + FileAllocate {
324     /// Returns the inner file consuming self.
into_inner(self: Box<Self>) -> Box<dyn DiskFile>325     fn into_inner(self: Box<Self>) -> Box<dyn DiskFile>;
326 
327     /// Asynchronously fsyncs any completed operations to the disk.
fsync(&self) -> Result<()>328     async fn fsync(&self) -> Result<()>;
329 
330     /// Reads from the file at 'file_offset' into memory `mem` at `mem_offsets`.
331     /// `mem_offsets` is similar to an iovec except relative to the start of `mem`.
read_to_mem<'a>( &'a self, file_offset: u64, mem: Arc<dyn BackingMemory + Send + Sync>, mem_offsets: &'a [cros_async::MemRegion], ) -> Result<usize>332     async fn read_to_mem<'a>(
333         &'a self,
334         file_offset: u64,
335         mem: Arc<dyn BackingMemory + Send + Sync>,
336         mem_offsets: &'a [cros_async::MemRegion],
337     ) -> Result<usize>;
338 
339     /// Writes to the file at 'file_offset' from memory `mem` at `mem_offsets`.
write_from_mem<'a>( &'a self, file_offset: u64, mem: Arc<dyn BackingMemory + Send + Sync>, mem_offsets: &'a [cros_async::MemRegion], ) -> Result<usize>340     async fn write_from_mem<'a>(
341         &'a self,
342         file_offset: u64,
343         mem: Arc<dyn BackingMemory + Send + Sync>,
344         mem_offsets: &'a [cros_async::MemRegion],
345     ) -> Result<usize>;
346 
347     /// Replaces a range of bytes with a hole.
punch_hole(&self, file_offset: u64, length: u64) -> Result<()>348     async fn punch_hole(&self, file_offset: u64, length: u64) -> Result<()>;
349 
350     /// 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<()>351     async fn write_zeroes_at(&self, file_offset: u64, length: u64) -> Result<()>;
352 
353     /// Reads from the file at 'file_offset' into `buf`.
354     ///
355     /// Less efficient than `read_to_mem` because of extra copies and allocations.
read_double_buffered(&self, file_offset: u64, buf: &mut [u8]) -> Result<usize>356     async fn read_double_buffered(&self, file_offset: u64, buf: &mut [u8]) -> Result<usize> {
357         let backing_mem = Arc::new(cros_async::VecIoWrapper::from(vec![0u8; buf.len()]));
358         let region = cros_async::MemRegion {
359             offset: 0,
360             len: buf.len(),
361         };
362         let n = self
363             .read_to_mem(file_offset, backing_mem.clone(), &[region])
364             .await?;
365         backing_mem
366             .get_volatile_slice(region)
367             .expect("BUG: the VecIoWrapper shrank?")
368             .sub_slice(0, n)
369             .expect("BUG: read_to_mem return value too large?")
370             .copy_to(buf);
371         Ok(n)
372     }
373 
374     /// Writes to the file at 'file_offset' from `buf`.
375     ///
376     /// Less efficient than `write_from_mem` because of extra copies and allocations.
write_double_buffered(&self, file_offset: u64, buf: &[u8]) -> Result<usize>377     async fn write_double_buffered(&self, file_offset: u64, buf: &[u8]) -> Result<usize> {
378         let backing_mem = Arc::new(cros_async::VecIoWrapper::from(buf.to_vec()));
379         let region = cros_async::MemRegion {
380             offset: 0,
381             len: buf.len(),
382         };
383         self.write_from_mem(file_offset, backing_mem, &[region])
384             .await
385     }
386 }
387 
388 /// A disk backed by a single file that implements `AsyncDisk` for access.
389 pub struct SingleFileDisk {
390     inner: IoSource<File>,
391 }
392 
393 impl SingleFileDisk {
new(disk: File, ex: &Executor) -> Result<Self>394     pub fn new(disk: File, ex: &Executor) -> Result<Self> {
395         ex.async_from(disk)
396             .map_err(Error::CreateSingleFileDisk)
397             .map(|inner| SingleFileDisk { inner })
398     }
399 }
400 
401 impl DiskGetLen for SingleFileDisk {
get_len(&self) -> io::Result<u64>402     fn get_len(&self) -> io::Result<u64> {
403         self.inner.as_source().get_len()
404     }
405 }
406 
407 impl FileSetLen for SingleFileDisk {
set_len(&self, len: u64) -> io::Result<()>408     fn set_len(&self, len: u64) -> io::Result<()> {
409         self.inner.as_source().set_len(len)
410     }
411 }
412 
413 impl FileAllocate for SingleFileDisk {
allocate(&mut self, offset: u64, len: u64) -> io::Result<()>414     fn allocate(&mut self, offset: u64, len: u64) -> io::Result<()> {
415         self.inner.as_source_mut().allocate(offset, len)
416     }
417 }
418 
419 #[async_trait(?Send)]
420 impl AsyncDisk for SingleFileDisk {
into_inner(self: Box<Self>) -> Box<dyn DiskFile>421     fn into_inner(self: Box<Self>) -> Box<dyn DiskFile> {
422         Box::new(self.inner.into_source())
423     }
424 
fsync(&self) -> Result<()>425     async fn fsync(&self) -> Result<()> {
426         self.inner.fsync().await.map_err(Error::Fsync)
427     }
428 
read_to_mem<'a>( &'a self, file_offset: u64, mem: Arc<dyn BackingMemory + Send + Sync>, mem_offsets: &'a [cros_async::MemRegion], ) -> Result<usize>429     async fn read_to_mem<'a>(
430         &'a self,
431         file_offset: u64,
432         mem: Arc<dyn BackingMemory + Send + Sync>,
433         mem_offsets: &'a [cros_async::MemRegion],
434     ) -> Result<usize> {
435         self.inner
436             .read_to_mem(Some(file_offset), mem, mem_offsets)
437             .await
438             .map_err(Error::ReadToMem)
439     }
440 
write_from_mem<'a>( &'a self, file_offset: u64, mem: Arc<dyn BackingMemory + Send + Sync>, mem_offsets: &'a [cros_async::MemRegion], ) -> Result<usize>441     async fn write_from_mem<'a>(
442         &'a self,
443         file_offset: u64,
444         mem: Arc<dyn BackingMemory + Send + Sync>,
445         mem_offsets: &'a [cros_async::MemRegion],
446     ) -> Result<usize> {
447         self.inner
448             .write_from_mem(Some(file_offset), mem, mem_offsets)
449             .await
450             .map_err(Error::WriteFromMem)
451     }
452 
punch_hole(&self, file_offset: u64, length: u64) -> Result<()>453     async fn punch_hole(&self, file_offset: u64, length: u64) -> Result<()> {
454         self.inner
455             .fallocate(file_offset, length, AllocateMode::PunchHole)
456             .await
457             .map_err(Error::Fallocate)
458     }
459 
write_zeroes_at(&self, file_offset: u64, length: u64) -> Result<()>460     async fn write_zeroes_at(&self, file_offset: u64, length: u64) -> Result<()> {
461         if self
462             .inner
463             .fallocate(file_offset, length, AllocateMode::ZeroRange)
464             .await
465             .is_ok()
466         {
467             return Ok(());
468         }
469 
470         // Fall back to writing zeros if fallocate doesn't work.
471         let buf_size = min(length, 0x10000);
472         let mut nwritten = 0;
473         while nwritten < length {
474             let remaining = length - nwritten;
475             let write_size = min(remaining, buf_size) as usize;
476             let buf = vec![0u8; write_size];
477             nwritten += self
478                 .inner
479                 .write_from_vec(Some(file_offset + nwritten as u64), buf)
480                 .await
481                 .map(|(n, _)| n as u64)
482                 .map_err(Error::WriteFromVec)?;
483         }
484         Ok(())
485     }
486 }
487