diff options
author | Dylan Reid <dgreid@chromium.org> | 2018-09-04 17:26:37 -0700 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2018-09-19 15:40:06 -0700 |
commit | cd9f86b2994a20347e1855e112369e5052f5a8cb (patch) | |
tree | 3a146777eefa5dcfa49d54928b2dfeec16274070 /qcow_utils | |
parent | 39401ff269cf74e0eaedbd7d0bff510ed01e4782 (diff) | |
download | crosvm-cd9f86b2994a20347e1855e112369e5052f5a8cb.tar crosvm-cd9f86b2994a20347e1855e112369e5052f5a8cb.tar.gz crosvm-cd9f86b2994a20347e1855e112369e5052f5a8cb.tar.bz2 crosvm-cd9f86b2994a20347e1855e112369e5052f5a8cb.tar.lz crosvm-cd9f86b2994a20347e1855e112369e5052f5a8cb.tar.xz crosvm-cd9f86b2994a20347e1855e112369e5052f5a8cb.tar.zst crosvm-cd9f86b2994a20347e1855e112369e5052f5a8cb.zip |
qcow: Add a utility program for qcow analysis
This program makes figuring out the state of a qcow file easier. Change-Id: If297eb0cd835a86d8f284d3aef3d7e962e095726 Signed-off-by: Dylan Reid <dgreid@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/1207455 Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Diffstat (limited to 'qcow_utils')
-rw-r--r-- | qcow_utils/Cargo.toml | 6 | ||||
-rw-r--r-- | qcow_utils/src/qcow_img.rs | 272 |
2 files changed, 278 insertions, 0 deletions
diff --git a/qcow_utils/Cargo.toml b/qcow_utils/Cargo.toml index 5ec3343..ca7e860 100644 --- a/qcow_utils/Cargo.toml +++ b/qcow_utils/Cargo.toml @@ -7,6 +7,12 @@ authors = ["The Chromium OS Authors"] path = "src/qcow_utils.rs" crate-type = ["cdylib"] +[[bin]] +name = "qcow_img" +path = "src/qcow_img.rs" + [dependencies] +getopts = "*" libc = "*" qcow = { path = "../qcow" } +sys_util = { path = "../sys_util" } diff --git a/qcow_utils/src/qcow_img.rs b/qcow_utils/src/qcow_img.rs new file mode 100644 index 0000000..1040de2 --- /dev/null +++ b/qcow_utils/src/qcow_img.rs @@ -0,0 +1,272 @@ +// Copyright 2018 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +extern crate getopts; +extern crate qcow; +extern crate sys_util; + +use std::fs::OpenOptions; +use std::io::{Read, Write}; + +use getopts::Options; + +use qcow::QcowFile; +use sys_util::WriteZeroes; + +fn show_usage(program_name: &str) { + println!("Usage: {} [subcommand] <subcommand args>", program_name); + println!("\nSubcommands:"); + println!( + "{} header <file name> - Show the qcow2 header for a file.", + program_name + ); + println!( + "{} l1_table <file name> - Show the L1 table entries for a file.", + program_name + ); + println!( + "{} l22table <file name> <l1 index> - Show the L2 table pointed to by the nth L1 entry.", + program_name + ); + println!( + "{} ref_table <file name> - Show the refblock table for the file.", + program_name + ); + println!( + "{} ref_block <file_name> <table index> - Show the nth reblock in the file.", + program_name + ); + println!( + "{} dd <file_name> <source_file> - Write bytes from the raw source_file to the file.", + program_name + ); +} + +fn main() -> std::result::Result<(), ()> { + let args: Vec<String> = std::env::args().collect(); + let opts = Options::new(); + + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => panic!(f.to_string()), + }; + + if matches.free.len() < 2 { + println!("Must specify the subcommand and the QCOW file to operate on."); + show_usage(&args[0]); + return Err(()); + } + + match matches.free[0].as_ref() { + "header" => show_header(&matches.free[1]), + "help" => { + show_usage(&args[0]); + Ok(()) + } + "l1_table" => show_l1_table(&matches.free[1]), + "l2_table" => { + if matches.free.len() < 2 { + println!("Filename and table index are required."); + show_usage(&args[0]); + return Err(()); + } + show_l2_table(&matches.free[1], matches.free[2].parse().unwrap()) + } + "ref_table" => show_ref_table(&matches.free[1]), + "ref_block" => { + if matches.free.len() < 2 { + println!("Filename and block index are required."); + show_usage(&args[0]); + return Err(()); + } + show_ref_block(&matches.free[1], matches.free[2].parse().unwrap()) + } + "dd" => { + if matches.free.len() < 2 { + println!("Qcow and source file are required."); + show_usage(&args[0]); + return Err(()); + } + let count = if matches.free.len() > 3 { + Some(matches.free[3].parse().unwrap()) + } else { + None + }; + dd(&matches.free[1], &matches.free[2], count) + } + c => { + println!("invalid subcommand: {:?}", c); + Err(()) + } + } +} + +fn show_header(file_path: &str) -> std::result::Result<(), ()> { + let file = match OpenOptions::new().read(true).open(file_path) { + Ok(f) => f, + Err(_) => { + println!("Failed to open {}", file_path); + return Err(()); + } + }; + + let qcow_file = QcowFile::from(file).map_err(|_| ())?; + let header = qcow_file.header(); + + println!("magic {:x}", header.magic); + println!("version {:x}", header.version); + println!("backing_file_offset {:x}", header.backing_file_offset); + println!("backing_file_size {:x}", header.backing_file_size); + println!("cluster_bits {:x}", header.cluster_bits); + println!("size {:x}", header.size); + println!("crypt_method {:x}", header.crypt_method); + println!("l1_size {:x}", header.l1_size); + println!("l1_table_offset {:x}", header.l1_table_offset); + println!("refcount_table_offset {:x}", header.refcount_table_offset); + println!( + "refcount_table_clusters {:x}", + header.refcount_table_clusters + ); + println!("nb_snapshots {:x}", header.nb_snapshots); + println!("snapshots_offset {:x}", header.snapshots_offset); + println!("incompatible_features {:x}", header.incompatible_features); + println!("compatible_features {:x}", header.compatible_features); + println!("autoclear_features {:x}", header.autoclear_features); + println!("refcount_order {:x}", header.refcount_order); + println!("header_size {:x}", header.header_size); + Ok(()) +} + +fn show_l1_table(file_path: &str) -> std::result::Result<(), ()> { + let file = match OpenOptions::new().read(true).open(file_path) { + Ok(f) => f, + Err(_) => { + println!("Failed to open {}", file_path); + return Err(()); + } + }; + + let qcow_file = QcowFile::from(file).map_err(|_| ())?; + let l1_table = qcow_file.l1_table(); + + for (i, l2_offset) in l1_table.iter().enumerate() { + println!("{}: {:x}", i, l2_offset); + } + + Ok(()) +} + +fn show_l2_table(file_path: &str, index: usize) -> std::result::Result<(), ()> { + let file = match OpenOptions::new().read(true).open(file_path) { + Ok(f) => f, + Err(_) => { + println!("Failed to open {}", file_path); + return Err(()); + } + }; + + let mut qcow_file = QcowFile::from(file).map_err(|_| ())?; + let l2_table = qcow_file.l2_table(index).unwrap(); + + if let Some(cluster_addrs) = l2_table { + for (i, addr) in cluster_addrs.iter().enumerate() { + if i % 16 == 0 { + print!("\n{:x}:", i); + } + print!(" {:x}", addr); + } + } + + Ok(()) +} + +fn show_ref_table(file_path: &str) -> std::result::Result<(), ()> { + let file = match OpenOptions::new().read(true).open(file_path) { + Ok(f) => f, + Err(_) => { + println!("Failed to open {}", file_path); + return Err(()); + } + }; + + let qcow_file = QcowFile::from(file).map_err(|_| ())?; + let ref_table = qcow_file.ref_table(); + + for (i, block_offset) in ref_table.iter().enumerate() { + println!("{}: {:x}", i, block_offset); + } + + Ok(()) +} + +fn show_ref_block(file_path: &str, index: usize) -> std::result::Result<(), ()> { + let file = match OpenOptions::new().read(true).open(file_path) { + Ok(f) => f, + Err(_) => { + println!("Failed to open {}", file_path); + return Err(()); + } + }; + + let mut qcow_file = QcowFile::from(file).map_err(|_| ())?; + let ref_table = qcow_file.refcount_block(index).unwrap(); + + if let Some(counts) = ref_table { + for (i, count) in counts.iter().enumerate() { + if i % 16 == 0 { + print!("\n{:x}:", i); + } + print!(" {:x}", count); + } + } + + Ok(()) +} + +// Transfers from a raw file specifiec in `source_path` to the qcow file specified in `file_path`. +fn dd(file_path: &str, source_path: &str, count: Option<usize>) -> std::result::Result<(), ()> { + let file = match OpenOptions::new().read(true).write(true).open(file_path) { + Ok(f) => f, + Err(_) => { + println!("Failed to open {}", file_path); + return Err(()); + } + }; + + let mut qcow_file = QcowFile::from(file).map_err(|_| ())?; + + let mut src_file = match OpenOptions::new().read(true).open(source_path) { + Ok(f) => f, + Err(_) => { + println!("Failed to open {}", file_path); + return Err(()); + } + }; + + let mut read_count = 0; + const CHUNK_SIZE: usize = 65536; + let mut buf = [0; CHUNK_SIZE]; + loop { + let this_count = if let Some(count) = count { + std::cmp::min(CHUNK_SIZE, count - read_count) + } else { + CHUNK_SIZE + }; + let nread = src_file.read(&mut buf[..this_count]).map_err(|_| ())?; + // If this block is all zeros, then use write_zeros so the output file is sparse. + if buf.iter().all(|b| *b == 0) { + qcow_file.write_zeroes(CHUNK_SIZE).map_err(|_| ())?; + } else { + qcow_file.write(&buf).map_err(|_| ())?; + } + read_count = read_count + nread; + if nread == 0 || Some(read_count) == count { + break; + } + } + + println!("wrote {} bytes", read_count); + + Ok(()) +} |