disk/
disk.rs

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
7use std::cmp::min;
8use std::fmt::Debug;
9use std::fs::File;
10use std::io;
11use std::io::Seek;
12use std::io::SeekFrom;
13use std::path::PathBuf;
14use std::sync::Arc;
15
16use async_trait::async_trait;
17use base::info;
18use base::AsRawDescriptors;
19use base::FileAllocate;
20use base::FileReadWriteAtVolatile;
21use base::FileSetLen;
22use cros_async::BackingMemory;
23use cros_async::Executor;
24use cros_async::IoOptions;
25use cros_async::IoSource;
26use cros_async::MemRegionIter;
27use thiserror::Error as ThisError;
28
29mod asynchronous;
30#[allow(unused)]
31pub(crate) use asynchronous::AsyncDiskFileWrapper;
32#[cfg(feature = "qcow")]
33mod qcow;
34#[cfg(feature = "qcow")]
35pub use qcow::QcowFile;
36#[cfg(feature = "qcow")]
37pub use qcow::QCOW_MAGIC;
38mod sys;
39
40#[cfg(feature = "composite-disk")]
41mod composite;
42#[cfg(feature = "composite-disk")]
43use composite::CompositeDiskFile;
44#[cfg(feature = "composite-disk")]
45use composite::CDISK_MAGIC;
46#[cfg(feature = "composite-disk")]
47mod gpt;
48#[cfg(feature = "composite-disk")]
49pub use composite::create_composite_disk;
50#[cfg(feature = "composite-disk")]
51pub use composite::create_zero_filler;
52#[cfg(feature = "composite-disk")]
53pub use composite::Error as CompositeError;
54#[cfg(feature = "composite-disk")]
55pub use composite::ImagePartitionType;
56#[cfg(feature = "composite-disk")]
57pub use composite::PartitionInfo;
58#[cfg(feature = "composite-disk")]
59pub use gpt::Error as GptError;
60
61#[cfg(feature = "android-sparse")]
62mod android_sparse;
63#[cfg(feature = "android-sparse")]
64use android_sparse::AndroidSparse;
65#[cfg(feature = "android-sparse")]
66use android_sparse::SPARSE_HEADER_MAGIC;
67use sys::read_from_disk;
68
69#[cfg(feature = "zstd")]
70mod zstd;
71#[cfg(feature = "zstd")]
72use zstd::ZstdDisk;
73#[cfg(feature = "zstd")]
74use zstd::ZSTD_FRAME_MAGIC;
75#[cfg(feature = "zstd")]
76use zstd::ZSTD_SKIPPABLE_MAGIC_HIGH;
77#[cfg(feature = "zstd")]
78use zstd::ZSTD_SKIPPABLE_MAGIC_LOW;
79
80/// Nesting depth limit for disk formats that can open other disk files.
81const MAX_NESTING_DEPTH: u32 = 10;
82
83#[derive(ThisError, Debug)]
84pub enum Error {
85    #[error("failed to create block device: {0}")]
86    BlockDeviceNew(base::Error),
87    #[error("requested file conversion not supported")]
88    ConversionNotSupported,
89    #[cfg(feature = "android-sparse")]
90    #[error("failure in android sparse disk: {0}")]
91    CreateAndroidSparseDisk(android_sparse::Error),
92    #[cfg(feature = "composite-disk")]
93    #[error("failure in composite disk: {0}")]
94    CreateCompositeDisk(composite::Error),
95    #[cfg(feature = "zstd")]
96    #[error("failure in zstd disk: {0}")]
97    CreateZstdDisk(anyhow::Error),
98    #[error("failure creating single file disk: {0}")]
99    CreateSingleFileDisk(cros_async::AsyncError),
100    #[error("failed to set O_DIRECT on disk image: {0}")]
101    DirectFailed(base::Error),
102    #[error("failure with fdatasync: {0}")]
103    Fdatasync(cros_async::AsyncError),
104    #[error("failure with fsync: {0}")]
105    Fsync(cros_async::AsyncError),
106    #[error("failed to lock file: {0}")]
107    LockFileFailure(base::Error),
108    #[error("failure with fdatasync: {0}")]
109    IoFdatasync(io::Error),
110    #[error("failure with flush: {0}")]
111    IoFlush(io::Error),
112    #[error("failure with fsync: {0}")]
113    IoFsync(io::Error),
114    #[error("failure to punch hole: {0}")]
115    IoPunchHole(io::Error),
116    #[error("checking host fs type: {0}")]
117    HostFsType(base::Error),
118    #[error("maximum disk nesting depth exceeded")]
119    MaxNestingDepthExceeded,
120    #[error("failed to open disk file \"{0}\": {1}")]
121    OpenFile(String, base::Error),
122    #[error("failure to punch hole: {0}")]
123    PunchHole(cros_async::AsyncError),
124    #[error("failure to punch hole for block device file: {0}")]
125    PunchHoleBlockDeviceFile(base::Error),
126    #[cfg(feature = "qcow")]
127    #[error("failure in qcow: {0}")]
128    QcowError(qcow::Error),
129    #[error("failed to read data: {0}")]
130    ReadingData(io::Error),
131    #[error("failed to read header: {0}")]
132    ReadingHeader(io::Error),
133    #[error("failed to read to memory: {0}")]
134    ReadToMem(cros_async::AsyncError),
135    #[error("failed to seek file: {0}")]
136    SeekingFile(io::Error),
137    #[error("failed to set file size: {0}")]
138    SettingFileSize(io::Error),
139    #[error("unknown disk type")]
140    UnknownType,
141    #[error("failed to write from memory: {0}")]
142    WriteFromMem(cros_async::AsyncError),
143    #[error("failed to write from vec: {0}")]
144    WriteFromVec(cros_async::AsyncError),
145    #[error("failed to write zeroes: {0}")]
146    WriteZeroes(io::Error),
147    #[error("failed to write data: {0}")]
148    WritingData(io::Error),
149    #[error("failed to convert to async: {0}")]
150    ToAsync(cros_async::AsyncError),
151    #[cfg(windows)]
152    #[error("failed to set disk file sparse: {0}")]
153    SetSparseFailure(io::Error),
154    #[error("failure with guest memory access: {0}")]
155    GuestMemory(cros_async::mem::Error),
156    #[error("unsupported operation")]
157    UnsupportedOperation,
158}
159
160pub type Result<T> = std::result::Result<T, Error>;
161
162/// A trait for getting the length of a disk image or raw block device.
163pub trait DiskGetLen {
164    /// Get the current length of the disk in bytes.
165    fn get_len(&self) -> io::Result<u64>;
166}
167
168impl DiskGetLen for File {
169    fn get_len(&self) -> io::Result<u64> {
170        let mut s = self;
171        let orig_seek = s.stream_position()?;
172        let end = s.seek(SeekFrom::End(0))?;
173        s.seek(SeekFrom::Start(orig_seek))?;
174        Ok(end)
175    }
176}
177
178/// The prerequisites necessary to support a block device.
179pub trait DiskFile:
180    FileSetLen + DiskGetLen + FileReadWriteAtVolatile + ToAsyncDisk + Send + AsRawDescriptors + Debug
181{
182    /// Creates a new DiskFile instance that shares the same underlying disk file image. IO
183    /// operations to a DiskFile should affect all DiskFile instances with the same underlying disk
184    /// file image.
185    ///
186    /// `try_clone()` returns [`io::ErrorKind::Unsupported`] Error if a DiskFile does not support
187    /// creating an instance with the same underlying disk file image.
188    fn try_clone(&self) -> io::Result<Box<dyn DiskFile>> {
189        Err(io::Error::new(
190            io::ErrorKind::Unsupported,
191            "unsupported operation",
192        ))
193    }
194}
195
196/// A `DiskFile` that can be converted for asychronous access.
197pub trait ToAsyncDisk: AsRawDescriptors + DiskGetLen + Send {
198    /// Convert a boxed self in to a box-wrapped implementaiton of AsyncDisk.
199    /// Used to convert a standard disk image to an async disk image. This conversion and the
200    /// inverse are needed so that the `Send` DiskImage can be given to the block thread where it is
201    /// converted to a non-`Send` AsyncDisk. The AsyncDisk can then be converted back and returned
202    /// to the main device thread if the block device is destroyed or reset.
203    fn to_async_disk(self: Box<Self>, ex: &Executor) -> Result<Box<dyn AsyncDisk>>;
204}
205
206impl ToAsyncDisk for File {
207    fn to_async_disk(self: Box<Self>, ex: &Executor) -> Result<Box<dyn AsyncDisk>> {
208        Ok(Box::new(SingleFileDisk::new(*self, ex)?))
209    }
210}
211
212/// The variants of image files on the host that can be used as virtual disks.
213#[derive(Clone, Copy, Debug, PartialEq, Eq)]
214pub enum ImageType {
215    Raw,
216    Qcow2,
217    CompositeDisk,
218    AndroidSparse,
219    Zstd,
220}
221
222/// Detect the type of an image file by checking for a valid header of the supported formats.
223pub fn detect_image_type(file: &File, overlapped_mode: bool) -> Result<ImageType> {
224    let mut f = file;
225    let disk_size = f.get_len().map_err(Error::SeekingFile)?;
226    let orig_seek = f.stream_position().map_err(Error::SeekingFile)?;
227
228    info!("disk size {}", disk_size);
229
230    // Try to read the disk in a nicely-aligned block size unless the whole file is smaller.
231    const MAGIC_BLOCK_SIZE: usize = 4096;
232    #[repr(align(4096))]
233    struct BlockAlignedBuffer {
234        data: [u8; MAGIC_BLOCK_SIZE],
235    }
236    let mut magic = BlockAlignedBuffer {
237        data: [0u8; MAGIC_BLOCK_SIZE],
238    };
239    let magic_read_len = if disk_size > MAGIC_BLOCK_SIZE as u64 {
240        MAGIC_BLOCK_SIZE
241    } else {
242        // This cast is safe since we know disk_size is less than MAGIC_BLOCK_SIZE (4096) and
243        // therefore is representable in usize.
244        disk_size as usize
245    };
246
247    read_from_disk(f, 0, &mut magic.data[0..magic_read_len], overlapped_mode)?;
248    f.seek(SeekFrom::Start(orig_seek))
249        .map_err(Error::SeekingFile)?;
250
251    #[cfg(feature = "composite-disk")]
252    if let Some(cdisk_magic) = magic.data.get(0..CDISK_MAGIC.len()) {
253        if cdisk_magic == CDISK_MAGIC.as_bytes() {
254            return Ok(ImageType::CompositeDisk);
255        }
256    }
257
258    #[allow(unused_variables)] // magic4 is only used with the qcow/android-sparse/zstd features.
259    if let Some(magic4) = magic
260        .data
261        .get(0..4)
262        .and_then(|v| <&[u8] as std::convert::TryInto<[u8; 4]>>::try_into(v).ok())
263    {
264        #[cfg(feature = "qcow")]
265        if magic4 == QCOW_MAGIC.to_be_bytes() {
266            return Ok(ImageType::Qcow2);
267        }
268        #[cfg(feature = "android-sparse")]
269        if magic4 == SPARSE_HEADER_MAGIC.to_le_bytes() {
270            return Ok(ImageType::AndroidSparse);
271        }
272        #[cfg(feature = "zstd")]
273        if u32::from_le_bytes(magic4) == ZSTD_FRAME_MAGIC
274            || (u32::from_le_bytes(magic4) >= ZSTD_SKIPPABLE_MAGIC_LOW
275                && u32::from_le_bytes(magic4) <= ZSTD_SKIPPABLE_MAGIC_HIGH)
276        {
277            return Ok(ImageType::Zstd);
278        }
279    }
280
281    Ok(ImageType::Raw)
282}
283
284impl DiskFile for File {
285    fn try_clone(&self) -> io::Result<Box<dyn DiskFile>> {
286        Ok(Box::new(self.try_clone()?))
287    }
288}
289
290pub struct DiskFileParams {
291    pub path: PathBuf,
292    pub is_read_only: bool,
293    // Whether to call `base::set_sparse_file` on the file. Currently only affects Windows and is
294    // irrelevant for read only files.
295    pub is_sparse_file: bool,
296    // Whether to open the file in overlapped mode. Only affects Windows.
297    pub is_overlapped: bool,
298    // Whether to disable OS page caches / buffering.
299    pub is_direct: bool,
300    // Whether to lock the file.
301    pub lock: bool,
302    // The nesting depth of the file. Used to avoid infinite recursion. Users outside the disk
303    // crate should set this to zero.
304    pub depth: u32,
305}
306
307impl Default for DiskFileParams {
308    fn default() -> Self {
309        Self {
310            path: PathBuf::new(),
311            is_read_only: false,
312            is_sparse_file: false,
313            is_overlapped: false,
314            is_direct: false,
315            lock: true,
316            depth: 0,
317        }
318    }
319}
320
321/// Inspect the image file type and create an appropriate disk file to match it.
322pub fn open_disk_file(params: DiskFileParams) -> Result<Box<dyn DiskFile>> {
323    if params.depth > MAX_NESTING_DEPTH {
324        return Err(Error::MaxNestingDepthExceeded);
325    }
326
327    let raw_image = sys::open_raw_disk_image(&params)?;
328    let image_type = detect_image_type(&raw_image, params.is_overlapped)?;
329    disk_file_from_file(raw_image, params, image_type)
330}
331
332/// Open an image file with an explicitly specified image type.
333pub fn open_disk_file_as(
334    params: DiskFileParams,
335    image_type: ImageType,
336) -> Result<Box<dyn DiskFile>> {
337    if params.depth > MAX_NESTING_DEPTH {
338        return Err(Error::MaxNestingDepthExceeded);
339    }
340
341    let raw_image = sys::open_raw_disk_image(&params)?;
342    disk_file_from_file(raw_image, params, image_type)
343}
344
345pub(crate) fn disk_file_from_file(
346    raw_image: File,
347    params: DiskFileParams,
348    image_type: ImageType,
349) -> Result<Box<dyn DiskFile>> {
350    Ok(match image_type {
351        ImageType::Raw => {
352            sys::apply_raw_disk_file_options(&raw_image, params.is_sparse_file)?;
353            Box::new(raw_image) as Box<dyn DiskFile>
354        }
355        #[cfg(feature = "qcow")]
356        ImageType::Qcow2 => Box::new(QcowFile::from(raw_image, params).map_err(Error::QcowError)?)
357            as Box<dyn DiskFile>,
358        #[cfg(feature = "composite-disk")]
359        ImageType::CompositeDisk => {
360            // Valid composite disk header present
361            Box::new(
362                CompositeDiskFile::from_file(raw_image, params)
363                    .map_err(Error::CreateCompositeDisk)?,
364            ) as Box<dyn DiskFile>
365        }
366        #[cfg(feature = "android-sparse")]
367        ImageType::AndroidSparse => {
368            Box::new(AndroidSparse::from_file(raw_image).map_err(Error::CreateAndroidSparseDisk)?)
369                as Box<dyn DiskFile>
370        }
371        #[cfg(feature = "zstd")]
372        ImageType::Zstd => Box::new(ZstdDisk::from_file(raw_image).map_err(Error::CreateZstdDisk)?)
373            as Box<dyn DiskFile>,
374        #[allow(unreachable_patterns)]
375        _ => return Err(Error::UnknownType),
376    })
377}
378
379/// An asynchronously accessible disk.
380#[async_trait(?Send)]
381pub trait AsyncDisk: DiskGetLen + FileSetLen + FileAllocate {
382    /// Flush intermediary buffers and/or dirty state to file. fsync not required.
383    async fn flush(&self) -> Result<()>;
384
385    /// Asynchronously fsyncs any completed operations to the disk.
386    async fn fsync(&self) -> Result<()>;
387
388    /// Asynchronously fdatasyncs any completed operations to the disk.
389    /// Note that an implementation may simply call fsync for fdatasync.
390    async fn fdatasync(&self) -> Result<()>;
391
392    /// Reads from the file at 'file_offset' into memory `mem` at `mem_offsets`.
393    /// `mem_offsets` is similar to an iovec except relative to the start of `mem`.
394    async fn read_to_mem<'a>(
395        &'a self,
396        file_offset: u64,
397        mem: Arc<dyn BackingMemory + Send + Sync>,
398        mem_offsets: cros_async::MemRegionIter<'a>,
399        options: IoOptions,
400    ) -> Result<usize>;
401
402    /// Writes to the file at 'file_offset' from memory `mem` at `mem_offsets`.
403    async fn write_from_mem<'a>(
404        &'a self,
405        file_offset: u64,
406        mem: Arc<dyn BackingMemory + Send + Sync>,
407        mem_offsets: cros_async::MemRegionIter<'a>,
408        options: IoOptions,
409    ) -> Result<usize>;
410
411    /// Replaces a range of bytes with a hole.
412    async fn punch_hole(&self, file_offset: u64, length: u64) -> Result<()>;
413
414    /// Writes up to `length` bytes of zeroes to the stream, returning how many bytes were written.
415    async fn write_zeroes_at(&self, file_offset: u64, length: u64) -> Result<()>;
416
417    /// Reads from the file at 'file_offset' into `buf`.
418    ///
419    /// Less efficient than `read_to_mem` because of extra copies and allocations.
420    async fn read_double_buffered(&self, file_offset: u64, buf: &mut [u8]) -> Result<usize> {
421        let backing_mem = Arc::new(cros_async::VecIoWrapper::from(vec![0u8; buf.len()]));
422        let region = cros_async::MemRegion {
423            offset: 0,
424            len: buf.len(),
425        };
426        let n = self
427            .read_to_mem(
428                file_offset,
429                backing_mem.clone(),
430                MemRegionIter::new(&[region]),
431                Default::default(),
432            )
433            .await?;
434        backing_mem
435            .get_volatile_slice(region)
436            .expect("BUG: the VecIoWrapper shrank?")
437            .sub_slice(0, n)
438            .expect("BUG: read_to_mem return value too large?")
439            .copy_to(buf);
440        Ok(n)
441    }
442
443    /// Writes to the file at 'file_offset' from `buf`.
444    ///
445    /// Less efficient than `write_from_mem` because of extra copies and allocations.
446    async fn write_double_buffered(&self, file_offset: u64, buf: &[u8]) -> Result<usize> {
447        let backing_mem = Arc::new(cros_async::VecIoWrapper::from(buf.to_vec()));
448        let region = cros_async::MemRegion {
449            offset: 0,
450            len: buf.len(),
451        };
452        self.write_from_mem(
453            file_offset,
454            backing_mem,
455            cros_async::MemRegionIter::new(&[region]),
456            Default::default(),
457        )
458        .await
459    }
460}
461
462/// A disk backed by a single file that implements `AsyncDisk` for access.
463pub struct SingleFileDisk {
464    inner: IoSource<File>,
465    // Whether the backed file is a block device since the punch-hole needs different operation.
466    #[cfg(any(target_os = "android", target_os = "linux"))]
467    is_block_device_file: bool,
468}
469
470impl DiskGetLen for SingleFileDisk {
471    fn get_len(&self) -> io::Result<u64> {
472        self.inner.as_source().get_len()
473    }
474}
475
476impl FileSetLen for SingleFileDisk {
477    fn set_len(&self, len: u64) -> io::Result<()> {
478        self.inner.as_source().set_len(len)
479    }
480}
481
482impl FileAllocate for SingleFileDisk {
483    fn allocate(&self, offset: u64, len: u64) -> io::Result<()> {
484        self.inner.as_source().allocate(offset, len)
485    }
486}
487
488#[async_trait(?Send)]
489impl AsyncDisk for SingleFileDisk {
490    async fn flush(&self) -> Result<()> {
491        // Nothing to flush, all file mutations are immediately sent to the OS.
492        Ok(())
493    }
494
495    async fn fsync(&self) -> Result<()> {
496        self.inner.fsync().await.map_err(Error::Fsync)
497    }
498
499    async fn fdatasync(&self) -> Result<()> {
500        self.inner.fdatasync().await.map_err(Error::Fdatasync)
501    }
502
503    async fn read_to_mem<'a>(
504        &'a self,
505        file_offset: u64,
506        mem: Arc<dyn BackingMemory + Send + Sync>,
507        mem_offsets: cros_async::MemRegionIter<'a>,
508        options: IoOptions,
509    ) -> Result<usize> {
510        self.inner
511            .read_to_mem(Some(file_offset), mem, mem_offsets, options)
512            .await
513            .map_err(Error::ReadToMem)
514    }
515
516    async fn write_from_mem<'a>(
517        &'a self,
518        file_offset: u64,
519        mem: Arc<dyn BackingMemory + Send + Sync>,
520        mem_offsets: cros_async::MemRegionIter<'a>,
521        options: IoOptions,
522    ) -> Result<usize> {
523        self.inner
524            .write_from_mem(Some(file_offset), mem, mem_offsets, options)
525            .await
526            .map_err(Error::WriteFromMem)
527    }
528
529    async fn punch_hole(&self, file_offset: u64, length: u64) -> Result<()> {
530        #[cfg(any(target_os = "android", target_os = "linux"))]
531        if self.is_block_device_file {
532            return base::linux::discard_block(self.inner.as_source(), file_offset, length)
533                .map_err(Error::PunchHoleBlockDeviceFile);
534        }
535        self.inner
536            .punch_hole(file_offset, length)
537            .await
538            .map_err(Error::PunchHole)
539    }
540
541    async fn write_zeroes_at(&self, file_offset: u64, length: u64) -> Result<()> {
542        if self
543            .inner
544            .write_zeroes_at(file_offset, length)
545            .await
546            .is_ok()
547        {
548            return Ok(());
549        }
550
551        // Fall back to filling zeros if more efficient write_zeroes_at doesn't work.
552        let buf_size = min(length, 0x10000);
553        let mut nwritten = 0;
554        while nwritten < length {
555            let remaining = length - nwritten;
556            let write_size = min(remaining, buf_size) as usize;
557            let buf = vec![0u8; write_size];
558            nwritten += self
559                .inner
560                .write_from_vec(Some(file_offset + nwritten), buf, Default::default())
561                .await
562                .map(|(n, _)| n as u64)
563                .map_err(Error::WriteFromVec)?;
564        }
565        Ok(())
566    }
567}