use args::{self, Args};
use byteorder::{ByteOrder, LittleEndian};
use cargo_metadata::{self, Metadata as CargoMetadata};
use config::{self, Config};
use failure::{Error, ResultExt};
use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use std::{io, process};
use xmas_elf;
const BLOCK_SIZE: usize = 512;
type KernelInfoBlock = [u8; BLOCK_SIZE];
pub(crate) fn build(args: Args) -> Result<(), Error> {
let (args, config, metadata, root_dir, out_dir) = common_setup(args)?;
build_impl(&args, &config, &metadata, &root_dir, &out_dir, true)?;
Ok(())
}
pub(crate) fn run(args: Args) -> Result<(), Error> {
let (args, config, metadata, root_dir, out_dir) = common_setup(args)?;
let output_path = build_impl(&args, &config, &metadata, &root_dir, &out_dir, true)?;
run_impl(&args, &config, &output_path)
}
pub(crate) fn common_setup(
mut args: Args,
) -> Result<(Args, Config, CargoMetadata, PathBuf, PathBuf), Error> {
fn out_dir(args: &Args, metadata: &CargoMetadata) -> PathBuf {
let target_dir = PathBuf::from(&metadata.target_directory);
let mut out_dir = target_dir;
if let &Some(ref target) = args.target() {
out_dir.push(Path::new(target).file_stem().unwrap().to_str().unwrap());
}
if args.release() {
out_dir.push("release");
} else {
out_dir.push("debug");
}
out_dir
}
let metadata = read_cargo_metadata(&args)?;
let crate_root = PathBuf::from(&metadata.workspace_root);
let manifest_path = args.manifest_path().as_ref().map(Clone::clone).unwrap_or({
let mut path = crate_root.clone();
path.push("Cargo.toml");
path
});
let config = config::read_config(manifest_path)?;
if args.target().is_none() {
if let Some(ref target) = config.default_target {
let mut canonicalized_target = crate_root.clone();
canonicalized_target.push(target);
args.set_target(canonicalized_target.to_string_lossy().into_owned());
}
}
if let &Some(ref target) = args.target() {
if !target.ends_with(".json") {
use std::io::{self, Write};
use std::process;
writeln!(
io::stderr(),
"Please pass a path to `--target` (with `.json` extension`): `--target {}.json`",
target
).unwrap();
process::exit(1);
}
}
let out_dir = out_dir(&args, &metadata);
Ok((args, config, metadata, crate_root, out_dir))
}
pub(crate) fn build_impl(
args: &Args,
config: &Config,
metadata: &CargoMetadata,
root_dir: &Path,
out_dir: &Path,
verbose: bool,
) -> Result<PathBuf, Error> {
let crate_ = metadata
.packages
.iter()
.find(|p| Path::new(&p.manifest_path) == config.manifest_path)
.expect("Could not read crate name from cargo metadata");
let bin_name: String = args.bin_name().as_ref().unwrap_or(&crate_.name).clone();
let kernel = build_kernel(&out_dir, &bin_name, &args, verbose)?;
let kernel_size = kernel
.metadata()
.context("Failed to read kernel output file")?
.len();
let kernel_info_block = create_kernel_info_block(kernel_size);
let bootloader = build_bootloader(&metadata, &config).context("Failed to build bootloader")?;
create_disk_image(
root_dir,
out_dir,
&bin_name,
&config,
kernel,
kernel_info_block,
&bootloader,
verbose,
)
}
fn run_impl(args: &Args, config: &Config, output_path: &Path) -> Result<(), Error> {
let command = &config.run_command[0];
let mut command = process::Command::new(command);
for arg in &config.run_command[1..] {
command.arg(arg.replace(
"{}",
output_path.to_str().expect("output must be valid unicode"),
));
}
command.args(&args.run_args);
command
.status()
.context(format_err!("Failed to execute run command: {:?}", command))?;
Ok(())
}
#[derive(Debug, Fail)]
#[fail(display = "Failed to execute `cargo metadata`")]
pub struct CargoMetadataError(Mutex<cargo_metadata::Error>);
fn read_cargo_metadata(args: &Args) -> Result<CargoMetadata, Error> {
let metadata =
cargo_metadata::metadata_deps(args.manifest_path().as_ref().map(PathBuf::as_path), true)
.map_err(|e| CargoMetadataError(Mutex::new(e)))?;
Ok(metadata)
}
fn build_kernel(
out_dir: &Path,
bin_name: &str,
args: &args::Args,
verbose: bool,
) -> Result<File, Error> {
if verbose {
println!("Building kernel");
}
let exit_status = run_xbuild(&args.cargo_args).context("Failed to run `cargo xbuild`")?;
if !exit_status.success() {
process::exit(1)
}
let mut kernel_path = out_dir.to_owned();
kernel_path.push(bin_name);
let kernel = File::open(kernel_path).context("Failed to open kernel output file")?;
Ok(kernel)
}
fn run_xbuild(args: &[String]) -> io::Result<process::ExitStatus> {
let mut command = process::Command::new("cargo");
command.arg("xbuild");
command.args(args);
let exit_status = command.status()?;
if !exit_status.success() {
let mut help_command = process::Command::new("cargo");
help_command.arg("xbuild").arg("--help");
help_command.stdout(process::Stdio::null());
help_command.stderr(process::Stdio::null());
if let Ok(help_exit_status) = help_command.status() {
if !help_exit_status.success() {
let mut stderr = io::stderr();
writeln!(
stderr,
"Failed to run `cargo xbuild`. Perhaps it is not installed?"
)?;
writeln!(stderr, "Run `cargo install cargo-xbuild` to install it.")?;
}
}
}
Ok(exit_status)
}
fn create_kernel_info_block(kernel_size: u64) -> KernelInfoBlock {
let kernel_size = if kernel_size <= u64::from(u32::max_value()) {
kernel_size as u32
} else {
panic!("Kernel can't be loaded by BIOS bootloader because is too big")
};
let mut kernel_info_block = [0u8; BLOCK_SIZE];
LittleEndian::write_u32(&mut kernel_info_block[0..4], kernel_size);
kernel_info_block
}
fn build_bootloader(metadata: &CargoMetadata, config: &Config) -> Result<Box<[u8]>, Error> {
use std::io::Read;
let bootloader_metadata = match metadata.packages.iter().find(|p| p.name == "bootloader") {
Some(package_metadata) => package_metadata.clone(),
None => Err(format_err!("Bootloader dependency not found"))?,
};
let bootloader_dir = Path::new(&bootloader_metadata.manifest_path)
.parent()
.unwrap();
let mut bootloader_target_path = PathBuf::from(bootloader_dir);
bootloader_target_path.push(&config.bootloader.target);
let bootloader_elf_path = {
let args = &[
String::from("--manifest-path"),
bootloader_metadata.manifest_path.clone(),
String::from("--target"),
bootloader_target_path.display().to_string(),
String::from("--release"),
];
println!("Building bootloader v{}", bootloader_metadata.version);
let exit_status = run_xbuild(args).context("Failed to run `cargo xbuild`")?;
if !exit_status.success() {
process::exit(1)
}
let mut bootloader_elf_path = bootloader_dir.to_path_buf();
bootloader_elf_path.push("target");
bootloader_elf_path.push(config.bootloader.target.file_stem().unwrap());
bootloader_elf_path.push("release");
bootloader_elf_path.push("bootloader");
bootloader_elf_path
};
let mut bootloader_elf_bytes = Vec::new();
let mut bootloader = File::open(&bootloader_elf_path).context("Could not open bootloader")?;
bootloader
.read_to_end(&mut bootloader_elf_bytes)
.context("Could not read bootloader")?;
let elf_file = xmas_elf::ElfFile::new(&bootloader_elf_bytes).unwrap();
xmas_elf::header::sanity_check(&elf_file).unwrap();
let bootloader_section = elf_file
.find_section_by_name(".bootloader")
.expect("bootloader must have a .bootloader section");
Ok(Vec::from(bootloader_section.raw_data(&elf_file)).into_boxed_slice())
}
fn create_disk_image(
root_dir: &Path,
out_dir: &Path,
bin_name: &str,
config: &Config,
mut kernel: File,
kernel_info_block: KernelInfoBlock,
bootloader_data: &[u8],
verbose: bool,
) -> Result<PathBuf, Error> {
use std::io::{Read, Write};
let mut output_path = PathBuf::from(out_dir);
let file_name = format!("bootimage-{}.bin", bin_name);
output_path.push(file_name);
if let Some(ref output) = config.output {
output_path = output.clone();
}
if verbose {
println!(
"Creating disk image at {}",
output_path
.strip_prefix(root_dir)
.unwrap_or(output_path.as_path())
.display()
);
}
let mut output = File::create(&output_path).context("Could not create output bootimage file")?;
output
.write_all(&bootloader_data)
.context("Could not write output bootimage file")?;
output
.write_all(&kernel_info_block)
.context("Could not write output bootimage file")?;
let kernel_size = kernel.metadata()?.len();
let mut buffer = [0u8; 1024];
loop {
let (n, interrupted) = match kernel.read(&mut buffer) {
Ok(0) => break,
Ok(n) => (n, false),
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => (0, true),
Err(e) => Err(e)?,
};
if !interrupted {
output.write_all(&buffer[..n])?
}
}
let padding_size = ((512 - (kernel_size % 512)) % 512) as usize;
let padding = [0u8; 512];
output
.write_all(&padding[..padding_size])
.context("Could not write output bootimage file")?;
if let Some(min_size) = config.minimum_image_size {
if output.metadata()?.len() < min_size {
output.set_len(min_size)?;
}
}
Ok(output_path)
}