1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
//! A library for working with qcow (version 1, 2, and 3) files.
//!
//! ## Example
//!
//! ```rust
//! # const PATH: &str = "/home/jamcleod/.panda/bionic-server-cloudimg-amd64-noaslr-nokaslr.qcow2";
//! # use std::fs::File;
//! # use std::io::{Read, BufReader};
//! // open qcow
//! let qcow = qcow::open(PATH).unwrap();
//!
//! // print out list of snapshots in the qcow
//! for snapshot in qcow.snapshots() {
//! println!(
//! "Snapshot {:?}: {:?} (size = {})",
//! snapshot.unique_id,
//! snapshot.name,
//! snapshot.vm_state_size
//! );
//! }
//!
//! // create a reader for accessing the virtual hard disk
//! let mut file = BufReader::new(File::open(PATH)?);
//! let qcow2 = qcow.unwrap_qcow2();
//! let mut reader = qcow2.reader(&mut file);
//!
//! // read the first 10 bytes of the virtual hard disk
//! let mut buf = [0; 10];
//! reader.read_exact(&mut buf)?;
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
//!
//! ## Important types/functions
//!
//! * Retrieving a qcow - [`open`] (from path), [`load`] (from reader), [`load_from_memory`] (from
//! slice)
//! * Converting to qcow2 - [`DynamicQcow::unwrap_qcow2`]
//! * Reading from a virtual hard disk - [`Qcow2::reader`] (returns [`Reader`], which implements
//! [`Read`](std::io::Read) + [`Seek`](std::io::Seek))
//!
//! ## Features
//!
//! * Parse qcow files
//! * Full qcow version 1 support
//! * Support for parsing the header and some associated data
//! * Full qcow version 2-3 support
//! * Header parsing, including extra version 3 header data
//! * Header extension parsing, allowing you to use addition data they provide
//! * Lookup table (L1 and L2) parsing, only loading L2 tables on demand
//! * Snapshot parsing, including snapshot L1 lookup tables
//! * Support for reading the contents of the virtual disk
//! * Includes compression support (for both zlib and zstd)
//! * Cluster lookup caching, backtracking on cache miss
//! * Allows arbitrary seeking within the guest
#![warn(missing_docs)]
use binread::{
derive_binread,
io::{Read, Seek, SeekFrom},
until_exclusive, BinRead, BinReaderExt, BinResult, ReadOptions,
};
use modular_bitfield::prelude::*;
use std::fs::File;
use std::io::{BufReader, Cursor};
use std::path::Path;
mod methods;
mod reader;
pub use reader::*;
mod error;
pub use error::Error;
mod header;
pub use header::*;
/// Module for types pertaining to QCOW header extensions
pub mod header_ext;
pub mod levels;
use levels::*;
/// Module containing structs specific to the legacy QCOW v1 format
pub mod v1;
use v1::Qcow1Header;
mod snapshots;
pub use snapshots::*;
mod features;
pub use features::*;
mod dynamic_qcow;
pub use dynamic_qcow::DynamicQcow;
/// Parsed representation of a qcow2 file.
///
/// Can be aquired by using one of:
///
/// * [`open`]
/// * [`load`]
/// * [`load_from_memory`]
///
/// and then using [`DynamicQcow::unwrap_qcow2`].
///
/// ## Example
///
/// ```rust
/// # const PATH: &str = "/home/jamcleod/.panda/bionic-server-cloudimg-amd64-noaslr-nokaslr.qcow2";
/// let qcow = qcow::open(PATH).unwrap();
/// ```
#[derive(BinRead, Debug)]
#[br(big)]
pub struct Qcow2 {
/// Header of the qcow as parsed from the file, contains top-level data about the qcow
pub header: QcowHeader,
/// List of snapshots present within this qcow
#[br(seek_before = SeekFrom::Start(header.snapshots_offset), count = header.nb_snapshots)]
pub snapshots: Vec<Snapshot>,
/// Active table of [`L1Entry`]s used for handling lookups of contents
#[br(seek_before = SeekFrom::Start(header.l1_table_offset), count = header.l1_size)]
pub l1_table: Vec<L1Entry>,
}
/// Parsed representation of a v1 qcow file (legacy)
#[derive(BinRead, Debug)]
#[br(big)]
pub struct Qcow1 {
/// Header of the qcow as parsed from the file
pub header: Qcow1Header,
}
#[derive(BinRead, Debug)]
#[br(big, magic = b"QFI\xfb")]
struct QcowVersion(u32);
/// Open a qcow or qcow2 file from a path
///
/// ## Example
///
/// ```rust
/// # const PATH: &str = "/home/jamcleod/.panda/bionic-server-cloudimg-amd64-noaslr-nokaslr.qcow2";
/// let qcow = qcow::open(PATH)?;
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn open(path: impl AsRef<Path>) -> Result<DynamicQcow, Error> {
let path = path.as_ref();
let mut file = BufReader::new(File::open(path).map_err(Error::FileNotFound)?);
load(&mut file)
}
/// Read a qcow or qcow2 file from a reader
///
/// **Note**: unlike [`open`] this does not buffer your I/O. Any buffering should be handled via a
/// wrapper such as [`BufReader`] in order to ensure good performance where applicable.
///
/// ## Example
///
/// ```rust
/// # const PATH: &str = "/home/jamcleod/.panda/bionic-server-cloudimg-amd64-noaslr-nokaslr.qcow2";
/// # use std::fs::File;
/// # use std::io::BufReader;
/// let mut file = BufReader::new(File::open(PATH)?);
/// let qcow = qcow::load(&mut file)?;
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn load(reader: &mut (impl Read + Seek)) -> Result<DynamicQcow, Error> {
let QcowVersion(version) = reader.read_be()?;
reader.seek(SeekFrom::Start(0)).unwrap();
if version == 1 {
reader.read_be().map(DynamicQcow::Qcow1)
} else {
reader.read_be().map(DynamicQcow::Qcow2)
}
.map_err(Error::from)
}
/// Read a qcow or qcow2 file from a slice
pub fn load_from_memory(bytes: &[u8]) -> Result<DynamicQcow, Error> {
load(&mut Cursor::new(bytes))
}