1
0
forked from brl/citadel-tools

179 lines
5.6 KiB
Rust
Raw Normal View History

2018-12-11 11:19:12 -05:00
use std::path::PathBuf;
use std::fs::OpenOptions;
use std::fs::{self,File};
use std::io::{self,Write};
use failure::ResultExt;
2018-12-31 18:27:17 -05:00
use libcitadel::{Result,ImageHeader,util,verity};
2018-12-11 11:19:12 -05:00
use BuildConfig;
pub struct UpdateBuilder {
config: BuildConfig,
2018-12-31 18:27:17 -05:00
target: PathBuf,
2018-12-11 11:19:12 -05:00
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 {
2018-12-31 18:27:17 -05:00
pub fn new(config: BuildConfig) -> UpdateBuilder {
let filename = UpdateBuilder::target_filename(&config);
let target = config.workdir_path(&filename);
UpdateBuilder {
config, target,
2018-12-11 11:19:12 -05:00
nblocks: None, shasum: None, verity_salt: None,
verity_root: None,
2018-12-31 18:27:17 -05:00
}
}
2018-12-11 11:19:12 -05:00
2018-12-31 18:27:17 -05:00
fn target_filename(config: &BuildConfig) -> String {
format!("citadel-{}-{}-{:03}", config.img_name(), config.channel(), config.version())
2018-12-11 11:19:12 -05:00
}
pub fn build(&mut self) -> Result<()> {
2018-12-31 18:27:17 -05:00
info!("Copying source file to {}", self.target.display());
fs::copy(self.config.source(), &self.target)?;
2018-12-11 11:19:12 -05:00
self.pad_image()
.context("failed writing padding to image")?;
self.generate_verity()
.context("failed generating dm-verity hash tree")?;
self.calculate_shasum()?;
self.compress_image()?;
self.write_final_image()
.context("failed to write final image file")?;
Ok(())
}
fn pad_image(&mut self) -> Result<()> {
2018-12-31 18:27:17 -05:00
let meta = self.target.metadata()?;
2018-12-11 11:19:12 -05:00
let len = meta.len() as usize;
2018-12-31 18:27:17 -05:00
if len % 512 != 0 {
bail!("Image file size is not a multiple of sector size (512 bytes)");
}
2018-12-11 11:19:12 -05:00
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)
2018-12-31 18:27:17 -05:00
.open(&self.target)?;
2018-12-11 11:19:12 -05:00
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<()> {
2018-12-31 18:27:17 -05:00
let shasum = util::sha256(&self.target)?;
2018-12-11 11:19:12 -05:00
info!("Sha256 of image data is {}", shasum);
self.shasum = Some(shasum);
Ok(())
}
fn generate_verity(&mut self) -> Result<()> {
let hashfile = self.config.workdir_path(&format!("verity-hash-{}-{:03}", self.config.image_type(), self.config.version()));
let outfile = self.config.workdir_path("verity-format.out");
2018-12-31 18:27:17 -05:00
let verity = verity::generate_initial_hashtree(&self.target, &hashfile)?;
2018-12-11 11:19:12 -05:00
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<()> {
info!("Compressing image data");
2018-12-31 18:27:17 -05:00
util::xz_compress(&self.target)
2018-12-11 11:19:12 -05:00
}
fn write_final_image(&self) -> Result<()> {
let header = self.generate_header()?;
2018-12-31 18:27:17 -05:00
let filename = format!("{}.img", UpdateBuilder::target_filename(&self.config));
let mut image_path = self.config.workdir_path(&filename);
2018-12-11 11:19:12 -05:00
2018-12-31 18:27:17 -05:00
let mut out = File::create(&image_path)
.context(format!("could not open output file {}", image_path.display()))?;
2018-12-11 11:19:12 -05:00
2018-12-31 18:27:17 -05:00
header.write_header(&out)?;
2018-12-11 11:19:12 -05:00
2018-12-31 18:27:17 -05:00
image_path.set_extension("xz");
let mut data = File::open(&image_path)
.context(format!("could not open compressed image data file {}", image_path.display()))?;
2018-12-11 11:19:12 -05:00
io::copy(&mut data, &mut out)
.context("error copying image data to output file")?;
Ok(())
}
2018-12-31 18:27:17 -05:00
fn generate_header(&self) -> Result<ImageHeader> {
let hdr = ImageHeader::new();
hdr.set_flag(ImageHeader::FLAG_DATA_COMPRESSED);
2018-12-11 11:19:12 -05:00
let metainfo = self.generate_metainfo();
2019-01-02 12:20:29 -05:00
fs::write(self.config.workdir_path("metainfo"), &metainfo)?;
2018-12-31 18:27:17 -05:00
hdr.set_metainfo_bytes(&metainfo);
2018-12-11 11:19:12 -05:00
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())?;
2019-01-02 12:20:29 -05:00
if let Some(kv) = self.config.kernel_version() {
writeln!(v, "kernel-version = \"{}\"", kv)?;
}
2018-12-11 11:19:12 -05:00
writeln!(v, "channel = \"{}\"", self.config.channel())?;
writeln!(v, "version = {}", self.config.version())?;
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)
}
}