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;
|
2019-01-05 20:40:07 -05:00
|
|
|
use libcitadel::{Result,ImageHeader,verity,util,devkeys};
|
2018-12-11 11:19:12 -05:00
|
|
|
|
2019-01-17 09:25:24 -05:00
|
|
|
use crate::BuildConfig;
|
2019-01-29 11:50:10 -05:00
|
|
|
use std::path::Path;
|
2018-12-11 11:19:12 -05:00
|
|
|
|
|
|
|
pub struct UpdateBuilder {
|
|
|
|
config: BuildConfig,
|
2019-01-15 09:38:42 -05:00
|
|
|
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 {
|
2019-01-29 11:50:10 -05:00
|
|
|
let image_data = config.workdir_path(UpdateBuilder::build_filename(&config));
|
2018-12-31 18:27:17 -05:00
|
|
|
UpdateBuilder {
|
2019-01-15 09:38:42 -05:00
|
|
|
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
|
|
|
|
2019-01-29 11:50:10 -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<()> {
|
2019-01-15 09:38:42 -05:00
|
|
|
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()?;
|
2019-01-29 11:50:10 -05:00
|
|
|
|
|
|
|
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(())
|
|
|
|
}
|
|
|
|
|
2019-01-29 11:50:10 -05:00
|
|
|
fn image(&self) -> &Path {
|
|
|
|
&self.image_data
|
|
|
|
}
|
|
|
|
|
2018-12-11 11:19:12 -05:00
|
|
|
fn pad_image(&mut self) -> Result<()> {
|
2019-01-29 11:50:10 -05:00
|
|
|
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)
|
2019-01-29 11:50:10 -05:00
|
|
|
.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<()> {
|
2019-01-29 11:50:10 -05:00
|
|
|
let output = util::exec_cmdline_with_output("sha256sum", self.image().display().to_string())
|
|
|
|
.context(format!("failed to calculate sha256 on {}", self.image().display()))?;
|
2019-01-05 20:40:07 -05:00
|
|
|
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(())
|
|
|
|
}
|
|
|
|
|
2019-01-29 11:50:10 -05:00
|
|
|
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<()> {
|
2019-01-29 11:50:10 -05:00
|
|
|
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");
|
|
|
|
|
2019-01-29 11:50:10 -05:00
|
|
|
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<()> {
|
2019-01-15 09:38:42 -05:00
|
|
|
if self.config.compress() {
|
|
|
|
info!("Compressing image data");
|
2019-01-29 11:50:10 -05:00
|
|
|
util::exec_cmdline("xz", format!("-T0 {}", self.image().display()))
|
|
|
|
.context(format!("failed to compress {}", self.image().display()))?;
|
2019-01-15 09:38:42 -05:00
|
|
|
// Rename back to original image_data filename
|
2019-01-29 11:50:10 -05:00
|
|
|
fs::rename(self.image().with_extension("xz"), self.image())?;
|
2019-01-15 09:38:42 -05:00
|
|
|
}
|
2019-01-05 20:40:07 -05:00
|
|
|
Ok(())
|
2018-12-11 11:19:12 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
fn write_final_image(&self) -> Result<()> {
|
|
|
|
let header = self.generate_header()?;
|
2019-01-29 11:50:10 -05:00
|
|
|
let target = self.config.workdir_path(self.target_filename());
|
2018-12-11 11:19:12 -05:00
|
|
|
|
2019-01-29 11:50:10 -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
|
|
|
|
2019-01-29 11:50:10 -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();
|
2019-01-15 09:38:42 -05:00
|
|
|
|
|
|
|
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)?;
|
|
|
|
}
|
2019-01-06 18:05:05 -05:00
|
|
|
if let Some(kid) = self.config.kernel_id() {
|
|
|
|
writeln!(v, "kernel-id = \"{}\"", kid)?;
|
|
|
|
}
|
2019-01-29 11:50:10 -05:00
|
|
|
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())?;
|
2019-01-07 18:59:21 -05:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|