blob: 8f06f093c94c38d396192d8ace9f8e2b4277322f [file] [log] [blame]
// Copyright 2022, The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Attach/Detach bootconfigs to initrd image
use anyhow::{bail, Result};
use clap::Parser;
use std::cmp::min;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write};
use std::mem::size_of;
use std::path::PathBuf;
const FOOTER_ALIGNMENT: usize = 4;
const ZEROS: [u8; 4] = [0u8; 4_usize];
const BOOTCONFIG_MAGIC: &str = "#BOOTCONFIG\n";
// Footer includes [size(le32)][checksum(le32)][#BOOTCONFIG\n] at the end of bootconfigs.
const INITRD_FOOTER_LEN: usize = 2 * std::mem::size_of::<u32>() + BOOTCONFIG_MAGIC.len();
#[derive(Parser, Debug)]
enum Opt {
/// Append bootconfig(s) to initrd image
Attach {
/// Initrd (without bootconfigs) <- Input
initrd: PathBuf,
/// Bootconfigs <- Input
bootconfigs: Vec<PathBuf>,
/// Initrd (with bootconfigs) <- Output
#[clap(long = "output")]
output: PathBuf,
},
/// Detach the initrd & bootconfigs - this is required for cases when we update
/// bootconfigs in sign_virt_apex
Detach {
/// Initrd (with bootconfigs) <- Input
initrd_with_bootconfigs: PathBuf,
/// Initrd (without bootconfigs) <- Output
initrd: PathBuf,
/// Bootconfigs <- Output
bootconfigs: PathBuf,
},
}
fn get_checksum(file_path: &PathBuf) -> Result<u32> {
File::open(file_path)?.bytes().map(|x| Ok(x? as u32)).sum()
}
// Copy n bytes of file_in to file_out. Note: copying starts from the current cursors of files.
// On successful return, the files' cursors would have moved forward by k bytes.
fn copyfile2file(file_in: &mut File, file_out: &mut File, n: usize) -> Result<()> {
let mut buf = vec![0; 1024];
let mut copied: usize = 0;
while copied < n {
let k = min(n - copied, buf.len());
file_in.read_exact(&mut buf[..k])?;
file_out.write_all(&buf[..k])?;
copied += k;
}
Ok(())
}
// Note: attaching & then detaching bootconfigs can lead to extra padding in bootconfigs
fn detach_bootconfig(initrd_bc: PathBuf, initrd: PathBuf, bootconfig: PathBuf) -> Result<()> {
let mut initrd_bc = File::open(initrd_bc)?;
let mut bootconfig = File::create(bootconfig)?;
let mut initrd = File::create(initrd)?;
let initrd_bc_size: usize = initrd_bc.metadata()?.len().try_into()?;
initrd_bc.seek(SeekFrom::End(-(BOOTCONFIG_MAGIC.len() as i64)))?;
let mut magic_buf = [0; BOOTCONFIG_MAGIC.len()];
initrd_bc.read_exact(&mut magic_buf)?;
if magic_buf != BOOTCONFIG_MAGIC.as_bytes() {
bail!("BOOTCONFIG_MAGIC not found in initrd. Bootconfigs might not be attached correctly");
}
let mut size_buf = [0; size_of::<u32>()];
initrd_bc.seek(SeekFrom::End(-(INITRD_FOOTER_LEN as i64)))?;
initrd_bc.read_exact(&mut size_buf)?;
let bc_size: usize = u32::from_le_bytes(size_buf) as usize;
let initrd_size: usize = initrd_bc_size - bc_size - INITRD_FOOTER_LEN;
initrd_bc.rewind()?;
copyfile2file(&mut initrd_bc, &mut initrd, initrd_size)?;
copyfile2file(&mut initrd_bc, &mut bootconfig, bc_size)?;
Ok(())
}
// Bootconfig is attached to the initrd in the following way:
// [initrd][bootconfig][padding][size(le32)][checksum(le32)][#BOOTCONFIG\n]
fn attach_bootconfig(initrd: PathBuf, bootconfigs: Vec<PathBuf>, output: PathBuf) -> Result<()> {
let mut output_file = File::create(output)?;
let mut initrd_file = File::open(initrd)?;
let initrd_size: usize = initrd_file.metadata()?.len().try_into()?;
let mut bootconfig_size: usize = 0;
let mut checksum: u32 = 0;
std::io::copy(&mut initrd_file, &mut output_file)?;
for bootconfig in bootconfigs {
let mut bootconfig_file = File::open(&bootconfig)?;
std::io::copy(&mut bootconfig_file, &mut output_file)?;
bootconfig_size += bootconfig_file.metadata()?.len() as usize;
checksum += get_checksum(&bootconfig)?;
}
let padding_size: usize =
(FOOTER_ALIGNMENT - (initrd_size + bootconfig_size) % FOOTER_ALIGNMENT) % FOOTER_ALIGNMENT;
output_file.write_all(&ZEROS[..padding_size])?;
output_file.write_all(&((padding_size + bootconfig_size) as u32).to_le_bytes())?;
output_file.write_all(&checksum.to_le_bytes())?;
output_file.write_all(BOOTCONFIG_MAGIC.as_bytes())?;
output_file.flush()?;
Ok(())
}
fn try_main() -> Result<()> {
let args = Opt::parse();
match args {
Opt::Attach { initrd, bootconfigs, output } => {
attach_bootconfig(initrd, bootconfigs, output)?
}
Opt::Detach { initrd_with_bootconfigs, initrd, bootconfigs } => {
detach_bootconfig(initrd_with_bootconfigs, initrd, bootconfigs)?
}
};
Ok(())
}
fn main() {
try_main().unwrap()
}
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory;
#[test]
fn verify_args() {
// Check that the command parsing has been configured in a valid way.
Opt::command().debug_assert();
}
}