1
0
forked from brl/citadel-tools
Bruce Leidl 3782668514 Refactor how images are built to support realmfs images
Main change in building images is that an empty 4096 byte block is
prepended to raw image before compression so that upon decompression
the header can be written without having to shuffle around decompressed
image.
2019-01-29 11:50:10 -05:00

222 lines
7.2 KiB
Rust

use std::path::PathBuf;
use std::fs::OpenOptions;
use std::fs::{self,File};
use std::io::{self,Write};
use failure::ResultExt;
use libcitadel::{Result,ImageHeader,verity,util,devkeys};
use crate::BuildConfig;
use std::path::Path;
pub struct UpdateBuilder {
config: BuildConfig,
image_data: PathBuf,
nblocks: Option<usize>,
shasum: Option<String>,
verity_salt: Option<String>,
verity_root: Option<String>,
}
const BLOCK_SIZE: usize = 4096;
fn align(sz: usize, n: usize) -> usize {
(sz + (n - 1)) & !(n - 1)
}
impl UpdateBuilder {
pub fn new(config: BuildConfig) -> UpdateBuilder {
let image_data = config.workdir_path(UpdateBuilder::build_filename(&config));
UpdateBuilder {
config, image_data,
nblocks: None, shasum: None, verity_salt: None,
verity_root: None,
}
}
fn target_filename(&self) -> String {
format!("citadel-{}-{}-{:03}.img", self.config.img_name(), self.config.channel(), self.config.version())
}
fn build_filename(config: &BuildConfig) -> String {
format!("citadel-{}-{}-{:03}", config.image_type(), config.channel(), config.version())
}
fn verity_filename(&self) -> String {
format!("verity-hash-{}-{:03}", self.config.image_type(), self.config.version())
}
pub fn build(&mut self) -> Result<()> {
info!("Copying source file to {}", self.image_data.display());
fs::copy(self.config.source(), &self.image_data)?;
self.pad_image()
.context("failed writing padding to image")?;
self.generate_verity()
.context("failed generating dm-verity hash tree")?;
self.calculate_shasum()?;
self.prepend_empty_block()?;
self.compress_image()?;
self.write_final_image()
.context("failed to write final image file")?;
Ok(())
}
fn image(&self) -> &Path {
&self.image_data
}
fn pad_image(&mut self) -> Result<()> {
let meta = self.image().metadata()?;
let len = meta.len() as usize;
if len % 512 != 0 {
bail!("Image file size is not a multiple of sector size (512 bytes)");
}
let padlen = align(len, BLOCK_SIZE) - len;
if padlen > 0 {
info!("Padding image with {} zero bytes to 4096 byte block boundary", padlen);
let zeros = vec![0u8; padlen];
let mut file = OpenOptions::new()
.append(true)
.open(self.image())?;
file.write_all(&zeros)?;
}
let nblocks = (len + padlen) / 4096;
info!("Image contains {} blocks of data", nblocks);
self.nblocks = Some(nblocks);
Ok(())
}
fn calculate_shasum(&mut self) -> Result<()> {
let output = util::exec_cmdline_with_output("sha256sum", self.image().display().to_string())
.context(format!("failed to calculate sha256 on {}", self.image().display()))?;
let v: Vec<&str> = output.split_whitespace().collect();
let shasum = v[0].trim().to_owned();
info!("Sha256 of image data is {}", shasum);
self.shasum = Some(shasum);
Ok(())
}
fn prepend_empty_block(&mut self) -> Result<()> {
let tmpfile = self.image().with_extension("tmp");
let args = format!("if={} of={} bs=4096 seek=1 conv=sparse", self.image().display(), tmpfile.display());
util::exec_cmdline("/usr/bin/dd", args)?;
fs::rename(tmpfile, self.image())?;
Ok(())
}
fn generate_verity(&mut self) -> Result<()> {
let hashfile = self.config.workdir_path(self.verity_filename());
let outfile = self.config.workdir_path("verity-format.out");
let verity = verity::generate_initial_hashtree(self.image(), &hashfile)?;
fs::write(outfile, verity.output())
.context("failed to write veritysetup command output to a file")?;
let root = match verity.root_hash() {
Some(s) => s.to_owned(),
None => bail!("no root hash found in verity format output"),
};
let salt = match verity.salt() {
Some(s) => s.to_owned(),
None => bail!("no verity salt found in verity format output"),
};
info!("Verity hash tree calculated, verity-root = {}", root);
self.verity_salt = Some(salt);
self.verity_root = Some(root);
Ok(())
}
fn compress_image(&self) -> Result<()> {
if self.config.compress() {
info!("Compressing image data");
util::exec_cmdline("xz", format!("-T0 {}", self.image().display()))
.context(format!("failed to compress {}", self.image().display()))?;
// Rename back to original image_data filename
fs::rename(self.image().with_extension("xz"), self.image())?;
}
Ok(())
}
fn write_final_image(&self) -> Result<()> {
let header = self.generate_header()?;
let target = self.config.workdir_path(self.target_filename());
let mut out = File::create(&target)
.context(format!("could not open output file {}", target.display()))?;
header.write_header(&out)?;
let mut data = File::open(&self.image())
.context(format!("could not open image data file {}", self.image().display()))?;
io::copy(&mut data, &mut out)
.context("error copying image data to output file")?;
Ok(())
}
fn generate_header(&self) -> Result<ImageHeader> {
let hdr = ImageHeader::new();
if self.config.compress() {
hdr.set_flag(ImageHeader::FLAG_DATA_COMPRESSED);
}
let metainfo = self.generate_metainfo();
fs::write(self.config.workdir_path("metainfo"), &metainfo)?;
hdr.set_metainfo_bytes(&metainfo);
if self.config.channel() == "dev" {
let sig = devkeys().sign(&metainfo);
hdr.set_signature(sig.to_bytes())?;
}
Ok(hdr)
}
fn generate_metainfo(&self) -> Vec<u8> {
// writes to Vec can't fail, unwrap once to avoid clutter
self._generate_metainfo().unwrap()
}
fn _generate_metainfo(&self) -> Result<Vec<u8>> {
assert!(self.verity_salt.is_some() && self.verity_root.is_some(),
"no verity-salt/verity-root in generate_metainfo()");
let mut v = Vec::new();
writeln!(v, "image-type = \"{}\"", self.config.image_type())?;
if let Some(kv) = self.config.kernel_version() {
writeln!(v, "kernel-version = \"{}\"", kv)?;
}
if let Some(kid) = self.config.kernel_id() {
writeln!(v, "kernel-id = \"{}\"", kid)?;
}
if let Some(name) = self.config.realmfs_name() {
writeln!(v, "realmfs-name = \"{}\"", name)?;
}
writeln!(v, "channel = \"{}\"", self.config.channel())?;
writeln!(v, "version = {}", self.config.version())?;
writeln!(v, "timestamp = \"{}\"", self.config.timestamp())?;
writeln!(v, "nblocks = {}", self.nblocks.unwrap())?;
writeln!(v, "shasum = \"{}\"", self.shasum.as_ref().unwrap())?;
writeln!(v, "verity-salt = \"{}\"", self.verity_salt.as_ref().unwrap())?;
writeln!(v, "verity-root = \"{}\"", self.verity_root.as_ref().unwrap())?;
Ok(v)
}
}