use std::fs::{File, OpenOptions};
use std::io::{BufReader, BufWriter, Read};
use std::path::Path;
use anyhow::{bail, Context, Result};
use bincode::Options;
use clap::crate_version;
use serde::{Deserialize, Serialize};
use xz2::bufread::XzDecoder;
use crate::io::{bincoder, BUFFER_SIZE};
use super::*;
const OSMET_FILE_HEADER_MAGIC: [u8; 8] = *b"OSMET\0\0\0";
const OSMET_FILE_VERSION: u32 = 1;
#[derive(Serialize, Deserialize, Debug)]
pub(super) struct OsmetFileHeader {
magic: [u8; 8],
version: u32,
app_version: String,
pub(super) sector_size: u32,
pub(super) os_description: String,
pub(super) os_architecture: String,
}
impl OsmetFileHeader {
pub(super) fn new(sector_size: u32, os_description: &str) -> Result<Self> {
Ok(Self {
magic: OSMET_FILE_HEADER_MAGIC,
version: OSMET_FILE_VERSION,
app_version: crate_version!().into(),
sector_size,
os_description: os_description.into(),
os_architecture: nix::sys::utsname::uname()
.context("uname(2)")?
.machine()
.to_str()
.context("OS architecture is invalid Unicode")?
.into(),
})
}
}
pub(super) fn osmet_file_write(
path: &Path,
header: OsmetFileHeader,
osmet: Osmet,
mut xzpacked_image: File,
) -> Result<()> {
validate_osmet(&osmet).context("validating before writing")?;
let mut f = BufWriter::with_capacity(
BUFFER_SIZE,
tempfile::Builder::new()
.prefix("coreos-installer-osmet")
.suffix(".partial")
.tempfile_in(path.parent().unwrap())?,
);
let coder = &mut bincoder();
coder
.serialize_into(&mut f, &header)
.context("failed to serialize osmet file header")?;
coder
.serialize_into(&mut f, &osmet)
.context("failed to serialize osmet")?;
copy(&mut xzpacked_image, &mut f)?;
f.into_inner()
.context("failed to flush write buffer")?
.persist(path)
.with_context(|| format!("failed to persist tempfile to {:?}", path))?;
Ok(())
}
fn read_and_check_header(mut f: &mut impl Read) -> Result<OsmetFileHeader> {
let header: OsmetFileHeader = bincoder()
.deserialize_from(&mut f)
.context("failed to deserialize osmet file")?;
if header.magic != OSMET_FILE_HEADER_MAGIC {
bail!("not an OSMET file!");
}
if header.version != OSMET_FILE_VERSION {
bail!("incompatible OSMET file version {}", header.version);
}
Ok(header)
}
pub(super) fn osmet_file_read_header(path: &Path) -> Result<OsmetFileHeader> {
let mut f = BufReader::with_capacity(
BUFFER_SIZE,
OpenOptions::new()
.read(true)
.open(path)
.with_context(|| format!("opening {:?}", path))?,
);
read_and_check_header(&mut f)
}
pub(super) fn osmet_file_read(path: &Path) -> Result<(OsmetFileHeader, Osmet, impl Read + Send)> {
let mut f = BufReader::with_capacity(
BUFFER_SIZE,
OpenOptions::new()
.read(true)
.open(path)
.with_context(|| format!("opening {:?}", path))?,
);
let header = read_and_check_header(&mut f)?;
let osmet: Osmet = bincoder()
.deserialize_from(&mut f)
.context("failed to deserialize osmet file")?;
validate_osmet(&osmet).context("validating after reading")?;
Ok((header, osmet, XzDecoder::new(f)))
}
fn validate_osmet(osmet: &Osmet) -> Result<()> {
if osmet.partitions.is_empty() {
bail!("OSMET file has no partitions!");
}
let mut cursor: u64 = 0;
for (i, partition) in osmet.partitions.iter().enumerate() {
if cursor > partition.start_offset {
bail!(
"cursor past partition start: {} vs {}",
cursor,
partition.start_offset
);
}
cursor = cursor
.checked_add(
verify_canonical(&partition.mappings)
.with_context(|| format!("partition {}", i))?,
)
.with_context(|| format!("overflow after partition {}", i))?;
if cursor > partition.end_offset {
bail!(
"cursor past partition end: {} vs {}",
cursor,
partition.end_offset
);
}
cursor = partition.end_offset;
}
Ok(())
}
fn verify_canonical(mappings: &[Mapping]) -> Result<u64> {
let mut cursor: u64 = 0;
for (i, mapping) in mappings.iter().enumerate() {
if cursor > mapping.extent.physical {
bail!(
"cursor past mapping start: {} vs {}",
cursor,
mapping.extent.physical
);
}
cursor = mapping
.extent
.physical
.checked_add(mapping.extent.length)
.with_context(|| format!("overflow after mapping {}", i))?;
}
Ok(cursor)
}