1use clap::{ArgGroup, Args, Parser, ValueHint};
2use color_eyre::{
3 eyre::{format_err, WrapErr},
4 Help,
5};
6use std::{
7 fmt,
8 path::{Path, PathBuf},
9};
10
11pub use color_eyre::eyre::Result;
12pub mod cargo;
13pub mod gdb;
14pub mod qemu;
15pub mod term;
16pub mod trace;
17
18#[derive(Debug, Parser)]
19#[clap(about, version, author = "Eliza Weisman <eliza@elizas.website>")]
20pub struct Options {
21 #[clap(subcommand)]
25 pub cmd: Option<Subcommand>,
26
27 #[clap(value_hint = ValueHint::FilePath)]
29 pub kernel_bin: PathBuf,
30
31 #[clap(value_hint = ValueHint::FilePath)]
34 #[clap(long, global = true)]
35 pub kernel_manifest: Option<PathBuf>,
36
37 #[clap(short, long, env = "OUT_DIR", value_hint = ValueHint::DirPath, global = true)]
39 pub out_dir: Option<PathBuf>,
40
41 #[clap(
43 short,
44 long,
45 env = "CARGO_TARGET_DIR",
46 value_hint = ValueHint::DirPath,
47 global = true
48 )]
49 pub target_dir: Option<PathBuf>,
50
51 #[clap(
55 long = "cargo",
56 env = "CARGO",
57 default_value = "cargo",
58 value_hint = ValueHint::ExecutablePath,
59 global = true
60 )]
61 pub cargo_path: PathBuf,
62
63 #[clap(flatten)]
64 pub output: term::OutputOptions,
65
66 #[clap(flatten)]
68 pub bootloader: BootloaderOptions,
69}
70
71#[derive(Clone, Debug, Args)]
72#[command(
73 next_help_heading = "Bootloader Options",
74 group = ArgGroup::new(Self::ARG_GROUP).multiple(true),
75)]
76pub struct BootloaderOptions {
77 #[clap(
82 long = "boot",
83 short = 'b',
84 default_value_t = BootMode::Uefi,
85 global = true,
86 group = Self::ARG_GROUP,
87 )]
88 pub mode: BootMode,
89
90 #[clap(
92 long,
93 default_value_t = BootLogLevel::Info,
94 global = true,
95 group = Self::ARG_GROUP,
96 )]
97 boot_log: BootLogLevel,
98
99 #[clap(long, global = true, group = Self::ARG_GROUP)]
103 framebuffer_height: Option<u64>,
104
105 #[clap(long, global = true, group = Self::ARG_GROUP)]
109 framebuffer_width: Option<u64>,
110}
111
112#[derive(Copy, Clone, Debug, Eq, PartialEq, clap::ValueEnum)]
113#[repr(u8)]
114#[clap(rename_all = "upper")]
115pub enum BootMode {
116 Uefi,
120 Bios,
124}
125
126#[derive(Debug, Parser)]
127pub enum Subcommand {
128 #[clap(flatten)]
129 Qemu(qemu::Cmd),
130 Gdb,
136}
137
138#[derive(Debug)]
139pub struct Paths {
140 pub pwd: PathBuf,
141 pub kernel_bin: PathBuf,
142 pub kernel_manifest: PathBuf,
143 pub out_dir: PathBuf,
144}
145
146#[derive(Copy, Clone, Debug, Eq, PartialEq, clap::ValueEnum)]
147#[repr(u8)]
148enum BootLogLevel {
149 Off,
151 Error,
153 Warn,
155 Info,
157 Debug,
159 Trace,
161}
162
163impl Subcommand {
164 pub fn run(&self, image: &Path, paths: &Paths, boot: BootMode) -> Result<()> {
165 match self {
166 Subcommand::Qemu(qemu) => qemu.run_qemu(image, paths, boot),
167 Subcommand::Gdb => crate::gdb::run_gdb(paths.kernel_bin(), 1234).map(|_| ()),
168 }
169 }
170}
171
172impl Options {
173 pub fn is_test(&self) -> bool {
174 matches!(self.cmd, Some(Subcommand::Qemu(qemu::Cmd::Test { .. })))
175 }
176
177 pub fn wheres_the_kernel(&self) -> Result<PathBuf> {
178 tracing::debug!("where's the kernel?");
179 if let Some(path) = self.kernel_manifest.as_ref() {
180 tracing::info!(path = %path.display(), "kernel manifest path path overridden");
181 return Ok(path.clone());
182 }
183 locate_cargo_manifest::locate_manifest()
184 .note("where the hell is the kernel's Cargo.toml?")
185 .note("this should never happen, seriously, wtf")
186 .suggestion("have you tried not having it be missing")
187 }
188
189 pub fn wheres_the_kernel_bin(&self) -> Result<PathBuf> {
190 tracing::debug!("where's the kernel binary?");
191 self.kernel_bin
192 .canonicalize()
195 .context("couldn't to canonicalize kernel manifest path")
196 .note("it should work")
197 }
198
199 pub fn paths(&self) -> Result<Paths> {
200 let kernel_manifest = self.wheres_the_kernel()?;
201 tracing::info!(path = %kernel_manifest.display(), "found kernel manifest");
202
203 let kernel_bin = self.wheres_the_kernel_bin()?;
204 tracing::info!(path = %kernel_bin.display(), "found kernel binary");
205
206 let pwd = std::env::current_dir().unwrap_or_else(|error| {
207 tracing::warn!(?error, "error getting current dir");
208 Default::default()
209 });
210 tracing::debug!(path = %pwd.display(), "found pwd");
211
212 let out_dir = self
213 .out_dir
214 .as_ref()
215 .map(|path| path.as_ref())
216 .or_else(|| kernel_bin.parent())
217 .ok_or_else(|| format_err!("can't find out dir, wtf"))
218 .context("determining out dir")
219 .note("somethings messed up lol")?
220 .to_path_buf();
221 tracing::debug!(path = %out_dir.display(), "determined output directory");
222
223 Ok(Paths {
224 kernel_manifest,
225 kernel_bin,
226 pwd,
227 out_dir,
228 })
229 }
230
231 pub fn make_image(&self, paths: &Paths) -> Result<PathBuf> {
232 let _span = tracing::info_span!("make_image").entered();
233
234 tracing::info!(
235 img = %paths.relative(paths.kernel_bin()).display(),
236 boot = %self.bootloader.mode,
237 "Building kernel disk image",
238 );
239
240 let bootcfg = self.bootloader.boot_config();
241 let path = match self.bootloader.mode {
242 BootMode::Uefi => {
243 let path = paths.uefi_img();
244 let mut builder = bootloader::UefiBoot::new(paths.kernel_bin());
245 builder.set_boot_config(&bootcfg);
246 builder
247 .create_disk_image(&path)
248 .map_err(|error| format_err!("failed to build UEFI image: {error}"))
249 .with_note(|| format!("output path: {}", path.display()))?;
250 path
251 }
252 BootMode::Bios => {
253 let path = paths.bios_img();
254 let mut builder = bootloader::BiosBoot::new(paths.kernel_bin());
255 builder.set_boot_config(&bootcfg);
256 builder
257 .create_disk_image(&path)
258 .map_err(|error| format_err!("failed to build BIOS image: {error}"))
259 .with_note(|| format!("output path: {}", path.display()))?;
260 path
261 }
262 };
263
264 tracing::info!(
265 "Created bootable disk image ({})",
266 paths.relative(&path).display()
267 );
268
269 Ok(path)
270 }
271}
272
273impl Paths {
276 pub fn kernel_bin(&self) -> &Path {
277 self.kernel_bin.as_ref()
278 }
279
280 pub fn kernel_manifest(&self) -> &Path {
281 self.kernel_manifest.as_ref()
282 }
283
284 pub fn pwd(&self) -> &Path {
285 self.pwd.as_ref()
286 }
287
288 pub fn uefi_img(&self) -> PathBuf {
289 self.out_dir.join("uefi.img")
290 }
291
292 pub fn bios_img(&self) -> PathBuf {
293 self.out_dir.join("bios.img")
294 }
295
296 pub fn relative<'path>(&self, path: &'path Path) -> &'path Path {
297 path.strip_prefix(self.pwd()).unwrap_or(path)
298 }
299}
300
301impl BootloaderOptions {
304 const ARG_GROUP: &'static str = "boot-opts";
305
306 fn boot_config(&self) -> bootloader_boot_config::BootConfig {
307 let mut bootcfg = bootloader::BootConfig::default();
308 bootcfg.log_level = self.boot_log.into();
309 bootcfg.frame_buffer_logging = true;
310 bootcfg.serial_logging = true;
311 if self.framebuffer_height.is_some() {
312 bootcfg.frame_buffer.minimum_framebuffer_height = self.framebuffer_height;
313 }
314 if self.framebuffer_width.is_some() {
315 bootcfg.frame_buffer.minimum_framebuffer_width = self.framebuffer_width;
316 }
317 tracing::debug!(
318 ?bootcfg.log_level,
319 bootcfg.frame_buffer_logging,
320 bootcfg.serial_logging,
321 ?bootcfg.frame_buffer
322 );
323 bootcfg
324 }
325}
326
327impl fmt::Display for BootMode {
330 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
331 match self {
332 BootMode::Uefi => f.pad("UEFI"),
333 BootMode::Bios => f.pad("BIOS"),
334 }
335 }
336}
337
338impl fmt::Display for BootLogLevel {
341 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
342 match self {
343 Self::Off => "off",
344 Self::Error => "error",
345 Self::Warn => "warn",
346 Self::Info => "info",
347 Self::Debug => "debug",
348 Self::Trace => "trace",
349 }
350 .fmt(f)
351 }
352}
353
354impl From<BootLogLevel> for bootloader_boot_config::LevelFilter {
355 fn from(level: BootLogLevel) -> bootloader_boot_config::LevelFilter {
356 use bootloader_boot_config::LevelFilter;
357 match level {
358 BootLogLevel::Off => LevelFilter::Off,
359 BootLogLevel::Error => LevelFilter::Error,
360 BootLogLevel::Warn => LevelFilter::Warn,
361 BootLogLevel::Info => LevelFilter::Info,
362 BootLogLevel::Debug => LevelFilter::Debug,
363 BootLogLevel::Trace => LevelFilter::Trace,
364 }
365 }
366}