blob: 66230da0c79023d9a1752c49b7e06f46edb41ab4 [file] [log] [blame]
// 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.
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
);
println!(
"{} convert <src_file> <dst_file> - Convert from src_file to dst_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)
}
"convert" => {
if matches.free.len() < 2 {
println!("Source and destination files are required.");
show_usage(&args[0]);
return Err(());
}
convert(&matches.free[1], &matches.free[2])
}
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(())
}
// Reads the file at `src_path` and writes it to `dst_path`.
// The output format is detected based on the `dst_path` file extension.
fn convert(src_path: &str, dst_path: &str) -> std::result::Result<(), ()> {
let src_file = match OpenOptions::new().read(true).open(src_path) {
Ok(f) => f,
Err(_) => {
println!("Failed to open source file {}", src_path);
return Err(());
}
};
let dst_file = match OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(dst_path)
{
Ok(f) => f,
Err(_) => {
println!("Failed to open destination file {}", dst_path);
return Err(());
}
};
let dst_type = if dst_path.ends_with("qcow2") {
qcow::ImageType::Qcow2
} else {
qcow::ImageType::Raw
};
match qcow::convert(src_file, dst_file, dst_type) {
Ok(_) => {
println!("Converted {} to {}", src_path, dst_path);
Ok(())
}
Err(_) => {
println!("Failed to copy from {} to {}", src_path, dst_path);
Err(())
}
}
}