mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2024-11-24 12:34:31 +00:00
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>
This commit is contained in:
parent
39401ff269
commit
cd9f86b299
3 changed files with 280 additions and 0 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -322,8 +322,10 @@ dependencies = [
|
|||
name = "qcow_utils"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"getopts 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"qcow 0.1.0",
|
||||
"sys_util 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -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" }
|
||||
|
|
272
qcow_utils/src/qcow_img.rs
Normal file
272
qcow_utils/src/qcow_img.rs
Normal file
|
@ -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(())
|
||||
}
|
Loading…
Reference in a new issue