1
0
forked from brl/citadel-tools

222 lines
7.2 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;
use libcitadel::{Result,ImageHeader,verity,util,devkeys};
2018-12-11 11:19:12 -05:00
use crate::BuildConfig;
use std::path::Path;
2018-12-11 11:19:12 -05:00
pub struct UpdateBuilder {
config: BuildConfig,
image_data: 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 image_data = config.workdir_path(UpdateBuilder::build_filename(&config));
2018-12-31 18:27:17 -05:00
UpdateBuilder {
config, image_data,
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
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())
2018-12-11 11:19:12 -05:00
}
pub fn build(&mut self) -> Result<()> {
info!("Copying source file to {}", self.image_data.display());
fs::copy(self.config.source(), &self.image_data)?;
2018-12-31 18:27:17 -05:00
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.prepend_empty_block()?;
2018-12-11 11:19:12 -05:00
self.compress_image()?;
self.write_final_image()
.context("failed to write final image file")?;
Ok(())
}
fn image(&self) -> &Path {
&self.image_data
}
2018-12-11 11:19:12 -05:00
fn pad_image(&mut self) -> Result<()> {
let meta = self.image().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)
.open(self.image())?;
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<()> {
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();
2018-12-11 11:19:12 -05:00
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(())
}
2018-12-11 11:19:12 -05:00
fn generate_verity(&mut self) -> Result<()> {
let hashfile = self.config.workdir_path(self.verity_filename());
2018-12-11 11:19:12 -05:00
let outfile = self.config.workdir_path("verity-format.out");
let verity = verity::generate_initial_hashtree(self.image(), &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<()> {
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(())
2018-12-11 11:19:12 -05:00
}
fn write_final_image(&self) -> Result<()> {
let header = self.generate_header()?;
let target = self.config.workdir_path(self.target_filename());
2018-12-11 11:19:12 -05:00
let mut out = File::create(&target)
.context(format!("could not open output file {}", target.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
let mut data = File::open(&self.image())
.context(format!("could not open image data file {}", self.image().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();
if self.config.compress() {
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);
2019-01-05 20:38:57 -05:00
if self.config.channel() == "dev" {
let sig = devkeys().sign(&metainfo);
hdr.set_signature(sig.to_bytes())?;
}
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)?;
}
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)?;
}
2018-12-11 11:19:12 -05:00
writeln!(v, "channel = \"{}\"", self.config.channel())?;
writeln!(v, "version = {}", self.config.version())?;
writeln!(v, "timestamp = \"{}\"", self.config.timestamp())?;
2018-12-11 11:19:12 -05:00
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)
}
}